]> granicus.if.org Git - postgresql/commitdiff
PL/pgSQL functions can return sets. Neil Conway's patch, modified so
authorTom Lane <tgl@sss.pgh.pa.us>
Fri, 30 Aug 2002 00:28:41 +0000 (00:28 +0000)
committerTom Lane <tgl@sss.pgh.pa.us>
Fri, 30 Aug 2002 00:28:41 +0000 (00:28 +0000)
that the functionality is available to anyone via ReturnSetInfo, rather
than hard-wiring it to PL/pgSQL.

21 files changed:
contrib/intagg/int_aggregate.c
doc/src/sgml/plpgsql.sgml
doc/src/sgml/release.sgml
doc/src/sgml/runtime.sgml
doc/src/sgml/xfunc.sgml
src/backend/executor/execQual.c
src/backend/executor/nodeFunctionscan.c
src/backend/utils/fmgr/README
src/include/executor/executor.h
src/include/fmgr.h
src/include/nodes/execnodes.h
src/pl/plpgsql/src/Makefile
src/pl/plpgsql/src/gram.y
src/pl/plpgsql/src/pl_comp.c
src/pl/plpgsql/src/pl_exec.c
src/pl/plpgsql/src/pl_funcs.c
src/pl/plpgsql/src/pl_handler.c
src/pl/plpgsql/src/plpgsql.h
src/pl/plpgsql/src/scan.l
src/test/regress/expected/plpgsql.out
src/test/regress/sql/plpgsql.sql

index 3801a3d91a48a32f36374ed9930a9b980039a3ac..4a2e6afdc6bd853988e9c46a0034a06eae06653f 100644 (file)
 #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();
                        }
                }
index acdd8a4f4dcc32c8aced2778371056362d2993fd..4da3f3c04050897164ae4f339a03d928f2323311 100644 (file)
@@ -1,5 +1,5 @@
 <!--
-$Header: /cvsroot/pgsql/doc/src/sgml/plpgsql.sgml,v 1.4 2002/08/29 04:12:02 tgl Exp $
+$Header: /cvsroot/pgsql/doc/src/sgml/plpgsql.sgml,v 1.5 2002/08/30 00:28:40 tgl Exp $
 -->
 
 <chapter id="plpgsql"> 
@@ -1142,11 +1142,20 @@ GET DIAGNOSTICS <replaceable>variable</replaceable> = <replaceable>item</replace
 RETURN <replaceable>expression</replaceable>;
 </synopsis>
 
+     RETURN with an expression is used to return from a
+     <application>PL/pgSQL</> function that does not return a set.
      The function terminates and the value of
-     <replaceable>expression</replaceable> will be returned to the
-     upper executor.
+     <replaceable>expression</replaceable> is returned to the caller.
+    </para>
+
+    <para>
+     To return a composite (row) value, you must write a record or row
+     variable as the <replaceable>expression</replaceable>.  When
+     returning a scalar type, any expression can be used.
      The expression's result will be automatically cast into the
      function's return type as described for assignments.
+     (If you have declared the function to return <type>void</>,
+     then the expression can be omitted, and will be ignored in any case.)
     </para>
 
     <para>
@@ -1155,6 +1164,28 @@ RETURN <replaceable>expression</replaceable>;
      the function without hitting a RETURN statement, a run-time error
      will occur.
     </para>
+
+    <para>
+     When a <application>PL/pgSQL</> function is declared to return
+     <literal>SETOF</literal> <replaceable>sometype</>, the procedure
+     to follow is slightly different.  The individual items to be returned
+     are specified in RETURN NEXT commands, and then a final RETURN with
+     no argument is given to indicate that the function is done generating
+     items.
+
+<synopsis>
+RETURN NEXT <replaceable>expression</replaceable>;
+</synopsis>
+
+     RETURN NEXT does not actually return from the function; it simply
+     saves away the value of the expression (or record or row variable,
+     as appropriate for the datatype being returned).
+     Execution then continues with the next statement in the
+     <application>PL/pgSQL</> function.  As successive RETURN NEXT
+     commands are executed, the result set is built up.  A final
+     RETURN, which need have no argument, causes control to exit
+     the function.
+    </para>
    </sect2>
     
    <sect2 id="plpgsql-conditionals">
@@ -1531,8 +1562,8 @@ END LOOP;
     to worry about that, since FOR loops automatically use a cursor
     internally to avoid memory problems.) A more interesting usage is to
     return a reference to a cursor that it has created, allowing the
-    caller to read the rows. This provides a way to return row sets
-    from functions.
+    caller to read the rows. This provides an efficient way to return
+    large row sets from functions.
    </para>
    
    <sect2 id="plpgsql-cursor-declarations">
@@ -1794,19 +1825,27 @@ COMMIT;
 RAISE <replaceable class="parameter">level</replaceable> '<replaceable class="parameter">format</replaceable>' <optional>, <replaceable class="parameter">variable</replaceable> <optional>...</optional></optional>;
 </synopsis>
 
-    Possible levels are DEBUG (write the message into the postmaster log),
-    NOTICE (write the message into the postmaster log and forward it to
-    the client application) and EXCEPTION (raise an error,
-    aborting the transaction).
+    Possible levels are <literal>DEBUG</literal> (write the message to
+    the server log), <literal>LOG</literal> (write the message to the
+    server log with a higher priority), <literal>INFO</literal>,
+    <literal>NOTICE</literal> and <literal>WARNING</literal> (write
+    the message to the server log and send it to the client, with
+    respectively higher priorities), and <literal>EXCEPTION</literal>
+    (raise an error and abort the current transaction). Whether error
+    messages of a particular priority are reported to the client,
+    written to the server log, or both is controlled by the
+    <option>SERVER_MIN_MESSAGES</option> and
+    <option>CLIENT_MIN_MESSAGES</option> configuration variables. See
+    the <citetitle>PostgreSQL Administrator's Guide</citetitle> for more
+    information.
    </para>
 
    <para>
-    Inside the format string, <literal>%</literal> is replaced by the next
-    optional argument's external representation.
-    Write <literal>%%</literal> to emit a literal <literal>%</literal>.
-    Note that the optional arguments must presently
-    be simple variables, not expressions, and the format must be a simple
-    string literal.
+    Inside the format string, <literal>%</literal> is replaced by the
+    next optional argument's external representation. Write
+    <literal>%%</literal> to emit a literal <literal>%</literal>. Note
+    that the optional arguments must presently be simple variables,
+    not expressions, and the format must be a simple string literal.
    </para>
 
    <!--
@@ -1820,8 +1859,9 @@ RAISE <replaceable class="parameter">level</replaceable> '<replaceable class="pa
 <programlisting>
 RAISE NOTICE ''Calling cs_create_job(%)'',v_job_id;
 </programlisting>
-    In this example, the value of v_job_id will replace the % in the
-    string.
+
+    In this example, the value of v_job_id will replace the
+    <literal>%</literal> in the string.
    </para>
 
    <para>
