mirror of
https://github.com/postgres/postgres.git
synced 2025-07-07 00:36:50 +03:00
Build out the planner support function infrastructure.
Add support function requests for estimating the selectivity, cost, and number of result rows (if a SRF) of the target function. The lack of a way to estimate selectivity of a boolean-returning function in WHERE has been a recognized deficiency of the planner since Berkeley days. This commit finally fixes it. In addition, non-constant estimates of cost and number of output rows are now possible. We still fall back to looking at procost and prorows if the support function doesn't service the request, of course. To make concrete use of the possibility of estimating output rowcount for SRFs, this commit adds support functions for array_unnest(anyarray) and the integer variants of generate_series; the lack of plausible rowcount estimates for those, even when it's obvious to a human, has been a repeated subject of complaints. Obviously, much more could now be done in this line, but I'm mostly just trying to get the infrastructure in place. Discussion: https://postgr.es/m/15193.1548028093@sss.pgh.pa.us
This commit is contained in:
@ -762,6 +762,21 @@ clause_selectivity(PlannerInfo *root,
|
||||
if (IsA(clause, DistinctExpr))
|
||||
s1 = 1.0 - s1;
|
||||
}
|
||||
else if (is_funcclause(clause))
|
||||
{
|
||||
FuncExpr *funcclause = (FuncExpr *) clause;
|
||||
|
||||
/* Try to get an estimate from the support function, if any */
|
||||
s1 = function_selectivity(root,
|
||||
funcclause->funcid,
|
||||
funcclause->args,
|
||||
funcclause->inputcollid,
|
||||
treat_as_join_clause(clause, rinfo,
|
||||
varRelid, sjinfo),
|
||||
varRelid,
|
||||
jointype,
|
||||
sjinfo);
|
||||
}
|
||||
else if (IsA(clause, ScalarArrayOpExpr))
|
||||
{
|
||||
/* Use node specific selectivity calculation function */
|
||||
|
@ -2112,9 +2112,9 @@ cost_agg(Path *path, PlannerInfo *root,
|
||||
/*
|
||||
* The transCost.per_tuple component of aggcosts should be charged once
|
||||
* per input tuple, corresponding to the costs of evaluating the aggregate
|
||||
* transfns and their input expressions (with any startup cost of course
|
||||
* charged but once). The finalCost component is charged once per output
|
||||
* tuple, corresponding to the costs of evaluating the finalfns.
|
||||
* transfns and their input expressions. The finalCost.per_tuple component
|
||||
* is charged once per output tuple, corresponding to the costs of
|
||||
* evaluating the finalfns. Startup costs are of course charged but once.
|
||||
*
|
||||
* If we are grouping, we charge an additional cpu_operator_cost per
|
||||
* grouping column per input tuple for grouping comparisons.
|
||||
@ -2136,7 +2136,8 @@ cost_agg(Path *path, PlannerInfo *root,
|
||||
startup_cost = input_total_cost;
|
||||
startup_cost += aggcosts->transCost.startup;
|
||||
startup_cost += aggcosts->transCost.per_tuple * input_tuples;
|
||||
startup_cost += aggcosts->finalCost;
|
||||
startup_cost += aggcosts->finalCost.startup;
|
||||
startup_cost += aggcosts->finalCost.per_tuple;
|
||||
/* we aren't grouping */
|
||||
total_cost = startup_cost + cpu_tuple_cost;
|
||||
output_tuples = 1;
|
||||
@ -2155,7 +2156,8 @@ cost_agg(Path *path, PlannerInfo *root,
|
||||
total_cost += aggcosts->transCost.startup;
|
||||
total_cost += aggcosts->transCost.per_tuple * input_tuples;
|
||||
total_cost += (cpu_operator_cost * numGroupCols) * input_tuples;
|
||||
total_cost += aggcosts->finalCost * numGroups;
|
||||
total_cost += aggcosts->finalCost.startup;
|
||||
total_cost += aggcosts->finalCost.per_tuple * numGroups;
|
||||
total_cost += cpu_tuple_cost * numGroups;
|
||||
output_tuples = numGroups;
|
||||
}
|
||||
@ -2168,8 +2170,9 @@ cost_agg(Path *path, PlannerInfo *root,
|
||||
startup_cost += aggcosts->transCost.startup;
|
||||
startup_cost += aggcosts->transCost.per_tuple * input_tuples;
|
||||
startup_cost += (cpu_operator_cost * numGroupCols) * input_tuples;
|
||||
startup_cost += aggcosts->finalCost.startup;
|
||||
total_cost = startup_cost;
|
||||
total_cost += aggcosts->finalCost * numGroups;
|
||||
total_cost += aggcosts->finalCost.per_tuple * numGroups;
|
||||
total_cost += cpu_tuple_cost * numGroups;
|
||||
output_tuples = numGroups;
|
||||
}
|
||||
@ -2234,7 +2237,11 @@ cost_windowagg(Path *path, PlannerInfo *root,
|
||||
Cost wfunccost;
|
||||
QualCost argcosts;
|
||||
|
||||
wfunccost = get_func_cost(wfunc->winfnoid) * cpu_operator_cost;
|
||||
argcosts.startup = argcosts.per_tuple = 0;
|
||||
add_function_cost(root, wfunc->winfnoid, (Node *) wfunc,
|
||||
&argcosts);
|
||||
startup_cost += argcosts.startup;
|
||||
wfunccost = argcosts.per_tuple;
|
||||
|
||||
/* also add the input expressions' cost to per-input-row costs */
|
||||
cost_qual_eval_node(&argcosts, (Node *) wfunc->args, root);
|
||||
@ -3864,8 +3871,8 @@ cost_qual_eval_walker(Node *node, cost_qual_eval_context *context)
|
||||
*/
|
||||
if (IsA(node, FuncExpr))
|
||||
{
|
||||
context->total.per_tuple +=
|
||||
get_func_cost(((FuncExpr *) node)->funcid) * cpu_operator_cost;
|
||||
add_function_cost(context->root, ((FuncExpr *) node)->funcid, node,
|
||||
&context->total);
|
||||
}
|
||||
else if (IsA(node, OpExpr) ||
|
||||
IsA(node, DistinctExpr) ||
|
||||
@ -3873,8 +3880,8 @@ cost_qual_eval_walker(Node *node, cost_qual_eval_context *context)
|
||||
{
|
||||
/* rely on struct equivalence to treat these all alike */
|
||||
set_opfuncid((OpExpr *) node);
|
||||
context->total.per_tuple +=
|
||||
get_func_cost(((OpExpr *) node)->opfuncid) * cpu_operator_cost;
|
||||
add_function_cost(context->root, ((OpExpr *) node)->opfuncid, node,
|
||||
&context->total);
|
||||
}
|
||||
else if (IsA(node, ScalarArrayOpExpr))
|
||||
{
|
||||
@ -3884,10 +3891,15 @@ cost_qual_eval_walker(Node *node, cost_qual_eval_context *context)
|
||||
*/
|
||||
ScalarArrayOpExpr *saop = (ScalarArrayOpExpr *) node;
|
||||
Node *arraynode = (Node *) lsecond(saop->args);
|
||||
QualCost sacosts;
|
||||
|
||||
set_sa_opfuncid(saop);
|
||||
context->total.per_tuple += get_func_cost(saop->opfuncid) *
|
||||
cpu_operator_cost * estimate_array_length(arraynode) * 0.5;
|
||||
sacosts.startup = sacosts.per_tuple = 0;
|
||||
add_function_cost(context->root, saop->opfuncid, NULL,
|
||||
&sacosts);
|
||||
context->total.startup += sacosts.startup;
|
||||
context->total.per_tuple += sacosts.per_tuple *
|
||||
estimate_array_length(arraynode) * 0.5;
|
||||
}
|
||||
else if (IsA(node, Aggref) ||
|
||||
IsA(node, WindowFunc))
|
||||
@ -3913,11 +3925,13 @@ cost_qual_eval_walker(Node *node, cost_qual_eval_context *context)
|
||||
/* check the result type's input function */
|
||||
getTypeInputInfo(iocoerce->resulttype,
|
||||
&iofunc, &typioparam);
|
||||
context->total.per_tuple += get_func_cost(iofunc) * cpu_operator_cost;
|
||||
add_function_cost(context->root, iofunc, NULL,
|
||||
&context->total);
|
||||
/* check the input type's output function */
|
||||
getTypeOutputInfo(exprType((Node *) iocoerce->arg),
|
||||
&iofunc, &typisvarlena);
|
||||
context->total.per_tuple += get_func_cost(iofunc) * cpu_operator_cost;
|
||||
add_function_cost(context->root, iofunc, NULL,
|
||||
&context->total);
|
||||
}
|
||||
else if (IsA(node, ArrayCoerceExpr))
|
||||
{
|
||||
@ -3941,8 +3955,8 @@ cost_qual_eval_walker(Node *node, cost_qual_eval_context *context)
|
||||
{
|
||||
Oid opid = lfirst_oid(lc);
|
||||
|
||||
context->total.per_tuple += get_func_cost(get_opcode(opid)) *
|
||||
cpu_operator_cost;
|
||||
add_function_cost(context->root, get_opcode(opid), NULL,
|
||||
&context->total);
|
||||
}
|
||||
}
|
||||
else if (IsA(node, MinMaxExpr) ||
|
||||
@ -4941,7 +4955,7 @@ set_function_size_estimates(PlannerInfo *root, RelOptInfo *rel)
|
||||
foreach(lc, rte->functions)
|
||||
{
|
||||
RangeTblFunction *rtfunc = (RangeTblFunction *) lfirst(lc);
|
||||
double ntup = expression_returns_set_rows(rtfunc->funcexpr);
|
||||
double ntup = expression_returns_set_rows(root, rtfunc->funcexpr);
|
||||
|
||||
if (ntup > rel->tuples)
|
||||
rel->tuples = ntup;
|
||||
|
@ -36,8 +36,8 @@
|
||||
#include "optimizer/clauses.h"
|
||||
#include "optimizer/cost.h"
|
||||
#include "optimizer/optimizer.h"
|
||||
#include "optimizer/plancat.h"
|
||||
#include "optimizer/planmain.h"
|
||||
#include "optimizer/prep.h"
|
||||
#include "parser/analyze.h"
|
||||
#include "parser/parse_agg.h"
|
||||
#include "parser/parse_coerce.h"
|
||||
@ -343,19 +343,24 @@ get_agg_clause_costs_walker(Node *node, get_agg_clause_costs_context *context)
|
||||
if (DO_AGGSPLIT_COMBINE(context->aggsplit))
|
||||
{
|
||||
/* charge for combining previously aggregated states */
|
||||
costs->transCost.per_tuple += get_func_cost(aggcombinefn) * cpu_operator_cost;
|
||||
add_function_cost(context->root, aggcombinefn, NULL,
|
||||
&costs->transCost);
|
||||
}
|
||||
else
|
||||
costs->transCost.per_tuple += get_func_cost(aggtransfn) * cpu_operator_cost;
|
||||
add_function_cost(context->root, aggtransfn, NULL,
|
||||
&costs->transCost);
|
||||
if (DO_AGGSPLIT_DESERIALIZE(context->aggsplit) &&
|
||||
OidIsValid(aggdeserialfn))
|
||||
costs->transCost.per_tuple += get_func_cost(aggdeserialfn) * cpu_operator_cost;
|
||||
add_function_cost(context->root, aggdeserialfn, NULL,
|
||||
&costs->transCost);
|
||||
if (DO_AGGSPLIT_SERIALIZE(context->aggsplit) &&
|
||||
OidIsValid(aggserialfn))
|
||||
costs->finalCost += get_func_cost(aggserialfn) * cpu_operator_cost;
|
||||
add_function_cost(context->root, aggserialfn, NULL,
|
||||
&costs->finalCost);
|
||||
if (!DO_AGGSPLIT_SKIPFINAL(context->aggsplit) &&
|
||||
OidIsValid(aggfinalfn))
|
||||
costs->finalCost += get_func_cost(aggfinalfn) * cpu_operator_cost;
|
||||
add_function_cost(context->root, aggfinalfn, NULL,
|
||||
&costs->finalCost);
|
||||
|
||||
/*
|
||||
* These costs are incurred only by the initial aggregate node, so we
|
||||
@ -392,8 +397,8 @@ get_agg_clause_costs_walker(Node *node, get_agg_clause_costs_context *context)
|
||||
{
|
||||
cost_qual_eval_node(&argcosts, (Node *) aggref->aggdirectargs,
|
||||
context->root);
|
||||
costs->transCost.startup += argcosts.startup;
|
||||
costs->finalCost += argcosts.per_tuple;
|
||||
costs->finalCost.startup += argcosts.startup;
|
||||
costs->finalCost.per_tuple += argcosts.per_tuple;
|
||||
}
|
||||
|
||||
/*
|
||||
@ -561,7 +566,7 @@ find_window_functions_walker(Node *node, WindowFuncLists *lists)
|
||||
* Note: keep this in sync with expression_returns_set() in nodes/nodeFuncs.c.
|
||||
*/
|
||||
double
|
||||
expression_returns_set_rows(Node *clause)
|
||||
expression_returns_set_rows(PlannerInfo *root, Node *clause)
|
||||
{
|
||||
if (clause == NULL)
|
||||
return 1.0;
|
||||
@ -570,7 +575,7 @@ expression_returns_set_rows(Node *clause)
|
||||
FuncExpr *expr = (FuncExpr *) clause;
|
||||
|
||||
if (expr->funcretset)
|
||||
return clamp_row_est(get_func_rows(expr->funcid));
|
||||
return clamp_row_est(get_function_rows(root, expr->funcid, clause));
|
||||
}
|
||||
if (IsA(clause, OpExpr))
|
||||
{
|
||||
@ -579,7 +584,7 @@ expression_returns_set_rows(Node *clause)
|
||||
if (expr->opretset)
|
||||
{
|
||||
set_opfuncid(expr);
|
||||
return clamp_row_est(get_func_rows(expr->opfuncid));
|
||||
return clamp_row_est(get_function_rows(root, expr->opfuncid, clause));
|
||||
}
|
||||
}
|
||||
return 1.0;
|
||||
|
@ -2711,7 +2711,7 @@ create_set_projection_path(PlannerInfo *root,
|
||||
Node *node = (Node *) lfirst(lc);
|
||||
double itemrows;
|
||||
|
||||
itemrows = expression_returns_set_rows(node);
|
||||
itemrows = expression_returns_set_rows(root, node);
|
||||
if (tlist_rows < itemrows)
|
||||
tlist_rows = itemrows;
|
||||
}
|
||||
|
@ -29,10 +29,12 @@
|
||||
#include "catalog/heap.h"
|
||||
#include "catalog/partition.h"
|
||||
#include "catalog/pg_am.h"
|
||||
#include "catalog/pg_proc.h"
|
||||
#include "catalog/pg_statistic_ext.h"
|
||||
#include "foreign/fdwapi.h"
|
||||
#include "miscadmin.h"
|
||||
#include "nodes/makefuncs.h"
|
||||
#include "nodes/supportnodes.h"
|
||||
#include "optimizer/clauses.h"
|
||||
#include "optimizer/cost.h"
|
||||
#include "optimizer/optimizer.h"
|
||||
@ -1772,6 +1774,8 @@ restriction_selectivity(PlannerInfo *root,
|
||||
* Returns the selectivity of a specified join operator clause.
|
||||
* This code executes registered procedures stored in the
|
||||
* operator relation, by calling the function manager.
|
||||
*
|
||||
* See clause_selectivity() for the meaning of the additional parameters.
|
||||
*/
|
||||
Selectivity
|
||||
join_selectivity(PlannerInfo *root,
|
||||
@ -1805,6 +1809,184 @@ join_selectivity(PlannerInfo *root,
|
||||
return (Selectivity) result;
|
||||
}
|
||||
|
||||
/*
|
||||
* function_selectivity
|
||||
*
|
||||
* Returns the selectivity of a specified boolean function clause.
|
||||
* This code executes registered procedures stored in the
|
||||
* pg_proc relation, by calling the function manager.
|
||||
*
|
||||
* See clause_selectivity() for the meaning of the additional parameters.
|
||||
*/
|
||||
Selectivity
|
||||
function_selectivity(PlannerInfo *root,
|
||||
Oid funcid,
|
||||
List *args,
|
||||
Oid inputcollid,
|
||||
bool is_join,
|
||||
int varRelid,
|
||||
JoinType jointype,
|
||||
SpecialJoinInfo *sjinfo)
|
||||
{
|
||||
RegProcedure prosupport = get_func_support(funcid);
|
||||
SupportRequestSelectivity req;
|
||||
SupportRequestSelectivity *sresult;
|
||||
|
||||
/*
|
||||
* If no support function is provided, use our historical default
|
||||
* estimate, 0.3333333. This seems a pretty unprincipled choice, but
|
||||
* Postgres has been using that estimate for function calls since 1992.
|
||||
* The hoariness of this behavior suggests that we should not be in too
|
||||
* much hurry to use another value.
|
||||
*/
|
||||
if (!prosupport)
|
||||
return (Selectivity) 0.3333333;
|
||||
|
||||
req.type = T_SupportRequestSelectivity;
|
||||
req.root = root;
|
||||
req.funcid = funcid;
|
||||
req.args = args;
|
||||
req.inputcollid = inputcollid;
|
||||
req.is_join = is_join;
|
||||
req.varRelid = varRelid;
|
||||
req.jointype = jointype;
|
||||
req.sjinfo = sjinfo;
|
||||
req.selectivity = -1; /* to catch failure to set the value */
|
||||
|
||||
sresult = (SupportRequestSelectivity *)
|
||||
DatumGetPointer(OidFunctionCall1(prosupport,
|
||||
PointerGetDatum(&req)));
|
||||
|
||||
/* If support function fails, use default */
|
||||
if (sresult != &req)
|
||||
return (Selectivity) 0.3333333;
|
||||
|
||||
if (req.selectivity < 0.0 || req.selectivity > 1.0)
|
||||
elog(ERROR, "invalid function selectivity: %f", req.selectivity);
|
||||
|
||||
return (Selectivity) req.selectivity;
|
||||
}
|
||||
|
||||
/*
|
||||
* add_function_cost
|
||||
*
|
||||
* Get an estimate of the execution cost of a function, and *add* it to
|
||||
* the contents of *cost. The estimate may include both one-time and
|
||||
* per-tuple components, since QualCost does.
|
||||
*
|
||||
* The funcid must always be supplied. If it is being called as the
|
||||
* implementation of a specific parsetree node (FuncExpr, OpExpr,
|
||||
* WindowFunc, etc), pass that as "node", else pass NULL.
|
||||
*
|
||||
* In some usages root might be NULL, too.
|
||||
*/
|
||||
void
|
||||
add_function_cost(PlannerInfo *root, Oid funcid, Node *node,
|
||||
QualCost *cost)
|
||||
{
|
||||
HeapTuple proctup;
|
||||
Form_pg_proc procform;
|
||||
|
||||
proctup = SearchSysCache1(PROCOID, ObjectIdGetDatum(funcid));
|
||||
if (!HeapTupleIsValid(proctup))
|
||||
elog(ERROR, "cache lookup failed for function %u", funcid);
|
||||
procform = (Form_pg_proc) GETSTRUCT(proctup);
|
||||
|
||||
if (OidIsValid(procform->prosupport))
|
||||
{
|
||||
SupportRequestCost req;
|
||||
SupportRequestCost *sresult;
|
||||
|
||||
req.type = T_SupportRequestCost;
|
||||
req.root = root;
|
||||
req.funcid = funcid;
|
||||
req.node = node;
|
||||
|
||||
/* Initialize cost fields so that support function doesn't have to */
|
||||
req.startup = 0;
|
||||
req.per_tuple = 0;
|
||||
|
||||
sresult = (SupportRequestCost *)
|
||||
DatumGetPointer(OidFunctionCall1(procform->prosupport,
|
||||
PointerGetDatum(&req)));
|
||||
|
||||
if (sresult == &req)
|
||||
{
|
||||
/* Success, so accumulate support function's estimate into *cost */
|
||||
cost->startup += req.startup;
|
||||
cost->per_tuple += req.per_tuple;
|
||||
ReleaseSysCache(proctup);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/* No support function, or it failed, so rely on procost */
|
||||
cost->per_tuple += procform->procost * cpu_operator_cost;
|
||||
|
||||
ReleaseSysCache(proctup);
|
||||
}
|
||||
|
||||
/*
|
||||
* get_function_rows
|
||||
*
|
||||
* Get an estimate of the number of rows returned by a set-returning function.
|
||||
*
|
||||
* The funcid must always be supplied. In current usage, the calling node
|
||||
* will always be supplied, and will be either a FuncExpr or OpExpr.
|
||||
* But it's a good idea to not fail if it's NULL.
|
||||
*
|
||||
* In some usages root might be NULL, too.
|
||||
*
|
||||
* Note: this returns the unfiltered result of the support function, if any.
|
||||
* It's usually a good idea to apply clamp_row_est() to the result, but we
|
||||
* leave it to the caller to do so.
|
||||
*/
|
||||
double
|
||||
get_function_rows(PlannerInfo *root, Oid funcid, Node *node)
|
||||
{
|
||||
HeapTuple proctup;
|
||||
Form_pg_proc procform;
|
||||
double result;
|
||||
|
||||
proctup = SearchSysCache1(PROCOID, ObjectIdGetDatum(funcid));
|
||||
if (!HeapTupleIsValid(proctup))
|
||||
elog(ERROR, "cache lookup failed for function %u", funcid);
|
||||
procform = (Form_pg_proc) GETSTRUCT(proctup);
|
||||
|
||||
Assert(procform->proretset); /* else caller error */
|
||||
|
||||
if (OidIsValid(procform->prosupport))
|
||||
{
|
||||
SupportRequestRows req;
|
||||
SupportRequestRows *sresult;
|
||||
|
||||
req.type = T_SupportRequestRows;
|
||||
req.root = root;
|
||||
req.funcid = funcid;
|
||||
req.node = node;
|
||||
|
||||
req.rows = 0; /* just for sanity */
|
||||
|
||||
sresult = (SupportRequestRows *)
|
||||
DatumGetPointer(OidFunctionCall1(procform->prosupport,
|
||||
PointerGetDatum(&req)));
|
||||
|
||||
if (sresult == &req)
|
||||
{
|
||||
/* Success */
|
||||
ReleaseSysCache(proctup);
|
||||
return req.rows;
|
||||
}
|
||||
}
|
||||
|
||||
/* No support function, or it failed, so rely on prorows */
|
||||
result = procform->prorows;
|
||||
|
||||
ReleaseSysCache(proctup);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/*
|
||||
* has_unique_index
|
||||
*
|
||||
|
Reference in New Issue
Block a user