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:
parent
1fb57af920
commit
a391ff3c3d
@ -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 */
|
||||
|
@ -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>
|
||||
|
@ -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
|
||||
*
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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;
|
||||
|
||||
|
39
src/backend/utils/cache/lsyscache.c
vendored
39
src/backend/utils/cache/lsyscache.c
vendored
@ -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;
|
||||
result = functup->prosupport;
|
||||
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;
|
||||
else
|
||||
return (RegProcedure) InvalidOid;
|
||||
}
|
||||
|
||||
/* ---------- RELATION CACHE ---------- */
|
||||
|
@ -53,6 +53,6 @@
|
||||
*/
|
||||
|
||||
/* yyyymmddN */
|
||||
#define CATALOG_VERSION_NO 201902091
|
||||
#define CATALOG_VERSION_NO 201902092
|
||||
|
||||
#endif
|
||||
|
@ -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',
|
||||
|
@ -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;
|
||||
|
||||
/*
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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 */
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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 */
|
||||
|
@ -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);
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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'';';
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
|
Loading…
x
Reference in New Issue
Block a user