Подтвердить что ты не робот

Вставка NEW. * Из общего триггера с использованием EXECUTE в PL/pgsql

У меня есть несколько таблиц, в которых используется функция "Разметка" Postgres. Я хочу определить общий триггер ПЕРЕД ВСТРОЕНЫ ROW для каждой таблицы, который будет: 1) динамически создавать раздел, если вставка происходит против родительской таблицы, и 2) повторно выполнить вставку против раздела.

Что-то вроде:

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

Но запись "NEW" не отображается внутри EXECUTE SQL. Как я могу сделать эту работу максимально простой?

Как альтернатива, могу ли я как-то перебирать поля в NEW-записи?

Я подумал об использовании временной таблицы:

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;

Но это также не работает из-за кэшированной ссылки на временную таблицу: Почему я получаю "отношение с OID ##### does не существует" при доступе к временным таблицам в функциях PL/PgSQL?

Я использую Postgres 8.2, и я не могу перейти на другую версию.

EDIT:
Как отметил @alvherre, это, вероятно, можно сделать в Postgres 8.4 с синтаксисом EXECUTE... USING. Пример: http://wiki.postgresql.org/wiki/PL/pgSQL_Dynamic_Triggers

4b9b3361

Ответ 1

Мне удалось заставить это работать, динамически компилируя функцию, которая принимает новую строку как параметр:

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

Поскольку функции Postgres являются полиморфными, это создаст другую функцию для каждой таблицы, которая использует этот триггер.

Несмотря на то, что это уродливый клоч, это, похоже, делает эту работу.

Хотя похоже, что я могу определить каждую полиморфную вариацию спереди, когда я строю систему, из-за кэширования, я должен перекомпилировать эту функцию всякий раз, когда создаю или удаляю дочернюю таблицу, чтобы функция использовала последнюю версию RULE. p >

РЕДАКТИРОВАТЬ: Дополнительные морщины
Там немного получилось с этой методикой: если это действие EXECUTE/PERFORM отменено с первой попытки из-за другой ошибки (например, в моем случае сбой ограничения CHECK), то функция, содержащая этот код, похоже, кэширует ссылку на откат функции partition_insert(), созданной с использованием EXECUTE, и последующие вызовы завершаются сбоем из-за отсутствия кэшированного объекта.

Я решил это, предварительно создав версии-заглушки функции для каждого требуемого параметра типа таблицы, когда я определяю базу данных.

Ответ 2

Вы можете использовать EXECUTE USING, чтобы передать NEW. Ваш пример будет

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

(Обратите внимание, что я использую TG_RELID, отлитый от regclass, вместо того, чтобы возиться с TG_TABLE_SCHEMA и TABLE_NAME, потому что он проще в использовании, если он нестандартен. Но тогда plpgsql нестандартно.)

Ответ 3

Да, вы можете использовать EXECUTE... ИСПОЛЬЗОВАНИЕ в 8.4. Например:

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

В более низких версиях (я только тестировал в 8.3) вы можете использовать:

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