@@ -1852,12 +1892,12 @@ RAISE EXCEPTION ''Inexistent ID --> %'',user_id;
     </para>
 
     <para>
-     Thus, the only thing <application>PL/pgSQL</application> currently does when it encounters
-     an abort during execution of a function or trigger
-     procedure is to write some additional NOTICE level log messages
-     telling in which function and where (line number and type of
-     statement) this happened.  The error always stops execution of
-     the function.
+     Thus, the only thing <application>PL/pgSQL</application>
+     currently does when it encounters an abort during execution of a
+     function or trigger procedure is to write some additional
+     <literal>NOTICE</literal> level log messages telling in which
+     function and where (line number and type of statement) this
+     happened.  The error always stops execution of the function.
     </para>
   </sect2>
  </sect1>
index 50df580404c993ac2bb73bfdaf9cace6efdf2df3..78606b68e94e3264e1faa9659370d517885c4769 100644 (file)
@@ -1,5 +1,5 @@
 <!--
-$Header: /cvsroot/pgsql/doc/src/sgml/release.sgml,v 1.154 2002/08/29 03:22:00 tgl Exp $
+$Header: /cvsroot/pgsql/doc/src/sgml/release.sgml,v 1.155 2002/08/30 00:28:40 tgl Exp $
 -->
 
 <appendix id="release">
@@ -24,6 +24,7 @@ CDATA means the content is "SGML-free", so you can write without
 worries about funny characters.
 -->
 <literallayout><![CDATA[
+Substantial improvements in functionality for functions returning sets
 Client libraries older than 6.3 no longer supported (version 0 protocol removed)
 PREPARE statement allows caching query plans for interactive statements
 Type OPAQUE is now deprecated in favor of pseudo-types cstring, trigger, etc
index fd96883e1891c471e33cf8b6be33be6fe36e276d..afb7659af55b5a504ef76b65870824e5fe7602d6 100644 (file)
@@ -1,5 +1,5 @@
 <!--
-$Header: /cvsroot/pgsql/doc/src/sgml/runtime.sgml,v 1.128 2002/08/29 19:53:58 momjian Exp $
+$Header: /cvsroot/pgsql/doc/src/sgml/runtime.sgml,v 1.129 2002/08/30 00:28:40 tgl Exp $
 -->
 
 <Chapter Id="runtime">
@@ -921,7 +921,8 @@ env PGOPTIONS='-c geqo=off' psql
         built (see the configure option
         <literal>--enable-cassert</literal>). Note that
         <literal>DEBUG_ASSERTIONS</literal> defaults to on if
-        <productname>PostgreSQL</productname> has been built this way.
+        <productname>PostgreSQL</productname> has been built with
+       assertions enabled.
        </para>
       </listitem>
      </varlistentry>
index fad7ad888d8931c2e440cf3cb2ac7bfc565783e1..9a7b79f0ddd4f33f4132155dbaaa28468663150e 100644 (file)
@@ -1,5 +1,5 @@
 <!--
-$Header: /cvsroot/pgsql/doc/src/sgml/xfunc.sgml,v 1.58 2002/08/29 17:14:32 tgl Exp $
+$Header: /cvsroot/pgsql/doc/src/sgml/xfunc.sgml,v 1.59 2002/08/30 00:28:40 tgl Exp $
 -->
 
  <chapter id="xfunc">
@@ -315,9 +315,7 @@ ERROR:  function declared to return emp returns varchar instead of text at colum
      function, as described below.  It can also be called in the context
      of an SQL expression, but only when you
      extract a single attribute out of the row or pass the entire row into
-     another function that accepts the same composite type.  (Trying to
-     display the entire row value will yield 
-     a meaningless number.)  For example,
+     another function that accepts the same composite type.  For example,
 
 <programlisting>
 SELECT (new_emp()).name;
index e481ea115969c4be53e6d0b1999096d5f7ec2246..b422adc2061ca2706569720956bad0b46b6a4e6c 100644 (file)
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *       $Header: /cvsroot/pgsql/src/backend/executor/execQual.c,v 1.101 2002/08/26 17:53:57 tgl Exp $
+ *       $Header: /cvsroot/pgsql/src/backend/executor/execQual.c,v 1.102 2002/08/30 00:28:41 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
 #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
index d58d312238e66d4fd8c99a45cf9d5630ebfd54e8..3d2c160fb4fa9517fd4c8c304e4d2280c28f23f4 100644 (file)
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *       $Header: /cvsroot/pgsql/src/backend/executor/nodeFunctionscan.c,v 1.7 2002/08/29 17:14:33 tgl Exp $
+ *       $Header: /cvsroot/pgsql/src/backend/executor/nodeFunctionscan.c,v 1.8 2002/08/30 00:28:41 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -22,7 +22,6 @@
  */
 #include "postgres.h"
 
-#include "miscadmin.h"
 #include "access/heapam.h"
 #include "catalog/pg_type.h"
 #include "executor/execdebug.h"
 #include "parser/parsetree.h"
 #include "parser/parse_expr.h"
 #include "parser/parse_type.h"
-#include "storage/lmgr.h"
-#include "tcop/pquery.h"
 #include "utils/lsyscache.h"
-#include "utils/syscache.h"
-#include "utils/tuplestore.h"
+
 
 static TupleTableSlot *FunctionNext(FunctionScan *node);
-static TupleTableSlot *function_getonetuple(FunctionScanState *scanstate,
-                                                                                       bool *isNull,
-                                                                                       ExprDoneCond *isDone);
-static FunctionMode get_functionmode(Node *expr);
 static bool tupledesc_mismatch(TupleDesc tupdesc1, TupleDesc tupdesc2);
 
 /* ----------------------------------------------------------------
@@ -76,53 +68,42 @@ FunctionNext(FunctionScan *node)
        tuplestorestate = scanstate->tuplestorestate;
 
        /*
-        * If first time through, read all tuples from function and pass them to
-        * tuplestore.c. Subsequent calls just fetch tuples from tuplestore.
+        * If first time through, read all tuples from function and put them
+        * in a tuplestore. Subsequent calls just fetch tuples from tuplestore.
         */
        if (tuplestorestate == NULL)
        {
-               /*
-                * Initialize tuplestore module.
-                */
-               tuplestorestate = tuplestore_begin_heap(true,   /* randomAccess */
-                                                                                               SortMem);
-
-               scanstate->tuplestorestate = (void *) tuplestorestate;
-
-               /*
-                * Compute all the function tuples and pass to tuplestore.
-                */
-               for (;;)
-               {
-                       bool                            isNull;
-                       ExprDoneCond            isDone;
-
-                       isNull = false;
-                       isDone = ExprSingleResult;
-                       slot = function_getonetuple(scanstate, &isNull, &isDone);
-                       if (TupIsNull(slot))
-                               break;
-
-                       tuplestore_puttuple(tuplestorestate, (void *) slot->val);
-                       ExecClearTuple(slot);
+               ExprContext        *econtext = scanstate->csstate.cstate.cs_ExprContext;
+               TupleDesc               funcTupdesc;
 
-                       if (isDone != ExprMultipleResult)
-                               break;
-               }
+               scanstate->tuplestorestate = tuplestorestate =
+                       ExecMakeTableFunctionResult((Expr *) scanstate->funcexpr,
+                                                                               econtext,
+                                                                               &funcTupdesc);
 
                /*
-                * Complete the store.
+                * If function provided a tupdesc, cross-check it.  We only really
+                * need to do this for functions returning RECORD, but might as well
+                * do it always.
                 */
-               tuplestore_donestoring(tuplestorestate);
+               if (funcTupdesc &&
+                       tupledesc_mismatch(scanstate->tupdesc, funcTupdesc))
+                       elog(ERROR, "Query-specified return tuple and actual function return tuple do not match");
        }
 
        /*
         * Get the next tuple from tuplestore. Return NULL if no more tuples.
         */
        slot = scanstate->csstate.css_ScanTupleSlot;
-       heapTuple = tuplestore_getheaptuple(tuplestorestate,
-                                                                               ScanDirectionIsForward(direction),
-                                                                               &should_free);
+       if (tuplestorestate)
+               heapTuple = tuplestore_getheaptuple(tuplestorestate,
+                                                                                       ScanDirectionIsForward(direction),
+                                                                                       &should_free);
+       else
+       {
+               heapTuple = NULL;
+               should_free = false;
+       }
 
        return ExecStoreTuple(heapTuple, slot, InvalidBuffer, should_free);
 }
@@ -219,7 +200,6 @@ ExecInitFunctionScan(FunctionScan *node, EState *estate, Plan *parent)
                rel = relation_open(funcrelid, AccessShareLock);
                tupdesc = CreateTupleDescCopy(RelationGetDescr(rel));
                relation_close(rel, AccessShareLock);
-               scanstate->returnsTuple = true;
        }
        else if (functyptype == 'b' || functyptype == 'd')
        {
@@ -236,7 +216,6 @@ ExecInitFunctionScan(FunctionScan *node, EState *estate, Plan *parent)
                                                   -1,
                                                   0,
                                                   false);
