简体   繁体   English

postgres:如何一般地使列不可变?

[英]postgres: How to generically make a column immutable?

Here's the problem.这就是问题所在。

create table customer (
  customer_id int generated by default as identity (start with 100) primary key
);
create table cart (
  cart_id int generated by default as identity (start with 100) primary key
);

I want to protect customer_id and cart_id from updating generically once they are inserted.我想保护customer_idcart_id在插入后不进行一般更新。 How?如何?


UPD: While I was writing the question I found the answer to my original question. UPD:当我写这个问题时,我找到了我原来问题的答案。 Here it is:这里是:

create table cart (
  cart_id int generated by default as identity (start with 100) primary key,
  name text not null,
  at timestamp with time zone
);

create or replace function table_update_guard() returns trigger
language plpgsql immutable parallel safe cost 1 as $body$
begin
  raise exception
    'trigger %: updating is prohibited for %',
    tg_name, tg_argv[0]
    using errcode = 'restrict_violation';
  return null;
end;
$body$;

create or replace trigger cart_update_guard
before update of cart_id, name on cart for each row
-- NOTE: the WHEN clause below is optional
when (
     old.cart_id is distinct from new.cart_id
  or old.name    is distinct from new.name
)
execute function table_update_guard('cart_id, name');

> insert into cart (cart_id, name) values (0, 'prado');
INSERT 0 1
> update cart set cart_id = -1 where cart_id = 0;
ERROR:  trigger cart_update_guard: updating is prohibited for cart_id, name
CONTEXT:  PL/pgSQL function table_update_guard() line 3 at RAISE
> update cart set name = 'nasa' where cart_id = 0;
ERROR:  trigger cart_update_guard: updating is prohibited for cart_id, name
CONTEXT:  PL/pgSQL function table_update_guard() line 3 at RAISE
> update cart set at = now() where cart_id = 0;
UPDATE 1

The WHEN clause was suggested by Belayer in his answer . Belayer在他的回答中建议使用WHEN子句。 The full explanation is in my research .完整的解释在我的研究中。 Additionally I examined the approach with playing with privileges.此外,我还研究了使用特权的方法 NOTE: Some people say that triggers like here are performance killers.注意:有人说这里的触发器是性能杀手。 They are wrong.他们错了。 How do you think postgres implements constraints internally?你认为postgres如何在内部实现约束? — Using implicit triggers like defined here. — 使用此处定义的隐式触发器。

TL;DR长话短说

What did I try?我尝试了什么? Revoking UPDATE privilege doesn't work.撤销UPDATE权限不起作用。

# \c danissimo danissimo
You are now connected to database "danissimo" as user "danissimo".

> revoke update (customer_id) on customer from danissimo;
REVOKE
> insert into customer (customer_id) values (0);
INSERT 0 1
> update customer set customer_id = 0 where customer_id = 0;
UPDATE 1
> update customer set customer_id = -1 where customer_id = 0;
UPDATE 1

Okay, let's put a guard on it.好吧,让我们保护它。

create or replace function customer_id_guard() returns trigger
language plpgsql as $body$
begin
  if old.customer_id != new.customer_id then
    raise exception
      'trigger %: updating is prohibited for %',
      tg_name, 'customer_id' using
      errcode = 'restrict_violation';
  end if;
  return new;
end;
$body$;

create or replace trigger customer_id_guard
after update on customer for each row
execute function customer_id_guard();

Now let's give them some work.现在让我们给他们一些工作。

> update customer set customer_id = -1 where customer_id = -1;
UPDATE 1

Right, I didn't change the value.对,我没有改变价值。 What about this:那这个呢:

> update customer set customer_id = 0 where customer_id = -1;
ERROR:  trigger customer_id_guard: updating is prohibited for customer_id
CONTEXT:  PL/pgSQL function customer_id_guard() line 4 at RAISE

Yeah, here it goes.是的,就是这样。 Good, let's protect order_id as well.好,让我们也保护order_id I don't want to copy–paste trigger functions, so I let's try to generalize it:我不想复制粘贴触发函数,所以我试着概括一下:

create or replace function generated_id_guard() returns trigger
language plpgsql as $body$
declare
  id_col_name text := tg_argv[0];
  equal boolean;
begin
  execute format('old.%1$I = new.%1$I', id_col_name) into equal;
  if not equal then
    raise exception
      'trigger %: updating is prohibited for %',
      tg_name, id_col_name using
      errcode = 'restrict_violation';
  end if;
  return new;
end;
$body$;

create or replace trigger cart_id_guard
after update on cart for each row
execute function generated_id_guard('cart_id');

As you might notice I pass the column name to the trigger function and generate an expression and put the result of that expression into equal which then test.正如您可能注意到的那样,我将列名传递给触发器 function 并生成一个表达式并将该表达式的结果放入equal中,然后进行测试。

> insert into cart (cart_id) values (0);
INSERT 0 1
> update cart set cart_id = 0 where cart_id = 0;
ERROR:  syntax error at or near "old"
LINE 1: old.cart_id = new.cart_id
        ^
QUERY:  old.cart_id = new.cart_id
CONTEXT:  PL/pgSQL function generated_id_guard() line 6 at EXECUTE

Hmmm... He's right, what the dangling old.cart_id = new.cart_id ?嗯...他是对的,悬空的old.cart_id = new.cart_id是什么? What if I write如果我写怎么办

execute format('select old.%1$I = new.%1$I', id_col_name) into equal;

> update cart set cart_id = 0 where cart_id = 0;
ERROR:  missing FROM-clause entry for table "old"
LINE 1: select old.cart_id = new.cart_id
               ^
