From 7f639719e8c1a4a06614dd50960377ffdba8fb31 Mon Sep 17 00:00:00 2001
From: Richard Guo <guofenglinux@gmail.com>
Date: Tue, 10 Jan 2023 16:03:56 +0800
Subject: [PATCH v1 1/3] postgres_fdw: Allow incremental sort atop of the
 epq_path

---
 .../postgres_fdw/expected/postgres_fdw.out    | 43 +++++++++++++++++++
 contrib/postgres_fdw/postgres_fdw.c           | 42 ++++++++++++++----
 contrib/postgres_fdw/sql/postgres_fdw.sql     |  5 +++
 3 files changed, 81 insertions(+), 9 deletions(-)

diff --git a/contrib/postgres_fdw/expected/postgres_fdw.out b/contrib/postgres_fdw/expected/postgres_fdw.out
index c0267a99d2..d3b559d10a 100644
--- a/contrib/postgres_fdw/expected/postgres_fdw.out
+++ b/contrib/postgres_fdw/expected/postgres_fdw.out
@@ -2553,6 +2553,49 @@ SELECT * FROM local_tbl LEFT JOIN (SELECT ft1.* FROM ft1 INNER JOIN ft2 ON (ft1.
 ALTER SERVER loopback OPTIONS (DROP fdw_startup_cost);
 ALTER SERVER loopback OPTIONS (ADD extensions 'postgres_fdw');
 DROP TABLE local_tbl;
+-- test that add_paths_with_pathkeys_for_rel() allows incremental sort atop of the epq_path
+EXPLAIN (VERBOSE, COSTS OFF)
+SELECT t1.c1, ss.a, ss.b FROM (SELECT c1 FROM "S 1"."T 3" WHERE c1 = 50) t1 INNER JOIN (SELECT t2.c1, t3.c1, t3.c2 FROM (SELECT c1 FROM ft4 WHERE c1 between 50 and 60) t2 INNER JOIN (SELECT c1, c2 FROM ft5 WHERE c1 between 50 and 60) t3 ON (t2.c1 = t3.c1) ) ss(a, b, c) ON (TRUE) ORDER BY t1.c1, ss.a, ss.b, ss.c FOR UPDATE OF t1;
+                                                                                                                                                                                                 QUERY PLAN                                                                                                                                                                                                  
+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ LockRows
+   Output: "T 3".c1, ft4.c1, ft5.c1, ft5.c2, "T 3".ctid, ft4.*, ft5.*
+   ->  Nested Loop
+         Output: "T 3".c1, ft4.c1, ft5.c1, ft5.c2, "T 3".ctid, ft4.*, ft5.*
+         ->  Foreign Scan
+               Output: ft4.c1, ft4.*, ft5.c1, ft5.c2, ft5.*
+               Relations: (public.ft4) INNER JOIN (public.ft5)
+               Remote SQL: SELECT r8.c1, CASE WHEN (r8.*)::text IS NOT NULL THEN ROW(r8.c1, r8.c2, r8.c3) END, r9.c1, r9.c2, CASE WHEN (r9.*)::text IS NOT NULL THEN ROW(r9.c1, r9.c2, r9.c3) END FROM ("S 1"."T 3" r8 INNER JOIN "S 1"."T 4" r9 ON (((r8.c1 = r9.c1)) AND ((r9.c1 >= 50)) AND ((r9.c1 <= 60)) AND ((r8.c1 >= 50)) AND ((r8.c1 <= 60)))) ORDER BY r8.c1 ASC NULLS LAST, r9.c2 ASC NULLS LAST
+               ->  Incremental Sort
+                     Output: ft4.c1, ft4.*, ft5.c1, ft5.c2, ft5.*
+                     Sort Key: ft4.c1, ft5.c2
+                     Presorted Key: ft4.c1
+                     ->  Merge Join
+                           Output: ft4.c1, ft4.*, ft5.c1, ft5.c2, ft5.*
+                           Merge Cond: (ft4.c1 = ft5.c1)
+                           ->  Sort
+                                 Output: ft4.c1, ft4.*
+                                 Sort Key: ft4.c1
+                                 ->  Foreign Scan on public.ft4
+                                       Output: ft4.c1, ft4.*
+                                       Remote SQL: SELECT c1, c2, c3 FROM "S 1"."T 3" WHERE ((c1 >= 50)) AND ((c1 <= 60))
+                           ->  Materialize
+                                 Output: ft5.c1, ft5.c2, ft5.*
+                                 ->  Foreign Scan on public.ft5
+                                       Output: ft5.c1, ft5.c2, ft5.*
+                                       Remote SQL: SELECT c1, c2, c3 FROM "S 1"."T 4" WHERE ((c1 >= 50)) AND ((c1 <= 60)) ORDER BY c1 ASC NULLS LAST, c2 ASC NULLS LAST
+         ->  Seq Scan on "S 1"."T 3"
+               Output: "T 3".c1, "T 3".ctid
+               Filter: ("T 3".c1 = 50)
+(29 rows)
+
+SELECT t1.c1, ss.a, ss.b FROM (SELECT c1 FROM "S 1"."T 3" WHERE c1 = 50) t1 INNER JOIN (SELECT t2.c1, t3.c1, t3.c2 FROM (SELECT c1 FROM ft4 WHERE c1 between 50 and 60) t2 INNER JOIN (SELECT c1, c2 FROM ft5 WHERE c1 between 50 and 60) t3 ON (t2.c1 = t3.c1) ) ss(a, b, c) ON (TRUE) ORDER BY t1.c1, ss.a, ss.b, ss.c FOR UPDATE OF t1;
+ c1 | a  | b  
+----+----+----
+ 50 | 54 | 54
+ 50 | 60 | 60
+(2 rows)
+
 -- check join pushdown in situations where multiple userids are involved
 CREATE ROLE regress_view_owner SUPERUSER;
 CREATE USER MAPPING FOR regress_view_owner SERVER loopback;
diff --git a/contrib/postgres_fdw/postgres_fdw.c b/contrib/postgres_fdw/postgres_fdw.c
index 53f890bb48..cec50a6926 100644
--- a/contrib/postgres_fdw/postgres_fdw.c
+++ b/contrib/postgres_fdw/postgres_fdw.c
@@ -6066,15 +6066,39 @@ add_paths_with_pathkeys_for_rel(PlannerInfo *root, RelOptInfo *rel,
 		 * case it gets used as input to a mergejoin.
 		 */
 		sorted_epq_path = epq_path;
-		if (sorted_epq_path != NULL &&
-			!pathkeys_contained_in(useful_pathkeys,
-								   sorted_epq_path->pathkeys))
-			sorted_epq_path = (Path *)
-				create_sort_path(root,
-								 rel,
-								 sorted_epq_path,
-								 useful_pathkeys,
-								 -1.0);
+		if (sorted_epq_path != NULL)
+		{
+			bool		is_sorted;
+			int			presorted_keys;
+
+			is_sorted = pathkeys_count_contained_in(useful_pathkeys,
+													sorted_epq_path->pathkeys,
+													&presorted_keys);
+
+			if (!is_sorted)
+			{
+				/*
+				 * We've no need to consider both a sort and incremental sort.
+				 * We'll just do a sort if there are no presorted keys and an
+				 * incremental sort when there are presorted keys.
+				 */
+				if (presorted_keys == 0 || !enable_incremental_sort)
+					sorted_epq_path = (Path *)
+						create_sort_path(root,
+										 rel,
+										 sorted_epq_path,
+										 useful_pathkeys,
+										 -1.0);
+				else
+					sorted_epq_path = (Path *)
+						create_incremental_sort_path(root,
+													 rel,
+													 sorted_epq_path,
+													 useful_pathkeys,
+													 presorted_keys,
+													 -1.0);
+			}
+		}
 
 		if (IS_SIMPLE_REL(rel))
 			add_path(rel, (Path *)
diff --git a/contrib/postgres_fdw/sql/postgres_fdw.sql b/contrib/postgres_fdw/sql/postgres_fdw.sql
index c37aa80383..5711f50690 100644
--- a/contrib/postgres_fdw/sql/postgres_fdw.sql
+++ b/contrib/postgres_fdw/sql/postgres_fdw.sql
@@ -687,6 +687,11 @@ ALTER SERVER loopback OPTIONS (ADD extensions 'postgres_fdw');
 
 DROP TABLE local_tbl;
 
+-- test that add_paths_with_pathkeys_for_rel() allows incremental sort atop of the epq_path
+EXPLAIN (VERBOSE, COSTS OFF)
+SELECT t1.c1, ss.a, ss.b FROM (SELECT c1 FROM "S 1"."T 3" WHERE c1 = 50) t1 INNER JOIN (SELECT t2.c1, t3.c1, t3.c2 FROM (SELECT c1 FROM ft4 WHERE c1 between 50 and 60) t2 INNER JOIN (SELECT c1, c2 FROM ft5 WHERE c1 between 50 and 60) t3 ON (t2.c1 = t3.c1) ) ss(a, b, c) ON (TRUE) ORDER BY t1.c1, ss.a, ss.b, ss.c FOR UPDATE OF t1;
+SELECT t1.c1, ss.a, ss.b FROM (SELECT c1 FROM "S 1"."T 3" WHERE c1 = 50) t1 INNER JOIN (SELECT t2.c1, t3.c1, t3.c2 FROM (SELECT c1 FROM ft4 WHERE c1 between 50 and 60) t2 INNER JOIN (SELECT c1, c2 FROM ft5 WHERE c1 between 50 and 60) t3 ON (t2.c1 = t3.c1) ) ss(a, b, c) ON (TRUE) ORDER BY t1.c1, ss.a, ss.b, ss.c FOR UPDATE OF t1;
+
 -- check join pushdown in situations where multiple userids are involved
 CREATE ROLE regress_view_owner SUPERUSER;
 CREATE USER MAPPING FOR regress_view_owner SERVER loopback;
-- 
2.31.0

