Checking constraints before commit OO to RDMS

Hallo PostGres Experten,

ich habe ein gewaltiges Problem, und versuche eine Lösung dafür.

Ich arbeite seit Dezember auf einem Projekt, wo sie eine schlechte Framework gewählt haben. Statt eine Mapping von OO zum RDMS Modell zu machen, haben sie das OO Modell in der Datenbank reingepresst. Also statt “normale” Tabellen, die gefüllt mit Attributen und verbunden via ForeignKey Constraints, habe ich ein platt gedrückte Schema bestehende aus eine Tabelle Object, eine Tabelle ObjectAttribute und eine Tabelle Association, wo die Beziehungen zwischen die Entitys gespeichert sind.

Sie können vorstellen, was das für Probleme kommen. Keine Referential Integrity, Keine Felddomain Checks usw. Ich habe keine Chance, das auszutauschen.

So, was ich wissen möchte ist, ob ich die normale Foreign Key constraints eines RDMS irgendwie teilweise simulieren kann. Ich stelle mir vor, vor jeden Scheißcommit, mehrere Querys laufen zu lassen , dass ein paar der Essential Referential Integrity Checks prüft. Und falls, die mit einem negativ Ergebnis zurückkommen, dann wird der Commit nicht ausgeführt.

Ich kenne mich NULL mit Postgres aus. Ich weiß in Oracle, ich kann PL/SQL code schreiben, die ich wahrscheinlich mit einem Commit Statement verbinden kann. Gibt es so was in Postgres? Oder hat jemand einen anderen Idee (außer Framework Hersteller erschießen).

MfG

Ian

das ginge über TRIGGER, aber einfacher wäre es zu kündigen.

Vielen Dank für die schnelle Antwort. Also, um wirklich sicher zu sein…

Beim Commit ist es möglich Triggers zu “triggern”, die einige Regeln prüfen könnte. Falls eine Regel nicht gewährleistet ist, dann könnte man den Commit abbrechen. Ich möchte hier keine “Post-Commit” Triggers auslösen, weil das Kind wäre schon im Brunnen.

Und “ja” kündigen ist eine Option, aber die zahlen wohl gut :slight_smile:

ja, ‘before insert’ z.B. existiert:

edb=*# \h create trigger
Command:     CREATE TRIGGER
Description: define a new trigger
Syntax:
CREATE [ CONSTRAINT ] TRIGGER name { BEFORE | AFTER | INSTEAD OF } { event [ OR ... ] }
    ON table_name
    [ FROM referenced_table_name ]
    [ NOT DEFERRABLE | [ DEFERRABLE ] [ INITIALLY IMMEDIATE | INITIALLY DEFERRED ] ]
    [ REFERENCING { { OLD | NEW } TABLE [ AS ] transition_relation_name } [ ... ] ]
    [ FOR [ EACH ] { ROW | STATEMENT } ]
    [ WHEN ( condition ) ]
    EXECUTE { FUNCTION | PROCEDURE } function_name ( arguments )

where event can be one of:

    INSERT
    UPDATE [ OF column_name [, ... ] ]
    DELETE
    TRUNCATE

URL: https://www.postgresql.org/docs/13/sql-createtrigger.html

Thanks for the syntax.

Aber “event” ist nicht commit. Nur beim commit weiss ich, ob ausreichend und passende Daten in der “object” Tabelle und “Association” Tabelle geschrieben ist.

Gibt es commit trigger?

‘before insert’ führt Aktionen aus bevor die Daten committed werden. Da kannst du Eingaben prüfen, ändern und ggf. komplett verwerfen.

Thanks Gisman, aber mit der aktuellen Tabellen Struktur, ist das vielleicht zu früh.

Für zwei Objekte A und B habe ich eine ForeignKey Beziehung. Also B enthält A.Key als Feld. In diesem Mistschema, statt einen Eintrag in Tabelle A und Tabelle B, habe ich zwei Einträge in der Tabelle “Object” zig Einträge in der Tabelle “ObjectAttribute” und wenn es gut läuft, hoffentlich einen Eintrag in der Tabelle “Association”.

Ich kann kein Update Trigger auf einer einzeln Tabelle haben, weil die Reihenfolge der Inserts in Object, ObjectAttribute und Association willkürlich ist. LOL. Fuck this. Also, erst wenn alle Einträge drin sind, kann ich erst die Referential Integrity prüfen. Deswegen suche ich etwas, was ich mit “Commit” verbinden kann.

Aber ich habe ein Idee. Ich könnte das Framework erweitern und eine Tabelle “IdiotenSchema” erzeugen. Es halt nur einen Feld, mit einem Sequence “Leckmich”. Das Framework sorgt dafür, dass bevor einen Commit gemacht wird, das Feld IdiotenSchema.Leckmich geupdatet wird. Ich könnte dann Triggers auf die Tabelle IdiotenSchema bauen, die irgendwelche minimale Prüfung machen wurde.

Aber ich wurde liebe das beim Commit Logik machen, wenn möglich.

