aboutsummaryrefslogtreecommitdiff
path: root/src/backend/optimizer
diff options
context:
space:
mode:
Diffstat (limited to 'src/backend/optimizer')
-rw-r--r--src/backend/optimizer/path/costsize.c174
-rw-r--r--src/backend/optimizer/plan/createplan.c168
-rw-r--r--src/backend/optimizer/plan/setrefs.c5
-rw-r--r--src/backend/optimizer/util/clauses.c75
4 files changed, 291 insertions, 131 deletions
diff --git a/src/backend/optimizer/path/costsize.c b/src/backend/optimizer/path/costsize.c
index 22cbf8e2b92..2d241e774d0 100644
--- a/src/backend/optimizer/path/costsize.c
+++ b/src/backend/optimizer/path/costsize.c
@@ -54,7 +54,7 @@
* Portions Copyright (c) 1994, Regents of the University of California
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/optimizer/path/costsize.c,v 1.175 2007/01/20 20:45:38 tgl Exp $
+ * $PostgreSQL: pgsql/src/backend/optimizer/path/costsize.c,v 1.176 2007/01/22 01:35:20 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -68,6 +68,7 @@
#include "optimizer/clauses.h"
#include "optimizer/cost.h"
#include "optimizer/pathnode.h"
+#include "optimizer/planmain.h"
#include "parser/parsetree.h"
#include "utils/lsyscache.h"
#include "utils/selfuncs.h"
@@ -842,17 +843,19 @@ cost_functionscan(Path *path, PlannerInfo *root, RelOptInfo *baserel)
Cost startup_cost = 0;
Cost run_cost = 0;
Cost cpu_per_tuple;
+ RangeTblEntry *rte;
+ QualCost exprcost;
/* Should only be applied to base relations that are functions */
Assert(baserel->relid > 0);
- Assert(baserel->rtekind == RTE_FUNCTION);
+ rte = rt_fetch(baserel->relid, root->parse->rtable);
+ Assert(rte->rtekind == RTE_FUNCTION);
- /*
- * For now, estimate function's cost at one operator eval per function
- * call. Someday we should revive the function cost estimate columns in
- * pg_proc...
- */
- cpu_per_tuple = cpu_operator_cost;
+ /* Estimate costs of executing the function expression */
+ cost_qual_eval_node(&exprcost, rte->funcexpr);
+
+ startup_cost += exprcost.startup;
+ cpu_per_tuple = exprcost.per_tuple;
/* Add scanning CPU costs */
startup_cost += baserel->baserestrictcost.startup;
@@ -1068,6 +1071,10 @@ cost_agg(Path *path, PlannerInfo *root,
* there's roundoff error we might do the wrong thing. So be sure that
* the computations below form the same intermediate values in the same
* order.
+ *
+ * Note: ideally we should use the pg_proc.procost costs of each
+ * aggregate's component functions, but for now that seems like an
+ * excessive amount of work.
*/
if (aggstrategy == AGG_PLAIN)
{
@@ -1694,7 +1701,8 @@ cost_hashjoin(HashPath *path, PlannerInfo *root)
* cost_qual_eval
* Estimate the CPU costs of evaluating a WHERE clause.
* The input can be either an implicitly-ANDed list of boolean
- * expressions, or a list of RestrictInfo nodes.
+ * expressions, or a list of RestrictInfo nodes. (The latter is
+ * preferred since it allows caching of the results.)
* The result includes both a one-time (startup) component,
* and a per-evaluation component.
*/
@@ -1712,43 +1720,22 @@ cost_qual_eval(QualCost *cost, List *quals)
{
Node *qual = (Node *) lfirst(l);
- /*
- * RestrictInfo nodes contain an eval_cost field reserved for this
- * routine's use, so that it's not necessary to evaluate the qual
- * clause's cost more than once. If the clause's cost hasn't been
- * computed yet, the field's startup value will contain -1.
- *
- * If the RestrictInfo is marked pseudoconstant, it will be tested
- * only once, so treat its cost as all startup cost.
- */
- if (qual && IsA(qual, RestrictInfo))
- {
- RestrictInfo *rinfo = (RestrictInfo *) qual;
-
- if (rinfo->eval_cost.startup < 0)
- {
- rinfo->eval_cost.startup = 0;
- rinfo->eval_cost.per_tuple = 0;
- cost_qual_eval_walker((Node *) rinfo->clause,
- &rinfo->eval_cost);
- if (rinfo->pseudoconstant)
- {
- /* count one execution during startup */
- rinfo->eval_cost.startup += rinfo->eval_cost.per_tuple;
- rinfo->eval_cost.per_tuple = 0;
- }
- }
- cost->startup += rinfo->eval_cost.startup;
- cost->per_tuple += rinfo->eval_cost.per_tuple;
- }
- else
- {
- /* If it's a bare expression, must always do it the hard way */
- cost_qual_eval_walker(qual, cost);
- }
+ cost_qual_eval_walker(qual, cost);
}
}
+/*
+ * cost_qual_eval_node
+ * As above, for a single RestrictInfo or expression.
+ */
+void
+cost_qual_eval_node(QualCost *cost, Node *qual)
+{
+ cost->startup = 0;
+ cost->per_tuple = 0;
+ cost_qual_eval_walker(qual, cost);
+}
+
static bool
cost_qual_eval_walker(Node *node, QualCost *total)
{
@@ -1756,19 +1743,75 @@ cost_qual_eval_walker(Node *node, QualCost *total)
return false;
/*
- * Our basic strategy is to charge one cpu_operator_cost for each operator
- * or function node in the given tree. Vars and Consts are charged zero,
- * and so are boolean operators (AND, OR, NOT). Simplistic, but a lot
- * better than no model at all.
+ * RestrictInfo nodes contain an eval_cost field reserved for this
+ * routine's use, so that it's not necessary to evaluate the qual
+ * clause's cost more than once. If the clause's cost hasn't been
+ * computed yet, the field's startup value will contain -1.
+ */
+ if (IsA(node, RestrictInfo))
+ {
+ RestrictInfo *rinfo = (RestrictInfo *) node;
+
+ if (rinfo->eval_cost.startup < 0)
+ {
+ rinfo->eval_cost.startup = 0;
+ rinfo->eval_cost.per_tuple = 0;
+ /*
+ * For an OR clause, recurse into the marked-up tree so that
+ * we set the eval_cost for contained RestrictInfos too.
+ */
+ if (rinfo->orclause)
+ cost_qual_eval_walker((Node *) rinfo->orclause,
+ &rinfo->eval_cost);
+ else
+ cost_qual_eval_walker((Node *) rinfo->clause,
+ &rinfo->eval_cost);
+ /*
+ * If the RestrictInfo is marked pseudoconstant, it will be tested
+ * only once, so treat its cost as all startup cost.
+ */
+ if (rinfo->pseudoconstant)
+ {
+ /* count one execution during startup */
+ rinfo->eval_cost.startup += rinfo->eval_cost.per_tuple;
+ rinfo->eval_cost.per_tuple = 0;
+ }
+ }
+ total->startup += rinfo->eval_cost.startup;
+ total->per_tuple += rinfo->eval_cost.per_tuple;
+ /* do NOT recurse into children */
+ return false;
+ }
+
+ /*
+ * For each operator or function node in the given tree, we charge the
+ * estimated execution cost given by pg_proc.procost (remember to
+ * multiply this by cpu_operator_cost).
+ *
+ * Vars and Consts are charged zero, and so are boolean operators (AND,
+ * OR, NOT). Simplistic, but a lot better than no model at all.
*
* Should we try to account for the possibility of short-circuit
- * evaluation of AND/OR?
+ * evaluation of AND/OR? Probably *not*, because that would make the
+ * results depend on the clause ordering, and we are not in any position
+ * to expect that the current ordering of the clauses is the one that's
+ * going to end up being used. (Is it worth applying order_qual_clauses
+ * much earlier in the planning process to fix this?)
*/
- if (IsA(node, FuncExpr) ||
- IsA(node, OpExpr) ||
- IsA(node, DistinctExpr) ||
- IsA(node, NullIfExpr))
- total->per_tuple += cpu_operator_cost;
+ if (IsA(node, FuncExpr))
+ {
+ total->per_tuple += get_func_cost(((FuncExpr *) node)->funcid) *
+ cpu_operator_cost;
+ }
+ else if (IsA(node, OpExpr) ||
+ IsA(node, DistinctExpr) ||
+ IsA(node, NullIfExpr))
+ {
+ /* rely on struct equivalence to treat these all alike */
+ set_opfuncid((OpExpr *) node);
+ total->per_tuple += get_func_cost(((OpExpr *) node)->opfuncid) *
+ cpu_operator_cost;
+ }
else if (IsA(node, ScalarArrayOpExpr))
{
/*
@@ -1778,15 +1821,23 @@ cost_qual_eval_walker(Node *node, QualCost *total)
ScalarArrayOpExpr *saop = (ScalarArrayOpExpr *) node;
Node *arraynode = (Node *) lsecond(saop->args);
- total->per_tuple +=
+ set_sa_opfuncid(saop);
+ total->per_tuple += get_func_cost(saop->opfuncid) *
cpu_operator_cost * estimate_array_length(arraynode) * 0.5;
}
else if (IsA(node, RowCompareExpr))
{
/* Conservatively assume we will check all the columns */
RowCompareExpr *rcexpr = (RowCompareExpr *) node;
+ ListCell *lc;
- total->per_tuple += cpu_operator_cost * list_length(rcexpr->opnos);
+ foreach(lc, rcexpr->opnos)
+ {
+ Oid opid = lfirst_oid(lc);
+
+ total->per_tuple += get_func_cost(get_opcode(opid)) *
+ cpu_operator_cost;
+ }
}
else if (IsA(node, SubLink))
{
@@ -1873,6 +1924,7 @@ cost_qual_eval_walker(Node *node, QualCost *total)
}
}
+ /* recurse into children */
return expression_tree_walker(node, cost_qual_eval_walker,
(void *) total);
}
@@ -2172,16 +2224,8 @@ set_function_size_estimates(PlannerInfo *root, RelOptInfo *rel)
rte = rt_fetch(rel->relid, root->parse->rtable);
Assert(rte->rtekind == RTE_FUNCTION);
- /*
- * Estimate number of rows the function itself will return.
- *
- * XXX no idea how to do this yet; but we can at least check whether
- * function returns set or not...
- */
- if (expression_returns_set(rte->funcexpr))
- rel->tuples = 1000;
- else
- rel->tuples = 1;
+ /* Estimate number of rows the function itself will return */
+ rel->tuples = clamp_row_est(expression_returns_set_rows(rte->funcexpr));
/* Now estimate number of output rows, etc */
set_baserel_size_estimates(root, rel);
diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c
index 143b04ca9cb..36f22c881c8 100644
--- a/src/backend/optimizer/plan/createplan.c
+++ b/src/backend/optimizer/plan/createplan.c
@@ -10,7 +10,7 @@
*
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/optimizer/plan/createplan.c,v 1.222 2007/01/20 20:45:39 tgl Exp $
+ * $PostgreSQL: pgsql/src/backend/optimizer/plan/createplan.c,v 1.223 2007/01/22 01:35:20 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -411,14 +411,15 @@ create_gating_plan(PlannerInfo *root, Plan *plan, List *quals)
{
List *pseudoconstants;
+ /* Sort into desirable execution order while still in RestrictInfo form */
+ quals = order_qual_clauses(root, quals);
+
/* Pull out any pseudoconstant quals from the RestrictInfo list */
pseudoconstants = extract_actual_clauses(quals, true);
if (!pseudoconstants)
return plan;
- pseudoconstants = order_qual_clauses(root, pseudoconstants);
-
return (Plan *) make_result((List *) copyObject(plan->targetlist),
(Node *) pseudoconstants,
plan);
@@ -553,6 +554,8 @@ create_result_plan(PlannerInfo *root, ResultPath *best_path)
Assert(best_path->path.parent == NULL);
tlist = NIL;
+ /* best_path->quals is just bare clauses */
+
quals = order_qual_clauses(root, best_path->quals);
return make_result(tlist, (Node *) quals, NULL);
@@ -810,12 +813,12 @@ create_seqscan_plan(PlannerInfo *root, Path *best_path,
Assert(scan_relid > 0);
Assert(best_path->parent->rtekind == RTE_RELATION);
- /* Reduce RestrictInfo list to bare expressions; ignore pseudoconstants */
- scan_clauses = extract_actual_clauses(scan_clauses, false);
-
/* Sort clauses into best execution order */
scan_clauses = order_qual_clauses(root, scan_clauses);
+ /* Reduce RestrictInfo list to bare expressions; ignore pseudoconstants */
+ scan_clauses = extract_actual_clauses(scan_clauses, false);
+
scan_plan = make_seqscan(tlist,
scan_clauses,
scan_relid);
@@ -916,10 +919,6 @@ create_indexscan_plan(PlannerInfo *root,
* predicate, but only in a plain SELECT; when scanning a target relation
* of UPDATE/DELETE/SELECT FOR UPDATE, we must leave such quals in the
* plan so that they'll be properly rechecked by EvalPlanQual testing.
- *
- * While at it, we strip off the RestrictInfos to produce a list of plain
- * expressions (this loop replaces extract_actual_clauses used in the
- * other routines in this file). We have to ignore pseudoconstants.
*/
qpqual = NIL;
foreach(l, scan_clauses)
@@ -928,7 +927,7 @@ create_indexscan_plan(PlannerInfo *root,
Assert(IsA(rinfo, RestrictInfo));
if (rinfo->pseudoconstant)
- continue;
+ continue; /* we may drop pseudoconstants here */
if (list_member_ptr(nonlossy_indexquals, rinfo))
continue;
if (!contain_mutable_functions((Node *) rinfo->clause))
@@ -946,12 +945,15 @@ create_indexscan_plan(PlannerInfo *root,
continue;
}
}
- qpqual = lappend(qpqual, rinfo->clause);
+ qpqual = lappend(qpqual, rinfo);
}
/* Sort clauses into best execution order */
qpqual = order_qual_clauses(root, qpqual);
+ /* Reduce RestrictInfo list to bare expressions; ignore pseudoconstants */
+ qpqual = extract_actual_clauses(qpqual, false);
+
/* Finally ready to build the plan node */
scan_plan = make_indexscan(tlist,
qpqual,
@@ -1281,6 +1283,9 @@ create_tidscan_plan(PlannerInfo *root, TidPath *best_path,
Assert(scan_relid > 0);
Assert(best_path->path.parent->rtekind == RTE_RELATION);
+ /* Sort clauses into best execution order */
+ scan_clauses = order_qual_clauses(root, scan_clauses);
+
/* Reduce RestrictInfo list to bare expressions; ignore pseudoconstants */
scan_clauses = extract_actual_clauses(scan_clauses, false);
@@ -1293,9 +1298,6 @@ create_tidscan_plan(PlannerInfo *root, TidPath *best_path,
ortidquals = list_make1(make_orclause(ortidquals));
scan_clauses = list_difference(scan_clauses, ortidquals);
- /* Sort clauses into best execution order */
- scan_clauses = order_qual_clauses(root, scan_clauses);
-
scan_plan = make_tidscan(tlist,
scan_clauses,
scan_relid,
@@ -1322,12 +1324,12 @@ create_subqueryscan_plan(PlannerInfo *root, Path *best_path,
Assert(scan_relid > 0);
Assert(best_path->parent->rtekind == RTE_SUBQUERY);
- /* Reduce RestrictInfo list to bare expressions; ignore pseudoconstants */
- scan_clauses = extract_actual_clauses(scan_clauses, false);
-
/* Sort clauses into best execution order */
scan_clauses = order_qual_clauses(root, scan_clauses);
+ /* Reduce RestrictInfo list to bare expressions; ignore pseudoconstants */
+ scan_clauses = extract_actual_clauses(scan_clauses, false);
+
scan_plan = make_subqueryscan(tlist,
scan_clauses,
scan_relid,
@@ -1354,12 +1356,12 @@ create_functionscan_plan(PlannerInfo *root, Path *best_path,
Assert(scan_relid > 0);
Assert(best_path->parent->rtekind == RTE_FUNCTION);
- /* Reduce RestrictInfo list to bare expressions; ignore pseudoconstants */
- scan_clauses = extract_actual_clauses(scan_clauses, false);
-
/* Sort clauses into best execution order */
scan_clauses = order_qual_clauses(root, scan_clauses);
+ /* Reduce RestrictInfo list to bare expressions; ignore pseudoconstants */
+ scan_clauses = extract_actual_clauses(scan_clauses, false);
+
scan_plan = make_functionscan(tlist, scan_clauses, scan_relid);
copy_path_costsize(&scan_plan->scan.plan, best_path);
@@ -1383,12 +1385,12 @@ create_valuesscan_plan(PlannerInfo *root, Path *best_path,
Assert(scan_relid > 0);
Assert(best_path->parent->rtekind == RTE_VALUES);
- /* Reduce RestrictInfo list to bare expressions; ignore pseudoconstants */
- scan_clauses = extract_actual_clauses(scan_clauses, false);
-
/* Sort clauses into best execution order */
scan_clauses = order_qual_clauses(root, scan_clauses);
+ /* Reduce RestrictInfo list to bare expressions; ignore pseudoconstants */
+ scan_clauses = extract_actual_clauses(scan_clauses, false);
+
scan_plan = make_valuesscan(tlist, scan_clauses, scan_relid);
copy_path_costsize(&scan_plan->scan.plan, best_path);
@@ -1471,6 +1473,9 @@ create_nestloop_plan(PlannerInfo *root,
}
}
+ /* Sort join qual clauses into best execution order */
+ joinrestrictclauses = order_qual_clauses(root, joinrestrictclauses);
+
/* Get the join qual clauses (in plain expression form) */
/* Any pseudoconstant clauses are ignored here */
if (IS_OUTER_JOIN(best_path->jointype))
@@ -1485,10 +1490,6 @@ create_nestloop_plan(PlannerInfo *root,
otherclauses = NIL;
}
- /* Sort clauses into best execution order */
- joinclauses = order_qual_clauses(root, joinclauses);
- otherclauses = order_qual_clauses(root, otherclauses);
-
join_plan = make_nestloop(tlist,
joinclauses,
otherclauses,
@@ -1527,18 +1528,21 @@ create_mergejoin_plan(PlannerInfo *root,
ListCell *lop;
ListCell *lip;
+ /* Sort join qual clauses into best execution order */
+ /* NB: do NOT reorder the mergeclauses */
+ joinclauses = order_qual_clauses(root, best_path->jpath.joinrestrictinfo);
+
/* Get the join qual clauses (in plain expression form) */
/* Any pseudoconstant clauses are ignored here */
if (IS_OUTER_JOIN(best_path->jpath.jointype))
{
- extract_actual_join_clauses(best_path->jpath.joinrestrictinfo,
+ extract_actual_join_clauses(joinclauses,
&joinclauses, &otherclauses);
}
else
{
/* We can treat all clauses alike for an inner join */
- joinclauses = extract_actual_clauses(best_path->jpath.joinrestrictinfo,
- false);
+ joinclauses = extract_actual_clauses(joinclauses, false);
otherclauses = NIL;
}
@@ -1557,11 +1561,6 @@ create_mergejoin_plan(PlannerInfo *root,
mergeclauses = get_switched_clauses(best_path->path_mergeclauses,
best_path->jpath.outerjoinpath->parent->relids);
- /* Sort clauses into best execution order */
- /* NB: do NOT reorder the mergeclauses */
- joinclauses = order_qual_clauses(root, joinclauses);
- otherclauses = order_qual_clauses(root, otherclauses);
-
/*
* Create explicit sort nodes for the outer and inner join paths if
* necessary. The sort cost was already accounted for in the path. Make
@@ -1699,18 +1698,21 @@ create_hashjoin_plan(PlannerInfo *root,
HashJoin *join_plan;
Hash *hash_plan;
+ /* Sort join qual clauses into best execution order */
+ joinclauses = order_qual_clauses(root, best_path->jpath.joinrestrictinfo);
+ /* There's no point in sorting the hash clauses ... */
+
/* Get the join qual clauses (in plain expression form) */
/* Any pseudoconstant clauses are ignored here */
if (IS_OUTER_JOIN(best_path->jpath.jointype))
{
- extract_actual_join_clauses(best_path->jpath.joinrestrictinfo,
+ extract_actual_join_clauses(joinclauses,
&joinclauses, &otherclauses);
}
else
{
/* We can treat all clauses alike for an inner join */
- joinclauses = extract_actual_clauses(best_path->jpath.joinrestrictinfo,
- false);
+ joinclauses = extract_actual_clauses(joinclauses, false);
otherclauses = NIL;
}
@@ -1728,11 +1730,6 @@ create_hashjoin_plan(PlannerInfo *root,
hashclauses = get_switched_clauses(best_path->path_hashclauses,
best_path->jpath.outerjoinpath->parent->relids);
- /* Sort clauses into best execution order */
- joinclauses = order_qual_clauses(root, joinclauses);
- otherclauses = order_qual_clauses(root, otherclauses);
- hashclauses = order_qual_clauses(root, hashclauses);
-
/* We don't want any excess columns in the hashed tuples */
disuse_physical_tlist(inner_plan, best_path->jpath.innerjoinpath);
@@ -2065,35 +2062,82 @@ get_switched_clauses(List *clauses, Relids outerrelids)
* in at runtime.
*
* Ideally the order should be driven by a combination of execution cost and
- * selectivity, but unfortunately we have so little information about
- * execution cost of operators that it's really hard to do anything smart.
- * For now, we just move any quals that contain SubPlan references (but not
- * InitPlan references) to the end of the list.
+ * selectivity, but it's not immediately clear how to account for both,
+ * and given the uncertainty of the estimates the reliability of the decisions
+ * would be doubtful anyway. So we just order by estimated per-tuple cost,
+ * being careful not to change the order when (as is often the case) the
+ * estimates are identical.
+ *
+ * Although this will work on either bare clauses or RestrictInfos, it's
+ * much faster to apply it to RestrictInfos, since it can re-use cost
+ * information that is cached in RestrictInfos.
+ *
+ * Note: some callers pass lists that contain entries that will later be
+ * removed; this is the easiest way to let this routine see RestrictInfos
+ * instead of bare clauses. It's OK because we only sort by cost, but
+ * a cost/selectivity combination would likely do the wrong thing.
*/
static List *
order_qual_clauses(PlannerInfo *root, List *clauses)
{
- List *nosubplans;
- List *withsubplans;
- ListCell *l;
+ typedef struct
+ {
+ Node *clause;
+ Cost cost;
+ } QualItem;
+ int nitems = list_length(clauses);
+ QualItem *items;
+ ListCell *lc;
+ int i;
+ List *result;
- /* No need to work hard if the query is subselect-free */
- if (!root->parse->hasSubLinks)
+ /* No need to work hard for 0 or 1 clause */
+ if (nitems <= 1)
return clauses;
- nosubplans = NIL;
- withsubplans = NIL;
- foreach(l, clauses)
+ /*
+ * Collect the items and costs into an array. This is to avoid repeated
+ * cost_qual_eval work if the inputs aren't RestrictInfos.
+ */
+ items = (QualItem *) palloc(nitems * sizeof(QualItem));
+ i = 0;
+ foreach(lc, clauses)
{
- Node *clause = (Node *) lfirst(l);
+ Node *clause = (Node *) lfirst(lc);
+ QualCost qcost;
- if (contain_subplans(clause))
- withsubplans = lappend(withsubplans, clause);
- else
- nosubplans = lappend(nosubplans, clause);
+ cost_qual_eval_node(&qcost, clause);
+ items[i].clause = clause;
+ items[i].cost = qcost.per_tuple;
+ i++;
}
- return list_concat(nosubplans, withsubplans);
+ /*
+ * Sort. We don't use qsort() because it's not guaranteed stable for
+ * equal keys. The expected number of entries is small enough that
+ * a simple insertion sort should be good enough.
+ */
+ for (i = 1; i < nitems; i++)
+ {
+ QualItem newitem = items[i];
+ int j;
+
+ /* insert newitem into the already-sorted subarray */
+ for (j = i; j > 0; j--)
+ {
+ if (newitem.cost >= items[j-1].cost)
+ break;
+ items[j] = items[j-1];
+ }
+ items[j] = newitem;
+ }
+
+ /* Convert back to a list */
+ result = NIL;
+ for (i = 0; i < nitems; i++)
+ result = lappend(result, items[i].clause);
+
+ return result;
}
/*
diff --git a/src/backend/optimizer/plan/setrefs.c b/src/backend/optimizer/plan/setrefs.c
index bda3f38136d..c356d265e84 100644
--- a/src/backend/optimizer/plan/setrefs.c
+++ b/src/backend/optimizer/plan/setrefs.c
@@ -9,7 +9,7 @@
*
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/optimizer/plan/setrefs.c,v 1.127 2007/01/05 22:19:32 momjian Exp $
+ * $PostgreSQL: pgsql/src/backend/optimizer/plan/setrefs.c,v 1.128 2007/01/22 01:35:20 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -83,7 +83,6 @@ static Node *replace_vars_with_subplan_refs(Node *node,
static Node *replace_vars_with_subplan_refs_mutator(Node *node,
replace_vars_with_subplan_refs_context *context);
static bool fix_opfuncids_walker(Node *node, void *context);
-static void set_sa_opfuncid(ScalarArrayOpExpr *opexpr);
/*****************************************************************************
@@ -1433,7 +1432,7 @@ set_opfuncid(OpExpr *opexpr)
* set_sa_opfuncid
* As above, for ScalarArrayOpExpr nodes.
*/
-static void
+void
set_sa_opfuncid(ScalarArrayOpExpr *opexpr)
{
if (opexpr->opfuncid == InvalidOid)
diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c
index 52dd8c22ca7..bfeb3375701 100644
--- a/src/backend/optimizer/util/clauses.c
+++ b/src/backend/optimizer/util/clauses.c
@@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/optimizer/util/clauses.c,v 1.230 2007/01/17 17:25:52 tgl Exp $
+ * $PostgreSQL: pgsql/src/backend/optimizer/util/clauses.c,v 1.231 2007/01/22 01:35:20 tgl Exp $
*
* HISTORY
* AUTHOR DATE MAJOR EVENT
@@ -64,6 +64,7 @@ typedef struct
static bool contain_agg_clause_walker(Node *node, void *context);
static bool count_agg_clauses_walker(Node *node, AggClauseCounts *counts);
static bool expression_returns_set_walker(Node *node, void *context);
+static bool expression_returns_set_rows_walker(Node *node, double *count);
static bool contain_subplans_walker(Node *node, void *context);
static bool contain_mutable_functions_walker(Node *node, void *context);
static bool contain_volatile_functions_walker(Node *node, void *context);
@@ -566,6 +567,78 @@ expression_returns_set_walker(Node *node, void *context)
context);
}
+/*
+ * expression_returns_set_rows
+ * Estimate the number of rows in a set result.
+ *
+ * We use the product of the rowcount estimates of all the functions in
+ * the given tree. The result is 1 if there are no set-returning functions.
+ */
+double
+expression_returns_set_rows(Node *clause)
+{
+ double result = 1;
+
+ (void) expression_returns_set_rows_walker(clause, &result);
+ return result;
+}
+
+static bool
+expression_returns_set_rows_walker(Node *node, double *count)
+{
+ if (node == NULL)
+ return false;
+ if (IsA(node, FuncExpr))
+ {
+ FuncExpr *expr = (FuncExpr *) node;
+
+ if (expr->funcretset)
+ *count *= get_func_rows(expr->funcid);
+ }
+ if (IsA(node, OpExpr))
+ {
+ OpExpr *expr = (OpExpr *) node;
+
+ if (expr->opretset)
+ {
+ set_opfuncid(expr);
+ *count *= get_func_rows(expr->opfuncid);
+ }
+ }
+
+ /* Avoid recursion for some cases that can't return a set */
+ if (IsA(node, Aggref))
+ return false;
+ if (IsA(node, DistinctExpr))
+ return false;
+ if (IsA(node, ScalarArrayOpExpr))
+ return false;
+ if (IsA(node, BoolExpr))
+ return false;
+ if (IsA(node, SubLink))
+ return false;
+ if (IsA(node, SubPlan))
+ return false;
+ if (IsA(node, ArrayExpr))
+ return false;
+ if (IsA(node, RowExpr))
+ return false;
+ if (IsA(node, RowCompareExpr))
+ return false;
+ if (IsA(node, CoalesceExpr))
+ return false;
+ if (IsA(node, MinMaxExpr))
+ return false;
+ if (IsA(node, XmlExpr))
+ return false;
+ if (IsA(node, NullIfExpr))
+ return false;
+
+ return expression_tree_walker(node, expression_returns_set_rows_walker,
+ (void *) count);
+}
+
+
/*****************************************************************************
* Subplan clause manipulation
*****************************************************************************/