]> git.ipfire.org Git - thirdparty/postgresql.git/commitdiff
Fix handling of updated tuples in the MERGE statement
authorAlexander Korotkov <akorotkov@postgresql.org>
Thu, 5 Mar 2026 17:47:20 +0000 (19:47 +0200)
committerAlexander Korotkov <akorotkov@postgresql.org>
Thu, 5 Mar 2026 17:56:42 +0000 (19:56 +0200)
This branch missed the IsolationUsesXactSnapshot() check.  That led to EPQ on
repeatable read and serializable isolation levels.  This commit fixes the
issue and provides a simple isolation check for that.  Backpatch through v15
where MERGE statement was introduced.

Reported-by: Tender Wang <tndrwang@gmail.com>
Discussion: https://postgr.es/m/CAPpHfdvzZSaNYdj5ac-tYRi6MuuZnYHiUkZ3D-AoY-ny8v%2BS%2Bw%40mail.gmail.com
Author: Tender Wang <tndrwang@gmail.com>
Reviewed-by: Dean Rasheed <dean.a.rasheed@gmail.com>
Backpatch-through: 15

src/backend/executor/nodeModifyTable.c
src/test/isolation/expected/merge-update.out
src/test/isolation/specs/merge-update.spec

index 4719cc9e840c616a013ce65483166d8144ec5131..14795857a7aab1c15865059b1eb6a488850ca458 100644 (file)
@@ -3178,6 +3178,11 @@ lmerge_matched:
                                                           *inputslot;
                                        LockTupleMode lockmode;
 
+                                       if (IsolationUsesXactSnapshot())
+                                               ereport(ERROR,
+                                                               (errcode(ERRCODE_T_R_SERIALIZATION_FAILURE),
+                                                                errmsg("could not serialize access due to concurrent update")));
+
                                        /*
                                         * The target tuple was concurrently updated by some other
                                         * transaction.  If we are currently processing a MATCHED
index 22a32e93b04da8a2ffa62eecb84224b865058864..dd6a9f6c6ca31adacebac1bd2b967cbb3870626a 100644 (file)
@@ -549,3 +549,36 @@ step c1: COMMIT;
 step pa_merge2c_dup: <... completed>
 ERROR:  MERGE command cannot affect row a second time
 step a2: ABORT;
+
+starting permutation: merge2a c1 s1beginrr merge1 c2
+step merge2a: 
+  MERGE INTO target t
+  USING (SELECT 1 as key, 'merge2a' as val) s
+  ON s.key = t.key
+  WHEN NOT MATCHED THEN
+       INSERT VALUES (s.key, s.val)
+  WHEN MATCHED THEN
+       UPDATE set key = t.key + 1, val = t.val || ' updated by ' || s.val
+  WHEN NOT MATCHED BY SOURCE THEN
+       UPDATE set key = t.key + 1, val = t.val || ' source not matched by merge2a'
+  RETURNING merge_action(), t.*;
+
+merge_action|key|val                      
+------------+---+-------------------------
+UPDATE      |  2|setup1 updated by merge2a
+(1 row)
+
+step c1: COMMIT;
+step s1beginrr: BEGIN ISOLATION LEVEL REPEATABLE READ;
+step merge1: 
+  MERGE INTO target t
+  USING (SELECT 1 as key, 'merge1' as val) s
+  ON s.key = t.key
+  WHEN NOT MATCHED THEN
+       INSERT VALUES (s.key, s.val)
+  WHEN MATCHED THEN
+    UPDATE set key = t.key + 1, val = t.val || ' updated by ' || s.val;
+ <waiting ...>
+step c2: COMMIT;
+step merge1: <... completed>
+ERROR:  could not serialize access due to concurrent update
index d3f031b15741a5237d66cad743aafd6d4856c4d3..36835ef345d9f67da4a39d12378bfc2eaabb1402 100644 (file)
@@ -93,6 +93,7 @@ step "pa_merge3"
 }
 step "c1" { COMMIT; }
 step "a1" { ABORT; }
+step "s1beginrr" { BEGIN ISOLATION LEVEL REPEATABLE READ; }
 
 session "s2"
 setup
@@ -223,3 +224,4 @@ permutation "pa_merge2" "c1" "pa_merge2a" "pa_select2" "c2" # succeeds
 permutation "pa_merge3" "pa_merge2b_when" "c1" "pa_select2" "c2" # WHEN not satisfied by updated tuple
 permutation "pa_merge1" "pa_merge2b_when" "c1" "pa_select2" "c2" # WHEN satisfied by updated tuple
 permutation "pa_merge1" "pa_merge2c_dup" "c1" "a2"
+permutation "merge2a" "c1" "s1beginrr" "merge1" "c2"