1
0
mirror of https://github.com/postgres/postgres.git synced 2025-08-24 09:27:52 +03:00

Add COST and ROWS options to CREATE/ALTER FUNCTION, plus underlying pg_proc

columns procost and prorows, to allow simple user adjustment of the estimated
cost of a function call, as well as control of the estimated number of rows
returned by a set-returning function.  We might eventually wish to extend this
to allow function-specific estimation routines, but there seems to be
consensus that we should try a simple constant estimate first.  In particular
this provides a relatively simple way to control the order in which different
WHERE clauses are applied in a plan node, which is a Good Thing in view of the
fact that the recent EquivalenceClass planner rewrite made that much less
predictable than before.
This commit is contained in:
Tom Lane
2007-01-22 01:35:23 +00:00
parent a85e9c61e5
commit 5a7471c307
27 changed files with 2550 additions and 2090 deletions

View File

@@ -8,7 +8,7 @@
* Portions Copyright (c) 1994, Regents of the University of California
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/bootstrap/bootstrap.c,v 1.228 2007/01/05 22:19:24 momjian Exp $
* $PostgreSQL: pgsql/src/backend/bootstrap/bootstrap.c,v 1.229 2007/01/22 01:35:19 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -115,16 +115,18 @@ static const struct typinfo TypInfo[] = {
F_BYTEAIN, F_BYTEAOUT},
{"char", CHAROID, 0, 1, true, 'c', 'p',
F_CHARIN, F_CHAROUT},
{"name", NAMEOID, CHAROID, NAMEDATALEN, false, 'i', 'p',
F_NAMEIN, F_NAMEOUT},
{"int2", INT2OID, 0, 2, true, 's', 'p',
F_INT2IN, F_INT2OUT},
{"int4", INT4OID, 0, 4, true, 'i', 'p',
F_INT4IN, F_INT4OUT},
{"regproc", REGPROCOID, 0, 4, true, 'i', 'p',
F_REGPROCIN, F_REGPROCOUT},
{"float4", FLOAT4OID, 0, 4, false, 'i', 'p',
F_FLOAT4IN, F_FLOAT4OUT},
{"name", NAMEOID, CHAROID, NAMEDATALEN, false, 'i', 'p',
F_NAMEIN, F_NAMEOUT},
{"regclass", REGCLASSOID, 0, 4, true, 'i', 'p',
F_REGCLASSIN, F_REGCLASSOUT},
{"regproc", REGPROCOID, 0, 4, true, 'i', 'p',
F_REGPROCIN, F_REGPROCOUT},
{"regtype", REGTYPEOID, 0, 4, true, 'i', 'p',
F_REGTYPEIN, F_REGTYPEOUT},
{"text", TEXTOID, 0, -1, false, 'i', 'x',

View File

@@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/catalog/pg_aggregate.c,v 1.84 2007/01/05 22:19:25 momjian Exp $
* $PostgreSQL: pgsql/src/backend/catalog/pg_aggregate.c,v 1.85 2007/01/22 01:35:20 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -214,7 +214,9 @@ AggregateCreate(const char *aggName,
numArgs), /* paramTypes */
PointerGetDatum(NULL), /* allParamTypes */
PointerGetDatum(NULL), /* parameterModes */
PointerGetDatum(NULL)); /* parameterNames */
PointerGetDatum(NULL), /* parameterNames */
1, /* procost */
0); /* prorows */
/*
* Okay to create the pg_aggregate entry.

View File

@@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/catalog/pg_proc.c,v 1.142 2007/01/05 22:19:25 momjian Exp $
* $PostgreSQL: pgsql/src/backend/catalog/pg_proc.c,v 1.143 2007/01/22 01:35:20 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -71,7 +71,9 @@ ProcedureCreate(const char *procedureName,
oidvector *parameterTypes,
Datum allParameterTypes,
Datum parameterModes,
Datum parameterNames)
Datum parameterNames,
float4 procost,
float4 prorows)
{
Oid retval;
int parameterCount;
@@ -219,6 +221,8 @@ ProcedureCreate(const char *procedureName,
values[Anum_pg_proc_pronamespace - 1] = ObjectIdGetDatum(procNamespace);
values[Anum_pg_proc_proowner - 1] = ObjectIdGetDatum(GetUserId());
values[Anum_pg_proc_prolang - 1] = ObjectIdGetDatum(languageObjectId);
values[Anum_pg_proc_procost - 1] = Float4GetDatum(procost);
values[Anum_pg_proc_prorows - 1] = Float4GetDatum(prorows);
values[Anum_pg_proc_proisagg - 1] = BoolGetDatum(isAgg);
values[Anum_pg_proc_prosecdef - 1] = BoolGetDatum(security_definer);
values[Anum_pg_proc_proisstrict - 1] = BoolGetDatum(isStrict);

View File

@@ -10,7 +10,7 @@
*
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/commands/functioncmds.c,v 1.81 2007/01/05 22:19:26 momjian Exp $
* $PostgreSQL: pgsql/src/backend/commands/functioncmds.c,v 1.82 2007/01/22 01:35:20 tgl Exp $
*
* DESCRIPTION
* These routines take the parse tree and pick out the
@@ -273,7 +273,9 @@ static bool
compute_common_attribute(DefElem *defel,
DefElem **volatility_item,
DefElem **strict_item,
DefElem **security_item)
DefElem **security_item,
DefElem **cost_item,
DefElem **rows_item)
{
if (strcmp(defel->defname, "volatility") == 0)
{
@@ -296,6 +298,20 @@ compute_common_attribute(DefElem *defel,
*security_item = defel;
}
else if (strcmp(defel->defname, "cost") == 0)
{
if (*cost_item)
goto duplicate_error;
*cost_item = defel;
}
else if (strcmp(defel->defname, "rows") == 0)
{
if (*rows_item)
goto duplicate_error;
*rows_item = defel;
}
else
return false;
@@ -337,7 +353,9 @@ compute_attributes_sql_style(List *options,
char **language,
char *volatility_p,
bool *strict_p,
bool *security_definer)
bool *security_definer,
float4 *procost,
float4 *prorows)
{
ListCell *option;
DefElem *as_item = NULL;
@@ -345,6 +363,8 @@ compute_attributes_sql_style(List *options,
DefElem *volatility_item = NULL;
DefElem *strict_item = NULL;
DefElem *security_item = NULL;
DefElem *cost_item = NULL;
DefElem *rows_item = NULL;
foreach(option, options)
{
@@ -369,7 +389,9 @@ compute_attributes_sql_style(List *options,
else if (compute_common_attribute(defel,
&volatility_item,
&strict_item,
&security_item))
&security_item,
&cost_item,
&rows_item))
{
/* recognized common option */
continue;
@@ -407,6 +429,22 @@ compute_attributes_sql_style(List *options,
*strict_p = intVal(strict_item->arg);
if (security_item)
*security_definer = intVal(security_item->arg);
if (cost_item)
{
*procost = defGetNumeric(cost_item);
if (*procost <= 0)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("COST must be positive")));
}
if (rows_item)
{
*prorows = defGetNumeric(rows_item);
if (*prorows <= 0)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("ROWS must be positive")));
}
}
@@ -519,6 +557,8 @@ CreateFunction(CreateFunctionStmt *stmt)
bool isStrict,
security;
char volatility;
float4 procost;
float4 prorows;
HeapTuple languageTuple;
Form_pg_language languageStruct;
List *as_clause;
@@ -537,10 +577,14 @@ CreateFunction(CreateFunctionStmt *stmt)
isStrict = false;
security = false;
volatility = PROVOLATILE_VOLATILE;
procost = -1; /* indicates not set */
prorows = -1; /* indicates not set */
/* override attributes from explicit list */
compute_attributes_sql_style(stmt->options,
&as_clause, &language, &volatility, &isStrict, &security);
&as_clause, &language,
&volatility, &isStrict, &security,
&procost, &prorows);
/* Convert language name to canonical case */
languageName = case_translate_language_name(language);
@@ -645,6 +689,32 @@ CreateFunction(CreateFunctionStmt *stmt)
prosrc_str = funcname;
}
/*
* Set default values for COST and ROWS depending on other parameters;
* reject ROWS if it's not returnsSet. NB: pg_dump knows these default
* values, keep it in sync if you change them.
*/
if (procost < 0)
{
/* SQL and PL-language functions are assumed more expensive */
if (languageOid == INTERNALlanguageId ||
languageOid == ClanguageId)
procost = 1;
else
procost = 100;
}
if (prorows < 0)
{
if (returnsSet)
prorows = 1000;
else
prorows = 0; /* dummy value if not returnsSet */
}
else if (!returnsSet)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("ROWS is not applicable when function does not return a set")));
/*
* And now that we have all the parameters, and know we're permitted to do
* so, go ahead and create the function.
@@ -665,7 +735,9 @@ CreateFunction(CreateFunctionStmt *stmt)
parameterTypes,
PointerGetDatum(allParameterTypes),
PointerGetDatum(parameterModes),
PointerGetDatum(parameterNames));
PointerGetDatum(parameterNames),
procost,
prorows);
}
@@ -1012,6 +1084,8 @@ AlterFunction(AlterFunctionStmt *stmt)
DefElem *volatility_item = NULL;
DefElem *strict_item = NULL;
DefElem *security_def_item = NULL;
DefElem *cost_item = NULL;
DefElem *rows_item = NULL;
rel = heap_open(ProcedureRelationId, RowExclusiveLock);
@@ -1046,7 +1120,9 @@ AlterFunction(AlterFunctionStmt *stmt)
if (compute_common_attribute(defel,
&volatility_item,
&strict_item,
&security_def_item) == false)
&security_def_item,
&cost_item,
&rows_item) == false)
elog(ERROR, "option \"%s\" not recognized", defel->defname);
}
@@ -1056,6 +1132,26 @@ AlterFunction(AlterFunctionStmt *stmt)
procForm->proisstrict = intVal(strict_item->arg);
if (security_def_item)
procForm->prosecdef = intVal(security_def_item->arg);
if (cost_item)
{
procForm->procost = defGetNumeric(cost_item);
if (procForm->procost <= 0)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("COST must be positive")));
}
if (rows_item)
{
procForm->prorows = defGetNumeric(rows_item);
if (procForm->prorows <= 0)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("ROWS must be positive")));
if (!procForm->proretset)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("ROWS is not applicable when function does not return a set")));
}
/* Do the update */
simple_heap_update(rel, &tup->t_self, tup);

