*
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/executor/execQual.c,v 1.233 2008/08/25 22:42:32 tgl Exp $
+ * $PostgreSQL: pgsql/src/backend/executor/execQual.c,v 1.234 2008/10/28 22:02:05 tgl Exp $
*
*-------------------------------------------------------------------------
*/
* 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"
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);
/*
* 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;
/* 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;
}
/*
{
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;
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;
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 */
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;
}
/*
- * 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)
{
*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)
* 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;
{
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;
}
}
+ /*
+ * 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;
}
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;
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;
{
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);
}
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);
}
{
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);
}
*
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/executor/nodeFunctionscan.c,v 1.47 2008/10/01 19:51:49 tgl Exp $
+ * $PostgreSQL: pgsql/src/backend/executor/nodeFunctionscan.c,v 1.48 2008/10/28 22:02:05 tgl Exp $
*
*-------------------------------------------------------------------------
*/
static TupleTableSlot *FunctionNext(FunctionScanState *node);
-static void tupledesc_match(TupleDesc dst_tupdesc, TupleDesc src_tupdesc);
/* ----------------------------------------------------------------
* Scan Support
*/
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);
}
/*
/*
* 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)
{
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)));
- }
-}