触发函数中的无限循环
Endless loop in trigger function
这是一个触发器,由 table 上的插入、更新或删除调用。保证调用 table 的所有列都受到影响,并且删除 table 也存在。
CREATE OR REPLACE FUNCTION sample_trigger_func() RETURNS TRIGGER AS $$
DECLARE
operation_code char;
table_name varchar(50);
delete_table_name varchar(50);
old_id integer;
BEGIN
table_name = TG_TABLE_NAME;
delete_table_name = TG_TABLE_NAME || '_deletes';
SELECT SUBSTR(TG_OP, 1, 1)::CHAR INTO operation_code;
IF TG_OP = 'DELETE' THEN
OLD.mod_op = operation_code;
OLD.mod_date = now();
RAISE INFO 'OLD: %', (OLD).name;
EXECUTE format('INSERT INTO %s VALUES %s', delete_table_name, (OLD).*);
ELSE
EXECUTE format('UPDATE TABLE %s SET mod_op = %s AND mod_date = %s'
, TG_TABLE_NAME, operation_code, now());
END IF;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
ELSE
分支触发死循环。可能会有更多问题。
如何解决?
ELSE
分支可以从根本上简化。但是还有一些事情效率低下/不准确/危险:
CREATE OR REPLACE FUNCTION sample_trigger_func()
RETURNS TRIGGER AS
$func$
BEGIN
IF TG_OP = 'DELETE' THEN
RAISE INFO 'OLD: %', OLD.name;
EXECUTE format('INSERT INTO %I SELECT ().*', TG_TABLE_NAME || '_deletes')
USING OLD #= hstore('{mod_op, mod_datetime}'::text[]
, ARRAY[left(TG_OP, 1), now()::text]);
RETURN OLD;
ELSE -- insert, update
NEW.mod_op := left(TG_OP, 1);
NEW.mod_datetime := now();
RETURN NEW;
END IF;
END
$func$ LANGUAGE plpgsql;
在ELSE
分支直接赋值给NEW
即可。不需要更动态的 SQL - 这会再次触发相同的触发器,导致无限循环。这是主要错误。
RETURN NEW;
在 IF
构造之外会破坏 DELETE
的触发函数,因为 NEW
未分配给 DELETE。
一个关键特性是使用 hstore
and the hstore operator #=
动态更改 众所周知的行类型 的两个选定字段 - 即 unknown 在编写代码时。这样您就不会篡改原始 OLD
值,如果您在事件链中有更多触发器,这可能会产生令人惊讶的副作用。
OLD #= hstore('{mod_op, mod_datetime}'::text[]
, ARRAY[left(TG_OP, 1), now()::text]);
必须安装附加模块 hstore
。详情:
- How to set value of composite variable field using dynamic SQL
- Passing column names dynamically for a record variable in PostgreSQL
在此处使用 hstore(text[], text[])
变体来动态构建具有多个字段的 hstore
值。
plpgsql中的赋值运算符是:=
:
- The forgotten assignment operator "=" and the commonplace ":="
请注意,我使用了列名 mod_datetime
而不是误导性的 mod_date
,因为该列显然是 timestamp
而不是 date
.
我添加了一些其他的改进。触发器本身应如下所示:
CREATE TRIGGER insupdel_bef
BEFORE INSERT OR UPDATE OR DELETE ON table_name
FOR EACH ROW EXECUTE PROCEDURE sample_trigger_func();
这是一个触发器,由 table 上的插入、更新或删除调用。保证调用 table 的所有列都受到影响,并且删除 table 也存在。
CREATE OR REPLACE FUNCTION sample_trigger_func() RETURNS TRIGGER AS $$
DECLARE
operation_code char;
table_name varchar(50);
delete_table_name varchar(50);
old_id integer;
BEGIN
table_name = TG_TABLE_NAME;
delete_table_name = TG_TABLE_NAME || '_deletes';
SELECT SUBSTR(TG_OP, 1, 1)::CHAR INTO operation_code;
IF TG_OP = 'DELETE' THEN
OLD.mod_op = operation_code;
OLD.mod_date = now();
RAISE INFO 'OLD: %', (OLD).name;
EXECUTE format('INSERT INTO %s VALUES %s', delete_table_name, (OLD).*);
ELSE
EXECUTE format('UPDATE TABLE %s SET mod_op = %s AND mod_date = %s'
, TG_TABLE_NAME, operation_code, now());
END IF;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
ELSE
分支触发死循环。可能会有更多问题。
如何解决?
ELSE
分支可以从根本上简化。但是还有一些事情效率低下/不准确/危险:
CREATE OR REPLACE FUNCTION sample_trigger_func()
RETURNS TRIGGER AS
$func$
BEGIN
IF TG_OP = 'DELETE' THEN
RAISE INFO 'OLD: %', OLD.name;
EXECUTE format('INSERT INTO %I SELECT ().*', TG_TABLE_NAME || '_deletes')
USING OLD #= hstore('{mod_op, mod_datetime}'::text[]
, ARRAY[left(TG_OP, 1), now()::text]);
RETURN OLD;
ELSE -- insert, update
NEW.mod_op := left(TG_OP, 1);
NEW.mod_datetime := now();
RETURN NEW;
END IF;
END
$func$ LANGUAGE plpgsql;
在
ELSE
分支直接赋值给NEW
即可。不需要更动态的 SQL - 这会再次触发相同的触发器,导致无限循环。这是主要错误。RETURN NEW;
在IF
构造之外会破坏DELETE
的触发函数,因为NEW
未分配给 DELETE。一个关键特性是使用
hstore
and the hstore operator#=
动态更改 众所周知的行类型 的两个选定字段 - 即 unknown 在编写代码时。这样您就不会篡改原始OLD
值,如果您在事件链中有更多触发器,这可能会产生令人惊讶的副作用。OLD #= hstore('{mod_op, mod_datetime}'::text[] , ARRAY[left(TG_OP, 1), now()::text]);
必须安装附加模块
hstore
。详情:- How to set value of composite variable field using dynamic SQL
- Passing column names dynamically for a record variable in PostgreSQL
在此处使用
hstore(text[], text[])
变体来动态构建具有多个字段的hstore
值。plpgsql中的赋值运算符是
:=
:- The forgotten assignment operator "=" and the commonplace ":="
请注意,我使用了列名
mod_datetime
而不是误导性的mod_date
,因为该列显然是timestamp
而不是date
.
我添加了一些其他的改进。触发器本身应如下所示:
CREATE TRIGGER insupdel_bef
BEFORE INSERT OR UPDATE OR DELETE ON table_name
FOR EACH ROW EXECUTE PROCEDURE sample_trigger_func();