diff --git a/backend/csv_parser/executor.py b/backend/csv_parser/executor.py index b018526..d667e9f 100644 --- a/backend/csv_parser/executor.py +++ b/backend/csv_parser/executor.py @@ -588,6 +588,9 @@ def _import_vitals_baseline( continue iso = d.isoformat() try: + # Ohne SAVEPOINT: erster fehlgeschlagener INSERT setzt die Xact auf „aborted“, + # alle folgenden Queries + commit() schlagen fehl → generischer 500. + cur.execute("SAVEPOINT vitals_csv_row") cur.execute( """ INSERT INTO vitals_baseline ( @@ -619,8 +622,15 @@ def _import_vitals_baseline( updated += 1 if result.get("id"): affected_ids["vitals_baseline"].append(str(result["id"])) + cur.execute("RELEASE SAVEPOINT vitals_csv_row") except Exception as e: - error_details.append({"row": rows_total, "error": str(e)}) + try: + cur.execute("ROLLBACK TO SAVEPOINT vitals_csv_row") + except Exception: + pass + error_details.append( + {"row": rows_total, "error": str(e), "context": "vitals_baseline upsert"}, + ) return { "rows_total": rows_total, diff --git a/backend/migrations/048_vitals_baseline_source_csv.sql b/backend/migrations/048_vitals_baseline_source_csv.sql index 58cd3ae..7844d06 100644 --- a/backend/migrations/048_vitals_baseline_source_csv.sql +++ b/backend/migrations/048_vitals_baseline_source_csv.sql @@ -1,5 +1,22 @@ --- Universal-CSV-Import schreibt source = 'csv'; bisherige CHECK erlaubte das nicht → 500 beim Import. -ALTER TABLE vitals_baseline DROP CONSTRAINT IF EXISTS vitals_baseline_source_check; +-- Universal-CSV-Import setzt source = 'csv'. Alte CHECK-Constraints kennen das nicht. +-- Namen können je nach PG-Version abweichen → alle passenden CHECK Constraints zu source droppen. +DO $$ +DECLARE + r RECORD; +BEGIN + FOR r IN + SELECT c.conname + FROM pg_constraint c + JOIN pg_class t ON c.conrelid = t.oid + WHERE t.relname = 'vitals_baseline' + AND c.contype = 'c' + AND pg_get_constraintdef(c.oid) ILIKE '%source%' + AND pg_get_constraintdef(c.oid) ILIKE '%IN %' + LOOP + EXECUTE format('ALTER TABLE vitals_baseline DROP CONSTRAINT %I', r.conname); + END LOOP; +END $$; + ALTER TABLE vitals_baseline ADD CONSTRAINT vitals_baseline_source_check CHECK (source IN ('manual', 'apple_health', 'garmin', 'withings', 'csv')); diff --git a/backend/migrations/049_vitals_baseline_source_csv_idempotent.sql b/backend/migrations/049_vitals_baseline_source_csv_idempotent.sql new file mode 100644 index 0000000..1eca76f --- /dev/null +++ b/backend/migrations/049_vitals_baseline_source_csv_idempotent.sql @@ -0,0 +1,20 @@ +-- Idempotent: erneute Bereinigung der source-CHECK, falls 048 zuvor nicht griff oder nur teilweise lief. +DO $$ +DECLARE + r RECORD; +BEGIN + FOR r IN + SELECT c.conname + FROM pg_constraint c + JOIN pg_class t ON c.conrelid = t.oid + WHERE t.relname = 'vitals_baseline' + AND c.contype = 'c' + AND pg_get_constraintdef(c.oid) ILIKE '%source%' + AND pg_get_constraintdef(c.oid) ILIKE '%IN %' + LOOP + EXECUTE format('ALTER TABLE vitals_baseline DROP CONSTRAINT %I', r.conname); + END LOOP; +END $$; + +ALTER TABLE vitals_baseline ADD CONSTRAINT vitals_baseline_source_check + CHECK (source IN ('manual', 'apple_health', 'garmin', 'withings', 'csv')); diff --git a/backend/routers/csv_import.py b/backend/routers/csv_import.py index 8a7a590..083ce84 100644 --- a/backend/routers/csv_import.py +++ b/backend/routers/csv_import.py @@ -477,6 +477,7 @@ async def csv_import_execute( m, ) except Exception as exec_err: + logger.exception("Universal-CSV-Import fehlgeschlagen: %s", exec_err) cur.execute("ROLLBACK TO SAVEPOINT csv_import_exec") cur.execute( """ diff --git a/backend/version.py b/backend/version.py index d7fdaa1..9f52bbf 100644 --- a/backend/version.py +++ b/backend/version.py @@ -9,7 +9,7 @@ Semantic Versioning: MAJOR.MINOR.PATCH APP_VERSION = "0.9p" BUILD_DATE = "2026-04-09" -DB_SCHEMA_VERSION = "20260409b" # u. a. 048 vitals_baseline.source csv +DB_SCHEMA_VERSION = "20260409c" # 048/049 vitals_baseline.source csv + SAVEPOINT Import MODULE_VERSIONS = { "auth": "1.2.0",