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:
Tom Lane 2019-02-09 18:32:23 -05:00
parent 1fb57af920
commit a391ff3c3d
27 changed files with 792 additions and 90 deletions

View File

@ -2779,6 +2779,7 @@ estimate_path_cost_size(PlannerInfo *root,
startup_cost = ofpinfo->rel_startup_cost;
startup_cost += aggcosts.transCost.startup;
startup_cost += aggcosts.transCost.per_tuple * input_rows;
startup_cost += aggcosts.finalCost.startup;
startup_cost += (cpu_operator_cost * numGroupCols) * input_rows;
/*-----
@ -2788,7 +2789,7 @@ estimate_path_cost_size(PlannerInfo *root,
*-----
*/
run_cost = ofpinfo->rel_total_cost - ofpinfo->rel_startup_cost;
run_cost += aggcosts.finalCost * numGroups;
run_cost += aggcosts.finalCost.per_tuple * numGroups;
run_cost += cpu_tuple_cost * numGroups;
/* Account for the eval cost of HAVING quals, if any */

View File

@ -3439,4 +3439,25 @@ supportfn(internal) returns internal
simplify. Ensure rigorous equivalence between the simplified
expression and an actual execution of the target function.
</para>
<para>
For target functions that return boolean, it is often useful to estimate
the fraction of rows that will be selected by a WHERE clause using that
function. This can be done by a support function that implements
the <literal>SupportRequestSelectivity</literal> request type.
</para>
<para>
If the target function's runtime is highly dependent on its inputs,
it may be useful to provide a non-constant cost estimate for it.
This can be done by a support function that implements
the <literal>SupportRequestCost</literal> request type.
</para>
<para>
For target functions that return sets, it is often useful to provide
a non-constant estimate for the number of rows that will be returned.
This can be done by a support function that implements
the <literal>SupportRequestRows</literal> request type.
</para>
</sect1>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -22,12 +22,16 @@
#include "catalog/pg_type.h"
#include "funcapi.h"
#include "libpq/pqformat.h"
#include "nodes/nodeFuncs.h"
#include "nodes/supportnodes.h"
#include "optimizer/optimizer.h"
#include "utils/array.h"
#include "utils/arrayaccess.h"
#include "utils/builtins.h"
#include "utils/datum.h"
#include "utils/lsyscache.h"
#include "utils/memutils.h"
#include "utils/selfuncs.h"
#include "utils/typcache.h"
@ -6025,6 +6029,36 @@ array_unnest(PG_FUNCTION_ARGS)
}
}
/*
* Planner support function for array_unnest(anyarray)
*/
Datum
array_unnest_support(PG_FUNCTION_ARGS)
{
Node *rawreq = (Node *) PG_GETARG_POINTER(0);
Node *ret = NULL;
if (IsA(rawreq, SupportRequestRows))
{
/* Try to estimate the number of rows returned */
SupportRequestRows *req = (SupportRequestRows *) rawreq;
if (is_funcclause(req->node)) /* be paranoid */
{
List *args = ((FuncExpr *) req->node)->args;
Node *arg1;
/* We can use estimated argument values here */
arg1 = estimate_expression_value(req->root, linitial(args));
req->rows = estimate_array_length(arg1);
ret = (Node *) req;
}
}
PG_RETURN_POINTER(ret);
}
/*
* array_replace/array_remove support

View File

@ -30,11 +30,15 @@
#include <ctype.h>
#include <limits.h>
#include <math.h>
#include "catalog/pg_type.h"
#include "common/int.h"
#include "funcapi.h"
#include "libpq/pqformat.h"
#include "nodes/nodeFuncs.h"
#include "nodes/supportnodes.h"
#include "optimizer/optimizer.h"
#include "utils/array.h"
#include "utils/builtins.h"
@ -1427,3 +1431,73 @@ generate_series_step_int4(PG_FUNCTION_ARGS)
/* do when there is no more left */
SRF_RETURN_DONE(funcctx);
}
/*
* Planner support function for generate_series(int4, int4 [, int4])
*/
Datum
generate_series_int4_support(PG_FUNCTION_ARGS)
{
Node *rawreq = (Node *) PG_GETARG_POINTER(0);
Node *ret = NULL;
if (IsA(rawreq, SupportRequestRows))
{
/* Try to estimate the number of rows returned */
SupportRequestRows *req = (SupportRequestRows *) rawreq;
if (is_funcclause(req->node)) /* be paranoid */
{
List *args = ((FuncExpr *) req->node)->args;
Node *arg1,
*arg2,
*arg3;
/* We can use estimated argument values here */
arg1 = estimate_expression_value(req->root, linitial(args));
arg2 = estimate_expression_value(req->root, lsecond(args));
if (list_length(args) >= 3)
arg3 = estimate_expression_value(req->root, lthird(args));
else
arg3 = NULL;
/*
* If any argument is constant NULL, we can safely assume that
* zero rows are returned. Otherwise, if they're all non-NULL
* constants, we can calculate the number of rows that will be
* returned. Use double arithmetic to avoid overflow hazards.
*/
if ((IsA(arg1, Const) &&
((Const *) arg1)->constisnull) ||
(IsA(arg2, Const) &&
((Const *) arg2)->constisnull) ||
(arg3 != NULL && IsA(arg3, Const) &&
((Const *) arg3)->constisnull))
{
req->rows = 0;
ret = (Node *) req;
}
else if (IsA(arg1, Const) &&
IsA(arg2, Const) &&
(arg3 == NULL || IsA(arg3, Const)))
{
double start,
finish,
step;
start = DatumGetInt32(((Const *) arg1)->constvalue);
finish = DatumGetInt32(((Const *) arg2)->constvalue);
step = arg3 ? DatumGetInt32(((Const *) arg3)->constvalue) : 1;
/* This equation works for either sign of step */
if (step != 0)
{
req->rows = floor((finish - start + step) / step);
ret = (Node *) req;
}
}
}
}
PG_RETURN_POINTER(ret);
}

