Skip to content

Commit b1160b1

Browse files
pjungwirCommitfest Bot
authored andcommitted
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 <[email protected]>
1 parent f0c19b6 commit b1160b1

File tree

7 files changed

+605
-6
lines changed

7 files changed

+605
-6
lines changed

doc/src/sgml/xfunc.sgml

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4159,6 +4159,107 @@ supportfn(internal) returns internal
41594159
expression and an actual execution of the target function.
41604160
</para>
41614161

4162+
<para>
4163+
Similarly, a <link linkend="queries-tablefunctions">set-returning function</link>
4164+
can implement <literal>SupportRequestInlineSRF</literal> to return a
4165+
<literal>Query</literal> node, which the planner will try to inline into
4166+
the outer query, just as <productname>PostgreSQL</productname> inlines
4167+
SQL functions. Normallly only SQL functions can be inlined, but this support
4168+
request allows a function in <link linkend="plpgsql">PL/pgSQL</link>
4169+
or another language to build a dynamic SQL query and have it inlined too.
4170+
The <literal>Query</literal> node must be a <literal>SELECT</literal> query
4171+
that has gone through parse analysis and rewriting.
4172+
You may include <literal>Param</literal> nodes referencing the original function's
4173+
parameters, and <productname>PostgreSQL</productname> will map those appropriately
4174+
to the arguments passed by the caller.
4175+
It is the responsibility of the support function to return
4176+
a node that matches the parent function's implementation.
4177+
We make no guarantee that <productname>PostgreSQL</productname> will
4178+
never call the target function in cases that the support function could
4179+
simplify. Functions called in <literal>SELECT</literal> are not simplified.
4180+
Or if the <literal>RangeTblEntry</literal> has more than one
4181+
<literal>RangeTblFunction</literal> (such as when using
4182+
<literal>ROWS FROM</literal>), the function will not be simplified.
4183+
Ensure rigorous equivalence between the simplified expression and an actual
4184+
execution of the target function.
4185+
</para>
4186+
4187+
<para>
4188+
One way to implement a <literal>SupportRequestInlineSRF</literal> support function
4189+
is to build a SQL string then parse it with <literal>pg_parse_query</literal>.
4190+
The outline of such a function might look like this:
4191+
<programlisting>
4192+
PG_FUNCTION_INFO_V1(my_support_function);
4193+
Datum
4194+
my_support_function(PG_FUNCTION_ARGS)
4195+
{
4196+
Node *rawreq = (Node *) PG_GETARG_POINTER(0);
4197+
SupportRequestInlineSRF *req
4198+
RangeTblFunction *rtfunc;
4199+
FuncExpr *expr;
4200+
Query *querytree;
4201+
StringInfoData sql;
4202+
HeapTuple func_tuple;
4203+
SQLFunctionParseInfoPtr pinfo;
4204+
List *raw_parsetree_list;
4205+
4206+
/* Return if it's not a type we handle. */
4207+
if (!IsA(rawreq, SupportRequestInlineSRF))
4208+
PG_RETURN_POINTER(NULL);
4209+
4210+
/* Get things we need off the support request node. */
4211+
req = (SupportRequestInlineSRF *) rawreq;
4212+
rtfunc = req->rtfunc;
4213+
expr = (FuncExpr *) rtfunc->funcexpr;
4214+
4215+
/* Generate the SQL string. */
4216+
initStringInfo(&amp;sql);
4217+
appendStringInfo(&amp;sql, "SELECT ...");
4218+
4219+
/* Build a SQLFunctionParseInfo. */
4220+
func_tuple = SearchSysCache1(PROCOID, ObjectIdGetDatum(expr->funcid));
4221+
if (!HeapTupleIsValid(func_tuple))
4222+
{
4223+
ereport(WARNING, (errmsg("cache lookup failed for function %u", expr->funcid)));
4224+
PG_RETURN_POINTER(NULL);
4225+
}
4226+
pinfo = prepare_sql_fn_parse_info(func_tuple,
4227+
(Node *) expr,
4228+
expr->inputcollid);
4229+
ReleaseSysCache(func_tuple);
4230+
4231+
/* Parse the SQL. */
4232+
raw_parsetree_list = pg_parse_query(sql.data);
4233+
if (list_length(raw_parsetree_list) != 1)
4234+
{
4235+
ereport(WARNING, (errmsg("my_support_func parsed to more than one node")));
4236+
PG_RETURN_POINTER(NULL);
4237+
}
4238+
4239+
/* Analyze the parse tree as if it were a SQL-language body. */
4240+
querytree_list = pg_analyze_and_rewrite_withcb(linitial(raw_parsetree_list),
4241+
sql.data,
4242+
(ParserSetupHook) sql_fn_parser_setup,
4243+
pinfo, NULL);
4244+
if (list_length(querytree_list) != 1)
4245+
{
4246+
ereport(WARNING, (errmsg("my_support_func rewrote to more than one node")));
4247+
PG_RETURN_POINTER(NULL);
4248+
}
4249+
4250+
querytree = linitial(querytree_list);
4251+
if (!IsA(querytree, Query))
4252+
{
4253+
ereport(WARNING, (errmsg("my_support_func didn't parse to a Query"),
4254+
errdetail("Got this instead: %s", nodeToString(querytree))));
4255+
PG_RETURN_POINTER(NULL);
4256+
}
4257+
4258+
PG_RETURN_POINTER(querytree);
4259+
}
4260+
</programlisting>
4261+
</para>
4262+
41624263
<para>
41634264
For target functions that return <type>boolean</type>, it is often useful to estimate
41644265
the fraction of rows that will be selected by a <literal>WHERE</literal> clause using that

