*
*
* IDENTIFICATION
- * $Header: /cvsroot/pgsql/src/backend/optimizer/plan/planner.c,v 1.144 2003/02/04 00:50:00 tgl Exp $
+ * $Header: /cvsroot/pgsql/src/backend/optimizer/plan/planner.c,v 1.157 2003/07/25 00:01:07 tgl Exp $
*
*-------------------------------------------------------------------------
*/
#include "catalog/pg_operator.h"
#include "catalog/pg_type.h"
+#include "executor/executor.h"
#include "miscadmin.h"
#include "nodes/makefuncs.h"
#ifdef OPTIMIZER_DEBUG
#define EXPRKIND_QUAL 0
#define EXPRKIND_TARGET 1
#define EXPRKIND_RTFUNC 2
-#define EXPRKIND_ININFO 3
+#define EXPRKIND_LIMIT 3
+#define EXPRKIND_ININFO 4
static Node *preprocess_expression(Query *parse, Node *expr, int kind);
List *tlist,
List *sub_tlist,
AttrNumber *groupColIdx);
-static Plan *make_groupsortplan(Query *parse,
- List *groupClause,
- AttrNumber *grpColIdx,
- Plan *subplan);
static List *postprocess_setop_tlist(List *new_tlist, List *orig_tlist);
*
*****************************************************************************/
Plan *
-planner(Query *parse)
+planner(Query *parse, bool isCursor, int cursorOptions)
{
+ double tuple_fraction;
Plan *result_plan;
Index save_PlannerQueryLevel;
- List *save_PlannerParamVar;
+ List *save_PlannerParamList;
/*
* The planner can be called recursively (an example is when
* subquery_planner, not here.
*/
save_PlannerQueryLevel = PlannerQueryLevel;
- save_PlannerParamVar = PlannerParamVar;
+ save_PlannerParamList = PlannerParamList;
/* Initialize state for handling outer-level references and params */
PlannerQueryLevel = 0; /* will be 1 in top-level subquery_planner */
- PlannerParamVar = NIL;
+ PlannerParamList = NIL;
+
+ /* Determine what fraction of the plan is likely to be scanned */
+ if (isCursor)
+ {
+ /*
+ * We have no real idea how many tuples the user will ultimately
+ * FETCH from a cursor, but it seems a good bet that he
+ * doesn't want 'em all. Optimize for 10% retrieval (you
+ * gotta better number? Should this be a SETtable parameter?)
+ */
+ tuple_fraction = 0.10;
+ }
+ else
+ {
+ /* Default assumption is we need all the tuples */
+ tuple_fraction = 0.0;
+ }
/* primary planning entry point (may recurse for subqueries) */
- result_plan = subquery_planner(parse, -1.0 /* default case */ );
+ result_plan = subquery_planner(parse, tuple_fraction);
Assert(PlannerQueryLevel == 0);
+ /*
+ * If creating a plan for a scrollable cursor, make sure it can
+ * run backwards on demand. Add a Material node at the top at need.
+ */
+ if (isCursor && (cursorOptions & CURSOR_OPT_SCROLL))
+ {
+ if (!ExecSupportsBackwardScan(result_plan))
+ result_plan = materialize_finished_plan(result_plan);
+ }
+
/* executor wants to know total number of Params used overall */
- result_plan->nParamExec = length(PlannerParamVar);
+ result_plan->nParamExec = length(PlannerParamList);
/* final cleanup of the plan */
set_plan_references(result_plan, parse->rtable);
/* restore state for outer planner, if any */
PlannerQueryLevel = save_PlannerQueryLevel;
- PlannerParamVar = save_PlannerParamVar;
+ PlannerParamList = save_PlannerParamList;
return result_plan;
}
{
List *saved_initplan = PlannerInitPlan;
int saved_planid = PlannerPlanId;
+ bool hasOuterJoins;
Plan *plan;
List *newHaving;
List *lst;
/*
* Detect whether any rangetable entries are RTE_JOIN kind; if not,
- * we can avoid the expense of doing flatten_join_alias_vars().
+ * we can avoid the expense of doing flatten_join_alias_vars(). Also
+ * check for outer joins --- if none, we can skip reduce_outer_joins().
* This must be done after we have done pull_up_subqueries, of course.
*/
parse->hasJoinRTEs = false;
+ hasOuterJoins = false;
foreach(lst, parse->rtable)
{
RangeTblEntry *rte = (RangeTblEntry *) lfirst(lst);
if (rte->rtekind == RTE_JOIN)
{
parse->hasJoinRTEs = true;
- break;
+ if (IS_OUTER_JOIN(rte->jointype))
+ {
+ hasOuterJoins = true;
+ /* Can quit scanning once we find an outer join */
+ break;
+ }
}
}
parse->havingQual = preprocess_expression(parse, parse->havingQual,
EXPRKIND_QUAL);
+ parse->limitOffset = preprocess_expression(parse, parse->limitOffset,
+ EXPRKIND_LIMIT);
+ parse->limitCount = preprocess_expression(parse, parse->limitCount,
+ EXPRKIND_LIMIT);
+
parse->in_info_list = (List *)
preprocess_expression(parse, (Node *) parse->in_info_list,
EXPRKIND_ININFO);
*
* Note that both havingQual and parse->jointree->quals are in
* implicitly-ANDed-list form at this point, even though they are
- * declared as Node *. Also note that contain_agg_clause does not
- * recurse into sub-selects, which is exactly what we need here.
+ * declared as Node *.
*/
newHaving = NIL;
foreach(lst, (List *) parse->havingQual)
}
parse->havingQual = (Node *) newHaving;
+ /*
+ * If we have any outer joins, try to reduce them to plain inner joins.
+ * This step is most easily done after we've done expression preprocessing.
+ */
+ if (hasOuterJoins)
+ reduce_outer_joins(parse);
+
/*
* See if we can simplify the jointree; opportunities for this may come
* from having pulled up subqueries, or from flattening explicit JOIN
* syntax. We must do this after flattening JOIN alias variables, since
* eliminating explicit JOIN nodes from the jointree will cause
- * get_relids_for_join() to fail.
+ * get_relids_for_join() to fail. But it should happen after
+ * reduce_outer_joins, anyway.
*/
parse->jointree = (FromExpr *)
- preprocess_jointree(parse, (Node *) parse->jointree);
+ simplify_jointree(parse, (Node *) parse->jointree);
/*
* Do the main planning. If we have an inherited target relation,
* grouping_planner.
*/
if (parse->resultRelation &&
- (lst = expand_inherted_rtentry(parse, parse->resultRelation, false))
- != NIL)
+ (lst = expand_inherited_rtentry(parse, parse->resultRelation,
+ false)) != NIL)
plan = inheritance_planner(parse, lst);
else
plan = grouping_planner(parse, tuple_fraction);
/*
* If any subplans were generated, or if we're inside a subplan, build
- * initPlan, extParam and locParam lists for plan nodes.
+ * initPlan list and extParam/allParam sets for plan nodes.
*/
if (PlannerPlanId != saved_planid || PlannerQueryLevel > 1)
{
Cost initplan_cost = 0;
- /* Prepare extParam/locParam data for all nodes in tree */
- (void) SS_finalize_plan(plan, parse->rtable);
+ /* 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
{
SubPlan *initplan = (SubPlan *) lfirst(lst);
- plan->extParam = set_unioni(plan->extParam,
- initplan->plan->extParam);
+ plan->extParam = bms_add_members(plan->extParam,
+ initplan->plan->extParam);
initplan_cost += initplan->plan->total_cost;
}
if (parse->hasSubLinks)
expr = SS_process_sublinks(expr, (kind == EXPRKIND_QUAL));
+ /*
+ * XXX do not insert anything here unless you have grokked the comments
+ * in SS_replace_correlation_vars ...
+ */
+
/* Replace uplevel vars with Param nodes */
if (PlannerQueryLevel > 1)
expr = SS_replace_correlation_vars(expr);
j->quals = preprocess_expression(parse, j->quals, EXPRKIND_QUAL);
}
else
- elog(ERROR, "preprocess_qual_conditions: unexpected node type %d",
- nodeTag(jtnode));
+ elog(ERROR, "unrecognized node type: %d",
+ (int) nodeTag(jtnode));
}
/*--------------------
{
int parentRTindex = parse->resultRelation;
Oid parentOID = getrelid(parentRTindex, parse->rtable);
+ int mainrtlength = length(parse->rtable);
List *subplans = NIL;
List *tlist = NIL;
List *l;
{
int childRTindex = lfirsti(l);
Oid childOID = getrelid(childRTindex, parse->rtable);
+ int subrtlength;
Query *subquery;
Plan *subplan;
/* Generate plan */
subplan = grouping_planner(subquery, 0.0 /* retrieve all tuples */ );
subplans = lappend(subplans, subplan);
+ /*
+ * It's possible that additional RTEs got added to the rangetable
+ * due to expansion of inherited source tables (see allpaths.c).
+ * If so, we must copy 'em back to the main parse tree's rtable.
+ *
+ * XXX my goodness this is ugly. Really need to think about ways
+ * to rein in planner's habit of scribbling on its input.
+ */
+ subrtlength = length(subquery->rtable);
+ if (subrtlength > mainrtlength)
+ {
+ List *subrt = subquery->rtable;
+
+ while (mainrtlength-- > 0) /* wish we had nthcdr() */
+ subrt = lnext(subrt);
+ parse->rtable = nconc(parse->rtable, subrt);
+ mainrtlength = subrtlength;
+ }
/* Save preprocessed tlist from first rel for use in Append */
if (tlist == NIL)
tlist = subplan->targetlist;
/* Save the target-relations list for the executor, too */
parse->resultRelations = inheritlist;
+ /* Mark result as unordered (probably unnecessary) */
+ parse->query_pathkeys = NIL;
+
return (Plan *) make_append(subplans, true, tlist);
}
* tuple_fraction is the fraction of tuples we expect will be retrieved
*
* tuple_fraction is interpreted as follows:
- * < 0: determine fraction by inspection of query (normal case)
- * 0: expect all tuples to be retrieved
+ * 0: expect all tuples to be retrieved (normal case)
* 0 < tuple_fraction < 1: expect the given fraction of tuples available
* from the plan to be retrieved
* tuple_fraction >= 1: tuple_fraction is the absolute number of tuples
* expected to be retrieved (ie, a LIMIT specification)
- * The normal case is to pass -1, but some callers pass values >= 0 to
- * override this routine's determination of the appropriate fraction.
*
- * Returns a query plan.
+ * Returns a query plan. Also, parse->query_pathkeys is returned as the
+ * actual output ordering of the plan (in pathkey format).
*--------------------
*/
static Plan *
* already, but let's make sure).
*/
if (parse->rowMarks)
- elog(ERROR, "SELECT FOR UPDATE is not allowed with UNION/INTERSECT/EXCEPT");
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("SELECT FOR UPDATE is not allowed with UNION/INTERSECT/EXCEPT")));
/*
* We set current_pathkeys NIL indicating we do not know sort
* level
*/
if (PlannerQueryLevel > 1)
- elog(ERROR, "SELECT FOR UPDATE is not allowed in subselects");
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("SELECT FOR UPDATE is not allowed in subselects")));
foreach(l, parse->rowMarks)
{
else
parse->query_pathkeys = NIL;
- /*
- * Figure out whether we expect to retrieve all the tuples that
- * the plan can generate, or to stop early due to outside factors
- * such as a cursor. If the caller passed a value >= 0, believe
- * that value, else do our own examination of the query context.
- */
- if (tuple_fraction < 0.0)
- {
- /* Initial assumption is we need all the tuples */
- tuple_fraction = 0.0;
-
- /*
- * Check for retrieve-into-portal, ie DECLARE CURSOR.
- *
- * We have no real idea how many tuples the user will ultimately
- * FETCH from a cursor, but it seems a good bet that he
- * doesn't want 'em all. Optimize for 10% retrieval (you
- * gotta better number? Should this be a SETtable parameter?)
- */
- if (parse->isPortal)
- tuple_fraction = 0.10;
- }
-
/*
* Adjust tuple_fraction if we see that we are going to apply
* limiting/grouping/aggregation/etc. This is not overridable by
if (parse->groupClause)
{
List *groupExprs;
+ double cheapest_path_rows;
+ int cheapest_path_width;
+
+ /*
+ * Beware in this section of the possibility that
+ * cheapest_path->parent is NULL. This could happen if user
+ * does something silly like SELECT 'foo' GROUP BY 1;
+ */
+ if (cheapest_path->parent)
+ {
+ cheapest_path_rows = cheapest_path->parent->rows;
+ cheapest_path_width = cheapest_path->parent->width;
+ }
+ else
+ {
+ cheapest_path_rows = 1; /* assume non-set result */
+ cheapest_path_width = 100; /* arbitrary */
+ }
/*
* Always estimate the number of groups. We can't do this until
parse->targetList);
dNumGroups = estimate_num_groups(parse,
groupExprs,
- cheapest_path->parent->rows);
+ cheapest_path_rows);
/* Also want it as a long int --- but 'ware overflow! */
numGroups = (long) Min(dNumGroups, (double) LONG_MAX);
* assume it is 100 bytes. Also set the overhead per hashtable
* entry at 64 bytes.
*/
- int hashentrysize = cheapest_path->parent->width + 64 +
- numAggs * 100;
+ int hashentrysize = cheapest_path_width + 64 + numAggs * 100;
if (hashentrysize * dNumGroups <= SortMem * 1024L)
{
numGroupCols, dNumGroups,
cheapest_path->startup_cost,
cheapest_path->total_cost,
- cheapest_path->parent->rows);
+ cheapest_path_rows);
/* Result of hashed agg is always unsorted */
if (sort_pathkeys)
cost_sort(&hashed_p, parse, sort_pathkeys,
hashed_p.total_cost,
dNumGroups,
- cheapest_path->parent->width);
+ cheapest_path_width);
if (sorted_path)
{
{
cost_sort(&sorted_p, parse, group_pathkeys,
sorted_p.total_cost,
- cheapest_path->parent->rows,
- cheapest_path->parent->width);
+ cheapest_path_rows,
+ cheapest_path_width);
current_pathkeys = group_pathkeys;
}
if (parse->hasAggs)
numGroupCols, dNumGroups,
sorted_p.startup_cost,
sorted_p.total_cost,
- cheapest_path->parent->rows);
+ cheapest_path_rows);
else
cost_group(&sorted_p, parse,
numGroupCols, dNumGroups,
sorted_p.startup_cost,
sorted_p.total_cost,
- cheapest_path->parent->rows);
+ cheapest_path_rows);
/* The Agg or Group node will preserve ordering */
if (sort_pathkeys &&
!pathkeys_contained_in(sort_pathkeys,
cost_sort(&sorted_p, parse, sort_pathkeys,
sorted_p.total_cost,
dNumGroups,
- cheapest_path->parent->width);
+ cheapest_path_width);
}
/*
tuple_fraction /= dNumGroups;
if (compare_fractional_path_costs(&hashed_p, &sorted_p,
- tuple_fraction) <= 0)
+ tuple_fraction) < 0)
{
/* Hashed is cheaper, so use it */
use_hashed_grouping = true;
{
if (!pathkeys_contained_in(group_pathkeys, current_pathkeys))
{
- result_plan = make_groupsortplan(parse,
- parse->groupClause,
- groupColIdx,
- result_plan);
+ result_plan = (Plan *)
+ make_sort_from_groupcols(parse,
+ parse->groupClause,
+ groupColIdx,
+ result_plan);
current_pathkeys = group_pathkeys;
}
aggstrategy = AGG_SORTED;
*/
if (!pathkeys_contained_in(group_pathkeys, current_pathkeys))
{
- result_plan = make_groupsortplan(parse,
- parse->groupClause,
- groupColIdx,
- result_plan);
+ result_plan = (Plan *)
+ make_sort_from_groupcols(parse,
+ parse->groupClause,
+ groupColIdx,
+ result_plan);
current_pathkeys = group_pathkeys;
}
if (parse->sortClause)
{
if (!pathkeys_contained_in(sort_pathkeys, current_pathkeys))
- result_plan = (Plan *) make_sort_from_sortclauses(parse,
- tlist,
- result_plan,
- parse->sortClause);
+ {
+ result_plan = (Plan *)
+ make_sort_from_sortclauses(parse,
+ tlist,
+ result_plan,
+ parse->sortClause);
+ current_pathkeys = sort_pathkeys;
+ }
}
/*
parse->limitCount);
}
+ /*
+ * Return the actual output ordering in query_pathkeys for possible
+ * use by an outer query level.
+ */
+ parse->query_pathkeys = current_pathkeys;
+
return result_plan;
}
* If we're not grouping or aggregating, nothing to do here;
* query_planner should receive the unmodified target list.
*/
- if (!parse->hasAggs && !parse->groupClause && !parse->havingQual)
+ if (!parse->hasAggs && !parse->groupClause)
{
*need_tlist_eval = true;
return tlist;
break;
}
if (!sl)
- elog(ERROR, "locate_grouping_columns: failed");
+ elog(ERROR, "failed to locate grouping columns");
groupColIdx[keyno++] = te->resdom->resno;
}
}
-/*
- * make_groupsortplan
- * Add a Sort node to explicitly sort according to the GROUP BY clause.
- *
- * Note: the Sort node always just takes a copy of the subplan's tlist
- * plus ordering information. (This might seem inefficient if the
- * subplan contains complex GROUP BY expressions, but in fact Sort
- * does not evaluate its targetlist --- it only outputs the same
- * tuples in a new order. So the expressions we might be copying
- * are just dummies with no extra execution cost.)
- */
-static Plan *
-make_groupsortplan(Query *parse,
- List *groupClause,
- AttrNumber *grpColIdx,
- Plan *subplan)
-{
- List *sort_tlist = new_unsorted_tlist(subplan->targetlist);
- int keyno = 0;
- List *gl;
-
- foreach(gl, groupClause)
- {
- GroupClause *grpcl = (GroupClause *) lfirst(gl);
- TargetEntry *te = nth(grpColIdx[keyno] - 1, sort_tlist);
- Resdom *resdom = te->resdom;
-
- /*
- * Check for the possibility of duplicate group-by clauses ---
- * the parser should have removed 'em, but the Sort executor
- * will get terribly confused if any get through!
- */
- if (resdom->reskey == 0)
- {
- /* OK, insert the ordering info needed by the executor. */
- resdom->reskey = ++keyno;
- resdom->reskeyop = grpcl->sortop;
- }
- }
-
- Assert(keyno > 0);
-
- return (Plan *) make_sort(parse, sort_tlist, subplan, keyno);
-}
-
/*
* postprocess_setop_tlist
* Fix up targetlist returned by plan_set_operations().
* We need to transpose sort key info from the orig_tlist into new_tlist.
* NOTE: this would not be good enough if we supported resjunk sort keys
* for results of set operations --- then, we'd need to project a whole
- * new tlist to evaluate the resjunk columns. For now, just elog if we
+ * new tlist to evaluate the resjunk columns. For now, just ereport if we
* find any resjunk columns in orig_tlist.
*/
static List *
Assert(orig_tlist != NIL);
orig_tle = (TargetEntry *) lfirst(orig_tlist);
orig_tlist = lnext(orig_tlist);
- if (orig_tle->resdom->resjunk)
- elog(ERROR, "postprocess_setop_tlist: resjunk output columns not implemented");
+ if (orig_tle->resdom->resjunk) /* should not happen */
+ elog(ERROR, "resjunk output columns are not implemented");
Assert(new_tle->resdom->resno == orig_tle->resdom->resno);
Assert(new_tle->resdom->restype == orig_tle->resdom->restype);
new_tle->resdom->ressortgroupref = orig_tle->resdom->ressortgroupref;
}
if (orig_tlist != NIL)
- elog(ERROR, "postprocess_setop_tlist: resjunk output columns not implemented");
+ elog(ERROR, "resjunk output columns are not implemented");
return new_tlist;
}