1
0
mirror of https://github.com/postgres/postgres.git synced 2025-11-24 00:23:06 +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:
Tom Lane
2025-11-22 19:33:34 -05:00
parent c0bc9af151
commit b140c8d7a3
9 changed files with 511 additions and 171 deletions

View File

@@ -4159,6 +4159,31 @@ supportfn(internal) returns internal
expression and an actual execution of the target function.
</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>
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

View File

@@ -1066,13 +1066,15 @@ pull_up_sublinks_qual_recurse(PlannerInfo *root, Node *node,
/*
* preprocess_function_rtes
* 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
* RTE_SUBQUERY entry exposing the SELECT directly. 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.
* RTE_SUBQUERY entry exposing the SELECT directly. Other sorts of functions
* are also inline-able if they have a support function that can generate
* 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
* 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);
/* Check safety of expansion, and expand if possible */
funcquery = inline_set_returning_function(root, rte);
funcquery = inline_function_in_from(root, rte);
if (funcquery)
{
/* Successful expansion, convert the RTE to a subquery */

View File

@@ -82,7 +82,7 @@ typedef struct
int nargs;
List *args;
int sublevels_up;
} substitute_actual_srf_parameters_context;
} substitute_actual_parameters_in_from_context;
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,
substitute_actual_parameters_context *context);
static void sql_inline_error_callback(void *arg);
static Query *substitute_actual_srf_parameters(Query *expr,
int nargs, List *args);
static Node *substitute_actual_srf_parameters_mutator(Node *node,
substitute_actual_srf_parameters_context *context);
static Query *inline_sql_function_in_from(PlannerInfo *root,
RangeTblFunction *rtfunc,
FuncExpr *fexpr,
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);
@@ -5149,50 +5155,42 @@ evaluate_expr(Expr *expr, Oid result_type, int32 result_typmod,
/*
* inline_set_returning_function
* Attempt to "inline" a set-returning function in the FROM clause.
* inline_function_in_from
* Attempt to "inline" a function in the FROM clause.
*
* "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
* and return the substitute Query structure. Otherwise, return NULL.
* function that can be inlined, expand the function and return the
* substitute Query structure. Otherwise, return NULL.
*
* We assume that the RTE's expression has already been put through
* eval_const_expressions(), which among other things will take care of
* default arguments and named-argument notation.
*
* 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.
*/
Query *
inline_set_returning_function(PlannerInfo *root, RangeTblEntry *rte)
inline_function_in_from(PlannerInfo *root, RangeTblEntry *rte)
{
RangeTblFunction *rtfunc;
FuncExpr *fexpr;
Oid func_oid;
HeapTuple func_tuple;
Form_pg_proc funcform;
char *src;
Datum tmp;
bool isNull;
MemoryContext oldcxt;
MemoryContext mycxt;
Datum tmp;
char *src;
inline_error_callback_arg callback_arg;
ErrorContextCallback sqlerrcontext;
SQLFunctionParseInfoPtr pinfo;
TypeFuncClass functypclass;
TupleDesc rettupdesc;
List *raw_parsetree_list;
List *querytree_list;
Query *querytree;
Query *querytree = NULL;
Assert(rte->rtekind == RTE_FUNCTION);
/*
* It doesn't make a lot of sense for a SQL SRF to refer to itself in its
* own FROM clause, since that must cause infinite recursion at runtime.
* It will cause this code to recurse too, so check for stack overflow.
* (There's no need to do more.)
* Guard against infinite recursion during expansion by checking for stack
* overflow. (There's no need to do more.)
*/
check_stack_depth();
@@ -5211,14 +5209,6 @@ inline_set_returning_function(PlannerInfo *root, RangeTblEntry *rte)
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
* 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);
/*
* 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 the function SETs any configuration parameters, inlining would cause
* us to miss making those changes.
*/
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 ||
!heap_attisnull(func_tuple, Anum_pg_proc_proconfig, NULL))
if (!heap_attisnull(func_tuple, Anum_pg_proc_proconfig, NULL))
{
ReleaseSysCache(func_tuple);
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
* 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,
"inline_set_returning_function",
"inline_function_in_from",
ALLOCSET_DEFAULT_SIZES);
oldcxt = MemoryContextSwitchTo(mycxt);
@@ -5285,9 +5262,30 @@ inline_set_returning_function(PlannerInfo *root, RangeTblEntry *rte)
tmp = SysCacheGetAttrNotNull(PROCOID, func_tuple, Anum_pg_proc_prosrc);
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
* 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.prosrc = src;
@@ -5297,117 +5295,29 @@ inline_set_returning_function(PlannerInfo *root, RangeTblEntry *rte)
sqlerrcontext.previous = error_context_stack;
error_context_stack = &sqlerrcontext;
/* If we have prosqlbody, pay attention to that not prosrc */
tmp = SysCacheGetAttr(PROCOID,
func_tuple,
Anum_pg_proc_prosqlbody,
&isNull);
if (!isNull)
{
Node *n;
/*
* If SupportRequestInlineInFrom didn't work, try our built-in inlining
* mechanism.
*/
if (!querytree)
querytree = inline_sql_function_in_from(root, rtfunc, fexpr,
func_tuple, funcform, src);
n = stringToNode(TextDatumGetCString(tmp));
if (IsA(n, List))
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);
}
if (!querytree)
goto fail; /* no luck there either, fail */
/*
* 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.)
* The result had better be a SELECT Query.
*/
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)
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);
Assert(IsA(querytree, Query));
Assert(querytree->commandType == CMD_SELECT);
/*
* Looks good --- substitute parameters into the query.
*/
querytree = substitute_actual_srf_parameters(querytree,
funcform->pronargs,
fexpr->args);
querytree = substitute_actual_parameters_in_from(querytree,
funcform->pronargs,
fexpr->args);
/*
* Copy the modified query out of the temporary memory context, and clean
@@ -5451,6 +5361,173 @@ fail:
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
*
@@ -5458,23 +5535,23 @@ fail:
* that it needs its own code.
*/
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.args = args;
context.sublevels_up = 1;
return query_tree_mutator(expr,
substitute_actual_srf_parameters_mutator,
substitute_actual_parameters_in_from_mutator,
&context,
0);
}
static Node *
substitute_actual_srf_parameters_mutator(Node *node,
substitute_actual_srf_parameters_context *context)
substitute_actual_parameters_in_from_mutator(Node *node,
substitute_actual_parameters_in_from_context *context)
{
Node *result;
@@ -5484,7 +5561,7 @@ substitute_actual_srf_parameters_mutator(Node *node,
{
context->sublevels_up++;
result = (Node *) query_tree_mutator((Query *) node,
substitute_actual_srf_parameters_mutator,
substitute_actual_parameters_in_from_mutator,
context,
0);
context->sublevels_up--;
@@ -5509,7 +5586,7 @@ substitute_actual_srf_parameters_mutator(Node *node,
}
}
return expression_tree_mutator(node,
substitute_actual_srf_parameters_mutator,
substitute_actual_parameters_in_from_mutator,
context);
}

View File

@@ -39,6 +39,8 @@ typedef struct PlannerInfo PlannerInfo; /* avoid including pathnodes.h here */
typedef struct IndexOptInfo IndexOptInfo;
typedef struct SpecialJoinInfo SpecialJoinInfo;
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
@@ -69,6 +71,34 @@ typedef struct SupportRequestSimplify
FuncExpr *fcall; /* Function call to be simplified */
} 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
* selectivity estimate for a function appearing at top level of a WHERE

