2010-01-04 8 views
12

Mam wiele tabel, które korzystają z funkcji "Partycjonowanie" PostgreSQL. Chcę zdefiniować typowy wyzwalacz PRZED WSTAWIENIEM RZECZY na każdej tabeli, która 1) dynamicznie utworzy partycję, jeśli wstawienie wystąpi względem tabeli nadrzędnej i 2) ponownie uruchomi wstawkę względem partycji.Wstawianie NEW. * Z ogólnego wyzwalacza przy użyciu EXECUTE w PL/pgsql

Coś jak:

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 

Ale "nowy" rekord nie jest widoczny wewnątrz SQL EXECUTE. Jak mogę to zrobić tak prostym sposobem, jak to tylko możliwe?

W jaki sposób mogę ewentualnie powtórzyć pola w NOWYM rekordzie?

Myślałem o użyciu temp-table:

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; 

Ale to też nie działa ze względu na odniesienie do pamięci podręcznej Temp-tabela: Why do I get "relation with OID ##### does not exist" errors when accessing temporary tables in PL/PgSQL functions?

używam PostgreSQL 8.2 i nie mogę zmienić żadnej innej wersji.

EDIT:
Jak @alvherre zauważył, to prawdopodobnie można zrobić w PostgreSQL 8.4 ze składnią EXECUTE ... korzystają. Zobacz przykład pod adresem http://wiki.postgresql.org/wiki/PL/pgSQL_Dynamic_Triggers

+0

Related później pytanie roztworem dla PostgreSQL 8.2: http: // stackoverflow. com/q/7519044/939860 –

+0

@ErwinBrandstetter: Twoje rozwiązanie w pokrewnym pytaniu jest podobne do rozwiązania tego problemu w mojej odpowiedzi poniżej, ale w tym przypadku funkcja musi zostać przekompilowana za każdym razem, gdy nowa partycja zostanie dodana do tabeli lub w przeciwnym razie funkcja nie będzie świadoma zaktualizowanych reguł partycji. –

Odpowiedz

1

udało mi się uzyskać to do pracy przez dynamicznie kompilacji funkcję, która akceptuje nowego wiersza jako parametr:

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

jako funkcje Postgres są polimorficzne, będzie to generować inną funkcję dla każdej tabeli, która wykorzystuje ten spust.

Pomimo tego, że jest to brzydki kaftan, wydaje się, że spełnia on swoje zadanie.

Chociaż wygląda na to, że mogłem zdefiniować każdą odmianę polimorficzną z góry, kiedy buduję system, z powodu buforowania, muszę ponownie skompilować funkcję za każdym razem, gdy utworzę lub upuszczę tabelę podrzędną, aby funkcja używała najnowszej wstawki RULE.

EDIT:Dodatkowe zmarszczki
Jest trochę haczyka z tej techniki: Jeśli ta EXECUTE/WYKONAJ działanie jest zwinięty z powrotem na pierwszą próbę z powodu innego błędu (na przykład w moim przypadku Ograniczenie sprawdzające niepowodzenie), wówczas funkcja zawierająca ten kod wydaje się buforować odwołanie do wycofanej funkcji partition_insert(), którą utworzyła przy użyciu instrukcji EXECUTE, a kolejne wywołania nie powiodły się, ponieważ nie znaleziono obiektu buforowanego.

Rozwiązałem to przez wstępne utworzenie wersji stub funkcji dla każdego wymaganego parametru typu tabeli podczas definiowania bazy danych.

+1

Interesująca sztuczka. Nigdy nie słyszałem o tym. Proponuję ci schemat - zakwalifikuj nazwę tabeli; nieprzestrzeganie tego spowoduje błąd w interesujący sposób, jeśli masz dwie tabele o tej samej nazwie na różnych schematach. Prawdopodobnie również chcesz uniknąć "CREATE OR REPLACE", gdy funkcja już istnieje, aby utrzymać niski katalog. – alvherre

+0

@alvherre: Nigdy o niej nie słyszałeś, ponieważ właśnie to wymyśliłem :) Jestem zmuszony do przekompilowania funkcji za każdym razem, ponieważ zmieniłem reguły CREATE REGLE ... ON INSERT DO INSTEAD, a stare reguły są buforowane z funkcja. Czy to prawda, że ​​jedynym sposobem na przekompilowanie funkcji jest TWORZENIE LUB WYMIANA? –

+0

... i tak, robię schemat - określ nazwę. Właśnie opuściłem to z przykładu, by zmniejszyć hałas. –

19

Możesz użyć EXECUTE USING, aby przekazać NEW. Twój przykład byłoby

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

(Zauważ, że używam TG_RELID lanego do regclass zamiast błahy z TG_TABLE_SCHEMA i TABLE_NAME ponieważ jest łatwiejszy w użyciu, jeśli niestandardowe. Ale wtedy, plpgsql jest nietypowa i tak.)

+0

Argh - WYKORZYSTANIE KORZYSTANIA jest nowością w Postgresie 8.4. Nie zauważyłem, że podałeś 8.2. – alvherre

+0

Dobra wskazówka na temat TG_RELID. –

+2

jeśli używasz nazwy tabeli zamiast relid, "wybierz 1 $" powinno być "wybierz 1 $. *" –

3

Tak, możesz użyć WYKONAJ ... USING w 8.4.Na przykład:

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

W niższych wersjach (Przetestowałem tylko w 8.3), można użyć:

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