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 ------ REL_19_STABLE Details ------- https://git.postgresql.org/pg/commitdiff/acdfaee1b947776ec381a4756f01979d31316774 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(-)
