aboutsummaryrefslogtreecommitdiff
path: root/src/backend/optimizer/plan/planagg.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/backend/optimizer/plan/planagg.c')
-rw-r--r--src/backend/optimizer/plan/planagg.c320
1 files changed, 90 insertions, 230 deletions
diff --git a/src/backend/optimizer/plan/planagg.c b/src/backend/optimizer/plan/planagg.c
index 373e6ccf3dd..9d6c181e365 100644
--- a/src/backend/optimizer/plan/planagg.c
+++ b/src/backend/optimizer/plan/planagg.c
@@ -35,13 +35,14 @@
#include "nodes/nodeFuncs.h"
#include "optimizer/clauses.h"
#include "optimizer/cost.h"
+#include "optimizer/pathnode.h"
#include "optimizer/paths.h"
#include "optimizer/planmain.h"
-#include "optimizer/planner.h"
#include "optimizer/subselect.h"
#include "optimizer/tlist.h"
#include "parser/parsetree.h"
#include "parser/parse_clause.h"
+#include "rewrite/rewriteManip.h"
#include "utils/lsyscache.h"
#include "utils/syscache.h"
@@ -50,8 +51,6 @@ static bool find_minmax_aggs_walker(Node *node, List **context);
static bool build_minmax_path(PlannerInfo *root, MinMaxAggInfo *mminfo,
Oid eqop, Oid sortop, bool nulls_first);
static void minmax_qp_callback(PlannerInfo *root, void *extra);
-static void make_agg_subplan(PlannerInfo *root, MinMaxAggInfo *mminfo);
-static Node *replace_aggs_with_params_mutator(Node *node, PlannerInfo *root);
static Oid fetch_agg_sort_op(Oid aggfnoid);
@@ -60,8 +59,14 @@ static Oid fetch_agg_sort_op(Oid aggfnoid);
*
* Check to see whether the query contains MIN/MAX aggregate functions that
* might be optimizable via indexscans. If it does, and all the aggregates
- * are potentially optimizable, then set up root->minmax_aggs with a list of
- * these aggregates.
+ * are potentially optimizable, then create a MinMaxAggPath and add it to
+ * the (UPPERREL_GROUP_AGG, NULL) upperrel.
+ *
+ * This should be called by grouping_planner() just before it's ready to call
+ * query_planner(), because we generate indexscan paths by cloning the
+ * planner's state and invoking query_planner() on a modified version of
+ * the query parsetree. Thus, all preprocessing needed before query_planner()
+ * must already be done.
*
* Note: we are passed the preprocessed targetlist separately, because it's
* not necessarily equal to root->parse->targetList.
@@ -74,6 +79,7 @@ preprocess_minmax_aggregates(PlannerInfo *root, List *tlist)
RangeTblRef *rtr;
RangeTblEntry *rte;
List *aggs_list;
+ RelOptInfo *grouped_rel;
ListCell *lc;
/* minmax_aggs list should be empty at this point */
@@ -91,12 +97,10 @@ preprocess_minmax_aggregates(PlannerInfo *root, List *tlist)
*
* We don't handle GROUP BY or windowing, because our current
* implementations of grouping require looking at all the rows anyway, and
- * so there's not much point in optimizing MIN/MAX. (Note: relaxing this
- * would likely require some restructuring in grouping_planner(), since it
- * performs assorted processing related to these features between calling
- * preprocess_minmax_aggregates and optimize_minmax_aggregates.)
+ * so there's not much point in optimizing MIN/MAX.
*/
- if (parse->groupClause || list_length(parse->groupingSets) > 1 || parse->hasWindowFuncs)
+ if (parse->groupClause || list_length(parse->groupingSets) > 1 ||
+ parse->hasWindowFuncs)
return;
/*
@@ -138,11 +142,9 @@ preprocess_minmax_aggregates(PlannerInfo *root, List *tlist)
/*
* OK, there is at least the possibility of performing the optimization.
- * Build an access path for each aggregate. (We must do this now because
- * we need to call query_planner with a pristine copy of the current query
- * tree; it'll be too late when optimize_minmax_aggregates gets called.)
- * If any of the aggregates prove to be non-indexable, give up; there is
- * no point in optimizing just some of them.
+ * Build an access path for each aggregate. If any of the aggregates
+ * prove to be non-indexable, give up; there is no point in optimizing
+ * just some of them.
*/
foreach(lc, aggs_list)
{
@@ -177,111 +179,40 @@ preprocess_minmax_aggregates(PlannerInfo *root, List *tlist)
}
/*
- * We're done until path generation is complete. Save info for later.
- * (Setting root->minmax_aggs non-NIL signals we succeeded in making index
- * access paths for all the aggregates.)
- */
- root->minmax_aggs = aggs_list;
-}
-
-/*
- * optimize_minmax_aggregates - check for optimizing MIN/MAX via indexes
- *
- * Check to see whether using the aggregate indexscans is cheaper than the
- * generic aggregate method. If so, generate and return a Plan that does it
- * that way. Otherwise, return NULL.
- *
- * Note: it seems likely that the generic method will never be cheaper
- * in practice, except maybe for tiny tables where it'd hardly matter.
- * Should we skip even trying to build the standard plan, if
- * preprocess_minmax_aggregates succeeds?
- *
- * We are passed the preprocessed tlist, as well as the estimated costs for
- * doing the aggregates the regular way, and the best path devised for
- * computing the input of a standard Agg node.
- */
-Plan *
-optimize_minmax_aggregates(PlannerInfo *root, List *tlist,
- const AggClauseCosts *aggcosts, Path *best_path)
-{
- Query *parse = root->parse;
- Cost total_cost;
- Path agg_p;
- Plan *plan;
- Node *hqual;
- ListCell *lc;
-
- /* Nothing to do if preprocess_minmax_aggs rejected the query */
- if (root->minmax_aggs == NIL)
- return NULL;
-
- /*
- * Now we have enough info to compare costs against the generic aggregate
- * implementation.
+ * OK, we can do the query this way. Prepare to create a MinMaxAggPath
+ * node.
*
- * Note that we don't include evaluation cost of the tlist here; this is
- * OK since it isn't included in best_path's cost either, and should be
- * the same in either case.
+ * First, create an output Param node for each agg. (If we end up not
+ * using the MinMaxAggPath, we'll waste a PARAM_EXEC slot for each agg,
+ * which is not worth worrying about. We can't wait till create_plan time
+ * to decide whether to make the Param, unfortunately.)
*/
- total_cost = 0;
- foreach(lc, root->minmax_aggs)
+ foreach(lc, aggs_list)
{
MinMaxAggInfo *mminfo = (MinMaxAggInfo *) lfirst(lc);
- total_cost += mminfo->pathcost;
+ mminfo->param =
+ SS_make_initplan_output_param(root,
+ exprType((Node *) mminfo->target),
+ -1,
+ exprCollation((Node *) mminfo->target));
}
- cost_agg(&agg_p, root, AGG_PLAIN, aggcosts,
- 0, 0,
- best_path->startup_cost, best_path->total_cost,
- best_path->parent->rows);
-
- if (total_cost > agg_p.total_cost)
- return NULL; /* too expensive */
-
/*
- * OK, we are going to generate an optimized plan.
+ * Create a MinMaxAggPath node with the appropriate estimated costs and
+ * other needed data, and add it to the UPPERREL_GROUP_AGG upperrel, where
+ * it will compete against the standard aggregate implementation. (It
+ * will likely always win, but we need not assume that here.)
*
- * First, generate a subplan and output Param node for each agg.
- */
- foreach(lc, root->minmax_aggs)
- {
- MinMaxAggInfo *mminfo = (MinMaxAggInfo *) lfirst(lc);
-
- make_agg_subplan(root, mminfo);
- }
-
- /*
- * Modify the targetlist and HAVING qual to reference subquery outputs
+ * Note: grouping_planner won't have created this upperrel yet, but it's
+ * fine for us to create it first.
*/
- tlist = (List *) replace_aggs_with_params_mutator((Node *) tlist, root);
- hqual = replace_aggs_with_params_mutator(parse->havingQual, root);
-
- /*
- * We have to replace Aggrefs with Params in equivalence classes too, else
- * ORDER BY or DISTINCT on an optimized aggregate will fail. We don't
- * need to process child eclass members though, since they aren't of
- * interest anymore --- and replace_aggs_with_params_mutator isn't able to
- * handle Aggrefs containing translated child Vars, anyway.
- *
- * Note: at some point it might become necessary to mutate other data
- * structures too, such as the query's sortClause or distinctClause. Right
- * now, those won't be examined after this point.
- */
- mutate_eclass_expressions(root,
- replace_aggs_with_params_mutator,
- (void *) root,
- false);
-
- /*
- * Generate the output plan --- basically just a Result
- */
- plan = (Plan *) make_result(root, tlist, hqual, NULL);
-
- /* Account for evaluation cost of the tlist (make_result did the rest) */
- add_tlist_costs_to_plan(root, plan, tlist);
-
- return plan;
+ grouped_rel = fetch_upper_rel(root, UPPERREL_GROUP_AGG, NULL);
+ add_path(grouped_rel, (Path *)
+ create_minmaxagg_path(root, grouped_rel,
+ create_pathtarget(root, tlist),
+ aggs_list,
+ (List *) parse->havingQual));
}
/*
@@ -403,6 +334,7 @@ build_minmax_path(PlannerInfo *root, MinMaxAggInfo *mminfo,
PlannerInfo *subroot;
Query *parse;
TargetEntry *tle;
+ List *tlist;
NullTest *ntest;
SortGroupClause *sortcl;
RelOptInfo *final_rel;
@@ -410,40 +342,51 @@ build_minmax_path(PlannerInfo *root, MinMaxAggInfo *mminfo,
Cost path_cost;
double path_fraction;
- /*----------
- * Generate modified query of the form
- * (SELECT col FROM tab
- * WHERE col IS NOT NULL AND existing-quals
- * ORDER BY col ASC/DESC
- * LIMIT 1)
- *
- * We cheat a bit here by building what is effectively a subplan query
- * level without taking the trouble to increment varlevelsup of outer
- * references. Therefore we don't increment the subroot's query_level nor
- * repoint its parent_root to the parent level. We can get away with that
- * because the plan will be an initplan and therefore cannot need any
- * parameters from the parent level. But see hackery in make_agg_subplan;
- * we might someday need to do this the hard way.
- *----------
+ /*
+ * We are going to construct what is effectively a sub-SELECT query, so
+ * clone the current query level's state and adjust it to make it look
+ * like a subquery. Any outer references will now be one level higher
+ * than before. (This means that when we are done, there will be no Vars
+ * of level 1, which is why the subquery can become an initplan.)
*/
subroot = (PlannerInfo *) palloc(sizeof(PlannerInfo));
memcpy(subroot, root, sizeof(PlannerInfo));
- subroot->parse = parse = (Query *) copyObject(root->parse);
+ subroot->query_level++;
+ subroot->parent_root = root;
/* reset subplan-related stuff */
subroot->plan_params = NIL;
subroot->outer_params = NULL;
subroot->init_plans = NIL;
+ subroot->cte_plan_ids = NIL;
+
+ subroot->parse = parse = (Query *) copyObject(root->parse);
+ IncrementVarSublevelsUp((Node *) parse, 1, 1);
+
+ /* append_rel_list might contain outer Vars? */
+ subroot->append_rel_list = (List *) copyObject(root->append_rel_list);
+ IncrementVarSublevelsUp((Node *) subroot->append_rel_list, 1, 1);
/* There shouldn't be any OJ info to translate, as yet */
Assert(subroot->join_info_list == NIL);
+ /* and we haven't made equivalence classes, either */
+ Assert(subroot->eq_classes == NIL);
/* and we haven't created PlaceHolderInfos, either */
Assert(subroot->placeholder_list == NIL);
+ /*----------
+ * Generate modified query of the form
+ * (SELECT col FROM tab
+ * WHERE col IS NOT NULL AND existing-quals
+ * ORDER BY col ASC/DESC
+ * LIMIT 1)
+ *----------
+ */
/* single tlist entry that is the aggregate target */
tle = makeTargetEntry(copyObject(mminfo->target),
(AttrNumber) 1,
pstrdup("agg_target"),
false);
- parse->targetList = list_make1(tle);
+ tlist = list_make1(tle);
+ subroot->processed_tlist = parse->targetList = tlist;
/* No HAVING, no DISTINCT, no aggregates anymore */
parse->havingQual = NULL;
@@ -467,7 +410,7 @@ build_minmax_path(PlannerInfo *root, MinMaxAggInfo *mminfo,
/* Build suitable ORDER BY clause */
sortcl = makeNode(SortGroupClause);
- sortcl->tleSortGroupRef = assignSortGroupRef(tle, parse->targetList);
+ sortcl->tleSortGroupRef = assignSortGroupRef(tle, tlist);
sortcl->eqop = eqop;
sortcl->sortop = sortop;
sortcl->nulls_first = nulls_first;
@@ -488,8 +431,16 @@ build_minmax_path(PlannerInfo *root, MinMaxAggInfo *mminfo,
subroot->tuple_fraction = 1.0;
subroot->limit_tuples = 1.0;
- final_rel = query_planner(subroot, parse->targetList,
- minmax_qp_callback, NULL);
+ final_rel = query_planner(subroot, tlist, minmax_qp_callback, NULL);
+
+ /*
+ * Since we didn't go through subquery_planner() to handle the subquery,
+ * we have to do some of the same cleanup it would do, in particular cope
+ * with params and initplans used within this subquery. (This won't
+ * matter if we end up not using the subplan.)
+ */
+ SS_identify_outer_params(subroot);
+ SS_charge_for_initplans(subroot, final_rel);
/*
* Get the best presorted path, that being the one that's cheapest for
@@ -509,6 +460,14 @@ build_minmax_path(PlannerInfo *root, MinMaxAggInfo *mminfo,
return false;
/*
+ * The path might not return exactly what we want, so fix that. (We
+ * assume that this won't change any conclusions about which was the
+ * cheapest path.)
+ */
+ sorted_path = apply_projection_to_path(subroot, final_rel, sorted_path,
+ create_pathtarget(root, tlist));
+
+ /*
* Determine cost to get just the first row of the presorted path.
*
* Note: cost calculation here should match
@@ -526,7 +485,7 @@ build_minmax_path(PlannerInfo *root, MinMaxAggInfo *mminfo,
}
/*
- * Compute query_pathkeys and other pathkeys during plan generation
+ * Compute query_pathkeys and other pathkeys during query_planner()
*/
static void
minmax_qp_callback(PlannerInfo *root, void *extra)
@@ -544,105 +503,6 @@ minmax_qp_callback(PlannerInfo *root, void *extra)
}
/*
- * Construct a suitable plan for a converted aggregate query
- */
-static void
-make_agg_subplan(PlannerInfo *root, MinMaxAggInfo *mminfo)
-{
- PlannerInfo *subroot = mminfo->subroot;
- Query *subparse = subroot->parse;
- Plan *plan;
-
- /*
- * Generate the plan for the subquery. We already have a Path, but we have
- * to convert it to a Plan and attach a LIMIT node above it.
- */
- plan = create_plan(subroot, mminfo->path);
-
- /*
- * If the top-level plan node is one that cannot do expression evaluation
- * and its existing target list isn't already what we need, we must insert
- * a Result node to project the desired tlist.
- */
- if (!is_projection_capable_plan(plan) &&
- !tlist_same_exprs(subparse->targetList, plan->targetlist))
- {
- plan = (Plan *) make_result(subroot,
- subparse->targetList,
- NULL,
- plan);
- }
- else
- {
- /*
- * Otherwise, just replace the subplan's flat tlist with the desired
- * tlist.
- */
- plan->targetlist = subparse->targetList;
- }
-
- plan = (Plan *) make_limit(plan,
- subparse->limitOffset,
- subparse->limitCount,
- 0, 1);
-
- /*
- * We have to do some of the same cleanup that subquery_planner() would
- * do, namely cope with params and initplans used within this plan tree.
- *
- * This is a little bit messy because although we initially created the
- * subroot by cloning the outer root, it really is a subplan and needs to
- * consider initplans belonging to the outer root as providing available
- * parameters. So temporarily change its parent_root pointer.
- * (Fortunately, SS_identify_outer_params doesn't care whether the depth
- * of parent_root nesting matches query_level.)
- */
- subroot->parent_root = root;
- SS_identify_outer_params(subroot);
- subroot->parent_root = root->parent_root;
-
- SS_attach_initplans(subroot, plan);
-
- /*
- * Convert the plan into an InitPlan, and make a Param for its result.
- */
- mminfo->param =
- SS_make_initplan_from_plan(root, subroot, plan,
- exprType((Node *) mminfo->target),
- -1,
- exprCollation((Node *) mminfo->target));
-}
-
-/*
- * Replace original aggregate calls with subplan output Params
- */
-static Node *
-replace_aggs_with_params_mutator(Node *node, PlannerInfo *root)
-{
- if (node == NULL)
- return NULL;
- if (IsA(node, Aggref))
- {
- Aggref *aggref = (Aggref *) node;
- TargetEntry *curTarget = (TargetEntry *) linitial(aggref->args);
- ListCell *lc;
-
- foreach(lc, root->minmax_aggs)
- {
- MinMaxAggInfo *mminfo = (MinMaxAggInfo *) lfirst(lc);
-
- if (mminfo->aggfnoid == aggref->aggfnoid &&
- equal(mminfo->target, curTarget->expr))
- return (Node *) mminfo->param;
- }
- elog(ERROR, "failed to re-find MinMaxAggInfo record");
- }
- Assert(!IsA(node, SubLink));
- return expression_tree_mutator(node, replace_aggs_with_params_mutator,
- (void *) root);
-}
-
-/*
* Get the OID of the sort operator, if any, associated with an aggregate.
* Returns InvalidOid if there is no such operator.
*/