From 861caae57cb3dbe2f7a4ebfca4f2427d86acc44e Mon Sep 17 00:00:00 2001 From: irrlicht Date: Mon, 7 Sep 2026 12:40:12 +0200 Subject: [PATCH] database migrations --- migrations/p2-000-migrationsbuch.sql | 26 ++++ migrations/p2-001-codings-mehrschichtig.sql | 128 ++++++++++++++++++++ 2 files changed, 154 insertions(+) create mode 100644 migrations/p2-000-migrationsbuch.sql create mode 100644 migrations/p2-001-codings-mehrschichtig.sql diff --git a/migrations/p2-000-migrationsbuch.sql b/migrations/p2-000-migrationsbuch.sql new file mode 100644 index 0000000..5714dc5 --- /dev/null +++ b/migrations/p2-000-migrationsbuch.sql @@ -0,0 +1,26 @@ +-- Migration p2-000: Buch ueber angewandte Migrationen +-- +-- psql "$DATABASE_URL" -v ON_ERROR_STOP=1 -f migrations/p2-000-migrationsbuch.sql +-- +-- Dev und Prod teilen dasselbe Schema; abweichende Staende sind der Weg, auf +-- dem sich das aendert, ohne dass es jemand merkt. Diese Tabelle macht den +-- Stand abfragbar. Sie traegt das Praefix p2-, weil die Ingest-Migrationen +-- (swp-01-ingest/migrations) eine eigene Zaehlung haben und beide auf +-- dieselbe Datenbank laufen. + +begin; + +create table if not exists schema_migrationen ( + name text primary key, -- Dateiname ohne .sql + angewandt_am timestamptz not null default now(), + notiz text +); + +comment on table schema_migrationen is + 'Nachtraeglich gepflegt: Ingest-Migrationen 001/002 liefen vor dieser Tabelle'; + +insert into schema_migrationen (name, notiz) values + ('p2-000-migrationsbuch', null) +on conflict (name) do nothing; + +commit; diff --git a/migrations/p2-001-codings-mehrschichtig.sql b/migrations/p2-001-codings-mehrschichtig.sql new file mode 100644 index 0000000..d02577a --- /dev/null +++ b/migrations/p2-001-codings-mehrschichtig.sql @@ -0,0 +1,128 @@ +-- Migration p2-001: codings fuer die fuenf Phase-2-Schichten oeffnen +-- +-- psql "$DATABASE_URL" -v ON_ERROR_STOP=1 -f migrations/p2-001-codings-mehrschichtig.sql +-- +-- Setzt das Ingest-Schema voraus (swp-01-ingest/schema.sql). Aendert an +-- raw_items nichts und loescht keine Zeile. +-- +-- Zwei Dinge im Bestand verhindern Phase 2 vollstaendig: +-- +-- unique (raw_item_id, coder_version) - laesst genau eine Zeile je Item +-- und Version zu, Phase 2 braucht fuenf +-- check ebene in ('keine','quad',...) - kennt nur die CAMEO-Ebenen aus +-- Phase 3, kein 'dublette' +-- +-- Beides wird ersetzt, nicht ergaenzt. `ebene` traegt damit zwei Bedeutungen: +-- fuer Phase 3 die erreichte CAMEO-Ebene, fuer Phase 2 den Modulnamen. +-- Auseinander zu halten sind sie ueber coder_version (Phase 2: 'p2-...'). +-- Eine eigene Spalte waere sauberer, kostet aber einen Eingriff in Phase 3, +-- die es noch nicht gibt - wenn, dann dort und dann. + +begin; + +alter table codings drop constraint if exists codings_raw_item_id_coder_version_key; +alter table codings drop constraint if exists codings_ebene_check; + +alter table codings + add constraint codings_uniq unique (raw_item_id, coder_version, ebene); + +alter table codings + add constraint codings_ebene_check check (ebene in ( + -- Phase 3, CAMEO + 'keine', 'quad', 'root', 'base', 'event', + -- Phase 2, Anreicherung + 'dublette', 'akteure', 'geo', 'tonalitaet', 'embedding' + )); + +comment on column codings.ebene is + 'Phase 3: erreichte CAMEO-Ebene. Phase 2: Modulname. Trennung ueber coder_version'; + +create index if not exists codings_ebene_idx on codings (ebene, kodiert_am); +create index if not exists codings_nutzlast_idx on codings using gin (nutzlast); + +-- -------------------------------------------------------------------------- +-- Keine Werkausschnitte in der Nutzlast. +-- +-- codings ist Kandidat fuer den OpenData-Dump. Offsets und Codes duerfen +-- hinaus, Text nicht - ausser Eigennamen (Akteurs- und Ortsformen), die kurz +-- sind. 100 Zeichen ist die Grenze, unter der kein Teaser wiederherstellbar +-- ist und ueber der kein Organisationsname liegen muss. +-- +-- Als Constraint, nicht als Vorsatz: Abnahmekriterium aus Abschnitt 10. +-- -------------------------------------------------------------------------- +create or replace function p2_nutzlast_ohne_text(nutzlast jsonb, grenze integer default 100) +returns boolean language sql immutable parallel safe as $$ + select not exists ( + select 1 + from jsonb_path_query(nutzlast, '$.**') as wert + where jsonb_typeof(wert) = 'string' + and length(wert #>> '{}') > grenze + ) +$$; + +comment on function p2_nutzlast_ohne_text is + 'Wahr, wenn keine Zeichenkette in der Nutzlast laenger als grenze ist'; + +alter table codings + add constraint codings_kein_volltext + check (p2_nutzlast_ohne_text(nutzlast)); + +-- -------------------------------------------------------------------------- +-- Was hinter einem coder_version-Hash steht. Ohne diese Tabelle ist die +-- Versionierung wertlos: der Hash sagt dann nur, dass sich etwas geaendert +-- hat, nicht was. +-- -------------------------------------------------------------------------- +create table if not exists coder_versionen ( + version text primary key, -- 'p2-2026-09-07-a3f1b2c4' + komponenten jsonb not null, -- Regelwerk, Lexika, Modelle, Quantisierung + erstellt_am timestamptz not null default now(), + notiz text +); + +-- -------------------------------------------------------------------------- +-- Betriebsprotokoll, analog poll_laeufe. Anders als dort ist die Einheit +-- nicht die Quelle, sondern Slot und Modul: Phase 2 arbeitet itemweise ueber +-- alle Quellen hinweg. +-- -------------------------------------------------------------------------- +create table if not exists p2_laeufe ( + id bigserial primary key, + slot timestamptz, -- null im Backfill ueber den Gesamtbestand + modul text not null, + coder_version text not null, + begonnen_am timestamptz not null default now(), + dauer_ms integer, + items_gesehen integer not null default 0, + items_kodiert integer not null default 0, + items_fehler integer not null default 0, + fehler text +); + +create index if not exists p2_laeufe_slot_idx on p2_laeufe (slot desc nulls last); +create index if not exists p2_laeufe_modul_idx on p2_laeufe (modul, begonnen_am desc); + +-- -------------------------------------------------------------------------- +-- Arbeitsvorrat je Schicht. `unkodiert(v)` aus dem Ingest-Schema fragt nur, +-- ob es *irgendeine* Zeile gibt, und liefert nach dem ersten Modul nichts +-- mehr zurueck. +-- +-- select * from unkodiert('p2-2026-09-07-a3f1b2c4', 'akteure') limit 500; +-- -------------------------------------------------------------------------- +create or replace function unkodiert(v text, schicht text) +returns setof raw_items language sql stable as $$ + select r.* from raw_items r + where not exists ( + select 1 from codings c + where c.raw_item_id = r.id + and c.coder_version = v + and c.ebene = schicht + ) + order by r.id +$$; + +-- Verwerfbarkeit (Invariante 4): der gesamte Phase-2-Bestand geht so weg. +-- delete from codings where coder_version like 'p2-%'; + +insert into schema_migrationen (name) values ('p2-001-codings-mehrschichtig') +on conflict (name) do nothing; + +commit;