mirror of
https://github.com/postgres/postgres.git
synced 2025-07-28 23:42:10 +03:00
PL/pgSQL functions can return sets. Neil Conway's patch, modified so
that the functionality is available to anyone via ReturnSetInfo, rather than hard-wiring it to PL/pgSQL.
This commit is contained in:
@ -16,14 +16,13 @@
|
|||||||
#include "postgres.h"
|
#include "postgres.h"
|
||||||
|
|
||||||
#include <ctype.h>
|
#include <ctype.h>
|
||||||
#include <stdio.h>
|
|
||||||
#include <sys/types.h>
|
#include <sys/types.h>
|
||||||
#include <string.h>
|
|
||||||
#include "postgres.h"
|
|
||||||
#include "access/heapam.h"
|
#include "access/heapam.h"
|
||||||
#include "catalog/catname.h"
|
#include "catalog/catname.h"
|
||||||
#include "catalog/indexing.h"
|
#include "catalog/indexing.h"
|
||||||
#include "catalog/pg_proc.h"
|
#include "catalog/pg_proc.h"
|
||||||
|
#include "catalog/pg_type.h"
|
||||||
#include "executor/executor.h"
|
#include "executor/executor.h"
|
||||||
#include "utils/fcache.h"
|
#include "utils/fcache.h"
|
||||||
#include "utils/sets.h"
|
#include "utils/sets.h"
|
||||||
@ -97,7 +96,7 @@ static PGARRAY * GetPGArray(int4 state, int fAdd)
|
|||||||
p->a.size = cb;
|
p->a.size = cb;
|
||||||
p->a.ndim = 0;
|
p->a.ndim = 0;
|
||||||
p->a.flags = 0;
|
p->a.flags = 0;
|
||||||
p->a.elmtype = INT4OID;
|
p->a.elemtype = INT4OID;
|
||||||
p->items = 0;
|
p->items = 0;
|
||||||
p->lower= START_NUM;
|
p->lower= START_NUM;
|
||||||
}
|
}
|
||||||
@ -150,7 +149,7 @@ static PGARRAY *ShrinkPGArray(PGARRAY *p)
|
|||||||
pnew->a.size = cb;
|
pnew->a.size = cb;
|
||||||
pnew->a.ndim=1;
|
pnew->a.ndim=1;
|
||||||
pnew->a.flags = 0;
|
pnew->a.flags = 0;
|
||||||
pnew->a.elmtype = INT4OID;
|
pnew->a.elemtype = INT4OID;
|
||||||
pnew->lower = 0;
|
pnew->lower = 0;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@ -171,11 +170,11 @@ Datum int_agg_state(PG_FUNCTION_ARGS)
|
|||||||
PGARRAY *p = GetPGArray(state, 1);
|
PGARRAY *p = GetPGArray(state, 1);
|
||||||
if(!p)
|
if(!p)
|
||||||
{
|
{
|
||||||
elog(ERROR,"No aggregate storage\n");
|
elog(ERROR,"No aggregate storage");
|
||||||
}
|
}
|
||||||
else if(p->items >= p->lower)
|
else if(p->items >= p->lower)
|
||||||
{
|
{
|
||||||
elog(ERROR,"aggregate storage too small\n");
|
elog(ERROR,"aggregate storage too small");
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@ -202,32 +201,24 @@ Datum int_agg_final_array(PG_FUNCTION_ARGS)
|
|||||||
/* This function accepts an array, and returns one item for each entry in the array */
|
/* This function accepts an array, and returns one item for each entry in the array */
|
||||||
Datum int_enum(PG_FUNCTION_ARGS)
|
Datum int_enum(PG_FUNCTION_ARGS)
|
||||||
{
|
{
|
||||||
CTX *pc;
|
|
||||||
PGARRAY *p = (PGARRAY *) PG_GETARG_POINTER(0);
|
PGARRAY *p = (PGARRAY *) PG_GETARG_POINTER(0);
|
||||||
|
CTX *pc;
|
||||||
ReturnSetInfo *rsi = (ReturnSetInfo *)fcinfo->resultinfo;
|
ReturnSetInfo *rsi = (ReturnSetInfo *)fcinfo->resultinfo;
|
||||||
|
|
||||||
|
if (!rsi || !IsA(rsi, ReturnSetInfo))
|
||||||
|
elog(ERROR, "No ReturnSetInfo sent! function must be declared returning a 'setof' integer");
|
||||||
|
|
||||||
if(!p)
|
if(!p)
|
||||||
{
|
{
|
||||||
elog(WARNING, "No data sent\n");
|
elog(WARNING, "No data sent");
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
if(!rsi)
|
|
||||||
{
|
|
||||||
elog(ERROR, "No ReturnSetInfo sent! function must be declared returning a 'setof' integer");
|
|
||||||
PG_RETURN_NULL();
|
PG_RETURN_NULL();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!fcinfo->context)
|
if(!fcinfo->context)
|
||||||
{
|
{
|
||||||
/* Allocate a working context */
|
/* Allocate a working context */
|
||||||
pc = (CTX *) palloc(sizeof(CTX));
|
pc = (CTX *) palloc(sizeof(CTX));
|
||||||
|
|
||||||
if(!pc)
|
|
||||||
{
|
|
||||||
elog(ERROR, "CTX Alocation failed\n");
|
|
||||||
PG_RETURN_NULL();
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Don't copy atribute if you don't need too */
|
/* Don't copy atribute if you don't need too */
|
||||||
if(VARATT_IS_EXTENDED(p) )
|
if(VARATT_IS_EXTENDED(p) )
|
||||||
{
|
{
|
||||||
@ -236,7 +227,7 @@ Datum int_enum(PG_FUNCTION_ARGS)
|
|||||||
pc->flags = TOASTED;
|
pc->flags = TOASTED;
|
||||||
if(!pc->p)
|
if(!pc->p)
|
||||||
{
|
{
|
||||||
elog(ERROR, "Error in toaster!!! no detoasting\n");
|
elog(ERROR, "Error in toaster!!! no detoasting");
|
||||||
PG_RETURN_NULL();
|
PG_RETURN_NULL();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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">
|
<chapter id="plpgsql">
|
||||||
@ -1142,11 +1142,20 @@ GET DIAGNOSTICS <replaceable>variable</replaceable> = <replaceable>item</replace
|
|||||||
RETURN <replaceable>expression</replaceable>;
|
RETURN <replaceable>expression</replaceable>;
|
||||||
</synopsis>
|
</synopsis>
|
||||||
|
|
||||||
|
RETURN with an expression is used to return from a
|
||||||
|
<application>PL/pgSQL</> function that does not return a set.
|
||||||
The function terminates and the value of
|
The function terminates and the value of
|
||||||
<replaceable>expression</replaceable> will be returned to the
|
<replaceable>expression</replaceable> is returned to the caller.
|
||||||
upper executor.
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
To return a composite (row) value, you must write a record or row
|
||||||
|
variable as the <replaceable>expression</replaceable>. When
|
||||||
|
returning a scalar type, any expression can be used.
|
||||||
The expression's result will be automatically cast into the
|
The expression's result will be automatically cast into the
|
||||||
function's return type as described for assignments.
|
function's return type as described for assignments.
|
||||||
|
(If you have declared the function to return <type>void</>,
|
||||||
|
then the expression can be omitted, and will be ignored in any case.)
|
||||||
</para>
|
</para>
|
||||||
|
|
||||||
<para>
|
<para>
|
||||||
@ -1155,6 +1164,28 @@ RETURN <replaceable>expression</replaceable>;
|
|||||||
the function without hitting a RETURN statement, a run-time error
|
the function without hitting a RETURN statement, a run-time error
|
||||||
will occur.
|
will occur.
|
||||||
</para>
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
When a <application>PL/pgSQL</> function is declared to return
|
||||||
|
<literal>SETOF</literal> <replaceable>sometype</>, the procedure
|
||||||
|
to follow is slightly different. The individual items to be returned
|
||||||
|
are specified in RETURN NEXT commands, and then a final RETURN with
|
||||||
|
no argument is given to indicate that the function is done generating
|
||||||
|
items.
|
||||||
|
|
||||||
|
<synopsis>
|
||||||
|
RETURN NEXT <replaceable>expression</replaceable>;
|
||||||
|
</synopsis>
|
||||||
|
|
||||||
|
RETURN NEXT does not actually return from the function; it simply
|
||||||
|
saves away the value of the expression (or record or row variable,
|
||||||
|
as appropriate for the datatype being returned).
|
||||||
|
Execution then continues with the next statement in the
|
||||||
|
<application>PL/pgSQL</> function. As successive RETURN NEXT
|
||||||
|
commands are executed, the result set is built up. A final
|
||||||
|
RETURN, which need have no argument, causes control to exit
|
||||||
|
the function.
|
||||||
|
</para>
|
||||||
</sect2>
|
</sect2>
|
||||||
|
|
||||||
<sect2 id="plpgsql-conditionals">
|
<sect2 id="plpgsql-conditionals">
|
||||||
@ -1531,8 +1562,8 @@ END LOOP;
|
|||||||
to worry about that, since FOR loops automatically use a cursor
|
to worry about that, since FOR loops automatically use a cursor
|
||||||
internally to avoid memory problems.) A more interesting usage is to
|
internally to avoid memory problems.) A more interesting usage is to
|
||||||
return a reference to a cursor that it has created, allowing the
|
return a reference to a cursor that it has created, allowing the
|
||||||
caller to read the rows. This provides a way to return row sets
|
caller to read the rows. This provides an efficient way to return
|
||||||
from functions.
|
large row sets from functions.
|
||||||
</para>
|
</para>
|
||||||
|
|
||||||
<sect2 id="plpgsql-cursor-declarations">
|
<sect2 id="plpgsql-cursor-declarations">
|
||||||
@ -1794,19 +1825,27 @@ COMMIT;
|
|||||||
RAISE <replaceable class="parameter">level</replaceable> '<replaceable class="parameter">format</replaceable>' <optional>, <replaceable class="parameter">variable</replaceable> <optional>...</optional></optional>;
|
RAISE <replaceable class="parameter">level</replaceable> '<replaceable class="parameter">format</replaceable>' <optional>, <replaceable class="parameter">variable</replaceable> <optional>...</optional></optional>;
|
||||||
</synopsis>
|
</synopsis>
|
||||||
|
|
||||||
Possible levels are DEBUG (write the message into the postmaster log),
|
Possible levels are <literal>DEBUG</literal> (write the message to
|
||||||
NOTICE (write the message into the postmaster log and forward it to
|
the server log), <literal>LOG</literal> (write the message to the
|
||||||
the client application) and EXCEPTION (raise an error,
|
server log with a higher priority), <literal>INFO</literal>,
|
||||||
aborting the transaction).
|
<literal>NOTICE</literal> and <literal>WARNING</literal> (write
|
||||||
|
the message to the server log and send it to the client, with
|
||||||
|
respectively higher priorities), and <literal>EXCEPTION</literal>
|
||||||
|
(raise an error and abort the current transaction). Whether error
|
||||||
|
messages of a particular priority are reported to the client,
|
||||||
|
written to the server log, or both is controlled by the
|
||||||
|
<option>SERVER_MIN_MESSAGES</option> and
|
||||||
|
<option>CLIENT_MIN_MESSAGES</option> configuration variables. See
|
||||||
|
the <citetitle>PostgreSQL Administrator's Guide</citetitle> for more
|
||||||
|
information.
|
||||||
</para>
|
</para>
|
||||||
|
|
||||||
<para>
|
<para>
|
||||||
Inside the format string, <literal>%</literal> is replaced by the next
|
Inside the format string, <literal>%</literal> is replaced by the
|
||||||
optional argument's external representation.
|
next optional argument's external representation. Write
|
||||||
Write <literal>%%</literal> to emit a literal <literal>%</literal>.
|
<literal>%%</literal> to emit a literal <literal>%</literal>. Note
|
||||||
Note that the optional arguments must presently
|
that the optional arguments must presently be simple variables,
|
||||||
be simple variables, not expressions, and the format must be a simple
|
not expressions, and the format must be a simple string literal.
|
||||||
string literal.
|
|
||||||
</para>
|
</para>
|
||||||
|
|
||||||
<!--
|
<!--
|
||||||
@ -1820,8 +1859,9 @@ RAISE <replaceable class="parameter">level</replaceable> '<replaceable class="pa
|
|||||||
<programlisting>
|
<programlisting>
|
||||||
RAISE NOTICE ''Calling cs_create_job(%)'',v_job_id;
|
RAISE NOTICE ''Calling cs_create_job(%)'',v_job_id;
|
||||||
</programlisting>
|
</programlisting>
|
||||||
In this example, the value of v_job_id will replace the % in the
|
|
||||||
string.
|
In this example, the value of v_job_id will replace the
|
||||||
|
<literal>%</literal> in the string.
|
||||||
</para>
|
</para>
|
||||||
|
|
||||||
<para>
|
<para>
|
||||||
@ -1852,12 +1892,12 @@ RAISE EXCEPTION ''Inexistent ID --> %'',user_id;
|
|||||||
</para>
|
</para>
|
||||||
|
|
||||||
<para>
|
<para>
|
||||||
Thus, the only thing <application>PL/pgSQL</application> currently does when it encounters
|
Thus, the only thing <application>PL/pgSQL</application>
|
||||||
an abort during execution of a function or trigger
|
currently does when it encounters an abort during execution of a
|
||||||
procedure is to write some additional NOTICE level log messages
|
function or trigger procedure is to write some additional
|
||||||
telling in which function and where (line number and type of
|
<literal>NOTICE</literal> level log messages telling in which
|
||||||
statement) this happened. The error always stops execution of
|
function and where (line number and type of statement) this
|
||||||
the function.
|
happened. The error always stops execution of the function.
|
||||||
</para>
|
</para>
|
||||||
</sect2>
|
</sect2>
|
||||||
</sect1>
|
</sect1>
|
||||||
|
@ -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">
|
<appendix id="release">
|
||||||
@ -24,6 +24,7 @@ CDATA means the content is "SGML-free", so you can write without
|
|||||||
worries about funny characters.
|
worries about funny characters.
|
||||||
-->
|
-->
|
||||||
<literallayout><![CDATA[
|
<literallayout><![CDATA[
|
||||||
|
Substantial improvements in functionality for functions returning sets
|
||||||
Client libraries older than 6.3 no longer supported (version 0 protocol removed)
|
Client libraries older than 6.3 no longer supported (version 0 protocol removed)
|
||||||
PREPARE statement allows caching query plans for interactive statements
|
PREPARE statement allows caching query plans for interactive statements
|
||||||
Type OPAQUE is now deprecated in favor of pseudo-types cstring, trigger, etc
|
Type OPAQUE is now deprecated in favor of pseudo-types cstring, trigger, etc
|
||||||
|
@ -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">
|
<Chapter Id="runtime">
|
||||||
@ -921,7 +921,8 @@ env PGOPTIONS='-c geqo=off' psql
|
|||||||
built (see the configure option
|
built (see the configure option
|
||||||
<literal>--enable-cassert</literal>). Note that
|
<literal>--enable-cassert</literal>). Note that
|
||||||
<literal>DEBUG_ASSERTIONS</literal> defaults to on if
|
<literal>DEBUG_ASSERTIONS</literal> defaults to on if
|
||||||
<productname>PostgreSQL</productname> has been built this way.
|
<productname>PostgreSQL</productname> has been built with
|
||||||
|
assertions enabled.
|
||||||
</para>
|
</para>
|
||||||
</listitem>
|
</listitem>
|
||||||
</varlistentry>
|
</varlistentry>
|
||||||
|
@ -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">
|
<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
|
function, as described below. It can also be called in the context
|
||||||
of an SQL expression, but only when you
|
of an SQL expression, but only when you
|
||||||
extract a single attribute out of the row or pass the entire row into
|
extract a single attribute out of the row or pass the entire row into
|
||||||
another function that accepts the same composite type. (Trying to
|
another function that accepts the same composite type. For example,
|
||||||
display the entire row value will yield
|
|
||||||
a meaningless number.) For example,
|
|
||||||
|
|
||||||
<programlisting>
|
<programlisting>
|
||||||
SELECT (new_emp()).name;
|
SELECT (new_emp()).name;
|
||||||
|
@ -8,7 +8,7 @@
|
|||||||
*
|
*
|
||||||
*
|
*
|
||||||
* IDENTIFICATION
|
* IDENTIFICATION
|
||||||
* $Header: /cvsroot/pgsql/src/backend/executor/execQual.c,v 1.101 2002/08/26 17:53:57 tgl Exp $
|
* $Header: /cvsroot/pgsql/src/backend/executor/execQual.c,v 1.102 2002/08/30 00:28:41 tgl Exp $
|
||||||
*
|
*
|
||||||
*-------------------------------------------------------------------------
|
*-------------------------------------------------------------------------
|
||||||
*/
|
*/
|
||||||
@ -35,12 +35,15 @@
|
|||||||
#include "postgres.h"
|
#include "postgres.h"
|
||||||
|
|
||||||
#include "access/heapam.h"
|
#include "access/heapam.h"
|
||||||
|
#include "catalog/pg_type.h"
|
||||||
#include "executor/execdebug.h"
|
#include "executor/execdebug.h"
|
||||||
#include "executor/functions.h"
|
#include "executor/functions.h"
|
||||||
#include "executor/nodeSubplan.h"
|
#include "executor/nodeSubplan.h"
|
||||||
|
#include "miscadmin.h"
|
||||||
#include "utils/array.h"
|
#include "utils/array.h"
|
||||||
#include "utils/builtins.h"
|
#include "utils/builtins.h"
|
||||||
#include "utils/fcache.h"
|
#include "utils/fcache.h"
|
||||||
|
#include "utils/lsyscache.h"
|
||||||
|
|
||||||
|
|
||||||
/* static function decls */
|
/* static function decls */
|
||||||
@ -646,9 +649,6 @@ ExecEvalFuncArgs(FunctionCallInfo fcinfo,
|
|||||||
* ExecMakeFunctionResult
|
* ExecMakeFunctionResult
|
||||||
*
|
*
|
||||||
* Evaluate the arguments to a function and then the function itself.
|
* 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
|
Datum
|
||||||
ExecMakeFunctionResult(FunctionCachePtr fcache,
|
ExecMakeFunctionResult(FunctionCachePtr fcache,
|
||||||
@ -707,6 +707,11 @@ ExecMakeFunctionResult(FunctionCachePtr fcache,
|
|||||||
fcinfo.resultinfo = (Node *) &rsinfo;
|
fcinfo.resultinfo = (Node *) &rsinfo;
|
||||||
rsinfo.type = T_ReturnSetInfo;
|
rsinfo.type = T_ReturnSetInfo;
|
||||||
rsinfo.econtext = econtext;
|
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
|
* ExecEvalOper
|
||||||
* ExecEvalDistinct
|
|
||||||
* ExecEvalFunc
|
* ExecEvalFunc
|
||||||
|
* ExecEvalDistinct
|
||||||
*
|
*
|
||||||
* Evaluate the functional result of a list of arguments by calling the
|
* Evaluate the functional result of a list of arguments by calling the
|
||||||
* function manager.
|
* function manager.
|
||||||
@ -886,6 +1121,48 @@ ExecEvalOper(Expr *opClause,
|
|||||||
isNull, isDone);
|
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
|
* ExecEvalDistinct
|
||||||
*
|
*
|
||||||
@ -960,48 +1237,6 @@ ExecEvalDistinct(Expr *opClause,
|
|||||||
return BoolGetDatum(result);
|
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
|
* ExecEvalNot
|
||||||
* ExecEvalOr
|
* ExecEvalOr
|
||||||
|
@ -8,7 +8,7 @@
|
|||||||
*
|
*
|
||||||
*
|
*
|
||||||
* IDENTIFICATION
|
* IDENTIFICATION
|
||||||
* $Header: /cvsroot/pgsql/src/backend/executor/nodeFunctionscan.c,v 1.7 2002/08/29 17:14:33 tgl Exp $
|
* $Header: /cvsroot/pgsql/src/backend/executor/nodeFunctionscan.c,v 1.8 2002/08/30 00:28:41 tgl Exp $
|
||||||
*
|
*
|
||||||
*-------------------------------------------------------------------------
|
*-------------------------------------------------------------------------
|
||||||
*/
|
*/
|
||||||
@ -22,7 +22,6 @@
|
|||||||
*/
|
*/
|
||||||
#include "postgres.h"
|
#include "postgres.h"
|
||||||
|
|
||||||
#include "miscadmin.h"
|
|
||||||
#include "access/heapam.h"
|
#include "access/heapam.h"
|
||||||
#include "catalog/pg_type.h"
|
#include "catalog/pg_type.h"
|
||||||
#include "executor/execdebug.h"
|
#include "executor/execdebug.h"
|
||||||
@ -32,17 +31,10 @@
|
|||||||
#include "parser/parsetree.h"
|
#include "parser/parsetree.h"
|
||||||
#include "parser/parse_expr.h"
|
#include "parser/parse_expr.h"
|
||||||
#include "parser/parse_type.h"
|
#include "parser/parse_type.h"
|
||||||
#include "storage/lmgr.h"
|
|
||||||
#include "tcop/pquery.h"
|
|
||||||
#include "utils/lsyscache.h"
|
#include "utils/lsyscache.h"
|
||||||
#include "utils/syscache.h"
|
|
||||||
#include "utils/tuplestore.h"
|
|
||||||
|
|
||||||
static TupleTableSlot *FunctionNext(FunctionScan *node);
|
static TupleTableSlot *FunctionNext(FunctionScan *node);
|
||||||
static TupleTableSlot *function_getonetuple(FunctionScanState *scanstate,
|
|
||||||
bool *isNull,
|
|
||||||
ExprDoneCond *isDone);
|
|
||||||
static FunctionMode get_functionmode(Node *expr);
|
|
||||||
static bool tupledesc_mismatch(TupleDesc tupdesc1, TupleDesc tupdesc2);
|
static bool tupledesc_mismatch(TupleDesc tupdesc1, TupleDesc tupdesc2);
|
||||||
|
|
||||||
/* ----------------------------------------------------------------
|
/* ----------------------------------------------------------------
|
||||||
@ -76,53 +68,42 @@ FunctionNext(FunctionScan *node)
|
|||||||
tuplestorestate = scanstate->tuplestorestate;
|
tuplestorestate = scanstate->tuplestorestate;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* If first time through, read all tuples from function and pass them to
|
* If first time through, read all tuples from function and put them
|
||||||
* tuplestore.c. Subsequent calls just fetch tuples from tuplestore.
|
* in a tuplestore. Subsequent calls just fetch tuples from tuplestore.
|
||||||
*/
|
*/
|
||||||
if (tuplestorestate == NULL)
|
if (tuplestorestate == NULL)
|
||||||
{
|
{
|
||||||
/*
|
ExprContext *econtext = scanstate->csstate.cstate.cs_ExprContext;
|
||||||
* Initialize tuplestore module.
|
TupleDesc funcTupdesc;
|
||||||
*/
|
|
||||||
tuplestorestate = tuplestore_begin_heap(true, /* randomAccess */
|
|
||||||
SortMem);
|
|
||||||
|
|
||||||
scanstate->tuplestorestate = (void *) tuplestorestate;
|
scanstate->tuplestorestate = tuplestorestate =
|
||||||
|
ExecMakeTableFunctionResult((Expr *) scanstate->funcexpr,
|
||||||
|
econtext,
|
||||||
|
&funcTupdesc);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Compute all the function tuples and pass to tuplestore.
|
* If function provided a tupdesc, cross-check it. We only really
|
||||||
|
* need to do this for functions returning RECORD, but might as well
|
||||||
|
* do it always.
|
||||||
*/
|
*/
|
||||||
for (;;)
|
if (funcTupdesc &&
|
||||||
{
|
tupledesc_mismatch(scanstate->tupdesc, funcTupdesc))
|
||||||
bool isNull;
|
elog(ERROR, "Query-specified return tuple and actual function return tuple do not match");
|
||||||
ExprDoneCond isDone;
|
|
||||||
|
|
||||||
isNull = false;
|
|
||||||
isDone = ExprSingleResult;
|
|
||||||
slot = function_getonetuple(scanstate, &isNull, &isDone);
|
|
||||||
if (TupIsNull(slot))
|
|
||||||
break;
|
|
||||||
|
|
||||||
tuplestore_puttuple(tuplestorestate, (void *) slot->val);
|
|
||||||
ExecClearTuple(slot);
|
|
||||||
|
|
||||||
if (isDone != ExprMultipleResult)
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Complete the store.
|
|
||||||
*/
|
|
||||||
tuplestore_donestoring(tuplestorestate);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Get the next tuple from tuplestore. Return NULL if no more tuples.
|
* Get the next tuple from tuplestore. Return NULL if no more tuples.
|
||||||
*/
|
*/
|
||||||
slot = scanstate->csstate.css_ScanTupleSlot;
|
slot = scanstate->csstate.css_ScanTupleSlot;
|
||||||
heapTuple = tuplestore_getheaptuple(tuplestorestate,
|
if (tuplestorestate)
|
||||||
ScanDirectionIsForward(direction),
|
heapTuple = tuplestore_getheaptuple(tuplestorestate,
|
||||||
&should_free);
|
ScanDirectionIsForward(direction),
|
||||||
|
&should_free);
|
||||||
|
else
|
||||||
|
{
|
||||||
|
heapTuple = NULL;
|
||||||
|
should_free = false;
|
||||||
|
}
|
||||||
|
|
||||||
return ExecStoreTuple(heapTuple, slot, InvalidBuffer, should_free);
|
return ExecStoreTuple(heapTuple, slot, InvalidBuffer, should_free);
|
||||||
}
|
}
|
||||||
@ -219,7 +200,6 @@ ExecInitFunctionScan(FunctionScan *node, EState *estate, Plan *parent)
|
|||||||
rel = relation_open(funcrelid, AccessShareLock);
|
rel = relation_open(funcrelid, AccessShareLock);
|
||||||
tupdesc = CreateTupleDescCopy(RelationGetDescr(rel));
|
tupdesc = CreateTupleDescCopy(RelationGetDescr(rel));
|
||||||
relation_close(rel, AccessShareLock);
|
relation_close(rel, AccessShareLock);
|
||||||
scanstate->returnsTuple = true;
|
|
||||||
}
|
}
|
||||||
else if (functyptype == 'b' || functyptype == 'd')
|
else if (functyptype == 'b' || functyptype == 'd')
|
||||||
{
|
{
|
||||||
@ -236,7 +216,6 @@ ExecInitFunctionScan(FunctionScan *node, EState *estate, Plan *parent)
|
|||||||
-1,
|
-1,
|
||||||
0,
|
0,
|
||||||
false);
|
false);
|
||||||
scanstate->returnsTuple = false;
|
|
||||||
}
|
}
|
||||||
else if (functyptype == 'p' && funcrettype == RECORDOID)
|
else if (functyptype == 'p' && funcrettype == RECORDOID)
|
||||||
{
|
{
|
||||||
@ -246,13 +225,10 @@ ExecInitFunctionScan(FunctionScan *node, EState *estate, Plan *parent)
|
|||||||
List *coldeflist = rte->coldeflist;
|
List *coldeflist = rte->coldeflist;
|
||||||
|
|
||||||
tupdesc = BuildDescForRelation(coldeflist);
|
tupdesc = BuildDescForRelation(coldeflist);
|
||||||
scanstate->returnsTuple = true;
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
elog(ERROR, "Unknown kind of return type specified for function");
|
elog(ERROR, "Unknown kind of return type specified for function");
|
||||||
|
|
||||||
scanstate->fn_typeid = funcrettype;
|
|
||||||
scanstate->fn_typtype = functyptype;
|
|
||||||
scanstate->tupdesc = tupdesc;
|
scanstate->tupdesc = tupdesc;
|
||||||
ExecSetSlotDescriptor(scanstate->csstate.css_ScanTupleSlot,
|
ExecSetSlotDescriptor(scanstate->csstate.css_ScanTupleSlot,
|
||||||
tupdesc, false);
|
tupdesc, false);
|
||||||
@ -263,8 +239,6 @@ ExecInitFunctionScan(FunctionScan *node, EState *estate, Plan *parent)
|
|||||||
scanstate->tuplestorestate = NULL;
|
scanstate->tuplestorestate = NULL;
|
||||||
scanstate->funcexpr = rte->funcexpr;
|
scanstate->funcexpr = rte->funcexpr;
|
||||||
|
|
||||||
scanstate->functionmode = get_functionmode(rte->funcexpr);
|
|
||||||
|
|
||||||
scanstate->csstate.cstate.cs_TupFromTlist = false;
|
scanstate->csstate.cstate.cs_TupFromTlist = false;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -322,7 +296,7 @@ ExecEndFunctionScan(FunctionScan *node)
|
|||||||
* Release tuplestore resources
|
* Release tuplestore resources
|
||||||
*/
|
*/
|
||||||
if (scanstate->tuplestorestate != NULL)
|
if (scanstate->tuplestorestate != NULL)
|
||||||
tuplestore_end((Tuplestorestate *) scanstate->tuplestorestate);
|
tuplestore_end(scanstate->tuplestorestate);
|
||||||
scanstate->tuplestorestate = NULL;
|
scanstate->tuplestorestate = NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -345,7 +319,7 @@ ExecFunctionMarkPos(FunctionScan *node)
|
|||||||
if (!scanstate->tuplestorestate)
|
if (!scanstate->tuplestorestate)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
tuplestore_markpos((Tuplestorestate *) scanstate->tuplestorestate);
|
tuplestore_markpos(scanstate->tuplestorestate);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ----------------------------------------------------------------
|
/* ----------------------------------------------------------------
|
||||||
@ -367,7 +341,7 @@ ExecFunctionRestrPos(FunctionScan *node)
|
|||||||
if (!scanstate->tuplestorestate)
|
if (!scanstate->tuplestorestate)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
tuplestore_restorepos((Tuplestorestate *) scanstate->tuplestorestate);
|
tuplestore_restorepos(scanstate->tuplestorestate);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ----------------------------------------------------------------
|
/* ----------------------------------------------------------------
|
||||||
@ -402,98 +376,13 @@ ExecFunctionReScan(FunctionScan *node, ExprContext *exprCtxt, Plan *parent)
|
|||||||
*/
|
*/
|
||||||
if (node->scan.plan.chgParam != NULL)
|
if (node->scan.plan.chgParam != NULL)
|
||||||
{
|
{
|
||||||
tuplestore_end((Tuplestorestate *) scanstate->tuplestorestate);
|
tuplestore_end(scanstate->tuplestorestate);
|
||||||
scanstate->tuplestorestate = NULL;
|
scanstate->tuplestorestate = NULL;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
tuplestore_rescan((Tuplestorestate *) scanstate->tuplestorestate);
|
tuplestore_rescan(scanstate->tuplestorestate);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
* Run the underlying function to get the next tuple
|
|
||||||
*/
|
|
||||||
static TupleTableSlot *
|
|
||||||
function_getonetuple(FunctionScanState *scanstate,
|
|
||||||
bool *isNull,
|
|
||||||
ExprDoneCond *isDone)
|
|
||||||
{
|
|
||||||
HeapTuple tuple;
|
|
||||||
Datum retDatum;
|
|
||||||
char nullflag;
|
|
||||||
TupleDesc tupdesc = scanstate->tupdesc;
|
|
||||||
bool returnsTuple = scanstate->returnsTuple;
|
|
||||||
Node *expr = scanstate->funcexpr;
|
|
||||||
Oid fn_typeid = scanstate->fn_typeid;
|
|
||||||
char fn_typtype = scanstate->fn_typtype;
|
|
||||||
ExprContext *econtext = scanstate->csstate.cstate.cs_ExprContext;
|
|
||||||
TupleTableSlot *slot = scanstate->csstate.css_ScanTupleSlot;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* reset per-tuple memory context before each call of the function.
|
|
||||||
* This cleans up any local memory the function may leak when called.
|
|
||||||
*/
|
|
||||||
ResetExprContext(econtext);
|
|
||||||
|
|
||||||
/*
|
|
||||||
* get the next Datum from the function
|
|
||||||
*/
|
|
||||||
retDatum = ExecEvalExprSwitchContext(expr, econtext, isNull, isDone);
|
|
||||||
|
|
||||||
/*
|
|
||||||
* check to see if we're really done
|
|
||||||
*/
|
|
||||||
if (*isDone == ExprEndResult)
|
|
||||||
slot = NULL;
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (returnsTuple)
|
|
||||||
{
|
|
||||||
/*
|
|
||||||
* Composite data type, i.e. a table's row type
|
|
||||||
* function returns pointer to tts??
|
|
||||||
*/
|
|
||||||
slot = (TupleTableSlot *) retDatum;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* if function return type was RECORD, we need to check to be
|
|
||||||
* sure the structure from the query matches the actual return
|
|
||||||
* structure
|
|
||||||
*/
|
|
||||||
if (fn_typtype == 'p' && fn_typeid == RECORDOID)
|
|
||||||
if (tupledesc_mismatch(tupdesc, slot->ttc_tupleDescriptor))
|
|
||||||
elog(ERROR, "Query-specified return tuple and actual function return tuple do not match");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
/*
|
|
||||||
* Must be a base data type, i.e. scalar
|
|
||||||
* turn it into a tuple
|
|
||||||
*/
|
|
||||||
nullflag = *isNull ? 'n' : ' ';
|
|
||||||
tuple = heap_formtuple(tupdesc, &retDatum, &nullflag);
|
|
||||||
|
|
||||||
/*
|
|
||||||
* save the tuple in the scan tuple slot and return the slot.
|
|
||||||
*/
|
|
||||||
slot = ExecStoreTuple(tuple, /* tuple to store */
|
|
||||||
slot, /* slot to store in */
|
|
||||||
InvalidBuffer, /* buffer associated with
|
|
||||||
* this tuple */
|
|
||||||
true); /* pfree this tuple */
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return slot;
|
|
||||||
}
|
|
||||||
|
|
||||||
static FunctionMode
|
|
||||||
get_functionmode(Node *expr)
|
|
||||||
{
|
|
||||||
/*
|
|
||||||
* for the moment, hardwire this
|
|
||||||
*/
|
|
||||||
return PM_REPEATEDCALL;
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool
|
static bool
|
||||||
tupledesc_mismatch(TupleDesc tupdesc1, TupleDesc tupdesc2)
|
tupledesc_mismatch(TupleDesc tupdesc1, TupleDesc tupdesc2)
|
||||||
|
@ -371,36 +371,61 @@ tuple toaster will decide whether toasting is needed.
|
|||||||
Functions accepting or returning sets
|
Functions accepting or returning sets
|
||||||
-------------------------------------
|
-------------------------------------
|
||||||
|
|
||||||
As of 7.1, Postgres has limited support for functions returning sets;
|
[ this section revised 29-Aug-2002 for 7.3 ]
|
||||||
this is presently handled only in SELECT output expressions, and the
|
|
||||||
behavior is to generate a separate output tuple for each set element.
|
|
||||||
There is no direct support for functions accepting sets; instead, the
|
|
||||||
function will be called multiple times, once for each element of the
|
|
||||||
input set. This behavior will very likely be changed in future releases,
|
|
||||||
but here is how it works now:
|
|
||||||
|
|
||||||
If a function is marked in pg_proc as returning a set, then it is called
|
If a function is marked in pg_proc as returning a set, then it is called
|
||||||
with fcinfo->resultinfo pointing to a node of type ReturnSetInfo. A
|
with fcinfo->resultinfo pointing to a node of type ReturnSetInfo. A
|
||||||
function that desires to return a set should raise an error "called in
|
function that desires to return a set should raise an error "called in
|
||||||
context that does not accept a set result" if resultinfo is NULL or does
|
context that does not accept a set result" if resultinfo is NULL or does
|
||||||
not point to a ReturnSetInfo node. ReturnSetInfo contains a field
|
not point to a ReturnSetInfo node.
|
||||||
|
|
||||||
|
There are currently two modes in which a function can return a set result:
|
||||||
|
value-per-call, or materialize. In value-per-call mode, the function returns
|
||||||
|
one value each time it is called, and finally reports "done" when it has no
|
||||||
|
more values to return. In materialize mode, the function's output set is
|
||||||
|
instantiated in a Tuplestore object; all the values are returned in one call.
|
||||||
|
Additional modes might be added in future.
|
||||||
|
|
||||||
|
ReturnSetInfo contains a field "allowedModes" which is set (by the caller)
|
||||||
|
to a bitmask that's the OR of the modes the caller can support. The actual
|
||||||
|
mode used by the function is returned in another field "returnMode". For
|
||||||
|
backwards-compatibility reasons, returnMode is initialized to value-per-call
|
||||||
|
and need only be changed if the function wants to use a different mode.
|
||||||
|
The function should elog() if it cannot use any of the modes the caller is
|
||||||
|
willing to support.
|
||||||
|
|
||||||
|
Value-per-call mode works like this: ReturnSetInfo contains a field
|
||||||
"isDone", which should be set to one of these values:
|
"isDone", which should be set to one of these values:
|
||||||
|
|
||||||
ExprSingleResult /* expression does not return a set */
|
ExprSingleResult /* expression does not return a set */
|
||||||
ExprMultipleResult /* this result is an element of a set */
|
ExprMultipleResult /* this result is an element of a set */
|
||||||
ExprEndResult /* there are no more elements in the set */
|
ExprEndResult /* there are no more elements in the set */
|
||||||
|
|
||||||
A function returning set returns one set element per call, setting
|
(the caller will initialize it to ExprSingleResult). If the function simply
|
||||||
fcinfo->resultinfo->isDone to ExprMultipleResult for each element.
|
returns a Datum without touching ReturnSetInfo, then the call is over and a
|
||||||
After all elements have been returned, the next call should set
|
single-item set has been returned. To return a set, the function must set
|
||||||
isDone to ExprEndResult and return a null result. (Note it is possible
|
isDone to ExprMultipleResult for each set element. After all elements have
|
||||||
to return an empty set by doing this on the first call.)
|
been returned, the next call should set isDone to ExprEndResult and return a
|
||||||
|
null result. (Note it is possible to return an empty set by doing this on
|
||||||
|
the first call.)
|
||||||
|
|
||||||
As of 7.3, the ReturnSetInfo node also contains a link to the ExprContext
|
The ReturnSetInfo node also contains a link to the ExprContext within which
|
||||||
within which the function is being evaluated. This is useful for functions
|
the function is being evaluated. This is useful for value-per-call functions
|
||||||
that need to close down internal state when they are not run to completion:
|
that need to close down internal state when they are not run to completion:
|
||||||
they can register a shutdown callback function in the ExprContext.
|
they can register a shutdown callback function in the ExprContext.
|
||||||
|
|
||||||
|
Materialize mode works like this: the function creates a Tuplestore holding
|
||||||
|
the (possibly empty) result set, and returns it. There are no multiple calls.
|
||||||
|
The function must also return a TupleDesc that indicates the tuple structure.
|
||||||
|
The Tuplestore and TupleDesc should be created in the context
|
||||||
|
econtext->ecxt_per_query_memory (note this will *not* be the context the
|
||||||
|
function is called in). The function stores pointers to the Tuplestore and
|
||||||
|
TupleDesc into ReturnSetInfo, sets returnMode to indicate materialize mode,
|
||||||
|
and returns null. isDone is not used and should be left at ExprSingleResult.
|
||||||
|
|
||||||
|
There is no support for functions accepting sets; instead, the function will
|
||||||
|
be called multiple times, once for each element of the input set.
|
||||||
|
|
||||||
|
|
||||||
Notes about function handlers
|
Notes about function handlers
|
||||||
-----------------------------
|
-----------------------------
|
||||||
|
@ -7,7 +7,7 @@
|
|||||||
* Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group
|
* Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group
|
||||||
* Portions Copyright (c) 1994, Regents of the University of California
|
* Portions Copyright (c) 1994, Regents of the University of California
|
||||||
*
|
*
|
||||||
* $Id: executor.h,v 1.74 2002/08/29 00:17:06 tgl Exp $
|
* $Id: executor.h,v 1.75 2002/08/30 00:28:41 tgl Exp $
|
||||||
*
|
*
|
||||||
*-------------------------------------------------------------------------
|
*-------------------------------------------------------------------------
|
||||||
*/
|
*/
|
||||||
@ -80,6 +80,9 @@ extern Datum ExecMakeFunctionResult(FunctionCachePtr fcache,
|
|||||||
ExprContext *econtext,
|
ExprContext *econtext,
|
||||||
bool *isNull,
|
bool *isNull,
|
||||||
ExprDoneCond *isDone);
|
ExprDoneCond *isDone);
|
||||||
|
extern Tuplestorestate *ExecMakeTableFunctionResult(Expr *funcexpr,
|
||||||
|
ExprContext *econtext,
|
||||||
|
TupleDesc *returnDesc);
|
||||||
extern Datum ExecEvalExpr(Node *expression, ExprContext *econtext,
|
extern Datum ExecEvalExpr(Node *expression, ExprContext *econtext,
|
||||||
bool *isNull, ExprDoneCond *isDone);
|
bool *isNull, ExprDoneCond *isDone);
|
||||||
extern Datum ExecEvalExprSwitchContext(Node *expression, ExprContext *econtext,
|
extern Datum ExecEvalExprSwitchContext(Node *expression, ExprContext *econtext,
|
||||||
|
@ -11,7 +11,7 @@
|
|||||||
* Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group
|
* Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group
|
||||||
* Portions Copyright (c) 1994, Regents of the University of California
|
* Portions Copyright (c) 1994, Regents of the University of California
|
||||||
*
|
*
|
||||||
* $Id: fmgr.h,v 1.22 2002/06/20 20:29:42 momjian Exp $
|
* $Id: fmgr.h,v 1.23 2002/08/30 00:28:41 tgl Exp $
|
||||||
*
|
*
|
||||||
*-------------------------------------------------------------------------
|
*-------------------------------------------------------------------------
|
||||||
*/
|
*/
|
||||||
@ -45,8 +45,7 @@ typedef struct FmgrInfo
|
|||||||
* count */
|
* count */
|
||||||
bool fn_strict; /* function is "strict" (NULL in => NULL
|
bool fn_strict; /* function is "strict" (NULL in => NULL
|
||||||
* out) */
|
* out) */
|
||||||
bool fn_retset; /* function returns a set (over multiple
|
bool fn_retset; /* function returns a set */
|
||||||
* calls) */
|
|
||||||
void *fn_extra; /* extra space for use by handler */
|
void *fn_extra; /* extra space for use by handler */
|
||||||
MemoryContext fn_mcxt; /* memory context to store fn_extra in */
|
MemoryContext fn_mcxt; /* memory context to store fn_extra in */
|
||||||
} FmgrInfo;
|
} FmgrInfo;
|
||||||
|
@ -7,7 +7,7 @@
|
|||||||
* Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group
|
* Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group
|
||||||
* Portions Copyright (c) 1994, Regents of the University of California
|
* Portions Copyright (c) 1994, Regents of the University of California
|
||||||
*
|
*
|
||||||
* $Id: execnodes.h,v 1.72 2002/08/29 00:17:06 tgl Exp $
|
* $Id: execnodes.h,v 1.73 2002/08/30 00:28:41 tgl Exp $
|
||||||
*
|
*
|
||||||
*-------------------------------------------------------------------------
|
*-------------------------------------------------------------------------
|
||||||
*/
|
*/
|
||||||
@ -21,6 +21,8 @@
|
|||||||
#include "fmgr.h"
|
#include "fmgr.h"
|
||||||
#include "nodes/params.h"
|
#include "nodes/params.h"
|
||||||
#include "nodes/primnodes.h"
|
#include "nodes/primnodes.h"
|
||||||
|
#include "utils/tuplestore.h"
|
||||||
|
|
||||||
|
|
||||||
/* ----------------
|
/* ----------------
|
||||||
* IndexInfo information
|
* IndexInfo information
|
||||||
@ -125,20 +127,32 @@ typedef enum
|
|||||||
ExprEndResult /* there are no more elements in the set */
|
ExprEndResult /* there are no more elements in the set */
|
||||||
} ExprDoneCond;
|
} ExprDoneCond;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Return modes for functions returning sets. Note values must be chosen
|
||||||
|
* as separate bits so that a bitmask can be formed to indicate supported
|
||||||
|
* modes.
|
||||||
|
*/
|
||||||
|
typedef enum
|
||||||
|
{
|
||||||
|
SFRM_ValuePerCall = 0x01, /* one value returned per call */
|
||||||
|
SFRM_Materialize = 0x02 /* result set instantiated in Tuplestore */
|
||||||
|
} SetFunctionReturnMode;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* When calling a function that might return a set (multiple rows),
|
* When calling a function that might return a set (multiple rows),
|
||||||
* a node of this type is passed as fcinfo->resultinfo to allow
|
* a node of this type is passed as fcinfo->resultinfo to allow
|
||||||
* return status to be passed back. A function returning set should
|
* return status to be passed back. A function returning set should
|
||||||
* raise an error if no such resultinfo is provided. The ExprContext
|
* raise an error if no such resultinfo is provided.
|
||||||
* in which the function is being called is also made available.
|
|
||||||
*
|
|
||||||
* XXX this mechanism is a quick hack and probably needs to be redesigned.
|
|
||||||
*/
|
*/
|
||||||
typedef struct ReturnSetInfo
|
typedef struct ReturnSetInfo
|
||||||
{
|
{
|
||||||
NodeTag type;
|
NodeTag type;
|
||||||
ExprDoneCond isDone;
|
ExprContext *econtext; /* context function is being called in */
|
||||||
ExprContext *econtext;
|
int allowedModes; /* bitmask: return modes caller can handle */
|
||||||
|
SetFunctionReturnMode returnMode; /* actual return mode */
|
||||||
|
ExprDoneCond isDone; /* status for ValuePerCall mode */
|
||||||
|
Tuplestorestate *setResult; /* return object for Materialize mode */
|
||||||
|
TupleDesc setDesc; /* descriptor for Materialize mode */
|
||||||
} ReturnSetInfo;
|
} ReturnSetInfo;
|
||||||
|
|
||||||
/* ----------------
|
/* ----------------
|
||||||
@ -509,32 +523,17 @@ typedef struct SubqueryScanState
|
|||||||
* Function nodes are used to scan the results of a
|
* Function nodes are used to scan the results of a
|
||||||
* function appearing in FROM (typically a function returning set).
|
* function appearing in FROM (typically a function returning set).
|
||||||
*
|
*
|
||||||
* functionmode function operating mode
|
* tupdesc expected return tuple description
|
||||||
* tupdesc function's return tuple description
|
|
||||||
* tuplestorestate private state of tuplestore.c
|
* tuplestorestate private state of tuplestore.c
|
||||||
* funcexpr function expression being evaluated
|
* funcexpr function expression being evaluated
|
||||||
* returnsTuple does function return tuples?
|
|
||||||
* fn_typeid OID of function return type
|
|
||||||
* fn_typtype return type's typtype
|
|
||||||
* ----------------
|
* ----------------
|
||||||
*/
|
*/
|
||||||
typedef enum FunctionMode
|
|
||||||
{
|
|
||||||
PM_REPEATEDCALL,
|
|
||||||
PM_MATERIALIZE,
|
|
||||||
PM_QUERY
|
|
||||||
} FunctionMode;
|
|
||||||
|
|
||||||
typedef struct FunctionScanState
|
typedef struct FunctionScanState
|
||||||
{
|
{
|
||||||
CommonScanState csstate; /* its first field is NodeTag */
|
CommonScanState csstate; /* its first field is NodeTag */
|
||||||
FunctionMode functionmode;
|
|
||||||
TupleDesc tupdesc;
|
TupleDesc tupdesc;
|
||||||
void *tuplestorestate;
|
Tuplestorestate *tuplestorestate;
|
||||||
Node *funcexpr;
|
Node *funcexpr;
|
||||||
bool returnsTuple;
|
|
||||||
Oid fn_typeid;
|
|
||||||
char fn_typtype;
|
|
||||||
} FunctionScanState;
|
} FunctionScanState;
|
||||||
|
|
||||||
/* ----------------------------------------------------------------
|
/* ----------------------------------------------------------------
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
#
|
#
|
||||||
# Makefile for the plpgsql shared object
|
# Makefile for the plpgsql shared object
|
||||||
#
|
#
|
||||||
# $Header: /cvsroot/pgsql/src/pl/plpgsql/src/Makefile,v 1.20 2001/11/16 16:32:33 petere Exp $
|
# $Header: /cvsroot/pgsql/src/pl/plpgsql/src/Makefile,v 1.21 2002/08/30 00:28:41 tgl Exp $
|
||||||
#
|
#
|
||||||
#-------------------------------------------------------------------------
|
#-------------------------------------------------------------------------
|
||||||
|
|
||||||
@ -78,7 +78,7 @@ endif
|
|||||||
|
|
||||||
$(srcdir)/pl_scan.c: scan.l
|
$(srcdir)/pl_scan.c: scan.l
|
||||||
ifdef FLEX
|
ifdef FLEX
|
||||||
$(FLEX) -i $(FLEXFLAGS) -Pplpgsql_base_yy -o'$@' $<
|
$(FLEX) $(FLEXFLAGS) -Pplpgsql_base_yy -o'$@' $<
|
||||||
else
|
else
|
||||||
@$(missing) flex $< $@
|
@$(missing) flex $< $@
|
||||||
endif
|
endif
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
* procedural language
|
* procedural language
|
||||||
*
|
*
|
||||||
* IDENTIFICATION
|
* IDENTIFICATION
|
||||||
* $Header: /cvsroot/pgsql/src/pl/plpgsql/src/gram.y,v 1.35 2002/08/28 20:46:24 momjian Exp $
|
* $Header: /cvsroot/pgsql/src/pl/plpgsql/src/gram.y,v 1.36 2002/08/30 00:28:41 tgl Exp $
|
||||||
*
|
*
|
||||||
* This software is copyrighted by Jan Wieck - Hamburg.
|
* This software is copyrighted by Jan Wieck - Hamburg.
|
||||||
*
|
*
|
||||||
@ -48,7 +48,7 @@ static PLpgSQL_type *read_datatype(int tok);
|
|||||||
static PLpgSQL_stmt *make_select_stmt(void);
|
static PLpgSQL_stmt *make_select_stmt(void);
|
||||||
static PLpgSQL_stmt *make_fetch_stmt(void);
|
static PLpgSQL_stmt *make_fetch_stmt(void);
|
||||||
static PLpgSQL_expr *make_tupret_expr(PLpgSQL_row *row);
|
static PLpgSQL_expr *make_tupret_expr(PLpgSQL_row *row);
|
||||||
static void check_assignable(PLpgSQL_datum *datum);
|
static void check_assignable(PLpgSQL_datum *datum);
|
||||||
|
|
||||||
%}
|
%}
|
||||||
|
|
||||||
@ -121,8 +121,8 @@ static void check_assignable(PLpgSQL_datum *datum);
|
|||||||
%type <stmts> proc_sect, proc_stmts, stmt_else, loop_body
|
%type <stmts> proc_sect, proc_stmts, stmt_else, loop_body
|
||||||
%type <stmt> proc_stmt, pl_block
|
%type <stmt> proc_stmt, pl_block
|
||||||
%type <stmt> stmt_assign, stmt_if, stmt_loop, stmt_while, stmt_exit
|
%type <stmt> stmt_assign, stmt_if, stmt_loop, stmt_while, stmt_exit
|
||||||
%type <stmt> stmt_return, stmt_raise, stmt_execsql, stmt_fori
|
%type <stmt> stmt_return, stmt_return_next, stmt_raise, stmt_execsql
|
||||||
%type <stmt> stmt_fors, stmt_select, stmt_perform
|
%type <stmt> stmt_fori, stmt_fors, stmt_select, stmt_perform
|
||||||
%type <stmt> stmt_dynexecute, stmt_dynfors, stmt_getdiag
|
%type <stmt> stmt_dynexecute, stmt_dynfors, stmt_getdiag
|
||||||
%type <stmt> stmt_open, stmt_fetch, stmt_close
|
%type <stmt> stmt_open, stmt_fetch, stmt_close
|
||||||
|
|
||||||
@ -166,6 +166,7 @@ static void check_assignable(PLpgSQL_datum *datum);
|
|||||||
%token K_IS
|
%token K_IS
|
||||||
%token K_LOG
|
%token K_LOG
|
||||||
%token K_LOOP
|
%token K_LOOP
|
||||||
|
%token K_NEXT
|
||||||
%token K_NOT
|
%token K_NOT
|
||||||
%token K_NOTICE
|
%token K_NOTICE
|
||||||
%token K_NULL
|
%token K_NULL
|
||||||
@ -177,6 +178,7 @@ static void check_assignable(PLpgSQL_datum *datum);
|
|||||||
%token K_RENAME
|
%token K_RENAME
|
||||||
%token K_RESULT_OID
|
%token K_RESULT_OID
|
||||||
%token K_RETURN
|
%token K_RETURN
|
||||||
|
%token K_RETURN_NEXT
|
||||||
%token K_REVERSE
|
%token K_REVERSE
|
||||||
%token K_SELECT
|
%token K_SELECT
|
||||||
%token K_THEN
|
%token K_THEN
|
||||||
@ -516,10 +518,8 @@ decl_aliasitem : T_WORD
|
|||||||
|
|
||||||
plpgsql_convert_ident(yytext, &name, 1);
|
plpgsql_convert_ident(yytext, &name, 1);
|
||||||
if (name[0] != '$')
|
if (name[0] != '$')
|
||||||
{
|
yyerror("can only alias positional parameters");
|
||||||
plpgsql_error_lineno = yylineno;
|
|
||||||
elog(ERROR, "can only alias positional parameters");
|
|
||||||
}
|
|
||||||
plpgsql_ns_setlocal(false);
|
plpgsql_ns_setlocal(false);
|
||||||
nsi = plpgsql_ns_lookup(name, NULL);
|
nsi = plpgsql_ns_lookup(name, NULL);
|
||||||
if (nsi == NULL)
|
if (nsi == NULL)
|
||||||
@ -609,14 +609,11 @@ decl_defval : ';'
|
|||||||
switch (tok)
|
switch (tok)
|
||||||
{
|
{
|
||||||
case 0:
|
case 0:
|
||||||
plpgsql_error_lineno = lno;
|
yyerror("unexpected end of file");
|
||||||
elog(ERROR, "unexpected end of file");
|
|
||||||
case K_NULL:
|
case K_NULL:
|
||||||
if (yylex() != ';')
|
if (yylex() != ';')
|
||||||
{
|
yyerror("expected ; after NULL");
|
||||||
plpgsql_error_lineno = lno;
|
|
||||||
elog(ERROR, "expected ; after NULL");
|
|
||||||
}
|
|
||||||
free(expr);
|
free(expr);
|
||||||
plpgsql_dstring_free(&ds);
|
plpgsql_dstring_free(&ds);
|
||||||
|
|
||||||
@ -628,10 +625,8 @@ decl_defval : ';'
|
|||||||
while ((tok = yylex()) != ';')
|
while ((tok = yylex()) != ';')
|
||||||
{
|
{
|
||||||
if (tok == 0)
|
if (tok == 0)
|
||||||
{
|
yyerror("unterminated default value");
|
||||||
plpgsql_error_lineno = lno;
|
|
||||||
elog(ERROR, "unterminated default value");
|
|
||||||
}
|
|
||||||
if (plpgsql_SpaceScanned)
|
if (plpgsql_SpaceScanned)
|
||||||
plpgsql_dstring_append(&ds, " ");
|
plpgsql_dstring_append(&ds, " ");
|
||||||
plpgsql_dstring_append(&ds, yytext);
|
plpgsql_dstring_append(&ds, yytext);
|
||||||
@ -663,7 +658,8 @@ proc_sect :
|
|||||||
|
|
||||||
proc_stmts : proc_stmts proc_stmt
|
proc_stmts : proc_stmts proc_stmt
|
||||||
{
|
{
|
||||||
if ($1->stmts_used == $1->stmts_alloc) {
|
if ($1->stmts_used == $1->stmts_alloc)
|
||||||
|
{
|
||||||
$1->stmts_alloc *= 2;
|
$1->stmts_alloc *= 2;
|
||||||
$1->stmts = realloc($1->stmts, sizeof(PLpgSQL_stmt *) * $1->stmts_alloc);
|
$1->stmts = realloc($1->stmts, sizeof(PLpgSQL_stmt *) * $1->stmts_alloc);
|
||||||
}
|
}
|
||||||
@ -708,6 +704,8 @@ proc_stmt : pl_block ';'
|
|||||||
{ $$ = $1; }
|
{ $$ = $1; }
|
||||||
| stmt_return
|
| stmt_return
|
||||||
{ $$ = $1; }
|
{ $$ = $1; }
|
||||||
|
| stmt_return_next
|
||||||
|
{ $$ = $1; }
|
||||||
| stmt_raise
|
| stmt_raise
|
||||||
{ $$ = $1; }
|
{ $$ = $1; }
|
||||||
| stmt_execsql
|
| stmt_execsql
|
||||||
@ -1121,45 +1119,73 @@ stmt_exit : K_EXIT lno opt_exitlabel opt_exitcond
|
|||||||
stmt_return : K_RETURN lno
|
stmt_return : K_RETURN lno
|
||||||
{
|
{
|
||||||
PLpgSQL_stmt_return *new;
|
PLpgSQL_stmt_return *new;
|
||||||
PLpgSQL_expr *expr = NULL;
|
|
||||||
int tok;
|
|
||||||
|
|
||||||
new = malloc(sizeof(PLpgSQL_stmt_return));
|
new = malloc(sizeof(PLpgSQL_stmt_return));
|
||||||
memset(new, 0, sizeof(PLpgSQL_stmt_return));
|
memset(new, 0, sizeof(PLpgSQL_stmt_return));
|
||||||
|
|
||||||
if (plpgsql_curr_compile->fn_retistuple)
|
if (plpgsql_curr_compile->fn_retistuple &&
|
||||||
|
!plpgsql_curr_compile->fn_retset)
|
||||||
{
|
{
|
||||||
new->retistuple = true;
|
|
||||||
new->retrecno = -1;
|
new->retrecno = -1;
|
||||||
switch (tok = yylex())
|
switch (yylex())
|
||||||
{
|
{
|
||||||
case K_NULL:
|
case K_NULL:
|
||||||
expr = NULL;
|
new->expr = NULL;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case T_ROW:
|
case T_ROW:
|
||||||
expr = make_tupret_expr(yylval.row);
|
new->expr = make_tupret_expr(yylval.row);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case T_RECORD:
|
case T_RECORD:
|
||||||
new->retrecno = yylval.rec->recno;
|
new->retrecno = yylval.rec->recno;
|
||||||
expr = NULL;
|
new->expr = NULL;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
yyerror("return type mismatch in function returning table row");
|
yyerror("return type mismatch in function returning tuple");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (yylex() != ';')
|
if (yylex() != ';')
|
||||||
yyerror("expected ';'");
|
yyerror("expected ';'");
|
||||||
} else {
|
|
||||||
new->retistuple = false;
|
|
||||||
expr = plpgsql_read_expression(';', ";");
|
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
new->expr = plpgsql_read_expression(';', ";");
|
||||||
|
|
||||||
new->cmd_type = PLPGSQL_STMT_RETURN;
|
new->cmd_type = PLPGSQL_STMT_RETURN;
|
||||||
new->lineno = $2;
|
new->lineno = $2;
|
||||||
new->expr = expr;
|
|
||||||
|
$$ = (PLpgSQL_stmt *)new;
|
||||||
|
}
|
||||||
|
;
|
||||||
|
|
||||||
|
/* FIXME: this syntax needs work, RETURN NEXT breaks stmt_return */
|
||||||
|
stmt_return_next: K_RETURN_NEXT lno
|
||||||
|
{
|
||||||
|
PLpgSQL_stmt_return_next *new;
|
||||||
|
|
||||||
|
new = malloc(sizeof(PLpgSQL_stmt_return_next));
|
||||||
|
memset(new, 0, sizeof(PLpgSQL_stmt_return_next));
|
||||||
|
|
||||||
|
new->cmd_type = PLPGSQL_STMT_RETURN_NEXT;
|
||||||
|
new->lineno = $2;
|
||||||
|
|
||||||
|
if (plpgsql_curr_compile->fn_retistuple)
|
||||||
|
{
|
||||||
|
int tok = yylex();
|
||||||
|
|
||||||
|
if (tok == T_RECORD)
|
||||||
|
new->rec = yylval.rec;
|
||||||
|
else if (tok == T_ROW)
|
||||||
|
new->row = yylval.row;
|
||||||
|
else
|
||||||
|
yyerror("Incorrect argument to RETURN NEXT");
|
||||||
|
|
||||||
|
if (yylex() != ';')
|
||||||
|
yyerror("Expected ';'");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
new->expr = plpgsql_read_expression(';', ";");
|
||||||
|
|
||||||
$$ = (PLpgSQL_stmt *)new;
|
$$ = (PLpgSQL_stmt *)new;
|
||||||
}
|
}
|
||||||
@ -1226,7 +1252,7 @@ raise_level : K_EXCEPTION
|
|||||||
}
|
}
|
||||||
| K_DEBUG
|
| K_DEBUG
|
||||||
{
|
{
|
||||||
$$ = DEBUG5;
|
$$ = DEBUG1;
|
||||||
}
|
}
|
||||||
;
|
;
|
||||||
|
|
||||||
@ -1377,10 +1403,7 @@ stmt_open : K_OPEN lno cursor_varptr
|
|||||||
cp += strlen(cp) - 1;
|
cp += strlen(cp) - 1;
|
||||||
|
|
||||||
if (*cp != ')')
|
if (*cp != ')')
|
||||||
{
|
yyerror("missing )");
|
||||||
plpgsql_error_lineno = yylineno;
|
|
||||||
elog(ERROR, "missing )");
|
|
||||||
}
|
|
||||||
*cp = '\0';
|
*cp = '\0';
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@ -1433,10 +1456,8 @@ stmt_close : K_CLOSE lno cursor_variable ';'
|
|||||||
cursor_varptr : T_VARIABLE
|
cursor_varptr : T_VARIABLE
|
||||||
{
|
{
|
||||||
if (yylval.variable->dtype != PLPGSQL_DTYPE_VAR)
|
if (yylval.variable->dtype != PLPGSQL_DTYPE_VAR)
|
||||||
{
|
yyerror("cursor variable must be a simple variable");
|
||||||
plpgsql_error_lineno = yylineno;
|
|
||||||
elog(ERROR, "cursor variable must be a simple variable");
|
|
||||||
}
|
|
||||||
if (((PLpgSQL_var *) yylval.variable)->datatype->typoid != REFCURSOROID)
|
if (((PLpgSQL_var *) yylval.variable)->datatype->typoid != REFCURSOROID)
|
||||||
{
|
{
|
||||||
plpgsql_error_lineno = yylineno;
|
plpgsql_error_lineno = yylineno;
|
||||||
@ -1450,10 +1471,8 @@ cursor_varptr : T_VARIABLE
|
|||||||
cursor_variable : T_VARIABLE
|
cursor_variable : T_VARIABLE
|
||||||
{
|
{
|
||||||
if (yylval.variable->dtype != PLPGSQL_DTYPE_VAR)
|
if (yylval.variable->dtype != PLPGSQL_DTYPE_VAR)
|
||||||
{
|
yyerror("cursor variable must be a simple variable");
|
||||||
plpgsql_error_lineno = yylineno;
|
|
||||||
elog(ERROR, "cursor variable must be a simple variable");
|
|
||||||
}
|
|
||||||
if (((PLpgSQL_var *) yylval.variable)->datatype->typoid != REFCURSOROID)
|
if (((PLpgSQL_var *) yylval.variable)->datatype->typoid != REFCURSOROID)
|
||||||
{
|
{
|
||||||
plpgsql_error_lineno = yylineno;
|
plpgsql_error_lineno = yylineno;
|
||||||
@ -1906,18 +1925,14 @@ make_fetch_stmt(void)
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
plpgsql_error_lineno = yylineno;
|
yyerror("syntax error");
|
||||||
elog(ERROR, "syntax error at '%s'", yytext);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!have_nexttok)
|
if (!have_nexttok)
|
||||||
tok = yylex();
|
tok = yylex();
|
||||||
|
|
||||||
if (tok != ';')
|
if (tok != ';')
|
||||||
{
|
yyerror("syntax error");
|
||||||
plpgsql_error_lineno = yylineno;
|
|
||||||
elog(ERROR, "syntax error at '%s'", yytext);
|
|
||||||
}
|
|
||||||
|
|
||||||
fetch = malloc(sizeof(PLpgSQL_stmt_select));
|
fetch = malloc(sizeof(PLpgSQL_stmt_select));
|
||||||
memset(fetch, 0, sizeof(PLpgSQL_stmt_fetch));
|
memset(fetch, 0, sizeof(PLpgSQL_stmt_fetch));
|
||||||
@ -1976,11 +1991,10 @@ check_assignable(PLpgSQL_datum *datum)
|
|||||||
/* always assignable? */
|
/* always assignable? */
|
||||||
break;
|
break;
|
||||||
case PLPGSQL_DTYPE_TRIGARG:
|
case PLPGSQL_DTYPE_TRIGARG:
|
||||||
plpgsql_error_lineno = yylineno;
|
yyerror("cannot assign to tg_argv");
|
||||||
elog(ERROR, "cannot assign to tg_argv");
|
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
elog(ERROR, "check_assignable: unexpected datum type");
|
yyerror("check_assignable: unexpected datum type");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
* procedural language
|
* procedural language
|
||||||
*
|
*
|
||||||
* IDENTIFICATION
|
* IDENTIFICATION
|
||||||
* $Header: /cvsroot/pgsql/src/pl/plpgsql/src/pl_comp.c,v 1.48 2002/08/28 20:46:24 momjian Exp $
|
* $Header: /cvsroot/pgsql/src/pl/plpgsql/src/pl_comp.c,v 1.49 2002/08/30 00:28:41 tgl Exp $
|
||||||
*
|
*
|
||||||
* This software is copyrighted by Jan Wieck - Hamburg.
|
* This software is copyrighted by Jan Wieck - Hamburg.
|
||||||
*
|
*
|
||||||
@ -37,8 +37,6 @@
|
|||||||
|
|
||||||
#include "plpgsql.h"
|
#include "plpgsql.h"
|
||||||
|
|
||||||
#include <unistd.h>
|
|
||||||
#include <fcntl.h>
|
|
||||||
#include <ctype.h>
|
#include <ctype.h>
|
||||||
#include <setjmp.h>
|
#include <setjmp.h>
|
||||||
|
|
||||||
@ -52,9 +50,6 @@
|
|||||||
#include "catalog/pg_class.h"
|
#include "catalog/pg_class.h"
|
||||||
#include "catalog/pg_proc.h"
|
#include "catalog/pg_proc.h"
|
||||||
#include "catalog/pg_type.h"
|
#include "catalog/pg_type.h"
|
||||||
#include "commands/trigger.h"
|
|
||||||
#include "executor/spi.h"
|
|
||||||
#include "fmgr.h"
|
|
||||||
#include "nodes/makefuncs.h"
|
#include "nodes/makefuncs.h"
|
||||||
#include "parser/gramparse.h"
|
#include "parser/gramparse.h"
|
||||||
#include "parser/parse_type.h"
|
#include "parser/parse_type.h"
|
||||||
@ -217,6 +212,7 @@ plpgsql_compile(Oid fn_oid, int functype)
|
|||||||
typeStruct = (Form_pg_type) GETSTRUCT(typeTup);
|
typeStruct = (Form_pg_type) GETSTRUCT(typeTup);
|
||||||
|
|
||||||
/* Disallow pseudotype result, except VOID */
|
/* Disallow pseudotype result, except VOID */
|
||||||
|
/* XXX someday allow RECORD? */
|
||||||
if (typeStruct->typtype == 'p')
|
if (typeStruct->typtype == 'p')
|
||||||
{
|
{
|
||||||
if (procStruct->prorettype == VOIDOID)
|
if (procStruct->prorettype == VOIDOID)
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
* procedural language
|
* procedural language
|
||||||
*
|
*
|
||||||
* IDENTIFICATION
|
* 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.
|
* This software is copyrighted by Jan Wieck - Hamburg.
|
||||||
*
|
*
|
||||||
@ -35,12 +35,6 @@
|
|||||||
*
|
*
|
||||||
**********************************************************************/
|
**********************************************************************/
|
||||||
|
|
||||||
#include <stdio.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <stdarg.h>
|
|
||||||
#include <unistd.h>
|
|
||||||
#include <fcntl.h>
|
|
||||||
#include <string.h>
|
|
||||||
#include <ctype.h>
|
#include <ctype.h>
|
||||||
#include <setjmp.h>
|
#include <setjmp.h>
|
||||||
|
|
||||||
@ -50,10 +44,8 @@
|
|||||||
#include "access/heapam.h"
|
#include "access/heapam.h"
|
||||||
#include "catalog/pg_proc.h"
|
#include "catalog/pg_proc.h"
|
||||||
#include "catalog/pg_type.h"
|
#include "catalog/pg_type.h"
|
||||||
#include "commands/trigger.h"
|
|
||||||
#include "executor/spi.h"
|
|
||||||
#include "executor/spi_priv.h"
|
#include "executor/spi_priv.h"
|
||||||
#include "fmgr.h"
|
#include "funcapi.h"
|
||||||
#include "optimizer/clauses.h"
|
#include "optimizer/clauses.h"
|
||||||
#include "parser/parse_expr.h"
|
#include "parser/parse_expr.h"
|
||||||
#include "tcop/tcopprot.h"
|
#include "tcop/tcopprot.h"
|
||||||
@ -105,6 +97,8 @@ static int exec_stmt_exit(PLpgSQL_execstate * estate,
|
|||||||
PLpgSQL_stmt_exit * stmt);
|
PLpgSQL_stmt_exit * stmt);
|
||||||
static int exec_stmt_return(PLpgSQL_execstate * estate,
|
static int exec_stmt_return(PLpgSQL_execstate * estate,
|
||||||
PLpgSQL_stmt_return * stmt);
|
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,
|
static int exec_stmt_raise(PLpgSQL_execstate * estate,
|
||||||
PLpgSQL_stmt_raise * stmt);
|
PLpgSQL_stmt_raise * stmt);
|
||||||
static int exec_stmt_execsql(PLpgSQL_execstate * estate,
|
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,
|
static int exec_stmt_dynfors(PLpgSQL_execstate * estate,
|
||||||
PLpgSQL_stmt_dynfors * stmt);
|
PLpgSQL_stmt_dynfors * stmt);
|
||||||
|
|
||||||
static void plpgsql_estate_setup(PLpgSQL_execstate * estate,
|
static void plpgsql_estate_setup(PLpgSQL_execstate *estate,
|
||||||
PLpgSQL_function * func);
|
PLpgSQL_function *func,
|
||||||
|
ReturnSetInfo *rsi);
|
||||||
static void exec_eval_cleanup(PLpgSQL_execstate * estate);
|
static void exec_eval_cleanup(PLpgSQL_execstate * estate);
|
||||||
|
|
||||||
static void exec_prepare_plan(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,
|
int32 reqtypmod,
|
||||||
bool *isnull);
|
bool *isnull);
|
||||||
static void exec_set_found(PLpgSQL_execstate * estate, bool state);
|
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
|
* 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
|
* 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
|
* We got a return value - process it
|
||||||
*/
|
*/
|
||||||
error_info_stmt = NULL;
|
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;
|
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)
|
if (estate.retistuple)
|
||||||
{
|
{
|
||||||
@ -455,7 +477,7 @@ plpgsql_exec_trigger(PLpgSQL_function * func,
|
|||||||
/*
|
/*
|
||||||
* Setup the execution state
|
* Setup the execution state
|
||||||
*/
|
*/
|
||||||
plpgsql_estate_setup(&estate, func);
|
plpgsql_estate_setup(&estate, func, NULL);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Make local execution copies of all the datums
|
* 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");
|
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,
|
* Check that the returned tuple structure has the same attributes,
|
||||||
* the relation that fired the trigger has.
|
* the relation that fired the trigger has.
|
||||||
@ -862,6 +887,8 @@ exec_stmt(PLpgSQL_execstate * estate, PLpgSQL_stmt * stmt)
|
|||||||
save_estmt = error_info_stmt;
|
save_estmt = error_info_stmt;
|
||||||
error_info_stmt = stmt;
|
error_info_stmt = stmt;
|
||||||
|
|
||||||
|
CHECK_FOR_INTERRUPTS();
|
||||||
|
|
||||||
switch (stmt->cmd_type)
|
switch (stmt->cmd_type)
|
||||||
{
|
{
|
||||||
case PLPGSQL_STMT_BLOCK:
|
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);
|
rc = exec_stmt_return(estate, (PLpgSQL_stmt_return *) stmt);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case PLPGSQL_STMT_RETURN_NEXT:
|
||||||
|
rc = exec_stmt_return_next(estate, (PLpgSQL_stmt_return_next *) stmt);
|
||||||
|
break;
|
||||||
|
|
||||||
case PLPGSQL_STMT_RAISE:
|
case PLPGSQL_STMT_RAISE:
|
||||||
rc = exec_stmt_raise(estate, (PLpgSQL_stmt_raise *) stmt);
|
rc = exec_stmt_raise(estate, (PLpgSQL_stmt_raise *) stmt);
|
||||||
break;
|
break;
|
||||||
@ -1302,13 +1333,10 @@ exec_stmt_fors(PLpgSQL_execstate * estate, PLpgSQL_stmt_fors * stmt)
|
|||||||
*/
|
*/
|
||||||
if (stmt->rec != NULL)
|
if (stmt->rec != NULL)
|
||||||
rec = (PLpgSQL_rec *) (estate->datums[stmt->rec->recno]);
|
rec = (PLpgSQL_rec *) (estate->datums[stmt->rec->recno]);
|
||||||
|
else if (stmt->row != NULL)
|
||||||
|
row = (PLpgSQL_row *) (estate->datums[stmt->row->rowno]);
|
||||||
else
|
else
|
||||||
{
|
elog(ERROR, "unsupported target in exec_stmt_fors()");
|
||||||
if (stmt->row != NULL)
|
|
||||||
row = (PLpgSQL_row *) (estate->datums[stmt->row->rowno]);
|
|
||||||
else
|
|
||||||
elog(ERROR, "unsupported target in exec_stmt_fors()");
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Open the implicit cursor for the statement and fetch the initial 10
|
* 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
|
static int
|
||||||
exec_stmt_return(PLpgSQL_execstate * estate, PLpgSQL_stmt_return * stmt)
|
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)
|
if (estate->retistuple)
|
||||||
{
|
{
|
||||||
/* initialize for null result tuple */
|
/* initialize for null result tuple */
|
||||||
@ -1532,13 +1568,155 @@ exec_stmt_return(PLpgSQL_execstate * estate, PLpgSQL_stmt_return * stmt)
|
|||||||
return PLPGSQL_RC_RETURN;
|
return PLPGSQL_RC_RETURN;
|
||||||
}
|
}
|
||||||
|
|
||||||
estate->retval = exec_eval_expr(estate, stmt->expr,
|
if (estate->fn_rettype == VOIDOID)
|
||||||
&(estate->retisnull),
|
{
|
||||||
&(estate->rettype));
|
/* 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;
|
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
|
* 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
|
static void
|
||||||
plpgsql_estate_setup(PLpgSQL_execstate * estate,
|
plpgsql_estate_setup(PLpgSQL_execstate *estate,
|
||||||
PLpgSQL_function * func)
|
PLpgSQL_function *func,
|
||||||
|
ReturnSetInfo *rsi)
|
||||||
{
|
{
|
||||||
estate->retval = (Datum) 0;
|
estate->retval = (Datum) 0;
|
||||||
estate->retisnull = true;
|
estate->retisnull = true;
|
||||||
estate->rettype = InvalidOid;
|
estate->rettype = InvalidOid;
|
||||||
|
|
||||||
|
estate->fn_rettype = func->fn_rettype;
|
||||||
estate->retistuple = func->fn_retistuple;
|
estate->retistuple = func->fn_retistuple;
|
||||||
estate->rettupdesc = NULL;
|
|
||||||
estate->retisset = func->fn_retset;
|
estate->retisset = func->fn_retset;
|
||||||
|
|
||||||
|
estate->rettupdesc = NULL;
|
||||||
estate->exitlabel = NULL;
|
estate->exitlabel = NULL;
|
||||||
|
|
||||||
|
estate->tuple_store = NULL;
|
||||||
|
estate->tuple_store_cxt = NULL;
|
||||||
|
estate->rsi = rsi;
|
||||||
|
|
||||||
estate->trig_nargs = 0;
|
estate->trig_nargs = 0;
|
||||||
estate->trig_argv = NULL;
|
estate->trig_argv = NULL;
|
||||||
|
|
||||||
@ -2099,13 +2285,10 @@ exec_stmt_dynfors(PLpgSQL_execstate * estate, PLpgSQL_stmt_dynfors * stmt)
|
|||||||
*/
|
*/
|
||||||
if (stmt->rec != NULL)
|
if (stmt->rec != NULL)
|
||||||
rec = (PLpgSQL_rec *) (estate->datums[stmt->rec->recno]);
|
rec = (PLpgSQL_rec *) (estate->datums[stmt->rec->recno]);
|
||||||
|
else if (stmt->row != NULL)
|
||||||
|
row = (PLpgSQL_row *) (estate->datums[stmt->row->rowno]);
|
||||||
else
|
else
|
||||||
{
|
elog(ERROR, "unsupported target in exec_stmt_dynfors()");
|
||||||
if (stmt->row != NULL)
|
|
||||||
row = (PLpgSQL_row *) (estate->datums[stmt->row->rowno]);
|
|
||||||
else
|
|
||||||
elog(ERROR, "unsupported target in exec_stmt_fors()");
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Evaluate the string expression after the EXECUTE keyword. It's
|
* Evaluate the string expression after the EXECUTE keyword. It's
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
* procedural language
|
* procedural language
|
||||||
*
|
*
|
||||||
* IDENTIFICATION
|
* IDENTIFICATION
|
||||||
* $Header: /cvsroot/pgsql/src/pl/plpgsql/src/pl_funcs.c,v 1.20 2002/08/29 07:22:30 ishii Exp $
|
* $Header: /cvsroot/pgsql/src/pl/plpgsql/src/pl_funcs.c,v 1.21 2002/08/30 00:28:41 tgl Exp $
|
||||||
*
|
*
|
||||||
* This software is copyrighted by Jan Wieck - Hamburg.
|
* This software is copyrighted by Jan Wieck - Hamburg.
|
||||||
*
|
*
|
||||||
@ -35,12 +35,6 @@
|
|||||||
*
|
*
|
||||||
**********************************************************************/
|
**********************************************************************/
|
||||||
|
|
||||||
#include <stdio.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <stdarg.h>
|
|
||||||
#include <unistd.h>
|
|
||||||
#include <fcntl.h>
|
|
||||||
#include <string.h>
|
|
||||||
#include <ctype.h>
|
#include <ctype.h>
|
||||||
|
|
||||||
#include "plpgsql.h"
|
#include "plpgsql.h"
|
||||||
@ -272,9 +266,7 @@ plpgsql_ns_lookup(char *name, char *label)
|
|||||||
return ns->items[i];
|
return ns->items[i];
|
||||||
}
|
}
|
||||||
if (ns_localmode)
|
if (ns_localmode)
|
||||||
{
|
|
||||||
return NULL; /* name not found in current namespace */
|
return NULL; /* name not found in current namespace */
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return NULL;
|
return NULL;
|
||||||
@ -461,6 +453,8 @@ plpgsql_stmt_typename(PLpgSQL_stmt * stmt)
|
|||||||
return "exit";
|
return "exit";
|
||||||
case PLPGSQL_STMT_RETURN:
|
case PLPGSQL_STMT_RETURN:
|
||||||
return "return";
|
return "return";
|
||||||
|
case PLPGSQL_STMT_RETURN_NEXT:
|
||||||
|
return "return next";
|
||||||
case PLPGSQL_STMT_RAISE:
|
case PLPGSQL_STMT_RAISE:
|
||||||
return "raise";
|
return "raise";
|
||||||
case PLPGSQL_STMT_EXECSQL:
|
case PLPGSQL_STMT_EXECSQL:
|
||||||
@ -500,6 +494,7 @@ static void dump_fors(PLpgSQL_stmt_fors * stmt);
|
|||||||
static void dump_select(PLpgSQL_stmt_select * stmt);
|
static void dump_select(PLpgSQL_stmt_select * stmt);
|
||||||
static void dump_exit(PLpgSQL_stmt_exit * stmt);
|
static void dump_exit(PLpgSQL_stmt_exit * stmt);
|
||||||
static void dump_return(PLpgSQL_stmt_return * stmt);
|
static void dump_return(PLpgSQL_stmt_return * stmt);
|
||||||
|
static void dump_return_next(PLpgSQL_stmt_return_next * stmt);
|
||||||
static void dump_raise(PLpgSQL_stmt_raise * stmt);
|
static void dump_raise(PLpgSQL_stmt_raise * stmt);
|
||||||
static void dump_execsql(PLpgSQL_stmt_execsql * stmt);
|
static void dump_execsql(PLpgSQL_stmt_execsql * stmt);
|
||||||
static void dump_dynexecute(PLpgSQL_stmt_dynexecute * stmt);
|
static void dump_dynexecute(PLpgSQL_stmt_dynexecute * stmt);
|
||||||
@ -556,6 +551,9 @@ dump_stmt(PLpgSQL_stmt * stmt)
|
|||||||
case PLPGSQL_STMT_RETURN:
|
case PLPGSQL_STMT_RETURN:
|
||||||
dump_return((PLpgSQL_stmt_return *) stmt);
|
dump_return((PLpgSQL_stmt_return *) stmt);
|
||||||
break;
|
break;
|
||||||
|
case PLPGSQL_STMT_RETURN_NEXT:
|
||||||
|
dump_return_next((PLpgSQL_stmt_return_next *) stmt);
|
||||||
|
break;
|
||||||
case PLPGSQL_STMT_RAISE:
|
case PLPGSQL_STMT_RAISE:
|
||||||
dump_raise((PLpgSQL_stmt_raise *) stmt);
|
dump_raise((PLpgSQL_stmt_raise *) stmt);
|
||||||
break;
|
break;
|
||||||
@ -839,6 +837,20 @@ dump_return(PLpgSQL_stmt_return * stmt)
|
|||||||
printf("\n");
|
printf("\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
dump_return_next(PLpgSQL_stmt_return_next * stmt)
|
||||||
|
{
|
||||||
|
dump_ind();
|
||||||
|
printf("RETURN NEXT ");
|
||||||
|
if (stmt->rec != NULL)
|
||||||
|
printf("target = %d %s\n", stmt->rec->recno, stmt->rec->refname);
|
||||||
|
else if (stmt->row != NULL)
|
||||||
|
printf("target = %d %s\n", stmt->row->rowno, stmt->row->refname);
|
||||||
|
else if (stmt->expr != NULL)
|
||||||
|
dump_expr(stmt->expr);
|
||||||
|
printf("\n");
|
||||||
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
dump_raise(PLpgSQL_stmt_raise * stmt)
|
dump_raise(PLpgSQL_stmt_raise * stmt)
|
||||||
{
|
{
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
* procedural language
|
* procedural language
|
||||||
*
|
*
|
||||||
* IDENTIFICATION
|
* IDENTIFICATION
|
||||||
* $Header: /cvsroot/pgsql/src/pl/plpgsql/src/pl_handler.c,v 1.11 2002/06/15 19:54:24 momjian Exp $
|
* $Header: /cvsroot/pgsql/src/pl/plpgsql/src/pl_handler.c,v 1.12 2002/08/30 00:28:41 tgl Exp $
|
||||||
*
|
*
|
||||||
* This software is copyrighted by Jan Wieck - Hamburg.
|
* This software is copyrighted by Jan Wieck - Hamburg.
|
||||||
*
|
*
|
||||||
@ -35,13 +35,6 @@
|
|||||||
*
|
*
|
||||||
**********************************************************************/
|
**********************************************************************/
|
||||||
|
|
||||||
#include <stdio.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <stdarg.h>
|
|
||||||
#include <unistd.h>
|
|
||||||
#include <fcntl.h>
|
|
||||||
#include <string.h>
|
|
||||||
|
|
||||||
#include "plpgsql.h"
|
#include "plpgsql.h"
|
||||||
#include "pl.tab.h"
|
#include "pl.tab.h"
|
||||||
|
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
* procedural language
|
* procedural language
|
||||||
*
|
*
|
||||||
* IDENTIFICATION
|
* IDENTIFICATION
|
||||||
* $Header: /cvsroot/pgsql/src/pl/plpgsql/src/plpgsql.h,v 1.25 2002/08/08 01:36:05 tgl Exp $
|
* $Header: /cvsroot/pgsql/src/pl/plpgsql/src/plpgsql.h,v 1.26 2002/08/30 00:28:41 tgl Exp $
|
||||||
*
|
*
|
||||||
* This software is copyrighted by Jan Wieck - Hamburg.
|
* This software is copyrighted by Jan Wieck - Hamburg.
|
||||||
*
|
*
|
||||||
@ -40,8 +40,10 @@
|
|||||||
#include "postgres.h"
|
#include "postgres.h"
|
||||||
|
|
||||||
#include "fmgr.h"
|
#include "fmgr.h"
|
||||||
|
#include "miscadmin.h"
|
||||||
#include "executor/spi.h"
|
#include "executor/spi.h"
|
||||||
#include "commands/trigger.h"
|
#include "commands/trigger.h"
|
||||||
|
#include "utils/tuplestore.h"
|
||||||
|
|
||||||
/**********************************************************************
|
/**********************************************************************
|
||||||
* Definitions
|
* Definitions
|
||||||
@ -90,6 +92,7 @@ enum
|
|||||||
PLPGSQL_STMT_SELECT,
|
PLPGSQL_STMT_SELECT,
|
||||||
PLPGSQL_STMT_EXIT,
|
PLPGSQL_STMT_EXIT,
|
||||||
PLPGSQL_STMT_RETURN,
|
PLPGSQL_STMT_RETURN,
|
||||||
|
PLPGSQL_STMT_RETURN_NEXT,
|
||||||
PLPGSQL_STMT_RAISE,
|
PLPGSQL_STMT_RAISE,
|
||||||
PLPGSQL_STMT_EXECSQL,
|
PLPGSQL_STMT_EXECSQL,
|
||||||
PLPGSQL_STMT_DYNEXECUTE,
|
PLPGSQL_STMT_DYNEXECUTE,
|
||||||
@ -420,11 +423,18 @@ typedef struct
|
|||||||
{ /* RETURN statement */
|
{ /* RETURN statement */
|
||||||
int cmd_type;
|
int cmd_type;
|
||||||
int lineno;
|
int lineno;
|
||||||
bool retistuple;
|
|
||||||
PLpgSQL_expr *expr;
|
PLpgSQL_expr *expr;
|
||||||
int retrecno;
|
int retrecno;
|
||||||
} PLpgSQL_stmt_return;
|
} PLpgSQL_stmt_return;
|
||||||
|
|
||||||
|
typedef struct
|
||||||
|
{ /* RETURN NEXT statement */
|
||||||
|
int cmd_type;
|
||||||
|
int lineno;
|
||||||
|
PLpgSQL_rec *rec;
|
||||||
|
PLpgSQL_row *row;
|
||||||
|
PLpgSQL_expr *expr;
|
||||||
|
} PLpgSQL_stmt_return_next;
|
||||||
|
|
||||||
typedef struct
|
typedef struct
|
||||||
{ /* RAISE statement */
|
{ /* RAISE statement */
|
||||||
@ -494,12 +504,19 @@ typedef struct
|
|||||||
{ /* Runtime execution data */
|
{ /* Runtime execution data */
|
||||||
Datum retval;
|
Datum retval;
|
||||||
bool retisnull;
|
bool retisnull;
|
||||||
Oid rettype;
|
Oid rettype; /* type of current retval */
|
||||||
|
|
||||||
|
Oid fn_rettype; /* info about declared function rettype */
|
||||||
bool retistuple;
|
bool retistuple;
|
||||||
TupleDesc rettupdesc;
|
|
||||||
bool retisset;
|
bool retisset;
|
||||||
|
|
||||||
|
TupleDesc rettupdesc;
|
||||||
char *exitlabel;
|
char *exitlabel;
|
||||||
|
|
||||||
|
Tuplestorestate *tuple_store; /* SRFs accumulate results here */
|
||||||
|
MemoryContext tuple_store_cxt;
|
||||||
|
ReturnSetInfo *rsi;
|
||||||
|
|
||||||
int trig_nargs;
|
int trig_nargs;
|
||||||
Datum *trig_argv;
|
Datum *trig_argv;
|
||||||
|
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
* procedural language
|
* procedural language
|
||||||
*
|
*
|
||||||
* IDENTIFICATION
|
* IDENTIFICATION
|
||||||
* $Header: /cvsroot/pgsql/src/pl/plpgsql/src/Attic/scan.l,v 1.21 2002/08/08 01:36:05 tgl Exp $
|
* $Header: /cvsroot/pgsql/src/pl/plpgsql/src/Attic/scan.l,v 1.22 2002/08/30 00:28:41 tgl Exp $
|
||||||
*
|
*
|
||||||
* This software is copyrighted by Jan Wieck - Hamburg.
|
* This software is copyrighted by Jan Wieck - Hamburg.
|
||||||
*
|
*
|
||||||
@ -46,6 +46,8 @@ static int scanner_functype;
|
|||||||
static int scanner_typereported;
|
static int scanner_typereported;
|
||||||
static int pushback_token;
|
static int pushback_token;
|
||||||
static bool have_pushback_token;
|
static bool have_pushback_token;
|
||||||
|
static int lookahead_token;
|
||||||
|
static bool have_lookahead_token;
|
||||||
|
|
||||||
int plpgsql_SpaceScanned = 0;
|
int plpgsql_SpaceScanned = 0;
|
||||||
|
|
||||||
@ -134,6 +136,7 @@ into { return K_INTO; }
|
|||||||
is { return K_IS; }
|
is { return K_IS; }
|
||||||
log { return K_LOG; }
|
log { return K_LOG; }
|
||||||
loop { return K_LOOP; }
|
loop { return K_LOOP; }
|
||||||
|
next { return K_NEXT; }
|
||||||
not { return K_NOT; }
|
not { return K_NOT; }
|
||||||
notice { return K_NOTICE; }
|
notice { return K_NOTICE; }
|
||||||
null { return K_NULL; }
|
null { return K_NULL; }
|
||||||
@ -255,18 +258,50 @@ plpgsql_input(char *buf, int *result, int max)
|
|||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* This is the yylex routine called from outside. It exists to provide
|
* This is the yylex routine called from outside. It exists to provide
|
||||||
* a token pushback facility.
|
* a pushback facility, as well as to allow us to parse syntax that
|
||||||
|
* requires more than one token of lookahead.
|
||||||
*/
|
*/
|
||||||
int
|
int
|
||||||
plpgsql_yylex(void)
|
plpgsql_yylex(void)
|
||||||
{
|
{
|
||||||
|
int cur_token;
|
||||||
|
|
||||||
if (have_pushback_token)
|
if (have_pushback_token)
|
||||||
{
|
{
|
||||||
have_pushback_token = false;
|
have_pushback_token = false;
|
||||||
return pushback_token;
|
cur_token = pushback_token;
|
||||||
}
|
}
|
||||||
return yylex();
|
else if (have_lookahead_token)
|
||||||
|
{
|
||||||
|
have_lookahead_token = false;
|
||||||
|
cur_token = lookahead_token;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
cur_token = yylex();
|
||||||
|
|
||||||
|
/* Do we need to look ahead for a possible multiword token? */
|
||||||
|
switch (cur_token)
|
||||||
|
{
|
||||||
|
/* RETURN NEXT must be reduced to a single token */
|
||||||
|
case K_RETURN:
|
||||||
|
if (!have_lookahead_token)
|
||||||
|
{
|
||||||
|
lookahead_token = yylex();
|
||||||
|
have_lookahead_token = true;
|
||||||
|
}
|
||||||
|
if (lookahead_token == K_NEXT)
|
||||||
|
{
|
||||||
|
have_lookahead_token = false;
|
||||||
|
cur_token = K_RETURN_NEXT;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return cur_token;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -312,4 +347,5 @@ plpgsql_setinput(char *source, int functype)
|
|||||||
scanner_typereported = 0;
|
scanner_typereported = 0;
|
||||||
|
|
||||||
have_pushback_token = false;
|
have_pushback_token = false;
|
||||||
|
have_lookahead_token = false;
|
||||||
}
|
}
|
||||||
|
@ -1535,6 +1535,10 @@ ERROR: system "notthere" does not exist
|
|||||||
insert into IFace values ('IF', 'orion', 'ethernet_interface_name_too_long', '');
|
insert into IFace values ('IF', 'orion', 'ethernet_interface_name_too_long', '');
|
||||||
ERROR: IFace slotname "IF.orion.ethernet_interface_name_too_long" too long (20 char max)
|
ERROR: IFace slotname "IF.orion.ethernet_interface_name_too_long" too long (20 char max)
|
||||||
--
|
--
|
||||||
|
-- The following tests are unrelated to the scenario outlined above;
|
||||||
|
-- they merely exercise specific parts of PL/PgSQL
|
||||||
|
--
|
||||||
|
--
|
||||||
-- Test recursion, per bug report 7-Sep-01
|
-- Test recursion, per bug report 7-Sep-01
|
||||||
--
|
--
|
||||||
CREATE FUNCTION recursion_test(int,int) RETURNS text AS '
|
CREATE FUNCTION recursion_test(int,int) RETURNS text AS '
|
||||||
@ -1557,7 +1561,7 @@ SELECT recursion_test(4,3);
|
|||||||
-- Test the FOUND magic variable
|
-- Test the FOUND magic variable
|
||||||
--
|
--
|
||||||
CREATE TABLE found_test_tbl (a int);
|
CREATE TABLE found_test_tbl (a int);
|
||||||
create function test_found ()
|
create function test_found()
|
||||||
returns boolean as '
|
returns boolean as '
|
||||||
declare
|
declare
|
||||||
begin
|
begin
|
||||||
@ -1609,3 +1613,70 @@ select * from found_test_tbl;
|
|||||||
6
|
6
|
||||||
(6 rows)
|
(6 rows)
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Test set-returning functions for PL/pgSQL
|
||||||
|
--
|
||||||
|
create function test_table_func_rec() returns setof found_test_tbl as '
|
||||||
|
DECLARE
|
||||||
|
rec RECORD;
|
||||||
|
BEGIN
|
||||||
|
FOR rec IN select * from found_test_tbl LOOP
|
||||||
|
RETURN NEXT rec;
|
||||||
|
END LOOP;
|
||||||
|
RETURN;
|
||||||
|
END;' language 'plpgsql';
|
||||||
|
select * from test_table_func_rec();
|
||||||
|
a
|
||||||
|
-----
|
||||||
|
2
|
||||||
|
100
|
||||||
|
3
|
||||||
|
4
|
||||||
|
5
|
||||||
|
6
|
||||||
|
(6 rows)
|
||||||
|
|
||||||
|
create function test_table_func_row() returns setof found_test_tbl as '
|
||||||
|
DECLARE
|
||||||
|
row found_test_tbl%ROWTYPE;
|
||||||
|
BEGIN
|
||||||
|
FOR row IN select * from found_test_tbl LOOP
|
||||||
|
RETURN NEXT row;
|
||||||
|
END LOOP;
|
||||||
|
RETURN;
|
||||||
|
END;' language 'plpgsql';
|
||||||
|
select * from test_table_func_row();
|
||||||
|
a
|
||||||
|
-----
|
||||||
|
2
|
||||||
|
100
|
||||||
|
3
|
||||||
|
4
|
||||||
|
5
|
||||||
|
6
|
||||||
|
(6 rows)
|
||||||
|
|
||||||
|
create function test_ret_set_scalar(int,int) returns setof int as '
|
||||||
|
DECLARE
|
||||||
|
i int;
|
||||||
|
BEGIN
|
||||||
|
FOR i IN $1 .. $2 LOOP
|
||||||
|
RETURN NEXT i + 1;
|
||||||
|
END LOOP;
|
||||||
|
RETURN;
|
||||||
|
END;' language 'plpgsql';
|
||||||
|
select * from test_ret_set_scalar(1,10);
|
||||||
|
test_ret_set_scalar
|
||||||
|
---------------------
|
||||||
|
2
|
||||||
|
3
|
||||||
|
4
|
||||||
|
5
|
||||||
|
6
|
||||||
|
7
|
||||||
|
8
|
||||||
|
9
|
||||||
|
10
|
||||||
|
11
|
||||||
|
(10 rows)
|
||||||
|
|
||||||
|
@ -1419,6 +1419,12 @@ delete from HSlot;
|
|||||||
insert into IFace values ('IF', 'notthere', 'eth0', '');
|
insert into IFace values ('IF', 'notthere', 'eth0', '');
|
||||||
insert into IFace values ('IF', 'orion', 'ethernet_interface_name_too_long', '');
|
insert into IFace values ('IF', 'orion', 'ethernet_interface_name_too_long', '');
|
||||||
|
|
||||||
|
|
||||||
|
--
|
||||||
|
-- The following tests are unrelated to the scenario outlined above;
|
||||||
|
-- they merely exercise specific parts of PL/PgSQL
|
||||||
|
--
|
||||||
|
|
||||||
--
|
--
|
||||||
-- Test recursion, per bug report 7-Sep-01
|
-- Test recursion, per bug report 7-Sep-01
|
||||||
--
|
--
|
||||||
@ -1440,7 +1446,7 @@ SELECT recursion_test(4,3);
|
|||||||
--
|
--
|
||||||
CREATE TABLE found_test_tbl (a int);
|
CREATE TABLE found_test_tbl (a int);
|
||||||
|
|
||||||
create function test_found ()
|
create function test_found()
|
||||||
returns boolean as '
|
returns boolean as '
|
||||||
declare
|
declare
|
||||||
begin
|
begin
|
||||||
@ -1478,3 +1484,43 @@ create function test_found ()
|
|||||||
|
|
||||||
select test_found();
|
select test_found();
|
||||||
select * from found_test_tbl;
|
select * from found_test_tbl;
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Test set-returning functions for PL/pgSQL
|
||||||
|
--
|
||||||
|
|
||||||
|
create function test_table_func_rec() returns setof found_test_tbl as '
|
||||||
|
DECLARE
|
||||||
|
rec RECORD;
|
||||||
|
BEGIN
|
||||||
|
FOR rec IN select * from found_test_tbl LOOP
|
||||||
|
RETURN NEXT rec;
|
||||||
|
END LOOP;
|
||||||
|
RETURN;
|
||||||
|
END;' language 'plpgsql';
|
||||||
|
|
||||||
|
select * from test_table_func_rec();
|
||||||
|
|
||||||
|
create function test_table_func_row() returns setof found_test_tbl as '
|
||||||
|
DECLARE
|
||||||
|
row found_test_tbl%ROWTYPE;
|
||||||
|
BEGIN
|
||||||
|
FOR row IN select * from found_test_tbl LOOP
|
||||||
|
RETURN NEXT row;
|
||||||
|
END LOOP;
|
||||||
|
RETURN;
|
||||||
|
END;' language 'plpgsql';
|
||||||
|
|
||||||
|
select * from test_table_func_row();
|
||||||
|
|
||||||
|
create function test_ret_set_scalar(int,int) returns setof int as '
|
||||||
|
DECLARE
|
||||||
|
i int;
|
||||||
|
BEGIN
|
||||||
|
FOR i IN $1 .. $2 LOOP
|
||||||
|
RETURN NEXT i + 1;
|
||||||
|
END LOOP;
|
||||||
|
RETURN;
|
||||||
|
END;' language 'plpgsql';
|
||||||
|
|
||||||
|
select * from test_ret_set_scalar(1,10);
|
||||||
|
Reference in New Issue
Block a user