View File

@@ -7,7 +7,7 @@
* Portions Copyright (c) 1994, Regents of the University of California
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/commands/proclang.c,v 1.70 2007/01/05 22:19:26 momjian Exp $
* $PostgreSQL: pgsql/src/backend/commands/proclang.c,v 1.71 2007/01/22 01:35:20 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -130,7 +130,9 @@ CreateProceduralLanguage(CreatePLangStmt *stmt)
buildoidvector(funcargtypes, 0),
PointerGetDatum(NULL),
PointerGetDatum(NULL),
PointerGetDatum(NULL));
PointerGetDatum(NULL),
1,
0);
}
/*
@@ -160,7 +162,9 @@ CreateProceduralLanguage(CreatePLangStmt *stmt)
buildoidvector(funcargtypes, 1),
PointerGetDatum(NULL),
PointerGetDatum(NULL),
PointerGetDatum(NULL));
PointerGetDatum(NULL),
1,
0);
}
}
else

View File

@@ -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);

View File

@@ -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;
}
/*

View File

@@ -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)

View File

@@ -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
*****************************************************************************/

View File

@@ -11,7 +11,7 @@
*
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/parser/gram.y,v 2.574 2007/01/14 13:11:53 petere Exp $
* $PostgreSQL: pgsql/src/backend/parser/gram.y,v 2.575 2007/01/22 01:35:21 tgl Exp $
*
* HISTORY
* AUTHOR DATE MAJOR EVENT
@@ -370,7 +370,7 @@ static Node *makeXmlExpr(XmlExprOp op, char *name, List *named_args, List *args)
CHARACTER CHARACTERISTICS CHECK CHECKPOINT CLASS CLOSE
CLUSTER COALESCE COLLATE COLUMN COMMENT COMMIT
COMMITTED CONCURRENTLY CONNECTION CONSTRAINT CONSTRAINTS
CONTENT_P CONVERSION_P CONVERT COPY CREATE CREATEDB
CONTENT_P CONVERSION_P CONVERT COPY COST CREATE CREATEDB
CREATEROLE CREATEUSER CROSS CSV CURRENT_DATE CURRENT_ROLE CURRENT_TIME
CURRENT_TIMESTAMP CURRENT_USER CURSOR CYCLE
@@ -3958,6 +3958,14 @@ common_func_opt_item:
{
$$ = makeDefElem("security", (Node *)makeInteger(FALSE));
}
| COST NumericOnly
{
$$ = makeDefElem("cost", (Node *)$2);
}
| ROWS NumericOnly
{
$$ = makeDefElem("rows", (Node *)$2);
}
;
createfunc_opt_item:
@@ -8564,6 +8572,7 @@ unreserved_keyword:
| CONTENT_P
| CONVERSION_P
| COPY
| COST
| CREATEDB
| CREATEROLE
| CREATEUSER

View File

@@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/parser/keywords.c,v 1.181 2007/01/09 02:14:14 tgl Exp $
* $PostgreSQL: pgsql/src/backend/parser/keywords.c,v 1.182 2007/01/22 01:35:21 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -93,6 +93,7 @@ static const ScanKeyword ScanKeywords[] = {
{"conversion", CONVERSION_P},
{"convert", CONVERT},
{"copy", COPY},
{"cost", COST},
{"create", CREATE},
{"createdb", CREATEDB},
{"createrole", CREATEROLE},

View File

@@ -9,7 +9,7 @@
#
#
# IDENTIFICATION
# $PostgreSQL: pgsql/src/backend/utils/Gen_fmgrtab.sh,v 1.34 2007/01/05 22:19:39 momjian Exp $
# $PostgreSQL: pgsql/src/backend/utils/Gen_fmgrtab.sh,v 1.35 2007/01/22 01:35:21 tgl Exp $
#
#-------------------------------------------------------------------------
@@ -72,6 +72,8 @@ trap 'echo "Caught signal." ; cleanup ; exit 1' 1 2 15
#
# Generate the file containing raw pg_proc tuple data
# (but only for "internal" language procedures...).
# Basically we strip off the DATA macro call, leaving procedure OID as $1
# and all the pg_proc field values as $2, $3, etc on each line.
#
# Note assumption here that prolang == $5 and INTERNALlanguageId == 12.
#
@@ -202,15 +204,15 @@ FuNkYfMgRtAbStUfF
# may seem tedious, but avoid the temptation to write a quick x?y:z
# conditional expression instead. Not all awks have conditional expressions.
#
# Note assumptions here that prosrc == $(NF-2), pronargs == $11,
# proisstrict == $8, proretset == $9
# Note assumptions here that prosrc == $(NF-2), pronargs == $13,
# proisstrict == $10, proretset == $11
$AWK 'BEGIN {
Bool["t"] = "true"
Bool["f"] = "false"
}
{ printf (" { %d, \"%s\", %d, %s, %s, %s },\n"), \
$1, $(NF-2), $11, Bool[$8], Bool[$9], $(NF-2)
$1, $(NF-2), $13, Bool[$10], Bool[$11], $(NF-2)
}' $SORTEDFILE >> "$$-$TABLEFILE"
if [ $? -ne 0 ]; then