-               scanstate->returnsTuple = false;
        }
        else if (functyptype == 'p' && funcrettype == RECORDOID)
        {
@@ -246,13 +225,10 @@ ExecInitFunctionScan(FunctionScan *node, EState *estate, Plan *parent)
                List *coldeflist = rte->coldeflist;
 
                tupdesc = BuildDescForRelation(coldeflist);
-               scanstate->returnsTuple = true;
        }
        else
                elog(ERROR, "Unknown kind of return type specified for function");
 
-       scanstate->fn_typeid = funcrettype;
-       scanstate->fn_typtype = functyptype;
        scanstate->tupdesc = tupdesc;
        ExecSetSlotDescriptor(scanstate->csstate.css_ScanTupleSlot,
                                                  tupdesc, false);
@@ -263,8 +239,6 @@ ExecInitFunctionScan(FunctionScan *node, EState *estate, Plan *parent)
        scanstate->tuplestorestate = NULL;
        scanstate->funcexpr = rte->funcexpr;
 
-       scanstate->functionmode = get_functionmode(rte->funcexpr);
-
        scanstate->csstate.cstate.cs_TupFromTlist = false;
 
        /*
@@ -322,7 +296,7 @@ ExecEndFunctionScan(FunctionScan *node)
         * Release tuplestore resources
         */
        if (scanstate->tuplestorestate != NULL)
-               tuplestore_end((Tuplestorestate *) scanstate->tuplestorestate);
+               tuplestore_end(scanstate->tuplestorestate);
        scanstate->tuplestorestate = NULL;
 }
 
@@ -345,7 +319,7 @@ ExecFunctionMarkPos(FunctionScan *node)
        if (!scanstate->tuplestorestate)
                return;
 
-       tuplestore_markpos((Tuplestorestate *) scanstate->tuplestorestate);
+       tuplestore_markpos(scanstate->tuplestorestate);
 }
 
 /* ----------------------------------------------------------------
@@ -367,7 +341,7 @@ ExecFunctionRestrPos(FunctionScan *node)
        if (!scanstate->tuplestorestate)
                return;
 
-       tuplestore_restorepos((Tuplestorestate *) scanstate->tuplestorestate);
+       tuplestore_restorepos(scanstate->tuplestorestate);
 }
 
 /* ----------------------------------------------------------------
@@ -402,98 +376,13 @@ ExecFunctionReScan(FunctionScan *node, ExprContext *exprCtxt, Plan *parent)
         */
        if (node->scan.plan.chgParam != NULL)
        {
-               tuplestore_end((Tuplestorestate *) scanstate->tuplestorestate);
+               tuplestore_end(scanstate->tuplestorestate);
                scanstate->tuplestorestate = NULL;
        }
        else
-               tuplestore_rescan((Tuplestorestate *) scanstate->tuplestorestate);
-}
-
-/*
- * Run the underlying function to get the next tuple
- */
-static TupleTableSlot *
-function_getonetuple(FunctionScanState *scanstate,
-                                        bool *isNull,
-                                        ExprDoneCond *isDone)
-{
-       HeapTuple               tuple;
-       Datum                   retDatum;
-       char                    nullflag;
-       TupleDesc               tupdesc = scanstate->tupdesc;
-       bool                    returnsTuple = scanstate->returnsTuple;
-       Node               *expr = scanstate->funcexpr;
-       Oid                             fn_typeid = scanstate->fn_typeid;
-       char                    fn_typtype = scanstate->fn_typtype;
-       ExprContext        *econtext = scanstate->csstate.cstate.cs_ExprContext;
-       TupleTableSlot *slot = scanstate->csstate.css_ScanTupleSlot;
-
-       /*
-        * reset per-tuple memory context before each call of the function.
-        * This cleans up any local memory the function may leak when called.
-        */
-       ResetExprContext(econtext);
-
-       /*
-        * get the next Datum from the function
-        */
-       retDatum = ExecEvalExprSwitchContext(expr, econtext, isNull, isDone);
-
-       /*
-        * check to see if we're really done
-        */
-       if (*isDone == ExprEndResult)
-               slot = NULL;
-       else
-       {
-               if (returnsTuple)
-               {
-                       /*
-                        * Composite data type, i.e. a table's row type
-                        * function returns pointer to tts??
-                        */
-                       slot = (TupleTableSlot *) retDatum;
-
-                       /*
-                        * if function return type was RECORD, we need to check to be
-                        * sure the structure from the query matches the actual return
-                        * structure
-                        */
-                       if (fn_typtype == 'p' && fn_typeid == RECORDOID)
-                               if (tupledesc_mismatch(tupdesc, slot->ttc_tupleDescriptor))
-                                       elog(ERROR, "Query-specified return tuple and actual function return tuple do not match");
-               }
-               else
-               {
-                       /*
-                        * Must be a base data type, i.e. scalar
-                        * turn it into a tuple
-                        */
-                       nullflag = *isNull ? 'n' : ' ';
-                       tuple = heap_formtuple(tupdesc, &retDatum, &nullflag);
-
-                       /*
-                        * save the tuple in the scan tuple slot and return the slot.
-                        */
-                       slot = ExecStoreTuple(tuple,                    /* tuple to store */
-                                                                 slot,                         /* slot to store in */
-                                                                 InvalidBuffer,        /* buffer associated with
-                                                                                                        * this tuple */
-                                                                 true);                        /* pfree this tuple */
-               }
-       }
-
-       return slot;
+               tuplestore_rescan(scanstate->tuplestorestate);
 }
 
