#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"
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;
}
pnew->a.size = cb;
pnew->a.ndim=1;
pnew->a.flags = 0;
- pnew->a.elmtype = INT4OID;
+ pnew->a.elemtype = INT4OID;
pnew->lower = 0;
}
else
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
{
/* 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) )
{
pc->flags = TOASTED;
if(!pc->p)
{
- elog(ERROR, "Error in toaster!!! no detoasting\n");
+ elog(ERROR, "Error in toaster!!! no detoasting");
PG_RETURN_NULL();
}
}
<!--
-$Header: /cvsroot/pgsql/doc/src/sgml/plpgsql.sgml,v 1.4 2002/08/29 04:12:02 tgl Exp $
+$Header: /cvsroot/pgsql/doc/src/sgml/plpgsql.sgml,v 1.5 2002/08/30 00:28:40 tgl Exp $
-->
<chapter id="plpgsql">
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>
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">
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">
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>
<!--
<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>
</para>
<para>
- Thus, the only thing <application>PL/pgSQL</application> currently does when it encounters
- an abort during execution of a function or trigger
- procedure is to write some additional NOTICE level log messages
- telling in which function and where (line number and type of
- statement) this happened. The error always stops execution of
- the function.
+ Thus, the only thing <application>PL/pgSQL</application>
+ currently does when it encounters an abort during execution of a
+ function or trigger procedure is to write some additional
+ <literal>NOTICE</literal> level log messages telling in which
+ function and where (line number and type of statement) this
+ happened. The error always stops execution of the function.
</para>
</sect2>
</sect1>
<!--
-$Header: /cvsroot/pgsql/doc/src/sgml/release.sgml,v 1.154 2002/08/29 03:22:00 tgl Exp $
+$Header: /cvsroot/pgsql/doc/src/sgml/release.sgml,v 1.155 2002/08/30 00:28:40 tgl Exp $
-->
<appendix id="release">
worries about funny characters.
-->
<literallayout><![CDATA[
+Substantial improvements in functionality for functions returning sets
Client libraries older than 6.3 no longer supported (version 0 protocol removed)
PREPARE statement allows caching query plans for interactive statements
Type OPAQUE is now deprecated in favor of pseudo-types cstring, trigger, etc
<!--
-$Header: /cvsroot/pgsql/doc/src/sgml/runtime.sgml,v 1.128 2002/08/29 19:53:58 momjian Exp $
+$Header: /cvsroot/pgsql/doc/src/sgml/runtime.sgml,v 1.129 2002/08/30 00:28:40 tgl Exp $
-->
<Chapter Id="runtime">
built (see the configure option
<literal>--enable-cassert</literal>). Note that
<literal>DEBUG_ASSERTIONS</literal> defaults to on if
- <productname>PostgreSQL</productname> has been built this way.
+ <productname>PostgreSQL</productname> has been built with
+ assertions enabled.
</para>
</listitem>
</varlistentry>
<!--
-$Header: /cvsroot/pgsql/doc/src/sgml/xfunc.sgml,v 1.58 2002/08/29 17:14:32 tgl Exp $
+$Header: /cvsroot/pgsql/doc/src/sgml/xfunc.sgml,v 1.59 2002/08/30 00:28:40 tgl Exp $
-->
<chapter id="xfunc">
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;
*
*
* 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 $
*
*-------------------------------------------------------------------------
*/
#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 */
* 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,
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;
}
/*
}
+/*
+ * 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.
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
*
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
*
*
* 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 $
*
*-------------------------------------------------------------------------
*/
*/
#include "postgres.h"
-#include "miscadmin.h"
#include "access/heapam.h"
#include "catalog/pg_type.h"
#include "executor/execdebug.h"
#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);
/* ----------------------------------------------------------------
tuplestorestate = scanstate->tuplestorestate;
/*
- * If first time through, read all tuples from function and pass them to
- * tuplestore.c. Subsequent calls just fetch tuples from tuplestore.
+ * If first time through, read all tuples from function and put them
+ * in a tuplestore. Subsequent calls just fetch tuples from tuplestore.
*/
if (tuplestorestate == NULL)
{
- /*
- * Initialize tuplestore module.
- */
- tuplestorestate = tuplestore_begin_heap(true, /* randomAccess */
- SortMem);
-
- scanstate->tuplestorestate = (void *) tuplestorestate;
-
- /*
- * Compute all the function tuples and pass to tuplestore.
- */
- for (;;)
- {
- bool isNull;
- ExprDoneCond isDone;
-
- isNull = false;
- isDone = ExprSingleResult;
- slot = function_getonetuple(scanstate, &isNull, &isDone);
- if (TupIsNull(slot))
- break;
-
- tuplestore_puttuple(tuplestorestate, (void *) slot->val);
- ExecClearTuple(slot);
+ ExprContext *econtext = scanstate->csstate.cstate.cs_ExprContext;
+ TupleDesc funcTupdesc;
- if (isDone != ExprMultipleResult)
- break;
- }
+ scanstate->tuplestorestate = tuplestorestate =
+ ExecMakeTableFunctionResult((Expr *) scanstate->funcexpr,
+ econtext,
+ &funcTupdesc);
/*
- * Complete the store.
+ * If function provided a tupdesc, cross-check it. We only really
+ * need to do this for functions returning RECORD, but might as well
+ * do it always.
*/
- tuplestore_donestoring(tuplestorestate);
+ if (funcTupdesc &&
+ tupledesc_mismatch(scanstate->tupdesc, funcTupdesc))
+ elog(ERROR, "Query-specified return tuple and actual function return tuple do not match");
}
/*
* Get the next tuple from tuplestore. Return NULL if no more tuples.
*/
slot = scanstate->csstate.css_ScanTupleSlot;
- heapTuple = tuplestore_getheaptuple(tuplestorestate,
- ScanDirectionIsForward(direction),
- &should_free);
+ if (tuplestorestate)
+ heapTuple = tuplestore_getheaptuple(tuplestorestate,
+ ScanDirectionIsForward(direction),
+ &should_free);
+ else
+ {
+ heapTuple = NULL;
+ should_free = false;
+ }
return ExecStoreTuple(heapTuple, slot, InvalidBuffer, should_free);
}
rel = relation_open(funcrelid, AccessShareLock);
tupdesc = CreateTupleDescCopy(RelationGetDescr(rel));
relation_close(rel, AccessShareLock);
- scanstate->returnsTuple = true;
}
else if (functyptype == 'b' || functyptype == 'd')
{
-1,
0,
false);
- scanstate->returnsTuple = false;
}
else if (functyptype == 'p' && funcrettype == RECORDOID)
{
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);
scanstate->tuplestorestate = NULL;
scanstate->funcexpr = rte->funcexpr;
- scanstate->functionmode = get_functionmode(rte->funcexpr);
-
scanstate->csstate.cstate.cs_TupFromTlist = false;
/*
* Release tuplestore resources
*/
if (scanstate->tuplestorestate != NULL)
- tuplestore_end((Tuplestorestate *) scanstate->tuplestorestate);
+ tuplestore_end(scanstate->tuplestorestate);
scanstate->tuplestorestate = NULL;
}
if (!scanstate->tuplestorestate)
return;
- tuplestore_markpos((Tuplestorestate *) scanstate->tuplestorestate);
+ tuplestore_markpos(scanstate->tuplestorestate);
}
/* ----------------------------------------------------------------
if (!scanstate->tuplestorestate)
return;
- tuplestore_restorepos((Tuplestorestate *) scanstate->tuplestorestate);
+ tuplestore_restorepos(scanstate->tuplestorestate);
}
/* ----------------------------------------------------------------
*/
if (node->scan.plan.chgParam != NULL)
{
- tuplestore_end((Tuplestorestate *) scanstate->tuplestorestate);
+ tuplestore_end(scanstate->tuplestorestate);
scanstate->tuplestorestate = NULL;
}
else
- tuplestore_rescan((Tuplestorestate *) scanstate->tuplestorestate);
-}
-
-/*
- * Run the underlying function to get the next tuple
- */
-static TupleTableSlot *
-function_getonetuple(FunctionScanState *scanstate,
- bool *isNull,
- ExprDoneCond *isDone)
-{
- HeapTuple tuple;
- Datum retDatum;
- char nullflag;
- TupleDesc tupdesc = scanstate->tupdesc;
- bool returnsTuple = scanstate->returnsTuple;
- Node *expr = scanstate->funcexpr;
- Oid fn_typeid = scanstate->fn_typeid;
- char fn_typtype = scanstate->fn_typtype;
- ExprContext *econtext = scanstate->csstate.cstate.cs_ExprContext;
- TupleTableSlot *slot = scanstate->csstate.css_ScanTupleSlot;
-
- /*
- * reset per-tuple memory context before each call of the function.
- * This cleans up any local memory the function may leak when called.
- */
- ResetExprContext(econtext);
-
- /*
- * get the next Datum from the function
- */
- retDatum = ExecEvalExprSwitchContext(expr, econtext, isNull, isDone);
-
- /*
- * check to see if we're really done
- */
- if (*isDone == ExprEndResult)
- slot = NULL;
- else
- {
- if (returnsTuple)
- {
- /*
- * Composite data type, i.e. a table's row type
- * function returns pointer to tts??
- */
- slot = (TupleTableSlot *) retDatum;
-
- /*
- * if function return type was RECORD, we need to check to be
- * sure the structure from the query matches the actual return
- * structure
- */
- if (fn_typtype == 'p' && fn_typeid == RECORDOID)
- if (tupledesc_mismatch(tupdesc, slot->ttc_tupleDescriptor))
- elog(ERROR, "Query-specified return tuple and actual function return tuple do not match");
- }
- else
- {
- /*
- * Must be a base data type, i.e. scalar
- * turn it into a tuple
- */
- nullflag = *isNull ? 'n' : ' ';
- tuple = heap_formtuple(tupdesc, &retDatum, &nullflag);
-
- /*
- * save the tuple in the scan tuple slot and return the slot.
- */
- slot = ExecStoreTuple(tuple, /* tuple to store */
- slot, /* slot to store in */
- InvalidBuffer, /* buffer associated with
- * this tuple */
- true); /* pfree this tuple */
- }
- }
-
- return slot;
+ tuplestore_rescan(scanstate->tuplestorestate);
}
-static FunctionMode
-get_functionmode(Node *expr)
-{
- /*
- * for the moment, hardwire this
- */
- return PM_REPEATEDCALL;
-}
static bool
tupledesc_mismatch(TupleDesc tupdesc1, TupleDesc tupdesc2)
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
-----------------------------
* 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 $
*
*-------------------------------------------------------------------------
*/
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,
* 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 $
*
*-------------------------------------------------------------------------
*/
* 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;
* 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 $
*
*-------------------------------------------------------------------------
*/
#include "fmgr.h"
#include "nodes/params.h"
#include "nodes/primnodes.h"
+#include "utils/tuplestore.h"
+
/* ----------------
* IndexInfo information
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;
/* ----------------
* 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;
/* ----------------------------------------------------------------
#
# 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 $
#
#-------------------------------------------------------------------------
$(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
* 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.
*
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);
%}
%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
%token K_IS
%token K_LOG
%token K_LOOP
+%token K_NEXT
%token K_NOT
%token K_NOTICE
%token K_NULL
%token K_RENAME
%token K_RESULT_OID
%token K_RETURN
+%token K_RETURN_NEXT
%token K_REVERSE
%token K_SELECT
%token K_THEN
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)
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);
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);
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);
}
{ $$ = $1; }
| stmt_return
{ $$ = $1; }
+ | stmt_return_next
+ { $$ = $1; }
| stmt_raise
{ $$ = $1; }
| stmt_execsql
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;
}
}
| K_DEBUG
{
- $$ = DEBUG5;
+ $$ = DEBUG1;
}
;
cp += strlen(cp) - 1;
if (*cp != ')')
- {
- plpgsql_error_lineno = yylineno;
- elog(ERROR, "missing )");
- }
+ yyerror("missing )");
*cp = '\0';
}
else
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;
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;
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));
/* 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;
}
}
* 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.
*
#include "plpgsql.h"
-#include <unistd.h>
-#include <fcntl.h>
#include <ctype.h>
#include <setjmp.h>
#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"
typeStruct = (Form_pg_type) GETSTRUCT(typeTup);
/* Disallow pseudotype result, except VOID */
+ /* XXX someday allow RECORD? */
if (typeStruct->typtype == 'p')
{
if (procStruct->prorettype == VOIDOID)
* 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.
*
*
**********************************************************************/
-#include <stdio.h>
-#include <stdlib.h>
-#include <stdarg.h>
-#include <unistd.h>
-#include <fcntl.h>
-#include <string.h>
#include <ctype.h>
#include <setjmp.h>
#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"
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,
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,
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);
/* ----------
/*
* 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
* 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)
{
/*
* Setup the execution state
*/
- plpgsql_estate_setup(&estate, func);
+ plpgsql_estate_setup(&estate, func, NULL);
/*
* Make local execution copies of all the datums
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.
save_estmt = error_info_stmt;
error_info_stmt = stmt;
+ CHECK_FOR_INTERRUPTS();
+
switch (stmt->cmd_type)
{
case PLPGSQL_STMT_BLOCK:
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;
*/
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
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 */
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
/* ----------
- * 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;
*/
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
* 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.
*
*
**********************************************************************/
-#include <stdio.h>
-#include <stdlib.h>
-#include <stdarg.h>
-#include <unistd.h>
-#include <fcntl.h>
-#include <string.h>
#include <ctype.h>
#include "plpgsql.h"
return ns->items[i];
}
if (ns_localmode)
- {
return NULL; /* name not found in current namespace */
- }
}
return NULL;
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:
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);
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;
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)
{
* 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.
*
*
**********************************************************************/
-#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"
* 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.
*
#include "postgres.h"
#include "fmgr.h"
+#include "miscadmin.h"
#include "executor/spi.h"
#include "commands/trigger.h"
+#include "utils/tuplestore.h"
/**********************************************************************
* Definitions
PLPGSQL_STMT_SELECT,
PLPGSQL_STMT_EXIT,
PLPGSQL_STMT_RETURN,
+ PLPGSQL_STMT_RETURN_NEXT,
PLPGSQL_STMT_RAISE,
PLPGSQL_STMT_EXECSQL,
PLPGSQL_STMT_DYNEXECUTE,
{ /* 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 */
{ /* 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;
* 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.
*
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;
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; }
}
/*
- * This is the yylex routine called from outside. It exists to provide
- * a token pushback facility.
+ * This is the yylex routine called from outside. It exists to provide
+ * a pushback facility, as well as to allow us to parse syntax that
+ * requires more than one token of lookahead.
*/
int
plpgsql_yylex(void)
{
+ int cur_token;
+
if (have_pushback_token)
{
have_pushback_token = false;
- return pushback_token;
+ cur_token = pushback_token;
+ }
+ else if (have_lookahead_token)
+ {
+ have_lookahead_token = false;
+ cur_token = lookahead_token;
+ }
+ else
+ cur_token = yylex();
+
+ /* Do we need to look ahead for a possible multiword token? */
+ switch (cur_token)
+ {
+ /* RETURN NEXT must be reduced to a single token */
+ case K_RETURN:
+ if (!have_lookahead_token)
+ {
+ lookahead_token = yylex();
+ have_lookahead_token = true;
+ }
+ if (lookahead_token == K_NEXT)
+ {
+ have_lookahead_token = false;
+ cur_token = K_RETURN_NEXT;
+ }
+ break;
+
+ default:
+ break;
}
- return yylex();
+
+ return cur_token;
}
/*
scanner_typereported = 0;
have_pushback_token = false;
+ have_lookahead_token = false;
}
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 '
-- 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
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)
+
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
--
--
CREATE TABLE found_test_tbl (a int);
-create function test_found ()
+create function test_found()
returns boolean as '
declare
begin
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);