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 @@ ...@@ -16,14 +16,13 @@
#include "postgres.h" #include "postgres.h"
#include <ctype.h> #include <ctype.h>
#include <stdio.h>
#include <sys/types.h> #include <sys/types.h>
#include <string.h>
#include "postgres.h"
#include "access/heapam.h" #include "access/heapam.h"
#include "catalog/catname.h" #include "catalog/catname.h"
#include "catalog/indexing.h" #include "catalog/indexing.h"
#include "catalog/pg_proc.h" #include "catalog/pg_proc.h"
#include "catalog/pg_type.h"
#include "executor/executor.h" #include "executor/executor.h"
#include "utils/fcache.h" #include "utils/fcache.h"
#include "utils/sets.h" #include "utils/sets.h"
...@@ -97,7 +96,7 @@ static PGARRAY * GetPGArray(int4 state, int fAdd) ...@@ -97,7 +96,7 @@ static PGARRAY * GetPGArray(int4 state, int fAdd)
p->a.size = cb; p->a.size = cb;
p->a.ndim = 0; p->a.ndim = 0;
p->a.flags = 0; p->a.flags = 0;
p->a.elmtype = INT4OID; p->a.elemtype = INT4OID;
p->items = 0; p->items = 0;
p->lower= START_NUM; p->lower= START_NUM;
} }
...@@ -150,7 +149,7 @@ static PGARRAY *ShrinkPGArray(PGARRAY *p) ...@@ -150,7 +149,7 @@ static PGARRAY *ShrinkPGArray(PGARRAY *p)
pnew->a.size = cb; pnew->a.size = cb;
pnew->a.ndim=1; pnew->a.ndim=1;
pnew->a.flags = 0; pnew->a.flags = 0;
pnew->a.elmtype = INT4OID; pnew->a.elemtype = INT4OID;
pnew->lower = 0; pnew->lower = 0;
} }
else else
...@@ -171,11 +170,11 @@ Datum int_agg_state(PG_FUNCTION_ARGS) ...@@ -171,11 +170,11 @@ Datum int_agg_state(PG_FUNCTION_ARGS)
PGARRAY *p = GetPGArray(state, 1); PGARRAY *p = GetPGArray(state, 1);
if(!p) if(!p)
{ {
elog(ERROR,"No aggregate storage\n"); elog(ERROR,"No aggregate storage");
} }
else if(p->items >= p->lower) else if(p->items >= p->lower)
{ {
elog(ERROR,"aggregate storage too small\n"); elog(ERROR,"aggregate storage too small");
} }
else else
{ {
...@@ -202,32 +201,24 @@ Datum int_agg_final_array(PG_FUNCTION_ARGS) ...@@ -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 */ /* This function accepts an array, and returns one item for each entry in the array */
Datum int_enum(PG_FUNCTION_ARGS) Datum int_enum(PG_FUNCTION_ARGS)
{ {
CTX *pc;
PGARRAY *p = (PGARRAY *) PG_GETARG_POINTER(0); PGARRAY *p = (PGARRAY *) PG_GETARG_POINTER(0);
CTX *pc;
ReturnSetInfo *rsi = (ReturnSetInfo *)fcinfo->resultinfo; 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) if(!p)
{ {
elog(WARNING, "No data sent\n"); elog(WARNING, "No data sent");
return 0;
}
if(!rsi)
{
elog(ERROR, "No ReturnSetInfo sent! function must be declared returning a 'setof' integer");
PG_RETURN_NULL(); PG_RETURN_NULL();
} }
if(!fcinfo->context) if(!fcinfo->context)
{ {
/* Allocate a working context */ /* Allocate a working context */
pc = (CTX *) palloc(sizeof(CTX)); 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 */ /* Don't copy atribute if you don't need too */
if(VARATT_IS_EXTENDED(p) ) if(VARATT_IS_EXTENDED(p) )
{ {
...@@ -236,7 +227,7 @@ Datum int_enum(PG_FUNCTION_ARGS) ...@@ -236,7 +227,7 @@ Datum int_enum(PG_FUNCTION_ARGS)
pc->flags = TOASTED; pc->flags = TOASTED;
if(!pc->p) if(!pc->p)
{ {
elog(ERROR, "Error in toaster!!! no detoasting\n"); elog(ERROR, "Error in toaster!!! no detoasting");
PG_RETURN_NULL(); 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"> <chapter id="plpgsql">
...@@ -1142,11 +1142,20 @@ GET DIAGNOSTICS <replaceable>variable</replaceable> = <replaceable>item</replace ...@@ -1142,11 +1142,20 @@ GET DIAGNOSTICS <replaceable>variable</replaceable> = <replaceable>item</replace
RETURN <replaceable>expression</replaceable>; RETURN <replaceable>expression</replaceable>;
</synopsis> </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 The function terminates and the value of
<replaceable>expression</replaceable> will be returned to the <replaceable>expression</replaceable> is returned to the caller.
upper executor. </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 The expression's result will be automatically cast into the
function's return type as described for assignments. 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>
<para> <para>
...@@ -1155,6 +1164,28 @@ RETURN <replaceable>expression</replaceable>; ...@@ -1155,6 +1164,28 @@ RETURN <replaceable>expression</replaceable>;
the function without hitting a RETURN statement, a run-time error the function without hitting a RETURN statement, a run-time error
will occur. will occur.
</para> </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>
<sect2 id="plpgsql-conditionals"> <sect2 id="plpgsql-conditionals">
...@@ -1531,8 +1562,8 @@ END LOOP; ...@@ -1531,8 +1562,8 @@ END LOOP;
to worry about that, since FOR loops automatically use a cursor to worry about that, since FOR loops automatically use a cursor
internally to avoid memory problems.) A more interesting usage is to internally to avoid memory problems.) A more interesting usage is to
return a reference to a cursor that it has created, allowing the 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 caller to read the rows. This provides an efficient way to return
from functions. large row sets from functions.
</para> </para>
<sect2 id="plpgsql-cursor-declarations"> <sect2 id="plpgsql-cursor-declarations">
...@@ -1794,19 +1825,27 @@ COMMIT; ...@@ -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>; RAISE <replaceable class="parameter">level</replaceable> '<replaceable class="parameter">format</replaceable>' <optional>, <replaceable class="parameter">variable</replaceable> <optional>...</optional></optional>;
</synopsis> </synopsis>
Possible levels are DEBUG (write the message into the postmaster log), Possible levels are <literal>DEBUG</literal> (write the message to
NOTICE (write the message into the postmaster log and forward it to the server log), <literal>LOG</literal> (write the message to the
the client application) and EXCEPTION (raise an error, server log with a higher priority), <literal>INFO</literal>,
aborting the transaction). <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>
<para> <para>
Inside the format string, <literal>%</literal> is replaced by the next Inside the format string, <literal>%</literal> is replaced by the
optional argument's external representation. next optional argument's external representation. Write
Write <literal>%%</literal> to emit a literal <literal>%</literal>. <literal>%%</literal> to emit a literal <literal>%</literal>. Note
Note that the optional arguments must presently that the optional arguments must presently be simple variables,
be simple variables, not expressions, and the format must be a simple not expressions, and the format must be a simple string literal.
string literal.
</para> </para>
<!-- <!--
...@@ -1820,8 +1859,9 @@ RAISE <replaceable class="parameter">level</replaceable> '<replaceable class="pa ...@@ -1820,8 +1859,9 @@ RAISE <replaceable class="parameter">level</replaceable> '<replaceable class="pa
<programlisting> <programlisting>
RAISE NOTICE ''Calling cs_create_job(%)'',v_job_id; RAISE NOTICE ''Calling cs_create_job(%)'',v_job_id;
</programlisting> </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>
<para> <para>
...@@ -1852,12 +1892,12 @@ RAISE EXCEPTION ''Inexistent ID --> %'',user_id; ...@@ -1852,12 +1892,12 @@ RAISE EXCEPTION ''Inexistent ID --> %'',user_id;
</para> </para>
<para> <para>
Thus, the only thing <application>PL/pgSQL</application> currently does when it encounters Thus, the only thing <application>PL/pgSQL</application>
an abort during execution of a function or trigger currently does when it encounters an abort during execution of a
procedure is to write some additional NOTICE level log messages function or trigger procedure is to write some additional
telling in which function and where (line number and type of <literal>NOTICE</literal> level log messages telling in which
statement) this happened. The error always stops execution of function and where (line number and type of statement) this
the function. happened. The error always stops execution of the function.
</para> </para>
</sect2> </sect2>
</sect1> </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"> <appendix id="release">
...@@ -24,6 +24,7 @@ CDATA means the content is "SGML-free", so you can write without ...@@ -24,6 +24,7 @@ CDATA means the content is "SGML-free", so you can write without
worries about funny characters. worries about funny characters.
--> -->
<literallayout><![CDATA[ <literallayout><![CDATA[
Substantial improvements in functionality for functions returning sets
Client libraries older than 6.3 no longer supported (version 0 protocol removed) Client libraries older than 6.3 no longer supported (version 0 protocol removed)
PREPARE statement allows caching query plans for interactive statements PREPARE statement allows caching query plans for interactive statements
Type OPAQUE is now deprecated in favor of pseudo-types cstring, trigger, etc 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"> <Chapter Id="runtime">
...@@ -921,7 +921,8 @@ env PGOPTIONS='-c geqo=off' psql ...@@ -921,7 +921,8 @@ env PGOPTIONS='-c geqo=off' psql
built (see the configure option built (see the configure option
<literal>--enable-cassert</literal>). Note that <literal>--enable-cassert</literal>). Note that
<literal>DEBUG_ASSERTIONS</literal> defaults to on if <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> </para>
</listitem> </listitem>
</varlistentry> </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"> <chapter id="xfunc">
...@@ -315,9 +315,7 @@ ERROR: function declared to return emp returns varchar instead of text at colum ...@@ -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 function, as described below. It can also be called in the context
of an SQL expression, but only when you of an SQL expression, but only when you
extract a single attribute out of the row or pass the entire row into extract a single attribute out of the row or pass the entire row into
another function that accepts the same composite type. (Trying to another function that accepts the same composite type. For example,
display the entire row value will yield
a meaningless number.) For example,
<programlisting> <programlisting>
SELECT (new_emp()).name; SELECT (new_emp()).name;
......
This diff is collapsed.
...@@ -8,7 +8,7 @@ ...@@ -8,7 +8,7 @@
* *
* *
* IDENTIFICATION * 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 @@ ...@@ -22,7 +22,6 @@
*/ */
#include "postgres.h" #include "postgres.h"
#include "miscadmin.h"
#include "access/heapam.h" #include "access/heapam.h"
#include "catalog/pg_type.h" #include "catalog/pg_type.h"
#include "executor/execdebug.h" #include "executor/execdebug.h"
...@@ -32,17 +31,10 @@ ...@@ -32,17 +31,10 @@
#include "parser/parsetree.h" #include "parser/parsetree.h"
#include "parser/parse_expr.h" #include "parser/parse_expr.h"
#include "parser/parse_type.h" #include "parser/parse_type.h"
#include "storage/lmgr.h"
#include "tcop/pquery.h"
#include "utils/lsyscache.h" #include "utils/lsyscache.h"
#include "utils/syscache.h"
#include "utils/tuplestore.h"
static TupleTableSlot *FunctionNext(FunctionScan *node); 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); static bool tupledesc_mismatch(TupleDesc tupdesc1, TupleDesc tupdesc2);
/* ---------------------------------------------------------------- /* ----------------------------------------------------------------
...@@ -76,53 +68,42 @@ FunctionNext(FunctionScan *node) ...@@ -76,53 +68,42 @@ FunctionNext(FunctionScan *node)
tuplestorestate = scanstate->tuplestorestate; tuplestorestate = scanstate->tuplestorestate;
/* /*
* If first time through, read all tuples from function and pass them to * If first time through, read all tuples from function and put them
* tuplestore.c. Subsequent calls just fetch tuples from tuplestore. * in a tuplestore. Subsequent calls just fetch tuples from tuplestore.
*/ */
if (tuplestorestate == NULL) if (tuplestorestate == NULL)
{ {
/* ExprContext *econtext = scanstate->csstate.cstate.cs_ExprContext;
* Initialize tuplestore module. TupleDesc funcTupdesc;
*/
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);
if (isDone != ExprMultipleResult) scanstate->tuplestorestate = tuplestorestate =
break; 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. * Get the next tuple from tuplestore. Return NULL if no more tuples.
*/ */
slot = scanstate->csstate.css_ScanTupleSlot; slot = scanstate->csstate.css_ScanTupleSlot;
heapTuple = tuplestore_getheaptuple(tuplestorestate, if (tuplestorestate)
ScanDirectionIsForward(direction), heapTuple = tuplestore_getheaptuple(tuplestorestate,
&should_free); ScanDirectionIsForward(direction),
&should_free);
else
{
heapTuple = NULL;
should_free = false;
}
return ExecStoreTuple(heapTuple, slot, InvalidBuffer, should_free); return ExecStoreTuple(heapTuple, slot, InvalidBuffer, should_free);
} }
...@@ -219,7 +200,6 @@ ExecInitFunctionScan(FunctionScan *node, EState *estate, Plan *parent) ...@@ -219,7 +200,6 @@ ExecInitFunctionScan(FunctionScan *node, EState *estate, Plan *parent)
rel = relation_open(funcrelid, AccessShareLock); rel = relation_open(funcrelid, AccessShareLock);
tupdesc = CreateTupleDescCopy(RelationGetDescr(rel)); tupdesc = CreateTupleDescCopy(RelationGetDescr(rel));
relation_close(rel, AccessShareLock); relation_close(rel, AccessShareLock);
scanstate->returnsTuple = true;
} }
else if (functyptype == 'b' || functyptype == 'd') else if (functyptype == 'b' || functyptype == 'd')
{ {
...@@ -236,7 +216,6 @@ ExecInitFunctionScan(FunctionScan *node, EState *estate, Plan *parent) ...@@ -236,7 +216,6 @@ ExecInitFunctionScan(FunctionScan *node, EState *estate, Plan *parent)
-1, -1,
0, 0,
false); false);
scanstate->returnsTuple = false;
} }
else if (functyptype == 'p' && funcrettype == RECORDOID) else if (functyptype == 'p' && funcrettype == RECORDOID)
{ {
...@@ -246,13 +225,10 @@ ExecInitFunctionScan(FunctionScan *node, EState *estate, Plan *parent) ...@@ -246,13 +225,10 @@ ExecInitFunctionScan(FunctionScan *node, EState *estate, Plan *parent)
List *coldeflist = rte->coldeflist; List *coldeflist = rte->coldeflist;
tupdesc = BuildDescForRelation(coldeflist); tupdesc = BuildDescForRelation(coldeflist);
scanstate->returnsTuple = true;
} }
else else
elog(ERROR, "Unknown kind of return type specified for function"); elog(ERROR, "Unknown kind of return type specified for function");
scanstate->fn_typeid = funcrettype;
scanstate->fn_typtype = functyptype;
scanstate->tupdesc = tupdesc; scanstate->tupdesc = tupdesc;
ExecSetSlotDescriptor(scanstate->csstate.css_ScanTupleSlot, ExecSetSlotDescriptor(scanstate->csstate.css_ScanTupleSlot,
tupdesc, false); tupdesc, false);
...@@ -263,8 +239,6 @@ ExecInitFunctionScan(FunctionScan *node, EState *estate, Plan *parent) ...@@ -263,8 +239,6 @@ ExecInitFunctionScan(FunctionScan *node, EState *estate, Plan *parent)
scanstate->tuplestorestate = NULL; scanstate->tuplestorestate = NULL;
scanstate->funcexpr = rte->funcexpr; scanstate->funcexpr = rte->funcexpr;
scanstate->functionmode = get_functionmode(rte->funcexpr);
scanstate->csstate.cstate.cs_TupFromTlist = false; scanstate->csstate.cstate.cs_TupFromTlist = false;
/* /*
...@@ -322,7 +296,7 @@ ExecEndFunctionScan(FunctionScan *node) ...@@ -322,7 +296,7 @@ ExecEndFunctionScan(FunctionScan *node)
* Release tuplestore resources * Release tuplestore resources
*/ */
if (scanstate->tuplestorestate != NULL) if (scanstate->tuplestorestate != NULL)
tuplestore_end((Tuplestorestate *) scanstate->tuplestorestate); tuplestore_end(scanstate->tuplestorestate);
scanstate->tuplestorestate = NULL; scanstate->tuplestorestate = NULL;
} }
...@@ -345,7 +319,7 @@ ExecFunctionMarkPos(FunctionScan *node) ...@@ -345,7 +319,7 @@ ExecFunctionMarkPos(FunctionScan *node)
if (!scanstate->tuplestorestate) if (!scanstate->tuplestorestate)
return; return;
tuplestore_markpos((Tuplestorestate *) scanstate->tuplestorestate); tuplestore_markpos(scanstate->tuplestorestate);
} }
/* ---------------------------------------------------------------- /* ----------------------------------------------------------------
...@@ -367,7 +341,7 @@ ExecFunctionRestrPos(FunctionScan *node) ...@@ -367,7 +341,7 @@ ExecFunctionRestrPos(FunctionScan *node)
if (!scanstate->tuplestorestate) if (!scanstate->tuplestorestate)
return; return;
tuplestore_restorepos((Tuplestorestate *) scanstate->tuplestorestate); tuplestore_restorepos(scanstate->tuplestorestate);
} }
/* ---------------------------------------------------------------- /* ----------------------------------------------------------------
...@@ -402,98 +376,13 @@ ExecFunctionReScan(FunctionScan *node, ExprContext *exprCtxt, Plan *parent) ...@@ -402,98 +376,13 @@ ExecFunctionReScan(FunctionScan *node, ExprContext *exprCtxt, Plan *parent)
*/ */
if (node->scan.plan.chgParam != NULL) if (node->scan.plan.chgParam != NULL)
{ {
tuplestore_end((Tuplestorestate *) scanstate->tuplestorestate); tuplestore_end(scanstate->tuplestorestate);
scanstate->tuplestorestate = NULL; scanstate->tuplestorestate = NULL;
} }
else 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 static bool
tupledesc_mismatch(TupleDesc tupdesc1, TupleDesc tupdesc2) tupledesc_mismatch(TupleDesc tupdesc1, TupleDesc tupdesc2)
......
...@@ -371,36 +371,61 @@ tuple toaster will decide whether toasting is needed. ...@@ -371,36 +371,61 @@ tuple toaster will decide whether toasting is needed.
Functions accepting or returning sets Functions accepting or returning sets
------------------------------------- -------------------------------------
As of 7.1, Postgres has limited support for functions returning sets; [ this section revised 29-Aug-2002 for 7.3 ]
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:
If a function is marked in pg_proc as returning a set, then it is called 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 with fcinfo->resultinfo pointing to a node of type ReturnSetInfo. A
function that desires to return a set should raise an error "called in 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 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: "isDone", which should be set to one of these values:
ExprSingleResult /* expression does not return a set */ ExprSingleResult /* expression does not return a set */
ExprMultipleResult /* this result is an element of a set */ ExprMultipleResult /* this result is an element of a set */
ExprEndResult /* there are no more elements in the set */ ExprEndResult /* there are no more elements in the set */
A function returning set returns one set element per call, setting (the caller will initialize it to ExprSingleResult). If the function simply
fcinfo->resultinfo->isDone to ExprMultipleResult for each element. returns a Datum without touching ReturnSetInfo, then the call is over and a
After all elements have been returned, the next call should set single-item set has been returned. To return a set, the function must set
isDone to ExprEndResult and return a null result. (Note it is possible isDone to ExprMultipleResult for each set element. After all elements have
to return an empty set by doing this on the first call.) 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 The ReturnSetInfo node also contains a link to the ExprContext within which
within which the function is being evaluated. This is useful for functions 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: that need to close down internal state when they are not run to completion:
they can register a shutdown callback function in the ExprContext. 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 Notes about function handlers
----------------------------- -----------------------------
......
...@@ -7,7 +7,7 @@ ...@@ -7,7 +7,7 @@
* Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group * Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California * 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, ...@@ -80,6 +80,9 @@ extern Datum ExecMakeFunctionResult(FunctionCachePtr fcache,
ExprContext *econtext, ExprContext *econtext,
bool *isNull, bool *isNull,
ExprDoneCond *isDone); ExprDoneCond *isDone);
extern Tuplestorestate *ExecMakeTableFunctionResult(Expr *funcexpr,
ExprContext *econtext,
TupleDesc *returnDesc);
extern Datum ExecEvalExpr(Node *expression, ExprContext *econtext, extern Datum ExecEvalExpr(Node *expression, ExprContext *econtext,
bool *isNull, ExprDoneCond *isDone); bool *isNull, ExprDoneCond *isDone);
extern Datum ExecEvalExprSwitchContext(Node *expression, ExprContext *econtext, extern Datum ExecEvalExprSwitchContext(Node *expression, ExprContext *econtext,
......
...@@ -11,7 +11,7 @@ ...@@ -11,7 +11,7 @@
* Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group * Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California * 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 ...@@ -45,8 +45,7 @@ typedef struct FmgrInfo
* count */ * count */
bool fn_strict; /* function is "strict" (NULL in => NULL bool fn_strict; /* function is "strict" (NULL in => NULL
* out) */ * out) */
bool fn_retset; /* function returns a set (over multiple bool fn_retset; /* function returns a set */
* calls) */
void *fn_extra; /* extra space for use by handler */ void *fn_extra; /* extra space for use by handler */
MemoryContext fn_mcxt; /* memory context to store fn_extra in */ MemoryContext fn_mcxt; /* memory context to store fn_extra in */
} FmgrInfo; } FmgrInfo;
......
...@@ -7,7 +7,7 @@ ...@@ -7,7 +7,7 @@
* Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group * Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California * 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 @@ ...@@ -21,6 +21,8 @@
#include "fmgr.h" #include "fmgr.h"
#include "nodes/params.h" #include "nodes/params.h"
#include "nodes/primnodes.h" #include "nodes/primnodes.h"
#include "utils/tuplestore.h"
/* ---------------- /* ----------------
* IndexInfo information * IndexInfo information
...@@ -125,20 +127,32 @@ typedef enum ...@@ -125,20 +127,32 @@ typedef enum
ExprEndResult /* there are no more elements in the set */ ExprEndResult /* there are no more elements in the set */
} ExprDoneCond; } 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), * When calling a function that might return a set (multiple rows),
* a node of this type is passed as fcinfo->resultinfo to allow * a node of this type is passed as fcinfo->resultinfo to allow
* return status to be passed back. A function returning set should * return status to be passed back. A function returning set should
* raise an error if no such resultinfo is provided. The ExprContext * raise an error if no such resultinfo is provided.
* in which the function is being called is also made available.
*
* XXX this mechanism is a quick hack and probably needs to be redesigned.
*/ */
typedef struct ReturnSetInfo typedef struct ReturnSetInfo
{ {
NodeTag type; NodeTag type;
ExprDoneCond isDone; ExprContext *econtext; /* context function is being called in */
ExprContext *econtext; 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; } ReturnSetInfo;
/* ---------------- /* ----------------
...@@ -509,32 +523,17 @@ typedef struct SubqueryScanState ...@@ -509,32 +523,17 @@ typedef struct SubqueryScanState
* Function nodes are used to scan the results of a * Function nodes are used to scan the results of a
* function appearing in FROM (typically a function returning set). * function appearing in FROM (typically a function returning set).
* *
* functionmode function operating mode * tupdesc expected return tuple description
* tupdesc function's return tuple description
* tuplestorestate private state of tuplestore.c * tuplestorestate private state of tuplestore.c
* funcexpr function expression being evaluated * 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 typedef struct FunctionScanState
{ {
CommonScanState csstate; /* its first field is NodeTag */ CommonScanState csstate; /* its first field is NodeTag */
FunctionMode functionmode;
TupleDesc tupdesc; TupleDesc tupdesc;
void *tuplestorestate; Tuplestorestate *tuplestorestate;
Node *funcexpr; Node *funcexpr;
bool returnsTuple;
Oid fn_typeid;
char fn_typtype;
} FunctionScanState; } FunctionScanState;
/* ---------------------------------------------------------------- /* ----------------------------------------------------------------
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
# #
# Makefile for the plpgsql shared object # 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 ...@@ -78,7 +78,7 @@ endif
$(srcdir)/pl_scan.c: scan.l $(srcdir)/pl_scan.c: scan.l
ifdef FLEX ifdef FLEX
$(FLEX) -i $(FLEXFLAGS) -Pplpgsql_base_yy -o'$@' $< $(FLEX) $(FLEXFLAGS) -Pplpgsql_base_yy -o'$@' $<
else else
@$(missing) flex $< $@ @$(missing) flex $< $@
endif endif
......
...@@ -4,7 +4,7 @@ ...@@ -4,7 +4,7 @@
* procedural language * procedural language
* *
* IDENTIFICATION * 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. * This software is copyrighted by Jan Wieck - Hamburg.
* *
...@@ -48,7 +48,7 @@ static PLpgSQL_type *read_datatype(int tok); ...@@ -48,7 +48,7 @@ static PLpgSQL_type *read_datatype(int tok);
static PLpgSQL_stmt *make_select_stmt(void); static PLpgSQL_stmt *make_select_stmt(void);
static PLpgSQL_stmt *make_fetch_stmt(void); static PLpgSQL_stmt *make_fetch_stmt(void);
static PLpgSQL_expr *make_tupret_expr(PLpgSQL_row *row); 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); ...@@ -121,8 +121,8 @@ static void check_assignable(PLpgSQL_datum *datum);
%type <stmts> proc_sect, proc_stmts, stmt_else, loop_body %type <stmts> proc_sect, proc_stmts, stmt_else, loop_body
%type <stmt> proc_stmt, pl_block %type <stmt> proc_stmt, pl_block
%type <stmt> stmt_assign, stmt_if, stmt_loop, stmt_while, stmt_exit %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_return, stmt_return_next, stmt_raise, stmt_execsql
%type <stmt> stmt_fors, stmt_select, stmt_perform %type <stmt> stmt_fori, stmt_fors, stmt_select, stmt_perform
%type <stmt> stmt_dynexecute, stmt_dynfors, stmt_getdiag %type <stmt> stmt_dynexecute, stmt_dynfors, stmt_getdiag
%type <stmt> stmt_open, stmt_fetch, stmt_close %type <stmt> stmt_open, stmt_fetch, stmt_close
...@@ -166,6 +166,7 @@ static void check_assignable(PLpgSQL_datum *datum); ...@@ -166,6 +166,7 @@ static void check_assignable(PLpgSQL_datum *datum);
%token K_IS %token K_IS
%token K_LOG %token K_LOG
%token K_LOOP %token K_LOOP
%token K_NEXT
%token K_NOT %token K_NOT
%token K_NOTICE %token K_NOTICE
%token K_NULL %token K_NULL
...@@ -177,6 +178,7 @@ static void check_assignable(PLpgSQL_datum *datum); ...@@ -177,6 +178,7 @@ static void check_assignable(PLpgSQL_datum *datum);
%token K_RENAME %token K_RENAME
%token K_RESULT_OID %token K_RESULT_OID
%token K_RETURN %token K_RETURN
%token K_RETURN_NEXT
%token K_REVERSE %token K_REVERSE
%token K_SELECT %token K_SELECT
%token K_THEN %token K_THEN
...@@ -516,10 +518,8 @@ decl_aliasitem : T_WORD ...@@ -516,10 +518,8 @@ decl_aliasitem : T_WORD
plpgsql_convert_ident(yytext, &name, 1); plpgsql_convert_ident(yytext, &name, 1);
if (name[0] != '$') if (name[0] != '$')
{ yyerror("can only alias positional parameters");
plpgsql_error_lineno = yylineno;
elog(ERROR, "can only alias positional parameters");
}
plpgsql_ns_setlocal(false); plpgsql_ns_setlocal(false);
nsi = plpgsql_ns_lookup(name, NULL); nsi = plpgsql_ns_lookup(name, NULL);
if (nsi == NULL) if (nsi == NULL)
...@@ -609,14 +609,11 @@ decl_defval : ';' ...@@ -609,14 +609,11 @@ decl_defval : ';'
switch (tok) switch (tok)
{ {
case 0: case 0:
plpgsql_error_lineno = lno; yyerror("unexpected end of file");
elog(ERROR, "unexpected end of file");
case K_NULL: case K_NULL:
if (yylex() != ';') if (yylex() != ';')
{ yyerror("expected ; after NULL");
plpgsql_error_lineno = lno;
elog(ERROR, "expected ; after NULL");
}
free(expr); free(expr);
plpgsql_dstring_free(&ds); plpgsql_dstring_free(&ds);
...@@ -628,10 +625,8 @@ decl_defval : ';' ...@@ -628,10 +625,8 @@ decl_defval : ';'
while ((tok = yylex()) != ';') while ((tok = yylex()) != ';')
{ {
if (tok == 0) if (tok == 0)
{ yyerror("unterminated default value");
plpgsql_error_lineno = lno;
elog(ERROR, "unterminated default value");
}
if (plpgsql_SpaceScanned) if (plpgsql_SpaceScanned)
plpgsql_dstring_append(&ds, " "); plpgsql_dstring_append(&ds, " ");
plpgsql_dstring_append(&ds, yytext); plpgsql_dstring_append(&ds, yytext);
...@@ -663,7 +658,8 @@ proc_sect : ...@@ -663,7 +658,8 @@ proc_sect :
proc_stmts : proc_stmts proc_stmt 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_alloc *= 2;
$1->stmts = realloc($1->stmts, sizeof(PLpgSQL_stmt *) * $1->stmts_alloc); $1->stmts = realloc($1->stmts, sizeof(PLpgSQL_stmt *) * $1->stmts_alloc);
} }
...@@ -708,6 +704,8 @@ proc_stmt : pl_block ';' ...@@ -708,6 +704,8 @@ proc_stmt : pl_block ';'
{ $$ = $1; } { $$ = $1; }
| stmt_return | stmt_return
{ $$ = $1; } { $$ = $1; }
| stmt_return_next
{ $$ = $1; }
| stmt_raise | stmt_raise
{ $$ = $1; } { $$ = $1; }
| stmt_execsql | stmt_execsql
...@@ -1121,45 +1119,73 @@ stmt_exit : K_EXIT lno opt_exitlabel opt_exitcond ...@@ -1121,45 +1119,73 @@ stmt_exit : K_EXIT lno opt_exitlabel opt_exitcond
stmt_return : K_RETURN lno stmt_return : K_RETURN lno
{ {
PLpgSQL_stmt_return *new; PLpgSQL_stmt_return *new;
PLpgSQL_expr *expr = NULL;
int tok;
new = malloc(sizeof(PLpgSQL_stmt_return)); new = malloc(sizeof(PLpgSQL_stmt_return));
memset(new, 0, 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; new->retrecno = -1;
switch (tok = yylex()) switch (yylex())
{ {
case K_NULL: case K_NULL:
expr = NULL; new->expr = NULL;
break; break;
case T_ROW: case T_ROW:
expr = make_tupret_expr(yylval.row); new->expr = make_tupret_expr(yylval.row);
break; break;
case T_RECORD: case T_RECORD:
new->retrecno = yylval.rec->recno; new->retrecno = yylval.rec->recno;
expr = NULL; new->expr = NULL;
break; break;
default: default:
yyerror("return type mismatch in function returning table row"); yyerror("return type mismatch in function returning tuple");
break; break;
} }
if (yylex() != ';') if (yylex() != ';')
yyerror("expected ';'"); yyerror("expected ';'");
} else {
new->retistuple = false;
expr = plpgsql_read_expression(';', ";");
} }
else
new->expr = plpgsql_read_expression(';', ";");
new->cmd_type = PLPGSQL_STMT_RETURN; new->cmd_type = PLPGSQL_STMT_RETURN;
new->lineno = $2; 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; $$ = (PLpgSQL_stmt *)new;
} }
...@@ -1226,7 +1252,7 @@ raise_level : K_EXCEPTION ...@@ -1226,7 +1252,7 @@ raise_level : K_EXCEPTION
} }
| K_DEBUG | K_DEBUG
{ {
$$ = DEBUG5; $$ = DEBUG1;
} }
; ;
...@@ -1377,10 +1403,7 @@ stmt_open : K_OPEN lno cursor_varptr ...@@ -1377,10 +1403,7 @@ stmt_open : K_OPEN lno cursor_varptr
cp += strlen(cp) - 1; cp += strlen(cp) - 1;
if (*cp != ')') if (*cp != ')')
{ yyerror("missing )");
plpgsql_error_lineno = yylineno;
elog(ERROR, "missing )");
}
*cp = '\0'; *cp = '\0';
} }
else else
...@@ -1433,10 +1456,8 @@ stmt_close : K_CLOSE lno cursor_variable ';' ...@@ -1433,10 +1456,8 @@ stmt_close : K_CLOSE lno cursor_variable ';'
cursor_varptr : T_VARIABLE cursor_varptr : T_VARIABLE
{ {
if (yylval.variable->dtype != PLPGSQL_DTYPE_VAR) if (yylval.variable->dtype != PLPGSQL_DTYPE_VAR)
{ yyerror("cursor variable must be a simple variable");
plpgsql_error_lineno = yylineno;
elog(ERROR, "cursor variable must be a simple variable");
}
if (((PLpgSQL_var *) yylval.variable)->datatype->typoid != REFCURSOROID) if (((PLpgSQL_var *) yylval.variable)->datatype->typoid != REFCURSOROID)
{ {
plpgsql_error_lineno = yylineno; plpgsql_error_lineno = yylineno;
...@@ -1450,10 +1471,8 @@ cursor_varptr : T_VARIABLE ...@@ -1450,10 +1471,8 @@ cursor_varptr : T_VARIABLE
cursor_variable : T_VARIABLE cursor_variable : T_VARIABLE
{ {
if (yylval.variable->dtype != PLPGSQL_DTYPE_VAR) if (yylval.variable->dtype != PLPGSQL_DTYPE_VAR)
{ yyerror("cursor variable must be a simple variable");
plpgsql_error_lineno = yylineno;
elog(ERROR, "cursor variable must be a simple variable");
}
if (((PLpgSQL_var *) yylval.variable)->datatype->typoid != REFCURSOROID) if (((PLpgSQL_var *) yylval.variable)->datatype->typoid != REFCURSOROID)
{ {
plpgsql_error_lineno = yylineno; plpgsql_error_lineno = yylineno;
...@@ -1906,18 +1925,14 @@ make_fetch_stmt(void) ...@@ -1906,18 +1925,14 @@ make_fetch_stmt(void)
break; break;
default: default:
plpgsql_error_lineno = yylineno; yyerror("syntax error");
elog(ERROR, "syntax error at '%s'", yytext);
} }
if (!have_nexttok) if (!have_nexttok)
tok = yylex(); tok = yylex();
if (tok != ';') if (tok != ';')
{ yyerror("syntax error");
plpgsql_error_lineno = yylineno;
elog(ERROR, "syntax error at '%s'", yytext);
}
fetch = malloc(sizeof(PLpgSQL_stmt_select)); fetch = malloc(sizeof(PLpgSQL_stmt_select));
memset(fetch, 0, sizeof(PLpgSQL_stmt_fetch)); memset(fetch, 0, sizeof(PLpgSQL_stmt_fetch));
...@@ -1976,11 +1991,10 @@ check_assignable(PLpgSQL_datum *datum) ...@@ -1976,11 +1991,10 @@ check_assignable(PLpgSQL_datum *datum)
/* always assignable? */ /* always assignable? */
break; break;
case PLPGSQL_DTYPE_TRIGARG: case PLPGSQL_DTYPE_TRIGARG:
plpgsql_error_lineno = yylineno; yyerror("cannot assign to tg_argv");
elog(ERROR, "cannot assign to tg_argv");
break; break;
default: default:
elog(ERROR, "check_assignable: unexpected datum type"); yyerror("check_assignable: unexpected datum type");
break; break;
} }
} }
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
* procedural language * procedural language
* *
* IDENTIFICATION * 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. * This software is copyrighted by Jan Wieck - Hamburg.
* *
...@@ -37,8 +37,6 @@ ...@@ -37,8 +37,6 @@
#include "plpgsql.h" #include "plpgsql.h"
#include <unistd.h>
#include <fcntl.h>
#include <ctype.h> #include <ctype.h>
#include <setjmp.h> #include <setjmp.h>
...@@ -52,9 +50,6 @@ ...@@ -52,9 +50,6 @@
#include "catalog/pg_class.h" #include "catalog/pg_class.h"
#include "catalog/pg_proc.h" #include "catalog/pg_proc.h"
#include "catalog/pg_type.h" #include "catalog/pg_type.h"
#include "commands/trigger.h"
#include "executor/spi.h"
#include "fmgr.h"
#include "nodes/makefuncs.h" #include "nodes/makefuncs.h"
#include "parser/gramparse.h" #include "parser/gramparse.h"
#include "parser/parse_type.h" #include "parser/parse_type.h"
...@@ -217,6 +212,7 @@ plpgsql_compile(Oid fn_oid, int functype) ...@@ -217,6 +212,7 @@ plpgsql_compile(Oid fn_oid, int functype)
typeStruct = (Form_pg_type) GETSTRUCT(typeTup); typeStruct = (Form_pg_type) GETSTRUCT(typeTup);
/* Disallow pseudotype result, except VOID */ /* Disallow pseudotype result, except VOID */
/* XXX someday allow RECORD? */
if (typeStruct->typtype == 'p') if (typeStruct->typtype == 'p')
{ {
if (procStruct->prorettype == VOIDOID) if (procStruct->prorettype == VOIDOID)
......
This diff is collapsed.
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
* procedural language * procedural language
* *
* IDENTIFICATION * 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. * This software is copyrighted by Jan Wieck - Hamburg.
* *
...@@ -35,12 +35,6 @@ ...@@ -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 <ctype.h>
#include "plpgsql.h" #include "plpgsql.h"
...@@ -272,9 +266,7 @@ plpgsql_ns_lookup(char *name, char *label) ...@@ -272,9 +266,7 @@ plpgsql_ns_lookup(char *name, char *label)
return ns->items[i]; return ns->items[i];
} }
if (ns_localmode) if (ns_localmode)
{
return NULL; /* name not found in current namespace */ return NULL; /* name not found in current namespace */
}
} }
return NULL; return NULL;
...@@ -461,6 +453,8 @@ plpgsql_stmt_typename(PLpgSQL_stmt * stmt) ...@@ -461,6 +453,8 @@ plpgsql_stmt_typename(PLpgSQL_stmt * stmt)
return "exit"; return "exit";
case PLPGSQL_STMT_RETURN: case PLPGSQL_STMT_RETURN:
return "return"; return "return";
case PLPGSQL_STMT_RETURN_NEXT:
return "return next";
case PLPGSQL_STMT_RAISE: case PLPGSQL_STMT_RAISE:
return "raise"; return "raise";
case PLPGSQL_STMT_EXECSQL: case PLPGSQL_STMT_EXECSQL:
...@@ -500,6 +494,7 @@ static void dump_fors(PLpgSQL_stmt_fors * stmt); ...@@ -500,6 +494,7 @@ static void dump_fors(PLpgSQL_stmt_fors * stmt);
static void dump_select(PLpgSQL_stmt_select * stmt); static void dump_select(PLpgSQL_stmt_select * stmt);
static void dump_exit(PLpgSQL_stmt_exit * stmt); static void dump_exit(PLpgSQL_stmt_exit * stmt);
static void dump_return(PLpgSQL_stmt_return * 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_raise(PLpgSQL_stmt_raise * stmt);
static void dump_execsql(PLpgSQL_stmt_execsql * stmt); static void dump_execsql(PLpgSQL_stmt_execsql * stmt);
static void dump_dynexecute(PLpgSQL_stmt_dynexecute * stmt); static void dump_dynexecute(PLpgSQL_stmt_dynexecute * stmt);
...@@ -556,6 +551,9 @@ dump_stmt(PLpgSQL_stmt * stmt) ...@@ -556,6 +551,9 @@ dump_stmt(PLpgSQL_stmt * stmt)
case PLPGSQL_STMT_RETURN: case PLPGSQL_STMT_RETURN:
dump_return((PLpgSQL_stmt_return *) stmt); dump_return((PLpgSQL_stmt_return *) stmt);
break; break;
case PLPGSQL_STMT_RETURN_NEXT:
dump_return_next((PLpgSQL_stmt_return_next *) stmt);
break;
case PLPGSQL_STMT_RAISE: case PLPGSQL_STMT_RAISE:
dump_raise((PLpgSQL_stmt_raise *) stmt); dump_raise((PLpgSQL_stmt_raise *) stmt);
break; break;
...@@ -839,6 +837,20 @@ dump_return(PLpgSQL_stmt_return * stmt) ...@@ -839,6 +837,20 @@ dump_return(PLpgSQL_stmt_return * stmt)
printf("\n"); 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 static void
dump_raise(PLpgSQL_stmt_raise * stmt) dump_raise(PLpgSQL_stmt_raise * stmt)
{ {
......
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
* procedural language * procedural language
* *
* IDENTIFICATION * 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. * This software is copyrighted by Jan Wieck - Hamburg.
* *
...@@ -35,13 +35,6 @@ ...@@ -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 "plpgsql.h"
#include "pl.tab.h" #include "pl.tab.h"
......
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
* procedural language * procedural language
* *
* IDENTIFICATION * 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. * This software is copyrighted by Jan Wieck - Hamburg.
* *
...@@ -40,8 +40,10 @@ ...@@ -40,8 +40,10 @@
#include "postgres.h" #include "postgres.h"
#include "fmgr.h" #include "fmgr.h"
#include "miscadmin.h"
#include "executor/spi.h" #include "executor/spi.h"
#include "commands/trigger.h" #include "commands/trigger.h"
#include "utils/tuplestore.h"
/********************************************************************** /**********************************************************************
* Definitions * Definitions
...@@ -90,6 +92,7 @@ enum ...@@ -90,6 +92,7 @@ enum
PLPGSQL_STMT_SELECT, PLPGSQL_STMT_SELECT,
PLPGSQL_STMT_EXIT, PLPGSQL_STMT_EXIT,
PLPGSQL_STMT_RETURN, PLPGSQL_STMT_RETURN,
PLPGSQL_STMT_RETURN_NEXT,
PLPGSQL_STMT_RAISE, PLPGSQL_STMT_RAISE,
PLPGSQL_STMT_EXECSQL, PLPGSQL_STMT_EXECSQL,
PLPGSQL_STMT_DYNEXECUTE, PLPGSQL_STMT_DYNEXECUTE,
...@@ -420,11 +423,18 @@ typedef struct ...@@ -420,11 +423,18 @@ typedef struct
{ /* RETURN statement */ { /* RETURN statement */
int cmd_type; int cmd_type;
int lineno; int lineno;
bool retistuple;
PLpgSQL_expr *expr; PLpgSQL_expr *expr;
int retrecno; int retrecno;
} PLpgSQL_stmt_return; } 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 typedef struct
{ /* RAISE statement */ { /* RAISE statement */
...@@ -494,12 +504,19 @@ typedef struct ...@@ -494,12 +504,19 @@ typedef struct
{ /* Runtime execution data */ { /* Runtime execution data */
Datum retval; Datum retval;
bool retisnull; bool retisnull;
Oid rettype; Oid rettype; /* type of current retval */
Oid fn_rettype; /* info about declared function rettype */
bool retistuple; bool retistuple;
TupleDesc rettupdesc;
bool retisset; bool retisset;
TupleDesc rettupdesc;
char *exitlabel; char *exitlabel;
Tuplestorestate *tuple_store; /* SRFs accumulate results here */
MemoryContext tuple_store_cxt;
ReturnSetInfo *rsi;
int trig_nargs; int trig_nargs;
Datum *trig_argv; Datum *trig_argv;
......
...@@ -4,7 +4,7 @@ ...@@ -4,7 +4,7 @@
* procedural language * procedural language
* *
* IDENTIFICATION * 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. * This software is copyrighted by Jan Wieck - Hamburg.
* *
...@@ -46,6 +46,8 @@ static int scanner_functype; ...@@ -46,6 +46,8 @@ static int scanner_functype;
static int scanner_typereported; static int scanner_typereported;
static int pushback_token; static int pushback_token;
static bool have_pushback_token; static bool have_pushback_token;
static int lookahead_token;
static bool have_lookahead_token;
int plpgsql_SpaceScanned = 0; int plpgsql_SpaceScanned = 0;
...@@ -134,6 +136,7 @@ into { return K_INTO; } ...@@ -134,6 +136,7 @@ into { return K_INTO; }
is { return K_IS; } is { return K_IS; }
log { return K_LOG; } log { return K_LOG; }
loop { return K_LOOP; } loop { return K_LOOP; }
next { return K_NEXT; }
not { return K_NOT; } not { return K_NOT; }
notice { return K_NOTICE; } notice { return K_NOTICE; }
null { return K_NULL; } null { return K_NULL; }
...@@ -255,18 +258,50 @@ plpgsql_input(char *buf, int *result, int max) ...@@ -255,18 +258,50 @@ plpgsql_input(char *buf, int *result, int max)
} }
/* /*
* This is the yylex routine called from outside. It exists to provide * This is the yylex routine called from outside. It exists to provide
* a token pushback facility. * a pushback facility, as well as to allow us to parse syntax that
* requires more than one token of lookahead.
*/ */
int int
plpgsql_yylex(void) plpgsql_yylex(void)
{ {
int cur_token;
if (have_pushback_token) if (have_pushback_token)
{ {
have_pushback_token = false; 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) ...@@ -312,4 +347,5 @@ plpgsql_setinput(char *source, int functype)
scanner_typereported = 0; scanner_typereported = 0;
have_pushback_token = false; have_pushback_token = false;
have_lookahead_token = false;
} }
...@@ -1535,6 +1535,10 @@ ERROR: system "notthere" does not exist ...@@ -1535,6 +1535,10 @@ ERROR: system "notthere" does not exist
insert into IFace values ('IF', 'orion', 'ethernet_interface_name_too_long', ''); 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) 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 -- Test recursion, per bug report 7-Sep-01
-- --
CREATE FUNCTION recursion_test(int,int) RETURNS text AS ' CREATE FUNCTION recursion_test(int,int) RETURNS text AS '
...@@ -1557,7 +1561,7 @@ SELECT recursion_test(4,3); ...@@ -1557,7 +1561,7 @@ SELECT recursion_test(4,3);
-- Test the FOUND magic variable -- Test the FOUND magic variable
-- --
CREATE TABLE found_test_tbl (a int); CREATE TABLE found_test_tbl (a int);
create function test_found () create function test_found()
returns boolean as ' returns boolean as '
declare declare
begin begin
...@@ -1609,3 +1613,70 @@ select * from found_test_tbl; ...@@ -1609,3 +1613,70 @@ select * from found_test_tbl;
6 6
(6 rows) (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; ...@@ -1419,6 +1419,12 @@ delete from HSlot;
insert into IFace values ('IF', 'notthere', 'eth0', ''); insert into IFace values ('IF', 'notthere', 'eth0', '');
insert into IFace values ('IF', 'orion', 'ethernet_interface_name_too_long', ''); 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 -- Test recursion, per bug report 7-Sep-01
-- --
...@@ -1440,7 +1446,7 @@ SELECT recursion_test(4,3); ...@@ -1440,7 +1446,7 @@ SELECT recursion_test(4,3);
-- --
CREATE TABLE found_test_tbl (a int); CREATE TABLE found_test_tbl (a int);
create function test_found () create function test_found()
returns boolean as ' returns boolean as '
declare declare
begin begin
...@@ -1478,3 +1484,43 @@ create function test_found () ...@@ -1478,3 +1484,43 @@ create function test_found ()
select test_found(); select test_found();
select * from found_test_tbl; 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