invy/backend/run_production_migration.py

112 lines
3.0 KiB
Python

#!/usr/bin/env python3
"""
run_production_migration.py
─────────────────────────────────────────────────────────────────────────────
Execute migrate_production.sql against the configured DATABASE_URL.
Usage
─────
python run_production_migration.py # normal run
python run_production_migration.py --dry-run # parse SQL but do NOT commit
Environment variables read from .env (or already in shell):
DATABASE_URL postgresql://user:pass@host:port/dbname
Exit codes:
0 success
1 error
"""
import argparse
import os
import sys
from pathlib import Path
import psycopg2
from dotenv import load_dotenv
MIGRATION_FILE = Path(__file__).parent / "migrate_production.sql"
def parse_args():
p = argparse.ArgumentParser(description="Run Invy production migration")
p.add_argument(
"--dry-run",
action="store_true",
help="Parse and validate the SQL but roll back instead of committing.",
)
return p.parse_args()
def main():
args = parse_args()
load_dotenv()
db_url = os.getenv(
"DATABASE_URL",
"postgresql://wedding_admin:Aa123456@localhost:5432/wedding_guests",
)
if not MIGRATION_FILE.exists():
print(f"❌ Migration file not found: {MIGRATION_FILE}")
sys.exit(1)
sql = MIGRATION_FILE.read_text(encoding="utf-8")
print(f"{'[DRY-RUN] ' if args.dry_run else ''}Connecting to database …")
try:
conn = psycopg2.connect(db_url)
except Exception as exc:
print(f"❌ Cannot connect: {exc}")
sys.exit(1)
conn.autocommit = False
cursor = conn.cursor()
# Capture NOTICE messages from PL/pgSQL RAISE NOTICE
import warnings
conn.notices = []
def _notice_handler(diag):
msg = diag.message_primary or str(diag)
conn.notices.append(msg)
print(f" [DB] {msg}")
conn.add_notice_handler(_notice_handler)
try:
print("Running migration …")
cursor.execute(sql)
# Print the summary SELECT result
try:
row = cursor.fetchone()
if row:
print(
f"\n📊 Summary after migration:\n"
f" users : {row[0]}\n"
f" events : {row[1]}\n"
f" guests_v2 : {row[2]}\n"
)
except Exception:
pass
if args.dry_run:
conn.rollback()
print("✅ Dry-run complete — rolled back (no changes written).")
else:
conn.commit()
print("✅ Migration committed successfully.")
except Exception as exc:
conn.rollback()
print(f"\n❌ Migration failed — rolled back.\n Error: {exc}")
sys.exit(1)
finally:
cursor.close()
conn.close()
if __name__ == "__main__":
main()