diff --git a/flight-comparator/database/init_db.py b/flight-comparator/database/init_db.py index 78af9a2..1697365 100644 --- a/flight-comparator/database/init_db.py +++ b/flight-comparator/database/init_db.py @@ -227,6 +227,28 @@ def _migrate_add_timing_columns_to_scans(conn, verbose=True): print(" βœ… Migration complete: started_at and completed_at columns added to scans") +def _recover_orphaned_new_tables(conn, verbose=True): + """ + Recovery: if a previous migration left behind scans_new or scheduled_scans_new + (e.g. after a crash between DROP TABLE scans and RENAME), restore them. + """ + tables = [r[0] for r in conn.execute( + "SELECT name FROM sqlite_master WHERE type='table'" + ).fetchall()] + + if 'scans_new' in tables and 'scans' not in tables: + if verbose: + print(" πŸ”§ Recovering: renaming orphaned scans_new β†’ scans") + conn.execute("ALTER TABLE scans_new RENAME TO scans") + conn.commit() + + if 'scheduled_scans_new' in tables and 'scheduled_scans' not in tables: + if verbose: + print(" πŸ”§ Recovering: renaming orphaned scheduled_scans_new β†’ scheduled_scans") + conn.execute("ALTER TABLE scheduled_scans_new RENAME TO scheduled_scans") + conn.commit() + + def _migrate_add_pause_cancel_status(conn, verbose=True): """ Migration: Extend status CHECK constraint to include 'paused' and 'cancelled'. @@ -248,6 +270,11 @@ def _migrate_add_pause_cancel_status(conn, verbose=True): # SQLite doesn't support ALTER TABLE MODIFY COLUMN, so recreate the table. # Use PRAGMA foreign_keys = OFF to avoid FK errors during the swap. conn.execute("PRAGMA foreign_keys = OFF") + # Drop views that reference scans so they can be cleanly recreated by executescript. + conn.execute("DROP VIEW IF EXISTS recent_scans") + conn.execute("DROP VIEW IF EXISTS active_scans") + # Drop any leftover _new table from a previously aborted migration. + conn.execute("DROP TABLE IF EXISTS scans_new") # Drop triggers that reference scans (they are recreated by executescript below). conn.execute("DROP TRIGGER IF EXISTS update_scans_timestamp") conn.execute("DROP TRIGGER IF EXISTS update_scan_flight_count_insert") @@ -318,6 +345,9 @@ def _migrate_add_reverse_scan_support(conn, verbose=True): if verbose: print(" πŸ”„ Migrating scans table: relaxing origin constraint, adding scan_mode…") conn.execute("PRAGMA foreign_keys = OFF") + conn.execute("DROP VIEW IF EXISTS recent_scans") + conn.execute("DROP VIEW IF EXISTS active_scans") + conn.execute("DROP TABLE IF EXISTS scans_new") conn.execute("DROP TRIGGER IF EXISTS update_scans_timestamp") conn.execute("DROP TRIGGER IF EXISTS update_scan_flight_count_insert") conn.execute("DROP TRIGGER IF EXISTS update_scan_flight_count_update") @@ -413,6 +443,7 @@ def _migrate_add_reverse_scan_support(conn, verbose=True): if sched_cols and 'scan_mode' not in sched_cols: if verbose: print(" πŸ”„ Migrating scheduled_scans table: relaxing origin constraint, adding scan_mode…") + conn.execute("DROP TABLE IF EXISTS scheduled_scans_new") conn.execute("DROP TRIGGER IF EXISTS update_scheduled_scans_timestamp") conn.execute(""" CREATE TABLE scheduled_scans_new ( @@ -508,6 +539,9 @@ def initialize_database(db_path=None, verbose=True): else: print(" No existing tables found") + # Recover any orphaned _new tables left by previously aborted migrations + _recover_orphaned_new_tables(conn, verbose) + # Apply migrations before running schema _migrate_relax_country_constraint(conn, verbose) _migrate_add_routes_unique_index(conn, verbose)