提交 0d49838d 编写于 作者: T Tom Lane

Arrange to "inline" SQL functions that appear in a query's FROM clause,

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.
上级 433c5238
...@@ -8,7 +8,7 @@ ...@@ -8,7 +8,7 @@
* *
* *
* IDENTIFICATION * 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) ...@@ -601,7 +601,8 @@ fmgr_sql_validator(PG_FUNCTION_ARGS)
proc->proargtypes.values, proc->proargtypes.values,
proc->pronargs); proc->pronargs);
(void) check_sql_fn_retval(funcoid, proc->prorettype, (void) check_sql_fn_retval(funcoid, proc->prorettype,
querytree_list, NULL); querytree_list,
false, NULL);
} }
else else
querytree_list = pg_parse_query(prosrc); querytree_list = pg_parse_query(prosrc);
......
...@@ -8,7 +8,7 @@ ...@@ -8,7 +8,7 @@
* *
* *
* IDENTIFICATION * 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 @@ ...@@ -20,6 +20,7 @@
#include "commands/trigger.h" #include "commands/trigger.h"
#include "executor/functions.h" #include "executor/functions.h"
#include "funcapi.h" #include "funcapi.h"
#include "nodes/makefuncs.h"
#include "parser/parse_coerce.h" #include "parser/parse_coerce.h"
#include "parser/parse_expr.h" #include "parser/parse_expr.h"
#include "tcop/tcopprot.h" #include "tcop/tcopprot.h"
...@@ -269,6 +270,7 @@ init_sql_fcache(FmgrInfo *finfo) ...@@ -269,6 +270,7 @@ init_sql_fcache(FmgrInfo *finfo)
fcache->returnsTuple = check_sql_fn_retval(foid, fcache->returnsTuple = check_sql_fn_retval(foid,
rettype, rettype,
queryTree_list, queryTree_list,
false,
&fcache->junkFilter); &fcache->junkFilter);
/* Finally, plan the queries */ /* Finally, plan the queries */
...@@ -856,7 +858,9 @@ ShutdownSQLFunction(Datum arg) ...@@ -856,7 +858,9 @@ ShutdownSQLFunction(Datum arg)
* *
* The return value of a sql function is the value returned by * 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 * 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 * For a polymorphic function the passed rettype must be the actual resolved
* output type of the function; we should never see a polymorphic pseudotype * output type of the function; we should never see a polymorphic pseudotype
...@@ -868,6 +872,10 @@ ShutdownSQLFunction(Datum arg) ...@@ -868,6 +872,10 @@ ShutdownSQLFunction(Datum arg)
* allow "SELECT rowtype_expression", this may be false even when the declared * allow "SELECT rowtype_expression", this may be false even when the declared
* function return type is a rowtype. * 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 * 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. * 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 * Whenever the result value is false (ie, the function isn't returning a
...@@ -875,6 +883,7 @@ ShutdownSQLFunction(Datum arg) ...@@ -875,6 +883,7 @@ ShutdownSQLFunction(Datum arg)
*/ */
bool bool
check_sql_fn_retval(Oid func_id, Oid rettype, List *queryTreeList, check_sql_fn_retval(Oid func_id, Oid rettype, List *queryTreeList,
bool insertRelabels,
JunkFilter **junkFilter) JunkFilter **junkFilter)
{ {
Query *parse; Query *parse;
...@@ -945,10 +954,12 @@ check_sql_fn_retval(Oid func_id, Oid rettype, List *queryTreeList, ...@@ -945,10 +954,12 @@ check_sql_fn_retval(Oid func_id, Oid rettype, List *queryTreeList,
rettype == VOIDOID) rettype == VOIDOID)
{ {
/* /*
* For scalar-type returns, the target list should have exactly one * For scalar-type returns, the target list must have exactly one
* entry, and its type should agree with what the user declared. (As * non-junk entry, and its type must agree with what the user
* of Postgres 7.2, we accept binary-compatible types too.) * declared; except we allow binary-compatible types too.
*/ */
TargetEntry *tle;
if (tlistlen != 1) if (tlistlen != 1)
ereport(ERROR, ereport(ERROR,
(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION), (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
...@@ -956,7 +967,11 @@ check_sql_fn_retval(Oid func_id, Oid rettype, List *queryTreeList, ...@@ -956,7 +967,11 @@ check_sql_fn_retval(Oid func_id, Oid rettype, List *queryTreeList,
format_type_be(rettype)), format_type_be(rettype)),
errdetail("Final SELECT must return exactly one column."))); 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)) if (!IsBinaryCoercible(restype, rettype))
ereport(ERROR, ereport(ERROR,
(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION), (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
...@@ -964,6 +979,11 @@ check_sql_fn_retval(Oid func_id, Oid rettype, List *queryTreeList, ...@@ -964,6 +979,11 @@ check_sql_fn_retval(Oid func_id, Oid rettype, List *queryTreeList,
format_type_be(rettype)), format_type_be(rettype)),
errdetail("Actual return type is %s.", errdetail("Actual return type is %s.",
format_type_be(restype)))); 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) else if (fn_typtype == TYPTYPE_COMPOSITE || rettype == RECORDOID)
{ {
...@@ -977,14 +997,24 @@ check_sql_fn_retval(Oid func_id, Oid rettype, List *queryTreeList, ...@@ -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 * 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. * the target list matches the declared return type, this is okay.
* This can happen, for example, where the body of the function is * This can happen, for example, where the body of the function is
* 'SELECT func2()', where func2 has the same return type as the * 'SELECT func2()', where func2 has the same composite return type
* function that's calling it. * as the function that's calling it.
*/ */
if (tlistlen == 1) 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 (IsBinaryCoercible(restype, rettype))
{
if (insertRelabels && restype != rettype)
tle->expr = (Expr *) makeRelabelType(tle->expr,
rettype,
-1,
COERCE_DONTCARE);
return false; /* NOT returning whole tuple */ return false; /* NOT returning whole tuple */
}
} }
/* Is the rowtype fixed, or determined only at runtime? */ /* Is the rowtype fixed, or determined only at runtime? */
...@@ -1043,6 +1073,11 @@ check_sql_fn_retval(Oid func_id, Oid rettype, List *queryTreeList, ...@@ -1043,6 +1073,11 @@ check_sql_fn_retval(Oid func_id, Oid rettype, List *queryTreeList,
format_type_be(tletype), format_type_be(tletype),
format_type_be(atttype), format_type_be(atttype),
tuplogcols))); tuplogcols)));
if (insertRelabels && tletype != atttype)
tle->expr = (Expr *) makeRelabelType(tle->expr,
atttype,
-1,
COERCE_DONTCARE);
} }
for (;;) for (;;)
...@@ -1070,14 +1105,6 @@ check_sql_fn_retval(Oid func_id, Oid rettype, List *queryTreeList, ...@@ -1070,14 +1105,6 @@ check_sql_fn_retval(Oid func_id, Oid rettype, List *queryTreeList,
/* Report that we are returning entire tuple result */ /* Report that we are returning entire tuple result */
return true; 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 else
ereport(ERROR, ereport(ERROR,
(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION), (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
......
...@@ -8,7 +8,7 @@ ...@@ -8,7 +8,7 @@
* *
* *
* IDENTIFICATION * 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, ...@@ -253,13 +253,20 @@ subquery_planner(PlannerGlobal *glob, Query *parse,
/* /*
* Look for IN clauses at the top level of WHERE, and transform them into * 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 * 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 * level of WHERE; if we pull up any subqueries below, their INs are
* INs are processed just before pulling them up. * processed just before pulling them up.
*/ */
if (parse->hasSubLinks) if (parse->hasSubLinks)
parse->jointree->quals = pull_up_IN_clauses(root, parse->jointree->quals = pull_up_IN_clauses(root,
parse->jointree->quals); 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 * Check to see if any subqueries in the rangetable can be merged into
* this query. * this query.
......
...@@ -5,6 +5,7 @@ ...@@ -5,6 +5,7 @@
* *
* NOTE: the intended sequence for invoking these operations is * NOTE: the intended sequence for invoking these operations is
* pull_up_IN_clauses * pull_up_IN_clauses
* inline_set_returning_functions
* pull_up_subqueries * pull_up_subqueries
* do expression preprocessing (including flattening JOIN alias vars) * do expression preprocessing (including flattening JOIN alias vars)
* reduce_outer_joins * reduce_outer_joins
...@@ -15,7 +16,7 @@ ...@@ -15,7 +16,7 @@
* *
* *
* IDENTIFICATION * 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) ...@@ -124,6 +125,52 @@ pull_up_IN_clauses(PlannerInfo *root, Node *node)
return 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 * pull_up_subqueries
* Look for subqueries in the rangetable that can be pulled up into * 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, ...@@ -296,6 +343,7 @@ pull_up_simple_subquery(PlannerInfo *root, Node *jtnode, RangeTblEntry *rte,
subroot->query_level = root->query_level; subroot->query_level = root->query_level;
subroot->planner_cxt = CurrentMemoryContext; subroot->planner_cxt = CurrentMemoryContext;
subroot->init_plans = NIL; subroot->init_plans = NIL;
subroot->eq_classes = NIL;
subroot->in_info_list = NIL; subroot->in_info_list = NIL;
subroot->append_rel_list = NIL; subroot->append_rel_list = NIL;
...@@ -307,6 +355,11 @@ pull_up_simple_subquery(PlannerInfo *root, Node *jtnode, RangeTblEntry *rte, ...@@ -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 = pull_up_IN_clauses(subroot,
subquery->jointree->quals); 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 * Recursively pull up the subquery's subqueries, so that
* pull_up_subqueries' processing is complete for its jointree and * pull_up_subqueries' processing is complete for its jointree and
......
...@@ -8,7 +8,7 @@ ...@@ -8,7 +8,7 @@
* *
* *
* IDENTIFICATION * 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 * HISTORY
* AUTHOR DATE MAJOR EVENT * AUTHOR DATE MAJOR EVENT
...@@ -38,6 +38,7 @@ ...@@ -38,6 +38,7 @@
#include "parser/parse_clause.h" #include "parser/parse_clause.h"
#include "parser/parse_coerce.h" #include "parser/parse_coerce.h"
#include "parser/parse_expr.h" #include "parser/parse_expr.h"
#include "rewrite/rewriteManip.h"
#include "tcop/tcopprot.h" #include "tcop/tcopprot.h"
#include "utils/acl.h" #include "utils/acl.h"
#include "utils/builtins.h" #include "utils/builtins.h"
...@@ -63,6 +64,13 @@ typedef struct ...@@ -63,6 +64,13 @@ typedef struct
int *usecounts; int *usecounts;
} substitute_actual_parameters_context; } 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 contain_agg_clause_walker(Node *node, void *context);
static bool count_agg_clauses_walker(Node *node, AggClauseCounts *counts); static bool count_agg_clauses_walker(Node *node, AggClauseCounts *counts);
static bool expression_returns_set_walker(Node *node, void *context); static bool expression_returns_set_walker(Node *node, void *context);
...@@ -100,6 +108,10 @@ static Node *substitute_actual_parameters_mutator(Node *node, ...@@ -100,6 +108,10 @@ static Node *substitute_actual_parameters_mutator(Node *node,
substitute_actual_parameters_context *context); substitute_actual_parameters_context *context);
static void sql_inline_error_callback(void *arg); static void sql_inline_error_callback(void *arg);
static Expr *evaluate_expr(Expr *expr, Oid result_type, int32 result_typmod); 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, ...@@ -3027,18 +3039,26 @@ inline_function(Oid funcid, Oid result_type, List *args,
list_length(querytree->targetList) != 1) list_length(querytree->targetList) != 1)
goto fail; goto fail;
newexpr = (Node *) ((TargetEntry *) linitial(querytree->targetList))->expr;
/* /*
* Make sure the function (still) returns what it's declared to. This * 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 * 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 * fail at runtime anyway. Note that check_sql_fn_retval will also insert
* that no rewriting was needed; that's probably not important, but let's * a RelabelType if needed to make the tlist expression match the declared
* be careful. * 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 */ 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, * 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 * 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, ...@@ -3122,21 +3142,6 @@ inline_function(Oid funcid, Oid result_type, List *args,
MemoryContextDelete(mycxt); 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 * Recursively try to simplify the modified expression. Here we must add
* the current function to the context list of active functions. * the current function to the context list of active functions.
...@@ -3307,6 +3312,285 @@ evaluate_expr(Expr *expr, Oid result_type, int32 result_typmod) ...@@ -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 * Standard expression-tree walking support
* *
......
...@@ -7,7 +7,7 @@ ...@@ -7,7 +7,7 @@
* Portions Copyright (c) 1996-2008, PostgreSQL Global Development Group * Portions Copyright (c) 1996-2008, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California * 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); ...@@ -21,6 +21,7 @@ extern Datum fmgr_sql(PG_FUNCTION_ARGS);
extern bool check_sql_fn_retval(Oid func_id, Oid rettype, extern bool check_sql_fn_retval(Oid func_id, Oid rettype,
List *queryTreeList, List *queryTreeList,
bool insertRelabels,
JunkFilter **junkFilter); JunkFilter **junkFilter);
#endif /* FUNCTIONS_H */ #endif /* FUNCTIONS_H */
...@@ -7,7 +7,7 @@ ...@@ -7,7 +7,7 @@
* Portions Copyright (c) 1996-2008, PostgreSQL Global Development Group * Portions Copyright (c) 1996-2008, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California * 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); ...@@ -79,6 +79,8 @@ extern Node *eval_const_expressions(Node *node);
extern Node *estimate_expression_value(PlannerInfo *root, 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) (), extern bool expression_tree_walker(Node *node, bool (*walker) (),
void *context); void *context);
extern Node *expression_tree_mutator(Node *node, Node *(*mutator) (), extern Node *expression_tree_mutator(Node *node, Node *(*mutator) (),
......
...@@ -7,7 +7,7 @@ ...@@ -7,7 +7,7 @@
* Portions Copyright (c) 1996-2008, PostgreSQL Global Development Group * Portions Copyright (c) 1996-2008, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California * 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 @@ ...@@ -22,6 +22,7 @@
* prototypes for prepjointree.c * prototypes for prepjointree.c
*/ */
extern Node *pull_up_IN_clauses(PlannerInfo *root, Node *node); 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, extern Node *pull_up_subqueries(PlannerInfo *root, Node *jtnode,
bool below_outer_join, bool append_rel_member); bool below_outer_join, bool append_rel_member);
extern void reduce_outer_joins(PlannerInfo *root); extern void reduce_outer_joins(PlannerInfo *root);
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册