Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
Skip to content

Commit 773c51d

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 abfb296 commit 773c51d

File tree

3 files changed

+99
-4
lines changed

3 files changed

+99
-4
lines changed

src/backend/commands/trigger.c

+4-4
Original file line numberDiff line numberDiff line change
@@ -5000,10 +5000,10 @@ MakeTransitionCaptureState(TriggerDesc *trigdesc, Oid relid, CmdType cmdType)
50005000

50015001
/* Now build the TransitionCaptureState struct, in caller's context */
50025002
state = (TransitionCaptureState *) palloc0(sizeof(TransitionCaptureState));
5003-
state->tcs_delete_old_table = trigdesc->trig_delete_old_table;
5004-
state->tcs_update_old_table = trigdesc->trig_update_old_table;
5005-
state->tcs_update_new_table = trigdesc->trig_update_new_table;
5006-
state->tcs_insert_new_table = trigdesc->trig_insert_new_table;
5003+
state->tcs_delete_old_table = need_old_del;
5004+
state->tcs_update_old_table = need_old_upd;
5005+
state->tcs_update_new_table = need_new_upd;
5006+
state->tcs_insert_new_table = need_new_ins;
50075007
state->tcs_private = table;
50085008

50095009
return state;

src/test/regress/expected/triggers.out

+49
Original file line numberDiff line numberDiff line change
@@ -3074,6 +3074,55 @@ drop trigger child_row_trig on child;
30743074
alter table parent attach partition child for values in ('AAA');
30753075
drop table child, parent;
30763076
--
3077+
-- Verify access of transition tables with UPDATE triggers and tuples
3078+
-- moved across partitions.
3079+
--
3080+
create or replace function dump_update_new() returns trigger language plpgsql as
3081+
$$
3082+
begin
3083+
raise notice 'trigger = %, new table = %', TG_NAME,
3084+
(select string_agg(new_table::text, ', ' order by a) from new_table);
3085+
return null;
3086+
end;
3087+
$$;
3088+
create or replace function dump_update_old() returns trigger language plpgsql as
3089+
$$
3090+
begin
3091+
raise notice 'trigger = %, old table = %', TG_NAME,
3092+
(select string_agg(old_table::text, ', ' order by a) from old_table);
3093+
return null;
3094+
end;
3095+
$$;
3096+
create table trans_tab_parent (a text) partition by list (a);
3097+
create table trans_tab_child1 partition of trans_tab_parent for values in ('AAA1', 'AAA2');
3098+
create table trans_tab_child2 partition of trans_tab_parent for values in ('BBB1', 'BBB2');
3099+
create trigger trans_tab_parent_update_trig
3100+
after update on trans_tab_parent referencing old table as old_table
3101+
for each statement execute procedure dump_update_old();
3102+
create trigger trans_tab_parent_insert_trig
3103+
after insert on trans_tab_parent referencing new table as new_table
3104+
for each statement execute procedure dump_insert();
3105+
create trigger trans_tab_parent_delete_trig
3106+
after delete on trans_tab_parent referencing old table as old_table
3107+
for each statement execute procedure dump_delete();
3108+
insert into trans_tab_parent values ('AAA1'), ('BBB1');
3109+
NOTICE: trigger = trans_tab_parent_insert_trig, new table = (AAA1), (BBB1)
3110+
-- should not trigger access to new table when moving across partitions.
3111+
update trans_tab_parent set a = 'BBB2' where a = 'AAA1';
3112+
NOTICE: trigger = trans_tab_parent_update_trig, old table = (AAA1)
3113+
drop trigger trans_tab_parent_update_trig on trans_tab_parent;
3114+
create trigger trans_tab_parent_update_trig
3115+
after update on trans_tab_parent referencing new table as new_table
3116+
for each statement execute procedure dump_update_new();
3117+
-- should not trigger access to old table when moving across partitions.
3118+
update trans_tab_parent set a = 'AAA2' where a = 'BBB1';
3119+
NOTICE: trigger = trans_tab_parent_update_trig, new table = (AAA2)
3120+
delete from trans_tab_parent;
3121+
NOTICE: trigger = trans_tab_parent_delete_trig, old table = (AAA2), (BBB2)
3122+
-- clean up
3123+
drop table trans_tab_parent, trans_tab_child1, trans_tab_child2;
3124+
drop function dump_update_new, dump_update_old;
3125+
--
30773126
-- Verify behavior of statement triggers on (non-partition)
30783127
-- inheritance hierarchy with transition tables; similar to the
30793128
-- 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
@@ -2187,6 +2187,52 @@ alter table parent attach partition child for values in ('AAA');
21872187

21882188
drop table child, parent;
21892189

2190+
--
2191+
-- Verify access of transition tables with UPDATE triggers and tuples
2192+
-- moved across partitions.
2193+
--
2194+
create or replace function dump_update_new() returns trigger language plpgsql as
2195+
$$
2196+
begin
2197+
raise notice 'trigger = %, new table = %', TG_NAME,
2198+
(select string_agg(new_table::text, ', ' order by a) from new_table);
2199+
return null;
2200+
end;
2201+
$$;
2202+
create or replace function dump_update_old() returns trigger language plpgsql as
2203+
$$
2204+
begin
2205+
raise notice 'trigger = %, old table = %', TG_NAME,
2206+
(select string_agg(old_table::text, ', ' order by a) from old_table);
2207+
return null;
2208+
end;
2209+
$$;
2210+
create table trans_tab_parent (a text) partition by list (a);
2211+
create table trans_tab_child1 partition of trans_tab_parent for values in ('AAA1', 'AAA2');
2212+
create table trans_tab_child2 partition of trans_tab_parent for values in ('BBB1', 'BBB2');
2213+
create trigger trans_tab_parent_update_trig
2214+
after update on trans_tab_parent referencing old table as old_table
2215+
for each statement execute procedure dump_update_old();
2216+
create trigger trans_tab_parent_insert_trig
2217+
after insert on trans_tab_parent referencing new table as new_table
2218+
for each statement execute procedure dump_insert();
2219+
create trigger trans_tab_parent_delete_trig
2220+
after delete on trans_tab_parent referencing old table as old_table
2221+
for each statement execute procedure dump_delete();
2222+
insert into trans_tab_parent values ('AAA1'), ('BBB1');
2223+
-- should not trigger access to new table when moving across partitions.
2224+
update trans_tab_parent set a = 'BBB2' where a = 'AAA1';
2225+
drop trigger trans_tab_parent_update_trig on trans_tab_parent;
2226+
create trigger trans_tab_parent_update_trig
2227+
after update on trans_tab_parent referencing new table as new_table
2228+
for each statement execute procedure dump_update_new();
2229+
-- should not trigger access to old table when moving across partitions.
2230+
update trans_tab_parent set a = 'AAA2' where a = 'BBB1';
2231+
delete from trans_tab_parent;
2232+
-- clean up
2233+
drop table trans_tab_parent, trans_tab_child1, trans_tab_child2;
2234+
drop function dump_update_new, dump_update_old;
2235+
21902236
--
21912237
-- Verify behavior of statement triggers on (non-partition)
21922238
-- inheritance hierarchy with transition tables; similar to the

0 commit comments

Comments
 (0)