2010-01-04 3 views
12

Ich habe eine Reihe von Tabellen, die die Postgres "Partitionierung" -Funktion verwenden. Ich möchte einen gemeinsamen BEFORE INSERT OF ROW-Trigger für jede Tabelle definieren, der 1) die Partition dynamisch erstellt, wenn die Einfügung für die Elterntabelle auftritt, und 2) die Einfügung für die Partition erneut ausführt.Einfügen von NEW. * Von einem generischen Trigger mit EXECUTE in PL/pgsql

Etwas wie:

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 

Aber die "NEW" Aufzeichnung ist innerhalb der EXECUTE SQL nicht sichtbar. Wie kann ich das so einfach wie möglich machen?

Als Alternative, kann ich irgendwie über die Felder im NEW-Datensatz iterieren?

Ich habe daran gedacht, mit einer temp-Tabelle:

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; 

Aber dies auch zu einer temp-Tabelle, da die im Cache gespeicherten Referenz nicht funktioniert: Why do I get "relation with OID ##### does not exist" errors when accessing temporary tables in PL/PgSQL functions?

Ich bin mit Postgres 8.2 und ich kann zu keiner anderen Version wechseln.

EDIT:
Wie @alvherre darauf hingewiesen, kann dies wahrscheinlich in Postgres 8.4 mit der EXECUTE ... weiter verwendet Syntax erfolgen. Ein Beispiel finden Sie unter http://wiki.postgresql.org/wiki/PL/pgSQL_Dynamic_Triggers

+0

Verwandte später Frage mit Lösung für Postgres 8.2: http: // Stackoverflow. com/q/7519044/939860 –

+0

@ErwinBrandstetter: Ihre Lösung in der verwandten Frage ist ähnlich, wie ich dieses Problem in meiner Antwort unten gelöst habe, aber in diesem Fall muss die Funktion jedes Mal neu kompiliert werden, wenn eine neue Partition der Tabelle hinzugefügt wird Andernfalls wird die Funktion die aktualisierten Partitionsregeln nicht kennen. –

Antwort

1

ich habe es geschafft, diese dynamisch zu arbeiten, um zu erhalten, indem eine Funktion kompilieren, die die neue Zeile als Parameter akzeptiert:

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

als Postgres Funktionen polymorph sind, diese eine andere Funktion für jede Tabelle generieren, die diese Trigger verwendet.

Obwohl es ein hässlicher Klotz ist, scheint dies die Aufgabe zu erfüllen.

Obwohl es so aussieht, als könnte ich jede polymorphe Variante beim Erstellen des Systems aufgrund des Cachings definieren, muss ich die Funktion immer neu kompilieren, wenn ich eine untergeordnete Tabelle erzeuge oder lösche, so dass die Funktion die letzte Einfüge-Regel verwendet.

EDIT:Zusätzliche Falten
Es ist ein wenig gotcha mit dieser Technik: Wenn diese EXECUTE/PERFORM Aktion für Rollback beim ersten Versuch wegen eines anderen Fehler (zB in meinem Fall eine CHECK-Einschränkung failure) dann scheint die Funktion, die diesen Code enthält, einen Verweis auf die zurückgesetzte Funktion partition_insert() zu cachen, die sie mit EXECUTE erstellt hat, und nachfolgende Aufrufe schlagen fehl, weil ein zwischengespeichertes Objekt nicht gefunden wurde.

Ich löste dies durch Pre-Creating Stub-Versionen der Funktion für jeden erforderlichen Tabelle-Typ-Parameter, wenn ich die Datenbank definieren.

+1

Interessanter Trick. Habe nie davon gehört. Ich schlage vor, Sie schematisieren den Tabellennamen; Wenn Sie dies nicht tun, wird dies auf interessante Weise fehlschlagen, wenn Sie zwei Tabellen mit demselben Namen in verschiedenen Schemas haben. Wahrscheinlich möchten Sie auch das "CREATE OR REPLACE" vermeiden, wenn die Funktion bereits existiert, um den Katalog-Thrashing niedrig zu halten. – alvherre

+0

@alvherre: Du hast noch nie davon gehört, weil ich es gerade erfunden habe :) Ich muss die Funktion jedes Mal neu kompilieren, weil ich die Regeln CREATE RULE ... ON INSERT DO INSTEAD geändert habe und die alten Regeln zwischengespeichert werden die Funktion. Ist es wahr, dass die einzige Möglichkeit, die Funktion neu zu kompilieren, ERSTELLEN ODER ERSETZEN ist? –

+0

... und ja, ich scheme den Namen. Ich habe es aus dem Beispiel weggelassen, um das Rauschen zu reduzieren. –

19

Sie können EXECUTE USING verwenden, um NEW zu übergeben. Ihr Beispiel würde

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

sein (Beachten Sie, dass ich TG_RELID statt Hantieren mit TG_TABLE_SCHEMA und TABLE_NAME regclass gegossen, weil es einfacher zu bedienen, wenn Nicht-Standard. Aber dann plpgsql ist nicht dem Standard entsprechende sowieso.)

+0

Argh - EXECUTE USING ist neu in Postgres 8.4. Ich habe dich nicht bemerkt 8.2. – alvherre

+0

Guter Tipp über TG_RELID. –

+2

Wenn Sie den Tabellennamen anstelle von relid verwenden, sollte 'select $ 1' 'select $ 1 *' sein. –

3

Ja, Sie können EXECUTE ... USING in 8.4 verwenden.Zum Beispiel:

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

In niedrigeren Versionen (ich habe in 8.3 nur getestet), können Sie verwenden:

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