QUERY:  select old.cart_id = new.cart_id
CONTEXT:  PL/pgSQL function generated_id_guard() line 6 at EXECUTE

Right, right... What if I write对对对...如果我写呢

declare
  id_old int;
  id_new int;
begin
  execute format('select %I from old', id_col_name) into id_old;
  execute format('select %I from new', id_col_name) into id_new;
  if id_old != id_new then

> update cart set cart_id = 0 where cart_id = 0;
ERROR:  relation "old" does not exist
LINE 1: select cart_id from old
                            ^
QUERY:  select cart_id from old
CONTEXT:  PL/pgSQL function generated_id_guard() line 7 at EXECUTE

Aha, «relation "old" does not exist»...啊哈,«关系“旧”不存在»...

Well, here's the last resort:好吧,这是最后的手段:

drop table cart;
create table cart (
  cart_id int generated by default as identity (start with 100) primary key,
  at timestamp with time zone
);
insert into cart (cart_id) values (0);

create or replace function surrogate_id_guard() returns trigger
language plpgsql immutable parallel safe cost 1 as $body$
begin
  raise exception
    'trigger %: updating is prohibited for %',
    tg_name, tg_argv[0] using
    errcode = 'restrict_violation';
  return null;
end;
$body$;

create or replace trigger cart_id_guard
before update of cart_id on cart for each row
execute function surrogate_id_guard('cart_id');

I just make it trigger on any attempt to update cart_id .我只是让它在任何更新cart_id的尝试时触发。 Let's check:让我们检查:

> update cart set cart_id = 0 where cart_id = 0;
ERROR:  trigger cart_id_guard: updating is prohibited for cart_id
CONTEXT:  PL/pgSQL function surrogate_id_guard() line 3 at RAISE
> update cart set at = now() where cart_id = 0;
UPDATE 1

Well, finally I answered my original question at this point.好吧,我终于在这一点上回答了我原来的问题。 But another question is still arisen: How to apply the same algorithm encoded in a function to columns given in args to that function?但另一个问题仍然出现:如何将在 function 中编码的相同算法应用于 args 中给定的列 function?

If I understand correctly you want to prevent any user from modifying the the table id once it is established and to have a generic function produce the exception, while still allowing other updates.如果我理解正确的话,您希望防止任何用户在表 ID 建立后对其进行修改,并让通用 function 产生异常,同时仍允许其他更新。 You can accomplish this this be modifying the trigger rather than the function. Specify the WHEN predicate on the trigger itself.您可以通过修改触发器而不是 function 来完成此操作。在触发器本身上指定 WHEN 谓词。 For the cart table then:那么对于cart表:

create or replace trigger cart_id_guard
   before update of cart_id 
       on cart for each row
          when (old.cart_id is distinct from new.cart_id)
       execute function surrogate_id_guard('cart_id');

The for the customer table the trigger becomes:对于customer表,触发器变为:

create or replace trigger customer_id_guard
   before update of customer_id 
       on customer for each row
     when (old.customer_id is distinct from new.customer_id)
  execute function surrogate_id_guard('customer_id');

The trigger function itself does not change.触发器 function 本身并没有改变。 ( demo here ) 这里演示

The very first attempt in my previous research was to revoke privileges.在我之前的研究中,第一次尝试是撤销特权。 As Laurenz Albe pointed in his comment I had to revoke the privilege to update the whole table instead of revoking the privilege to update a certain column.正如Laurenz Albe在他的评论中指出的那样,我不得不撤销更新整个表的权限,而不是撤销更新特定列的权限。 Here's the code:这是代码:

# \c danissimo danissimo
You are now connected to database "danissimo" as user "danissimo".

create table cart (
  cart_id int generated by default as identity (start with 100) primary key,
  at timestamp with time zone default now()
);
insert into cart default values;

revoke update on cart from danissimo;

Can I update the table now?我现在可以更新表格吗?

> update cart set at = at - interval '1 day';
ERROR:  permission denied for table cart

Okay, let's grant the privilege to update columns other than cart_id :好的,让我们授予更新cart_id以外的列的权限:

> grant update (at) on cart to danissimo;
> update cart set at = at - interval '1 day';
UPDATE 1

So far, so good.到目前为止,一切都很好。 Now time ticks and eventually danissimo adds another column item_ids :现在时间流逝,最终 danissimo 添加了另一列item_ids

alter table cart add column item_ids int[];

Can danissimo update the new column now? danissimo 现在可以更新新专栏吗? Keep in mind that the privilege to update the whole table was revoked from him and the privilege to update the new column was not granted:请记住,更新整个表的权限已被取消,并且未授予更新新列的权限:

> update cart set item_ids = array[1, 3, 7 ,5];
ERROR:  permission denied for table cart

And if I grant him the privilege?如果我授予他特权?

> grant update (item_ids) on cart to danissimo;
> update cart set item_ids = array[1, 3, 7 ,5];
UPDATE 1

What does that all mean?这一切意味着什么? I considered two approaches.我考虑了两种方法。 One is to prohibit updates of a column once a value is given to the column.一种是在给列赋值后禁止更新该列。 Another is to play with privileges.还有一个就是玩特权。 In our projects it's usual that we add new columns while the projects evolve.在我们的项目中,我们通常会在项目发展时添加新列。 If I stick with privileges I have to grant the privilege to update the new column each time I add a new one.如果我坚持使用特权,则每次添加新列时都必须授予更新新列的特权。 On the other hand, if I protect some columns with a trigger I just add new columns and bother no more.另一方面,如果我用触发器保护某些列,我只需要添加新列就可以了。

CONCLUSION: Use triggers as shown above.结论:如上所示使用触发器。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM