"""Review-Funde (postgres-/sql-expert): fehlende FK-Indizes + partielle Unique- Constraints gegen Race Conditions (doppelte aktive Kontrolle/Mindermenge) Revision ID: 0007_indizes_und_locks Revises: 0006_add_eskalation_felder Create Date: 2026-09-04 Zwei unabhängige Fixes aus dem Agenten-Review dieser Session: 1. Partielle Unique-Indizes als DB-Sicherheitsnetz gegen die TOCTOU-Race zwischen Check und Insert in starte_kontrolle()/genehmigen() - Anwendungscode fängt die resultierende IntegrityError bereits ab (lifecycle.py, mindermenge.py). 2. Fehlende Indizes auf häufig gefilterten Fremdschlüsseln (Eskalations-Cron, Kontroll-Sperre-Check, Dashboard-Ablaufdatum-Abfrage u.a.). Ein op.execute() pro Statement (nicht mehrere per Semikolon gebündelt) - der asyncpg-Treiber lehnt mehrere Kommandos in einem prepared statement ab ("cannot insert multiple commands into a prepared statement", Fund beim echten Deploy-Lauf gegen die Live-DB). """ from typing import Sequence, Union from alembic import op revision: str = "0007_indizes_und_locks" down_revision: Union[str, None] = "0006_add_eskalation_felder" branch_labels: Union[str, Sequence[str], None] = None depends_on: Union[str, Sequence[str], None] = None _UPGRADE_STATEMENTS = [ "CREATE UNIQUE INDEX idx_kontrolle_eine_aktive_je_objekt " "ON kontrolle (objekt_id) WHERE status = 'in_bearbeitung'", "CREATE UNIQUE INDEX idx_mindermenge_eine_aktive_je_fehlbestand " "ON mindermengen_genehmigung (fehlbestand_id) WHERE status = 'aktiv'", "CREATE INDEX idx_fehlbestand_material ON fehlbestand (material_id)", "CREATE INDEX idx_fehlbestand_status_entstanden ON fehlbestand (status, entstanden_am)", "CREATE INDEX idx_kontrolle_objekt_status ON kontrolle (objekt_id, status)", "CREATE INDEX idx_kontrollposition_kontrolle ON kontrollposition (kontrolle_id)", "CREATE INDEX idx_zustaendigkeit_objekt ON zustaendigkeit (objekt_id)", "CREATE INDEX idx_zustaendigkeit_standort ON zustaendigkeit (standort_id)", "CREATE INDEX idx_benutzer_rolle_rolle ON benutzer_rolle (rolle)", "CREATE INDEX idx_objektposition_material ON objektposition (material_id)", "CREATE INDEX idx_nachfuellung_fehlbestand ON nachfuellung (fehlbestand_id)", "CREATE INDEX idx_objektposition_ablauf ON objektposition (ablaufdatum) WHERE ablaufdatum IS NOT NULL", ] _INDEX_NAMEN_ABWAERTS = [ "idx_objektposition_ablauf", "idx_nachfuellung_fehlbestand", "idx_objektposition_material", "idx_benutzer_rolle_rolle", "idx_zustaendigkeit_standort", "idx_zustaendigkeit_objekt", "idx_kontrollposition_kontrolle", "idx_kontrolle_objekt_status", "idx_fehlbestand_status_entstanden", "idx_fehlbestand_material", "idx_mindermenge_eine_aktive_je_fehlbestand", "idx_kontrolle_eine_aktive_je_objekt", ] def upgrade() -> None: for statement in _UPGRADE_STATEMENTS: op.execute(statement) def downgrade() -> None: for name in _INDEX_NAMEN_ABWAERTS: op.execute(f"DROP INDEX {name}")