Commit e107f3a7 authored by Tom Lane's avatar Tom Lane

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.
parent 82ccb420
......@@ -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();
}
}
......
<!--
$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>
......
<!--
$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
......
<!--
$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>
......
<!--
$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;
......
This diff is collapsed.
......@@ -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);
scanstate->tuplestorestate = (void *) tuplestorestate;
/*
* Compute all the function tuples and pass to tuplestore.
*/
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);
ExprContext *econtext = scanstate->csstate.cstate.cs_ExprContext;
TupleDesc funcTupdesc;
if (isDone != ExprMultipleResult)
break;
}
scanstate->tuplestorestate = tuplestorestate =
ExecMakeTableFunctionResult((Expr *) scanstate->funcexpr,
econtext,
&funcTupdesc);
/*
* Complete the store.
* 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.
*/
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);
}
/*
* 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;
tuplestore_rescan(scanstate->tuplestorestate);
}
static FunctionMode
get_functionmode(Node *expr)
{
/*
* for the moment, hardwire this
*/
return PM_REPEATEDCALL;
}
static bool
tupledesc_mismatch(TupleDesc tupdesc1, TupleDesc tupdesc2)
......
......@@ -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
-----------------------------
......
......@@ -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,
......
......@@ -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;
......
......@@ -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;
/* ----------------------------------------------------------------
......
......@@ -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
......
......@@ -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;
}
}
......@@ -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)
......
This diff is collapsed.
......@@ -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)
{
......
......@@ -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"
......
......@@ -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;
......
......@@ -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;
}
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 yylex();
return cur_token;
}
/*
......@@ -312,4 +347,5 @@ plpgsql_setinput(char *source, int functype)
scanner_typereported = 0;
have_pushback_token = false;
have_lookahead_token = false;
}
......@@ -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)
......@@ -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);
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