From 04c89be4b2802a0b51b10ca4828108a338c77e71 Mon Sep 17 00:00:00 2001
From: Alexander Pyhalov <a.pyhalov@postgrespro.ru>
Date: Tue, 18 Mar 2025 13:46:08 +0300
Subject: [PATCH] Avoid pulling up restrict infos from subqueries

Semi-join joins below left/right join are deparsed as
subqueries. We can't refer to subqueries vars from upper relations,
so avoid pulling conditions from them.
---
 .../postgres_fdw/expected/postgres_fdw.out    | 39 +++++++++++++++++++
 contrib/postgres_fdw/postgres_fdw.c           | 22 +++++++++--
 contrib/postgres_fdw/sql/postgres_fdw.sql     | 18 +++++++++
 3 files changed, 75 insertions(+), 4 deletions(-)

diff --git a/contrib/postgres_fdw/expected/postgres_fdw.out b/contrib/postgres_fdw/expected/postgres_fdw.out
index bb4ed3059c4..88385e06f31 100644
--- a/contrib/postgres_fdw/expected/postgres_fdw.out
+++ b/contrib/postgres_fdw/expected/postgres_fdw.out
@@ -4936,6 +4936,45 @@ SELECT ft2.*, ft4.* FROM ft2 INNER JOIN
 ----+----+----+----+----+----+----+----+----+----+----
 (0 rows)
 
+-- semi-join conditions shouldn't pop up as
+-- left/right join join clause
+set enable_material to off;
+EXPLAIN (verbose, costs off)
+	SELECT NULL FROM
+	(SELECT * FROM ft2 WHERE EXISTS (SELECT 1 FROM ft4 WHERE ft2.c1 < 10))
+	RIGHT JOIN
+	(SELECT * FROM ft2 WHERE EXISTS (SELECT 1 FROM ft4 WHERE ft2.c1 < 10))
+	ON (TRUE)
+LIMIT 10;
+                                                                                                                                        QUERY PLAN                                                                                                                                         
+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ Foreign Scan
+   Output: NULL::text
+   Relations: ((public.ft2 ft2_1) SEMI JOIN (public.ft4 ft4_1)) LEFT JOIN ((public.ft2) SEMI JOIN (public.ft4))
+   Remote SQL: SELECT NULL FROM ((SELECT NULL FROM "S 1"."T 1" r6 WHERE ((r6."C 1" < 10)) AND EXISTS (SELECT NULL FROM "S 1"."T 3" r7)) s8 LEFT JOIN (SELECT NULL FROM "S 1"."T 1" r4 WHERE ((r4."C 1" < 10)) AND EXISTS (SELECT NULL FROM "S 1"."T 3" r5)) s9 ON (TRUE)) LIMIT 10::bigint
+(4 rows)
+
+SELECT NULL FROM
+	(SELECT * FROM ft2 WHERE EXISTS (SELECT 1 FROM ft4 WHERE ft2.c1 < 10))
+	RIGHT JOIN
+	(SELECT * FROM ft2 WHERE EXISTS (SELECT 1 FROM ft4 WHERE ft2.c1 < 10))
+	ON (TRUE)
+LIMIT 10;
+ ?column? 
+----------
+ 
+ 
+ 
+ 
+ 
+ 
+ 
+ 
+ 
+ 
+(10 rows)
+
+reset enable_material;
 -- Can't push down semi-join with inner rel vars in targetlist
 EXPLAIN (verbose, costs off)
 SELECT ft1.c1 FROM ft1 JOIN ft2 on ft1.c1 = ft2.c1 WHERE
diff --git a/contrib/postgres_fdw/postgres_fdw.c b/contrib/postgres_fdw/postgres_fdw.c
index 1131a8bf77e..8af8b802a24 100644
--- a/contrib/postgres_fdw/postgres_fdw.c
+++ b/contrib/postgres_fdw/postgres_fdw.c
@@ -5964,16 +5964,30 @@ foreign_join_ok(PlannerInfo *root, RelOptInfo *joinrel, JoinType jointype,
 			break;
 
 		case JOIN_LEFT:
-			fpinfo->joinclauses = list_concat(fpinfo->joinclauses,
+			/*
+			 * When semi-join is involved in inner or outer part of left join,
+			 * it's deparsed as a subquery and we can't refer to its vars
+			 * on upper level
+			 */
+			if (bms_is_empty(fpinfo_i->hidden_subquery_rels))
+				fpinfo->joinclauses = list_concat(fpinfo->joinclauses,
 											  fpinfo_i->remote_conds);
-			fpinfo->remote_conds = list_concat(fpinfo->remote_conds,
+			if (bms_is_empty(fpinfo_o->hidden_subquery_rels))
+				fpinfo->remote_conds = list_concat(fpinfo->remote_conds,
 											   fpinfo_o->remote_conds);
 			break;
 
 		case JOIN_RIGHT:
-			fpinfo->joinclauses = list_concat(fpinfo->joinclauses,
+			/*
+			 * When semi-join is involved in inner or outer part of right join,
+			 * it's deparsed as a subquery and we can't refer to its vars
+			 * on upper level
+			 */
+			if (bms_is_empty(fpinfo_o->hidden_subquery_rels))
+				fpinfo->joinclauses = list_concat(fpinfo->joinclauses,
 											  fpinfo_o->remote_conds);
-			fpinfo->remote_conds = list_concat(fpinfo->remote_conds,
+			if (bms_is_empty(fpinfo_i->hidden_subquery_rels))
+				fpinfo->remote_conds = list_concat(fpinfo->remote_conds,
 											   fpinfo_i->remote_conds);
 			break;
 
diff --git a/contrib/postgres_fdw/sql/postgres_fdw.sql b/contrib/postgres_fdw/sql/postgres_fdw.sql
index d45e9f8ab52..faedeab9d41 100644
--- a/contrib/postgres_fdw/sql/postgres_fdw.sql
+++ b/contrib/postgres_fdw/sql/postgres_fdw.sql
@@ -1455,6 +1455,24 @@ SELECT ft2.*, ft4.* FROM ft2 INNER JOIN
 	WHERE ft2.c1 > 900
 	ORDER BY ft2.c1 LIMIT 10;
 
+-- semi-join conditions shouldn't pop up as
+-- left/right join join clause
+set enable_material to off;
+EXPLAIN (verbose, costs off)
+	SELECT NULL FROM
+	(SELECT * FROM ft2 WHERE EXISTS (SELECT 1 FROM ft4 WHERE ft2.c1 < 10))
+	RIGHT JOIN
+	(SELECT * FROM ft2 WHERE EXISTS (SELECT 1 FROM ft4 WHERE ft2.c1 < 10))
+	ON (TRUE)
+LIMIT 10;
+SELECT NULL FROM
+	(SELECT * FROM ft2 WHERE EXISTS (SELECT 1 FROM ft4 WHERE ft2.c1 < 10))
+	RIGHT JOIN
+	(SELECT * FROM ft2 WHERE EXISTS (SELECT 1 FROM ft4 WHERE ft2.c1 < 10))
+	ON (TRUE)
+LIMIT 10;
+reset enable_material;
+
 -- Can't push down semi-join with inner rel vars in targetlist
 EXPLAIN (verbose, costs off)
 SELECT ft1.c1 FROM ft1 JOIN ft2 on ft1.c1 = ft2.c1 WHERE
-- 
2.43.0

