Skip to content

Commit be86b49

Browse files
pjungwirCommitfest Bot
authored andcommitted
Move some things outside of inline_set_returning_function.
Added a new inline_sql_set_returning_function in preparation for inline_set_returning_function_with_support. Then inline_set_returning_function can call both, handling their shared needs itself. Author: Paul A. Jungwirth <[email protected]>
1 parent 36aed19 commit be86b49

File tree

1 file changed

+157
-116
lines changed

1 file changed

+157
-116
lines changed

src/backend/optimizer/util/clauses.c

Lines changed: 157 additions & 116 deletions
Original file line numberDiff line numberDiff line change
@@ -5146,36 +5146,21 @@ evaluate_expr(Expr *expr, Oid result_type, int32 result_typmod,
51465146

51475147

51485148
/*
5149-
* inline_set_returning_function
5150-
* Attempt to "inline" a set-returning function in the FROM clause.
5151-
*
5152-
* "rte" is an RTE_FUNCTION rangetable entry. If it represents a call of a
5153-
* set-returning SQL function that can safely be inlined, expand the function
5154-
* and return the substitute Query structure. Otherwise, return NULL.
5149+
* inline_sql_set_returning_function
51555150
*
5156-
* We assume that the RTE's expression has already been put through
5157-
* eval_const_expressions(), which among other things will take care of
5158-
* default arguments and named-argument notation.
5151+
* This implements inline_set_returning_function for sql-language functions.
5152+
* It parses the body (or uses the pre-parsed body if available).
51595153
*
5160-
* This has a good deal of similarity to inline_function(), but that's
5161-
* for the non-set-returning case, and there are enough differences to
5162-
* justify separate functions.
5154+
* Returns NULL if the function couldn't be inlined.
51635155
*/
5164-
Query *
5165-
inline_set_returning_function(PlannerInfo *root, RangeTblEntry *rte)
5156+
static Query *
5157+
inline_sql_set_returning_function(PlannerInfo *root, RangeTblEntry *rte,
5158+
RangeTblFunction *rtfunc,
5159+
FuncExpr *fexpr, Oid func_oid, HeapTuple func_tuple,
5160+
Form_pg_proc funcform, char *src)
51665161
{
5167-
RangeTblFunction *rtfunc;
5168-
FuncExpr *fexpr;
5169-
Oid func_oid;
5170-
HeapTuple func_tuple;
5171-
Form_pg_proc funcform;
5172-
char *src;
5173-
Datum tmp;
5162+
Datum sqlbody;
51745163
bool isNull;
5175-
MemoryContext oldcxt;
5176-
MemoryContext mycxt;
5177-
inline_error_callback_arg callback_arg;
5178-
ErrorContextCallback sqlerrcontext;
51795164
SQLFunctionParseInfoPtr pinfo;
51805165
TypeFuncClass functypclass;
51815166
TupleDesc rettupdesc;
@@ -5185,29 +5170,6 @@ inline_set_returning_function(PlannerInfo *root, RangeTblEntry *rte)
51855170

51865171
Assert(rte->rtekind == RTE_FUNCTION);
51875172

5188-
/*
5189-
* It doesn't make a lot of sense for a SQL SRF to refer to itself in its
5190-
* own FROM clause, since that must cause infinite recursion at runtime.
5191-
* It will cause this code to recurse too, so check for stack overflow.
5192-
* (There's no need to do more.)
5193-
*/
5194-
check_stack_depth();
5195-
5196-
/* Fail if the RTE has ORDINALITY - we don't implement that here. */
5197-
if (rte->funcordinality)
5198-
return NULL;
5199-
5200-
/* Fail if RTE isn't a single, simple FuncExpr */
5201-
if (list_length(rte->functions) != 1)
5202-
return NULL;
5203-
rtfunc = (RangeTblFunction *) linitial(rte->functions);
5204-
5205-
if (!IsA(rtfunc->funcexpr, FuncExpr))
5206-
return NULL;
5207-
fexpr = (FuncExpr *) rtfunc->funcexpr;
5208-
5209-
func_oid = fexpr->funcid;
5210-
52115173
/*
52125174
* The function must be declared to return a set, else inlining would
52135175
* change the results if the contained SELECT didn't return exactly one
@@ -5216,35 +5178,6 @@ inline_set_returning_function(PlannerInfo *root, RangeTblEntry *rte)
52165178
if (!fexpr->funcretset)
52175179
return NULL;
52185180

5219-
/*
5220-
* Refuse to inline if the arguments contain any volatile functions or
5221-
* sub-selects. Volatile functions are rejected because inlining may
5222-
* result in the arguments being evaluated multiple times, risking a
5223-
* change in behavior. Sub-selects are rejected partly for implementation
5224-
* reasons (pushing them down another level might change their behavior)
5225-
* and partly because they're likely to be expensive and so multiple
5226-
* evaluation would be bad.
5227-
*/
5228-
if (contain_volatile_functions((Node *) fexpr->args) ||
5229-
contain_subplans((Node *) fexpr->args))
5230-
return NULL;
5231-
5232-
/* Check permission to call function (fail later, if not) */
5233-
if (object_aclcheck(ProcedureRelationId, func_oid, GetUserId(), ACL_EXECUTE) != ACLCHECK_OK)
5234-
return NULL;
5235-
5236-
/* Check whether a plugin wants to hook function entry/exit */
5237-
if (FmgrHookIsNeeded(func_oid))
5238-
return NULL;
5239-
5240-
/*
5241-
* OK, let's take a look at the function's pg_proc entry.
5242-
*/
5243-
func_tuple = SearchSysCache1(PROCOID, ObjectIdGetDatum(func_oid));
5244-
if (!HeapTupleIsValid(func_tuple))
5245-
elog(ERROR, "cache lookup failed for function %u", func_oid);
5246-
funcform = (Form_pg_proc) GETSTRUCT(func_tuple);
5247-
52485181
/*
52495182
* Forget it if the function is not SQL-language or has other showstopper
52505183
* properties. In particular it mustn't be declared STRICT, since we
@@ -5262,61 +5195,34 @@ inline_set_returning_function(PlannerInfo *root, RangeTblEntry *rte)
52625195
funcform->prorettype == VOIDOID ||
52635196
funcform->prosecdef ||
52645197
!funcform->proretset ||
5265-
list_length(fexpr->args) != funcform->pronargs ||
5266-
!heap_attisnull(func_tuple, Anum_pg_proc_proconfig, NULL))
5198+
list_length(fexpr->args) != funcform->pronargs)
52675199
{
5268-
ReleaseSysCache(func_tuple);
52695200
return NULL;
52705201
}
52715202

5272-
/*
5273-
* Make a temporary memory context, so that we don't leak all the stuff
5274-
* that parsing might create.
5275-
*/
5276-
mycxt = AllocSetContextCreate(CurrentMemoryContext,
5277-
"inline_set_returning_function",
5278-
ALLOCSET_DEFAULT_SIZES);
5279-
oldcxt = MemoryContextSwitchTo(mycxt);
5280-
5281-
/* Fetch the function body */
5282-
tmp = SysCacheGetAttrNotNull(PROCOID, func_tuple, Anum_pg_proc_prosrc);
5283-
src = TextDatumGetCString(tmp);
5284-
5285-
/*
5286-
* Setup error traceback support for ereport(). This is so that we can
5287-
* finger the function that bad information came from.
5288-
*/
5289-
callback_arg.proname = NameStr(funcform->proname);
5290-
callback_arg.prosrc = src;
5291-
5292-
sqlerrcontext.callback = sql_inline_error_callback;
5293-
sqlerrcontext.arg = &callback_arg;
5294-
sqlerrcontext.previous = error_context_stack;
5295-
error_context_stack = &sqlerrcontext;
5296-
52975203
/* If we have prosqlbody, pay attention to that not prosrc */
5298-
tmp = SysCacheGetAttr(PROCOID,
5299-
func_tuple,
5300-
Anum_pg_proc_prosqlbody,
5301-
&isNull);
5204+
sqlbody = SysCacheGetAttr(PROCOID,
5205+
func_tuple,
5206+
Anum_pg_proc_prosqlbody,
5207+
&isNull);
53025208
if (!isNull)
53035209
{
53045210
Node *n;
53055211

5306-
n = stringToNode(TextDatumGetCString(tmp));
5212+
n = stringToNode(TextDatumGetCString(sqlbody));
53075213
if (IsA(n, List))
53085214
querytree_list = linitial_node(List, castNode(List, n));
53095215
else
53105216
querytree_list = list_make1(n);
53115217
if (list_length(querytree_list) != 1)
5312-
goto fail;
5218+
return NULL;
53135219
querytree = linitial(querytree_list);
53145220

53155221
/* Acquire necessary locks, then apply rewriter. */
53165222
AcquireRewriteLocks(querytree, true, false);
53175223
querytree_list = pg_rewrite_query(querytree);
53185224
if (list_length(querytree_list) != 1)
5319-
goto fail;
5225+
return NULL;
53205226
querytree = linitial(querytree_list);
53215227
}
53225228
else
@@ -5337,14 +5243,14 @@ inline_set_returning_function(PlannerInfo *root, RangeTblEntry *rte)
53375243
*/
53385244
raw_parsetree_list = pg_parse_query(src);
53395245
if (list_length(raw_parsetree_list) != 1)
5340-
goto fail;
5246+
return NULL;
53415247

53425248
querytree_list = pg_analyze_and_rewrite_withcb(linitial(raw_parsetree_list),
53435249
src,
53445250
(ParserSetupHook) sql_fn_parser_setup,
53455251
pinfo, NULL);
53465252
if (list_length(querytree_list) != 1)
5347-
goto fail;
5253+
return NULL;
53485254
querytree = linitial(querytree_list);
53495255
}
53505256

@@ -5369,7 +5275,7 @@ inline_set_returning_function(PlannerInfo *root, RangeTblEntry *rte)
53695275
*/
53705276
if (!IsA(querytree, Query) ||
53715277
querytree->commandType != CMD_SELECT)
5372-
goto fail;
5278+
return NULL;
53735279

53745280
/*
53755281
* Make sure the function (still) returns what it's declared to. This
@@ -5391,14 +5297,149 @@ inline_set_returning_function(PlannerInfo *root, RangeTblEntry *rte)
53915297
(functypclass == TYPEFUNC_COMPOSITE ||
53925298
functypclass == TYPEFUNC_COMPOSITE_DOMAIN ||
53935299
functypclass == TYPEFUNC_RECORD))
5394-
goto fail; /* reject not-whole-tuple-result cases */
5300+
return NULL; /* reject not-whole-tuple-result cases */
53955301

53965302
/*
53975303
* check_sql_fn_retval might've inserted a projection step, but that's
53985304
* fine; just make sure we use the upper Query.
53995305
*/
54005306
querytree = linitial_node(Query, querytree_list);
54015307

5308+
return querytree;
5309+
}
5310+
5311+
/*
5312+
* inline_set_returning_function
5313+
* Attempt to "inline" an SQL set-returning function in the FROM clause.
5314+
*
5315+
* "rte" is an RTE_FUNCTION rangetable entry. If it represents a call of a
5316+
* set-returning SQL function that can safely be inlined, expand the function
5317+
* and return the substitute Query structure. Otherwise, return NULL.
5318+
*
5319+
* We assume that the RTE's expression has already been put through
5320+
* eval_const_expressions(), which among other things will take care of
5321+
* default arguments and named-argument notation.
5322+
*
5323+
* This has a good deal of similarity to inline_function(), but that's
5324+
* for the non-set-returning case, and there are enough differences to
5325+
* justify separate functions.
5326+
*
5327+
* It allocates its own temporary MemoryContext for the parsing, then copies
5328+
* the result into the caller's context.
5329+
*/
5330+
Query *
5331+
inline_set_returning_function(PlannerInfo *root, RangeTblEntry *rte)
5332+
{
5333+
RangeTblFunction *rtfunc;
5334+
FuncExpr *fexpr;
5335+
Oid func_oid;
5336+
HeapTuple func_tuple;
5337+
Form_pg_proc funcform;
5338+
Datum tmp;
5339+
char *src;
5340+
inline_error_callback_arg callback_arg;
5341+
ErrorContextCallback sqlerrcontext;
5342+
MemoryContext oldcxt;
5343+
MemoryContext mycxt;
5344+
Query *querytree;
5345+
5346+
Assert(rte->rtekind == RTE_FUNCTION);
5347+
5348+
/*
5349+
* It doesn't make a lot of sense for a SRF to refer to itself in its own
5350+
* FROM clause, since that must cause infinite recursion at runtime. It
5351+
* will cause this code to recurse too, so check for stack overflow.
5352+
* (There's no need to do more.)
5353+
*/
5354+
check_stack_depth();
5355+
5356+
/* Fail if the RTE has ORDINALITY - we don't implement that here. */
5357+
if (rte->funcordinality)
5358+
return NULL;
5359+
5360+
/* Fail if RTE isn't a single, simple FuncExpr */
5361+
if (list_length(rte->functions) != 1)
5362+
return NULL;
5363+
rtfunc = (RangeTblFunction *) linitial(rte->functions);
5364+
5365+
if (!IsA(rtfunc->funcexpr, FuncExpr))
5366+
return NULL;
5367+
fexpr = (FuncExpr *) rtfunc->funcexpr;
5368+
5369+
func_oid = fexpr->funcid;
5370+
5371+
/*
5372+
* Refuse to inline if the arguments contain any volatile functions or
5373+
* sub-selects. Volatile functions are rejected because inlining may
5374+
* result in the arguments being evaluated multiple times, risking a
5375+
* change in behavior. Sub-selects are rejected partly for implementation
5376+
* reasons (pushing them down another level might change their behavior)
5377+
* and partly because they're likely to be expensive and so multiple
5378+
* evaluation would be bad.
5379+
*/
5380+
if (contain_volatile_functions((Node *) fexpr->args) ||
5381+
contain_subplans((Node *) fexpr->args))
5382+
return NULL;
5383+
5384+
/* Check permission to call function (fail later, if not) */
5385+
if (object_aclcheck(ProcedureRelationId, func_oid, GetUserId(), ACL_EXECUTE) != ACLCHECK_OK)
5386+
return NULL;
5387+
5388+
/* Check whether a plugin wants to hook function entry/exit */
5389+
if (FmgrHookIsNeeded(func_oid))
5390+
return NULL;
5391+
5392+
/*
5393+
* OK, let's take a look at the function's pg_proc entry.
5394+
*/
5395+
func_tuple = SearchSysCache1(PROCOID, ObjectIdGetDatum(func_oid));
5396+
if (!HeapTupleIsValid(func_tuple))
5397+
elog(ERROR, "cache lookup failed for function %u", func_oid);
5398+
funcform = (Form_pg_proc) GETSTRUCT(func_tuple);
5399+
5400+
/*
5401+
* Make a temporary memory context, so that we don't leak all the stuff
5402+
* that parsing might create.
5403+
*/
5404+
mycxt = AllocSetContextCreate(CurrentMemoryContext,
5405+
"inline_set_returning_function",
5406+
ALLOCSET_DEFAULT_SIZES);
5407+
oldcxt = MemoryContextSwitchTo(mycxt);
5408+
5409+
/* Fetch the function body */
5410+
tmp = SysCacheGetAttrNotNull(PROCOID, func_tuple, Anum_pg_proc_prosrc);
5411+
src = TextDatumGetCString(tmp);
5412+
5413+
/*
5414+
* Setup error traceback support for ereport(). This is so that we can
5415+
* finger the function that bad information came from.
5416+
*/
5417+
callback_arg.proname = NameStr(funcform->proname);
5418+
callback_arg.prosrc = src;
5419+
5420+
sqlerrcontext.callback = sql_inline_error_callback;
5421+
sqlerrcontext.arg = &callback_arg;
5422+
sqlerrcontext.previous = error_context_stack;
5423+
error_context_stack = &sqlerrcontext;
5424+
5425+
/*
5426+
* If the function SETs configuration parameters, inlining would cause us
5427+
* to skip those changes.
5428+
*/
5429+
if (!heap_attisnull(func_tuple, Anum_pg_proc_proconfig, NULL))
5430+
goto fail;
5431+
5432+
querytree = inline_sql_set_returning_function(root, rte, rtfunc, fexpr,
5433+
func_oid, func_tuple, funcform,
5434+
src);
5435+
5436+
if (!querytree)
5437+
goto fail;
5438+
5439+
/* Only SELECTs are permitted */
5440+
Assert(IsA(querytree, Query));
5441+
Assert(querytree->commandType == CMD_SELECT);
5442+
54025443
/*
54035444
* Looks good --- substitute parameters into the query.
54045445
*/

0 commit comments

Comments
 (0)