INSERT с динамическим именем таблицы в функции триггера - PullRequest
29 голосов
/ 27 октября 2011

Я не уверен, как добиться чего-то вроде следующего:

CREATE OR REPLACE FUNCTION fnJobQueueBEFORE() RETURNS trigger AS $$
    DECLARE
        shadowname varchar := TG_TABLE_NAME || 'shadow';
    BEGIN
        INSERT INTO shadowname VALUES(OLD.*);
        RETURN OLD;
    END;
$$
LANGUAGE plpgsql;

т.е. вставка значений в таблицу с динамически генерируемым именем.
Выполнение кода выше дает:

ERROR:  relation "shadowname" does not exist
LINE 1: INSERT INTO shadowname VALUES(OLD.*)

Похоже, что переменные не раскрываются / не допускаются как имена таблиц. Я не нашел ссылки на это в руководстве Postgres.

Я уже экспериментировал с EXECUTE вот так:

  EXECUTE 'INSERT INTO ' || quote_ident(shadowname) || ' VALUES ' || OLD.*;

Но не повезло:

ERROR:  syntax error at or near ","
LINE 1: INSERT INTO personenshadow VALUES (1,sven,,,)

Тип RECORD, похоже, потерян: OLD.*, кажется, преобразован в строку и обработан методом get, что приводит к всевозможным проблемам типов (например, NULL значения).

Есть идеи?

Ответы [ 2 ]

51 голосов
/ 27 октября 2011

PostgreSQL 9.1 или новее

format() имеет встроенный способ экранирования идентификаторов.Проще, чем раньше:

CREATE OR REPLACE FUNCTION foo_before()
  RETURNS trigger AS
$func$
BEGIN
   EXECUTE format('INSERT INTO %I.%I SELECT $1.*'
                , TG_TABLE_SCHEMA, TG_TABLE_NAME || 'shadow')
   USING OLD;

   RETURN OLD;
END
$func$  LANGUAGE plpgsql;

Работает также с выражением VALUES.

db <> fiddle здесь
Старый sqlfiddle.

Основные точки

  • Использование format() или quote_ident() заключать в кавычки идентификаторы (автоматически и только при необходимости), тем самым защищая от SQL-инъекций и простых синтаксических нарушений.
    Это необходимо , даже с вашими именами таблиц!
  • Схема-квалифицировать имя таблицы.В зависимости от текущей настройки search_path пустое имя таблицы может в противном случае преобразоваться в другую таблицу с тем же именем в другой схеме.
  • Использовать EXECUTE длядинамические операторы DDL.
  • Передача значений безопасно с предложением USING.
  • Обратитесь к подробному руководству по Выполнение динамических команд вplpgsql .
  • Обратите внимание, что RETURN OLD; в функции триггера требуется для триггера BEFORE DELETE. Подробности в руководстве здесь.

Вы получаете сообщение об ошибке в вашей почти успешной версии, потому что OLD это не видно внутри EXECUTE.И если вы хотите объединить отдельные значения разложенной строки, как вы пытались, вы должны подготовить текстовое представление каждого отдельного столбца с quote_literal(), чтобы гарантировать правильный синтаксис.Вы также должны были бы заранее знать имен столбцов, чтобы обрабатывать их или запрашивать системные каталоги - что противоречит вашей идее иметь простую, динамическую функцию триггера ...

Мое решение избегает всехэти осложнения.Также немного упрощено.

PostgreSQL 9.0 или более ранняя версия

format() пока недоступна, поэтому:

CREATE OR REPLACE FUNCTION foo_before()
  RETURNS trigger AS
$func$
BEGIN
    EXECUTE 'INSERT INTO ' || quote_ident(TG_TABLE_SCHEMA)
                    || '.' || quote_ident(TG_TABLE_NAME || 'shadow')
                    || ' SELECT $1.*'
    USING OLD;

    RETURN OLD;
END
$func$  LANGUAGE plpgsql;

Похожие:

1 голос
/ 06 марта 2016

Я просто наткнулся на это, потому что искал динамический триггер INSTEAD OF DELETE.В качестве благодарности за вопрос и ответы я выложу свое решение для Postgres 9.3.

CREATE OR REPLACE FUNCTION set_deleted_instead_of_delete()
RETURNS TRIGGER AS $$
BEGIN
    EXECUTE format('UPDATE %I set deleted = now() WHERE id = $1.id', TG_TABLE_NAME)
    USING OLD;
    RETURN NULL;
END;
$$ language plpgsql;
...