src/backend/optimizer/util/clauses.c

Lines changed: 57 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5148,6 +5148,47 @@ evaluate_expr(Expr *expr, Oid result_type, int32 result_typmod,
51485148
}
51495149

51505150

5151+
/*
5152+
* inline_set_returning_function_with_support
5153+
*
5154+
* This implements inline_set_returning_function for functions with
5155+
* a support function that can handle SupportRequestInlineSRF.
5156+
* We check fewer things than inline_sql_set_returning_function,
5157+
* so that support functions can make their own decisions about what
5158+
* to handle. For instance we don't forbid a VOLATILE function.
5159+
*/
5160+
static Query *
5161+
inline_set_returning_function_with_support(PlannerInfo *root, RangeTblEntry *rte,
5162+
RangeTblFunction *rtfunc,
5163+
FuncExpr *fexpr, HeapTuple func_tuple,
5164+
Form_pg_proc funcform)
5165+
{
5166+
SupportRequestInlineSRF req;
5167+
Node *newnode;
5168+
5169+
/* It must have a support function. */
5170+
Assert(funcform->prosupport);
5171+
5172+
req.root = root;
5173+
req.type = T_SupportRequestInlineSRF;
5174+
req.rtfunc = rtfunc;
5175+
req.proc = func_tuple;
5176+
5177+
newnode = (Node *)
5178+
DatumGetPointer(OidFunctionCall1(funcform->prosupport,
5179+
PointerGetDatum(&req)));
5180+
5181+
if (!newnode)
5182+
return NULL;
5183+
5184+
if (!IsA(newnode, Query))
5185+
elog(ERROR,
5186+
"Got unexpected node type %d from %s for function %s",
5187+
newnode->type, "SupportRequestInlineSRF", NameStr(funcform->proname));
5188+
5189+
return (Query *) newnode;
5190+
}
5191+
51515192
/*
51525193
* inline_sql_set_returning_function
51535194
*
@@ -5313,10 +5354,10 @@ inline_sql_set_returning_function(PlannerInfo *root, RangeTblEntry *rte,
53135354

53145355
/*
53155356
* inline_set_returning_function
5316-
* Attempt to "inline" an SQL set-returning function in the FROM clause.
5357+
* Attempt to "inline" a set-returning function in the FROM clause.
53175358
*
53185359
* "rte" is an RTE_FUNCTION rangetable entry. If it represents a call of a
5319-
* set-returning SQL function that can safely be inlined, expand the function
5360+
* set-returning function that can safely be inlined, expand the function
53205361
* and return the substitute Query structure. Otherwise, return NULL.
53215362
*
53225363
* We assume that the RTE's expression has already been put through
@@ -5344,7 +5385,7 @@ inline_set_returning_function(PlannerInfo *root, RangeTblEntry *rte)
53445385
ErrorContextCallback sqlerrcontext;
53455386
MemoryContext oldcxt;
53465387
MemoryContext mycxt;
5347-
Query *querytree;
5388+
Query *querytree = NULL;
53485389

53495390
Assert(rte->rtekind == RTE_FUNCTION);
53505391

@@ -5432,9 +5473,19 @@ inline_set_returning_function(PlannerInfo *root, RangeTblEntry *rte)
54325473
if (!heap_attisnull(func_tuple, Anum_pg_proc_proconfig, NULL))
54335474
goto fail;
54345475

5435-
querytree = inline_sql_set_returning_function(root, rte, rtfunc, fexpr,
5436-
func_oid, func_tuple, funcform,
5437-
src);
5476+
/*
5477+
* If the function has an attached support function that can handle
5478+
* SupportRequestInlineSRF, then attempt to inline with that. Return the
5479+
* result if we get one, otherwise proceed.
5480+
*/
5481+
if (funcform->prosupport)
5482+
querytree = inline_set_returning_function_with_support(root, rte, rtfunc, fexpr,
5483+
func_tuple, funcform);
5484+
5485+
/* Try to inline automatically */
5486+
if (!querytree)
5487+
querytree = inline_sql_set_returning_function(root, rte, rtfunc, fexpr,
5488+
func_oid, func_tuple, funcform, src);
54385489