View File

@ -20,6 +20,9 @@
#include "common/int.h"
#include "funcapi.h"
#include "libpq/pqformat.h"
#include "nodes/nodeFuncs.h"
#include "nodes/supportnodes.h"
#include "optimizer/optimizer.h"
#include "utils/int8.h"
#include "utils/builtins.h"
@ -1373,3 +1376,73 @@ generate_series_step_int8(PG_FUNCTION_ARGS)
/* do when there is no more left */
SRF_RETURN_DONE(funcctx);
}
/*
* Planner support function for generate_series(int8, int8 [, int8])
*/
Datum
generate_series_int8_support(PG_FUNCTION_ARGS)
{
Node *rawreq = (Node *) PG_GETARG_POINTER(0);
Node *ret = NULL;
if (IsA(rawreq, SupportRequestRows))
{
/* Try to estimate the number of rows returned */
SupportRequestRows *req = (SupportRequestRows *) rawreq;
if (is_funcclause(req->node)) /* be paranoid */
{
List *args = ((FuncExpr *) req->node)->args;
Node *arg1,
*arg2,
*arg3;
/* We can use estimated argument values here */
arg1 = estimate_expression_value(req->root, linitial(args));
arg2 = estimate_expression_value(req->root, lsecond(args));
if (list_length(args) >= 3)
arg3 = estimate_expression_value(req->root, lthird(args));
else
arg3 = NULL;
/*
* If any argument is constant NULL, we can safely assume that
* zero rows are returned. Otherwise, if they're all non-NULL
* constants, we can calculate the number of rows that will be
* returned. Use double arithmetic to avoid overflow hazards.
*/
if ((IsA(arg1, Const) &&
((Const *) arg1)->constisnull) ||
(IsA(arg2, Const) &&
((Const *) arg2)->constisnull) ||
(arg3 != NULL && IsA(arg3, Const) &&
((Const *) arg3)->constisnull))
{
req->rows = 0;
ret = (Node *) req;
}
else if (IsA(arg1, Const) &&
IsA(arg2, Const) &&
(arg3 == NULL || IsA(arg3, Const)))
{
double start,
finish,
step;
start = DatumGetInt64(((Const *) arg1)->constvalue);
finish = DatumGetInt64(((Const *) arg2)->constvalue);
step = arg3 ? DatumGetInt64(((Const *) arg3)->constvalue) : 1;
/* This equation works for either sign of step */
if (step != 0)
{
req->rows = floor((finish - start + step) / step);
ret = (Node *) req;
}
}
}
}
PG_RETURN_POINTER(ret);
}

View File

