Skip to content

Commit d7c19e6

Browse files
committed
Teach create_projection_plan to omit projection where possible.
We sometimes insert a ProjectionPath into a plan tree when projection is not strictly required. The existing code already arranges to avoid emitting a Result node when the ProjectionPath's subpath can perform the projection itself, but previously it didn't consider the possibility that the parent node might not actually require the projection to be performed at all. Skipping projection when it's not required can not only avoid Result nodes that aren't needed, but also avoid losing the "physical tlist" optimization unneccessarily. Patch by me, reviewed by Amit Kapila. Discussion: http://postgr.es/m/CA+TgmoakT5gmahbPWGqrR2nAdFOMAOnOXYoWHRdVfGWs34t6_A@mail.gmail.com
1 parent 20b4323 commit d7c19e6

File tree

1 file changed

+75
-23
lines changed

1 file changed

+75
-23
lines changed

src/backend/optimizer/plan/createplan.c

+75-23
Original file line numberDiff line numberDiff line change
@@ -62,10 +62,14 @@
6262
* any sortgrouprefs specified in its pathtarget, with appropriate
6363
* ressortgroupref labels. This is passed down by parent nodes such as Sort
6464
* and Group, which need these values to be available in their inputs.
65+
*
66+
* CP_IGNORE_TLIST specifies that the caller plans to replace the targetlist,
67+
* and therefore it doens't matter a bit what target list gets generated.
6568
*/
6669
#define CP_EXACT_TLIST 0x0001 /* Plan must return specified tlist */
6770
#define CP_SMALL_TLIST 0x0002 /* Prefer narrower tlists */
6871
#define CP_LABEL_TLIST 0x0004 /* tlist must contain sortgrouprefs */
72+
#define CP_IGNORE_TLIST 0x0008 /* caller will replace tlist */
6973

7074

