*
*
* IDENTIFICATION
- * $Header: /cvsroot/pgsql/src/backend/optimizer/plan/planner.c,v 1.101 2001/01/27 04:42:32 tgl Exp $
+ * $Header: /cvsroot/pgsql/src/backend/optimizer/plan/planner.c,v 1.117 2002/05/12 23:43:03 tgl Exp $
*
*-------------------------------------------------------------------------
*/
#include "catalog/pg_type.h"
#include "nodes/makefuncs.h"
+#ifdef OPTIMIZER_DEBUG
+#include "nodes/print.h"
+#endif
#include "optimizer/clauses.h"
#include "optimizer/paths.h"
#include "optimizer/planmain.h"
/* Expression kind codes for preprocess_expression */
-#define EXPRKIND_TARGET 0
+#define EXPRKIND_TARGET 0
#define EXPRKIND_WHERE 1
-#define EXPRKIND_HAVING 2
+#define EXPRKIND_HAVING 2
-static Node *pull_up_subqueries(Query *parse, Node *jtnode);
+static Node *pull_up_subqueries(Query *parse, Node *jtnode,
+ bool below_outer_join);
static bool is_simple_subquery(Query *subquery);
+static bool has_nullable_targetlist(Query *subquery);
static void resolvenew_in_jointree(Node *jtnode, int varno, List *subtlist);
static Node *preprocess_jointree(Query *parse, Node *jtnode);
static Node *preprocess_expression(Query *parse, Node *expr, int kind);
static Plan *grouping_planner(Query *parse, double tuple_fraction);
static List *make_subplanTargetList(Query *parse, List *tlist,
AttrNumber **groupColIdx);
-static Plan *make_groupplan(List *group_tlist, bool tuplePerGroup,
+static Plan *make_groupplan(Query *parse,
+ List *group_tlist, bool tuplePerGroup,
List *groupClause, AttrNumber *grpColIdx,
bool is_presorted, Plan *subplan);
static List *postprocess_setop_tlist(List *new_tlist, List *orig_tlist);
/*
* The planner can be called recursively (an example is when
- * eval_const_expressions tries to pre-evaluate an SQL function).
- * So, these global state variables must be saved and restored.
+ * eval_const_expressions tries to pre-evaluate an SQL function). So,
+ * these global state variables must be saved and restored.
*
- * These vars cannot be moved into the Query structure since their
- * whole purpose is communication across multiple sub-Queries.
+ * These vars cannot be moved into the Query structure since their whole
+ * purpose is communication across multiple sub-Queries.
*
* Note we do NOT save and restore PlannerPlanId: it exists to assign
- * unique IDs to SubPlan nodes, and we want those IDs to be unique
- * for the life of a backend. Also, PlannerInitPlan is saved/restored
- * in subquery_planner, not here.
+ * unique IDs to SubPlan nodes, and we want those IDs to be unique for
+ * the life of a backend. Also, PlannerInitPlan is saved/restored in
+ * subquery_planner, not here.
*/
save_PlannerQueryLevel = PlannerQueryLevel;
save_PlannerParamVar = PlannerParamVar;
result_plan->nParamExec = length(PlannerParamVar);
/* final cleanup of the plan */
- set_plan_references(result_plan);
+ set_plan_references(parse, result_plan);
/* restore state for outer planner, if any */
PlannerQueryLevel = save_PlannerQueryLevel;
* this query.
*/
parse->jointree = (FromExpr *)
- pull_up_subqueries(parse, (Node *) parse->jointree);
+ pull_up_subqueries(parse, (Node *) parse->jointree, false);
+
/*
* If so, we may have created opportunities to simplify the jointree.
*/
parse->havingQual = preprocess_expression(parse, parse->havingQual,
EXPRKIND_HAVING);
+ /*
+ * Check for ungrouped variables passed to subplans in targetlist and
+ * HAVING clause (but not in WHERE or JOIN/ON clauses, since those are
+ * evaluated before grouping). We can't do this any earlier because
+ * we must use the preprocessed targetlist for comparisons of grouped
+ * expressions.
+ */
+ if (parse->hasSubLinks &&
+ (parse->groupClause != NIL || parse->hasAggs))
+ check_subplans_for_ungrouped_vars(parse);
+
/*
* A HAVING clause without aggregates is equivalent to a WHERE clause
- * (except it can only refer to grouped fields). Transfer any agg-free
- * clauses of the HAVING qual into WHERE. This may seem like wasting
- * cycles to cater to stupidly-written queries, but there are other
- * reasons for doing it. Firstly, if the query contains no aggs at all,
- * then we aren't going to generate an Agg plan node, and so there'll be
- * no place to execute HAVING conditions; without this transfer, we'd
- * lose the HAVING condition entirely, which is wrong. Secondly, when
- * we push down a qual condition into a sub-query, it's easiest to push
- * the qual into HAVING always, in case it contains aggs, and then let
- * this code sort it out.
+ * (except it can only refer to grouped fields). Transfer any
+ * agg-free clauses of the HAVING qual into WHERE. This may seem like
+ * wasting cycles to cater to stupidly-written queries, but there are
+ * other reasons for doing it. Firstly, if the query contains no aggs
+ * at all, then we aren't going to generate an Agg plan node, and so
+ * there'll be no place to execute HAVING conditions; without this
+ * transfer, we'd lose the HAVING condition entirely, which is wrong.
+ * Secondly, when we push down a qual condition into a sub-query, it's
+ * easiest to push the qual into HAVING always, in case it contains
+ * aggs, and then let this code sort it out.
*
* 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
+ * declared as Node *. Also note that contain_agg_clause does not
* recurse into sub-selects, which is exactly what we need here.
*/
newHaving = NIL;
foreach(lst, (List *) parse->havingQual)
{
- Node *havingclause = (Node *) lfirst(lst);
+ Node *havingclause = (Node *) lfirst(lst);
if (contain_agg_clause(havingclause))
newHaving = lappend(newHaving, havingclause);
/*
* Do the main planning. If we have an inherited target relation,
- * that needs special processing, else go straight to grouping_planner.
+ * that needs special processing, else go straight to
+ * grouping_planner.
*/
if (parse->resultRelation &&
- (lst = expand_inherted_rtentry(parse, parse->resultRelation)) != NIL)
+ (lst = expand_inherted_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 subPlan, extParam and locParam lists for plan nodes.
+ * If any subplans were generated, or if we're inside a subplan, build
+ * subPlan, extParam and locParam lists for plan nodes.
*/
if (PlannerPlanId != saved_planid || PlannerQueryLevel > 1)
{
(void) SS_finalize_plan(plan);
+
/*
- * At the moment, SS_finalize_plan doesn't handle initPlans
- * and so we assign them to the topmost plan node.
+ * At the moment, SS_finalize_plan doesn't handle initPlans and so
+ * we assign them to the topmost plan node.
*/
plan->initPlan = PlannerInitPlan;
/* Must add the initPlans' extParams to the topmost node's, too */
foreach(lst, plan->initPlan)
{
- SubPlan *subplan = (SubPlan *) lfirst(lst);
+ SubPlan *subplan = (SubPlan *) lfirst(lst);
plan->extParam = set_unioni(plan->extParam,
subplan->plan->extParam);
* the parent query. If the subquery has no special features like
* grouping/aggregation then we can merge it into the parent's jointree.
*
+ * below_outer_join is true if this jointree node is within the nullable
+ * side of an outer join. This restricts what we can do.
+ *
* A tricky aspect of this code is that if we pull up a subquery we have
* to replace Vars that reference the subquery's outputs throughout the
* parent query, including quals attached to jointree nodes above the one
* copy of the tree; we have to invoke it just on the quals, instead.
*/
static Node *
-pull_up_subqueries(Query *parse, Node *jtnode)
+pull_up_subqueries(Query *parse, Node *jtnode, bool below_outer_join)
{
if (jtnode == NULL)
return NULL;
Query *subquery = rte->subquery;
/*
- * Is this a subquery RTE, and if so, is the subquery simple enough
- * to pull up? (If not, do nothing at this node.)
+ * Is this a subquery RTE, and if so, is the subquery simple
+ * enough to pull up? (If not, do nothing at this node.)
+ *
+ * If we are inside an outer join, only pull up subqueries whose
+ * targetlists are nullable --- otherwise substituting their tlist
+ * entries for upper Var references would do the wrong thing
+ * (the results wouldn't become NULL when they're supposed to).
+ * XXX This could be improved by generating pseudo-variables for
+ * such expressions; we'd have to figure out how to get the pseudo-
+ * variables evaluated at the right place in the modified plan tree.
+ * Fix it someday.
+ *
+ * Note: even if the subquery itself is simple enough, we can't pull
+ * it up if there is a reference to its whole tuple result. Perhaps
+ * a pseudo-variable is the answer here too.
*/
- if (subquery && is_simple_subquery(subquery))
+ if (rte->rtekind == RTE_SUBQUERY && is_simple_subquery(subquery) &&
+ (!below_outer_join || has_nullable_targetlist(subquery)) &&
+ !contain_whole_tuple_var((Node *) parse, varno, 0))
{
- int rtoffset;
- Node *subjointree;
- List *subtlist;
- List *l;
+ int rtoffset;
+ List *subtlist;
+ List *rt;
/*
- * First, recursively pull up the subquery's subqueries,
- * so that this routine's processing is complete for its
- * jointree and rangetable. NB: if the same subquery is
- * referenced from multiple jointree items (which can't happen
- * normally, but might after rule rewriting), then we will invoke
- * this processing multiple times on that subquery. OK because
+ * First, recursively pull up the subquery's subqueries, so
+ * that this routine's processing is complete for its jointree
+ * and rangetable. NB: if the same subquery is referenced
+ * from multiple jointree items (which can't happen normally,
+ * but might after rule rewriting), then we will invoke this
+ * processing multiple times on that subquery. OK because
* nothing will happen after the first time. We do have to be
* careful to copy everything we pull up, however, or risk
* having chunks of structure multiply linked.
*/
subquery->jointree = (FromExpr *)
- pull_up_subqueries(subquery, (Node *) subquery->jointree);
+ pull_up_subqueries(subquery, (Node *) subquery->jointree,
+ below_outer_join);
+
/*
- * Append the subquery's rangetable to mine (currently,
- * no adjustments will be needed in the subquery's rtable).
+ * Now make a modifiable copy of the subquery that we can
+ * run OffsetVarNodes on.
*/
- rtoffset = length(parse->rtable);
- parse->rtable = nconc(parse->rtable,
- copyObject(subquery->rtable));
+ subquery = copyObject(subquery);
+
/*
- * Make copies of the subquery's jointree and targetlist
- * with varnos adjusted to match the merged rangetable.
+ * Adjust varnos in subquery so that we can append its
+ * rangetable to upper query's.
*/
- subjointree = copyObject(subquery->jointree);
- OffsetVarNodes(subjointree, rtoffset, 0);
- subtlist = copyObject(subquery->targetList);
- OffsetVarNodes((Node *) subtlist, rtoffset, 0);
+ rtoffset = length(parse->rtable);
+ OffsetVarNodes((Node *) subquery, rtoffset, 0);
+
/*
* Replace all of the top query's references to the subquery's
* outputs with copies of the adjusted subtlist items, being
* careful not to replace any of the jointree structure.
+ * (This'd be a lot cleaner if we could use query_tree_mutator.)
*/
+ subtlist = subquery->targetList;
parse->targetList = (List *)
ResolveNew((Node *) parse->targetList,
varno, 0, subtlist, CMD_SELECT, 0);
resolvenew_in_jointree((Node *) parse->jointree, varno, subtlist);
+ Assert(parse->setOperations == NULL);
parse->havingQual =
ResolveNew(parse->havingQual,
varno, 0, subtlist, CMD_SELECT, 0);
- /*
- * Pull up any FOR UPDATE markers, too.
- */
- foreach(l, subquery->rowMarks)
+
+ foreach(rt, parse->rtable)
{
- int submark = lfirsti(l);
+ RangeTblEntry *rte = (RangeTblEntry *) lfirst(rt);
- parse->rowMarks = lappendi(parse->rowMarks,
- submark + rtoffset);
+ if (rte->rtekind == RTE_JOIN)
+ rte->joinaliasvars = (List *)
+ ResolveNew((Node *) rte->joinaliasvars,
+ varno, 0, subtlist, CMD_SELECT, 0);
}
+
+ /*
+ * Now append the adjusted rtable entries to upper query.
+ * (We hold off until after fixing the upper rtable entries;
+ * no point in running that code on the subquery ones too.)
+ */
+ parse->rtable = nconc(parse->rtable, subquery->rtable);
+
+ /*
+ * Pull up any FOR UPDATE markers, too. (OffsetVarNodes
+ * already adjusted the marker values, so just nconc the list.)
+ */
+ parse->rowMarks = nconc(parse->rowMarks, subquery->rowMarks);
+
/*
* Miscellaneous housekeeping.
*/
* Return the adjusted subquery jointree to replace the
* RangeTblRef entry in my jointree.
*/
- return subjointree;
+ return (Node *) subquery->jointree;
}
}
else if (IsA(jtnode, FromExpr))
List *l;
foreach(l, f->fromlist)
- {
- lfirst(l) = pull_up_subqueries(parse, lfirst(l));
- }
+ lfirst(l) = pull_up_subqueries(parse, lfirst(l),
+ below_outer_join);
}
else if (IsA(jtnode, JoinExpr))
{
JoinExpr *j = (JoinExpr *) jtnode;
- j->larg = pull_up_subqueries(parse, j->larg);
- j->rarg = pull_up_subqueries(parse, j->rarg);
+ /* Recurse, being careful to tell myself when inside outer join */
+ switch (j->jointype)
+ {
+ case JOIN_INNER:
+ j->larg = pull_up_subqueries(parse, j->larg,
+ below_outer_join);
+ j->rarg = pull_up_subqueries(parse, j->rarg,
+ below_outer_join);
+ break;
+ case JOIN_LEFT:
+ j->larg = pull_up_subqueries(parse, j->larg,
+ below_outer_join);
+ j->rarg = pull_up_subqueries(parse, j->rarg,
+ true);
+ break;
+ case JOIN_FULL:
+ j->larg = pull_up_subqueries(parse, j->larg,
+ true);
+ j->rarg = pull_up_subqueries(parse, j->rarg,
+ true);
+ break;
+ case JOIN_RIGHT:
+ j->larg = pull_up_subqueries(parse, j->larg,
+ true);
+ j->rarg = pull_up_subqueries(parse, j->rarg,
+ below_outer_join);
+ break;
+ case JOIN_UNION:
+
+ /*
+ * This is where we fail if upper levels of planner
+ * haven't rewritten UNION JOIN as an Append ...
+ */
+ elog(ERROR, "UNION JOIN is not implemented yet");
+ break;
+ default:
+ elog(ERROR, "pull_up_subqueries: unexpected join type %d",
+ j->jointype);
+ break;
+ }
}
else
elog(ERROR, "pull_up_subqueries: unexpected node type %d",
subquery->into != NULL ||
subquery->isPortal)
elog(ERROR, "is_simple_subquery: subquery is bogus");
+
/*
- * Can't currently pull up a query with setops.
- * Maybe after querytree redesign...
+ * Can't currently pull up a query with setops. Maybe after querytree
+ * redesign...
*/
if (subquery->setOperations)
return false;
+
/*
* Can't pull up a subquery involving grouping, aggregation, sorting,
* or limiting.
subquery->limitOffset ||
subquery->limitCount)
return false;
+
+ /*
+ * Don't pull up a subquery that has any set-returning functions in
+ * its targetlist. Otherwise we might well wind up inserting
+ * set-returning functions into places where they mustn't go,
+ * such as quals of higher queries.
+ */
+ if (expression_returns_set((Node *) subquery->targetList))
+ return false;
+
/*
* Hack: don't try to pull up a subquery with an empty jointree.
* query_planner() will correctly generate a Result plan for a
* jointree that's totally empty, but I don't think the right things
- * happen if an empty FromExpr appears lower down in a jointree.
- * Not worth working hard on this, just to collapse SubqueryScan/Result
+ * happen if an empty FromExpr appears lower down in a jointree. Not
+ * worth working hard on this, just to collapse SubqueryScan/Result
* into Result...
*/
if (subquery->jointree->fromlist == NIL)
return true;
}
+/*
+ * has_nullable_targetlist
+ * Check a subquery in the range table to see if all the non-junk
+ * targetlist items are simple variables (and, hence, will correctly
+ * go to NULL when examined above the point of an outer join).
+ *
+ * A possible future extension is to accept strict functions of simple
+ * variables, eg, "x + 1".
+ */
+static bool
+has_nullable_targetlist(Query *subquery)
+{
+ List *l;
+
+ foreach(l, subquery->targetList)
+ {
+ TargetEntry *tle = (TargetEntry *) lfirst(l);
+
+ /* ignore resjunk columns */
+ if (tle->resdom->resjunk)
+ continue;
+
+ /* Okay if tlist item is a simple Var */
+ if (tle->expr && IsA(tle->expr, Var))
+ continue;
+
+ return false;
+ }
+ return true;
+}
+
/*
* Helper routine for pull_up_subqueries: do ResolveNew on every expression
* in the jointree, without changing the jointree structure itself. Ugly,
resolvenew_in_jointree(j->rarg, varno, subtlist);
j->quals = ResolveNew(j->quals,
varno, 0, subtlist, CMD_SELECT, 0);
- /* We don't bother to update the colvars list, since it won't be
+
+ /*
+ * We don't bother to update the colvars list, since it won't be
* used again ...
*/
}
*
* If we succeed in pulling up a subquery then we might form a jointree
* in which a FromExpr is a direct child of another FromExpr. In that
- * case we can consider collapsing the two FromExprs into one. This is
+ * case we can consider collapsing the two FromExprs into one. This is
* an optional conversion, since the planner will work correctly either
* way. But we may find a better plan (at the cost of more planning time)
* if we merge the two nodes.
*
* NOTE: don't try to do this in the same jointree scan that does subquery
- * pullup! Since we're changing the jointree structure here, that wouldn't
+ * pullup! Since we're changing the jointree structure here, that wouldn't
* work reliably --- see comments for pull_up_subqueries().
*/
static Node *
foreach(l, f->fromlist)
{
- Node *child = (Node *) lfirst(l);
+ Node *child = (Node *) lfirst(l);
/* Recursively simplify the child... */
child = preprocess_jointree(parse, child);
if (child && IsA(child, FromExpr))
{
/*
- * Yes, so do we want to merge it into parent? Always do so
- * if child has just one element (since that doesn't make the
- * parent's list any longer). Otherwise we have to be careful
- * about the increase in planning time caused by combining the
- * two join search spaces into one. Our heuristic is to merge
- * if the merge will produce a join list no longer than
- * GEQO_RELS/2. (Perhaps need an additional user parameter?)
+ * Yes, so do we want to merge it into parent? Always do
+ * so if child has just one element (since that doesn't
+ * make the parent's list any longer). Otherwise we have
+ * to be careful about the increase in planning time
+ * caused by combining the two join search spaces into
+ * one. Our heuristic is to merge if the merge will
+ * produce a join list no longer than GEQO_RELS/2.
+ * (Perhaps need an additional user parameter?)
*/
FromExpr *subf = (FromExpr *) child;
int childlen = length(subf->fromlist);
int myothers = length(newlist) + length(lnext(l));
- if (childlen <= 1 || (childlen+myothers) <= geqo_rels/2)
+ if (childlen <= 1 || (childlen + myothers) <= geqo_rels / 2)
{
newlist = nconc(newlist, subf->fromlist);
f->quals = make_and_qual(f->quals, subf->quals);
static Node *
preprocess_expression(Query *parse, Node *expr, int kind)
{
+ bool has_join_rtes;
+ List *rt;
+
/*
* Simplify constant expressions.
*
expr = eval_const_expressions(expr);
/*
- * If it's a qual or havingQual, canonicalize it, and convert it
- * to implicit-AND format.
+ * If it's a qual or havingQual, canonicalize it, and convert it to
+ * implicit-AND format.
*
* XXX Is there any value in re-applying eval_const_expressions after
* canonicalize_qual?
#endif
}
+ /* Expand SubLinks to SubPlans */
if (parse->hasSubLinks)
- {
- /* Expand SubLinks to SubPlans */
expr = SS_process_sublinks(expr);
- if (kind != EXPRKIND_WHERE &&
- (parse->groupClause != NIL || parse->hasAggs))
- {
- /*
- * Check for ungrouped variables passed to subplans. Note we
- * do NOT do this for subplans in WHERE (or JOIN/ON); it's legal
- * there because WHERE is evaluated pre-GROUP.
- */
- check_subplans_for_ungrouped_vars(expr, parse);
- }
- }
-
/* Replace uplevel vars with Param nodes */
if (PlannerQueryLevel > 1)
expr = SS_replace_correlation_vars(expr);
+ /*
+ * If the query has any join RTEs, try to replace join alias variables
+ * with base-relation variables, to allow quals to be pushed down.
+ * We must do this after sublink processing, since it does not recurse
+ * into sublinks.
+ *
+ * The flattening pass is expensive enough that it seems worthwhile to
+ * scan the rangetable to see if we can avoid it.
+ */
+ has_join_rtes = false;
+ foreach(rt, parse->rtable)
+ {
+ RangeTblEntry *rte = lfirst(rt);
+
+ if (rte->rtekind == RTE_JOIN)
+ {
+ has_join_rtes = true;
+ break;
+ }
+ }
+ if (has_join_rtes)
+ expr = flatten_join_alias_vars(expr, parse, false);
+
return expr;
}
* inheritance set.
*
* We have to handle this case differently from cases where a source
- * relation is an inheritance set. Source inheritance is expanded at
+ * relation is an inheritance set. Source inheritance is expanded at
* the bottom of the plan tree (see allpaths.c), but target inheritance
* has to be expanded at the top. The reason is that for UPDATE, each
* target relation needs a different targetlist matching its own column
* set. (This is not so critical for DELETE, but for simplicity we treat
- * inherited DELETE the same way.) Fortunately, the UPDATE/DELETE target
+ * inherited DELETE the same way.) Fortunately, the UPDATE/DELETE target
* can never be the nullable side of an outer join, so it's OK to generate
* the plan this way.
*
foreach(l, inheritlist)
{
- int childRTindex = lfirsti(l);
- Oid childOID = getrelid(childRTindex, parse->rtable);
- Query *subquery;
- Plan *subplan;
+ int childRTindex = lfirsti(l);
+ Oid childOID = getrelid(childRTindex, parse->rtable);
+ Query *subquery;
+ Plan *subplan;
/* Generate modified query with this rel as target */
subquery = (Query *) adjust_inherited_attrs((Node *) parse,
- parentRTindex, parentOID,
- childRTindex, childOID);
+ parentRTindex, parentOID,
+ childRTindex, childOID);
/* Generate plan */
- subplan = grouping_planner(subquery, 0.0 /* retrieve all tuples */);
+ subplan = grouping_planner(subquery, 0.0 /* retrieve all tuples */ );
subplans = lappend(subplans, subplan);
/* Save preprocessed tlist from first rel for use in Append */
if (tlist == NIL)
tlist = postprocess_setop_tlist(result_plan->targetlist, tlist);
/*
- * Can't handle FOR UPDATE here (parser should have checked already,
- * but let's make sure).
+ * Can't handle FOR UPDATE here (parser should have checked
+ * already, but let's make sure).
*/
if (parse->rowMarks)
elog(ERROR, "SELECT FOR UPDATE is not allowed with UNION/INTERSECT/EXCEPT");
/*
* We set current_pathkeys NIL indicating we do not know sort
- * order. This is correct when the top set operation is UNION ALL,
- * since the appended-together results are unsorted even if the
- * subplans were sorted. For other set operations we could be
+ * order. This is correct when the top set operation is UNION
+ * ALL, since the appended-together results are unsorted even if
+ * the subplans were sorted. For other set operations we could be
* smarter --- room for future improvement!
*/
current_pathkeys = NIL;
/*
* Add TID targets for rels selected FOR UPDATE (should this be
- * done in preprocess_targetlist?). The executor uses the TID
- * to know which rows to lock, much as for UPDATE or DELETE.
+ * done in preprocess_targetlist?). The executor uses the TID to
+ * know which rows to lock, much as for UPDATE or DELETE.
*/
if (parse->rowMarks)
{
List *l;
/*
- * We've got trouble if the FOR UPDATE appears inside grouping,
- * since grouping renders a reference to individual tuple CTIDs
- * invalid. This is also checked at parse time, but that's
- * insufficient because of rule substitution, query pullup, etc.
+ * We've got trouble if the FOR UPDATE appears inside
+ * grouping, since grouping renders a reference to individual
+ * tuple CTIDs invalid. This is also checked at parse time,
+ * but that's insufficient because of rule substitution, query
+ * pullup, etc.
*/
CheckSelectForUpdate(parse);
- /* Currently the executor only supports FOR UPDATE at top level */
+ /*
+ * Currently the executor only supports FOR UPDATE at top
+ * level
+ */
if (PlannerQueryLevel > 1)
elog(ERROR, "SELECT FOR UPDATE is not allowed in subselects");
/*
* Figure out whether we expect to retrieve all the tuples that
- * the plan can generate, or to stop early due to a LIMIT or other
- * factors. If the caller passed a value >= 0, believe that
- * value, else do our own examination of the query context.
+ * 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)
{
tuple_fraction = 0.0;
/*
- * Check for a LIMIT clause.
+ * 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
+ * the caller, since it reflects plan actions that this routine
+ * will certainly take, not assumptions about context.
+ */
+ if (parse->limitCount != NULL)
+ {
+ /*
+ * A LIMIT clause limits the absolute number of tuples
+ * returned. However, if it's not a constant LIMIT then we
+ * have to punt; for lack of a better idea, assume 10% of the
+ * plan's result is wanted.
*/
- if (parse->limitCount != NULL)
+ double limit_fraction = 0.0;
+
+ if (IsA(parse->limitCount, Const))
{
- if (IsA(parse->limitCount, Const))
+ Const *limitc = (Const *) parse->limitCount;
+ int32 count = DatumGetInt32(limitc->constvalue);
+
+ /*
+ * A NULL-constant LIMIT represents "LIMIT ALL", which we
+ * treat the same as no limit (ie, expect to retrieve all
+ * the tuples).
+ */
+ if (!limitc->constisnull && count > 0)
{
- Const *limitc = (Const *) parse->limitCount;
- int32 count = DatumGetInt32(limitc->constvalue);
-
- /*
- * A NULL-constant LIMIT represents "LIMIT ALL",
- * which we treat the same as no limit (ie,
- * expect to retrieve all the tuples).
- */
- if (!limitc->constisnull && count > 0)
+ limit_fraction = (double) count;
+ /* We must also consider the OFFSET, if present */
+ if (parse->limitOffset != NULL)
{
- tuple_fraction = (double) count;
- /* We must also consider the OFFSET, if present */
- if (parse->limitOffset != NULL)
+ if (IsA(parse->limitOffset, Const))
+ {
+ int32 offset;
+
+ limitc = (Const *) parse->limitOffset;
+ offset = DatumGetInt32(limitc->constvalue);
+ if (!limitc->constisnull && offset > 0)
+ limit_fraction += (double) offset;
+ }
+ else
{
- if (IsA(parse->limitOffset, Const))
- {
- int32 offset;
-
- limitc = (Const *) parse->limitOffset;
- offset = DatumGetInt32(limitc->constvalue);
- if (!limitc->constisnull && offset > 0)
- tuple_fraction += (double) offset;
- }
- else
- {
- /* It's an expression ... punt ... */
- tuple_fraction = 0.10;
- }
+ /* OFFSET is an expression ... punt ... */
+ limit_fraction = 0.10;
}
}
}
+ }
+ else
+ {
+ /* LIMIT is an expression ... punt ... */
+ limit_fraction = 0.10;
+ }
+
+ if (limit_fraction > 0.0)
+ {
+ /*
+ * If we have absolute limits from both caller and LIMIT,
+ * use the smaller value; if one is fractional and the
+ * other absolute, treat the fraction as a fraction of the
+ * absolute value; else we can multiply the two fractions
+ * together.
+ */
+ if (tuple_fraction >= 1.0)
+ {
+ if (limit_fraction >= 1.0)
+ {
+ /* both absolute */
+ tuple_fraction = Min(tuple_fraction, limit_fraction);
+ }
+ else
+ {
+ /* caller absolute, limit fractional */
+ tuple_fraction *= limit_fraction;
+ if (tuple_fraction < 1.0)
+ tuple_fraction = 1.0;
+ }
+ }
+ else if (tuple_fraction > 0.0)
+ {
+ if (limit_fraction >= 1.0)
+ {
+ /* caller fractional, limit absolute */
+ tuple_fraction *= limit_fraction;
+ if (tuple_fraction < 1.0)
+ tuple_fraction = 1.0;
+ }
+ else
+ {
+ /* both fractional */
+ tuple_fraction *= limit_fraction;
+ }
+ }
else
{
- /*
- * COUNT is an expression ... don't know exactly what the
- * limit will be, but for lack of a better idea assume
- * 10% of the plan's result is wanted.
- */
- tuple_fraction = 0.10;
+ /* no info from caller, just use limit */
+ tuple_fraction = limit_fraction;
}
}
-
- /*
- * If no LIMIT, 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?)
- */
- else if (parse->isPortal)
- tuple_fraction = 0.10;
}
- /*
- * Adjust tuple_fraction if we see that we are going to apply
- * grouping/aggregation/etc. This is not overridable by the
- * caller, since it reflects plan actions that this routine will
- * certainly take, not assumptions about context.
- */
if (parse->groupClause)
{
-
/*
* In GROUP BY mode, we have the little problem that we don't
* really know how many input tuples will be needed to make a
}
else if (parse->hasAggs)
{
-
/*
* Ungrouped aggregate will certainly want all the input
* tuples.
}
else if (parse->distinctClause)
{
-
/*
* SELECT DISTINCT, like GROUP, will absorb an unpredictable
* number of input tuples per output tuple. Handle the same
/*
* If there are aggregates then the Group node should just return
- * the same set of vars as the subplan did (but we can exclude any
- * GROUP BY expressions). If there are no aggregates then the
- * Group node had better compute the final tlist.
+ * the same set of vars as the subplan did. If there are no aggs
+ * then the Group node had better compute the final tlist.
*/
if (parse->hasAggs)
- group_tlist = flatten_tlist(result_plan->targetlist);
+ group_tlist = new_unsorted_tlist(result_plan->targetlist);
else
group_tlist = tlist;
}
else
{
-
/*
* We will need to do an explicit sort by the GROUP BY clause.
* make_groupplan will do the work, but set current_pathkeys
current_pathkeys = group_pathkeys;
}
- result_plan = make_groupplan(group_tlist,
+ result_plan = make_groupplan(parse,
+ group_tlist,
tuplePerGroup,
parse->groupClause,
groupColIdx,
if (parse->sortClause)
{
if (!pathkeys_contained_in(sort_pathkeys, current_pathkeys))
- result_plan = make_sortplan(tlist, result_plan,
+ result_plan = make_sortplan(parse, tlist, result_plan,
parse->sortClause);
}
* where the a+b target will be used by the Sort/Group steps, and the
* other targets will be used for computing the final results. (In the
* above example we could theoretically suppress the a and b targets and
- * use only a+b, but it's not really worth the trouble.)
+ * pass down only c,d,a+b, but it's not really worth the trouble to
+ * eliminate simple var references from the subplan. We will avoid doing
+ * the extra computation to recompute a+b at the outer level; see
+ * replace_vars_with_subplan_refs() in setrefs.c.)
*
* 'parse' is the query being processed.
* 'tlist' is the query's target list.
* first add an explicit Sort node.
*/
static Plan *
-make_groupplan(List *group_tlist,
+make_groupplan(Query *parse,
+ List *group_tlist,
bool tuplePerGroup,
List *groupClause,
AttrNumber *grpColIdx,
if (!is_presorted)
{
-
/*
* The Sort node always just takes a copy of the subplan's tlist
* plus ordering information. (This might seem inefficient if the
{
/* OK, insert the ordering info needed by the executor. */
resdom->reskey = ++keyno;
- resdom->reskeyop = get_opcode(grpcl->sortop);
+ resdom->reskeyop = grpcl->sortop;
}
}
Assert(keyno > 0);
- subplan = (Plan *) make_sort(sort_tlist, subplan, keyno);
+ subplan = (Plan *) make_sort(parse, sort_tlist, subplan, keyno);
}
return (Plan *) make_group(group_tlist, tuplePerGroup, numCols,
* Add a Sort node to implement an explicit ORDER BY clause.
*/
Plan *
-make_sortplan(List *tlist, Plan *plannode, List *sortcls)
+make_sortplan(Query *parse, List *tlist, Plan *plannode, List *sortcls)
{
List *sort_tlist;
List *i;
{
/* OK, insert the ordering info needed by the executor. */
resdom->reskey = ++keyno;
- resdom->reskeyop = get_opcode(sortcl->sortop);
+ resdom->reskeyop = sortcl->sortop;
}
}
Assert(keyno > 0);
- return (Plan *) make_sort(sort_tlist, plannode, keyno);
+ return (Plan *) make_sort(parse, sort_tlist, plannode, keyno);
}
/*