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. How?
UPD: While I was writing the question I found the answer to my original question. 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. 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? — Using implicit triggers like defined here.
TL;DR
What did I try? Revoking
UPDATEprivilege doesn't work.Okay, let's put a guard on it.
Now let's give them some work.
Right, I didn't change the value. What about this:
Yeah, here it goes. Good, let's protect
cart_idas well. I don't want to copy–paste trigger functions, so I let's try to generalize it: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
equalwhich then test.Hmmm... He's right, what the dangling
old.cart_id = new.cart_id? What if I writeRight, right... What if I write
Aha, «relation "old" does not exist»...
Well, here's the last resort:
I just make it trigger on any attempt to update
cart_id. Let's check: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?