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(-)
