Enabling and Disabling a trigger in a Postgres function - postgresql

I have a trigger on a table and I want to catch the messages that are introduced between an interval. I had created a function who recive an integer() as a parameter.
The pseudocode is something like this:
alter table <table_name> enable trigger <trigger_name>;
PERFORM pg_sleep(<nrofseconds>);
alter table <table_name> disable trigger <trigger_name>;
The problem is that, a Postgres function works as a single transaction and the trigger is enabled, then disabled at the end of execution, when commit is called so it does not track anything.
How can I solve this problem?

Related

How do I create a temporary trigger in Postgres? [duplicate]

This question already has an answer here:
Drop trigger/function at end of session in PostgreSQL?
(1 answer)
Closed last month.
I'm trying to create a system in Postgres where each client can create its own subscriptions via listen + notify + triggers. I.e. the client will listen to a channel, and then create a trigger which runs notify on that channel when its conditions have been met. The issue is that I want Postgres to clean up properly in case of improper client termination (e.g. the remote client process dies). To be more specific, I want that trigger which is calling the notify to be removed as there is no longer a listener anyways. How can I accomplish this?
I've thought about having a table to map triggers to client ids and then using that to remove triggers where the client is gone, but it seems like a not so great solution.
I found an answer to this in another question: Drop trigger/function at end of session in PostgreSQL?
In reasonably recent Postgres versions you can create a function in
pg_temp schema:
create function pg_temp.get_true() returns boolean language sql as $$ select true; $$;
select pg_temp.get_true();
This is the schema in which temporary tables are created. All its
contents, including your function, will be deleted on end of session.
You can also create triggers using temporary functions on tables. I've
just tested this and it works as expected:
create function pg_temp.ignore_writes() returns trigger language plpgsql as $$
begin
return NULL;
end;
$$;
create table test (id int);
create trigger test_ignore_writes
before insert, update, delete on test
for each row execute procedure pg_temp.ignore_writes();
Because this trigger function always returns NULL and is before [event] it should make any writes to this table to be ignored. And
indeed:
insert into test values(1);
select count(*) from test;
count
-------
0
But after logout and login this function and the trigger would not be
present anymore, so writes would work:
insert into test values(1);
select count(*) from test;
count
-------
1
But you should be aware that this is somewhat hackish — not often used
and might not be very thoroughly tested.
That's not how it works. CREATE TRIGGER requires that you either own the table or have the TRIGGER privilege on it (which nobody in their right mind will give you, because it enables you to run arbitrary code in their name). Moreover, CREATE TRIGGER requires an SHARE ROW EXCLUSIVE lock on the table and DROP TRIGGER requires an ACCESS EXCLUSIVE lock, which can be disruptive.
Create a single trigger and keep that around.

Postgres concurrent transactions unexpected issue

When the following transaction is run concurrently on different connections it sometimes errors with
trigger "my_trigger" for relation "my_table" already exists
What am I doing wrong?
BEGIN;
DROP TRIGGER IF EXISTS my_trigger ON my_table;
CREATE TRIGGER my_trigger
AFTER INSERT ON my_table
REFERENCING NEW TABLE AS new_table
FOR EACH STATEMENT EXECUTE PROCEDURE my_function();
COMMIT;
I am trying to set up a system where I can add triggers to notify about data changes in specific tables. If a table already has such a trigger then skip it. Otherwise CREATE all CRUD triggers. This logic needs to run sequentially in case of concurrent requests.
After trying ISOLATION LEVEL SERIALIZABLE I noticed that any conflicting transactions are failed and dropped (I would need to manually check sql status and retry). But what I want is to queue up these transactions and run afterwards one by one in the order they're sent.
At the moment I am trying to achieve this by having a my_triggers (table_name TEXT) table that has a BEFORE INSERT OR DELETE trigger. Within this trigger I do the actual table trigger upsert logic. Inserts or deletes on my_triggers are made with LOCK TABLE my_triggers IN ACCESS EXCLUSIVE MODE ... which should queue up conflicting CRUD transactions ?!
What happens is following:
BEGIN....DROP TRIGGER IF EXISTS....CREATE TRIGGER....COMMIT;
..BEGIN....DROP TRIGGER IF EXISTS....CREATE TRIGGER--------EXCEPTION.
Both transactions starts when trigger is not present.
Both succeed in drop trigger because of "IF EXISTS" statement.
First transaction starts creating a trigger. For that a SHARE ROW EXCLUSIVE lock is placed on table my_table. The lock SHARE ROW EXCLUSIVE conflicts with it self so no other transaction is allowed to create a trigger until the first one completes.
Second transaction blocks on CREATE TRIGGER.
First transaction completes.
Second transaction proceeds with CREATE TRIGGER but it already exists. Exception is raised.
What you need is adding a LOCK before DROP TRIGGER statement. This way you will ensure the trigger is dropped and not created in concurrent transaction.
BEGIN;
LOCK TABLE my_table IN SHARE ROW EXCLUSIVE MODE ;
DROP TRIGGER IF EXISTS my_trigger ON my_table;
CREATE TRIGGER my_trigger
AFTER INSERT ON my_table
REFERENCING NEW TABLE AS new_table
FOR EACH STATEMENT EXECUTE PROCEDURE my_function();
COMMIT;

