From d6409f6d61019e54ae5c1a49ef57b7bded80de7b Mon Sep 17 00:00:00 2001 From: "Paul A. Jungwirth" Date: Tue, 24 Jun 2025 21:30:02 -0700 Subject: [PATCH v3 2/2] Add SupportRequestInlineSRF If a set-returning function has an attached support function that can handle SupportRequestInlineSRF, then we replace the FuncExpr with a Query node built by the support function. Then the planner can rewrite the Query as if it were from a SQL-language function, merging it with the outer query. Author: Paul A. Jungwirth --- doc/src/sgml/xfunc.sgml | 101 +++++++++ src/backend/optimizer/util/clauses.c | 63 +++++- src/include/nodes/supportnodes.h | 34 +++ src/test/regress/expected/misc_functions.out | 212 +++++++++++++++++++ src/test/regress/regress.c | 114 ++++++++++ src/test/regress/sql/misc_functions.sql | 86 ++++++++ src/tools/pgindent/typedefs.list | 1 + 7 files changed, 605 insertions(+), 6 deletions(-) diff --git a/doc/src/sgml/xfunc.sgml b/doc/src/sgml/xfunc.sgml index 30219f432d9..ecd7ced8be6 100644 --- a/doc/src/sgml/xfunc.sgml +++ b/doc/src/sgml/xfunc.sgml @@ -4166,6 +4166,107 @@ supportfn(internal) returns internal expression and an actual execution of the target function. + + Similarly, a set-returning function + can implement SupportRequestInlineSRF to return a + Query node, which the planner will try to inline into + the outer query, just as PostgreSQL inlines + SQL functions. Normallly only SQL functions can be inlined, but this support + request allows a function in PL/pgSQL + or another language to build a dynamic SQL query and have it inlined too. + The Query node must be a SELECT query + that has gone through parse analysis and rewriting. + You may include Param nodes referencing the original function's + parameters, and PostgreSQL will map those appropriately + to the arguments passed by the caller. + It is the responsibility of the support function to return + a node that matches the parent function's implementation. + We make no guarantee that PostgreSQL will + never call the target function in cases that the support function could + simplify. Functions called in SELECT are not simplified. + Or if the RangeTblEntry has more than one + RangeTblFunction (such as when using + ROWS FROM), the function will not be simplified. + Ensure rigorous equivalence between the simplified expression and an actual + execution of the target function. + + + + One way to implement a SupportRequestInlineSRF support function + is to build a SQL string then parse it with pg_parse_query. + The outline of such a function might look like this: + +PG_FUNCTION_INFO_V1(my_support_function); +Datum +my_support_function(PG_FUNCTION_ARGS) +{ + Node *rawreq = (Node *) PG_GETARG_POINTER(0); + SupportRequestInlineSRF *req + RangeTblFunction *rtfunc; + FuncExpr *expr; + Query *querytree; + StringInfoData sql; + HeapTuple func_tuple; + SQLFunctionParseInfoPtr pinfo; + List *raw_parsetree_list; + + /* Return if it's not a type we handle. */ + if (!IsA(rawreq, SupportRequestInlineSRF)) + PG_RETURN_POINTER(NULL); + + /* Get things we need off the support request node. */ + req = (SupportRequestInlineSRF *) rawreq; + rtfunc = req->rtfunc; + expr = (FuncExpr *) rtfunc->funcexpr; + + /* Generate the SQL string. */ + initStringInfo(&sql); + appendStringInfo(&sql, "SELECT ..."); + + /* Build a SQLFunctionParseInfo. */ + func_tuple = SearchSysCache1(PROCOID, ObjectIdGetDatum(expr->funcid)); + if (!HeapTupleIsValid(func_tuple)) + { + ereport(WARNING, (errmsg("cache lookup failed for function %u", expr->funcid))); + PG_RETURN_POINTER(NULL); + } + pinfo = prepare_sql_fn_parse_info(func_tuple, + (Node *) expr, + expr->inputcollid); + ReleaseSysCache(func_tuple); + + /* Parse the SQL. */ + raw_parsetree_list = pg_parse_query(sql.data); + if (list_length(raw_parsetree_list) != 1) + { + ereport(WARNING, (errmsg("my_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("my_support_func rewrote to more than one node"))); + PG_RETURN_POINTER(NULL); + } + + querytree = linitial(querytree_list); + if (!IsA(querytree, Query)) + { + ereport(WARNING, (errmsg("my_support_func didn't parse to a Query"), + errdetail("Got this instead: %s", nodeToString(querytree)))); + PG_RETURN_POINTER(NULL); + } + + PG_RETURN_POINTER(querytree); +} + + + 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/util/clauses.c b/src/backend/optimizer/util/clauses.c index 77f48ff4069..312237a5e13 100644 --- a/src/backend/optimizer/util/clauses.c +++ b/src/backend/optimizer/util/clauses.c @@ -5145,6 +5145,47 @@ evaluate_expr(Expr *expr, Oid result_type, int32 result_typmod, } +/* + * inline_set_returning_function_with_support + * + * This implements inline_set_returning_function for functions with + * a support function that can handle SupportRequestInlineSRF. + * We check fewer things than inline_sql_set_returning_function, + * so that support functions can make their own decisions about what + * to handle. For instance we don't forbid a VOLATILE function. + */ +static Query * +inline_set_returning_function_with_support(PlannerInfo *root, RangeTblEntry *rte, + RangeTblFunction *rtfunc, + FuncExpr *fexpr, HeapTuple func_tuple, + Form_pg_proc funcform) +{ + SupportRequestInlineSRF req; + Node *newnode; + + /* It must have a support function. */ + Assert(funcform->prosupport); + + req.root = root; + req.type = T_SupportRequestInlineSRF; + req.rtfunc = rtfunc; + req.proc = func_tuple; + + newnode = (Node *) + DatumGetPointer(OidFunctionCall1(funcform->prosupport, + PointerGetDatum(&req))); + + if (!newnode) + return NULL; + + if (!IsA(newnode, Query)) + elog(ERROR, + "Got unexpected node type %d from %s for function %s", + newnode->type, "SupportRequestInlineSRF", NameStr(funcform->proname)); + + return (Query *) newnode; +} + /* * inline_sql_set_returning_function * @@ -5310,10 +5351,10 @@ inline_sql_set_returning_function(PlannerInfo *root, RangeTblEntry *rte, /* * inline_set_returning_function - * Attempt to "inline" an SQL set-returning function in the FROM clause. + * Attempt to "inline" a set-returning 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 + * set-returning 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 @@ -5341,7 +5382,7 @@ inline_set_returning_function(PlannerInfo *root, RangeTblEntry *rte) ErrorContextCallback sqlerrcontext; MemoryContext oldcxt; MemoryContext mycxt; - Query *querytree; + Query *querytree = NULL; Assert(rte->rtekind == RTE_FUNCTION); @@ -5429,9 +5470,19 @@ inline_set_returning_function(PlannerInfo *root, RangeTblEntry *rte) if (!heap_attisnull(func_tuple, Anum_pg_proc_proconfig, NULL)) goto fail; - querytree = inline_sql_set_returning_function(root, rte, rtfunc, fexpr, - func_oid, func_tuple, funcform, - src); + /* + * If the function has an attached support function that can handle + * SupportRequestInlineSRF, then attempt to inline with that. Return the + * result if we get one, otherwise proceed. + */ + if (funcform->prosupport) + querytree = inline_set_returning_function_with_support(root, rte, rtfunc, fexpr, + func_tuple, funcform); + + /* Try to inline automatically */ + if (!querytree) + querytree = inline_sql_set_returning_function(root, rte, rtfunc, fexpr, + func_oid, func_tuple, funcform, src); if (!querytree) goto fail; diff --git a/src/include/nodes/supportnodes.h b/src/include/nodes/supportnodes.h index 9c047cc401b..94f8a52de2b 100644 --- a/src/include/nodes/supportnodes.h +++ b/src/include/nodes/supportnodes.h @@ -33,6 +33,7 @@ #ifndef SUPPORTNODES_H #define SUPPORTNODES_H +#include "catalog/pg_proc.h" #include "nodes/plannodes.h" struct PlannerInfo; /* avoid including pathnodes.h here */ @@ -69,6 +70,39 @@ typedef struct SupportRequestSimplify FuncExpr *fcall; /* Function call to be simplified */ } SupportRequestSimplify; +/* + * The InlineSRF request allows the support function to perform plan-time + * simplification of a call to its target set-returning function. For + * example a PL/pgSQL function could build a dynamic SQL query and execute it. + * Normally only SQL functions can be inlined, but with this support function + * the dynamic query can be inlined as well. + * + * 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 function being replaced. + * The support function is only called if rtfunc->functions contains a + * single FuncExpr node. (ROWS FROM is one way to get more than one.) + * + * "proc" will be the HeapTuple for the pg_proc record of the function being + * replaced. + * + * The result should be a semantically-equivalent transformed node tree, + * or NULL if no simplification could be performed. It should be allocated + * in the CurrentMemoryContext. Do *not* return or modify the FuncExpr node + * tree, as it isn't really a separately allocated Node. But it's okay to + * use its args, or parts of it, in the result tree. + */ +typedef struct SupportRequestInlineSRF +{ + NodeTag type; + + struct PlannerInfo *root; /* Planner's infrastructure */ + RangeTblFunction *rtfunc; /* Function call to be simplified */ + HeapTuple proc; /* Function definition from pg_proc */ +} SupportRequestInlineSRF; + /* * 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/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out index c3b2b9d8603..13388ebc715 100644 --- a/src/test/regress/expected/misc_functions.out +++ b/src/test/regress/expected/misc_functions.out @@ -601,6 +601,11 @@ CREATE FUNCTION test_support_func(internal) RETURNS internal AS :'regresslib', 'test_support_func' LANGUAGE C STRICT; +-- With a support function that inlines SRFs +CREATE FUNCTION test_inline_srf_support_func(internal) + RETURNS internal + AS :'regresslib', 'test_inline_srf_support_func' + LANGUAGE C STRICT; ALTER FUNCTION my_int_eq(int, int) SUPPORT test_support_func; EXPLAIN (COSTS OFF) SELECT * FROM tenk1 a JOIN tenk1 b ON a.unique1 = b.unique1 @@ -777,6 +782,213 @@ false, true, false, true); Function Scan on generate_series g (cost=N..N rows=1000 width=N) (1 row) +-- +-- Test inlining PL/pgSQL functions +-- +-- RETURNS SETOF TEXT: +CREATE OR REPLACE 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 LEAKPROOF; +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 SELECT * FROM foo_from_bar('f1', 'text_tbl', NULL); + QUERY PLAN +---------------------------------------------------------------------- + Function Scan on foo_from_bar (cost=0.25..10.25 rows=1000 width=32) +(1 row) + +EXPLAIN SELECT * FROM foo_from_bar('f1', 'text_tbl', 'doh!'); + QUERY PLAN +---------------------------------------------------------------------- + Function Scan on foo_from_bar (cost=0.25..10.25 rows=1000 width=32) +(1 row) + +ALTER FUNCTION foo_from_bar(TEXT, TEXT, TEXT) SUPPORT test_inline_srf_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 SELECT * FROM foo_from_bar('f1', 'text_tbl', NULL); + QUERY PLAN +--------------------------------------------------------- + Seq Scan on text_tbl (cost=0.00..1.02 rows=2 width=32) +(1 row) + +EXPLAIN SELECT * FROM foo_from_bar('f1', 'text_tbl', 'doh!'); + QUERY PLAN +--------------------------------------------------------- + Seq Scan on text_tbl (cost=0.00..1.02 rows=1 width=32) + Filter: (f1 = 'doh!'::text) +(2 rows) + +DROP FUNCTION foo_from_bar; +-- RETURNS SETOF RECORD: +CREATE OR REPLACE FUNCTION foo_from_bar(colname TEXT, tablename TEXT, filter TEXT) +RETURNS SETOF RECORD +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 LEAKPROOF; +SELECT * FROM foo_from_bar('f1', 'text_tbl', NULL) AS bar(foo TEXT); + foo +------------------- + doh! + hi de ho neighbor +(2 rows) + +SELECT * FROM foo_from_bar('f1', 'text_tbl', 'doh!') AS bar(foo TEXT); + foo +------ + doh! +(1 row) + +EXPLAIN SELECT * FROM foo_from_bar('f1', 'text_tbl', NULL) AS bar(foo TEXT); + QUERY PLAN +-------------------------------------------------------------------------- + Function Scan on foo_from_bar bar (cost=0.25..10.25 rows=1000 width=32) +(1 row) + +EXPLAIN SELECT * FROM foo_from_bar('f1', 'text_tbl', 'doh!') AS bar(foo TEXT); + QUERY PLAN +-------------------------------------------------------------------------- + Function Scan on foo_from_bar bar (cost=0.25..10.25 rows=1000 width=32) +(1 row) + +ALTER FUNCTION foo_from_bar(TEXT, TEXT, TEXT) SUPPORT test_inline_srf_support_func; +SELECT * FROM foo_from_bar('f1', 'text_tbl', NULL) AS bar(foo TEXT); + foo +------------------- + doh! + hi de ho neighbor +(2 rows) + +SELECT * FROM foo_from_bar('f1', 'text_tbl', 'doh!') AS bar(foo TEXT); + foo +------ + doh! +(1 row) + +EXPLAIN SELECT * FROM foo_from_bar('f1', 'text_tbl', NULL) AS bar(foo TEXT); + QUERY PLAN +--------------------------------------------------------- + Seq Scan on text_tbl (cost=0.00..1.02 rows=2 width=32) +(1 row) + +EXPLAIN SELECT * FROM foo_from_bar('f1', 'text_tbl', 'doh!') AS bar(foo TEXT); + QUERY PLAN +--------------------------------------------------------- + Seq Scan on text_tbl (cost=0.00..1.02 rows=1 width=32) + Filter: (f1 = 'doh!'::text) +(2 rows) + +DROP FUNCTION foo_from_bar; +-- RETURNS TABLE: +CREATE OR REPLACE FUNCTION foo_from_bar(colname TEXT, tablename TEXT, filter TEXT) +RETURNS TABLE(foo 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 LEAKPROOF; +SELECT * FROM foo_from_bar('f1', 'text_tbl', NULL); + foo +------------------- + doh! + hi de ho neighbor +(2 rows) + +SELECT * FROM foo_from_bar('f1', 'text_tbl', 'doh!'); + foo +------ + doh! +(1 row) + +EXPLAIN SELECT * FROM foo_from_bar('f1', 'text_tbl', NULL); + QUERY PLAN +---------------------------------------------------------------------- + Function Scan on foo_from_bar (cost=0.25..10.25 rows=1000 width=32) +(1 row) + +EXPLAIN SELECT * FROM foo_from_bar('f1', 'text_tbl', 'doh!'); + QUERY PLAN +---------------------------------------------------------------------- + Function Scan on foo_from_bar (cost=0.25..10.25 rows=1000 width=32) +(1 row) + +ALTER FUNCTION foo_from_bar(TEXT, TEXT, TEXT) SUPPORT test_inline_srf_support_func; +SELECT * FROM foo_from_bar('f1', 'text_tbl', NULL); + foo +------------------- + doh! + hi de ho neighbor +(2 rows) + +SELECT * FROM foo_from_bar('f1', 'text_tbl', 'doh!'); + foo +------ + doh! +(1 row) + +EXPLAIN SELECT * FROM foo_from_bar('f1', 'text_tbl', NULL); + QUERY PLAN +--------------------------------------------------------- + Seq Scan on text_tbl (cost=0.00..1.02 rows=2 width=32) +(1 row) + +EXPLAIN SELECT * FROM foo_from_bar('f1', 'text_tbl', 'doh!'); + QUERY PLAN +--------------------------------------------------------- + Seq Scan on text_tbl (cost=0.00..1.02 rows=1 width=32) + 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 3dbba069024..eadb40369d4 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,11 +40,13 @@ #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" #include "utils/memutils.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/typcache.h" #define EXPECT_TRUE(expr) \ @@ -803,6 +806,117 @@ test_support_func(PG_FUNCTION_ARGS) PG_RETURN_POINTER(ret); } +PG_FUNCTION_INFO_V1(test_inline_srf_support_func); +Datum +test_inline_srf_support_func(PG_FUNCTION_ARGS) +{ + Node *rawreq = (Node *) PG_GETARG_POINTER(0); + Query *querytree = NULL; + + if (IsA(rawreq, SupportRequestInlineSRF)) + { + /* + * Assume that the target is foo_from_bar; that's safe as long as we + * don't attach this to any other set-returning function. + */ + SupportRequestInlineSRF *req = (SupportRequestInlineSRF *) 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; + + if (list_length(expr->args) != 3) + { + ereport(WARNING, (errmsg("test_inline_srf_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_srf_support_func called with non-Const parameters"))); + PG_RETURN_POINTER(NULL); + } + + c = (Const *) node; + if (c->consttype != TEXTOID) + { + ereport(WARNING, (errmsg("test_inline_srf_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_srf_support_func called with non-Const parameters"))); + PG_RETURN_POINTER(NULL); + } + + c = (Const *) node; + if (c->consttype != TEXTOID) + { + ereport(WARNING, (errmsg("test_inline_srf_support_func called with non-TEXT parameters"))); + PG_RETURN_POINTER(NULL); + } + tablename = TextDatumGetCString(c->constvalue); + + initStringInfo(&sql); + appendStringInfo(&sql, "SELECT %s::text FROM %s", quote_identifier(colname), quote_identifier(tablename)); + + /* Get filter if present */ + node = lthird(expr->args); + if (!(IsA(node, Const) && ((Const *) node)->constisnull)) + { + /* Only filter if $3 is Const */ + appendStringInfo(&sql, " WHERE %s::text = $3", quote_identifier(colname)); + } + + /* Build a SQLFunctionParseInfo. */ + + pinfo = prepare_sql_fn_parse_info(req->proc, + (Node *) expr, + expr->inputcollid); + + /* Parse the SQL. */ + raw_parsetree_list = pg_parse_query(sql.data); + if (list_length(raw_parsetree_list) != 1) + { + ereport(WARNING, (errmsg("test_inline_srf_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_srf_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_srf_support_func didn't parse to a Query"), + errdetail("Got this instead: %s", nodeToString(querytree)))); + PG_RETURN_POINTER(NULL); + } + } + + PG_RETURN_POINTER(querytree); +} + 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 23792c4132a..56c6e7a8a88 100644 --- a/src/test/regress/sql/misc_functions.sql +++ b/src/test/regress/sql/misc_functions.sql @@ -248,6 +248,12 @@ CREATE FUNCTION test_support_func(internal) AS :'regresslib', 'test_support_func' LANGUAGE C STRICT; +-- With a support function that inlines SRFs +CREATE FUNCTION test_inline_srf_support_func(internal) + RETURNS internal + AS :'regresslib', 'test_inline_srf_support_func' + LANGUAGE C STRICT; + ALTER FUNCTION my_int_eq(int, int) SUPPORT test_support_func; EXPLAIN (COSTS OFF) @@ -349,6 +355,86 @@ SELECT explain_mask_costs($$ SELECT * FROM generate_series(25.0, 2.0, 0.0) g(s);$$, false, true, false, true); +-- +-- Test inlining PL/pgSQL functions +-- + +-- RETURNS SETOF TEXT: +CREATE OR REPLACE 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 LEAKPROOF; +SELECT * FROM foo_from_bar('f1', 'text_tbl', NULL); +SELECT * FROM foo_from_bar('f1', 'text_tbl', 'doh!'); +EXPLAIN SELECT * FROM foo_from_bar('f1', 'text_tbl', NULL); +EXPLAIN SELECT * FROM foo_from_bar('f1', 'text_tbl', 'doh!'); +ALTER FUNCTION foo_from_bar(TEXT, TEXT, TEXT) SUPPORT test_inline_srf_support_func; +SELECT * FROM foo_from_bar('f1', 'text_tbl', NULL); +SELECT * FROM foo_from_bar('f1', 'text_tbl', 'doh!'); +EXPLAIN SELECT * FROM foo_from_bar('f1', 'text_tbl', NULL); +EXPLAIN SELECT * FROM foo_from_bar('f1', 'text_tbl', 'doh!'); +DROP FUNCTION foo_from_bar; +-- RETURNS SETOF RECORD: +CREATE OR REPLACE FUNCTION foo_from_bar(colname TEXT, tablename TEXT, filter TEXT) +RETURNS SETOF RECORD +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 LEAKPROOF; +SELECT * FROM foo_from_bar('f1', 'text_tbl', NULL) AS bar(foo TEXT); +SELECT * FROM foo_from_bar('f1', 'text_tbl', 'doh!') AS bar(foo TEXT); +EXPLAIN SELECT * FROM foo_from_bar('f1', 'text_tbl', NULL) AS bar(foo TEXT); +EXPLAIN SELECT * FROM foo_from_bar('f1', 'text_tbl', 'doh!') AS bar(foo TEXT); +ALTER FUNCTION foo_from_bar(TEXT, TEXT, TEXT) SUPPORT test_inline_srf_support_func; +SELECT * FROM foo_from_bar('f1', 'text_tbl', NULL) AS bar(foo TEXT); +SELECT * FROM foo_from_bar('f1', 'text_tbl', 'doh!') AS bar(foo TEXT); +EXPLAIN SELECT * FROM foo_from_bar('f1', 'text_tbl', NULL) AS bar(foo TEXT); +EXPLAIN SELECT * FROM foo_from_bar('f1', 'text_tbl', 'doh!') AS bar(foo TEXT); +DROP FUNCTION foo_from_bar; +-- RETURNS TABLE: +CREATE OR REPLACE FUNCTION foo_from_bar(colname TEXT, tablename TEXT, filter TEXT) +RETURNS TABLE(foo 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 LEAKPROOF; +SELECT * FROM foo_from_bar('f1', 'text_tbl', NULL); +SELECT * FROM foo_from_bar('f1', 'text_tbl', 'doh!'); +EXPLAIN SELECT * FROM foo_from_bar('f1', 'text_tbl', NULL); +EXPLAIN SELECT * FROM foo_from_bar('f1', 'text_tbl', 'doh!'); +ALTER FUNCTION foo_from_bar(TEXT, TEXT, TEXT) SUPPORT test_inline_srf_support_func; +SELECT * FROM foo_from_bar('f1', 'text_tbl', NULL); +SELECT * FROM foo_from_bar('f1', 'text_tbl', 'doh!'); +EXPLAIN SELECT * FROM foo_from_bar('f1', 'text_tbl', NULL); +EXPLAIN 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 e6f2e93b2d6..8b681e9e44e 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -2900,6 +2900,7 @@ SubscriptionRelState SummarizerReadLocalXLogPrivate SupportRequestCost SupportRequestIndexCondition +SupportRequestInlineSRF SupportRequestModifyInPlace SupportRequestOptimizeWindowClause SupportRequestRows -- 2.45.0