There are several ways for determining if the trigger is recursing. Checking the trigger depth (pg_trigger_depth()
) is not one of them, because you don't know if the trigger itself is recursing or is being fired from another trigger.
The only direct way to know analysing the execution stack during an exception:
DECLARE
context text;
BEGIN
BEGIN
SELECT 1/0;
EXCEPTION WHEN OTHERS THEN
GET STACKED DIAGNOSTICS context = PG_EXCEPTION_CONTEXT;
END;
-- analyse here the context variable to find if the last item in the stack
-- is this same function
END;
Exception handling is costly, but so are the other options, which require leaving a session marker for avoiding recursion, either using a temporary table, or a session variable:
BEGIN
IF to_regclass('pg_temp.' || quote_ident(TG_NAME)) THEN -- it's recursing
RETURN NEW; -- or NULL, or whatever action is required when recursing
END IF;
EXECUTE format('CREATE TEMP TABLE %I (a int) ON COMMIT DROP', TG_NAME);
-- general body of trigger
EXECUTE format('DROP TABLE pg_temp.%I', TG_NAME);
RETURN NEW; -- or whatever appropriate
END;
or using a session variable:
BEGIN
IF current_setting('x.' || TG_NAME, true) IS DISTINCT FROM '1' THEN
-- it's recursing
RETURN NEW; -- or NULL, or whatever action is required when recursing
END IF;
PERFORM set_config('x.' || TG_NAME, '1', true);
-- general body of trigger
PERFORM set_config('x.' || TG_NAME, '', true);
RETURN NEW; -- or whatever appropriate
END;
All of the options above have a significant performance impact. A possibly better performing option could involve a table for this purpose:
CREATE TABLE trigger_execs (
pid int NOT NULL DEFAULT pg_backend_pid(),
tgname name NOT NULL,
PRIMARY KEY (pid, tgname)
);
which can be used like:
BEGIN
IF EXISTS (SELECT 1 FROM trigger_execs
WHERE pid = pg_backend_pid() AND tgname = TG_NAME) THEN
-- it's recursing
RETURN NEW; -- or NULL, or whatever action is required when recursing
END IF;
INSERT INTO trigger_execs (tgname) VALUES (TG_NAME);
-- general body of trigger
DELETE FROM trigger_execs
WHERE pid = pg_backend_pid() AND tgname = TG_NAME;
RETURN NEW; -- or whatever appropriate
END;
I'd prefer to use RETURN
rather than RETURN QUERY SELECT
in pl/pgsql.
But there's no need to jump through hoops. The cost of returning results with SELECT
is negligible compared to other costs in pl/pgsql procedures.
Everything is a result-set in PostgreSQL anyway, even a single scalar, so you're not saving much with RETURN
.
Best Answer
Syntax errors
Immediate causes for the error:
And a missing semicolon at the end of the
DO
statement:Better query
On a closer look, this does not seem to be another case of
UPSERT
. It would seem one of the inserted columns needs to depend on multiple input values in combination with the freshly retrieved value for a serial ID. Try this largely simplified statement:An explicit cast to
text
(or your actual undisclosed data type(s)) is needed.This is equivalent to the presented code, just with legal syntax and much faster - unless there are triggers or non-standard default values you did not declare ...
Simpler with
currval()
On an even closer look, this can be simplified further:
Detailed explanation: