fix: make migrations resilient to orphaned scans_new tables
All checks were successful
Deploy / deploy (push) Successful in 12s
All checks were successful
Deploy / deploy (push) Successful in 12s
- Add _recover_orphaned_new_tables() called at DB init startup: if scans_new exists but scans doesn't (from a previously aborted migration), rename it back - Drop VIEW recent_scans/active_scans before dropping the scans table in both _migrate_add_pause_cancel_status and _migrate_add_reverse_scan_support, so executescript can cleanly recreate them - Add DROP TABLE IF EXISTS scans_new / scheduled_scans_new at the start of each table-recreation migration to prevent 'table scans_new already exists' errors Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -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")
|
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):
|
def _migrate_add_pause_cancel_status(conn, verbose=True):
|
||||||
"""
|
"""
|
||||||
Migration: Extend status CHECK constraint to include 'paused' and 'cancelled'.
|
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.
|
# SQLite doesn't support ALTER TABLE MODIFY COLUMN, so recreate the table.
|
||||||
# Use PRAGMA foreign_keys = OFF to avoid FK errors during the swap.
|
# Use PRAGMA foreign_keys = OFF to avoid FK errors during the swap.
|
||||||
conn.execute("PRAGMA foreign_keys = OFF")
|
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).
|
# 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_scans_timestamp")
|
||||||
conn.execute("DROP TRIGGER IF EXISTS update_scan_flight_count_insert")
|
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:
|
if verbose:
|
||||||
print(" 🔄 Migrating scans table: relaxing origin constraint, adding scan_mode…")
|
print(" 🔄 Migrating scans table: relaxing origin constraint, adding scan_mode…")
|
||||||
conn.execute("PRAGMA foreign_keys = OFF")
|
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_scans_timestamp")
|
||||||
conn.execute("DROP TRIGGER IF EXISTS update_scan_flight_count_insert")
|
conn.execute("DROP TRIGGER IF EXISTS update_scan_flight_count_insert")
|
||||||
conn.execute("DROP TRIGGER IF EXISTS update_scan_flight_count_update")
|
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 sched_cols and 'scan_mode' not in sched_cols:
|
||||||
if verbose:
|
if verbose:
|
||||||
print(" 🔄 Migrating scheduled_scans table: relaxing origin constraint, adding scan_mode…")
|
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("DROP TRIGGER IF EXISTS update_scheduled_scans_timestamp")
|
||||||
conn.execute("""
|
conn.execute("""
|
||||||
CREATE TABLE scheduled_scans_new (
|
CREATE TABLE scheduled_scans_new (
|
||||||
@@ -508,6 +539,9 @@ def initialize_database(db_path=None, verbose=True):
|
|||||||
else:
|
else:
|
||||||
print(" No existing tables found")
|
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
|
# Apply migrations before running schema
|
||||||
_migrate_relax_country_constraint(conn, verbose)
|
_migrate_relax_country_constraint(conn, verbose)
|
||||||
_migrate_add_routes_unique_index(conn, verbose)
|
_migrate_add_routes_unique_index(conn, verbose)
|
||||||
|
|||||||
Reference in New Issue
Block a user