Postgresql,一个触发器,它阻止除chosen列之外的所有列的更新

pb3s4cty  于 2023-04-05  发布在  PostgreSQL
关注(0)|答案(3)|浏览(142)

我想创建触发器函数,不指定其他列的名称,这些列不能更新。
示例:

Create table test(id integer, name text, amount numeric);

我想阻止更新除amount以外的任何列,但要避免像这样通过名称指定列idname

IF NEW.id <> OLD.id or NEW.name <> OLD.name THEN RAISE EXCEPTION 'UPDATE DISALLOWED'; END IF;

我想将此触发器应用于多个表,因此不想指定其他列。

也许它是某种可能使用行级安全?但我需要防止也postgres角色从更新.我还没有找到如何做到这一点.
@Edit提示后我想我已经接近解决方案,但在尝试执行生成的IF语句时出现语法错误。https://dbfiddle.uk/vp85wHgc

vngu2lb8

vngu2lb81#

我想出了以下解决方案:

CREATE OR REPLACE FUNCTION public.table_update_guard()
  RETURNS trigger AS
$BODY$
  DECLARE  rec record; 
_columns text;  _old text; _new text;
  BEGIN  

FOR rec IN
SELECT column_name FROM information_schema.columns WHERE table_schema = 'public' 
    AND table_name = TG_TABLE_NAME  and column_name <> 'amount' 
LOOP
_columns = concat(_columns , '$1.' , rec.column_name , ',');
END LOOP;

_columns = left(_columns, -1);

EXECUTE format('SELECT concat(%s )', _columns) into _old
   USING OLD;
EXECUTE format('SELECT concat(%s )', _columns) into _new
   USING NEW;

raise notice '
_new_ % 
_old_ %', _new, _old ;

IF _new <> _old then raise exception 'UPDATE NOT ALLOWED!!';
    END IF;

RETURN NEW;
END;
 
$BODY$
  LANGUAGE plpgsql VOLATILE
  COST 100;
htrmnn0y

htrmnn0y2#

您可以这样做,但是确定更改的列可能需要动态SQL。

CREATE OR REPLACE FUNCTION z_trig()
RETURNS TRIGGER
AS $$
DECLARE
    old_r RECORD;
    new_r RECORD;
BEGIN
    old_r := OLD;
    new_r := NEW;
    old_r.foo := new_r.foo;
    IF old_r.* IS DISTINCT FROM new_r.* THEN
        RAISE EXCEPTION 'Forbidden change!';
    END IF;

    RETURN NULL;
END;
$$
LANGUAGE plpgsql;
mm5n2pyu

mm5n2pyu3#

使用@RichardHuxton的答案。但是如果你想要动态SQL,你可以得到一个列列表作为字符串列表。然后将它们拆分。表名看起来像是触发器函数的预定义变量TG_TABLE_NAME。
要获得列列表,这是典型的方法。但是,除非显式列出列,否则这里的ORDER BY不会正确地给予列的顺序。99/100 ORDER BY将是正确的,但不要相信它。

SELECT column_name
  FROM information_schema.columns
 WHERE table_schema = TG_TABLE_SCHEMA
   AND table_name   = TG_TABLE_NAME
     ;

如果你想要你构建表的顺序,而不是 * 在Postgres中显示列的顺序,你需要看这个。attnum是顺序索引。attname是列名。

SELECT pg_attribute.attname 
FROM pg_catalog.pg_attribute 
INNER JOIN pg_catalog.pg_class ON pg_class.oid = pg_attribute.attrelid 
INNER JOIN pg_catalog.pg_namespace ON pg_namespace.oid = pg_class.relnamespace 
WHERE pg_namespace.nspname = TG_TABLE_SCHEMA and pg_class.relname = TG_TABLE_NAME 
ORDER BY pg_attribute.attnum

参考文献:
https://www.postgresql.org/docs/current/plpgsql-trigger.html
动态SQL示例:

EXECUTE 'SELECT * FROM ' || TG_TABLE_NAME

EXECUTE之后的字符串中的任何内容都是你通常会拥有的有效SQL代码。所以IF语句在那里。如果IF语句包含赋给变量的值,那么就像我对TG_TABLE_NAME所做的那样将变量连接到字符串。
根据你的问题代码,我们就有了这个。

EXECUTE 'IF NEW.' || ID_COLUMN || ' <> OLD' || '.' || ID_COLUMN || ' or NEW.' || NAME_COLUMN + ' <> OLD.' || NAME_COLUMN || ' THEN RAISE EXCEPTION ''UPDATE DISALLOWED''; END IF;'

现在更进一步,使用上面的查询查找ID_COLUMN和NAME_COLUMN变量来查找列名。如果id是第一列,name是第二列,指定列名列表(按正确顺序)使用结果集列表函数返回到变量COLUMN_LIST(这是你的作业)。然后在Postgres中找到函数来抓取片段1并将其分配给ID_COLUMN变量,片段2分配给NAME_COLUMN。
你的代码中的IF语句的错误是在THEN之前没有空格。我把所有的东西都推到了一行。

execute 'IF ' || _sql || ' THEN raise exception ''UPDATE FORBIDDEN!'' END IF;';

相关问题