typedef struct
{
DestReceiver pub; /* publicly-known function pointers */
- Tuplestorestate *tstore; /* where to put result tuples */
+ Tuplestorestate *tstore; /* where to put result tuples, or NULL */
JunkFilter *filter; /* filter to convert tuple type */
} DR_sqlfunction;
bool lazyEvalOK; /* true if lazyEval is safe */
bool shutdown_reg; /* true if registered shutdown callback */
bool lazyEval; /* true if using lazyEval for result query */
+ bool randomAccess; /* true if tstore needs random access */
bool ownSubcontext; /* is subcontext really a separate context? */
ParamListInfo paramLI; /* Param list representing current args */
- Tuplestorestate *tstore; /* where we accumulate result tuples */
+ Tuplestorestate *tstore; /* where we accumulate result for a SRF */
+ MemoryContext tscontext; /* memory context that tstore should be in */
JunkFilter *junkFilter; /* will be NULL if function returns VOID */
int jf_generation; /* tracks whether junkFilter is up-to-date */
postquel_start(execution_state *es, SQLFunctionCachePtr fcache)
{
DestReceiver *dest;
- MemoryContext oldcontext;
+ MemoryContext oldcontext = CurrentMemoryContext;
Assert(es->qd == NULL);
fcache->ownSubcontext = false;
}
+ /*
+ * Build a tuplestore if needed, that is if it's a set-returning function
+ * and we're producing the function result without using lazyEval mode.
+ */
+ if (es->setsResult)
+ {
+ Assert(fcache->tstore == NULL);
+ if (fcache->func->returnsSet && !es->lazyEval)
+ {
+ MemoryContextSwitchTo(fcache->tscontext);
+ fcache->tstore = tuplestore_begin_heap(fcache->randomAccess,
+ false, work_mem);
+ }
+ }
+
/* Switch into the selected subcontext (might be a no-op) */
- oldcontext = MemoryContextSwitchTo(fcache->subcontext);
+ MemoryContextSwitchTo(fcache->subcontext);
/*
- * If this query produces the function result, send its output to the
- * tuplestore; else discard any output.
+ * If this query produces the function result, collect its output using
+ * our custom DestReceiver; else discard any output.
*/
if (es->setsResult)
{
/* pass down the needed info to the dest receiver routines */
myState = (DR_sqlfunction *) dest;
Assert(myState->pub.mydest == DestSQLFunction);
- myState->tstore = fcache->tstore;
+ myState->tstore = fcache->tstore; /* might be NULL */
myState->filter = fcache->junkFilter;
+
+ /* Make very sure the junkfilter's result slot is empty */
+ ExecClearTuple(fcache->junkFilter->jf_resultSlot);
}
else
dest = None_Receiver;
/*
* Set up to return the function value. For pass-by-reference datatypes,
* be sure to copy the result into the current context. We can't leave
- * the data in the TupleTableSlot because we intend to clear the slot
- * before returning.
+ * the data in the TupleTableSlot because we must clear the slot before
+ * returning.
*/
if (fcache->func->returnsTuple)
{
value = datumCopy(value, fcache->func->typbyval, fcache->func->typlen);
}
+ /* Clear the slot for next time */
+ ExecClearTuple(slot);
+
return value;
}
{
SQLFunctionCachePtr fcache;
ErrorContextCallback sqlerrcontext;
+ MemoryContext tscontext;
bool randomAccess;
bool lazyEvalOK;
bool pushed_snapshot;
errmsg("set-valued function called in context that cannot accept a set")));
randomAccess = rsi->allowedModes & SFRM_Materialize_Random;
lazyEvalOK = !(rsi->allowedModes & SFRM_Materialize_Preferred);
+ /* tuplestore, if used, must have query lifespan */
+ tscontext = rsi->econtext->ecxt_per_query_memory;
}
else
{
randomAccess = false;
lazyEvalOK = true;
+ /* we won't need a tuplestore */
+ tscontext = NULL;
}
/*
*/
fcache = init_sql_fcache(fcinfo, lazyEvalOK);
+ /* Remember info that we might need later to construct tuplestore */
+ fcache->tscontext = tscontext;
+ fcache->randomAccess = randomAccess;
+
/*
* Now we can set up error traceback support for ereport()
*/
sqlerrcontext.previous = error_context_stack;
error_context_stack = &sqlerrcontext;
- /*
- * Build tuplestore to hold results, if we don't have one already. We
- * want to re-use the tuplestore across calls, so it needs to live in
- * fcontext.
- */
- if (!fcache->tstore)
- {
- MemoryContext oldcontext;
-
- oldcontext = MemoryContextSwitchTo(fcache->fcontext);
- fcache->tstore = tuplestore_begin_heap(randomAccess, false, work_mem);
- MemoryContextSwitchTo(oldcontext);
- }
-
/*
* Find first unfinished execution_state. If none, advance to the next
* query in function.
/*
* If we ran the command to completion, we can shut it down now. Any
- * row(s) we need to return are safely stashed in the tuplestore, and
- * we want to be sure that, for example, AFTER triggers get fired
- * before we return anything. Also, if the function doesn't return
- * set, we can shut it down anyway because it must be a SELECT and we
- * don't care about fetching any more result rows.
+ * row(s) we need to return are safely stashed in the result slot or
+ * tuplestore, and we want to be sure that, for example, AFTER
+ * triggers get fired before we return anything. Also, if the
+ * function doesn't return set, we can shut it down anyway because it
+ * must be a SELECT and we don't care about fetching any more result
+ * rows.
*/
if (completed || !fcache->func->returnsSet)
postquel_end(es, fcache);
}
/*
- * The tuplestore now contains whatever row(s) we are supposed to return.
+ * The result slot or tuplestore now contains whatever row(s) we are
+ * supposed to return.
*/
if (fcache->func->returnsSet)
{
* row.
*/
Assert(es->lazyEval);
- /* Re-use the junkfilter's output slot to fetch back the tuple */
+ /* The junkfilter's result slot contains the query result tuple */
Assert(fcache->junkFilter);
slot = fcache->junkFilter->jf_resultSlot;
- if (!tuplestore_gettupleslot(fcache->tstore, true, false, slot))
- elog(ERROR, "failed to fetch lazy-eval tuple");
+ Assert(!TTS_EMPTY(slot));
/* Extract the result as a datum, and copy out from the slot */
result = postquel_get_single_result(slot, fcinfo, fcache);
- /* Clear the tuplestore, but keep it for next time */
- /* NB: this might delete the slot's content, but we don't care */
- tuplestore_clear(fcache->tstore);
/*
* Let caller know we're not finished.
else if (fcache->lazyEval)
{
/*
- * We are done with a lazy evaluation. Clean up.
- */
- tuplestore_clear(fcache->tstore);
-
- /*
- * Let caller know we're finished.
+ * We are done with a lazy evaluation. Let caller know we're
+ * finished.
*/
rsi->isDone = ExprEndResult;
* We are done with a non-lazy evaluation. Return whatever is in
* the tuplestore. (It is now caller's responsibility to free the
* tuplestore when done.)
+ *
+ * Note an edge case: we could get here without having made a
+ * tuplestore if the function is declared to return SETOF VOID.
+ * ExecMakeTableFunctionResult will cope with null setResult.
*/
+ Assert(fcache->tstore || fcache->func->rettype == VOIDOID);
rsi->returnMode = SFRM_Materialize;
rsi->setResult = fcache->tstore;
fcache->tstore = NULL;
*/
if (fcache->junkFilter)
{
- /* Re-use the junkfilter's output slot to fetch back the tuple */
+ /* The junkfilter's result slot contains the query result tuple */
slot = fcache->junkFilter->jf_resultSlot;
- if (tuplestore_gettupleslot(fcache->tstore, true, false, slot))
+ if (!TTS_EMPTY(slot))
result = postquel_get_single_result(slot, fcinfo, fcache);
else
{
fcinfo->isnull = true;
result = (Datum) 0;
}
-
- /* Clear the tuplestore, but keep it for next time */
- tuplestore_clear(fcache->tstore);
}
/* Pop snapshot if we have pushed one */
{
DR_sqlfunction *myState = (DR_sqlfunction *) self;
- /* Filter tuple as needed */
- slot = ExecFilterJunk(myState->filter, slot);
+ if (myState->tstore)
+ {
+ /* We are collecting all of a set result into the tuplestore */
+
+ /* Filter tuple as needed */
+ slot = ExecFilterJunk(myState->filter, slot);
- /* Store the filtered tuple into the tuplestore */
- tuplestore_puttupleslot(myState->tstore, slot);
+ /* Store the filtered tuple into the tuplestore */
+ tuplestore_puttupleslot(myState->tstore, slot);
+ }
+ else
+ {
+ /*
+ * We only want the first tuple, which we'll save in the junkfilter's
+ * result slot. Ignore any additional tuples passed.
+ */
+ if (TTS_EMPTY(myState->filter->jf_resultSlot))
+ {
+ /* Filter tuple as needed */
+ slot = ExecFilterJunk(myState->filter, slot);
+ Assert(slot == myState->filter->jf_resultSlot);
+
+ /* Materialize the slot so it preserves pass-by-ref values */
+ ExecMaterializeSlot(slot);
+ }
+ }
return true;
}