From ce09cd2b960d489f0b2c9ffc7abf1a54c774efc5 Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Fri, 14 Jun 2024 22:16:54 +0800
Subject: [PATCH v2 2/2] make SJE to apply DML

make SJE to apply DML (MERGE, UPDATE, INSERT, DELETE).

replace_varno and replace_varno_walker didn't replace
Query->resultRelation, Query->mergeTargetRelation
as ChangeVarNodes did.
So replace_varno will have problem with DELETE, UPDATE, MERGE.
ChangeVarNodes solved this problem.

* from the INSERT synopsis, we can see that  INSERT cannot join with another table,
so SJE cannot be used.

DELETE/UPDATE
excerpt from the synopsis:

UPDATE table_name FROM from_item
DELETE FROM table_name USING from_item

as you can see UPDATE/DELETE, the join order is fixed. so we only need to check
if the table_name is totally replaced by from_item or not.

MERGE command: (merge_insert, merge_update, merge_delete).

for merge_insert: SJE cannot to applicable. it will out in follow code,
for which, i cannot fully explain the reasoning:
```
			/*
			 * It is impossible to eliminate join of two relations if they
			 * belong to different rules of order. Otherwise planner can't be
			 * able to find any variants of correct query plan.
			 */
			foreach(lc, root->join_info_list)
			{
				SpecialJoinInfo *info = (SpecialJoinInfo *) lfirst(lc);

				if ((bms_is_member(k, info->syn_lefthand) ^
					 bms_is_member(r, info->syn_lefthand)) ||
					(bms_is_member(k, info->syn_righthand) ^
					 bms_is_member(r, info->syn_righthand)))
				{
					jinfo_check = false;
					break;
				}
			}
			if (!jinfo_check)
				continue;
```
merge_delete and merge_update works fine with base table and updateable view.
but SJE cannot work with non-updatable view.
---
 src/backend/optimizer/plan/analyzejoins.c     |  21 +-
 src/test/regress/expected/join.out            | 309 +++++++++++++++++-
 src/test/regress/expected/updatable_views.out |  17 +-
 src/test/regress/sql/join.sql                 |  97 ++++++
 4 files changed, 401 insertions(+), 43 deletions(-)