-static FunctionMode
-get_functionmode(Node *expr)
-{
-       /*
-        * for the moment, hardwire this
-        */
-       return PM_REPEATEDCALL;
-}
 
 static bool
 tupledesc_mismatch(TupleDesc tupdesc1, TupleDesc tupdesc2)
index 043a3a7e8d918d5f56a0d7557395700a9f2ee5ad..98d4e7bd51a88bfb0f465003cfb10743e1817ba5 100644 (file)
@@ -371,36 +371,61 @@ tuple toaster will decide whether toasting is needed.
 Functions accepting or returning sets
 -------------------------------------
 
-As of 7.1, Postgres has limited support for functions returning sets;
-this is presently handled only in SELECT output expressions, and the
-behavior is to generate a separate output tuple for each set element.
-There is no direct support for functions accepting sets; instead, the
-function will be called multiple times, once for each element of the
-input set.  This behavior will very likely be changed in future releases,
-but here is how it works now:
+[ this section revised 29-Aug-2002 for 7.3 ]
 
 If a function is marked in pg_proc as returning a set, then it is called
 with fcinfo->resultinfo pointing to a node of type ReturnSetInfo.  A
 function that desires to return a set should raise an error "called in
 context that does not accept a set result" if resultinfo is NULL or does
-not point to a ReturnSetInfo node.  ReturnSetInfo contains a field
+not point to a ReturnSetInfo node.
+
+There are currently two modes in which a function can return a set result:
+value-per-call, or materialize.  In value-per-call mode, the function returns
+one value each time it is called, and finally reports "done" when it has no
+more values to return.  In materialize mode, the function's output set is
+instantiated in a Tuplestore object; all the values are returned in one call.
+Additional modes might be added in future.
+
+ReturnSetInfo contains a field "allowedModes" which is set (by the caller)
+to a bitmask that's the OR of the modes the caller can support.  The actual
+mode used by the function is returned in another field "returnMode".  For
+backwards-compatibility reasons, returnMode is initialized to value-per-call
+and need only be changed if the function wants to use a different mode.
+The function should elog() if it cannot use any of the modes the caller is
+willing to support.
+
+Value-per-call mode works like this: ReturnSetInfo contains a field
 "isDone", which should be set to one of these values:
 
     ExprSingleResult             /* expression does not return a set */
     ExprMultipleResult           /* this result is an element of a set */
     ExprEndResult                /* there are no more elements in the set */
 
-A function returning set returns one set element per call, setting
-fcinfo->resultinfo->isDone to ExprMultipleResult for each element.
-After all elements have been returned, the next call should set
-isDone to ExprEndResult and return a null result.  (Note it is possible
-to return an empty set by doing this on the first call.)
+(the caller will initialize it to ExprSingleResult).  If the function simply
+returns a Datum without touching ReturnSetInfo, then the call is over and a
+single-item set has been returned.  To return a set, the function must set
+isDone to ExprMultipleResult for each set element.  After all elements have
+been returned, the next call should set isDone to ExprEndResult and return a
+null result.  (Note it is possible to return an empty set by doing this on
+the first call.)
 
-As of 7.3, the ReturnSetInfo node also contains a link to the ExprContext
-within which the function is being evaluated.  This is useful for functions
+The ReturnSetInfo node also contains a link to the ExprContext within which
+the function is being evaluated.  This is useful for value-per-call functions
 that need to close down internal state when they are not run to completion:
 they can register a shutdown callback function in the ExprContext.
 
+Materialize mode works like this: the function creates a Tuplestore holding
+the (possibly empty) result set, and returns it.  There are no multiple calls.
+The function must also return a TupleDesc that indicates the tuple structure.
+The Tuplestore and TupleDesc should be created in the context
+econtext->ecxt_per_query_memory (note this will *not* be the context the
+function is called in).  The function stores pointers to the Tuplestore and
+TupleDesc into ReturnSetInfo, sets returnMode to indicate materialize mode,
+and returns null.  isDone is not used and should be left at ExprSingleResult.
+
+There is no support for functions accepting sets; instead, the function will
+be called multiple times, once for each element of the input set.
+
 
 Notes about function handlers
 -----------------------------
index 88104565976736d15467b93a144b912113675725..31a2b4a2399447611fbbfaad1823b6d624505d2c 100644 (file)
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $Id: executor.h,v 1.74 2002/08/29 00:17:06 tgl Exp $
+ * $Id: executor.h,v 1.75 2002/08/30 00:28:41 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -80,6 +80,9 @@ extern Datum ExecMakeFunctionResult(FunctionCachePtr fcache,
                                           ExprContext *econtext,
                                           bool *isNull,
                                           ExprDoneCond *isDone);
+extern Tuplestorestate *ExecMakeTableFunctionResult(Expr *funcexpr,
+                                                                                                       ExprContext *econtext,
+                                                                                                       TupleDesc *returnDesc);
 extern Datum ExecEvalExpr(Node *expression, ExprContext *econtext,
                         bool *isNull, ExprDoneCond *isDone);
 extern Datum ExecEvalExprSwitchContext(Node *expression, ExprContext *econtext,
index 74e90c930014a769ff396ee7381e65aa4d0f7aba..7b04a1d7058f64c6055a8b2769a5ddffe2445d76 100644 (file)
@@ -11,7 +11,7 @@
  * Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $Id: fmgr.h,v 1.22 2002/06/20 20:29:42 momjian Exp $
+ * $Id: fmgr.h,v 1.23 2002/08/30 00:28:41 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -45,8 +45,7 @@ typedef struct FmgrInfo
                                                                 * count */
        bool            fn_strict;              /* function is "strict" (NULL in => NULL
                                                                 * out) */
-       bool            fn_retset;              /* function returns a set (over multiple
-                                                                * calls) */
+       bool            fn_retset;              /* function returns a set */
        void       *fn_extra;           /* extra space for use by handler */
        MemoryContext fn_mcxt;          /* memory context to store fn_extra in */
 } FmgrInfo;
