From ee97293ba0c5d85fa1c97d7541a3a922283c89e8 Mon Sep 17 00:00:00 2001 From: Andrei Lepikhov Date: Tue, 3 Nov 2026 09:18:55 +0100 Subject: [PATCH v0 1/2] pg_plan_advice: Look through a Sort on the outer side of a NestLoop. pgpa_decompose_join() already disregards a Sort or IncrementalSort node beneath a MergeJoin, but assumed that nothing similar can appear directly under a NestLoop. If the planner pre-sorts the outer input of a nested loop to deliver the order required by an upper node, the Sort was taken as the real outer subplan, and pgpa_build_scan() then failed with "plan node has no RTIs". Such a Sort is not part of the join strategy, so descend through it in the same way as we do for MergeJoin, unless an elided node sits on top of the outer subplan. --- contrib/pg_plan_advice/pgpa_join.c | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/contrib/pg_plan_advice/pgpa_join.c b/contrib/pg_plan_advice/pgpa_join.c index acb6073ef52..ee9475dce19 100644 --- a/contrib/pg_plan_advice/pgpa_join.c +++ b/contrib/pg_plan_advice/pgpa_join.c @@ -134,7 +134,7 @@ pgpa_unroll_join(pgpa_plan_walker_context *walker, Plan *plan, * join tree where the planner decided to initiate parallelism, * * (3) Sort and IncrementalSort nodes, which can occur beneath MergeJoin - * or GatherMerge, + * or GatherMerge, or on the outer side of a NestLoop, * * (4) Agg and Unique nodes, which can occur when we decide to make the * nullable side of a semijoin unique and then join the result, and @@ -309,9 +309,9 @@ pgpa_destroy_join_unroller(pgpa_join_unroller *join_unroller) * for all intents and purposes the real inner input is the Hash node's child, * not the Hash node itself. * - * Likewise, a Merge Join may have Sort node on the inner or outer side; if - * it does, the real input to the join is the Sort node's child, not the - * Sort node itself. + * Likewise, a Merge Join may have Sort node on the inner or outer side, and + * a Nested Loop may have one on the outer side; if it does, the real input + * to the join is the Sort node's child, not the Sort node itself. * * In addition, with a Merge Join or a Nested Loop, the join planning code * may add additional nodes such as Materialize or Memoize. We regard these @@ -407,6 +407,15 @@ pgpa_decompose_join(pgpa_plan_walker_context *walker, Plan *plan, } else strategy = JSTRAT_NESTED_LOOP_PLAIN; + + /* + * The planner may also have sorted the outer subplan, so that the + * NestLoop delivers its output in the order required above it. + * Such a Sort is not part of the join strategy, so look through + * it to find the real outer subplan. + */ + if (elidedouter == NULL && is_sorting_plan(outerplan)) + elidedouter = pgpa_descend_node(pstmt, &outerplan); break; case T_HashJoin: -- 2.56.0