From d481a59d49c1f7df6ea11dcfc061ed217369245e Mon Sep 17 00:00:00 2001 From: Richard Guo Date: Tue, 13 Oct 2026 09:24:40 +0900 Subject: [PATCH v1] Fix lateral references in sort keys added by createplan.c When prepare_sort_from_pathkeys() cannot find a pathkey's expression in the input plan's targetlist, it adds a resjunk entry computing an EquivalenceClass member's expression. It copied that expression as-is, without replacing lateral references with nestloop Params the way build_path_tlist() does for the rest of the targetlist. This matters when the sort key contains a PlaceHolderVar whose expression has a lateral reference, and the sort is placed directly atop the plan node that evaluates the PlaceHolderVar. The targetlist already contains the PlaceHolderVar, so the sort key is considered computable, but the entry added for it still has the outer relation's Var inside the PlaceHolderVar. If the node is a join, setrefs.c fails with "variable not found in subplan target lists". If it is a scan, setrefs.c leaves the Var in place, and the executor fetches it from the scan tuple, leading to wrong results. To fix, pass root to prepare_sort_from_pathkeys() and apply replace_nestloop_params() to the expression before adding it to the targetlist. --- src/backend/optimizer/plan/createplan.c | 58 +++++++++------- src/test/regress/expected/join.out | 92 +++++++++++++++++++++++++ src/test/regress/sql/join.sql | 33 +++++++++ 3 files changed, 160 insertions(+), 23 deletions(-) diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c index 2f8a08ad78e..9a4a782c74b 100644 --- a/src/backend/optimizer/plan/createplan.c +++ b/src/backend/optimizer/plan/createplan.c @@ -267,7 +267,8 @@ static IncrementalSort *make_incrementalsort(Plan *lefttree, int numCols, int nPresortedCols, AttrNumber *sortColIdx, Oid *sortOperators, Oid *collations, bool *nullsFirst); -static Plan *prepare_sort_from_pathkeys(Plan *lefttree, List *pathkeys, +static Plan *prepare_sort_from_pathkeys(PlannerInfo *root, + Plan *lefttree, List *pathkeys, Relids relids, const AttrNumber *reqColIdx, bool adjust_tlist_in_place, @@ -276,10 +277,13 @@ static Plan *prepare_sort_from_pathkeys(Plan *lefttree, List *pathkeys, Oid **p_sortOperators, Oid **p_collations, bool **p_nullsFirst); -static Sort *make_sort_from_pathkeys(Plan *lefttree, List *pathkeys, - Relids relids); -static IncrementalSort *make_incrementalsort_from_pathkeys(Plan *lefttree, - List *pathkeys, Relids relids, int nPresortedCols); +static Sort *make_sort_from_pathkeys(PlannerInfo *root, Plan *lefttree, + List *pathkeys, Relids relids); +static IncrementalSort *make_incrementalsort_from_pathkeys(PlannerInfo *root, + Plan *lefttree, + List *pathkeys, + Relids relids, + int nPresortedCols); static Sort *make_sort_from_groupcols(List *groupcls, AttrNumber *grpColIdx, Plan *lefttree); @@ -1281,7 +1285,7 @@ create_append_plan(PlannerInfo *root, AppendPath *best_path, int flags) * function result; it must be the same plan node. However, we then * need to detect whether any tlist entries were added. */ - (void) prepare_sort_from_pathkeys((Plan *) plan, pathkeys, + (void) prepare_sort_from_pathkeys(root, (Plan *) plan, pathkeys, best_path->path.parent->relids, NULL, true, @@ -1326,7 +1330,7 @@ create_append_plan(PlannerInfo *root, AppendPath *best_path, int flags) * don't need an explicit sort, to make sure they are returning * the same sort key columns the Append expects. */ - subplan = prepare_sort_from_pathkeys(subplan, pathkeys, + subplan = prepare_sort_from_pathkeys(root, subplan, pathkeys, subpath->parent->relids, nodeSortColIdx, false, @@ -1492,7 +1496,7 @@ create_merge_append_plan(PlannerInfo *root, MergeAppendPath *best_path, * function result; it must be the same plan node. However, we then need * to detect whether any tlist entries were added. */ - (void) prepare_sort_from_pathkeys(plan, pathkeys, + (void) prepare_sort_from_pathkeys(root, plan, pathkeys, best_path->path.parent->relids, NULL, true, @@ -1524,7 +1528,7 @@ create_merge_append_plan(PlannerInfo *root, MergeAppendPath *best_path, subplan = create_plan_recurse(root, subpath, CP_EXACT_TLIST); /* Compute sort column info, and adjust subplan's tlist as needed */ - subplan = prepare_sort_from_pathkeys(subplan, pathkeys, + subplan = prepare_sort_from_pathkeys(root, subplan, pathkeys, subpath->parent->relids, node->sortColIdx, false, @@ -1830,7 +1834,7 @@ create_gather_merge_plan(PlannerInfo *root, GatherMergePath *best_path) Assert(pathkeys != NIL); /* Compute sort column info, and adjust subplan's tlist as needed */ - subplan = prepare_sort_from_pathkeys(subplan, pathkeys, + subplan = prepare_sort_from_pathkeys(root, subplan, pathkeys, best_path->subpath->parent->relids, gm_plan->sortColIdx, false, @@ -2043,7 +2047,7 @@ create_sort_plan(PlannerInfo *root, SortPath *best_path, int flags) * relids. Thus, if this sort path is based on a child relation, we must * pass its relids. */ - plan = make_sort_from_pathkeys(subplan, best_path->path.pathkeys, + plan = make_sort_from_pathkeys(root, subplan, best_path->path.pathkeys, IS_OTHER_REL(best_path->subpath->parent) ? best_path->path.parent->relids : NULL); @@ -2067,7 +2071,7 @@ create_incrementalsort_plan(PlannerInfo *root, IncrementalSortPath *best_path, /* See comments in create_sort_plan() above */ subplan = create_plan_recurse(root, best_path->spath.subpath, flags | CP_SMALL_TLIST); - plan = make_incrementalsort_from_pathkeys(subplan, + plan = make_incrementalsort_from_pathkeys(root, subplan, best_path->spath.path.pathkeys, IS_OTHER_REL(best_path->spath.subpath->parent) ? best_path->spath.path.parent->relids : NULL, @@ -4492,7 +4496,7 @@ create_mergejoin_plan(PlannerInfo *root, if (enable_incremental_sort && best_path->outer_presorted_keys > 0) { sort_plan = (Plan *) - make_incrementalsort_from_pathkeys(outer_plan, + make_incrementalsort_from_pathkeys(root, outer_plan, best_path->outersortkeys, outer_relids, best_path->outer_presorted_keys); @@ -4505,7 +4509,7 @@ create_mergejoin_plan(PlannerInfo *root, else { sort_plan = (Plan *) - make_sort_from_pathkeys(outer_plan, + make_sort_from_pathkeys(root, outer_plan, best_path->outersortkeys, outer_relids); @@ -4536,7 +4540,7 @@ create_mergejoin_plan(PlannerInfo *root, Assert(!pathkeys_contained_in(best_path->innersortkeys, inner_path->pathkeys)); - sort = make_sort_from_pathkeys(inner_plan, + sort = make_sort_from_pathkeys(root, inner_plan, best_path->innersortkeys, inner_relids); @@ -6174,6 +6178,7 @@ make_incrementalsort(Plan *lefttree, int numCols, int nPresortedCols, * adjusts the plan targetlist if needed to add resjunk sort columns. * * Input parameters: + * 'root' is the PlannerInfo, needed to replace lateral references * 'lefttree' is the plan node which yields input tuples * 'pathkeys' is the list of pathkeys by which the result is to be sorted * 'relids' identifies the child relation being sorted, if any @@ -6207,7 +6212,7 @@ make_incrementalsort(Plan *lefttree, int numCols, int nPresortedCols, * or a Result stacked atop lefttree). */ static Plan * -prepare_sort_from_pathkeys(Plan *lefttree, List *pathkeys, +prepare_sort_from_pathkeys(PlannerInfo *root, Plan *lefttree, List *pathkeys, Relids relids, const AttrNumber *reqColIdx, bool adjust_tlist_in_place, @@ -6242,6 +6247,7 @@ prepare_sort_from_pathkeys(Plan *lefttree, List *pathkeys, EquivalenceClass *ec = pathkey->pk_eclass; EquivalenceMember *em; TargetEntry *tle = NULL; + Node *sortexpr; Oid pk_datatype = InvalidOid; Oid sortop; ListCell *j; @@ -6339,9 +6345,11 @@ prepare_sort_from_pathkeys(Plan *lefttree, List *pathkeys, adjust_tlist_in_place = true; /* - * Add resjunk entry to input's tlist + * Add resjunk entry to input's tlist, replacing lateral + * references. */ - tle = makeTargetEntry(copyObject(em->em_expr), + sortexpr = replace_nestloop_params(root, (Node *) em->em_expr); + tle = makeTargetEntry(copyObject((Expr *) sortexpr), list_length(tlist) + 1, NULL, true); @@ -6384,12 +6392,14 @@ prepare_sort_from_pathkeys(Plan *lefttree, List *pathkeys, * make_sort_from_pathkeys * Create sort plan to sort according to given pathkeys * + * 'root' is the PlannerInfo required by prepare_sort_from_pathkeys() * 'lefttree' is the node which yields input tuples * 'pathkeys' is the list of pathkeys by which the result is to be sorted * 'relids' is the set of relations required by prepare_sort_from_pathkeys() */ static Sort * -make_sort_from_pathkeys(Plan *lefttree, List *pathkeys, Relids relids) +make_sort_from_pathkeys(PlannerInfo *root, Plan *lefttree, List *pathkeys, + Relids relids) { int numsortkeys; AttrNumber *sortColIdx; @@ -6398,7 +6408,7 @@ make_sort_from_pathkeys(Plan *lefttree, List *pathkeys, Relids relids) bool *nullsFirst; /* Compute sort column info, and adjust lefttree as needed */ - lefttree = prepare_sort_from_pathkeys(lefttree, pathkeys, + lefttree = prepare_sort_from_pathkeys(root, lefttree, pathkeys, relids, NULL, false, @@ -6418,14 +6428,16 @@ make_sort_from_pathkeys(Plan *lefttree, List *pathkeys, Relids relids) * make_incrementalsort_from_pathkeys * Create sort plan to sort according to given pathkeys * + * 'root' is the PlannerInfo required by prepare_sort_from_pathkeys() * 'lefttree' is the node which yields input tuples * 'pathkeys' is the list of pathkeys by which the result is to be sorted * 'relids' is the set of relations required by prepare_sort_from_pathkeys() * 'nPresortedCols' is the number of presorted columns in input tuples */ static IncrementalSort * -make_incrementalsort_from_pathkeys(Plan *lefttree, List *pathkeys, - Relids relids, int nPresortedCols) +make_incrementalsort_from_pathkeys(PlannerInfo *root, Plan *lefttree, + List *pathkeys, Relids relids, + int nPresortedCols) { int numsortkeys; AttrNumber *sortColIdx; @@ -6434,7 +6446,7 @@ make_incrementalsort_from_pathkeys(Plan *lefttree, List *pathkeys, bool *nullsFirst; /* Compute sort column info, and adjust lefttree as needed */ - lefttree = prepare_sort_from_pathkeys(lefttree, pathkeys, + lefttree = prepare_sort_from_pathkeys(root, lefttree, pathkeys, relids, NULL, false, diff --git a/src/test/regress/expected/join.out b/src/test/regress/expected/join.out index 4ee94d00ade..70557913344 100644 --- a/src/test/regress/expected/join.out +++ b/src/test/regress/expected/join.out @@ -11332,6 +11332,98 @@ order by 1, 2; 0 | 4567890123456789 | 1 (5 rows) +-- check that lateral references in a sort key added below a mergejoin are +-- replaced with nestloop params +set enable_hashjoin to off; +set enable_nestloop to off; +explain (verbose, costs off) +select * from int4_tbl t1 + left join lateral + (select t1.f1 from int4_tbl t3 where t3.f1 = 0) s(x) on true + join int4_tbl t2 on -s.x = t2.f1 +where t1.f1 = 123456; + QUERY PLAN +-------------------------------------------------- + Nested Loop + Disabled: true + Output: t1.f1, (t1.f1), t2.f1 + -> Seq Scan on public.int4_tbl t1 + Output: t1.f1 + Filter: (t1.f1 = 123456) + -> Merge Join + Output: (t1.f1), t2.f1 + Merge Cond: (((- t1.f1)) = t2.f1) + -> Sort + Output: (t1.f1), ((- t1.f1)) + Sort Key: ((- t1.f1)) + -> Seq Scan on public.int4_tbl t3 + Output: t1.f1, (- t1.f1) + Filter: (t3.f1 = 0) + -> Sort + Output: t2.f1 + Sort Key: t2.f1 + -> Seq Scan on public.int4_tbl t2 + Output: t2.f1 +(20 rows) + +select * from int4_tbl t1 + left join lateral + (select t1.f1 from int4_tbl t3 where t3.f1 = 0) s(x) on true + join int4_tbl t2 on -s.x = t2.f1 +where t1.f1 = 123456; + f1 | x | f1 +--------+--------+--------- + 123456 | 123456 | -123456 +(1 row) + +-- same, with the sort key computed atop a join +explain (verbose, costs off) +select count(*) from int4_tbl t1 + left join lateral + (select t1.f1, t3.f1 from int4_tbl t3, int4_tbl t4) s(x, y) on true + join int4_tbl t2 on -s.x = t2.f1 +where t1.f1 = 123456 and s.y is not null; + QUERY PLAN +-------------------------------------------------------------- + Aggregate + Output: count(*) + -> Nested Loop + Disabled: true + -> Seq Scan on public.int4_tbl t1 + Output: t1.f1 + Filter: (t1.f1 = 123456) + -> Merge Join + Merge Cond: (t2.f1 = ((- t1.f1))) + -> Sort + Output: t2.f1 + Sort Key: t2.f1 + -> Seq Scan on public.int4_tbl t2 + Output: t2.f1 + -> Sort + Output: (t1.f1), ((- t1.f1)) + Sort Key: ((- t1.f1)) + -> Nested Loop + Disabled: true + Output: t1.f1, (- t1.f1) + -> Seq Scan on public.int4_tbl t3 + Output: t3.f1 + Filter: (t3.f1 IS NOT NULL) + -> Seq Scan on public.int4_tbl t4 + Output: t4.f1 +(25 rows) + +select count(*) from int4_tbl t1 + left join lateral + (select t1.f1, t3.f1 from int4_tbl t3, int4_tbl t4) s(x, y) on true + join int4_tbl t2 on -s.x = t2.f1 +where t1.f1 = 123456 and s.y is not null; + count +------- + 25 +(1 row) + +reset enable_hashjoin; +reset enable_nestloop; -- test some error cases where LATERAL should have been used but wasn't select f1,g from int4_tbl a, (select f1 as g) ss; ERROR: column "f1" does not exist diff --git a/src/test/regress/sql/join.sql b/src/test/regress/sql/join.sql index bf8153afbcd..ed84d65e6f5 100644 --- a/src/test/regress/sql/join.sql +++ b/src/test/regress/sql/join.sql @@ -4263,6 +4263,39 @@ from int4_tbl i4, where i4.f1 = 0 order by 1, 2; +-- check that lateral references in a sort key added below a mergejoin are +-- replaced with nestloop params +set enable_hashjoin to off; +set enable_nestloop to off; + +explain (verbose, costs off) +select * from int4_tbl t1 + left join lateral + (select t1.f1 from int4_tbl t3 where t3.f1 = 0) s(x) on true + join int4_tbl t2 on -s.x = t2.f1 +where t1.f1 = 123456; +select * from int4_tbl t1 + left join lateral + (select t1.f1 from int4_tbl t3 where t3.f1 = 0) s(x) on true + join int4_tbl t2 on -s.x = t2.f1 +where t1.f1 = 123456; + +-- same, with the sort key computed atop a join +explain (verbose, costs off) +select count(*) from int4_tbl t1 + left join lateral + (select t1.f1, t3.f1 from int4_tbl t3, int4_tbl t4) s(x, y) on true + join int4_tbl t2 on -s.x = t2.f1 +where t1.f1 = 123456 and s.y is not null; +select count(*) from int4_tbl t1 + left join lateral + (select t1.f1, t3.f1 from int4_tbl t3, int4_tbl t4) s(x, y) on true + join int4_tbl t2 on -s.x = t2.f1 +where t1.f1 = 123456 and s.y is not null; + +reset enable_hashjoin; +reset enable_nestloop; + -- test some error cases where LATERAL should have been used but wasn't select f1,g from int4_tbl a, (select f1 as g) ss; select f1,g from int4_tbl a, (select a.f1 as g) ss; -- 2.37.1 (Apple Git-137.1)