Extend ExecMakeFunctionResult() to support set-returning functions that return
authorTom Lane <tgl@sss.pgh.pa.us>
Tue, 28 Oct 2008 22:02:06 +0000 (22:02 +0000)
committerTom Lane <tgl@sss.pgh.pa.us>
Tue, 28 Oct 2008 22:02:06 +0000 (22:02 +0000)
via a tuplestore instead of value-per-call.  Refactor a few things to reduce
ensuing code duplication with nodeFunctionscan.c.  This represents the
reasonably noncontroversial part of my proposed patch to switch SQL functions
over to returning tuplestores.  For the moment, SQL functions still do things
the old way.  However, this change enables PL SRFs to be called in targetlists
(observe changes in plperl regression results).

contrib/tablefunc/tablefunc.c
doc/src/sgml/plpgsql.sgml
src/backend/executor/execQual.c
src/backend/executor/execTuples.c
src/backend/executor/nodeFunctionscan.c
src/backend/utils/fmgr/README
src/include/executor/executor.h
src/include/executor/tuptable.h
src/include/nodes/execnodes.h
src/pl/plperl/expected/plperl.out

index 5efb900635bfe6a77729b37493cbb6d332a1ab93..f0ac5e8e4fba676efdd851f174218f5de7f481ea 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * $PostgreSQL:
+ * $PostgreSQL$ 
  *
  *
  * tablefunc
@@ -709,7 +709,8 @@ crosstab_hash(PG_FUNCTION_ARGS)
                ereport(ERROR,
                                (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
                                 errmsg("set-valued function called in context that cannot accept a set")));
