From 7b14e20b12cc8358cad9bdd05dd6b7de7f73c431 Mon Sep 17 00:00:00 2001 From: Dean Rasheed Date: Mon, 13 Mar 2023 11:09:39 +0000 Subject: [PATCH] Fix MERGE command tag for actions blocked by BEFORE ROW triggers. This ensures that the row count in the command tag for a MERGE is correctly computed in the case where UPDATEs or DELETEs are skipped due to a BEFORE ROW trigger returning NULL (the INSERT case was already handled correctly by ExecMergeNotMatched() calling ExecInsert()). Back-patch to v15, where MERGE was introduced. Discussion: https://postgr.es/m/CAEZATCU8XEmR0JWKDtyb7iZ%3DqCffxS9uyJt0iOZ4TV4RT%2Bow1w%40mail.gmail.com --- src/backend/executor/nodeModifyTable.c | 10 ++++++---- src/test/regress/expected/merge.out | 15 +++++++++++++++ src/test/regress/sql/merge.sql | 13 +++++++++++++ 3 files changed, 34 insertions(+), 4 deletions(-) diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index e8d655868a..3fa2b930a5 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -2883,8 +2883,9 @@ lmerge_matched: if (!ExecUpdatePrologue(context, resultRelInfo, tupleid, NULL, newslot, &result)) { - /* Blocked by trigger, or concurrent update/delete */ - break; + if (result == TM_Ok) + return true; /* "do nothing" */ + break; /* concurrent update/delete */ } result = ExecUpdateAct(context, resultRelInfo, tupleid, NULL, newslot, false, &updateCxt); @@ -2901,8 +2902,9 @@ lmerge_matched: if (!ExecDeletePrologue(context, resultRelInfo, tupleid, NULL, NULL, &result)) { - /* Blocked by trigger, or concurrent update/delete */ - break; + if (result == TM_Ok) + return true; /* "do nothing" */ + break; /* concurrent update/delete */ } result = ExecDeleteAct(context, resultRelInfo, tupleid, false); if (result == TM_Ok) diff --git a/src/test/regress/expected/merge.out b/src/test/regress/expected/merge.out index e1d363966c..e32afc3b0c 100644 --- a/src/test/regress/expected/merge.out +++ b/src/test/regress/expected/merge.out @@ -942,12 +942,25 @@ SELECT * FROM target full outer join source on (sid = tid); create trigger merge_skip BEFORE INSERT OR UPDATE or DELETE ON target FOR EACH ROW EXECUTE FUNCTION skip_merge_op(); +DO $$ +DECLARE + result integer; +BEGIN MERGE INTO target t USING source AS s ON t.tid = s.sid WHEN MATCHED AND s.sid = 3 THEN UPDATE SET balance = t.balance + s.delta WHEN MATCHED THEN DELETE WHEN NOT MATCHED THEN INSERT VALUES (sid, delta); +IF FOUND THEN + RAISE NOTICE 'Found'; +ELSE + RAISE NOTICE 'Not found'; +END IF; +GET DIAGNOSTICS result := ROW_COUNT; +RAISE NOTICE 'ROW_COUNT = %', result; +END; +$$; NOTICE: BEFORE INSERT STATEMENT trigger NOTICE: BEFORE UPDATE STATEMENT trigger NOTICE: BEFORE DELETE STATEMENT trigger @@ -957,6 +970,8 @@ NOTICE: BEFORE INSERT ROW trigger row: (4,40) NOTICE: AFTER DELETE STATEMENT trigger NOTICE: AFTER UPDATE STATEMENT trigger NOTICE: AFTER INSERT STATEMENT trigger +NOTICE: Not found +NOTICE: ROW_COUNT = 0 SELECT * FROM target FULL OUTER JOIN source ON (sid = tid); tid | balance | sid | delta -----+---------+-----+------- diff --git a/src/test/regress/sql/merge.sql b/src/test/regress/sql/merge.sql index f0854162a7..cae6902f2c 100644 --- a/src/test/regress/sql/merge.sql +++ b/src/test/regress/sql/merge.sql @@ -636,12 +636,25 @@ $$; SELECT * FROM target full outer join source on (sid = tid); create trigger merge_skip BEFORE INSERT OR UPDATE or DELETE ON target FOR EACH ROW EXECUTE FUNCTION skip_merge_op(); +DO $$ +DECLARE + result integer; +BEGIN MERGE INTO target t USING source AS s ON t.tid = s.sid WHEN MATCHED AND s.sid = 3 THEN UPDATE SET balance = t.balance + s.delta WHEN MATCHED THEN DELETE WHEN NOT MATCHED THEN INSERT VALUES (sid, delta); +IF FOUND THEN + RAISE NOTICE 'Found'; +ELSE + RAISE NOTICE 'Not found'; +END IF; +GET DIAGNOSTICS result := ROW_COUNT; +RAISE NOTICE 'ROW_COUNT = %', result; +END; +$$; SELECT * FROM target FULL OUTER JOIN source ON (sid = tid); DROP TRIGGER merge_skip ON target; DROP FUNCTION skip_merge_op();