Как динамически использовать TG_TABLE_NAME в PostgreSQL 8.2?

Я пытаюсь написать триггерную функцию в PostgreSQL 8.2, которая будет динамически использовать TG_TABLE_NAME для генерации и выполнения оператора SQL. Я могу найти всевозможные примеры для более поздних версий PostgreSQL, но я застрял на 8.2 из-за некоторых требований. Вот моя функция в ее нынешнем виде, которая работает, но вряд ли она динамична:

CREATE OR REPLACE FUNCTION cdc_TABLENAME_function() RETURNS trigger AS $cdc_function$
        DECLARE 
        op  cdc_operation_enum;
    BEGIN
        op = TG_OP;

        IF (TG_WHEN = 'BEFORE') THEN
            IF (TG_OP = 'UPDATE') THEN
                op = 'UPDATE_BEFORE';
            END IF;

            INSERT INTO cdc_test VALUES (DEFAULT,DEFAULT,op,DEFAULT,DEFAULT,OLD.*); 
        ELSE
            IF (TG_OP = 'UPDATE') THEN
                op = 'UPDATE_AFTER';
            END IF;

            INSERT INTO cdc_test VALUES (DEFAULT,DEFAULT,op,DEFAULT,DEFAULT,NEW.*); 
        END IF;

        IF (TG_OP = 'DELETE') THEN
            RETURN OLD;
        ELSE
            RETURN NEW;
        END IF;
    END;

То, как это в настоящее время написано, я должен был бы написать отдельную функцию триггера для каждой таблицы. Я хотел бы использовать TG_TABLE_NAME для динамического построения моего оператора INSERT и просто поставить перед ним префикс "cdc_", поскольку все таблицы следуют одному и тому же соглашению об именах. Тогда я могу иметь каждый триггер для каждого вызова таблицы только одну функцию.

2 ответа

Решение

Я искал то же самое пару лет назад. Одна триггерная функция, чтобы управлять ими всеми! Я спрашивал в списках usenet, пробовал разные подходы, но безрезультатно. Консенсус по этому вопросу был невозможен. Недостаток PostgreSQL 8.3 или старше.

Начиная с PostgreSQL 8.4 вы можете просто:

EXECUTE 'INSERT INTO ' || TG_RELID::regclass::text || ' SELECT ($1).*'
USING NEW;

С pg 8.2 у вас проблема:

  • не может динамически получить доступ к столбцам NEW / OLD, Вам нужно знать имена столбцов во время написания триггерной функции.
  • NEW / OLD не видны внутри EXECUTE,
  • EXECUTE .. USING еще не родился.

Однако есть хитрость.

Каждое имя таблицы в системе может служить составным типом с тем же именем. Поэтому вы можете создать функцию, которая принимает NEW / OLD в качестве параметра и выполнить это. Вы можете динамически создавать и уничтожать эту функцию при каждом событии триггера:

Функция запуска:

CREATE OR REPLACE FUNCTION trg_cdc()
  RETURNS trigger AS
$func$
DECLARE
   op      text := TG_OP || '_' || TG_WHEN;
   tbl     text := quote_ident(TG_TABLE_SCHEMA) || '.'
                || quote_ident(TG_TABLE_NAME);
   cdc_tbl text := quote_ident(TG_TABLE_SCHEMA) || '.'
                || quote_ident('cdc_' || TG_TABLE_NAME);
BEGIN

EXECUTE 'CREATE FUNCTION f_cdc(n ' || tbl || ', op text)
  RETURNS void AS $x$ BEGIN
  INSERT INTO ' || cdc_tbl || ' SELECT op, (n).*;
END $x$ LANGUAGE plpgsql';

CASE TG_OP
WHEN 'INSERT', 'UPDATE' THEN
   PERFORM f_cdc(NEW, op);
WHEN 'DELETE' THEN
   PERFORM f_cdc(OLD, op);
ELSE
   RAISE EXCEPTION 'Unknown TG_OP: "%". Should not occur!', TG_OP;
END CASE;

EXECUTE 'DROP FUNCTION f_cdc(' || tbl || ', text)';

IF TG_OP = 'DELETE' THEN
    RETURN OLD;
ELSE
    RETURN NEW;
END IF;

END
$func$  LANGUAGE plpgsql;

Спусковой крючок:

CREATE TRIGGER cdc
BEFORE INSERT OR UPDATE OR DELETE ON my_tbl
FOR EACH ROW EXECUTE PROCEDURE trg_cdc();

Имена таблиц должны обрабатываться как пользовательский ввод. использование quote_ident() защищаться от внедрения SQL.

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

Середина

PostgreSQL поддерживает перегрузку функций. Следовательно, одна функция на таблицу с одним и тем же базовым именем (но с другим типом параметра) может сосуществовать. Вы можете занять золотую середину и резко уменьшить шум, создав f_cdc(..) один раз для таблицы одновременно вы создаете триггер. Это одна крошечная функция на таблицу. Вы должны наблюдать за изменениями в определениях таблиц, но таблицы не должны так часто меняться. Удалить CREATE а также DROP FUNCTION из функции триггера, получая небольшой, быстрый и элегантный триггер.

Я мог видеть, что я делаю это в pg 8.2. За исключением того, что я больше не вижу, что я делаю что-либо в pg 8.2. Он достиг конца жизни в декабре 2011 года. Может быть, вы можете обновить как-нибудь в конце концов.

Я также задавал подобный вопрос пару лет назад.

Посмотрите на этот вопрос и посмотрите, дает ли он вам полезные идеи:

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

Другие вопросы по тегам