Files
saas_backend/app/core/migration_helpers.py
T
2026-08-31 20:04:12 -04:00

45 lines
1.6 KiB
Python

"""Small utilities shared by migrations.
Kept here rather than copied into each one because a migration that has already
run is awkward to change later — the fewer places the same fix has to be applied,
the better.
"""
from alembic import op
import sqlalchemy as sa
def drop_foreign_key_on(table: str, column: str) -> None:
"""Drop the foreign key on a column, whatever the database called it.
Alembic's autogenerate writes `op.drop_constraint(None, ...)` for a
constraint it created without a name, and that cannot be emitted: a
constraint with no name is one Alembic cannot address. Every downgrade
containing it fails, which means the migration chain is not reversible —
discovered by trying it rather than by anything failing in normal use.
Hard-coding the name Postgres happens to generate would work on one database
and not on another created at a different time, so the name is looked up.
Absent, nothing happens: a downgrade should not fail because the thing it
wants to remove is already gone.
"""
name = op.get_bind().execute(
sa.text(
"""
SELECT con.conname
FROM pg_constraint con
JOIN pg_attribute att
ON att.attrelid = con.conrelid
AND att.attnum = ANY(con.conkey)
WHERE con.conrelid = CAST(:table AS regclass)
AND con.contype = 'f'
AND att.attname = :column
LIMIT 1
"""
),
{"table": table, "column": column},
).scalar()
if name:
op.drop_constraint(name, table, type_="foreignkey")