diff --git a/src/backend/optimizer/plan/analyzejoins.c b/src/backend/optimizer/plan/analyzejoins.c
index bb145977..eab39ee0 100644
--- a/src/backend/optimizer/plan/analyzejoins.c
+++ b/src/backend/optimizer/plan/analyzejoins.c
@@ -1860,10 +1860,6 @@ remove_self_join_rel(PlannerInfo *root, PlanRowMark *kmark, PlanRowMark *rmark,
 	/* restore the rangetblref in a proper order. */
 	restore_rangetblref((Node *) root->parse, toKeep->relid, toRemove->relid, 0, 0);
 
-	/* See remove_self_joins_one_group() */
-	Assert(root->parse->resultRelation != toRemove->relid);
-	Assert(root->parse->resultRelation != toKeep->relid);
-
 	/* Replace links in the planner info */
 	remove_rel_from_query(root, toRemove, toKeep->relid, NULL, NULL);
 
@@ -2046,14 +2042,6 @@ remove_self_joins_one_group(PlannerInfo *root, Relids relids)
 	{
 		RelOptInfo *inner = root->simple_rel_array[r];
 
-		/*
-		 * We don't accept result relation as either source or target relation
-		 * of SJE, because result relation has different behavior in
-		 * EvalPlanQual() and RETURNING clause.
-		 */
-		if (root->parse->resultRelation == r)
-			continue;
-
 		k = r;
 
 		while ((k = bms_next_member(relids, k)) > 0)
@@ -2069,9 +2057,6 @@ remove_self_joins_one_group(PlannerInfo *root, Relids relids)
 			PlanRowMark *imark = NULL;
 			List	   *uclauses = NIL;
 
-			if (root->parse->resultRelation == k)
-				continue;
-
 			/* A sanity check: the relations have the same Oid. */
 			Assert(root->simple_rte_array[k]->relid ==
 				   root->simple_rte_array[r]->relid);
@@ -2391,6 +2376,12 @@ remove_useless_self_joins(PlannerInfo *root, List *joinlist)
 		(list_length(joinlist) == 1 && !IsA(linitial(joinlist), List)))
 		return joinlist;
 
+	/*
+	 * we don't accept RETURNING clause with SJE.
+	*/
+	if (root->parse && root->parse->returningList != NIL)
+		return joinlist;
+
 	/*
 	 * Merge pairs of relations participated in self-join. Remove unnecessary
 	 * range table entries.
diff --git a/src/test/regress/expected/join.out b/src/test/regress/expected/join.out
index 16327779..6209854a 100644
--- a/src/test/regress/expected/join.out
+++ b/src/test/regress/expected/join.out
@@ -6385,6 +6385,285 @@ on true;
                ->  Seq Scan on int8_tbl y
 (7 rows)
 
+------------------- UPDATE SJE (self join elimination) applicable cases.
+EXPLAIN (COSTS OFF) UPDATE sj sq SET b = 1 FROM sj as sz WHERE sz.a = sq.a and (sq.b = 2 or sq.a = 2);
+                         QUERY PLAN                         
+------------------------------------------------------------
+ Update on sj sz
+   ->  Seq Scan on sj sz
+         Filter: ((a IS NOT NULL) AND ((b = 2) OR (a = 2)))
+(3 rows)
+
+EXPLAIN (COSTS OFF) WITH t1 AS (SELECT * FROM sj) UPDATE sj SET a = sj.a + 1 FROM t1 WHERE t1.b = sj.b and t1.a = 3 and sj.a = 3;
+                  QUERY PLAN                   
+-----------------------------------------------
+ Update on sj
+   ->  Seq Scan on sj
+         Filter: ((b IS NOT NULL) AND (a = 3))
+(3 rows)
+
+EXPLAIN (COSTS OFF) WITH t1 AS (SELECT *, (select count(*) from tenk1) FROM sj) UPDATE sj SET a = sj.a + 1 FROM t1 WHERE t1.a = sj.a;
+           QUERY PLAN            
+---------------------------------
+ Update on sj
+   ->  Seq Scan on sj
+         Filter: (a IS NOT NULL)
+(3 rows)
+
+------------------- UPDATE SJE not applicable cases.
+EXPLAIN (COSTS OFF) UPDATE sj sq SET b = 1 FROM sj as sz WHERE sz.a = sq.a or (sq.b = 2 or sq.a = 2);
+                            QUERY PLAN                            
+------------------------------------------------------------------
+ Update on sj sq
+   ->  Nested Loop
+         Join Filter: ((sz.a = sq.a) OR (sq.b = 2) OR (sq.a = 2))
+         ->  Seq Scan on sj sq
+         ->  Materialize
+               ->  Seq Scan on sj sz
+(6 rows)
+
+EXPLAIN (COSTS OFF) UPDATE sj sq SET b = 1 FROM sj as sz WHERE sq.a = sz.b and sq.b = sz.b and sz.b = sq.a;
+             QUERY PLAN             
+------------------------------------
+ Update on sj sq
+   ->  Nested Loop
+         Join Filter: (sq.a = sz.b)
+         ->  Seq Scan on sj sq
+               Filter: (a = b)
+         ->  Seq Scan on sj sz
+(6 rows)
+
+EXPLAIN (COSTS OFF) UPDATE sj sq SET b = 1 FROM sj as sz WHERE sq.b = sz.b and sz.a = 2 and sq.a = 3;
+             QUERY PLAN             
+------------------------------------
+ Update on sj sq
+   ->  Nested Loop
+         Join Filter: (sq.b = sz.b)
+         ->  Seq Scan on sj sq
+               Filter: (a = 3)
+         ->  Seq Scan on sj sz
+               Filter: (a = 2)
+(7 rows)
+
+EXPLAIN (COSTS OFF) WITH t1 AS (SELECT * FROM sj) UPDATE sj SET a = sj.a + 1 FROM t1 WHERE t1.b = sj.b and t1.a = 2 and sj.a = 3;
+              QUERY PLAN              
+--------------------------------------
+ Update on sj
+   ->  Nested Loop
+         Join Filter: (sj.b = sj_1.b)
+         ->  Seq Scan on sj
+               Filter: (a = 3)
+         ->  Seq Scan on sj sj_1
+               Filter: (a = 2)
+(7 rows)
+
+------------------- DELETE SJE applicable cases.
+EXPLAIN (COSTS OFF) DELETE FROM sj sq using sj as sz WHERE sz.a = sq.a;
+           QUERY PLAN            
+---------------------------------
+ Delete on sj sz
+   ->  Seq Scan on sj sz
+         Filter: (a IS NOT NULL)
+(3 rows)
+
+EXPLAIN (COSTS OFF) DELETE FROM sj sq using sj as sz WHERE sz.a = sq.a and (sz.a = 2 or sz.b =3);
+                         QUERY PLAN                         
+------------------------------------------------------------
+ Delete on sj sz
+   ->  Seq Scan on sj sz
+         Filter: ((a IS NOT NULL) AND ((a = 2) OR (b = 3)))
+(3 rows)
+
+EXPLAIN (COSTS OFF) WITH t1 AS (SELECT * FROM sj) DELETE FROM sj sq USING sj as t1 WHERE t1.b = sq.b and t1.a = 3 and sq.a = 3;
+                  QUERY PLAN                   
+-----------------------------------------------
+ Delete on sj t1
+   ->  Seq Scan on sj t1
+         Filter: ((b IS NOT NULL) AND (a = 3))
+(3 rows)
+
+EXPLAIN (COSTS OFF) WITH t1 AS (SELECT *, (select count(*) from sj) FROM sj) DELETE FROM sj sq using t1 as sz where sq.a = sz.a;
+           QUERY PLAN            
+---------------------------------
+ Delete on sj
+   ->  Seq Scan on sj
+         Filter: (a IS NOT NULL)
+(3 rows)
+
+------------------- DELETE SJE not applicable cases.
+EXPLAIN (COSTS OFF) DELETE FROM sj sq USING sj as sz WHERE sq.a = sz.b and sq.b = sz.b and sz.b = sq.a;
+             QUERY PLAN             
+------------------------------------
+ Delete on sj sq
+   ->  Nested Loop
+         Join Filter: (sq.a = sz.b)
+         ->  Seq Scan on sj sq
+               Filter: (a = b)
+         ->  Seq Scan on sj sz
+(6 rows)
+
+EXPLAIN (COSTS OFF) DELETE FROM sj sq USING sj as sz WHERE sq.b = sz.b and sz.a = 2 and sq.a = 3;
+             QUERY PLAN             
+------------------------------------
+ Delete on sj sq
+   ->  Nested Loop
+         Join Filter: (sq.b = sz.b)
+         ->  Seq Scan on sj sq
+               Filter: (a = 3)
+         ->  Seq Scan on sj sz
+               Filter: (a = 2)
+(7 rows)
+
+EXPLAIN (COSTS OFF) WITH t1 AS (SELECT * FROM sj) DELETE FROM sj sq USING sj as t1 WHERE t1.b = sq.b and t1.a = 3;
+             QUERY PLAN             
+------------------------------------
+ Delete on sj sq
+   ->  Nested Loop
+         Join Filter: (sq.b = t1.b)
+         ->  Seq Scan on sj t1
+               Filter: (a = 3)
+         ->  Seq Scan on sj sq
+(6 rows)
+
+EXPLAIN (COSTS OFF) WITH t1 AS (SELECT * FROM sj) DELETE FROM sj sq USING sj as t1 WHERE t1.a = sq.c;
+             QUERY PLAN              
+-------------------------------------
+ Delete on sj sq
+   ->  Nested Loop
+         Join Filter: (sq.c = t1.a)
+         ->  Seq Scan on sj sq
+         ->  Materialize
+               ->  Seq Scan on sj t1
+(6 rows)
+
+------------------- MERGE SJE table setup
+CREATE TABLE sj_target (tid integer primary key, balance integer) WITH (autovacuum_enabled=off);
+INSERT INTO sj_target VALUES (1, 10),(2, 20), (3, 30), (4, 40),(5, 50), (6, 60);
+create view rw_sj_target as select * from sj_target where tid >= 2;
+create or replace view no_rw_sj_target as select * from sj_target where balance = 60 limit 1;
+--cannot use SJE for RETURNING
+EXPLAIN (COSTS OFF) MERGE INTO sj_target t USING sj_target AS s ON t.tid = s.tid
+    WHEN MATCHED AND t.balance = 10
+    THEN update set balance = t.balance + 11 RETURNING *;
+                         QUERY PLAN                         
+------------------------------------------------------------
+ Merge on sj_target t
+   ->  Nested Loop
+         ->  Seq Scan on sj_target t
+         ->  Index Scan using sj_target_pkey on sj_target s
+               Index Cond: (tid = t.tid)
+(5 rows)
+
+--cannot use SJE for merge insert
+EXPLAIN (COSTS OFF) MERGE INTO sj_target t USING sj_target AS s ON t.tid = s.tid
+    WHEN NOT MATCHED AND s.balance = 10
+    THEN INSERT VALUES (s.tid, s.balance);
+                         QUERY PLAN                         
+------------------------------------------------------------
+ Merge on sj_target t
+   ->  Nested Loop Left Join
+         ->  Seq Scan on sj_target s
+         ->  Index Scan using sj_target_pkey on sj_target t
+               Index Cond: (tid = s.tid)
+(5 rows)
+
+--cannot use SJE for merge with non-updatable view
+EXPLAIN (COSTS OFF) MERGE INTO sj_target t USING no_rw_sj_target AS s ON t.tid = s.tid
+    WHEN MATCHED AND t.balance = 60
+    THEN update set balance = t.balance + 6;
+                         QUERY PLAN                         
+------------------------------------------------------------
+ Merge on sj_target t
+   ->  Nested Loop
+         ->  Subquery Scan on s
+               ->  Limit
+                     ->  Seq Scan on sj_target
+                           Filter: (balance = 60)
+         ->  Index Scan using sj_target_pkey on sj_target t
+               Index Cond: (tid = s.tid)
+(8 rows)
+
+---- the following cases can apply SJE with merge.
+EXPLAIN (COSTS OFF) MERGE INTO rw_sj_target t USING sj_target AS s ON t.tid = s.tid
+    WHEN MATCHED AND t.balance = 20
+    THEN update set balance = t.balance + 2;
+                   QUERY PLAN                    
+-------------------------------------------------
+ Merge on sj_target
+   ->  Bitmap Heap Scan on sj_target
+         Recheck Cond: (tid >= 2)
+         ->  Bitmap Index Scan on sj_target_pkey
+               Index Cond: (tid >= 2)
+(5 rows)
+
+EXPLAIN (COSTS OFF) MERGE INTO rw_sj_target t USING sj_target AS s ON t.tid = s.tid
+    WHEN MATCHED AND t.balance = 10
+    THEN update set balance = t.balance + 2;
+                   QUERY PLAN                    
+-------------------------------------------------
+ Merge on sj_target
+   ->  Bitmap Heap Scan on sj_target
+         Recheck Cond: (tid >= 2)
+         ->  Bitmap Index Scan on sj_target_pkey
+               Index Cond: (tid >= 2)
+(5 rows)
+
+EXPLAIN (COSTS OFF) MERGE INTO rw_sj_target t USING rw_sj_target AS s ON t.tid = s.tid
+    WHEN MATCHED AND t.balance = 30
+    THEN update set balance = t.balance + 3;
+                   QUERY PLAN                    
+-------------------------------------------------
+ Merge on sj_target
+   ->  Bitmap Heap Scan on sj_target
+         Recheck Cond: (tid >= 2)
+         ->  Bitmap Index Scan on sj_target_pkey
+               Index Cond: (tid >= 2)
+(5 rows)
+
+EXPLAIN (COSTS OFF) MERGE INTO sj_target t USING sj_target AS s ON t.tid = s.tid
+    WHEN MATCHED AND t.balance = 40
+    THEN update set balance = t.balance + 4;
+          QUERY PLAN           
+-------------------------------
+ Merge on sj_target s
+   ->  Seq Scan on sj_target s
+(2 rows)
+
+EXPLAIN (COSTS OFF) MERGE INTO sj_target t USING sj_target AS s ON t.tid = s.tid
+    WHEN MATCHED AND t.balance = 50
+    THEN DELETE;
+          QUERY PLAN           
+-------------------------------
+ Merge on sj_target s
+   ->  Seq Scan on sj_target s
+(2 rows)
+
+--- and run the actual query.
+MERGE INTO sj_target t USING no_rw_sj_target AS s ON t.tid = s.tid
+    WHEN MATCHED AND t.balance = 60 THEN update set balance = t.balance + 6;
+MERGE INTO rw_sj_target t USING sj_target AS s ON t.tid = s.tid
+    WHEN MATCHED AND t.balance = 20 THEN update set balance = t.balance + 2;
+MERGE INTO rw_sj_target t USING rw_sj_target AS s ON t.tid = s.tid
+    WHEN MATCHED AND t.balance = 30 THEN update set balance = t.balance + 3;
+MERGE INTO sj_target t USING sj_target AS s ON t.tid = s.tid
+    WHEN MATCHED AND t.balance = 40 THEN update set balance = t.balance + 4;
+MERGE INTO sj_target t USING sj_target AS s ON t.tid = s.tid
+    WHEN MATCHED AND t.balance = 50 THEN DELETE;
+MERGE INTO rw_sj_target t USING sj_target AS s ON t.tid = s.tid
+    WHEN MATCHED AND t.balance = 10 THEN update set balance = t.balance + 1;
+select * from sj_target order by tid;
+ tid | balance 
+-----+---------
+   1 |      10
+   2 |      22
+   3 |      33
+   4 |      44
+   6 |      66
+(5 rows)
+
+DROP VIEW no_rw_sj_target;
+DROP VIEW rw_sj_target;
+DROP TABLE sj_target;
 -- Test that references to the removed rel in lateral subqueries are replaced
 -- correctly after join removal
 explain (verbose, costs off)
@@ -7003,29 +7282,23 @@ WHERE t1.id = emp1.id RETURNING emp1.id, emp1.code, t1.code;
 TRUNCATE emp1;
 EXPLAIN (COSTS OFF)
 UPDATE sj sq SET b = 1 FROM sj as sz WHERE sq.a = sz.a;
-             QUERY PLAN              
--------------------------------------
- Update on sj sq
-   ->  Nested Loop
-         Join Filter: (sq.a = sz.a)
-         ->  Seq Scan on sj sq
-         ->  Materialize
-               ->  Seq Scan on sj sz
-(6 rows)
+           QUERY PLAN            
+---------------------------------
+ Update on sj sz
+   ->  Seq Scan on sj sz
+         Filter: (a IS NOT NULL)
+(3 rows)
 
 CREATE RULE sj_del_rule AS ON DELETE TO sj
   DO INSTEAD
     UPDATE sj SET a = 1 WHERE a = old.a;
 EXPLAIN (COSTS OFF) DELETE FROM sj;
-              QUERY PLAN              
---------------------------------------
- Update on sj sj_1
-   ->  Nested Loop
-         Join Filter: (sj.a = sj_1.a)
-         ->  Seq Scan on sj sj_1
-         ->  Materialize
-               ->  Seq Scan on sj
-(6 rows)
+           QUERY PLAN            
+---------------------------------
+ Update on sj
+   ->  Seq Scan on sj
+         Filter: (a IS NOT NULL)
+(3 rows)
 
 DROP RULE sj_del_rule ON sj CASCADE;
 -- Check that SJE does not mistakenly omit qual clauses (bug #18187)
diff --git a/src/test/regress/expected/updatable_views.out b/src/test/regress/expected/updatable_views.out
index 1d1f568b..dd5b3cd4 100644
--- a/src/test/regress/expected/updatable_views.out
+++ b/src/test/regress/expected/updatable_views.out
@@ -3111,16 +3111,13 @@ SELECT * FROM rw_view1;
 (1 row)
 
 EXPLAIN (costs off) DELETE FROM rw_view1 WHERE id = 1 AND snoop(data);
-                            QUERY PLAN                             
--------------------------------------------------------------------
- Update on base_tbl base_tbl_1
-   ->  Nested Loop
-         ->  Index Scan using base_tbl_pkey on base_tbl base_tbl_1
-               Index Cond: (id = 1)
-         ->  Index Scan using base_tbl_pkey on base_tbl
-               Index Cond: (id = 1)
-               Filter: ((NOT deleted) AND snoop(data))
-(7 rows)
+                    QUERY PLAN                    
+--------------------------------------------------
+ Update on base_tbl
+   ->  Index Scan using base_tbl_pkey on base_tbl
+         Index Cond: (id = 1)
+         Filter: ((NOT deleted) AND snoop(data))
+(4 rows)
 
 DELETE FROM rw_view1 WHERE id = 1 AND snoop(data);
 NOTICE:  snooped value: Row 1
diff --git a/src/test/regress/sql/join.sql b/src/test/regress/sql/join.sql
index 0cc6c692..c1d7144c 100644
--- a/src/test/regress/sql/join.sql
+++ b/src/test/regress/sql/join.sql
@@ -2426,6 +2426,103 @@ left join (select coalesce(y.q1, 1) from int8_tbl y
 	on true) z
 on true;
 
+------------------- UPDATE SJE (self join elimination) applicable cases.
+EXPLAIN (COSTS OFF) UPDATE sj sq SET b = 1 FROM sj as sz WHERE sz.a = sq.a and (sq.b = 2 or sq.a = 2);
+
+EXPLAIN (COSTS OFF) WITH t1 AS (SELECT * FROM sj) UPDATE sj SET a = sj.a + 1 FROM t1 WHERE t1.b = sj.b and t1.a = 3 and sj.a = 3;
+
+EXPLAIN (COSTS OFF) WITH t1 AS (SELECT *, (select count(*) from tenk1) FROM sj) UPDATE sj SET a = sj.a + 1 FROM t1 WHERE t1.a = sj.a;
+
+
+------------------- UPDATE SJE not applicable cases.
+EXPLAIN (COSTS OFF) UPDATE sj sq SET b = 1 FROM sj as sz WHERE sz.a = sq.a or (sq.b = 2 or sq.a = 2);
+
+EXPLAIN (COSTS OFF) UPDATE sj sq SET b = 1 FROM sj as sz WHERE sq.a = sz.b and sq.b = sz.b and sz.b = sq.a;
+
+EXPLAIN (COSTS OFF) UPDATE sj sq SET b = 1 FROM sj as sz WHERE sq.b = sz.b and sz.a = 2 and sq.a = 3;
+
+EXPLAIN (COSTS OFF) WITH t1 AS (SELECT * FROM sj) UPDATE sj SET a = sj.a + 1 FROM t1 WHERE t1.b = sj.b and t1.a = 2 and sj.a = 3;
+
+------------------- DELETE SJE applicable cases.
+EXPLAIN (COSTS OFF) DELETE FROM sj sq using sj as sz WHERE sz.a = sq.a;
+
+EXPLAIN (COSTS OFF) DELETE FROM sj sq using sj as sz WHERE sz.a = sq.a and (sz.a = 2 or sz.b =3);
+
+EXPLAIN (COSTS OFF) WITH t1 AS (SELECT * FROM sj) DELETE FROM sj sq USING sj as t1 WHERE t1.b = sq.b and t1.a = 3 and sq.a = 3;
+
+EXPLAIN (COSTS OFF) WITH t1 AS (SELECT *, (select count(*) from sj) FROM sj) DELETE FROM sj sq using t1 as sz where sq.a = sz.a;
+
+
+------------------- DELETE SJE not applicable cases.
+EXPLAIN (COSTS OFF) DELETE FROM sj sq USING sj as sz WHERE sq.a = sz.b and sq.b = sz.b and sz.b = sq.a;
+
+EXPLAIN (COSTS OFF) DELETE FROM sj sq USING sj as sz WHERE sq.b = sz.b and sz.a = 2 and sq.a = 3;
+
+EXPLAIN (COSTS OFF) WITH t1 AS (SELECT * FROM sj) DELETE FROM sj sq USING sj as t1 WHERE t1.b = sq.b and t1.a = 3;
+
+EXPLAIN (COSTS OFF) WITH t1 AS (SELECT * FROM sj) DELETE FROM sj sq USING sj as t1 WHERE t1.a = sq.c;
+
+------------------- MERGE SJE table setup
+CREATE TABLE sj_target (tid integer primary key, balance integer) WITH (autovacuum_enabled=off);
+INSERT INTO sj_target VALUES (1, 10),(2, 20), (3, 30), (4, 40),(5, 50), (6, 60);
+create view rw_sj_target as select * from sj_target where tid >= 2;
+create or replace view no_rw_sj_target as select * from sj_target where balance = 60 limit 1;
+
+--cannot use SJE for RETURNING
+EXPLAIN (COSTS OFF) MERGE INTO sj_target t USING sj_target AS s ON t.tid = s.tid
+    WHEN MATCHED AND t.balance = 10
+    THEN update set balance = t.balance + 11 RETURNING *;
+--cannot use SJE for merge insert
+EXPLAIN (COSTS OFF) MERGE INTO sj_target t USING sj_target AS s ON t.tid = s.tid
+    WHEN NOT MATCHED AND s.balance = 10
+    THEN INSERT VALUES (s.tid, s.balance);
+
+--cannot use SJE for merge with non-updatable view
+EXPLAIN (COSTS OFF) MERGE INTO sj_target t USING no_rw_sj_target AS s ON t.tid = s.tid
+    WHEN MATCHED AND t.balance = 60
+    THEN update set balance = t.balance + 6;
+
+---- the following cases can apply SJE with merge.
+EXPLAIN (COSTS OFF) MERGE INTO rw_sj_target t USING sj_target AS s ON t.tid = s.tid
+    WHEN MATCHED AND t.balance = 20
+    THEN update set balance = t.balance + 2;
+EXPLAIN (COSTS OFF) MERGE INTO rw_sj_target t USING sj_target AS s ON t.tid = s.tid
+    WHEN MATCHED AND t.balance = 10
+    THEN update set balance = t.balance + 2;
+EXPLAIN (COSTS OFF) MERGE INTO rw_sj_target t USING rw_sj_target AS s ON t.tid = s.tid
+    WHEN MATCHED AND t.balance = 30
+    THEN update set balance = t.balance + 3;
+EXPLAIN (COSTS OFF) MERGE INTO sj_target t USING sj_target AS s ON t.tid = s.tid
+    WHEN MATCHED AND t.balance = 40
+    THEN update set balance = t.balance + 4;
+EXPLAIN (COSTS OFF) MERGE INTO sj_target t USING sj_target AS s ON t.tid = s.tid
+    WHEN MATCHED AND t.balance = 50
+    THEN DELETE;
+
+--- and run the actual query.
+MERGE INTO sj_target t USING no_rw_sj_target AS s ON t.tid = s.tid
+    WHEN MATCHED AND t.balance = 60 THEN update set balance = t.balance + 6;
+
+MERGE INTO rw_sj_target t USING sj_target AS s ON t.tid = s.tid
+    WHEN MATCHED AND t.balance = 20 THEN update set balance = t.balance + 2;
+
+MERGE INTO rw_sj_target t USING rw_sj_target AS s ON t.tid = s.tid
+    WHEN MATCHED AND t.balance = 30 THEN update set balance = t.balance + 3;
+
+MERGE INTO sj_target t USING sj_target AS s ON t.tid = s.tid
+    WHEN MATCHED AND t.balance = 40 THEN update set balance = t.balance + 4;
+
+MERGE INTO sj_target t USING sj_target AS s ON t.tid = s.tid
+    WHEN MATCHED AND t.balance = 50 THEN DELETE;
+
+MERGE INTO rw_sj_target t USING sj_target AS s ON t.tid = s.tid
+    WHEN MATCHED AND t.balance = 10 THEN update set balance = t.balance + 1;
+
+select * from sj_target order by tid;
+DROP VIEW no_rw_sj_target;
+DROP VIEW rw_sj_target;
+DROP TABLE sj_target;
+
 -- Test that references to the removed rel in lateral subqueries are replaced
 -- correctly after join removal
 explain (verbose, costs off)
-- 
2.34.1

