2016-04-11 15 views
5

Il nostro prodotto ha un modulo principale e diversi plugin opzionali.PostgreSQL: applica 1: 1 per plugin opzionale

Nel nucleo è una tabella di database denominata ticket_type.

Un plug-in facoltativo estende la tabella ticket_type tramite una relazione 1: 1. Questa tabella è denominata myplugin_ticket_type_extension.

Per ogni riga in myplugin_ticket_type_extension è presente una riga in ticket_type. Questo viene applicato tramite un ForeignKey. Fino ad ora per i problemi :-)

Ora la parte difficile: come far rispettare che c'è una riga in myplugin_ticket_type_extension per ogni riga in ticket_type?

La parte difficile: myplugin è un plug-in opzionale. Il nucleo del prodotto non dovrebbe sapere nulla di questo plugin.

+0

Cosa ti impedisce di aggiungere un vincolo di chiave esterna a 'ticket_type' facendo riferimento alla tabella di estensione? –

+0

@NickBarnes Buona domanda. La tabella 'ticket_type' viene dal core. Poiché 'myplugin' è facoltativo, la tabella iniziale non deve avere questo vincolo FK. Ma potrei aggiungerlo in una migrazione specifica dello schema di plugin. – guettli

risposta

3

Il modo più semplice per imporre ciò è aggiungere una seconda chiave esterna a ticket_type che fa riferimento alla tabella di estensione.

La difficoltà con questa dipendenza circolare è che un INSERT in entrambe le tabelle viola un vincolo di chiave esterna, prima di avere la possibilità di creare l'altro record. È possibile evitare questo utilizzando i vincoli anticipate, che ritarderà il controllo della chiave estera fino a quando la transazione si impegna:

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; 

Un approccio alternativo che può essere opportuno prendere in considerazione è quello di utilizzare table inheritance:

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); 

record inseriti nella tabella di estensione verrà visualizzato quando si esegue una query su ticket_type, pertanto il modulo principale non deve essere modificato. È possibile impedire inserti direttamente nella tabella ticket_type aggiungendo un trigger, che potrebbe o blocchi inseriti complessivamente (alzando un'eccezione), o potrebbe reindirizzare automaticamente nuovi record alla tabella estensione:

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 sul differibile vincolo. C'è una cosa che manca a quella risposta e cioè che è possibile eseguire SET CONSTRAINTS IMMEDIATE per forzare PostgreSQL ad eseguire i controlli, anziché attendere il commit. Di solito è meglio per il debugging se la tua applicazione sa che i vincoli ci sono.

In tal caso, le query del tipo:

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; 

Si noti che l'esempio precedente si dà un problema se si dispone di più vincoli differiti. In questi casi si desidera farlo per nome (SET CONSTRAINTS name1, name2 IMMEDIATE)

+0

Sì, hai ragione. Ho dato l'abbondanza all'altra risposta dato che era più veloce. Spero che vada bene per te. – guettli

+0

Beh, ho anche scritto delle cose come supplemento a quella risposta, quindi ho pensato che è più appropriato che lo capisca :-) –

0

Il mio approccio sarebbe myplugin_ticket_type_extension che crea inserimenti, aggiorna ed elimina trigger nella tabella ticket_type per controllarlo. Quindi avresti il ​​controllo a grana fine su queste operazioni e puoi garantire questo di sicuro.

Poiché si tratta di una tabella di tipi e questi tipi di tabelle normalmente non sono molto grandi, è anche possibile eseguire una scansione completa della tabella per assicurare che tutti i record contengano corrispondenze e riempire la tabella di conseguenza.

Un altro approccio sarebbe eseguire questa scansione completa della tabella e aggiornarla in una funzione chiamata dall'applicazione in un momento appropriato.

Problemi correlati