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:
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user