2005-04-12 01:06:57 +02:00
|
|
|
/*-------------------------------------------------------------------------
|
|
|
|
*
|
|
|
|
* planagg.c
|
|
|
|
* Special planning for aggregate queries.
|
|
|
|
*
|
2009-01-01 18:24:05 +01:00
|
|
|
* Portions Copyright (c) 1996-2009, PostgreSQL Global Development Group
|
2005-04-12 01:06:57 +02:00
|
|
|
* Portions Copyright (c) 1994, Regents of the University of California
|
|
|
|
*
|
|
|
|
*
|
|
|
|
* IDENTIFICATION
|
2009-06-11 16:49:15 +02:00
|
|
|
* $PostgreSQL: pgsql/src/backend/optimizer/plan/planagg.c,v 1.46 2009/06/11 14:48:59 momjian Exp $
|
2005-04-12 01:06:57 +02:00
|
|
|
*
|
|
|
|
*-------------------------------------------------------------------------
|
|
|
|
*/
|
|
|
|
#include "postgres.h"
|
|
|
|
|
|
|
|
#include "catalog/pg_aggregate.h"
|
2008-06-19 02:46:06 +02:00
|
|
|
#include "catalog/pg_am.h"
|
2005-04-12 01:06:57 +02:00
|
|
|
#include "catalog/pg_type.h"
|
|
|
|
#include "nodes/makefuncs.h"
|
2008-08-26 00:42:34 +02:00
|
|
|
#include "nodes/nodeFuncs.h"
|
2005-04-12 01:06:57 +02:00
|
|
|
#include "optimizer/clauses.h"
|
|
|
|
#include "optimizer/cost.h"
|
|
|
|
#include "optimizer/pathnode.h"
|
|
|
|
#include "optimizer/paths.h"
|
|
|
|
#include "optimizer/planmain.h"
|
2007-10-13 02:58:03 +02:00
|
|
|
#include "optimizer/predtest.h"
|
2005-04-12 01:06:57 +02:00
|
|
|
#include "optimizer/subselect.h"
|
|
|
|
#include "parser/parse_clause.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 */
|
2007-10-13 02:58:03 +02:00
|
|
|
Expr *notnulltest; /* expression for "target IS NOT NULL" */
|
2005-04-12 01:06:57 +02:00
|
|
|
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;
|
2007-02-06 07:50:26 +01:00
|
|
|
FromExpr *jtnode;
|
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.
|
|
|
|
*
|
2008-12-28 19:54:01 +01:00
|
|
|
* We don't handle GROUP BY or windowing, because our current
|
2009-06-11 16:49:15 +02:00
|
|
|
* 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
|
|
|
*/
|
2008-12-28 19:54:01 +01:00
|
|
|
if (parse->groupClause || parse->hasWindowFuncs)
|
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
|
2007-02-06 07:50:26 +01:00
|
|
|
* trouble.) However, the single real table could be buried in several
|
|
|
|
* levels of FromExpr.
|
2005-04-12 01:06:57 +02:00
|
|
|
*/
|
2007-02-06 07:50:26 +01:00
|
|
|
jtnode = parse->jointree;
|
|
|
|
while (IsA(jtnode, FromExpr))
|
|
|
|
{
|
|
|
|
if (list_length(jtnode->fromlist) != 1)
|
|
|
|
return NULL;
|
|
|
|
jtnode = linitial(jtnode->fromlist);
|
|
|
|
}
|
|
|
|
if (!IsA(jtnode, RangeTblRef))
|
2005-04-12 01:06:57 +02:00
|
|
|
return NULL;
|
2007-02-06 07:50:26 +01:00
|
|
|
rtr = (RangeTblRef *) jtnode;
|
2007-04-21 23:01:45 +02:00
|
|
|
rte = planner_rt_fetch(rtr->rtindex, root);
|
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);
|
|
|
|
|
2008-03-31 18:59:26 +02:00
|
|
|
/*
|
2009-06-11 16:49:15 +02:00
|
|
|
* We have to replace Aggrefs with Params in equivalence classes too, else
|
|
|
|
* ORDER BY or DISTINCT on an optimized aggregate will fail.
|
2008-03-31 18:59:26 +02:00
|
|
|
*
|
2009-06-11 16:49:15 +02:00
|
|
|
* 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.
|
2008-03-31 18:59:26 +02:00
|
|
|
*/
|
|
|
|
mutate_eclass_expressions(root,
|
|
|
|
replace_aggs_with_params_mutator,
|
|
|
|
&aggs_list);
|
|
|
|
|
2005-04-12 01:06:57 +02:00
|
|
|
/*
|
|
|
|
* Generate the output plan --- basically just a Result
|
|
|
|
*/
|
2007-02-22 23:00:26 +01:00
|
|
|
plan = (Plan *) make_result(root, tlist, hqual, NULL);
|
2005-04-12 01:06:57 +02:00
|
|
|
|
|
|
|
/* Account for evaluation cost of the tlist (make_result did the rest) */
|
2007-02-22 23:00:26 +01:00
|
|
|
cost_qual_eval(&tlist_cost, tlist, root);
|
2005-04-12 01:06:57 +02:00
|
|
|
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;
|
2007-10-13 02:58:03 +02:00
|
|
|
NullTest *ntest;
|
|
|
|
List *allquals;
|
2005-04-12 01:06:57 +02:00
|
|
|
ListCell *l;
|
|
|
|
|
2007-10-13 02:58:03 +02:00
|
|
|
/* Build "target IS NOT NULL" expression for use below */
|
|
|
|
ntest = makeNode(NullTest);
|
|
|
|
ntest->nulltesttype = IS_NOT_NULL;
|
|
|
|
ntest->arg = copyObject(info->target);
|
|
|
|
info->notnulltest = (Expr *) ntest;
|
|
|
|
|
|
|
|
/*
|
2007-11-15 22:14:46 +01:00
|
|
|
* Build list of existing restriction clauses plus the notnull test. We
|
|
|
|
* cheat a bit by not bothering with a RestrictInfo node for the notnull
|
|
|
|
* test --- predicate_implied_by() won't care.
|
2007-10-13 02:58:03 +02:00
|
|
|
*/
|
|
|
|
allquals = list_concat(list_make1(ntest), rel->baserestrictinfo);
|
|
|
|
|
2005-04-12 01:06:57 +02:00
|
|
|
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;
|
|
|
|
|
2007-10-13 02:58:03 +02:00
|
|
|
/*
|
2007-11-15 22:14:46 +01:00
|
|
|
* Ignore partial indexes that do not match the query --- unless their
|
|
|
|
* predicates can be proven from the baserestrict list plus the IS NOT
|
|
|
|
* NULL test. In that case we can use them.
|
2007-10-13 02:58:03 +02:00
|
|
|
*/
|
|
|
|
if (index->indpred != NIL && !index->predOK &&
|
|
|
|
!predicate_implied_by(index->indpred, allquals))
|
2005-04-12 01:06:57 +02:00
|
|
|
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;
|
|
|
|
|
2007-12-03 23:37:17 +01:00
|
|
|
/* Could be an IS_NULL test, if so ignore */
|
|
|
|
if (!is_opclause(rinfo->clause))
|
|
|
|
continue;
|
2005-04-12 01:06:57 +02:00
|
|
|
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-11-15 22:14:46 +01:00
|
|
|
ScanDirection result;
|
2007-01-09 03:14:16 +01:00
|
|
|
|
|
|
|
/* 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;
|
2008-08-02 23:32:01 +02:00
|
|
|
SortGroupClause *sortcl;
|
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;
|
2007-02-20 18:32:18 +01:00
|
|
|
subparse->returningList = NIL;
|
2007-04-28 00:05:49 +02:00
|
|
|
subparse->utilityStmt = NULL;
|
|
|
|
subparse->intoClause = NULL;
|
2005-06-06 00:32:58 +02:00
|
|
|
subparse->hasAggs = false;
|
2008-08-02 23:32:01 +02:00
|
|
|
subparse->hasDistinctOn = false;
|
2005-06-06 00:32:58 +02:00
|
|
|
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 */
|
2008-08-02 23:32:01 +02:00
|
|
|
sortcl = makeNode(SortGroupClause);
|
2005-06-06 00:32:58 +02:00
|
|
|
sortcl->tleSortGroupRef = assignSortGroupRef(tle, subparse->targetList);
|
2008-08-02 23:32:01 +02:00
|
|
|
sortcl->eqop = get_equality_op_for_ordering_op(info->aggsortop, NULL);
|
|
|
|
if (!OidIsValid(sortcl->eqop)) /* shouldn't happen */
|
|
|
|
elog(ERROR, "could not find equality operator for ordering operator %u",
|
|
|
|
info->aggsortop);
|
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;
|
2007-03-17 01:11:05 +01:00
|
|
|
subparse->limitCount = (Node *) makeConst(INT8OID, -1, sizeof(int64),
|
2008-04-21 02:26:47 +02:00
|
|
|
Int64GetDatum(1), false,
|
|
|
|
FLOAT8PASSBYVAL);
|
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
|
|
|
*
|
2007-10-13 02:58:03 +02:00
|
|
|
* Also we must add a "WHERE target IS NOT NULL" restriction to the
|
2005-11-22 19:17:34 +01:00
|
|
|
* 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.
|
2007-10-13 02:58:03 +02:00
|
|
|
*
|
2007-11-15 22:14:46 +01:00
|
|
|
* We can skip adding the NOT NULL qual if it's redundant with either an
|
|
|
|
* already-given WHERE condition, or a clause of the index predicate.
|
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));
|
|
|
|
|
2007-10-13 02:58:03 +02:00
|
|
|
if (!list_member(iplan->qual, info->notnulltest) &&
|
|
|
|
!list_member(info->path->indexinfo->indpred, info->notnulltest))
|
|
|
|
iplan->qual = lcons(info->notnulltest, 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);
|
2007-02-19 08:03:34 +01:00
|
|
|
|
Fix mis-calculation of extParam/allParam sets for plan nodes, as seen in
bug #4290. The fundamental bug is that masking extParam by outer_params,
as finalize_plan had been doing, caused us to lose the information that
an initPlan depended on the output of a sibling initPlan. On reflection
the best thing to do seemed to be not to try to adjust outer_params for
this case but get rid of it entirely. The only thing it was really doing
for us was to filter out param IDs associated with SubPlan nodes, and that
can be done (with greater accuracy) while processing individual SubPlan
nodes in finalize_primnode. This approach was vindicated by the discovery
that the masking method was hiding a second bug: SS_finalize_plan failed to
remove extParam bits for initPlan output params that were referenced in the
main plan tree (it only got rid of those referenced by other initPlans).
It's not clear that this caused any real problems, given the limited use
of extParam by the executor, but it's certainly not what was intended.
I originally thought that there was also a problem with needing to include
indirect dependencies on external params in initPlans' param sets, but it
turns out that the executor handles this correctly so long as the depended-on
initPlan is earlier in the initPlans list than the one using its output.
That seems a bit of a fragile assumption, but it is true at the moment,
so I just documented it in some code comments rather than making what would
be rather invasive changes to remove the assumption.
Back-patch to 8.1. Previous versions don't have the case of initPlans
referring to other initPlans' outputs, so while the existing logic is still
questionable for them, there are not any known bugs to be fixed. So I'll
refrain from changing them for now.
2008-07-10 03:17:29 +02:00
|
|
|
/*
|
2008-07-10 04:14:03 +02:00
|
|
|
* Put the updated list of InitPlans back into the outer PlannerInfo.
|
Fix mis-calculation of extParam/allParam sets for plan nodes, as seen in
bug #4290. The fundamental bug is that masking extParam by outer_params,
as finalize_plan had been doing, caused us to lose the information that
an initPlan depended on the output of a sibling initPlan. On reflection
the best thing to do seemed to be not to try to adjust outer_params for
this case but get rid of it entirely. The only thing it was really doing
for us was to filter out param IDs associated with SubPlan nodes, and that
can be done (with greater accuracy) while processing individual SubPlan
nodes in finalize_primnode. This approach was vindicated by the discovery
that the masking method was hiding a second bug: SS_finalize_plan failed to
remove extParam bits for initPlan output params that were referenced in the
main plan tree (it only got rid of those referenced by other initPlans).
It's not clear that this caused any real problems, given the limited use
of extParam by the executor, but it's certainly not what was intended.
I originally thought that there was also a problem with needing to include
indirect dependencies on external params in initPlans' param sets, but it
turns out that the executor handles this correctly so long as the depended-on
initPlan is earlier in the initPlans list than the one using its output.
That seems a bit of a fragile assumption, but it is true at the moment,
so I just documented it in some code comments rather than making what would
be rather invasive changes to remove the assumption.
Back-patch to 8.1. Previous versions don't have the case of initPlans
referring to other initPlans' outputs, so while the existing logic is still
questionable for them, there are not any known bugs to be fixed. So I'll
refrain from changing them for now.
2008-07-10 03:17:29 +02:00
|
|
|
*/
|
2008-07-10 04:14:03 +02:00
|
|
|
root->init_plans = subroot.init_plans;
|
2005-04-12 01:06:57 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* 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;
|
|
|
|
}
|