View File

@@ -50,8 +50,8 @@ extern int NumRelids(PlannerInfo *root, Node *clause);
extern void CommuteOpExpr(OpExpr *clause);
extern Query *inline_set_returning_function(PlannerInfo *root,
RangeTblEntry *rte);
extern Query *inline_function_in_from(PlannerInfo *root,
RangeTblEntry *rte);
extern Bitmapset *pull_paramids(Expr *expr);

View File

@@ -808,6 +808,56 @@ false, true, false, true);
Function Scan on generate_series g (cost=N..N rows=1000 width=N)
(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
SELECT count(*) > 0 AS ok FROM pg_control_checkpoint();
ok

View File

@@ -28,6 +28,7 @@
#include "commands/sequence.h"
#include "commands/trigger.h"
#include "executor/executor.h"
#include "executor/functions.h"
#include "executor/spi.h"
#include "funcapi.h"
#include "mb/pg_wchar.h"
@@ -39,6 +40,7 @@
#include "port/atomics.h"
#include "postmaster/postmaster.h" /* for MAX_BACKENDS */
#include "storage/spin.h"
#include "tcop/tcopprot.h"
#include "utils/array.h"
#include "utils/builtins.h"
#include "utils/geo_decls.h"
@@ -803,6 +805,125 @@ test_support_func(PG_FUNCTION_ARGS)
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);
Datum
test_opclass_options_func(PG_FUNCTION_ARGS)

View File

@@ -360,6 +360,40 @@ SELECT explain_mask_costs($$
SELECT * FROM generate_series(25.0, 2.0, 0.0) g(s);$$,
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
SELECT count(*) > 0 AS ok FROM pg_control_checkpoint();
SELECT count(*) > 0 AS ok FROM pg_control_init();

View File

@@ -2917,6 +2917,7 @@ SubscriptionRelState
SummarizerReadLocalXLogPrivate
SupportRequestCost
SupportRequestIndexCondition
SupportRequestInlineInFrom
SupportRequestModifyInPlace
SupportRequestOptimizeWindowClause
SupportRequestRows
@@ -4140,7 +4141,7 @@ storeRes_func
stream_stop_callback
string
substitute_actual_parameters_context
substitute_actual_srf_parameters_context
substitute_actual_parameters_in_from_context
substitute_grouped_columns_context
substitute_phv_relids_context
subxids_array_status