prevent any update or delete from table - sql

I have a table where I added a boolean column done
When this column is True I want to prevent any unautorized action (delete, update) on the row.
I wrote this trigger:
CREATE TRIGGER productstock_beforeupdate
BEFORE UPDATE OR DELETE
ON table
FOR EACH ROW
EXECUTE PROCEDURE trig()
CREATE OR REPLACE FUNCTION trig()
RETURNS trigger AS
$$
begin
if TG_OP='UPDATE' or TG_OP='DELETE' then
if old.done=true then
raise exception 'cant do that';
return null;
end if;
end if;
return new;
end;
$$
If autorized transaction wants to change data it will have first to disable this trigger.
This idea should work however when I try to delete a row with
done=False
it returns me "0 rows deleted" and does not perform the delete.
Any idea what is wrong with my trigger?

This is caused by the fact that for an UPDATE to go through you need to return the NEW record. However for a DELETE to go through, you need to return the OLDrecord.
Quote from the manual
Thus, if the trigger function wants the triggering action to succeed normally without altering the row value, NEW (or a value equal thereto) has to be returned [...] Note that NEW is null in DELETE triggers, so returning that is usually not sensible. The usual idiom in DELETE triggers is to return OLD.
So your trigger function should look like this:
CREATE OR REPLACE FUNCTION trig()
RETURNS trigger AS
$$
begin
-- no need to check for the action here as the trigger
-- will only fire for UPDATE or DELETE
if old.done then
raise exception 'cant do that';
return null;
end if;
if TG_OP='UPDATE' then
return new;
end if;
if TG_OP='DELETE' then
return old;
end if;
end;
$$
language plpgsql;

When the trigger allows DELETE it should return old. In case of UPDATE it returns new.
If it raises an exception it do not need to return anything.
create or replace function trig()
returns trigger language plpgsql as
$$
begin
if old.done then
raise exception 'cant do that';
end if;
if tg_op = 'DELETE' then
return old;
else
return new;
end if;
end $$;

Related

IF condition evaluation in AFTER TRIGGER function on PostgreSQL

I'm writing a plpgsql function in PostgreSQL (version 10) which is called by a TRIGGER after an UPDATE
CREATE TRIGGER trigger_1
AFTER UPDATE
ON entities
FOR EACH ROW
WHEN ( condition_on_new_and_old )
EXECUTE PROCEDURE function_1();
entities is a table which has a column data of type JSONB.
The code of function_1 is essentially (I modified the code to isolate the RAISE in the IF, originally the condition was the inverse):
CREATE OR REPLACE FUNCTION function_1() RETURNS TRIGGER AS
$$
BEGIN
IF (
TG_OP <> 'UPDATE'
OR TG_WHEN <> 'AFTER'
OR NOT (NEW.data ? 'XXX')
OR NOT (OLD.data ? 'XXX')
OR NOT (NEW.object_id = OLD.object_id)
OR NOT (NEW.workspace = OLD.workspace)) THEN
RAISE EXCEPTION 'XXX not found';
END IF;
-- SOME INSERTs
-- SOME DELETEs
RETURN NULL;
END
$$ LANGUAGE plpgsql;
As everyone can expect if the condition in the IF is true we raise the exception. The problem is the RAISE is always executed despite the value of the condition. In all my tests the values of OLD and NEW have always been the same.
Even more surprising was the fact that if I did something like
CREATE OR REPLACE FUNCTION function_1() RETURNS TRIGGER AS
$$
BEGIN
IF (
TG_OP <> 'UPDATE'
OR TG_WHEN <> 'AFTER'
OR NOT (NEW.data ? 'XXX')
OR NOT (OLD.data ? 'XXX')
OR NOT (NEW.object_id = OLD.object_id)
OR NOT (NEW.workspace = OLD.workspace)) THEN
RAISE EXCEPTION 'XXX not found';
END IF;
RAISE EXCEPTION 'TEST'
-- SOME INSERTs
-- SOME DELETEs
RETURN NULL;
END
$$ LANGUAGE plpgsql;
I had the exception 'TEST' raised but if I wrote:
CREATE OR REPLACE FUNCTION function_1() RETURNS TRIGGER AS
$$
BEGIN
IF (
TG_OP <> 'UPDATE'
OR TG_WHEN <> 'AFTER'
OR NOT (NEW.data ? 'XXX')
OR NOT (OLD.data ? 'XXX')
OR NOT (NEW.object_id = OLD.object_id)
OR NOT (NEW.workspace = OLD.workspace)) THEN
RAISE EXCEPTION 'XXX not found';
END IF;
-- RAISE EXCEPTION 'TEST' (I commented the RAISE)
-- SOME INSERTs
-- SOME DELETEs
RETURN NULL;
END
$$ LANGUAGE plpgsql;
I had the exception 'XXX not found' thrown.
To notice the trigger used to be on BEFORE UPDATE and it did work as expected, the problem arrived when we set it to AFTER.
I'm quite sure I'm missing something about how AFTER triggers behave. Do you have any ideas?
Thank you all in advance
Thanks for the comments on the question. I'm sorry but I didn't notice in the logs that there was actually a problem in the WHEN condition of the TRIGGER.
After fixing it it's working.

