From 0a87bc9ca2494cbc5a41a1ee506dac45c794d060 Mon Sep 17 00:00:00 2001 From: Fujii Masao Date: Thu, 17 Sep 2026 14:24:22 +0900 Subject: [PATCH] Fix assertion after aborting internal subtransaction at transaction end MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Previously, aborting an internal subtransaction during COMMIT or PREPARE TRANSACTION could cause the following assertion failure. This could happen, for example, when a deferred constraint trigger fired at COMMIT and its PL/pgSQL exception block caught an error raised while executing the trigger function. TRAP: failed Assert("s->blockState == TBLOCK_SUBINPROGRESS || s->blockState == TBLOCK_INPROGRESS || s->blockState == TBLOCK_IMPLICIT_INPROGRESS || s->blockState == TBLOCK_PARALLEL_INPROGRESS || s->blockState == TBLOCK_STARTED"), File: "xact.c", Line: 4851, PID: 73455 An internal subtransaction should be able to be aborted while the parent transaction is in the COMMIT or PREPARE TRANSACTION phase. However, RollbackAndReleaseCurrentSubTransaction()'s assertion check previously did not allow TBLOCK_END and TBLOCK_PREPARE as parent transaction states, causing the assertion failure. This commit fixes the assertion check by allowing those two parent transaction states. Backpatch to all supported versions. Reported-by: Fabrízio de Royes Mello Author: Patrick Reynolds Reviewed-by: Fujii Masao Reviewed-by: Chao Li Reviewed-by: Fabrízio de Royes Mello Discussion: https://postgr.es/m/CABo-N97AeMbWuYTWg-3%3D2DkTR3EkvS%2BFt%3DyEaWB181STsR1mBg%40mail.gmail.com Backpatch-through: 14 --- src/backend/access/transam/xact.c | 2 ++ src/test/regress/expected/triggers.out | 28 ++++++++++++++++++++++++++ src/test/regress/sql/triggers.sql | 25 +++++++++++++++++++++++ 3 files changed, 55 insertions(+) diff --git a/src/backend/access/transam/xact.c b/src/backend/access/transam/xact.c index 3a89149016f..342b5f1e3e5 100644 --- a/src/backend/access/transam/xact.c +++ b/src/backend/access/transam/xact.c @@ -4902,6 +4902,8 @@ RollbackAndReleaseCurrentSubTransaction(void) s->blockState == TBLOCK_INPROGRESS || s->blockState == TBLOCK_IMPLICIT_INPROGRESS || s->blockState == TBLOCK_PARALLEL_INPROGRESS || + s->blockState == TBLOCK_END || + s->blockState == TBLOCK_PREPARE || s->blockState == TBLOCK_STARTED); } diff --git a/src/test/regress/expected/triggers.out b/src/test/regress/expected/triggers.out index 511e7cfb6ce..0943a8d49bb 100644 --- a/src/test/regress/expected/triggers.out +++ b/src/test/regress/expected/triggers.out @@ -2308,6 +2308,34 @@ create constraint trigger crtr ERROR: constraint triggers cannot be marked NOT ENFORCED LINE 2: after insert on foo not enforced ^ +-- Test exception handling in a deferred constraint trigger at COMMIT. +create table deferred_trigger_test (a int); +create function deferred_trigger_func() returns trigger + language plpgsql as $$ +begin + perform 1 / 0; + return new; +exception when division_by_zero then + raise notice 'caught division_by_zero'; + return new; +end; +$$; +create constraint trigger deferred_trigger + after insert on deferred_trigger_test + deferrable initially deferred + for each row execute function deferred_trigger_func(); +begin; +insert into deferred_trigger_test values (1); +commit; +NOTICE: caught division_by_zero +select * from deferred_trigger_test; + a +--- + 1 +(1 row) + +drop table deferred_trigger_test; +drop function deferred_trigger_func(); -- -- Constraint triggers and partitioned tables create table parted_constr_ancestor (a int, b text) diff --git a/src/test/regress/sql/triggers.sql b/src/test/regress/sql/triggers.sql index ea39817ee3d..e73f43942b0 100644 --- a/src/test/regress/sql/triggers.sql +++ b/src/test/regress/sql/triggers.sql @@ -1590,6 +1590,31 @@ create constraint trigger crtr after insert on foo not enforced for each row execute procedure foo (); +-- Test exception handling in a deferred constraint trigger at COMMIT. +create table deferred_trigger_test (a int); +create function deferred_trigger_func() returns trigger + language plpgsql as $$ +begin + perform 1 / 0; + return new; +exception when division_by_zero then + raise notice 'caught division_by_zero'; + return new; +end; +$$; +create constraint trigger deferred_trigger + after insert on deferred_trigger_test + deferrable initially deferred + for each row execute function deferred_trigger_func(); + +begin; +insert into deferred_trigger_test values (1); +commit; +select * from deferred_trigger_test; + +drop table deferred_trigger_test; +drop function deferred_trigger_func(); + -- -- Constraint triggers and partitioned tables create table parted_constr_ancestor (a int, b text)