"""Database dialect guard helpers for app startup.""" from __future__ import annotations import os from ..config import settings from ..logging import get_logger logger = get_logger(__name__) def is_missing_table_error(exc: Exception) -> bool: text = str(exc).lower() return ( "no such table" in text or "undefined table" in text or "relation" in text and "does not exist" in text ) def normalize_dialect(value: str) -> str: raw = str(value or "").strip().lower() if not raw: return "" if raw.startswith("postgres"): return "postgresql" if raw.startswith("mysql"): return "mysql" if "+" in raw: raw = raw.split("+", 1)[0] if ":" in raw: raw = raw.split(":", 1)[0] return raw def mask_db_url(url: str) -> str: value = str(url or "").strip() if "@" not in value: return value prefix, suffix = value.split("@", 1) if "://" not in prefix: return f"***@{suffix}" scheme, creds = prefix.split("://", 1) user = creds.split(":", 1)[0] if ":" in creds else creds return f"{scheme}://{user}:***@{suffix}" def check_database_dialect_guard(sync_conn) -> None: configured = normalize_dialect(settings.database_url) actual = normalize_dialect(sync_conn.dialect.name) expected = normalize_dialect(os.getenv("PMFLOW_EXPECTED_DB_DIALECT", "postgresql")) or "postgresql" env_urls = [ os.getenv("DATABASE_URL", ""), os.getenv("SQLALCHEMY_DATABASE_URL", ""), os.getenv("SYNC_DATABASE_URL", ""), ] configured_families = {normalize_dialect(item) for item in env_urls if str(item or "").strip()} configured_families.discard("") logger.info( "database_dialect_guard", configured=configured, actual=actual, expected=expected or "unset", database_url=mask_db_url(settings.database_url), ) print( "[DB-Guard] " f"configured={configured}, actual={actual}, expected={expected or 'unset'}, " f"url={mask_db_url(settings.database_url)}" ) if configured != "postgresql": raise RuntimeError(f"DATABASE_URL 必须为 PostgreSQL,当前 configured={configured or 'unset'}。") if actual != "postgresql": raise RuntimeError(f"运行时数据库必须为 PostgreSQL,当前 actual={actual or 'unset'}。") if configured and actual and configured != actual: raise RuntimeError( f"数据库方言不一致: configured={configured}, actual={actual}。" "请确保同一服务只连接单一数据库方言。" ) if expected and actual and expected != actual: raise RuntimeError( f"数据库方言与 PMFLOW_EXPECTED_DB_DIALECT 不一致: expected={expected}, actual={actual}。" ) if len(configured_families) > 1: raise RuntimeError( f"检测到多种数据库方言配置: {sorted(configured_families)}。" "禁止同一服务混用多数据库方言。" )