@ -1577,17 +1577,6 @@ boolvarsel(PlannerInfo *root, Node *arg, int varRelid)
selec = var_eq_const(&vardata, BooleanEqualOperator,
BoolGetDatum(true), false, true, false);
}
else if (is_funcclause(arg))
{
/*
* If we have no stats and it's a function call, 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.
*/
selec = 0.3333333;
}
else
{
/* Otherwise, the default estimate is 0.5 */
@ -3502,7 +3491,7 @@ estimate_num_groups(PlannerInfo *root, List *groupExprs, double input_rows,
* pointless to worry too much about this without much better
* estimates for SRF output rowcounts than we have today.)
*/
this_srf_multiplier = expression_returns_set_rows(groupexpr);
this_srf_multiplier = expression_returns_set_rows(root, groupexpr);
if (srf_multiplier < this_srf_multiplier)
srf_multiplier = this_srf_multiplier;

View File

@ -1605,41 +1605,28 @@ get_func_leakproof(Oid funcid)
}
/*
* get_func_cost
* Given procedure id, return the function's procost field.
* get_func_support
*
* Returns the support function OID associated with a given function,
* or InvalidOid if there is none.
*/
float4
get_func_cost(Oid funcid)
RegProcedure
get_func_support(Oid funcid)
{
HeapTuple tp;
float4 result;
tp = SearchSysCache1(PROCOID, ObjectIdGetDatum(funcid));
if (!HeapTupleIsValid(tp))
elog(ERROR, "cache lookup failed for function %u", funcid);
if (HeapTupleIsValid(tp))
{
Form_pg_proc functup = (Form_pg_proc) GETSTRUCT(tp);
RegProcedure result;
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 = SearchSysCache1(PROCOID, ObjectIdGetDatum(funcid));
if (!HeapTupleIsValid(tp))
elog(ERROR, "cache lookup failed for function %u", funcid);
result = ((Form_pg_proc) GETSTRUCT(tp))->prorows;
ReleaseSysCache(tp);
return result;
result = functup->prosupport;
ReleaseSysCache(tp);
return result;
}
else
return (RegProcedure) InvalidOid;
}
/* ---------- RELATION CACHE ---------- */

View File

@ -53,6 +53,6 @@
*/
/* yyyymmddN */
#define CATALOG_VERSION_NO 201902091
#define CATALOG_VERSION_NO 201902092
#endif

View File

@ -1530,9 +1530,12 @@
proargtypes => 'anyelement _int4 _int4',
prosrc => 'array_fill_with_lower_bounds' },
{ oid => '2331', descr => 'expand array to set of rows',
proname => 'unnest', prorows => '100', proretset => 't',
prorettype => 'anyelement', proargtypes => 'anyarray',
proname => 'unnest', prorows => '100', prosupport => 'array_unnest_support',
proretset => 't', prorettype => 'anyelement', proargtypes => 'anyarray',
prosrc => 'array_unnest' },
{ oid => '3996', descr => 'planner support for array_unnest',
proname => 'array_unnest_support', prorettype => 'internal',
proargtypes => 'internal', prosrc => 'array_unnest_support' },
{ oid => '3167',
descr => 'remove any occurrences of an element from an array',
proname => 'array_remove', proisstrict => 'f', prorettype => 'anyarray',
@ -7536,21 +7539,31 @@
# non-persistent series generator
{ oid => '1066', descr => 'non-persistent series generator',
proname => 'generate_series', prorows => '1000', proretset => 't',
proname => 'generate_series', prorows => '1000',
prosupport => 'generate_series_int4_support', proretset => 't',
prorettype => 'int4', proargtypes => 'int4 int4 int4',
prosrc => 'generate_series_step_int4' },
{ oid => '1067', descr => 'non-persistent series generator',
proname => 'generate_series', prorows => '1000', proretset => 't',
proname => 'generate_series', prorows => '1000',
prosupport => 'generate_series_int4_support', proretset => 't',
prorettype => 'int4', proargtypes => 'int4 int4',
prosrc => 'generate_series_int4' },
{ oid => '3994', descr => 'planner support for generate_series',
proname => 'generate_series_int4_support', prorettype => 'internal',
proargtypes => 'internal', prosrc => 'generate_series_int4_support' },
{ oid => '1068', descr => 'non-persistent series generator',
proname => 'generate_series', prorows => '1000', proretset => 't',
proname => 'generate_series', prorows => '1000',
prosupport => 'generate_series_int8_support', proretset => 't',
prorettype => 'int8', proargtypes => 'int8 int8 int8',
prosrc => 'generate_series_step_int8' },
{ oid => '1069', descr => 'non-persistent series generator',
proname => 'generate_series', prorows => '1000', proretset => 't',
proname => 'generate_series', prorows => '1000',
prosupport => 'generate_series_int8_support', proretset => 't',
prorettype => 'int8', proargtypes => 'int8 int8',
prosrc => 'generate_series_int8' },
{ oid => '3995', descr => 'planner support for generate_series',
proname => 'generate_series_int8_support', prorettype => 'internal',
proargtypes => 'internal', prosrc => 'generate_series_int8_support' },
{ oid => '3259', descr => 'non-persistent series generator',
proname => 'generate_series', prorows => '1000', proretset => 't',
prorettype => 'numeric', proargtypes => 'numeric numeric numeric',

View File

@ -507,7 +507,10 @@ typedef enum NodeTag
T_TsmRoutine, /* in access/tsmapi.h */
T_ForeignKeyCacheInfo, /* in utils/rel.h */
T_CallContext, /* in nodes/parsenodes.h */
T_SupportRequestSimplify /* in nodes/supportnodes.h */
T_SupportRequestSimplify, /* in nodes/supportnodes.h */
T_SupportRequestSelectivity, /* in nodes/supportnodes.h */
T_SupportRequestCost, /* in nodes/supportnodes.h */
T_SupportRequestRows /* in nodes/supportnodes.h */
} NodeTag;
/*

View File

@ -61,7 +61,7 @@ typedef struct AggClauseCosts
bool hasNonPartial; /* does any agg not support partial mode? */
bool hasNonSerial; /* is any partial agg non-serializable? */
QualCost transCost; /* total per-input-row execution costs */
Cost finalCost; /* total per-aggregated-row costs */
QualCost finalCost; /* total per-aggregated-row costs */
Size transitionSpace; /* space for pass-by-ref transition data */
} AggClauseCosts;