54395490
if (!querytree)
54405491
goto fail;

src/include/nodes/supportnodes.h

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
#ifndef SUPPORTNODES_H
3434
#define SUPPORTNODES_H
3535

36+
#include "catalog/pg_proc.h"
3637
#include "nodes/plannodes.h"
3738

3839
typedef struct PlannerInfo PlannerInfo; /* avoid including pathnodes.h here */
@@ -69,6 +70,39 @@ typedef struct SupportRequestSimplify
6970
FuncExpr *fcall; /* Function call to be simplified */
7071
} SupportRequestSimplify;
7172

73+
/*
74+
* The InlineSRF request allows the support function to perform plan-time
75+
* simplification of a call to its target set-returning function. For
76+
* example a PL/pgSQL function could build a dynamic SQL query and execute it.
77+
* Normally only SQL functions can be inlined, but with this support function
78+
* the dynamic query can be inlined as well.
79+
*
80+
* The planner's PlannerInfo "root" is typically not needed, but can be
81+
* consulted if it's necessary to obtain info about Vars present in
82+
* the given node tree. Beware that root could be NULL in some usages.
83+
*
84+
* "rtfunc" will be a RangeTblFunction node for the function being replaced.
85+
* The support function is only called if rtfunc->functions contains a
86+
* single FuncExpr node. (ROWS FROM is one way to get more than one.)
87+
*
88+
* "proc" will be the HeapTuple for the pg_proc record of the function being
89+
* replaced.
90+
*
91+
* The result should be a semantically-equivalent transformed node tree,
92+
* or NULL if no simplification could be performed. It should be allocated
93+
* in the CurrentMemoryContext. Do *not* return or modify the FuncExpr node
94+
* tree, as it isn't really a separately allocated Node. But it's okay to
95+
* use its args, or parts of it, in the result tree.
96+
*/
97+
typedef struct SupportRequestInlineSRF
98+
{
99+
NodeTag type;
100+
101+
struct PlannerInfo *root; /* Planner's infrastructure */
102+
RangeTblFunction *rtfunc; /* Function call to be simplified */
103+
HeapTuple proc; /* Function definition from pg_proc */
104+
} SupportRequestInlineSRF;
105+
72106
/*
73107
* The Selectivity request allows the support function to provide a
74108
* selectivity estimate for a function appearing at top level of a WHERE

0 commit comments

Comments
 (0)