]> granicus.if.org Git - postgresql/commitdiff
Create the planner mechanism for optimizing simple MIN and MAX queries
authorTom Lane <tgl@sss.pgh.pa.us>
Mon, 11 Apr 2005 23:06:57 +0000 (23:06 +0000)
committerTom Lane <tgl@sss.pgh.pa.us>
Mon, 11 Apr 2005 23:06:57 +0000 (23:06 +0000)
into indexscans on matching indexes.  For the moment, it only handles
int4 and text datatypes; next step is to add a column to pg_aggregate
so that all MIN/MAX aggregates can be handled.  Per my recent proposal.

14 files changed:
src/backend/optimizer/path/indxpath.c
src/backend/optimizer/plan/Makefile
src/backend/optimizer/plan/planagg.c [new file with mode: 0644]
src/backend/optimizer/plan/planner.c
src/backend/optimizer/plan/subselect.c
src/backend/utils/cache/lsyscache.c
src/include/optimizer/paths.h
src/include/optimizer/planmain.h
src/include/optimizer/subselect.h
src/include/utils/lsyscache.h
src/test/regress/expected/aggregates.out
src/test/regress/expected/create_index.out
src/test/regress/sql/aggregates.sql
src/test/regress/sql/create_index.sql

index e1328c0f9b84d881563d367d4923017915460718..21cf27745a4ff31530ad1e546a2bf583abc4d78b 100644 (file)
@@ -9,7 +9,7 @@
  *
  *
  * IDENTIFICATION
- *       $PostgreSQL: pgsql/src/backend/optimizer/path/indxpath.c,v 1.172 2005/03/28 00:58:22 tgl Exp $
+ *       $PostgreSQL: pgsql/src/backend/optimizer/path/indxpath.c,v 1.173 2005/04/11 23:06:55 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -54,7 +54,6 @@
        ((opclass) == BOOL_BTREE_OPS_OID || (opclass) == BOOL_HASH_OPS_OID)
 
 
