PL/pgSQL functions can return sets. Neil Conway's patch, modified so

that the functionality is available to anyone via ReturnSetInfo, rather
than hard-wiring it to PL/pgSQL.
This commit is contained in:
Tom Lane 2002-08-30 00:28:41 +00:00
parent 82ccb420d5
commit e107f3a7e3
21 changed files with 957 additions and 408 deletions

View File

@ -16,14 +16,13 @@
#include "postgres.h"
#include <ctype.h>
#include <stdio.h>
#include <sys/types.h>
#include <string.h>
#include "postgres.h"
#include "access/heapam.h"
#include "catalog/catname.h"
#include "catalog/indexing.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_type.h"
#include "executor/executor.h"
#include "utils/fcache.h"
#include "utils/sets.h"
@ -97,7 +96,7 @@ static PGARRAY * GetPGArray(int4 state, int fAdd)
p->a.size = cb;
p->a.ndim = 0;
p->a.flags = 0;
p->a.elmtype = INT4OID;
p->a.elemtype = INT4OID;
p->items = 0;
p->lower= START_NUM;
}
@ -150,7 +149,7 @@ static PGARRAY *ShrinkPGArray(PGARRAY *p)
pnew->a.size = cb;
pnew->a.ndim=1;
pnew->a.flags = 0;
pnew->a.elmtype = INT4OID;
pnew->a.elemtype = INT4OID;
pnew->lower = 0;
}
else
@ -171,11 +170,11 @@ Datum int_agg_state(PG_FUNCTION_ARGS)
PGARRAY *p = GetPGArray(state, 1);
if(!p)
{
elog(ERROR,"No aggregate storage\n");
elog(ERROR,"No aggregate storage");
}
else if(p->items >= p->lower)
{
elog(ERROR,"aggregate storage too small\n");
elog(ERROR,"aggregate storage too small");
}
else
{
@ -202,32 +201,24 @@ Datum int_agg_final_array(PG_FUNCTION_ARGS)
/* This function accepts an array, and returns one item for each entry in the array */
Datum int_enum(PG_FUNCTION_ARGS)
{
CTX *pc;
PGARRAY *p = (PGARRAY *) PG_GETARG_POINTER(0);
CTX *pc;
ReturnSetInfo *rsi = (ReturnSetInfo *)fcinfo->resultinfo;
if (!rsi || !IsA(rsi, ReturnSetInfo))
elog(ERROR, "No ReturnSetInfo sent! function must be declared returning a 'setof' integer");
if(!p)
{
elog(WARNING, "No data sent\n");
return 0;
}
if(!rsi)
{
elog(ERROR, "No ReturnSetInfo sent! function must be declared returning a 'setof' integer");
elog(WARNING, "No data sent");
PG_RETURN_NULL();
}
if(!fcinfo->context)
{
/* Allocate a working context */
pc = (CTX *) palloc(sizeof(CTX));
if(!pc)
{
elog(ERROR, "CTX Alocation failed\n");
PG_RETURN_NULL();
}
/* Don't copy atribute if you don't need too */
if(VARATT_IS_EXTENDED(p) )
{
@ -236,7 +227,7 @@ Datum int_enum(PG_FUNCTION_ARGS)
pc->flags = TOASTED;
if(!pc->p)
{
elog(ERROR, "Error in toaster!!! no detoasting\n");
elog(ERROR, "Error in toaster!!! no detoasting");
PG_RETURN_NULL();
}
}

View File

@ -1,5 +1,5 @@
<!--
$Header: /cvsroot/pgsql/doc/src/sgml/plpgsql.sgml,v 1.4 2002/08/29 04:12:02 tgl Exp $
$Header: /cvsroot/pgsql/doc/src/sgml/plpgsql.sgml,v 1.5 2002/08/30 00:28:40 tgl Exp $
-->
<chapter id="plpgsql">
@ -1142,11 +1142,20 @@ GET DIAGNOSTICS <replaceable>variable</replaceable> = <replaceable>item</replace
RETURN <replaceable>expression</replaceable>;
</synopsis>
RETURN with an expression is used to return from a
<application>PL/pgSQL</> function that does not return a set.
The function terminates and the value of
<replaceable>expression</replaceable> will be returned to the
upper executor.
<replaceable>expression</replaceable> is returned to the caller.
</para>
<para>
To return a composite (row) value, you must write a record or row
variable as the <replaceable>expression</replaceable>. When
returning a scalar type, any expression can be used.
The expression's result will be automatically cast into the
function's return type as described for assignments.
(If you have declared the function to return <type>void</>,
then the expression can be omitted, and will be ignored in any case.)
</para>
<para>
@ -1155,6 +1164,28 @@ RETURN <replaceable>expression</replaceable>;
the function without hitting a RETURN statement, a run-time error
will occur.
</para>
<para>
When a <application>PL/pgSQL</> function is declared to return
<literal>SETOF</literal> <replaceable>sometype</>, the procedure
to follow is slightly different. The individual items to be returned
are specified in RETURN NEXT commands, and then a final RETURN with
no argument is given to indicate that the function is done generating
items.
<synopsis>
RETURN NEXT <replaceable>expression</replaceable>;
</synopsis>
RETURN NEXT does not actually return from the function; it simply
saves away the value of the expression (or record or row variable,
as appropriate for the datatype being returned).
Execution then continues with the next statement in the
<application>PL/pgSQL</> function. As successive RETURN NEXT
commands are executed, the result set is built up. A final
RETURN, which need have no argument, causes control to exit
the function.
</para>
</sect2>
<sect2 id="plpgsql-conditionals">
@ -1531,8 +1562,8 @@ END LOOP;
to worry about that, since FOR loops automatically use a cursor
internally to avoid memory problems.) A more interesting usage is to
return a reference to a cursor that it has created, allowing the
caller to read the rows. This provides a way to return row sets
from functions.
caller to read the rows. This provides an efficient way to return
large row sets from functions.
</para>
<sect2 id="plpgsql-cursor-declarations">
@ -1794,19 +1825,27 @@ COMMIT;
RAISE <replaceable class="parameter">level</replaceable> '<replaceable class="parameter">format</replaceable>' <optional>, <replaceable class="parameter">variable</replaceable> <optional>...</optional></optional>;
</synopsis>
Possible levels are DEBUG (write the message into the postmaster log),
NOTICE (write the message into the postmaster log and forward it to
the client application) and EXCEPTION (raise an error,
aborting the transaction).
Possible levels are <literal>DEBUG</literal> (write the message to
the server log), <literal>LOG</literal> (write the message to the
server log with a higher priority), <literal>INFO</literal>,
<literal>NOTICE</literal> and <literal>WARNING</literal> (write
the message to the server log and send it to the client, with
respectively higher priorities), and <literal>EXCEPTION</literal>
(raise an error and abort the current transaction). Whether error
messages of a particular priority are reported to the client,
written to the server log, or both is controlled by the
<option>SERVER_MIN_MESSAGES</option> and
<option>CLIENT_MIN_MESSAGES</option> configuration variables. See
the <citetitle>PostgreSQL Administrator's Guide</citetitle> for more
information.
</para>
<para>
Inside the format string, <literal>%</literal> is replaced by the next
optional argument's external representation.
Write <literal>%%</literal> to emit a literal <literal>%</literal>.
Note that the optional arguments must presently
be simple variables, not expressions, and the format must be a simple
string literal.
Inside the format string, <literal>%</literal> is replaced by the
next optional argument's external representation. Write
<literal>%%</literal> to emit a literal <literal>%</literal>. Note
that the optional arguments must presently be simple variables,
not expressions, and the format must be a simple string literal.
</para>
<!--
@ -1820,8 +1859,9 @@ RAISE <replaceable class="parameter">level</replaceable> '<replaceable class="pa
<programlisting>
RAISE NOTICE ''Calling cs_create_job(%)'',v_job_id;
</programlisting>
In this example, the value of v_job_id will replace the % in the
string.
In this example, the value of v_job_id will replace the
<literal>%</literal> in the string.
</para>
<para>
@ -1852,12 +1892,12 @@ RAISE EXCEPTION ''Inexistent ID --> %'',user_id;
</para>
<para>
Thus, the only thing <application>PL/pgSQL</application> currently does when it encounters
an abort during execution of a function or trigger
procedure is to write some additional NOTICE level log messages
telling in which function and where (line number and type of
statement) this happened. The error always stops execution of
the function.
Thus, the only thing <application>PL/pgSQL</application>
currently does when it encounters an abort during execution of a
function or trigger procedure is to write some additional
<literal>NOTICE</literal> level log messages telling in which
function and where (line number and type of statement) this
happened. The error always stops execution of the function.
</para>
</sect2>
</sect1>

View File

@ -1,5 +1,5 @@
<!--
$Header: /cvsroot/pgsql/doc/src/sgml/release.sgml,v 1.154 2002/08/29 03:22:00 tgl Exp $
$Header: /cvsroot/pgsql/doc/src/sgml/release.sgml,v 1.155 2002/08/30 00:28:40 tgl Exp $
-->
<appendix id="release">
@ -24,6 +24,7 @@ CDATA means the content is "SGML-free", so you can write without
worries about funny characters.
-->
<literallayout><![CDATA[
Substantial improvements in functionality for functions returning sets
Client libraries older than 6.3 no longer supported (version 0 protocol removed)
PREPARE statement allows caching query plans for interactive statements
Type OPAQUE is now deprecated in favor of pseudo-types cstring, trigger, etc

View File

@ -1,5 +1,5 @@
<!--
$Header: /cvsroot/pgsql/doc/src/sgml/runtime.sgml,v 1.128 2002/08/29 19:53:58 momjian Exp $
$Header: /cvsroot/pgsql/doc/src/sgml/runtime.sgml,v 1.129 2002/08/30 00:28:40 tgl Exp $
-->
<Chapter Id="runtime">
@ -921,7 +921,8 @@ env PGOPTIONS='-c geqo=off' psql
built (see the configure option
<literal>--enable-cassert</literal>). Note that
<literal>DEBUG_ASSERTIONS</literal> defaults to on if
<productname>PostgreSQL</productname> has been built this way.
<productname>PostgreSQL</productname> has been built with
assertions enabled.
</para>
</listitem>
</varlistentry>

View File

@ -1,5 +1,5 @@
<!--
$Header: /cvsroot/pgsql/doc/src/sgml/xfunc.sgml,v 1.58 2002/08/29 17:14:32 tgl Exp $
$Header: /cvsroot/pgsql/doc/src/sgml/xfunc.sgml,v 1.59 2002/08/30 00:28:40 tgl Exp $
-->
<chapter id="xfunc">
@ -315,9 +315,7 @@ ERROR: function declared to return emp returns varchar instead of text at colum
function, as described below. It can also be called in the context
of an SQL expression, but only when you
extract a single attribute out of the row or pass the entire row into
another function that accepts the same composite type. (Trying to
display the entire row value will yield
a meaningless number.) For example,
another function that accepts the same composite type. For example,
<programlisting>
SELECT (new_emp()).name;

View File

@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
* $Header: /cvsroot/pgsql/src/backend/executor/execQual.c,v 1.101 2002/08/26 17:53:57 tgl Exp $
* $Header: /cvsroot/pgsql/src/backend/executor/execQual.c,v 1.102 2002/08/30 00:28:41 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -35,12 +35,15 @@
#include "postgres.h"
#include "access/heapam.h"
#include "catalog/pg_type.h"
#include "executor/execdebug.h"
#include "executor/functions.h"
#include "executor/nodeSubplan.h"
#include "miscadmin.h"
#include "utils/array.h"
#include "utils/builtins.h"
#include "utils/fcache.h"
#include "utils/lsyscache.h"
/* static function decls */
@ -646,9 +649,6 @@ ExecEvalFuncArgs(FunctionCallInfo fcinfo,
* ExecMakeFunctionResult
*
* Evaluate the arguments to a function and then the function itself.
*
* NOTE: econtext is used only for evaluating the argument expressions;
* it is not passed to the function itself.
*/
Datum
ExecMakeFunctionResult(FunctionCachePtr fcache,
@ -707,6 +707,11 @@ ExecMakeFunctionResult(FunctionCachePtr fcache,
fcinfo.resultinfo = (Node *) &rsinfo;
rsinfo.type = T_ReturnSetInfo;
rsinfo.econtext = econtext;
rsinfo.allowedModes = (int) SFRM_ValuePerCall;
rsinfo.returnMode = SFRM_ValuePerCall;
/* isDone is filled below */
rsinfo.setResult = NULL;
rsinfo.setDesc = NULL;
}
/*
@ -837,10 +842,240 @@ ExecMakeFunctionResult(FunctionCachePtr fcache,
}
/*
* ExecMakeTableFunctionResult
*
* Evaluate a table function, producing a materialized result in a Tuplestore
* object. (If function returns an empty set, we just return NULL instead.)
*/
Tuplestorestate *
ExecMakeTableFunctionResult(Expr *funcexpr,
ExprContext *econtext,
TupleDesc *returnDesc)
{
Tuplestorestate *tupstore = NULL;
TupleDesc tupdesc = NULL;
Func *func;
List *argList;
FunctionCachePtr fcache;
FunctionCallInfoData fcinfo;
ReturnSetInfo rsinfo; /* for functions returning sets */
ExprDoneCond argDone;
MemoryContext callerContext;
MemoryContext oldcontext;
TupleTableSlot *slot;
bool first_time = true;
bool returnsTuple = false;
/* Extract data from function-call expression node */
if (!funcexpr || !IsA(funcexpr, Expr) || funcexpr->opType != FUNC_EXPR)
elog(ERROR, "ExecMakeTableFunctionResult: expression is not a function call");
func = (Func *) funcexpr->oper;
argList = funcexpr->args;
/*
* get the fcache from the Func node. If it is NULL, then initialize it
*/
fcache = func->func_fcache;
if (fcache == NULL)
{
fcache = init_fcache(func->funcid, length(argList),
econtext->ecxt_per_query_memory);
func->func_fcache = fcache;
}
/*
* Evaluate the function's argument list.
*
* Note: ideally, we'd do this in the per-tuple context, but then the
* argument values would disappear when we reset the context in the
* inner loop. So do it in caller context. Perhaps we should make a
* separate context just to hold the evaluated arguments?
*/
MemSet(&fcinfo, 0, sizeof(fcinfo));
fcinfo.flinfo = &(fcache->func);
argDone = ExecEvalFuncArgs(&fcinfo, argList, econtext);
/* We don't allow sets in the arguments of the table function */
if (argDone != ExprSingleResult)
elog(ERROR, "Set-valued function called in context that cannot accept a set");
/*
* If function is strict, and there are any NULL arguments, skip
* calling the function and return NULL (actually an empty set).
*/
if (fcache->func.fn_strict)
{
int i;
for (i = 0; i < fcinfo.nargs; i++)
{
if (fcinfo.argnull[i])
{
*returnDesc = NULL;
return NULL;
}
}
}
/*
* If function returns set, prepare a resultinfo node for
* communication
*/
if (fcache->func.fn_retset)
{
fcinfo.resultinfo = (Node *) &rsinfo;
rsinfo.type = T_ReturnSetInfo;
rsinfo.econtext = econtext;
rsinfo.allowedModes = (int) (SFRM_ValuePerCall | SFRM_Materialize);
}
/* we set these fields always since we examine them below */
rsinfo.returnMode = SFRM_ValuePerCall;
/* isDone is filled below */
rsinfo.setResult = NULL;
rsinfo.setDesc = NULL;
/*
* Switch to short-lived context for calling the function.
*/
callerContext = MemoryContextSwitchTo(econtext->ecxt_per_tuple_memory);
/*
* Loop to handle the ValuePerCall protocol.
*/
for (;;)
{
Datum result;
HeapTuple tuple;
/*
* reset per-tuple memory context before each call of the function.
* This cleans up any local memory the function may leak when called.
*/
ResetExprContext(econtext);
/* Call the function one time */
fcinfo.isnull = false;
rsinfo.isDone = ExprSingleResult;
result = FunctionCallInvoke(&fcinfo);
/* Which protocol does function want to use? */
if (rsinfo.returnMode == SFRM_ValuePerCall)
{
/*
* Check for end of result set.
*
* Note: if function returns an empty set, we don't build a
* tupdesc or tuplestore (since we can't get a tupdesc in the
* function-returning-tuple case)
*/
if (rsinfo.isDone == ExprEndResult)
break;
/*
* If first time through, build tupdesc and tuplestore for result
*/
if (first_time)
{
Oid funcrettype = funcexpr->typeOid;
oldcontext = MemoryContextSwitchTo(econtext->ecxt_per_query_memory);
if (funcrettype == RECORDOID ||
get_typtype(funcrettype) == 'c')
{
/*
* Composite type, so function should have returned a
* TupleTableSlot; use its descriptor
*/
slot = (TupleTableSlot *) DatumGetPointer(result);
if (fcinfo.isnull || !slot || !IsA(slot, TupleTableSlot) ||
!slot->ttc_tupleDescriptor)
elog(ERROR, "ExecMakeTableFunctionResult: Invalid result from function returning tuple");
tupdesc = CreateTupleDescCopy(slot->ttc_tupleDescriptor);
returnsTuple = true;
}
else
{
/*
* Scalar type, so make a single-column descriptor
*/
tupdesc = CreateTemplateTupleDesc(1, WITHOUTOID);
TupleDescInitEntry(tupdesc,
(AttrNumber) 1,
"column",
funcrettype,
-1,
0,
false);
}
tupstore = tuplestore_begin_heap(true, /* randomAccess */
SortMem);
MemoryContextSwitchTo(oldcontext);
rsinfo.setResult = tupstore;
rsinfo.setDesc = tupdesc;
}
/*
* Store current resultset item.
*/
if (returnsTuple)
{
slot = (TupleTableSlot *) DatumGetPointer(result);
if (fcinfo.isnull || !slot || !IsA(slot, TupleTableSlot) ||
TupIsNull(slot))
elog(ERROR, "ExecMakeTableFunctionResult: Invalid result from function returning tuple");
tuple = slot->val;
}
else
{
char nullflag;
nullflag = fcinfo.isnull ? 'n' : ' ';
tuple = heap_formtuple(tupdesc, &result, &nullflag);
}
oldcontext = MemoryContextSwitchTo(econtext->ecxt_per_query_memory);
tuplestore_puttuple(tupstore, tuple);
MemoryContextSwitchTo(oldcontext);
/*
* Are we done?
*/
if (rsinfo.isDone != ExprMultipleResult)
break;
}
else if (rsinfo.returnMode == SFRM_Materialize)
{
/* check we're on the same page as the function author */
if (!first_time || rsinfo.isDone != ExprSingleResult)
elog(ERROR, "ExecMakeTableFunctionResult: Materialize-mode protocol not followed");
/* Done evaluating the set result */
break;
}
else
elog(ERROR, "ExecMakeTableFunctionResult: unknown returnMode %d",
(int) rsinfo.returnMode);
first_time = false;
}
/* If we have a locally-created tupstore, close it up */
if (tupstore)
{
MemoryContextSwitchTo(econtext->ecxt_per_query_memory);
tuplestore_donestoring(tupstore);
}
MemoryContextSwitchTo(callerContext);
/* The returned pointers are those in rsinfo */
*returnDesc = rsinfo.setDesc;
return rsinfo.setResult;
}
/* ----------------------------------------------------------------
* ExecEvalOper
* ExecEvalDistinct
* ExecEvalFunc
* ExecEvalDistinct
*
* Evaluate the functional result of a list of arguments by calling the
* function manager.
@ -886,6 +1121,48 @@ ExecEvalOper(Expr *opClause,
isNull, isDone);
}
/* ----------------------------------------------------------------
* ExecEvalFunc
* ----------------------------------------------------------------
*/
static Datum
ExecEvalFunc(Expr *funcClause,
ExprContext *econtext,
bool *isNull,
ExprDoneCond *isDone)
{
Func *func;
List *argList;
FunctionCachePtr fcache;
/*
* we extract the oid of the function associated with the func node
* and then pass the work onto ExecMakeFunctionResult which evaluates
* the arguments and returns the result of calling the function on the
* evaluated arguments.
*
* this is nearly identical to the ExecEvalOper code.
*/
func = (Func *) funcClause->oper;
argList = funcClause->args;
/*
* get the fcache from the Func node. If it is NULL, then initialize
* it
*/
fcache = func->func_fcache;
if (fcache == NULL)
{
fcache = init_fcache(func->funcid, length(argList),
econtext->ecxt_per_query_memory);
func->func_fcache = fcache;
}
return ExecMakeFunctionResult(fcache, argList, econtext,
isNull, isDone);
}
/* ----------------------------------------------------------------
* ExecEvalDistinct
*
@ -960,48 +1237,6 @@ ExecEvalDistinct(Expr *opClause,
return BoolGetDatum(result);
}
/* ----------------------------------------------------------------
* ExecEvalFunc
* ----------------------------------------------------------------
*/
static Datum
ExecEvalFunc(Expr *funcClause,
ExprContext *econtext,
bool *isNull,
ExprDoneCond *isDone)
{
Func *func;
List *argList;
FunctionCachePtr fcache;
/*
* we extract the oid of the function associated with the func node
* and then pass the work onto ExecMakeFunctionResult which evaluates
* the arguments and returns the result of calling the function on the
* evaluated arguments.
*
* this is nearly identical to the ExecEvalOper code.
*/
func = (Func *) funcClause->oper;
argList = funcClause->args;
/*
* get the fcache from the Func node. If it is NULL, then initialize
* it
*/
fcache = func->func_fcache;
if (fcache == NULL)
{
fcache = init_fcache(func->funcid, length(argList),
econtext->ecxt_per_query_memory);
func->func_fcache = fcache;
}
return ExecMakeFunctionResult(fcache, argList, econtext,
isNull, isDone);
}
/* ----------------------------------------------------------------
* ExecEvalNot
* ExecEvalOr

View File

@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
* $Header: /cvsroot/pgsql/src/backend/executor/nodeFunctionscan.c,v 1.7 2002/08/29 17:14:33 tgl Exp $
* $Header: /cvsroot/pgsql/src/backend/executor/nodeFunctionscan.c,v 1.8 2002/08/30 00:28:41 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -22,7 +22,6 @@
*/
#include "postgres.h"
#include "miscadmin.h"
#include "access/heapam.h"
#include "catalog/pg_type.h"
#include "executor/execdebug.h"
@ -32,17 +31,10 @@
#include "parser/parsetree.h"
#include "parser/parse_expr.h"
#include "parser/parse_type.h"
#include "storage/lmgr.h"
#include "tcop/pquery.h"
#include "utils/lsyscache.h"
#include "utils/syscache.h"
#include "utils/tuplestore.h"
static TupleTableSlot *FunctionNext(FunctionScan *node);
static TupleTableSlot *function_getonetuple(FunctionScanState *scanstate,
bool *isNull,
ExprDoneCond *isDone);
static FunctionMode get_functionmode(Node *expr);
static bool tupledesc_mismatch(TupleDesc tupdesc1, TupleDesc tupdesc2);
/* ----------------------------------------------------------------
@ -76,53 +68,42 @@ FunctionNext(FunctionScan *node)
tuplestorestate = scanstate->tuplestorestate;
/*
* If first time through, read all tuples from function and pass them to
* tuplestore.c. Subsequent calls just fetch tuples from tuplestore.
* If first time through, read all tuples from function and put them
* in a tuplestore. Subsequent calls just fetch tuples from tuplestore.
*/
if (tuplestorestate == NULL)
{
/*
* Initialize tuplestore module.
*/
tuplestorestate = tuplestore_begin_heap(true, /* randomAccess */
SortMem);
ExprContext *econtext = scanstate->csstate.cstate.cs_ExprContext;
TupleDesc funcTupdesc;
scanstate->tuplestorestate = (void *) tuplestorestate;
scanstate->tuplestorestate = tuplestorestate =
ExecMakeTableFunctionResult((Expr *) scanstate->funcexpr,
econtext,
&funcTupdesc);
/*
* Compute all the function tuples and pass to tuplestore.
* If function provided a tupdesc, cross-check it. We only really
* need to do this for functions returning RECORD, but might as well
* do it always.
*/
for (;;)
{
bool isNull;
ExprDoneCond isDone;
isNull = false;
isDone = ExprSingleResult;
slot = function_getonetuple(scanstate, &isNull, &isDone);
if (TupIsNull(slot))
break;
tuplestore_puttuple(tuplestorestate, (void *) slot->val);
ExecClearTuple(slot);
if (isDone != ExprMultipleResult)
break;
}
/*
* Complete the store.
*/
tuplestore_donestoring(tuplestorestate);
if (funcTupdesc &&
tupledesc_mismatch(scanstate->tupdesc, funcTupdesc))
elog(ERROR, "Query-specified return tuple and actual function return tuple do not match");
}
/*
* Get the next tuple from tuplestore. Return NULL if no more tuples.
*/
slot = scanstate->csstate.css_ScanTupleSlot;
heapTuple = tuplestore_getheaptuple(tuplestorestate,
ScanDirectionIsForward(direction),
&should_free);
if (tuplestorestate)
heapTuple = tuplestore_getheaptuple(tuplestorestate,
ScanDirectionIsForward(direction),
&should_free);
else
{
heapTuple = NULL;
should_free = false;
}
return ExecStoreTuple(heapTuple, slot, InvalidBuffer, should_free);
}
@ -219,7 +200,6 @@ ExecInitFunctionScan(FunctionScan *node, EState *estate, Plan *parent)
rel = relation_open(funcrelid, AccessShareLock);
tupdesc = CreateTupleDescCopy(RelationGetDescr(rel));
relation_close(rel, AccessShareLock);
scanstate->returnsTuple = true;
}
else if (functyptype == 'b' || functyptype == 'd')
{
@ -236,7 +216,6 @@ ExecInitFunctionScan(FunctionScan *node, EState *estate, Plan *parent)
-1,
0,
false);
scanstate->returnsTuple = false;
}
else if (functyptype == 'p' && funcrettype == RECORDOID)
{
@ -246,13 +225,10 @@ ExecInitFunctionScan(FunctionScan *node, EState *estate, Plan *parent)
List *coldeflist = rte->coldeflist;
tupdesc = BuildDescForRelation(coldeflist);
scanstate->returnsTuple = true;
}
else
elog(ERROR, "Unknown kind of return type specified for function");
scanstate->fn_typeid = funcrettype;
scanstate->fn_typtype = functyptype;
scanstate->tupdesc = tupdesc;
ExecSetSlotDescriptor(scanstate->csstate.css_ScanTupleSlot,
tupdesc, false);
@ -263,8 +239,6 @@ ExecInitFunctionScan(FunctionScan *node, EState *estate, Plan *parent)
scanstate->tuplestorestate = NULL;
scanstate->funcexpr = rte->funcexpr;
scanstate->functionmode = get_functionmode(rte->funcexpr);
scanstate->csstate.cstate.cs_TupFromTlist = false;
/*
@ -322,7 +296,7 @@ ExecEndFunctionScan(FunctionScan *node)
* Release tuplestore resources
*/
if (scanstate->tuplestorestate != NULL)
tuplestore_end((Tuplestorestate *) scanstate->tuplestorestate);
tuplestore_end(scanstate->tuplestorestate);
scanstate->tuplestorestate = NULL;
}
@ -345,7 +319,7 @@ ExecFunctionMarkPos(FunctionScan *node)
if (!scanstate->tuplestorestate)
return;
tuplestore_markpos((Tuplestorestate *) scanstate->tuplestorestate);
tuplestore_markpos(scanstate->tuplestorestate);
}
/* ----------------------------------------------------------------
@ -367,7 +341,7 @@ ExecFunctionRestrPos(FunctionScan *node)
if (!scanstate->tuplestorestate)
return;
tuplestore_restorepos((Tuplestorestate *) scanstate->tuplestorestate);
tuplestore_restorepos(scanstate->tuplestorestate);
}
/* ----------------------------------------------------------------
@ -402,98 +376,13 @@ ExecFunctionReScan(FunctionScan *node, ExprContext *exprCtxt, Plan *parent)
*/
if (node->scan.plan.chgParam != NULL)
{
tuplestore_end((Tuplestorestate *) scanstate->tuplestorestate);
tuplestore_end(scanstate->tuplestorestate);
scanstate->tuplestorestate = NULL;
}
else
tuplestore_rescan((Tuplestorestate *) scanstate->tuplestorestate);
tuplestore_rescan(scanstate->tuplestorestate);
}
/*
* Run the underlying function to get the next tuple
*/
static TupleTableSlot *
function_getonetuple(FunctionScanState *scanstate,
bool *isNull,
ExprDoneCond *isDone)
{
HeapTuple tuple;
Datum retDatum;
char nullflag;
TupleDesc tupdesc = scanstate->tupdesc;
bool returnsTuple = scanstate->returnsTuple;
Node *expr = scanstate->funcexpr;
Oid fn_typeid = scanstate->fn_typeid;
char fn_typtype = scanstate->fn_typtype;
ExprContext *econtext = scanstate->csstate.cstate.cs_ExprContext;
TupleTableSlot *slot = scanstate->csstate.css_ScanTupleSlot;
/*
* reset per-tuple memory context before each call of the function.
* This cleans up any local memory the function may leak when called.
*/
ResetExprContext(econtext);
/*
* get the next Datum from the function
*/
retDatum = ExecEvalExprSwitchContext(expr, econtext, isNull, isDone);
/*
* check to see if we're really done
*/
if (*isDone == ExprEndResult)
slot = NULL;
else
{
if (returnsTuple)
{
/*
* Composite data type, i.e. a table's row type
* function returns pointer to tts??
*/
slot = (TupleTableSlot *) retDatum;
/*
* if function return type was RECORD, we need to check to be
* sure the structure from the query matches the actual return
* structure
*/
if (fn_typtype == 'p' && fn_typeid == RECORDOID)
if (tupledesc_mismatch(tupdesc, slot->ttc_tupleDescriptor))
elog(ERROR, "Query-specified return tuple and actual function return tuple do not match");
}
else
{
/*
* Must be a base data type, i.e. scalar
* turn it into a tuple
*/
nullflag = *isNull ? 'n' : ' ';
tuple = heap_formtuple(tupdesc, &retDatum, &nullflag);
/*
* save the tuple in the scan tuple slot and return the slot.
*/
slot = ExecStoreTuple(tuple, /* tuple to store */
slot, /* slot to store in */
InvalidBuffer, /* buffer associated with
* this tuple */
true); /* pfree this tuple */
}
}
return slot;
}
static FunctionMode
get_functionmode(Node *expr)
{
/*
* for the moment, hardwire this
*/
return PM_REPEATEDCALL;
}
static bool
tupledesc_mismatch(TupleDesc tupdesc1, TupleDesc tupdesc2)

