Files
MABEA/backend/alembic/versions/0007_indizes_und_locks.py
patrickandClaude Sonnet 5 d7c3e52002
CI / backend-tests (push) Successful in 1m8s
CI / frontend-build (push) Successful in 18s
fix(migration): revision-id 0007 kürzen (alembic_version ist VARCHAR(32))
"0007_indizes_und_unique_constraints" (37 Zeichen) sprengte die Spalte,
Statements liefen alle durch, nur das abschließende UPDATE auf
alembic_version schlug fehl - sauber zurückgerollt (transaktionale DDL),
beim echten Deploy-Lauf aufgefallen.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CVgbozhYmuEhiEJHffRXCV
2026-09-04 18:49:24 +02:00

70 lines
3.0 KiB
Python

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