From 34438e9a6a4ebd0eae7612b5bde4679f7d0eae0a Mon Sep 17 00:00:00 2001
From: Lukas Fittl <lukas@fittl.com>
Date: Sat, 4 Apr 2026 11:41:03 -0700
Subject: [PATCH v1] pg_plan_advice: Skip Sort nodes under all join types
 during analysis

With current core planning logic, only Merge Joins may get a Sort node
between them and the actual activity producing tuples underneath. However,
extensions may add Sort nodes under Nested Loops or Hash Joins as well.

Generalize the handling in pgpa_decompose_join that skips such nodes,
and ignore intermediate Sort nodes for any join type. This avoids a
"ERROR:  plan node has no RTIs: 380" that was reported to occur otherwise.

Reported-by: Andrei Lepikhov <lepihov@gmail.com>
Author: Lukas Fittl <lukas@fittl.com>
Reviewed-by:
Discussion: https://www.postgresql.org/message-id/flat/78dd9572-7569-4025-984d-e07d7f381b6e%40gmail.com
---
 contrib/pg_plan_advice/pgpa_join.c | 22 ++++++++++++----------
 1 file changed, 12 insertions(+), 10 deletions(-)

diff --git a/contrib/pg_plan_advice/pgpa_join.c b/contrib/pg_plan_advice/pgpa_join.c
index 4610d02356f..57f4ff6795d 100644
--- a/contrib/pg_plan_advice/pgpa_join.c
+++ b/contrib/pg_plan_advice/pgpa_join.c
@@ -373,16 +373,6 @@ pgpa_decompose_join(pgpa_plan_walker_context *walker, Plan *plan,
 			else
 				strategy = JSTRAT_MERGE_JOIN_PLAIN;
 
-			/*
-			 * For a MergeJoin, either the outer or the inner subplan, or
-			 * both, may have needed to be sorted; we must disregard any Sort
-			 * or IncrementalSort node to find the real inner or outer
-			 * subplan.
-			 */
-			if (elidedouter == NULL && is_sorting_plan(outerplan))
-				elidedouter = pgpa_descend_node(pstmt, &outerplan);
-			if (elidedinner == NULL && is_sorting_plan(innerplan))
-				elidedinner = pgpa_descend_node(pstmt, &innerplan);
 			break;
 
 		case T_NestLoop:
@@ -422,6 +412,18 @@ pgpa_decompose_join(pgpa_plan_walker_context *walker, Plan *plan,
 			elog(ERROR, "unrecognized node type: %d", (int) nodeTag(plan));
 	}
 
+	/*
+	 * For any join type, the planner may have inserted a Sort or
+	 * IncrementalSort node on either side; we must disregard it to find the
+	 * real inner or outer subplan. In practice this only happens with Merge
+	 * Join in core today, but extensions can cause this for other join types
+	 * to happen as well.
+	 */
+	if (elidedouter == NULL && is_sorting_plan(outerplan))
+		elidedouter = pgpa_descend_node(pstmt, &outerplan);
+	if (elidedinner == NULL && is_sorting_plan(innerplan))
+		elidedinner = pgpa_descend_node(pstmt, &innerplan);
+
 	/*
 	 * The planner may have decided to implement a semijoin by first making
 	 * the nullable side of the plan unique, and then performing a normal join
-- 
2.47.1