View File

@ -371,36 +371,61 @@ tuple toaster will decide whether toasting is needed.
Functions accepting or returning sets
-------------------------------------
As of 7.1, Postgres has limited support for functions returning sets;
this is presently handled only in SELECT output expressions, and the
behavior is to generate a separate output tuple for each set element.
There is no direct support for functions accepting sets; instead, the
function will be called multiple times, once for each element of the
input set. This behavior will very likely be changed in future releases,
but here is how it works now:
[ this section revised 29-Aug-2002 for 7.3 ]
If a function is marked in pg_proc as returning a set, then it is called
with fcinfo->resultinfo pointing to a node of type ReturnSetInfo. A
function that desires to return a set should raise an error "called in
context that does not accept a set result" if resultinfo is NULL or does
not point to a ReturnSetInfo node. ReturnSetInfo contains a field
not point to a ReturnSetInfo node.
There are currently two modes in which a function can return a set result:
value-per-call, or materialize. In value-per-call mode, the function returns
one value each time it is called, and finally reports "done" when it has no
more values to return. In materialize mode, the function's output set is
instantiated in a Tuplestore object; all the values are returned in one call.
Additional modes might be added in future.
ReturnSetInfo contains a field "allowedModes" which is set (by the caller)
to a bitmask that's the OR of the modes the caller can support. The actual
mode used by the function is returned in another field "returnMode". For
backwards-compatibility reasons, returnMode is initialized to value-per-call
and need only be changed if the function wants to use a different mode.
The function should elog() if it cannot use any of the modes the caller is
willing to support.
Value-per-call mode works like this: ReturnSetInfo contains a field
"isDone", which should be set to one of these values:
ExprSingleResult /* expression does not return a set */
ExprMultipleResult /* this result is an element of a set */
ExprEndResult /* there are no more elements in the set */
A function returning set returns one set element per call, setting
fcinfo->resultinfo->isDone to ExprMultipleResult for each element.
After all elements have been returned, the next call should set
isDone to ExprEndResult and return a null result. (Note it is possible
to return an empty set by doing this on the first call.)
(the caller will initialize it to ExprSingleResult). If the function simply
returns a Datum without touching ReturnSetInfo, then the call is over and a
single-item set has been returned. To return a set, the function must set
isDone to ExprMultipleResult for each set element. After all elements have
been returned, the next call should set isDone to ExprEndResult and return a
null result. (Note it is possible to return an empty set by doing this on
the first call.)
As of 7.3, the ReturnSetInfo node also contains a link to the ExprContext
within which the function is being evaluated. This is useful for functions
The ReturnSetInfo node also contains a link to the ExprContext within which
the function is being evaluated. This is useful for value-per-call functions
that need to close down internal state when they are not run to completion:
they can register a shutdown callback function in the ExprContext.
Materialize mode works like this: the function creates a Tuplestore holding
the (possibly empty) result set, and returns it. There are no multiple calls.
The function must also return a TupleDesc that indicates the tuple structure.
The Tuplestore and TupleDesc should be created in the context
econtext->ecxt_per_query_memory (note this will *not* be the context the
function is called in). The function stores pointers to the Tuplestore and
TupleDesc into ReturnSetInfo, sets returnMode to indicate materialize mode,
and returns null. isDone is not used and should be left at ExprSingleResult.
There is no support for functions accepting sets; instead, the function will
be called multiple times, once for each element of the input set.
Notes about function handlers
-----------------------------