View File

@ -36,6 +36,7 @@
#include "nodes/primnodes.h"
struct PlannerInfo; /* avoid including relation.h here */
struct SpecialJoinInfo;
/*
@ -67,4 +68,103 @@ typedef struct SupportRequestSimplify
FuncExpr *fcall; /* Function call to be simplified */
} SupportRequestSimplify;
/*
* The Selectivity request allows the support function to provide a
* selectivity estimate for a function appearing at top level of a WHERE
* clause (so it applies only to functions returning boolean).
*
* The input arguments are the same as are supplied to operator restriction
* and join estimators, except that we unify those two APIs into just one
* request type. See clause_selectivity() for the details.
*
* If an estimate can be made, store it into the "selectivity" field and
* return the address of the SupportRequestSelectivity node; the estimate
* must be between 0 and 1 inclusive. Return NULL if no estimate can be
* made (in which case the planner will fall back to a default estimate,
* traditionally 1/3).
*
* If the target function is being used as the implementation of an operator,
* the support function will not be used for this purpose; the operator's
* restriction or join estimator is consulted instead.
*/
typedef struct SupportRequestSelectivity
{
NodeTag type;
/* Input fields: */
struct PlannerInfo *root; /* Planner's infrastructure */
Oid funcid; /* function we are inquiring about */
List *args; /* pre-simplified arguments to function */
Oid inputcollid; /* function's input collation */
bool is_join; /* is this a join or restriction case? */
int varRelid; /* if restriction, RTI of target relation */
JoinType jointype; /* if join, outer join type */
struct SpecialJoinInfo *sjinfo; /* if outer join, info about join */
/* Output fields: */
Selectivity selectivity; /* returned selectivity estimate */
} SupportRequestSelectivity;
/*
* The Cost request allows the support function to provide an execution
* cost estimate for its target function. The cost estimate can include
* both a one-time (query startup) component and a per-execution component.
* The estimate should *not* include the costs of evaluating the target
* function's arguments, only the target function itself.
*
* The "node" argument is normally the parse node that is invoking the
* target function. This is a FuncExpr in the simplest case, but it could
* also be an OpExpr, DistinctExpr, NullIfExpr, or WindowFunc, or possibly
* other cases in future. NULL is passed if the function cannot presume
* its arguments to be equivalent to what the calling node presents as
* arguments; that happens for, e.g., aggregate support functions and
* per-column comparison operators used by RowExprs.
*
* If an estimate can be made, store it into the cost fields and return the
* address of the SupportRequestCost node. Return NULL if no estimate can be
* made, in which case the planner will rely on the target function's procost
* field. (Note: while procost is automatically scaled by cpu_operator_cost,
* this is not the case for the outputs of the Cost request; the support
* function must scale its results appropriately on its own.)
*/
typedef struct SupportRequestCost
{
NodeTag type;
/* Input fields: */
struct PlannerInfo *root; /* Planner's infrastructure (could be NULL) */
Oid funcid; /* function we are inquiring about */
Node *node; /* parse node invoking function, or NULL */
/* Output fields: */
Cost startup; /* one-time cost */
Cost per_tuple; /* per-evaluation cost */
} SupportRequestCost;
/*
* The Rows request allows the support function to provide an output rowcount
* estimate for its target function (so it applies only to set-returning
* functions).
*
* The "node" argument is the parse node that is invoking the target function;
* currently this will always be a FuncExpr or OpExpr.
*
* If an estimate can be made, store it into the rows field and return the
* address of the SupportRequestRows node. Return NULL if no estimate can be
* made, in which case the planner will rely on the target function's prorows
* field.
*/
typedef struct SupportRequestRows
{
NodeTag type;
/* Input fields: */
struct PlannerInfo *root; /* Planner's infrastructure (could be NULL) */
Oid funcid; /* function we are inquiring about */
Node *node; /* parse node invoking function */
/* Output fields: */
double rows; /* number of rows expected to be returned */
} SupportRequestRows;
#endif /* SUPPORTNODES_H */

