Commit 9b46abb7 authored by Tom Lane's avatar Tom Lane

Allow SQL-language functions to return the output of an INSERT/UPDATE/DELETE

RETURNING clause, not just a SELECT as formerly.

A side effect of this patch is that when a set-returning SQL function is used
in a FROM clause, performance is improved because the output is collected into
a tuplestore within the function, rather than using the less efficient
value-per-call mechanism.
parent cd97f988
<!-- $PostgreSQL: pgsql/doc/src/sgml/xfunc.sgml,v 1.132 2008/07/18 03:32:52 tgl Exp $ -->
<!-- $PostgreSQL: pgsql/doc/src/sgml/xfunc.sgml,v 1.133 2008/10/31 19:37:56 tgl Exp $ -->
<sect1 id="xfunc">
<title>User-Defined Functions</title>
......@@ -106,7 +106,9 @@
The body of an SQL function must be a list of SQL
statements separated by semicolons. A semicolon after the last
statement is optional. Unless the function is declared to return
<type>void</>, the last statement must be a <command>SELECT</>.
<type>void</>, the last statement must be a <command>SELECT</>,
or an <command>INSERT</>, <command>UPDATE</>, or <command>DELETE</>
that has a <literal>RETURNING</> clause.
</para>
<para>
......@@ -119,11 +121,11 @@
<command>BEGIN</>, <command>COMMIT</>, <command>ROLLBACK</>, or
<command>SAVEPOINT</> commands into a <acronym>SQL</acronym> function.)
However, the final command
must be a <command>SELECT</command> that returns whatever is
must be a <command>SELECT</command> or have a <literal>RETURNING</>
clause that returns whatever is
specified as the function's return type. Alternatively, if you
want to define a SQL function that performs actions but has no
useful value to return, you can define it as returning <type>void</>.
In that case, the function body must not end with a <command>SELECT</command>.
For example, this function removes rows with negative salaries from
the <literal>emp</> table:
......@@ -257,6 +259,16 @@ $$ LANGUAGE SQL;
</programlisting>
which adjusts the balance and returns the new balance.
The same thing could be done in one command using <literal>RETURNING</>:
<programlisting>
CREATE FUNCTION tf1 (integer, numeric) RETURNS numeric AS $$
UPDATE bank
SET balance = balance - $2
WHERE accountno = $1
RETURNING balance;
$$ LANGUAGE SQL;
</programlisting>
</para>
</sect2>
......@@ -422,7 +434,7 @@ SELECT (new_emp()).name;
<screen>
SELECT new_emp().name;
ERROR: syntax error at or near "." at character 17
ERROR: syntax error at or near "."
LINE 1: SELECT new_emp().name;
^
</screen>
......@@ -705,7 +717,7 @@ SELECT *, upper(fooname) FROM getfoo(1) AS t1;
<para>
When an SQL function is declared as returning <literal>SETOF
<replaceable>sometype</></literal>, the function's final
<command>SELECT</> query is executed to completion, and each row it
query is executed to completion, and each row it
outputs is returned as an element of the result set.
</para>
......@@ -798,6 +810,18 @@ SELECT name, listchildren(name) FROM nodes;
This happens because <function>listchildren</function> returns an empty set
for those arguments, so no result rows are generated.
</para>
<note>
<para>
If a function's last command is <command>INSERT</>, <command>UPDATE</>,
or <command>DELETE</> with <literal>RETURNING</>, that command will
always be executed to completion, even if the function is not declared
with <literal>SETOF</> or the calling query does not fetch all the
result rows. Any extra rows produced by the <literal>RETURNING</>
clause are silently dropped, but the commanded table modifications
still happen (and are all completed before returning from the function).
</para>
</note>
</sect2>
<sect2 id="xfunc-sql-functions-returning-table">
......@@ -1459,16 +1483,13 @@ PG_MODULE_MAGIC;
<para>
By-value types can only be 1, 2, or 4 bytes in length
(also 8 bytes, if <literal>sizeof(Datum)</literal> is 8 on your machine).
You should be careful
to define your types such that they will be the same
size (in bytes) on all architectures. For example, the
<literal>long</literal> type is dangerous because it
is 4 bytes on some machines and 8 bytes on others, whereas
<type>int</type> type is 4 bytes on most
Unix machines. A reasonable implementation of
the <type>int4</type> type on Unix
machines might be:
You should be careful to define your types such that they will be the
same size (in bytes) on all architectures. For example, the
<literal>long</literal> type is dangerous because it is 4 bytes on some
machines and 8 bytes on others, whereas <type>int</type> type is 4 bytes
on most Unix machines. A reasonable implementation of the
<type>int4</type> type on Unix machines might be:
<programlisting>
/* 4-byte integer, passed by value */
typedef int int4;
......@@ -1479,7 +1500,7 @@ typedef int int4;
On the other hand, fixed-length types of any size can
be passed by-reference. For example, here is a sample
implementation of a <productname>PostgreSQL</productname> type:
<programlisting>
/* 16-byte structure, passed by reference */
typedef struct
......@@ -1502,7 +1523,7 @@ typedef struct
Finally, all variable-length types must also be passed
by reference. All variable-length types must begin
with a length field of exactly 4 bytes, and all data to
be stored within that type must be located in the memory
be stored within that type must be located in the memory
immediately following that length field. The
length field contains the total length of the structure,
that is, it includes the size of the length field
......@@ -1540,8 +1561,8 @@ typedef struct {
</para>
<para>
When manipulating
variable-length types, we must be careful to allocate
When manipulating
variable-length types, we must be careful to allocate
the correct amount of memory and set the length field correctly.
For example, if we wanted to store 40 bytes in a <structname>text</>
structure, we might use a code fragment like this:
......@@ -1772,7 +1793,7 @@ memcpy(destination-&gt;data, buffer, 40);
#include &lt;string.h&gt;
/* by value */
int
add_one(int arg)
{
......@@ -1787,7 +1808,7 @@ add_one_float8(float8 *arg)
float8 *result = (float8 *) palloc(sizeof(float8));
*result = *arg + 1.0;
return result;
}
......@@ -1798,7 +1819,7 @@ makepoint(Point *pointx, Point *pointy)
new_point-&gt;x = pointx-&gt;x;
new_point-&gt;y = pointy-&gt;y;
return new_point;
}
......@@ -1841,7 +1862,7 @@ concat_text(text *arg1, text *arg2)
<filename>funcs.c</filename> and compiled into a shared object,
we could define the functions to <productname>PostgreSQL</productname>
with commands like this:
<programlisting>
CREATE FUNCTION add_one(integer) RETURNS integer
AS '<replaceable>DIRECTORY</replaceable>/funcs', 'add_one'
......@@ -1855,7 +1876,7 @@ CREATE FUNCTION add_one(double precision) RETURNS double precision
CREATE FUNCTION makepoint(point, point) RETURNS point
AS '<replaceable>DIRECTORY</replaceable>/funcs', 'makepoint'
LANGUAGE C STRICT;
CREATE FUNCTION copytext(text) RETURNS text
AS '<replaceable>DIRECTORY</replaceable>/funcs', 'copytext'
LANGUAGE C STRICT;
......@@ -1947,7 +1968,7 @@ PG_FUNCTION_INFO_V1(funcname);
/* by value */
PG_FUNCTION_INFO_V1(add_one);
Datum
add_one(PG_FUNCTION_ARGS)
{
......@@ -1981,7 +2002,7 @@ makepoint(PG_FUNCTION_ARGS)
new_point-&gt;x = pointx-&gt;x;
new_point-&gt;y = pointy-&gt;y;
PG_RETURN_POINT_P(new_point);
}
......@@ -2447,7 +2468,7 @@ include $(PGXS)
in the <literal>results/</literal> directory), and copying them to
<literal>expected/</literal> if they match what you want from the test.
</para>
</tip>
</sect2>
......@@ -2476,7 +2497,7 @@ SELECT name, c_overpaid(emp, 1500) AS overpaid
Using call conventions version 0, we can define
<function>c_overpaid</> as:
<programlisting>
#include "postgres.h"
#include "executor/executor.h" /* for GetAttributeByName() */
......@@ -2522,11 +2543,11 @@ c_overpaid(PG_FUNCTION_ARGS)
</para>
<para>
<function>GetAttributeByName</function> is the
<function>GetAttributeByName</function> is the
<productname>PostgreSQL</productname> system function that
returns attributes out of the specified row. It has
three arguments: the argument of type <type>HeapTupleHeader</type> passed
into
into
the function, the name of the desired attribute, and a
return parameter that tells whether the attribute
is null. <function>GetAttributeByName</function> returns a <type>Datum</type>
......@@ -2733,7 +2754,7 @@ typedef struct
{
/*
* Number of times we've been called before
*
*
* call_cntr is initialized to 0 for you by SRF_FIRSTCALL_INIT(), and
* incremented for you every time SRF_RETURN_NEXT() is called.
*/
......@@ -2750,7 +2771,7 @@ typedef struct
/*
* OPTIONAL pointer to result slot
*
*
* This is obsolete and only present for backwards compatibility, viz,
* user-defined SRFs that use the deprecated TupleDescGetSlot().
*/
......@@ -2758,7 +2779,7 @@ typedef struct
/*
* OPTIONAL pointer to miscellaneous user-provided context information
*
*
* user_fctx is for use as a pointer to your own data to retain
* arbitrary context information between calls of your function.
*/
......@@ -2766,7 +2787,7 @@ typedef struct
/*
* OPTIONAL pointer to struct containing attribute type input metadata
*
*
* attinmeta is for use when returning tuples (i.e., composite data types)
* and is not used when returning base data types. It is only needed
* if you intend to use BuildTupleFromCStrings() to create the return
......@@ -2948,7 +2969,7 @@ retcomposite(PG_FUNCTION_ARGS)
call_cntr = funcctx-&gt;call_cntr;
max_calls = funcctx-&gt;max_calls;
attinmeta = funcctx-&gt;attinmeta;
if (call_cntr &lt; max_calls) /* do when there is more left to send */
{
char **values;
......@@ -3126,7 +3147,7 @@ CREATE FUNCTION make_array(anyelement) RETURNS anyarray
<para>
Add-ins can reserve LWLocks and an allocation of shared memory on server
startup. The add-in's shared library must be preloaded by specifying
it in
it in
<xref linkend="guc-shared-preload-libraries"><indexterm><primary>shared-preload-libraries</></>.
Shared memory is reserved by calling:
<programlisting>
......@@ -3139,11 +3160,11 @@ void RequestAddinShmemSpace(int size)
<programlisting>
void RequestAddinLWLocks(int n)
</programlisting>
from <function>_PG_init</>.
from <function>_PG_init</>.
</para>
<para>
To avoid possible race-conditions, each backend should use the LWLock
<function>AddinShmemInitLock</> when connecting to and initializing
<function>AddinShmemInitLock</> when connecting to and initializing
its allocation of shared memory, as shown here:
<programlisting>
static mystruct *ptr = NULL;
......
......@@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/executor/execQual.c,v 1.235 2008/10/29 00:00:38 tgl Exp $
* $PostgreSQL: pgsql/src/backend/executor/execQual.c,v 1.236 2008/10/31 19:37:56 tgl Exp $
*
*-------------------------------------------------------------------------
*/
......@@ -1334,7 +1334,8 @@ ExecMakeFunctionResult(FuncExprState *fcache,
{
List *arguments;
Datum result;
FunctionCallInfoData fcinfo;
FunctionCallInfoData fcinfo_data;
FunctionCallInfo fcinfo;
PgStat_FunctionCallUsage fcusage;
ReturnSetInfo rsinfo; /* for functions returning sets */
ExprDoneCond argDone;
......@@ -1384,6 +1385,20 @@ restart:
Assert(!fcache->setArgsValid);
}
/*
* For non-set-returning functions, we just use a local-variable
* FunctionCallInfoData. For set-returning functions we keep the callinfo
* record in fcache->setArgs so that it can survive across multiple
* value-per-call invocations. (The reason we don't just do the latter
* all the time is that plpgsql expects to be able to use simple expression
* trees re-entrantly. Which might not be a good idea, but the penalty
* for not doing so is high.)
*/
if (fcache->func.fn_retset)
fcinfo = &fcache->setArgs;
else
fcinfo = &fcinfo_data;
/*
* arguments is a list of expressions to evaluate before passing to the
* function manager. We skip the evaluation if it was already done in the
......@@ -1394,8 +1409,8 @@ restart:
if (!fcache->setArgsValid)
{
/* Need to prep callinfo structure */
InitFunctionCallInfoData(fcinfo, &(fcache->func), 0, NULL, NULL);
argDone = ExecEvalFuncArgs(&fcinfo, arguments, econtext);
InitFunctionCallInfoData(*fcinfo, &(fcache->func), 0, NULL, NULL);
argDone = ExecEvalFuncArgs(fcinfo, arguments, econtext);
if (argDone == ExprEndResult)
{
/* input is an empty set, so return an empty set. */
......@@ -1412,8 +1427,7 @@ restart:
}
else
{
/* Copy callinfo from previous evaluation */
memcpy(&fcinfo, &fcache->setArgs, sizeof(fcinfo));
/* Re-use callinfo from previous evaluation */
hasSetArg = fcache->setHasSetArg;
/* Reset flag (we may set it again below) */
fcache->setArgsValid = false;
......@@ -1424,12 +1438,12 @@ restart:
*/
if (fcache->func.fn_retset)
{
fcinfo.resultinfo = (Node *) &rsinfo;
fcinfo->resultinfo = (Node *) &rsinfo;
rsinfo.type = T_ReturnSetInfo;
rsinfo.econtext = econtext;
rsinfo.expectedDesc = fcache->funcResultDesc;
rsinfo.allowedModes = (int) (SFRM_ValuePerCall | SFRM_Materialize);
/* note we do not set SFRM_Materialize_Random */
/* note we do not set SFRM_Materialize_Random or _Preferred */
rsinfo.returnMode = SFRM_ValuePerCall;
/* isDone is filled below */
rsinfo.setResult = NULL;
......@@ -1468,9 +1482,9 @@ restart:
if (fcache->func.fn_strict)
{
for (i = 0; i < fcinfo.nargs; i++)
for (i = 0; i < fcinfo->nargs; i++)
{
if (fcinfo.argnull[i])
if (fcinfo->argnull[i])
{
callit = false;
break;
......@@ -1480,12 +1494,12 @@ restart:
if (callit)
{
pgstat_init_function_usage(&fcinfo, &fcusage);
pgstat_init_function_usage(fcinfo, &fcusage);
fcinfo.isnull = false;
fcinfo->isnull = false;
rsinfo.isDone = ExprSingleResult;
result = FunctionCallInvoke(&fcinfo);
*isNull = fcinfo.isnull;
result = FunctionCallInvoke(fcinfo);
*isNull = fcinfo->isnull;
*isDone = rsinfo.isDone;
pgstat_end_function_usage(&fcusage,
......@@ -1511,7 +1525,7 @@ restart:
if (fcache->func.fn_retset &&
*isDone == ExprMultipleResult)
{
memcpy(&fcache->setArgs, &fcinfo, sizeof(fcinfo));
Assert(fcinfo == &fcache->setArgs);
fcache->setHasSetArg = hasSetArg;
fcache->setArgsValid = true;
/* Register cleanup callback if we didn't already */
......@@ -1567,7 +1581,7 @@ restart:
break; /* input not a set, so done */
/* Re-eval args to get the next element of the input set */
argDone = ExecEvalFuncArgs(&fcinfo, arguments, econtext);
argDone = ExecEvalFuncArgs(fcinfo, arguments, econtext);
if (argDone != ExprMultipleResult)
{
......@@ -1605,9 +1619,9 @@ restart:
*/
if (fcache->func.fn_strict)
{
for (i = 0; i < fcinfo.nargs; i++)
for (i = 0; i < fcinfo->nargs; i++)
{
if (fcinfo.argnull[i])
if (fcinfo->argnull[i])
{
*isNull = true;
return (Datum) 0;
......@@ -1615,11 +1629,11 @@ restart:
}
}
pgstat_init_function_usage(&fcinfo, &fcusage);
pgstat_init_function_usage(fcinfo, &fcusage);
fcinfo.isnull = false;
result = FunctionCallInvoke(&fcinfo);
*isNull = fcinfo.isnull;
fcinfo->isnull = false;
result = FunctionCallInvoke(fcinfo);
*isNull = fcinfo->isnull;
pgstat_end_function_usage(&fcusage, true);
}
......@@ -1737,7 +1751,7 @@ ExecMakeTableFunctionResult(ExprState *funcexpr,
rsinfo.type = T_ReturnSetInfo;
rsinfo.econtext = econtext;
rsinfo.expectedDesc = expectedDesc;
rsinfo.allowedModes = (int) (SFRM_ValuePerCall | SFRM_Materialize);
rsinfo.allowedModes = (int) (SFRM_ValuePerCall | SFRM_Materialize | SFRM_Materialize_Preferred);
if (randomAccess)
rsinfo.allowedModes |= (int) SFRM_Materialize_Random;
rsinfo.returnMode = SFRM_ValuePerCall;
......
......@@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/executor/functions.c,v 1.126 2008/08/25 22:42:32 tgl Exp $
* $PostgreSQL: pgsql/src/backend/executor/functions.c,v 1.127 2008/10/31 19:37:56 tgl Exp $
*
*-------------------------------------------------------------------------
*/
......@@ -20,19 +20,29 @@
#include "commands/trigger.h"
#include "executor/functions.h"
#include "funcapi.h"
#include "miscadmin.h"
#include "nodes/makefuncs.h"
#include "nodes/nodeFuncs.h"
#include "parser/parse_coerce.h"
#include "tcop/tcopprot.h"
#include "tcop/utility.h"
#include "utils/builtins.h"
#include "utils/datum.h"
#include "utils/lsyscache.h"
#include "utils/snapmgr.h"
#include "utils/syscache.h"
#include "utils/typcache.h"
/*
* Specialized DestReceiver for collecting query output in a SQL function
*/
typedef struct
{
DestReceiver pub; /* publicly-known function pointers */
Tuplestorestate *tstore; /* where to put result tuples */
MemoryContext cxt; /* context containing tstore */
JunkFilter *filter; /* filter to convert tuple type */
} DR_sqlfunction;
/*
* We have an execution_state record for each query in a function. Each
* record contains a plantree for its query. If the query is currently in
......@@ -43,20 +53,24 @@ typedef enum
F_EXEC_START, F_EXEC_RUN, F_EXEC_DONE
} ExecStatus;
typedef struct local_es
typedef struct execution_state
{
struct local_es *next;
struct execution_state *next;
ExecStatus status;
bool setsResult; /* true if this query produces func's result */
bool lazyEval; /* true if should fetch one row at a time */
Node *stmt; /* PlannedStmt or utility statement */
QueryDesc *qd; /* null unless status == RUN */
} execution_state;
#define LAST_POSTQUEL_COMMAND(es) ((es)->next == NULL)
/*
* An SQLFunctionCache record is built during the first call,
* and linked to from the fn_extra field of the FmgrInfo struct.
*
* Note that currently this has only the lifespan of the calling query.
* Someday we might want to consider caching the parse/plan results longer
* than that.
*/
typedef struct
{
......@@ -66,13 +80,17 @@ typedef struct
Oid rettype; /* actual return type */
int16 typlen; /* length of the return type */
bool typbyval; /* true if return type is pass by value */
bool returnsSet; /* true if returning multiple rows */
bool returnsTuple; /* true if returning whole tuple result */
bool shutdown_reg; /* true if registered shutdown callback */
bool readonly_func; /* true to run in "read only" mode */
bool lazyEval; /* true if using lazyEval for result query */
ParamListInfo paramLI; /* Param list representing current args */
JunkFilter *junkFilter; /* used only if returnsTuple */
Tuplestorestate *tstore; /* where we accumulate result tuples */
JunkFilter *junkFilter; /* will be NULL if function returns VOID */
/* head of linked list of execution_state records */
execution_state *func_state;
......@@ -83,32 +101,41 @@ typedef SQLFunctionCache *SQLFunctionCachePtr;
/* non-export function prototypes */
static execution_state *init_execution_state(List *queryTree_list,
bool readonly_func);
static void init_sql_fcache(FmgrInfo *finfo);
SQLFunctionCachePtr fcache,
bool lazyEvalOK);
static void init_sql_fcache(FmgrInfo *finfo, bool lazyEvalOK);
static void postquel_start(execution_state *es, SQLFunctionCachePtr fcache);
static TupleTableSlot *postquel_getnext(execution_state *es,
SQLFunctionCachePtr fcache);
static void postquel_end(execution_state *es);
static void postquel_sub_params(SQLFunctionCachePtr fcache,
FunctionCallInfo fcinfo);
static Datum postquel_execute(execution_state *es,
FunctionCallInfo fcinfo,
SQLFunctionCachePtr fcache,
MemoryContext resultcontext);
static Datum postquel_get_single_result(TupleTableSlot *slot,
FunctionCallInfo fcinfo,
SQLFunctionCachePtr fcache,
MemoryContext resultcontext);
static void sql_exec_error_callback(void *arg);
static void ShutdownSQLFunction(Datum arg);
static void sqlfunction_startup(DestReceiver *self, int operation, TupleDesc typeinfo);
static void sqlfunction_receive(TupleTableSlot *slot, DestReceiver *self);
static void sqlfunction_shutdown(DestReceiver *self);
static void sqlfunction_destroy(DestReceiver *self);
/* Set up the list of per-query execution_state records for a SQL function */
static execution_state *
init_execution_state(List *queryTree_list, bool readonly_func)
init_execution_state(List *queryTree_list,
SQLFunctionCachePtr fcache,
bool lazyEvalOK)
{
execution_state *firstes = NULL;
execution_state *preves = NULL;
execution_state *lasttages = NULL;
ListCell *qtl_item;
foreach(qtl_item, queryTree_list)
{
Query *queryTree = lfirst(qtl_item);
Query *queryTree = (Query *) lfirst(qtl_item);
Node *stmt;
execution_state *newes;
......@@ -127,7 +154,7 @@ init_execution_state(List *queryTree_list, bool readonly_func)
errmsg("%s is not allowed in a SQL function",
CreateCommandTag(stmt))));
if (readonly_func && !CommandIsReadOnly(stmt))
if (fcache->readonly_func && !CommandIsReadOnly(stmt))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
/* translator: %s is a SQL statement name */
......@@ -142,18 +169,53 @@ init_execution_state(List *queryTree_list, bool readonly_func)
newes->next = NULL;
newes->status = F_EXEC_START;
newes->setsResult = false; /* might change below */
newes->lazyEval = false; /* might change below */
newes->stmt = stmt;
newes->qd = NULL;
if (queryTree->canSetTag)
lasttages = newes;
preves = newes;
}
/*
* Mark the last canSetTag query as delivering the function result;
* then, if it is a plain SELECT, mark it for lazy evaluation.
* If it's not a SELECT we must always run it to completion.
*
* Note: at some point we might add additional criteria for whether to use
* lazy eval. However, we should prefer to use it whenever the function
* doesn't return set, since fetching more than one row is useless in that
* case.
*
* Note: don't set setsResult if the function returns VOID, as evidenced
* by not having made a junkfilter. This ensures we'll throw away any
* output from a utility statement that check_sql_fn_retval deemed to
* not have output.
*/
if (lasttages && fcache->junkFilter)
{
lasttages->setsResult = true;
if (lazyEvalOK &&
IsA(lasttages->stmt, PlannedStmt))
{
PlannedStmt *ps = (PlannedStmt *) lasttages->stmt;
if (ps->commandType == CMD_SELECT &&
ps->utilityStmt == NULL &&
ps->intoClause == NULL)
fcache->lazyEval = lasttages->lazyEval = true;
}
}
return firstes;
}
/* Initialize the SQLFunctionCache for a SQL function */
static void
init_sql_fcache(FmgrInfo *finfo)
init_sql_fcache(FmgrInfo *finfo, bool lazyEvalOK)
{
Oid foid = finfo->fn_oid;
Oid rettype;
......@@ -199,6 +261,9 @@ init_sql_fcache(FmgrInfo *finfo)
/* Fetch the typlen and byval info for the result type */
get_typlenbyval(rettype, &fcache->typlen, &fcache->typbyval);
/* Remember whether we're returning setof something */
fcache->returnsSet = procedureStruct->proretset;
/* Remember if function is STABLE/IMMUTABLE */
fcache->readonly_func =
(procedureStruct->provolatile != PROVOLATILE_VOLATILE);
......@@ -262,11 +327,14 @@ init_sql_fcache(FmgrInfo *finfo)
* Note: we set fcache->returnsTuple according to whether we are returning
* the whole tuple result or just a single column. In the latter case we
* clear returnsTuple because we need not act different from the scalar
* result case, even if it's a rowtype column.
* result case, even if it's a rowtype column. (However, we have to
* force lazy eval mode in that case; otherwise we'd need extra code to
* expand the rowtype column into multiple columns, since we have no
* way to notify the caller that it should do that.)
*
* In the returnsTuple case, check_sql_fn_retval will also construct a
* JunkFilter we can use to coerce the returned rowtype to the desired
* form.
* check_sql_fn_retval will also construct a JunkFilter we can use to
* coerce the returned rowtype to the desired form (unless the result type
* is VOID, in which case there's nothing to coerce to).
*/
fcache->returnsTuple = check_sql_fn_retval(foid,
rettype,
......@@ -274,20 +342,38 @@ init_sql_fcache(FmgrInfo *finfo)
false,
&fcache->junkFilter);
if (fcache->returnsTuple)
{
/* Make sure output rowtype is properly blessed */
BlessTupleDesc(fcache->junkFilter->jf_resultSlot->tts_tupleDescriptor);
}
else if (fcache->returnsSet && type_is_rowtype(fcache->rettype))
{
/*
* Returning rowtype as if it were scalar --- materialize won't work.
* Right now it's sufficient to override any caller preference for
* materialize mode, but to add more smarts in init_execution_state
* about this, we'd probably need a three-way flag instead of bool.
*/
lazyEvalOK = true;
}
/* Finally, plan the queries */
fcache->func_state = init_execution_state(queryTree_list,
fcache->readonly_func);
fcache,
lazyEvalOK);
ReleaseSysCache(procedureTuple);
finfo->fn_extra = (void *) fcache;
}
/* Start up execution of one execution_state node */
static void
postquel_start(execution_state *es, SQLFunctionCachePtr fcache)
{
Snapshot snapshot;
DestReceiver *dest;
Assert(es->qd == NULL);
......@@ -305,15 +391,34 @@ postquel_start(execution_state *es, SQLFunctionCachePtr fcache)
snapshot = GetTransactionSnapshot();
}
/*
* If this query produces the function result, send its output to the
* tuplestore; else discard any output.
*/
if (es->setsResult)
{
DR_sqlfunction *myState;
dest = CreateDestReceiver(DestSQLFunction, NULL);
/* pass down the needed info to the dest receiver routines */
myState = (DR_sqlfunction *) dest;
Assert(myState->pub.mydest == DestSQLFunction);
myState->tstore = fcache->tstore;
myState->cxt = CurrentMemoryContext;
myState->filter = fcache->junkFilter;
}
else
dest = None_Receiver;
if (IsA(es->stmt, PlannedStmt))
es->qd = CreateQueryDesc((PlannedStmt *) es->stmt,
snapshot, InvalidSnapshot,
None_Receiver,
dest,
fcache->paramLI, false);
else
es->qd = CreateUtilityQueryDesc(es->stmt,
snapshot,
None_Receiver,
dest,
fcache->paramLI);
/* We assume we don't need to set up ActiveSnapshot for ExecutorStart */
......@@ -335,11 +440,11 @@ postquel_start(execution_state *es, SQLFunctionCachePtr fcache)
es->status = F_EXEC_RUN;
}
/* Run one execution_state; either to completion or to first result row */
static TupleTableSlot *
postquel_getnext(execution_state *es, SQLFunctionCachePtr fcache)
{
TupleTableSlot *result;
long count;
/* Make our snapshot the active one for any called functions */
PushActiveSnapshot(es->qd->snapshot);
......@@ -359,19 +464,8 @@ postquel_getnext(execution_state *es, SQLFunctionCachePtr fcache)
}
else
{
/*
* If it's the function's last command, and it's a SELECT, fetch
* one row at a time so we can return the results. Otherwise just
* run it to completion. (If we run to completion then
* ExecutorRun is guaranteed to return NULL.)
*/
if (LAST_POSTQUEL_COMMAND(es) &&
es->qd->operation == CMD_SELECT &&
es->qd->plannedstmt->utilityStmt == NULL &&
es->qd->plannedstmt->intoClause == NULL)
count = 1L;
else
count = 0L;
/* Run regular commands to completion unless lazyEval */
long count = (es->lazyEval) ? 1L : 0L;
result = ExecutorRun(es->qd, ForwardScanDirection, count);
}
......@@ -381,6 +475,7 @@ postquel_getnext(execution_state *es, SQLFunctionCachePtr fcache)
return result;
}
/* Shut down execution of one execution_state node */
static void
postquel_end(execution_state *es)
{
......@@ -409,17 +504,26 @@ static void
postquel_sub_params(SQLFunctionCachePtr fcache,
FunctionCallInfo fcinfo)
{
ParamListInfo paramLI;
int nargs = fcinfo->nargs;
if (nargs > 0)
{
ParamListInfo paramLI;
int i;
/* sizeof(ParamListInfoData) includes the first array element */
paramLI = (ParamListInfo) palloc(sizeof(ParamListInfoData) +
if (fcache->paramLI == NULL)
{
/* sizeof(ParamListInfoData) includes the first array element */
paramLI = (ParamListInfo) palloc(sizeof(ParamListInfoData) +
(nargs - 1) *sizeof(ParamExternData));
paramLI->numParams = nargs;
paramLI->numParams = nargs;
fcache->paramLI = paramLI;
}
else
{
paramLI = fcache->paramLI;
Assert(paramLI->numParams == nargs);
}
for (i = 0; i < nargs; i++)
{
......@@ -432,116 +536,37 @@ postquel_sub_params(SQLFunctionCachePtr fcache,
}
}
else
paramLI = NULL;
if (fcache->paramLI)
pfree(fcache->paramLI);
fcache->paramLI = paramLI;
fcache->paramLI = NULL;
}
/*
* Extract the SQL function's value from a single result row. This is used
* both for scalar (non-set) functions and for each row of a lazy-eval set
* result.
*/
static Datum
postquel_execute(execution_state *es,
FunctionCallInfo fcinfo,
SQLFunctionCachePtr fcache,
MemoryContext resultcontext)
postquel_get_single_result(TupleTableSlot *slot,
FunctionCallInfo fcinfo,
SQLFunctionCachePtr fcache,
MemoryContext resultcontext)
{
TupleTableSlot *slot;
Datum value;
MemoryContext oldcontext;
if (es->status == F_EXEC_START)
postquel_start(es, fcache);
slot = postquel_getnext(es, fcache);
if (TupIsNull(slot))
{
/*
* We fall out here for all cases except where we have obtained a row
* from a function's final SELECT.
*/
postquel_end(es);
fcinfo->isnull = true;
return (Datum) NULL;
}
/*
* If we got a row from a command within the function it has to be the
* final command. All others shouldn't be returning anything.
*/
Assert(LAST_POSTQUEL_COMMAND(es));
/*
* Set up to return the function value. For pass-by-reference datatypes,
* be sure to allocate the result in resultcontext, not the current memory
* context (which has query lifespan).
* context (which has query lifespan). We can't leave the data in the
* TupleTableSlot because we intend to clear the slot before returning.
*/
oldcontext = MemoryContextSwitchTo(resultcontext);
if (fcache->returnsTuple)
{
/*
* We are returning the whole tuple, so filter it and apply the proper
* labeling to make it a valid Datum. There are several reasons why
* we do this:
*
* 1. To copy the tuple out of the child execution context and into
* the desired result context.
*
* 2. To remove any junk attributes present in the raw subselect
* result. (This is probably not absolutely necessary, but it seems
* like good policy.)
*
* 3. To insert dummy null columns if the declared result type has any
* attisdropped columns.
*/
HeapTuple newtup;
HeapTupleHeader dtup;
uint32 t_len;
Oid dtuptype;
int32 dtuptypmod;
newtup = ExecRemoveJunk(fcache->junkFilter, slot);
/*
* Compress out the HeapTuple header data. We assume that
* heap_form_tuple made the tuple with header and body in one palloc'd
* chunk. We want to return a pointer to the chunk start so that it
* will work if someone tries to free it.
*/
t_len = newtup->t_len;
dtup = (HeapTupleHeader) newtup;
memmove((char *) dtup, (char *) newtup->t_data, t_len);
/*
* Use the declared return type if it's not RECORD; else take the type
* from the computed result, making sure a typmod has been assigned.
*/
if (fcache->rettype != RECORDOID)
{
/* function has a named composite return type */
dtuptype = fcache->rettype;
dtuptypmod = -1;
}
else
{
/* function is declared to return RECORD */
TupleDesc tupDesc = fcache->junkFilter->jf_cleanTupType;
if (tupDesc->tdtypeid == RECORDOID &&
tupDesc->tdtypmod < 0)
assign_record_type_typmod(tupDesc);
dtuptype = tupDesc->tdtypeid;
dtuptypmod = tupDesc->tdtypmod;
}
HeapTupleHeaderSetDatumLength(dtup, t_len);
HeapTupleHeaderSetTypeId(dtup, dtuptype);
HeapTupleHeaderSetTypMod(dtup, dtuptypmod);
value = PointerGetDatum(dtup);
/* We must return the whole tuple as a Datum. */
fcinfo->isnull = false;
value = ExecFetchSlotTupleDatum(slot);
value = datumCopy(value, fcache->typbyval, fcache->typlen);
}
else
{
......@@ -557,24 +582,23 @@ postquel_execute(execution_state *es,
MemoryContextSwitchTo(oldcontext);
/*
* If this is a single valued function we have to end the function
* execution now.
*/
if (!fcinfo->flinfo->fn_retset)
postquel_end(es);
return value;
}
/*
* fmgr_sql: function call manager for SQL functions
*/
Datum
fmgr_sql(PG_FUNCTION_ARGS)
{
MemoryContext oldcontext;
SQLFunctionCachePtr fcache;
ErrorContextCallback sqlerrcontext;
bool randomAccess;
bool lazyEvalOK;
execution_state *es;
Datum result = 0;
TupleTableSlot *slot;
Datum result;
/*
* Switch to context in which the fcache lives. This ensures that
......@@ -591,13 +615,39 @@ fmgr_sql(PG_FUNCTION_ARGS)
sqlerrcontext.previous = error_context_stack;
error_context_stack = &sqlerrcontext;
/* Check call context */
if (fcinfo->flinfo->fn_retset)
{
ReturnSetInfo *rsi = (ReturnSetInfo *) fcinfo->resultinfo;
/*
* For simplicity, we require callers to support both set eval modes.
* There are cases where we must use one or must use the other, and
* it's not really worthwhile to postpone the check till we know.
*/
if (!rsi || !IsA(rsi, ReturnSetInfo) ||
(rsi->allowedModes & SFRM_ValuePerCall) == 0 ||
(rsi->allowedModes & SFRM_Materialize) == 0 ||
rsi->expectedDesc == NULL)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("set-valued function called in context that cannot accept a set")));
randomAccess = rsi->allowedModes & SFRM_Materialize_Random;
lazyEvalOK = !(rsi->allowedModes & SFRM_Materialize_Preferred);
}
else
{
randomAccess = false;
lazyEvalOK = true;
}
/*
* Initialize fcache (build plans) if first time through.
*/
fcache = (SQLFunctionCachePtr) fcinfo->flinfo->fn_extra;
if (fcache == NULL)
{
init_sql_fcache(fcinfo->flinfo);
init_sql_fcache(fcinfo->flinfo, lazyEvalOK);
fcache = (SQLFunctionCachePtr) fcinfo->flinfo->fn_extra;
}
es = fcache->func_state;
......@@ -609,6 +659,13 @@ fmgr_sql(PG_FUNCTION_ARGS)
if (es && es->status == F_EXEC_START)
postquel_sub_params(fcache, fcinfo);
/*
* Build tuplestore to hold results, if we don't have one already.
* Note it's in the query-lifespan context.
*/
if (!fcache->tstore)
fcache->tstore = tuplestore_begin_heap(randomAccess, false, work_mem);
/*
* Find first unfinished query in function.
*/
......@@ -616,45 +673,97 @@ fmgr_sql(PG_FUNCTION_ARGS)
es = es->next;
/*
* Execute each command in the function one after another until we're
* executing the final command and get a result or we run out of commands.
* Execute each command in the function one after another until we either
* run out of commands or get a result row from a lazily-evaluated SELECT.
*/
while (es)
{
result = postquel_execute(es, fcinfo, fcache, oldcontext);
TupleTableSlot *slot;
if (es->status == F_EXEC_START)
postquel_start(es, fcache);
slot = postquel_getnext(es, fcache);
/*
* If we ran the command to completion, we can shut it down now.
* Any row(s) we need to return are safely stashed in the tuplestore,
* and we want to be sure that, for example, AFTER triggers get fired
* before we return anything. Also, if the function doesn't return
* set, we can shut it down anyway because we don't care about
* fetching any more result rows.
*/
if (TupIsNull(slot) || !fcache->returnsSet)
postquel_end(es);
/*
* Break from loop if we didn't shut down (implying we got a
* lazily-evaluated row). Otherwise we'll press on till the
* whole function is done, relying on the tuplestore to keep hold
* of the data to eventually be returned. This is necessary since
* an INSERT/UPDATE/DELETE RETURNING that sets the result might be
* followed by additional rule-inserted commands, and we want to
* finish doing all those commands before we return anything.
*/
if (es->status != F_EXEC_DONE)
break;
es = es->next;
}
/*
* If we've gone through every command in this function, we are done.
* The tuplestore now contains whatever row(s) we are supposed to return.
*/
if (es == NULL)
if (fcache->returnsSet)
{
/*
* Reset the execution states to start over again on next call.
*/
es = fcache->func_state;
while (es)
ReturnSetInfo *rsi = (ReturnSetInfo *) fcinfo->resultinfo;
if (es)
{
es->status = F_EXEC_START;
es = es->next;
}
/*
* If we stopped short of being done, we must have a lazy-eval row.
*/
Assert(es->lazyEval);
/* Re-use the junkfilter's output slot to fetch back the tuple */
Assert(fcache->junkFilter);
slot = fcache->junkFilter->jf_resultSlot;
if (!tuplestore_gettupleslot(fcache->tstore, true, slot))
elog(ERROR, "failed to fetch lazy-eval tuple");
/* Extract the result as a datum, and copy out from the slot */
result = postquel_get_single_result(slot, fcinfo,
fcache, oldcontext);
/* Clear the tuplestore, but keep it for next time */
/* NB: this might delete the slot's content, but we don't care */
tuplestore_clear(fcache->tstore);
/*
* Let caller know we're finished.
*/
if (fcinfo->flinfo->fn_retset)
/*
* Let caller know we're not finished.
*/
rsi->isDone = ExprMultipleResult;
/*
* Ensure we will get shut down cleanly if the exprcontext is not
* run to completion.
*/
if (!fcache->shutdown_reg)
{
RegisterExprContextCallback(rsi->econtext,
ShutdownSQLFunction,
PointerGetDatum(fcache));
fcache->shutdown_reg = true;
}
}
else if (fcache->lazyEval)
{
ReturnSetInfo *rsi = (ReturnSetInfo *) fcinfo->resultinfo;
/*
* We are done with a lazy evaluation. Clean up.
*/
tuplestore_clear(fcache->tstore);
/*
* Let caller know we're finished.
*/
rsi->isDone = ExprEndResult;
if (rsi && IsA(rsi, ReturnSetInfo))
rsi->isDone = ExprEndResult;
else
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("set-valued function called in context that cannot accept a set")));
fcinfo->isnull = true;
result = (Datum) 0;
......@@ -667,44 +776,74 @@ fmgr_sql(PG_FUNCTION_ARGS)
fcache->shutdown_reg = false;
}
}
else
{
/*
* We are done with a non-lazy evaluation. Return whatever is
* in the tuplestore. (It is now caller's responsibility to
* free the tuplestore when done.)
*/
rsi->returnMode = SFRM_Materialize;
rsi->setResult = fcache->tstore;
fcache->tstore = NULL;
/* must copy desc because execQual will free it */
if (fcache->junkFilter)
rsi->setDesc = CreateTupleDescCopy(fcache->junkFilter->jf_cleanTupType);
error_context_stack = sqlerrcontext.previous;
MemoryContextSwitchTo(oldcontext);
fcinfo->isnull = true;
result = (Datum) 0;
return result;
/* Deregister shutdown callback, if we made one */
if (fcache->shutdown_reg)
{
UnregisterExprContextCallback(rsi->econtext,
ShutdownSQLFunction,
PointerGetDatum(fcache));
fcache->shutdown_reg = false;
}
}
}
else
{
/*
* Non-set function. If we got a row, return it; else return NULL.
*/
if (fcache->junkFilter)
{
/* Re-use the junkfilter's output slot to fetch back the tuple */
slot = fcache->junkFilter->jf_resultSlot;
if (tuplestore_gettupleslot(fcache->tstore, true, slot))
result = postquel_get_single_result(slot, fcinfo,
fcache, oldcontext);
else
{
fcinfo->isnull = true;
result = (Datum) 0;
}
}
else
{
/* Should only get here for VOID functions */
Assert(fcache->rettype == VOIDOID);
fcinfo->isnull = true;
result = (Datum) 0;
}
/*
* If we got a result from a command within the function it has to be the
* final command. All others shouldn't be returning anything.
*/
Assert(LAST_POSTQUEL_COMMAND(es));
/* Clear the tuplestore, but keep it for next time */
tuplestore_clear(fcache->tstore);
}
/*
* Let caller know we're not finished.
* If we've gone through every command in the function, we are done.
* Reset the execution states to start over again on next call.
*/
if (fcinfo->flinfo->fn_retset)
if (es == NULL)
{
ReturnSetInfo *rsi = (ReturnSetInfo *) fcinfo->resultinfo;
if (rsi && IsA(rsi, ReturnSetInfo))
rsi->isDone = ExprMultipleResult;
else
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("set-valued function called in context that cannot accept a set")));
/*
* Ensure we will get shut down cleanly if the exprcontext is not run
* to completion.
*/
if (!fcache->shutdown_reg)
es = fcache->func_state;
while (es)
{
RegisterExprContextCallback(rsi->econtext,
ShutdownSQLFunction,
PointerGetDatum(fcache));
fcache->shutdown_reg = true;
es->status = F_EXEC_START;
es = es->next;
}
}
......@@ -823,6 +962,11 @@ ShutdownSQLFunction(Datum arg)
es = es->next;
}
/* Release tuplestore if we have one */
if (fcache->tstore)
tuplestore_end(fcache->tstore);
fcache->tstore = NULL;
/* execUtils will deregister the callback... */
fcache->shutdown_reg = false;
}
......@@ -831,8 +975,8 @@ ShutdownSQLFunction(Datum arg)
/*
* check_sql_fn_retval() -- check return value of a list of sql parse trees.
*
* The return value of a sql function is the value returned by
* the final query in the function. We do some ad-hoc type checking here
* The return value of a sql function is the value returned by the last
* canSetTag query in the function. We do some ad-hoc type checking here
* to be sure that the user is returning the type he claims. There are
* also a couple of strange-looking features to assist callers in dealing
* with allowed special cases, such as binary-compatible result types.
......@@ -843,18 +987,19 @@ ShutdownSQLFunction(Datum arg)
* function definition of a polymorphic function.)
*
* This function returns true if the sql function returns the entire tuple
* result of its final SELECT, and false otherwise. Note that because we
* result of its final statement, and false otherwise. Note that because we
* allow "SELECT rowtype_expression", this may be false even when the declared
* function return type is a rowtype.
*
* If insertRelabels is true, then binary-compatible cases are dealt with
* by actually inserting RelabelType nodes into the final SELECT; obviously
* the caller must pass a parsetree that it's okay to modify in this case.
* by actually inserting RelabelType nodes into the output targetlist;
* obviously the caller must pass a parsetree that it's okay to modify in this
* case.
*
* If junkFilter isn't NULL, then *junkFilter is set to a JunkFilter defined
* to convert the function's tuple result to the correct output tuple type.
* Whenever the result value is false (ie, the function isn't returning a
* tuple result), *junkFilter is set to NULL.
* Exception: if the function is defined to return VOID then *junkFilter is
* set to NULL.
*/
bool
check_sql_fn_retval(Oid func_id, Oid rettype, List *queryTreeList,
......@@ -863,62 +1008,79 @@ check_sql_fn_retval(Oid func_id, Oid rettype, List *queryTreeList,
{
Query *parse;
List *tlist;
ListCell *tlistitem;
int tlistlen;
char fn_typtype;
Oid restype;
ListCell *lc;
AssertArg(!IsPolymorphicType(rettype));
if (junkFilter)
*junkFilter = NULL; /* default result */
*junkFilter = NULL; /* initialize in case of VOID result */
/* guard against empty function body; OK only if void return type */
if (queryTreeList == NIL)
/*
* Find the last canSetTag query in the list. This isn't necessarily
* the last parsetree, because rule rewriting can insert queries after
* what the user wrote.
*/
parse = NULL;
foreach(lc, queryTreeList)
{
if (rettype != VOIDOID)
ereport(ERROR,
(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
errmsg("return type mismatch in function declared to return %s",
format_type_be(rettype)),
errdetail("Function's final statement must be a SELECT.")));
return false;
}
Query *q = (Query *) lfirst(lc);
/* find the final query */
parse = (Query *) lfirst(list_tail(queryTreeList));
if (q->canSetTag)
parse = q;
}
/*
* If the last query isn't a SELECT, the return type must be VOID.
* If it's a plain SELECT, it returns whatever the targetlist says.
* Otherwise, if it's INSERT/UPDATE/DELETE with RETURNING, it returns that.
* Otherwise, the function return type must be VOID.
*
* Note: eventually replace this test with QueryReturnsTuples? We'd need
* a more general method of determining the output type, though.
* a more general method of determining the output type, though. Also,
* it seems too dangerous to consider FETCH or EXECUTE as returning a
* determinable rowtype, since they depend on relatively short-lived
* entities.
*/
if (!(parse->commandType == CMD_SELECT &&
parse->utilityStmt == NULL &&
parse->intoClause == NULL))
if (parse &&
parse->commandType == CMD_SELECT &&
parse->utilityStmt == NULL &&
parse->intoClause == NULL)
{
tlist = parse->targetList;
}
else if (parse &&
(parse->commandType == CMD_INSERT ||
parse->commandType == CMD_UPDATE ||
parse->commandType == CMD_DELETE) &&
parse->returningList)
{
tlist = parse->returningList;
}
else
{
/* Empty function body, or last statement is a utility command */
if (rettype != VOIDOID)
ereport(ERROR,
(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
errmsg("return type mismatch in function declared to return %s",
format_type_be(rettype)),
errdetail("Function's final statement must be a SELECT.")));
errdetail("Function's final statement must be SELECT or INSERT/UPDATE/DELETE RETURNING.")));
return false;
}
/*
* OK, it's a SELECT, so it must return something matching the declared
* OK, check that the targetlist returns something matching the declared
* type. (We used to insist that the declared type not be VOID in this
* case, but that makes it hard to write a void function that exits after
* calling another void function. Instead, we insist that the SELECT
* calling another void function. Instead, we insist that the tlist
* return void ... so void is treated as if it were a scalar type below.)
*/
/*
* Count the non-junk entries in the result targetlist.
*/
tlist = parse->targetList;
tlistlen = ExecCleanTargetListLength(tlist);
fn_typtype = get_typtype(rettype);
......@@ -940,7 +1102,7 @@ check_sql_fn_retval(Oid func_id, Oid rettype, List *queryTreeList,
(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
errmsg("return type mismatch in function declared to return %s",
format_type_be(rettype)),
errdetail("Final SELECT must return exactly one column.")));
errdetail("Final statement must return exactly one column.")));
/* We assume here that non-junk TLEs must come first in tlists */
tle = (TargetEntry *) linitial(tlist);
......@@ -959,6 +1121,10 @@ check_sql_fn_retval(Oid func_id, Oid rettype, List *queryTreeList,
rettype,
-1,
COERCE_DONTCARE);
/* Set up junk filter if needed */
if (junkFilter)
*junkFilter = ExecInitJunkFilter(tlist, false, NULL);
}
else if (fn_typtype == TYPTYPE_COMPOSITE || rettype == RECORDOID)
{
......@@ -988,6 +1154,9 @@ check_sql_fn_retval(Oid func_id, Oid rettype, List *queryTreeList,
rettype,
-1,
COERCE_DONTCARE);
/* Set up junk filter if needed */
if (junkFilter)
*junkFilter = ExecInitJunkFilter(tlist, false, NULL);
return false; /* NOT returning whole tuple */
}
}
......@@ -1014,9 +1183,9 @@ check_sql_fn_retval(Oid func_id, Oid rettype, List *queryTreeList,
tuplogcols = 0; /* we'll count nondeleted cols as we go */
colindex = 0;
foreach(tlistitem, tlist)
foreach(lc, tlist)
{
TargetEntry *tle = (TargetEntry *) lfirst(tlistitem);
TargetEntry *tle = (TargetEntry *) lfirst(lc);
Form_pg_attribute attr;
Oid tletype;
Oid atttype;
......@@ -1032,7 +1201,7 @@ check_sql_fn_retval(Oid func_id, Oid rettype, List *queryTreeList,
(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
errmsg("return type mismatch in function declared to return %s",
format_type_be(rettype)),
errdetail("Final SELECT returns too many columns.")));
errdetail("Final statement returns too many columns.")));
attr = tupdesc->attrs[colindex - 1];
} while (attr->attisdropped);
tuplogcols++;
......@@ -1044,7 +1213,7 @@ check_sql_fn_retval(Oid func_id, Oid rettype, List *queryTreeList,
(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
errmsg("return type mismatch in function declared to return %s",
format_type_be(rettype)),
errdetail("Final SELECT returns %s instead of %s at column %d.",
errdetail("Final statement returns %s instead of %s at column %d.",
format_type_be(tletype),
format_type_be(atttype),
tuplogcols)));
......@@ -1069,7 +1238,7 @@ check_sql_fn_retval(Oid func_id, Oid rettype, List *queryTreeList,
(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
errmsg("return type mismatch in function declared to return %s",
format_type_be(rettype)),
errdetail("Final SELECT returns too few columns.")));
errdetail("Final statement returns too few columns.")));
/* Set up junk filter if needed */
if (junkFilter)
......@@ -1088,3 +1257,70 @@ check_sql_fn_retval(Oid func_id, Oid rettype, List *queryTreeList,
return false;
}
/*
* CreateSQLFunctionDestReceiver -- create a suitable DestReceiver object
*
* Since CreateDestReceiver doesn't accept the parameters we'd need,
* we just leave the private fields zeroed here. postquel_start will
* fill them in.
*/
DestReceiver *
CreateSQLFunctionDestReceiver(void)
{
DR_sqlfunction *self = (DR_sqlfunction *) palloc0(sizeof(DR_sqlfunction));
self->pub.receiveSlot = sqlfunction_receive;
self->pub.rStartup = sqlfunction_startup;
self->pub.rShutdown = sqlfunction_shutdown;
self->pub.rDestroy = sqlfunction_destroy;
self->pub.mydest = DestSQLFunction;
return (DestReceiver *) self;
}
/*
* sqlfunction_startup --- executor startup
*/
static void
sqlfunction_startup(DestReceiver *self, int operation, TupleDesc typeinfo)
{
/* no-op */
}
/*
* sqlfunction_receive --- receive one tuple
*/
static void
sqlfunction_receive(TupleTableSlot *slot, DestReceiver *self)
{
DR_sqlfunction *myState = (DR_sqlfunction *) self;
MemoryContext oldcxt;
/* Filter tuple as needed */
slot = ExecFilterJunk(myState->filter, slot);
/* Store the filtered tuple into the tuplestore */
oldcxt = MemoryContextSwitchTo(myState->cxt);
tuplestore_puttupleslot(myState->tstore, slot);
MemoryContextSwitchTo(oldcxt);
}
/*
* sqlfunction_shutdown --- executor end
*/
static void
sqlfunction_shutdown(DestReceiver *self)
{
/* no-op */
}
/*
* sqlfunction_destroy --- release DestReceiver object
*/
static void
sqlfunction_destroy(DestReceiver *self)
{
pfree(self);
}
......@@ -8,7 +8,7 @@
* Portions Copyright (c) 1994, Regents of the University of California
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/tcop/dest.c,v 1.72 2008/01/01 19:45:52 momjian Exp $
* $PostgreSQL: pgsql/src/backend/tcop/dest.c,v 1.73 2008/10/31 19:37:56 tgl Exp $
*
*-------------------------------------------------------------------------
*/
......@@ -32,6 +32,7 @@
#include "access/xact.h"
#include "commands/copy.h"
#include "executor/executor.h"
#include "executor/functions.h"
#include "executor/tstoreReceiver.h"
#include "libpq/libpq.h"
#include "libpq/pqformat.h"
......@@ -132,6 +133,9 @@ CreateDestReceiver(CommandDest dest, Portal portal)
case DestCopyOut:
return CreateCopyDestReceiver();
case DestSQLFunction:
return CreateSQLFunctionDestReceiver();
}
/* should never get here */
......@@ -158,6 +162,7 @@ EndCommand(const char *commandTag, CommandDest dest)
case DestTuplestore:
case DestIntoRel:
case DestCopyOut:
case DestSQLFunction:
break;
}
}
......@@ -198,6 +203,7 @@ NullCommand(CommandDest dest)
case DestTuplestore:
case DestIntoRel:
case DestCopyOut:
case DestSQLFunction:
break;
}
}
......@@ -240,6 +246,7 @@ ReadyForQuery(CommandDest dest)
case DestTuplestore:
case DestIntoRel:
case DestCopyOut:
case DestSQLFunction:
break;
}
}
$PostgreSQL: pgsql/src/backend/utils/fmgr/README,v 1.15 2008/10/29 00:00:38 tgl Exp $
$PostgreSQL: pgsql/src/backend/utils/fmgr/README,v 1.16 2008/10/31 19:37:56 tgl Exp $
Function Manager
================
......@@ -434,7 +434,9 @@ and returns null. isDone is not used and should be left at ExprSingleResult.
The Tuplestore must be created with randomAccess = true if
SFRM_Materialize_Random is set in allowedModes, but it can (and preferably
should) be created with randomAccess = false if not.
should) be created with randomAccess = false if not. Callers that can support
both ValuePerCall and Materialize mode will set SFRM_Materialize_Preferred,
or not, depending on which mode they prefer.
If available, the expected tuple descriptor is passed in ReturnSetInfo;
in other contexts the expectedDesc field will be NULL. The function need
......
......@@ -7,7 +7,7 @@
* Portions Copyright (c) 1996-2008, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
* $PostgreSQL: pgsql/src/include/executor/functions.h,v 1.31 2008/03/18 22:04:14 tgl Exp $
* $PostgreSQL: pgsql/src/include/executor/functions.h,v 1.32 2008/10/31 19:37:56 tgl Exp $
*
*-------------------------------------------------------------------------
*/
......@@ -15,6 +15,7 @@
#define FUNCTIONS_H
#include "nodes/execnodes.h"
#include "tcop/dest.h"
extern Datum fmgr_sql(PG_FUNCTION_ARGS);
......@@ -24,4 +25,6 @@ extern bool check_sql_fn_retval(Oid func_id, Oid rettype,
bool insertRelabels,
JunkFilter **junkFilter);
extern DestReceiver *CreateSQLFunctionDestReceiver(void);
#endif /* FUNCTIONS_H */
......@@ -7,7 +7,7 @@
* Portions Copyright (c) 1996-2008, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
* $PostgreSQL: pgsql/src/include/nodes/execnodes.h,v 1.193 2008/10/29 00:00:39 tgl Exp $
* $PostgreSQL: pgsql/src/include/nodes/execnodes.h,v 1.194 2008/10/31 19:37:56 tgl Exp $
*
*-------------------------------------------------------------------------
*/
......@@ -151,13 +151,15 @@ typedef enum
/*
* Return modes for functions returning sets. Note values must be chosen
* as separate bits so that a bitmask can be formed to indicate supported
* modes.
* modes. SFRM_Materialize_Random and SFRM_Materialize_Preferred are
* auxiliary flags about SFRM_Materialize mode, rather than separate modes.
*/
typedef enum
{
SFRM_ValuePerCall = 0x01, /* one value returned per call */
SFRM_Materialize = 0x02, /* result set instantiated in Tuplestore */
SFRM_Materialize_Random = 0x04 /* Tuplestore needs randomAccess */
SFRM_Materialize_Random = 0x04, /* Tuplestore needs randomAccess */
SFRM_Materialize_Preferred = 0x08 /* caller prefers Tuplestore */
} SetFunctionReturnMode;
/*
......
......@@ -54,7 +54,7 @@
* Portions Copyright (c) 1996-2008, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
* $PostgreSQL: pgsql/src/include/tcop/dest.h,v 1.54 2008/01/01 19:45:59 momjian Exp $
* $PostgreSQL: pgsql/src/include/tcop/dest.h,v 1.55 2008/10/31 19:37:56 tgl Exp $
*
*-------------------------------------------------------------------------
*/
......@@ -86,7 +86,8 @@ typedef enum
DestSPI, /* results sent to SPI manager */
DestTuplestore, /* results sent to Tuplestore */
DestIntoRel, /* results sent to relation (SELECT INTO) */
DestCopyOut /* results sent to COPY TO code */
DestCopyOut, /* results sent to COPY TO code */
DestSQLFunction /* results sent to SQL-language func mgr */
} CommandDest;
/* ----------------
......
......@@ -567,3 +567,179 @@ SELECT * FROM foo(3);
(9 rows)
DROP FUNCTION foo(int);
--
-- some tests on SQL functions with RETURNING
--
create temp table tt(f1 serial, data text);
NOTICE: CREATE TABLE will create implicit sequence "tt_f1_seq" for serial column "tt.f1"
create function insert_tt(text) returns int as
$$ insert into tt(data) values($1) returning f1 $$
language sql;
select insert_tt('foo');
insert_tt
-----------
1
(1 row)
select insert_tt('bar');
insert_tt
-----------
2
(1 row)
select * from tt;
f1 | data
----+------
1 | foo
2 | bar
(2 rows)
-- insert will execute to completion even if function needs just 1 row
create or replace function insert_tt(text) returns int as
$$ insert into tt(data) values($1),($1||$1) returning f1 $$
language sql;
select insert_tt('fool');
insert_tt
-----------
3
(1 row)
select * from tt;
f1 | data
----+----------
1 | foo
2 | bar
3 | fool
4 | foolfool
(4 rows)
-- setof does what's expected
create or replace function insert_tt2(text,text) returns setof int as
$$ insert into tt(data) values($1),($2) returning f1 $$
language sql;
select insert_tt2('foolish','barrish');
insert_tt2
------------
5
6
(2 rows)
select * from insert_tt2('baz','quux');
insert_tt2
------------
7
8
(2 rows)
select * from tt;
f1 | data
----+----------
1 | foo
2 | bar
3 | fool
4 | foolfool
5 | foolish
6 | barrish
7 | baz
8 | quux
(8 rows)
-- limit doesn't prevent execution to completion
select insert_tt2('foolish','barrish') limit 1;
insert_tt2
------------
9
(1 row)
select * from tt;
f1 | data
----+----------
1 | foo
2 | bar
3 | fool
4 | foolfool
5 | foolish
6 | barrish
7 | baz
8 | quux
9 | foolish
10 | barrish
(10 rows)
-- triggers will fire, too
create function noticetrigger() returns trigger as $$
begin
raise notice 'noticetrigger % %', new.f1, new.data;
return null;
end $$ language plpgsql;
create trigger tnoticetrigger after insert on tt for each row
execute procedure noticetrigger();
select insert_tt2('foolme','barme') limit 1;
NOTICE: noticetrigger 11 foolme
CONTEXT: SQL function "insert_tt2" statement 1
NOTICE: noticetrigger 12 barme
CONTEXT: SQL function "insert_tt2" statement 1
insert_tt2
------------
11
(1 row)
select * from tt;
f1 | data
----+----------
1 | foo
2 | bar
3 | fool
4 | foolfool
5 | foolish
6 | barrish
7 | baz
8 | quux
9 | foolish
10 | barrish
11 | foolme
12 | barme
(12 rows)
-- and rules work
create temp table tt_log(f1 int, data text);
create rule insert_tt_rule as on insert to tt do also
insert into tt_log values(new.*);
select insert_tt2('foollog','barlog') limit 1;
NOTICE: noticetrigger 13 foollog
CONTEXT: SQL function "insert_tt2" statement 1
NOTICE: noticetrigger 14 barlog
CONTEXT: SQL function "insert_tt2" statement 1
insert_tt2
------------
13
(1 row)
select * from tt;
f1 | data
----+----------
1 | foo
2 | bar
3 | fool
4 | foolfool
5 | foolish
6 | barrish
7 | baz
8 | quux
9 | foolish
10 | barrish
11 | foolme
12 | barme
13 | foollog
14 | barlog
(14 rows)
-- note that nextval() gets executed a second time in the rule expansion,
-- which is expected.
select * from tt_log;
f1 | data
----+---------
15 | foollog
16 | barlog
(2 rows)
......@@ -61,7 +61,7 @@ LINE 2: AS 'not even SQL';
CREATE FUNCTION test1 (int) RETURNS int LANGUAGE SQL
AS 'SELECT 1, 2, 3;';
ERROR: return type mismatch in function declared to return integer
DETAIL: Final SELECT must return exactly one column.
DETAIL: Final statement must return exactly one column.
CONTEXT: SQL function "test1"
CREATE FUNCTION test1 (int) RETURNS int LANGUAGE SQL
AS 'SELECT $2;';
......
......@@ -279,3 +279,62 @@ AS $$ SELECT a, b
generate_series(1,$1) b(b) $$ LANGUAGE sql;
SELECT * FROM foo(3);
DROP FUNCTION foo(int);
--
-- some tests on SQL functions with RETURNING
--
create temp table tt(f1 serial, data text);
create function insert_tt(text) returns int as
$$ insert into tt(data) values($1) returning f1 $$
language sql;
select insert_tt('foo');
select insert_tt('bar');
select * from tt;
-- insert will execute to completion even if function needs just 1 row
create or replace function insert_tt(text) returns int as
$$ insert into tt(data) values($1),($1||$1) returning f1 $$
language sql;
select insert_tt('fool');
select * from tt;
-- setof does what's expected
create or replace function insert_tt2(text,text) returns setof int as
$$ insert into tt(data) values($1),($2) returning f1 $$
language sql;
select insert_tt2('foolish','barrish');
select * from insert_tt2('baz','quux');
select * from tt;
-- limit doesn't prevent execution to completion
select insert_tt2('foolish','barrish') limit 1;
select * from tt;
-- triggers will fire, too
create function noticetrigger() returns trigger as $$
begin
raise notice 'noticetrigger % %', new.f1, new.data;
return null;
end $$ language plpgsql;
create trigger tnoticetrigger after insert on tt for each row
execute procedure noticetrigger();
select insert_tt2('foolme','barme') limit 1;
select * from tt;
-- and rules work
create temp table tt_log(f1 int, data text);
create rule insert_tt_rule as on insert to tt do also
insert into tt_log values(new.*);
select insert_tt2('foollog','barlog') limit 1;
select * from tt;
-- note that nextval() gets executed a second time in the rule expansion,
-- which is expected.
select * from tt_log;
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment