From 44c411e5d7c89e45a87c6fa7c0739f1d3e97ec69 Mon Sep 17 00:00:00 2001 From: Gerd Date: Tue, 15 Sep 2026 17:36:52 +0200 Subject: [PATCH] =?UTF-8?q?fix(crm):=20DB-Migrationen=20idempotent=20mache?= =?UTF-8?q?n=20f=C3=BCr=20Neuinstallationen?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bei Neuinstallation erzeugt create_all bereits das gesamte Schema, so dass nachfolgende ALTER-Table-Migrationen fehlschlagen ("duplicate column name"). V01_04 prüft nun die Spalten-Existenz per PRAGMA table_info. checkupdates läuft wieder sequentiell ohne Überspringen. Konvention in AGENTS.md dokumentiert. Generated by Mistral Vibe. Co-Authored-By: Mistral Vibe --- AGENTS.md | 5 +++++ App_Server/root/crm/model/checkdb.py | 20 +++++++++++++++++--- 2 files changed, 22 insertions(+), 3 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index e26a72c..966f29c 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -74,3 +74,8 @@ Bei neuen projektbezogenen Entitäten muss: ## Generierte Dateien nicht beachten Das Verzeichnis `Client/tsout/` enthält die JavaScript-Ausgabe der TypeScript-Kompilation und wird durch den Build-Prozess (tsc + rollup) erzeugt. Diese Dateien sind nicht manuell zu bearbeiten und brauchen bei der Codeanalyse nicht berücksichtigt zu werden. Ebenso sind die JavaScript-Dateien in `NGINX_Server/html/static/` (z.B. `kbp_crm_00.00.01.js`) die Ausgabe des Rollup-Builds und nicht zu untersuchen. + +## Vorgaben für DB-Migrationen (checkdb.py) +Die DB-Versionierung erfolgt über `_db_Vxx_xx`-Funktionen in `auth/model/checkdb.py` und `crm/model/checkdb.py`. Beim Start wird `checkupdates()` aufgerufen und führt alle Funktionen aus, deren Versionsnummer größer ist als die in der Systeminfo-Tabelle vermerkte. Bei einer Neuinstallation (leere Datenbank) erzeugt `_db_V01_00` über `create_all` bereits das **gesamte aktuelle Schema** aus den Models — inklusive aller Spalten, Tabellen und Indizes, die in späteren Migrationen erst nachträglich ergänzt wurden. Die inkrementellen Migrationen laufen danach dennoch sequentiell nach. + +Daher gilt die Konvention: **Jede `_db_Vxx_xx`-Migration muss idempotent sein** — sie muss fehlerfrei durchlaufen, auch wenn das von ihr geänderte Schema-Element bereits durch `create_all` existiert. Insbesondere müssen `ALTER TABLE ADD COLUMN`-Anweisungen vorab prüfen, ob die Spalte bereits existiert (z.B. via `PRAGMA table_info`). `CREATE INDEX` verwendet `IF NOT EXISTS`. Daten-UPDATEs auf leeren Tabellen matchen nichts und sind somit unbedenklich. Migrationen, die Seed-Daten einfügen, müssen vorab prüfen, ob diese bereits vorhanden sind. diff --git a/App_Server/root/crm/model/checkdb.py b/App_Server/root/crm/model/checkdb.py index 86d0648..f364a82 100644 --- a/App_Server/root/crm/model/checkdb.py +++ b/App_Server/root/crm/model/checkdb.py @@ -83,10 +83,19 @@ def _db_V01_04(): Fügt die Spalte 'position' zur Tabelle 'kontaktadressen' hinzu. Bei bestehenden Datenbanken, die mit Version 01.03 initialisiert wurden, fehlt diese Spalte, da das Feld erst nachträglich ergänzt wurde. + + Idempotent: Bei Neuinstallation hat create_all die Spalte bereits + angelegt — in diesem Fall wird das ALTER TABLE übersprungen. """ from sqlalchemy import text - session.execute(text("ALTER TABLE kontaktadressen ADD COLUMN position VARCHAR(200)")) + # Prüfen, ob die Spalte bereits existiert (Neuinstallation via create_all) + result = session.execute(text("PRAGMA table_info(kontaktadressen)")) + spalten = {row[1] for row in result} + if 'position' not in spalten: + session.execute(text( + "ALTER TABLE kontaktadressen ADD COLUMN position VARCHAR(200)" + )) _updatesysteminfo('01.04') session.commit() @@ -217,7 +226,14 @@ def checkupdates(): Ermittelt die aktuelle DB-Version aus der Systeminfo-Tabelle und führt alle _db_Vxx_xx-Funktionen aus, deren Versionsnummer größer ist. Ist die Datenbank noch leer (Tabelle fehlt), wird die Ersteinrichtung ausgeführt. + + Bei einer Neuinstallation erzeugt _db_V01_00 über create_all bereits alle + Tabellen mit dem aktuellen Schema (inklusive aller später ergänzten Spalten, + Tabellen und Indizes). Die inkrementellen Migrationen laufen trotzdem + sequentiell nach — jede einzelne muss idempotent sein, damit sie auf der + frisch erzeugten DB fehlerfrei durchläuft (siehe Konvention in AGENTS.md). """ + versionen = sorted(filter(lambda f: f.startswith("_db_V"), globals())) curr_dbversion = '_db_V01_00' try: systeminfo: CrmSysteminfo = CrmSysteminfo.query.get(1) @@ -225,8 +241,6 @@ def checkupdates(): except (OperationalError, AttributeError): _db_V01_00() - versionen = sorted(filter(lambda f: f.startswith("_db_V"), globals())) - for v in versionen: if v > curr_dbversion: globals()[v]()