Skip to content

Commit a4f7d33

Browse files
committed
Fix AFTER ROW trigger execution in MERGE cross-partition update.
When executing a MERGE UPDATE action, if the UPDATE is turned into a cross-partition DELETE then INSERT, do not attempt to invoke AFTER UPDATE ROW triggers, or any of the other post-update actions in ExecUpdateEpilogue(). For consistency with a plain UPDATE command, such triggers should not be fired (and typically fail anyway), and similarly, other post-update actions, such as WCO/RLS checks should not be executed, and might also lead to unexpected failures. Therefore, as with ExecUpdate(), make ExecMergeMatched() return immediately if ExecUpdateAct() reports that a cross-partition update was done, to be sure that no further processing is done for that tuple. Back-patch to v15, where MERGE was introduced. Discussion: https://postgr.es/m/CAEZATCWjBgagyNZs02vgDF0DvASYj-iHTFtXG2-nP3orZhmtcw%40mail.gmail.com
1 parent 10d34fe commit a4f7d33

File tree

3 files changed

+97
-0
lines changed

3 files changed

+97
-0
lines changed

src/backend/executor/nodeModifyTable.c

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2899,6 +2899,22 @@ ExecMergeMatched(ModifyTableContext *context, ResultRelInfo *resultRelInfo,
28992899
}
29002900
result = ExecUpdateAct(context, resultRelInfo, tupleid, NULL,
29012901
newslot, false, &updateCxt);
2902+
2903+
/*
2904+
* As in ExecUpdate(), if ExecUpdateAct() reports that a
2905+
* cross-partition update was done, then there's nothing else
2906+
* for us to do --- the UPDATE has been turned into a DELETE
2907+
* and an INSERT, and we must not perform any of the usual
2908+
* post-update tasks.
2909+
*/
2910+
if (updateCxt.crossPartUpdate)
2911+
{
2912+
mtstate->mt_merge_updated += 1;
2913+
if (canSetTag)
2914+
(estate->es_processed)++;
2915+
return true;
2916+
}
2917+
29022918
if (result == TM_Ok && updateCxt.updated)
29032919
{
29042920
ExecUpdateEpilogue(context, &updateCxt, resultRelInfo,

src/test/regress/expected/triggers.out

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2309,6 +2309,51 @@ NOTICE: trigger zzz on parted_trig_1_1 AFTER INSERT for ROW
23092309
NOTICE: trigger bbb on parted_trig_2 AFTER INSERT for ROW
23102310
NOTICE: trigger zzz on parted_trig_2 AFTER INSERT for ROW
23112311
drop table parted_trig;
2312+
-- Verify that the correct triggers fire for cross-partition updates
2313+
create table parted_trig (a int) partition by list (a);
2314+
create table parted_trig1 partition of parted_trig for values in (1);
2315+
create table parted_trig2 partition of parted_trig for values in (2);
2316+
insert into parted_trig values (1);
2317+
create or replace function trigger_notice() returns trigger as $$
2318+
begin
2319+
raise notice 'trigger % on % % % for %', TG_NAME, TG_TABLE_NAME, TG_WHEN, TG_OP, TG_LEVEL;
2320+
if TG_LEVEL = 'ROW' then
2321+
if TG_OP = 'DELETE' then
2322+
return OLD;
2323+
else
2324+
return NEW;
2325+
end if;
2326+
end if;
2327+
return null;
2328+
end;
2329+
$$ language plpgsql;
2330+
create trigger parted_trig_before_stmt before insert or update or delete on parted_trig
2331+
for each statement execute procedure trigger_notice();
2332+
create trigger parted_trig_before_row before insert or update or delete on parted_trig
2333+
for each row execute procedure trigger_notice();
2334+
create trigger parted_trig_after_row after insert or update or delete on parted_trig
2335+
for each row execute procedure trigger_notice();
2336+
create trigger parted_trig_after_stmt after insert or update or delete on parted_trig
2337+
for each statement execute procedure trigger_notice();
2338+
update parted_trig set a = 2 where a = 1;
2339+
NOTICE: trigger parted_trig_before_stmt on parted_trig BEFORE UPDATE for STATEMENT
2340+
NOTICE: trigger parted_trig_before_row on parted_trig1 BEFORE UPDATE for ROW
2341+
NOTICE: trigger parted_trig_before_row on parted_trig1 BEFORE DELETE for ROW
2342+
NOTICE: trigger parted_trig_before_row on parted_trig2 BEFORE INSERT for ROW
2343+
NOTICE: trigger parted_trig_after_row on parted_trig1 AFTER DELETE for ROW
2344+
NOTICE: trigger parted_trig_after_row on parted_trig2 AFTER INSERT for ROW
2345+
NOTICE: trigger parted_trig_after_stmt on parted_trig AFTER UPDATE for STATEMENT
2346+
-- update action in merge should behave the same
2347+
merge into parted_trig using (select 1) as ss on true
2348+
when matched and a = 2 then update set a = 1;
2349+
NOTICE: trigger parted_trig_before_stmt on parted_trig BEFORE UPDATE for STATEMENT
2350+
NOTICE: trigger parted_trig_before_row on parted_trig2 BEFORE UPDATE for ROW
2351+
NOTICE: trigger parted_trig_before_row on parted_trig2 BEFORE DELETE for ROW
2352+
NOTICE: trigger parted_trig_before_row on parted_trig1 BEFORE INSERT for ROW
2353+
NOTICE: trigger parted_trig_after_row on parted_trig2 AFTER DELETE for ROW
2354+
NOTICE: trigger parted_trig_after_row on parted_trig1 AFTER INSERT for ROW
2355+
NOTICE: trigger parted_trig_after_stmt on parted_trig AFTER UPDATE for STATEMENT
2356+
drop table parted_trig;
23122357
-- Verify propagation of trigger arguments to partitions
23132358
create table parted_trig (a int) partition by list (a);
23142359
create table parted_trig1 partition of parted_trig for values in (1);

src/test/regress/sql/triggers.sql

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1583,6 +1583,42 @@ create trigger qqq after insert on parted_trig_1_1 for each row execute procedur
15831583
insert into parted_trig values (50), (1500);
15841584
drop table parted_trig;
15851585

1586+
-- Verify that the correct triggers fire for cross-partition updates
1587+
create table parted_trig (a int) partition by list (a);
1588+
create table parted_trig1 partition of parted_trig for values in (1);
1589+
create table parted_trig2 partition of parted_trig for values in (2);
1590+
insert into parted_trig values (1);
1591+
1592+
create or replace function trigger_notice() returns trigger as $$
1593+
begin
1594+
raise notice 'trigger % on % % % for %', TG_NAME, TG_TABLE_NAME, TG_WHEN, TG_OP, TG_LEVEL;
1595+
if TG_LEVEL = 'ROW' then
1596+
if TG_OP = 'DELETE' then
1597+
return OLD;
1598+
else
1599+
return NEW;
1600+
end if;
1601+
end if;
1602+
return null;
1603+
end;
1604+
$$ language plpgsql;
1605+
create trigger parted_trig_before_stmt before insert or update or delete on parted_trig
1606+
for each statement execute procedure trigger_notice();
1607+
create trigger parted_trig_before_row before insert or update or delete on parted_trig
1608+
for each row execute procedure trigger_notice();
1609+
create trigger parted_trig_after_row after insert or update or delete on parted_trig
1610+
for each row execute procedure trigger_notice();
1611+
create trigger parted_trig_after_stmt after insert or update or delete on parted_trig
1612+
for each statement execute procedure trigger_notice();
1613+
1614+
update parted_trig set a = 2 where a = 1;
1615+
1616+
-- update action in merge should behave the same
1617+
merge into parted_trig using (select 1) as ss on true
1618+
when matched and a = 2 then update set a = 1;
1619+
1620+
drop table parted_trig;
1621+
15861622
-- Verify propagation of trigger arguments to partitions
15871623
create table parted_trig (a int) partition by list (a);
15881624
create table parted_trig1 partition of parted_trig for values in (1);

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy