Skip to content

Commit 5209058

Browse files
committed
Fix MakeTransitionCaptureState() to return a consistent result
When an UPDATE trigger referencing a new table and a DELETE trigger referencing an old table are both present, MakeTransitionCaptureState() returns an inconsistent result for UPDATE commands in its set of flags and tuplestores holding the TransitionCaptureState for transition tables. As proved by the test added here, this issue causes a crash in v14 and earlier versions (down to 11, actually, older versions do not support triggers on partitioned tables) during cross-partition updates on a partitioned table. v15 and newer versions are safe thanks to 7103ebb. This commit fixes the function so that it returns a consistent state by using portions of the changes made in commit 7103ebb for v13 and v14. v15 and newer versions are slightly tweaked to match with the older versions, mainly for consistency across branches. Author: Kyotaro Horiguchi Discussion: https://postgr.es/m/20250207.150238.968446820828052276.horikyota.ntt@gmail.com Backpatch-through: 13
1 parent 412047f commit 5209058

File tree

3 files changed

+115
-16
lines changed

3 files changed

+115
-16
lines changed

src/backend/commands/trigger.c

+20-16
Original file line numberDiff line numberDiff line change
@@ -4413,8 +4413,10 @@ TransitionCaptureState *
44134413
MakeTransitionCaptureState(TriggerDesc *trigdesc, Oid relid, CmdType cmdType)
44144414
{
44154415
TransitionCaptureState *state;
4416-
bool need_old,
4417-
need_new;
4416+
bool need_old_upd,
4417+
need_new_upd,
4418+
need_old_del,
4419+
need_new_ins;
44184420
AfterTriggersTableData *table;
44194421
MemoryContext oldcxt;
44204422
ResourceOwner saveResourceOwner;
@@ -4426,23 +4428,25 @@ MakeTransitionCaptureState(TriggerDesc *trigdesc, Oid relid, CmdType cmdType)
44264428
switch (cmdType)
44274429
{
44284430
case CMD_INSERT:
4429-
need_old = false;
4430-
need_new = trigdesc->trig_insert_new_table;
4431+
need_old_upd = need_old_del = need_new_upd = false;
4432+
need_new_ins = trigdesc->trig_insert_new_table;
44314433
break;
44324434
case CMD_UPDATE:
4433-
need_old = trigdesc->trig_update_old_table;
4434-
need_new = trigdesc->trig_update_new_table;
4435+
need_old_upd = trigdesc->trig_update_old_table;
4436+
need_new_upd = trigdesc->trig_update_new_table;
4437+
need_old_del = need_new_ins = false;
44354438
break;
44364439
case CMD_DELETE:
4437-
need_old = trigdesc->trig_delete_old_table;
4438-
need_new = false;
4440+
need_old_del = trigdesc->trig_delete_old_table;
4441+
need_old_upd = need_new_upd = need_new_ins = false;
44394442
break;
44404443
default:
44414444
elog(ERROR, "unexpected CmdType: %d", (int) cmdType);
4442-
need_old = need_new = false; /* keep compiler quiet */
4445+
/* keep compiler quiet */
4446+
need_old_upd = need_new_upd = need_old_del = need_new_ins = false;
44434447
break;
44444448
}
4445-
if (!need_old && !need_new)
4449+
if (!need_old_upd && !need_new_upd && !need_new_ins && !need_old_del)
44464450
return NULL;
44474451

44484452
/* Check state, like AfterTriggerSaveEvent. */
@@ -4472,20 +4476,20 @@ MakeTransitionCaptureState(TriggerDesc *trigdesc, Oid relid, CmdType cmdType)
44724476
saveResourceOwner = CurrentResourceOwner;
44734477
CurrentResourceOwner = CurTransactionResourceOwner;
44744478

4475-
if (need_old && table->old_tuplestore == NULL)
4479+
if ((need_old_upd || need_old_del) && table->old_tuplestore == NULL)
44764480
table->old_tuplestore = tuplestore_begin_heap(false, false, work_mem);
4477-
if (need_new && table->new_tuplestore == NULL)
4481+
if ((need_new_upd || need_new_ins) && table->new_tuplestore == NULL)
44784482
table->new_tuplestore = tuplestore_begin_heap(false, false, work_mem);
44794483

44804484
CurrentResourceOwner = saveResourceOwner;
44814485
MemoryContextSwitchTo(oldcxt);
44824486

44834487
/* Now build the TransitionCaptureState struct, in caller's context */
44844488
state = (TransitionCaptureState *) palloc0(sizeof(TransitionCaptureState));
4485-
state->tcs_delete_old_table = trigdesc->trig_delete_old_table;
4486-
state->tcs_update_old_table = trigdesc->trig_update_old_table;
4487-
state->tcs_update_new_table = trigdesc->trig_update_new_table;
4488-
state->tcs_insert_new_table = trigdesc->trig_insert_new_table;
4489+
state->tcs_delete_old_table = need_old_del;
4490+
state->tcs_update_old_table = need_old_upd;
4491+
state->tcs_update_new_table = need_new_upd;
4492+
state->tcs_insert_new_table = need_new_ins;
44894493
state->tcs_private = table;
44904494

44914495
return state;

src/test/regress/expected/triggers.out

+49
Original file line numberDiff line numberDiff line change
@@ -2988,6 +2988,55 @@ drop trigger child_row_trig on child;
29882988
alter table parent attach partition child for values in ('AAA');
29892989
drop table child, parent;
29902990
--
2991+
-- Verify access of transition tables with UPDATE triggers and tuples
2992+
-- moved across partitions.
2993+
--
2994+
create or replace function dump_update_new() returns trigger language plpgsql as
2995+
$$
2996+
begin
2997+
raise notice 'trigger = %, new table = %', TG_NAME,
2998+
(select string_agg(new_table::text, ', ' order by a) from new_table);
2999+
return null;
3000+
end;
3001+
$$;
3002+
create or replace function dump_update_old() returns trigger language plpgsql as
3003+
$$
3004+
begin
3005+
raise notice 'trigger = %, old table = %', TG_NAME,
3006+
(select string_agg(old_table::text, ', ' order by a) from old_table);
3007+
return null;
3008+
end;
3009+
$$;
3010+
create table trans_tab_parent (a text) partition by list (a);
3011+
create table trans_tab_child1 partition of trans_tab_parent for values in ('AAA1', 'AAA2');
3012+
create table trans_tab_child2 partition of trans_tab_parent for values in ('BBB1', 'BBB2');
3013+
create trigger trans_tab_parent_update_trig
3014+
after update on trans_tab_parent referencing old table as old_table
3015+
for each statement execute procedure dump_update_old();
3016+
create trigger trans_tab_parent_insert_trig
3017+
after insert on trans_tab_parent referencing new table as new_table
3018+
for each statement execute procedure dump_insert();
3019+
create trigger trans_tab_parent_delete_trig
3020+
after delete on trans_tab_parent referencing old table as old_table
3021+
for each statement execute procedure dump_delete();
3022+
insert into trans_tab_parent values ('AAA1'), ('BBB1');
3023+
NOTICE: trigger = trans_tab_parent_insert_trig, new table = (AAA1), (BBB1)
3024+
-- should not trigger access to new table when moving across partitions.
3025+
update trans_tab_parent set a = 'BBB2' where a = 'AAA1';
3026+
NOTICE: trigger = trans_tab_parent_update_trig, old table = (AAA1)
3027+
drop trigger trans_tab_parent_update_trig on trans_tab_parent;
3028+
create trigger trans_tab_parent_update_trig
3029+
after update on trans_tab_parent referencing new table as new_table
3030+
for each statement execute procedure dump_update_new();
3031+
-- should not trigger access to old table when moving across partitions.
3032+
update trans_tab_parent set a = 'AAA2' where a = 'BBB1';
3033+
NOTICE: trigger = trans_tab_parent_update_trig, new table = (AAA2)
3034+
delete from trans_tab_parent;
3035+
NOTICE: trigger = trans_tab_parent_delete_trig, old table = (AAA2), (BBB2)
3036+
-- clean up
3037+
drop table trans_tab_parent, trans_tab_child1, trans_tab_child2;
3038+
drop function dump_update_new, dump_update_old;
3039+
--
29913040
-- Verify behavior of statement triggers on (non-partition)
29923041
-- inheritance hierarchy with transition tables; similar to the
29933042
-- partition case, except there is no rerouting on insertion and child

src/test/regress/sql/triggers.sql

+46
Original file line numberDiff line numberDiff line change
@@ -2115,6 +2115,52 @@ alter table parent attach partition child for values in ('AAA');
21152115

21162116
drop table child, parent;
21172117

2118+
--
2119+
-- Verify access of transition tables with UPDATE triggers and tuples
2120+
-- moved across partitions.
2121+
--
2122+
create or replace function dump_update_new() returns trigger language plpgsql as
2123+
$$
2124+
begin
2125+
raise notice 'trigger = %, new table = %', TG_NAME,
2126+
(select string_agg(new_table::text, ', ' order by a) from new_table);
2127+
return null;
2128+
end;
2129+
$$;
2130+
create or replace function dump_update_old() returns trigger language plpgsql as
2131+
$$
2132+
begin
2133+
raise notice 'trigger = %, old table = %', TG_NAME,
2134+
(select string_agg(old_table::text, ', ' order by a) from old_table);
2135+
return null;
2136+
end;
2137+
$$;
2138+
create table trans_tab_parent (a text) partition by list (a);
2139+
create table trans_tab_child1 partition of trans_tab_parent for values in ('AAA1', 'AAA2');
2140+
create table trans_tab_child2 partition of trans_tab_parent for values in ('BBB1', 'BBB2');
2141+
create trigger trans_tab_parent_update_trig
2142+
after update on trans_tab_parent referencing old table as old_table
2143+
for each statement execute procedure dump_update_old();
2144+
create trigger trans_tab_parent_insert_trig
2145+
after insert on trans_tab_parent referencing new table as new_table
2146+
for each statement execute procedure dump_insert();
2147+
create trigger trans_tab_parent_delete_trig
2148+
after delete on trans_tab_parent referencing old table as old_table
2149+
for each statement execute procedure dump_delete();
2150+
insert into trans_tab_parent values ('AAA1'), ('BBB1');
2151+
-- should not trigger access to new table when moving across partitions.
2152+
update trans_tab_parent set a = 'BBB2' where a = 'AAA1';
2153+
drop trigger trans_tab_parent_update_trig on trans_tab_parent;
2154+
create trigger trans_tab_parent_update_trig
2155+
after update on trans_tab_parent referencing new table as new_table
2156+
for each statement execute procedure dump_update_new();
2157+
-- should not trigger access to old table when moving across partitions.
2158+
update trans_tab_parent set a = 'AAA2' where a = 'BBB1';
2159+
delete from trans_tab_parent;
2160+
-- clean up
2161+
drop table trans_tab_parent, trans_tab_child1, trans_tab_child2;
2162+
drop function dump_update_new, dump_update_old;
2163+
21182164
--
21192165
-- Verify behavior of statement triggers on (non-partition)
21202166
-- inheritance hierarchy with transition tables; similar to the

0 commit comments

Comments
 (0)