-static List *group_clauses_by_indexkey(IndexOptInfo *index);
 static List *group_clauses_by_indexkey_for_join(Query *root,
                                                                   IndexOptInfo *index,
                                                                   Relids outer_relids,
@@ -72,8 +71,6 @@ static bool pred_test_simple_clause(Expr *predicate, Node *clause);
 static Relids indexable_outerrelids(IndexOptInfo *index);
 static Path *make_innerjoin_index_path(Query *root, IndexOptInfo *index,
                                                  List *clausegroups);
-static bool match_index_to_operand(Node *operand, int indexcol,
-                                          IndexOptInfo *index);
 static bool match_boolean_index_clause(Node *clause, int indexcol,
                                                                           IndexOptInfo *index);
 static bool match_special_index_operator(Expr *clause, Oid opclass,
@@ -234,7 +231,7 @@ create_index_paths(Query *root, RelOptInfo *rel)
  * clauses matching column C, because the executor couldn't use them anyway.
  * Therefore, there are no empty sublists in the result.
  */
-static List *
+List *
 group_clauses_by_indexkey(IndexOptInfo *index)
 {
        List       *clausegroup_list = NIL;
@@ -1774,7 +1771,7 @@ make_expr_from_indexclauses(List *indexclauses)
  * indexcol: the column number of the index (counting from 0)
  * index: the index of interest
  */
-static bool
+bool
 match_index_to_operand(Node *operand,
                                           int indexcol,
                                           IndexOptInfo *index)
index cc017bd681729efd233852154e93e5ee0dadaec2..a6690cc98daa8b560d8d66984c5c447cec152c9e 100644 (file)
@@ -4,7 +4,7 @@
 #    Makefile for optimizer/plan
 #
 # IDENTIFICATION
-#    $PostgreSQL: pgsql/src/backend/optimizer/plan/Makefile,v 1.12 2003/11/29 19:51:50 pgsql Exp $
+#    $PostgreSQL: pgsql/src/backend/optimizer/plan/Makefile,v 1.13 2005/04/11 23:06:55 tgl Exp $
 #
 #-------------------------------------------------------------------------
 
@@ -12,7 +12,8 @@ subdir = src/backend/optimizer/plan
 top_builddir = ../../../..
 include $(top_builddir)/src/Makefile.global
 
-OBJS = createplan.o initsplan.o planmain.o planner.o setrefs.o subselect.o
+OBJS = createplan.o initsplan.o planagg.o planmain.o planner.o \
+       setrefs.o subselect.o
 
 all: SUBSYS.o
 
diff --git a/src/backend/optimizer/plan/planagg.c b/src/backend/optimizer/plan/planagg.c
new file mode 100644 (file)
index 0000000..299931c
--- /dev/null
@@ -0,0 +1,575 @@
+/*-------------------------------------------------------------------------
+ *
+ * planagg.c
+ *       Special planning for aggregate queries.
+ *
+ * Portions Copyright (c) 1996-2005, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ *       $PostgreSQL: pgsql/src/backend/optimizer/plan/planagg.c,v 1.1 2005/04/11 23:06:55 tgl Exp $
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "access/skey.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/parsetree.h"
+#include "parser/parse_clause.h"
+#include "parser/parse_expr.h"
+#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 */
+       Param      *param;                      /* param for subplan's output */
+} MinMaxAggInfo;
+
+static bool find_minmax_aggs_walker(Node *node, List **context);
+static bool build_minmax_path(Query *root, RelOptInfo *rel,
+                                                         MinMaxAggInfo *info);
+static ScanDirection match_agg_to_index_col(MinMaxAggInfo *info,
+                                                                                       IndexOptInfo *index, int indexcol);
+static void make_agg_subplan(Query *root, MinMaxAggInfo *info,
+                                                        List *constant_quals);
+static Node *replace_aggs_with_params_mutator(Node *node,  List **context);
+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.
+ *
+ * We are passed the Query, the preprocessed tlist, and the best path
+ * devised for computing the input of a standard Agg node.  If we are able
+ * 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 *
+optimize_minmax_aggregates(Query *root, List *tlist, Path *best_path)
+{
+       RangeTblRef *rtr;
+       RangeTblEntry *rte;
+       RelOptInfo *rel;
+       List       *aggs_list;
+       ListCell   *l;
+       Cost            total_cost;
+       Path            agg_p;
+       Plan       *plan;
+       Node       *hqual;
+       QualCost        tlist_cost;
+       List       *constant_quals;
+
+       /* Nothing to do if query has no aggregates */
+       if (!root->hasAggs)
+               return NULL;
+
+       Assert(!root->setOperations); /* shouldn't get here if a setop */
+       Assert(root->rowMarks == NIL); /* nor if FOR UPDATE */
+
+       /*
+        * Reject unoptimizable cases.
+        *
+        * 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.
+        */
+       if (root->groupClause)
+               return NULL;
+
+       /*
+        * 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.)
+        */
+       Assert(root->jointree != NULL && IsA(root->jointree, FromExpr));
+       if (list_length(root->jointree->fromlist) != 1)
+               return NULL;
+       rtr = (RangeTblRef *) linitial(root->jointree->fromlist);
+       if (!IsA(rtr, RangeTblRef))
+               return NULL;
+       rte = rt_fetch(rtr->rtindex, root->rtable);
+       if (rte->rtekind != RTE_RELATION)
+               return NULL;
+       rel = find_base_rel(root, rtr->rtindex);
+
+       /*
+        * Also reject cases with subplans or volatile functions in WHERE.
+        * This may be overly paranoid, but it's not entirely clear if the
+        * transformation is safe then.
+        */
+       if (contain_subplans(root->jointree->quals) ||
+               contain_volatile_functions(root->jointree->quals))
+               return NULL;
+
+       /*
+        * 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.
+        */
+
+       /* Pass 1: find all the aggregates */
+       aggs_list = NIL;
+       if (find_minmax_aggs_walker((Node *) tlist, &aggs_list))
+               return NULL;
+       if (find_minmax_aggs_walker(root->havingQual, &aggs_list))
+               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.
+        *
+        * 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.
+        */
+       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 */
+
+       /*
+        * OK, we are going to generate an optimized plan.  The first thing we
+        * need to do is look for any non-variable WHERE clauses that query_planner
+        * might have removed from the basic plan.  (Normal WHERE clauses will
+        * be properly incorporated into the sub-plans by create_plan.)  If there
+        * are any, they will be in a gating Result node atop the best_path.
+        * They have to be incorporated into a gating Result in each sub-plan
+        * in order to produce the semantically correct result.
+        */
+       if (IsA(best_path, ResultPath))
+       {
+               Assert(((ResultPath *) best_path)->subpath != NULL);
+               constant_quals = ((ResultPath *) best_path)->constantqual;
+       }
+       else
+               constant_quals = NIL;
+
+       /* Pass 3: generate subplans and output Param nodes */
+       foreach(l, aggs_list)
+       {
+               make_agg_subplan(root, (MinMaxAggInfo *) lfirst(l), constant_quals);
+       }
+
+       /*
+        * Modify the targetlist and HAVING qual to reference subquery outputs
+        */
+       tlist = (List *) replace_aggs_with_params_mutator((Node *) tlist,
+                                                                                                         &aggs_list);
+       hqual = replace_aggs_with_params_mutator(root->havingQual,
+                                                                                        &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;
+               MinMaxAggInfo *info;
+               ListCell   *l;
+
+               Assert(aggref->agglevelsup == 0);
+               if (aggref->aggstar)
+                       return true;            /* foo(*) is surely not optimizable */
+               /* 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.
+                */
+               foreach(l, *context)
+               {
+                       info = (MinMaxAggInfo *) lfirst(l);
+                       if (info->aggfnoid == aggref->aggfnoid &&
+                               equal(info->target, aggref->target))
+                               return false;
+               }
+
+               info = (MinMaxAggInfo *) palloc0(sizeof(MinMaxAggInfo));
+               info->aggfnoid = aggref->aggfnoid;
+               info->aggsortop = aggsortop;
+               info->target = aggref->target;
+
+               *context = lappend(*context, info);
+
+               /*
+                * We need not recurse into the argument, since it can't contain
+                * any aggregates.
+                */
+               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
+build_minmax_path(Query *root, RelOptInfo *rel, MinMaxAggInfo *info)
+{
+       IndexPath  *best_path = NULL;
+       Cost            best_cost = 0;
+       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;
+
+               /* 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
+                * designed index, there could be multiple matches, but we only
+                * care about the first one.)
+                */
+               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
+                * 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.
+                */
+               restrictclauses = group_clauses_by_indexkey(index);
+
+               if (list_length(restrictclauses) < indexcol)
+                       continue;                       /* definitely haven't got enough */
+               for (prevcol = 0; prevcol < indexcol; prevcol++)
+               {
+                       List   *rinfos = (List *) list_nth(restrictclauses, prevcol);
+                       ListCell *ll;
+
+                       foreach(ll, rinfos)
+                       {
+                               RestrictInfo *rinfo = (RestrictInfo *) lfirst(ll);
+                               int                     strategy;
+
+                               Assert(is_opclause(rinfo->clause));
+                               strategy =
+                                       get_op_opclass_strategy(((OpExpr *) rinfo->clause)->opno,
+                                                                                       index->classlist[prevcol]);
+                               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,
+                                                                        indexscandir);
+
+               /*
+                * 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;
+               }
+       }
+
+       info->path = best_path;
+       info->pathcost = best_cost;
+       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
+ * sortop is either the LessThan or GreaterThan member of the column's opclass.
+ *
+ * We return ForwardScanDirection if match the LessThan member,
+ * BackwardScanDirection if match the GreaterThan member,
+ * and NoMovementScanDirection if there's no match.
+ */
+static ScanDirection
+match_agg_to_index_col(MinMaxAggInfo *info, IndexOptInfo *index, int indexcol)
+{
+       int                     strategy;
+
+       /* Check for data match */
+       if (!match_index_to_operand((Node *) info->target, indexcol, index))
+               return NoMovementScanDirection;
+
+       /* Look up the operator in the opclass */
+       strategy = get_op_opclass_strategy(info->aggsortop,
+                                                                          index->classlist[indexcol]);
+       if (strategy == BTLessStrategyNumber)
+               return ForwardScanDirection;
+       if (strategy == BTGreaterStrategyNumber)
+               return BackwardScanDirection;
+       return NoMovementScanDirection;
+}
+
+/*
+ * Construct a suitable plan for a converted aggregate query
+ */
+static void
+make_agg_subplan(Query *root, MinMaxAggInfo *info, List *constant_quals)
+{
+       Query      *subquery;
+       Path       *path;
+       Plan       *plan;
+       TargetEntry *tle;
+       SortClause *sortcl;
+
+       /*
+        * Generate a suitably modified Query node.  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.
+        */
+       subquery = (Query *) copyObject(root);
+       subquery->commandType = CMD_SELECT;
+       subquery->resultRelation = 0;
+       subquery->resultRelations = NIL;
+       subquery->into = NULL;
+       subquery->hasAggs = false;
+       subquery->groupClause = NIL;
+       subquery->havingQual = NULL;
+       subquery->hasHavingQual = false;
+       subquery->distinctClause = NIL;
+
+       /* single tlist entry that is the aggregate target */
+       tle = makeTargetEntry(copyObject(info->target),
+                                                 1,
+                                                 pstrdup("agg_target"),
+                                                 false);
+       subquery->targetList = list_make1(tle);
+
+       /* set up the appropriate ORDER BY entry */
+       sortcl = makeNode(SortClause);
+       sortcl->tleSortGroupRef = assignSortGroupRef(tle, subquery->targetList);
+       sortcl->sortop = info->aggsortop;
+       subquery->sortClause = list_make1(sortcl);
+
+       /* set up LIMIT 1 */
+       subquery->limitOffset = NULL;
+       subquery->limitCount = (Node *) makeConst(INT4OID, sizeof(int4),
+                                                                                         Int32GetDatum(1),
+                                                                                         false, true);
+
+       /*
+        * 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 node above it.  We might need a gating Result, too,
+        * which is most easily added at the Path stage.
+        */
+       path = (Path *) info->path;
+
+       if (constant_quals)
+               path = (Path *) create_result_path(NULL,
+                                                                                  path,
+                                                                                  copyObject(constant_quals));
+
+       plan = create_plan(subquery, path);
+
+       plan->targetlist = copyObject(subquery->targetList);
+
+       plan = (Plan *) make_limit(plan, 
+                                                          subquery->limitOffset,
+                                                          subquery->limitCount);
+
+       /*
+        * Convert the plan into an InitPlan, and make a Param for its result.
+        */
+       info->param = SS_make_initplan_from_plan(subquery, plan,
+                                                                                        exprType((Node *) tle->expr),
+                                                                                        -1);
+}
+
+/*
+ * Replace original aggregate calls with subplan output Params
+ */
+static Node *
+replace_aggs_with_params_mutator(Node *node,  List **context)
+{
+       if (node == NULL)
+               return NULL;
+       if (IsA(node, Aggref))
+       {
+               Aggref     *aggref = (Aggref *) node;
+               ListCell   *l;
+
+               foreach(l, *context)
+               {
+                       MinMaxAggInfo *info = (MinMaxAggInfo *) lfirst(l);
+
+                       if (info->aggfnoid == aggref->aggfnoid &&
+                               equal(info->target, aggref->target))
+                               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)
+{
+#ifdef NOT_YET
+       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;
+#else
+       /*
+        * XXX stub implementation for testing: hardwire a few cases.
+        */
+       if (aggfnoid == 2132)           /* min(int4) -> int4lt */
+               return 97;
+       if (aggfnoid == 2116)           /* max(int4) -> int4gt */
+               return 521;
+       if (aggfnoid == 2145)           /* min(text) -> text_lt */
+               return 664;
+       if (aggfnoid == 2129)           /* max(text) -> text_gt */
+               return 666;
+       return InvalidOid;
+#endif
+}
index eea58e45a15ee90f38d1911cd269153a5e21513f..b542fef61e26a697c1fbd44dec2be43147456b70 100644 (file)
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *       $PostgreSQL: pgsql/src/backend/optimizer/plan/planner.c,v 1.183 2005/04/10 19:50:08 tgl Exp $
+ *       $PostgreSQL: pgsql/src/backend/optimizer/plan/planner.c,v 1.184 2005/04/11 23:06:55 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -362,43 +362,12 @@ subquery_planner(Query *parse, double tuple_fraction)
 
        /*
         * If any subplans were generated, or if we're inside a subplan, build
-        * initPlan list and extParam/allParam sets for plan nodes.
+        * initPlan list and extParam/allParam sets for plan nodes, and attach
+        * the initPlans to the top plan node.
         */
        if (PlannerPlanId != saved_planid || PlannerQueryLevel > 1)
-       {
-               Cost            initplan_cost = 0;
-
-               /* Prepare extParam/allParam sets for all nodes in tree */
                SS_finalize_plan(plan, parse->rtable);
 
-               /*
-                * SS_finalize_plan doesn't handle initPlans, so we have to
-                * manually attach them to the topmost plan node, and add their
-                * extParams to the topmost node's, too.
-                *
-                * We also add the total_cost of each initPlan to the startup cost of
-                * the top node.  This is a conservative overestimate, since in
-                * fact each initPlan might be executed later than plan startup,
-                * or even not at all.
-                */
-               plan->initPlan = PlannerInitPlan;
-
-               foreach(l, plan->initPlan)
-               {
-                       SubPlan    *initplan = (SubPlan *) lfirst(l);
-
-                       plan->extParam = bms_add_members(plan->extParam,
-                                                                                        initplan->plan->extParam);
-                       /* allParam must include all members of extParam */
-                       plan->allParam = bms_add_members(plan->allParam,
-                                                                                        plan->extParam);
-                       initplan_cost += initplan->plan->total_cost;
-               }
-
-               plan->startup_cost += initplan_cost;
-               plan->total_cost += initplan_cost;
-       }
-
        /* Return to outer subquery context */
        PlannerQueryLevel--;
        PlannerInitPlan = saved_initplan;
@@ -692,6 +661,7 @@ grouping_planner(Query *parse, double tuple_fraction)
                double          sub_tuple_fraction;
                Path       *cheapest_path;
                Path       *sorted_path;
+               Path       *best_path;
                double          dNumGroups = 0;
                long            numGroups = 0;
                AggClauseCounts agg_counts;
@@ -959,114 +929,175 @@ grouping_planner(Query *parse, double tuple_fraction)
                }
 
                /*
-                * Select the best path and create a plan to execute it.
-                *
-                * If we are doing hashed grouping, we will always read all the input
-                * tuples, so use the cheapest-total path.      Otherwise, trust
-                * query_planner's decision about which to use.
+                * Select the best path.  If we are doing hashed grouping, we will
+                * always read all the input tuples, so use the cheapest-total
+                * path. Otherwise, trust query_planner's decision about which to use.
                 */
-               if (sorted_path && !use_hashed_grouping)
-               {
-                       result_plan = create_plan(parse, sorted_path);
-                       current_pathkeys = sorted_path->pathkeys;
-               }
+               if (use_hashed_grouping || !sorted_path)
+                       best_path = cheapest_path;
                else
-               {
-                       result_plan = create_plan(parse, cheapest_path);
-                       current_pathkeys = cheapest_path->pathkeys;
-               }
+                       best_path = sorted_path;
 
                /*
-                * create_plan() returns a plan with just a "flat" tlist of
-                * required Vars.  Usually we need to insert the sub_tlist as the
-                * tlist of the top plan node.  However, we can skip that if we
-                * determined that whatever query_planner chose to return will be
-                * good enough.
+                * Check to see if it's possible to optimize MIN/MAX aggregates.
+                * If so, we will forget all the work we did so far to choose a
+                * "regular" path ... but we had to do it anyway to be able to
+                * tell which way is cheaper.
                 */
-               if (need_tlist_eval)
+               result_plan = optimize_minmax_aggregates(parse,
+                                                                                                tlist,
+                                                                                                best_path);
+               if (result_plan != NULL)
+               {
+                       /*
+                        * optimize_minmax_aggregates generated the full plan, with
+                        * the right tlist, and it has no sort order.
+                        */
+                       current_pathkeys = NIL;
+               }
+               else
                {
                        /*
-                        * If the top-level plan node is one that cannot do expression
-                        * evaluation, we must insert a Result node to project the
-                        * desired tlist.
+                        * Normal case --- create a plan according to query_planner's
+                        * results.
                         */
-                       if (!is_projection_capable_plan(result_plan))
+                       result_plan = create_plan(parse, best_path);
+                       current_pathkeys = best_path->pathkeys;
+
+                       /*
+                        * create_plan() returns a plan with just a "flat" tlist of
+                        * required Vars.  Usually we need to insert the sub_tlist as the
+                        * tlist of the top plan node.  However, we can skip that if we
+                        * determined that whatever query_planner chose to return will be
+                        * good enough.
+                        */
+                       if (need_tlist_eval)
                        {
-                               result_plan = (Plan *) make_result(sub_tlist, NULL,
-                                                                                                  result_plan);
+                               /*
+                                * If the top-level plan node is one that cannot do expression
+                                * evaluation, we must insert a Result node to project the
+                                * desired tlist.
+                                */
+                               if (!is_projection_capable_plan(result_plan))
+                               {
+                                       result_plan = (Plan *) make_result(sub_tlist, NULL,
+                                                                                                          result_plan);
+                               }
+                               else
+                               {
+                                       /*
+                                        * Otherwise, just replace the subplan's flat tlist with
+                                        * the desired tlist.
+                                        */
+                                       result_plan->targetlist = sub_tlist;
+                               }
+
+                               /*
+                                * Also, account for the cost of evaluation of the sub_tlist.
+                                *
+                                * Up to now, we have only been dealing with "flat" tlists,
+                                * containing just Vars.  So their evaluation cost is zero
+                                * according to the model used by cost_qual_eval() (or if you
+                                * prefer, the cost is factored into cpu_tuple_cost).  Thus we
+                                * can avoid accounting for tlist cost throughout
+                                * query_planner() and subroutines.  But now we've inserted a
+                                * tlist that might contain actual operators, sub-selects, etc
+                                * --- so we'd better account for its cost.
+                                *
+                                * Below this point, any tlist eval cost for added-on nodes
+                                * should be accounted for as we create those nodes.
+                                * Presently, of the node types we can add on, only Agg and
+                                * Group project new tlists (the rest just copy their input
+                                * tuples) --- so make_agg() and make_group() are responsible
+                                * for computing the added cost.
+                                */
+                               cost_qual_eval(&tlist_cost, sub_tlist);
+                               result_plan->startup_cost += tlist_cost.startup;
+                               result_plan->total_cost += tlist_cost.startup +
+                                       tlist_cost.per_tuple * result_plan->plan_rows;
                        }
                        else
                        {
                                /*
-                                * Otherwise, just replace the subplan's flat tlist with
-                                * the desired tlist.
+                                * Since we're using query_planner's tlist and not the one
+                                * make_subplanTargetList calculated, we have to refigure any
+                                * grouping-column indexes make_subplanTargetList computed.
                                 */
-                               result_plan->targetlist = sub_tlist;
+                               locate_grouping_columns(parse, tlist, result_plan->targetlist,
+                                                                               groupColIdx);
                        }
 
                        /*
-                        * Also, account for the cost of evaluation of the sub_tlist.
-                        *
-                        * Up to now, we have only been dealing with "flat" tlists,
-                        * containing just Vars.  So their evaluation cost is zero
-                        * according to the model used by cost_qual_eval() (or if you
-                        * prefer, the cost is factored into cpu_tuple_cost).  Thus we
-                        * can avoid accounting for tlist cost throughout
-                        * query_planner() and subroutines.  But now we've inserted a
-                        * tlist that might contain actual operators, sub-selects, etc
-                        * --- so we'd better account for its cost.
+                        * Insert AGG or GROUP node if needed, plus an explicit sort step
+                        * if necessary.
                         *
-                        * Below this point, any tlist eval cost for added-on nodes
-                        * should be accounted for as we create those nodes.
-                        * Presently, of the node types we can add on, only Agg and
-                        * Group project new tlists (the rest just copy their input
-                        * tuples) --- so make_agg() and make_group() are responsible
-                        * for computing the added cost.
-                        */
-                       cost_qual_eval(&tlist_cost, sub_tlist);
-                       result_plan->startup_cost += tlist_cost.startup;
-                       result_plan->total_cost += tlist_cost.startup +
-                               tlist_cost.per_tuple * result_plan->plan_rows;
-               }
-               else
-               {
-                       /*
-                        * Since we're using query_planner's tlist and not the one
-                        * make_subplanTargetList calculated, we have to refigure any
-                        * grouping-column indexes make_subplanTargetList computed.
+                        * HAVING clause, if any, becomes qual of the Agg or Group node.
                         */
-                       locate_grouping_columns(parse, tlist, result_plan->targetlist,
-                                                                       groupColIdx);
-               }
+                       if (use_hashed_grouping)
+                       {
+                               /* Hashed aggregate plan --- no sort needed */
+                               result_plan = (Plan *) make_agg(parse,
+                                                                                               tlist,
+                                                                                               (List *) parse->havingQual,
+                                                                                               AGG_HASHED,
+                                                                                               numGroupCols,
+                                                                                               groupColIdx,
+                                                                                               numGroups,
+                                                                                               agg_counts.numAggs,
+                                                                                               result_plan);
+                               /* Hashed aggregation produces randomly-ordered results */
+                               current_pathkeys = NIL;
+                       }
+                       else if (parse->hasAggs)
+                       {
+                               /* Plain aggregate plan --- sort if needed */
+                               AggStrategy aggstrategy;
 
-               /*
-                * Insert AGG or GROUP node if needed, plus an explicit sort step
-                * if necessary.
-                *
-                * HAVING clause, if any, becomes qual of the Agg or Group node.
-                */
-               if (use_hashed_grouping)
-               {
-                       /* Hashed aggregate plan --- no sort needed */
-                       result_plan = (Plan *) make_agg(parse,
-                                                                                       tlist,
-                                                                                       (List *) parse->havingQual,
-                                                                                       AGG_HASHED,
-                                                                                       numGroupCols,
-                                                                                       groupColIdx,
-                                                                                       numGroups,
-                                                                                       agg_counts.numAggs,
-                                                                                       result_plan);
-                       /* Hashed aggregation produces randomly-ordered results */
-                       current_pathkeys = NIL;
-               }
-               else if (parse->hasAggs)
-               {
-                       /* Plain aggregate plan --- sort if needed */
-                       AggStrategy aggstrategy;
+                               if (parse->groupClause)
+                               {
+                                       if (!pathkeys_contained_in(group_pathkeys,
+                                                                                          current_pathkeys))
+                                       {
+                                               result_plan = (Plan *)
+                                                       make_sort_from_groupcols(parse,
+                                                                                                        parse->groupClause,
+                                                                                                        groupColIdx,
+                                                                                                        result_plan);
+                                               current_pathkeys = group_pathkeys;
+                                       }
+                                       aggstrategy = AGG_SORTED;
 
-                       if (parse->groupClause)
+                                       /*
+                                        * The AGG node will not change the sort ordering of its
+                                        * groups, so current_pathkeys describes the result too.
+                                        */
+                               }
+                               else
+                               {
+                                       aggstrategy = AGG_PLAIN;
+                                       /* Result will be only one row anyway; no sort order */
+                                       current_pathkeys = NIL;
+                               }
+
+                               result_plan = (Plan *) make_agg(parse,
+                                                                                               tlist,
+                                                                                               (List *) parse->havingQual,
+                                                                                               aggstrategy,
+                                                                                               numGroupCols,
+                                                                                               groupColIdx,
+                                                                                               numGroups,
+                                                                                               agg_counts.numAggs,
+                                                                                               result_plan);
+                       }
+                       else if (parse->groupClause)
                        {
+                               /*
+                                * GROUP BY without aggregation, so insert a group node (plus
+                                * the appropriate sort node, if necessary).
+                                *
+                                * Add an explicit sort if we couldn't make the path come
+                                * out the way the GROUP node needs it.
+                                */
                                if (!pathkeys_contained_in(group_pathkeys, current_pathkeys))
                                {
                                        result_plan = (Plan *)
@@ -1076,75 +1107,34 @@ grouping_planner(Query *parse, double tuple_fraction)
                                                                                                 result_plan);
                                        current_pathkeys = group_pathkeys;
                                }
-                               aggstrategy = AGG_SORTED;
 
-                               /*
-                                * The AGG node will not change the sort ordering of its
-                                * groups, so current_pathkeys describes the result too.
-                                */
+                               result_plan = (Plan *) make_group(parse,
+                                                                                                 tlist,
+                                                                                                 (List *) parse->havingQual,
+                                                                                                 numGroupCols,
+                                                                                                 groupColIdx,
+                                                                                                 dNumGroups,
+                                                                                                 result_plan);
+                               /* The Group node won't change sort ordering */
                        }
-                       else
+                       else if (parse->hasHavingQual)
                        {
-                               aggstrategy = AGG_PLAIN;
-                               /* Result will be only one row anyway; no sort order */
-                               current_pathkeys = NIL;
-                       }
-
-                       result_plan = (Plan *) make_agg(parse,
-                                                                                       tlist,
-                                                                                       (List *) parse->havingQual,
-                                                                                       aggstrategy,
-                                                                                       numGroupCols,
-                                                                                       groupColIdx,
-                                                                                       numGroups,
-                                                                                       agg_counts.numAggs,
-                                                                                       result_plan);
-               }
-               else if (parse->groupClause)
-               {
-                       /*
-                        * GROUP BY without aggregation, so insert a group node (plus the
-                        * appropriate sort node, if necessary).
-                        *
-                        * Add an explicit sort if we couldn't make the path come
-                        * out the way the GROUP node needs it.
-                        */
-                       if (!pathkeys_contained_in(group_pathkeys, current_pathkeys))
-                       {
-                               result_plan = (Plan *)
-                                       make_sort_from_groupcols(parse,
-                                                                                        parse->groupClause,
-                                                                                        groupColIdx,
-                                                                                        result_plan);
-                               current_pathkeys = group_pathkeys;
+                               /*
+                                * No aggregates, and no GROUP BY, but we have a HAVING qual.
+                                * This is a degenerate case in which we are supposed to emit
+                                * either 0 or 1 row depending on whether HAVING succeeds.
+                                * Furthermore, there cannot be any variables in either HAVING
+                                * or the targetlist, so we actually do not need the FROM table
+                                * at all!  We can just throw away the plan-so-far and generate
+                                * a Result node.  This is a sufficiently unusual corner case
+                                * that it's not worth contorting the structure of this routine
+                                * to avoid having to generate the plan in the first place.
+                                */
+                               result_plan = (Plan *) make_result(tlist,
+                                                                                                  parse->havingQual,
+                                                                                                  NULL);
                        }
-
-                       result_plan = (Plan *) make_group(parse,
-                                                                                         tlist,
-                                                                                         (List *) parse->havingQual,
-                                                                                         numGroupCols,
-                                                                                         groupColIdx,
-                                                                                         dNumGroups,
-                                                                                         result_plan);
-                       /* The Group node won't change sort ordering */
-               }
-               else if (parse->hasHavingQual)
-               {
-                       /*
-                        * No aggregates, and no GROUP BY, but we have a HAVING qual.
-                        * This is a degenerate case in which we are supposed to emit
-                        * either 0 or 1 row depending on whether HAVING succeeds.
-                        * Furthermore, there cannot be any variables in either HAVING
-                        * or the targetlist, so we actually do not need the FROM table
-                        * at all!  We can just throw away the plan-so-far and generate
-                        * a Result node.  This is a sufficiently unusual corner case
-                        * that it's not worth contorting the structure of this routine
-                        * to avoid having to generate the plan in the first place.
-                        */
-                       result_plan = (Plan *) make_result(tlist,
-                                                                                          parse->havingQual,
-                                                                                          NULL);
-               }
+               }                                               /* end of non-minmax-aggregate case */
        }                                                       /* end of if (setOperations) */
 
        /*
index c92fb3153167fe10adffea424bdbf35b1413ec93..f1519569a85f30a3dbae3a438ebbd5ede3bd3ebb 100644 (file)
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1994, Regents of the University of California
  *
  * IDENTIFICATION
- *       $PostgreSQL: pgsql/src/backend/optimizer/plan/subselect.c,v 1.95 2005/04/06 16:34:05 tgl Exp $
+ *       $PostgreSQL: pgsql/src/backend/optimizer/plan/subselect.c,v 1.96 2005/04/11 23:06:55 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -915,14 +915,16 @@ process_sublinks_mutator(Node *node, bool *isTopQual)
 /*
  * SS_finalize_plan - do final sublink processing for a completed Plan.
  *
- * This recursively computes the extParam and allParam sets
- * for every Plan node in the given plan tree.
+ * This recursively computes the extParam and allParam sets for every Plan
+ * node in the given plan tree.  It also attaches any generated InitPlans
+ * to the top plan node.
  */
 void
 SS_finalize_plan(Plan *plan, List *rtable)
 {
        Bitmapset  *outer_params = NULL;
        Bitmapset  *valid_params = NULL;
+       Cost            initplan_cost = 0;
        int                     paramid;
        ListCell   *l;
 
@@ -959,6 +961,33 @@ SS_finalize_plan(Plan *plan, List *rtable)
 
        bms_free(outer_params);
        bms_free(valid_params);
+
+       /*
+        * Finally, attach any initPlans to the topmost plan node,
+        * and add their extParams to the topmost node's, too.
+        *
+        * We also add the total_cost of each initPlan to the startup cost of
+        * the top node.  This is a conservative overestimate, since in
+        * fact each initPlan might be executed later than plan startup,
+        * or even not at all.
+        */
+       plan->initPlan = PlannerInitPlan;
+       PlannerInitPlan = NIL;          /* make sure they're not attached twice */
+
+       foreach(l, plan->initPlan)
+       {
+               SubPlan    *initplan = (SubPlan *) lfirst(l);
+
+               plan->extParam = bms_add_members(plan->extParam,
+                                                                                initplan->plan->extParam);
+               /* allParam must include all members of extParam */
+               plan->allParam = bms_add_members(plan->allParam,
+                                                                                plan->extParam);
+               initplan_cost += initplan->plan->total_cost;
+       }
+
+       plan->startup_cost += initplan_cost;
+       plan->total_cost += initplan_cost;
 }
 
 /*
@@ -1165,3 +1194,75 @@ finalize_primnode(Node *node, finalize_primnode_context *context)
        return expression_tree_walker(node, finalize_primnode,
                                                                  (void *) context);
 }
+
+/*
+ * SS_make_initplan_from_plan - given a plan tree, make it an InitPlan
+ *
+ * The plan is expected to return a scalar value of the indicated type.
+ * We build an EXPR_SUBLINK SubPlan node and put it into the initplan
+ * list for the current query level.  A Param that represents the initplan's
+ * output is returned.
+ *
+ * We assume the plan hasn't been put through SS_finalize_plan.
+ */
+Param *
+SS_make_initplan_from_plan(Query *root, Plan *plan,
+                                                  Oid resulttype, int32 resulttypmod)
+{
+       List       *saved_initplan = PlannerInitPlan;
+       SubPlan    *node;
+       Param      *prm;
+       Bitmapset  *tmpset;
+       int                     paramid;
+
+       /*
+        * Set up for a new level of subquery.  This is just to keep
+        * SS_finalize_plan from becoming confused.
+        */
+       PlannerQueryLevel++;
+       PlannerInitPlan = NIL;
+
+       /*
+        * Build extParam/allParam sets for plan nodes.
+        */
+       SS_finalize_plan(plan, root->rtable);
+
+       /* Return to outer subquery context */
+       PlannerQueryLevel--;
+       PlannerInitPlan = saved_initplan;
+
+       /*
+        * Create a SubPlan node and add it to the outer list of InitPlans.
+        */
+       node = makeNode(SubPlan);
+       node->subLinkType = EXPR_SUBLINK;
+       node->plan = plan;
+       node->plan_id = PlannerPlanId++;        /* Assign unique ID to this
+                                                                                * SubPlan */
+
+       node->rtable = root->rtable;
+
+       PlannerInitPlan = lappend(PlannerInitPlan, node);
+
+       /*
+        * Make parParam list of params that current query level will pass to
+        * this child plan.  (In current usage there probably aren't any.)
+        */
+       tmpset = bms_copy(plan->extParam);
+       while ((paramid = bms_first_member(tmpset)) >= 0)
+       {
+               PlannerParamItem *pitem = list_nth(PlannerParamList, paramid);
+
+               if (pitem->abslevel == PlannerQueryLevel)
+                       node->parParam = lappend_int(node->parParam, paramid);
+       }
+       bms_free(tmpset);
+
+       /*
+        * Make a Param that will be the subplan's output.
+        */
+       prm = generate_new_param(resulttype, resulttypmod);
+       node->setParam = list_make1_int(prm->paramid);
+
+       return prm;
+}
index 3abbf65fa4833d6ef99ece190603088a49b4fac6..2c4d20576a5552da22c9e6912c261d646a711798 100644 (file)
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1994, Regents of the University of California
  *
  * IDENTIFICATION
- *       $PostgreSQL: pgsql/src/backend/utils/cache/lsyscache.c,v 1.122 2005/03/31 22:46:14 tgl Exp $
+ *       $PostgreSQL: pgsql/src/backend/utils/cache/lsyscache.c,v 1.123 2005/04/11 23:06:56 tgl Exp $
  *
  * NOTES
  *       Eventually, the index information should go through here, too.
@@ -53,6 +53,31 @@ op_in_opclass(Oid opno, Oid opclass)
                                                                0, 0);
 }
 
+/*
+ * get_op_opclass_strategy
+ *
+ *             Get the operator's strategy number within the specified opclass,
+ *             or 0 if it's not a member of the opclass.
+ */
+int
+get_op_opclass_strategy(Oid opno, Oid opclass)
+{
+       HeapTuple       tp;
+       Form_pg_amop amop_tup;
+       int                     result;
+
+       tp = SearchSysCache(AMOPOPID,
+                                               ObjectIdGetDatum(opno),
+                                               ObjectIdGetDatum(opclass),
+                                               0, 0);
+       if (!HeapTupleIsValid(tp))
+               return 0;
+       amop_tup = (Form_pg_amop) GETSTRUCT(tp);
+       result = amop_tup->amopstrategy;
+       ReleaseSysCache(tp);
+       return result;
+}
+
 /*
  * get_op_opclass_properties
  *
index 28c44444d70cd59471cbf6ac8cf8e42ecc403636..e5ca24807d0027e4221da6a45c4719b1befbc61a 100644 (file)
@@ -8,7 +8,7 @@
  * Portions Copyright (c) 1996-2005, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/include/optimizer/paths.h,v 1.80 2005/03/27 06:29:49 tgl Exp $
+ * $PostgreSQL: pgsql/src/include/optimizer/paths.h,v 1.81 2005/04/11 23:06:56 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -38,8 +38,11 @@ extern void debug_print_rel(Query *root, RelOptInfo *rel);
 extern void create_index_paths(Query *root, RelOptInfo *rel);
 extern Path *best_inner_indexscan(Query *root, RelOptInfo *rel,
                                         Relids outer_relids, JoinType jointype);
+extern List *group_clauses_by_indexkey(IndexOptInfo *index);
 extern List *group_clauses_by_indexkey_for_or(IndexOptInfo *index,
                                                                 Expr *orsubclause);
+extern bool match_index_to_operand(Node *operand, int indexcol,
+                                          IndexOptInfo *index);
 extern List *expand_indexqual_conditions(IndexOptInfo *index,
                                                                                 List *clausegroups);
 extern void check_partial_indexes(Query *root, RelOptInfo *rel);
index e76d55ea4968ef796b3b09d9b212a9f7127bdb3d..f2a48e77c9e3bda6726ec764f43d1abc82997c2f 100644 (file)
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2005, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/include/optimizer/planmain.h,v 1.80 2005/03/10 23:21:25 tgl Exp $
+ * $PostgreSQL: pgsql/src/include/optimizer/planmain.h,v 1.81 2005/04/11 23:06:56 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
 extern void query_planner(Query *root, List *tlist, double tuple_fraction,
                          Path **cheapest_path, Path **sorted_path);
 
+/*
+ * prototypes for plan/planagg.c
+ */
+extern Plan *optimize_minmax_aggregates(Query *root, List *tlist,
+                                                                               Path *best_path);
+
 /*
  * prototypes for plan/createplan.c
  */
index c07593ad48e82306b33046b1e7e274604f6b66c4..9381321a46fa7bbc01a327a148dbe8e04ba52882 100644 (file)
@@ -5,7 +5,7 @@
  * Portions Copyright (c) 1996-2005, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/include/optimizer/subselect.h,v 1.23 2004/12/31 22:03:36 pgsql Exp $
+ * $PostgreSQL: pgsql/src/include/optimizer/subselect.h,v 1.24 2005/04/11 23:06:56 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -24,5 +24,7 @@ extern Node *convert_IN_to_join(Query *parse, SubLink *sublink);
 extern Node *SS_replace_correlation_vars(Node *expr);
 extern Node *SS_process_sublinks(Node *expr, bool isQual);
 extern void SS_finalize_plan(Plan *plan, List *rtable);
+extern Param *SS_make_initplan_from_plan(Query *root, Plan *plan,
+                                                                                Oid resulttype, int32 resulttypmod);
 
 #endif   /* SUBSELECT_H */
index 845a886ba82ff0241f7eca5b240d11e51a15ef8e..b98b53c60fc618d4c4c02e8fbd53c6f94d693c4c 100644 (file)
@@ -6,7 +6,7 @@
  * Portions Copyright (c) 1996-2005, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/include/utils/lsyscache.h,v 1.96 2005/03/31 22:46:27 tgl Exp $
+ * $PostgreSQL: pgsql/src/include/utils/lsyscache.h,v 1.97 2005/04/11 23:06:56 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -25,6 +25,7 @@ typedef enum IOFuncSelector
 } IOFuncSelector;
 
 extern bool op_in_opclass(Oid opno, Oid opclass);
+extern int     get_op_opclass_strategy(Oid opno, Oid opclass);
 extern void get_op_opclass_properties(Oid opno, Oid opclass,
                                                  int *strategy, Oid *subtype,
                                                  bool *recheck);
index d07c6d195b7c7a23d7292ce86fcbad8488266928..8aed186403462a0b15b08a1060e0b613c1a6c15e 100644 (file)
@@ -293,3 +293,58 @@ FROM bool_test;
  t | t | f |   | f | t
 (1 row)
 
+--
+-- Test several cases that should be optimized into indexscans instead of
+-- the generic aggregate implementation.  We can't actually verify that they
+-- are done as indexscans, but we can check that the results are correct.
+--
+-- Basic cases
+select max(unique1) from tenk1;
+ max  
+------
+ 9999
+(1 row)
+
+select max(unique1) from tenk1 where unique1 < 42;
+ max 
+-----
+  41
+(1 row)
+
+select max(unique1) from tenk1 where unique1 > 42;
+ max  
+------
+ 9999
+(1 row)
+
+select max(unique1) from tenk1 where unique1 > 42000;
+ max 
+-----
+    
+(1 row)
+
+-- multi-column index (uses tenk1_thous_tenthous)
+select max(tenthous) from tenk1 where thousand = 33;
+ max  
+------
+ 9033
+(1 row)
+
+select min(tenthous) from tenk1 where thousand = 33;
+ min 
+-----
+  33
+(1 row)
+
+-- check parameter propagation into an indexscan subquery
+select f1, (select min(unique1) from tenk1 where unique1 > f1) AS gt
+from int4_tbl;
+     f1      | gt 
+-------------+----
+           0 |  1
+      123456 |   
+     -123456 |  0
+  2147483647 |   
+ -2147483647 |  0
+(5 rows)
+
index 6643070e71700d75a633caef11b9d3c4f8e14a44..a7332c86716349891746a9170e2a59202e6eb5e0 100644 (file)
@@ -12,6 +12,7 @@ CREATE INDEX onek_stringu1 ON onek USING btree(stringu1 name_ops);
 CREATE INDEX tenk1_unique1 ON tenk1 USING btree(unique1 int4_ops);
 CREATE INDEX tenk1_unique2 ON tenk1 USING btree(unique2 int4_ops);
 CREATE INDEX tenk1_hundred ON tenk1 USING btree(hundred int4_ops);
+CREATE INDEX tenk1_thous_tenthous ON tenk1 (thousand, tenthous);
 CREATE INDEX tenk2_unique1 ON tenk2 USING btree(unique1 int4_ops);
 CREATE INDEX tenk2_unique2 ON tenk2 USING btree(unique2 int4_ops);
 CREATE INDEX tenk2_hundred ON tenk2 USING btree(hundred int4_ops);
index d9fdcb502fbbb1ac5a7780bd852fa79b0df9ea95..b6aba0d66b7b2a0499c1a03b5cf233716183c615 100644 (file)
@@ -180,3 +180,23 @@ SELECT
   BOOL_OR(NOT b2)  AS "f",
   BOOL_OR(NOT b3)  AS "t"
 FROM bool_test;
+
+--
+-- Test several cases that should be optimized into indexscans instead of
+-- the generic aggregate implementation.  We can't actually verify that they
+-- are done as indexscans, but we can check that the results are correct.
+--
+
+-- Basic cases
+select max(unique1) from tenk1;
+select max(unique1) from tenk1 where unique1 < 42;
+select max(unique1) from tenk1 where unique1 > 42;
+select max(unique1) from tenk1 where unique1 > 42000;
+
+-- multi-column index (uses tenk1_thous_tenthous)
+select max(tenthous) from tenk1 where thousand = 33;
+select min(tenthous) from tenk1 where thousand = 33;
+
+-- check parameter propagation into an indexscan subquery
+select f1, (select min(unique1) from tenk1 where unique1 > f1) AS gt
+from int4_tbl;
index 6fa0b91e83c96b62022d34e26df72ae017813c0f..71c0b0c2b0add1464ae9e5c3a419b3ed9706b785 100644 (file)
@@ -20,6 +20,8 @@ CREATE INDEX tenk1_unique2 ON tenk1 USING btree(unique2 int4_ops);
 
 CREATE INDEX tenk1_hundred ON tenk1 USING btree(hundred int4_ops);
 
+CREATE INDEX tenk1_thous_tenthous ON tenk1 (thousand, tenthous);
+
 CREATE INDEX tenk2_unique1 ON tenk2 USING btree(unique1 int4_ops);
 
 CREATE INDEX tenk2_unique2 ON tenk2 USING btree(unique2 int4_ops);