PostgreSQL trigger not executing after update [duplicate]

Here is my trigger function
CREATE OR REPLACE FUNCTION test_table_insert()
RETURNS TRIGGER
AS $$
BEGIN
IF NEW.id IS NULL THEN
RAISE EXCEPTION 'id is null';
END IF;
UPDATE e_sub_agreement SET ro_id = NEW.id WHERE id = NEW.id;
RETURN NEW;
END;
$$
LANGUAGE plpgsql;
CREATE TRIGGER test_table_insert AFTER INSERT ON e_sub_agreement FOR EACH ROW EXECUTE PROCEDURE test_table_insert();
The problem is that it doesn't update table e_sub_agreement. I checked NEW value and everything is good. It returns with the new id. If I change where statement id = "some existing id in table", then it works. It changes ro_id to the new id. How is it possible? My guess is that data has not been inserted into table and trigger function can't find row with the given id. But it's not how trigger's after insert function works. What's the magic?
An AFTER trigger can not change anything. Running an additional UPDATE is also quite inefficient. Change this to a BEFORE trigger and assign the value you want:
CREATE OR REPLACE FUNCTION test_table_insert()
RETURNS TRIGGER
AS $$
BEGIN
IF NEW.id IS NULL THEN
RAISE EXCEPTION 'id is null';
END IF;
NEW.ro_id := NEW.id;
RETURN NEW;
END;
$$
LANGUAGE plpgsql;
CREATE TRIGGER test_table_insert
BEFORE INSERT ON e_sub_agreement
FOR EACH ROW EXECUTE PROCEDURE test_table_insert();
Note that the NOT NULL check is better done by defining the column as NOT NULL.

How to check for value of field in a trigger

I'm trying to check for a field missing during an UPDATE statement. I was thinking something like this
CREATE OR REPLACE FUNCTION UPDATED_BY_TRIGGER()
RETURNS TRIGGER AS
$BODY$
BEGIN
IF NEW."updated_by" IS NULL THEN
RAISE EXCEPTION 'updated_by is missing in UPDATE to %', TG_TABLE_NAME;
END IF;
RETURN NEW;
END;
$BODY$ LANGUAGE PLPGSQL;
Here's the catch: the value in the DB for the field is already not NULL (because it has a NOT NULL constraint), so I can't use NEW."updated_by". I am able to run UPDATE statements that don't change the value of updated_by right now. I want to raise an exception when this happens.

Conditionally drop an insert in a before insert trigger without returning error

I have the following function in a before insert trigger:
CREATE OR REPLACE FUNCTION schema.table_somefun()
RETURNS trigger AS
LANGUAGE 'plpgsql';
$BODY$
BEGIN
IF NEW.col2 NOT NULL THEN
NEW.col1 := CASE NEW.col1
WHEN '121432' THEN '321123'
ELSE <command> END CASE; --there should be a command aborting insertion without error or exception
END IF;
RETURN NEW;
END;
$BODY$
The ELSE statement should abort insertion. Is there a command which drops the query without telling it to the client and leaving the table untouched?
Just use
RETURN NULL;
instead of
RETURN NEW;
to cancel the INSERT for the row and do nothing instead.
But you cannot execute a PL/pgSQL statement inside an SQL CASE expression. (Don't confuse SQL CASE with the similar control structure CASE of PL/pgSQL!)
Could look like this:
CREATE OR REPLACE FUNCTION schema.table_somefun()
RETURNS trigger AS
$func$
BEGIN
IF NEW.col2 NOT NULL THEN
IF NEW.col1 = '121432' THEN -- could also be plpgsql CASE ...
NEW.col1 := '321123';
ELSE
RETURN NULL;
END IF;
END IF;
RETURN NEW;
END
$func$ LANGUAGE plpgsql;

How to get the event name from pg_notify

I'm getting the event(delete or create) from pg_notify with python using pg_channels but I need to put an if to check if the trigged event is a delete or create, than I can apply a rule but I don't know how to get the event name.
Thanks for your help guys.
pcg = pg_channels.connect(host='', database='', user='', password='', port='5432')
# listening an event
pcg.listen('xgracco')
#loop to watch events from posgres pg_notify
for event in pcg.events():
info = json.loads(event.payload)
my function and trigger
create or replace function public.notify() returns
trigger as $BODY$
begin
if new.tp_status = 'ERRO' then
perform pg_notify('xgracco', row_to_json(NEW)::text);
end if;
return new;
end
$BODY$
language 'plpgsql';
create trigger after_insert
after insert or update
on "tb_fila"
for each row
execute procedure public.notify()
create or replace function public.notify_delete() returns
trigger as $BODY$
begin
if old.tp_status = 'ERRO' then
perform pg_notify('xgracco', row_to_json(OLD)::text);
end if;
return old;
end
$BODY$
language 'plpgsql';
create trigger before_delete_xgracco
before delete
on "tb_fila"
for each row
execute procedure public.notify_delete()