Add SupportRequestInlineInFrom planner support request.
authorTom Lane <[email protected]>
Sun, 23 Nov 2025 00:33:34 +0000 (19:33 -0500)
committerTom Lane <[email protected]>
Sun, 23 Nov 2025 00:33:34 +0000 (19:33 -0500)
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 <[email protected]>
Reviewed-by: Tom Lane <[email protected]>
Discussion: https://postgr.es/m/09de6afa-c33d-4d94-a5cb-afc6cea0d2bb@illuminatedcomputing.com

doc/src/sgml/xfunc.sgml
src/backend/optimizer/prep/prepjointree.c
src/backend/optimizer/util/clauses.c
src/include/nodes/supportnodes.h
src/include/optimizer/clauses.h
src/test/regress/expected/misc_functions.out
src/test/regress/regress.c
src/test/regress/sql/misc_functions.sql
src/tools/pgindent/typedefs.list

index 55a99c0ff34fd3ace6095bb0059fe98a88492265..537ee6fa25408c69da0f6191cb2f0557743d832f 100644 (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
index 481d8011791bd51fbc0020bbc1dbc05328a4ffc5..7581695647daa98e63b2f32b86a49fc0eb40d9c6 100644 (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 */
index 81d768ff2a26684e9b7c95905e3e141ebe9d1db5..202ba8ed4bb939d8a8a4846d71637305a5957601 100644 (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,33 +5295,158 @@ inline_set_returning_function(PlannerInfo *root, RangeTblEntry *rte)
        sqlerrcontext.previous = error_context_stack;
        error_context_stack = &sqlerrcontext;
 
+       /*
+        * 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);
+
+       if (!querytree)
+               goto fail;                              /* no luck there either, fail */
+
+       /*
+        * The result had better be a SELECT Query.
+        */
+       Assert(IsA(querytree, Query));
+       Assert(querytree->commandType == CMD_SELECT);
+
+       /*
+        * Looks good --- substitute parameters into the query.
+        */
+       querytree = substitute_actual_parameters_in_from(querytree,
+                                                                                                        funcform->pronargs,
+                                                                                                        fexpr->args);
+
+       /*
+        * Copy the modified query out of the temporary memory context, and clean
+        * up.
+        */
+       MemoryContextSwitchTo(oldcxt);
+
+       querytree = copyObject(querytree);
+
+       MemoryContextDelete(mycxt);
+       error_context_stack = sqlerrcontext.previous;
+       ReleaseSysCache(func_tuple);
+
+       /*
+        * We don't have to fix collations here because the upper query is already
+        * parsed, ie, the collations in the RTE are what count.
+        */
+
+       /*
+        * Since there is now no trace of the function in the plan tree, we must
+        * explicitly record the plan's dependency on the function.
+        */
+       record_plan_function_dependency(root, func_oid);
+
+       /*
+        * We must also notice if the inserted query adds a dependency on the
+        * calling role due to RLS quals.
+        */
+       if (querytree->hasRowSecurity)
+               root->glob->dependsOnRole = true;
+
+       return querytree;
+
+       /* Here if func is not inlinable: release temp memory and return NULL */
+fail:
+       MemoryContextSwitchTo(oldcxt);
+       MemoryContextDelete(mycxt);
+       error_context_stack = sqlerrcontext.previous;
+       ReleaseSysCache(func_tuple);
+
+       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 */
-       tmp = SysCacheGetAttr(PROCOID,
-                                                 func_tuple,
-                                                 Anum_pg_proc_prosqlbody,
-                                                 &isNull);
+       sqlbody = SysCacheGetAttr(PROCOID,
+                                                         func_tuple,
+                                                         Anum_pg_proc_prosqlbody,
+                                                         &isNull);
        if (!isNull)
        {
                Node       *n;
 
-               n = stringToNode(TextDatumGetCString(tmp));
+               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)
-                       goto fail;
+                       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)
-                       goto fail;
+                       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
@@ -5340,14 +5463,14 @@ inline_set_returning_function(PlannerInfo *root, RangeTblEntry *rte)
                 */
                raw_parsetree_list = pg_parse_query(src);
                if (list_length(raw_parsetree_list) != 1)
-                       goto fail;
+                       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)
-                       goto fail;
+                       return NULL;
                querytree = linitial(querytree_list);
        }
 
