Fix MERGE command tag for actions blocked by BEFORE ROW triggers.
authorDean Rasheed <dean.a.rasheed@gmail.com>
Mon, 13 Mar 2023 11:09:39 +0000 (11:09 +0000)
committerDean Rasheed <dean.a.rasheed@gmail.com>
Mon, 13 Mar 2023 11:12:20 +0000 (11:12 +0000)
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
src/test/regress/expected/merge.out
src/test/regress/sql/merge.sql

index e8d655868ae2724c645136d39a678b57f55ea16a..3fa2b930a525b59c23a60d1e3b59f461dcfee550 100644 (file)
@@ -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)
index e1d363966c798e62d6e0c18106009b631136473a..e32afc3b0c0fb42fd630cc85aaa084bfce74f82b 100644 (file)
@@ -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 
 -----+---------+-----+-------
index f0854162a792c9bf28b1ced0b35670fde3ec07fe..cae6902f2c327773a2e876d368625e8da779bb51 100644 (file)
@@ -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();