View File

@ -31,7 +31,7 @@ extern void get_agg_clause_costs(PlannerInfo *root, Node *clause,
extern bool contain_window_function(Node *clause);
extern WindowFuncLists *find_window_functions(Node *clause, Index maxWinRef);
extern double expression_returns_set_rows(Node *clause);
extern double expression_returns_set_rows(PlannerInfo *root, Node *clause);
extern bool contain_subplans(Node *clause);

View File

@ -55,6 +55,20 @@ extern Selectivity join_selectivity(PlannerInfo *root,
JoinType jointype,
SpecialJoinInfo *sjinfo);
extern Selectivity function_selectivity(PlannerInfo *root,
Oid funcid,
List *args,
Oid inputcollid,
bool is_join,
int varRelid,
JoinType jointype,
SpecialJoinInfo *sjinfo);
extern void add_function_cost(PlannerInfo *root, Oid funcid, Node *node,
QualCost *cost);
extern double get_function_rows(PlannerInfo *root, Oid funcid, Node *node);
extern bool has_row_triggers(PlannerInfo *root, Index rti, CmdType event);
#endif /* PLANCAT_H */

View File

@ -120,8 +120,7 @@ extern char func_volatile(Oid funcid);
extern char func_parallel(Oid funcid);
extern char get_func_prokind(Oid funcid);
extern bool get_func_leakproof(Oid funcid);
extern float4 get_func_cost(Oid funcid);
extern float4 get_func_rows(Oid funcid);
extern RegProcedure get_func_support(Oid funcid);
extern Oid get_relname_relid(const char *relname, Oid relnamespace);
extern char *get_rel_name(Oid relid);
extern Oid get_rel_namespace(Oid relid);

View File

@ -133,3 +133,63 @@ ERROR: function num_nulls() does not exist
LINE 1: SELECT num_nulls();
^
HINT: No function matches the given name and argument types. You might need to add explicit type casts.
--
-- Test adding a support function to a subject function
--
CREATE FUNCTION my_int_eq(int, int) RETURNS bool
LANGUAGE internal STRICT IMMUTABLE PARALLEL SAFE
AS $$int4eq$$;
-- By default, planner does not think that's selective
EXPLAIN (COSTS OFF)
SELECT * FROM tenk1 a JOIN tenk1 b ON a.unique1 = b.unique1
WHERE my_int_eq(a.unique2, 42);
QUERY PLAN
----------------------------------------------
Hash Join
Hash Cond: (b.unique1 = a.unique1)
-> Seq Scan on tenk1 b
-> Hash
-> Seq Scan on tenk1 a
Filter: my_int_eq(unique2, 42)
(6 rows)
-- With support function that knows it's int4eq, we get a different plan
ALTER FUNCTION my_int_eq(int, int) SUPPORT test_support_func;
EXPLAIN (COSTS OFF)
SELECT * FROM tenk1 a JOIN tenk1 b ON a.unique1 = b.unique1
WHERE my_int_eq(a.unique2, 42);
QUERY PLAN
-------------------------------------------------
Nested Loop
-> Seq Scan on tenk1 a
Filter: my_int_eq(unique2, 42)
-> Index Scan using tenk1_unique1 on tenk1 b
Index Cond: (unique1 = a.unique1)
(5 rows)
-- Also test non-default rowcount estimate
CREATE FUNCTION my_gen_series(int, int) RETURNS SETOF integer
LANGUAGE internal STRICT IMMUTABLE PARALLEL SAFE
AS $$generate_series_int4$$
SUPPORT test_support_func;
EXPLAIN (COSTS OFF)
SELECT * FROM tenk1 a JOIN my_gen_series(1,1000) g ON a.unique1 = g;
QUERY PLAN
----------------------------------------
Hash Join
Hash Cond: (g.g = a.unique1)
-> Function Scan on my_gen_series g
-> Hash
-> Seq Scan on tenk1 a
(5 rows)
EXPLAIN (COSTS OFF)
SELECT * FROM tenk1 a JOIN my_gen_series(1,10) g ON a.unique1 = g;
QUERY PLAN
-------------------------------------------------
Nested Loop
-> Function Scan on my_gen_series g
-> Index Scan using tenk1_unique1 on tenk1 a
Index Cond: (unique1 = g.g)
(4 rows)

View File

@ -904,7 +904,7 @@ select * from int4_tbl where
--
explain (verbose, costs off)
select * from int4_tbl o where (f1, f1) in
(select f1, generate_series(1,2) / 10 g from int4_tbl i group by f1);
(select f1, generate_series(1,50) / 10 g from int4_tbl i group by f1);
QUERY PLAN
-------------------------------------------------------------------
Nested Loop Semi Join
@ -918,9 +918,9 @@ select * from int4_tbl o where (f1, f1) in
Output: "ANY_subquery".f1, "ANY_subquery".g
Filter: ("ANY_subquery".f1 = "ANY_subquery".g)
-> Result
Output: i.f1, ((generate_series(1, 2)) / 10)
Output: i.f1, ((generate_series(1, 50)) / 10)
-> ProjectSet
Output: generate_series(1, 2), i.f1
Output: generate_series(1, 50), i.f1
-> HashAggregate
Output: i.f1
Group Key: i.f1
@ -929,7 +929,7 @@ select * from int4_tbl o where (f1, f1) in
(19 rows)
select * from int4_tbl o where (f1, f1) in
(select f1, generate_series(1,2) / 10 g from int4_tbl i group by f1);
(select f1, generate_series(1,50) / 10 g from int4_tbl i group by f1);
f1
----
0

View File

@ -68,6 +68,11 @@ CREATE FUNCTION test_fdw_handler()
AS '@libdir@/regress@DLSUFFIX@', 'test_fdw_handler'
LANGUAGE C;
CREATE FUNCTION test_support_func(internal)
RETURNS internal
AS '@libdir@/regress@DLSUFFIX@', 'test_support_func'
LANGUAGE C STRICT;
-- Things that shouldn't work:
CREATE FUNCTION test1 (int) RETURNS int LANGUAGE SQL

View File

@ -60,6 +60,10 @@ CREATE FUNCTION test_fdw_handler()
RETURNS fdw_handler
AS '@libdir@/regress@DLSUFFIX@', 'test_fdw_handler'
LANGUAGE C;
CREATE FUNCTION test_support_func(internal)
RETURNS internal
AS '@libdir@/regress@DLSUFFIX@', 'test_support_func'
LANGUAGE C STRICT;
-- Things that shouldn't work:
CREATE FUNCTION test1 (int) RETURNS int LANGUAGE SQL
AS 'SELECT ''not an integer'';';

View File

@ -23,12 +23,16 @@
#include "access/transam.h"
#include "access/tuptoaster.h"
#include "access/xact.h"
#include "catalog/pg_operator.h"
#include "catalog/pg_type.h"
#include "commands/sequence.h"
#include "commands/trigger.h"
#include "executor/executor.h"
#include "executor/spi.h"
#include "miscadmin.h"
#include "nodes/supportnodes.h"
#include "optimizer/optimizer.h"
#include "optimizer/plancat.h"
#include "port/atomics.h"
#include "utils/builtins.h"
#include "utils/geo_decls.h"
@ -863,3 +867,76 @@ test_fdw_handler(PG_FUNCTION_ARGS)
elog(ERROR, "test_fdw_handler is not implemented");
PG_RETURN_NULL();
}
PG_FUNCTION_INFO_V1(test_support_func);
Datum
test_support_func(PG_FUNCTION_ARGS)
{
Node *rawreq = (Node *) PG_GETARG_POINTER(0);
Node *ret = NULL;
if (IsA(rawreq, SupportRequestSelectivity))
{
/*
* Assume that the target is int4eq; that's safe as long as we don't
* attach this to any other boolean-returning function.
*/
SupportRequestSelectivity *req = (SupportRequestSelectivity *) rawreq;
Selectivity s1;
if (req->is_join)
s1 = join_selectivity(req->root, Int4EqualOperator,
req->args,
req->inputcollid,
req->jointype,
req->sjinfo);
else
s1 = restriction_selectivity(req->root, Int4EqualOperator,
req->args,
req->inputcollid,
req->varRelid);
req->selectivity = s1;
ret = (Node *) req;
}
if (IsA(rawreq, SupportRequestCost))
{
/* Provide some generic estimate */
SupportRequestCost *req = (SupportRequestCost *) rawreq;
req->startup = 0;
req->per_tuple = 2 * cpu_operator_cost;
ret = (Node *) req;
}
if (IsA(rawreq, SupportRequestRows))
{
/*
* Assume that the target is generate_series_int4; that's safe as long
* as we don't attach this to any other set-returning function.
*/
SupportRequestRows *req = (SupportRequestRows *) rawreq;
if (req->node && IsA(req->node, FuncExpr)) /* be paranoid */
{
List *args = ((FuncExpr *) req->node)->args;
Node *arg1 = linitial(args);
Node *arg2 = lsecond(args);
if (IsA(arg1, Const) &&
!((Const *) arg1)->constisnull &&
IsA(arg2, Const) &&
!((Const *) arg2)->constisnull)
{
int32 val1 = DatumGetInt32(((Const *) arg1)->constvalue);
int32 val2 = DatumGetInt32(((Const *) arg2)->constvalue);
req->rows = val2 - val1 + 1;
ret = (Node *) req;
}
}
}
PG_RETURN_POINTER(ret);
}