View File

@ -7,7 +7,7 @@
* Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
* $Id: executor.h,v 1.74 2002/08/29 00:17:06 tgl Exp $
* $Id: executor.h,v 1.75 2002/08/30 00:28:41 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -80,6 +80,9 @@ extern Datum ExecMakeFunctionResult(FunctionCachePtr fcache,
ExprContext *econtext,
bool *isNull,
ExprDoneCond *isDone);
extern Tuplestorestate *ExecMakeTableFunctionResult(Expr *funcexpr,
ExprContext *econtext,
TupleDesc *returnDesc);
extern Datum ExecEvalExpr(Node *expression, ExprContext *econtext,
bool *isNull, ExprDoneCond *isDone);
extern Datum ExecEvalExprSwitchContext(Node *expression, ExprContext *econtext,

View File

@ -11,7 +11,7 @@
* Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
* $Id: fmgr.h,v 1.22 2002/06/20 20:29:42 momjian Exp $
* $Id: fmgr.h,v 1.23 2002/08/30 00:28:41 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -45,8 +45,7 @@ typedef struct FmgrInfo
* count */
bool fn_strict; /* function is "strict" (NULL in => NULL
* out) */
bool fn_retset; /* function returns a set (over multiple
* calls) */
bool fn_retset; /* function returns a set */
void *fn_extra; /* extra space for use by handler */
MemoryContext fn_mcxt; /* memory context to store fn_extra in */
} FmgrInfo;

