diff --git a/run_migrations.py b/run_migrations.py index 8e79a9e..5a98939 100644 --- a/run_migrations.py +++ b/run_migrations.py @@ -68,6 +68,45 @@ def ensure_tracking_table(conn): conn.commit() +def seed_pre_tracking_migrations(conn): + """ + Retroactively mark migrations as applied if their schema objects already + exist. Required when the tracking table is introduced to a database that + was migrated before tracking existed — prevents re-running non-idempotent + statements (TimescaleDB policies, triggers, continuous aggregates). + """ + seeds = [] + + with conn.cursor() as cur: + # Migration 02: tracksolid.devices is the canonical sentinel table + cur.execute(""" + SELECT 1 FROM information_schema.tables + WHERE table_schema = 'tracksolid' AND table_name = 'devices' + """) + if cur.fetchone(): + seeds.append("02_tracksolid_full_schema_rev.sql") + + # Migration 03: position_history.altitude column added in this migration + cur.execute(""" + SELECT 1 FROM information_schema.columns + WHERE table_schema = 'tracksolid' + AND table_name = 'position_history' + AND column_name = 'altitude' + """) + if cur.fetchone(): + seeds.append("03_webhook_schema_migration.sql") + + for filename in seeds: + cur.execute(""" + INSERT INTO tracksolid.schema_migrations (filename) + VALUES (%s) ON CONFLICT DO NOTHING + """, (filename,)) + + conn.commit() + if seeds: + print(f" Seeded pre-tracking migrations as applied: {', '.join(seeds)}") + + def already_applied(conn, filename): with conn.cursor() as cur: cur.execute( @@ -128,6 +167,7 @@ def main(): conn = get_conn() ensure_tracking_table(conn) + seed_pre_tracking_migrations(conn) applied = skipped = 0 for sql_file in MIGRATIONS: