mirror of
https://github.com/postgres/postgres.git
synced 2025-11-25 12:03:53 +03:00
Add SupportRequestInlineInFrom planner support request.
This request allows a support function to replace a function call appearing in FROM (typically a set-returning function) with an equivalent SELECT subquery. The subquery will then be subject to the planner's usual optimizations, potentially allowing a much better plan to be generated. While the planner has long done this automatically for simple SQL-language functions, it's now possible for extensions to do it for functions outside that group. Notably, this could be useful for functions that are presently implemented in PL/pgSQL and work by generating and then EXECUTE'ing a SQL query. Author: Paul A Jungwirth <pj@illuminatedcomputing.com> Reviewed-by: Tom Lane <tgl@sss.pgh.pa.us> Discussion: https://postgr.es/m/09de6afa-c33d-4d94-a5cb-afc6cea0d2bb@illuminatedcomputing.com
This commit is contained in:
@@ -4159,6 +4159,31 @@ supportfn(internal) returns internal
|
|||||||
expression and an actual execution of the target function.
|
expression and an actual execution of the target function.
|
||||||
</para>
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
<literal>SupportRequestSimplify</literal> is not used
|
||||||
|
for <link linkend="queries-tablefunctions">set-returning
|
||||||
|
functions</link>. Instead, support functions can implement
|
||||||
|
the <literal>SupportRequestInlineInFrom</literal> request to expand
|
||||||
|
function calls appearing in the <literal>FROM</literal> clause of a
|
||||||
|
query. (It's also allowed to support this request for
|
||||||
|
non-set-returning functions, although
|
||||||
|
typically <literal>SupportRequestSimplify</literal> would serve as
|
||||||
|
well.) For this request type, a successful result must be
|
||||||
|
a <literal>SELECT</literal> Query tree, which will replace
|
||||||
|
the <literal>FROM</literal> item as though a sub-select had been
|
||||||
|
written instead. The Query tree must appear as it would after parse
|
||||||
|
analysis and rewrite processing. One way to ensure that that's true
|
||||||
|
is to build a SQL string then feed it
|
||||||
|
through <function>pg_parse_query</function>
|
||||||
|
and <function>pg_analyze_and_rewrite</function>, or related
|
||||||
|
functions. <literal>PARAM_EXTERN</literal> <structname>Param</structname>
|
||||||
|
nodes can appear within the Query to represent the function's
|
||||||
|
arguments; they will be replaced by the actual argument expressions.
|
||||||
|
As with <literal>SupportRequestSimplify</literal>, it is the support
|
||||||
|
function's responsibility that the replacement Query be equivalent to
|
||||||
|
normal execution of the target function.
|
||||||
|
</para>
|
||||||
|
|
||||||
<para>
|
<para>
|
||||||
For target functions that return <type>boolean</type>, it is often useful to estimate
|
For target functions that return <type>boolean</type>, it is often useful to estimate
|
||||||
the fraction of rows that will be selected by a <literal>WHERE</literal> clause using that
|
the fraction of rows that will be selected by a <literal>WHERE</literal> clause using that
|
||||||
|
|||||||
@@ -1066,13 +1066,15 @@ pull_up_sublinks_qual_recurse(PlannerInfo *root, Node *node,
|
|||||||
/*
|
/*
|
||||||
* preprocess_function_rtes
|
* preprocess_function_rtes
|
||||||
* Constant-simplify any FUNCTION RTEs in the FROM clause, and then
|
* Constant-simplify any FUNCTION RTEs in the FROM clause, and then
|
||||||
* attempt to "inline" any that are set-returning functions.
|
* attempt to "inline" any that can be converted to simple subqueries.
|
||||||
*
|
*
|
||||||
* If an RTE_FUNCTION rtable entry invokes a set-returning function that
|
* If an RTE_FUNCTION rtable entry invokes a set-returning SQL function that
|
||||||
* contains just a simple SELECT, we can convert the rtable entry to an
|
* contains just a simple SELECT, we can convert the rtable entry to an
|
||||||
* RTE_SUBQUERY entry exposing the SELECT directly. This is especially
|
* RTE_SUBQUERY entry exposing the SELECT directly. Other sorts of functions
|
||||||
* useful if the subquery can then be "pulled up" for further optimization,
|
* are also inline-able if they have a support function that can generate
|
||||||
* but we do it even if not, to reduce executor overhead.
|
* the replacement sub-Query. This is especially useful if the subquery can
|
||||||
|
* then be "pulled up" for further optimization, but we do it even if not,
|
||||||
|
* to reduce executor overhead.
|
||||||
*
|
*
|
||||||
* This has to be done before we have started to do any optimization of
|
* This has to be done before we have started to do any optimization of
|
||||||
* subqueries, else any such steps wouldn't get applied to subqueries
|
* subqueries, else any such steps wouldn't get applied to subqueries
|
||||||
@@ -1107,7 +1109,7 @@ preprocess_function_rtes(PlannerInfo *root)
|
|||||||
eval_const_expressions(root, (Node *) rte->functions);
|
eval_const_expressions(root, (Node *) rte->functions);
|
||||||
|
|
||||||
/* Check safety of expansion, and expand if possible */
|
/* Check safety of expansion, and expand if possible */
|
||||||
funcquery = inline_set_returning_function(root, rte);
|
funcquery = inline_function_in_from(root, rte);
|
||||||
if (funcquery)
|
if (funcquery)
|
||||||
{
|
{
|
||||||
/* Successful expansion, convert the RTE to a subquery */
|
/* Successful expansion, convert the RTE to a subquery */
|
||||||
|
|||||||
@@ -82,7 +82,7 @@ typedef struct
|
|||||||
int nargs;
|
int nargs;
|
||||||
List *args;
|
List *args;
|
||||||
int sublevels_up;
|
int sublevels_up;
|
||||||
} substitute_actual_srf_parameters_context;
|
} substitute_actual_parameters_in_from_context;
|
||||||
|
|
||||||
typedef struct
|
typedef struct
|
||||||
{
|
{
|
||||||
@@ -154,10 +154,16 @@ static Node *substitute_actual_parameters(Node *expr, int nargs, List *args,
|
|||||||
static Node *substitute_actual_parameters_mutator(Node *node,
|
static Node *substitute_actual_parameters_mutator(Node *node,
|
||||||
substitute_actual_parameters_context *context);
|
substitute_actual_parameters_context *context);
|
||||||
static void sql_inline_error_callback(void *arg);
|
static void sql_inline_error_callback(void *arg);
|
||||||
static Query *substitute_actual_srf_parameters(Query *expr,
|
static Query *inline_sql_function_in_from(PlannerInfo *root,
|
||||||
int nargs, List *args);
|
RangeTblFunction *rtfunc,
|
||||||
static Node *substitute_actual_srf_parameters_mutator(Node *node,
|
FuncExpr *fexpr,
|
||||||
substitute_actual_srf_parameters_context *context);
|
HeapTuple func_tuple,
|
||||||
|
Form_pg_proc funcform,
|
||||||
|
const char *src);
|
||||||
|
static Query *substitute_actual_parameters_in_from(Query *expr,
|
||||||
|
int nargs, List *args);
|
||||||
|
static Node *substitute_actual_parameters_in_from_mutator(Node *node,
|
||||||
|
substitute_actual_parameters_in_from_context *context);
|
||||||
static bool pull_paramids_walker(Node *node, Bitmapset **context);
|
static bool pull_paramids_walker(Node *node, Bitmapset **context);
|
||||||
|
|
||||||
|
|
||||||
@@ -5149,50 +5155,42 @@ evaluate_expr(Expr *expr, Oid result_type, int32 result_typmod,
|
|||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* inline_set_returning_function
|
* inline_function_in_from
|
||||||
* Attempt to "inline" a set-returning function in the FROM clause.
|
* Attempt to "inline" a function in the FROM clause.
|
||||||
*
|
*
|
||||||
* "rte" is an RTE_FUNCTION rangetable entry. If it represents a call of a
|
* "rte" is an RTE_FUNCTION rangetable entry. If it represents a call of a
|
||||||
* set-returning SQL function that can safely be inlined, expand the function
|
* function that can be inlined, expand the function and return the
|
||||||
* and return the substitute Query structure. Otherwise, return NULL.
|
* substitute Query structure. Otherwise, return NULL.
|
||||||
*
|
*
|
||||||
* We assume that the RTE's expression has already been put through
|
* We assume that the RTE's expression has already been put through
|
||||||
* eval_const_expressions(), which among other things will take care of
|
* eval_const_expressions(), which among other things will take care of
|
||||||
* default arguments and named-argument notation.
|
* default arguments and named-argument notation.
|
||||||
*
|
*
|
||||||
* This has a good deal of similarity to inline_function(), but that's
|
* This has a good deal of similarity to inline_function(), but that's
|
||||||
* for the non-set-returning case, and there are enough differences to
|
* for the general-expression case, and there are enough differences to
|
||||||
* justify separate functions.
|
* justify separate functions.
|
||||||
*/
|
*/
|
||||||
Query *
|
Query *
|
||||||
inline_set_returning_function(PlannerInfo *root, RangeTblEntry *rte)
|
inline_function_in_from(PlannerInfo *root, RangeTblEntry *rte)
|
||||||
{
|
{
|
||||||
RangeTblFunction *rtfunc;
|
RangeTblFunction *rtfunc;
|
||||||
FuncExpr *fexpr;
|
FuncExpr *fexpr;
|
||||||
Oid func_oid;
|
Oid func_oid;
|
||||||
HeapTuple func_tuple;
|
HeapTuple func_tuple;
|
||||||
Form_pg_proc funcform;
|
Form_pg_proc funcform;
|
||||||
char *src;
|
|
||||||
Datum tmp;
|
|
||||||
bool isNull;
|
|
||||||
MemoryContext oldcxt;
|
MemoryContext oldcxt;
|
||||||
MemoryContext mycxt;
|
MemoryContext mycxt;
|
||||||
|
Datum tmp;
|
||||||
|
char *src;
|
||||||
inline_error_callback_arg callback_arg;
|
inline_error_callback_arg callback_arg;
|
||||||
ErrorContextCallback sqlerrcontext;
|
ErrorContextCallback sqlerrcontext;
|
||||||
SQLFunctionParseInfoPtr pinfo;
|
Query *querytree = NULL;
|
||||||
TypeFuncClass functypclass;
|
|
||||||
TupleDesc rettupdesc;
|
|
||||||
List *raw_parsetree_list;
|
|
||||||
List *querytree_list;
|
|
||||||
Query *querytree;
|
|
||||||
|
|
||||||
Assert(rte->rtekind == RTE_FUNCTION);
|
Assert(rte->rtekind == RTE_FUNCTION);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* It doesn't make a lot of sense for a SQL SRF to refer to itself in its
|
* Guard against infinite recursion during expansion by checking for stack
|
||||||
* own FROM clause, since that must cause infinite recursion at runtime.
|
* overflow. (There's no need to do more.)
|
||||||
* It will cause this code to recurse too, so check for stack overflow.
|
|
||||||
* (There's no need to do more.)
|
|
||||||
*/
|
*/
|
||||||
check_stack_depth();
|
check_stack_depth();
|
||||||
|
|
||||||
@@ -5211,14 +5209,6 @@ inline_set_returning_function(PlannerInfo *root, RangeTblEntry *rte)
|
|||||||
|
|
||||||
func_oid = fexpr->funcid;
|
func_oid = fexpr->funcid;
|
||||||
|
|
||||||
/*
|
|
||||||
* The function must be declared to return a set, else inlining would
|
|
||||||
* change the results if the contained SELECT didn't return exactly one
|
|
||||||
* row.
|
|
||||||
*/
|
|
||||||
if (!fexpr->funcretset)
|
|
||||||
return NULL;
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Refuse to inline if the arguments contain any volatile functions or
|
* Refuse to inline if the arguments contain any volatile functions or
|
||||||
* sub-selects. Volatile functions are rejected because inlining may
|
* sub-selects. Volatile functions are rejected because inlining may
|
||||||
@@ -5249,24 +5239,10 @@ inline_set_returning_function(PlannerInfo *root, RangeTblEntry *rte)
|
|||||||
funcform = (Form_pg_proc) GETSTRUCT(func_tuple);
|
funcform = (Form_pg_proc) GETSTRUCT(func_tuple);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Forget it if the function is not SQL-language or has other showstopper
|
* If the function SETs any configuration parameters, inlining would cause
|
||||||
* properties. In particular it mustn't be declared STRICT, since we
|
* us to miss making those changes.
|
||||||
* couldn't enforce that. It also mustn't be VOLATILE, because that is
|
|
||||||
* supposed to cause it to be executed with its own snapshot, rather than
|
|
||||||
* sharing the snapshot of the calling query. We also disallow returning
|
|
||||||
* SETOF VOID, because inlining would result in exposing the actual result
|
|
||||||
* of the function's last SELECT, which should not happen in that case.
|
|
||||||
* (Rechecking prokind, proretset, and pronargs is just paranoia.)
|
|
||||||
*/
|
*/
|
||||||
if (funcform->prolang != SQLlanguageId ||
|
if (!heap_attisnull(func_tuple, Anum_pg_proc_proconfig, NULL))
|
||||||
funcform->prokind != PROKIND_FUNCTION ||
|
|
||||||
funcform->proisstrict ||
|
|
||||||
funcform->provolatile == PROVOLATILE_VOLATILE ||
|
|
||||||
funcform->prorettype == VOIDOID ||
|
|
||||||
funcform->prosecdef ||
|
|
||||||
!funcform->proretset ||
|
|
||||||
list_length(fexpr->args) != funcform->pronargs ||
|
|
||||||
!heap_attisnull(func_tuple, Anum_pg_proc_proconfig, NULL))
|
|
||||||
{
|
{
|
||||||
ReleaseSysCache(func_tuple);
|
ReleaseSysCache(func_tuple);
|
||||||
return NULL;
|
return NULL;
|
||||||
@@ -5274,10 +5250,11 @@ inline_set_returning_function(PlannerInfo *root, RangeTblEntry *rte)
|
|||||||
|
|
||||||
/*
|
/*
|
||||||
* Make a temporary memory context, so that we don't leak all the stuff
|
* Make a temporary memory context, so that we don't leak all the stuff
|
||||||
* that parsing might create.
|
* that parsing and rewriting might create. If we succeed, we'll copy
|
||||||
|
* just the finished query tree back up to the caller's context.
|
||||||
*/
|
*/
|
||||||
mycxt = AllocSetContextCreate(CurrentMemoryContext,
|
mycxt = AllocSetContextCreate(CurrentMemoryContext,
|
||||||
"inline_set_returning_function",
|
"inline_function_in_from",
|
||||||
ALLOCSET_DEFAULT_SIZES);
|
ALLOCSET_DEFAULT_SIZES);
|
||||||
oldcxt = MemoryContextSwitchTo(mycxt);
|
oldcxt = MemoryContextSwitchTo(mycxt);
|
||||||
|
|
||||||
@@ -5285,9 +5262,30 @@ inline_set_returning_function(PlannerInfo *root, RangeTblEntry *rte)
|
|||||||
tmp = SysCacheGetAttrNotNull(PROCOID, func_tuple, Anum_pg_proc_prosrc);
|
tmp = SysCacheGetAttrNotNull(PROCOID, func_tuple, Anum_pg_proc_prosrc);
|
||||||
src = TextDatumGetCString(tmp);
|
src = TextDatumGetCString(tmp);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* If the function has an attached support function that can handle
|
||||||
|
* SupportRequestInlineInFrom, then attempt to inline with that.
|
||||||
|
*/
|
||||||
|
if (funcform->prosupport)
|
||||||
|
{
|
||||||
|
SupportRequestInlineInFrom req;
|
||||||
|
|
||||||
|
req.type = T_SupportRequestInlineInFrom;
|
||||||
|
req.root = root;
|
||||||
|
req.rtfunc = rtfunc;
|
||||||
|
req.proc = func_tuple;
|
||||||
|
|
||||||
|
querytree = (Query *)
|
||||||
|
DatumGetPointer(OidFunctionCall1(funcform->prosupport,
|
||||||
|
PointerGetDatum(&req)));
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Setup error traceback support for ereport(). This is so that we can
|
* Setup error traceback support for ereport(). This is so that we can
|
||||||
* finger the function that bad information came from.
|
* finger the function that bad information came from. We don't install
|
||||||
|
* this while running the support function, since it'd be likely to do the
|
||||||
|
* wrong thing: any parse errors reported during that are very likely not
|
||||||
|
* against the raw function source text.
|
||||||
*/
|
*/
|
||||||
callback_arg.proname = NameStr(funcform->proname);
|
callback_arg.proname = NameStr(funcform->proname);
|
||||||
callback_arg.prosrc = src;
|
callback_arg.prosrc = src;
|
||||||
@@ -5297,117 +5295,29 @@ inline_set_returning_function(PlannerInfo *root, RangeTblEntry *rte)
|
|||||||
sqlerrcontext.previous = error_context_stack;
|
sqlerrcontext.previous = error_context_stack;
|
||||||
error_context_stack = &sqlerrcontext;
|
error_context_stack = &sqlerrcontext;
|
||||||
|
|
||||||
/* If we have prosqlbody, pay attention to that not prosrc */
|
/*
|
||||||
tmp = SysCacheGetAttr(PROCOID,
|
* If SupportRequestInlineInFrom didn't work, try our built-in inlining
|
||||||
func_tuple,
|
* mechanism.
|
||||||
Anum_pg_proc_prosqlbody,
|
*/
|
||||||
&isNull);
|
if (!querytree)
|
||||||
if (!isNull)
|
querytree = inline_sql_function_in_from(root, rtfunc, fexpr,
|
||||||
{
|
func_tuple, funcform, src);
|
||||||
Node *n;
|
|
||||||
|
|
||||||
n = stringToNode(TextDatumGetCString(tmp));
|
if (!querytree)
|
||||||
if (IsA(n, List))
|
goto fail; /* no luck there either, fail */
|
||||||
querytree_list = linitial_node(List, castNode(List, n));
|
|
||||||
else
|
|
||||||
querytree_list = list_make1(n);
|
|
||||||
if (list_length(querytree_list) != 1)
|
|
||||||
goto fail;
|
|
||||||
querytree = linitial(querytree_list);
|
|
||||||
|
|
||||||
/* Acquire necessary locks, then apply rewriter. */
|
|
||||||
AcquireRewriteLocks(querytree, true, false);
|
|
||||||
querytree_list = pg_rewrite_query(querytree);
|
|
||||||
if (list_length(querytree_list) != 1)
|
|
||||||
goto fail;
|
|
||||||
querytree = linitial(querytree_list);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
/*
|
|
||||||
* Set up to handle parameters while parsing the function body. We
|
|
||||||
* can use the FuncExpr just created as the input for
|
|
||||||
* prepare_sql_fn_parse_info.
|
|
||||||
*/
|
|
||||||
pinfo = prepare_sql_fn_parse_info(func_tuple,
|
|
||||||
(Node *) fexpr,
|
|
||||||
fexpr->inputcollid);
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Parse, analyze, and rewrite (unlike inline_function(), we can't
|
|
||||||
* skip rewriting here). We can fail as soon as we find more than one
|
|
||||||
* query, though.
|
|
||||||
*/
|
|
||||||
raw_parsetree_list = pg_parse_query(src);
|
|
||||||
if (list_length(raw_parsetree_list) != 1)
|
|
||||||
goto fail;
|
|
||||||
|
|
||||||
querytree_list = pg_analyze_and_rewrite_withcb(linitial(raw_parsetree_list),
|
|
||||||
src,
|
|
||||||
(ParserSetupHook) sql_fn_parser_setup,
|
|
||||||
pinfo, NULL);
|
|
||||||
if (list_length(querytree_list) != 1)
|
|
||||||
goto fail;
|
|
||||||
querytree = linitial(querytree_list);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Also resolve the actual function result tupdesc, if composite. If we
|
* The result had better be a SELECT Query.
|
||||||
* have a coldeflist, believe that; otherwise use get_expr_result_type.
|
|
||||||
* (This logic should match ExecInitFunctionScan.)
|
|
||||||
*/
|
*/
|
||||||
if (rtfunc->funccolnames != NIL)
|
Assert(IsA(querytree, Query));
|
||||||
{
|
Assert(querytree->commandType == CMD_SELECT);
|
||||||
functypclass = TYPEFUNC_RECORD;
|
|
||||||
rettupdesc = BuildDescFromLists(rtfunc->funccolnames,
|
|
||||||
rtfunc->funccoltypes,
|
|
||||||
rtfunc->funccoltypmods,
|
|
||||||
rtfunc->funccolcollations);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
functypclass = get_expr_result_type((Node *) fexpr, NULL, &rettupdesc);
|
|
||||||
|
|
||||||
/*
|
|
||||||
* The single command must be a plain SELECT.
|
|
||||||
*/
|
|
||||||
if (!IsA(querytree, Query) ||
|
|
||||||
querytree->commandType != CMD_SELECT)
|
|
||||||
goto fail;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Make sure the function (still) returns what it's declared to. This
|
|
||||||
* will raise an error if wrong, but that's okay since the function would
|
|
||||||
* fail at runtime anyway. Note that check_sql_fn_retval will also insert
|
|
||||||
* coercions if needed to make the tlist expression(s) match the declared
|
|
||||||
* type of the function. We also ask it to insert dummy NULL columns for
|
|
||||||
* any dropped columns in rettupdesc, so that the elements of the modified
|
|
||||||
* tlist match up to the attribute numbers.
|
|
||||||
*
|
|
||||||
* If the function returns a composite type, don't inline unless the check
|
|
||||||
* shows it's returning a whole tuple result; otherwise what it's
|
|
||||||
* returning is a single composite column which is not what we need.
|
|
||||||
*/
|
|
||||||
if (!check_sql_fn_retval(list_make1(querytree_list),
|
|
||||||
fexpr->funcresulttype, rettupdesc,
|
|
||||||
funcform->prokind,
|
|
||||||
true) &&
|
|
||||||
(functypclass == TYPEFUNC_COMPOSITE ||
|
|
||||||
functypclass == TYPEFUNC_COMPOSITE_DOMAIN ||
|
|
||||||
functypclass == TYPEFUNC_RECORD))
|
|
||||||
goto fail; /* reject not-whole-tuple-result cases */
|
|
||||||
|
|
||||||
/*
|
|
||||||
* check_sql_fn_retval might've inserted a projection step, but that's
|
|
||||||
* fine; just make sure we use the upper Query.
|
|
||||||
*/
|
|
||||||
querytree = linitial_node(Query, querytree_list);
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Looks good --- substitute parameters into the query.
|
* Looks good --- substitute parameters into the query.
|
||||||
*/
|
*/
|
||||||
querytree = substitute_actual_srf_parameters(querytree,
|
querytree = substitute_actual_parameters_in_from(querytree,
|
||||||
funcform->pronargs,
|
funcform->pronargs,
|
||||||
fexpr->args);
|
fexpr->args);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Copy the modified query out of the temporary memory context, and clean
|
* Copy the modified query out of the temporary memory context, and clean
|
||||||
@@ -5451,6 +5361,173 @@ fail:
|
|||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* inline_sql_function_in_from
|
||||||
|
*
|
||||||
|
* This implements inline_function_in_from for SQL-language functions.
|
||||||
|
* Returns NULL if the function couldn't be inlined.
|
||||||
|
*
|
||||||
|
* The division of labor between here and inline_function_in_from is based
|
||||||
|
* on the rule that inline_function_in_from should make all checks that are
|
||||||
|
* certain to be required in both this case and the support-function case.
|
||||||
|
* Support functions might also want to make checks analogous to the ones
|
||||||
|
* made here, but then again they might not, or they might just assume that
|
||||||
|
* the function they are attached to can validly be inlined.
|
||||||
|
*/
|
||||||
|
static Query *
|
||||||
|
inline_sql_function_in_from(PlannerInfo *root,
|
||||||
|
RangeTblFunction *rtfunc,
|
||||||
|
FuncExpr *fexpr,
|
||||||
|
HeapTuple func_tuple,
|
||||||
|
Form_pg_proc funcform,
|
||||||
|
const char *src)
|
||||||
|
{
|
||||||
|
Datum sqlbody;
|
||||||
|
bool isNull;
|
||||||
|
List *querytree_list;
|
||||||
|
Query *querytree;
|
||||||
|
TypeFuncClass functypclass;
|
||||||
|
TupleDesc rettupdesc;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The function must be declared to return a set, else inlining would
|
||||||
|
* change the results if the contained SELECT didn't return exactly one
|
||||||
|
* row.
|
||||||
|
*/
|
||||||
|
if (!fexpr->funcretset)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Forget it if the function is not SQL-language or has other showstopper
|
||||||
|
* properties. In particular it mustn't be declared STRICT, since we
|
||||||
|
* couldn't enforce that. It also mustn't be VOLATILE, because that is
|
||||||
|
* supposed to cause it to be executed with its own snapshot, rather than
|
||||||
|
* sharing the snapshot of the calling query. We also disallow returning
|
||||||
|
* SETOF VOID, because inlining would result in exposing the actual result
|
||||||
|
* of the function's last SELECT, which should not happen in that case.
|
||||||
|
* (Rechecking prokind, proretset, and pronargs is just paranoia.)
|
||||||
|
*/
|
||||||
|
if (funcform->prolang != SQLlanguageId ||
|
||||||
|
funcform->prokind != PROKIND_FUNCTION ||
|
||||||
|
funcform->proisstrict ||
|
||||||
|
funcform->provolatile == PROVOLATILE_VOLATILE ||
|
||||||
|
funcform->prorettype == VOIDOID ||
|
||||||
|
funcform->prosecdef ||
|
||||||
|
!funcform->proretset ||
|
||||||
|
list_length(fexpr->args) != funcform->pronargs)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
/* If we have prosqlbody, pay attention to that not prosrc */
|
||||||
|
sqlbody = SysCacheGetAttr(PROCOID,
|
||||||
|
func_tuple,
|
||||||
|
Anum_pg_proc_prosqlbody,
|
||||||
|
&isNull);
|
||||||
|
if (!isNull)
|
||||||
|
{
|
||||||
|
Node *n;
|
||||||
|
|
||||||
|
n = stringToNode(TextDatumGetCString(sqlbody));
|
||||||
|
if (IsA(n, List))
|
||||||
|
querytree_list = linitial_node(List, castNode(List, n));
|
||||||
|
else
|
||||||
|
querytree_list = list_make1(n);
|
||||||
|
if (list_length(querytree_list) != 1)
|
||||||
|
return NULL;
|
||||||
|
querytree = linitial(querytree_list);
|
||||||
|
|
||||||
|
/* Acquire necessary locks, then apply rewriter. */
|
||||||
|
AcquireRewriteLocks(querytree, true, false);
|
||||||
|
querytree_list = pg_rewrite_query(querytree);
|
||||||
|
if (list_length(querytree_list) != 1)
|
||||||
|
return NULL;
|
||||||
|
querytree = linitial(querytree_list);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
SQLFunctionParseInfoPtr pinfo;
|
||||||
|
List *raw_parsetree_list;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Set up to handle parameters while parsing the function body. We
|
||||||
|
* can use the FuncExpr just created as the input for
|
||||||
|
* prepare_sql_fn_parse_info.
|
||||||
|
*/
|
||||||
|
pinfo = prepare_sql_fn_parse_info(func_tuple,
|
||||||
|
(Node *) fexpr,
|
||||||
|
fexpr->inputcollid);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Parse, analyze, and rewrite (unlike inline_function(), we can't
|
||||||
|
* skip rewriting here). We can fail as soon as we find more than one
|
||||||
|
* query, though.
|
||||||
|
*/
|
||||||
|
raw_parsetree_list = pg_parse_query(src);
|
||||||
|
if (list_length(raw_parsetree_list) != 1)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
querytree_list = pg_analyze_and_rewrite_withcb(linitial(raw_parsetree_list),
|
||||||
|
src,
|
||||||
|
(ParserSetupHook) sql_fn_parser_setup,
|
||||||
|
pinfo, NULL);
|
||||||
|
if (list_length(querytree_list) != 1)
|
||||||
|
return NULL;
|
||||||
|
querytree = linitial(querytree_list);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Also resolve the actual function result tupdesc, if composite. If we
|
||||||
|
* have a coldeflist, believe that; otherwise use get_expr_result_type.
|
||||||
|
* (This logic should match ExecInitFunctionScan.)
|
||||||
|
*/
|
||||||
|
if (rtfunc->funccolnames != NIL)
|
||||||
|
{
|
||||||
|
functypclass = TYPEFUNC_RECORD;
|
||||||
|
rettupdesc = BuildDescFromLists(rtfunc->funccolnames,
|
||||||
|
rtfunc->funccoltypes,
|
||||||
|
rtfunc->funccoltypmods,
|
||||||
|
rtfunc->funccolcollations);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
functypclass = get_expr_result_type((Node *) fexpr, NULL, &rettupdesc);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The single command must be a plain SELECT.
|
||||||
|
*/
|
||||||
|
if (!IsA(querytree, Query) ||
|
||||||
|
querytree->commandType != CMD_SELECT)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Make sure the function (still) returns what it's declared to. This
|
||||||
|
* will raise an error if wrong, but that's okay since the function would
|
||||||
|
* fail at runtime anyway. Note that check_sql_fn_retval will also insert
|
||||||
|
* coercions if needed to make the tlist expression(s) match the declared
|
||||||
|
* type of the function. We also ask it to insert dummy NULL columns for
|
||||||
|
* any dropped columns in rettupdesc, so that the elements of the modified
|
||||||
|
* tlist match up to the attribute numbers.
|
||||||
|
*
|
||||||
|
* If the function returns a composite type, don't inline unless the check
|
||||||
|
* shows it's returning a whole tuple result; otherwise what it's
|
||||||
|
* returning is a single composite column which is not what we need.
|
||||||
|
*/
|
||||||
|
if (!check_sql_fn_retval(list_make1(querytree_list),
|
||||||
|
fexpr->funcresulttype, rettupdesc,
|
||||||
|
funcform->prokind,
|
||||||
|
true) &&
|
||||||
|
(functypclass == TYPEFUNC_COMPOSITE ||
|
||||||
|
functypclass == TYPEFUNC_COMPOSITE_DOMAIN ||
|
||||||
|
functypclass == TYPEFUNC_RECORD))
|
||||||
|
return NULL; /* reject not-whole-tuple-result cases */
|
||||||
|
|
||||||
|
/*
|
||||||
|
* check_sql_fn_retval might've inserted a projection step, but that's
|
||||||
|
* fine; just make sure we use the upper Query.
|
||||||
|
*/
|
||||||
|
querytree = linitial_node(Query, querytree_list);
|
||||||
|
|
||||||
|
return querytree;
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Replace Param nodes by appropriate actual parameters
|
* Replace Param nodes by appropriate actual parameters
|
||||||
*
|
*
|
||||||
@@ -5458,23 +5535,23 @@ fail:
|
|||||||
* that it needs its own code.
|
* that it needs its own code.
|
||||||
*/
|
*/
|
||||||
static Query *
|
static Query *
|
||||||
substitute_actual_srf_parameters(Query *expr, int nargs, List *args)
|
substitute_actual_parameters_in_from(Query *expr, int nargs, List *args)
|
||||||
{
|
{
|
||||||
substitute_actual_srf_parameters_context context;
|
substitute_actual_parameters_in_from_context context;
|
||||||
|
|
||||||
context.nargs = nargs;
|
context.nargs = nargs;
|
||||||
context.args = args;
|
context.args = args;
|
||||||
context.sublevels_up = 1;
|
context.sublevels_up = 1;
|
||||||
|
|
||||||
return query_tree_mutator(expr,
|
return query_tree_mutator(expr,
|
||||||
substitute_actual_srf_parameters_mutator,
|
substitute_actual_parameters_in_from_mutator,
|
||||||
&context,
|
&context,
|
||||||
0);
|
0);
|
||||||
}
|
}
|
||||||
|
|
||||||
static Node *
|
static Node *
|
||||||
substitute_actual_srf_parameters_mutator(Node *node,
|
substitute_actual_parameters_in_from_mutator(Node *node,
|
||||||
substitute_actual_srf_parameters_context *context)
|
substitute_actual_parameters_in_from_context *context)
|
||||||
{
|
{
|
||||||
Node *result;
|
Node *result;
|
||||||
|
|
||||||
@@ -5484,7 +5561,7 @@ substitute_actual_srf_parameters_mutator(Node *node,
|
|||||||
{
|
{
|
||||||
context->sublevels_up++;
|
context->sublevels_up++;
|
||||||
result = (Node *) query_tree_mutator((Query *) node,
|
result = (Node *) query_tree_mutator((Query *) node,
|
||||||
substitute_actual_srf_parameters_mutator,
|
substitute_actual_parameters_in_from_mutator,
|
||||||
context,
|
context,
|
||||||
0);
|
0);
|
||||||
context->sublevels_up--;
|
context->sublevels_up--;
|
||||||
@@ -5509,7 +5586,7 @@ substitute_actual_srf_parameters_mutator(Node *node,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
return expression_tree_mutator(node,
|
return expression_tree_mutator(node,
|
||||||
substitute_actual_srf_parameters_mutator,
|
substitute_actual_parameters_in_from_mutator,
|
||||||
context);
|
context);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -39,6 +39,8 @@ typedef struct PlannerInfo PlannerInfo; /* avoid including pathnodes.h here */
|
|||||||
typedef struct IndexOptInfo IndexOptInfo;
|
typedef struct IndexOptInfo IndexOptInfo;
|
||||||
typedef struct SpecialJoinInfo SpecialJoinInfo;
|
typedef struct SpecialJoinInfo SpecialJoinInfo;
|
||||||
typedef struct WindowClause WindowClause;
|
typedef struct WindowClause WindowClause;
|
||||||
|
typedef struct RangeTblFunction RangeTblFunction; /* ditto for parsenodes.h */
|
||||||
|
typedef struct HeapTupleData *HeapTuple; /* and htup.h too */
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* The Simplify request allows the support function to perform plan-time
|
* The Simplify request allows the support function to perform plan-time
|
||||||
@@ -69,6 +71,34 @@ typedef struct SupportRequestSimplify
|
|||||||
FuncExpr *fcall; /* Function call to be simplified */
|
FuncExpr *fcall; /* Function call to be simplified */
|
||||||
} SupportRequestSimplify;
|
} SupportRequestSimplify;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The InlineInFrom request allows the support function to perform plan-time
|
||||||
|
* simplification of a call to its target function that appears in FROM.
|
||||||
|
* The rules for this are sufficiently different from ordinary expressions
|
||||||
|
* that it's best to make this a separate request from Simplify.
|
||||||
|
*
|
||||||
|
* The planner's PlannerInfo "root" is typically not needed, but can be
|
||||||
|
* consulted if it's necessary to obtain info about Vars present in
|
||||||
|
* the given node tree. Beware that root could be NULL in some usages.
|
||||||
|
*
|
||||||
|
* "rtfunc" will be a RangeTblFunction node for the support function's target
|
||||||
|
* function. The call appeared alone (and without ORDINALITY) in FROM.
|
||||||
|
*
|
||||||
|
* "proc" will be the HeapTuple for the pg_proc row of the target function.
|
||||||
|
*
|
||||||
|
* The result should be a semantically-equivalent SELECT Query tree,
|
||||||
|
* or NULL if no simplification could be performed. The tree must have
|
||||||
|
* been passed through parse analysis and rewrite.
|
||||||
|
*/
|
||||||
|
typedef struct SupportRequestInlineInFrom
|
||||||
|
{
|
||||||
|
NodeTag type;
|
||||||
|
|
||||||
|
PlannerInfo *root; /* Planner's infrastructure */
|
||||||
|
RangeTblFunction *rtfunc; /* Function call to be simplified */
|
||||||
|
HeapTuple proc; /* Function definition from pg_proc */
|
||||||
|
} SupportRequestInlineInFrom;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* The Selectivity request allows the support function to provide a
|
* The Selectivity request allows the support function to provide a
|
||||||
* selectivity estimate for a function appearing at top level of a WHERE
|
* selectivity estimate for a function appearing at top level of a WHERE
|
||||||
|
|||||||
@@ -50,8 +50,8 @@ extern int NumRelids(PlannerInfo *root, Node *clause);
|
|||||||
|
|
||||||
extern void CommuteOpExpr(OpExpr *clause);
|
extern void CommuteOpExpr(OpExpr *clause);
|
||||||
|
|
||||||
extern Query *inline_set_returning_function(PlannerInfo *root,
|
extern Query *inline_function_in_from(PlannerInfo *root,
|
||||||
RangeTblEntry *rte);
|
RangeTblEntry *rte);
|
||||||
|
|
||||||
extern Bitmapset *pull_paramids(Expr *expr);
|
extern Bitmapset *pull_paramids(Expr *expr);
|
||||||
|
|
||||||
|
|||||||
@@ -808,6 +808,56 @@ false, true, false, true);
|
|||||||
Function Scan on generate_series g (cost=N..N rows=1000 width=N)
|
Function Scan on generate_series g (cost=N..N rows=1000 width=N)
|
||||||
(1 row)
|
(1 row)
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Test SupportRequestInlineInFrom request
|
||||||
|
--
|
||||||
|
CREATE FUNCTION test_inline_in_from_support_func(internal)
|
||||||
|
RETURNS internal
|
||||||
|
AS :'regresslib', 'test_inline_in_from_support_func'
|
||||||
|
LANGUAGE C STRICT;
|
||||||
|
CREATE FUNCTION foo_from_bar(colname TEXT, tablename TEXT, filter TEXT)
|
||||||
|
RETURNS SETOF TEXT
|
||||||
|
LANGUAGE plpgsql
|
||||||
|
AS $function$
|
||||||
|
DECLARE
|
||||||
|
sql TEXT;
|
||||||
|
BEGIN
|
||||||
|
sql := format('SELECT %I::text FROM %I', colname, tablename);
|
||||||
|
IF filter IS NOT NULL THEN
|
||||||
|
sql := CONCAT(sql, format(' WHERE %I::text = $1', colname));
|
||||||
|
END IF;
|
||||||
|
RETURN QUERY EXECUTE sql USING filter;
|
||||||
|
END;
|
||||||
|
$function$ STABLE;
|
||||||
|
ALTER FUNCTION foo_from_bar(TEXT, TEXT, TEXT)
|
||||||
|
SUPPORT test_inline_in_from_support_func;
|
||||||
|
SELECT * FROM foo_from_bar('f1', 'text_tbl', NULL);
|
||||||
|
foo_from_bar
|
||||||
|
-------------------
|
||||||
|
doh!
|
||||||
|
hi de ho neighbor
|
||||||
|
(2 rows)
|
||||||
|
|
||||||
|
SELECT * FROM foo_from_bar('f1', 'text_tbl', 'doh!');
|
||||||
|
foo_from_bar
|
||||||
|
--------------
|
||||||
|
doh!
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
EXPLAIN (COSTS OFF) SELECT * FROM foo_from_bar('f1', 'text_tbl', NULL);
|
||||||
|
QUERY PLAN
|
||||||
|
----------------------
|
||||||
|
Seq Scan on text_tbl
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
EXPLAIN (COSTS OFF) SELECT * FROM foo_from_bar('f1', 'text_tbl', 'doh!');
|
||||||
|
QUERY PLAN
|
||||||
|
-------------------------------
|
||||||
|
Seq Scan on text_tbl
|
||||||
|
Filter: (f1 = 'doh!'::text)
|
||||||
|
(2 rows)
|
||||||
|
|
||||||
|
DROP FUNCTION foo_from_bar;
|
||||||
-- Test functions for control data
|
-- Test functions for control data
|
||||||
SELECT count(*) > 0 AS ok FROM pg_control_checkpoint();
|
SELECT count(*) > 0 AS ok FROM pg_control_checkpoint();
|
||||||
ok
|
ok
|
||||||
|
|||||||
@@ -28,6 +28,7 @@
|
|||||||
#include "commands/sequence.h"
|
#include "commands/sequence.h"
|
||||||
#include "commands/trigger.h"
|
#include "commands/trigger.h"
|
||||||
#include "executor/executor.h"
|
#include "executor/executor.h"
|
||||||
|
#include "executor/functions.h"
|
||||||
#include "executor/spi.h"
|
#include "executor/spi.h"
|
||||||
#include "funcapi.h"
|
#include "funcapi.h"
|
||||||
#include "mb/pg_wchar.h"
|
#include "mb/pg_wchar.h"
|
||||||
@@ -39,6 +40,7 @@
|
|||||||
#include "port/atomics.h"
|
#include "port/atomics.h"
|
||||||
#include "postmaster/postmaster.h" /* for MAX_BACKENDS */
|
#include "postmaster/postmaster.h" /* for MAX_BACKENDS */
|
||||||
#include "storage/spin.h"
|
#include "storage/spin.h"
|
||||||
|
#include "tcop/tcopprot.h"
|
||||||
#include "utils/array.h"
|
#include "utils/array.h"
|
||||||
#include "utils/builtins.h"
|
#include "utils/builtins.h"
|
||||||
#include "utils/geo_decls.h"
|
#include "utils/geo_decls.h"
|
||||||
@@ -803,6 +805,125 @@ test_support_func(PG_FUNCTION_ARGS)
|
|||||||
PG_RETURN_POINTER(ret);
|
PG_RETURN_POINTER(ret);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
PG_FUNCTION_INFO_V1(test_inline_in_from_support_func);
|
||||||
|
Datum
|
||||||
|
test_inline_in_from_support_func(PG_FUNCTION_ARGS)
|
||||||
|
{
|
||||||
|
Node *rawreq = (Node *) PG_GETARG_POINTER(0);
|
||||||
|
|
||||||
|
if (IsA(rawreq, SupportRequestInlineInFrom))
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
* Assume that the target is foo_from_bar; that's safe as long as we
|
||||||
|
* don't attach this to any other function.
|
||||||
|
*/
|
||||||
|
SupportRequestInlineInFrom *req = (SupportRequestInlineInFrom *) rawreq;
|
||||||
|
StringInfoData sql;
|
||||||
|
RangeTblFunction *rtfunc = req->rtfunc;
|
||||||
|
FuncExpr *expr = (FuncExpr *) rtfunc->funcexpr;
|
||||||
|
Node *node;
|
||||||
|
Const *c;
|
||||||
|
char *colname;
|
||||||
|
char *tablename;
|
||||||
|
SQLFunctionParseInfoPtr pinfo;
|
||||||
|
List *raw_parsetree_list;
|
||||||
|
List *querytree_list;
|
||||||
|
Query *querytree;
|
||||||
|
|
||||||
|
if (list_length(expr->args) != 3)
|
||||||
|
{
|
||||||
|
ereport(WARNING, (errmsg("test_inline_in_from_support_func called with %d args but expected 3", list_length(expr->args))));
|
||||||
|
PG_RETURN_POINTER(NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Get colname */
|
||||||
|
node = linitial(expr->args);
|
||||||
|
if (!IsA(node, Const))
|
||||||
|
{
|
||||||
|
ereport(WARNING, (errmsg("test_inline_in_from_support_func called with non-Const parameters")));
|
||||||
|
PG_RETURN_POINTER(NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
c = (Const *) node;
|
||||||
|
if (c->consttype != TEXTOID || c->constisnull)
|
||||||
|
{
|
||||||
|
ereport(WARNING, (errmsg("test_inline_in_from_support_func called with non-TEXT parameters")));
|
||||||
|
PG_RETURN_POINTER(NULL);
|
||||||
|
}
|
||||||
|
colname = TextDatumGetCString(c->constvalue);
|
||||||
|
|
||||||
|
/* Get tablename */
|
||||||
|
node = lsecond(expr->args);
|
||||||
|
if (!IsA(node, Const))
|
||||||
|
{
|
||||||
|
ereport(WARNING, (errmsg("test_inline_in_from_support_func called with non-Const parameters")));
|
||||||
|
PG_RETURN_POINTER(NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
c = (Const *) node;
|
||||||
|
if (c->consttype != TEXTOID || c->constisnull)
|
||||||
|
{
|
||||||
|
ereport(WARNING, (errmsg("test_inline_in_from_support_func called with non-TEXT parameters")));
|
||||||
|
PG_RETURN_POINTER(NULL);
|
||||||
|
}
|
||||||
|
tablename = TextDatumGetCString(c->constvalue);
|
||||||
|
|
||||||
|
/* Begin constructing replacement SELECT query. */
|
||||||
|
initStringInfo(&sql);
|
||||||
|
appendStringInfo(&sql, "SELECT %s::text FROM %s",
|
||||||
|
quote_identifier(colname),
|
||||||
|
quote_identifier(tablename));
|
||||||
|
|
||||||
|
/* Add filter expression if present. */
|
||||||
|
node = lthird(expr->args);
|
||||||
|
if (!(IsA(node, Const) && ((Const *) node)->constisnull))
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
* We only filter if $3 is not constant-NULL. This is not a very
|
||||||
|
* exact implementation of the PL/pgSQL original, but it's close
|
||||||
|
* enough for demonstration purposes.
|
||||||
|
*/
|
||||||
|
appendStringInfo(&sql, " WHERE %s::text = $3",
|
||||||
|
quote_identifier(colname));
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Build a SQLFunctionParseInfo with the parameters of my function. */
|
||||||
|
pinfo = prepare_sql_fn_parse_info(req->proc,
|
||||||
|
(Node *) expr,
|
||||||
|
expr->inputcollid);
|
||||||
|
|
||||||
|
/* Parse the generated SQL. */
|
||||||
|
raw_parsetree_list = pg_parse_query(sql.data);
|
||||||
|
if (list_length(raw_parsetree_list) != 1)
|
||||||
|
{
|
||||||
|
ereport(WARNING, (errmsg("test_inline_in_from_support_func parsed to more than one node")));
|
||||||
|
PG_RETURN_POINTER(NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Analyze the parse tree as if it were a SQL-language body. */
|
||||||
|
querytree_list = pg_analyze_and_rewrite_withcb(linitial(raw_parsetree_list),
|
||||||
|
sql.data,
|
||||||
|
(ParserSetupHook) sql_fn_parser_setup,
|
||||||
|
pinfo, NULL);
|
||||||
|
if (list_length(querytree_list) != 1)
|
||||||
|
{
|
||||||
|
ereport(WARNING, (errmsg("test_inline_in_from_support_func rewrote to more than one node")));
|
||||||
|
PG_RETURN_POINTER(NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
querytree = linitial(querytree_list);
|
||||||
|
if (!IsA(querytree, Query))
|
||||||
|
{
|
||||||
|
ereport(WARNING, (errmsg("test_inline_in_from_support_func didn't parse to a Query")));
|
||||||
|
PG_RETURN_POINTER(NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
PG_RETURN_POINTER(querytree);
|
||||||
|
}
|
||||||
|
|
||||||
|
PG_RETURN_POINTER(NULL);
|
||||||
|
}
|
||||||
|
|
||||||
PG_FUNCTION_INFO_V1(test_opclass_options_func);
|
PG_FUNCTION_INFO_V1(test_opclass_options_func);
|
||||||
Datum
|
Datum
|
||||||
test_opclass_options_func(PG_FUNCTION_ARGS)
|
test_opclass_options_func(PG_FUNCTION_ARGS)
|
||||||
|
|||||||
@@ -360,6 +360,40 @@ SELECT explain_mask_costs($$
|
|||||||
SELECT * FROM generate_series(25.0, 2.0, 0.0) g(s);$$,
|
SELECT * FROM generate_series(25.0, 2.0, 0.0) g(s);$$,
|
||||||
false, true, false, true);
|
false, true, false, true);
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Test SupportRequestInlineInFrom request
|
||||||
|
--
|
||||||
|
|
||||||
|
CREATE FUNCTION test_inline_in_from_support_func(internal)
|
||||||
|
RETURNS internal
|
||||||
|
AS :'regresslib', 'test_inline_in_from_support_func'
|
||||||
|
LANGUAGE C STRICT;
|
||||||
|
|
||||||
|
CREATE FUNCTION foo_from_bar(colname TEXT, tablename TEXT, filter TEXT)
|
||||||
|
RETURNS SETOF TEXT
|
||||||
|
LANGUAGE plpgsql
|
||||||
|
AS $function$
|
||||||
|
DECLARE
|
||||||
|
sql TEXT;
|
||||||
|
BEGIN
|
||||||
|
sql := format('SELECT %I::text FROM %I', colname, tablename);
|
||||||
|
IF filter IS NOT NULL THEN
|
||||||
|
sql := CONCAT(sql, format(' WHERE %I::text = $1', colname));
|
||||||
|
END IF;
|
||||||
|
RETURN QUERY EXECUTE sql USING filter;
|
||||||
|
END;
|
||||||
|
$function$ STABLE;
|
||||||
|
|
||||||
|
ALTER FUNCTION foo_from_bar(TEXT, TEXT, TEXT)
|
||||||
|
SUPPORT test_inline_in_from_support_func;
|
||||||
|
|
||||||
|
SELECT * FROM foo_from_bar('f1', 'text_tbl', NULL);
|
||||||
|
SELECT * FROM foo_from_bar('f1', 'text_tbl', 'doh!');
|
||||||
|
EXPLAIN (COSTS OFF) SELECT * FROM foo_from_bar('f1', 'text_tbl', NULL);
|
||||||
|
EXPLAIN (COSTS OFF) SELECT * FROM foo_from_bar('f1', 'text_tbl', 'doh!');
|
||||||
|
|
||||||
|
DROP FUNCTION foo_from_bar;
|
||||||
|
|
||||||
-- Test functions for control data
|
-- Test functions for control data
|
||||||
SELECT count(*) > 0 AS ok FROM pg_control_checkpoint();
|
SELECT count(*) > 0 AS ok FROM pg_control_checkpoint();
|
||||||
SELECT count(*) > 0 AS ok FROM pg_control_init();
|
SELECT count(*) > 0 AS ok FROM pg_control_init();
|
||||||
|
|||||||
@@ -2917,6 +2917,7 @@ SubscriptionRelState
|
|||||||
SummarizerReadLocalXLogPrivate
|
SummarizerReadLocalXLogPrivate
|
||||||
SupportRequestCost
|
SupportRequestCost
|
||||||
SupportRequestIndexCondition
|
SupportRequestIndexCondition
|
||||||
|
SupportRequestInlineInFrom
|
||||||
SupportRequestModifyInPlace
|
SupportRequestModifyInPlace
|
||||||
SupportRequestOptimizeWindowClause
|
SupportRequestOptimizeWindowClause
|
||||||
SupportRequestRows
|
SupportRequestRows
|
||||||
@@ -4140,7 +4141,7 @@ storeRes_func
|
|||||||
stream_stop_callback
|
stream_stop_callback
|
||||||
string
|
string
|
||||||
substitute_actual_parameters_context
|
substitute_actual_parameters_context
|
||||||
substitute_actual_srf_parameters_context
|
substitute_actual_parameters_in_from_context
|
||||||
substitute_grouped_columns_context
|
substitute_grouped_columns_context
|
||||||
substitute_phv_relids_context
|
substitute_phv_relids_context
|
||||||
subxids_array_status
|
subxids_array_status
|
||||||
|
|||||||
Reference in New Issue
Block a user