1
0
mirror of https://github.com/postgres/postgres.git synced 2025-12-19 17:02:53 +03:00

Add SupportRequestInlineInFrom planner support request.

This request allows a support function to replace a function call
appearing in FROM (typically a set-returning function) with an
equivalent SELECT subquery.  The subquery will then be subject
to the planner's usual optimizations, potentially allowing a much
better plan to be generated.  While the planner has long done this
automatically for simple SQL-language functions, it's now possible
for extensions to do it for functions outside that group.
Notably, this could be useful for functions that are presently
implemented in PL/pgSQL and work by generating and then EXECUTE'ing
a SQL query.

Author: Paul A Jungwirth <pj@illuminatedcomputing.com>
Reviewed-by: Tom Lane <tgl@sss.pgh.pa.us>
Discussion: https://postgr.es/m/09de6afa-c33d-4d94-a5cb-afc6cea0d2bb@illuminatedcomputing.com
This commit is contained in:
Tom Lane
2025-11-22 19:33:34 -05:00
parent c0bc9af151
commit b140c8d7a3
9 changed files with 511 additions and 171 deletions

View File

@@ -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

View File

@@ -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)

View File

@@ -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();