index 6e146e2ca6d67b6ca179c951b9c121b8ddb51ccb..3081a6842ebe8e418fabaf5e2aed4527ff47a0ec 100644 (file)
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $Id: execnodes.h,v 1.72 2002/08/29 00:17:06 tgl Exp $
+ * $Id: execnodes.h,v 1.73 2002/08/30 00:28:41 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -21,6 +21,8 @@
 #include "fmgr.h"
 #include "nodes/params.h"
 #include "nodes/primnodes.h"
+#include "utils/tuplestore.h"
+
 
 /* ----------------
  *       IndexInfo information
@@ -125,20 +127,32 @@ typedef enum
        ExprEndResult                           /* there are no more elements in the set */
 } ExprDoneCond;
 
+/*
+ * Return modes for functions returning sets.  Note values must be chosen
+ * as separate bits so that a bitmask can be formed to indicate supported
+ * modes.
+ */
+typedef enum
+{
+       SFRM_ValuePerCall = 0x01,       /* one value returned per call */
+       SFRM_Materialize = 0x02         /* result set instantiated in Tuplestore */
+} SetFunctionReturnMode;
+
 /*
  * When calling a function that might return a set (multiple rows),
  * a node of this type is passed as fcinfo->resultinfo to allow
  * return status to be passed back.  A function returning set should
- * raise an error if no such resultinfo is provided.  The ExprContext
- * in which the function is being called is also made available.
- *
- * XXX this mechanism is a quick hack and probably needs to be redesigned.
+ * raise an error if no such resultinfo is provided.
  */
 typedef struct ReturnSetInfo
 {
        NodeTag         type;
-       ExprDoneCond isDone;
-       ExprContext *econtext;
+       ExprContext *econtext;          /* context function is being called in */
+       int                     allowedModes;   /* bitmask: return modes caller can handle */
+       SetFunctionReturnMode   returnMode;     /* actual return mode */
+       ExprDoneCond isDone;            /* status for ValuePerCall mode */
+       Tuplestorestate *setResult;     /* return object for Materialize mode */
+       TupleDesc       setDesc;                /* descriptor for Materialize mode */
 } ReturnSetInfo;
 
 /* ----------------
@@ -509,32 +523,17 @@ typedef struct SubqueryScanState
  *             Function nodes are used to scan the results of a
  *             function appearing in FROM (typically a function returning set).
  *
- *             functionmode            function operating mode
- *             tupdesc                         function's return tuple description
+ *             tupdesc                         expected return tuple description
  *             tuplestorestate         private state of tuplestore.c
  *             funcexpr                        function expression being evaluated
- *             returnsTuple            does function return tuples?
- *             fn_typeid                       OID of function return type
- *             fn_typtype                      return type's typtype
  * ----------------
  */