@@ -5372,7 +5495,7 @@ inline_set_returning_function(PlannerInfo *root, RangeTblEntry *rte)
         */
        if (!IsA(querytree, Query) ||
                querytree->commandType != CMD_SELECT)
-               goto fail;
+               return NULL;
 
        /*
         * Make sure the function (still) returns what it's declared to.  This
@@ -5394,7 +5517,7 @@ inline_set_returning_function(PlannerInfo *root, RangeTblEntry *rte)
                (functypclass == TYPEFUNC_COMPOSITE ||
                 functypclass == TYPEFUNC_COMPOSITE_DOMAIN ||
                 functypclass == TYPEFUNC_RECORD))
-               goto fail;                              /* reject not-whole-tuple-result cases */
+               return NULL;                    /* reject not-whole-tuple-result cases */
 
        /*
         * check_sql_fn_retval might've inserted a projection step, but that's
@@ -5402,53 +5525,7 @@ inline_set_returning_function(PlannerInfo *root, RangeTblEntry *rte)
         */
        querytree = linitial_node(Query, querytree_list);
 
-       /*
-        * Looks good --- substitute parameters into the query.
-        */
-       querytree = substitute_actual_srf_parameters(querytree,
-                                                                                                funcform->pronargs,
-                                                                                                fexpr->args);
-
-       /*
-        * Copy the modified query out of the temporary memory context, and clean
-        * up.
-        */
-       MemoryContextSwitchTo(oldcxt);
-
-       querytree = copyObject(querytree);
-
-       MemoryContextDelete(mycxt);
-       error_context_stack = sqlerrcontext.previous;
-       ReleaseSysCache(func_tuple);
-
-       /*
-        * We don't have to fix collations here because the upper query is already
-        * parsed, ie, the collations in the RTE are what count.
-        */
-
-       /*
-        * Since there is now no trace of the function in the plan tree, we must
-        * explicitly record the plan's dependency on the function.
-        */
-       record_plan_function_dependency(root, func_oid);
-
-       /*
-        * We must also notice if the inserted query adds a dependency on the
-        * calling role due to RLS quals.
-        */
-       if (querytree->hasRowSecurity)
-               root->glob->dependsOnRole = true;
-
        return querytree;
-
-       /* Here if func is not inlinable: release temp memory and return NULL */
-fail:
-       MemoryContextSwitchTo(oldcxt);
-       MemoryContextDelete(mycxt);
-       error_context_stack = sqlerrcontext.previous;
-       ReleaseSysCache(func_tuple);
-
-       return NULL;
 }
 
 /*
@@ -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);
 }
 
index 7b623d540587429cba05a16ee3c34bcff84e546a..ea774c7ef6ae8f28984dcbbdf07e2644d4c8c468 100644 (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
index 0dffec00ede939a449386b3ffc2af90a25c53086..fc38eae5c5a0f7fd007006df3e140f0bdf9c58cc 100644 (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);
 
index e76e28b95ce68f9bb0c36a711662a14ef44a58fc..d7d965d884a1cd471c68d86ab555c8b480499f7c 100644 (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 
index a2db6080876e1789c40baec9ddb2366932a333bb..56cc0567b1c6595180c7c146532b63d6740b14c8 100644 (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)
index 220472d5ad19e7588fc9b04d7bfbd9bfcd859805..0fc20fbb6b40a10c5d8a5861b365c7840bf072ea 100644 (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();
index 27a4d131897874b8212d1d92252ed16378dec2de..0d1ea4ec63d63c95674b34bea2c8cb72cd2c3629 100644 (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