Fix: seed pre-tracking migrations to skip already-applied 02 and 03

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 <noreply@anthropic.com>
This commit is contained in:
David Kiania 2026-04-10 23:43:44 +03:00
parent 63e555b822
commit 5d47eece6b

View file

@ -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: