v2-0001-Fix-MERGE-match-do-update-in-RR-isolation-level.patch

text/plain

Filename: v2-0001-Fix-MERGE-match-do-update-in-RR-isolation-level.patch
Type: text/plain
Part: 0
Message: Re: MERGE behavior with REPEATABLE READ isolation level

Patch

Format: format-patch
Series: patch v2-0001
Subject: Fix MERGE match do update in RR isolation level.
File+
src/backend/executor/nodeModifyTable.c 4 0
src/test/isolation/expected/merge-update.out 33 0
src/test/isolation/specs/merge-update.spec 2 0
From 6a8dadb846338648e289ae3ac7e90eba53e35564 Mon Sep 17 00:00:00 2001
From: Tender Wang <tndrwang@gmail.com>
Date: Tue, 24 Feb 2026 11:04:26 +0800
Subject: [PATCH v2] Fix MERGE match do update in RR isolation level.

---
 src/backend/executor/nodeModifyTable.c       |  4 +++
 src/test/isolation/expected/merge-update.out | 33 ++++++++++++++++++++
 src/test/isolation/specs/merge-update.spec   |  2 ++
 3 files changed, 39 insertions(+)

diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 793c76d4f82..8d8c9fb54e3 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -3547,6 +3547,10 @@ 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
diff --git a/src/test/isolation/expected/merge-update.out b/src/test/isolation/expected/merge-update.out
index feceacf4818..821565b4303 100644
--- a/src/test/isolation/expected/merge-update.out
+++ b/src/test/isolation/expected/merge-update.out
@@ -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(), old, new, t.*;
+
+merge_action|old       |new                            |key|val                      
+------------+----------+-------------------------------+---+-------------------------
+UPDATE      |(1,setup1)|(2,"setup1 updated by merge2a")|  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
diff --git a/src/test/isolation/specs/merge-update.spec b/src/test/isolation/specs/merge-update.spec
index 771ee5b70cf..b902779edd6 100644
--- a/src/test/isolation/specs/merge-update.spec
+++ b/src/test/isolation/specs/merge-update.spec
@@ -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"
-- 
2.34.1