From 5d47eece6b14ac4b03940f3b7bd2a3224bbf44d4 Mon Sep 17 00:00:00 2001 From: David Kiania Date: Fri, 10 Apr 2026 23:43:44 +0300 Subject: [PATCH] Fix: seed pre-tracking migrations to skip already-applied 02 and 03 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Migration 02 and 03 were applied before the schema_migrations tracking table existed, so they had no record and the runner tried to re-run them, hitting non-idempotent TimescaleDB policy/trigger/cagg statements. seed_pre_tracking_migrations() checks for sentinel schema objects and inserts records for any migration that was clearly already applied: - 02: tracksolid.devices table exists - 03: position_history.altitude column exists Called immediately after ensure_tracking_table() on every startup. Safe on fresh databases (objects absent → nothing seeded → runs normally). Co-Authored-By: Claude Sonnet 4.6 --- run_migrations.py | 40 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) 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: