#!/usr/bin/env python3 """ NULL値チェック・デフォルト値テストスクリプト """ import os import psycopg2 import logging # ログ設定 logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s') logger = logging.getLogger(__name__) # データベース設定 OLD_ROGDB_CONFIG = { 'host': os.getenv('OLD_ROGDB_HOST', 'postgres-db'), 'database': os.getenv('OLD_ROGDB_NAME', 'old_rogdb'), 'user': os.getenv('OLD_ROGDB_USER', 'admin'), 'password': os.getenv('OLD_ROGDB_PASSWORD', 'admin123456'), 'port': int(os.getenv('OLD_ROGDB_PORT', 5432)) } ROGDB_CONFIG = { 'host': os.getenv('ROGDB_HOST', 'postgres-db'), 'database': os.getenv('ROGDB_NAME', 'rogdb'), 'user': os.getenv('ROGDB_USER', 'admin'), 'password': os.getenv('ROGDB_PASSWORD', 'admin123456'), 'port': int(os.getenv('ROGDB_PORT', 5432)) } def check_null_values(): """NULL値の問題を事前チェック""" try: old_conn = psycopg2.connect(**OLD_ROGDB_CONFIG) new_conn = psycopg2.connect(**ROGDB_CONFIG) old_conn.autocommit = True new_conn.autocommit = True old_cursor = old_conn.cursor() new_cursor = new_conn.cursor() # 共通テーブル取得 old_cursor.execute(""" SELECT table_name FROM information_schema.tables WHERE table_schema = 'public' AND table_name LIKE 'rog_%' """) old_tables = [row[0] for row in old_cursor.fetchall()] new_cursor.execute(""" SELECT table_name FROM information_schema.tables WHERE table_schema = 'public' AND table_name LIKE 'rog_%' """) new_tables = [row[0] for row in new_cursor.fetchall()] common_tables = list(set(old_tables) & set(new_tables)) logger.info(f"チェック対象テーブル: {len(common_tables)}個") null_issues = {} for table_name in common_tables: logger.info(f"=== {table_name} NULL値チェック ===") # 新しいDBのNOT NULL制約確認 new_cursor.execute(""" SELECT column_name, is_nullable, column_default FROM information_schema.columns WHERE table_name = %s AND table_schema = 'public' AND is_nullable = 'NO' ORDER BY ordinal_position """, (table_name,)) not_null_columns = new_cursor.fetchall() if not not_null_columns: logger.info(f" NOT NULL制約なし") continue logger.info(f" NOT NULL制約カラム: {[col[0] for col in not_null_columns]}") # 古いDBのNULL値チェック for col_name, is_nullable, default_val in not_null_columns: try: # PostgreSQL予約語とcamelCaseカラムのクォート処理 reserved_words = ['group', 'like', 'order', 'user', 'table', 'index', 'where', 'from', 'select'] quoted_col = f'"{col_name}"' if (col_name.lower() in reserved_words or any(c.isupper() for c in col_name)) else col_name # カラム存在チェック old_cursor.execute(""" SELECT COUNT(*) FROM information_schema.columns WHERE table_name = %s AND column_name = %s AND table_schema = 'public' """, (table_name, col_name)) if old_cursor.fetchone()[0] == 0: logger.warning(f" ⚠️ {col_name}: 古いDBに存在しないカラム") continue old_cursor.execute(f""" SELECT COUNT(*) FROM {table_name} WHERE {quoted_col} IS NULL """) null_count = old_cursor.fetchone()[0] if null_count > 0: logger.warning(f" ⚠️ {col_name}: {null_count}件のNULL値あり (デフォルト: {default_val})") if table_name not in null_issues: null_issues[table_name] = [] null_issues[table_name].append((col_name, null_count, default_val)) else: logger.info(f" ✅ {col_name}: NULL値なし") except Exception as e: logger.error(f" ❌ {col_name}: チェックエラー: {e}") # サマリー if null_issues: logger.warning("=" * 60) logger.warning("NULL値問題のあるテーブル:") for table, issues in null_issues.items(): logger.warning(f" {table}:") for col, count, default in issues: logger.warning(f" - {col}: {count}件 (デフォルト: {default})") else: logger.info("✅ NULL値の問題はありません") old_cursor.close() new_cursor.close() old_conn.close() new_conn.close() return null_issues except Exception as e: logger.error(f"❌ NULL値チェックエラー: {e}") return {} def suggest_default_values(null_issues): """デフォルト値の提案""" if not null_issues: return logger.info("=" * 60) logger.info("推奨デフォルト値設定:") for table_name, issues in null_issues.items(): logger.info(f" '{table_name}': {{") for col_name, count, default in issues: # データ型に基づくデフォルト値推測 if 'trial' in col_name.lower() or 'is_' in col_name.lower(): suggested = 'False' elif 'public' in col_name.lower(): suggested = 'True' elif 'name' in col_name.lower() or 'description' in col_name.lower(): suggested = "''" elif 'order' in col_name.lower() or 'sort' in col_name.lower(): suggested = '0' else: suggested = 'None # 要確認' logger.info(f" '{col_name}': {suggested}, # {count}件のNULL") logger.info(" },") def main(): logger.info("=" * 60) logger.info("NULL値チェック・デフォルト値提案スクリプト") logger.info("=" * 60) null_issues = check_null_values() suggest_default_values(null_issues) logger.info("=" * 60) logger.info("チェック完了") logger.info("=" * 60) if __name__ == "__main__": main()