2016-04-11 7 views
5

Unser Produkt hat ein Kernmodul und mehrere optionale Plugins.PostgreSQL: Erzwingen 1: 1 für optionales Plugin

Im Kern befindet sich eine Datenbanktabelle ticket_type.

Ein optionales Plugin erweitert die Tabelle ticket_type um eine 1: 1-Beziehung. Diese Tabelle heißt myplugin_ticket_type_extension. Für jede Zeile in myplugin_ticket_type_extension gibt es eine Zeile in ticket_type. Dies wird über ForeignKey erzwungen. Bis jetzt zu Problemen :-)

Jetzt der schwierige Teil: Wie erzwingen, dass es eine Zeile in myplugin_ticket_type_extension für jede Zeile in ticket_type gibt?

Der schwierige Teil: myplugin ist ein optionales Plugin. Der Kern des Produkts sollte nichts über dieses Plugin wissen.

+0

Was hindert Sie daran, eine Fremdschlüsseleinschränkung zu "ticket_type" hinzuzufügen, die auf die Erweiterungstabelle verweist? –

+0

@NickBarnes Gute Frage. Die Tabelle 'ticket_type' kommt von core. Da 'myplugin' optional ist, darf die initiale Tabelle diese FK-Bedingung nicht haben. Aber ich könnte es in einer Plugin-spezifischen Schemamigration hinzufügen. – guettli

Antwort

3

Die einfachste Möglichkeit, dies zu erzwingen, wäre das Hinzufügen eines zweiten Fremdschlüssels zu ticket_type, der auf die Erweiterungstabelle verweist.

Die Schwierigkeit mit dieser zirkulären Abhängigkeit besteht darin, dass eine INSERT in eine der Tabellen eine Fremdschlüsseleinschränkung verletzt, bevor Sie die Möglichkeit haben, den anderen Datensatz zu erstellen. Sie können dies vermeiden, indem latente Einschränkungen verwenden, die die Fremdschlüsselprüfung verzögern, bis die Transaktion festgeschrieben:

CREATE TABLE ticket_type (id INT PRIMARY KEY); 

CREATE TABLE myplugin_ticket_type_extension (
    id INT PRIMARY KEY, 
    ticket_type_id INT UNIQUE NOT NULL FOREIGN KEY 
    REFERENCES ticket_type (id) 
    DEFERRABLE INITIALLY DEFERRED 
); 
ALTER TABLE ticket_type ADD FOREIGN KEY (id) 
    REFERENCES myplugin_ticket_type_extension (ticket_type_id) 
    DEFERRABLE INITIALLY DEFERRED; 

BEGIN; 
INSERT INTO ticket_type VALUES (1); 
INSERT INTO myplugin_ticket_type_extension VALUES (1,1); 
COMMIT; 

Ein alternativer Ansatz, die eine Überlegung wert sein kann, ist table inheritance zu verwenden:

CREATE TABLE ticket_type (id INT PRIMARY KEY); 
CREATE TABLE myplugin_ticket_type_extension (extension_field INT) INHERITS (ticket_type); 

INSERT INTO myplugin_ticket_type_extension (id, extension_field) VALUES (1,1); 

Datensätze eingefügt in die Erweiterungstabelle wird angezeigt, wenn Sie ticket_type abfragen, so sollte Ihr Kernmodul nicht betroffen sein. Sie können Einsätze direkt in die ticket_type Tabelle verhindern, indem ein Trigger hinzufügen, die entweder Block-Einsätze insgesamt konnte (durch Auslösen einer Ausnahme) oder automatisch neue Datensätze in die Erweiterungstabelle umleiten könnte:

CREATE FUNCTION ticket_type_trg() RETURNS TRIGGER AS $$ 
BEGIN 
    INSERT INTO myplugin_ticket_type_extension (id) VALUES (new.id); 
    RETURN NULL; 
END 
$$ 
LANGUAGE plpgsql; 

CREATE TRIGGER ticket_type_trg 
    BEFORE INSERT ON ticket_type FOR EACH ROW 
    EXECUTE PROCEDURE ticket_type_trg(); 
2

+1 auf dem aufschiebbaren Zwang. Es gibt eine Sache, die in dieser Antwort fehlt und das ist, dass Sie SET CONSTRAINTS IMMEDIATE ausführen können, um PostgreSQL zu zwingen, die Überprüfungen dann auszuführen, anstatt auf Festschreibung zu warten. Das ist normalerweise besser zum Debuggen usw., wenn Ihre Anwendung weiß, dass die Einschränkungen vorhanden sind.

In diesem Fall Ihre Fragen wie folgt aussehen:

BEGIN; 
INSERT INTO ticket_type VALUES (1); 
INSERT INTO myplugin_ticket_type_extension VALUES (1,1); 
SET CONSTRAINTS ALL IMMEDIATE; 
-- Do other work here, knowing that the above foreign key is 
-- already enforced 
COMMIT; 

Beachten Sie, dass das obige Beispiel ein Problem gibt, wenn Sie mehrere latente Einschränkungen haben. In diesen Fällen möchten Sie das nach Name tun (SET CONSTRAINTS name1, name2 IMMEDIATE)

+0

Ja, Sie haben Recht. Ich gab der anderen Antwort das Kopfgeld, da er schneller war. Ich hoffe es geht dir gut. – guettli

+0

Nun, ich habe auch Dinge als Ergänzung zu dieser Antwort geschrieben, so dass es besser passt, dass er es bekommt :-) –

0

Mein Ansatz wäre myplugin_ticket_type_extension Erstellen von Einfügen, Aktualisieren und Löschen von Triggern in ticket_type Tabelle, um dies zu steuern. Dann hätten Sie eine feinkörnige Kontrolle über diese Operationen und können dies sicher garantieren.

Da dies eine Typentabelle ist und diese Art von Tabellen normalerweise nicht sehr groß sind, können Sie sogar einen vollständigen Tabellenscan durchführen, um sicherzustellen, dass alle Datensätze Korrespondenten enthalten und die Tabelle entsprechend füllen.

Ein anderer Ansatz wäre, diese vollständige Tabelle zu scannen und in einer Funktion zu aktualisieren, die von der Anwendung zu einem geeigneten Zeitpunkt aufgerufen wird.

Verwandte Themen