84 lines
3 KiB
Python
84 lines
3 KiB
Python
|
|
"""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")
|