View File

@ -29,3 +29,35 @@ SELECT num_nulls(VARIADIC '{}'::int[]);
-- should fail, one or more arguments is required
SELECT num_nonnulls();
SELECT num_nulls();
--
-- Test adding a support function to a subject function
--
CREATE FUNCTION my_int_eq(int, int) RETURNS bool
LANGUAGE internal STRICT IMMUTABLE PARALLEL SAFE
AS $$int4eq$$;
-- By default, planner does not think that's selective
EXPLAIN (COSTS OFF)
SELECT * FROM tenk1 a JOIN tenk1 b ON a.unique1 = b.unique1
WHERE my_int_eq(a.unique2, 42);
-- With support function that knows it's int4eq, we get a different plan
ALTER FUNCTION my_int_eq(int, int) SUPPORT test_support_func;
EXPLAIN (COSTS OFF)
SELECT * FROM tenk1 a JOIN tenk1 b ON a.unique1 = b.unique1
WHERE my_int_eq(a.unique2, 42);
-- Also test non-default rowcount estimate
CREATE FUNCTION my_gen_series(int, int) RETURNS SETOF integer
LANGUAGE internal STRICT IMMUTABLE PARALLEL SAFE
AS $$generate_series_int4$$
SUPPORT test_support_func;
EXPLAIN (COSTS OFF)
SELECT * FROM tenk1 a JOIN my_gen_series(1,1000) g ON a.unique1 = g;
EXPLAIN (COSTS OFF)
SELECT * FROM tenk1 a JOIN my_gen_series(1,10) g ON a.unique1 = g;

View File

@ -498,9 +498,9 @@ select * from int4_tbl where
--
explain (verbose, costs off)
select * from int4_tbl o where (f1, f1) in
(select f1, generate_series(1,2) / 10 g from int4_tbl i group by f1);
(select f1, generate_series(1,50) / 10 g from int4_tbl i group by f1);
select * from int4_tbl o where (f1, f1) in
(select f1, generate_series(1,2) / 10 g from int4_tbl i group by f1);
(select f1, generate_series(1,50) / 10 g from int4_tbl i group by f1);
--
-- check for over-optimization of whole-row Var referencing an Append plan