#!/usr/bin/env python # backend/scripts/migrate-sqlite-to-postgres.py — SQLite → Postgres 데이터 이관(행 수 일치 검증) # 사용: SQLITE_URL=sqlite:////data/ari.db PG_URL=postgresql+psycopg://ari:pw@db/ari python -m scripts.migrate-sqlite-to-postgres import os import sys from sqlmodel import Session, SQLModel, create_engine, select # 모든 테이블 메타데이터 로드 import app.models # noqa: F401 def main() -> int: src_url = os.environ.get("SQLITE_URL", "sqlite:////data/ari.db") dst_url = os.environ["PG_URL"] src = create_engine(src_url) dst = create_engine(dst_url) SQLModel.metadata.create_all(dst) # 대상 스키마 생성(또는 alembic upgrade head 선행) mismatches = [] with Session(src) as s_src, Session(dst) as s_dst: for table in SQLModel.metadata.sorted_tables: model = _model_for(table.name) if model is None: continue rows = s_src.exec(select(model)).all() for r in rows: s_dst.merge(model(**r.model_dump())) s_dst.commit() n_src = len(rows) n_dst = len(s_dst.exec(select(model)).all()) print(f"{table.name}: src={n_src} dst={n_dst}") if n_src != n_dst: mismatches.append(table.name) if mismatches: print("행 수 불일치:", mismatches, file=sys.stderr) return 1 print("이관 완료 — 행 수 일치") return 0 def _model_for(tablename: str): for cls in SQLModel.__subclasses__(): if getattr(cls, "__tablename__", None) == tablename: return cls return None if __name__ == "__main__": raise SystemExit(main())