fix(migrations): guard f0e9d8c7b6a5 per-table for create_all()-only tables

dataset_review_sessions (and potentially other tables in FK_DEFS) are
created at runtime by init_db() → Base.metadata.create_all(), not by
Alembic migrations. On fresh databases, these tables don't exist when
migrations run, causing ALTER TABLE to crash.

Fix: check table existence before operating on each FK. If a table
doesn't exist, create_all() will create it with the correct FK
definition (the model already has ondelete='CASCADE').

This is the same pattern used by other migrations (9f8e7d6c5b4a,
ed28d34edde7, c9d8e7f6a5b4, 86c7b1d6a710) for create_all()-only tables
like llm_providers, roles, validation_policies, llm_validation_results.
This commit is contained in:
2026-06-11 17:04:37 +03:00
parent 35a403ac5d
commit 6855bb7010

View File

@@ -1,17 +1,28 @@
"""cascade ondelete for all remaining environments FK references
Revision ID: f0e9d8c7b6a5
Revises: e1f2a3b4c5d6, e5f4d3c2b1a
Revises: a5b6c7d8e9f0, e5f4d3c2b1a
Create Date: 2026-05-21 19:00:00.000000
@RATIONALE The dataset_review_sessions table is created at runtime by
init_db() → Base.metadata.create_all(), not by any Alembic migration.
On a fresh database, it does not exist when Alembic runs (entrypoint
runs alembic upgrade head BEFORE the backend starts). The model already
defines the FK with ondelete='CASCADE', so on fresh databases the FK is
correct. This migration only needs to alter the FK on databases that
were upgraded from before the FK had CASCADE.
Guard: skip dataset_review_sessions FK if the table doesn't exist.
"""
from collections.abc import Sequence
from alembic import op
from sqlalchemy import inspect
# revision identifiers, used by Alembic.
revision: str = 'f0e9d8c7b6a5'
down_revision: str | Sequence[str] | None = ('e1f2a3b4c5d6', 'e5f4d3c2b1a')
down_revision: str | Sequence[str] | None = ('a5b6c7d8e9f0', 'e5f4d3c2b1a')
branch_labels: str | Sequence[str] | None = None
depends_on: str | Sequence[str] | None = None
@@ -25,9 +36,18 @@ FK_DEFS = [
]
def _table_exists(table: str) -> bool:
"""Check if a table exists in the current database connection."""
conn = op.get_bind()
inspector = inspect(conn)
return inspector.has_table(table)
def upgrade() -> None:
"""Upgrade schema."""
for table, constraint_name, column in FK_DEFS:
if not _table_exists(table):
continue
op.drop_constraint(constraint_name, table, type_='foreignkey')
op.create_foreign_key(
constraint_name,
@@ -42,6 +62,8 @@ def upgrade() -> None:
def downgrade() -> None:
"""Downgrade schema."""
for table, constraint_name, column in reversed(FK_DEFS):
if not _table_exists(table):
continue
op.drop_constraint(constraint_name, table, type_='foreignkey')
op.create_foreign_key(
constraint_name,