]> granicus.if.org Git - postgresql/commitdiff
Arrange to "inline" SQL functions that appear in a query's FROM clause,
authorTom Lane <tgl@sss.pgh.pa.us>
Tue, 18 Mar 2008 22:04:14 +0000 (22:04 +0000)
committerTom Lane <tgl@sss.pgh.pa.us>
Tue, 18 Mar 2008 22:04:14 +0000 (22:04 +0000)
are declared to return set, and consist of just a single SELECT.  We
can replace the FROM-item with a sub-SELECT and then optimize much as
if we were dealing with a view.  Patch from Richard Rowell, cleaned up
by me.

src/backend/catalog/pg_proc.c
src/backend/executor/functions.c
src/backend/optimizer/plan/planner.c
src/backend/optimizer/prep/prepjointree.c
src/backend/optimizer/util/clauses.c
src/include/executor/functions.h
src/include/optimizer/clauses.h
src/include/optimizer/prep.h

index a9feb2a56bdaba0b7bcaebe2faa4f25de386f712..f86b742cada7bd578e36db3b950cda53c08de03b 100644 (file)
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *       $PostgreSQL: pgsql/src/backend/catalog/pg_proc.c,v 1.148 2008/01/01 19:45:48 momjian Exp $
+ *       $PostgreSQL: pgsql/src/backend/catalog/pg_proc.c,v 1.149 2008/03/18 22:04:14 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -601,7 +601,8 @@ fmgr_sql_validator(PG_FUNCTION_ARGS)
                                                                                                  proc->proargtypes.values,
                                                                                                  proc->pronargs);
                        (void) check_sql_fn_retval(funcoid, proc->prorettype,
-                                                                          querytree_list, NULL);
+                                                                          querytree_list,
+                                                                          false, NULL);
                }
                else
                        querytree_list = pg_parse_query(prosrc);
index 0dec881d37e8ec447ae865059c58b7b213920a2e..da6976b62f794041dcccd4417eb1a3bd992e86da 100644 (file)
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *       $PostgreSQL: pgsql/src/backend/executor/functions.c,v 1.120 2008/01/01 19:45:49 momjian Exp $
+ *       $PostgreSQL: pgsql/src/backend/executor/functions.c,v 1.121 2008/03/18 22:04:14 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -20,6 +20,7 @@
 #include "commands/trigger.h"
 #include "executor/functions.h"
 #include "funcapi.h"
+#include "nodes/makefuncs.h"
 #include "parser/parse_coerce.h"
 #include "parser/parse_expr.h"
 #include "tcop/tcopprot.h"
@@ -269,6 +270,7 @@ init_sql_fcache(FmgrInfo *finfo)
        fcache->returnsTuple = check_sql_fn_retval(foid,
                                                                                           rettype,
                                                                                           queryTree_list,
+                                                                                          false,
                                                                                           &fcache->junkFilter);
 
        /* Finally, plan the queries */
@@ -856,7 +858,9 @@ ShutdownSQLFunction(Datum arg)
  *
  * The return value of a sql function is the value returned by
  * the final query in the function.  We do some ad-hoc type checking here
- * to be sure that the user is returning the type he claims.
+ * to be sure that the user is returning the type he claims.  There are
+ * also a couple of strange-looking features to assist callers in dealing
+ * with allowed special cases, such as binary-compatible result types.
  *
  * For a polymorphic function the passed rettype must be the actual resolved
  * output type of the function; we should never see a polymorphic pseudotype
@@ -868,6 +872,10 @@ ShutdownSQLFunction(Datum arg)
  * allow "SELECT rowtype_expression", this may be false even when the declared
  * function return type is a rowtype.
  *
