From 4d27e7aa71943fb9c60669c20510d40abd0e0179 Mon Sep 17 00:00:00 2001
From: Richard Guo <guofenglinux@gmail.com>
Date: Tue, 13 Oct 2026 00:00:00 +0900
Subject: [PATCH v1 1/2] Fix nestloop parameter assignment for PHVs evaluated
 above outer joins

A NestLoop can supply a PlaceHolderVar as a parameter if the PHV can
be evaluated from the lefthand rels plus the required-outer rels of
the join.  A PHV's ph_eval_at can include outer-join relids, but
required-outer sets need not: create_lateral_join_info() keeps
outer-join relids out of the lateral dependencies it derives from
ph_lateral.  So when an outer join within ph_eval_at had been computed
by an enclosing nestloop's outer side, the test in
identify_current_nestloop_params() failed and no plan node claimed the
parameter.  This resulted in "failed to assign all NestLoopParams to
plan nodes", or, if an unrelated upper NestLoop happened to cover
ph_eval_at and claimed the parameter, "plan should not reference
subplan's variable".

To fix, have create_nestloop_plan() also count the outer joins that
have already been computed by the outer rels in scope.
---
 src/backend/optimizer/plan/createplan.c |  9 +++++
 src/test/regress/expected/join.out      | 53 +++++++++++++++++++++++++
 src/test/regress/sql/join.sql           | 18 +++++++++
 3 files changed, 80 insertions(+)

diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c
index 2f8a08ad78e..1fddfe40502 100644
--- a/src/backend/optimizer/plan/createplan.c
+++ b/src/backend/optimizer/plan/createplan.c
@@ -4301,6 +4301,15 @@ create_nestloop_plan(PlannerInfo *root,
 		}
 	}
 
+	/*
+	 * Required-outer sets can lack outer-join relids that appear in
+	 * ph_eval_at (see create_lateral_join_info), so add the outer joins
+	 * already computed in the rels supplying our parameters.
+	 */
+	req_outer = bms_union(req_outer,
+						  bms_intersect(root->curOuterRels,
+										root->outer_join_rels));
+
 	/*
 	 * Identify any nestloop parameters that should be supplied by this join
 	 * node, and remove them from root->curOuterParams.
diff --git a/src/test/regress/expected/join.out b/src/test/regress/expected/join.out
index 4ee94d00ade..a309732be20 100644
--- a/src/test/regress/expected/join.out
+++ b/src/test/regress/expected/join.out
@@ -5453,6 +5453,59 @@ select * from
 (21 rows)
 
 rollback;
+-- nestloop parameter PHV whose ph_eval_at mentions an outer join that is
+-- computed in the rels supplying the parameters
+explain (verbose, costs off)
+select v.k from (select 1) d
+  left join (select t1.f1, 1 from int4_tbl t1, onerow t2
+               left join onerow t3 on true) s(x, y) on true
+  left join lateral (select s.y from onerow t4) v(k) on true
+where v.k > s.x;
+                 QUERY PLAN                 
+--------------------------------------------
+ Nested Loop
+   Output: ((1))
+   ->  Nested Loop Left Join
+         ->  Seq Scan on pg_temp.onerow t2
+         ->  Seq Scan on pg_temp.onerow t3
+   ->  Nested Loop
+         Output: ((1))
+         ->  Seq Scan on public.int4_tbl t1
+               Output: t1.f1, 1
+         ->  Seq Scan on pg_temp.onerow t4
+               Output: (1)
+               Filter: ((1) > t1.f1)
+(12 rows)
+
+-- likewise, where an upper nestloop could wrongly claim the parameter
+explain (verbose, costs off)
+select v.k, g from (select 1) d
+  left join (select t1.f1, 1 from int4_tbl t1, onerow t2
+               left join onerow t3 on true) s(x, y) on true
+  left join lateral (select s.y from onerow t4) v(k) on true,
+  lateral generate_series(v.k, 1) g
+where v.k > s.x;
+                     QUERY PLAN                      
+-----------------------------------------------------
+ Nested Loop
+   Output: ((1)), g.g
+   ->  Nested Loop
+         Output: ((1))
+         ->  Nested Loop Left Join
+               ->  Seq Scan on pg_temp.onerow t2
+               ->  Seq Scan on pg_temp.onerow t3
+         ->  Nested Loop
+               Output: ((1))
+               ->  Seq Scan on public.int4_tbl t1
+                     Output: t1.f1, 1
+               ->  Seq Scan on pg_temp.onerow t4
+                     Output: (1)
+                     Filter: ((1) > t1.f1)
+   ->  Function Scan on pg_catalog.generate_series g
+         Output: g.g
+         Function Call: generate_series(((1)), 1)
+(17 rows)
+
 -- PHVs containing SubLinks are quite tricky to get right
 explain (verbose, costs off)
 select *
diff --git a/src/test/regress/sql/join.sql b/src/test/regress/sql/join.sql
index bf8153afbcd..9c62a418bcf 100644
--- a/src/test/regress/sql/join.sql
+++ b/src/test/regress/sql/join.sql
@@ -1867,6 +1867,24 @@ select * from
 
 rollback;
 
+-- nestloop parameter PHV whose ph_eval_at mentions an outer join that is
+-- computed in the rels supplying the parameters
+explain (verbose, costs off)
+select v.k from (select 1) d
+  left join (select t1.f1, 1 from int4_tbl t1, onerow t2
+               left join onerow t3 on true) s(x, y) on true
+  left join lateral (select s.y from onerow t4) v(k) on true
+where v.k > s.x;
+
+-- likewise, where an upper nestloop could wrongly claim the parameter
+explain (verbose, costs off)
+select v.k, g from (select 1) d
+  left join (select t1.f1, 1 from int4_tbl t1, onerow t2
+               left join onerow t3 on true) s(x, y) on true
+  left join lateral (select s.y from onerow t4) v(k) on true,
+  lateral generate_series(v.k, 1) g
+where v.k > s.x;
+
 -- PHVs containing SubLinks are quite tricky to get right
 explain (verbose, costs off)
 select *
-- 
2.37.1 (Apple Git-137.1)

