I have multiple tables in a PostgreSQL 9.6 database which changes I want to monitor and handle in an extern application.
Handling only data changes wasn't hard, but now I want to monitor structure changes of my database to be stored. This is what I have:
CREATE OR REPLACE FUNCTION log_structureChanged()
RETURNS event_trigger AS $$
BEGIN
UPDATE dbchanged SET changed=2 WHERE table_name = TG_ARGV[0];
RETURN NEW;
END;
$$ LANGUAGE PLPGSQL;
CREATE EVENT TRIGGER testData_struc
ON ddl_command_end
WHEN TAG IN ( 'CREATE TABLE', 'ALTER TABLE', 'DROP TABLE' )
EXECUTE PROCEDURE log_structureChanged();
When the EXECUTE PROCEDURE gets called, I want to parse the table that the changes have been made to. The official PostgreSQL documentation didn't really help me - I also may have not fully understood some parts.
So how do I parse the table on that the EVENT TRIGGER fired on? Is it stored inside a variable?
As described in the documentation, you can call the function pg_event_trigger_ddl_commands(), which will return one row per affected object.
You can either use the result column object_identity which contains a textual description of the affected object, or classid and objid, which contain the object ID of the catalog table that contains the object and the object ID of the affected object.
Related
I am trying to understand how transaction works in Postgres and what happens when multiple commands try to work on the same table. My doubt is related to a small experiment that I carried out.
Consider a table called experiment with a trigger (experiment_log) on it that is fired after every update, delete, or insert.
Now consider this function.
CREATE OR REPLACE FUNCTION test_func() RETURNS void AS $body$
DECLARE
_q_txt text;
version_var integer;
BEGIN
EXECUTE 'DROP TRIGGER IF EXISTS experiment_log ON experiment';
SELECT version INTO version_var FROM experiment;
RAISE NOTICE 'VERSION AFTER DROPPING TRIGGER: %', version_var;
EXECUTE 'SELECT pg_sleep(20);';
SELECT version INTO version_var FROM experiment;
RAISE NOTICE 'VERSION BEFORE RECREATING TRIGGER: %', version_var;
EXECUTE 'CREATE TRIGGER experiment_log AFTER INSERT OR UPDATE OR DELETE ON experiment FOR EACH ROW EXECUTE PROCEDURE experiment_log_trigger_func();';
END;
$body$
language 'plpgsql';
So, this function drops the trigger and waits for 20 seconds before re-creating this trigger. Now any update operation performed during the time when function is sleeping, the update operation blocks. It means that I can not update the experiment table until the function test_func has executed completely.
Can anyone explain this behaviour? It seems I am missing something out to reason this behaviour.
That is because DROP TRIGGER places an ACCESS EXCLUSIVE lock on the table, and the lock is held until the transaction ends, that is, for the whole duration of the function call.
If you want to disable a trigger temporarily, use
ALTER TABLE experiment DISABLE TRIGGER experiment_log;
I would like to give you a reference from the documentation, but the lock level of DROP TRIGGER is not documented. However, it is documented that the SQL statement takes the lock:
Also, most PostgreSQL commands automatically acquire locks of appropriate modes to ensure that referenced tables are not dropped or modified in incompatible ways while the command executes.
There you can also find how long a lock is held:
Once acquired, a lock is normally held until the end of the transaction.
To find the lock taken by DROP TRIGGER, try this simple experiment:
CREATE TABLE t();
CREATE TRIGGER whatever BEFORE UPDATE ON t
FOR EACH ROW EXECUTE FUNCTION suppress_redundant_updates_trigger();
BEGIN; -- start a transaction
DROP TRIGGER whatever ON t;
SELECT mode FROM pg_locks
WHERE pid = pg_backend_pid() -- only locks for the current session
AND relation = 't'::regclass; -- only locks on "t"
mode
═════════════════════
AccessShareLock
AccessExclusiveLock
(2 rows)
COMMIT;
You see that an ACCESS SHARE lock and an ACCESS EXCLUSIVE lock are held on the table.
My goal is to create a schema, based on the uuid of a user.
I've therfor wrote a function that gets executed every time a user is created and confirmed.
As the documentation says PostgreSQL Documentation
i may have to write another function that creates a schema because of compatibility.
the 'create_user_schema()' function works in a new query but seems to not work if used in my trigger function. I've tried a lot with casting the uuid to a string but it still don't work.
Did i do something wrong, has this something to do with security and won't work in any case?
CREATE OR REPLACE FUNCTION user_setup() RETURNS trigger AS $user_setup$
DECLARE
s_name uuid := NEW.id;
BEGIN
-- cutout content that works so far
SELECT create_user_schema(CAST(s_name AS TEXT));
RETURN NULL;
END;
$user_setup$ LANGUAGE plpgsql;
CREATE TRIGGER user_setup AFTER INSERT OR UPDATE ON auth.users
FOR EACH ROW EXECUTE FUNCTION user_setup();
CREATE OR REPLACE FUNCTION create_user_schema(s_name text) RETURNS void AS $$
BEGIN
EXECUTE 'CREATE SCHEMA ' || quote_ident(s_name);
END;
$$ LANGUAGE plpgsql;
Well it wasn't really a bug and more some missing piece of information in "how postgres works".
It was necessary to add a 'Security Definer' keyword to the function to let the trigger have the correct privilges on execution.
I'm working on a script for school right now and I've encountered a weird error that I can't fix.
So I've got a playlist, tracks and a playlistInput table.
My task is to create a trigger that if I add/delete a track to my playlist, the amountOfTracks variable should be increased/decreased.
So now to the weird error:
I've made a function "playlistInputAdd" which already works when doing "select playlistInputAdd(1,1)".
The thing is that if I create my trigger, it says that this function doesn't exist. I've seen that someone asked the same question but he didn't add the parameters and I actually did.
The playlistInputAdd function:
CREATE FUNCTION playlistInputAdd(ID int, Amount int)
RETURNS VOID
AS $$
BEGIN
UPDATE playlist
SET amountOfTracks = amountOfTracks + Amount
WHERE playListID = ID;
END;
$$ LANGUAGE plpgsql;
The playlistInputAdd_trigger trigger:
CREATE TRIGGER playlistInputAdd_trigger
AFTER INSERT
ON playlistInput
FOR EACH ROW
EXECUTE PROCEDURE playlistInputAdd(playlistID, 1);
The playlistInputDelete_trigger trigger:
CREATE TRIGGER playlistInputDelete_trigger
AFTER INSERT
ON playlistInput
FOR EACH ROW
EXECUTE PROCEDURE playlistInputAdd(playlistID, 1);
The error message:
ERROR: function playlistinputadd() does not exist
Thank you, in advance!
Trigger functions must be defined with empty argument list and with RETURNS trigger. It is the argument list that causes the error you get.
You can pass arguments to the function in CREATE TRIGGER (even though the argument list has to be empty), but these have to be constants and can be accessed via TG_ARGV in the function body.
You don't need to pass the columns of the modified table as arguments: the row being inserted is available in the NEW variable in the trigger body.
See the documentation for more.
New to Postgres and PL/pgSQL here.
How do I go about writing a PL/pgSQL function to perform different actions based on the type of update (insert,delete,etc) made to the table/record in a postgres database.
You seem to be looking for a trigger.
In SQL, triggers are procedures that are called (fired) when a specific event happens on an object, for example when a table is updated, deleted from or insterted into. Triggers can respond to many use cases such as implementing business integrity rules, cleaning data, auditing, security, ...
In Postgres, you should first define a PL/pgSQL function, and then reference it in the trigger declaration.
CREATE OR REPLACE FUNCTION my_table_function() RETURNS TRIGGER AS $my_table_trigger$
BEGIN
...
END
$my_table_trigger$ LANGUAGE plpgsql;
CREATE TRIGGER my_table_trigger
AFTER INSERT OR UPDATE OR DELETE ON mytable
FOR EACH ROW EXECUTE PROCEDURE my_table_function();
From within the trigger code, you have access a set of special variables such as :
NEW, OLD : pseudo records that contain new/old database records affected by the query
TG_OP : operation that fired the trigger (INSERT, UPDATE, DELETE, ...)
Using these variables and other triggers mechanisms, you can analyze or alter the on-going operation, or even abort it by raising an exception.
I would recommend reading Postgres documentation for the CREATE TRIGGER statement and Trigger Procedure (the latest gives lots of examples).
Hey, I'm trying to create a trigger in my Oracle database that changes all other records except the one that has just been changed and launched the trigger to 0. Because I am updating records in the same table as the one that launched the trigger I got the mutating table error. To solve this, I put the code as an anonymous transaction, however this causes a deadlock.
Trigger code:
CREATE OR REPLACE TRIGGER check_thumbnail AFTER INSERT OR UPDATE OF thumbnail ON photograph
FOR EACH ROW
BEGIN
IF :new.thumbnail = 1 THEN
check_thumbnail_set_others(:new.url);
END IF;
END;
Procedure code:
CREATE OR REPLACE PROCEDURE check_thumbnail_set_others(p_url IN VARCHAR2)
IS PRAGMA AUTONOMOUS_TRANSACTION;
BEGIN
UPDATE photograph SET thumbnail = 0 WHERE url <> p_url;
COMMIT;
END;
I assume I'm causing a deadlock because the trigger is launching itself within itself. Any ideas?
Using an autonomous transaction for this sort of thing is almost certainly a mistake. What happens if the transaction that inserted the new thumbnail needs to rollback? You've already committed the change to the other rows in the table.
If you want the data to be transactionally consistent, you would need multiple triggers and some way of storing state. The simplest option would be to create a package with a collection of thumbnail.url%type then create three triggers on the table. A before statement trigger would clear out the collection. A row-level trigger would insert the :new.url value into the collection. An after statement trigger would then read the values from the collection and call the check_thumbnail_set_others procedure (which would not be an autonomous transaction).