From 19f9054ccd1b66b7c9d0a34fa3a5bf72d9088e40 Mon Sep 17 00:00:00 2001 From: Amit Langote Date: Thu, 9 Jul 2026 18:21:57 +0900 Subject: [PATCH v1 3/3] Assert RI fast-path batches don't span subtransactions A fast-path batch is filled and flushed within a single trigger-firing cycle, so all rows in a batch come from the subtransaction that created the cache entry. AtEOSubXact_RI() relies on this: it identifies an aborting subtransaction's entries by the subid stamped at entry creation. Assert the invariant in ri_FastPathBatchAdd() to catch any future code that would violate it. Suggested-by: Noah Misch Discussion: https://postgr.es/m/20260705222115.be.noahmisch@microsoft.com Backpatch-through: 19 --- src/backend/utils/adt/ri_triggers.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/backend/utils/adt/ri_triggers.c b/src/backend/utils/adt/ri_triggers.c index a5d0fa4721a..a8c5277bfdc 100644 --- a/src/backend/utils/adt/ri_triggers.c +++ b/src/backend/utils/adt/ri_triggers.c @@ -2907,6 +2907,14 @@ ri_FastPathBatchAdd(RI_ConstraintInfo *riinfo, return; } + /* + * A batch is filled and flushed within a single trigger-firing cycle, so + * every row added to an entry comes from the subtransaction that created + * it. AtEOSubXact_RI() relies on this to identify an aborting + * subtransaction's entries by the subid stamped at entry creation. + */ + Assert(fpentry->subid == GetCurrentSubTransactionId()); + /* * Buffer the row. A full batch is flushed below and re-entry is handled * above, so there is always room here; the bounds check just guards the -- 2.47.3