mirror of
https://git.postgresql.org/git/postgresql.git
synced 2025-03-19 20:00:51 +08:00
PL/pgSQL functions can return sets. Neil Conway's patch, modified so
that the functionality is available to anyone via ReturnSetInfo, rather than hard-wiring it to PL/pgSQL.
This commit is contained in:
parent
82ccb420d5
commit
e107f3a7e3
@ -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();
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
<!--
|
||||
$Header: /cvsroot/pgsql/doc/src/sgml/plpgsql.sgml,v 1.4 2002/08/29 04:12:02 tgl Exp $
|
||||
$Header: /cvsroot/pgsql/doc/src/sgml/plpgsql.sgml,v 1.5 2002/08/30 00:28:40 tgl Exp $
|
||||
-->
|
||||
|
||||
<chapter id="plpgsql">
|
||||
@ -1142,11 +1142,20 @@ GET DIAGNOSTICS <replaceable>variable</replaceable> = <replaceable>item</replace
|
||||
RETURN <replaceable>expression</replaceable>;
|
||||
</synopsis>
|
||||
|
||||
RETURN with an expression is used to return from a
|
||||
<application>PL/pgSQL</> function that does not return a set.
|
||||
The function terminates and the value of
|
||||
<replaceable>expression</replaceable> will be returned to the
|
||||
upper executor.
|
||||
<replaceable>expression</replaceable> is returned to the caller.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
To return a composite (row) value, you must write a record or row
|
||||
variable as the <replaceable>expression</replaceable>. When
|
||||
returning a scalar type, any expression can be used.
|
||||
The expression's result will be automatically cast into the
|
||||
function's return type as described for assignments.
|
||||
(If you have declared the function to return <type>void</>,
|
||||
then the expression can be omitted, and will be ignored in any case.)
|
||||
</para>
|
||||
|
||||
<para>
|
||||
@ -1155,6 +1164,28 @@ RETURN <replaceable>expression</replaceable>;
|
||||
the function without hitting a RETURN statement, a run-time error
|
||||
will occur.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
When a <application>PL/pgSQL</> function is declared to return
|
||||
<literal>SETOF</literal> <replaceable>sometype</>, the procedure
|
||||
to follow is slightly different. The individual items to be returned
|
||||
are specified in RETURN NEXT commands, and then a final RETURN with
|
||||
no argument is given to indicate that the function is done generating
|
||||
items.
|
||||
|
||||
<synopsis>
|
||||
RETURN NEXT <replaceable>expression</replaceable>;
|
||||
</synopsis>
|
||||
|
||||
RETURN NEXT does not actually return from the function; it simply
|
||||
saves away the value of the expression (or record or row variable,
|
||||
as appropriate for the datatype being returned).
|
||||
Execution then continues with the next statement in the
|
||||
<application>PL/pgSQL</> function. As successive RETURN NEXT
|
||||
commands are executed, the result set is built up. A final
|
||||
RETURN, which need have no argument, causes control to exit
|
||||
the function.
|
||||
</para>
|
||||
</sect2>
|
||||
|
||||
<sect2 id="plpgsql-conditionals">
|
||||
@ -1531,8 +1562,8 @@ END LOOP;
|
||||
to worry about that, since FOR loops automatically use a cursor
|
||||
internally to avoid memory problems.) A more interesting usage is to
|
||||
return a reference to a cursor that it has created, allowing the
|
||||
caller to read the rows. This provides a way to return row sets
|
||||
from functions.
|
||||
caller to read the rows. This provides an efficient way to return
|
||||
large row sets from functions.
|
||||
</para>
|
||||
|
||||
<sect2 id="plpgsql-cursor-declarations">
|
||||
@ -1794,19 +1825,27 @@ COMMIT;
|
||||
RAISE <replaceable class="parameter">level</replaceable> '<replaceable class="parameter">format</replaceable>' <optional>, <replaceable class="parameter">variable</replaceable> <optional>...</optional></optional>;
|
||||
</synopsis>
|
||||
|
||||
Possible levels are DEBUG (write the message into the postmaster log),
|
||||
NOTICE (write the message into the postmaster log and forward it to
|
||||
the client application) and EXCEPTION (raise an error,
|
||||
aborting the transaction).
|
||||
Possible levels are <literal>DEBUG</literal> (write the message to
|
||||
the server log), <literal>LOG</literal> (write the message to the
|
||||
server log with a higher priority), <literal>INFO</literal>,
|
||||
<literal>NOTICE</literal> and <literal>WARNING</literal> (write
|
||||
the message to the server log and send it to the client, with
|
||||
respectively higher priorities), and <literal>EXCEPTION</literal>
|
||||
(raise an error and abort the current transaction). Whether error
|
||||
messages of a particular priority are reported to the client,
|
||||
written to the server log, or both is controlled by the
|
||||
<option>SERVER_MIN_MESSAGES</option> and
|
||||
<option>CLIENT_MIN_MESSAGES</option> configuration variables. See
|
||||
the <citetitle>PostgreSQL Administrator's Guide</citetitle> for more
|
||||
information.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
Inside the format string, <literal>%</literal> is replaced by the next
|
||||
optional argument's external representation.
|
||||
Write <literal>%%</literal> to emit a literal <literal>%</literal>.
|
||||
Note that the optional arguments must presently
|
||||
be simple variables, not expressions, and the format must be a simple
|
||||
string literal.
|
||||
Inside the format string, <literal>%</literal> is replaced by the
|
||||
next optional argument's external representation. Write
|
||||
<literal>%%</literal> to emit a literal <literal>%</literal>. Note
|
||||
that the optional arguments must presently be simple variables,
|
||||
not expressions, and the format must be a simple string literal.
|
||||
</para>
|
||||
|
||||
<!--
|
||||
@ -1820,8 +1859,9 @@ RAISE <replaceable class="parameter">level</replaceable> '<replaceable class="pa
|
||||
<programlisting>
|
||||
RAISE NOTICE ''Calling cs_create_job(%)'',v_job_id;
|
||||
</programlisting>
|
||||
In this example, the value of v_job_id will replace the % in the
|
||||
string.
|
||||
|
||||
In this example, the value of v_job_id will replace the
|
||||
<literal>%</literal> in the string.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
@ -1852,12 +1892,12 @@ RAISE EXCEPTION ''Inexistent ID --> %'',user_id;
|
||||
</para>
|
||||
|
||||
<para>
|
||||
Thus, the only thing <application>PL/pgSQL</application> currently does when it encounters
|
||||
an abort during execution of a function or trigger
|
||||
procedure is to write some additional NOTICE level log messages
|
||||
telling in which function and where (line number and type of
|
||||
statement) this happened. The error always stops execution of
|
||||
the function.
|
||||
Thus, the only thing <application>PL/pgSQL</application>
|
||||
currently does when it encounters an abort during execution of a
|
||||
function or trigger procedure is to write some additional
|
||||
<literal>NOTICE</literal> level log messages telling in which
|
||||
function and where (line number and type of statement) this
|
||||
happened. The error always stops execution of the function.
|
||||
</para>
|
||||
</sect2>
|
||||
</sect1>
|
||||
|
@ -1,5 +1,5 @@
|
||||
<!--
|
||||
$Header: /cvsroot/pgsql/doc/src/sgml/release.sgml,v 1.154 2002/08/29 03:22:00 tgl Exp $
|
||||
$Header: /cvsroot/pgsql/doc/src/sgml/release.sgml,v 1.155 2002/08/30 00:28:40 tgl Exp $
|
||||
-->
|
||||
|
||||
<appendix id="release">
|
||||
@ -24,6 +24,7 @@ CDATA means the content is "SGML-free", so you can write without
|
||||
worries about funny characters.
|
||||
-->
|
||||
<literallayout><![CDATA[
|
||||
Substantial improvements in functionality for functions returning sets
|
||||
Client libraries older than 6.3 no longer supported (version 0 protocol removed)
|
||||
PREPARE statement allows caching query plans for interactive statements
|
||||
Type OPAQUE is now deprecated in favor of pseudo-types cstring, trigger, etc
|
||||
|
@ -1,5 +1,5 @@
|
||||
<!--
|
||||
$Header: /cvsroot/pgsql/doc/src/sgml/runtime.sgml,v 1.128 2002/08/29 19:53:58 momjian Exp $
|
||||
$Header: /cvsroot/pgsql/doc/src/sgml/runtime.sgml,v 1.129 2002/08/30 00:28:40 tgl Exp $
|
||||
-->
|
||||
|
||||
<Chapter Id="runtime">
|
||||
@ -921,7 +921,8 @@ env PGOPTIONS='-c geqo=off' psql
|
||||
built (see the configure option
|
||||
<literal>--enable-cassert</literal>). Note that
|
||||
<literal>DEBUG_ASSERTIONS</literal> defaults to on if
|
||||
<productname>PostgreSQL</productname> has been built this way.
|
||||
<productname>PostgreSQL</productname> has been built with
|
||||
assertions enabled.
|
||||
</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
|
@ -1,5 +1,5 @@
|
||||
<!--
|
||||
$Header: /cvsroot/pgsql/doc/src/sgml/xfunc.sgml,v 1.58 2002/08/29 17:14:32 tgl Exp $
|
||||
$Header: /cvsroot/pgsql/doc/src/sgml/xfunc.sgml,v 1.59 2002/08/30 00:28:40 tgl Exp $
|
||||
-->
|
||||
|
||||
<chapter id="xfunc">
|
||||
@ -315,9 +315,7 @@ ERROR: function declared to return emp returns varchar instead of text at colum
|
||||
function, as described below. It can also be called in the context
|
||||
of an SQL expression, but only when you
|
||||
extract a single attribute out of the row or pass the entire row into
|
||||
another function that accepts the same composite type. (Trying to
|
||||
display the entire row value will yield
|
||||
a meaningless number.) For example,
|
||||
another function that accepts the same composite type. For example,
|
||||
|
||||
<programlisting>
|
||||
SELECT (new_emp()).name;
|
||||
|
@ -8,7 +8,7 @@
|
||||
*
|
||||
*
|
||||
* IDENTIFICATION
|
||||
* $Header: /cvsroot/pgsql/src/backend/executor/execQual.c,v 1.101 2002/08/26 17:53:57 tgl Exp $
|
||||
* $Header: /cvsroot/pgsql/src/backend/executor/execQual.c,v 1.102 2002/08/30 00:28:41 tgl Exp $
|
||||
*
|
||||
*-------------------------------------------------------------------------
|
||||
*/
|
||||
@ -35,12 +35,15 @@
|
||||
#include "postgres.h"
|
||||
|
||||
#include "access/heapam.h"
|
||||
#include "catalog/pg_type.h"
|
||||
#include "executor/execdebug.h"
|
||||
#include "executor/functions.h"
|
||||
#include "executor/nodeSubplan.h"
|
||||
#include "miscadmin.h"
|
||||
#include "utils/array.h"
|
||||
#include "utils/builtins.h"
|
||||
#include "utils/fcache.h"
|
||||
#include "utils/lsyscache.h"
|
||||
|
||||
|
||||
/* static function decls */
|
||||
@ -646,9 +649,6 @@ ExecEvalFuncArgs(FunctionCallInfo fcinfo,
|
||||
* ExecMakeFunctionResult
|
||||
*
|
||||
* Evaluate the arguments to a function and then the function itself.
|
||||
*
|
||||
* NOTE: econtext is used only for evaluating the argument expressions;
|
||||
* it is not passed to the function itself.
|
||||
*/
|
||||
Datum
|
||||
ExecMakeFunctionResult(FunctionCachePtr fcache,
|
||||
@ -707,6 +707,11 @@ ExecMakeFunctionResult(FunctionCachePtr fcache,
|
||||
fcinfo.resultinfo = (Node *) &rsinfo;
|
||||
rsinfo.type = T_ReturnSetInfo;
|
||||
rsinfo.econtext = econtext;
|
||||
rsinfo.allowedModes = (int) SFRM_ValuePerCall;
|
||||
rsinfo.returnMode = SFRM_ValuePerCall;
|
||||
/* isDone is filled below */
|
||||
rsinfo.setResult = NULL;
|
||||
rsinfo.setDesc = NULL;
|
||||
}
|
||||
|
||||
/*
|
||||
@ -837,10 +842,240 @@ ExecMakeFunctionResult(FunctionCachePtr fcache,
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* ExecMakeTableFunctionResult
|
||||
*
|
||||
* Evaluate a table function, producing a materialized result in a Tuplestore
|
||||
* object. (If function returns an empty set, we just return NULL instead.)
|
||||
*/
|
||||
Tuplestorestate *
|
||||
ExecMakeTableFunctionResult(Expr *funcexpr,
|
||||
ExprContext *econtext,
|
||||
TupleDesc *returnDesc)
|
||||
{
|
||||
Tuplestorestate *tupstore = NULL;
|
||||
TupleDesc tupdesc = NULL;
|
||||
Func *func;
|
||||
List *argList;
|
||||
FunctionCachePtr fcache;
|
||||
FunctionCallInfoData fcinfo;
|
||||
ReturnSetInfo rsinfo; /* for functions returning sets */
|
||||
ExprDoneCond argDone;
|
||||
MemoryContext callerContext;
|
||||
MemoryContext oldcontext;
|
||||
TupleTableSlot *slot;
|
||||
bool first_time = true;
|
||||
bool returnsTuple = false;
|
||||
|
||||
/* Extract data from function-call expression node */
|
||||
if (!funcexpr || !IsA(funcexpr, Expr) || funcexpr->opType != FUNC_EXPR)
|
||||
elog(ERROR, "ExecMakeTableFunctionResult: expression is not a function call");
|
||||
func = (Func *) funcexpr->oper;
|
||||
argList = funcexpr->args;
|
||||
|
||||
/*
|
||||
* get the fcache from the Func node. If it is NULL, then initialize it
|
||||
*/
|
||||
fcache = func->func_fcache;
|
||||
if (fcache == NULL)
|
||||
{
|
||||
fcache = init_fcache(func->funcid, length(argList),
|
||||
econtext->ecxt_per_query_memory);
|
||||
func->func_fcache = fcache;
|
||||
}
|
||||
|
||||
/*
|
||||
* Evaluate the function's argument list.
|
||||
*
|
||||
* Note: ideally, we'd do this in the per-tuple context, but then the
|
||||
* argument values would disappear when we reset the context in the
|
||||
* inner loop. So do it in caller context. Perhaps we should make a
|
||||
* separate context just to hold the evaluated arguments?
|
||||
*/
|
||||
MemSet(&fcinfo, 0, sizeof(fcinfo));
|
||||
fcinfo.flinfo = &(fcache->func);
|
||||
argDone = ExecEvalFuncArgs(&fcinfo, argList, econtext);
|
||||
/* We don't allow sets in the arguments of the table function */
|
||||
if (argDone != ExprSingleResult)
|
||||
elog(ERROR, "Set-valued function called in context that cannot accept a set");
|
||||
|
||||
/*
|
||||
* If function is strict, and there are any NULL arguments, skip
|
||||
* calling the function and return NULL (actually an empty set).
|
||||
*/
|
||||
if (fcache->func.fn_strict)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < fcinfo.nargs; i++)
|
||||
{
|
||||
if (fcinfo.argnull[i])
|
||||
{
|
||||
*returnDesc = NULL;
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* If function returns set, prepare a resultinfo node for
|
||||
* communication
|
||||
*/
|
||||
if (fcache->func.fn_retset)
|
||||
{
|
||||
fcinfo.resultinfo = (Node *) &rsinfo;
|
||||
rsinfo.type = T_ReturnSetInfo;
|
||||
rsinfo.econtext = econtext;
|
||||
rsinfo.allowedModes = (int) (SFRM_ValuePerCall | SFRM_Materialize);
|
||||
}
|
||||
/* we set these fields always since we examine them below */
|
||||
rsinfo.returnMode = SFRM_ValuePerCall;
|
||||
/* isDone is filled below */
|
||||
rsinfo.setResult = NULL;
|
||||
rsinfo.setDesc = NULL;
|
||||
|
||||
/*
|
||||
* Switch to short-lived context for calling the function.
|
||||
*/
|
||||
callerContext = MemoryContextSwitchTo(econtext->ecxt_per_tuple_memory);
|
||||
|
||||
/*
|
||||
* Loop to handle the ValuePerCall protocol.
|
||||
*/
|
||||
for (;;)
|
||||
{
|
||||
Datum result;
|
||||
HeapTuple tuple;
|
||||
|
||||
/*
|
||||
* reset per-tuple memory context before each call of the function.
|
||||
* This cleans up any local memory the function may leak when called.
|
||||
*/
|
||||
ResetExprContext(econtext);
|
||||
|
||||
/* Call the function one time */
|
||||
fcinfo.isnull = false;
|
||||
rsinfo.isDone = ExprSingleResult;
|
||||
result = FunctionCallInvoke(&fcinfo);
|
||||
|
||||
/* Which protocol does function want to use? */
|
||||
if (rsinfo.returnMode == SFRM_ValuePerCall)
|
||||
{
|
||||
/*
|
||||
* Check for end of result set.
|
||||
*
|
||||
* Note: if function returns an empty set, we don't build a
|
||||
* tupdesc or tuplestore (since we can't get a tupdesc in the
|
||||
* function-returning-tuple case)
|
||||
*/
|
||||
if (rsinfo.isDone == ExprEndResult)
|
||||
break;
|
||||
/*
|
||||
* If first time through, build tupdesc and tuplestore for result
|
||||
*/
|
||||
if (first_time)
|
||||
{
|
||||
Oid funcrettype = funcexpr->typeOid;
|
||||
|
||||
oldcontext = MemoryContextSwitchTo(econtext->ecxt_per_query_memory);
|
||||
if (funcrettype == RECORDOID ||
|
||||
get_typtype(funcrettype) == 'c')
|
||||
{
|
||||
/*
|
||||
* Composite type, so function should have returned a
|
||||
* TupleTableSlot; use its descriptor
|
||||
*/
|
||||
slot = (TupleTableSlot *) DatumGetPointer(result);
|
||||
if (fcinfo.isnull || !slot || !IsA(slot, TupleTableSlot) ||
|
||||
!slot->ttc_tupleDescriptor)
|
||||
elog(ERROR, "ExecMakeTableFunctionResult: Invalid result from function returning tuple");
|
||||
tupdesc = CreateTupleDescCopy(slot->ttc_tupleDescriptor);
|
||||
returnsTuple = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
/*
|
||||
* Scalar type, so make a single-column descriptor
|
||||
*/
|
||||
tupdesc = CreateTemplateTupleDesc(1, WITHOUTOID);
|
||||
TupleDescInitEntry(tupdesc,
|
||||
(AttrNumber) 1,
|
||||
"column",
|
||||
funcrettype,
|
||||
-1,
|
||||
0,
|
||||
false);
|
||||
}
|
||||
tupstore = tuplestore_begin_heap(true, /* randomAccess */
|
||||
SortMem);
|
||||
MemoryContextSwitchTo(oldcontext);
|
||||
rsinfo.setResult = tupstore;
|
||||
rsinfo.setDesc = tupdesc;
|
||||
}
|
||||
|
||||
/*
|
||||
* Store current resultset item.
|
||||
*/
|
||||
if (returnsTuple)
|
||||
{
|
||||
slot = (TupleTableSlot *) DatumGetPointer(result);
|
||||
if (fcinfo.isnull || !slot || !IsA(slot, TupleTableSlot) ||
|
||||
TupIsNull(slot))
|
||||
elog(ERROR, "ExecMakeTableFunctionResult: Invalid result from function returning tuple");
|
||||
tuple = slot->val;
|
||||
}
|
||||
else
|
||||
{
|
||||
char nullflag;
|
||||
|
||||
nullflag = fcinfo.isnull ? 'n' : ' ';
|
||||
tuple = heap_formtuple(tupdesc, &result, &nullflag);
|
||||
}
|
||||
|
||||
oldcontext = MemoryContextSwitchTo(econtext->ecxt_per_query_memory);
|
||||
tuplestore_puttuple(tupstore, tuple);
|
||||
MemoryContextSwitchTo(oldcontext);
|
||||
|
||||
/*
|
||||
* Are we done?
|
||||
*/
|
||||
if (rsinfo.isDone != ExprMultipleResult)
|
||||
break;
|
||||
}
|
||||
else if (rsinfo.returnMode == SFRM_Materialize)
|
||||
{
|
||||
/* check we're on the same page as the function author */
|
||||
if (!first_time || rsinfo.isDone != ExprSingleResult)
|
||||
elog(ERROR, "ExecMakeTableFunctionResult: Materialize-mode protocol not followed");
|
||||
/* Done evaluating the set result */
|
||||
break;
|
||||
}
|
||||
else
|
||||
elog(ERROR, "ExecMakeTableFunctionResult: unknown returnMode %d",
|
||||
(int) rsinfo.returnMode);
|
||||
|
||||
first_time = false;
|
||||
}
|
||||
|
||||
/* If we have a locally-created tupstore, close it up */
|
||||
if (tupstore)
|
||||
{
|
||||
MemoryContextSwitchTo(econtext->ecxt_per_query_memory);
|
||||
tuplestore_donestoring(tupstore);
|
||||
}
|
||||
|
||||
MemoryContextSwitchTo(callerContext);
|
||||
|
||||
/* The returned pointers are those in rsinfo */
|
||||
*returnDesc = rsinfo.setDesc;
|
||||
return rsinfo.setResult;
|
||||
}
|
||||
|
||||
|
||||
/* ----------------------------------------------------------------
|
||||
* ExecEvalOper
|
||||
* ExecEvalDistinct
|
||||
* ExecEvalFunc
|
||||
* ExecEvalDistinct
|
||||
*
|
||||
* Evaluate the functional result of a list of arguments by calling the
|
||||
* function manager.
|
||||
@ -886,6 +1121,48 @@ ExecEvalOper(Expr *opClause,
|
||||
isNull, isDone);
|
||||
}
|
||||
|
||||
/* ----------------------------------------------------------------
|
||||
* ExecEvalFunc
|
||||
* ----------------------------------------------------------------
|
||||
*/
|
||||
|
||||
static Datum
|
||||
ExecEvalFunc(Expr *funcClause,
|
||||
ExprContext *econtext,
|
||||
bool *isNull,
|
||||
ExprDoneCond *isDone)
|
||||
{
|
||||
Func *func;
|
||||
List *argList;
|
||||
FunctionCachePtr fcache;
|
||||
|
||||
/*
|
||||
* we extract the oid of the function associated with the func node
|
||||
* and then pass the work onto ExecMakeFunctionResult which evaluates
|
||||
* the arguments and returns the result of calling the function on the
|
||||
* evaluated arguments.
|
||||
*
|
||||
* this is nearly identical to the ExecEvalOper code.
|
||||
*/
|
||||
func = (Func *) funcClause->oper;
|
||||
argList = funcClause->args;
|
||||
|
||||
/*
|
||||
* get the fcache from the Func node. If it is NULL, then initialize
|
||||
* it
|
||||
*/
|
||||
fcache = func->func_fcache;
|
||||
if (fcache == NULL)
|
||||
{
|
||||
fcache = init_fcache(func->funcid, length(argList),
|
||||
econtext->ecxt_per_query_memory);
|
||||
func->func_fcache = fcache;
|
||||
}
|
||||
|
||||
return ExecMakeFunctionResult(fcache, argList, econtext,
|
||||
isNull, isDone);
|
||||
}
|
||||
|
||||
/* ----------------------------------------------------------------
|
||||
* ExecEvalDistinct
|
||||
*
|
||||
@ -960,48 +1237,6 @@ ExecEvalDistinct(Expr *opClause,
|
||||
return BoolGetDatum(result);
|
||||
}
|
||||
|
||||
/* ----------------------------------------------------------------
|
||||
* ExecEvalFunc
|
||||
* ----------------------------------------------------------------
|
||||
*/
|
||||
|
||||
static Datum
|
||||
ExecEvalFunc(Expr *funcClause,
|
||||
ExprContext *econtext,
|
||||
bool *isNull,
|
||||
ExprDoneCond *isDone)
|
||||
{
|
||||
Func *func;
|
||||
List *argList;
|
||||
FunctionCachePtr fcache;
|
||||
|
||||
/*
|
||||
* we extract the oid of the function associated with the func node
|
||||
* and then pass the work onto ExecMakeFunctionResult which evaluates
|
||||
* the arguments and returns the result of calling the function on the
|
||||
* evaluated arguments.
|
||||
*
|
||||
* this is nearly identical to the ExecEvalOper code.
|
||||
*/
|
||||
func = (Func *) funcClause->oper;
|
||||
argList = funcClause->args;
|
||||
|
||||
/*
|
||||
* get the fcache from the Func node. If it is NULL, then initialize
|
||||
* it
|
||||
*/
|
||||
fcache = func->func_fcache;
|
||||
if (fcache == NULL)
|
||||
{
|
||||
fcache = init_fcache(func->funcid, length(argList),
|
||||
econtext->ecxt_per_query_memory);
|
||||
func->func_fcache = fcache;
|
||||
}
|
||||
|
||||
return ExecMakeFunctionResult(fcache, argList, econtext,
|
||||
isNull, isDone);
|
||||
}
|
||||
|
||||
/* ----------------------------------------------------------------
|
||||
* ExecEvalNot
|
||||
* ExecEvalOr
|
||||
|
@ -8,7 +8,7 @@
|
||||
*
|
||||
*
|
||||
* IDENTIFICATION
|
||||
* $Header: /cvsroot/pgsql/src/backend/executor/nodeFunctionscan.c,v 1.7 2002/08/29 17:14:33 tgl Exp $
|
||||
* $Header: /cvsroot/pgsql/src/backend/executor/nodeFunctionscan.c,v 1.8 2002/08/30 00:28:41 tgl Exp $
|
||||
*
|
||||
*-------------------------------------------------------------------------
|
||||
*/
|
||||
@ -22,7 +22,6 @@
|
||||
*/
|
||||
#include "postgres.h"
|
||||
|
||||
#include "miscadmin.h"
|
||||
#include "access/heapam.h"
|
||||
#include "catalog/pg_type.h"
|
||||
#include "executor/execdebug.h"
|
||||
@ -32,17 +31,10 @@
|
||||
#include "parser/parsetree.h"
|
||||
#include "parser/parse_expr.h"
|
||||
#include "parser/parse_type.h"
|
||||
#include "storage/lmgr.h"
|
||||
#include "tcop/pquery.h"
|
||||
#include "utils/lsyscache.h"
|
||||
#include "utils/syscache.h"
|
||||
#include "utils/tuplestore.h"
|
||||
|
||||
|
||||
static TupleTableSlot *FunctionNext(FunctionScan *node);
|
||||
static TupleTableSlot *function_getonetuple(FunctionScanState *scanstate,
|
||||
bool *isNull,
|
||||
ExprDoneCond *isDone);
|
||||
static FunctionMode get_functionmode(Node *expr);
|
||||
static bool tupledesc_mismatch(TupleDesc tupdesc1, TupleDesc tupdesc2);
|
||||
|
||||
/* ----------------------------------------------------------------
|
||||
@ -76,53 +68,42 @@ FunctionNext(FunctionScan *node)
|
||||
tuplestorestate = scanstate->tuplestorestate;
|
||||
|
||||
/*
|
||||
* If first time through, read all tuples from function and pass them to
|
||||
* tuplestore.c. Subsequent calls just fetch tuples from tuplestore.
|
||||
* If first time through, read all tuples from function and put them
|
||||
* in a tuplestore. Subsequent calls just fetch tuples from tuplestore.
|
||||
*/
|
||||
if (tuplestorestate == NULL)
|
||||
{
|
||||
/*
|
||||
* Initialize tuplestore module.
|
||||
*/
|
||||
tuplestorestate = tuplestore_begin_heap(true, /* randomAccess */
|
||||
SortMem);
|
||||
ExprContext *econtext = scanstate->csstate.cstate.cs_ExprContext;
|
||||
TupleDesc funcTupdesc;
|
||||
|
||||
scanstate->tuplestorestate = (void *) tuplestorestate;
|
||||
scanstate->tuplestorestate = tuplestorestate =
|
||||
ExecMakeTableFunctionResult((Expr *) scanstate->funcexpr,
|
||||
econtext,
|
||||
&funcTupdesc);
|
||||
|
||||
/*
|
||||
* Compute all the function tuples and pass to tuplestore.
|
||||
* If function provided a tupdesc, cross-check it. We only really
|
||||
* need to do this for functions returning RECORD, but might as well
|
||||
* do it always.
|
||||
*/
|
||||
for (;;)
|
||||
{
|
||||
bool isNull;
|
||||
ExprDoneCond isDone;
|
||||
|
||||
isNull = false;
|
||||
isDone = ExprSingleResult;
|
||||
slot = function_getonetuple(scanstate, &isNull, &isDone);
|
||||
if (TupIsNull(slot))
|
||||
break;
|
||||
|
||||
tuplestore_puttuple(tuplestorestate, (void *) slot->val);
|
||||
ExecClearTuple(slot);
|
||||
|
||||
if (isDone != ExprMultipleResult)
|
||||
break;
|
||||
}
|
||||
|
||||
/*
|
||||
* Complete the store.
|
||||
*/
|
||||
tuplestore_donestoring(tuplestorestate);
|
||||
if (funcTupdesc &&
|
||||
tupledesc_mismatch(scanstate->tupdesc, funcTupdesc))
|
||||
elog(ERROR, "Query-specified return tuple and actual function return tuple do not match");
|
||||
}
|
||||
|
||||
/*
|
||||
* Get the next tuple from tuplestore. Return NULL if no more tuples.
|
||||
*/
|
||||
slot = scanstate->csstate.css_ScanTupleSlot;
|
||||
heapTuple = tuplestore_getheaptuple(tuplestorestate,
|
||||
ScanDirectionIsForward(direction),
|
||||
&should_free);
|
||||
if (tuplestorestate)
|
||||
heapTuple = tuplestore_getheaptuple(tuplestorestate,
|
||||
ScanDirectionIsForward(direction),
|
||||
&should_free);
|
||||
else
|
||||
{
|
||||
heapTuple = NULL;
|
||||
should_free = false;
|
||||
}
|
||||
|
||||
return ExecStoreTuple(heapTuple, slot, InvalidBuffer, should_free);
|
||||
}
|
||||
@ -219,7 +200,6 @@ ExecInitFunctionScan(FunctionScan *node, EState *estate, Plan *parent)
|
||||
rel = relation_open(funcrelid, AccessShareLock);
|
||||
tupdesc = CreateTupleDescCopy(RelationGetDescr(rel));
|
||||
relation_close(rel, AccessShareLock);
|
||||
scanstate->returnsTuple = true;
|
||||
}
|
||||
else if (functyptype == 'b' || functyptype == 'd')
|
||||
{
|
||||
@ -236,7 +216,6 @@ ExecInitFunctionScan(FunctionScan *node, EState *estate, Plan *parent)
|
||||
-1,
|
||||
0,
|
||||
false);
|
||||
scanstate->returnsTuple = false;
|
||||
}
|
||||
else if (functyptype == 'p' && funcrettype == RECORDOID)
|
||||
{
|
||||
@ -246,13 +225,10 @@ ExecInitFunctionScan(FunctionScan *node, EState *estate, Plan *parent)
|
||||
List *coldeflist = rte->coldeflist;
|
||||
|
||||
tupdesc = BuildDescForRelation(coldeflist);
|
||||
scanstate->returnsTuple = true;
|
||||
}
|
||||
else
|
||||
elog(ERROR, "Unknown kind of return type specified for function");
|
||||
|
||||
scanstate->fn_typeid = funcrettype;
|
||||
scanstate->fn_typtype = functyptype;
|
||||
scanstate->tupdesc = tupdesc;
|
||||
ExecSetSlotDescriptor(scanstate->csstate.css_ScanTupleSlot,
|
||||
tupdesc, false);
|
||||
@ -263,8 +239,6 @@ ExecInitFunctionScan(FunctionScan *node, EState *estate, Plan *parent)
|
||||
scanstate->tuplestorestate = NULL;
|
||||
scanstate->funcexpr = rte->funcexpr;
|
||||
|
||||
scanstate->functionmode = get_functionmode(rte->funcexpr);
|
||||
|
||||
scanstate->csstate.cstate.cs_TupFromTlist = false;
|
||||
|
||||
/*
|
||||
@ -322,7 +296,7 @@ ExecEndFunctionScan(FunctionScan *node)
|
||||
* Release tuplestore resources
|
||||
*/
|
||||
if (scanstate->tuplestorestate != NULL)
|
||||
tuplestore_end((Tuplestorestate *) scanstate->tuplestorestate);
|
||||
tuplestore_end(scanstate->tuplestorestate);
|
||||
scanstate->tuplestorestate = NULL;
|
||||
}
|
||||
|
||||
@ -345,7 +319,7 @@ ExecFunctionMarkPos(FunctionScan *node)
|
||||
if (!scanstate->tuplestorestate)
|
||||
return;
|
||||
|
||||
tuplestore_markpos((Tuplestorestate *) scanstate->tuplestorestate);
|
||||
tuplestore_markpos(scanstate->tuplestorestate);
|
||||
}
|
||||
|
||||
/* ----------------------------------------------------------------
|
||||
@ -367,7 +341,7 @@ ExecFunctionRestrPos(FunctionScan *node)
|
||||
if (!scanstate->tuplestorestate)
|
||||
return;
|
||||
|
||||
tuplestore_restorepos((Tuplestorestate *) scanstate->tuplestorestate);
|
||||
tuplestore_restorepos(scanstate->tuplestorestate);
|
||||
}
|
||||
|
||||
/* ----------------------------------------------------------------
|
||||
@ -402,98 +376,13 @@ ExecFunctionReScan(FunctionScan *node, ExprContext *exprCtxt, Plan *parent)
|
||||
*/
|
||||
if (node->scan.plan.chgParam != NULL)
|
||||
{
|
||||
tuplestore_end((Tuplestorestate *) scanstate->tuplestorestate);
|
||||
tuplestore_end(scanstate->tuplestorestate);
|
||||
scanstate->tuplestorestate = NULL;
|
||||
}
|
||||
else
|
||||
tuplestore_rescan((Tuplestorestate *) scanstate->tuplestorestate);
|
||||
tuplestore_rescan(scanstate->tuplestorestate);
|
||||
}
|
||||
|
||||
/*
|
||||
* Run the underlying function to get the next tuple
|
||||
*/
|
||||
static TupleTableSlot *
|
||||
function_getonetuple(FunctionScanState *scanstate,
|
||||
bool *isNull,
|
||||
ExprDoneCond *isDone)
|
||||
{
|
||||
HeapTuple tuple;
|
||||
Datum retDatum;
|
||||
char nullflag;
|
||||
TupleDesc tupdesc = scanstate->tupdesc;
|
||||
bool returnsTuple = scanstate->returnsTuple;
|
||||
Node *expr = scanstate->funcexpr;
|
||||
Oid fn_typeid = scanstate->fn_typeid;
|
||||
char fn_typtype = scanstate->fn_typtype;
|
||||
ExprContext *econtext = scanstate->csstate.cstate.cs_ExprContext;
|
||||
TupleTableSlot *slot = scanstate->csstate.css_ScanTupleSlot;
|
||||
|
||||
/*
|
||||
* reset per-tuple memory context before each call of the function.
|
||||
* This cleans up any local memory the function may leak when called.
|
||||
*/
|
||||
ResetExprContext(econtext);
|
||||
|
||||
/*
|
||||
* get the next Datum from the function
|
||||
*/
|
||||
retDatum = ExecEvalExprSwitchContext(expr, econtext, isNull, isDone);
|
||||
|
||||
/*
|
||||
* check to see if we're really done
|
||||
*/
|
||||
if (*isDone == ExprEndResult)
|
||||
slot = NULL;
|
||||
else
|
||||
{
|
||||
if (returnsTuple)
|
||||
{
|
||||
/*
|
||||
* Composite data type, i.e. a table's row type
|
||||
* function returns pointer to tts??
|
||||
*/
|
||||
slot = (TupleTableSlot *) retDatum;
|
||||
|
||||
/*
|
||||
* if function return type was RECORD, we need to check to be
|
||||
* sure the structure from the query matches the actual return
|
||||
* structure
|
||||
*/
|
||||
if (fn_typtype == 'p' && fn_typeid == RECORDOID)
|
||||
if (tupledesc_mismatch(tupdesc, slot->ttc_tupleDescriptor))
|
||||
elog(ERROR, "Query-specified return tuple and actual function return tuple do not match");
|
||||
}
|
||||
else
|
||||
{
|
||||
/*
|
||||
* Must be a base data type, i.e. scalar
|
||||
* turn it into a tuple
|
||||
*/
|
||||
nullflag = *isNull ? 'n' : ' ';
|
||||
tuple = heap_formtuple(tupdesc, &retDatum, &nullflag);
|
||||
|
||||
/*
|
||||
* save the tuple in the scan tuple slot and return the slot.
|
||||
*/
|
||||
slot = ExecStoreTuple(tuple, /* tuple to store */
|
||||
slot, /* slot to store in */
|
||||
InvalidBuffer, /* buffer associated with
|
||||
* this tuple */
|
||||
true); /* pfree this tuple */
|
||||
}
|
||||
}
|
||||
|
||||
return slot;
|
||||
}
|
||||
|
||||
static FunctionMode
|
||||
get_functionmode(Node *expr)
|
||||
{
|
||||
/*
|
||||
* for the moment, hardwire this
|
||||
*/
|
||||
return PM_REPEATEDCALL;
|
||||
}
|
||||
|
||||
static bool
|
||||
tupledesc_mismatch(TupleDesc tupdesc1, TupleDesc tupdesc2)
|
||||
|
@ -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)
|
||||
|
@ -3,7 +3,7 @@
|
||||
* procedural language
|
||||
*
|
||||
* IDENTIFICATION
|
||||
* $Header: /cvsroot/pgsql/src/pl/plpgsql/src/pl_exec.c,v 1.59 2002/08/29 04:12:03 tgl Exp $
|
||||
* $Header: /cvsroot/pgsql/src/pl/plpgsql/src/pl_exec.c,v 1.60 2002/08/30 00:28:41 tgl Exp $
|
||||
*
|
||||
* This software is copyrighted by Jan Wieck - Hamburg.
|
||||
*
|
||||
@ -35,12 +35,6 @@
|
||||
*
|
||||
**********************************************************************/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdarg.h>
|
||||
#include <unistd.h>
|
||||
#include <fcntl.h>
|
||||
#include <string.h>
|
||||
#include <ctype.h>
|
||||
#include <setjmp.h>
|
||||
|
||||
@ -50,10 +44,8 @@
|
||||
#include "access/heapam.h"
|
||||
#include "catalog/pg_proc.h"
|
||||
#include "catalog/pg_type.h"
|
||||
#include "commands/trigger.h"
|
||||
#include "executor/spi.h"
|
||||
#include "executor/spi_priv.h"
|
||||
#include "fmgr.h"
|
||||
#include "funcapi.h"
|
||||
#include "optimizer/clauses.h"
|
||||
#include "parser/parse_expr.h"
|
||||
#include "tcop/tcopprot.h"
|
||||
@ -105,6 +97,8 @@ static int exec_stmt_exit(PLpgSQL_execstate * estate,
|
||||
PLpgSQL_stmt_exit * stmt);
|
||||
static int exec_stmt_return(PLpgSQL_execstate * estate,
|
||||
PLpgSQL_stmt_return * stmt);
|
||||
static int exec_stmt_return_next(PLpgSQL_execstate * estate,
|
||||
PLpgSQL_stmt_return_next * stmt);
|
||||
static int exec_stmt_raise(PLpgSQL_execstate * estate,
|
||||
PLpgSQL_stmt_raise * stmt);
|
||||
static int exec_stmt_execsql(PLpgSQL_execstate * estate,
|
||||
@ -114,8 +108,9 @@ static int exec_stmt_dynexecute(PLpgSQL_execstate * estate,
|
||||
static int exec_stmt_dynfors(PLpgSQL_execstate * estate,
|
||||
PLpgSQL_stmt_dynfors * stmt);
|
||||
|
||||
static void plpgsql_estate_setup(PLpgSQL_execstate * estate,
|
||||
PLpgSQL_function * func);
|
||||
static void plpgsql_estate_setup(PLpgSQL_execstate *estate,
|
||||
PLpgSQL_function *func,
|
||||
ReturnSetInfo *rsi);
|
||||
static void exec_eval_cleanup(PLpgSQL_execstate * estate);
|
||||
|
||||
static void exec_prepare_plan(PLpgSQL_execstate * estate,
|
||||
@ -150,6 +145,8 @@ static Datum exec_cast_value(Datum value, Oid valtype,
|
||||
int32 reqtypmod,
|
||||
bool *isnull);
|
||||
static void exec_set_found(PLpgSQL_execstate * estate, bool state);
|
||||
static void exec_init_tuple_store(PLpgSQL_execstate *estate);
|
||||
static void exec_set_ret_tupdesc(PLpgSQL_execstate *estate, List *labels);
|
||||
|
||||
|
||||
/* ----------
|
||||
@ -211,7 +208,7 @@ plpgsql_exec_function(PLpgSQL_function * func, FunctionCallInfo fcinfo)
|
||||
/*
|
||||
* Setup the execution state
|
||||
*/
|
||||
plpgsql_estate_setup(&estate, func);
|
||||
plpgsql_estate_setup(&estate, func, (ReturnSetInfo *) fcinfo->resultinfo);
|
||||
|
||||
/*
|
||||
* Make local execution copies of all the datums
|
||||
@ -332,11 +329,36 @@ plpgsql_exec_function(PLpgSQL_function * func, FunctionCallInfo fcinfo)
|
||||
* We got a return value - process it
|
||||
*/
|
||||
error_info_stmt = NULL;
|
||||
error_info_text = "while casting return value to functions return type";
|
||||
error_info_text = "while casting return value to function's return type";
|
||||
|
||||
fcinfo->isnull = estate.retisnull;
|
||||
|
||||
if (!estate.retisnull)
|
||||
if (estate.retisset)
|
||||
{
|
||||
ReturnSetInfo *rsi = estate.rsi;
|
||||
|
||||
/* Check caller can handle a set result */
|
||||
if (!rsi || !IsA(rsi, ReturnSetInfo) ||
|
||||
(rsi->allowedModes & SFRM_Materialize) == 0)
|
||||
elog(ERROR, "Set-valued function called in context that cannot accept a set");
|
||||
rsi->returnMode = SFRM_Materialize;
|
||||
|
||||
/* If we produced any tuples, send back the result */
|
||||
if (estate.tuple_store)
|
||||
{
|
||||
MemoryContext oldcxt;
|
||||
|
||||
oldcxt = MemoryContextSwitchTo(estate.tuple_store_cxt);
|
||||
tuplestore_donestoring(estate.tuple_store);
|
||||
rsi->setResult = estate.tuple_store;
|
||||
if (estate.rettupdesc)
|
||||
rsi->setDesc = CreateTupleDescCopy(estate.rettupdesc);
|
||||
MemoryContextSwitchTo(oldcxt);
|
||||
}
|
||||
estate.retval = (Datum) 0;
|
||||
fcinfo->isnull = true;
|
||||
}
|
||||
else if (!estate.retisnull)
|
||||
{
|
||||
if (estate.retistuple)
|
||||
{
|
||||
@ -455,7 +477,7 @@ plpgsql_exec_trigger(PLpgSQL_function * func,
|
||||
/*
|
||||
* Setup the execution state
|
||||
*/
|
||||
plpgsql_estate_setup(&estate, func);
|
||||
plpgsql_estate_setup(&estate, func, NULL);
|
||||
|
||||
/*
|
||||
* Make local execution copies of all the datums
|
||||
@ -642,6 +664,9 @@ plpgsql_exec_trigger(PLpgSQL_function * func,
|
||||
elog(ERROR, "control reaches end of trigger procedure without RETURN");
|
||||
}
|
||||
|
||||
if (estate.retisset)
|
||||
elog(ERROR, "trigger procedure cannot return a set");
|
||||
|
||||
/*
|
||||
* Check that the returned tuple structure has the same attributes,
|
||||
* the relation that fired the trigger has.
|
||||
@ -862,6 +887,8 @@ exec_stmt(PLpgSQL_execstate * estate, PLpgSQL_stmt * stmt)
|
||||
save_estmt = error_info_stmt;
|
||||
error_info_stmt = stmt;
|
||||
|
||||
CHECK_FOR_INTERRUPTS();
|
||||
|
||||
switch (stmt->cmd_type)
|
||||
{
|
||||
case PLPGSQL_STMT_BLOCK:
|
||||
@ -908,6 +935,10 @@ exec_stmt(PLpgSQL_execstate * estate, PLpgSQL_stmt * stmt)
|
||||
rc = exec_stmt_return(estate, (PLpgSQL_stmt_return *) stmt);
|
||||
break;
|
||||
|
||||
case PLPGSQL_STMT_RETURN_NEXT:
|
||||
rc = exec_stmt_return_next(estate, (PLpgSQL_stmt_return_next *) stmt);
|
||||
break;
|
||||
|
||||
case PLPGSQL_STMT_RAISE:
|
||||
rc = exec_stmt_raise(estate, (PLpgSQL_stmt_raise *) stmt);
|
||||
break;
|
||||
@ -1302,13 +1333,10 @@ exec_stmt_fors(PLpgSQL_execstate * estate, PLpgSQL_stmt_fors * stmt)
|
||||
*/
|
||||
if (stmt->rec != NULL)
|
||||
rec = (PLpgSQL_rec *) (estate->datums[stmt->rec->recno]);
|
||||
else if (stmt->row != NULL)
|
||||
row = (PLpgSQL_row *) (estate->datums[stmt->row->rowno]);
|
||||
else
|
||||
{
|
||||
if (stmt->row != NULL)
|
||||
row = (PLpgSQL_row *) (estate->datums[stmt->row->rowno]);
|
||||
else
|
||||
elog(ERROR, "unsupported target in exec_stmt_fors()");
|
||||
}
|
||||
elog(ERROR, "unsupported target in exec_stmt_fors()");
|
||||
|
||||
/*
|
||||
* Open the implicit cursor for the statement and fetch the initial 10
|
||||
@ -1499,6 +1527,14 @@ exec_stmt_exit(PLpgSQL_execstate * estate, PLpgSQL_stmt_exit * stmt)
|
||||
static int
|
||||
exec_stmt_return(PLpgSQL_execstate * estate, PLpgSQL_stmt_return * stmt)
|
||||
{
|
||||
/*
|
||||
* If processing a set-returning PL/PgSQL function, the final RETURN
|
||||
* indicates that the function is finished producing tuples. The rest
|
||||
* of the work will be done at the top level.
|
||||
*/
|
||||
if (estate->retisset)
|
||||
return PLPGSQL_RC_RETURN;
|
||||
|
||||
if (estate->retistuple)
|
||||
{
|
||||
/* initialize for null result tuple */
|
||||
@ -1532,13 +1568,155 @@ exec_stmt_return(PLpgSQL_execstate * estate, PLpgSQL_stmt_return * stmt)
|
||||
return PLPGSQL_RC_RETURN;
|
||||
}
|
||||
|
||||
estate->retval = exec_eval_expr(estate, stmt->expr,
|
||||
&(estate->retisnull),
|
||||
&(estate->rettype));
|
||||
if (estate->fn_rettype == VOIDOID)
|
||||
{
|
||||
/* Special hack for function returning VOID */
|
||||
estate->retval = (Datum) 0;
|
||||
estate->retisnull = false;
|
||||
estate->rettype = VOIDOID;
|
||||
}
|
||||
else
|
||||
{
|
||||
/* Normal case for scalar results */
|
||||
estate->retval = exec_eval_expr(estate, stmt->expr,
|
||||
&(estate->retisnull),
|
||||
&(estate->rettype));
|
||||
}
|
||||
|
||||
return PLPGSQL_RC_RETURN;
|
||||
}
|
||||
|
||||
/*
|
||||
* Notes:
|
||||
* - the tuple store must be created in a sufficiently long-lived
|
||||
* memory context, as the same store must be used within the executor
|
||||
* after the PL/PgSQL call returns. At present, the code uses
|
||||
* TopTransactionContext.
|
||||
*/
|
||||
static int
|
||||
exec_stmt_return_next(PLpgSQL_execstate *estate,
|
||||
PLpgSQL_stmt_return_next *stmt)
|
||||
{
|
||||
HeapTuple tuple;
|
||||
bool free_tuple = false;
|
||||
|
||||
if (!estate->retisset)
|
||||
elog(ERROR, "Cannot use RETURN NEXT in a non-SETOF function");
|
||||
|
||||
if (estate->tuple_store == NULL)
|
||||
exec_init_tuple_store(estate);
|
||||
|
||||
if (stmt->rec)
|
||||
{
|
||||
PLpgSQL_rec *rec = (PLpgSQL_rec *) (estate->datums[stmt->rec->recno]);
|
||||
tuple = rec->tup;
|
||||
estate->rettupdesc = rec->tupdesc;
|
||||
}
|
||||
else if (stmt->row)
|
||||
{
|
||||
PLpgSQL_var *var;
|
||||
TupleDesc tupdesc;
|
||||
Datum *dvalues;
|
||||
char *nulls;
|
||||
int natts;
|
||||
int i;
|
||||
|
||||
if (!estate->rettupdesc)
|
||||
exec_set_ret_tupdesc(estate, NIL);
|
||||
|
||||
tupdesc = estate->rettupdesc;
|
||||
natts = tupdesc->natts;
|
||||
dvalues = (Datum *) palloc(natts * sizeof(Datum));
|
||||
nulls = (char *) palloc(natts * sizeof(char));
|
||||
|
||||
MemSet(dvalues, 0, natts * sizeof(Datum));
|
||||
MemSet(nulls, 'n', natts);
|
||||
|
||||
for (i = 0; i < stmt->row->nfields; i++)
|
||||
{
|
||||
var = (PLpgSQL_var *) (estate->datums[stmt->row->varnos[i]]);
|
||||
dvalues[i] = var->value;
|
||||
if (!var->isnull)
|
||||
nulls[i] = ' ';
|
||||
}
|
||||
|
||||
tuple = heap_formtuple(tupdesc, dvalues, nulls);
|
||||
|
||||
pfree(dvalues);
|
||||
pfree(nulls);
|
||||
free_tuple = true;
|
||||
}
|
||||
else if (stmt->expr)
|
||||
{
|
||||
Datum retval;
|
||||
bool isNull;
|
||||
char nullflag;
|
||||
|
||||
if (!estate->rettupdesc)
|
||||
exec_set_ret_tupdesc(estate, makeList1(makeString("unused")));
|
||||
|
||||
retval = exec_eval_expr(estate,
|
||||
stmt->expr,
|
||||
&isNull,
|
||||
&(estate->rettype));
|
||||
|
||||
nullflag = isNull ? 'n' : ' ';
|
||||
|
||||
tuple = heap_formtuple(estate->rettupdesc, &retval, &nullflag);
|
||||
|
||||
free_tuple = true;
|
||||
|
||||
exec_eval_cleanup(estate);
|
||||
}
|
||||
else
|
||||
{
|
||||
elog(ERROR, "Blank RETURN NEXT not allowed");
|
||||
tuple = NULL; /* keep compiler quiet */
|
||||
}
|
||||
|
||||
if (HeapTupleIsValid(tuple))
|
||||
{
|
||||
MemoryContext oldcxt;
|
||||
|
||||
oldcxt = MemoryContextSwitchTo(estate->tuple_store_cxt);
|
||||
tuplestore_puttuple(estate->tuple_store, tuple);
|
||||
MemoryContextSwitchTo(oldcxt);
|
||||
|
||||
if (free_tuple)
|
||||
heap_freetuple(tuple);
|
||||
}
|
||||
|
||||
return PLPGSQL_RC_OK;
|
||||
}
|
||||
|
||||
static void
|
||||
exec_set_ret_tupdesc(PLpgSQL_execstate *estate, List *labels)
|
||||
{
|
||||
estate->rettype = estate->fn_rettype;
|
||||
estate->rettupdesc = TypeGetTupleDesc(estate->rettype, labels);
|
||||
|
||||
if (!estate->rettupdesc)
|
||||
elog(ERROR, "Could not produce descriptor for rowtype");
|
||||
}
|
||||
|
||||
static void
|
||||
exec_init_tuple_store(PLpgSQL_execstate *estate)
|
||||
{
|
||||
ReturnSetInfo *rsi = estate->rsi;
|
||||
MemoryContext oldcxt;
|
||||
|
||||
/* Check caller can handle a set result */
|
||||
if (!rsi || !IsA(rsi, ReturnSetInfo) ||
|
||||
(rsi->allowedModes & SFRM_Materialize) == 0)
|
||||
elog(ERROR, "Set-valued function called in context that cannot accept a set");
|
||||
|
||||
estate->tuple_store_cxt = rsi->econtext->ecxt_per_query_memory;
|
||||
|
||||
oldcxt = MemoryContextSwitchTo(estate->tuple_store_cxt);
|
||||
estate->tuple_store = tuplestore_begin_heap(true, SortMem);
|
||||
MemoryContextSwitchTo(oldcxt);
|
||||
}
|
||||
|
||||
|
||||
/* ----------
|
||||
* exec_stmt_raise Build a message and throw it with
|
||||
@ -1700,21 +1878,29 @@ exec_stmt_raise(PLpgSQL_execstate * estate, PLpgSQL_stmt_raise * stmt)
|
||||
|
||||
|
||||
/* ----------
|
||||
* Initialize an empty estate
|
||||
* Initialize a mostly empty execution state
|
||||
* ----------
|
||||
*/
|
||||
static void
|
||||
plpgsql_estate_setup(PLpgSQL_execstate * estate,
|
||||
PLpgSQL_function * func)
|
||||
plpgsql_estate_setup(PLpgSQL_execstate *estate,
|
||||
PLpgSQL_function *func,
|
||||
ReturnSetInfo *rsi)
|
||||
{
|
||||
estate->retval = (Datum) 0;
|
||||
estate->retisnull = true;
|
||||
estate->rettype = InvalidOid;
|
||||
|
||||
estate->fn_rettype = func->fn_rettype;
|
||||
estate->retistuple = func->fn_retistuple;
|
||||
estate->rettupdesc = NULL;
|
||||
estate->retisset = func->fn_retset;
|
||||
|
||||
estate->rettupdesc = NULL;
|
||||
estate->exitlabel = NULL;
|
||||
|
||||
estate->tuple_store = NULL;
|
||||
estate->tuple_store_cxt = NULL;
|
||||
estate->rsi = rsi;
|
||||
|
||||
estate->trig_nargs = 0;
|
||||
estate->trig_argv = NULL;
|
||||
|
||||
@ -2099,13 +2285,10 @@ exec_stmt_dynfors(PLpgSQL_execstate * estate, PLpgSQL_stmt_dynfors * stmt)
|
||||
*/
|
||||
if (stmt->rec != NULL)
|
||||
rec = (PLpgSQL_rec *) (estate->datums[stmt->rec->recno]);
|
||||
else if (stmt->row != NULL)
|
||||
row = (PLpgSQL_row *) (estate->datums[stmt->row->rowno]);
|
||||
else
|
||||
{
|
||||
if (stmt->row != NULL)
|
||||
row = (PLpgSQL_row *) (estate->datums[stmt->row->rowno]);
|
||||
else
|
||||
elog(ERROR, "unsupported target in exec_stmt_fors()");
|
||||
}
|
||||
elog(ERROR, "unsupported target in exec_stmt_dynfors()");
|
||||
|
||||
/*
|
||||
* Evaluate the string expression after the EXECUTE keyword. It's
|
||||
|
@ -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;
|
||||
}
|
||||
return yylex();
|
||||
else if (have_lookahead_token)
|
||||
{
|
||||
have_lookahead_token = false;
|
||||
cur_token = lookahead_token;
|
||||
}
|
||||
else
|
||||
cur_token = yylex();
|
||||
|
||||
/* Do we need to look ahead for a possible multiword token? */
|
||||
switch (cur_token)
|
||||
{
|
||||
/* RETURN NEXT must be reduced to a single token */
|
||||
case K_RETURN:
|
||||
if (!have_lookahead_token)
|
||||
{
|
||||
lookahead_token = yylex();
|
||||
have_lookahead_token = true;
|
||||
}
|
||||
if (lookahead_token == K_NEXT)
|
||||
{
|
||||
have_lookahead_token = false;
|
||||
cur_token = K_RETURN_NEXT;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return cur_token;
|
||||
}
|
||||
|
||||
/*
|
||||
@ -312,4 +347,5 @@ plpgsql_setinput(char *source, int functype)
|
||||
scanner_typereported = 0;
|
||||
|
||||
have_pushback_token = false;
|
||||
have_lookahead_token = false;
|
||||
}
|
||||
|
@ -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);
|
||||
|
Loading…
x
Reference in New Issue
Block a user