View File

@@ -7,7 +7,7 @@
* Portions Copyright (c) 1994, Regents of the University of California
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/utils/cache/lsyscache.c,v 1.145 2007/01/21 00:57:15 tgl Exp $
* $PostgreSQL: pgsql/src/backend/utils/cache/lsyscache.c,v 1.146 2007/01/22 01:35:21 tgl Exp $
*
* NOTES
* Eventually, the index information should go through here, too.
@@ -1295,6 +1295,48 @@ func_volatile(Oid funcid)
return result;
}
/*
* get_func_cost
* Given procedure id, return the function's procost field.
*/
float4
get_func_cost(Oid funcid)
{
HeapTuple tp;
float4 result;
tp = SearchSysCache(PROCOID,
ObjectIdGetDatum(funcid),
0, 0, 0);
if (!HeapTupleIsValid(tp))
elog(ERROR, "cache lookup failed for function %u", funcid);
result = ((Form_pg_proc) GETSTRUCT(tp))->procost;
ReleaseSysCache(tp);
return result;
}
/*
* get_func_rows
* Given procedure id, return the function's prorows field.
*/
float4
get_func_rows(Oid funcid)
{
HeapTuple tp;
float4 result;
tp = SearchSysCache(PROCOID,
ObjectIdGetDatum(funcid),
0, 0, 0);
if (!HeapTupleIsValid(tp))
elog(ERROR, "cache lookup failed for function %u", funcid);
result = ((Form_pg_proc) GETSTRUCT(tp))->prorows;
ReleaseSysCache(tp);
return result;
}
/* ---------- RELATION CACHE ---------- */
/*