1
0
mirror of https://github.com/postgres/postgres.git synced 2026-10-23 13:29:09 +03:00

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.

Back-patch to all supported branches.

Author: Richard Guo <guofenglinux@gmail.com>
Discussion: https://postgr.es/m/CAMbWs49LWNm7_OxuW1V71wLi+Ev3KbfY1bMY36uydNKwSPnbtQ@mail.gmail.com
Backpatch-through: 14
This commit is contained in:
Richard Guo
2026-10-21 18:00:20 +09:00
parent 456ee18045
commit 6b9f40d49a
3 changed files with 156 additions and 23 deletions

View File

@@ -267,7 +267,8 @@ static IncrementalSort *make_incrementalsort(Plan *lefttree,
int numCols, int nPresortedCols, int numCols, int nPresortedCols,
AttrNumber *sortColIdx, Oid *sortOperators, AttrNumber *sortColIdx, Oid *sortOperators,
Oid *collations, bool *nullsFirst); 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, Relids relids,
const AttrNumber *reqColIdx, const AttrNumber *reqColIdx,
bool adjust_tlist_in_place, bool adjust_tlist_in_place,
@@ -276,10 +277,13 @@ static Plan *prepare_sort_from_pathkeys(Plan *lefttree, List *pathkeys,
Oid **p_sortOperators, Oid **p_sortOperators,
Oid **p_collations, Oid **p_collations,
bool **p_nullsFirst); bool **p_nullsFirst);
static Sort *make_sort_from_pathkeys(Plan *lefttree, List *pathkeys, static Sort *make_sort_from_pathkeys(PlannerInfo *root, Plan *lefttree,
Relids relids); List *pathkeys, Relids relids);
static IncrementalSort *make_incrementalsort_from_pathkeys(Plan *lefttree, static IncrementalSort *make_incrementalsort_from_pathkeys(PlannerInfo *root,
List *pathkeys, Relids relids, int nPresortedCols); Plan *lefttree,
List *pathkeys,
Relids relids,
int nPresortedCols);
static Sort *make_sort_from_groupcols(List *groupcls, static Sort *make_sort_from_groupcols(List *groupcls,
AttrNumber *grpColIdx, AttrNumber *grpColIdx,
Plan *lefttree); 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 * function result; it must be the same plan node. However, we then
* need to detect whether any tlist entries were added. * 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, best_path->path.parent->relids,
NULL, NULL,
true, 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 * don't need an explicit sort, to make sure they are returning
* the same sort key columns the Append expects. * 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, subpath->parent->relids,
nodeSortColIdx, nodeSortColIdx,
false, 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 * function result; it must be the same plan node. However, we then need
* to detect whether any tlist entries were added. * 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, best_path->path.parent->relids,
NULL, NULL,
true, true,
@@ -1524,7 +1528,7 @@ create_merge_append_plan(PlannerInfo *root, MergeAppendPath *best_path,
subplan = create_plan_recurse(root, subpath, CP_EXACT_TLIST); subplan = create_plan_recurse(root, subpath, CP_EXACT_TLIST);
/* Compute sort column info, and adjust subplan's tlist as needed */ /* 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, subpath->parent->relids,
node->sortColIdx, node->sortColIdx,
false, false,
@@ -1830,7 +1834,7 @@ create_gather_merge_plan(PlannerInfo *root, GatherMergePath *best_path)
Assert(pathkeys != NIL); Assert(pathkeys != NIL);
/* Compute sort column info, and adjust subplan's tlist as needed */ /* 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, best_path->subpath->parent->relids,
gm_plan->sortColIdx, gm_plan->sortColIdx,
false, 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 * relids. Thus, if this sort path is based on a child relation, we must
* pass its relids. * 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) ? IS_OTHER_REL(best_path->subpath->parent) ?
best_path->path.parent->relids : NULL); 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 */ /* See comments in create_sort_plan() above */
subplan = create_plan_recurse(root, best_path->spath.subpath, subplan = create_plan_recurse(root, best_path->spath.subpath,
flags | CP_SMALL_TLIST); flags | CP_SMALL_TLIST);
plan = make_incrementalsort_from_pathkeys(subplan, plan = make_incrementalsort_from_pathkeys(root, subplan,
best_path->spath.path.pathkeys, best_path->spath.path.pathkeys,
IS_OTHER_REL(best_path->spath.subpath->parent) ? IS_OTHER_REL(best_path->spath.subpath->parent) ?
best_path->spath.path.parent->relids : NULL, 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) if (enable_incremental_sort && best_path->outer_presorted_keys > 0)
{ {
sort_plan = (Plan *) sort_plan = (Plan *)
make_incrementalsort_from_pathkeys(outer_plan, make_incrementalsort_from_pathkeys(root, outer_plan,
best_path->outersortkeys, best_path->outersortkeys,
outer_relids, outer_relids,
best_path->outer_presorted_keys); best_path->outer_presorted_keys);
@@ -4505,7 +4509,7 @@ create_mergejoin_plan(PlannerInfo *root,
else else
{ {
sort_plan = (Plan *) sort_plan = (Plan *)
make_sort_from_pathkeys(outer_plan, make_sort_from_pathkeys(root, outer_plan,
best_path->outersortkeys, best_path->outersortkeys,
outer_relids); outer_relids);
@@ -4536,7 +4540,7 @@ create_mergejoin_plan(PlannerInfo *root,
Assert(!pathkeys_contained_in(best_path->innersortkeys, Assert(!pathkeys_contained_in(best_path->innersortkeys,
inner_path->pathkeys)); inner_path->pathkeys));
sort = make_sort_from_pathkeys(inner_plan, sort = make_sort_from_pathkeys(root, inner_plan,
best_path->innersortkeys, best_path->innersortkeys,
inner_relids); 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. * adjusts the plan targetlist if needed to add resjunk sort columns.
* *
* Input parameters: * Input parameters:
* 'root' is the PlannerInfo, needed to replace lateral references
* 'lefttree' is the plan node which yields input tuples * 'lefttree' is the plan node which yields input tuples
* 'pathkeys' is the list of pathkeys by which the result is to be sorted * 'pathkeys' is the list of pathkeys by which the result is to be sorted
* 'relids' identifies the relation being sorted, or is NULL * 'relids' identifies the relation being sorted, or is NULL
@@ -6209,7 +6214,7 @@ make_incrementalsort(Plan *lefttree, int numCols, int nPresortedCols,
* or a Result stacked atop lefttree). * or a Result stacked atop lefttree).
*/ */
static Plan * static Plan *
prepare_sort_from_pathkeys(Plan *lefttree, List *pathkeys, prepare_sort_from_pathkeys(PlannerInfo *root, Plan *lefttree, List *pathkeys,
Relids relids, Relids relids,
const AttrNumber *reqColIdx, const AttrNumber *reqColIdx,
bool adjust_tlist_in_place, bool adjust_tlist_in_place,
@@ -6244,6 +6249,7 @@ prepare_sort_from_pathkeys(Plan *lefttree, List *pathkeys,
EquivalenceClass *ec = pathkey->pk_eclass; EquivalenceClass *ec = pathkey->pk_eclass;
EquivalenceMember *em; EquivalenceMember *em;
TargetEntry *tle = NULL; TargetEntry *tle = NULL;
Node *sortexpr;
Oid pk_datatype = InvalidOid; Oid pk_datatype = InvalidOid;
Oid sortop; Oid sortop;
ListCell *j; ListCell *j;
@@ -6341,9 +6347,11 @@ prepare_sort_from_pathkeys(Plan *lefttree, List *pathkeys,
adjust_tlist_in_place = true; 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, list_length(tlist) + 1,
NULL, NULL,
true); true);
@@ -6386,12 +6394,14 @@ prepare_sort_from_pathkeys(Plan *lefttree, List *pathkeys,
* make_sort_from_pathkeys * make_sort_from_pathkeys
* Create sort plan to sort according to given 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 * 'lefttree' is the node which yields input tuples
* 'pathkeys' is the list of pathkeys by which the result is to be sorted * '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() * 'relids' is the set of relations required by prepare_sort_from_pathkeys()
*/ */
static Sort * 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; int numsortkeys;
AttrNumber *sortColIdx; AttrNumber *sortColIdx;
@@ -6400,7 +6410,7 @@ make_sort_from_pathkeys(Plan *lefttree, List *pathkeys, Relids relids)
bool *nullsFirst; bool *nullsFirst;
/* Compute sort column info, and adjust lefttree as needed */ /* 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, relids,
NULL, NULL,
false, false,
@@ -6420,14 +6430,16 @@ make_sort_from_pathkeys(Plan *lefttree, List *pathkeys, Relids relids)
* make_incrementalsort_from_pathkeys * make_incrementalsort_from_pathkeys
* Create sort plan to sort according to given 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 * 'lefttree' is the node which yields input tuples
* 'pathkeys' is the list of pathkeys by which the result is to be sorted * '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() * 'relids' is the set of relations required by prepare_sort_from_pathkeys()
* 'nPresortedCols' is the number of presorted columns in input tuples * 'nPresortedCols' is the number of presorted columns in input tuples
*/ */
static IncrementalSort * static IncrementalSort *
make_incrementalsort_from_pathkeys(Plan *lefttree, List *pathkeys, make_incrementalsort_from_pathkeys(PlannerInfo *root, Plan *lefttree,
Relids relids, int nPresortedCols) List *pathkeys, Relids relids,
int nPresortedCols)
{ {
int numsortkeys; int numsortkeys;
AttrNumber *sortColIdx; AttrNumber *sortColIdx;
@@ -6436,7 +6448,7 @@ make_incrementalsort_from_pathkeys(Plan *lefttree, List *pathkeys,
bool *nullsFirst; bool *nullsFirst;
/* Compute sort column info, and adjust lefttree as needed */ /* 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, relids,
NULL, NULL,
false, false,

View File

@@ -11670,6 +11670,93 @@ order by 1, 2;
0 | 4567890123456789 | 1 0 | 4567890123456789 | 1
(5 rows) (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)
reset enable_nestloop;
-- 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.g from generate_series(1, 1000) t3(g), int4_tbl t4) s(x, y) on true
join tenk1 t2 on s.x + s.y = t2.unique1
where t1.f1 = 0;
QUERY PLAN
------------------------------------------------------------------------------
Aggregate
Output: count(*)
-> Nested Loop
-> Seq Scan on public.int4_tbl t1
Output: t1.f1
Filter: (t1.f1 = 0)
-> Merge Join
Merge Cond: (((t1.f1 + t3.g)) = t2.unique1)
-> Sort
Output: t3.g, (t1.f1), ((t1.f1 + t3.g))
Sort Key: ((t1.f1 + t3.g))
-> Nested Loop
Output: t3.g, t1.f1, (t1.f1 + t3.g)
-> Function Scan on pg_catalog.generate_series t3
Output: t3.g
Function Call: generate_series(1, 1000)
-> Materialize
-> Seq Scan on public.int4_tbl t4
-> Index Only Scan using tenk1_unique1 on public.tenk1 t2
Output: t2.unique1
(20 rows)
select count(*) from int4_tbl t1
left join lateral
(select t1.f1, t3.g from generate_series(1, 1000) t3(g), int4_tbl t4) s(x, y) on true
join tenk1 t2 on s.x + s.y = t2.unique1
where t1.f1 = 0;
count
-------
5000
(1 row)
reset enable_hashjoin;
-- test some error cases where LATERAL should have been used but wasn't -- 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 f1 as g) ss;
ERROR: column "f1" does not exist ERROR: column "f1" does not exist

View File

@@ -4390,6 +4390,40 @@ from int4_tbl i4,
where i4.f1 = 0 where i4.f1 = 0
order by 1, 2; 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;
reset enable_nestloop;
-- 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.g from generate_series(1, 1000) t3(g), int4_tbl t4) s(x, y) on true
join tenk1 t2 on s.x + s.y = t2.unique1
where t1.f1 = 0;
select count(*) from int4_tbl t1
left join lateral
(select t1.f1, t3.g from generate_series(1, 1000) t3(g), int4_tbl t4) s(x, y) on true
join tenk1 t2 on s.x + s.y = t2.unique1
where t1.f1 = 0;
reset enable_hashjoin;
-- test some error cases where LATERAL should have been used but wasn't -- 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 f1 as g) ss;
select f1,g from int4_tbl a, (select a.f1 as g) ss; select f1,g from int4_tbl a, (select a.f1 as g) ss;