Fire fast-path FK batches inside the deferred trigger loop

AfterTriggerFireDeferred() drained its queued events in a loop, then
called FireAfterTriggerBatchCallbacks() once after the loop to flush
the RI fast-path FK-check batches the fired triggers accumulated.

A batch callback runs user-supplied cast or equality functions, whose
DML can queue further deferred trigger events.  Because the flush ran
after the loop had exited, such an event was left in
afterTriggers.events with nothing to fire it, since
AfterTriggerFireDeferred() is the last drainer at commit.  The deferred
check was skipped and a row violating the constraint committed.

Move the flush inside the loop, after afterTriggerInvokeEvents(), and
drop the "all fired" break, so afterTriggerMarkEvents() re-checks after
each flush and fires events a flush queued at the correct time.  The
other FireAfterTriggerBatchCallbacks() callers leave any queued event
for the eventual commit-time AfterTriggerFireDeferred(), so only the
commit-time firing, which has no later drainer, lost events.

Reported-by: Noah Misch <[email protected]>
Reviewed-by: Ayush Tiwari <[email protected]>
Discussion: https://postgr.es/m/[email protected]
Backpatch-through: 19

Branch
------
master

Details
-------
https://git.postgresql.org/pg/commitdiff/291a4bd2ca1cac695f34a7d2dc4322bd235641de

Modified Files
--------------
src/backend/commands/trigger.c            | 20 ++++++++++++----
src/test/regress/expected/foreign_key.out | 39 +++++++++++++++++++++++++++++++
src/test/regress/sql/foreign_key.sql      | 38 ++++++++++++++++++++++++++++++
3 files changed, 92 insertions(+), 5 deletions(-)

Reply via email to