-       if (!(rsinfo->allowedModes & SFRM_Materialize))
+       if (!(rsinfo->allowedModes & SFRM_Materialize) ||
+               rsinfo->expectedDesc == NULL)
                ereport(ERROR,
                                (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
                                 errmsg("materialize mode required, but it is not " \
@@ -1072,7 +1073,8 @@ connectby_text(PG_FUNCTION_ARGS)
                ereport(ERROR,
                                (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
                                 errmsg("set-valued function called in context that cannot accept a set")));
-       if (!(rsinfo->allowedModes & SFRM_Materialize))
+       if (!(rsinfo->allowedModes & SFRM_Materialize) ||
+               rsinfo->expectedDesc == NULL)
                ereport(ERROR,
                                (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
                                 errmsg("materialize mode required, but it is not " \
@@ -1139,7 +1141,6 @@ connectby_text_serial(PG_FUNCTION_ARGS)
        char       *branch_delim = NULL;
        bool            show_branch = false;
        bool            show_serial = true;
-
        ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
        TupleDesc       tupdesc;
        AttInMetadata *attinmeta;
@@ -1151,7 +1152,8 @@ connectby_text_serial(PG_FUNCTION_ARGS)
                ereport(ERROR,
                                (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
                                 errmsg("set-valued function called in context that cannot accept a set")));
-       if (!(rsinfo->allowedModes & SFRM_Materialize))
+       if (!(rsinfo->allowedModes & SFRM_Materialize) ||
+               rsinfo->expectedDesc == NULL)
                ereport(ERROR,
                                (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
                                 errmsg("materialize mode required, but it is not " \
index bdc16dbacf2fa842158f26929224398c33e7663b..a94d470695db227374af5bb757e2bbacd1702170 100644 (file)
@@ -1575,10 +1575,6 @@ LANGUAGE 'plpgsql' ;
 
 SELECT * FROM getallfoo();
 </programlisting>
-
-      Note that functions using <command>RETURN NEXT</command> or
-      <command>RETURN QUERY</command> must be called as a table source in
-      a <literal>FROM</literal> clause.
      </para>
 
      <note>
index a0c205746cf26b60593e9495f9db7ee2e99291ce..931efed4a3e407e0aee874ea675ec264a034236b 100644 (file)
@@ -29,9 +29,9 @@
  *             instead of doing needless copying.      -cim 5/31/91
  *
  *             During expression evaluation, we check_stack_depth only in
- *             ExecMakeFunctionResult rather than at every single node.  This
- *             is a compromise that trades off precision of the stack limit setting
- *             to gain speed.
+ *             ExecMakeFunctionResult (and substitute routines) rather than at every
+ *             single node.  This is a compromise that trades off precision of the
+ *             stack limit setting to gain speed.
  */
 
 #include "postgres.h"
@@ -74,12 +74,23 @@ static Datum ExecEvalConst(ExprState *exprstate, ExprContext *econtext,
                          bool *isNull, ExprDoneCond *isDone);
 static Datum ExecEvalParam(ExprState *exprstate, ExprContext *econtext,
                          bool *isNull, ExprDoneCond *isDone);
+static void init_fcache(Oid foid, FuncExprState *fcache,
+                                               MemoryContext fcacheCxt, bool needDescForSets);
 static void ShutdownFuncExpr(Datum arg);
 static TupleDesc get_cached_rowtype(Oid type_id, int32 typmod,
                                   TupleDesc *cache_field, ExprContext *econtext);
 static void ShutdownTupleDescRef(Datum arg);
 static ExprDoneCond ExecEvalFuncArgs(FunctionCallInfo fcinfo,
                                 List *argList, ExprContext *econtext);
+static void ExecPrepareTuplestoreResult(FuncExprState *fcache,
+                                                       ExprContext *econtext,
+                                                       Tuplestorestate *resultStore,
+                                                       TupleDesc resultDesc);
+static void tupledesc_match(TupleDesc dst_tupdesc, TupleDesc src_tupdesc);
+static Datum ExecMakeFunctionResult(FuncExprState *fcache,
+                                          ExprContext *econtext,
+                                          bool *isNull,
+                                          ExprDoneCond *isDone);
 static Datum ExecMakeFunctionResultNoSets(FuncExprState *fcache,
                                                         ExprContext *econtext,
                                                         bool *isNull, ExprDoneCond *isDone);
@@ -986,8 +997,9 @@ GetAttributeByName(HeapTupleHeader tuple, const char *attname, bool *isNull)
 /*
  * init_fcache - initialize a FuncExprState node during first use
  */
-void
-init_fcache(Oid foid, FuncExprState *fcache, MemoryContext fcacheCxt)
+static void
+init_fcache(Oid foid, FuncExprState *fcache,
+                       MemoryContext fcacheCxt, bool needDescForSets)
 {
        AclResult       aclresult;
 
@@ -1010,11 +1022,60 @@ init_fcache(Oid foid, FuncExprState *fcache, MemoryContext fcacheCxt)
 
        /* Set up the primary fmgr lookup information */
        fmgr_info_cxt(foid, &(fcache->func), fcacheCxt);
+       fcache->func.fn_expr = (Node *) fcache->xprstate.expr;
+
+       /* If function returns set, prepare expected tuple descriptor */
+       if (fcache->func.fn_retset && needDescForSets)
+       {
+               TypeFuncClass functypclass;
+               Oid                     funcrettype;
+               TupleDesc       tupdesc;
+               MemoryContext oldcontext;
+
+               functypclass = get_expr_result_type(fcache->func.fn_expr,
+                                                                                       &funcrettype,
+                                                                                       &tupdesc);
 
-       /* Initialize additional info */
+               /* Must save tupdesc in fcache's context */
+               oldcontext = MemoryContextSwitchTo(fcacheCxt);
+
+               if (functypclass == TYPEFUNC_COMPOSITE)
+               {
+                       /* Composite data type, e.g. a table's row type */
+                       Assert(tupdesc);
+                       /* Must copy it out of typcache for safety */
+                       fcache->funcResultDesc = CreateTupleDescCopy(tupdesc);
+                       fcache->funcReturnsTuple = true;
+               }
+               else if (functypclass == TYPEFUNC_SCALAR)
+               {
+                       /* Base data type, i.e. scalar */
+                       tupdesc = CreateTemplateTupleDesc(1, false);
+                       TupleDescInitEntry(tupdesc,
+                                                          (AttrNumber) 1,
+                                                          NULL,
+                                                          funcrettype,
+                                                          -1,
+                                                          0);
+                       fcache->funcResultDesc = tupdesc;
+                       fcache->funcReturnsTuple = false;
+               }
+               else
+               {
+                       /* Else, we will complain if function wants materialize mode */
+                       fcache->funcResultDesc = NULL;
+               }
+
+               MemoryContextSwitchTo(oldcontext);
+       }
+       else
+               fcache->funcResultDesc = NULL;
+
+       /* Initialize additional state */
+       fcache->funcResultStore = NULL;
+       fcache->funcResultSlot = NULL;
        fcache->setArgsValid = false;
        fcache->shutdown_reg = false;
-       fcache->func.fn_expr = (Node *) fcache->xprstate.expr;
 }
 
 /*
@@ -1026,6 +1087,15 @@ ShutdownFuncExpr(Datum arg)
 {
        FuncExprState *fcache = (FuncExprState *) DatumGetPointer(arg);
 
+       /* If we have a slot, make sure it's let go of any tuplestore pointer */
+       if (fcache->funcResultSlot)
+               ExecClearTuple(fcache->funcResultSlot);
+
+       /* Release any open tuplestore */
+       if (fcache->funcResultStore)
+               tuplestore_end(fcache->funcResultStore);
+       fcache->funcResultStore = NULL;
+
        /* Clear any active set-argument state */
        fcache->setArgsValid = false;
 
@@ -1134,18 +1204,135 @@ ExecEvalFuncArgs(FunctionCallInfo fcinfo,
        return argIsDone;
 }
 
+/*
+ *             ExecPrepareTuplestoreResult
+ *
+ * Subroutine for ExecMakeFunctionResult: prepare to extract rows from a
+ * tuplestore function result.  We must set up a funcResultSlot (unless
+ * already done in a previous call cycle) and verify that the function
+ * returned the expected tuple descriptor.
+ */
+static void
+ExecPrepareTuplestoreResult(FuncExprState *fcache,
+                                                       ExprContext *econtext,
+                                                       Tuplestorestate *resultStore,
+                                                       TupleDesc resultDesc)
+{
+       fcache->funcResultStore = resultStore;
+
+       if (fcache->funcResultSlot == NULL)
+       {
+               /* Create a slot so we can read data out of the tuplestore */
+               MemoryContext oldcontext;
+
+               /* We must have been able to determine the result rowtype */
+               if (fcache->funcResultDesc == NULL)
+                       ereport(ERROR,
+                                       (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+                                        errmsg("function returning setof record called in "
+                                                       "context that cannot accept type record")));
+
+               oldcontext = MemoryContextSwitchTo(fcache->func.fn_mcxt);
+               fcache->funcResultSlot =
+                       MakeSingleTupleTableSlot(fcache->funcResultDesc);
+               MemoryContextSwitchTo(oldcontext);
+       }
+
+       /*
+        * 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.
+        */
+       if (resultDesc)
+       {
+               if (fcache->funcResultDesc)
+                       tupledesc_match(fcache->funcResultDesc, resultDesc);
+
+               /*
+                * If it is a dynamically-allocated TupleDesc, free it: it is
+                * typically allocated in a per-query context, so we must avoid
+                * leaking it across multiple usages.
+                */
+               if (resultDesc->tdrefcount == -1)
+                       FreeTupleDesc(resultDesc);
+       }
+
+       /* Register cleanup callback if we didn't already */
+       if (!fcache->shutdown_reg)
+       {
+               RegisterExprContextCallback(econtext,
+                                                                       ShutdownFuncExpr,
+                                                                       PointerGetDatum(fcache));
+               fcache->shutdown_reg = true;
+       }
+}
+
+/*
+ * Check that function result tuple type (src_tupdesc) matches or can
+ * be considered to match what the query expects (dst_tupdesc). If
+ * they don't match, ereport.
+ *
+ * We really only care about number of attributes and data type.
+ * Also, we can ignore type mismatch on columns that are dropped in the
+ * destination type, so long as the physical storage matches.  This is
+ * helpful in some cases involving out-of-date cached plans.
+ */
+static void
+tupledesc_match(TupleDesc dst_tupdesc, TupleDesc src_tupdesc)
+{
+       int                     i;
+
+       if (dst_tupdesc->natts != src_tupdesc->natts)
+               ereport(ERROR,
+                               (errcode(ERRCODE_DATATYPE_MISMATCH),
+                                errmsg("function return row and query-specified return row do not match"),
+                                errdetail("Returned row contains %d attributes, but query expects %d.",
+                                                  src_tupdesc->natts, dst_tupdesc->natts)));
+
+       for (i = 0; i < dst_tupdesc->natts; i++)
+       {
+               Form_pg_attribute dattr = dst_tupdesc->attrs[i];
+               Form_pg_attribute sattr = src_tupdesc->attrs[i];
+
+               if (dattr->atttypid == sattr->atttypid)
+                       continue;                       /* no worries */
+               if (!dattr->attisdropped)
+                       ereport(ERROR,
+                                       (errcode(ERRCODE_DATATYPE_MISMATCH),
+                                        errmsg("function return row and query-specified return row do not match"),
+                                        errdetail("Returned type %s at ordinal position %d, but query expects %s.",
+                                                          format_type_be(sattr->atttypid),
+                                                          i + 1,
+                                                          format_type_be(dattr->atttypid))));
+
+               if (dattr->attlen != sattr->attlen ||
+                       dattr->attalign != sattr->attalign)
+                       ereport(ERROR,
+                                       (errcode(ERRCODE_DATATYPE_MISMATCH),
+                                        errmsg("function return row and query-specified return row do not match"),
+                                        errdetail("Physical storage mismatch on dropped attribute at ordinal position %d.",
+                                                          i + 1)));
+       }
+}
+
 /*
  *             ExecMakeFunctionResult
  *
  * Evaluate the arguments to a function and then the function itself.
+ * init_fcache is presumed already run on the FuncExprState.
+ *
+ * This function handles the most general case, wherein the function or
+ * one of its arguments might (or might not) return a set.  If we find
+ * no sets involved, we will change the FuncExprState's function pointer
+ * to use a simpler method on subsequent calls.
  */
-Datum
+static Datum
 ExecMakeFunctionResult(FuncExprState *fcache,
                                           ExprContext *econtext,
                                           bool *isNull,
                                           ExprDoneCond *isDone)
 {
-       List       *arguments = fcache->args;
+       List       *arguments;
        Datum           result;
        FunctionCallInfoData fcinfo;
        PgStat_FunctionCallUsage fcusage;
@@ -1154,15 +1341,56 @@ ExecMakeFunctionResult(FuncExprState *fcache,
        bool            hasSetArg;
        int                     i;
 
+restart:
+
        /* Guard against stack overflow due to overly complex expressions */
        check_stack_depth();
 
+       /*
+        * If a previous call of the function returned a set result in the form
+        * of a tuplestore, continue reading rows from the tuplestore until it's
+        * empty.
+        */
+       if (fcache->funcResultStore)
+       {
+               Assert(isDone);                         /* it was provided before ... */
+               if (tuplestore_gettupleslot(fcache->funcResultStore, true,
+                                                                       fcache->funcResultSlot))
+               {
+                       *isDone = ExprMultipleResult;
+                       if (fcache->funcReturnsTuple)
+                       {
+                               /* We must return the whole tuple as a Datum. */
+                               *isNull = false;
+                               return ExecFetchSlotTupleDatum(fcache->funcResultSlot);
+                       }
+                       else
+                       {
+                               /* Extract the first column and return it as a scalar. */
+                               return slot_getattr(fcache->funcResultSlot, 1, isNull);
+                       }
+               }
+               /* Exhausted the tuplestore, so clean up */
+               tuplestore_end(fcache->funcResultStore);
+               fcache->funcResultStore = NULL;
+               /* We are done unless there was a set-valued argument */
+               if (!fcache->setHasSetArg)
+               {
+                       *isDone = ExprEndResult;
+                       *isNull = true;
+                       return (Datum) 0;
+               }
+               /* If there was, continue evaluating the argument values */
+               Assert(!fcache->setArgsValid);
+       }
+
        /*
         * arguments is a list of expressions to evaluate before passing to the
         * function manager.  We skip the evaluation if it was already done in the
         * previous call (ie, we are continuing the evaluation of a set-valued
         * function).  Otherwise, collect the current argument values into fcinfo.
         */
+       arguments = fcache->args;
        if (!fcache->setArgsValid)
        {
                /* Need to prep callinfo structure */
@@ -1199,8 +1427,8 @@ ExecMakeFunctionResult(FuncExprState *fcache,
                fcinfo.resultinfo = (Node *) &rsinfo;
                rsinfo.type = T_ReturnSetInfo;
                rsinfo.econtext = econtext;
-               rsinfo.expectedDesc = NULL;
-               rsinfo.allowedModes = (int) SFRM_ValuePerCall;
+               rsinfo.expectedDesc = fcache->funcResultDesc;
+               rsinfo.allowedModes = (int) (SFRM_ValuePerCall | SFRM_Materialize);
                rsinfo.returnMode = SFRM_ValuePerCall;
                /* isDone is filled below */
                rsinfo.setResult = NULL;
@@ -1208,8 +1436,7 @@ ExecMakeFunctionResult(FuncExprState *fcache,
        }
 
        /*
-        * now return the value gotten by calling the function manager, passing
-        * the function the evaluated parameter values.
+        * Now call the function, passing the evaluated parameter values.
         */
        if (fcache->func.fn_retset || hasSetArg)
        {
@@ -1270,36 +1497,69 @@ ExecMakeFunctionResult(FuncExprState *fcache,
                                *isDone = ExprEndResult;
                        }
 
-                       if (*isDone != ExprEndResult)
+                       /* Which protocol does function want to use? */
+                       if (rsinfo.returnMode == SFRM_ValuePerCall)
                        {
-                               /*
-                                * Got a result from current argument.  If function itself
-                                * returns set, save the current argument values to re-use on
-                                * the next call.
-                                */
-                               if (fcache->func.fn_retset && *isDone == ExprMultipleResult)
+                               if (*isDone != ExprEndResult)
                                {
-                                       memcpy(&fcache->setArgs, &fcinfo, sizeof(fcinfo));
-                                       fcache->setHasSetArg = hasSetArg;
-                                       fcache->setArgsValid = true;
-                                       /* Register cleanup callback if we didn't already */
-                                       if (!fcache->shutdown_reg)
+                                       /*
+                                        * Got a result from current argument. If function itself
+                                        * returns set, save the current argument values to re-use
+                                        * on the next call.
+                                        */
+                                       if (fcache->func.fn_retset &&
+                                               *isDone == ExprMultipleResult)
                                        {
-                                               RegisterExprContextCallback(econtext,
-                                                                                                       ShutdownFuncExpr,
-                                                                                                       PointerGetDatum(fcache));
-                                               fcache->shutdown_reg = true;
+                                               memcpy(&fcache->setArgs, &fcinfo, sizeof(fcinfo));
+                                               fcache->setHasSetArg = hasSetArg;
+                                               fcache->setArgsValid = true;
+                                               /* Register cleanup callback if we didn't already */
+                                               if (!fcache->shutdown_reg)
+                                               {
+                                                       RegisterExprContextCallback(econtext,
+                                                                                                               ShutdownFuncExpr,
+                                                                                                               PointerGetDatum(fcache));
+                                                       fcache->shutdown_reg = true;
+                                               }
                                        }
-                               }
 
-                               /*
-                                * Make sure we say we are returning a set, even if the
-                                * function itself doesn't return sets.
-                                */
-                               if (hasSetArg)
-                                       *isDone = ExprMultipleResult;
-                               break;
+                                       /*
+                                        * Make sure we say we are returning a set, even if the
+                                        * function itself doesn't return sets.
+                                        */
+                                       if (hasSetArg)
+                                               *isDone = ExprMultipleResult;
+                                       break;
+                               }
+                       }
+                       else if (rsinfo.returnMode == SFRM_Materialize)
+                       {
+                               /* check we're on the same page as the function author */
+                               if (rsinfo.isDone != ExprSingleResult)
+                                       ereport(ERROR,
+                                                       (errcode(ERRCODE_E_R_I_E_SRF_PROTOCOL_VIOLATED),
+                                                        errmsg("table-function protocol for materialize mode was not followed")));
+                               if (rsinfo.setResult != NULL)
+                               {
+                                       /* prepare to return values from the tuplestore */
+                                       ExecPrepareTuplestoreResult(fcache, econtext,
+                                                                                               rsinfo.setResult,
+                                                                                               rsinfo.setDesc);
+                                       /* remember whether we had set arguments */
+                                       fcache->setHasSetArg = hasSetArg;
+                                       /* loop back to top to start returning from tuplestore */
+                                       goto restart;
+                               }
+                               /* if setResult was left null, treat it as empty set */
+                               *isDone = ExprEndResult;
+                               *isNull = true;
+                               result = (Datum) 0;
                        }
+                       else
+                               ereport(ERROR,
+                                               (errcode(ERRCODE_E_R_I_E_SRF_PROTOCOL_VIOLATED),
+                                                errmsg("unrecognized table-function returnMode: %d",
+                                                               (int) rsinfo.returnMode)));
 
                        /* Else, done with this argument */
                        if (!hasSetArg)
@@ -1437,14 +1697,12 @@ ExecMakeFunctionResultNoSets(FuncExprState *fcache,
  *             ExecMakeTableFunctionResult
  *
  * Evaluate a table function, producing a materialized result in a Tuplestore
- * object.     *returnDesc is set to the tupledesc actually returned by the
- * function, or NULL if it didn't provide one.
+ * object.
  */
 Tuplestorestate *
 ExecMakeTableFunctionResult(ExprState *funcexpr,
                                                        ExprContext *econtext,
-                                                       TupleDesc expectedDesc,
-                                                       TupleDesc *returnDesc)
+                                                       TupleDesc expectedDesc)
 {
        Tuplestorestate *tupstore = NULL;
        TupleDesc       tupdesc = NULL;
@@ -1511,7 +1769,8 @@ ExecMakeTableFunctionResult(ExprState *funcexpr,
                {
                        FuncExpr   *func = (FuncExpr *) fcache->xprstate.expr;
 
-                       init_fcache(func->funcid, fcache, econtext->ecxt_per_query_memory);
+                       init_fcache(func->funcid, fcache,
+                                               econtext->ecxt_per_query_memory, false);
                }
                returnsSet = fcache->func.fn_retset;
 
@@ -1734,10 +1993,27 @@ no_function_result:
                }
        }
 
+       /*
+        * 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.
+        */
+       if (rsinfo.setDesc)
+       {
+               tupledesc_match(expectedDesc, rsinfo.setDesc);
+
+               /*
+                * If it is a dynamically-allocated TupleDesc, free it: it is
+                * typically allocated in a per-query context, so we must avoid
+                * leaking it across multiple usages.
+                */
+               if (rsinfo.setDesc->tdrefcount == -1)
+                       FreeTupleDesc(rsinfo.setDesc);
+       }
+
        MemoryContextSwitchTo(callerContext);
 
-       /* The returned pointers are those in rsinfo */
-       *returnDesc = rsinfo.setDesc;
+       /* All done, pass back the tuplestore */
        return rsinfo.setResult;
 }
 
@@ -1765,7 +2041,7 @@ ExecEvalFunc(FuncExprState *fcache,
        FuncExpr   *func = (FuncExpr *) fcache->xprstate.expr;
 
        /* Initialize function lookup info */
-       init_fcache(func->funcid, fcache, econtext->ecxt_per_query_memory);
+       init_fcache(func->funcid, fcache, econtext->ecxt_per_query_memory, true);
 
        /* Go directly to ExecMakeFunctionResult on subsequent uses */
        fcache->xprstate.evalfunc = (ExprStateEvalFunc) ExecMakeFunctionResult;
@@ -1787,7 +2063,7 @@ ExecEvalOper(FuncExprState *fcache,
        OpExpr     *op = (OpExpr *) fcache->xprstate.expr;
 
        /* Initialize function lookup info */
-       init_fcache(op->opfuncid, fcache, econtext->ecxt_per_query_memory);
+       init_fcache(op->opfuncid, fcache, econtext->ecxt_per_query_memory, true);
 
        /* Go directly to ExecMakeFunctionResult on subsequent uses */
        fcache->xprstate.evalfunc = (ExprStateEvalFunc) ExecMakeFunctionResult;
@@ -1829,7 +2105,8 @@ ExecEvalDistinct(FuncExprState *fcache,
        {
                DistinctExpr *op = (DistinctExpr *) fcache->xprstate.expr;
 
-               init_fcache(op->opfuncid, fcache, econtext->ecxt_per_query_memory);
+               init_fcache(op->opfuncid, fcache,
+                                       econtext->ecxt_per_query_memory, true);
                Assert(!fcache->func.fn_retset);
        }
 
@@ -1909,7 +2186,7 @@ ExecEvalScalarArrayOp(ScalarArrayOpExprState *sstate,
        if (sstate->fxprstate.func.fn_oid == InvalidOid)
        {
                init_fcache(opexpr->opfuncid, &sstate->fxprstate,
-                                       econtext->ecxt_per_query_memory);
+                                       econtext->ecxt_per_query_memory, true);
                Assert(!sstate->fxprstate.func.fn_retset);
        }
 
@@ -3096,7 +3373,8 @@ ExecEvalNullIf(FuncExprState *nullIfExpr,
        {
                NullIfExpr *op = (NullIfExpr *) nullIfExpr->xprstate.expr;
 
-               init_fcache(op->opfuncid, nullIfExpr, econtext->ecxt_per_query_memory);
+               init_fcache(op->opfuncid, nullIfExpr,
+                                       econtext->ecxt_per_query_memory, true);
                Assert(!nullIfExpr->func.fn_retset);
        }
 
index b7bba4d0adc5cff92448fb81703516311393d353..42d550b223a07aed47f6e75fd1262c01b1435c81 100644 (file)
@@ -766,6 +766,33 @@ ExecFetchSlotMinimalTuple(TupleTableSlot *slot)
        return slot->tts_mintuple;
 }
 
+/* --------------------------------
+ *             ExecFetchSlotTupleDatum
+ *                     Fetch the slot's tuple as a composite-type Datum.
+ *
+ *             We convert the slot's contents to local physical-tuple form,
+ *             and fill in the Datum header fields.  Note that the result
+ *             always points to storage owned by the slot.
+ * --------------------------------
+ */
+Datum
+ExecFetchSlotTupleDatum(TupleTableSlot *slot)
+{
+       HeapTuple       tup;
+       HeapTupleHeader td;
+       TupleDesc       tupdesc;
+
+       /* Make sure we can scribble on the slot contents ... */
+       tup = ExecMaterializeSlot(slot);
+       /* ... and set up the composite-Datum header fields, in case not done */
+       td = tup->t_data;
+       tupdesc = slot->tts_tupleDescriptor;
+       HeapTupleHeaderSetDatumLength(td, tup->t_len);
+       HeapTupleHeaderSetTypeId(td, tupdesc->tdtypeid);
+       HeapTupleHeaderSetTypMod(td, tupdesc->tdtypmod);
+       return PointerGetDatum(td);
+}
+
 /* --------------------------------
  *             ExecMaterializeSlot
  *                     Force a slot into the "materialized" state.
index 989ece58b62286ea7ff2b21b2eab43e2e275abd8..c93865f0223325c615d61675a1c453f1b066d55a 100644 (file)
@@ -28,7 +28,6 @@
 
 
 static TupleTableSlot *FunctionNext(FunctionScanState *node);
-static void tupledesc_match(TupleDesc dst_tupdesc, TupleDesc src_tupdesc);
 
 /* ----------------------------------------------------------------
  *                                             Scan Support
@@ -62,32 +61,10 @@ FunctionNext(FunctionScanState *node)
         */
        if (tuplestorestate == NULL)
        {
-               ExprContext *econtext = node->ss.ps.ps_ExprContext;
-               TupleDesc       funcTupdesc;
-
                node->tuplestorestate = tuplestorestate =
                        ExecMakeTableFunctionResult(node->funcexpr,
-                                                                               econtext,
-                                                                               node->tupdesc,
-                                                                               &funcTupdesc);
-
-               /*
-                * 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.
-                */
-               if (funcTupdesc)
-               {
-                       tupledesc_match(node->tupdesc, funcTupdesc);
-
-                       /*
-                        * If it is a dynamically-allocated TupleDesc, free it: it is
-                        * typically allocated in the EState's per-query context, so we
-                        * must avoid leaking it on rescan.
-                        */
-                       if (funcTupdesc->tdrefcount == -1)
-                               FreeTupleDesc(funcTupdesc);
-               }
+                                                                               node->ss.ps.ps_ExprContext,
+                                                                               node->tupdesc);
        }
 
        /*
@@ -296,9 +273,9 @@ ExecFunctionReScan(FunctionScanState *node, ExprContext *exprCtxt)
 
        /*
         * Here we have a choice whether to drop the tuplestore (and recompute the
-        * function outputs) or just rescan it.  This should depend on whether the
-        * function expression contains parameters and/or is marked volatile.
-        * FIXME soon.
+        * function outputs) or just rescan it.  We must recompute if the
+        * expression contains parameters, else we rescan.  XXX maybe we should
+        * recompute if the function is volatile?
         */
        if (node->ss.ps.chgParam != NULL)
        {
@@ -308,51 +285,3 @@ ExecFunctionReScan(FunctionScanState *node, ExprContext *exprCtxt)
        else
                tuplestore_rescan(node->tuplestorestate);
 }
-
-/*
- * Check that function result tuple type (src_tupdesc) matches or can
- * be considered to match what the query expects (dst_tupdesc). If
- * they don't match, ereport.
- *
- * We really only care about number of attributes and data type.
- * Also, we can ignore type mismatch on columns that are dropped in the
- * destination type, so long as the physical storage matches.  This is
- * helpful in some cases involving out-of-date cached plans.
- */
-static void
-tupledesc_match(TupleDesc dst_tupdesc, TupleDesc src_tupdesc)
-{
-       int                     i;
-
-       if (dst_tupdesc->natts != src_tupdesc->natts)
-               ereport(ERROR,
-                               (errcode(ERRCODE_DATATYPE_MISMATCH),
-                                errmsg("function return row and query-specified return row do not match"),
-                                errdetail("Returned row contains %d attributes, but query expects %d.",
-                                                  src_tupdesc->natts, dst_tupdesc->natts)));
-
-       for (i = 0; i < dst_tupdesc->natts; i++)
-       {
-               Form_pg_attribute dattr = dst_tupdesc->attrs[i];
-               Form_pg_attribute sattr = src_tupdesc->attrs[i];
-
-               if (dattr->atttypid == sattr->atttypid)
-                       continue;                       /* no worries */
-               if (!dattr->attisdropped)
-                       ereport(ERROR,
-                                       (errcode(ERRCODE_DATATYPE_MISMATCH),
-                                        errmsg("function return row and query-specified return row do not match"),
-                                        errdetail("Returned type %s at ordinal position %d, but query expects %s.",
-                                                          format_type_be(sattr->atttypid),
-                                                          i + 1,
-                                                          format_type_be(dattr->atttypid))));
-
-               if (dattr->attlen != sattr->attlen ||
-                       dattr->attalign != sattr->attalign)
-                       ereport(ERROR,
-                                       (errcode(ERRCODE_DATATYPE_MISMATCH),
-                                        errmsg("function return row and query-specified return row do not match"),
-                                        errdetail("Physical storage mismatch on dropped attribute at ordinal position %d.",
-                                                          i + 1)));
-       }
-}
index cf927985de275dcdad6238581624bf319a5a704c..112c2dce9a8e4feb9f24cbac075b3ef64cee9e43 100644 (file)
@@ -432,8 +432,7 @@ 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.
 
-If the function is being called as a table function (ie, it appears in a
-FROM item), then the expected tuple descriptor is passed in ReturnSetInfo;
+If available, the expected tuple descriptor is passed in ReturnSetInfo;
 in other contexts the expectedDesc field will be NULL.  The function need
 not pay attention to expectedDesc, but it may be useful in special cases.
 
index d8973bc049e47ae9c8d9d76ca116602dfa56a0a4..d1e97daa8ef9213fbff9d43f8a9c53e95c924440 100644 (file)
@@ -176,16 +176,9 @@ extern Datum GetAttributeByNum(HeapTupleHeader tuple, AttrNumber attrno,
                                  bool *isNull);
 extern Datum GetAttributeByName(HeapTupleHeader tuple, const char *attname,
                                   bool *isNull);
-extern void init_fcache(Oid foid, FuncExprState *fcache,
-                       MemoryContext fcacheCxt);
-extern Datum ExecMakeFunctionResult(FuncExprState *fcache,
-                                          ExprContext *econtext,
-                                          bool *isNull,
-                                          ExprDoneCond *isDone);
 extern Tuplestorestate *ExecMakeTableFunctionResult(ExprState *funcexpr,
                                                        ExprContext *econtext,
-                                                       TupleDesc expectedDesc,
-                                                       TupleDesc *returnDesc);
+                                                       TupleDesc expectedDesc);
 extern Datum ExecEvalExprSwitchContext(ExprState *expression, ExprContext *econtext,
                                                  bool *isNull, ExprDoneCond *isDone);
 extern ExprState *ExecInitExpr(Expr *node, PlanState *parent);
index 4d42dd16e1038cb27445ca72d22f9170f8902d61..ef0f5cf08e44304a8f32e48deb2786f21f03fbef 100644 (file)
@@ -160,6 +160,7 @@ extern HeapTuple ExecCopySlotTuple(TupleTableSlot *slot);
 extern MinimalTuple ExecCopySlotMinimalTuple(TupleTableSlot *slot);
 extern HeapTuple ExecFetchSlotTuple(TupleTableSlot *slot);
 extern MinimalTuple ExecFetchSlotMinimalTuple(TupleTableSlot *slot);
+extern Datum ExecFetchSlotTupleDatum(TupleTableSlot *slot);
 extern HeapTuple ExecMaterializeSlot(TupleTableSlot *slot);
 extern TupleTableSlot *ExecCopySlot(TupleTableSlot *dstslot,
                         TupleTableSlot *srcslot);
index 003456d228f2a9768051179cf85b4f43f0649f93..04dee5dd479ffec627dc31e48ee6f54cbb51e941 100644 (file)
@@ -538,13 +538,29 @@ typedef struct FuncExprState
 
        /*
         * Function manager's lookup info for the target function.  If func.fn_oid
-        * is InvalidOid, we haven't initialized it yet.
+        * is InvalidOid, we haven't initialized it yet (nor any of the following
+        * fields).
         */
        FmgrInfo        func;
 
        /*
-        * We also need to store argument values across calls when evaluating a
-        * function-returning-set.
+        * For a set-returning function (SRF) that returns a tuplestore, we
+        * keep the tuplestore here and dole out the result rows one at a time.
+        * The slot holds the row currently being returned.
+        */
+       Tuplestorestate *funcResultStore;
+       TupleTableSlot *funcResultSlot;
+
+       /*
+        * In some cases we need to compute a tuple descriptor for the function's
+        * output.  If so, it's stored here.
+        */
+       TupleDesc       funcResultDesc;
+       bool            funcReturnsTuple;       /* valid when funcResultDesc isn't NULL */
+
+       /*
+        * We need to store argument values across calls when evaluating a SRF
+        * that uses value-per-call mode.
         *
         * setArgsValid is true when we are evaluating a set-valued function and
         * we are in the middle of a call series; we want to pass the same
@@ -556,14 +572,15 @@ typedef struct FuncExprState
        /*
         * Flag to remember whether we found a set-valued argument to the
         * function. This causes the function result to be a set as well. Valid
-        * only when setArgsValid is true.
+        * only when setArgsValid is true or funcResultStore isn't NULL.
         */
        bool            setHasSetArg;   /* some argument returns a set */
 
        /*
         * Flag to remember whether we have registered a shutdown callback for
-        * this FuncExprState.  We do so only if setArgsValid has been true at
-        * least once (since all the callback is for is to clear setArgsValid).
+        * this FuncExprState.  We do so only if funcResultStore or setArgsValid
+        * has been set at least once (since all the callback is for is to release
+        * the tuplestore or clear setArgsValid).
         */
        bool            shutdown_reg;   /* a shutdown callback is registered */
 
index 9b3e7168145ec99600ed9de064d80551c0330d3a..708723d3e5f2485a9ce764812084fda7468c4324 100644 (file)
@@ -35,7 +35,10 @@ CREATE OR REPLACE FUNCTION perl_set_int(int) RETURNS SETOF INTEGER AS $$
 return undef;
 $$ LANGUAGE plperl;
 SELECT perl_set_int(5);
-ERROR:  set-valued function called in context that cannot accept a set
+ perl_set_int 
+--------------
+(0 rows)
+
 SELECT * FROM perl_set_int(5);
  perl_set_int 
 --------------
@@ -45,7 +48,16 @@ CREATE OR REPLACE FUNCTION perl_set_int(int) RETURNS SETOF INTEGER AS $$
 return [0..$_[0]];
 $$ LANGUAGE plperl;
 SELECT perl_set_int(5);
-ERROR:  set-valued function called in context that cannot accept a set
+ perl_set_int 
+--------------
+            0
+            1
+            2
+            3
+            4
+            5
+(6 rows)
+
 SELECT * FROM perl_set_int(5);
  perl_set_int 
 --------------
@@ -92,7 +104,10 @@ CREATE OR REPLACE FUNCTION perl_set() RETURNS SETOF testrowperl AS $$
     return undef;
 $$  LANGUAGE plperl;
 SELECT perl_set();
-ERROR:  set-valued function called in context that cannot accept a set
+ perl_set 
+----------
+(0 rows)
+
 SELECT * FROM perl_set();
  f1 | f2 | f3 
 ----+----+----
@@ -106,7 +121,7 @@ CREATE OR REPLACE FUNCTION perl_set() RETURNS SETOF testrowperl AS $$
     ];
 $$  LANGUAGE plperl;
 SELECT perl_set();
-ERROR:  set-valued function called in context that cannot accept a set
+ERROR:  setof-composite-returning Perl function must call return_next with reference to hash
 SELECT * FROM perl_set();
 ERROR:  setof-composite-returning Perl function must call return_next with reference to hash
 CREATE OR REPLACE FUNCTION perl_set() RETURNS SETOF testrowperl AS $$
@@ -117,7 +132,13 @@ CREATE OR REPLACE FUNCTION perl_set() RETURNS SETOF testrowperl AS $$
     ];
 $$  LANGUAGE plperl;
 SELECT perl_set();
-ERROR:  set-valued function called in context that cannot accept a set
+       perl_set       
+----------------------
+ (1,Hello,World)
+ (2,Hello,PostgreSQL)
+ (3,Hello,PL/Perl)
+(3 rows)
+
 SELECT * FROM perl_set();
  f1 |  f2   |     f3     
 ----+-------+------------
@@ -242,7 +263,13 @@ RETURNS SETOF record AS $$
     ];
 $$  LANGUAGE plperl;
 SELECT perl_out_params_set();
-ERROR:  set-valued function called in context that cannot accept a set
+ perl_out_params_set  
+----------------------
+ (1,Hello,World)
+ (2,Hello,PostgreSQL)
+ (3,Hello,PL/Perl)
+(3 rows)
+
 SELECT * FROM perl_out_params_set();
  f1 |  f2   |     f3     
 ----+-------+------------
@@ -252,7 +279,13 @@ SELECT * FROM perl_out_params_set();
 (3 rows)
 
 SELECT (perl_out_params_set()).f3;
-ERROR:  set-valued function called in context that cannot accept a set
+     f3     
+------------
+ World
+ PostgreSQL
+ PL/Perl
+(3 rows)
+
 --
 -- Check behavior with erroneous return values
 --