du kannst TRIGGER auf deferred setzen, damit kannst Du die erst beim COMMIT feuern lassen. Dann ist es egal, wenn VOR dem Commit Constraints noch nicht erfüllt sind.

Good tip with Deferred.

Gibt es intern eine Optimierung. Wenn ich zwei Inserts auf der Megatabelle “Object” brauche ich nur einmal das Trigger (im Deffered modus)

edb=# \h create trigger
Command:     CREATE TRIGGER
Description: define a new trigger
Syntax:
CREATE [ CONSTRAINT ] TRIGGER name { BEFORE | AFTER | INSTEAD OF } { event [ OR ... ] }
    ON table_name
    [ FROM referenced_table_name ]
    [ NOT DEFERRABLE | [ DEFERRABLE ] [ INITIALLY IMMEDIATE | INITIALLY DEFERRED ] ]
    [ REFERENCING { { OLD | NEW } TABLE [ AS ] transition_relation_name } [ ... ] ]
    [ FOR [ EACH ] { ROW | STATEMENT } ]
    [ WHEN ( condition ) ]
    EXECUTE { FUNCTION | PROCEDURE } function_name ( arguments )

where event can be one of:

    INSERT
    UPDATE [ OF column_name [, ... ] ]
    DELETE
    TRUNCATE

URL: https://www.postgresql.org/docs/13/sql-createtrigger.html

edb=# 

for each statement … könnte Dein Freund sein.

Wo liegt denn überhaupt das Problem?

Aus Objektsicht ist „alles ok“. Die Persistierung erfolgt in einer dummen Datenbank und wenn Objektdaten geschrieben werden, deren Verweise ins Nichts gehen, bleiben halt Leichen übrig. Das würde bedeuten, dass auch aus Objektsicht eine Speicherung nicht erfolgreich oder nicht erfolgreich war. Ziemlich transparent. Eben etwas eingetragen, jetzt ist es futsch. Pech.

Na klar, es entstehen in der DB viele Leichen. Diese Leichen fressen Platz. So what?!

Trotzdem: Die Klassenhierarchie hat wegen der fehlerhaften Speicherung eben auch ein Leck. Sie muss erneut, richtig speichern. Das muss der User durchführen oder der Entwickler verbessern oder erforschen. Das Problem bleibt aber ungefähr da, wo es entsteht. Und in der entsprechenden Verantwortung.

So mal als Gedankenspiel:

Erst in dem Moment, wo ich „hintenrum“, in der DB für Reports, Batchverarbeitung oder solche Sachen Abfragen und Operationen durchführe, die „neben“ dem Objektmodell irgendwelche Dinge tun und wenn ich dabei die Klassenhierarchie „von unten“ anfasse, tote Objekte verarbeite, summiere usw. erst dann habe ich ein echtes Problem. Es kommt nur Müll raus. Im schlimmsten Fall nicht mal offensichtlicher Müll, nur wenn man genau hinschaut, passen Details aus der „Objekt App“ nie zu den DB Reports…

An der Stelle muss ich mich entscheiden, mache ich die oben genannten Jobs mit dem gleichen Klassenmodell „von oben“, habe irgendwo ein paar Leichen und bin „glücklich“ oder fange ich einen aussichtslosen Kampf an?

Fazit:
Wenn es „nur“ darum geht, irgendwelchen fehlerhaften Serialisierungsprozessen auf die Schliche zu kommen, Entwickler zu ermahnen, Entwicklern den produzierten Schrott zu zeigen, dann kann ich das mit ein paar Select Statements exemplarisch, worst case (größte Müllhaufen zuerst) und zyklisch immer wieder durchführen. Bis es behoben ist. Wenn ich aber auf Commit Ebene in laufende Prozesse eingreife (und wenn ich dabei selbst vielleicht auch mal irgendwo einen Fehler mache), dann kann das in der Produktion ganz schön nach hinten los gehen. Im Zweifel ist die Programmierung so schlecht, dass ein Commit/Rollback noch nicht mal im Programm hoch kommt als Fehler:

try
persist
except
do nothing
end

Ein echter Anwender wird dabei wahnsinnig, eine Maschine interessierts auch nicht, die Daten landen schön im Nirvana und zwar komplett (statt gespeichert zu werden, plus ein paar Zombies zu produzieren)

Würde ich mir sehr genau überlegen, ob ich so einen Forced Rollback Mechanismus anlege. Wenn es ein großes System ist, besteht auch noch die Gefahr, dass die Trigger das Laufzeitverhalten stark beeinflussen.

Lieber erst mal ein paar Select Statements zur Prüfung bauen, daraus Monitoring Views anlegen und ständig Reporten, von wo welcher Dreck rein kommt. Kann man auch schön ein Dashboard draus bauen, Grafana oder so. Dann die Firmenseite hacken und es als Landingpage einrichten. Dann mit den Leuten reden und Lösegeldforderungen stellen.

1 „Gefällt mir“