* planner.c
* The query optimizer external interface.
*
- * Portions Copyright (c) 1996-2006, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1996-2007, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/optimizer/plan/planner.c,v 1.205 2006/07/26 19:31:50 tgl Exp $
+ * $PostgreSQL: pgsql/src/backend/optimizer/plan/planner.c,v 1.211 2007/01/10 18:06:03 tgl Exp $
*
*-------------------------------------------------------------------------
*/
#include "parser/parse_expr.h"
#include "parser/parse_oper.h"
#include "parser/parsetree.h"
+#include "utils/lsyscache.h"
#include "utils/syscache.h"
#define EXPRKIND_QUAL 0
#define EXPRKIND_TARGET 1
#define EXPRKIND_RTFUNC 2
-#define EXPRKIND_LIMIT 3
-#define EXPRKIND_ININFO 4
-#define EXPRKIND_APPINFO 5
+#define EXPRKIND_VALUES 3
+#define EXPRKIND_LIMIT 4
+#define EXPRKIND_ININFO 5
+#define EXPRKIND_APPINFO 6
static Node *preprocess_expression(PlannerInfo *root, Node *expr, int kind);
static void preprocess_qual_conditions(PlannerInfo *root, Node *jtnode);
static Plan *inheritance_planner(PlannerInfo *root);
static Plan *grouping_planner(PlannerInfo *root, double tuple_fraction);
+static bool is_dummy_plan(Plan *plan);
static double preprocess_limit(PlannerInfo *root,
double tuple_fraction,
int64 *offset_est, int64 *count_est);
+static Oid *extract_grouping_ops(List *groupClause);
static bool choose_hashed_grouping(PlannerInfo *root, double tuple_fraction,
Path *cheapest_path, Path *sorted_path,
- double dNumGroups, AggClauseCounts *agg_counts);
-static bool hash_safe_grouping(PlannerInfo *root);
+ Oid *groupOperators, double dNumGroups,
+ AggClauseCounts *agg_counts);
static List *make_subplanTargetList(PlannerInfo *root, List *tlist,
AttrNumber **groupColIdx, bool *need_tlist_eval);
static void locate_grouping_columns(PlannerInfo *root,
preprocess_expression(root, (Node *) parse->targetList,
EXPRKIND_TARGET);
+ parse->returningList = (List *)
+ preprocess_expression(root, (Node *) parse->returningList,
+ EXPRKIND_TARGET);
+
preprocess_qual_conditions(root, (Node *) parse->jointree);
parse->havingQual = preprocess_expression(root, parse->havingQual,
preprocess_expression(root, (Node *) root->append_rel_list,
EXPRKIND_APPINFO);
- /* Also need to preprocess expressions for function RTEs */
+ /* Also need to preprocess expressions for function and values RTEs */
foreach(l, parse->rtable)
{
RangeTblEntry *rte = (RangeTblEntry *) lfirst(l);
if (rte->rtekind == RTE_FUNCTION)
rte->funcexpr = preprocess_expression(root, rte->funcexpr,
EXPRKIND_RTFUNC);
+ else if (rte->rtekind == RTE_VALUES)
+ rte->values_lists = (List *)
+ preprocess_expression(root, (Node *) rte->values_lists,
+ EXPRKIND_VALUES);
}
/*
/*
* If the query has any join RTEs, replace join alias variables with
* base-relation variables. We must do this before sublink processing,
- * else sublinks expanded out from join aliases wouldn't get processed.
+ * else sublinks expanded out from join aliases wouldn't get processed. We
+ * can skip it in VALUES lists, however, since they can't contain any Vars
+ * at all.
*/
- if (root->hasJoinRTEs)
+ if (root->hasJoinRTEs && kind != EXPRKIND_VALUES)
expr = flatten_join_alias_vars(root, expr);
/*
* and we will waste cycles copying the tree. Notice however that we
* still must do it for quals (to get AND/OR flatness); and if we are in a
* subquery we should not assume it will be done only once.
+ *
+ * For VALUES lists we never do this at all, again on the grounds that we
+ * should optimize for one-time evaluation.
*/
- if (root->parse->jointree->fromlist != NIL ||
- kind == EXPRKIND_QUAL ||
- PlannerQueryLevel > 1)
+ if (kind != EXPRKIND_VALUES &&
+ (root->parse->jointree->fromlist != NIL ||
+ kind == EXPRKIND_QUAL ||
+ PlannerQueryLevel > 1))
expr = eval_const_expressions(expr);
/*
* SS_replace_correlation_vars ...
*/
- /* Replace uplevel vars with Param nodes */
+ /* Replace uplevel vars with Param nodes (this IS possible in VALUES) */
if (PlannerQueryLevel > 1)
expr = SS_replace_correlation_vars(expr);
Query *parse = root->parse;
int parentRTindex = parse->resultRelation;
List *subplans = NIL;
+ List *resultRelations = NIL;
+ List *returningLists = NIL;
+ List *rtable = NIL;
List *tlist = NIL;
PlannerInfo subroot;
ListCell *l;
- subroot.parse = NULL; /* catch it if no matches in loop */
-
- parse->resultRelations = NIL;
-
foreach(l, root->append_rel_list)
{
AppendRelInfo *appinfo = (AppendRelInfo *) lfirst(l);
if (appinfo->parent_relid != parentRTindex)
continue;
- /* Build target-relations list for the executor */
- parse->resultRelations = lappend_int(parse->resultRelations,
- appinfo->child_relid);
-
/*
* Generate modified query with this rel as target. We have to be
* prepared to translate varnos in in_info_list as well as in the
/* Generate plan */
subplan = grouping_planner(&subroot, 0.0 /* retrieve all tuples */ );
- subplans = lappend(subplans, subplan);
+ /*
+ * If this child rel was excluded by constraint exclusion, exclude it
+ * from the plan.
+ */
+ if (is_dummy_plan(subplan))
+ continue;
- /* Save preprocessed tlist from first rel for use in Append */
- if (tlist == NIL)
+ /* Save rtable and tlist from first rel for use below */
+ if (subplans == NIL)
+ {
+ rtable = subroot.parse->rtable;
tlist = subplan->targetlist;
+ }
+
+ subplans = lappend(subplans, subplan);
+
+ /* Build target-relations list for the executor */
+ resultRelations = lappend_int(resultRelations, appinfo->child_relid);
+
+ /* Build list of per-relation RETURNING targetlists */
+ if (parse->returningList)
+ {
+ Assert(list_length(subroot.parse->returningLists) == 1);
+ returningLists = list_concat(returningLists,
+ subroot.parse->returningLists);
+ }
}
+ parse->resultRelations = resultRelations;
+ parse->returningLists = returningLists;
+
+ /* Mark result as unordered (probably unnecessary) */
+ root->query_pathkeys = NIL;
+
+ /*
+ * If we managed to exclude every child rel, return a dummy plan
+ */
+ if (subplans == NIL)
+ return (Plan *) make_result(tlist,
+ (Node *) list_make1(makeBoolConst(false,
+ false)),
+ NULL);
+
/*
* Planning might have modified the rangetable, due to changes of the
* Query structures inside subquery RTEs. We have to ensure that this
*
* XXX should clean this up someday
*/
- parse->rtable = subroot.parse->rtable;
-
- /* Mark result as unordered (probably unnecessary) */
- root->query_pathkeys = NIL;
+ parse->rtable = rtable;
return (Plan *) make_append(subplans, true, tlist);
}
List *sub_tlist;
List *group_pathkeys;
AttrNumber *groupColIdx = NULL;
+ Oid *groupOperators = NULL;
bool need_tlist_eval = true;
QualCost tlist_cost;
Path *cheapest_path;
sort_pathkeys = root->sort_pathkeys;
/*
- * If grouping, decide whether we want to use hashed grouping.
+ * If grouping, extract the grouping operators and decide whether we
+ * want to use hashed grouping.
*/
if (parse->groupClause)
{
+ groupOperators = extract_grouping_ops(parse->groupClause);
use_hashed_grouping =
choose_hashed_grouping(root, tuple_fraction,
cheapest_path, sorted_path,
- dNumGroups, &agg_counts);
+ groupOperators, dNumGroups,
+ &agg_counts);
/* Also convert # groups to long int --- but 'ware overflow! */
numGroups = (long) Min(dNumGroups, (double) LONG_MAX);
AGG_HASHED,
numGroupCols,
groupColIdx,
+ groupOperators,
numGroups,
agg_counts.numAggs,
result_plan);
aggstrategy,
numGroupCols,
groupColIdx,
+ groupOperators,
numGroups,
agg_counts.numAggs,
result_plan);
(List *) parse->havingQual,
numGroupCols,
groupColIdx,
+ groupOperators,
dNumGroups,
result_plan);
/* The Group node won't change sort ordering */
count_est);
}
+ /*
+ * Deal with the RETURNING clause if any. It's convenient to pass the
+ * returningList through setrefs.c now rather than at top level (if we
+ * waited, handling inherited UPDATE/DELETE would be much harder).
+ */
+ if (parse->returningList)
+ {
+ List *rlist;
+
+ rlist = set_returning_clause_references(parse->returningList,
+ result_plan,
+ parse->resultRelation);
+ parse->returningLists = list_make1(rlist);
+ }
+
/*
* Return the actual output ordering in query_pathkeys for possible use by
* an outer query level.
return result_plan;
}
+/*
+ * Detect whether a plan node is a "dummy" plan created when a relation
+ * is deemed not to need scanning due to constraint exclusion.
+ *
+ * Currently, such dummy plans are Result nodes with constant FALSE
+ * filter quals.
+ */
+static bool
+is_dummy_plan(Plan *plan)
+{
+ if (IsA(plan, Result))
+ {
+ List *rcqual = (List *) ((Result *) plan)->resconstantqual;
+
+ if (list_length(rcqual) == 1)
+ {
+ Const *constqual = (Const *) linitial(rcqual);
+
+ if (constqual && IsA(constqual, Const))
+ {
+ if (!constqual->constisnull &&
+ !DatumGetBool(constqual->constvalue))
+ return true;
+ }
+ }
+ }
+ return false;
+}
+
/*
* preprocess_limit - do pre-estimation for LIMIT and/or OFFSET clauses
*
return tuple_fraction;
}
+/*
+ * extract_grouping_ops - make an array of the equality operator OIDs
+ * for the GROUP BY clause
+ */
+static Oid *
+extract_grouping_ops(List *groupClause)
+{
+ int numCols = list_length(groupClause);
+ int colno = 0;
+ Oid *groupOperators;
+ ListCell *glitem;
+
+ groupOperators = (Oid *) palloc(sizeof(Oid) * numCols);
+
+ foreach(glitem, groupClause)
+ {
+ GroupClause *groupcl = (GroupClause *) lfirst(glitem);
+
+ groupOperators[colno] = get_equality_op_for_ordering_op(groupcl->sortop);
+ if (!OidIsValid(groupOperators[colno])) /* shouldn't happen */
+ elog(ERROR, "could not find equality operator for ordering operator %u",
+ groupcl->sortop);
+ colno++;
+ }
+
+ return groupOperators;
+}
+
/*
* choose_hashed_grouping - should we use hashed grouping?
*/
static bool
choose_hashed_grouping(PlannerInfo *root, double tuple_fraction,
Path *cheapest_path, Path *sorted_path,
- double dNumGroups, AggClauseCounts *agg_counts)
+ Oid *groupOperators, double dNumGroups,
+ AggClauseCounts *agg_counts)
{
int numGroupCols = list_length(root->parse->groupClause);
double cheapest_path_rows;
List *current_pathkeys;
Path hashed_p;
Path sorted_p;
+ int i;
/*
* Check can't-do-it conditions, including whether the grouping operators
- * are hashjoinable.
+ * are hashjoinable. (We assume hashing is OK if they are marked
+ * oprcanhash. If there isn't actually a supporting hash function,
+ * the executor will complain at runtime.)
*
* Executor doesn't support hashed aggregation with DISTINCT aggregates.
* (Doing so would imply storing *all* the input values in the hash table,
return false;
if (agg_counts->numDistinctAggs != 0)
return false;
- if (!hash_safe_grouping(root))
- return false;
+ for (i = 0; i < numGroupCols; i++)
+ {
+ if (!op_hashjoinable(groupOperators[i]))
+ return false;
+ }
/*
* Don't do it if it doesn't look like the hashtable will fit into
return false;
}
-/*
- * hash_safe_grouping - are grouping operators hashable?
- *
- * We assume hashed aggregation will work if the datatype's equality operator
- * is marked hashjoinable.
- */
-static bool
-hash_safe_grouping(PlannerInfo *root)
-{
- ListCell *gl;
-
- foreach(gl, root->parse->groupClause)
- {
- GroupClause *grpcl = (GroupClause *) lfirst(gl);
- TargetEntry *tle = get_sortgroupclause_tle(grpcl,
- root->parse->targetList);
- Operator optup;
- bool oprcanhash;
-
- optup = equality_oper(exprType((Node *) tle->expr), true);
- if (!optup)
- return false;
- oprcanhash = ((Form_pg_operator) GETSTRUCT(optup))->oprcanhash;
- ReleaseSysCache(optup);
- if (!oprcanhash)
- return false;
- }
- return true;
-}
-
/*---------------
* make_subplanTargetList
* Generate appropriate target list when grouping is required.