+ * If insertRelabels is true, then binary-compatible cases are dealt with
+ * by actually inserting RelabelType nodes into the final SELECT; obviously
+ * the caller must pass a parsetree that it's okay to modify in this case.
+ *
  * If junkFilter isn't NULL, then *junkFilter is set to a JunkFilter defined
  * to convert the function's tuple result to the correct output tuple type.
  * Whenever the result value is false (ie, the function isn't returning a
@@ -875,6 +883,7 @@ ShutdownSQLFunction(Datum arg)
  */
 bool
 check_sql_fn_retval(Oid func_id, Oid rettype, List *queryTreeList,
+                                       bool insertRelabels,
                                        JunkFilter **junkFilter)
 {
        Query      *parse;
@@ -945,10 +954,12 @@ check_sql_fn_retval(Oid func_id, Oid rettype, List *queryTreeList,
                rettype == VOIDOID)
        {
                /*
-                * For scalar-type returns, the target list should have exactly one
-                * entry, and its type should agree with what the user declared. (As
-                * of Postgres 7.2, we accept binary-compatible types too.)
+                * For scalar-type returns, the target list must have exactly one
+                * non-junk entry, and its type must agree with what the user
+                * declared; except we allow binary-compatible types too.
                 */
+               TargetEntry *tle;
+
                if (tlistlen != 1)
                        ereport(ERROR,
                                        (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
@@ -956,7 +967,11 @@ check_sql_fn_retval(Oid func_id, Oid rettype, List *queryTreeList,
                                        format_type_be(rettype)),
                                 errdetail("Final SELECT must return exactly one column.")));
 
-               restype = exprType((Node *) ((TargetEntry *) linitial(tlist))->expr);
+               /* We assume here that non-junk TLEs must come first in tlists */
+               tle = (TargetEntry *) linitial(tlist);
+               Assert(!tle->resjunk);
+
+               restype = exprType((Node *) tle->expr);
                if (!IsBinaryCoercible(restype, rettype))
                        ereport(ERROR,
                                        (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
@@ -964,6 +979,11 @@ check_sql_fn_retval(Oid func_id, Oid rettype, List *queryTreeList,
                                        format_type_be(rettype)),
                                         errdetail("Actual return type is %s.",
                                                           format_type_be(restype))));
+               if (insertRelabels && restype != rettype)
+                       tle->expr = (Expr *) makeRelabelType(tle->expr,
+                                                                                                rettype,
+                                                                                                -1,
+                                                                                                COERCE_DONTCARE);
        }
        else if (fn_typtype == TYPTYPE_COMPOSITE || rettype == RECORDOID)
        {
@@ -977,14 +997,24 @@ check_sql_fn_retval(Oid func_id, Oid rettype, List *queryTreeList,
                 * If the target list is of length 1, and the type of the varnode in
                 * the target list matches the declared return type, this is okay.
                 * This can happen, for example, where the body of the function is
-                * 'SELECT func2()', where func2 has the same return type as the
-                * function that's calling it.
+                * 'SELECT func2()', where func2 has the same composite return type
+                * as the function that's calling it.
                 */
                if (tlistlen == 1)
                {
-                       restype = exprType((Node *) ((TargetEntry *) linitial(tlist))->expr);
+                       TargetEntry *tle = (TargetEntry *) linitial(tlist);
+
+                       Assert(!tle->resjunk);
+                       restype = exprType((Node *) tle->expr);
                        if (IsBinaryCoercible(restype, rettype))
+                       {
+                               if (insertRelabels && restype != rettype)
+                                       tle->expr = (Expr *) makeRelabelType(tle->expr,
+                                                                                                                rettype,
+                                                                                                                -1,
+                                                                                                                COERCE_DONTCARE);
                                return false;   /* NOT returning whole tuple */
+                       }
                }
 
                /* Is the rowtype fixed, or determined only at runtime? */
@@ -1043,6 +1073,11 @@ check_sql_fn_retval(Oid func_id, Oid rettype, List *queryTreeList,
                                                                   format_type_be(tletype),
                                                                   format_type_be(atttype),
                                                                   tuplogcols)));
+                       if (insertRelabels && tletype != atttype)
+                               tle->expr = (Expr *) makeRelabelType(tle->expr,
+                                                                                                        atttype,
+                                                                                                        -1,
+                                                                                                        COERCE_DONTCARE);
                }
 
                for (;;)
@@ -1070,14 +1105,6 @@ check_sql_fn_retval(Oid func_id, Oid rettype, List *queryTreeList,
                /* Report that we are returning entire tuple result */
                return true;
        }
-       else if (IsPolymorphicType(rettype))
-       {
-               /* This should already have been caught ... */
-               ereport(ERROR,
-                               (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
-                                errmsg("cannot determine result data type"),
-                                errdetail("A function returning a polymorphic type must have at least one polymorphic argument.")));
-       }
        else
                ereport(ERROR,
                                (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
index 0103ea826cabc33d7f076a3fbd3566b032b6f418..2f469bd924e1c56d5344f8edee5a1f49d2c8fd7d 100644 (file)
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *       $PostgreSQL: pgsql/src/backend/optimizer/plan/planner.c,v 1.226 2008/01/01 19:45:50 momjian Exp $
+ *       $PostgreSQL: pgsql/src/backend/optimizer/plan/planner.c,v 1.227 2008/03/18 22:04:14 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -253,13 +253,20 @@ subquery_planner(PlannerGlobal *glob, Query *parse,
        /*
         * Look for IN clauses at the top level of WHERE, and transform them into
         * joins.  Note that this step only handles IN clauses originally at top
-        * level of WHERE; if we pull up any subqueries in the next step, their
-        * INs are processed just before pulling them up.
+        * level of WHERE; if we pull up any subqueries below, their INs are
+        * processed just before pulling them up.
         */
        if (parse->hasSubLinks)
                parse->jointree->quals = pull_up_IN_clauses(root,
                                                                                                        parse->jointree->quals);
 
+       /*
+        * Scan the rangetable for set-returning functions, and inline them
+        * if possible (producing subqueries that might get pulled up next).
+        * Recursion issues here are handled in the same way as for IN clauses.
+        */
+       inline_set_returning_functions(root);
+
        /*
         * Check to see if any subqueries in the rangetable can be merged into
         * this query.
index 8e726ff7dad4089eecd4f860b9baef4ee1c9df71..755bed363b12af6757910beb1b4dcf8d737bb1a5 100644 (file)
@@ -5,6 +5,7 @@
  *
  * NOTE: the intended sequence for invoking these operations is
  *             pull_up_IN_clauses
+ *             inline_set_returning_functions
  *             pull_up_subqueries
  *             do expression preprocessing (including flattening JOIN alias vars)
  *             reduce_outer_joins
@@ -15,7 +16,7 @@
  *
  *
  * IDENTIFICATION
- *       $PostgreSQL: pgsql/src/backend/optimizer/prep/prepjointree.c,v 1.49 2008/01/01 19:45:50 momjian Exp $
+ *       $PostgreSQL: pgsql/src/backend/optimizer/prep/prepjointree.c,v 1.50 2008/03/18 22:04:14 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -124,6 +125,52 @@ pull_up_IN_clauses(PlannerInfo *root, Node *node)
        return node;
 }
 
+/*
+ * inline_set_returning_functions
+ *             Attempt to "inline" set-returning functions in the FROM clause.
+ *
+ * If an RTE_FUNCTION rtable entry invokes a set-returning 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.
+ *
+ * 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
+ * obtained via inlining.  However, we do it after pull_up_IN_clauses
+ * so that we can inline any functions used in IN subselects.
+ *
+ * Like most of the planner, this feels free to scribble on its input data
+ * structure.
+ */
+void
+inline_set_returning_functions(PlannerInfo *root)
+{
+       ListCell   *rt;
+
+       foreach(rt, root->parse->rtable)
+       {
+               RangeTblEntry *rte = (RangeTblEntry *) lfirst(rt);
+
+               if (rte->rtekind == RTE_FUNCTION)
+               {
+                       Query  *funcquery;
+
+                       /* Check safety of expansion, and expand if possible */
+                       funcquery = inline_set_returning_function(root, rte->funcexpr);
+                       if (funcquery)
+                       {
+                               /* Successful expansion, replace the rtable entry */
+                               rte->rtekind = RTE_SUBQUERY;
+                               rte->subquery = funcquery;
+                               rte->funcexpr = NULL;
+                               rte->funccoltypes = NIL;
+                               rte->funccoltypmods = NIL;
+                       }
+               }
+       }
+}
+
 /*
  * pull_up_subqueries
  *             Look for subqueries in the rangetable that can be pulled up into
@@ -296,6 +343,7 @@ pull_up_simple_subquery(PlannerInfo *root, Node *jtnode, RangeTblEntry *rte,
        subroot->query_level = root->query_level;
        subroot->planner_cxt = CurrentMemoryContext;
        subroot->init_plans = NIL;
+       subroot->eq_classes = NIL;
        subroot->in_info_list = NIL;
        subroot->append_rel_list = NIL;
 
@@ -307,6 +355,11 @@ pull_up_simple_subquery(PlannerInfo *root, Node *jtnode, RangeTblEntry *rte,
                subquery->jointree->quals = pull_up_IN_clauses(subroot,
                                                                                                  subquery->jointree->quals);
 
+       /*
+        * Similarly, inline any set-returning functions in its rangetable.
+        */
+       inline_set_returning_functions(subroot);
+
        /*
         * Recursively pull up the subquery's subqueries, so that
         * pull_up_subqueries' processing is complete for its jointree and
index bee9c7a9dda21e6f592e6250382fdea0082cad76..818894886a560ee31ee1e5dac0cbdfdfda7890db 100644 (file)
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *       $PostgreSQL: pgsql/src/backend/optimizer/util/clauses.c,v 1.254 2008/01/11 18:39:40 tgl Exp $
+ *       $PostgreSQL: pgsql/src/backend/optimizer/util/clauses.c,v 1.255 2008/03/18 22:04:14 tgl Exp $
  *
  * HISTORY
  *       AUTHOR                        DATE                    MAJOR EVENT
@@ -38,6 +38,7 @@
 #include "parser/parse_clause.h"
 #include "parser/parse_coerce.h"
 #include "parser/parse_expr.h"
+#include "rewrite/rewriteManip.h"
 #include "tcop/tcopprot.h"
 #include "utils/acl.h"
 #include "utils/builtins.h"
@@ -63,6 +64,13 @@ typedef struct
        int                *usecounts;
 } substitute_actual_parameters_context;
 
+typedef struct
+{
+       int                     nargs;
+       List       *args;
+       int                     sublevels_up;
+} substitute_actual_srf_parameters_context;
+
 static bool contain_agg_clause_walker(Node *node, void *context);
 static bool count_agg_clauses_walker(Node *node, AggClauseCounts *counts);
 static bool expression_returns_set_walker(Node *node, void *context);
@@ -100,6 +108,10 @@ static Node *substitute_actual_parameters_mutator(Node *node,
                                                          substitute_actual_parameters_context *context);
 static void sql_inline_error_callback(void *arg);
 static Expr *evaluate_expr(Expr *expr, Oid result_type, int32 result_typmod);
+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);
 
 
 /*****************************************************************************
@@ -3027,18 +3039,26 @@ inline_function(Oid funcid, Oid result_type, List *args,
                list_length(querytree->targetList) != 1)
                goto fail;
 
-       newexpr = (Node *) ((TargetEntry *) linitial(querytree->targetList))->expr;
-
        /*
         * 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 we do not try this until we have verified
-        * that no rewriting was needed; that's probably not important, but let's
-        * be careful.
+        * fail at runtime anyway.  Note that check_sql_fn_retval will also insert
+        * a RelabelType if needed to make the tlist expression match the declared
+        * type of the function.
+        *
+        * Note: we do not try this until we have verified that no rewriting was
+        * needed; that's probably not important, but let's be careful.
         */
-       if (check_sql_fn_retval(funcid, result_type, list_make1(querytree), NULL))
+       if (check_sql_fn_retval(funcid, result_type, list_make1(querytree),
+                                                       true, NULL))
                goto fail;                              /* reject whole-tuple-result cases */
 
+       /* Now we can grab the tlist expression */
+       newexpr = (Node *) ((TargetEntry *) linitial(querytree->targetList))->expr;
+
+       /* Assert that check_sql_fn_retval did the right thing */
+       Assert(exprType(newexpr) == result_type);
+
        /*
         * Additional validity checks on the expression.  It mustn't return a set,
         * and it mustn't be more volatile than the surrounding function (this is
@@ -3122,21 +3142,6 @@ inline_function(Oid funcid, Oid result_type, List *args,
 
        MemoryContextDelete(mycxt);
 
-       /*
-        * Since check_sql_fn_retval allows binary-compatibility cases, the
-        * expression we now have might return some type that's only binary
-        * compatible with the original expression result type.  To avoid
-        * confusing matters, insert a RelabelType in such cases.
-        */
-       if (exprType(newexpr) != result_type)
-       {
-               Assert(IsBinaryCoercible(exprType(newexpr), result_type));
-               newexpr = (Node *) makeRelabelType((Expr *) newexpr,
-                                                                                  result_type,
-                                                                                  -1,
-                                                                                  COERCE_IMPLICIT_CAST);
-       }
-
        /*
         * Recursively try to simplify the modified expression.  Here we must add
         * the current function to the context list of active functions.
@@ -3307,6 +3312,285 @@ 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.
+ *
+ * "node" is the expression from 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.
+ *
+ * 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
+ * justify separate functions.
+ */
+Query *
+inline_set_returning_function(PlannerInfo *root, Node *node)
+{
+       FuncExpr   *fexpr;
+       HeapTuple       func_tuple;
+       Form_pg_proc funcform;
+       Oid                *argtypes;
+       char       *src;
+       Datum           tmp;
+       bool            isNull;
+       MemoryContext oldcxt;
+       MemoryContext mycxt;
+       ErrorContextCallback sqlerrcontext;
+       List       *raw_parsetree_list;
+       List       *querytree_list;
+       Query      *querytree;
+       int                     i;
+
+       /*
+        * 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.)
+        */
+       check_stack_depth();
+
+       /* Fail if FROM item isn't a simple FuncExpr */
+       if (node == NULL || !IsA(node, FuncExpr))
+               return NULL;
+       fexpr = (FuncExpr *) node;
+
+       /*
+        * 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;
+
+       /* Fail if function returns RECORD ... we don't have enough context */
+       if (fexpr->funcresulttype == RECORDOID)
+               return NULL;
+
+       /*
+        * Refuse to inline if the arguments contain any volatile functions or
+        * sub-selects.  Volatile functions are rejected because inlining may
+        * result in the arguments being evaluated multiple times, risking a
+        * change in behavior.  Sub-selects are rejected partly for implementation
+        * reasons (pushing them down another level might change their behavior)
+        * and partly because they're likely to be expensive and so multiple
+        * evaluation would be bad.
+        */
+       if (contain_volatile_functions((Node *) fexpr->args) ||
+               contain_subplans((Node *) fexpr->args))
+               return NULL;
+
+       /* Check permission to call function (fail later, if not) */
+       if (pg_proc_aclcheck(fexpr->funcid, GetUserId(), ACL_EXECUTE) != ACLCHECK_OK)
+               return NULL;
+
+       /*
+        * OK, let's take a look at the function's pg_proc entry.
+        */
+       func_tuple = SearchSysCache(PROCOID,
+                                                               ObjectIdGetDatum(fexpr->funcid),
+                                                               0, 0, 0);
+       if (!HeapTupleIsValid(func_tuple))
+               elog(ERROR, "cache lookup failed for function %u", fexpr->funcid);
+       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.  (The nargs check is just
+        * paranoia, ditto rechecking proretset.)
+        */
+       if (funcform->prolang != SQLlanguageId ||
+               funcform->proisstrict ||
+               funcform->provolatile == PROVOLATILE_VOLATILE ||
+               funcform->prosecdef ||
+               !funcform->proretset ||
+               !heap_attisnull(func_tuple, Anum_pg_proc_proconfig) ||
+               funcform->pronargs != list_length(fexpr->args))
+       {
+               ReleaseSysCache(func_tuple);
+               return NULL;
+       }
+
+       /*
+        * Setup error traceback support for ereport().  This is so that we can
+        * finger the function that bad information came from.
+        */
+       sqlerrcontext.callback = sql_inline_error_callback;
+       sqlerrcontext.arg = func_tuple;
+       sqlerrcontext.previous = error_context_stack;
+       error_context_stack = &sqlerrcontext;
+
+       /*
+        * Make a temporary memory context, so that we don't leak all the stuff
+        * that parsing might create.
+        */
+       mycxt = AllocSetContextCreate(CurrentMemoryContext,
+                                                                 "inline_set_returning_function",
+                                                                 ALLOCSET_DEFAULT_MINSIZE,
+                                                                 ALLOCSET_DEFAULT_INITSIZE,
+                                                                 ALLOCSET_DEFAULT_MAXSIZE);
+       oldcxt = MemoryContextSwitchTo(mycxt);
+
+       /* Check for polymorphic arguments, and substitute actual arg types */
+       argtypes = (Oid *) palloc(funcform->pronargs * sizeof(Oid));
+       memcpy(argtypes, funcform->proargtypes.values,
+                  funcform->pronargs * sizeof(Oid));
+       for (i = 0; i < funcform->pronargs; i++)
+       {
+               if (IsPolymorphicType(argtypes[i]))
+               {
+                       argtypes[i] = exprType((Node *) list_nth(fexpr->args, i));
+               }
+       }
+
+       /* Fetch and parse the function body */
+       tmp = SysCacheGetAttr(PROCOID,
+                                                 func_tuple,
+                                                 Anum_pg_proc_prosrc,
+                                                 &isNull);
+       if (isNull)
+               elog(ERROR, "null prosrc for function %u", fexpr->funcid);
+       src = DatumGetCString(DirectFunctionCall1(textout, tmp));
+
+       /*
+        * 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(linitial(raw_parsetree_list), src,
+                                                         argtypes, funcform->pronargs);
+       if (list_length(querytree_list) != 1)
+               goto fail;
+       querytree = linitial(querytree_list);
+
+       /*
+        * The single command must be a regular results-returning SELECT.
+        */
+       if (!IsA(querytree, Query) ||
+               querytree->commandType != CMD_SELECT ||
+               querytree->utilityStmt ||
+               querytree->intoClause)
+               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
+        * RelabelType(s) if needed to make the tlist expression(s) match the
+        * declared type of the function.
+        *
+        * 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(fexpr->funcid, fexpr->funcresulttype,
+                                                        querytree_list,
+                                                        true, NULL) &&
+               get_typtype(fexpr->funcresulttype) == TYPTYPE_COMPOSITE)
+               goto fail;                              /* reject not-whole-tuple-result cases */
+
+       /*
+        * 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);
+
+       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;
+}
+
+/*
+ * Replace Param nodes by appropriate actual parameters
+ *
+ * This is just enough different from substitute_actual_parameters()
+ * that it needs its own code.
+ */
+static Query *
+substitute_actual_srf_parameters(Query *expr, int nargs, List *args)
+{
+       substitute_actual_srf_parameters_context context;
+
+       context.nargs = nargs;
+       context.args = args;
+       context.sublevels_up = 1;
+
+       return query_tree_mutator(expr,
+                                                         substitute_actual_srf_parameters_mutator,
+                                                         &context,
+                                                         0);
+}
+
+static Node *
+substitute_actual_srf_parameters_mutator(Node *node,
+                                                       substitute_actual_srf_parameters_context *context)
+{
+       Node   *result;
+
+       if (node == NULL)
+               return NULL;
+       if (IsA(node, Query))
+       {
+               context->sublevels_up++;
+               result = (Node *) query_tree_mutator((Query *) node,
+                                                                         substitute_actual_srf_parameters_mutator,
+                                                                                        (void *) context,
+                                                                                        0);
+               context->sublevels_up--;
+               return result;
+       }
+       if (IsA(node, Param))
+       {
+               Param      *param = (Param *) node;
+
+               if (param->paramkind == PARAM_EXTERN)
+               {
+                       if (param->paramid <= 0 || param->paramid > context->nargs)
+                               elog(ERROR, "invalid paramid: %d", param->paramid);
+
+                       /*
+                        * Since the parameter is being inserted into a subquery,
+                        * we must adjust levels.
+                        */
+                       result = copyObject(list_nth(context->args, param->paramid - 1));
+                       IncrementVarSublevelsUp(result, context->sublevels_up, 0);
+                       return result;
+               }
+       }
+       return expression_tree_mutator(node,
+                                                                  substitute_actual_srf_parameters_mutator,
+                                                                  (void *) context);
+}
+
+
 /*
  * Standard expression-tree walking support
  *
index 3a5c4eb012e8b614bf1e3f73968ad0a894ce0109..b5451ad43b880a6468caf22b90ff22c8b6b8ec76 100644 (file)
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2008, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/include/executor/functions.h,v 1.30 2008/01/01 19:45:57 momjian Exp $
+ * $PostgreSQL: pgsql/src/include/executor/functions.h,v 1.31 2008/03/18 22:04:14 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -21,6 +21,7 @@ extern Datum fmgr_sql(PG_FUNCTION_ARGS);
 
 extern bool check_sql_fn_retval(Oid func_id, Oid rettype,
                                        List *queryTreeList,
+                                       bool insertRelabels,
                                        JunkFilter **junkFilter);
 
 #endif   /* FUNCTIONS_H */
index 0a2b3a2bb8033d5a22388da8f874143276f7f938..b5d618595f74f08331ea83ea66155331e5d44c90 100644 (file)
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2008, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/include/optimizer/clauses.h,v 1.88 2008/01/01 19:45:58 momjian Exp $
+ * $PostgreSQL: pgsql/src/include/optimizer/clauses.h,v 1.89 2008/03/18 22:04:14 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -79,6 +79,8 @@ extern Node *eval_const_expressions(Node *node);
 
 extern Node *estimate_expression_value(PlannerInfo *root, Node *node);
 
+extern Query *inline_set_returning_function(PlannerInfo *root, Node *node);
+
 extern bool expression_tree_walker(Node *node, bool (*walker) (),
                                                                                           void *context);
 extern Node *expression_tree_mutator(Node *node, Node *(*mutator) (),
index b98040a498afd8a6b8cb0d78e48174001d17dc38..80fa3b515268c564e3c4eab300a7dc89e2e791c8 100644 (file)
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2008, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/include/optimizer/prep.h,v 1.59 2008/01/01 19:45:58 momjian Exp $
+ * $PostgreSQL: pgsql/src/include/optimizer/prep.h,v 1.60 2008/03/18 22:04:14 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -22,6 +22,7 @@
  * prototypes for prepjointree.c
  */
 extern Node *pull_up_IN_clauses(PlannerInfo *root, Node *node);
+extern void inline_set_returning_functions(PlannerInfo *root);
 extern Node *pull_up_subqueries(PlannerInfo *root, Node *jtnode,
                                   bool below_outer_join, bool append_rel_member);
 extern void reduce_outer_joins(PlannerInfo *root);