-typedef enum FunctionMode
-{
-       PM_REPEATEDCALL,
-       PM_MATERIALIZE,
-       PM_QUERY
-} FunctionMode;
-
 typedef struct FunctionScanState
 {
        CommonScanState csstate;                /* its first field is NodeTag */
-       FunctionMode    functionmode;
        TupleDesc               tupdesc;
-       void               *tuplestorestate;
+       Tuplestorestate *tuplestorestate;
        Node               *funcexpr;
-       bool                    returnsTuple;
-       Oid                             fn_typeid;
-       char                    fn_typtype;
 } FunctionScanState;
 
 /* ----------------------------------------------------------------
index 88bd611402f18a7400c296c0471829a998ad69f3..cb5b6c21fa5845219d881cedafcd95e1421e731b 100644 (file)
@@ -2,7 +2,7 @@
 #
 # Makefile for the plpgsql shared object
 #
-# $Header: /cvsroot/pgsql/src/pl/plpgsql/src/Makefile,v 1.20 2001/11/16 16:32:33 petere Exp $
+# $Header: /cvsroot/pgsql/src/pl/plpgsql/src/Makefile,v 1.21 2002/08/30 00:28:41 tgl Exp $
 #
 #-------------------------------------------------------------------------
 
@@ -78,7 +78,7 @@ endif
 
 $(srcdir)/pl_scan.c: scan.l
 ifdef FLEX
-       $(FLEX) -i $(FLEXFLAGS) -Pplpgsql_base_yy -o'$@' $<
+       $(FLEX) $(FLEXFLAGS) -Pplpgsql_base_yy -o'$@' $<
 else
        @$(missing) flex $< $@
 endif
index 75d0a0b07a254dde16e529551d859bba19449775..47c8a9c19192e99972e0f8b2e2b1e1c7915d162c 100644 (file)
@@ -4,7 +4,7 @@
  *                                               procedural language
  *
  * IDENTIFICATION
- *       $Header: /cvsroot/pgsql/src/pl/plpgsql/src/gram.y,v 1.35 2002/08/28 20:46:24 momjian Exp $
+ *       $Header: /cvsroot/pgsql/src/pl/plpgsql/src/gram.y,v 1.36 2002/08/30 00:28:41 tgl Exp $
  *
  *       This software is copyrighted by Jan Wieck - Hamburg.
  *
@@ -48,7 +48,7 @@ static        PLpgSQL_type    *read_datatype(int tok);
 static PLpgSQL_stmt    *make_select_stmt(void);
 static PLpgSQL_stmt    *make_fetch_stmt(void);
 static PLpgSQL_expr    *make_tupret_expr(PLpgSQL_row *row);
-static void check_assignable(PLpgSQL_datum *datum);
+static void check_assignable(PLpgSQL_datum *datum);
 
 %}
 
@@ -121,8 +121,8 @@ static void check_assignable(PLpgSQL_datum *datum);
 %type <stmts>  proc_sect, proc_stmts, stmt_else, loop_body
 %type <stmt>   proc_stmt, pl_block
 %type <stmt>   stmt_assign, stmt_if, stmt_loop, stmt_while, stmt_exit
-%type <stmt>   stmt_return, stmt_raise, stmt_execsql, stmt_fori
-%type <stmt>   stmt_fors, stmt_select, stmt_perform
+%type <stmt>   stmt_return, stmt_return_next, stmt_raise, stmt_execsql
+%type <stmt>   stmt_fori, stmt_fors, stmt_select, stmt_perform
 %type <stmt>   stmt_dynexecute, stmt_dynfors, stmt_getdiag
 %type <stmt>   stmt_open, stmt_fetch, stmt_close
 
@@ -166,6 +166,7 @@ static void check_assignable(PLpgSQL_datum *datum);
 %token K_IS
 %token K_LOG
 %token K_LOOP
+%token K_NEXT
 %token K_NOT
 %token K_NOTICE
 %token K_NULL
@@ -177,6 +178,7 @@ static void check_assignable(PLpgSQL_datum *datum);
 %token K_RENAME
 %token K_RESULT_OID
 %token K_RETURN
+%token K_RETURN_NEXT
 %token K_REVERSE
 %token K_SELECT
 %token K_THEN
@@ -516,10 +518,8 @@ decl_aliasitem     : T_WORD
 
                                                plpgsql_convert_ident(yytext, &name, 1);
                                                if (name[0] != '$')
-                                               {
-                                                       plpgsql_error_lineno = yylineno;
-                                                       elog(ERROR, "can only alias positional parameters");
-                                               }
+                                                       yyerror("can only alias positional parameters");
+
                                                plpgsql_ns_setlocal(false);
                                                nsi = plpgsql_ns_lookup(name, NULL);
                                                if (nsi == NULL)
@@ -609,14 +609,11 @@ decl_defval               : ';'
                                                switch (tok)
                                                {
                                                        case 0:
-                                                               plpgsql_error_lineno = lno;
-                                                               elog(ERROR, "unexpected end of file");
+                                                               yyerror("unexpected end of file");
                                                        case K_NULL:
                                                                if (yylex() != ';')
-                                                               {
-                                                                       plpgsql_error_lineno = lno;
-                                                                       elog(ERROR, "expected ; after NULL");
-                                                               }
+                                                                       yyerror("expected ; after NULL");
+
                                                                free(expr);
                                                                plpgsql_dstring_free(&ds);
 
@@ -628,10 +625,8 @@ decl_defval                : ';'
                                                                while ((tok = yylex()) != ';')
                                                                {
                                                                        if (tok == 0)
-                                                                       {
-                                                                               plpgsql_error_lineno = lno;
-                                                                               elog(ERROR, "unterminated default value");
-                                                                       }
+                                                                               yyerror("unterminated default value");
+
                                                                        if (plpgsql_SpaceScanned)
                                                                                plpgsql_dstring_append(&ds, " ");
                                                                        plpgsql_dstring_append(&ds, yytext);
@@ -663,7 +658,8 @@ proc_sect           :
 
 proc_stmts             : proc_stmts proc_stmt
                                                {
-                                                               if ($1->stmts_used == $1->stmts_alloc) {
+                                                               if ($1->stmts_used == $1->stmts_alloc)
+                                                               {
                                                                        $1->stmts_alloc *= 2;
                                                                        $1->stmts = realloc($1->stmts, sizeof(PLpgSQL_stmt *) * $1->stmts_alloc);
                                                                }
@@ -708,6 +704,8 @@ proc_stmt           : pl_block ';'
                                                { $$ = $1; }
                                | stmt_return
                                                { $$ = $1; }
+                               | stmt_return_next
+                                               { $$ = $1; }
                                | stmt_raise
                                                { $$ = $1; }
                                | stmt_execsql
@@ -1121,45 +1119,73 @@ stmt_exit               : K_EXIT lno opt_exitlabel opt_exitcond
 stmt_return            : K_RETURN lno
                                        {
                                                PLpgSQL_stmt_return *new;
-                                               PLpgSQL_expr    *expr = NULL;
-                                               int                             tok;
 
                                                new = malloc(sizeof(PLpgSQL_stmt_return));
                                                memset(new, 0, sizeof(PLpgSQL_stmt_return));
 
-                                               if (plpgsql_curr_compile->fn_retistuple)
+                                               if (plpgsql_curr_compile->fn_retistuple &&
+                                                       !plpgsql_curr_compile->fn_retset)
                                                {
-                                                       new->retistuple = true;
                                                        new->retrecno   = -1;
-                                                       switch (tok = yylex())
+                                                       switch (yylex())
                                                        {
                                                                case K_NULL:
-                                                                       expr = NULL;
+                                                                       new->expr = NULL;
                                                                        break;
 
                                                                case T_ROW:
-                                                                       expr = make_tupret_expr(yylval.row);
+                                                                       new->expr = make_tupret_expr(yylval.row);
                                                                        break;
 
                                                                case T_RECORD:
                                                                        new->retrecno = yylval.rec->recno;
-                                                                       expr = NULL;
+                                                                       new->expr = NULL;
                                                                        break;
 
                                                                default:
-                                                                       yyerror("return type mismatch in function returning table row");
+                                                                       yyerror("return type mismatch in function returning tuple");
                                                                        break;
                                                        }
                                                        if (yylex() != ';')
                                                                yyerror("expected ';'");
-                                               } else {
-                                                       new->retistuple = false;
-                                                       expr = plpgsql_read_expression(';', ";");
                                                }
+                                               else
+                                                       new->expr = plpgsql_read_expression(';', ";");
 
                                                new->cmd_type = PLPGSQL_STMT_RETURN;
                                                new->lineno   = $2;
-                                               new->expr         = expr;
+
+                                               $$ = (PLpgSQL_stmt *)new;
+                                       }
+                               ;
+
+/* FIXME: this syntax needs work, RETURN NEXT breaks stmt_return */
+stmt_return_next: K_RETURN_NEXT lno
+                                       {
+                                               PLpgSQL_stmt_return_next *new;
+
+                                               new = malloc(sizeof(PLpgSQL_stmt_return_next));
+                                               memset(new, 0, sizeof(PLpgSQL_stmt_return_next));
+
+                                               new->cmd_type   = PLPGSQL_STMT_RETURN_NEXT;
+                                               new->lineno             = $2;
+
+                                               if (plpgsql_curr_compile->fn_retistuple)
+                                               {
+                                                       int tok = yylex();
+
+                                                       if (tok == T_RECORD)
+                                                               new->rec = yylval.rec;
+                                                       else if (tok == T_ROW)
+                                                               new->row = yylval.row;
+                                                       else
+                                                               yyerror("Incorrect argument to RETURN NEXT");
+
+                                                       if (yylex() != ';')
+                                                               yyerror("Expected ';'");
+                                               }
+                                               else
+                                                       new->expr = plpgsql_read_expression(';', ";");
 
                                                $$ = (PLpgSQL_stmt *)new;
                                        }
@@ -1226,7 +1252,7 @@ raise_level               : K_EXCEPTION
                                        }
                                | K_DEBUG
                                        {
-                                               $$ = DEBUG5;
+                                               $$ = DEBUG1;
                                        }
                                ;
 
@@ -1377,10 +1403,7 @@ stmt_open                : K_OPEN lno cursor_varptr
                                                                cp += strlen(cp) - 1;
 
                                                                if (*cp != ')')
-                                                               {
-                                                                       plpgsql_error_lineno = yylineno;
-                                                                       elog(ERROR, "missing )");
-                                                               }
+                                                                       yyerror("missing )");
                                                                *cp = '\0';
                                                        }
                                                        else
