From 5f1b32f1dcc56bbc451f79b10c4fc786ae84153b Mon Sep 17 00:00:00 2001 From: David Kiania Date: Fri, 10 Apr 2026 23:48:30 +0300 Subject: [PATCH] Extend seed sentinels to cover migrations 04 and 05 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Containers share one DB — when ingest_movement applies 04, ingest_events and webhook_receiver start later and find distance_m already renamed, causing a spurious FATAL before the next restart catches the recorded row. Added sentinels for all four migrations so any container self-heals on first startup regardless of which container ran first: 04 — trips.distance_km column exists 05 — tracksolid.device_events table exists Co-Authored-By: Claude Sonnet 4.6 --- run_migrations.py | 73 +++++++++++++++++++++++++++++------------------ 1 file changed, 45 insertions(+), 28 deletions(-) diff --git a/run_migrations.py b/run_migrations.py index 5a98939..d5f3e84 100644 --- a/run_migrations.py +++ b/run_migrations.py @@ -71,40 +71,57 @@ def ensure_tracking_table(conn): 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). + exist. Checked on every startup — safe to run repeatedly (ON CONFLICT DO + NOTHING). Prevents re-running non-idempotent statements when a second + container starts after another has already applied the migration, or when + the tracking table is introduced to a database migrated before it existed. + + Sentinel objects per migration: + 02 — tracksolid.devices table exists + 03 — position_history.altitude column exists + 04 — trips.distance_km column exists (renamed from distance_m) + 05 — tracksolid.device_events table exists (new in 05) """ + checks = [ + ( + "02_tracksolid_full_schema_rev.sql", + "SELECT 1 FROM information_schema.tables " + "WHERE table_schema='tracksolid' AND table_name='devices'", + ), + ( + "03_webhook_schema_migration.sql", + "SELECT 1 FROM information_schema.columns " + "WHERE table_schema='tracksolid' AND table_name='position_history' " + "AND column_name='altitude'", + ), + ( + "04_bug_fix_migration.sql", + "SELECT 1 FROM information_schema.columns " + "WHERE table_schema='tracksolid' AND table_name='trips' " + "AND column_name='distance_km'", + ), + ( + "05_enhancement_migration.sql", + "SELECT 1 FROM information_schema.tables " + "WHERE table_schema='tracksolid' AND table_name='device_events'", + ), + ] + 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,)) + for filename, query in checks: + cur.execute(query) + if cur.fetchone(): + cur.execute( + "INSERT INTO tracksolid.schema_migrations (filename) " + "VALUES (%s) ON CONFLICT DO NOTHING", + (filename,), + ) + seeds.append(filename) conn.commit() if seeds: - print(f" Seeded pre-tracking migrations as applied: {', '.join(seeds)}") + print(f" Seeded as applied: {', '.join(seeds)}") def already_applied(conn, filename):