"""Manual migration runner. docker-entrypoint-initdb.d 는 fresh DB 첫 기동 때만 동작. 이미 동작 중인 DB 에 새 마이그레이션을 적용하려면 이 스크립트로: python -m app.db.migrate 모든 SQL 파일은 idempotent (CREATE IF NOT EXISTS / CREATE OR REPLACE) 여야 함. """ from __future__ import annotations import logging from pathlib import Path from sqlalchemy import text from app.db.connection import get_engine logger = logging.getLogger(__name__) MIGRATIONS_DIR = Path(__file__).parent / "migrations" def apply_all() -> dict[str, str]: """migrations/ 안 .sql 들을 이름순으로 적용. 결과: {filename: 'ok'|'failed: ...'}.""" eng = get_engine() results: dict[str, str] = {} files = sorted(MIGRATIONS_DIR.glob("*.sql")) if not files: logger.warning("no migration files in %s", MIGRATIONS_DIR) return results for path in files: sql = path.read_text(encoding="utf-8") # psql meta-command 제거 (\set ON_ERROR_STOP 등) cleaned = "\n".join( ln for ln in sql.splitlines() if not ln.strip().startswith("\\") ) try: with eng.begin() as conn: conn.execute(text(cleaned)) results[path.name] = "ok" logger.info("applied %s", path.name) except Exception as exc: # noqa: BLE001 results[path.name] = f"failed: {exc}" logger.exception("migration %s failed", path.name) return results if __name__ == "__main__": logging.basicConfig(level=logging.INFO, format="%(asctime)s %(levelname)s %(name)s: %(message)s") out = apply_all() for k, v in out.items(): print(f"{k}: {v}")