repertory-api/migrations/versions/e5f6a2b3c4d1_unique_names_musicians_sources.py

84 lines
3 KiB
Python
Raw Normal View History

"""unique name constraints on musicians and sources
Revision ID: e5f6a2b3c4d1
Revises: d7e3b19f0a2c
Create Date: 2026-06-09
"""
from alembic import op
import sqlalchemy as sa
from sqlalchemy.sql import text
revision = "e5f6a2b3c4d1"
down_revision = "d7e3b19f0a2c"
branch_labels = None
depends_on = None
def _dedup_musicians(conn):
"""Keep the lowest-id musician for each name; re-point FKs and delete dupes."""
rows = conn.execute(text(
"SELECT MIN(id) AS keep_id, LOWER(name) AS lname "
"FROM musicians GROUP BY LOWER(name) HAVING COUNT(*) > 1"
)).fetchall()
for row in rows:
keep_id = row[0]
lname = row[1]
dupe_ids = [r[0] for r in conn.execute(
text("SELECT id FROM musicians WHERE LOWER(name) = :n AND id != :k"),
{"n": lname, "k": keep_id}
).fetchall()]
for dupe_id in dupe_ids:
# Re-point learned_from_id in tune_by_instrument
conn.execute(text(
"UPDATE tune_by_instrument SET learned_from_id = :k "
"WHERE learned_from_id = :d"
), {"k": keep_id, "d": dupe_id})
# Re-point reference_musicians (skip if keep_id row already exists for that ref)
conn.execute(text(
"DELETE FROM reference_musicians WHERE musician_id = :d "
"AND reference_id IN ("
" SELECT reference_id FROM reference_musicians WHERE musician_id = :k"
")"
), {"d": dupe_id, "k": keep_id})
conn.execute(text(
"UPDATE reference_musicians SET musician_id = :k WHERE musician_id = :d"
), {"k": keep_id, "d": dupe_id})
conn.execute(text("DELETE FROM musicians WHERE id = :d"), {"d": dupe_id})
def _dedup_sources(conn):
"""Keep the lowest-id source for each name; re-point tunes.source_id and delete dupes."""
rows = conn.execute(text(
"SELECT MIN(id) AS keep_id, LOWER(name) AS lname "
"FROM sources GROUP BY LOWER(name) HAVING COUNT(*) > 1"
)).fetchall()
for row in rows:
keep_id = row[0]
lname = row[1]
dupe_ids = [r[0] for r in conn.execute(
text("SELECT id FROM sources WHERE LOWER(name) = :n AND id != :k"),
{"n": lname, "k": keep_id}
).fetchall()]
for dupe_id in dupe_ids:
conn.execute(text(
"UPDATE tunes SET source_id = :k WHERE source_id = :d"
), {"k": keep_id, "d": dupe_id})
conn.execute(text("DELETE FROM sources WHERE id = :d"), {"d": dupe_id})
def upgrade() -> None:
conn = op.get_bind()
_dedup_musicians(conn)
_dedup_sources(conn)
op.create_unique_constraint("uq_musicians_name", "musicians", ["name"])
op.create_unique_constraint("uq_sources_name", "sources", ["name"])
def downgrade() -> None:
op.drop_constraint("uq_musicians_name", "musicians", type_="unique")
op.drop_constraint("uq_sources_name", "sources", type_="unique")