Files
leocrm/alembic/versions/0095_fix_guest_users_unique.py
Agent Zero 3eb11b1745 Phase 1: Migrationsaudit + Forward-Migrationen 0093-0096
Audit (docs/migration_history_audit.md):
- files.size_bytes: INTEGER (Alembic) vs BIGINT (Produktion/Plugin)
- GIN-Indizes: Fehlendes USING GIN in Alembic 0002
- guest_users: ix_guest_users_email_tenant fehlt UNIQUE in Alembic 0059
- plugins.name: Doppelter Unique-Index in Produktion

Forward-Migrationen:
- 0093: files.size_bytes INTEGER → BIGINT
- 0094: GIN-Indizes reparieren + plugins.name doppelten Index entfernen
- 0095: guest_users email+tenant_id UNIQUE INDEX (mit Dubletten-Check)
- 0096: Workspace tenant_integrity (tenant-bound FKs)

Tests: 41/41 bestanden (17 Workspace + 24 Command)
Alembic Head: 0096
2026-08-03 13:29:16 +02:00

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)"
)