2010-01-04 9 views
12

Ho un numero di tabelle che utilizzano la funzione "Partizionamento" di Postgres. Voglio definire un trigger BEFORE INSERT OF ROW comune su ogni tabella che 1) creerà dinamicamente la partizione se l'inserto si verifica contro la tabella padre e 2) rieseguire l'inserto contro la partizione.Inserimento NEW. * Da un trigger generico utilizzando EXECUTE in PL/pgsql

Qualcosa di simile:

CREATE OR REPLACE FUNCTION partition_insert_redirect() 
RETURNS trigger AS $BODY$ 
BEGIN 
    ... create the new partition and set up the redirect Rules ... 

    /* Redo the INSERT dynamically. The new RULE will redirect it to the child table */ 
    EXECUTE 'INSERT INTO ' || quote_ident(TG_TABLE_SCHEMA) || '.' || quote_ident(TG_TABLE_NAME) || 
      ' SELECT NEW.*' 
END 

Ma il record di "NEW" non è visibile all'interno del SQL EXECUTE. Come posso rendere questo lavoro il più semplice possibile?

In alternativa, posso scorrere in qualche modo i campi del NUOVO record?

ho pensato di utilizzare un temp-tavolo:

EXECUTE 'CREATE TEMPORARY TABLE new_row (LIKE ' || 
     quote_ident(TG_TABLE_SCHEMA) || '.' || quote_ident(TG_TABLE_NAME) || 
     ') ON COMMIT DROP'; 

INSERT INTO new_row SELECT NEW.*; 

EXECUTE 'INSERT INTO ' || quote_ident(TG_TABLE_SCHEMA) || '.' || quote_ident(TG_TABLE_NAME) || 
     ' SELECT * FROM new_row'; 
DROP TABLE new_row; 

Ma anche questo non funziona a causa del riferimento a una cache temp-tavolo: Why do I get "relation with OID ##### does not exist" errors when accessing temporary tables in PL/PgSQL functions?

sto usando Postgres 8.2 e non posso passare a nessuna altra versione.

EDIT:
Come @alvherre sottolineato, questo può essere fatto probabilmente in Postgres 8.4 con la sintassi EXECUTE ... UTILIZZO. Vedere un esempio su http://wiki.postgresql.org/wiki/PL/pgSQL_Dynamic_Triggers

+0

questione connessa successivamente con soluzione per Postgres 8.2: http: // StackOverflow. com/q/7519044/939860 –

+0

@ErwinBrandstetter: la soluzione nella domanda correlata è simile a come ho risolto questo problema nella mia risposta di seguito, ma in questo caso, la funzione deve essere ricompilata ogni volta che una nuova partizione viene aggiunta alla tabella o altrimenti la funzione non sarà a conoscenza delle regole di partizione aggiornate. –

risposta

1

sono riuscito a ottenere questo al lavoro compilando dinamicamente una funzione che accetta la nuova riga come parametro:

EXECUTE 'create or replace function partition_insert(r ' || TG_TABLE_NAME || ') RETURNS void AS $FUNC$' || 
      'BEGIN ' || 
       'insert into ' || TG_TABLE_NAME || ' SELECT r.*; ' || 
      'END $FUNC$ LANGUAGE plpgsql VOLATILE'; 
    PERFORM partition_insert(NEW); 

come funzioni Postgres sono polimorfici, questo genererà una funzione diversa per ogni tabella che utilizza questo trigger.

Nonostante sia un brutto scherzo, questo sembra fare il lavoro.

Anche se sembra che sia possibile definire ogni variazione polimorfica in anticipo durante la creazione del sistema, a causa della memorizzazione nella cache, è necessario ricompilare la funzione ogni volta che creo o rilasci una tabella figlio in modo che la funzione utilizzi l'ultimo inserto REGOLA.

EDIT:rughe Ulteriori
C'è un po 'Gotcha con questa tecnica: Se questo ESEGUIRE/eseguire l'azione è laminati-back al primo tentativo a causa di un altro errore (ad esempio, nel mio caso un vincolo CHECK errore), quindi la funzione contenente questo codice sembra memorizzare nella cache un riferimento alla funzione roll_backer partition_insert() creata utilizzando EXECUTE e le chiamate successive non riescono a causa di un oggetto memorizzato nella cache non trovato.

Ho risolto questo problema pre-creando versioni stub della funzione per ogni parametro di tipo tabella richiesto quando definisco il database.

+1

Interessante trucco. Non ne ho mai sentito parlare. Ti suggerisco schema qualificare il nome della tabella; non farlo fallirebbe in modo interessante quando si hanno due tabelle con lo stesso nome su schemi diversi. Probabilmente vorrete anche evitare il comando 'CREATE O REPLACE' quando la funzione esiste già, per mantenere basso il catalogo. – alvherre

+0

@alvherre: Non ne hai mai sentito parlare perché ho appena inventato :) Sono costretto a ricompilare la funzione ogni volta perché ho modificato la regola CREATE ... ON INSERT DO INSTEAD e le vecchie regole sono memorizzate nella cache la funzione. È vero che l'unico modo per ricompilare la funzione è CREARE O SOSTITUIRLA? –

+0

... e sì, faccio schema qualificare il nome. L'ho appena escluso dall'esempio per ridurre il rumore. –

19

È possibile utilizzare EXECUTE USING per passare NOVITÀ. Il tuo esempio potrebbe essere

EXECUTE 'INSERT INTO ' || TG_RELID || '::regclass SELECT $1' USING NEW; 

(Si noti che uso TG_RELID colato a regclass invece di giocherellare con TG_TABLE_SCHEMA e TABLE_NAME perché è più facile da usare, se non standard. Ma poi, plpgsql non è standard in ogni caso.)

+0

Argh - EXECUTE USING è nuovo in Postgres 8.4. Non ho notato che hai specificato 8.2. – alvherre

+0

Buon consiglio su TG_RELID. –

+2

se si utilizza il nome della tabella invece di relid, 'selezionare $ 1' dovrebbe essere 'selezionare $ 1. *' –

3

Sì, è possibile utilizzare EXECUTE ... USING in 8.4.Per esempio:

EXECUTE 'INSERT INTO ' || table_name || ' SELECT $1.*' USING NEW;

Nelle versioni più bassi (ho testato solo in 8.3), è possibile utilizzare:

EXECUTE 'INSERT INTO ' || table_name || 
    ' SELECT (' || quote_literal(NEW) || '::' || TG_RELID::regclass || ').*';