Skip to content

Commit fe194f7

Browse files
committed
Fix mis-planning of repeated application of a projection.
create_projection_plan contains a hidden assumption (here made explicit by an Assert) that a projection-capable Path will yield a projection-capable Plan. Unfortunately, that assumption is violated only a few lines away, by create_projection_plan itself. This means that two stacked ProjectionPaths can yield an outcome where we try to jam the upper path's tlist into a non-projection-capable child node, resulting in an invalid plan. There isn't any good reason to have stacked ProjectionPaths; indeed the whole concept is faulty, since the set of Vars/Aggs/etc needed by the upper one wouldn't necessarily be available in the output of the lower one, nor could the lower one create such values if they weren't available from its input. Hence, we can fix this by adjusting create_projection_path to strip any top-level ProjectionPath from the subpath it's given. (This amounts to saying "oh, we changed our minds about what we need to project here".) The test case added here only fails in v13 and HEAD; before that, we don't attempt to shove the Sort into the parallel part of the plan, for reasons that aren't entirely clear to me. However, all the directly-related code looks generally the same as far back as v11, where the hazard was introduced (by d7c19e6). So I've got no faith that the same type of bug doesn't exist in v11 and v12, given the right test case. Hence, back-patch the code changes, but not the irrelevant test case, into those branches. Per report from Bas Poot. Discussion: https://postgr.es/m/534fca83789c4a378c7de379e9067d4f@politie.nl
1 parent b6ca46e commit fe194f7

File tree

2 files changed

+19
-1
lines changed

2 files changed

+19
-1
lines changed

src/backend/optimizer/plan/createplan.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1671,6 +1671,7 @@ create_projection_plan(PlannerInfo *root, ProjectionPath *best_path, int flags)
16711671
*/
16721672
subplan = create_plan_recurse(root, best_path->subpath,
16731673
CP_IGNORE_TLIST);
1674+
Assert(is_projection_capable_plan(subplan));
16741675
tlist = build_path_tlist(root, &best_path->path);
16751676
}
16761677
else

src/backend/optimizer/util/pathnode.c

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2440,7 +2440,23 @@ create_projection_path(PlannerInfo *root,
24402440
PathTarget *target)
24412441
{
24422442
ProjectionPath *pathnode = makeNode(ProjectionPath);
2443-
PathTarget *oldtarget = subpath->pathtarget;
2443+
PathTarget *oldtarget;
2444+
2445+
/*
2446+
* We mustn't put a ProjectionPath directly above another; it's useless
2447+
* and will confuse create_projection_plan. Rather than making sure all
2448+
* callers handle that, let's implement it here, by stripping off any
2449+
* ProjectionPath in what we're given. Given this rule, there won't be
2450+
* more than one.
2451+
*/
2452+
if (IsA(subpath, ProjectionPath))
2453+
{
2454+
ProjectionPath *subpp = (ProjectionPath *) subpath;
2455+
2456+
Assert(subpp->path.parent == rel);
2457+
subpath = subpp->subpath;
2458+
Assert(!IsA(subpath, ProjectionPath));
2459+
}
24442460

24452461
pathnode->path.pathtype = T_Result;
24462462
pathnode->path.parent = rel;
@@ -2466,6 +2482,7 @@ create_projection_path(PlannerInfo *root,
24662482
* Note: in the latter case, create_projection_plan has to recheck our
24672483
* conclusion; see comments therein.
24682484
*/
2485+
oldtarget = subpath->pathtarget;
24692486
if (is_projection_capable_path(subpath) ||
24702487
equal(oldtarget->exprs, target->exprs))
24712488
{

0 commit comments

Comments
 (0)