7175
static Plan *create_plan_recurse(PlannerInfo *root, Path *best_path,
@@ -87,7 +91,9 @@ static Material *create_material_plan(PlannerInfo *root, MaterialPath *best_path
8791
static Plan *create_unique_plan(PlannerInfo *root, UniquePath *best_path,
8892
int flags);
8993
static Gather *create_gather_plan(PlannerInfo *root, GatherPath *best_path);
90-
static Plan *create_projection_plan(PlannerInfo *root, ProjectionPath *best_path);
94+
static Plan *create_projection_plan(PlannerInfo *root,
95+
ProjectionPath *best_path,
96+
int flags);
9197
static Plan *inject_projection_plan(Plan *subplan, List *tlist, bool parallel_safe);
9298
static Sort *create_sort_plan(PlannerInfo *root, SortPath *best_path, int flags);
9399
static Group *create_group_plan(PlannerInfo *root, GroupPath *best_path);
@@ -400,7 +406,8 @@ create_plan_recurse(PlannerInfo *root, Path *best_path, int flags)
400406
if (IsA(best_path, ProjectionPath))
401407
{
402408
plan = create_projection_plan(root,
403-
(ProjectionPath *) best_path);
409+
(ProjectionPath *) best_path,
410+
flags);
404411
}
405412
else if (IsA(best_path, MinMaxAggPath))
406413
{
@@ -563,8 +570,16 @@ create_scan_plan(PlannerInfo *root, Path *best_path, int flags)
563570
* only those Vars actually needed by the query), we prefer to generate a
564571
* tlist containing all Vars in order. This will allow the executor to
565572
* optimize away projection of the table tuples, if possible.
573+
*
574+
* But if the caller is going to ignore our tlist anyway, then don't
575+
* bother generating one at all. We use an exact equality test here, so
576+
* that this only applies when CP_IGNORE_TLIST is the only flag set.
566577
*/
567-
if (use_physical_tlist(root, best_path, flags))
578+
if (flags == CP_IGNORE_TLIST)
579+
{
580+
tlist = NULL;
581+
}
582+
else if (use_physical_tlist(root, best_path, flags))
568583
{
569584
if (best_path->pathtype == T_IndexOnlyScan)
570585
{
@@ -1567,34 +1582,71 @@ create_gather_merge_plan(PlannerInfo *root, GatherMergePath *best_path)
15671582
* but sometimes we can just let the subplan do the work.
15681583
*/
15691584
static Plan *
1570-
create_projection_plan(PlannerInfo *root, ProjectionPath *best_path)
1585+
create_projection_plan(PlannerInfo *root, ProjectionPath *best_path, int flags)
15711586
{
15721587
Plan *plan;
15731588
Plan *subplan;
15741589
List *tlist;
1590+
bool needs_result_node = false;
15751591

1576-
/* Since we intend to project, we don't need to constrain child tlist */
1577-
subplan = create_plan_recurse(root, best_path->subpath, 0);
1578-
1579-
tlist = build_path_tlist(root, &best_path->path);
1592+
/*
1593+
* Convert our subpath to a Plan and determine whether we need a Result
1594+
* node.
1595+
*
1596+
* In most cases where we don't need to project, creation_projection_path
1597+
* will have set dummypp, but not always. First, some createplan.c
1598+
* routines change the tlists of their nodes. (An example is that
1599+
* create_merge_append_plan might add resjunk sort columns to a
1600+
* MergeAppend.) Second, create_projection_path has no way of knowing
1601+
* what path node will be placed on top of the projection path and
1602+
* therefore can't predict whether it will require an exact tlist. For
1603+
* both of these reasons, we have to recheck here.
1604+
*/
1605+
if (use_physical_tlist(root, &best_path->path, flags))
1606+
{
1607+
/*
1608+
* Our caller doesn't really care what tlist we return, so we don't
1609+
* actually need to project. However, we may still need to ensure
1610+
* proper sortgroupref labels, if the caller cares about those.
1611+
*/
1612+
subplan = create_plan_recurse(root, best_path->subpath, 0);
1613+
tlist = subplan->targetlist;
1614+
if ((flags & CP_LABEL_TLIST) != 0)
1615+
apply_pathtarget_labeling_to_tlist(tlist,
1616+
best_path->path.pathtarget);
1617+
}
1618+
else if (is_projection_capable_path(best_path->subpath))
1619+
{
1620+
/*
1621+
* Our caller requires that we return the exact tlist, but no separate
1622+
* result node is needed because the subpath is projection-capable.
1623+
* Tell create_plan_recurse that we're going to ignore the tlist it
1624+
* produces.
1625+
*/
1626+
subplan = create_plan_recurse(root, best_path->subpath,
1627+
CP_IGNORE_TLIST);
1628+
tlist = build_path_tlist(root, &best_path->path);
1629+
}
1630+
else
1631+
{
1632+
/*
1633+
* It looks like we need a result node, unless by good fortune the
1634+
* requested tlist is exactly the one the child wants to produce.
1635+
*/
1636+
subplan = create_plan_recurse(root, best_path->subpath, 0);
1637+
tlist = build_path_tlist(root, &best_path->path);
1638+
needs_result_node = !tlist_same_exprs(tlist, subplan->targetlist);
1639+
}
15801640

15811641
/*
1582-
* We might not really need a Result node here, either because the subplan
1583-
* can project or because it's returning the right list of expressions
1584-
* anyway. Usually create_projection_path will have detected that and set
1585-
* dummypp if we don't need a Result; but its decision can't be final,
1586-
* because some createplan.c routines change the tlists of their nodes.
1587-
* (An example is that create_merge_append_plan might add resjunk sort
1588-
* columns to a MergeAppend.) So we have to recheck here. If we do
1589-
* arrive at a different answer than create_projection_path did, we'll
1590-
* have made slightly wrong cost estimates; but label the plan with the
1591-
* cost estimates we actually used, not "corrected" ones. (XXX this could
1592-
* be cleaned up if we moved more of the sortcolumn setup logic into Path
1593-
* creation, but that would add expense to creating Paths we might end up
1594-
* not using.)
1642+
* If we make a different decision about whether to include a Result node
1643+
* than create_projection_path did, we'll have made slightly wrong cost
1644+
* estimates; but label the plan with the cost estimates we actually used,
1645+
* not "corrected" ones. (XXX this could be cleaned up if we moved more
1646+
* of the sortcolumn setup logic into Path creation, but that would add
1647+
* expense to creating Paths we might end up not using.)
15951648
*/
1596-
if (is_projection_capable_path(best_path->subpath) ||
1597-
tlist_same_exprs(tlist, subplan->targetlist))
1649+
if (!needs_result_node)
15981650
{
15991651
/* Don't need a separate Result, just assign tlist to subplan */
16001652
plan = subplan;

0 commit comments

Comments
 (0)