触发函数中的无限循环

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

SQL Fiddle.