Files
plm-lottery/migrations/versions/c1d4a97b5e10_case_insensitive_usernames.py
T

47 lines
1.7 KiB
Python
Raw Normal View History

"""case-insensitive usernames (B-57)
Revision ID: c1d4a97b5e10
Revises: be71fdac734e
Create Date: 2026-08-03 18:10:00.000000
"""
from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision: str = 'c1d4a97b5e10'
down_revision: Union[str, Sequence[str], None] = 'be71fdac734e'
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
def upgrade() -> None:
"""Upgrade schema."""
# The index cannot be created while two accounts differ only by case, and
# nothing here may guess which of them is the "real" one: both are custodial
# accounts that may hold funds, so merging or renaming one automatically would
# be the migration silently deciding who owns what. Fail loudly instead, naming
# the collisions, and let the operator rename one account (and tell that user)
# before retrying. The container runs `alembic upgrade head` at startup, so this
# surfaces as a refusal to start rather than as a half-applied schema.
collisions = op.get_bind().exec_driver_sql(
"SELECT group_concat(username, ', ') FROM users "
"GROUP BY lower(username) HAVING count(*) > 1"
).fetchall()
if collisions:
groups = "; ".join(row[0] for row in collisions)
raise RuntimeError(
"cannot enforce case-insensitive usernames: these accounts differ only "
f"by case and must be resolved by hand first — {groups}"
)
op.create_index("ix_users_username_lower", "users", [sa.text("lower(username)")], unique=True)
def downgrade() -> None:
"""Downgrade schema."""
op.drop_index("ix_users_username_lower", table_name="users")