I want to be able to call set_config
to set a configuration parameter within the same transaction/scope as an INSERT statement. There is a function triggered AFTER the insert which reads from this configuration parameter.
I need to accomplish this as a single statement so CTEs seems like a good idea:
WITH
idx AS (SELECT set_config('audit.trace_id', '123', true)),
rows AS (INSERT INTO "person" ("name") values ('Mary') RETURNING *)
SELECT * FROM rows
However, it seems audit.trace_id
is not available to the trigger function. So then I thought I’d structure it like below, which should create a dependency:
WITH
idx AS (SELECT set_config('audit.trace_id', '123', true))
INSERT INTO "person" ("name") values ('Mary') RETURNING *
But still, the value in audit.trace_id
is still not set within scope of the triggered function. I believe this is because Postgre avoids running the SELECT as the result isn’t required further into the CTE.
If I select out from both, like below, then audit.trace_id
is successfully set when the trigger function runs:
WITH
idx AS (SELECT set_config('audit.trace_id', '123', true) as __trace_id),
row as (INSERT INTO "person" ("name") values ('Mary') RETURNING *)
SELCET * FROM idx, row
Question
Is ‘audit.trace_id’ guaranteed to be set by the time the trigger executes on the person table? Or is there a possible race condition here?
EDIT:
What about this? It seems to work fine:
INSERT INTO "person" ("name") values ('Mary') RETURNING *, set_config('audit.trace_id', '123', true)
2
Answers
In PostgreSQL, within a Common Table Expression (CTE), the queries are not guaranteed to be executed in the order they are written, they are optimized by the PostgreSQL engine and might be executed in parallel or in a different order that might not be expected.
However, data-modifying statements in
WITH
are executed exactly once, and always to completion, which does mean that they will always fully complete before the main query starts to run.Given your constraints and the problems you are facing, it seems that the final solution you proposed:
would be the most reliable way to ensure the configuration parameter
'audit.trace_id'
is set before the trigger function executes, as it sets the configuration parameter during theINSERT
statement’sRETURNING
clause, essentially making it a part of the data modification statement, ensuring it is executed within the same transaction and before anyAFTER INSERT
triggers.PostgreSQL executes the CTE
idx
only if it is referenced in the main statement. That’s why your third attempt was successful, unlike the first two.The placeholder parameter will certainly be set by the time an
AFTER INSERT
trigger executes, because the CTE has to be executed before theINSERT
.There cannot be any race conditions, because the execution plan is deterministic, and no parallel processing is taking place.
set_config
is markedPARALLEL UNSAFE
, so a statement containing it will never be executed using parallel query.