Как динамически использовать 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