pg.ddx.io  pgsql-committers@postgresql.org mailing list archive  
help / color / mirror / Atom feed
From: Amit Langote <amitlan@postgresql.org>
To: pgsql-committers@lists.postgresql.org
Subject: pgsql: Restore after-trigger firing context at subtransaction end
Date: Thu, 20 Aug 2026 05:03:49 +0000
Message-ID: <E1wwuwO-00000001JW2-1p8X@gemulon.postgresql.org> (raw)

Restore after-trigger firing context at subtransaction end

AfterTriggerEndQuery(), AfterTriggerFireDeferred(), and
AfterTriggerSetState() bracket their firing loops with
firing_depth++/--.  The decrement runs after the loop and is not
protected by PG_FINALLY, so an error caught by a subtransaction (e.g. a
PL/pgSQL EXCEPTION block) leaves firing_depth too high.  Separately,
AfterTriggerEndSubXact() unconditionally cleared
firing_batch_callbacks, even if the subtransaction began while an outer
batch-callback loop was active.

firing_depth feeds AfterTriggerIsActive(), which the RI fast path uses
to decide whether an FK check is running inside trigger firing and may
batch.  A stranded firing_depth makes AfterTriggerIsActive() wrongly
report firing as active afterwards.  This is reachable and results in
silent data corruption: after a caught FK-check error, an ALTER TABLE ...
ADD FOREIGN KEY whose validation runs per-row (RI_Initial_Check() having
bailed, e.g. because RLS is enabled on the referenced table) calls
RI_FKey_check() with AfterTriggerIsActive() wrongly true.  The check is
routed into the batched fast path, but a utility command has no
AfterTriggerEndQuery() to fire the flush callback.  The violating row is
not reported, the constraint is marked validated, and the cached PK
relation and index leak.

Save firing_depth and firing_batch_callbacks at subtransaction start and
restore them in AfterTriggerEndSubXact(), next to the existing query_depth
handling.  Restoring, rather than zeroing or clearing, is required because
a subtransaction can begin and end while an outer query is firing, where
firing_depth is legitimately positive and firing_batch_callbacks may be
legitimately set.

Reported-by: Noah Misch <noah@leadboat.com>
Discussion: https://postgr.es/m/20260705222115.be.noahmisch@microsoft.com
Backpatch-through: 19

Branch
------
REL_19_STABLE

Details
-------
https://git.postgresql.org/pg/commitdiff/f3a52a229adeb9c54e5b656b45af609b967874d6

Modified Files
--------------
src/backend/commands/trigger.c            | 29 ++++++++++++++++++++--
src/test/regress/expected/foreign_key.out | 41 +++++++++++++++++++++++++++++++
src/test/regress/sql/foreign_key.sql      | 40 ++++++++++++++++++++++++++++++
3 files changed, 108 insertions(+), 2 deletions(-)



view thread (2+ messages)  latest in thread

Message-ID: <E1wwuwO-00000001JW2-1p8X@gemulon.postgresql.org>
Permalink:  ../E1wwuwO-00000001JW2-1p8X@gemulon.postgresql.org/
Also on:    postgresql.org/message-id/E1wwuwO-00000001JW2-1p8X@gemulon.postgresql.org

 · 

reply

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Reply to all the recipients using the --to and --cc options:
  reply via email

  To: pgsql-committers@postgresql.org
  Cc: amitlan@postgresql.org, pgsql-committers@lists.postgresql.org
  Subject: Re: pgsql: Restore after-trigger firing context at subtransaction end
  In-Reply-To: <E1wwuwO-00000001JW2-1p8X@gemulon.postgresql.org>

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

This inbox is served by DDX for PostgreSQL; see mirroring instructions
for how to clone and mirror all data and code used for this inbox