mirror of
https://github.com/postgres/postgres.git
synced 2025-05-17 00:03:56 -04:00
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.
This commit is contained in:
parent
433c5238bf
commit
0d49838df6
@ -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)
|
|||||||
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 @@
|
|||||||
*
|
*
|
||||||
*
|
*
|
||||||
* 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 @@
|
|||||||
#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)
|
|||||||
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)
|
|||||||
*
|
*
|
||||||
* 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)
|
|||||||
* 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)
|
|||||||
*/
|
*/
|
||||||
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,
|
|||||||
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,
|
|||||||
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,
|
|||||||
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,
|
|||||||
* 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,
|
|||||||
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,
|
|||||||
/* 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 @@
|
|||||||
*
|
*
|
||||||
*
|
*
|
||||||
* 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,
|
|||||||
/*
|
/*
|
||||||
* 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 @@
|
|||||||
*
|
*
|
||||||
* 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 @@
|
|||||||
*
|
*
|
||||||
*
|
*
|
||||||
* 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)
|
|||||||
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,
|
|||||||
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,
|
|||||||
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 @@
|
|||||||
*
|
*
|
||||||
*
|
*
|
||||||
* 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 @@
|
|||||||
#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
|
|||||||
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,
|
|||||||
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,
|
|||||||
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,
|
|||||||
|
|
||||||
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)
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 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 @@
|
|||||||
* 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);
|
|||||||
|
|
||||||
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 @@
|
|||||||
* 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);
|
|||||||
|
|
||||||
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 @@
|
|||||||
* 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 @@
|
|||||||
* 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);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user