diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index da0d1ba6791..b87b4b40d07 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -5239,7 +5239,12 @@ AfterTriggerEndQuery(EState *estate) * Fire batch callbacks before releasing query-level storage and before * decrementing query_depth. Callbacks may do real work (index probes, * error reporting). + * + * Recompute qs first: the loop above refreshes it after each + * afterTriggerInvokeEvents() call (see comment there), but the "all + * fired" break exits without doing so, leaving qs potentially stale here. */ + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; FireAfterTriggerBatchCallbacks(qs->batch_callbacks); /* Release query-level-local storage, including tuplestores if any */ diff --git a/src/test/regress/expected/triggers.out b/src/test/regress/expected/triggers.out index 511e7cfb6ce..8fcb33ac81a 100644 --- a/src/test/regress/expected/triggers.out +++ b/src/test/regress/expected/triggers.out @@ -3644,3 +3644,27 @@ drop table defer_trig; drop function whoami(); drop role regress_fn_owner; drop role regress_caller; +-- +-- Test a recursive AFTER ROW trigger that nests after-trigger query levels +-- deeply enough to grow query_stack mid-fire. Outer levels then resume their +-- post-loop cleanup against the relocated stack. +-- +create table trigger_recursive (id int); +create function trigger_recursive_fn() returns trigger language plpgsql as $$ +begin + if new.id < 10 then + insert into trigger_recursive values (new.id + 1); + end if; + return new; +end$$; +create trigger trigger_recursive after insert on trigger_recursive + for each row execute function trigger_recursive_fn(); +insert into trigger_recursive values (1); +select count(*) from trigger_recursive; + count +------- + 10 +(1 row) + +drop table trigger_recursive; +drop function trigger_recursive_fn(); diff --git a/src/test/regress/sql/triggers.sql b/src/test/regress/sql/triggers.sql index ea39817ee3d..2285e90110e 100644 --- a/src/test/regress/sql/triggers.sql +++ b/src/test/regress/sql/triggers.sql @@ -2788,3 +2788,26 @@ drop table defer_trig; drop function whoami(); drop role regress_fn_owner; drop role regress_caller; + +-- +-- Test a recursive AFTER ROW trigger that nests after-trigger query levels +-- deeply enough to grow query_stack mid-fire. Outer levels then resume their +-- post-loop cleanup against the relocated stack. +-- +create table trigger_recursive (id int); +create function trigger_recursive_fn() returns trigger language plpgsql as $$ +begin + if new.id < 10 then + insert into trigger_recursive values (new.id + 1); + end if; + return new; +end$$; + +create trigger trigger_recursive after insert on trigger_recursive + for each row execute function trigger_recursive_fn(); + +insert into trigger_recursive values (1); +select count(*) from trigger_recursive; + +drop table trigger_recursive; +drop function trigger_recursive_fn();