55 lines
1.6 KiB
Python
55 lines
1.6 KiB
Python
|
|
"""Fix guest_users email+tenant_id unique index.
|
||
|
|
|
||
|
|
Alembic 0059 created ix_guest_users_email_tenant as a normal (non-unique) index.
|
||
|
|
The SQLAlchemy model defines it as unique=True, and production already has
|
||
|
|
a UNIQUE INDEX. This migration aligns Alembic with production.
|
||
|
|
|
||
|
|
Before creating the unique index, checks for duplicate (email, tenant_id) pairs.
|
||
|
|
If duplicates exist, the migration aborts with a data cleanup report.
|
||
|
|
|
||
|
|
Revision ID: 0095
|
||
|
|
Revises: 0094
|
||
|
|
"""
|
||
|
|
|
||
|
|
from __future__ import annotations
|
||
|
|
|
||
|
|
from alembic import op
|
||
|
|
import sqlalchemy as sa
|
||
|
|
|
||
|
|
revision = "0095"
|
||
|
|
down_revision = "0094"
|
||
|
|
branch_labels = None
|
||
|
|
depends_on = None
|
||
|
|
|
||
|
|
|
||
|
|
def upgrade() -> None:
|
||
|
|
# Check for duplicates before creating unique index
|
||
|
|
conn = op.get_bind()
|
||
|
|
duplicates = conn.execute(
|
||
|
|
sa.text(
|
||
|
|
"SELECT email, tenant_id, count(*) FROM guest_users "
|
||
|
|
"GROUP BY email, tenant_id HAVING count(*) > 1"
|
||
|
|
)
|
||
|
|
).fetchall()
|
||
|
|
|
||
|
|
if duplicates:
|
||
|
|
raise RuntimeError(
|
||
|
|
f"Cannot create unique index: {len(duplicates)} duplicate (email, tenant_id) pairs found. "
|
||
|
|
"Data cleanup required before migration."
|
||
|
|
)
|
||
|
|
|
||
|
|
# Drop the non-unique index and recreate as unique
|
||
|
|
op.execute("DROP INDEX IF EXISTS ix_guest_users_email_tenant")
|
||
|
|
op.execute(
|
||
|
|
"CREATE UNIQUE INDEX IF NOT EXISTS ix_guest_users_email_tenant "
|
||
|
|
"ON guest_users (email, tenant_id)"
|
||
|
|
)
|
||
|
|
|
||
|
|
|
||
|
|
def downgrade() -> None:
|
||
|
|
op.execute("DROP INDEX IF EXISTS ix_guest_users_email_tenant")
|
||
|
|
op.execute(
|
||
|
|
"CREATE INDEX IF NOT EXISTS ix_guest_users_email_tenant "
|
||
|
|
"ON guest_users (email, tenant_id)"
|
||
|
|
)
|