Files
ewoooc/database/schema_repair.py
OoO 2c47a79f05
All checks were successful
CD Pipeline / deploy (push) Successful in 1m7s
[V10.328] 強化 PChome 比價診斷與狀態分流
2026-05-20 13:24:38 +08:00

111 lines
6.2 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""啟動時資料庫 schema 自動修復。
從 app.py 抽出的 repair_database_schema()。
歷史背景:早期 V9.53 起為了讓部署不必執行 migration在啟動時動態檢查欄位並
ALTER TABLE 補上。長期應改走 Alembic / Migration script但這個 fallback 還活著。
呼叫端app.py 啟動末段呼叫一次。
"""
from utils.logger_manager import SystemLogger
_log = SystemLogger("SchemaRepair").get_logger()
def _ensure_column(engine, text_fn, table, column, ddl, post_sql=None):
"""補上單一欄位 helper若不存在則 ALTER TABLE。"""
from sqlalchemy import inspect
inspector = inspect(engine)
if table not in inspector.get_table_names():
return False
columns = [c['name'] for c in inspector.get_columns(table)]
if column in columns:
return False
_log.warning(f"[Database] [Schema] ⚠️ 偵測到 {table} 表缺少 {column} 欄位 | 正在自動修復...")
with engine.connect() as conn:
conn.execute(text_fn(ddl))
if post_sql:
conn.execute(text_fn(post_sql))
conn.commit()
_log.info(f"[Database] [Schema] ✅ {table}.{column} 欄位修復完成")
return True
def repair_database_schema():
"""啟動時資料庫結構自動修復V9.53)。"""
from database.manager import DatabaseManager
from sqlalchemy import inspect, text
from config import DATABASE_TYPE
db = DatabaseManager()
engine = db.engine
try:
# V9.96: SQLite WAL 模式(解決 database is locked
if DATABASE_TYPE == 'sqlite':
with engine.connect() as conn:
conn.execute(text("PRAGMA journal_mode=WAL"))
conn.commit()
_log.info("[Database] [WAL] ✅ SQLite WAL 模式已啟用 | 提升並發寫入效能")
else:
_log.info(f"[Database] ✅ 使用 {DATABASE_TYPE.upper()} 資料庫")
inspector = inspect(engine)
# V9.70: products 表
_ensure_column(engine, text, 'products', 'image_url',
"ALTER TABLE products ADD COLUMN image_url TEXT")
_ensure_column(engine, text, 'products', 'created_at',
"ALTER TABLE products ADD COLUMN created_at DATETIME",
post_sql="UPDATE products SET created_at = updated_at WHERE created_at IS NULL")
# promo_products 表
promo_columns = [
('url', "ALTER TABLE promo_products ADD COLUMN url TEXT"),
('image_url', "ALTER TABLE promo_products ADD COLUMN image_url TEXT"),
('previous_price', "ALTER TABLE promo_products ADD COLUMN previous_price INTEGER"),
('session_time_text', "ALTER TABLE promo_products ADD COLUMN session_time_text TEXT"),
('remain_qty', "ALTER TABLE promo_products ADD COLUMN remain_qty INTEGER"),
('discount_text', "ALTER TABLE promo_products ADD COLUMN discount_text TEXT"),
('page_type', "ALTER TABLE promo_products ADD COLUMN page_type TEXT DEFAULT 'edm'"),
]
for col_name, ddl in promo_columns:
_ensure_column(engine, text, 'promo_products', col_name, ddl)
# V10.328: PChome/MOMO 比價診斷欄位。正式價差與待審嘗試都需可回溯
# URL、圖片、庫存與 matcher 結構化原因,避免「待比對」無法被人工處理。
json_type = 'JSONB' if DATABASE_TYPE == 'postgresql' else 'TEXT'
competitor_price_columns = [
('competitor_product_url', "ALTER TABLE competitor_prices ADD COLUMN competitor_product_url TEXT"),
('competitor_image_url', "ALTER TABLE competitor_prices ADD COLUMN competitor_image_url TEXT"),
('competitor_stock', "ALTER TABLE competitor_prices ADD COLUMN competitor_stock INTEGER"),
('match_diagnostic_json', f"ALTER TABLE competitor_prices ADD COLUMN match_diagnostic_json {json_type}"),
('comparison_mode', "ALTER TABLE competitor_prices ADD COLUMN comparison_mode VARCHAR(40)"),
('hard_veto', "ALTER TABLE competitor_prices ADD COLUMN hard_veto BOOLEAN"),
('diagnostic_codes', f"ALTER TABLE competitor_prices ADD COLUMN diagnostic_codes {json_type}"),
]
competitor_history_columns = [
('competitor_product_url', "ALTER TABLE competitor_price_history ADD COLUMN competitor_product_url TEXT"),
('competitor_image_url', "ALTER TABLE competitor_price_history ADD COLUMN competitor_image_url TEXT"),
('competitor_stock', "ALTER TABLE competitor_price_history ADD COLUMN competitor_stock INTEGER"),
('match_diagnostic_json', f"ALTER TABLE competitor_price_history ADD COLUMN match_diagnostic_json {json_type}"),
('comparison_mode', "ALTER TABLE competitor_price_history ADD COLUMN comparison_mode VARCHAR(40)"),
('hard_veto', "ALTER TABLE competitor_price_history ADD COLUMN hard_veto BOOLEAN"),
('diagnostic_codes', f"ALTER TABLE competitor_price_history ADD COLUMN diagnostic_codes {json_type}"),
]
competitor_attempt_columns = [
('competitor_product_url', "ALTER TABLE competitor_match_attempts ADD COLUMN competitor_product_url TEXT"),
('competitor_image_url', "ALTER TABLE competitor_match_attempts ADD COLUMN competitor_image_url TEXT"),
('competitor_stock', "ALTER TABLE competitor_match_attempts ADD COLUMN competitor_stock INTEGER"),
('match_diagnostic_json', f"ALTER TABLE competitor_match_attempts ADD COLUMN match_diagnostic_json {json_type}"),
('comparison_mode', "ALTER TABLE competitor_match_attempts ADD COLUMN comparison_mode VARCHAR(40)"),
('hard_veto', "ALTER TABLE competitor_match_attempts ADD COLUMN hard_veto BOOLEAN"),
('diagnostic_codes', f"ALTER TABLE competitor_match_attempts ADD COLUMN diagnostic_codes {json_type}"),
]
for table_columns in (competitor_price_columns, competitor_history_columns, competitor_attempt_columns):
for col_name, ddl in table_columns:
table_name = ddl.split()[2]
_ensure_column(engine, text, table_name, col_name, ddl)
except Exception as e:
_log.error(f"[Database] [Schema] ❌ 資料庫修復失敗 | Error: {e}")