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
This commit is contained in:
@@ -0,0 +1,54 @@
|
||||
"""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)"
|
||||
)
|
||||
Reference in New Issue
Block a user