aboutsummaryrefslogtreecommitdiff
path: root/src/backend/executor/functions.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/backend/executor/functions.c')
-rw-r--r--src/backend/executor/functions.c135
1 files changed, 85 insertions, 50 deletions
diff --git a/src/backend/executor/functions.c b/src/backend/executor/functions.c
index e0bca7cb81c..8d4d062d579 100644
--- a/src/backend/executor/functions.c
+++ b/src/backend/executor/functions.c
@@ -44,7 +44,7 @@
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;
@@ -145,11 +145,13 @@ typedef struct SQLFunctionCache
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 */
@@ -1250,7 +1252,7 @@ static void
postquel_start(execution_state *es, SQLFunctionCachePtr fcache)
{
DestReceiver *dest;
- MemoryContext oldcontext;
+ MemoryContext oldcontext = CurrentMemoryContext;
Assert(es->qd == NULL);
@@ -1296,12 +1298,27 @@ postquel_start(execution_state *es, SQLFunctionCachePtr fcache)
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)
{
@@ -1311,8 +1328,11 @@ postquel_start(execution_state *es, SQLFunctionCachePtr fcache)
/* 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;
@@ -1500,8 +1520,8 @@ postquel_get_single_result(TupleTableSlot *slot,
/*
* 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)
{
@@ -1521,6 +1541,9 @@ postquel_get_single_result(TupleTableSlot *slot,
value = datumCopy(value, fcache->func->typbyval, fcache->func->typlen);
}
+ /* Clear the slot for next time */
+ ExecClearTuple(slot);
+
return value;
}
@@ -1532,6 +1555,7 @@ fmgr_sql(PG_FUNCTION_ARGS)
{
SQLFunctionCachePtr fcache;
ErrorContextCallback sqlerrcontext;
+ MemoryContext tscontext;
bool randomAccess;
bool lazyEvalOK;
bool pushed_snapshot;
@@ -1558,11 +1582,15 @@ fmgr_sql(PG_FUNCTION_ARGS)
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;
}
/*
@@ -1570,6 +1598,10 @@ fmgr_sql(PG_FUNCTION_ARGS)
*/
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()
*/
@@ -1579,20 +1611,6 @@ fmgr_sql(PG_FUNCTION_ARGS)
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.
*/
@@ -1661,11 +1679,12 @@ fmgr_sql(PG_FUNCTION_ARGS)
/*
* 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);
@@ -1708,7 +1727,8 @@ fmgr_sql(PG_FUNCTION_ARGS)
}
/*
- * 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)
{
@@ -1721,16 +1741,12 @@ fmgr_sql(PG_FUNCTION_ARGS)
* 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.
@@ -1752,12 +1768,8 @@ fmgr_sql(PG_FUNCTION_ARGS)
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;
@@ -1779,7 +1791,12 @@ fmgr_sql(PG_FUNCTION_ARGS)
* 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;
@@ -1807,9 +1824,9 @@ fmgr_sql(PG_FUNCTION_ARGS)
*/
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
{
@@ -1824,9 +1841,6 @@ fmgr_sql(PG_FUNCTION_ARGS)
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 */
@@ -2604,11 +2618,32 @@ sqlfunction_receive(TupleTableSlot *slot, DestReceiver *self)
{
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;
}