diff options
Diffstat (limited to 'src/backend/optimizer')
-rw-r--r-- | src/backend/optimizer/path/costsize.c | 174 | ||||
-rw-r--r-- | src/backend/optimizer/plan/createplan.c | 168 | ||||
-rw-r--r-- | src/backend/optimizer/plan/setrefs.c | 5 | ||||
-rw-r--r-- | src/backend/optimizer/util/clauses.c | 75 |
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 *****************************************************************************/ |