aboutsummaryrefslogtreecommitdiff
path: root/src/backend/optimizer
diff options
context:
space:
mode:
Diffstat (limited to 'src/backend/optimizer')
-rw-r--r--src/backend/optimizer/path/allpaths.c36
-rw-r--r--src/backend/optimizer/plan/createplan.c8
-rw-r--r--src/backend/optimizer/plan/setrefs.c264
-rw-r--r--src/backend/optimizer/util/pathnode.c42
4 files changed, 245 insertions, 105 deletions
diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c
index b2c5c833f72..da0d7787214 100644
--- a/src/backend/optimizer/path/allpaths.c
+++ b/src/backend/optimizer/path/allpaths.c
@@ -1044,8 +1044,8 @@ set_append_rel_size(PlannerInfo *root, RelOptInfo *rel,
/*
* We have to copy the parent's targetlist and quals to the child,
* with appropriate substitution of variables. If any constant false
- * or NULL clauses turn up, we can disregard the child right away.
- * If not, we can apply constraint exclusion with just the
+ * or NULL clauses turn up, we can disregard the child right away. If
+ * not, we can apply constraint exclusion with just the
* baserestrictinfo quals.
*/
if (!apply_child_basequals(root, rel, childrel, childRTE, appinfo))
@@ -1708,6 +1708,38 @@ add_paths_to_append_rel(PlannerInfo *root, RelOptInfo *rel,
required_outer, 0, false,
partitioned_rels, -1));
}
+
+ /*
+ * When there is only a single child relation, the Append path can inherit
+ * any ordering available for the child rel's path, so that it's useful to
+ * consider ordered partial paths. Above we only considered the cheapest
+ * partial path for each child, but let's also make paths using any
+ * partial paths that have pathkeys.
+ */
+ if (list_length(live_childrels) == 1)
+ {
+ RelOptInfo *childrel = (RelOptInfo *) linitial(live_childrels);
+
+ foreach(l, childrel->partial_pathlist)
+ {
+ Path *path = (Path *) lfirst(l);
+ AppendPath *appendpath;
+
+ /*
+ * Skip paths with no pathkeys. Also skip the cheapest partial
+ * path, since we already used that above.
+ */
+ if (path->pathkeys == NIL ||
+ path == linitial(childrel->partial_pathlist))
+ continue;
+
+ appendpath = create_append_path(root, rel, NIL, list_make1(path),
+ NULL, path->parallel_workers,
+ true,
+ partitioned_rels, partial_rows);
+ add_partial_path(rel, (Path *) appendpath);
+ }
+ }
}
/*
diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c
index 93c56c657ce..979c3c212fd 100644
--- a/src/backend/optimizer/plan/createplan.c
+++ b/src/backend/optimizer/plan/createplan.c
@@ -1134,10 +1134,10 @@ create_append_plan(PlannerInfo *root, AppendPath *best_path)
}
/*
- * XXX ideally, if there's just one child, we'd not bother to generate an
- * Append node but just return the single child. At the moment this does
- * not work because the varno of the child scan plan won't match the
- * parent-rel Vars it'll be asked to emit.
+ * And build the Append plan. Note that if there's just one child, the
+ * Append is pretty useless; but we wait till setrefs.c to get rid of it.
+ * Doing so here doesn't work because the varno of the child scan plan
+ * won't match the parent-rel Vars it'll be asked to emit.
*/
plan = make_append(subplans, best_path->first_partial_path,
diff --git a/src/backend/optimizer/plan/setrefs.c b/src/backend/optimizer/plan/setrefs.c
index 0213a376705..4204ca4025d 100644
--- a/src/backend/optimizer/plan/setrefs.c
+++ b/src/backend/optimizer/plan/setrefs.c
@@ -94,12 +94,19 @@ static Plan *set_subqueryscan_references(PlannerInfo *root,
SubqueryScan *plan,
int rtoffset);
static bool trivial_subqueryscan(SubqueryScan *plan);
+static Plan *clean_up_removed_plan_level(Plan *parent, Plan *child);
static void set_foreignscan_references(PlannerInfo *root,
ForeignScan *fscan,
int rtoffset);
static void set_customscan_references(PlannerInfo *root,
CustomScan *cscan,
int rtoffset);
+static Plan *set_append_references(PlannerInfo *root,
+ Append *aplan,
+ int rtoffset);
+static Plan *set_mergeappend_references(PlannerInfo *root,
+ MergeAppend *mplan,
+ int rtoffset);
static Node *fix_scan_expr(PlannerInfo *root, Node *node, int rtoffset);
static Node *fix_scan_expr_mutator(Node *node, fix_scan_expr_context *context);
static bool fix_scan_expr_walker(Node *node, fix_scan_expr_context *context);
@@ -181,19 +188,22 @@ static List *set_returning_clause_references(PlannerInfo *root,
* 8. We assign every plan node in the tree a unique ID.
*
* We also perform one final optimization step, which is to delete
- * SubqueryScan plan nodes that aren't doing anything useful (ie, have
- * no qual and a no-op targetlist). The reason for doing this last is that
+ * SubqueryScan, Append, and MergeAppend plan nodes that aren't doing
+ * anything useful. The reason for doing this last is that
* it can't readily be done before set_plan_references, because it would
- * break set_upper_references: the Vars in the subquery's top tlist
- * wouldn't match up with the Vars in the outer plan tree. The SubqueryScan
+ * break set_upper_references: the Vars in the child plan's top tlist
+ * wouldn't match up with the Vars in the outer plan tree. A SubqueryScan
* serves a necessary function as a buffer between outer query and subquery
* variable numbering ... but after we've flattened the rangetable this is
* no longer a problem, since then there's only one rtindex namespace.
+ * Likewise, Append and MergeAppend buffer between the parent and child vars
+ * of an appendrel, but we don't need to worry about that once we've done
+ * set_plan_references.
*
* set_plan_references recursively traverses the whole plan tree.
*
* The return value is normally the same Plan node passed in, but can be
- * different when the passed-in Plan is a SubqueryScan we decide isn't needed.
+ * different when the passed-in Plan is a node we decide isn't needed.
*
* The flattened rangetable entries are appended to root->glob->finalrtable.
* Also, rowmarks entries are appended to root->glob->finalrowmarks, and the
@@ -897,71 +907,15 @@ set_plan_refs(PlannerInfo *root, Plan *plan, int rtoffset)
}
break;
case T_Append:
- {
- Append *splan = (Append *) plan;
-
- /*
- * Append, like Sort et al, doesn't actually evaluate its
- * targetlist or check quals.
- */
- set_dummy_tlist_references(plan, rtoffset);
- Assert(splan->plan.qual == NIL);
- foreach(l, splan->appendplans)
- {
- lfirst(l) = set_plan_refs(root,
- (Plan *) lfirst(l),
- rtoffset);
- }
- if (splan->part_prune_info)
- {
- foreach(l, splan->part_prune_info->prune_infos)
- {
- List *prune_infos = lfirst(l);
- ListCell *l2;
-
- foreach(l2, prune_infos)
- {
- PartitionedRelPruneInfo *pinfo = lfirst(l2);
-
- pinfo->rtindex += rtoffset;
- }
- }
- }
- }
- break;
+ /* Needs special treatment, see comments below */
+ return set_append_references(root,
+ (Append *) plan,
+ rtoffset);
case T_MergeAppend:
- {
- MergeAppend *splan = (MergeAppend *) plan;
-
- /*
- * MergeAppend, like Sort et al, doesn't actually evaluate its
- * targetlist or check quals.
- */
- set_dummy_tlist_references(plan, rtoffset);
- Assert(splan->plan.qual == NIL);
- foreach(l, splan->mergeplans)
- {
- lfirst(l) = set_plan_refs(root,
- (Plan *) lfirst(l),
+ /* Needs special treatment, see comments below */
+ return set_mergeappend_references(root,
+ (MergeAppend *) plan,
rtoffset);
- }
- if (splan->part_prune_info)
- {
- foreach(l, splan->part_prune_info->prune_infos)
- {
- List *prune_infos = lfirst(l);
- ListCell *l2;
-
- foreach(l2, prune_infos)
- {
- PartitionedRelPruneInfo *pinfo = lfirst(l2);
-
- pinfo->rtindex += rtoffset;
- }
- }
- }
- }
- break;
case T_RecursiveUnion:
/* This doesn't evaluate targetlist or check quals either */
set_dummy_tlist_references(plan, rtoffset);
@@ -1086,30 +1040,7 @@ set_subqueryscan_references(PlannerInfo *root,
/*
* We can omit the SubqueryScan node and just pull up the subplan.
*/
- ListCell *lp,
- *lc;
-
- result = plan->subplan;
-
- /* We have to be sure we don't lose any initplans */
- result->initPlan = list_concat(plan->scan.plan.initPlan,
- result->initPlan);
-
- /*
- * We also have to transfer the SubqueryScan's result-column names
- * into the subplan, else columns sent to client will be improperly
- * labeled if this is the topmost plan level. Copy the "source
- * column" information too.
- */
- forboth(lp, plan->scan.plan.targetlist, lc, result->targetlist)
- {
- TargetEntry *ptle = (TargetEntry *) lfirst(lp);
- TargetEntry *ctle = (TargetEntry *) lfirst(lc);
-
- ctle->resname = ptle->resname;
- ctle->resorigtbl = ptle->resorigtbl;
- ctle->resorigcol = ptle->resorigcol;
- }
+ result = clean_up_removed_plan_level((Plan *) plan, plan->subplan);
}
else
{
@@ -1191,6 +1122,30 @@ trivial_subqueryscan(SubqueryScan *plan)
}
/*
+ * clean_up_removed_plan_level
+ * Do necessary cleanup when we strip out a SubqueryScan, Append, etc
+ *
+ * We are dropping the "parent" plan in favor of returning just its "child".
+ * A few small tweaks are needed.
+ */
+static Plan *
+clean_up_removed_plan_level(Plan *parent, Plan *child)
+{
+ /* We have to be sure we don't lose any initplans */
+ child->initPlan = list_concat(parent->initPlan,
+ child->initPlan);
+
+ /*
+ * We also have to transfer the parent's column labeling info into the
+ * child, else columns sent to client will be improperly labeled if this
+ * is the topmost plan level. resjunk and so on may be important too.
+ */
+ apply_tlist_labeling(child->targetlist, parent->targetlist);
+
+ return child;
+}
+
+/*
* set_foreignscan_references
* Do set_plan_references processing on a ForeignScan
*/
@@ -1341,6 +1296,131 @@ set_customscan_references(PlannerInfo *root,
}
/*
+ * set_append_references
+ * Do set_plan_references processing on an Append
+ *
+ * We try to strip out the Append entirely; if we can't, we have
+ * to do the normal processing on it.
+ */
+static Plan *
+set_append_references(PlannerInfo *root,
+ Append *aplan,
+ int rtoffset)
+{
+ ListCell *l;
+
+ /*
+ * Append, like Sort et al, doesn't actually evaluate its targetlist or
+ * check quals. If it's got exactly one child plan, then it's not doing
+ * anything useful at all, and we can strip it out.
+ */
+ Assert(aplan->plan.qual == NIL);
+
+ /* First, we gotta recurse on the children */
+ foreach(l, aplan->appendplans)
+ {
+ lfirst(l) = set_plan_refs(root, (Plan *) lfirst(l), rtoffset);
+ }
+
+ /* Now, if there's just one, forget the Append and return that child */
+ if (list_length(aplan->appendplans) == 1)
+ return clean_up_removed_plan_level((Plan *) aplan,
+ (Plan *) linitial(aplan->appendplans));
+
+ /*
+ * Otherwise, clean up the Append as needed. It's okay to do this after
+ * recursing to the children, because set_dummy_tlist_references doesn't
+ * look at those.
+ */
+ set_dummy_tlist_references((Plan *) aplan, rtoffset);
+
+ if (aplan->part_prune_info)
+ {
+ foreach(l, aplan->part_prune_info->prune_infos)
+ {
+ List *prune_infos = lfirst(l);
+ ListCell *l2;
+
+ foreach(l2, prune_infos)
+ {
+ PartitionedRelPruneInfo *pinfo = lfirst(l2);
+
+ pinfo->rtindex += rtoffset;
+ }
+ }
+ }
+
+ /* We don't need to recurse to lefttree or righttree ... */
+ Assert(aplan->plan.lefttree == NULL);
+ Assert(aplan->plan.righttree == NULL);
+
+ return (Plan *) aplan;
+}
+
+/*
+ * set_mergeappend_references
+ * Do set_plan_references processing on a MergeAppend
+ *
+ * We try to strip out the MergeAppend entirely; if we can't, we have
+ * to do the normal processing on it.
+ */
+static Plan *
+set_mergeappend_references(PlannerInfo *root,
+ MergeAppend *mplan,
+ int rtoffset)
+{
+ ListCell *l;
+
+ /*
+ * MergeAppend, like Sort et al, doesn't actually evaluate its targetlist
+ * or check quals. If it's got exactly one child plan, then it's not
+ * doing anything useful at all, and we can strip it out.
+ */
+ Assert(mplan->plan.qual == NIL);
+
+ /* First, we gotta recurse on the children */
+ foreach(l, mplan->mergeplans)
+ {
+ lfirst(l) = set_plan_refs(root, (Plan *) lfirst(l), rtoffset);
+ }
+
+ /* Now, if there's just one, forget the MergeAppend and return that child */
+ if (list_length(mplan->mergeplans) == 1)
+ return clean_up_removed_plan_level((Plan *) mplan,
+ (Plan *) linitial(mplan->mergeplans));
+
+ /*
+ * Otherwise, clean up the MergeAppend as needed. It's okay to do this
+ * after recursing to the children, because set_dummy_tlist_references
+ * doesn't look at those.
+ */
+ set_dummy_tlist_references((Plan *) mplan, rtoffset);
+
+ if (mplan->part_prune_info)
+ {
+ foreach(l, mplan->part_prune_info->prune_infos)
+ {
+ List *prune_infos = lfirst(l);
+ ListCell *l2;
+
+ foreach(l2, prune_infos)
+ {
+ PartitionedRelPruneInfo *pinfo = lfirst(l2);
+
+ pinfo->rtindex += rtoffset;
+ }
+ }
+ }
+
+ /* We don't need to recurse to lefttree or righttree ... */
+ Assert(mplan->plan.lefttree == NULL);
+ Assert(mplan->plan.righttree == NULL);
+
+ return (Plan *) mplan;
+}
+
+
+/*
* copyVar
* Copy a Var node.
*
diff --git a/src/backend/optimizer/util/pathnode.c b/src/backend/optimizer/util/pathnode.c
index 169e51e7921..56de8fc370a 100644
--- a/src/backend/optimizer/util/pathnode.c
+++ b/src/backend/optimizer/util/pathnode.c
@@ -1242,7 +1242,6 @@ create_append_path(PlannerInfo *root,
pathnode->path.parallel_aware = parallel_aware;
pathnode->path.parallel_safe = rel->consider_parallel;
pathnode->path.parallel_workers = parallel_workers;
- pathnode->path.pathkeys = NIL; /* result is always considered unsorted */
pathnode->partitioned_rels = list_copy(partitioned_rels);
/*
@@ -1276,7 +1275,26 @@ create_append_path(PlannerInfo *root,
Assert(!parallel_aware || pathnode->path.parallel_safe);
- cost_append(pathnode);
+ /*
+ * If there's exactly one child path, the Append is a no-op and will be
+ * discarded later (in setrefs.c); therefore, we can inherit the child's
+ * size, cost, and pathkeys if any. Otherwise, it's unsorted, and we must
+ * do the normal costsize calculation.
+ */
+ if (list_length(pathnode->subpaths) == 1)
+ {
+ Path *child = (Path *) linitial(pathnode->subpaths);
+
+ pathnode->path.rows = child->rows;
+ pathnode->path.startup_cost = child->startup_cost;
+ pathnode->path.total_cost = child->total_cost;
+ pathnode->path.pathkeys = child->pathkeys;
+ }
+ else
+ {
+ pathnode->path.pathkeys = NIL; /* unsorted if more than 1 subpath */
+ cost_append(pathnode);
+ }
/* If the caller provided a row estimate, override the computed value. */
if (rows >= 0)
@@ -1408,11 +1426,21 @@ create_merge_append_path(PlannerInfo *root,
Assert(bms_equal(PATH_REQ_OUTER(subpath), required_outer));
}
- /* Now we can compute total costs of the MergeAppend */
- cost_merge_append(&pathnode->path, root,
- pathkeys, list_length(subpaths),
- input_startup_cost, input_total_cost,
- pathnode->path.rows);
+ /*
+ * Now we can compute total costs of the MergeAppend. If there's exactly
+ * one child path, the MergeAppend is a no-op and will be discarded later
+ * (in setrefs.c); otherwise we do the normal cost calculation.
+ */
+ if (list_length(subpaths) == 1)
+ {
+ pathnode->path.startup_cost = input_startup_cost;
+ pathnode->path.total_cost = input_total_cost;
+ }
+ else
+ cost_merge_append(&pathnode->path, root,
+ pathkeys, list_length(subpaths),
+ input_startup_cost, input_total_cost,
+ pathnode->path.rows);
return pathnode;
}