-- --------------------------------------------------------------------------- -- HANDGESCHRIEBEN. Prisma erzeugt hier zusaetzlich Loeschbefehle fuer die -- zusammengesetzten Fremdschluessel (Mandantengrenze), die Trigramm-Indizes -- (Archivsuche), die partiellen eindeutigen Indizes und alle Trigger: es kennt -- sie nicht, weil sie sich im Schema nicht ausdruecken lassen. Diese -- Loeschbefehle sind bewusst gestrichen. -- -- tests/datenbankregeln.test.ts prueft, dass alle diese Objekte vorhanden sind – -- ein versehentlich stehengelassener Loeschbefehl faellt dort sofort auf. -- --------------------------------------------------------------------------- -- Live-Aktualisierung per SSE statt Polling (#033). -- -- Die Datenbank sagt selbst, wenn sich an einem Lauf etwas geaendert hat: -- `NOTIFY lauf_geaendert, ''`. Der Route Handler haelt eine -- LISTEN-Verbindung und schiebt weiter. -- -- **Warum in der Datenbank und nicht im Dienst.** Ein Ereignis, das der Service -- verschickt, entsteht nur auf dem Weg durch den Service. Die Regeln dieses -- Projekts stehen aber absichtlich auch in der Datenbank, weil jemand an der -- Service-Schicht vorbei schreiben kann — der Seed, eine Migration, eine -- Reparatur per psql. Ein Trigger sieht all das. -- -- **Warum es mit mehreren Instanzen funktioniert.** NOTIFY geht an alle -- Verbindungen, die LISTEN auf dem Kanal haben, also an jede Instanz der -- Anwendung. Ein zweiter Dienst (Redis, ein Nachrichtenbus) waere dafuer nicht -- noetig. -- -- **Zugestellt wird erst beim COMMIT.** Das ist hier genau richtig: ein -- Abonnent, der eine noch nicht festgeschriebene Aenderung meldet, wuerde die -- Seite mit Daten neu bauen lassen, die es gleich wieder nicht gibt. CREATE OR REPLACE FUNCTION laufzettel_lauf_geaendert() RETURNS trigger AS $$ DECLARE betroffener TEXT; BEGIN /* * Bei DELETE gibt es kein NEW. Der Kanalname ist fest, die Nutzlast nicht – * deshalb pg_notify() und nicht NOTIFY, das keine Variablen nimmt. */ IF TG_OP = 'DELETE' THEN betroffener := OLD."laufId"; ELSE betroffener := NEW."laufId"; END IF; IF betroffener IS NOT NULL THEN PERFORM pg_notify('lauf_geaendert', betroffener); END IF; RETURN NULL; END; $$ LANGUAGE plpgsql; -- Derselbe Rumpf fuer die Laufzeile selbst: dort heisst der Schluessel "id". CREATE OR REPLACE FUNCTION laufzettel_lauf_geaendert_selbst() RETURNS trigger AS $$ BEGIN PERFORM pg_notify('lauf_geaendert', NEW."id"); RETURN NULL; END; $$ LANGUAGE plpgsql; /* * **Welche Tabellen melden, haengt daran, was `laufZustand` liefert** — nicht * daran, was sich "irgendwie" aendert. Das Lesemodell traegt Status, * Schrittzustaende, die Zahl der Kommentare und (seit #033) die Zahl der * Anhaenge. Wuerde eine davon nicht melden, waere der Strom gegenueber dem * Polling, das alle zehn Sekunden vollstaendig vergleicht, ein **Rueckschritt**: * ein neuer Kommentar kaeme nie an. */ CREATE TRIGGER "Lauf_geaendert_melden" AFTER UPDATE ON "Lauf" FOR EACH ROW EXECUTE FUNCTION laufzettel_lauf_geaendert_selbst(); CREATE TRIGGER "Schrittergebnis_geaendert_melden" AFTER INSERT OR UPDATE OR DELETE ON "Schrittergebnis" FOR EACH ROW EXECUTE FUNCTION laufzettel_lauf_geaendert(); CREATE TRIGGER "Kommentar_geaendert_melden" AFTER INSERT OR UPDATE OR DELETE ON "Kommentar" FOR EACH ROW EXECUTE FUNCTION laufzettel_lauf_geaendert(); CREATE TRIGGER "Anhang_geaendert_melden" AFTER INSERT OR DELETE ON "Anhang" FOR EACH ROW EXECUTE FUNCTION laufzettel_lauf_geaendert();