0040_repair_schema_drift_idempotent.py
file-level
1
files
1
commits
0
hotspots
0
🧊 dead
0
💥 blast risk
| 1 | """Repair schema drift — idempotent retry of 0039 changes. |
| 2 | |
| 3 | Revision ID: 0040 |
| 4 | Revises: 0039 |
| 5 | Create Date: 2026-05-07 |
| 6 | |
| 7 | Context |
| 8 | ------- |
| 9 | Migration 0039 dropped ix_musehub_coord_reservations_repo_id but on some |
| 10 | environments that index was absent, causing the migration to fail. The |
| 11 | deploy script stamped the alembic_version to 0039 and continued, leaving |
| 12 | all 0039 changes unapplied on those environments. |
| 13 | |
| 14 | This migration re-applies all 0039 changes using IF EXISTS / IF NOT EXISTS |
| 15 | guards so it is safe to run regardless of which state the DB is in. |
| 16 | """ |
| 17 | from __future__ import annotations |
| 18 | |
| 19 | from alembic import op |
| 20 | import sqlalchemy as sa |
| 21 | |
| 22 | |
| 23 | revision: str = "0040" |
| 24 | down_revision: str | None = "0039" |
| 25 | branch_labels = None |
| 26 | depends_on = None |
| 27 | |
| 28 | |
| 29 | def upgrade() -> None: |
| 30 | # muse_tags.tag_id — widen VARCHAR(36) to VARCHAR(128) if not already done |
| 31 | bind = op.get_bind() |
| 32 | cols = {c["name"]: c for c in sa.inspect(bind).get_columns("muse_tags")} |
| 33 | tag_id_col = cols.get("tag_id") |
| 34 | if tag_id_col and isinstance(tag_id_col["type"], sa.VARCHAR) and tag_id_col["type"].length == 36: |
| 35 | op.alter_column( |
| 36 | "muse_tags", "tag_id", |
| 37 | existing_type=sa.VARCHAR(length=36), |
| 38 | type_=sa.String(length=128), |
| 39 | existing_nullable=False, |
| 40 | ) |
| 41 | |
| 42 | # coord_reservations — drop wrong-named index; ensure correct name exists |
| 43 | op.execute("DROP INDEX IF EXISTS ix_musehub_coord_reservations_repo_id") |
| 44 | op.execute( |
| 45 | "CREATE INDEX IF NOT EXISTS ix_coord_reservations_repo_id " |
| 46 | "ON musehub_coord_reservations(repo_id)" |
| 47 | ) |
| 48 | |
| 49 | # breakage_issues — add missing index |
| 50 | op.execute( |
| 51 | "CREATE INDEX IF NOT EXISTS ix_musehub_intel_breakage_issues_repo_id " |
| 52 | "ON musehub_intel_breakage_issues(repo_id)" |
| 53 | ) |
| 54 | |
| 55 | # refactor_events — add missing index |
| 56 | op.execute( |
| 57 | "CREATE INDEX IF NOT EXISTS ix_musehub_intel_refactor_events_repo_id " |
| 58 | "ON musehub_intel_refactor_events(repo_id)" |
| 59 | ) |
| 60 | |
| 61 | # velocity — drop stale composite index |
| 62 | op.execute("DROP INDEX IF EXISTS ix_intel_velocity_repo_active") |
| 63 | |
| 64 | # symbol_intel — drop stale DESC-expression blast index |
| 65 | op.execute("DROP INDEX IF EXISTS ix_symbol_intel_repo_blast") |
| 66 | |
| 67 | # symbol_intel gravity_pct — rebuild without DESC expression |
| 68 | op.execute("DROP INDEX IF EXISTS ix_symbol_intel_repo_gravity_pct") |
| 69 | op.execute( |
| 70 | "CREATE INDEX IF NOT EXISTS ix_symbol_intel_repo_gravity_pct " |
| 71 | "ON musehub_symbol_intel(repo_id, gravity_pct)" |
| 72 | ) |
| 73 | |
| 74 | |
| 75 | def downgrade() -> None: |
| 76 | op.execute("DROP INDEX IF EXISTS ix_symbol_intel_repo_gravity_pct") |
| 77 | op.execute( |
| 78 | "CREATE INDEX IF NOT EXISTS ix_symbol_intel_repo_gravity_pct " |
| 79 | "ON musehub_symbol_intel(repo_id, gravity_pct DESC NULLS LAST)" |
| 80 | ) |
| 81 | op.execute( |
| 82 | "CREATE INDEX IF NOT EXISTS ix_symbol_intel_repo_blast " |
| 83 | "ON musehub_symbol_intel(repo_id, blast DESC NULLS LAST)" |
| 84 | ) |
| 85 | op.execute( |
| 86 | "CREATE INDEX IF NOT EXISTS ix_intel_velocity_repo_active " |
| 87 | "ON musehub_intel_velocity(repo_id, active_commits)" |
| 88 | ) |
| 89 | op.execute("DROP INDEX IF EXISTS ix_musehub_intel_refactor_events_repo_id") |
| 90 | op.execute("DROP INDEX IF EXISTS ix_musehub_intel_breakage_issues_repo_id") |
| 91 | op.execute("DROP INDEX IF EXISTS ix_coord_reservations_repo_id") |
| 92 | op.execute( |
| 93 | "CREATE INDEX IF NOT EXISTS ix_musehub_coord_reservations_repo_id " |
| 94 | "ON musehub_coord_reservations(repo_id)" |
| 95 | ) |
| 96 | cols = {c["name"]: c for c in sa.inspect(op.get_bind()).get_columns("muse_tags")} |
| 97 | tag_id_col = cols.get("tag_id") |
| 98 | if tag_id_col and isinstance(tag_id_col["type"], sa.VARCHAR) and tag_id_col["type"].length == 128: |
| 99 | op.alter_column( |
| 100 | "muse_tags", "tag_id", |
| 101 | existing_type=sa.String(length=128), |
| 102 | type_=sa.VARCHAR(length=36), |
| 103 | existing_nullable=False, |
| 104 | ) |