If I set up an AFTER trigger in PostgreSQL to fire after an insert/update, will the calling software have to wait for the trigger to finish before returning control to the calling software? Or will the trigger run on its own behind the scenes?
Yes, because it's executed within the same transaction. If the trigger fails, the insert/update will also fail. Just do a test executing a query that will fail (SELECT a table that does not exist) and you can see how things work and how your application will behave.
CREATE OR REPLACE FUNCTION foo() RETURNS TRIGGER
AS
$$
BEGIN
EXECUTE 'SELECT fail';
END;
$$
LANGUAGE plpgsql;
Related
I'm new to Postgres, but with experience from Oracle. Trying to create a stored procedure which is going to:
Insert a row
Handle exceptions and in case of an exception insert a row into a log table by calling dedicated procedure
Emit an audit log record into a log table in case the whole procedure ran successfully
By pseudo code:
CREATE OR REPLACE PROCEDURE test.p_insert(IN p_test_param character varying)
LANGUAGE 'plpgsql'
SECURITY DEFINER
AS $BODY$
DECLARE
-- some declarations
BEGIN
BEGIN
INSERT INTO test.a(a) VALUES (p_test_param);
EXCEPTION
WHEN OTHERS THEN
-- GET STACKED DIAGNOSTICS
CALL test.p_insert_log(...); -- Inserts a row into a log table, another COMMIT may be required?
RAISE;
END;
COMMIT; -- CAN'T DO
BEGIN
IF (SELECT test.f_debug()) THEN
CALL test.p_insert_log(...); -- Audit the execution
END IF;
END;
COMMIT; -- CAN'T DO EITHER
END;
$$BODY$$;
However when I try to test the procedure out from an anonymous block in PgAdmin such as:
BEGIN;
DO
LANGUAGE plpgsql
$$
BEGIN
CALL test.p_insert(
p_test_param => 'test'
);
END;
$$
I'm getting an error ERROR: invalid transaction termination. How can I get rid of it? My objective is to let the procedure carry out the transaction control, I don't want the caller to COMMIT or ROLLBACK anything. If I remove both COMMIT commands from the code of the procedure, it executes well, however the invoker must explicitly COMMIT or REVOKE the transaction afterwards, which is not desired. In Oracle the pseudo code with COMMIT statements would work, in Postgres it doesn't seem to work as I would like to. Could you please help me out? Thanks
Your code will work as intended. Perhaps you made some mistake in calling the code:
you cannot call the procedure from a function
you cannot call the procedure in an explicitly started transaction:
BEGIN;
CALL p_insert('something); -- will fail
COMMIT;
I created the trigger function with the below code:
CREATE OR REPLACE FUNCTION snitch() RETURNS event_trigger AS $$
BEGIN
select pgr_createTopology('public.roads_noded', 0.001);
END;
$$ LANGUAGE plpgsql;
CREATE EVENT TRIGGER snitch ON ddl_command_start EXECUTE FUNCTION snitch();
It is located in the public schema. Now I want to drop this function but when I try to drop it executes the given function and the server hangs. After restarting the server, the same thing happens. I can't create or drop any table. Is there a way to remove this trigger function?
The DROP FUNCTION command is itself a DDL command so the trigger will fire. You should first remove the trigger using DROP EVENT TRIGGER, then the function.
I use postgresql 11.
I think my problem comes from a trigger that doesn't do an update so the next trigger doesn't launch.
I have a table projet with columns : projet_temps_doe, projet_temps_etudes, projet_temps_globale.
The goal is to update each columns depending on other columns values.
The idea is : projet_temps_globale = projet_temps_doe + projet_temps_etudes.
I have a first trigger on projet_temps_doe which works perfectly:
create function temps_globale_doe() returns trigger
language plpgsql
as
$$
begin
new.projet_temps_globale_doe := new.projet_temps_doe_gc_bts + new.projet_temps_doe_gc_nra;
return new;
end;
$$;
CREATE TRIGGER temps_globale_doe
BEFORE UPDATE OF projet_temps_doe_gc_bts, projet_temps_doe_gc_nra
ON public.projet
FOR EACH ROW
EXECUTE PROCEDURE public.temps_globale_doe();
I have a similar trigger on projet_temps_etudes which works perfectly too.
Then the trigger I struggle with on projet_temps_globale :
create trigger maj_temps_globale_projet
before update of projet_temps_doe, projet_temps_etudes on projet
for each row
execute procedure maj_temps_globale_projet();
create or replace function maj_temps_globale_projet()returns trigger
language plpgsql
as
$$
begin
new.projet_temps_globale := new.projet_temps_doe + new.projet_temps_etudes;
raise info 'TEST!!';
return new;
end;
$$;
When projet_temps_doe and/or projet_temps_etudes are updated via triggers my last trigger doesn't launch. However when I manually change projet_temps_doe and/or projet_temps_etudes values the trigger maj_temps_globale_projet is fired.
I want to learn from this, so, if possible, explain to me what I'm doing wrong here, or if my approach is lacking insight.
The doc says
The trigger will only fire if at least one of the listed columns is
mentioned as a target of the UPDATE command.
The column projet_temps_globale_doe is not part of the update command but is rather set in another trigger via new.projet_temps_globale_doe = ... so the trigger on this particular column is not called.
It would be easier to have only one trigger on the entire table that sets the 3 derived values.
I have many stored procedure in my postgresql db,
and for some reason i need to run many procedure in transaction so if there is a error it will rollback.
is there any way to do this?
edit 1
i run this through java and for some reason i cant make transaction from java and i cant run query string, just store procedure only.
I actually thinking making procedure like this
CREATE OR REPLACE FUNCTION ldt_pricing_rule_v1_api.start()
RETURNS VOID
LANGUAGE PLPGSQL
SECURITY DEFINER
AS $$
BEGIN
EXECUTE 'begin transaction'
RETURN;
END
$$;
select ldt_pricing_rule_v1_api.start();
but it's will display this
ERROR: cannot begin/end transactions in PL/pgSQL
HINT: Use a BEGIN block with an EXCEPTION clause instead.
BEGIN ... COMMIT should to work.
BEGIN
SELECT func1();
SELECT func2();
COMMIT;
PostgreSQL 11 (it is not released yet) has procedures where you can control transactions explicitly. Procedures are started by CALL statement like any other databases. Now, PostgreSQL functions doesn't allow control transactions (explicitly).
Any PostgreSQL function is executed under transaction - explicitly started by user (like my example), or implicitly started by system (by autocommit mode).
So outer BEGIN starts explicit transaction:
BEGIN
SELECT func1();
SELECT func2();
COMMIT;
and if there is any unhandled fail, then only ROLLBACK command is available.
or implicit transaction:
CREATE OR REPLACE FUNCTION outerfx()
RETURNS void AS $$
BEGIN
PERFORM func1();
PERFORM func2();
END;
$$ LANGUAGE plpgsql;
SELECT outerfx(); -- starts outer transaction implicitly.
Now, functions func1, func2 are executed under transaction too.
In Postgresql I can have two kinds of triggers: FOR EACH ROW and FOR EACH STATEMENT. If I do a FOR EACH ROW trigger, I can add a WHERE clause something like OLD.* != NEW.* so it only fires if something has actually changed. Is there any way to do something similar with STATEMENT level triggers? I know I can't do the same thing since OLD and NEW aren't available, but I was thinking perhaps there might be a way to check the number of rows changed from within my function itself or the like.
Usage case: I am using the postgresql NOTIFY system to notify my app when data changes. Ideally, the app would get a single notification each time one or more records changes, and not get notified at all if data stays the same (even if an UPDATE was run). With a basic AFTER UPDATE FOR EACH STATEMENT trigger, I am getting notified every time an update statement runs - even if it doesn't actually change anything.
You should create two triggers: before update for each row and after update for each statement.
The first trigger checks if the table is being updated and sets a flag if so.
The second trigger checks the flag and performs notify if it was set.
You can use a custom configuration parameter as the flag (e.g. flags.the_table).
The solution is simple and safe, as the parameter is local in the current session.
create or replace function before_each_row_on_the_table()
returns trigger language plpgsql
as $$
begin
if new <> old then
set flags.the_table to 'on';
end if;
return new;
end $$;
create or replace function after_each_statement_on_the_table()
returns trigger language plpgsql
as $$
begin
if current_setting('flags.the_table', true) = 'on' then
notify your_channel, 'the_table was updated';
set flags.the_table to 'off';
end if;
return null;
end $$;
create trigger before_each_row_on_the_table
before update on the_table
for each row execute procedure before_each_row_on_the_table();
create trigger after_each_statement_on_the_table
after update on the_table
for each statement execute procedure after_each_statement_on_the_table();
The function current_setting() with two arguments is available in Postgres 9.6 or later.