diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index 95752fa31d..751c465bff 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3832,6 +3832,8 @@ static void AfterTriggerExecute(EState *estate, TupleTableSlot *trig_tuple_slot2); static AfterTriggersTableData *GetAfterTriggersTableData(Oid relid, CmdType cmdType); +static TupleTableSlot *GetAfterTriggersStoreSlot(AfterTriggersTableData *table, + TupleDesc tupdesc); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState state); @@ -4634,6 +4636,31 @@ GetAfterTriggersTableData(Oid relid, CmdType cmdType) return table; } +/* + * Returns a TupleTableSlot suitable for holding the tuples to be put + * into AfterTriggersTableData's transition table tuplestores. + */ +static TupleTableSlot * +GetAfterTriggersStoreSlot(AfterTriggersTableData *table, + TupleDesc tupdesc) +{ + /* Create it if not already done. */ + if (!table->storeslot) + { + MemoryContext oldcxt; + + /* + * We only need this slot only until AfterTriggerEndQuery, but making + * it last till end-of-subxact is good enough. It'll be freed by + * AfterTriggerFreeQuery(). + */ + oldcxt = MemoryContextSwitchTo(CurTransactionContext); + table->storeslot = MakeSingleTupleTableSlot(tupdesc, &TTSOpsVirtual); + MemoryContextSwitchTo(oldcxt); + } + + return table->storeslot; +} /* * MakeTransitionCaptureState @@ -4922,6 +4949,8 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) table->new_tuplestore = NULL; if (ts) tuplestore_end(ts); + if (table->storeslot) + ExecDropSingleTupleTableSlot(table->storeslot); } /* @@ -5771,17 +5800,10 @@ AfterTriggerSaveEvent(EState *estate, ResultRelInfo *relinfo, if (map != NULL) { + AfterTriggersTableData *table = transition_capture->tcs_private; TupleTableSlot *storeslot; - storeslot = transition_capture->tcs_private->storeslot; - if (!storeslot) - { - storeslot = ExecAllocTableSlot(&estate->es_tupleTable, - map->outdesc, - &TTSOpsVirtual); - transition_capture->tcs_private->storeslot = storeslot; - } - + storeslot = GetAfterTriggersStoreSlot(table, map->outdesc); execute_attr_map_slot(map->attrMap, oldslot, storeslot); tuplestore_puttupleslot(old_tuplestore, storeslot); } @@ -5801,18 +5823,10 @@ AfterTriggerSaveEvent(EState *estate, ResultRelInfo *relinfo, original_insert_tuple); else if (map != NULL) { + AfterTriggersTableData *table = transition_capture->tcs_private; TupleTableSlot *storeslot; - storeslot = transition_capture->tcs_private->storeslot; - - if (!storeslot) - { - storeslot = ExecAllocTableSlot(&estate->es_tupleTable, - map->outdesc, - &TTSOpsVirtual); - transition_capture->tcs_private->storeslot = storeslot; - } - + storeslot = GetAfterTriggersStoreSlot(table, map->outdesc); execute_attr_map_slot(map->attrMap, newslot, storeslot); tuplestore_puttupleslot(new_tuplestore, storeslot); } diff --git a/src/test/regress/expected/triggers.out b/src/test/regress/expected/triggers.out index 316eefdb6b..38406a2879 100644 --- a/src/test/regress/expected/triggers.out +++ b/src/test/regress/expected/triggers.out @@ -3083,3 +3083,62 @@ drop table self_ref; drop function dump_insert(); drop function dump_update(); drop function dump_delete(); +-- verify transition table conversion slot's lifetime +-- https://postgr.es/m/39a71864-b120-5a5c-8cc5-c632b6f16761@amazon.com +create table convslot_test_parent (col1 text primary key); +create table convslot_test_child (col1 text primary key, + foreign key (col1) references convslot_test_parent(col1) on delete cascade on update cascade +); +alter table convslot_test_child add column col2 text not null default 'tutu'; +insert into convslot_test_parent(col1) values ('1'); +insert into convslot_test_child(col1) values ('1'); +insert into convslot_test_parent(col1) values ('3'); +insert into convslot_test_child(col1) values ('3'); +create or replace function trigger_function1() +returns trigger +language plpgsql +AS $$ +begin +raise notice 'trigger = %, old_table = %', + TG_NAME, + (select string_agg(old_table::text, ', ' order by col1) from old_table); +return null; +end; $$; +create or replace function trigger_function2() +returns trigger +language plpgsql +AS $$ +begin +raise notice 'trigger = %, new table = %', + TG_NAME, + (select string_agg(new_table::text, ', ' order by col1) from new_table); +return null; +end; $$; +create trigger but_trigger after update on convslot_test_child +referencing new table as new_table +for each statement execute function trigger_function2(); +update convslot_test_parent set col1 = col1 || '1'; +NOTICE: trigger = but_trigger, new table = (11,tutu), (31,tutu) +create or replace function trigger_function3() +returns trigger +language plpgsql +AS $$ +begin +raise notice 'trigger = %, old_table = %, new table = %', + TG_NAME, + (select string_agg(old_table::text, ', ' order by col1) from old_table), + (select string_agg(new_table::text, ', ' order by col1) from new_table); +return null; +end; $$; +create trigger but_trigger2 after update on convslot_test_child +referencing old table as old_table new table as new_table +for each statement execute function trigger_function3(); +update convslot_test_parent set col1 = col1 || '1'; +NOTICE: trigger = but_trigger, new table = (111,tutu), (311,tutu) +NOTICE: trigger = but_trigger2, old_table = (11,tutu), (31,tutu), new table = (111,tutu), (311,tutu) +create trigger bdt_trigger after delete on convslot_test_child +referencing old table as old_table +for each statement execute function trigger_function1(); +delete from convslot_test_parent; +NOTICE: trigger = bdt_trigger, old_table = (111,tutu), (311,tutu) +drop table convslot_test_child, convslot_test_parent; diff --git a/src/test/regress/sql/triggers.sql b/src/test/regress/sql/triggers.sql index 4caa11d63b..f81ce18109 100644 --- a/src/test/regress/sql/triggers.sql +++ b/src/test/regress/sql/triggers.sql @@ -2296,3 +2296,68 @@ drop table self_ref; drop function dump_insert(); drop function dump_update(); drop function dump_delete(); + +-- verify transition table conversion slot's lifetime +-- https://postgr.es/m/39a71864-b120-5a5c-8cc5-c632b6f16761@amazon.com +create table convslot_test_parent (col1 text primary key); +create table convslot_test_child (col1 text primary key, + foreign key (col1) references convslot_test_parent(col1) on delete cascade on update cascade +); + +alter table convslot_test_child add column col2 text not null default 'tutu'; +insert into convslot_test_parent(col1) values ('1'); +insert into convslot_test_child(col1) values ('1'); +insert into convslot_test_parent(col1) values ('3'); +insert into convslot_test_child(col1) values ('3'); + +create or replace function trigger_function1() +returns trigger +language plpgsql +AS $$ +begin +raise notice 'trigger = %, old_table = %', + TG_NAME, + (select string_agg(old_table::text, ', ' order by col1) from old_table); +return null; +end; $$; + +create or replace function trigger_function2() +returns trigger +language plpgsql +AS $$ +begin +raise notice 'trigger = %, new table = %', + TG_NAME, + (select string_agg(new_table::text, ', ' order by col1) from new_table); +return null; +end; $$; + +create trigger but_trigger after update on convslot_test_child +referencing new table as new_table +for each statement execute function trigger_function2(); + +update convslot_test_parent set col1 = col1 || '1'; + +create or replace function trigger_function3() +returns trigger +language plpgsql +AS $$ +begin +raise notice 'trigger = %, old_table = %, new table = %', + TG_NAME, + (select string_agg(old_table::text, ', ' order by col1) from old_table), + (select string_agg(new_table::text, ', ' order by col1) from new_table); +return null; +end; $$; + +create trigger but_trigger2 after update on convslot_test_child +referencing old table as old_table new table as new_table +for each statement execute function trigger_function3(); +update convslot_test_parent set col1 = col1 || '1'; + +create trigger bdt_trigger after delete on convslot_test_child +referencing old table as old_table +for each statement execute function trigger_function1(); +delete from convslot_test_parent; + +drop table convslot_test_child, convslot_test_parent;