Iva*_*pov 5 postgresql trigger database-design permissions sequence
我想防止显式插入串行列。我想到了以下触发器:
drop table test_table;
create table test_table(
id bigserial primary key,
foobar text
);
create or replace function serial_id_check() returns trigger as
$$
begin
if new.id != currval(TG_TABLE_NAME||'_id_seq') then
raise exception 'Explicit insert into serial id, currval = %, tried to insert = %', currval(TG_TABLE_NAME||'_id_seq'), new.id;
end if;
return new;
end;
$$ language plpgsql;
create trigger test_table_serial_id_check
before insert on test_table
for each row
execute procedure serial_id_check();
Run Code Online (Sandbox Code Playgroud)
也许有更好的方法?也许这种方法被打破了,这根本无法实现?
PS 我也考虑不授予插入和更新的权限,而只授予 pgplsql 插入/更新程序的权限 - 但这种方法现在对我来说是不可能的。
Erw*_*ter 13
在Postgres 10或更高版本中,请考虑使用IDENTITY列。喜欢:
id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY
Run Code Online (Sandbox Code Playgroud)
看:
旧版本的原始答案:
在Postgres 9.4 中,视图可以自动按列更新。即,只要满足基本条件,就可以自动更新列以用于对基础列的简单引用。表达式不是。我们可以利用这个特性:
CREATE TABLE test_table(
id serial PRIMARY KEY
, foobar text
);
CREATE VIEW test_view AS
SELECT id * 1 AS id -- id unchanged but not updatable!
, foobar
FROM test_table;
Run Code Online (Sandbox Code Playgroud)
通过id与相乘1,该值不变,但该列不再自动更新。
REVOKE INSERT/UPDATE权限test_table来自您的用户。GRANT INSERT/UPDATE在test_view你的用户。现在他们可以做任何事情,但他们不能手动设置或更改id. 如何处理是你的选择DELETE。
这个简单快速的解决方案在 Postgres 9.4 中开箱即用。Postgres 9.3 引入了可自动更新的视图,但该版本中的所有列都需要可更新才能使该功能正常工作。
在Postgres 9.3 或更早版本中,提供INSTEAD OF INSERT触发器或无条件ON INSERT DO INSTEAD规则。
手动编写规则/触发器时,您不需要id * 1技巧。您甚至可能希望在 Postgres 9.4+ 中使用它来微调功能。例子:
CREATE VIEW test_view1 AS TABLE test_table;
CREATE OR REPLACE RULE ins_up AS
ON INSERT TO test_view1 DO INSTEAD
INSERT INTO test_table (foobar) VALUES (NEW.foobar);
Run Code Online (Sandbox Code Playgroud)
现在,INSERT在视图上是可能的,但还没有UPDATE或DELETE. 如果需要,请编写更多规则。
SQL 小提琴 (Postgres 9.6)
重要区别:虽然自动更新视图拒绝尝试INSERT/ UPDATEvalues inid有异常,但演示RULE只是忽略值id并毫无例外地继续。
或者,您可以简单地用序列中的下一个值无条件地覆盖序列列:
CREATE OR REPLACE FUNCTION force_serial_id()
RETURNS trigger
LANGUAGE plpgsql AS
$func$
BEGIN
NEW.id := nextval(pg_get_serial_sequence(quote_ident(TG_TABLE_NAME), 'id'));
RETURN NEW;
END
$func$;
Run Code Online (Sandbox Code Playgroud)
这比尝试聪明起来更简单、更便宜、更不容易出错。quote_ident()安全地转义其他非法名称(也可以防止 SQL 注入)。
就像@dezso 评论的那样,这会在正常操作中用一列烧掉每行两个数字,serial因为在触发器函数启动之前获取默认值。通常,序列中的间隙应该不是问题(无论如何都是可以预料的),但是您可以通过DEFAULT从色谱柱中取出 来避免副作用。然后你完全依赖触发器。
您可以UPDATE使用单独的触发器和触发器本身的条件来微调案例WHEN (OLD.id <> NEW.id)。语法示例:
请注意 的使用pg_get_serial_sequence(),它不会像您原来的非基本标识符那样中断。想想"MyTable"或非默认的序列名称。仍然假设id我个人从未使用过的列名,因为它没有描述性。
这不正是一个答案还,但你想要的是GENERATED ALWAYS。而且,它正在路上,也许在 PostgreSQL 的下一个版本 PostgreSQL 10 发布时
CREATE TABLE itest4 (
a int GENERATED ALWAYS AS IDENTITY,
b text
);
Run Code Online (Sandbox Code Playgroud)
从 SQL 规范,
If <identity column specification> is specified, then:
i) An indication that the column is an identity column.
ii) If ALWAYS is specified, then an indication that values are always generated.
iii) If BY DEFAULT is specified, then an indication that values are generated by default.
iv) The General Rules of Subclause 9.26, “Creation of a sequence generator”, are applied with
SGO as OPTIONS and ICT as DATA TYPE; let the descriptor of the sequence generator SG be
the SEQGENDESC returned from the application of those General Rules.
Run Code Online (Sandbox Code Playgroud)