View File

@ -7,7 +7,7 @@
* Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
* $Id: execnodes.h,v 1.72 2002/08/29 00:17:06 tgl Exp $
* $Id: execnodes.h,v 1.73 2002/08/30 00:28:41 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -21,6 +21,8 @@
#include "fmgr.h"
#include "nodes/params.h"
#include "nodes/primnodes.h"
#include "utils/tuplestore.h"
/* ----------------
* IndexInfo information
@ -125,20 +127,32 @@ typedef enum
ExprEndResult /* there are no more elements in the set */
} ExprDoneCond;
/*
* 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.
*/
typedef enum
{
SFRM_ValuePerCall = 0x01, /* one value returned per call */
SFRM_Materialize = 0x02 /* result set instantiated in Tuplestore */
} SetFunctionReturnMode;
/*
* When calling a function that might return a set (multiple rows),
* a node of this type is passed as fcinfo->resultinfo to allow
* return status to be passed back. A function returning set should
* raise an error if no such resultinfo is provided. The ExprContext
* in which the function is being called is also made available.
*
* XXX this mechanism is a quick hack and probably needs to be redesigned.
* raise an error if no such resultinfo is provided.
*/
typedef struct ReturnSetInfo
{
NodeTag type;
ExprDoneCond isDone;
ExprContext *econtext;
ExprContext *econtext; /* context function is being called in */
int allowedModes; /* bitmask: return modes caller can handle */
SetFunctionReturnMode returnMode; /* actual return mode */
ExprDoneCond isDone; /* status for ValuePerCall mode */
Tuplestorestate *setResult; /* return object for Materialize mode */
TupleDesc setDesc; /* descriptor for Materialize mode */
} ReturnSetInfo;
/* ----------------
@ -509,32 +523,17 @@ typedef struct SubqueryScanState
* Function nodes are used to scan the results of a
* function appearing in FROM (typically a function returning set).
*
* functionmode function operating mode
* tupdesc function's return tuple description
* tupdesc expected return tuple description
* tuplestorestate private state of tuplestore.c
* funcexpr function expression being evaluated
* returnsTuple does function return tuples?
* fn_typeid OID of function return type
* fn_typtype return type's typtype
* ----------------
*/
typedef enum FunctionMode
{
PM_REPEATEDCALL,
PM_MATERIALIZE,
PM_QUERY
} FunctionMode;
typedef struct FunctionScanState
{
CommonScanState csstate; /* its first field is NodeTag */
FunctionMode functionmode;
TupleDesc tupdesc;
void *tuplestorestate;
Tuplestorestate *tuplestorestate;
Node *funcexpr;
bool returnsTuple;
Oid fn_typeid;
char fn_typtype;
} FunctionScanState;
/* ----------------------------------------------------------------

View File

@ -2,7 +2,7 @@
#
# Makefile for the plpgsql shared object
#
# $Header: /cvsroot/pgsql/src/pl/plpgsql/src/Makefile,v 1.20 2001/11/16 16:32:33 petere Exp $
# $Header: /cvsroot/pgsql/src/pl/plpgsql/src/Makefile,v 1.21 2002/08/30 00:28:41 tgl Exp $
#
#-------------------------------------------------------------------------
@ -78,7 +78,7 @@ endif
$(srcdir)/pl_scan.c: scan.l
ifdef FLEX
$(FLEX) -i $(FLEXFLAGS) -Pplpgsql_base_yy -o'$@' $<
$(FLEX) $(FLEXFLAGS) -Pplpgsql_base_yy -o'$@' $<
else
@$(missing) flex $< $@
endif

View File

@ -4,7 +4,7 @@
* procedural language
*
* IDENTIFICATION
* $Header: /cvsroot/pgsql/src/pl/plpgsql/src/gram.y,v 1.35 2002/08/28 20:46:24 momjian Exp $
* $Header: /cvsroot/pgsql/src/pl/plpgsql/src/gram.y,v 1.36 2002/08/30 00:28:41 tgl Exp $
*
* This software is copyrighted by Jan Wieck - Hamburg.
*
@ -48,7 +48,7 @@ static PLpgSQL_type *read_datatype(int tok);
static PLpgSQL_stmt *make_select_stmt(void);
static PLpgSQL_stmt *make_fetch_stmt(void);
static PLpgSQL_expr *make_tupret_expr(PLpgSQL_row *row);
static void check_assignable(PLpgSQL_datum *datum);
static void check_assignable(PLpgSQL_datum *datum);
%}
@ -121,8 +121,8 @@ static void check_assignable(PLpgSQL_datum *datum);
%type <stmts> proc_sect, proc_stmts, stmt_else, loop_body
%type <stmt> proc_stmt, pl_block
%type <stmt> stmt_assign, stmt_if, stmt_loop, stmt_while, stmt_exit
%type <stmt> stmt_return, stmt_raise, stmt_execsql, stmt_fori
%type <stmt> stmt_fors, stmt_select, stmt_perform
%type <stmt> stmt_return, stmt_return_next, stmt_raise, stmt_execsql
%type <stmt> stmt_fori, stmt_fors, stmt_select, stmt_perform
%type <stmt> stmt_dynexecute, stmt_dynfors, stmt_getdiag
%type <stmt> stmt_open, stmt_fetch, stmt_close
@ -166,6 +166,7 @@ static void check_assignable(PLpgSQL_datum *datum);
%token K_IS
%token K_LOG
%token K_LOOP
%token K_NEXT
%token K_NOT
%token K_NOTICE
%token K_NULL
@ -177,6 +178,7 @@ static void check_assignable(PLpgSQL_datum *datum);
%token K_RENAME
%token K_RESULT_OID
%token K_RETURN
%token K_RETURN_NEXT
%token K_REVERSE
%token K_SELECT
%token K_THEN
@ -516,10 +518,8 @@ decl_aliasitem : T_WORD
plpgsql_convert_ident(yytext, &name, 1);
if (name[0] != '$')
{
plpgsql_error_lineno = yylineno;
elog(ERROR, "can only alias positional parameters");
}
yyerror("can only alias positional parameters");
plpgsql_ns_setlocal(false);
nsi = plpgsql_ns_lookup(name, NULL);
if (nsi == NULL)
@ -609,14 +609,11 @@ decl_defval : ';'
switch (tok)
{
case 0:
plpgsql_error_lineno = lno;
elog(ERROR, "unexpected end of file");
yyerror("unexpected end of file");
case K_NULL:
if (yylex() != ';')
{
plpgsql_error_lineno = lno;
elog(ERROR, "expected ; after NULL");
}
yyerror("expected ; after NULL");
free(expr);
plpgsql_dstring_free(&ds);
@ -628,10 +625,8 @@ decl_defval : ';'
while ((tok = yylex()) != ';')
{
if (tok == 0)
{
plpgsql_error_lineno = lno;
elog(ERROR, "unterminated default value");
}
yyerror("unterminated default value");
if (plpgsql_SpaceScanned)
plpgsql_dstring_append(&ds, " ");
plpgsql_dstring_append(&ds, yytext);
@ -663,7 +658,8 @@ proc_sect :
proc_stmts : proc_stmts proc_stmt
{
if ($1->stmts_used == $1->stmts_alloc) {
if ($1->stmts_used == $1->stmts_alloc)
{
$1->stmts_alloc *= 2;
$1->stmts = realloc($1->stmts, sizeof(PLpgSQL_stmt *) * $1->stmts_alloc);
}
@ -708,6 +704,8 @@ proc_stmt : pl_block ';'
{ $$ = $1; }
| stmt_return
{ $$ = $1; }
| stmt_return_next
{ $$ = $1; }
| stmt_raise
{ $$ = $1; }
| stmt_execsql
@ -1121,45 +1119,73 @@ stmt_exit : K_EXIT lno opt_exitlabel opt_exitcond
stmt_return : K_RETURN lno
{
PLpgSQL_stmt_return *new;
PLpgSQL_expr *expr = NULL;
int tok;
new = malloc(sizeof(PLpgSQL_stmt_return));
memset(new, 0, sizeof(PLpgSQL_stmt_return));
if (plpgsql_curr_compile->fn_retistuple)
if (plpgsql_curr_compile->fn_retistuple &&
!plpgsql_curr_compile->fn_retset)
{
new->retistuple = true;
new->retrecno = -1;
switch (tok = yylex())
switch (yylex())
{
case K_NULL:
expr = NULL;
new->expr = NULL;
break;
case T_ROW:
expr = make_tupret_expr(yylval.row);
new->expr = make_tupret_expr(yylval.row);
break;
case T_RECORD:
new->retrecno = yylval.rec->recno;
expr = NULL;
new->expr = NULL;
break;
default:
yyerror("return type mismatch in function returning table row");
yyerror("return type mismatch in function returning tuple");
break;
}
if (yylex() != ';')
yyerror("expected ';'");
} else {
new->retistuple = false;
expr = plpgsql_read_expression(';', ";");
}
else
new->expr = plpgsql_read_expression(';', ";");
new->cmd_type = PLPGSQL_STMT_RETURN;
new->lineno = $2;
new->expr = expr;
$$ = (PLpgSQL_stmt *)new;
}
;
/* FIXME: this syntax needs work, RETURN NEXT breaks stmt_return */
stmt_return_next: K_RETURN_NEXT lno
{
PLpgSQL_stmt_return_next *new;
new = malloc(sizeof(PLpgSQL_stmt_return_next));
memset(new, 0, sizeof(PLpgSQL_stmt_return_next));
new->cmd_type = PLPGSQL_STMT_RETURN_NEXT;
new->lineno = $2;
if (plpgsql_curr_compile->fn_retistuple)
{
int tok = yylex();
if (tok == T_RECORD)
new->rec = yylval.rec;
else if (tok == T_ROW)
new->row = yylval.row;
else
yyerror("Incorrect argument to RETURN NEXT");
if (yylex() != ';')
yyerror("Expected ';'");
}
else
new->expr = plpgsql_read_expression(';', ";");
$$ = (PLpgSQL_stmt *)new;
}
@ -1226,7 +1252,7 @@ raise_level : K_EXCEPTION
}
| K_DEBUG
{
$$ = DEBUG5;
$$ = DEBUG1;
}
;
@ -1377,10 +1403,7 @@ stmt_open : K_OPEN lno cursor_varptr
cp += strlen(cp) - 1;
if (*cp != ')')
{
plpgsql_error_lineno = yylineno;
elog(ERROR, "missing )");
}
yyerror("missing )");
*cp = '\0';
}
else
@ -1433,10 +1456,8 @@ stmt_close : K_CLOSE lno cursor_variable ';'
cursor_varptr : T_VARIABLE
{
if (yylval.variable->dtype != PLPGSQL_DTYPE_VAR)
{
plpgsql_error_lineno = yylineno;
elog(ERROR, "cursor variable must be a simple variable");
}
yyerror("cursor variable must be a simple variable");
if (((PLpgSQL_var *) yylval.variable)->datatype->typoid != REFCURSOROID)
{
plpgsql_error_lineno = yylineno;
@ -1450,10 +1471,8 @@ cursor_varptr : T_VARIABLE
cursor_variable : T_VARIABLE
{
if (yylval.variable->dtype != PLPGSQL_DTYPE_VAR)
{
plpgsql_error_lineno = yylineno;
elog(ERROR, "cursor variable must be a simple variable");
}
yyerror("cursor variable must be a simple variable");
if (((PLpgSQL_var *) yylval.variable)->datatype->typoid != REFCURSOROID)
{
plpgsql_error_lineno = yylineno;
@ -1906,18 +1925,14 @@ make_fetch_stmt(void)
break;
default:
plpgsql_error_lineno = yylineno;
elog(ERROR, "syntax error at '%s'", yytext);
yyerror("syntax error");
}
if (!have_nexttok)
tok = yylex();
if (tok != ';')
{
plpgsql_error_lineno = yylineno;
elog(ERROR, "syntax error at '%s'", yytext);
}
yyerror("syntax error");
fetch = malloc(sizeof(PLpgSQL_stmt_select));
memset(fetch, 0, sizeof(PLpgSQL_stmt_fetch));
@ -1976,11 +1991,10 @@ check_assignable(PLpgSQL_datum *datum)
/* always assignable? */
break;
case PLPGSQL_DTYPE_TRIGARG:
plpgsql_error_lineno = yylineno;
elog(ERROR, "cannot assign to tg_argv");
yyerror("cannot assign to tg_argv");
break;
default:
elog(ERROR, "check_assignable: unexpected datum type");
yyerror("check_assignable: unexpected datum type");
break;
}
}

View File

@ -3,7 +3,7 @@
* procedural language
*
* IDENTIFICATION
* $Header: /cvsroot/pgsql/src/pl/plpgsql/src/pl_comp.c,v 1.48 2002/08/28 20:46:24 momjian Exp $
* $Header: /cvsroot/pgsql/src/pl/plpgsql/src/pl_comp.c,v 1.49 2002/08/30 00:28:41 tgl Exp $
*
* This software is copyrighted by Jan Wieck - Hamburg.
*
@ -37,8 +37,6 @@
#include "plpgsql.h"
#include <unistd.h>
#include <fcntl.h>
#include <ctype.h>
#include <setjmp.h>
@ -52,9 +50,6 @@
#include "catalog/pg_class.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_type.h"
#include "commands/trigger.h"
#include "executor/spi.h"
#include "fmgr.h"
#include "nodes/makefuncs.h"
#include "parser/gramparse.h"
#include "parser/parse_type.h"
@ -217,6 +212,7 @@ plpgsql_compile(Oid fn_oid, int functype)
typeStruct = (Form_pg_type) GETSTRUCT(typeTup);
/* Disallow pseudotype result, except VOID */
/* XXX someday allow RECORD? */
if (typeStruct->typtype == 'p')
{
if (procStruct->prorettype == VOIDOID)

View File

@ -3,7 +3,7 @@
* procedural language
*
* IDENTIFICATION
* $Header: /cvsroot/pgsql/src/pl/plpgsql/src/pl_exec.c,v 1.59 2002/08/29 04:12:03 tgl Exp $
* $Header: /cvsroot/pgsql/src/pl/plpgsql/src/pl_exec.c,v 1.60 2002/08/30 00:28:41 tgl Exp $
*
* This software is copyrighted by Jan Wieck - Hamburg.
*
@ -35,12 +35,6 @@
*
**********************************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <ctype.h>
#include <setjmp.h>
@ -50,10 +44,8 @@
#include "access/heapam.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_type.h"
#include "commands/trigger.h"
#include "executor/spi.h"
#include "executor/spi_priv.h"
#include "fmgr.h"
#include "funcapi.h"
#include "optimizer/clauses.h"
#include "parser/parse_expr.h"
#include "tcop/tcopprot.h"
@ -105,6 +97,8 @@ static int exec_stmt_exit(PLpgSQL_execstate * estate,
PLpgSQL_stmt_exit * stmt);
static int exec_stmt_return(PLpgSQL_execstate * estate,
PLpgSQL_stmt_return * stmt);
static int exec_stmt_return_next(PLpgSQL_execstate * estate,
PLpgSQL_stmt_return_next * stmt);
static int exec_stmt_raise(PLpgSQL_execstate * estate,
PLpgSQL_stmt_raise * stmt);
static int exec_stmt_execsql(PLpgSQL_execstate * estate,
@ -114,8 +108,9 @@ static int exec_stmt_dynexecute(PLpgSQL_execstate * estate,
static int exec_stmt_dynfors(PLpgSQL_execstate * estate,
PLpgSQL_stmt_dynfors * stmt);
static void plpgsql_estate_setup(PLpgSQL_execstate * estate,
PLpgSQL_function * func);
static void plpgsql_estate_setup(PLpgSQL_execstate *estate,
PLpgSQL_function *func,
ReturnSetInfo *rsi);
static void exec_eval_cleanup(PLpgSQL_execstate * estate);
static void exec_prepare_plan(PLpgSQL_execstate * estate,
@ -150,6 +145,8 @@ static Datum exec_cast_value(Datum value, Oid valtype,
int32 reqtypmod,
bool *isnull);
static void exec_set_found(PLpgSQL_execstate * estate, bool state);
static void exec_init_tuple_store(PLpgSQL_execstate *estate);
static void exec_set_ret_tupdesc(PLpgSQL_execstate *estate, List *labels);
/* ----------
@ -211,7 +208,7 @@ plpgsql_exec_function(PLpgSQL_function * func, FunctionCallInfo fcinfo)
/*
* Setup the execution state
*/
plpgsql_estate_setup(&estate, func);
plpgsql_estate_setup(&estate, func, (ReturnSetInfo *) fcinfo->resultinfo);
/*
* Make local execution copies of all the datums
@ -332,11 +329,36 @@ plpgsql_exec_function(PLpgSQL_function * func, FunctionCallInfo fcinfo)
* We got a return value - process it
*/
error_info_stmt = NULL;
error_info_text = "while casting return value to functions return type";
error_info_text = "while casting return value to function's return type";
fcinfo->isnull = estate.retisnull;
if (!estate.retisnull)
if (estate.retisset)
{
ReturnSetInfo *rsi = estate.rsi;
/* Check caller can handle a set result */
if (!rsi || !IsA(rsi, ReturnSetInfo) ||
(rsi->allowedModes & SFRM_Materialize) == 0)
elog(ERROR, "Set-valued function called in context that cannot accept a set");
rsi->returnMode = SFRM_Materialize;
/* If we produced any tuples, send back the result */
if (estate.tuple_store)
{
MemoryContext oldcxt;
oldcxt = MemoryContextSwitchTo(estate.tuple_store_cxt);
tuplestore_donestoring(estate.tuple_store);
rsi->setResult = estate.tuple_store;
if (estate.rettupdesc)
rsi->setDesc = CreateTupleDescCopy(estate.rettupdesc);
MemoryContextSwitchTo(oldcxt);
}
estate.retval = (Datum) 0;
fcinfo->isnull = true;
}
else if (!estate.retisnull)
{
if (estate.retistuple)
{
@ -455,7 +477,7 @@ plpgsql_exec_trigger(PLpgSQL_function * func,
/*
* Setup the execution state
*/
plpgsql_estate_setup(&estate, func);
plpgsql_estate_setup(&estate, func, NULL);
/*
* Make local execution copies of all the datums
@ -642,6 +664,9 @@ plpgsql_exec_trigger(PLpgSQL_function * func,
elog(ERROR, "control reaches end of trigger procedure without RETURN");
}
if (estate.retisset)
elog(ERROR, "trigger procedure cannot return a set");
/*
* Check that the returned tuple structure has the same attributes,
* the relation that fired the trigger has.
@ -862,6 +887,8 @@ exec_stmt(PLpgSQL_execstate * estate, PLpgSQL_stmt * stmt)
save_estmt = error_info_stmt;
error_info_stmt = stmt;
CHECK_FOR_INTERRUPTS();
switch (stmt->cmd_type)
{
case PLPGSQL_STMT_BLOCK:
@ -908,6 +935,10 @@ exec_stmt(PLpgSQL_execstate * estate, PLpgSQL_stmt * stmt)
rc = exec_stmt_return(estate, (PLpgSQL_stmt_return *) stmt);
break;
case PLPGSQL_STMT_RETURN_NEXT:
rc = exec_stmt_return_next(estate, (PLpgSQL_stmt_return_next *) stmt);
break;
case PLPGSQL_STMT_RAISE:
rc = exec_stmt_raise(estate, (PLpgSQL_stmt_raise *) stmt);
break;
@ -1302,13 +1333,10 @@ exec_stmt_fors(PLpgSQL_execstate * estate, PLpgSQL_stmt_fors * stmt)
*/
if (stmt->rec != NULL)
rec = (PLpgSQL_rec *) (estate->datums[stmt->rec->recno]);
else if (stmt->row != NULL)
row = (PLpgSQL_row *) (estate->datums[stmt->row->rowno]);
else
{
if (stmt->row != NULL)
row = (PLpgSQL_row *) (estate->datums[stmt->row->rowno]);
else
elog(ERROR, "unsupported target in exec_stmt_fors()");
}
elog(ERROR, "unsupported target in exec_stmt_fors()");
/*
* Open the implicit cursor for the statement and fetch the initial 10
@ -1499,6 +1527,14 @@ exec_stmt_exit(PLpgSQL_execstate * estate, PLpgSQL_stmt_exit * stmt)
static int
exec_stmt_return(PLpgSQL_execstate * estate, PLpgSQL_stmt_return * stmt)
{
/*
* If processing a set-returning PL/PgSQL function, the final RETURN
* indicates that the function is finished producing tuples. The rest
* of the work will be done at the top level.
*/
if (estate->retisset)
return PLPGSQL_RC_RETURN;
if (estate->retistuple)
{
/* initialize for null result tuple */
@ -1532,13 +1568,155 @@ exec_stmt_return(PLpgSQL_execstate * estate, PLpgSQL_stmt_return * stmt)
return PLPGSQL_RC_RETURN;
}
estate->retval = exec_eval_expr(estate, stmt->expr,
&(estate->retisnull),
&(estate->rettype));
if (estate->fn_rettype == VOIDOID)
{
/* Special hack for function returning VOID */
estate->retval = (Datum) 0;
estate->retisnull = false;
estate->rettype = VOIDOID;
}
else
{
/* Normal case for scalar results */
estate->retval = exec_eval_expr(estate, stmt->expr,
&(estate->retisnull),
&(estate->rettype));
}
return PLPGSQL_RC_RETURN;
}
/*
* Notes:
* - the tuple store must be created in a sufficiently long-lived
* memory context, as the same store must be used within the executor
* after the PL/PgSQL call returns. At present, the code uses
* TopTransactionContext.
*/
static int
exec_stmt_return_next(PLpgSQL_execstate *estate,
PLpgSQL_stmt_return_next *stmt)
{
HeapTuple tuple;
bool free_tuple = false;
if (!estate->retisset)
elog(ERROR, "Cannot use RETURN NEXT in a non-SETOF function");
if (estate->tuple_store == NULL)
exec_init_tuple_store(estate);
if (stmt->rec)
{
PLpgSQL_rec *rec = (PLpgSQL_rec *) (estate->datums[stmt->rec->recno]);
tuple = rec->tup;
estate->rettupdesc = rec->tupdesc;
}
else if (stmt->row)
{
PLpgSQL_var *var;
TupleDesc tupdesc;
Datum *dvalues;
char *nulls;
int natts;
int i;
if (!estate->rettupdesc)
exec_set_ret_tupdesc(estate, NIL);
tupdesc = estate->rettupdesc;
natts = tupdesc->natts;
dvalues = (Datum *) palloc(natts * sizeof(Datum));
nulls = (char *) palloc(natts * sizeof(char));
MemSet(dvalues, 0, natts * sizeof(Datum));
MemSet(nulls, 'n', natts);
for (i = 0; i < stmt->row->nfields; i++)
{
var = (PLpgSQL_var *) (estate->datums[stmt->row->varnos[i]]);
dvalues[i] = var->value;
if (!var->isnull)
nulls[i] = ' ';
}
tuple = heap_formtuple(tupdesc, dvalues, nulls);
pfree(dvalues);
pfree(nulls);
free_tuple = true;
}
else if (stmt->expr)
{
Datum retval;
bool isNull;
char nullflag;
if (!estate->rettupdesc)
exec_set_ret_tupdesc(estate, makeList1(makeString("unused")));
retval = exec_eval_expr(estate,
stmt->expr,
&isNull,
&(estate->rettype));
nullflag = isNull ? 'n' : ' ';
tuple = heap_formtuple(estate->rettupdesc, &retval, &nullflag);
free_tuple = true;
exec_eval_cleanup(estate);
}
else
{
elog(ERROR, "Blank RETURN NEXT not allowed");
tuple = NULL; /* keep compiler quiet */
}
if (HeapTupleIsValid(tuple))
{
MemoryContext oldcxt;
oldcxt = MemoryContextSwitchTo(estate->tuple_store_cxt);
tuplestore_puttuple(estate->tuple_store, tuple);
MemoryContextSwitchTo(oldcxt);
if (free_tuple)
heap_freetuple(tuple);
}
return PLPGSQL_RC_OK;
}
static void
exec_set_ret_tupdesc(PLpgSQL_execstate *estate, List *labels)
{
estate->rettype = estate->fn_rettype;
estate->rettupdesc = TypeGetTupleDesc(estate->rettype, labels);
if (!estate->rettupdesc)
elog(ERROR, "Could not produce descriptor for rowtype");
}
static void
exec_init_tuple_store(PLpgSQL_execstate *estate)
{
ReturnSetInfo *rsi = estate->rsi;
MemoryContext oldcxt;
/* Check caller can handle a set result */
if (!rsi || !IsA(rsi, ReturnSetInfo) ||
(rsi->allowedModes & SFRM_Materialize) == 0)
elog(ERROR, "Set-valued function called in context that cannot accept a set");
estate->tuple_store_cxt = rsi->econtext->ecxt_per_query_memory;
oldcxt = MemoryContextSwitchTo(estate->tuple_store_cxt);
estate->tuple_store = tuplestore_begin_heap(true, SortMem);
MemoryContextSwitchTo(oldcxt);
}
/* ----------
* exec_stmt_raise Build a message and throw it with
@ -1700,21 +1878,29 @@ exec_stmt_raise(PLpgSQL_execstate * estate, PLpgSQL_stmt_raise * stmt)
/* ----------
* Initialize an empty estate
* Initialize a mostly empty execution state
* ----------
*/
static void
plpgsql_estate_setup(PLpgSQL_execstate * estate,
PLpgSQL_function * func)
plpgsql_estate_setup(PLpgSQL_execstate *estate,
PLpgSQL_function *func,
ReturnSetInfo *rsi)
{
estate->retval = (Datum) 0;
estate->retisnull = true;
estate->rettype = InvalidOid;
estate->fn_rettype = func->fn_rettype;
estate->retistuple = func->fn_retistuple;
estate->rettupdesc = NULL;
estate->retisset = func->fn_retset;
estate->rettupdesc = NULL;
estate->exitlabel = NULL;
estate->tuple_store = NULL;
estate->tuple_store_cxt = NULL;
estate->rsi = rsi;
estate->trig_nargs = 0;
estate->trig_argv = NULL;
@ -2099,13 +2285,10 @@ exec_stmt_dynfors(PLpgSQL_execstate * estate, PLpgSQL_stmt_dynfors * stmt)
*/
if (stmt->rec != NULL)
rec = (PLpgSQL_rec *) (estate->datums[stmt->rec->recno]);
else if (stmt->row != NULL)
row = (PLpgSQL_row *) (estate->datums[stmt->row->rowno]);
else
{
if (stmt->row != NULL)
row = (PLpgSQL_row *) (estate->datums[stmt->row->rowno]);
else
elog(ERROR, "unsupported target in exec_stmt_fors()");
}
elog(ERROR, "unsupported target in exec_stmt_dynfors()");
/*
* Evaluate the string expression after the EXECUTE keyword. It's

View File

@ -3,7 +3,7 @@
* procedural language
*
* IDENTIFICATION
* $Header: /cvsroot/pgsql/src/pl/plpgsql/src/pl_funcs.c,v 1.20 2002/08/29 07:22:30 ishii Exp $
* $Header: /cvsroot/pgsql/src/pl/plpgsql/src/pl_funcs.c,v 1.21 2002/08/30 00:28:41 tgl Exp $
*
* This software is copyrighted by Jan Wieck - Hamburg.
*
@ -35,12 +35,6 @@
*
**********************************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <ctype.h>
#include "plpgsql.h"
@ -272,9 +266,7 @@ plpgsql_ns_lookup(char *name, char *label)
return ns->items[i];
}
if (ns_localmode)
{
return NULL; /* name not found in current namespace */
}
}
return NULL;
@ -461,6 +453,8 @@ plpgsql_stmt_typename(PLpgSQL_stmt * stmt)
return "exit";
case PLPGSQL_STMT_RETURN:
return "return";
case PLPGSQL_STMT_RETURN_NEXT:
return "return next";
case PLPGSQL_STMT_RAISE:
return "raise";
case PLPGSQL_STMT_EXECSQL:
@ -500,6 +494,7 @@ static void dump_fors(PLpgSQL_stmt_fors * stmt);
static void dump_select(PLpgSQL_stmt_select * stmt);
static void dump_exit(PLpgSQL_stmt_exit * stmt);
static void dump_return(PLpgSQL_stmt_return * stmt);
static void dump_return_next(PLpgSQL_stmt_return_next * stmt);
static void dump_raise(PLpgSQL_stmt_raise * stmt);
static void dump_execsql(PLpgSQL_stmt_execsql * stmt);
static void dump_dynexecute(PLpgSQL_stmt_dynexecute * stmt);
@ -556,6 +551,9 @@ dump_stmt(PLpgSQL_stmt * stmt)
case PLPGSQL_STMT_RETURN:
dump_return((PLpgSQL_stmt_return *) stmt);
break;
case PLPGSQL_STMT_RETURN_NEXT:
dump_return_next((PLpgSQL_stmt_return_next *) stmt);
break;
case PLPGSQL_STMT_RAISE:
dump_raise((PLpgSQL_stmt_raise *) stmt);
break;
@ -839,6 +837,20 @@ dump_return(PLpgSQL_stmt_return * stmt)
printf("\n");
}
static void
dump_return_next(PLpgSQL_stmt_return_next * stmt)
{
dump_ind();
printf("RETURN NEXT ");
if (stmt->rec != NULL)
printf("target = %d %s\n", stmt->rec->recno, stmt->rec->refname);
else if (stmt->row != NULL)
printf("target = %d %s\n", stmt->row->rowno, stmt->row->refname);
else if (stmt->expr != NULL)
dump_expr(stmt->expr);
printf("\n");
}
static void
dump_raise(PLpgSQL_stmt_raise * stmt)
{

View File

@ -3,7 +3,7 @@
* procedural language
*
* IDENTIFICATION
* $Header: /cvsroot/pgsql/src/pl/plpgsql/src/pl_handler.c,v 1.11 2002/06/15 19:54:24 momjian Exp $
* $Header: /cvsroot/pgsql/src/pl/plpgsql/src/pl_handler.c,v 1.12 2002/08/30 00:28:41 tgl Exp $
*
* This software is copyrighted by Jan Wieck - Hamburg.
*
@ -35,13 +35,6 @@
*
**********************************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include "plpgsql.h"
#include "pl.tab.h"

View File

@ -3,7 +3,7 @@
* procedural language
*
* IDENTIFICATION
* $Header: /cvsroot/pgsql/src/pl/plpgsql/src/plpgsql.h,v 1.25 2002/08/08 01:36:05 tgl Exp $
* $Header: /cvsroot/pgsql/src/pl/plpgsql/src/plpgsql.h,v 1.26 2002/08/30 00:28:41 tgl Exp $
*
* This software is copyrighted by Jan Wieck - Hamburg.
*
@ -40,8 +40,10 @@
#include "postgres.h"
#include "fmgr.h"
#include "miscadmin.h"
#include "executor/spi.h"
#include "commands/trigger.h"
#include "utils/tuplestore.h"
/**********************************************************************
* Definitions
@ -90,6 +92,7 @@ enum
PLPGSQL_STMT_SELECT,
PLPGSQL_STMT_EXIT,
PLPGSQL_STMT_RETURN,
PLPGSQL_STMT_RETURN_NEXT,
PLPGSQL_STMT_RAISE,
PLPGSQL_STMT_EXECSQL,
PLPGSQL_STMT_DYNEXECUTE,
@ -420,11 +423,18 @@ typedef struct
{ /* RETURN statement */
int cmd_type;
int lineno;
bool retistuple;
PLpgSQL_expr *expr;
int retrecno;
} PLpgSQL_stmt_return;
typedef struct
{ /* RETURN NEXT statement */
int cmd_type;
int lineno;
PLpgSQL_rec *rec;
PLpgSQL_row *row;
PLpgSQL_expr *expr;
} PLpgSQL_stmt_return_next;
typedef struct
{ /* RAISE statement */
@ -494,12 +504,19 @@ typedef struct
{ /* Runtime execution data */
Datum retval;
bool retisnull;
Oid rettype;
Oid rettype; /* type of current retval */
Oid fn_rettype; /* info about declared function rettype */
bool retistuple;
TupleDesc rettupdesc;
bool retisset;
TupleDesc rettupdesc;
char *exitlabel;
Tuplestorestate *tuple_store; /* SRFs accumulate results here */
MemoryContext tuple_store_cxt;
ReturnSetInfo *rsi;
int trig_nargs;
Datum *trig_argv;

View File

@ -4,7 +4,7 @@
* procedural language
*
* IDENTIFICATION
* $Header: /cvsroot/pgsql/src/pl/plpgsql/src/Attic/scan.l,v 1.21 2002/08/08 01:36:05 tgl Exp $
* $Header: /cvsroot/pgsql/src/pl/plpgsql/src/Attic/scan.l,v 1.22 2002/08/30 00:28:41 tgl Exp $
*
* This software is copyrighted by Jan Wieck - Hamburg.
*
@ -46,6 +46,8 @@ static int scanner_functype;
static int scanner_typereported;
static int pushback_token;
static bool have_pushback_token;
static int lookahead_token;
static bool have_lookahead_token;
int plpgsql_SpaceScanned = 0;
@ -134,6 +136,7 @@ into { return K_INTO; }
is { return K_IS; }
log { return K_LOG; }
loop { return K_LOOP; }
next { return K_NEXT; }
not { return K_NOT; }
notice { return K_NOTICE; }
null { return K_NULL; }
@ -255,18 +258,50 @@ plpgsql_input(char *buf, int *result, int max)
}
/*
* This is the yylex routine called from outside. It exists to provide
* a token pushback facility.
* This is the yylex routine called from outside. It exists to provide
* a pushback facility, as well as to allow us to parse syntax that
* requires more than one token of lookahead.
*/
int
plpgsql_yylex(void)
{
int cur_token;
if (have_pushback_token)
{
have_pushback_token = false;
return pushback_token;
cur_token = pushback_token;
}
return yylex();
else if (have_lookahead_token)
{
have_lookahead_token = false;
cur_token = lookahead_token;
}
else
cur_token = yylex();
/* Do we need to look ahead for a possible multiword token? */
switch (cur_token)
{
/* RETURN NEXT must be reduced to a single token */
case K_RETURN:
if (!have_lookahead_token)
{
lookahead_token = yylex();
have_lookahead_token = true;
}
if (lookahead_token == K_NEXT)
{
have_lookahead_token = false;
cur_token = K_RETURN_NEXT;
}
break;
default:
break;
}
return cur_token;
}
/*
@ -312,4 +347,5 @@ plpgsql_setinput(char *source, int functype)
scanner_typereported = 0;
have_pushback_token = false;
have_lookahead_token = false;
}

View File

@ -1535,6 +1535,10 @@ ERROR: system "notthere" does not exist
insert into IFace values ('IF', 'orion', 'ethernet_interface_name_too_long', '');
ERROR: IFace slotname "IF.orion.ethernet_interface_name_too_long" too long (20 char max)
--
-- The following tests are unrelated to the scenario outlined above;
-- they merely exercise specific parts of PL/PgSQL
--
--
-- Test recursion, per bug report 7-Sep-01
--
CREATE FUNCTION recursion_test(int,int) RETURNS text AS '
@ -1557,7 +1561,7 @@ SELECT recursion_test(4,3);
-- Test the FOUND magic variable
--
CREATE TABLE found_test_tbl (a int);
create function test_found ()
create function test_found()
returns boolean as '
declare
begin
@ -1609,3 +1613,70 @@ select * from found_test_tbl;
6
(6 rows)
--
-- Test set-returning functions for PL/pgSQL
--
create function test_table_func_rec() returns setof found_test_tbl as '
DECLARE
rec RECORD;
BEGIN
FOR rec IN select * from found_test_tbl LOOP
RETURN NEXT rec;
END LOOP;
RETURN;
END;' language 'plpgsql';
select * from test_table_func_rec();
a
-----
2
100
3
4
5
6
(6 rows)
create function test_table_func_row() returns setof found_test_tbl as '
DECLARE
row found_test_tbl%ROWTYPE;
BEGIN
FOR row IN select * from found_test_tbl LOOP
RETURN NEXT row;
END LOOP;
RETURN;
END;' language 'plpgsql';
select * from test_table_func_row();
a
-----
2
100
3
4
5
6
(6 rows)
create function test_ret_set_scalar(int,int) returns setof int as '
DECLARE
i int;
BEGIN
FOR i IN $1 .. $2 LOOP
RETURN NEXT i + 1;
END LOOP;
RETURN;
END;' language 'plpgsql';
select * from test_ret_set_scalar(1,10);
test_ret_set_scalar
---------------------
2
3
4
5
6
7
8
9
10
11
(10 rows)

View File

@ -1419,6 +1419,12 @@ delete from HSlot;
insert into IFace values ('IF', 'notthere', 'eth0', '');
insert into IFace values ('IF', 'orion', 'ethernet_interface_name_too_long', '');
--
-- The following tests are unrelated to the scenario outlined above;
-- they merely exercise specific parts of PL/PgSQL
--
--
-- Test recursion, per bug report 7-Sep-01
--
@ -1440,7 +1446,7 @@ SELECT recursion_test(4,3);
--
CREATE TABLE found_test_tbl (a int);
create function test_found ()
create function test_found()
returns boolean as '
declare
begin
@ -1478,3 +1484,43 @@ create function test_found ()
select test_found();
select * from found_test_tbl;
--
-- Test set-returning functions for PL/pgSQL
--
create function test_table_func_rec() returns setof found_test_tbl as '
DECLARE
rec RECORD;
BEGIN
FOR rec IN select * from found_test_tbl LOOP
RETURN NEXT rec;
END LOOP;
RETURN;
END;' language 'plpgsql';
select * from test_table_func_rec();
create function test_table_func_row() returns setof found_test_tbl as '
DECLARE
row found_test_tbl%ROWTYPE;
BEGIN
FOR row IN select * from found_test_tbl LOOP
RETURN NEXT row;
END LOOP;
RETURN;
END;' language 'plpgsql';
select * from test_table_func_row();
create function test_ret_set_scalar(int,int) returns setof int as '
DECLARE
i int;
BEGIN
FOR i IN $1 .. $2 LOOP
RETURN NEXT i + 1;
END LOOP;
RETURN;
END;' language 'plpgsql';
select * from test_ret_set_scalar(1,10);