mirror of
https://github.com/postgres/postgres.git
synced 2025-09-02 04:21:28 +03:00
Allow functions-in-FROM to be pulled up if they reduce to constants.
This allows simplification of the plan tree in some common usage patterns: we can get rid of a join to the function RTE. In principle we could pull up any immutable expression, but restricting it to Consts avoids the risk that multiple evaluations of the expression might cost more than we can save. (Possibly this could be improved in future --- but we've more or less promised people that putting a function in FROM guarantees single evaluation, so we'd have to tread carefully.) To do this, we need to rearrange when eval_const_expressions() happens for expressions in function RTEs. I moved it to inline_set_returning_functions(), which already has to iterate over every function RTE, and in consequence renamed that function to preprocess_function_rtes(). A useful consequence is that inline_set_returning_function() no longer has to do this for itself, simplifying that code. In passing, break out pull_up_simple_subquery's code that knows where everything that needs pullup_replace_vars() processing is, so that the new pull_up_constant_function() routine can share it. We'd gotten away with one-and-a-half copies of that code so far, since pull_up_simple_values() could assume that a lot of cases didn't apply to it --- but I don't think pull_up_constant_function() can make any simplifying assumptions. Might as well make pull_up_simple_values() use it too. (Possibly this refactoring should go further: maybe we could share some of the code to fill in the pullup_replace_vars_context struct? For now, I left it that the callers fill that completely.) Note: the one existing test case that this patch changes has to be changed because inlining its function RTEs would destroy the point of the test, namely to check join order. Alexander Kuzmenkov and Aleksandr Parfenov, reviewed by Antonin Houska and Anastasia Lubennikova, and whacked around some more by me Discussion: https://postgr.es/m/402356c32eeb93d4fed01f66d6c7fe2d@postgrespro.ru
This commit is contained in:
@@ -4870,6 +4870,10 @@ evaluate_expr(Expr *expr, Oid result_type, int32 result_typmod,
|
||||
* set-returning SQL function that can safely be inlined, expand the function
|
||||
* and return the substitute Query structure. Otherwise, return NULL.
|
||||
*
|
||||
* We assume that the RTE's expression has already been put through
|
||||
* eval_const_expressions(), which among other things will take care of
|
||||
* default arguments and named-argument notation.
|
||||
*
|
||||
* This has a good deal of similarity to inline_function(), but that's
|
||||
* for the non-set-returning case, and there are enough differences to
|
||||
* justify separate functions.
|
||||
@@ -4888,7 +4892,6 @@ inline_set_returning_function(PlannerInfo *root, RangeTblEntry *rte)
|
||||
bool modifyTargetList;
|
||||
MemoryContext oldcxt;
|
||||
MemoryContext mycxt;
|
||||
List *saveInvalItems;
|
||||
inline_error_callback_arg callback_arg;
|
||||
ErrorContextCallback sqlerrcontext;
|
||||
SQLFunctionParseInfoPtr pinfo;
|
||||
@@ -4966,7 +4969,7 @@ inline_set_returning_function(PlannerInfo *root, RangeTblEntry *rte)
|
||||
* sharing the snapshot of the calling query. We also disallow returning
|
||||
* SETOF VOID, because inlining would result in exposing the actual result
|
||||
* of the function's last SELECT, which should not happen in that case.
|
||||
* (Rechecking prokind and proretset is just paranoia.)
|
||||
* (Rechecking prokind, proretset, and pronargs is just paranoia.)
|
||||
*/
|
||||
if (funcform->prolang != SQLlanguageId ||
|
||||
funcform->prokind != PROKIND_FUNCTION ||
|
||||
@@ -4975,6 +4978,7 @@ inline_set_returning_function(PlannerInfo *root, RangeTblEntry *rte)
|
||||
funcform->prorettype == VOIDOID ||
|
||||
funcform->prosecdef ||
|
||||
!funcform->proretset ||
|
||||
list_length(fexpr->args) != funcform->pronargs ||
|
||||
!heap_attisnull(func_tuple, Anum_pg_proc_proconfig, NULL))
|
||||
{
|
||||
ReleaseSysCache(func_tuple);
|
||||
@@ -4990,16 +4994,6 @@ inline_set_returning_function(PlannerInfo *root, RangeTblEntry *rte)
|
||||
ALLOCSET_DEFAULT_SIZES);
|
||||
oldcxt = MemoryContextSwitchTo(mycxt);
|
||||
|
||||
/*
|
||||
* When we call eval_const_expressions below, it might try to add items to
|
||||
* root->glob->invalItems. Since it is running in the temp context, those
|
||||
* items will be in that context, and will need to be copied out if we're
|
||||
* successful. Temporarily reset the list so that we can keep those items
|
||||
* separate from the pre-existing list contents.
|
||||
*/
|
||||
saveInvalItems = root->glob->invalItems;
|
||||
root->glob->invalItems = NIL;
|
||||
|
||||
/* Fetch the function body */
|
||||
tmp = SysCacheGetAttr(PROCOID,
|
||||
func_tuple,
|
||||
@@ -5021,24 +5015,6 @@ inline_set_returning_function(PlannerInfo *root, RangeTblEntry *rte)
|
||||
sqlerrcontext.previous = error_context_stack;
|
||||
error_context_stack = &sqlerrcontext;
|
||||
|
||||
/*
|
||||
* Run eval_const_expressions on the function call. This is necessary to
|
||||
* ensure that named-argument notation is converted to positional notation
|
||||
* and any default arguments are inserted. It's a bit of overkill for the
|
||||
* arguments, since they'll get processed again later, but no harm will be
|
||||
* done.
|
||||
*/
|
||||
fexpr = (FuncExpr *) eval_const_expressions(root, (Node *) fexpr);
|
||||
|
||||
/* It should still be a call of the same function, but let's check */
|
||||
if (!IsA(fexpr, FuncExpr) ||
|
||||
fexpr->funcid != func_oid)
|
||||
goto fail;
|
||||
|
||||
/* Arg list length should now match the function */
|
||||
if (list_length(fexpr->args) != funcform->pronargs)
|
||||
goto fail;
|
||||
|
||||
/*
|
||||
* Set up to handle parameters while parsing the function body. We can
|
||||
* use the FuncExpr just created as the input for
|
||||
@@ -5129,10 +5105,6 @@ inline_set_returning_function(PlannerInfo *root, RangeTblEntry *rte)
|
||||
|
||||
querytree = copyObject(querytree);
|
||||
|
||||
/* copy up any new invalItems, too */
|
||||
root->glob->invalItems = list_concat(saveInvalItems,
|
||||
copyObject(root->glob->invalItems));
|
||||
|
||||
MemoryContextDelete(mycxt);
|
||||
error_context_stack = sqlerrcontext.previous;
|
||||
ReleaseSysCache(func_tuple);
|
||||
@@ -5153,7 +5125,6 @@ inline_set_returning_function(PlannerInfo *root, RangeTblEntry *rte)
|
||||
/* Here if func is not inlinable: release temp memory and return NULL */
|
||||
fail:
|
||||
MemoryContextSwitchTo(oldcxt);
|
||||
root->glob->invalItems = saveInvalItems;
|
||||
MemoryContextDelete(mycxt);
|
||||
error_context_stack = sqlerrcontext.previous;
|
||||
ReleaseSysCache(func_tuple);
|
||||
|
Reference in New Issue
Block a user