@@ -1433,10 +1456,8 @@ stmt_close               : K_CLOSE lno cursor_variable ';'
 cursor_varptr  : T_VARIABLE
                                        {
                                                if (yylval.variable->dtype != PLPGSQL_DTYPE_VAR)
-                                               {
-                                                       plpgsql_error_lineno = yylineno;
-                                                       elog(ERROR, "cursor variable must be a simple variable");
-                                               }
+                                                       yyerror("cursor variable must be a simple variable");
+
                                                if (((PLpgSQL_var *) yylval.variable)->datatype->typoid != REFCURSOROID)
                                                {
                                                        plpgsql_error_lineno = yylineno;
@@ -1450,10 +1471,8 @@ cursor_varptr    : T_VARIABLE
 cursor_variable        : T_VARIABLE
                                        {
                                                if (yylval.variable->dtype != PLPGSQL_DTYPE_VAR)
-                                               {
-                                                       plpgsql_error_lineno = yylineno;
-                                                       elog(ERROR, "cursor variable must be a simple variable");
-                                               }
+                                                       yyerror("cursor variable must be a simple variable");
+
                                                if (((PLpgSQL_var *) yylval.variable)->datatype->typoid != REFCURSOROID)
                                                {
                                                        plpgsql_error_lineno = yylineno;
@@ -1906,18 +1925,14 @@ make_fetch_stmt(void)
                        break;
 
                default:
-                       plpgsql_error_lineno = yylineno;
-                       elog(ERROR, "syntax error at '%s'", yytext);
+                       yyerror("syntax error");
        }
 
        if (!have_nexttok)
                tok = yylex();
 
        if (tok != ';')
-       {
-               plpgsql_error_lineno = yylineno;
-               elog(ERROR, "syntax error at '%s'", yytext);
-       }
+               yyerror("syntax error");
 
        fetch = malloc(sizeof(PLpgSQL_stmt_select));
        memset(fetch, 0, sizeof(PLpgSQL_stmt_fetch));
@@ -1976,11 +1991,10 @@ check_assignable(PLpgSQL_datum *datum)
                        /* always assignable? */
                        break;
                case PLPGSQL_DTYPE_TRIGARG:
-                       plpgsql_error_lineno = yylineno;
-                       elog(ERROR, "cannot assign to tg_argv");
+                       yyerror("cannot assign to tg_argv");
                        break;
                default:
-                       elog(ERROR, "check_assignable: unexpected datum type");
+                       yyerror("check_assignable: unexpected datum type");
                        break;
        }
 }
index 70f1de470c94c026d1b7a72812193b874adef6c9..1878c5e396e78c3f37f7fed702788538fbc1a0d1 100644 (file)
@@ -3,7 +3,7 @@
  *                       procedural language
  *
  * IDENTIFICATION
- *       $Header: /cvsroot/pgsql/src/pl/plpgsql/src/pl_comp.c,v 1.48 2002/08/28 20:46:24 momjian Exp $
+ *       $Header: /cvsroot/pgsql/src/pl/plpgsql/src/pl_comp.c,v 1.49 2002/08/30 00:28:41 tgl Exp $
  *
  *       This software is copyrighted by Jan Wieck - Hamburg.
  *
@@ -37,8 +37,6 @@
 
 #include "plpgsql.h"
 
-#include <unistd.h>
-#include <fcntl.h>
 #include <ctype.h>
 #include <setjmp.h>
 
@@ -52,9 +50,6 @@
 #include "catalog/pg_class.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_type.h"
-#include "commands/trigger.h"
-#include "executor/spi.h"
-#include "fmgr.h"
 #include "nodes/makefuncs.h"
 #include "parser/gramparse.h"
 #include "parser/parse_type.h"
@@ -217,6 +212,7 @@ plpgsql_compile(Oid fn_oid, int functype)
                        typeStruct = (Form_pg_type) GETSTRUCT(typeTup);
 
                        /* Disallow pseudotype result, except VOID */
+                       /* XXX someday allow RECORD? */
                        if (typeStruct->typtype == 'p')
                        {
                                if (procStruct->prorettype == VOIDOID)
index 363a04839a441edf53fdc6b262fd74cd4a1da5cb..49c88f3a09063d76af666477b5ebc1fb535a211c 100644 (file)
@@ -3,7 +3,7 @@
  *                       procedural language
  *
  * IDENTIFICATION
- *       $Header: /cvsroot/pgsql/src/pl/plpgsql/src/pl_exec.c,v 1.59 2002/08/29 04:12:03 tgl Exp $
+ *       $Header: /cvsroot/pgsql/src/pl/plpgsql/src/pl_exec.c,v 1.60 2002/08/30 00:28:41 tgl Exp $
  *
  *       This software is copyrighted by Jan Wieck - Hamburg.
  *
  *
  **********************************************************************/
 
-#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"
@@ -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
index 33103a9eb4ae25a5eeeadbf65f4e6b9a3ac61a0a..deaa2690e316cb72b8e5bca7f1ddbab665957e75 100644 (file)
@@ -3,7 +3,7 @@
  *                       procedural language
  *
  * IDENTIFICATION
- *       $Header: /cvsroot/pgsql/src/pl/plpgsql/src/pl_funcs.c,v 1.20 2002/08/29 07:22:30 ishii Exp $
+ *       $Header: /cvsroot/pgsql/src/pl/plpgsql/src/pl_funcs.c,v 1.21 2002/08/30 00:28:41 tgl Exp $
  *
  *       This software is copyrighted by Jan Wieck - Hamburg.
  *
  *
  **********************************************************************/
 
-#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)
 {
index 3ca0b13ffd1f3b12d432089cf36069ac5527e094..17b9cf2e42b4c77e1f5770b400670fccc3f6dab3 100644 (file)
@@ -3,7 +3,7 @@
  *                       procedural language
  *
  * IDENTIFICATION
- *       $Header: /cvsroot/pgsql/src/pl/plpgsql/src/pl_handler.c,v 1.11 2002/06/15 19:54:24 momjian Exp $
+ *       $Header: /cvsroot/pgsql/src/pl/plpgsql/src/pl_handler.c,v 1.12 2002/08/30 00:28:41 tgl Exp $
  *
  *       This software is copyrighted by Jan Wieck - Hamburg.
  *
  *
  **********************************************************************/
 
-#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"
 
index e991aa96ee754d00b97cce244dec715c0f99ff52..c81b0a3b1bc99d7b538f1f25b2883f07bec65807 100644 (file)
@@ -3,7 +3,7 @@
  *                       procedural language
  *
  * IDENTIFICATION
- *       $Header: /cvsroot/pgsql/src/pl/plpgsql/src/plpgsql.h,v 1.25 2002/08/08 01:36:05 tgl Exp $
+ *       $Header: /cvsroot/pgsql/src/pl/plpgsql/src/plpgsql.h,v 1.26 2002/08/30 00:28:41 tgl Exp $
  *
  *       This software is copyrighted by Jan Wieck - Hamburg.
  *
 #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;
 
index 5f0f281ada5a3ca48b661e728f4d4c89845828e9..3976b542756a2c5c37f6a55124133bed228e072a 100644 (file)
@@ -4,7 +4,7 @@
  *                       procedural language
  *
  * IDENTIFICATION
- *    $Header: /cvsroot/pgsql/src/pl/plpgsql/src/Attic/scan.l,v 1.21 2002/08/08 01:36:05 tgl Exp $
+ *    $Header: /cvsroot/pgsql/src/pl/plpgsql/src/Attic/scan.l,v 1.22 2002/08/30 00:28:41 tgl Exp $
  *
  *    This software is copyrighted by Jan Wieck - Hamburg.
  *
@@ -46,6 +46,8 @@ static int    scanner_functype;
 static int     scanner_typereported;
 static int     pushback_token;
 static bool have_pushback_token;
+static int     lookahead_token;        
+static bool have_lookahead_token;
 
 int    plpgsql_SpaceScanned = 0;
 
@@ -134,6 +136,7 @@ into                        { return K_INTO;                        }
 is                             { return K_IS;                          }
 log                            { return K_LOG;                         }
 loop                   { return K_LOOP;                        }
+next                   { return K_NEXT;                        }
 not                            { return K_NOT;                         }
 notice                 { return K_NOTICE;                      }
 null                   { return K_NULL;                        }
@@ -255,18 +258,50 @@ plpgsql_input(char *buf, int *result, int max)
 }
 
 /*
- * This is the yylex routine called from outside.  It exists to provide
- * a token pushback facility.
+ * This is the yylex routine called from outside. It exists to provide
+ * a pushback facility, as well as to allow us to parse syntax that
+ * requires more than one token of lookahead.
  */
 int
 plpgsql_yylex(void)
 {
+       int cur_token;
+
        if (have_pushback_token)
        {
                have_pushback_token = false;
-               return pushback_token;
+               cur_token = pushback_token;
+       }
+       else if (have_lookahead_token)
+       {
+               have_lookahead_token = false;
+               cur_token = lookahead_token;
+       }
+       else
+               cur_token = yylex();
+
+       /* Do we need to look ahead for a possible multiword token? */
+       switch (cur_token)
+       {
+               /* RETURN NEXT must be reduced to a single token */
+               case K_RETURN:
+                       if (!have_lookahead_token)
+                       {
+                               lookahead_token = yylex();
+                               have_lookahead_token = true;
+                       }
+                       if (lookahead_token == K_NEXT)
+                       {
+                               have_lookahead_token = false;
+                               cur_token = K_RETURN_NEXT;
+                       }
+                       break;
+
+               default:
+                       break;
        }
-       return yylex();
+
+       return cur_token;
 }
 
 /*
@@ -312,4 +347,5 @@ plpgsql_setinput(char *source, int functype)
     scanner_typereported = 0;
 
        have_pushback_token = false;
+       have_lookahead_token = false;
 }
index a8578ea5944ac7ac38d8dfa1646cc1991380a50c..583543262ed518e26b3b2798900500fba0ad0409 100644 (file)
@@ -1535,6 +1535,10 @@ ERROR:  system "notthere" does not exist
 insert into IFace values ('IF', 'orion', 'ethernet_interface_name_too_long', '');
 ERROR:  IFace slotname "IF.orion.ethernet_interface_name_too_long" too long (20 char max)
 --
+-- The following tests are unrelated to the scenario outlined above;
+-- they merely exercise specific parts of PL/PgSQL
+--
+--
 -- Test recursion, per bug report 7-Sep-01
 --
 CREATE FUNCTION recursion_test(int,int) RETURNS text AS '
@@ -1557,7 +1561,7 @@ SELECT recursion_test(4,3);
 -- Test the FOUND magic variable
 --
 CREATE TABLE found_test_tbl (a int);
-create function test_found ()
+create function test_found()
   returns boolean as '
   declare
   begin
@@ -1609,3 +1613,70 @@ select * from found_test_tbl;
    6
 (6 rows)
 
+--
+-- Test set-returning functions for PL/pgSQL
+--
+create function test_table_func_rec() returns setof found_test_tbl as '
+DECLARE
+       rec RECORD;
+BEGIN
+       FOR rec IN select * from found_test_tbl LOOP
+               RETURN NEXT rec;
+       END LOOP;
+       RETURN;
+END;' language 'plpgsql';
+select * from test_table_func_rec();
+  a  
+-----
+   2
+ 100
+   3
+   4
+   5
+   6
+(6 rows)
+
+create function test_table_func_row() returns setof found_test_tbl as '
+DECLARE
+       row found_test_tbl%ROWTYPE;
+BEGIN
+       FOR row IN select * from found_test_tbl LOOP
+               RETURN NEXT row;
+       END LOOP;
+       RETURN;
+END;' language 'plpgsql';
+select * from test_table_func_row();
+  a  
+-----
+   2
+ 100
+   3
+   4
+   5
+   6
+(6 rows)
+
+create function test_ret_set_scalar(int,int) returns setof int as '
+DECLARE
+       i int;
+BEGIN
+       FOR i IN $1 .. $2 LOOP
+               RETURN NEXT i + 1;
+       END LOOP;
+       RETURN;
+END;' language 'plpgsql';
+select * from test_ret_set_scalar(1,10);
+ test_ret_set_scalar 
+---------------------
+                   2
+                   3
+                   4
+                   5
+                   6
+                   7
+                   8
+                   9
+                  10
+                  11
+(10 rows)
+
index 509dfd2df9c9d22f6dbb57df5cb1d8b1da00e8f0..e6795ed10a6fd45a3f98911eedfa3790c5f8adf1 100644 (file)
@@ -1419,6 +1419,12 @@ delete from HSlot;
 insert into IFace values ('IF', 'notthere', 'eth0', '');
 insert into IFace values ('IF', 'orion', 'ethernet_interface_name_too_long', '');
 
+
+--
+-- The following tests are unrelated to the scenario outlined above;
+-- they merely exercise specific parts of PL/PgSQL
+--
+
 --
 -- Test recursion, per bug report 7-Sep-01
 --
@@ -1440,7 +1446,7 @@ SELECT recursion_test(4,3);
 --
 CREATE TABLE found_test_tbl (a int);
 
-create function test_found ()
+create function test_found()
   returns boolean as '
   declare
   begin
@@ -1478,3 +1484,43 @@ create function test_found ()
 
 select test_found();
 select * from found_test_tbl;
+
+--
+-- Test set-returning functions for PL/pgSQL
+--
+
+create function test_table_func_rec() returns setof found_test_tbl as '
+DECLARE
+       rec RECORD;
+BEGIN
+       FOR rec IN select * from found_test_tbl LOOP
+               RETURN NEXT rec;
+       END LOOP;
+       RETURN;
+END;' language 'plpgsql';
+
+select * from test_table_func_rec();
+
+create function test_table_func_row() returns setof found_test_tbl as '
+DECLARE
+       row found_test_tbl%ROWTYPE;
+BEGIN
+       FOR row IN select * from found_test_tbl LOOP
+               RETURN NEXT row;
+       END LOOP;
+       RETURN;
+END;' language 'plpgsql';
+
+select * from test_table_func_row();
+
+create function test_ret_set_scalar(int,int) returns setof int as '
+DECLARE
+       i int;
+BEGIN
+       FOR i IN $1 .. $2 LOOP
+               RETURN NEXT i + 1;
+       END LOOP;
+       RETURN;
+END;' language 'plpgsql';
+
+select * from test_ret_set_scalar(1,10);