2005-04-12 01:06:57 +02:00
|
|
|
/*-------------------------------------------------------------------------
|
|
|
|
*
|
|
|
|
* planagg.c
|
|
|
|
* Special planning for aggregate queries.
|
|
|
|
*
|
2007-01-05 23:20:05 +01:00
|
|
|
* Portions Copyright (c) 1996-2007, PostgreSQL Global Development Group
|
2005-04-12 01:06:57 +02:00
|
|
|
* Portions Copyright (c) 1994, Regents of the University of California
|
|
|
|
*
|
|
|
|
*
|
|
|
|
* IDENTIFICATION
|
2007-01-09 03:14:16 +01:00
|
|
|
* $PostgreSQL: pgsql/src/backend/optimizer/plan/planagg.c,v 1.25 2007/01/09 02:14:13 tgl Exp $
|
2005-04-12 01:06:57 +02:00
|
|
|
*
|
|
|
|
*-------------------------------------------------------------------------
|
|
|
|
*/
|
|
|
|
#include "postgres.h"
|
|
|
|
|
|
|
|
#include "catalog/pg_aggregate.h"
|
|
|
|
#include "catalog/pg_type.h"
|
|
|
|
#include "nodes/makefuncs.h"
|
|
|
|
#include "optimizer/clauses.h"
|
|
|
|
#include "optimizer/cost.h"
|
|
|
|
#include "optimizer/pathnode.h"
|
|
|
|
#include "optimizer/paths.h"
|
|
|
|
#include "optimizer/planmain.h"
|
|
|
|
#include "optimizer/subselect.h"
|
|
|
|
#include "parser/parse_clause.h"
|
|
|
|
#include "parser/parse_expr.h"
|
2006-07-11 19:26:59 +02:00
|
|
|
#include "parser/parsetree.h"
|
2005-04-12 01:06:57 +02:00
|
|
|
#include "utils/lsyscache.h"
|
|
|
|
#include "utils/syscache.h"
|
|
|
|
|
|
|
|
|
|
|
|
typedef struct
|
|
|
|
{
|
|
|
|
Oid aggfnoid; /* pg_proc Oid of the aggregate */
|
|
|
|
Oid aggsortop; /* Oid of its sort operator */
|
|
|
|
Expr *target; /* expression we are aggregating on */
|
|
|
|
IndexPath *path; /* access path for index scan */
|
|
|
|
Cost pathcost; /* estimated cost to fetch first row */
|
2007-01-09 03:14:16 +01:00
|
|
|
bool nulls_first; /* null ordering direction matching index */
|
2005-04-12 01:06:57 +02:00
|
|
|
Param *param; /* param for subplan's output */
|
|
|
|
} MinMaxAggInfo;
|
|
|
|
|
|
|
|
static bool find_minmax_aggs_walker(Node *node, List **context);
|
2005-06-06 00:32:58 +02:00
|
|
|
static bool build_minmax_path(PlannerInfo *root, RelOptInfo *rel,
|
2005-10-15 04:49:52 +02:00
|
|
|
MinMaxAggInfo *info);
|
2005-04-12 01:06:57 +02:00
|
|
|
static ScanDirection match_agg_to_index_col(MinMaxAggInfo *info,
|
2005-10-15 04:49:52 +02:00
|
|
|
IndexOptInfo *index, int indexcol);
|
Revise the planner's handling of "pseudoconstant" WHERE clauses, that is
clauses containing no variables and no volatile functions. Such a clause
can be used as a one-time qual in a gating Result plan node, to suppress
plan execution entirely when it is false. Even when the clause is true,
putting it in a gating node wins by avoiding repeated evaluation of the
clause. In previous PG releases, query_planner() would do this for
pseudoconstant clauses appearing at the top level of the jointree, but
there was no ability to generate a gating Result deeper in the plan tree.
To fix it, get rid of the special case in query_planner(), and instead
process pseudoconstant clauses through the normal RestrictInfo qual
distribution mechanism. When a pseudoconstant clause is found attached to
a path node in create_plan(), pull it out and generate a gating Result at
that point. This requires special-casing pseudoconstants in selectivity
estimation and cost_qual_eval, but on the whole it's pretty clean.
It probably even makes the planner a bit faster than before for the normal
case of no pseudoconstants, since removing pull_constant_clauses saves one
useless traversal of the qual tree. Per gripe from Phil Frost.
2006-07-01 20:38:33 +02:00
|
|
|
static void make_agg_subplan(PlannerInfo *root, MinMaxAggInfo *info);
|
2005-10-15 04:49:52 +02:00
|
|
|
static Node *replace_aggs_with_params_mutator(Node *node, List **context);
|
2005-04-12 01:06:57 +02:00
|
|
|
static Oid fetch_agg_sort_op(Oid aggfnoid);
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
* optimize_minmax_aggregates - check for optimizing MIN/MAX via indexes
|
|
|
|
*
|
|
|
|
* This checks to see if we can replace MIN/MAX aggregate functions by
|
|
|
|
* subqueries of the form
|
|
|
|
* (SELECT col FROM tab WHERE ... ORDER BY col ASC/DESC LIMIT 1)
|
|
|
|
* Given a suitable index on tab.col, this can be much faster than the
|
|
|
|
* generic scan-all-the-rows plan.
|
|
|
|
*
|
2005-06-06 00:32:58 +02:00
|
|
|
* We are passed the preprocessed tlist, and the best path
|
2005-10-15 04:49:52 +02:00
|
|
|
* devised for computing the input of a standard Agg node. If we are able
|
2005-04-12 01:06:57 +02:00
|
|
|
* to optimize all the aggregates, and the result is estimated to be cheaper
|
|
|
|
* than the generic aggregate method, then generate and return a Plan that
|
|
|
|
* does it that way. Otherwise, return NULL.
|
|
|
|
*/
|
|
|
|
Plan *
|
2005-06-06 00:32:58 +02:00
|
|
|
optimize_minmax_aggregates(PlannerInfo *root, List *tlist, Path *best_path)
|
2005-04-12 01:06:57 +02:00
|
|
|
{
|
2005-06-06 00:32:58 +02:00
|
|
|
Query *parse = root->parse;
|
2005-04-12 01:06:57 +02:00
|
|
|
RangeTblRef *rtr;
|
|
|
|
RangeTblEntry *rte;
|
|
|
|
RelOptInfo *rel;
|
|
|
|
List *aggs_list;
|
|
|
|
ListCell *l;
|
|
|
|
Cost total_cost;
|
|
|
|
Path agg_p;
|
|
|
|
Plan *plan;
|
|
|
|
Node *hqual;
|
|
|
|
QualCost tlist_cost;
|
|
|
|
|
|
|
|
/* Nothing to do if query has no aggregates */
|
2005-06-06 00:32:58 +02:00
|
|
|
if (!parse->hasAggs)
|
2005-04-12 01:06:57 +02:00
|
|
|
return NULL;
|
|
|
|
|
2005-10-15 04:49:52 +02:00
|
|
|
Assert(!parse->setOperations); /* shouldn't get here if a setop */
|
|
|
|
Assert(parse->rowMarks == NIL); /* nor if FOR UPDATE */
|
2005-04-12 01:06:57 +02:00
|
|
|
|
|
|
|
/*
|
|
|
|
* Reject unoptimizable cases.
|
|
|
|
*
|
2005-11-22 19:17:34 +01:00
|
|
|
* We don't handle GROUP BY, because our current implementations of
|
|
|
|
* grouping require looking at all the rows anyway, and so there's not
|
|
|
|
* much point in optimizing MIN/MAX.
|
2005-04-12 01:06:57 +02:00
|
|
|
*/
|
2005-06-06 00:32:58 +02:00
|
|
|
if (parse->groupClause)
|
2005-04-12 01:06:57 +02:00
|
|
|
return NULL;
|
|
|
|
|
|
|
|
/*
|
2005-10-15 04:49:52 +02:00
|
|
|
* We also restrict the query to reference exactly one table, since join
|
|
|
|
* conditions can't be handled reasonably. (We could perhaps handle a
|
|
|
|
* query containing cartesian-product joins, but it hardly seems worth the
|
|
|
|
* trouble.)
|
2005-04-12 01:06:57 +02:00
|
|
|
*/
|
2005-06-06 00:32:58 +02:00
|
|
|
Assert(parse->jointree != NULL && IsA(parse->jointree, FromExpr));
|
|
|
|
if (list_length(parse->jointree->fromlist) != 1)
|
2005-04-12 01:06:57 +02:00
|
|
|
return NULL;
|
2005-06-06 00:32:58 +02:00
|
|
|
rtr = (RangeTblRef *) linitial(parse->jointree->fromlist);
|
2005-04-12 01:06:57 +02:00
|
|
|
if (!IsA(rtr, RangeTblRef))
|
|
|
|
return NULL;
|
2005-06-06 00:32:58 +02:00
|
|
|
rte = rt_fetch(rtr->rtindex, parse->rtable);
|
2005-09-21 21:15:27 +02:00
|
|
|
if (rte->rtekind != RTE_RELATION || rte->inh)
|
2005-04-12 01:06:57 +02:00
|
|
|
return NULL;
|
|
|
|
rel = find_base_rel(root, rtr->rtindex);
|
|
|
|
|
|
|
|
/*
|
2005-10-15 04:49:52 +02:00
|
|
|
* Since this optimization is not applicable all that often, we want to
|
|
|
|
* fall out before doing very much work if possible. Therefore we do the
|
|
|
|
* work in several passes. The first pass scans the tlist and HAVING qual
|
|
|
|
* to find all the aggregates and verify that each of them is a MIN/MAX
|
|
|
|
* aggregate. If that succeeds, the second pass looks at each aggregate
|
|
|
|
* to see if it is optimizable; if so we make an IndexPath describing how
|
|
|
|
* we would scan it. (We do not try to optimize if only some aggs are
|
|
|
|
* optimizable, since that means we'll have to scan all the rows anyway.)
|
|
|
|
* If that succeeds, we have enough info to compare costs against the
|
|
|
|
* generic implementation. Only if that test passes do we build a Plan.
|
2005-04-12 01:06:57 +02:00
|
|
|
*/
|
|
|
|
|
|
|
|
/* Pass 1: find all the aggregates */
|
|
|
|
aggs_list = NIL;
|
|
|
|
if (find_minmax_aggs_walker((Node *) tlist, &aggs_list))
|
|
|
|
return NULL;
|
2005-06-06 00:32:58 +02:00
|
|
|
if (find_minmax_aggs_walker(parse->havingQual, &aggs_list))
|
2005-04-12 01:06:57 +02:00
|
|
|
return NULL;
|
|
|
|
|
|
|
|
/* Pass 2: see if each one is optimizable */
|
|
|
|
total_cost = 0;
|
|
|
|
foreach(l, aggs_list)
|
|
|
|
{
|
|
|
|
MinMaxAggInfo *info = (MinMaxAggInfo *) lfirst(l);
|
|
|
|
|
|
|
|
if (!build_minmax_path(root, rel, info))
|
|
|
|
return NULL;
|
|
|
|
total_cost += info->pathcost;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Make the cost comparison.
|
|
|
|
*
|
2005-11-22 19:17:34 +01:00
|
|
|
* 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.
|
2005-04-12 01:06:57 +02:00
|
|
|
*/
|
|
|
|
cost_agg(&agg_p, root, AGG_PLAIN, list_length(aggs_list),
|
|
|
|
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 */
|
|
|
|
|
|
|
|
/*
|
Revise the planner's handling of "pseudoconstant" WHERE clauses, that is
clauses containing no variables and no volatile functions. Such a clause
can be used as a one-time qual in a gating Result plan node, to suppress
plan execution entirely when it is false. Even when the clause is true,
putting it in a gating node wins by avoiding repeated evaluation of the
clause. In previous PG releases, query_planner() would do this for
pseudoconstant clauses appearing at the top level of the jointree, but
there was no ability to generate a gating Result deeper in the plan tree.
To fix it, get rid of the special case in query_planner(), and instead
process pseudoconstant clauses through the normal RestrictInfo qual
distribution mechanism. When a pseudoconstant clause is found attached to
a path node in create_plan(), pull it out and generate a gating Result at
that point. This requires special-casing pseudoconstants in selectivity
estimation and cost_qual_eval, but on the whole it's pretty clean.
It probably even makes the planner a bit faster than before for the normal
case of no pseudoconstants, since removing pull_constant_clauses saves one
useless traversal of the qual tree. Per gripe from Phil Frost.
2006-07-01 20:38:33 +02:00
|
|
|
* OK, we are going to generate an optimized plan.
|
2005-04-12 01:06:57 +02:00
|
|
|
*/
|
|
|
|
|
|
|
|
/* Pass 3: generate subplans and output Param nodes */
|
|
|
|
foreach(l, aggs_list)
|
|
|
|
{
|
Revise the planner's handling of "pseudoconstant" WHERE clauses, that is
clauses containing no variables and no volatile functions. Such a clause
can be used as a one-time qual in a gating Result plan node, to suppress
plan execution entirely when it is false. Even when the clause is true,
putting it in a gating node wins by avoiding repeated evaluation of the
clause. In previous PG releases, query_planner() would do this for
pseudoconstant clauses appearing at the top level of the jointree, but
there was no ability to generate a gating Result deeper in the plan tree.
To fix it, get rid of the special case in query_planner(), and instead
process pseudoconstant clauses through the normal RestrictInfo qual
distribution mechanism. When a pseudoconstant clause is found attached to
a path node in create_plan(), pull it out and generate a gating Result at
that point. This requires special-casing pseudoconstants in selectivity
estimation and cost_qual_eval, but on the whole it's pretty clean.
It probably even makes the planner a bit faster than before for the normal
case of no pseudoconstants, since removing pull_constant_clauses saves one
useless traversal of the qual tree. Per gripe from Phil Frost.
2006-07-01 20:38:33 +02:00
|
|
|
make_agg_subplan(root, (MinMaxAggInfo *) lfirst(l));
|
2005-04-12 01:06:57 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Modify the targetlist and HAVING qual to reference subquery outputs
|
|
|
|
*/
|
|
|
|
tlist = (List *) replace_aggs_with_params_mutator((Node *) tlist,
|
|
|
|
&aggs_list);
|
2005-06-06 00:32:58 +02:00
|
|
|
hqual = replace_aggs_with_params_mutator(parse->havingQual,
|
2005-04-12 01:06:57 +02:00
|
|
|
&aggs_list);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Generate the output plan --- basically just a Result
|
|
|
|
*/
|
|
|
|
plan = (Plan *) make_result(tlist, hqual, NULL);
|
|
|
|
|
|
|
|
/* Account for evaluation cost of the tlist (make_result did the rest) */
|
|
|
|
cost_qual_eval(&tlist_cost, tlist);
|
|
|
|
plan->startup_cost += tlist_cost.startup;
|
|
|
|
plan->total_cost += tlist_cost.startup + tlist_cost.per_tuple;
|
|
|
|
|
|
|
|
return plan;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* find_minmax_aggs_walker
|
|
|
|
* Recursively scan the Aggref nodes in an expression tree, and check
|
|
|
|
* that each one is a MIN/MAX aggregate. If so, build a list of the
|
|
|
|
* distinct aggregate calls in the tree.
|
|
|
|
*
|
|
|
|
* Returns TRUE if a non-MIN/MAX aggregate is found, FALSE otherwise.
|
|
|
|
* (This seemingly-backward definition is used because expression_tree_walker
|
|
|
|
* aborts the scan on TRUE return, which is what we want.)
|
|
|
|
*
|
|
|
|
* Found aggregates are added to the list at *context; it's up to the caller
|
|
|
|
* to initialize the list to NIL.
|
|
|
|
*
|
|
|
|
* This does not descend into subqueries, and so should be used only after
|
|
|
|
* reduction of sublinks to subplans. There mustn't be outer-aggregate
|
|
|
|
* references either.
|
|
|
|
*/
|
|
|
|
static bool
|
|
|
|
find_minmax_aggs_walker(Node *node, List **context)
|
|
|
|
{
|
|
|
|
if (node == NULL)
|
|
|
|
return false;
|
|
|
|
if (IsA(node, Aggref))
|
|
|
|
{
|
|
|
|
Aggref *aggref = (Aggref *) node;
|
|
|
|
Oid aggsortop;
|
2006-10-04 02:30:14 +02:00
|
|
|
Expr *curTarget;
|
2005-04-12 01:06:57 +02:00
|
|
|
MinMaxAggInfo *info;
|
|
|
|
ListCell *l;
|
|
|
|
|
|
|
|
Assert(aggref->agglevelsup == 0);
|
2006-07-27 21:52:07 +02:00
|
|
|
if (list_length(aggref->args) != 1)
|
|
|
|
return true; /* it couldn't be MIN/MAX */
|
2005-04-12 01:06:57 +02:00
|
|
|
/* note: we do not care if DISTINCT is mentioned ... */
|
|
|
|
|
|
|
|
aggsortop = fetch_agg_sort_op(aggref->aggfnoid);
|
|
|
|
if (!OidIsValid(aggsortop))
|
|
|
|
return true; /* not a MIN/MAX aggregate */
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Check whether it's already in the list, and add it if not.
|
|
|
|
*/
|
2006-07-27 21:52:07 +02:00
|
|
|
curTarget = linitial(aggref->args);
|
2005-04-12 01:06:57 +02:00
|
|
|
foreach(l, *context)
|
|
|
|
{
|
|
|
|
info = (MinMaxAggInfo *) lfirst(l);
|
|
|
|
if (info->aggfnoid == aggref->aggfnoid &&
|
2006-07-27 21:52:07 +02:00
|
|
|
equal(info->target, curTarget))
|
2005-04-12 01:06:57 +02:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
info = (MinMaxAggInfo *) palloc0(sizeof(MinMaxAggInfo));
|
|
|
|
info->aggfnoid = aggref->aggfnoid;
|
|
|
|
info->aggsortop = aggsortop;
|
2006-07-27 21:52:07 +02:00
|
|
|
info->target = curTarget;
|
2005-04-12 01:06:57 +02:00
|
|
|
|
|
|
|
*context = lappend(*context, info);
|
|
|
|
|
|
|
|
/*
|
2005-10-15 04:49:52 +02:00
|
|
|
* We need not recurse into the argument, since it can't contain any
|
|
|
|
* aggregates.
|
2005-04-12 01:06:57 +02:00
|
|
|
*/
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
Assert(!IsA(node, SubLink));
|
|
|
|
return expression_tree_walker(node, find_minmax_aggs_walker,
|
|
|
|
(void *) context);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* build_minmax_path
|
|
|
|
* Given a MIN/MAX aggregate, try to find an index it can be optimized
|
|
|
|
* with. Build a Path describing the best such index path.
|
|
|
|
*
|
|
|
|
* Returns TRUE if successful, FALSE if not. In the TRUE case, info->path
|
|
|
|
* is filled in.
|
|
|
|
*
|
|
|
|
* XXX look at sharing more code with indxpath.c.
|
|
|
|
*
|
|
|
|
* Note: check_partial_indexes() must have been run previously.
|
|
|
|
*/
|
|
|
|
static bool
|
2005-06-06 00:32:58 +02:00
|
|
|
build_minmax_path(PlannerInfo *root, RelOptInfo *rel, MinMaxAggInfo *info)
|
2005-04-12 01:06:57 +02:00
|
|
|
{
|
|
|
|
IndexPath *best_path = NULL;
|
|
|
|
Cost best_cost = 0;
|
2007-01-09 03:14:16 +01:00
|
|
|
bool best_nulls_first = false;
|
2005-04-12 01:06:57 +02:00
|
|
|
ListCell *l;
|
|
|
|
|
|
|
|
foreach(l, rel->indexlist)
|
|
|
|
{
|
|
|
|
IndexOptInfo *index = (IndexOptInfo *) lfirst(l);
|
|
|
|
ScanDirection indexscandir = NoMovementScanDirection;
|
|
|
|
int indexcol;
|
|
|
|
int prevcol;
|
|
|
|
List *restrictclauses;
|
|
|
|
IndexPath *new_path;
|
|
|
|
Cost new_cost;
|
2005-07-28 22:26:22 +02:00
|
|
|
bool found_clause;
|
2005-04-12 01:06:57 +02:00
|
|
|
|
|
|
|
/* Ignore non-btree indexes */
|
|
|
|
if (index->relam != BTREE_AM_OID)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
/* Ignore partial indexes that do not match the query */
|
|
|
|
if (index->indpred != NIL && !index->predOK)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Look for a match to one of the index columns. (In a stupidly
|
2005-10-15 04:49:52 +02:00
|
|
|
* designed index, there could be multiple matches, but we only care
|
|
|
|
* about the first one.)
|
2005-04-12 01:06:57 +02:00
|
|
|
*/
|
|
|
|
for (indexcol = 0; indexcol < index->ncolumns; indexcol++)
|
|
|
|
{
|
|
|
|
indexscandir = match_agg_to_index_col(info, index, indexcol);
|
|
|
|
if (!ScanDirectionIsNoMovement(indexscandir))
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
if (ScanDirectionIsNoMovement(indexscandir))
|
|
|
|
continue;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* If the match is not at the first index column, we have to verify
|
|
|
|
* that there are "x = something" restrictions on all the earlier
|
2005-10-15 04:49:52 +02:00
|
|
|
* index columns. Since we'll need the restrictclauses list anyway to
|
|
|
|
* build the path, it's convenient to extract that first and then look
|
|
|
|
* through it for the equality restrictions.
|
2005-04-12 01:06:57 +02:00
|
|
|
*/
|
2005-04-22 23:58:32 +02:00
|
|
|
restrictclauses = group_clauses_by_indexkey(index,
|
2005-10-15 04:49:52 +02:00
|
|
|
index->rel->baserestrictinfo,
|
2005-04-22 23:58:32 +02:00
|
|
|
NIL,
|
2005-07-28 22:26:22 +02:00
|
|
|
NULL,
|
2005-11-25 20:47:50 +01:00
|
|
|
SAOP_FORBID,
|
2005-07-28 22:26:22 +02:00
|
|
|
&found_clause);
|
2005-04-12 01:06:57 +02:00
|
|
|
|
|
|
|
if (list_length(restrictclauses) < indexcol)
|
|
|
|
continue; /* definitely haven't got enough */
|
|
|
|
for (prevcol = 0; prevcol < indexcol; prevcol++)
|
|
|
|
{
|
2005-10-15 04:49:52 +02:00
|
|
|
List *rinfos = (List *) list_nth(restrictclauses, prevcol);
|
|
|
|
ListCell *ll;
|
2005-04-12 01:06:57 +02:00
|
|
|
|
|
|
|
foreach(ll, rinfos)
|
|
|
|
{
|
|
|
|
RestrictInfo *rinfo = (RestrictInfo *) lfirst(ll);
|
|
|
|
int strategy;
|
|
|
|
|
|
|
|
Assert(is_opclause(rinfo->clause));
|
|
|
|
strategy =
|
2006-12-23 01:43:13 +01:00
|
|
|
get_op_opfamily_strategy(((OpExpr *) rinfo->clause)->opno,
|
|
|
|
index->opfamily[prevcol]);
|
2005-04-12 01:06:57 +02:00
|
|
|
if (strategy == BTEqualStrategyNumber)
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
if (ll == NULL)
|
|
|
|
break; /* none are Equal for this index col */
|
|
|
|
}
|
|
|
|
if (prevcol < indexcol)
|
|
|
|
continue; /* didn't find all Equal clauses */
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Build the access path. We don't bother marking it with pathkeys.
|
|
|
|
*/
|
|
|
|
new_path = create_index_path(root, index,
|
|
|
|
restrictclauses,
|
|
|
|
NIL,
|
2005-04-22 23:58:32 +02:00
|
|
|
indexscandir,
|
2006-06-06 19:59:58 +02:00
|
|
|
NULL);
|
2005-04-12 01:06:57 +02:00
|
|
|
|
|
|
|
/*
|
|
|
|
* Estimate actual cost of fetching just one row.
|
|
|
|
*/
|
|
|
|
if (new_path->rows > 1.0)
|
|
|
|
new_cost = new_path->path.startup_cost +
|
|
|
|
(new_path->path.total_cost - new_path->path.startup_cost)
|
|
|
|
* 1.0 / new_path->rows;
|
|
|
|
else
|
|
|
|
new_cost = new_path->path.total_cost;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Keep if first or if cheaper than previous best.
|
|
|
|
*/
|
|
|
|
if (best_path == NULL || new_cost < best_cost)
|
|
|
|
{
|
|
|
|
best_path = new_path;
|
|
|
|
best_cost = new_cost;
|
2007-01-09 03:14:16 +01:00
|
|
|
if (ScanDirectionIsForward(indexscandir))
|
|
|
|
best_nulls_first = index->nulls_first[indexcol];
|
|
|
|
else
|
|
|
|
best_nulls_first = !index->nulls_first[indexcol];
|
2005-04-12 01:06:57 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
info->path = best_path;
|
|
|
|
info->pathcost = best_cost;
|
2007-01-09 03:14:16 +01:00
|
|
|
info->nulls_first = best_nulls_first;
|
2005-04-12 01:06:57 +02:00
|
|
|
return (best_path != NULL);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* match_agg_to_index_col
|
|
|
|
* Does an aggregate match an index column?
|
|
|
|
*
|
|
|
|
* It matches if its argument is equal to the index column's data and its
|
2007-01-09 03:14:16 +01:00
|
|
|
* sortop is either the forward or reverse sort operator for the column.
|
2005-04-12 01:06:57 +02:00
|
|
|
*
|
2007-01-09 03:14:16 +01:00
|
|
|
* We return ForwardScanDirection if match the forward sort operator,
|
|
|
|
* BackwardScanDirection if match the reverse sort operator,
|
2005-04-12 01:06:57 +02:00
|
|
|
* and NoMovementScanDirection if there's no match.
|
|
|
|
*/
|
|
|
|
static ScanDirection
|
|
|
|
match_agg_to_index_col(MinMaxAggInfo *info, IndexOptInfo *index, int indexcol)
|
|
|
|
{
|
2007-01-09 03:14:16 +01:00
|
|
|
ScanDirection result;
|
|
|
|
|
|
|
|
/* Check for operator match first (cheaper) */
|
|
|
|
if (info->aggsortop == index->fwdsortop[indexcol])
|
|
|
|
result = ForwardScanDirection;
|
|
|
|
else if (info->aggsortop == index->revsortop[indexcol])
|
|
|
|
result = BackwardScanDirection;
|
|
|
|
else
|
|
|
|
return NoMovementScanDirection;
|
2005-04-12 01:06:57 +02:00
|
|
|
|
|
|
|
/* Check for data match */
|
|
|
|
if (!match_index_to_operand((Node *) info->target, indexcol, index))
|
|
|
|
return NoMovementScanDirection;
|
|
|
|
|
2007-01-09 03:14:16 +01:00
|
|
|
return result;
|
2005-04-12 01:06:57 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Construct a suitable plan for a converted aggregate query
|
|
|
|
*/
|
|
|
|
static void
|
Revise the planner's handling of "pseudoconstant" WHERE clauses, that is
clauses containing no variables and no volatile functions. Such a clause
can be used as a one-time qual in a gating Result plan node, to suppress
plan execution entirely when it is false. Even when the clause is true,
putting it in a gating node wins by avoiding repeated evaluation of the
clause. In previous PG releases, query_planner() would do this for
pseudoconstant clauses appearing at the top level of the jointree, but
there was no ability to generate a gating Result deeper in the plan tree.
To fix it, get rid of the special case in query_planner(), and instead
process pseudoconstant clauses through the normal RestrictInfo qual
distribution mechanism. When a pseudoconstant clause is found attached to
a path node in create_plan(), pull it out and generate a gating Result at
that point. This requires special-casing pseudoconstants in selectivity
estimation and cost_qual_eval, but on the whole it's pretty clean.
It probably even makes the planner a bit faster than before for the normal
case of no pseudoconstants, since removing pull_constant_clauses saves one
useless traversal of the qual tree. Per gripe from Phil Frost.
2006-07-01 20:38:33 +02:00
|
|
|
make_agg_subplan(PlannerInfo *root, MinMaxAggInfo *info)
|
2005-04-12 01:06:57 +02:00
|
|
|
{
|
2005-06-06 00:32:58 +02:00
|
|
|
PlannerInfo subroot;
|
|
|
|
Query *subparse;
|
2005-04-12 01:06:57 +02:00
|
|
|
Plan *plan;
|
Revise the planner's handling of "pseudoconstant" WHERE clauses, that is
clauses containing no variables and no volatile functions. Such a clause
can be used as a one-time qual in a gating Result plan node, to suppress
plan execution entirely when it is false. Even when the clause is true,
putting it in a gating node wins by avoiding repeated evaluation of the
clause. In previous PG releases, query_planner() would do this for
pseudoconstant clauses appearing at the top level of the jointree, but
there was no ability to generate a gating Result deeper in the plan tree.
To fix it, get rid of the special case in query_planner(), and instead
process pseudoconstant clauses through the normal RestrictInfo qual
distribution mechanism. When a pseudoconstant clause is found attached to
a path node in create_plan(), pull it out and generate a gating Result at
that point. This requires special-casing pseudoconstants in selectivity
estimation and cost_qual_eval, but on the whole it's pretty clean.
It probably even makes the planner a bit faster than before for the normal
case of no pseudoconstants, since removing pull_constant_clauses saves one
useless traversal of the qual tree. Per gripe from Phil Frost.
2006-07-01 20:38:33 +02:00
|
|
|
Plan *iplan;
|
2005-04-12 01:06:57 +02:00
|
|
|
TargetEntry *tle;
|
|
|
|
SortClause *sortcl;
|
2005-04-12 07:11:28 +02:00
|
|
|
NullTest *ntest;
|
2005-04-12 01:06:57 +02:00
|
|
|
|
|
|
|
/*
|
2005-10-15 04:49:52 +02:00
|
|
|
* Generate a suitably modified query. Much of the work here is probably
|
|
|
|
* unnecessary in the normal case, but we want to make it look good if
|
|
|
|
* someone tries to EXPLAIN the result.
|
2005-04-12 01:06:57 +02:00
|
|
|
*/
|
2005-06-06 00:32:58 +02:00
|
|
|
memcpy(&subroot, root, sizeof(PlannerInfo));
|
|
|
|
subroot.parse = subparse = (Query *) copyObject(root->parse);
|
|
|
|
subparse->commandType = CMD_SELECT;
|
|
|
|
subparse->resultRelation = 0;
|
|
|
|
subparse->resultRelations = NIL;
|
2006-08-12 04:52:06 +02:00
|
|
|
subparse->returningLists = NIL;
|
2005-06-06 00:32:58 +02:00
|
|
|
subparse->into = NULL;
|
|
|
|
subparse->hasAggs = false;
|
|
|
|
subparse->groupClause = NIL;
|
|
|
|
subparse->havingQual = NULL;
|
|
|
|
subparse->distinctClause = NIL;
|
|
|
|
subroot.hasHavingQual = false;
|
2005-04-12 01:06:57 +02:00
|
|
|
|
|
|
|
/* single tlist entry that is the aggregate target */
|
|
|
|
tle = makeTargetEntry(copyObject(info->target),
|
|
|
|
1,
|
|
|
|
pstrdup("agg_target"),
|
|
|
|
false);
|
2005-06-06 00:32:58 +02:00
|
|
|
subparse->targetList = list_make1(tle);
|
2005-04-12 01:06:57 +02:00
|
|
|
|
|
|
|
/* set up the appropriate ORDER BY entry */
|
|
|
|
sortcl = makeNode(SortClause);
|
2005-06-06 00:32:58 +02:00
|
|
|
sortcl->tleSortGroupRef = assignSortGroupRef(tle, subparse->targetList);
|
2005-04-12 01:06:57 +02:00
|
|
|
sortcl->sortop = info->aggsortop;
|
2007-01-09 03:14:16 +01:00
|
|
|
sortcl->nulls_first = info->nulls_first;
|
2005-06-06 00:32:58 +02:00
|
|
|
subparse->sortClause = list_make1(sortcl);
|
2005-04-12 01:06:57 +02:00
|
|
|
|
|
|
|
/* set up LIMIT 1 */
|
2005-06-06 00:32:58 +02:00
|
|
|
subparse->limitOffset = NULL;
|
2006-07-26 21:31:51 +02:00
|
|
|
subparse->limitCount = (Node *) makeConst(INT8OID, sizeof(int64),
|
|
|
|
Int64GetDatum(1),
|
2006-10-04 02:30:14 +02:00
|
|
|
false, false /* not by val */ );
|
2005-04-12 01:06:57 +02:00
|
|
|
|
|
|
|
/*
|
2005-10-15 04:49:52 +02:00
|
|
|
* Generate the plan for the subquery. We already have a Path for the
|
|
|
|
* basic indexscan, but we have to convert it to a Plan and attach a LIMIT
|
Revise the planner's handling of "pseudoconstant" WHERE clauses, that is
clauses containing no variables and no volatile functions. Such a clause
can be used as a one-time qual in a gating Result plan node, to suppress
plan execution entirely when it is false. Even when the clause is true,
putting it in a gating node wins by avoiding repeated evaluation of the
clause. In previous PG releases, query_planner() would do this for
pseudoconstant clauses appearing at the top level of the jointree, but
there was no ability to generate a gating Result deeper in the plan tree.
To fix it, get rid of the special case in query_planner(), and instead
process pseudoconstant clauses through the normal RestrictInfo qual
distribution mechanism. When a pseudoconstant clause is found attached to
a path node in create_plan(), pull it out and generate a gating Result at
that point. This requires special-casing pseudoconstants in selectivity
estimation and cost_qual_eval, but on the whole it's pretty clean.
It probably even makes the planner a bit faster than before for the normal
case of no pseudoconstants, since removing pull_constant_clauses saves one
useless traversal of the qual tree. Per gripe from Phil Frost.
2006-07-01 20:38:33 +02:00
|
|
|
* node above it.
|
2005-04-12 07:11:28 +02:00
|
|
|
*
|
2005-11-22 19:17:34 +01:00
|
|
|
* Also we must add a "WHERE foo IS NOT NULL" restriction to the
|
|
|
|
* indexscan, to be sure we don't return a NULL, which'd be contrary to
|
|
|
|
* the standard behavior of MIN/MAX. XXX ideally this should be done
|
|
|
|
* earlier, so that the selectivity of the restriction could be included
|
|
|
|
* in our cost estimates. But that looks painful, and in most cases the
|
|
|
|
* fraction of NULLs isn't high enough to change the decision.
|
Revise the planner's handling of "pseudoconstant" WHERE clauses, that is
clauses containing no variables and no volatile functions. Such a clause
can be used as a one-time qual in a gating Result plan node, to suppress
plan execution entirely when it is false. Even when the clause is true,
putting it in a gating node wins by avoiding repeated evaluation of the
clause. In previous PG releases, query_planner() would do this for
pseudoconstant clauses appearing at the top level of the jointree, but
there was no ability to generate a gating Result deeper in the plan tree.
To fix it, get rid of the special case in query_planner(), and instead
process pseudoconstant clauses through the normal RestrictInfo qual
distribution mechanism. When a pseudoconstant clause is found attached to
a path node in create_plan(), pull it out and generate a gating Result at
that point. This requires special-casing pseudoconstants in selectivity
estimation and cost_qual_eval, but on the whole it's pretty clean.
It probably even makes the planner a bit faster than before for the normal
case of no pseudoconstants, since removing pull_constant_clauses saves one
useless traversal of the qual tree. Per gripe from Phil Frost.
2006-07-01 20:38:33 +02:00
|
|
|
*
|
2006-10-04 02:30:14 +02:00
|
|
|
* The NOT NULL qual has to go on the actual indexscan; create_plan might
|
|
|
|
* have stuck a gating Result atop that, if there were any pseudoconstant
|
|
|
|
* quals.
|
2005-04-12 01:06:57 +02:00
|
|
|
*/
|
2005-06-06 00:32:58 +02:00
|
|
|
plan = create_plan(&subroot, (Path *) info->path);
|
2005-04-12 01:06:57 +02:00
|
|
|
|
2005-06-06 00:32:58 +02:00
|
|
|
plan->targetlist = copyObject(subparse->targetList);
|
2005-04-12 01:06:57 +02:00
|
|
|
|
Revise the planner's handling of "pseudoconstant" WHERE clauses, that is
clauses containing no variables and no volatile functions. Such a clause
can be used as a one-time qual in a gating Result plan node, to suppress
plan execution entirely when it is false. Even when the clause is true,
putting it in a gating node wins by avoiding repeated evaluation of the
clause. In previous PG releases, query_planner() would do this for
pseudoconstant clauses appearing at the top level of the jointree, but
there was no ability to generate a gating Result deeper in the plan tree.
To fix it, get rid of the special case in query_planner(), and instead
process pseudoconstant clauses through the normal RestrictInfo qual
distribution mechanism. When a pseudoconstant clause is found attached to
a path node in create_plan(), pull it out and generate a gating Result at
that point. This requires special-casing pseudoconstants in selectivity
estimation and cost_qual_eval, but on the whole it's pretty clean.
It probably even makes the planner a bit faster than before for the normal
case of no pseudoconstants, since removing pull_constant_clauses saves one
useless traversal of the qual tree. Per gripe from Phil Frost.
2006-07-01 20:38:33 +02:00
|
|
|
if (IsA(plan, Result))
|
|
|
|
iplan = plan->lefttree;
|
|
|
|
else
|
|
|
|
iplan = plan;
|
|
|
|
Assert(IsA(iplan, IndexScan));
|
|
|
|
|
2005-04-12 07:11:28 +02:00
|
|
|
ntest = makeNode(NullTest);
|
|
|
|
ntest->nulltesttype = IS_NOT_NULL;
|
|
|
|
ntest->arg = copyObject(info->target);
|
2005-04-12 01:06:57 +02:00
|
|
|
|
Revise the planner's handling of "pseudoconstant" WHERE clauses, that is
clauses containing no variables and no volatile functions. Such a clause
can be used as a one-time qual in a gating Result plan node, to suppress
plan execution entirely when it is false. Even when the clause is true,
putting it in a gating node wins by avoiding repeated evaluation of the
clause. In previous PG releases, query_planner() would do this for
pseudoconstant clauses appearing at the top level of the jointree, but
there was no ability to generate a gating Result deeper in the plan tree.
To fix it, get rid of the special case in query_planner(), and instead
process pseudoconstant clauses through the normal RestrictInfo qual
distribution mechanism. When a pseudoconstant clause is found attached to
a path node in create_plan(), pull it out and generate a gating Result at
that point. This requires special-casing pseudoconstants in selectivity
estimation and cost_qual_eval, but on the whole it's pretty clean.
It probably even makes the planner a bit faster than before for the normal
case of no pseudoconstants, since removing pull_constant_clauses saves one
useless traversal of the qual tree. Per gripe from Phil Frost.
2006-07-01 20:38:33 +02:00
|
|
|
iplan->qual = lcons(ntest, iplan->qual);
|
2005-04-12 01:06:57 +02:00
|
|
|
|
2005-10-15 04:49:52 +02:00
|
|
|
plan = (Plan *) make_limit(plan,
|
2005-06-06 00:32:58 +02:00
|
|
|
subparse->limitOffset,
|
2005-08-18 19:51:12 +02:00
|
|
|
subparse->limitCount,
|
|
|
|
0, 1);
|
2005-04-12 01:06:57 +02:00
|
|
|
|
|
|
|
/*
|
|
|
|
* Convert the plan into an InitPlan, and make a Param for its result.
|
|
|
|
*/
|
2005-06-06 00:32:58 +02:00
|
|
|
info->param = SS_make_initplan_from_plan(&subroot, plan,
|
2005-04-12 01:06:57 +02:00
|
|
|
exprType((Node *) tle->expr),
|
|
|
|
-1);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Replace original aggregate calls with subplan output Params
|
|
|
|
*/
|
|
|
|
static Node *
|
2005-10-15 04:49:52 +02:00
|
|
|
replace_aggs_with_params_mutator(Node *node, List **context)
|
2005-04-12 01:06:57 +02:00
|
|
|
{
|
|
|
|
if (node == NULL)
|
|
|
|
return NULL;
|
|
|
|
if (IsA(node, Aggref))
|
|
|
|
{
|
|
|
|
Aggref *aggref = (Aggref *) node;
|
|
|
|
ListCell *l;
|
2006-07-27 21:52:07 +02:00
|
|
|
Expr *curTarget = linitial(aggref->args);
|
2005-04-12 01:06:57 +02:00
|
|
|
|
|
|
|
foreach(l, *context)
|
|
|
|
{
|
|
|
|
MinMaxAggInfo *info = (MinMaxAggInfo *) lfirst(l);
|
|
|
|
|
|
|
|
if (info->aggfnoid == aggref->aggfnoid &&
|
2006-07-27 21:52:07 +02:00
|
|
|
equal(info->target, curTarget))
|
2005-04-12 01:06:57 +02:00
|
|
|
return (Node *) info->param;
|
|
|
|
}
|
|
|
|
elog(ERROR, "failed to re-find aggregate info record");
|
|
|
|
}
|
|
|
|
Assert(!IsA(node, SubLink));
|
|
|
|
return expression_tree_mutator(node, replace_aggs_with_params_mutator,
|
|
|
|
(void *) context);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Get the OID of the sort operator, if any, associated with an aggregate.
|
|
|
|
* Returns InvalidOid if there is no such operator.
|
|
|
|
*/
|
|
|
|
static Oid
|
|
|
|
fetch_agg_sort_op(Oid aggfnoid)
|
|
|
|
{
|
|
|
|
HeapTuple aggTuple;
|
|
|
|
Form_pg_aggregate aggform;
|
|
|
|
Oid aggsortop;
|
|
|
|
|
|
|
|
/* fetch aggregate entry from pg_aggregate */
|
|
|
|
aggTuple = SearchSysCache(AGGFNOID,
|
|
|
|
ObjectIdGetDatum(aggfnoid),
|
|
|
|
0, 0, 0);
|
|
|
|
if (!HeapTupleIsValid(aggTuple))
|
|
|
|
return InvalidOid;
|
|
|
|
aggform = (Form_pg_aggregate) GETSTRUCT(aggTuple);
|
|
|
|
aggsortop = aggform->aggsortop;
|
|
|
|
ReleaseSysCache(aggTuple);
|
|
|
|
|
|
|
|
return aggsortop;
|
|
|
|
}
|