diff --git a/doc/src/sgml/xfunc.sgml b/doc/src/sgml/xfunc.sgml index 55a99c0ff34..537ee6fa254 100644 --- a/doc/src/sgml/xfunc.sgml +++ b/doc/src/sgml/xfunc.sgml @@ -4159,6 +4159,31 @@ supportfn(internal) returns internal expression and an actual execution of the target function. + + SupportRequestSimplify is not used + for set-returning + functions. Instead, support functions can implement + the SupportRequestInlineInFrom request to expand + function calls appearing in the FROM clause of a + query. (It's also allowed to support this request for + non-set-returning functions, although + typically SupportRequestSimplify would serve as + well.) For this request type, a successful result must be + a SELECT Query tree, which will replace + the FROM item as though a sub-select had been + written instead. The Query tree must appear as it would after parse + analysis and rewrite processing. One way to ensure that that's true + is to build a SQL string then feed it + through pg_parse_query + and pg_analyze_and_rewrite, or related + functions. PARAM_EXTERN Param + nodes can appear within the Query to represent the function's + arguments; they will be replaced by the actual argument expressions. + As with SupportRequestSimplify, it is the support + function's responsibility that the replacement Query be equivalent to + normal execution of the target function. + + For target functions that return boolean, it is often useful to estimate the fraction of rows that will be selected by a WHERE clause using that diff --git a/src/backend/optimizer/prep/prepjointree.c b/src/backend/optimizer/prep/prepjointree.c index 481d8011791..7581695647d 100644 --- a/src/backend/optimizer/prep/prepjointree.c +++ b/src/backend/optimizer/prep/prepjointree.c @@ -1066,13 +1066,15 @@ pull_up_sublinks_qual_recurse(PlannerInfo *root, Node *node, /* * preprocess_function_rtes * Constant-simplify any FUNCTION RTEs in the FROM clause, and then - * attempt to "inline" any that are set-returning functions. + * attempt to "inline" any that can be converted to simple subqueries. * - * If an RTE_FUNCTION rtable entry invokes a set-returning function that + * If an RTE_FUNCTION rtable entry invokes a set-returning SQL 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. + * RTE_SUBQUERY entry exposing the SELECT directly. Other sorts of functions + * are also inline-able if they have a support function that can generate + * the replacement sub-Query. 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 @@ -1107,7 +1109,7 @@ preprocess_function_rtes(PlannerInfo *root) eval_const_expressions(root, (Node *) rte->functions); /* Check safety of expansion, and expand if possible */ - funcquery = inline_set_returning_function(root, rte); + funcquery = inline_function_in_from(root, rte); if (funcquery) { /* Successful expansion, convert the RTE to a subquery */ diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c index 81d768ff2a2..202ba8ed4bb 100644 --- a/src/backend/optimizer/util/clauses.c +++ b/src/backend/optimizer/util/clauses.c @@ -82,7 +82,7 @@ typedef struct int nargs; List *args; int sublevels_up; -} substitute_actual_srf_parameters_context; +} substitute_actual_parameters_in_from_context; typedef struct { @@ -154,10 +154,16 @@ static Node *substitute_actual_parameters(Node *expr, int nargs, List *args, static Node *substitute_actual_parameters_mutator(Node *node, substitute_actual_parameters_context *context); static void sql_inline_error_callback(void *arg); -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); +static Query *inline_sql_function_in_from(PlannerInfo *root, + RangeTblFunction *rtfunc, + FuncExpr *fexpr, + HeapTuple func_tuple, + Form_pg_proc funcform, + const char *src); +static Query *substitute_actual_parameters_in_from(Query *expr, + int nargs, List *args); +static Node *substitute_actual_parameters_in_from_mutator(Node *node, + substitute_actual_parameters_in_from_context *context); static bool pull_paramids_walker(Node *node, Bitmapset **context); @@ -5149,50 +5155,42 @@ 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. + * inline_function_in_from + * Attempt to "inline" a function in the FROM clause. * * "rte" is 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. + * function that can 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 + * for the general-expression case, and there are enough differences to * justify separate functions. */ Query * -inline_set_returning_function(PlannerInfo *root, RangeTblEntry *rte) +inline_function_in_from(PlannerInfo *root, RangeTblEntry *rte) { RangeTblFunction *rtfunc; FuncExpr *fexpr; Oid func_oid; HeapTuple func_tuple; Form_pg_proc funcform; - char *src; - Datum tmp; - bool isNull; MemoryContext oldcxt; MemoryContext mycxt; + Datum tmp; + char *src; inline_error_callback_arg callback_arg; ErrorContextCallback sqlerrcontext; - SQLFunctionParseInfoPtr pinfo; - TypeFuncClass functypclass; - TupleDesc rettupdesc; - List *raw_parsetree_list; - List *querytree_list; - Query *querytree; + Query *querytree = NULL; Assert(rte->rtekind == RTE_FUNCTION); /* - * 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.) + * Guard against infinite recursion during expansion by checking for stack + * overflow. (There's no need to do more.) */ check_stack_depth(); @@ -5211,14 +5209,6 @@ inline_set_returning_function(PlannerInfo *root, RangeTblEntry *rte) func_oid = fexpr->funcid; - /* - * 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; - /* * Refuse to inline if the arguments contain any volatile functions or * sub-selects. Volatile functions are rejected because inlining may @@ -5249,24 +5239,10 @@ inline_set_returning_function(PlannerInfo *root, RangeTblEntry *rte) 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. 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, proretset, and pronargs is just paranoia.) + * If the function SETs any configuration parameters, inlining would cause + * us to miss making those changes. */ - if (funcform->prolang != SQLlanguageId || - funcform->prokind != PROKIND_FUNCTION || - funcform->proisstrict || - funcform->provolatile == PROVOLATILE_VOLATILE || - funcform->prorettype == VOIDOID || - funcform->prosecdef || - !funcform->proretset || - list_length(fexpr->args) != funcform->pronargs || - !heap_attisnull(func_tuple, Anum_pg_proc_proconfig, NULL)) + if (!heap_attisnull(func_tuple, Anum_pg_proc_proconfig, NULL)) { ReleaseSysCache(func_tuple); return NULL; @@ -5274,10 +5250,11 @@ inline_set_returning_function(PlannerInfo *root, RangeTblEntry *rte) /* * Make a temporary memory context, so that we don't leak all the stuff - * that parsing might create. + * that parsing and rewriting might create. If we succeed, we'll copy + * just the finished query tree back up to the caller's context. */ mycxt = AllocSetContextCreate(CurrentMemoryContext, - "inline_set_returning_function", + "inline_function_in_from", ALLOCSET_DEFAULT_SIZES); oldcxt = MemoryContextSwitchTo(mycxt); @@ -5285,9 +5262,30 @@ inline_set_returning_function(PlannerInfo *root, RangeTblEntry *rte) tmp = SysCacheGetAttrNotNull(PROCOID, func_tuple, Anum_pg_proc_prosrc); src = TextDatumGetCString(tmp); + /* + * If the function has an attached support function that can handle + * SupportRequestInlineInFrom, then attempt to inline with that. + */ + if (funcform->prosupport) + { + SupportRequestInlineInFrom req; + + req.type = T_SupportRequestInlineInFrom; + req.root = root; + req.rtfunc = rtfunc; + req.proc = func_tuple; + + querytree = (Query *) + DatumGetPointer(OidFunctionCall1(funcform->prosupport, + PointerGetDatum(&req))); + } + /* * Setup error traceback support for ereport(). This is so that we can - * finger the function that bad information came from. + * finger the function that bad information came from. We don't install + * this while running the support function, since it'd be likely to do the + * wrong thing: any parse errors reported during that are very likely not + * against the raw function source text. */ callback_arg.proname = NameStr(funcform->proname); callback_arg.prosrc = src; @@ -5297,117 +5295,29 @@ inline_set_returning_function(PlannerInfo *root, RangeTblEntry *rte) sqlerrcontext.previous = error_context_stack; error_context_stack = &sqlerrcontext; - /* If we have prosqlbody, pay attention to that not prosrc */ - tmp = SysCacheGetAttr(PROCOID, - func_tuple, - Anum_pg_proc_prosqlbody, - &isNull); - if (!isNull) - { - Node *n; + /* + * If SupportRequestInlineInFrom didn't work, try our built-in inlining + * mechanism. + */ + if (!querytree) + querytree = inline_sql_function_in_from(root, rtfunc, fexpr, + func_tuple, funcform, src); - n = stringToNode(TextDatumGetCString(tmp)); - if (IsA(n, List)) - querytree_list = linitial_node(List, castNode(List, n)); - else - querytree_list = list_make1(n); - if (list_length(querytree_list) != 1) - goto fail; - querytree = linitial(querytree_list); - - /* Acquire necessary locks, then apply rewriter. */ - AcquireRewriteLocks(querytree, true, false); - querytree_list = pg_rewrite_query(querytree); - if (list_length(querytree_list) != 1) - goto fail; - querytree = linitial(querytree_list); - } - else - { - /* - * Set up to handle parameters while parsing the function body. We - * can use the FuncExpr just created as the input for - * prepare_sql_fn_parse_info. - */ - pinfo = prepare_sql_fn_parse_info(func_tuple, - (Node *) fexpr, - fexpr->inputcollid); - - /* - * 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_withcb(linitial(raw_parsetree_list), - src, - (ParserSetupHook) sql_fn_parser_setup, - pinfo, NULL); - if (list_length(querytree_list) != 1) - goto fail; - querytree = linitial(querytree_list); - } + if (!querytree) + goto fail; /* no luck there either, fail */ /* - * Also resolve the actual function result tupdesc, if composite. If we - * have a coldeflist, believe that; otherwise use get_expr_result_type. - * (This logic should match ExecInitFunctionScan.) + * The result had better be a SELECT Query. */ - if (rtfunc->funccolnames != NIL) - { - functypclass = TYPEFUNC_RECORD; - rettupdesc = BuildDescFromLists(rtfunc->funccolnames, - rtfunc->funccoltypes, - rtfunc->funccoltypmods, - rtfunc->funccolcollations); - } - else - functypclass = get_expr_result_type((Node *) fexpr, NULL, &rettupdesc); - - /* - * The single command must be a plain SELECT. - */ - if (!IsA(querytree, Query) || - querytree->commandType != CMD_SELECT) - 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 - * coercions if needed to make the tlist expression(s) match the declared - * type of the function. We also ask it to insert dummy NULL columns for - * any dropped columns in rettupdesc, so that the elements of the modified - * tlist match up to the attribute numbers. - * - * 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(list_make1(querytree_list), - fexpr->funcresulttype, rettupdesc, - funcform->prokind, - true) && - (functypclass == TYPEFUNC_COMPOSITE || - functypclass == TYPEFUNC_COMPOSITE_DOMAIN || - functypclass == TYPEFUNC_RECORD)) - goto fail; /* reject not-whole-tuple-result cases */ - - /* - * check_sql_fn_retval might've inserted a projection step, but that's - * fine; just make sure we use the upper Query. - */ - querytree = linitial_node(Query, querytree_list); + Assert(IsA(querytree, Query)); + Assert(querytree->commandType == CMD_SELECT); /* * Looks good --- substitute parameters into the query. */ - querytree = substitute_actual_srf_parameters(querytree, - funcform->pronargs, - fexpr->args); + querytree = substitute_actual_parameters_in_from(querytree, + funcform->pronargs, + fexpr->args); /* * Copy the modified query out of the temporary memory context, and clean @@ -5451,6 +5361,173 @@ fail: return NULL; } +/* + * inline_sql_function_in_from + * + * This implements inline_function_in_from for SQL-language functions. + * Returns NULL if the function couldn't be inlined. + * + * The division of labor between here and inline_function_in_from is based + * on the rule that inline_function_in_from should make all checks that are + * certain to be required in both this case and the support-function case. + * Support functions might also want to make checks analogous to the ones + * made here, but then again they might not, or they might just assume that + * the function they are attached to can validly be inlined. + */ +static Query * +inline_sql_function_in_from(PlannerInfo *root, + RangeTblFunction *rtfunc, + FuncExpr *fexpr, + HeapTuple func_tuple, + Form_pg_proc funcform, + const char *src) +{ + Datum sqlbody; + bool isNull; + List *querytree_list; + Query *querytree; + TypeFuncClass functypclass; + TupleDesc rettupdesc; + + /* + * 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; + + /* + * 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. 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, proretset, and pronargs is just paranoia.) + */ + if (funcform->prolang != SQLlanguageId || + funcform->prokind != PROKIND_FUNCTION || + funcform->proisstrict || + funcform->provolatile == PROVOLATILE_VOLATILE || + funcform->prorettype == VOIDOID || + funcform->prosecdef || + !funcform->proretset || + list_length(fexpr->args) != funcform->pronargs) + return NULL; + + /* If we have prosqlbody, pay attention to that not prosrc */ + sqlbody = SysCacheGetAttr(PROCOID, + func_tuple, + Anum_pg_proc_prosqlbody, + &isNull); + if (!isNull) + { + Node *n; + + n = stringToNode(TextDatumGetCString(sqlbody)); + if (IsA(n, List)) + querytree_list = linitial_node(List, castNode(List, n)); + else + querytree_list = list_make1(n); + if (list_length(querytree_list) != 1) + return NULL; + querytree = linitial(querytree_list); + + /* Acquire necessary locks, then apply rewriter. */ + AcquireRewriteLocks(querytree, true, false); + querytree_list = pg_rewrite_query(querytree); + if (list_length(querytree_list) != 1) + return NULL; + querytree = linitial(querytree_list); + } + else + { + SQLFunctionParseInfoPtr pinfo; + List *raw_parsetree_list; + + /* + * Set up to handle parameters while parsing the function body. We + * can use the FuncExpr just created as the input for + * prepare_sql_fn_parse_info. + */ + pinfo = prepare_sql_fn_parse_info(func_tuple, + (Node *) fexpr, + fexpr->inputcollid); + + /* + * 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) + return NULL; + + querytree_list = pg_analyze_and_rewrite_withcb(linitial(raw_parsetree_list), + src, + (ParserSetupHook) sql_fn_parser_setup, + pinfo, NULL); + if (list_length(querytree_list) != 1) + return NULL; + querytree = linitial(querytree_list); + } + + /* + * Also resolve the actual function result tupdesc, if composite. If we + * have a coldeflist, believe that; otherwise use get_expr_result_type. + * (This logic should match ExecInitFunctionScan.) + */ + if (rtfunc->funccolnames != NIL) + { + functypclass = TYPEFUNC_RECORD; + rettupdesc = BuildDescFromLists(rtfunc->funccolnames, + rtfunc->funccoltypes, + rtfunc->funccoltypmods, + rtfunc->funccolcollations); + } + else + functypclass = get_expr_result_type((Node *) fexpr, NULL, &rettupdesc); + + /* + * The single command must be a plain SELECT. + */ + if (!IsA(querytree, Query) || + querytree->commandType != CMD_SELECT) + return NULL; + + /* + * 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 + * coercions if needed to make the tlist expression(s) match the declared + * type of the function. We also ask it to insert dummy NULL columns for + * any dropped columns in rettupdesc, so that the elements of the modified + * tlist match up to the attribute numbers. + * + * 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(list_make1(querytree_list), + fexpr->funcresulttype, rettupdesc, + funcform->prokind, + true) && + (functypclass == TYPEFUNC_COMPOSITE || + functypclass == TYPEFUNC_COMPOSITE_DOMAIN || + functypclass == TYPEFUNC_RECORD)) + return NULL; /* reject not-whole-tuple-result cases */ + + /* + * check_sql_fn_retval might've inserted a projection step, but that's + * fine; just make sure we use the upper Query. + */ + querytree = linitial_node(Query, querytree_list); + + return querytree; +} + /* * Replace Param nodes by appropriate actual parameters * @@ -5458,23 +5535,23 @@ fail: * that it needs its own code. */ static Query * -substitute_actual_srf_parameters(Query *expr, int nargs, List *args) +substitute_actual_parameters_in_from(Query *expr, int nargs, List *args) { - substitute_actual_srf_parameters_context context; + substitute_actual_parameters_in_from_context context; context.nargs = nargs; context.args = args; context.sublevels_up = 1; return query_tree_mutator(expr, - substitute_actual_srf_parameters_mutator, + substitute_actual_parameters_in_from_mutator, &context, 0); } static Node * -substitute_actual_srf_parameters_mutator(Node *node, - substitute_actual_srf_parameters_context *context) +substitute_actual_parameters_in_from_mutator(Node *node, + substitute_actual_parameters_in_from_context *context) { Node *result; @@ -5484,7 +5561,7 @@ substitute_actual_srf_parameters_mutator(Node *node, { context->sublevels_up++; result = (Node *) query_tree_mutator((Query *) node, - substitute_actual_srf_parameters_mutator, + substitute_actual_parameters_in_from_mutator, context, 0); context->sublevels_up--; @@ -5509,7 +5586,7 @@ substitute_actual_srf_parameters_mutator(Node *node, } } return expression_tree_mutator(node, - substitute_actual_srf_parameters_mutator, + substitute_actual_parameters_in_from_mutator, context); } diff --git a/src/include/nodes/supportnodes.h b/src/include/nodes/supportnodes.h index 7b623d54058..ea774c7ef6a 100644 --- a/src/include/nodes/supportnodes.h +++ b/src/include/nodes/supportnodes.h @@ -39,6 +39,8 @@ typedef struct PlannerInfo PlannerInfo; /* avoid including pathnodes.h here */ typedef struct IndexOptInfo IndexOptInfo; typedef struct SpecialJoinInfo SpecialJoinInfo; typedef struct WindowClause WindowClause; +typedef struct RangeTblFunction RangeTblFunction; /* ditto for parsenodes.h */ +typedef struct HeapTupleData *HeapTuple; /* and htup.h too */ /* * The Simplify request allows the support function to perform plan-time @@ -69,6 +71,34 @@ typedef struct SupportRequestSimplify FuncExpr *fcall; /* Function call to be simplified */ } SupportRequestSimplify; +/* + * The InlineInFrom request allows the support function to perform plan-time + * simplification of a call to its target function that appears in FROM. + * The rules for this are sufficiently different from ordinary expressions + * that it's best to make this a separate request from Simplify. + * + * The planner's PlannerInfo "root" is typically not needed, but can be + * consulted if it's necessary to obtain info about Vars present in + * the given node tree. Beware that root could be NULL in some usages. + * + * "rtfunc" will be a RangeTblFunction node for the support function's target + * function. The call appeared alone (and without ORDINALITY) in FROM. + * + * "proc" will be the HeapTuple for the pg_proc row of the target function. + * + * The result should be a semantically-equivalent SELECT Query tree, + * or NULL if no simplification could be performed. The tree must have + * been passed through parse analysis and rewrite. + */ +typedef struct SupportRequestInlineInFrom +{ + NodeTag type; + + PlannerInfo *root; /* Planner's infrastructure */ + RangeTblFunction *rtfunc; /* Function call to be simplified */ + HeapTuple proc; /* Function definition from pg_proc */ +} SupportRequestInlineInFrom; + /* * The Selectivity request allows the support function to provide a * selectivity estimate for a function appearing at top level of a WHERE diff --git a/src/include/optimizer/clauses.h b/src/include/optimizer/clauses.h index 0dffec00ede..fc38eae5c5a 100644 --- a/src/include/optimizer/clauses.h +++ b/src/include/optimizer/clauses.h @@ -50,8 +50,8 @@ extern int NumRelids(PlannerInfo *root, Node *clause); extern void CommuteOpExpr(OpExpr *clause); -extern Query *inline_set_returning_function(PlannerInfo *root, - RangeTblEntry *rte); +extern Query *inline_function_in_from(PlannerInfo *root, + RangeTblEntry *rte); extern Bitmapset *pull_paramids(Expr *expr); diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out index e76e28b95ce..d7d965d884a 100644 --- a/src/test/regress/expected/misc_functions.out +++ b/src/test/regress/expected/misc_functions.out @@ -808,6 +808,56 @@ false, true, false, true); Function Scan on generate_series g (cost=N..N rows=1000 width=N) (1 row) +-- +-- Test SupportRequestInlineInFrom request +-- +CREATE FUNCTION test_inline_in_from_support_func(internal) + RETURNS internal + AS :'regresslib', 'test_inline_in_from_support_func' + LANGUAGE C STRICT; +CREATE FUNCTION foo_from_bar(colname TEXT, tablename TEXT, filter TEXT) +RETURNS SETOF TEXT +LANGUAGE plpgsql +AS $function$ +DECLARE + sql TEXT; +BEGIN + sql := format('SELECT %I::text FROM %I', colname, tablename); + IF filter IS NOT NULL THEN + sql := CONCAT(sql, format(' WHERE %I::text = $1', colname)); + END IF; + RETURN QUERY EXECUTE sql USING filter; +END; +$function$ STABLE; +ALTER FUNCTION foo_from_bar(TEXT, TEXT, TEXT) + SUPPORT test_inline_in_from_support_func; +SELECT * FROM foo_from_bar('f1', 'text_tbl', NULL); + foo_from_bar +------------------- + doh! + hi de ho neighbor +(2 rows) + +SELECT * FROM foo_from_bar('f1', 'text_tbl', 'doh!'); + foo_from_bar +-------------- + doh! +(1 row) + +EXPLAIN (COSTS OFF) SELECT * FROM foo_from_bar('f1', 'text_tbl', NULL); + QUERY PLAN +---------------------- + Seq Scan on text_tbl +(1 row) + +EXPLAIN (COSTS OFF) SELECT * FROM foo_from_bar('f1', 'text_tbl', 'doh!'); + QUERY PLAN +------------------------------- + Seq Scan on text_tbl + Filter: (f1 = 'doh!'::text) +(2 rows) + +DROP FUNCTION foo_from_bar; -- Test functions for control data SELECT count(*) > 0 AS ok FROM pg_control_checkpoint(); ok diff --git a/src/test/regress/regress.c b/src/test/regress/regress.c index a2db6080876..56cc0567b1c 100644 --- a/src/test/regress/regress.c +++ b/src/test/regress/regress.c @@ -28,6 +28,7 @@ #include "commands/sequence.h" #include "commands/trigger.h" #include "executor/executor.h" +#include "executor/functions.h" #include "executor/spi.h" #include "funcapi.h" #include "mb/pg_wchar.h" @@ -39,6 +40,7 @@ #include "port/atomics.h" #include "postmaster/postmaster.h" /* for MAX_BACKENDS */ #include "storage/spin.h" +#include "tcop/tcopprot.h" #include "utils/array.h" #include "utils/builtins.h" #include "utils/geo_decls.h" @@ -803,6 +805,125 @@ test_support_func(PG_FUNCTION_ARGS) PG_RETURN_POINTER(ret); } +PG_FUNCTION_INFO_V1(test_inline_in_from_support_func); +Datum +test_inline_in_from_support_func(PG_FUNCTION_ARGS) +{ + Node *rawreq = (Node *) PG_GETARG_POINTER(0); + + if (IsA(rawreq, SupportRequestInlineInFrom)) + { + /* + * Assume that the target is foo_from_bar; that's safe as long as we + * don't attach this to any other function. + */ + SupportRequestInlineInFrom *req = (SupportRequestInlineInFrom *) rawreq; + StringInfoData sql; + RangeTblFunction *rtfunc = req->rtfunc; + FuncExpr *expr = (FuncExpr *) rtfunc->funcexpr; + Node *node; + Const *c; + char *colname; + char *tablename; + SQLFunctionParseInfoPtr pinfo; + List *raw_parsetree_list; + List *querytree_list; + Query *querytree; + + if (list_length(expr->args) != 3) + { + ereport(WARNING, (errmsg("test_inline_in_from_support_func called with %d args but expected 3", list_length(expr->args)))); + PG_RETURN_POINTER(NULL); + } + + /* Get colname */ + node = linitial(expr->args); + if (!IsA(node, Const)) + { + ereport(WARNING, (errmsg("test_inline_in_from_support_func called with non-Const parameters"))); + PG_RETURN_POINTER(NULL); + } + + c = (Const *) node; + if (c->consttype != TEXTOID || c->constisnull) + { + ereport(WARNING, (errmsg("test_inline_in_from_support_func called with non-TEXT parameters"))); + PG_RETURN_POINTER(NULL); + } + colname = TextDatumGetCString(c->constvalue); + + /* Get tablename */ + node = lsecond(expr->args); + if (!IsA(node, Const)) + { + ereport(WARNING, (errmsg("test_inline_in_from_support_func called with non-Const parameters"))); + PG_RETURN_POINTER(NULL); + } + + c = (Const *) node; + if (c->consttype != TEXTOID || c->constisnull) + { + ereport(WARNING, (errmsg("test_inline_in_from_support_func called with non-TEXT parameters"))); + PG_RETURN_POINTER(NULL); + } + tablename = TextDatumGetCString(c->constvalue); + + /* Begin constructing replacement SELECT query. */ + initStringInfo(&sql); + appendStringInfo(&sql, "SELECT %s::text FROM %s", + quote_identifier(colname), + quote_identifier(tablename)); + + /* Add filter expression if present. */ + node = lthird(expr->args); + if (!(IsA(node, Const) && ((Const *) node)->constisnull)) + { + /* + * We only filter if $3 is not constant-NULL. This is not a very + * exact implementation of the PL/pgSQL original, but it's close + * enough for demonstration purposes. + */ + appendStringInfo(&sql, " WHERE %s::text = $3", + quote_identifier(colname)); + } + + /* Build a SQLFunctionParseInfo with the parameters of my function. */ + pinfo = prepare_sql_fn_parse_info(req->proc, + (Node *) expr, + expr->inputcollid); + + /* Parse the generated SQL. */ + raw_parsetree_list = pg_parse_query(sql.data); + if (list_length(raw_parsetree_list) != 1) + { + ereport(WARNING, (errmsg("test_inline_in_from_support_func parsed to more than one node"))); + PG_RETURN_POINTER(NULL); + } + + /* Analyze the parse tree as if it were a SQL-language body. */ + querytree_list = pg_analyze_and_rewrite_withcb(linitial(raw_parsetree_list), + sql.data, + (ParserSetupHook) sql_fn_parser_setup, + pinfo, NULL); + if (list_length(querytree_list) != 1) + { + ereport(WARNING, (errmsg("test_inline_in_from_support_func rewrote to more than one node"))); + PG_RETURN_POINTER(NULL); + } + + querytree = linitial(querytree_list); + if (!IsA(querytree, Query)) + { + ereport(WARNING, (errmsg("test_inline_in_from_support_func didn't parse to a Query"))); + PG_RETURN_POINTER(NULL); + } + + PG_RETURN_POINTER(querytree); + } + + PG_RETURN_POINTER(NULL); +} + PG_FUNCTION_INFO_V1(test_opclass_options_func); Datum test_opclass_options_func(PG_FUNCTION_ARGS) diff --git a/src/test/regress/sql/misc_functions.sql b/src/test/regress/sql/misc_functions.sql index 220472d5ad1..0fc20fbb6b4 100644 --- a/src/test/regress/sql/misc_functions.sql +++ b/src/test/regress/sql/misc_functions.sql @@ -360,6 +360,40 @@ SELECT explain_mask_costs($$ SELECT * FROM generate_series(25.0, 2.0, 0.0) g(s);$$, false, true, false, true); +-- +-- Test SupportRequestInlineInFrom request +-- + +CREATE FUNCTION test_inline_in_from_support_func(internal) + RETURNS internal + AS :'regresslib', 'test_inline_in_from_support_func' + LANGUAGE C STRICT; + +CREATE FUNCTION foo_from_bar(colname TEXT, tablename TEXT, filter TEXT) +RETURNS SETOF TEXT +LANGUAGE plpgsql +AS $function$ +DECLARE + sql TEXT; +BEGIN + sql := format('SELECT %I::text FROM %I', colname, tablename); + IF filter IS NOT NULL THEN + sql := CONCAT(sql, format(' WHERE %I::text = $1', colname)); + END IF; + RETURN QUERY EXECUTE sql USING filter; +END; +$function$ STABLE; + +ALTER FUNCTION foo_from_bar(TEXT, TEXT, TEXT) + SUPPORT test_inline_in_from_support_func; + +SELECT * FROM foo_from_bar('f1', 'text_tbl', NULL); +SELECT * FROM foo_from_bar('f1', 'text_tbl', 'doh!'); +EXPLAIN (COSTS OFF) SELECT * FROM foo_from_bar('f1', 'text_tbl', NULL); +EXPLAIN (COSTS OFF) SELECT * FROM foo_from_bar('f1', 'text_tbl', 'doh!'); + +DROP FUNCTION foo_from_bar; + -- Test functions for control data SELECT count(*) > 0 AS ok FROM pg_control_checkpoint(); SELECT count(*) > 0 AS ok FROM pg_control_init(); diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 27a4d131897..0d1ea4ec63d 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -2917,6 +2917,7 @@ SubscriptionRelState SummarizerReadLocalXLogPrivate SupportRequestCost SupportRequestIndexCondition +SupportRequestInlineInFrom SupportRequestModifyInPlace SupportRequestOptimizeWindowClause SupportRequestRows @@ -4140,7 +4141,7 @@ storeRes_func stream_stop_callback string substitute_actual_parameters_context -substitute_actual_srf_parameters_context +substitute_actual_parameters_in_from_context substitute_grouped_columns_context substitute_phv_relids_context subxids_array_status