How do I change the owner of an event trigger in postgresql?

The documentation states that the command for altering the owner of an event trigger is:
ALTER EVENT TRIGGER name OWNER TO { new_owner | CURRENT_USER | SESSION_USER }
But it errors with:
[42704] ERROR: event trigger "insert_component_relationship" does not exist
I thought that meant I needed to qualify it with the schema name so I tried:
ALTER EVENT TRIGGER schema.name OWNER TO { new_owner }
but that screamed at me as well with:
[42601] ERROR: syntax error at or near "."
Then I just started trying other combinations of commands I thought would work, ex:
ALTER TRIGGER name ON table OWNER TO { new_owner }
ALTER TRIGGER name ON schema.table OWNER TO { new_owner }
To no avail. Under the name parameter on this page seems to suggest that the schema is inferred when it's created but makes no mention of schema qualifying when trying to alter the trigger.
EDIT Actual command run with pseudo values:
ALTER EVENT TRIGGER modify_relationship OWNER TO new_admin_user;
ALTER EVENT TRIGGER products.modify_relationship OWNER TO new_admin_user;
Relatively minor mistake.
Make sure the trigger is an actual event trigger.
Check with the meta-command \dy. If it doesn't show up here, it's probably not an event trigger.
The CREATE TRIGGER statement in PostgreSQL implements a subset of the SQL standard. The following functionalities are currently missing:
Triggers are defined by the SQL standard and there is no mention of ownership on the documentation for them
Altering the ownership of an event trigger is given as a option in the docs for ALTER EVENT TRIGGER
There is no CREATE EVENT TRIGGER statement in the SQL standard.
Thanks to #eurotrash for the comment that led me to the distinction.

INSERT statement that does not fire an INSERT trigger

I am using PostgreSQL 9.2 and I need to write an INSERT statement which copies data from table A to table B without firing the INSERT trigger defined on table B (maybe some sort of bulk insertion operation??).
On this specific table (table B) many INSERT, UPDATE and DELETE operations are executed. During each and every one of this executions, a trigger must fire.
I cannot temporary disable the triggers because of standard, day-to-day DML operations.
Can anyone help me with the syntax for this non-trigger-firing INSERT statement?
Run your "privileged" inserts as a different user. That way your trigger can check the current user and exit if it shouldn't do anything.

how to stop/fire trigger in stored procedure dynamically?

Suppose I have a table MyTab in database. I have a trigger, for example, delete trigger on this table.
Then in a stored procedure, I try to delete data from this table but want to stop the delete trigger only for this deletion. After that, put the trigger back on normal. Is it possible to have codes in stored procedure like:
stop trigger on MyTab;
delete from MyTab where ...;
put the trigger back;
As mentioned here, you can disable and enable the trigger, though I'd probably put this in a try-catch and/or transaction so you don't get stuck with your trigger disabled because of an error.
For example:
set xact_abort on; -- Auto-rollback on any error
begin transaction;
alter table MyTab disable trigger TR_MyTab_Delete;
delete from MyTab where 1/0 = 1; -- Causes div by zero error
alter table MyTab enable trigger TR_MyTab_Delete; -- Won't run becuase of above error
commit;
The script above will throw an error but won't leave the trigger disabled, as set xact_abort on guarantees that my transaction is rolled back, including the disabling of the trigger.