diff --git a/migrations/versions/phase_b_mobile_local_id.py b/migrations/versions/phase_b_mobile_local_id.py index 56c10ec..9658fd0 100644 --- a/migrations/versions/phase_b_mobile_local_id.py +++ b/migrations/versions/phase_b_mobile_local_id.py @@ -7,69 +7,73 @@ on the device (mobile_local_id). The server checks this field before creating a new record so that network retries never produce duplicate rows. Revision ID: phase_b_mobile_local_id -Revises: phase9_user_full_name +Revises: phase12_performance_indexes """ from alembic import op import sqlalchemy as sa revision = 'phase_b_mobile_local_id' -down_revision = 'phase9_user_full_name' +down_revision = 'phase12_performance_indexes' branch_labels = None depends_on = None +def _column_exists(conn, table, column): + result = conn.execute(sa.text( + "SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS " + "WHERE TABLE_SCHEMA = DATABASE() " + "AND TABLE_NAME = :table AND COLUMN_NAME = :column" + ), {"table": table, "column": column}) + return result.scalar() > 0 + + +def _index_exists(conn, table, index): + result = conn.execute(sa.text( + "SELECT COUNT(*) FROM INFORMATION_SCHEMA.STATISTICS " + "WHERE TABLE_SCHEMA = DATABASE() " + "AND TABLE_NAME = :table AND INDEX_NAME = :index" + ), {"table": table, "index": index}) + return result.scalar() > 0 + + def upgrade(): + conn = op.get_bind() + # ── inspections.mobile_local_id ─────────────────────────────────────── - with op.batch_alter_table('inspections') as batch_op: - # Check if column already exists (safe re-run) - conn = op.get_bind() - columns = [row[1] for row in conn.execute(sa.text('PRAGMA table_info(inspections)')).fetchall()] \ - if conn.dialect.name == 'sqlite' \ - else [row['COLUMN_NAME'] for row in conn.execute( - sa.text( - "SELECT COLUMN_NAME FROM INFORMATION_SCHEMA.COLUMNS " - "WHERE TABLE_NAME = 'inspections' AND TABLE_SCHEMA = DATABASE()" - ) - ).mappings()] + if not _column_exists(conn, 'inspections', 'mobile_local_id'): + op.execute(sa.text( + "ALTER TABLE inspections ADD COLUMN mobile_local_id VARCHAR(64) NULL" + )) - if 'mobile_local_id' not in columns: - batch_op.add_column( - sa.Column('mobile_local_id', sa.String(64), nullable=True) - ) - - # Index for fast idempotency lookups - op.execute(sa.text( - "CREATE INDEX IF NOT EXISTS idx_inspections_mobile_local_id " - "ON inspections(mobile_local_id)" - )) + if not _index_exists(conn, 'inspections', 'idx_inspections_mobile_local_id'): + op.execute(sa.text( + "CREATE INDEX idx_inspections_mobile_local_id ON inspections(mobile_local_id)" + )) # ── issues.mobile_local_id ──────────────────────────────────────────── - with op.batch_alter_table('issues') as batch_op: - conn = op.get_bind() - columns = [row[1] for row in conn.execute(sa.text('PRAGMA table_info(issues)')).fetchall()] \ - if conn.dialect.name == 'sqlite' \ - else [row['COLUMN_NAME'] for row in conn.execute( - sa.text( - "SELECT COLUMN_NAME FROM INFORMATION_SCHEMA.COLUMNS " - "WHERE TABLE_NAME = 'issues' AND TABLE_SCHEMA = DATABASE()" - ) - ).mappings()] + if not _column_exists(conn, 'issues', 'mobile_local_id'): + op.execute(sa.text( + "ALTER TABLE issues ADD COLUMN mobile_local_id VARCHAR(64) NULL" + )) - if 'mobile_local_id' not in columns: - batch_op.add_column( - sa.Column('mobile_local_id', sa.String(64), nullable=True) - ) - - op.execute(sa.text( - "CREATE INDEX IF NOT EXISTS idx_issues_mobile_local_id " - "ON issues(mobile_local_id)" - )) + if not _index_exists(conn, 'issues', 'idx_issues_mobile_local_id'): + op.execute(sa.text( + "CREATE INDEX idx_issues_mobile_local_id ON issues(mobile_local_id)" + )) def downgrade(): - with op.batch_alter_table('inspections') as batch_op: - batch_op.drop_column('mobile_local_id') + conn = op.get_bind() - with op.batch_alter_table('issues') as batch_op: - batch_op.drop_column('mobile_local_id') \ No newline at end of file + if _index_exists(conn, 'inspections', 'idx_inspections_mobile_local_id'): + op.execute(sa.text("DROP INDEX idx_inspections_mobile_local_id ON inspections")) + + if _column_exists(conn, 'inspections', 'mobile_local_id'): + op.execute(sa.text("ALTER TABLE inspections DROP COLUMN mobile_local_id")) + + if _index_exists(conn, 'issues', 'idx_issues_mobile_local_id'): + op.execute(sa.text("DROP INDEX idx_issues_mobile_local_id ON issues")) + + if _column_exists(conn, 'issues', 'mobile_local_id'): + op.execute(sa.text("ALTER TABLE issues DROP COLUMN mobile_local_id")) \ No newline at end of file