diff options
author | Tom Lane <tgl@sss.pgh.pa.us> | 2002-08-30 00:28:41 +0000 |
---|---|---|
committer | Tom Lane <tgl@sss.pgh.pa.us> | 2002-08-30 00:28:41 +0000 |
commit | e107f3a7e3feb7eaef8853ba117465f4f3f8ceed (patch) | |
tree | d28fb377b7d00f8171c208cc5ad9ceaec7a623ab /src | |
parent | 82ccb420d5c6f62cec1bf042cf0b6472fabdff42 (diff) | |
download | postgresql-e107f3a7e3feb7eaef8853ba117465f4f3f8ceed.tar.gz postgresql-e107f3a7e3feb7eaef8853ba117465f4f3f8ceed.zip |
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.
Diffstat (limited to 'src')
-rw-r--r-- | src/backend/executor/execQual.c | 329 | ||||
-rw-r--r-- | src/backend/executor/nodeFunctionscan.c | 171 | ||||
-rw-r--r-- | src/backend/utils/fmgr/README | 55 | ||||
-rw-r--r-- | src/include/executor/executor.h | 5 | ||||
-rw-r--r-- | src/include/fmgr.h | 5 | ||||
-rw-r--r-- | src/include/nodes/execnodes.h | 47 | ||||
-rw-r--r-- | src/pl/plpgsql/src/Makefile | 4 | ||||
-rw-r--r-- | src/pl/plpgsql/src/gram.y | 122 | ||||
-rw-r--r-- | src/pl/plpgsql/src/pl_comp.c | 8 | ||||
-rw-r--r-- | src/pl/plpgsql/src/pl_exec.c | 253 | ||||
-rw-r--r-- | src/pl/plpgsql/src/pl_funcs.c | 30 | ||||
-rw-r--r-- | src/pl/plpgsql/src/pl_handler.c | 9 | ||||
-rw-r--r-- | src/pl/plpgsql/src/plpgsql.h | 25 | ||||
-rw-r--r-- | src/pl/plpgsql/src/scan.l | 46 | ||||
-rw-r--r-- | src/test/regress/expected/plpgsql.out | 73 | ||||
-rw-r--r-- | src/test/regress/sql/plpgsql.sql | 48 |
16 files changed, 874 insertions, 356 deletions
diff --git a/src/backend/executor/execQual.c b/src/backend/executor/execQual.c index e481ea11596..b422adc2061 100644 --- a/src/backend/executor/execQual.c +++ b/src/backend/executor/execQual.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/executor/execQual.c,v 1.101 2002/08/26 17:53:57 tgl Exp $ + * $Header: /cvsroot/pgsql/src/backend/executor/execQual.c,v 1.102 2002/08/30 00:28:41 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -35,12 +35,15 @@ #include "postgres.h" #include "access/heapam.h" +#include "catalog/pg_type.h" #include "executor/execdebug.h" #include "executor/functions.h" #include "executor/nodeSubplan.h" +#include "miscadmin.h" #include "utils/array.h" #include "utils/builtins.h" #include "utils/fcache.h" +#include "utils/lsyscache.h" /* static function decls */ @@ -646,9 +649,6 @@ ExecEvalFuncArgs(FunctionCallInfo fcinfo, * ExecMakeFunctionResult * * Evaluate the arguments to a function and then the function itself. - * - * NOTE: econtext is used only for evaluating the argument expressions; - * it is not passed to the function itself. */ Datum ExecMakeFunctionResult(FunctionCachePtr fcache, @@ -707,6 +707,11 @@ ExecMakeFunctionResult(FunctionCachePtr fcache, fcinfo.resultinfo = (Node *) &rsinfo; rsinfo.type = T_ReturnSetInfo; rsinfo.econtext = econtext; + rsinfo.allowedModes = (int) SFRM_ValuePerCall; + rsinfo.returnMode = SFRM_ValuePerCall; + /* isDone is filled below */ + rsinfo.setResult = NULL; + rsinfo.setDesc = NULL; } /* @@ -837,10 +842,240 @@ ExecMakeFunctionResult(FunctionCachePtr fcache, } +/* + * ExecMakeTableFunctionResult + * + * Evaluate a table function, producing a materialized result in a Tuplestore + * object. (If function returns an empty set, we just return NULL instead.) + */ +Tuplestorestate * +ExecMakeTableFunctionResult(Expr *funcexpr, + ExprContext *econtext, + TupleDesc *returnDesc) +{ + Tuplestorestate *tupstore = NULL; + TupleDesc tupdesc = NULL; + Func *func; + List *argList; + FunctionCachePtr fcache; + FunctionCallInfoData fcinfo; + ReturnSetInfo rsinfo; /* for functions returning sets */ + ExprDoneCond argDone; + MemoryContext callerContext; + MemoryContext oldcontext; + TupleTableSlot *slot; + bool first_time = true; + bool returnsTuple = false; + + /* Extract data from function-call expression node */ + if (!funcexpr || !IsA(funcexpr, Expr) || funcexpr->opType != FUNC_EXPR) + elog(ERROR, "ExecMakeTableFunctionResult: expression is not a function call"); + func = (Func *) funcexpr->oper; + argList = funcexpr->args; + + /* + * get the fcache from the Func node. If it is NULL, then initialize it + */ + fcache = func->func_fcache; + if (fcache == NULL) + { + fcache = init_fcache(func->funcid, length(argList), + econtext->ecxt_per_query_memory); + func->func_fcache = fcache; + } + + /* + * Evaluate the function's argument list. + * + * Note: ideally, we'd do this in the per-tuple context, but then the + * argument values would disappear when we reset the context in the + * inner loop. So do it in caller context. Perhaps we should make a + * separate context just to hold the evaluated arguments? + */ + MemSet(&fcinfo, 0, sizeof(fcinfo)); + fcinfo.flinfo = &(fcache->func); + argDone = ExecEvalFuncArgs(&fcinfo, argList, econtext); + /* We don't allow sets in the arguments of the table function */ + if (argDone != ExprSingleResult) + elog(ERROR, "Set-valued function called in context that cannot accept a set"); + + /* + * If function is strict, and there are any NULL arguments, skip + * calling the function and return NULL (actually an empty set). + */ + if (fcache->func.fn_strict) + { + int i; + + for (i = 0; i < fcinfo.nargs; i++) + { + if (fcinfo.argnull[i]) + { + *returnDesc = NULL; + return NULL; + } + } + } + + /* + * If function returns set, prepare a resultinfo node for + * communication + */ + if (fcache->func.fn_retset) + { + fcinfo.resultinfo = (Node *) &rsinfo; + rsinfo.type = T_ReturnSetInfo; + rsinfo.econtext = econtext; + rsinfo.allowedModes = (int) (SFRM_ValuePerCall | SFRM_Materialize); + } + /* we set these fields always since we examine them below */ + rsinfo.returnMode = SFRM_ValuePerCall; + /* isDone is filled below */ + rsinfo.setResult = NULL; + rsinfo.setDesc = NULL; + + /* + * Switch to short-lived context for calling the function. + */ + callerContext = MemoryContextSwitchTo(econtext->ecxt_per_tuple_memory); + + /* + * Loop to handle the ValuePerCall protocol. + */ + for (;;) + { + Datum result; + HeapTuple tuple; + + /* + * reset per-tuple memory context before each call of the function. + * This cleans up any local memory the function may leak when called. + */ + ResetExprContext(econtext); + + /* Call the function one time */ + fcinfo.isnull = false; + rsinfo.isDone = ExprSingleResult; + result = FunctionCallInvoke(&fcinfo); + + /* Which protocol does function want to use? */ + if (rsinfo.returnMode == SFRM_ValuePerCall) + { + /* + * Check for end of result set. + * + * Note: if function returns an empty set, we don't build a + * tupdesc or tuplestore (since we can't get a tupdesc in the + * function-returning-tuple case) + */ + if (rsinfo.isDone == ExprEndResult) + break; + /* + * If first time through, build tupdesc and tuplestore for result + */ + if (first_time) + { + Oid funcrettype = funcexpr->typeOid; + + oldcontext = MemoryContextSwitchTo(econtext->ecxt_per_query_memory); + if (funcrettype == RECORDOID || + get_typtype(funcrettype) == 'c') + { + /* + * Composite type, so function should have returned a + * TupleTableSlot; use its descriptor + */ + slot = (TupleTableSlot *) DatumGetPointer(result); + if (fcinfo.isnull || !slot || !IsA(slot, TupleTableSlot) || + !slot->ttc_tupleDescriptor) + elog(ERROR, "ExecMakeTableFunctionResult: Invalid result from function returning tuple"); + tupdesc = CreateTupleDescCopy(slot->ttc_tupleDescriptor); + returnsTuple = true; + } + else + { + /* + * Scalar type, so make a single-column descriptor + */ + tupdesc = CreateTemplateTupleDesc(1, WITHOUTOID); + TupleDescInitEntry(tupdesc, + (AttrNumber) 1, + "column", + funcrettype, + -1, + 0, + false); + } + tupstore = tuplestore_begin_heap(true, /* randomAccess */ + SortMem); + MemoryContextSwitchTo(oldcontext); + rsinfo.setResult = tupstore; + rsinfo.setDesc = tupdesc; + } + + /* + * Store current resultset item. + */ + if (returnsTuple) + { + slot = (TupleTableSlot *) DatumGetPointer(result); + if (fcinfo.isnull || !slot || !IsA(slot, TupleTableSlot) || + TupIsNull(slot)) + elog(ERROR, "ExecMakeTableFunctionResult: Invalid result from function returning tuple"); + tuple = slot->val; + } + else + { + char nullflag; + + nullflag = fcinfo.isnull ? 'n' : ' '; + tuple = heap_formtuple(tupdesc, &result, &nullflag); + } + + oldcontext = MemoryContextSwitchTo(econtext->ecxt_per_query_memory); + tuplestore_puttuple(tupstore, tuple); + MemoryContextSwitchTo(oldcontext); + + /* + * Are we done? + */ + if (rsinfo.isDone != ExprMultipleResult) + break; + } + else if (rsinfo.returnMode == SFRM_Materialize) + { + /* check we're on the same page as the function author */ + if (!first_time || rsinfo.isDone != ExprSingleResult) + elog(ERROR, "ExecMakeTableFunctionResult: Materialize-mode protocol not followed"); + /* Done evaluating the set result */ + break; + } + else + elog(ERROR, "ExecMakeTableFunctionResult: unknown returnMode %d", + (int) rsinfo.returnMode); + + first_time = false; + } + + /* If we have a locally-created tupstore, close it up */ + if (tupstore) + { + MemoryContextSwitchTo(econtext->ecxt_per_query_memory); + tuplestore_donestoring(tupstore); + } + + MemoryContextSwitchTo(callerContext); + + /* The returned pointers are those in rsinfo */ + *returnDesc = rsinfo.setDesc; + return rsinfo.setResult; +} + + /* ---------------------------------------------------------------- * ExecEvalOper - * ExecEvalDistinct * ExecEvalFunc + * ExecEvalDistinct * * Evaluate the functional result of a list of arguments by calling the * function manager. @@ -887,6 +1122,48 @@ ExecEvalOper(Expr *opClause, } /* ---------------------------------------------------------------- + * 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 * * IS DISTINCT FROM must evaluate arguments to determine whether @@ -961,48 +1238,6 @@ ExecEvalDistinct(Expr *opClause, } /* ---------------------------------------------------------------- - * ExecEvalFunc - * ---------------------------------------------------------------- - */ - -static Datum -ExecEvalFunc(Expr *funcClause, - ExprContext *econtext, - bool *isNull, - ExprDoneCond *isDone) -{ - Func *func; - List *argList; - FunctionCachePtr fcache; - - /* - * we extract the oid of the function associated with the func node - * and then pass the work onto ExecMakeFunctionResult which evaluates - * the arguments and returns the result of calling the function on the - * evaluated arguments. - * - * this is nearly identical to the ExecEvalOper code. - */ - func = (Func *) funcClause->oper; - argList = funcClause->args; - - /* - * get the fcache from the Func node. If it is NULL, then initialize - * it - */ - fcache = func->func_fcache; - if (fcache == NULL) - { - fcache = init_fcache(func->funcid, length(argList), - econtext->ecxt_per_query_memory); - func->func_fcache = fcache; - } - - return ExecMakeFunctionResult(fcache, argList, econtext, - isNull, isDone); -} - -/* ---------------------------------------------------------------- * ExecEvalNot * ExecEvalOr * ExecEvalAnd diff --git a/src/backend/executor/nodeFunctionscan.c b/src/backend/executor/nodeFunctionscan.c index d58d312238e..3d2c160fb4f 100644 --- a/src/backend/executor/nodeFunctionscan.c +++ b/src/backend/executor/nodeFunctionscan.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/executor/nodeFunctionscan.c,v 1.7 2002/08/29 17:14:33 tgl Exp $ + * $Header: /cvsroot/pgsql/src/backend/executor/nodeFunctionscan.c,v 1.8 2002/08/30 00:28:41 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -22,7 +22,6 @@ */ #include "postgres.h" -#include "miscadmin.h" #include "access/heapam.h" #include "catalog/pg_type.h" #include "executor/execdebug.h" @@ -32,17 +31,10 @@ #include "parser/parsetree.h" #include "parser/parse_expr.h" #include "parser/parse_type.h" -#include "storage/lmgr.h" -#include "tcop/pquery.h" #include "utils/lsyscache.h" -#include "utils/syscache.h" -#include "utils/tuplestore.h" + static TupleTableSlot *FunctionNext(FunctionScan *node); -static TupleTableSlot *function_getonetuple(FunctionScanState *scanstate, - bool *isNull, - ExprDoneCond *isDone); -static FunctionMode get_functionmode(Node *expr); static bool tupledesc_mismatch(TupleDesc tupdesc1, TupleDesc tupdesc2); /* ---------------------------------------------------------------- @@ -76,53 +68,42 @@ FunctionNext(FunctionScan *node) tuplestorestate = scanstate->tuplestorestate; /* - * If first time through, read all tuples from function and pass them to - * tuplestore.c. Subsequent calls just fetch tuples from tuplestore. + * If first time through, read all tuples from function and put them + * in a tuplestore. Subsequent calls just fetch tuples from tuplestore. */ if (tuplestorestate == NULL) { - /* - * Initialize tuplestore module. - */ - tuplestorestate = tuplestore_begin_heap(true, /* randomAccess */ - SortMem); - - scanstate->tuplestorestate = (void *) tuplestorestate; - - /* - * Compute all the function tuples and pass to tuplestore. - */ - for (;;) - { - bool isNull; - ExprDoneCond isDone; - - isNull = false; - isDone = ExprSingleResult; - slot = function_getonetuple(scanstate, &isNull, &isDone); - if (TupIsNull(slot)) - break; - - tuplestore_puttuple(tuplestorestate, (void *) slot->val); - ExecClearTuple(slot); + ExprContext *econtext = scanstate->csstate.cstate.cs_ExprContext; + TupleDesc funcTupdesc; - if (isDone != ExprMultipleResult) - break; - } + scanstate->tuplestorestate = tuplestorestate = + ExecMakeTableFunctionResult((Expr *) scanstate->funcexpr, + econtext, + &funcTupdesc); /* - * Complete the store. + * If function provided a tupdesc, cross-check it. We only really + * need to do this for functions returning RECORD, but might as well + * do it always. */ - tuplestore_donestoring(tuplestorestate); + if (funcTupdesc && + tupledesc_mismatch(scanstate->tupdesc, funcTupdesc)) + elog(ERROR, "Query-specified return tuple and actual function return tuple do not match"); } /* * Get the next tuple from tuplestore. Return NULL if no more tuples. */ slot = scanstate->csstate.css_ScanTupleSlot; - heapTuple = tuplestore_getheaptuple(tuplestorestate, - ScanDirectionIsForward(direction), - &should_free); + if (tuplestorestate) + heapTuple = tuplestore_getheaptuple(tuplestorestate, + ScanDirectionIsForward(direction), + &should_free); + else + { + heapTuple = NULL; + should_free = false; + } return ExecStoreTuple(heapTuple, slot, InvalidBuffer, should_free); } @@ -219,7 +200,6 @@ ExecInitFunctionScan(FunctionScan *node, EState *estate, Plan *parent) rel = relation_open(funcrelid, AccessShareLock); tupdesc = CreateTupleDescCopy(RelationGetDescr(rel)); relation_close(rel, AccessShareLock); - scanstate->returnsTuple = true; } else if (functyptype == 'b' || functyptype == 'd') { @@ -236,7 +216,6 @@ ExecInitFunctionScan(FunctionScan *node, EState *estate, Plan *parent) -1, 0, false); - scanstate->returnsTuple = false; } else if (functyptype == 'p' && funcrettype == RECORDOID) { @@ -246,13 +225,10 @@ ExecInitFunctionScan(FunctionScan *node, EState *estate, Plan *parent) List *coldeflist = rte->coldeflist; tupdesc = BuildDescForRelation(coldeflist); - scanstate->returnsTuple = true; } else elog(ERROR, "Unknown kind of return type specified for function"); - scanstate->fn_typeid = funcrettype; - scanstate->fn_typtype = functyptype; scanstate->tupdesc = tupdesc; ExecSetSlotDescriptor(scanstate->csstate.css_ScanTupleSlot, tupdesc, false); @@ -263,8 +239,6 @@ ExecInitFunctionScan(FunctionScan *node, EState *estate, Plan *parent) scanstate->tuplestorestate = NULL; scanstate->funcexpr = rte->funcexpr; - scanstate->functionmode = get_functionmode(rte->funcexpr); - scanstate->csstate.cstate.cs_TupFromTlist = false; /* @@ -322,7 +296,7 @@ ExecEndFunctionScan(FunctionScan *node) * Release tuplestore resources */ if (scanstate->tuplestorestate != NULL) - tuplestore_end((Tuplestorestate *) scanstate->tuplestorestate); + tuplestore_end(scanstate->tuplestorestate); scanstate->tuplestorestate = NULL; } @@ -345,7 +319,7 @@ ExecFunctionMarkPos(FunctionScan *node) if (!scanstate->tuplestorestate) return; - tuplestore_markpos((Tuplestorestate *) scanstate->tuplestorestate); + tuplestore_markpos(scanstate->tuplestorestate); } /* ---------------------------------------------------------------- @@ -367,7 +341,7 @@ ExecFunctionRestrPos(FunctionScan *node) if (!scanstate->tuplestorestate) return; - tuplestore_restorepos((Tuplestorestate *) scanstate->tuplestorestate); + tuplestore_restorepos(scanstate->tuplestorestate); } /* ---------------------------------------------------------------- @@ -402,98 +376,13 @@ ExecFunctionReScan(FunctionScan *node, ExprContext *exprCtxt, Plan *parent) */ if (node->scan.plan.chgParam != NULL) { - tuplestore_end((Tuplestorestate *) scanstate->tuplestorestate); + tuplestore_end(scanstate->tuplestorestate); scanstate->tuplestorestate = NULL; } else - tuplestore_rescan((Tuplestorestate *) scanstate->tuplestorestate); -} - -/* - * Run the underlying function to get the next tuple - */ -static TupleTableSlot * -function_getonetuple(FunctionScanState *scanstate, - bool *isNull, - ExprDoneCond *isDone) -{ - HeapTuple tuple; - Datum retDatum; - char nullflag; - TupleDesc tupdesc = scanstate->tupdesc; - bool returnsTuple = scanstate->returnsTuple; - Node *expr = scanstate->funcexpr; - Oid fn_typeid = scanstate->fn_typeid; - char fn_typtype = scanstate->fn_typtype; - ExprContext *econtext = scanstate->csstate.cstate.cs_ExprContext; - TupleTableSlot *slot = scanstate->csstate.css_ScanTupleSlot; - - /* - * reset per-tuple memory context before each call of the function. - * This cleans up any local memory the function may leak when called. - */ - ResetExprContext(econtext); - - /* - * get the next Datum from the function - */ - retDatum = ExecEvalExprSwitchContext(expr, econtext, isNull, isDone); - - /* - * check to see if we're really done - */ - if (*isDone == ExprEndResult) - slot = NULL; - else - { - if (returnsTuple) - { - /* - * Composite data type, i.e. a table's row type - * function returns pointer to tts?? - */ - slot = (TupleTableSlot *) retDatum; - - /* - * if function return type was RECORD, we need to check to be - * sure the structure from the query matches the actual return - * structure - */ - if (fn_typtype == 'p' && fn_typeid == RECORDOID) - if (tupledesc_mismatch(tupdesc, slot->ttc_tupleDescriptor)) - elog(ERROR, "Query-specified return tuple and actual function return tuple do not match"); - } - else - { - /* - * Must be a base data type, i.e. scalar - * turn it into a tuple - */ - nullflag = *isNull ? 'n' : ' '; - tuple = heap_formtuple(tupdesc, &retDatum, &nullflag); - - /* - * save the tuple in the scan tuple slot and return the slot. - */ - slot = ExecStoreTuple(tuple, /* tuple to store */ - slot, /* slot to store in */ - InvalidBuffer, /* buffer associated with - * this tuple */ - true); /* pfree this tuple */ - } - } - - return slot; + tuplestore_rescan(scanstate->tuplestorestate); } -static FunctionMode -get_functionmode(Node *expr) -{ - /* - * for the moment, hardwire this - */ - return PM_REPEATEDCALL; -} static bool tupledesc_mismatch(TupleDesc tupdesc1, TupleDesc tupdesc2) diff --git a/src/backend/utils/fmgr/README b/src/backend/utils/fmgr/README index 043a3a7e8d9..98d4e7bd51a 100644 --- a/src/backend/utils/fmgr/README +++ b/src/backend/utils/fmgr/README @@ -371,36 +371,61 @@ tuple toaster will decide whether toasting is needed. Functions accepting or returning sets ------------------------------------- -As of 7.1, Postgres has limited support for functions returning sets; -this is presently handled only in SELECT output expressions, and the -behavior is to generate a separate output tuple for each set element. -There is no direct support for functions accepting sets; instead, the -function will be called multiple times, once for each element of the -input set. This behavior will very likely be changed in future releases, -but here is how it works now: +[ this section revised 29-Aug-2002 for 7.3 ] If a function is marked in pg_proc as returning a set, then it is called with fcinfo->resultinfo pointing to a node of type ReturnSetInfo. A function that desires to return a set should raise an error "called in context that does not accept a set result" if resultinfo is NULL or does -not point to a ReturnSetInfo node. ReturnSetInfo contains a field +not point to a ReturnSetInfo node. + +There are currently two modes in which a function can return a set result: +value-per-call, or materialize. In value-per-call mode, the function returns +one value each time it is called, and finally reports "done" when it has no +more values to return. In materialize mode, the function's output set is +instantiated in a Tuplestore object; all the values are returned in one call. +Additional modes might be added in future. + +ReturnSetInfo contains a field "allowedModes" which is set (by the caller) +to a bitmask that's the OR of the modes the caller can support. The actual +mode used by the function is returned in another field "returnMode". For +backwards-compatibility reasons, returnMode is initialized to value-per-call +and need only be changed if the function wants to use a different mode. +The function should elog() if it cannot use any of the modes the caller is +willing to support. + +Value-per-call mode works like this: ReturnSetInfo contains a field "isDone", which should be set to one of these values: ExprSingleResult /* expression does not return a set */ ExprMultipleResult /* this result is an element of a set */ ExprEndResult /* there are no more elements in the set */ -A function returning set returns one set element per call, setting -fcinfo->resultinfo->isDone to ExprMultipleResult for each element. -After all elements have been returned, the next call should set -isDone to ExprEndResult and return a null result. (Note it is possible -to return an empty set by doing this on the first call.) +(the caller will initialize it to ExprSingleResult). If the function simply +returns a Datum without touching ReturnSetInfo, then the call is over and a +single-item set has been returned. To return a set, the function must set +isDone to ExprMultipleResult for each set element. After all elements have +been returned, the next call should set isDone to ExprEndResult and return a +null result. (Note it is possible to return an empty set by doing this on +the first call.) -As of 7.3, the ReturnSetInfo node also contains a link to the ExprContext -within which the function is being evaluated. This is useful for functions +The ReturnSetInfo node also contains a link to the ExprContext within which +the function is being evaluated. This is useful for value-per-call functions that need to close down internal state when they are not run to completion: they can register a shutdown callback function in the ExprContext. +Materialize mode works like this: the function creates a Tuplestore holding +the (possibly empty) result set, and returns it. There are no multiple calls. +The function must also return a TupleDesc that indicates the tuple structure. +The Tuplestore and TupleDesc should be created in the context +econtext->ecxt_per_query_memory (note this will *not* be the context the +function is called in). The function stores pointers to the Tuplestore and +TupleDesc into ReturnSetInfo, sets returnMode to indicate materialize mode, +and returns null. isDone is not used and should be left at ExprSingleResult. + +There is no support for functions accepting sets; instead, the function will +be called multiple times, once for each element of the input set. + Notes about function handlers ----------------------------- diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h index 88104565976..31a2b4a2399 100644 --- a/src/include/executor/executor.h +++ b/src/include/executor/executor.h @@ -7,7 +7,7 @@ * Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $Id: executor.h,v 1.74 2002/08/29 00:17:06 tgl Exp $ + * $Id: executor.h,v 1.75 2002/08/30 00:28:41 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -80,6 +80,9 @@ extern Datum ExecMakeFunctionResult(FunctionCachePtr fcache, ExprContext *econtext, bool *isNull, ExprDoneCond *isDone); +extern Tuplestorestate *ExecMakeTableFunctionResult(Expr *funcexpr, + ExprContext *econtext, + TupleDesc *returnDesc); extern Datum ExecEvalExpr(Node *expression, ExprContext *econtext, bool *isNull, ExprDoneCond *isDone); extern Datum ExecEvalExprSwitchContext(Node *expression, ExprContext *econtext, diff --git a/src/include/fmgr.h b/src/include/fmgr.h index 74e90c93001..7b04a1d7058 100644 --- a/src/include/fmgr.h +++ b/src/include/fmgr.h @@ -11,7 +11,7 @@ * Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $Id: fmgr.h,v 1.22 2002/06/20 20:29:42 momjian Exp $ + * $Id: fmgr.h,v 1.23 2002/08/30 00:28:41 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -45,8 +45,7 @@ typedef struct FmgrInfo * count */ bool fn_strict; /* function is "strict" (NULL in => NULL * out) */ - bool fn_retset; /* function returns a set (over multiple - * calls) */ + bool fn_retset; /* function returns a set */ void *fn_extra; /* extra space for use by handler */ MemoryContext fn_mcxt; /* memory context to store fn_extra in */ } FmgrInfo; diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h index 6e146e2ca6d..3081a6842eb 100644 --- a/src/include/nodes/execnodes.h +++ b/src/include/nodes/execnodes.h @@ -7,7 +7,7 @@ * Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $Id: execnodes.h,v 1.72 2002/08/29 00:17:06 tgl Exp $ + * $Id: execnodes.h,v 1.73 2002/08/30 00:28:41 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -21,6 +21,8 @@ #include "fmgr.h" #include "nodes/params.h" #include "nodes/primnodes.h" +#include "utils/tuplestore.h" + /* ---------------- * IndexInfo information @@ -126,19 +128,31 @@ typedef enum } ExprDoneCond; /* + * Return modes for functions returning sets. Note values must be chosen + * as separate bits so that a bitmask can be formed to indicate supported + * modes. + */ +typedef enum +{ + SFRM_ValuePerCall = 0x01, /* one value returned per call */ + SFRM_Materialize = 0x02 /* result set instantiated in Tuplestore */ +} SetFunctionReturnMode; + +/* * When calling a function that might return a set (multiple rows), * a node of this type is passed as fcinfo->resultinfo to allow * return status to be passed back. A function returning set should - * raise an error if no such resultinfo is provided. The ExprContext - * in which the function is being called is also made available. - * - * XXX this mechanism is a quick hack and probably needs to be redesigned. + * raise an error if no such resultinfo is provided. */ typedef struct ReturnSetInfo { NodeTag type; - ExprDoneCond isDone; - ExprContext *econtext; + ExprContext *econtext; /* context function is being called in */ + int allowedModes; /* bitmask: return modes caller can handle */ + SetFunctionReturnMode returnMode; /* actual return mode */ + ExprDoneCond isDone; /* status for ValuePerCall mode */ + Tuplestorestate *setResult; /* return object for Materialize mode */ + TupleDesc setDesc; /* descriptor for Materialize mode */ } ReturnSetInfo; /* ---------------- @@ -509,32 +523,17 @@ typedef struct SubqueryScanState * Function nodes are used to scan the results of a * function appearing in FROM (typically a function returning set). * - * functionmode function operating mode - * tupdesc function's return tuple description + * tupdesc expected return tuple description * tuplestorestate private state of tuplestore.c * funcexpr function expression being evaluated - * returnsTuple does function return tuples? - * fn_typeid OID of function return type - * fn_typtype return type's typtype * ---------------- */ -typedef enum FunctionMode -{ - PM_REPEATEDCALL, - PM_MATERIALIZE, - PM_QUERY -} FunctionMode; - typedef struct FunctionScanState { CommonScanState csstate; /* its first field is NodeTag */ - FunctionMode functionmode; TupleDesc tupdesc; - void *tuplestorestate; + Tuplestorestate *tuplestorestate; Node *funcexpr; - bool returnsTuple; - Oid fn_typeid; - char fn_typtype; } FunctionScanState; /* ---------------------------------------------------------------- diff --git a/src/pl/plpgsql/src/Makefile b/src/pl/plpgsql/src/Makefile index 88bd611402f..cb5b6c21fa5 100644 --- a/src/pl/plpgsql/src/Makefile +++ b/src/pl/plpgsql/src/Makefile @@ -2,7 +2,7 @@ # # Makefile for the plpgsql shared object # -# $Header: /cvsroot/pgsql/src/pl/plpgsql/src/Makefile,v 1.20 2001/11/16 16:32:33 petere Exp $ +# $Header: /cvsroot/pgsql/src/pl/plpgsql/src/Makefile,v 1.21 2002/08/30 00:28:41 tgl Exp $ # #------------------------------------------------------------------------- @@ -78,7 +78,7 @@ endif $(srcdir)/pl_scan.c: scan.l ifdef FLEX - $(FLEX) -i $(FLEXFLAGS) -Pplpgsql_base_yy -o'$@' $< + $(FLEX) $(FLEXFLAGS) -Pplpgsql_base_yy -o'$@' $< else @$(missing) flex $< $@ endif diff --git a/src/pl/plpgsql/src/gram.y b/src/pl/plpgsql/src/gram.y index 75d0a0b07a2..47c8a9c1919 100644 --- a/src/pl/plpgsql/src/gram.y +++ b/src/pl/plpgsql/src/gram.y @@ -4,7 +4,7 @@ * procedural language * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/pl/plpgsql/src/gram.y,v 1.35 2002/08/28 20:46:24 momjian Exp $ + * $Header: /cvsroot/pgsql/src/pl/plpgsql/src/gram.y,v 1.36 2002/08/30 00:28:41 tgl Exp $ * * This software is copyrighted by Jan Wieck - Hamburg. * @@ -48,7 +48,7 @@ static PLpgSQL_type *read_datatype(int tok); static PLpgSQL_stmt *make_select_stmt(void); static PLpgSQL_stmt *make_fetch_stmt(void); static PLpgSQL_expr *make_tupret_expr(PLpgSQL_row *row); -static void check_assignable(PLpgSQL_datum *datum); +static void check_assignable(PLpgSQL_datum *datum); %} @@ -121,8 +121,8 @@ static void check_assignable(PLpgSQL_datum *datum); %type <stmts> proc_sect, proc_stmts, stmt_else, loop_body %type <stmt> proc_stmt, pl_block %type <stmt> stmt_assign, stmt_if, stmt_loop, stmt_while, stmt_exit -%type <stmt> stmt_return, stmt_raise, stmt_execsql, stmt_fori -%type <stmt> stmt_fors, stmt_select, stmt_perform +%type <stmt> stmt_return, stmt_return_next, stmt_raise, stmt_execsql +%type <stmt> stmt_fori, stmt_fors, stmt_select, stmt_perform %type <stmt> stmt_dynexecute, stmt_dynfors, stmt_getdiag %type <stmt> stmt_open, stmt_fetch, stmt_close @@ -166,6 +166,7 @@ static void check_assignable(PLpgSQL_datum *datum); %token K_IS %token K_LOG %token K_LOOP +%token K_NEXT %token K_NOT %token K_NOTICE %token K_NULL @@ -177,6 +178,7 @@ static void check_assignable(PLpgSQL_datum *datum); %token K_RENAME %token K_RESULT_OID %token K_RETURN +%token K_RETURN_NEXT %token K_REVERSE %token K_SELECT %token K_THEN @@ -516,10 +518,8 @@ decl_aliasitem : T_WORD plpgsql_convert_ident(yytext, &name, 1); if (name[0] != '$') - { - plpgsql_error_lineno = yylineno; - elog(ERROR, "can only alias positional parameters"); - } + yyerror("can only alias positional parameters"); + plpgsql_ns_setlocal(false); nsi = plpgsql_ns_lookup(name, NULL); if (nsi == NULL) @@ -609,14 +609,11 @@ decl_defval : ';' switch (tok) { case 0: - plpgsql_error_lineno = lno; - elog(ERROR, "unexpected end of file"); + yyerror("unexpected end of file"); case K_NULL: if (yylex() != ';') - { - plpgsql_error_lineno = lno; - elog(ERROR, "expected ; after NULL"); - } + yyerror("expected ; after NULL"); + free(expr); plpgsql_dstring_free(&ds); @@ -628,10 +625,8 @@ decl_defval : ';' while ((tok = yylex()) != ';') { if (tok == 0) - { - plpgsql_error_lineno = lno; - elog(ERROR, "unterminated default value"); - } + yyerror("unterminated default value"); + if (plpgsql_SpaceScanned) plpgsql_dstring_append(&ds, " "); plpgsql_dstring_append(&ds, yytext); @@ -663,7 +658,8 @@ proc_sect : proc_stmts : proc_stmts proc_stmt { - if ($1->stmts_used == $1->stmts_alloc) { + if ($1->stmts_used == $1->stmts_alloc) + { $1->stmts_alloc *= 2; $1->stmts = realloc($1->stmts, sizeof(PLpgSQL_stmt *) * $1->stmts_alloc); } @@ -708,6 +704,8 @@ proc_stmt : pl_block ';' { $$ = $1; } | stmt_return { $$ = $1; } + | stmt_return_next + { $$ = $1; } | stmt_raise { $$ = $1; } | stmt_execsql @@ -1121,45 +1119,73 @@ stmt_exit : K_EXIT lno opt_exitlabel opt_exitcond stmt_return : K_RETURN lno { PLpgSQL_stmt_return *new; - PLpgSQL_expr *expr = NULL; - int tok; new = malloc(sizeof(PLpgSQL_stmt_return)); memset(new, 0, sizeof(PLpgSQL_stmt_return)); - if (plpgsql_curr_compile->fn_retistuple) + if (plpgsql_curr_compile->fn_retistuple && + !plpgsql_curr_compile->fn_retset) { - new->retistuple = true; new->retrecno = -1; - switch (tok = yylex()) + switch (yylex()) { case K_NULL: - expr = NULL; + new->expr = NULL; break; case T_ROW: - expr = make_tupret_expr(yylval.row); + new->expr = make_tupret_expr(yylval.row); break; case T_RECORD: new->retrecno = yylval.rec->recno; - expr = NULL; + new->expr = NULL; break; default: - yyerror("return type mismatch in function returning table row"); + yyerror("return type mismatch in function returning tuple"); break; } if (yylex() != ';') yyerror("expected ';'"); - } else { - new->retistuple = false; - expr = plpgsql_read_expression(';', ";"); } + else + new->expr = plpgsql_read_expression(';', ";"); new->cmd_type = PLPGSQL_STMT_RETURN; new->lineno = $2; - new->expr = expr; + + $$ = (PLpgSQL_stmt *)new; + } + ; + +/* FIXME: this syntax needs work, RETURN NEXT breaks stmt_return */ +stmt_return_next: K_RETURN_NEXT lno + { + PLpgSQL_stmt_return_next *new; + + new = malloc(sizeof(PLpgSQL_stmt_return_next)); + memset(new, 0, sizeof(PLpgSQL_stmt_return_next)); + + new->cmd_type = PLPGSQL_STMT_RETURN_NEXT; + new->lineno = $2; + + if (plpgsql_curr_compile->fn_retistuple) + { + int tok = yylex(); + + if (tok == T_RECORD) + new->rec = yylval.rec; + else if (tok == T_ROW) + new->row = yylval.row; + else + yyerror("Incorrect argument to RETURN NEXT"); + + if (yylex() != ';') + yyerror("Expected ';'"); + } + else + new->expr = plpgsql_read_expression(';', ";"); $$ = (PLpgSQL_stmt *)new; } @@ -1226,7 +1252,7 @@ raise_level : K_EXCEPTION } | K_DEBUG { - $$ = DEBUG5; + $$ = DEBUG1; } ; @@ -1377,10 +1403,7 @@ stmt_open : K_OPEN lno cursor_varptr cp += strlen(cp) - 1; if (*cp != ')') - { - plpgsql_error_lineno = yylineno; - elog(ERROR, "missing )"); - } + yyerror("missing )"); *cp = '\0'; } else @@ -1433,10 +1456,8 @@ stmt_close : K_CLOSE lno cursor_variable ';' cursor_varptr : T_VARIABLE { if (yylval.variable->dtype != PLPGSQL_DTYPE_VAR) - { - plpgsql_error_lineno = yylineno; - elog(ERROR, "cursor variable must be a simple variable"); - } + yyerror("cursor variable must be a simple variable"); + if (((PLpgSQL_var *) yylval.variable)->datatype->typoid != REFCURSOROID) { plpgsql_error_lineno = yylineno; @@ -1450,10 +1471,8 @@ cursor_varptr : T_VARIABLE cursor_variable : T_VARIABLE { if (yylval.variable->dtype != PLPGSQL_DTYPE_VAR) - { - plpgsql_error_lineno = yylineno; - elog(ERROR, "cursor variable must be a simple variable"); - } + yyerror("cursor variable must be a simple variable"); + if (((PLpgSQL_var *) yylval.variable)->datatype->typoid != REFCURSOROID) { plpgsql_error_lineno = yylineno; @@ -1906,18 +1925,14 @@ make_fetch_stmt(void) break; default: - plpgsql_error_lineno = yylineno; - elog(ERROR, "syntax error at '%s'", yytext); + yyerror("syntax error"); } if (!have_nexttok) tok = yylex(); if (tok != ';') - { - plpgsql_error_lineno = yylineno; - elog(ERROR, "syntax error at '%s'", yytext); - } + yyerror("syntax error"); fetch = malloc(sizeof(PLpgSQL_stmt_select)); memset(fetch, 0, sizeof(PLpgSQL_stmt_fetch)); @@ -1976,11 +1991,10 @@ check_assignable(PLpgSQL_datum *datum) /* always assignable? */ break; case PLPGSQL_DTYPE_TRIGARG: - plpgsql_error_lineno = yylineno; - elog(ERROR, "cannot assign to tg_argv"); + yyerror("cannot assign to tg_argv"); break; default: - elog(ERROR, "check_assignable: unexpected datum type"); + yyerror("check_assignable: unexpected datum type"); break; } } diff --git a/src/pl/plpgsql/src/pl_comp.c b/src/pl/plpgsql/src/pl_comp.c index 70f1de470c9..1878c5e396e 100644 --- a/src/pl/plpgsql/src/pl_comp.c +++ b/src/pl/plpgsql/src/pl_comp.c @@ -3,7 +3,7 @@ * procedural language * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/pl/plpgsql/src/pl_comp.c,v 1.48 2002/08/28 20:46:24 momjian Exp $ + * $Header: /cvsroot/pgsql/src/pl/plpgsql/src/pl_comp.c,v 1.49 2002/08/30 00:28:41 tgl Exp $ * * This software is copyrighted by Jan Wieck - Hamburg. * @@ -37,8 +37,6 @@ #include "plpgsql.h" -#include <unistd.h> -#include <fcntl.h> #include <ctype.h> #include <setjmp.h> @@ -52,9 +50,6 @@ #include "catalog/pg_class.h" #include "catalog/pg_proc.h" #include "catalog/pg_type.h" -#include "commands/trigger.h" -#include "executor/spi.h" -#include "fmgr.h" #include "nodes/makefuncs.h" #include "parser/gramparse.h" #include "parser/parse_type.h" @@ -217,6 +212,7 @@ plpgsql_compile(Oid fn_oid, int functype) typeStruct = (Form_pg_type) GETSTRUCT(typeTup); /* Disallow pseudotype result, except VOID */ + /* XXX someday allow RECORD? */ if (typeStruct->typtype == 'p') { if (procStruct->prorettype == VOIDOID) diff --git a/src/pl/plpgsql/src/pl_exec.c b/src/pl/plpgsql/src/pl_exec.c index 363a04839a4..49c88f3a090 100644 --- a/src/pl/plpgsql/src/pl_exec.c +++ b/src/pl/plpgsql/src/pl_exec.c @@ -3,7 +3,7 @@ * procedural language * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/pl/plpgsql/src/pl_exec.c,v 1.59 2002/08/29 04:12:03 tgl Exp $ + * $Header: /cvsroot/pgsql/src/pl/plpgsql/src/pl_exec.c,v 1.60 2002/08/30 00:28:41 tgl Exp $ * * This software is copyrighted by Jan Wieck - Hamburg. * @@ -35,12 +35,6 @@ * **********************************************************************/ -#include <stdio.h> -#include <stdlib.h> -#include <stdarg.h> -#include <unistd.h> -#include <fcntl.h> -#include <string.h> #include <ctype.h> #include <setjmp.h> @@ -50,10 +44,8 @@ #include "access/heapam.h" #include "catalog/pg_proc.h" #include "catalog/pg_type.h" -#include "commands/trigger.h" -#include "executor/spi.h" #include "executor/spi_priv.h" -#include "fmgr.h" +#include "funcapi.h" #include "optimizer/clauses.h" #include "parser/parse_expr.h" #include "tcop/tcopprot.h" @@ -105,6 +97,8 @@ static int exec_stmt_exit(PLpgSQL_execstate * estate, PLpgSQL_stmt_exit * stmt); static int exec_stmt_return(PLpgSQL_execstate * estate, PLpgSQL_stmt_return * stmt); +static int exec_stmt_return_next(PLpgSQL_execstate * estate, + PLpgSQL_stmt_return_next * stmt); static int exec_stmt_raise(PLpgSQL_execstate * estate, PLpgSQL_stmt_raise * stmt); static int exec_stmt_execsql(PLpgSQL_execstate * estate, @@ -114,8 +108,9 @@ static int exec_stmt_dynexecute(PLpgSQL_execstate * estate, static int exec_stmt_dynfors(PLpgSQL_execstate * estate, PLpgSQL_stmt_dynfors * stmt); -static void plpgsql_estate_setup(PLpgSQL_execstate * estate, - PLpgSQL_function * func); +static void plpgsql_estate_setup(PLpgSQL_execstate *estate, + PLpgSQL_function *func, + ReturnSetInfo *rsi); static void exec_eval_cleanup(PLpgSQL_execstate * estate); static void exec_prepare_plan(PLpgSQL_execstate * estate, @@ -150,6 +145,8 @@ static Datum exec_cast_value(Datum value, Oid valtype, int32 reqtypmod, bool *isnull); static void exec_set_found(PLpgSQL_execstate * estate, bool state); +static void exec_init_tuple_store(PLpgSQL_execstate *estate); +static void exec_set_ret_tupdesc(PLpgSQL_execstate *estate, List *labels); /* ---------- @@ -211,7 +208,7 @@ plpgsql_exec_function(PLpgSQL_function * func, FunctionCallInfo fcinfo) /* * Setup the execution state */ - plpgsql_estate_setup(&estate, func); + plpgsql_estate_setup(&estate, func, (ReturnSetInfo *) fcinfo->resultinfo); /* * Make local execution copies of all the datums @@ -332,11 +329,36 @@ plpgsql_exec_function(PLpgSQL_function * func, FunctionCallInfo fcinfo) * We got a return value - process it */ error_info_stmt = NULL; - error_info_text = "while casting return value to functions return type"; + error_info_text = "while casting return value to function's return type"; fcinfo->isnull = estate.retisnull; - if (!estate.retisnull) + if (estate.retisset) + { + ReturnSetInfo *rsi = estate.rsi; + + /* Check caller can handle a set result */ + if (!rsi || !IsA(rsi, ReturnSetInfo) || + (rsi->allowedModes & SFRM_Materialize) == 0) + elog(ERROR, "Set-valued function called in context that cannot accept a set"); + rsi->returnMode = SFRM_Materialize; + + /* If we produced any tuples, send back the result */ + if (estate.tuple_store) + { + MemoryContext oldcxt; + + oldcxt = MemoryContextSwitchTo(estate.tuple_store_cxt); + tuplestore_donestoring(estate.tuple_store); + rsi->setResult = estate.tuple_store; + if (estate.rettupdesc) + rsi->setDesc = CreateTupleDescCopy(estate.rettupdesc); + MemoryContextSwitchTo(oldcxt); + } + estate.retval = (Datum) 0; + fcinfo->isnull = true; + } + else if (!estate.retisnull) { if (estate.retistuple) { @@ -455,7 +477,7 @@ plpgsql_exec_trigger(PLpgSQL_function * func, /* * Setup the execution state */ - plpgsql_estate_setup(&estate, func); + plpgsql_estate_setup(&estate, func, NULL); /* * Make local execution copies of all the datums @@ -642,6 +664,9 @@ plpgsql_exec_trigger(PLpgSQL_function * func, elog(ERROR, "control reaches end of trigger procedure without RETURN"); } + if (estate.retisset) + elog(ERROR, "trigger procedure cannot return a set"); + /* * Check that the returned tuple structure has the same attributes, * the relation that fired the trigger has. @@ -862,6 +887,8 @@ exec_stmt(PLpgSQL_execstate * estate, PLpgSQL_stmt * stmt) save_estmt = error_info_stmt; error_info_stmt = stmt; + CHECK_FOR_INTERRUPTS(); + switch (stmt->cmd_type) { case PLPGSQL_STMT_BLOCK: @@ -908,6 +935,10 @@ exec_stmt(PLpgSQL_execstate * estate, PLpgSQL_stmt * stmt) rc = exec_stmt_return(estate, (PLpgSQL_stmt_return *) stmt); break; + case PLPGSQL_STMT_RETURN_NEXT: + rc = exec_stmt_return_next(estate, (PLpgSQL_stmt_return_next *) stmt); + break; + case PLPGSQL_STMT_RAISE: rc = exec_stmt_raise(estate, (PLpgSQL_stmt_raise *) stmt); break; @@ -1302,13 +1333,10 @@ exec_stmt_fors(PLpgSQL_execstate * estate, PLpgSQL_stmt_fors * stmt) */ if (stmt->rec != NULL) rec = (PLpgSQL_rec *) (estate->datums[stmt->rec->recno]); + else if (stmt->row != NULL) + row = (PLpgSQL_row *) (estate->datums[stmt->row->rowno]); else - { - if (stmt->row != NULL) - row = (PLpgSQL_row *) (estate->datums[stmt->row->rowno]); - else - elog(ERROR, "unsupported target in exec_stmt_fors()"); - } + elog(ERROR, "unsupported target in exec_stmt_fors()"); /* * Open the implicit cursor for the statement and fetch the initial 10 @@ -1499,6 +1527,14 @@ exec_stmt_exit(PLpgSQL_execstate * estate, PLpgSQL_stmt_exit * stmt) static int exec_stmt_return(PLpgSQL_execstate * estate, PLpgSQL_stmt_return * stmt) { + /* + * If processing a set-returning PL/PgSQL function, the final RETURN + * indicates that the function is finished producing tuples. The rest + * of the work will be done at the top level. + */ + if (estate->retisset) + return PLPGSQL_RC_RETURN; + if (estate->retistuple) { /* initialize for null result tuple */ @@ -1532,13 +1568,155 @@ exec_stmt_return(PLpgSQL_execstate * estate, PLpgSQL_stmt_return * stmt) return PLPGSQL_RC_RETURN; } - estate->retval = exec_eval_expr(estate, stmt->expr, - &(estate->retisnull), - &(estate->rettype)); + if (estate->fn_rettype == VOIDOID) + { + /* Special hack for function returning VOID */ + estate->retval = (Datum) 0; + estate->retisnull = false; + estate->rettype = VOIDOID; + } + else + { + /* Normal case for scalar results */ + estate->retval = exec_eval_expr(estate, stmt->expr, + &(estate->retisnull), + &(estate->rettype)); + } return PLPGSQL_RC_RETURN; } +/* + * Notes: + * - the tuple store must be created in a sufficiently long-lived + * memory context, as the same store must be used within the executor + * after the PL/PgSQL call returns. At present, the code uses + * TopTransactionContext. + */ +static int +exec_stmt_return_next(PLpgSQL_execstate *estate, + PLpgSQL_stmt_return_next *stmt) +{ + HeapTuple tuple; + bool free_tuple = false; + + if (!estate->retisset) + elog(ERROR, "Cannot use RETURN NEXT in a non-SETOF function"); + + if (estate->tuple_store == NULL) + exec_init_tuple_store(estate); + + if (stmt->rec) + { + PLpgSQL_rec *rec = (PLpgSQL_rec *) (estate->datums[stmt->rec->recno]); + tuple = rec->tup; + estate->rettupdesc = rec->tupdesc; + } + else if (stmt->row) + { + PLpgSQL_var *var; + TupleDesc tupdesc; + Datum *dvalues; + char *nulls; + int natts; + int i; + + if (!estate->rettupdesc) + exec_set_ret_tupdesc(estate, NIL); + + tupdesc = estate->rettupdesc; + natts = tupdesc->natts; + dvalues = (Datum *) palloc(natts * sizeof(Datum)); + nulls = (char *) palloc(natts * sizeof(char)); + + MemSet(dvalues, 0, natts * sizeof(Datum)); + MemSet(nulls, 'n', natts); + + for (i = 0; i < stmt->row->nfields; i++) + { + var = (PLpgSQL_var *) (estate->datums[stmt->row->varnos[i]]); + dvalues[i] = var->value; + if (!var->isnull) + nulls[i] = ' '; + } + + tuple = heap_formtuple(tupdesc, dvalues, nulls); + + pfree(dvalues); + pfree(nulls); + free_tuple = true; + } + else if (stmt->expr) + { + Datum retval; + bool isNull; + char nullflag; + + if (!estate->rettupdesc) + exec_set_ret_tupdesc(estate, makeList1(makeString("unused"))); + + retval = exec_eval_expr(estate, + stmt->expr, + &isNull, + &(estate->rettype)); + + nullflag = isNull ? 'n' : ' '; + + tuple = heap_formtuple(estate->rettupdesc, &retval, &nullflag); + + free_tuple = true; + + exec_eval_cleanup(estate); + } + else + { + elog(ERROR, "Blank RETURN NEXT not allowed"); + tuple = NULL; /* keep compiler quiet */ + } + + if (HeapTupleIsValid(tuple)) + { + MemoryContext oldcxt; + + oldcxt = MemoryContextSwitchTo(estate->tuple_store_cxt); + tuplestore_puttuple(estate->tuple_store, tuple); + MemoryContextSwitchTo(oldcxt); + + if (free_tuple) + heap_freetuple(tuple); + } + + return PLPGSQL_RC_OK; +} + +static void +exec_set_ret_tupdesc(PLpgSQL_execstate *estate, List *labels) +{ + estate->rettype = estate->fn_rettype; + estate->rettupdesc = TypeGetTupleDesc(estate->rettype, labels); + + if (!estate->rettupdesc) + elog(ERROR, "Could not produce descriptor for rowtype"); +} + +static void +exec_init_tuple_store(PLpgSQL_execstate *estate) +{ + ReturnSetInfo *rsi = estate->rsi; + MemoryContext oldcxt; + + /* Check caller can handle a set result */ + if (!rsi || !IsA(rsi, ReturnSetInfo) || + (rsi->allowedModes & SFRM_Materialize) == 0) + elog(ERROR, "Set-valued function called in context that cannot accept a set"); + + estate->tuple_store_cxt = rsi->econtext->ecxt_per_query_memory; + + oldcxt = MemoryContextSwitchTo(estate->tuple_store_cxt); + estate->tuple_store = tuplestore_begin_heap(true, SortMem); + MemoryContextSwitchTo(oldcxt); +} + /* ---------- * exec_stmt_raise Build a message and throw it with @@ -1700,21 +1878,29 @@ exec_stmt_raise(PLpgSQL_execstate * estate, PLpgSQL_stmt_raise * stmt) /* ---------- - * Initialize an empty estate + * Initialize a mostly empty execution state * ---------- */ static void -plpgsql_estate_setup(PLpgSQL_execstate * estate, - PLpgSQL_function * func) +plpgsql_estate_setup(PLpgSQL_execstate *estate, + PLpgSQL_function *func, + ReturnSetInfo *rsi) { estate->retval = (Datum) 0; estate->retisnull = true; estate->rettype = InvalidOid; + + estate->fn_rettype = func->fn_rettype; estate->retistuple = func->fn_retistuple; - estate->rettupdesc = NULL; estate->retisset = func->fn_retset; + + estate->rettupdesc = NULL; estate->exitlabel = NULL; + estate->tuple_store = NULL; + estate->tuple_store_cxt = NULL; + estate->rsi = rsi; + estate->trig_nargs = 0; estate->trig_argv = NULL; @@ -2099,13 +2285,10 @@ exec_stmt_dynfors(PLpgSQL_execstate * estate, PLpgSQL_stmt_dynfors * stmt) */ if (stmt->rec != NULL) rec = (PLpgSQL_rec *) (estate->datums[stmt->rec->recno]); + else if (stmt->row != NULL) + row = (PLpgSQL_row *) (estate->datums[stmt->row->rowno]); else - { - if (stmt->row != NULL) - row = (PLpgSQL_row *) (estate->datums[stmt->row->rowno]); - else - elog(ERROR, "unsupported target in exec_stmt_fors()"); - } + elog(ERROR, "unsupported target in exec_stmt_dynfors()"); /* * Evaluate the string expression after the EXECUTE keyword. It's diff --git a/src/pl/plpgsql/src/pl_funcs.c b/src/pl/plpgsql/src/pl_funcs.c index 33103a9eb4a..deaa2690e31 100644 --- a/src/pl/plpgsql/src/pl_funcs.c +++ b/src/pl/plpgsql/src/pl_funcs.c @@ -3,7 +3,7 @@ * procedural language * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/pl/plpgsql/src/pl_funcs.c,v 1.20 2002/08/29 07:22:30 ishii Exp $ + * $Header: /cvsroot/pgsql/src/pl/plpgsql/src/pl_funcs.c,v 1.21 2002/08/30 00:28:41 tgl Exp $ * * This software is copyrighted by Jan Wieck - Hamburg. * @@ -35,12 +35,6 @@ * **********************************************************************/ -#include <stdio.h> -#include <stdlib.h> -#include <stdarg.h> -#include <unistd.h> -#include <fcntl.h> -#include <string.h> #include <ctype.h> #include "plpgsql.h" @@ -272,9 +266,7 @@ plpgsql_ns_lookup(char *name, char *label) return ns->items[i]; } if (ns_localmode) - { return NULL; /* name not found in current namespace */ - } } return NULL; @@ -461,6 +453,8 @@ plpgsql_stmt_typename(PLpgSQL_stmt * stmt) return "exit"; case PLPGSQL_STMT_RETURN: return "return"; + case PLPGSQL_STMT_RETURN_NEXT: + return "return next"; case PLPGSQL_STMT_RAISE: return "raise"; case PLPGSQL_STMT_EXECSQL: @@ -500,6 +494,7 @@ static void dump_fors(PLpgSQL_stmt_fors * stmt); static void dump_select(PLpgSQL_stmt_select * stmt); static void dump_exit(PLpgSQL_stmt_exit * stmt); static void dump_return(PLpgSQL_stmt_return * stmt); +static void dump_return_next(PLpgSQL_stmt_return_next * stmt); static void dump_raise(PLpgSQL_stmt_raise * stmt); static void dump_execsql(PLpgSQL_stmt_execsql * stmt); static void dump_dynexecute(PLpgSQL_stmt_dynexecute * stmt); @@ -556,6 +551,9 @@ dump_stmt(PLpgSQL_stmt * stmt) case PLPGSQL_STMT_RETURN: dump_return((PLpgSQL_stmt_return *) stmt); break; + case PLPGSQL_STMT_RETURN_NEXT: + dump_return_next((PLpgSQL_stmt_return_next *) stmt); + break; case PLPGSQL_STMT_RAISE: dump_raise((PLpgSQL_stmt_raise *) stmt); break; @@ -840,6 +838,20 @@ dump_return(PLpgSQL_stmt_return * stmt) } static void +dump_return_next(PLpgSQL_stmt_return_next * stmt) +{ + dump_ind(); + printf("RETURN NEXT "); + if (stmt->rec != NULL) + printf("target = %d %s\n", stmt->rec->recno, stmt->rec->refname); + else if (stmt->row != NULL) + printf("target = %d %s\n", stmt->row->rowno, stmt->row->refname); + else if (stmt->expr != NULL) + dump_expr(stmt->expr); + printf("\n"); +} + +static void dump_raise(PLpgSQL_stmt_raise * stmt) { int i; diff --git a/src/pl/plpgsql/src/pl_handler.c b/src/pl/plpgsql/src/pl_handler.c index 3ca0b13ffd1..17b9cf2e42b 100644 --- a/src/pl/plpgsql/src/pl_handler.c +++ b/src/pl/plpgsql/src/pl_handler.c @@ -3,7 +3,7 @@ * procedural language * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/pl/plpgsql/src/pl_handler.c,v 1.11 2002/06/15 19:54:24 momjian Exp $ + * $Header: /cvsroot/pgsql/src/pl/plpgsql/src/pl_handler.c,v 1.12 2002/08/30 00:28:41 tgl Exp $ * * This software is copyrighted by Jan Wieck - Hamburg. * @@ -35,13 +35,6 @@ * **********************************************************************/ -#include <stdio.h> -#include <stdlib.h> -#include <stdarg.h> -#include <unistd.h> -#include <fcntl.h> -#include <string.h> - #include "plpgsql.h" #include "pl.tab.h" diff --git a/src/pl/plpgsql/src/plpgsql.h b/src/pl/plpgsql/src/plpgsql.h index e991aa96ee7..c81b0a3b1bc 100644 --- a/src/pl/plpgsql/src/plpgsql.h +++ b/src/pl/plpgsql/src/plpgsql.h @@ -3,7 +3,7 @@ * procedural language * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/pl/plpgsql/src/plpgsql.h,v 1.25 2002/08/08 01:36:05 tgl Exp $ + * $Header: /cvsroot/pgsql/src/pl/plpgsql/src/plpgsql.h,v 1.26 2002/08/30 00:28:41 tgl Exp $ * * This software is copyrighted by Jan Wieck - Hamburg. * @@ -40,8 +40,10 @@ #include "postgres.h" #include "fmgr.h" +#include "miscadmin.h" #include "executor/spi.h" #include "commands/trigger.h" +#include "utils/tuplestore.h" /********************************************************************** * Definitions @@ -90,6 +92,7 @@ enum PLPGSQL_STMT_SELECT, PLPGSQL_STMT_EXIT, PLPGSQL_STMT_RETURN, + PLPGSQL_STMT_RETURN_NEXT, PLPGSQL_STMT_RAISE, PLPGSQL_STMT_EXECSQL, PLPGSQL_STMT_DYNEXECUTE, @@ -420,11 +423,18 @@ typedef struct { /* RETURN statement */ int cmd_type; int lineno; - bool retistuple; PLpgSQL_expr *expr; int retrecno; } PLpgSQL_stmt_return; +typedef struct +{ /* RETURN NEXT statement */ + int cmd_type; + int lineno; + PLpgSQL_rec *rec; + PLpgSQL_row *row; + PLpgSQL_expr *expr; +} PLpgSQL_stmt_return_next; typedef struct { /* RAISE statement */ @@ -494,12 +504,19 @@ typedef struct { /* Runtime execution data */ Datum retval; bool retisnull; - Oid rettype; + Oid rettype; /* type of current retval */ + + Oid fn_rettype; /* info about declared function rettype */ bool retistuple; - TupleDesc rettupdesc; bool retisset; + + TupleDesc rettupdesc; char *exitlabel; + Tuplestorestate *tuple_store; /* SRFs accumulate results here */ + MemoryContext tuple_store_cxt; + ReturnSetInfo *rsi; + int trig_nargs; Datum *trig_argv; diff --git a/src/pl/plpgsql/src/scan.l b/src/pl/plpgsql/src/scan.l index 5f0f281ada5..3976b542756 100644 --- a/src/pl/plpgsql/src/scan.l +++ b/src/pl/plpgsql/src/scan.l @@ -4,7 +4,7 @@ * procedural language * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/pl/plpgsql/src/Attic/scan.l,v 1.21 2002/08/08 01:36:05 tgl Exp $ + * $Header: /cvsroot/pgsql/src/pl/plpgsql/src/Attic/scan.l,v 1.22 2002/08/30 00:28:41 tgl Exp $ * * This software is copyrighted by Jan Wieck - Hamburg. * @@ -46,6 +46,8 @@ static int scanner_functype; static int scanner_typereported; static int pushback_token; static bool have_pushback_token; +static int lookahead_token; +static bool have_lookahead_token; int plpgsql_SpaceScanned = 0; @@ -134,6 +136,7 @@ into { return K_INTO; } is { return K_IS; } log { return K_LOG; } loop { return K_LOOP; } +next { return K_NEXT; } not { return K_NOT; } notice { return K_NOTICE; } null { return K_NULL; } @@ -255,18 +258,50 @@ plpgsql_input(char *buf, int *result, int max) } /* - * This is the yylex routine called from outside. It exists to provide - * a token pushback facility. + * This is the yylex routine called from outside. It exists to provide + * a pushback facility, as well as to allow us to parse syntax that + * requires more than one token of lookahead. */ int plpgsql_yylex(void) { + int cur_token; + if (have_pushback_token) { have_pushback_token = false; - return pushback_token; + cur_token = pushback_token; + } + else if (have_lookahead_token) + { + have_lookahead_token = false; + cur_token = lookahead_token; + } + else + cur_token = yylex(); + + /* Do we need to look ahead for a possible multiword token? */ + switch (cur_token) + { + /* RETURN NEXT must be reduced to a single token */ + case K_RETURN: + if (!have_lookahead_token) + { + lookahead_token = yylex(); + have_lookahead_token = true; + } + if (lookahead_token == K_NEXT) + { + have_lookahead_token = false; + cur_token = K_RETURN_NEXT; + } + break; + + default: + break; } - return yylex(); + + return cur_token; } /* @@ -312,4 +347,5 @@ plpgsql_setinput(char *source, int functype) scanner_typereported = 0; have_pushback_token = false; + have_lookahead_token = false; } diff --git a/src/test/regress/expected/plpgsql.out b/src/test/regress/expected/plpgsql.out index a8578ea5944..583543262ed 100644 --- a/src/test/regress/expected/plpgsql.out +++ b/src/test/regress/expected/plpgsql.out @@ -1535,6 +1535,10 @@ ERROR: system "notthere" does not exist insert into IFace values ('IF', 'orion', 'ethernet_interface_name_too_long', ''); ERROR: IFace slotname "IF.orion.ethernet_interface_name_too_long" too long (20 char max) -- +-- The following tests are unrelated to the scenario outlined above; +-- they merely exercise specific parts of PL/PgSQL +-- +-- -- Test recursion, per bug report 7-Sep-01 -- CREATE FUNCTION recursion_test(int,int) RETURNS text AS ' @@ -1557,7 +1561,7 @@ SELECT recursion_test(4,3); -- Test the FOUND magic variable -- CREATE TABLE found_test_tbl (a int); -create function test_found () +create function test_found() returns boolean as ' declare begin @@ -1609,3 +1613,70 @@ select * from found_test_tbl; 6 (6 rows) +-- +-- Test set-returning functions for PL/pgSQL +-- +create function test_table_func_rec() returns setof found_test_tbl as ' +DECLARE + rec RECORD; +BEGIN + FOR rec IN select * from found_test_tbl LOOP + RETURN NEXT rec; + END LOOP; + RETURN; +END;' language 'plpgsql'; +select * from test_table_func_rec(); + a +----- + 2 + 100 + 3 + 4 + 5 + 6 +(6 rows) + +create function test_table_func_row() returns setof found_test_tbl as ' +DECLARE + row found_test_tbl%ROWTYPE; +BEGIN + FOR row IN select * from found_test_tbl LOOP + RETURN NEXT row; + END LOOP; + RETURN; +END;' language 'plpgsql'; +select * from test_table_func_row(); + a +----- + 2 + 100 + 3 + 4 + 5 + 6 +(6 rows) + +create function test_ret_set_scalar(int,int) returns setof int as ' +DECLARE + i int; +BEGIN + FOR i IN $1 .. $2 LOOP + RETURN NEXT i + 1; + END LOOP; + RETURN; +END;' language 'plpgsql'; +select * from test_ret_set_scalar(1,10); + test_ret_set_scalar +--------------------- + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 10 + 11 +(10 rows) + diff --git a/src/test/regress/sql/plpgsql.sql b/src/test/regress/sql/plpgsql.sql index 509dfd2df9c..e6795ed10a6 100644 --- a/src/test/regress/sql/plpgsql.sql +++ b/src/test/regress/sql/plpgsql.sql @@ -1419,6 +1419,12 @@ delete from HSlot; insert into IFace values ('IF', 'notthere', 'eth0', ''); insert into IFace values ('IF', 'orion', 'ethernet_interface_name_too_long', ''); + +-- +-- The following tests are unrelated to the scenario outlined above; +-- they merely exercise specific parts of PL/PgSQL +-- + -- -- Test recursion, per bug report 7-Sep-01 -- @@ -1440,7 +1446,7 @@ SELECT recursion_test(4,3); -- CREATE TABLE found_test_tbl (a int); -create function test_found () +create function test_found() returns boolean as ' declare begin @@ -1478,3 +1484,43 @@ create function test_found () select test_found(); select * from found_test_tbl; + +-- +-- Test set-returning functions for PL/pgSQL +-- + +create function test_table_func_rec() returns setof found_test_tbl as ' +DECLARE + rec RECORD; +BEGIN + FOR rec IN select * from found_test_tbl LOOP + RETURN NEXT rec; + END LOOP; + RETURN; +END;' language 'plpgsql'; + +select * from test_table_func_rec(); + +create function test_table_func_row() returns setof found_test_tbl as ' +DECLARE + row found_test_tbl%ROWTYPE; +BEGIN + FOR row IN select * from found_test_tbl LOOP + RETURN NEXT row; + END LOOP; + RETURN; +END;' language 'plpgsql'; + +select * from test_table_func_row(); + +create function test_ret_set_scalar(int,int) returns setof int as ' +DECLARE + i int; +BEGIN + FOR i IN $1 .. $2 LOOP + RETURN NEXT i + 1; + END LOOP; + RETURN; +END;' language 'plpgsql'; + +select * from test_ret_set_scalar(1,10); |