diff --git a/src/backend/executor/execAmi.c b/src/backend/executor/execAmi.c
index 1f18e5d..f3205e4 100644
--- a/src/backend/executor/execAmi.c
+++ b/src/backend/executor/execAmi.c
@@ -554,7 +554,6 @@ ExecSupportsBackwardScan(Plan *node)
 
 		case T_SeqScan:
 		case T_TidScan:
-		case T_FunctionScan:
 		case T_ValuesScan:
 		case T_CteScan:
 		case T_Material:
@@ -613,7 +612,6 @@ ExecMaterializesOutput(NodeTag plantype)
 	switch (plantype)
 	{
 		case T_Material:
-		case T_FunctionScan:
 		case T_TableFuncScan:
 		case T_CteScan:
 		case T_NamedTuplestoreScan:
diff --git a/src/backend/executor/execSRF.c b/src/backend/executor/execSRF.c
index c8a3efc..1037909 100644
--- a/src/backend/executor/execSRF.c
+++ b/src/backend/executor/execSRF.c
@@ -21,6 +21,7 @@
 #include "access/htup_details.h"
 #include "catalog/objectaccess.h"
 #include "executor/execdebug.h"
+#include "executor/nodeFunctionscan.h"
 #include "funcapi.h"
 #include "miscadmin.h"
 #include "nodes/nodeFuncs.h"
@@ -45,6 +46,23 @@ static void ExecPrepareTuplestoreResult(SetExprState *sexpr,
 										Tuplestorestate *resultStore,
 										TupleDesc resultDesc);
 static void tupledesc_match(TupleDesc dst_tupdesc, TupleDesc src_tupdesc);
+static void ExecPrepareFuncResultslot(SetExprState *sexpr,
+										TupleDesc resultDesc);
+static void slot_puttuple_offset (TupleTableSlot *scanslot,
+								  TupleDesc expectedDesc,
+								  AttrNumber scanslot_off,
+								  TupleDesc resultdesc,
+								  Datum result);
+static void slot_copyslot_offset (TupleTableSlot *scanslot,
+								  TupleDesc expectedDesc,
+								  AttrNumber scanslot_off,
+								  TupleDesc resultdesc,
+								  TupleTableSlot *result);
+static void slot_putscalar_offset (TupleTableSlot *scanslot,
+								   TupleDesc expectedDesc,
+								   AttrNumber scanslot_off,
+								   Datum result,
+								   bool isNull);
 
 
 /*
@@ -89,39 +107,134 @@ ExecInitTableFunctionResult(Expr *expr,
 	return state;
 }
 
+static void
+ExecFetchFromTableFunctionTuplestore(SetExprState *setexpr,
+									 TupleDesc expectedDesc,
+									 TupleTableSlot *resultslot,
+									 AttrNumber scanslot_off,
+									 ExprDoneCond *isDone)
+{
+	MemoryContext oldContext;
+	bool		foundTup;
+	
+	/*
+	 * Have to make sure tuple in slot lives long enough, otherwise
+	 * clearing the slot could end up trying to free something already
+	 * freed.
+	 */
+	oldContext = MemoryContextSwitchTo(resultslot->tts_mcxt);
+	foundTup = tuplestore_gettupleslot(setexpr->funcResultStore, true, false,
+									   setexpr->funcResultSlot);
+	MemoryContextSwitchTo(oldContext);
+	
+	if (foundTup)
+	{
+		*isDone = ExprMultipleResult;
+		
+		if (setexpr->funcReturnsTuple)
+		{
+			/* We must expand the whole tuple. */
+			slot_getallattrs(setexpr->funcResultSlot);
+			
+			/*
+			 * Copy it to the result cols.
+			 */
+			slot_copyslot_offset (resultslot, expectedDesc, scanslot_off, setexpr->funcResultSlot->tts_tupleDescriptor, setexpr->funcResultSlot);
+		}
+		else
+		{
+			bool		isNull = false;
+			
+			/* Extract the first column and return it as a scalar. */
+			Datum result = slot_getattr(setexpr->funcResultSlot, 1, &isNull);
+			
+			slot_putscalar_offset (resultslot, expectedDesc, scanslot_off, result, isNull);
+		}
+	}
+	else
+	{
+		/* Exhausted the tuplestore, so clean up */
+		tuplestore_end(setexpr->funcResultStore);
+		setexpr->funcResultStore = NULL;
+		
+		/* We must store a row of NULLs in case we are used in ROWS FROM */
+		slot_puttuple_offset (resultslot, setexpr->funcResultDesc, scanslot_off, NULL, 0);
+
+		*isDone = ExprEndResult;
+	}
+}
+
 /*
  *		ExecMakeTableFunctionResult
  *
- * Evaluate a table function, producing a materialized result in a Tuplestore
- * object.
+ * Evaluate a table function, storing a single row in scanslot starting at
+ * attribute scanslot_off.
  *
  * This is used by nodeFunctionscan.c.
  */
-Tuplestorestate *
-ExecMakeTableFunctionResult(SetExprState *setexpr,
+void
+ExecMakeTableFunctionResult(FunctionScanPerFuncState *fs,
 							ExprContext *econtext,
 							MemoryContext argContext,
-							TupleDesc expectedDesc,
-							bool randomAccess)
+							TupleTableSlot *resultslot,
+							AttrNumber scanslot_off,
+							ExprDoneCond *isDone)
 {
-	Tuplestorestate *tupstore = NULL;
-	TupleDesc	tupdesc = NULL;
-	Oid			funcrettype;
-	bool		returnsTuple;
-	bool		returnsSet = false;
-	FunctionCallInfo fcinfo;
+	SetExprState *setexpr = fs->setexpr;
+	bool		call_fn = true; /* whether to actually call the SRF */
+	bool		already_stored = false; /* has the result been stored? */
+	FunctionCallInfo fcinfo = setexpr->fcinfo;
 	PgStat_FunctionCallUsage fcusage;
-	ReturnSetInfo rsinfo;
-	HeapTupleData tmptup;
-	MemoryContext callerContext;
+	ReturnSetInfo *rsinfo;
 	MemoryContext oldcontext;
-	bool		first_time = true;
+	Datum		result = 0;
+
+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 (setexpr->funcResultStore)
+	{
+		rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; /* always set if funcResultStore is set */
+
+		ExecFetchFromTableFunctionTuplestore(setexpr, setexpr->funcResultDesc, resultslot, scanslot_off, &rsinfo->isDone);
+
+		/*
+		 * We are done here: fall through below with isDone as either
+		 * ExprMultipleResult or ExprEndResult.
+		 */
+
+		already_stored = true;
+		call_fn = false;
+	}
+	/*
+	 * The elidedFuncState case isn't related to the SFRM_Materialize/
+	 * FetchFromTuplestore decision, except that it cannot occur in that
+	 * case, so we code it as if/elseif, rather than if/if.
+	 */
+	else if (setexpr->elidedFuncState)
+	{
 
-	callerContext = CurrentMemoryContext;
+		/* For SRFs, fcinfo would have been allocated by init_sexpr(). */
+		if (fcinfo == NULL)
+		{
+			oldcontext = MemoryContextSwitchTo(econtext->ecxt_per_query_memory);
+			
+			/* By performing InitFunctionCallInfoData here, we avoid palloc0() */
+			setexpr->fcinfo = fcinfo = palloc(SizeForFunctionCallInfo(list_length(setexpr->args)));
 
-	funcrettype = exprType((Node *) setexpr->expr);
+			MemoryContextSwitchTo(oldcontext);
 
-	returnsTuple = type_is_rowtype(funcrettype);
+			/* Treat setexpr as a generic expression */
+			InitFunctionCallInfoData(*fcinfo, NULL, 0, InvalidOid, NULL, NULL);
+		}
+	}
 
 	/*
 	 * Prepare a resultinfo node for communication.  We always do this even if
@@ -130,18 +243,50 @@ ExecMakeTableFunctionResult(SetExprState *setexpr,
 	 * resultinfo, but set it up anyway because we use some of the fields as
 	 * our own state variables.
 	 */
-	rsinfo.type = T_ReturnSetInfo;
-	rsinfo.econtext = econtext;
-	rsinfo.expectedDesc = expectedDesc;
-	rsinfo.allowedModes = (int) (SFRM_ValuePerCall | SFRM_Materialize | SFRM_Materialize_Preferred);
-	if (randomAccess)
-		rsinfo.allowedModes |= (int) SFRM_Materialize_Random;
-	rsinfo.returnMode = SFRM_ValuePerCall;
-	/* isDone is filled below */
-	rsinfo.setResult = NULL;
-	rsinfo.setDesc = NULL;
+	rsinfo = (ReturnSetInfo *) setexpr->fcinfo->resultinfo;
+	
+	if (rsinfo == NULL)
+	{
+		oldcontext = MemoryContextSwitchTo(argContext);
+
+		rsinfo = makeNode (ReturnSetInfo);
+		rsinfo->econtext = econtext;
+		rsinfo->expectedDesc = setexpr->funcResultDesc;
+		rsinfo->allowedModes = (int) (SFRM_ValuePerCall | SFRM_Materialize);
+		rsinfo->returnMode = SFRM_ValuePerCall;
+		fcinfo->resultinfo = (Node *) rsinfo;
 
-	fcinfo = palloc(SizeForFunctionCallInfo(list_length(setexpr->args)));
+		MemoryContextSwitchTo(oldcontext);
+	}
+	else
+	{
+		/*
+		 * If rsinfo was already present, it means we're being asked
+		 * to continue projecting. In turn, if last time we projected
+		 * a SingleResult, then all future calls should be handled as
+		 * if it was the last row from an SRF.
+		 *
+		 * Note: this is different from the ProjectSet case, which
+		 * instead re-invokes the non-SRF function for each row.
+		 */
+		if (rsinfo->isDone == ExprSingleResult)
+			rsinfo->isDone = ExprEndResult;
+	}
+
+	/*
+	 * If we're asked to continuing to project output rows despite the SRF
+	 * being exhausted (indicated by isDone being alreday set to ExprEndResult),
+	 * return NULLs forever.
+	 */
+	if (rsinfo->isDone == ExprEndResult && !already_stored)
+	{
+		call_fn = false; /* don't invoke the function again */
+		rsinfo->returnMode = SFRM_ValuePerCall; /* returning a row at a time */
+		result = 0; /* result and NULL is set later */
+		fcinfo->isnull = true;
+
+		/* the actual result is written later */
+	}
 
 	/*
 	 * Normally the passed expression tree will be a SetExprState, since the
@@ -153,37 +298,46 @@ ExecMakeTableFunctionResult(SetExprState *setexpr,
 	 * don't get a chance to pass a special ReturnSetInfo to any functions
 	 * buried in the expression.
 	 */
-	if (!setexpr->elidedFuncState)
+	if (call_fn && !setexpr->elidedFuncState)
 	{
 		/*
 		 * This path is similar to ExecMakeFunctionResultSet.
 		 */
-		returnsSet = setexpr->funcReturnsSet;
 		InitFunctionCallInfoData(*fcinfo, &(setexpr->func),
 								 list_length(setexpr->args),
 								 setexpr->fcinfo->fncollation,
-								 NULL, (Node *) &rsinfo);
+								 NULL, (Node *) rsinfo);
 
 		/*
 		 * Evaluate the function's argument list.
 		 *
-		 * We can't do this in the per-tuple context: the argument values
-		 * would disappear when we reset that context in the inner loop.  And
-		 * the caller's CurrentMemoryContext is typically a query-lifespan
-		 * context, so we don't want to leak memory there.  We require the
-		 * caller to pass a separate memory context that can be used for this,
-		 * and can be reset each time through to avoid bloat.
+		 * 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.
+		 *
+		 * The arguments have to live in a context that lives at least until all
+		 * rows from this SRF have been returned, otherwise ValuePerCall SRFs
+		 * would reference freed memory after the first returned row.
 		 */
-		MemoryContextReset(argContext);
-		oldcontext = MemoryContextSwitchTo(argContext);
-		ExecEvalFuncArgs(fcinfo, setexpr->args, econtext);
-		MemoryContextSwitchTo(oldcontext);
+		if (!setexpr->setArgsValid)
+		{
+			oldcontext = MemoryContextSwitchTo(argContext);
+			ExecEvalFuncArgs(fcinfo, setexpr->args, econtext);
+			MemoryContextSwitchTo(oldcontext);
+		}
+		else
+		{
+			/* Reset flag (we may set it again below) */
+			setexpr->setArgsValid = false;
+		}
 
 		/*
 		 * If function is strict, and there are any NULL arguments, skip
 		 * calling the function and act like it returned NULL (or an empty
 		 * set, in the returns-set case).
 		 */
+		call_fn = true;
 		if (setexpr->func.fn_strict)
 		{
 			int			i;
@@ -191,100 +345,90 @@ ExecMakeTableFunctionResult(SetExprState *setexpr,
 			for (i = 0; i < fcinfo->nargs; i++)
 			{
 				if (fcinfo->args[i].isnull)
-					goto no_function_result;
+				{
+					call_fn = false;
+
+					result = 0;
+					fcinfo->isnull = true;
+					rsinfo->isDone = ExprEndResult;
+
+					break;
+				}
 			}
 		}
 	}
-	else
-	{
-		/* Treat setexpr as a generic expression */
-		InitFunctionCallInfoData(*fcinfo, NULL, 0, InvalidOid, NULL, NULL);
-	}
-
-	/*
-	 * Switch to short-lived context for calling the function or expression.
-	 */
-	MemoryContextSwitchTo(econtext->ecxt_per_tuple_memory);
 
-	/*
-	 * Loop to handle the ValuePerCall protocol (which is also the same
-	 * behavior needed in the generic ExecEvalExpr path).
-	 */
-	for (;;)
+	/* Call the function or expression one time */
+	if (call_fn)
 	{
-		Datum		result;
-
-		CHECK_FOR_INTERRUPTS();
-
-		/*
-		 * reset per-tuple memory context before each call of the function or
-		 * expression. This cleans up any local memory the function may leak
-		 * when called.
-		 */
-		ResetExprContext(econtext);
-
-		/* Call the function or expression one time */
 		if (!setexpr->elidedFuncState)
 		{
 			pgstat_init_function_usage(fcinfo, &fcusage);
 
 			fcinfo->isnull = false;
-			rsinfo.isDone = ExprSingleResult;
+			rsinfo->isDone = ExprSingleResult;
 			result = FunctionCallInvoke(fcinfo);
 
 			pgstat_end_function_usage(&fcusage,
-									  rsinfo.isDone != ExprMultipleResult);
+									  rsinfo->isDone != ExprMultipleResult);
 		}
 		else
 		{
 			result =
 				ExecEvalExpr(setexpr->elidedFuncState, econtext, &fcinfo->isnull);
-			rsinfo.isDone = ExprSingleResult;
+			rsinfo->isDone = ExprSingleResult;
 		}
+	}
+	
+	/* Which protocol does function want to use? */
+	if (rsinfo->returnMode == SFRM_ValuePerCall)
+	{
+		HeapTupleHeader td = NULL;
 
-		/* Which protocol does function want to use? */
-		if (rsinfo.returnMode == SFRM_ValuePerCall)
+		if (rsinfo->isDone != ExprEndResult)
 		{
 			/*
-			 * Check for end of result set.
+			 * Save the current argument values to re-use on the next call.
 			 */
-			if (rsinfo.isDone == ExprEndResult)
-				break;
+			if (rsinfo->isDone == ExprMultipleResult)
+			{
+				setexpr->setArgsValid = true;
+				/* Register cleanup callback if we didn't already */
+				if (!setexpr->shutdown_reg)
+				{
+					RegisterExprContextCallback(econtext,
+												ShutdownSetExpr,
+												PointerGetDatum(setexpr));
+					setexpr->shutdown_reg = true;
+				}
+			}
 
 			/*
-			 * If first time through, build tuplestore for result.  For a
-			 * scalar function result type, also make a suitable tupdesc.
+			 * Obtain a suitable tupdesc, when we first encounter a non-NULL result.
 			 */
-			if (first_time)
+			if (rsinfo->setDesc == NULL)
 			{
-				oldcontext = MemoryContextSwitchTo(econtext->ecxt_per_query_memory);
-				tupstore = tuplestore_begin_heap(randomAccess, false, work_mem);
-				rsinfo.setResult = tupstore;
-				if (!returnsTuple)
+				if (!setexpr->funcReturnsTuple)
 				{
-					tupdesc = CreateTemplateTupleDesc(1);
-					TupleDescInitEntry(tupdesc,
+					/*
+					 * Make a copy for the query.
+					 */
+					oldcontext = MemoryContextSwitchTo(econtext->ecxt_per_query_memory);
+					rsinfo->setDesc = CreateTemplateTupleDesc(1);
+					TupleDescInitEntry(rsinfo->setDesc,
 									   (AttrNumber) 1,
 									   "column",
-									   funcrettype,
+									   exprType((Node *) setexpr->expr),
 									   -1,
 									   0);
-					rsinfo.setDesc = tupdesc;
+					MemoryContextSwitchTo(oldcontext);
 				}
-				MemoryContextSwitchTo(oldcontext);
-			}
-
-			/*
-			 * Store current resultset item.
-			 */
-			if (returnsTuple)
-			{
-				if (!fcinfo->isnull)
+				else if (!fcinfo->isnull)
 				{
-					HeapTupleHeader td = DatumGetHeapTupleHeader(result);
-
-					if (tupdesc == NULL)
+					if (result != 0)
 					{
+						td = DatumGetHeapTupleHeader(result);
+
 						/*
 						 * This is the first non-NULL result from the
 						 * function.  Use the type info embedded in the
@@ -292,32 +436,45 @@ ExecMakeTableFunctionResult(SetExprState *setexpr,
 						 * a copy for the query.
 						 */
 						oldcontext = MemoryContextSwitchTo(econtext->ecxt_per_query_memory);
-						tupdesc = lookup_rowtype_tupdesc_copy(HeapTupleHeaderGetTypeId(td),
+						rsinfo->setDesc = lookup_rowtype_tupdesc_copy(HeapTupleHeaderGetTypeId(td),
 															  HeapTupleHeaderGetTypMod(td));
-						rsinfo.setDesc = tupdesc;
 						MemoryContextSwitchTo(oldcontext);
 					}
-					else
-					{
-						/*
-						 * Verify all later returned rows have same subtype;
-						 * necessary in case the type is RECORD.
-						 */
-						if (HeapTupleHeaderGetTypeId(td) != tupdesc->tdtypeid ||
-							HeapTupleHeaderGetTypMod(td) != tupdesc->tdtypmod)
-							ereport(ERROR,
-									(errcode(ERRCODE_DATATYPE_MISMATCH),
-									 errmsg("rows returned by function are not all of the same row type")));
-					}
+				}
+			}
+
+			/* If we obtained a tupdesc, check it is appropriate */
+			if (rsinfo->setDesc && setexpr->funcResultDesc &&
+				!fs->tupdesc_checked)
+			{
+				tupledesc_match (setexpr->funcResultDesc, rsinfo->setDesc);
+				fs->tupdesc_checked = true;
+			}
+		}
 
+		if (!already_stored)
+		{
+			/*
+			 * Store current resultset item.
+			 */
+			if (setexpr->funcReturnsTuple)
+			{
+				if (!fcinfo->isnull)
+				{
+					if (td == NULL)
+						td = DatumGetHeapTupleHeader(result);
+					
 					/*
-					 * tuplestore_puttuple needs a HeapTuple not a bare
-					 * HeapTupleHeader, but it doesn't need all the fields.
+					 * Verify all later returned rows have same subtype;
+					 * necessary in case the type is RECORD.
 					 */
-					tmptup.t_len = HeapTupleHeaderGetDatumLength(td);
-					tmptup.t_data = td;
+					if (HeapTupleHeaderGetTypeId(td) != rsinfo->setDesc->tdtypeid ||
+						HeapTupleHeaderGetTypMod(td) != rsinfo->setDesc->tdtypmod)
+						ereport(ERROR,
+								(errcode(ERRCODE_DATATYPE_MISMATCH),
+								 errmsg("rows returned by function are not all of the same row type")));
 
-					tuplestore_puttuple(tupstore, &tmptup);
+					slot_puttuple_offset (resultslot, setexpr->funcResultDesc, scanslot_off, rsinfo->setDesc, result);
 				}
 				else
 				{
@@ -329,91 +486,57 @@ ExecMakeTableFunctionResult(SetExprState *setexpr,
 					 * the provided descriptor, since that might not match
 					 * what we get from the function itself.  But it doesn't.)
 					 */
-					int			natts = expectedDesc->natts;
-					bool	   *nullflags;
-
-					nullflags = (bool *) palloc(natts * sizeof(bool));
-					memset(nullflags, true, natts * sizeof(bool));
-					tuplestore_putvalues(tupstore, expectedDesc, NULL, nullflags);
+					slot_puttuple_offset (resultslot, setexpr->funcResultDesc, scanslot_off, rsinfo->setDesc, 0);
 				}
 			}
 			else
 			{
 				/* Scalar-type case: just store the function result */
-				tuplestore_putvalues(tupstore, tupdesc, &result, &fcinfo->isnull);
+				slot_putscalar_offset (resultslot, setexpr->funcResultDesc, scanslot_off, result, fcinfo->isnull);
 			}
-
-			/*
-			 * 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)
-				ereport(ERROR,
-						(errcode(ERRCODE_E_R_I_E_SRF_PROTOCOL_VIOLATED),
-						 errmsg("table-function protocol for materialize mode was not followed")));
-			/* Done evaluating the set result */
-			break;
-		}
-		else
-			ereport(ERROR,
-					(errcode(ERRCODE_E_R_I_E_SRF_PROTOCOL_VIOLATED),
-					 errmsg("unrecognized table-function returnMode: %d",
-							(int) rsinfo.returnMode)));
-
-		first_time = false;
 	}
-
-no_function_result:
-
-	/*
-	 * If we got nothing from the function (ie, an empty-set or NULL result),
-	 * we have to create the tuplestore to return, and if it's a
-	 * non-set-returning function then insert a single all-nulls row.  As
-	 * above, we depend on the expectedDesc to manufacture the dummy row.
-	 */
-	if (rsinfo.setResult == NULL)
+	else if (rsinfo->returnMode == SFRM_Materialize)
 	{
-		MemoryContextSwitchTo(econtext->ecxt_per_query_memory);
-		tupstore = tuplestore_begin_heap(randomAccess, false, work_mem);
-		rsinfo.setResult = tupstore;
-		if (!returnsSet)
+		/* 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")));
+		/* prepare to return values from the tuplestore */
+		ExecPrepareFuncResultslot(setexpr, rsinfo->setDesc);
+		
+		setexpr->funcResultStore = rsinfo->setResult;
+		
+		if (rsinfo->setDesc && setexpr->funcResultDesc)
 		{
-			int			natts = expectedDesc->natts;
-			bool	   *nullflags;
-
-			MemoryContextSwitchTo(econtext->ecxt_per_tuple_memory);
-			nullflags = (bool *) palloc(natts * sizeof(bool));
-			memset(nullflags, true, natts * sizeof(bool));
-			tuplestore_putvalues(tupstore, expectedDesc, NULL, nullflags);
+			tupledesc_match(setexpr->funcResultDesc, rsinfo->setDesc);
+			fs->tupdesc_checked = true;
 		}
-	}
 
-	/*
-	 * 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);
+		/* Register cleanup callback if we didn't already */
+		if (!setexpr->shutdown_reg)
+		{
+			RegisterExprContextCallback(econtext,
+										ShutdownSetExpr,
+										PointerGetDatum(setexpr));
+			setexpr->shutdown_reg = true;
+		}
 
-		/*
-		 * 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);
+		/* Now process from tuplestore, returning one value per call */
+		rsinfo->returnMode = SFRM_ValuePerCall;
+		
+		goto restart;
 	}
+	else
+		ereport(ERROR,
+				(errcode(ERRCODE_E_R_I_E_SRF_PROTOCOL_VIOLATED),
+				 errmsg("unrecognized table-function returnMode: %d",
+						(int) rsinfo->returnMode)));
 
-	MemoryContextSwitchTo(callerContext);
+	*isDone = rsinfo->isDone;
 
-	/* All done, pass back the tuplestore */
-	return rsinfo.setResult;
+	/* All done, result is in the tupleslot */
 }
 
 
@@ -650,6 +773,7 @@ restart:
 		if (rsinfo.setResult != NULL)
 		{
 			/* prepare to return values from the tuplestore */
+			ExecPrepareFuncResultslot(fcache, rsinfo.setDesc);
 			ExecPrepareTuplestoreResult(fcache, econtext,
 										rsinfo.setResult,
 										rsinfo.setDesc);
@@ -712,6 +836,7 @@ init_sexpr(Oid foid, Oid input_collation, Expr *node,
 	InitFunctionCallInfoData(*sexpr->fcinfo, &(sexpr->func),
 							 numargs,
 							 input_collation, NULL, NULL);
+	sexpr->fcinfo->resultinfo = NULL;
 
 	/* If function returns set, check if that's allowed by caller */
 	if (sexpr->func.fn_retset && !allowSRF)
@@ -835,21 +960,16 @@ ExecEvalFuncArgs(FunctionCallInfo fcinfo,
 }
 
 /*
- *		ExecPrepareTuplestoreResult
+ *		ExecPrepareFuncResultslot
  *
- * Subroutine for ExecMakeFunctionResultSet: 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.
+ * Subroutine for ExecMakeFunctionResultSet: in preparation to extract rows from a
+ * tuplestore function result, we must set up a funcResultSlot (unless
+ * already done in a previous call cycle).
  */
 static void
-ExecPrepareTuplestoreResult(SetExprState *sexpr,
-							ExprContext *econtext,
-							Tuplestorestate *resultStore,
+ExecPrepareFuncResultslot(SetExprState *sexpr,
 							TupleDesc resultDesc)
 {
-	sexpr->funcResultStore = resultStore;
-
 	if (sexpr->funcResultSlot == NULL)
 	{
 		/* Create a slot so we can read data out of the tuplestore */
@@ -882,6 +1002,23 @@ ExecPrepareTuplestoreResult(SetExprState *sexpr,
 														 &TTSOpsMinimalTuple);
 		MemoryContextSwitchTo(oldcontext);
 	}
+}
+
+/*
+ *		ExecPrepareTuplestoreResult
+ *
+ * Subroutine for ExecMakeFunctionResultSet: in preparation to extract rows from a
+ * tuplestore function result, we must verify that the function
+ * returned the expected tuple descriptor, and ensure we are called back to clean up
+ * at the end of the scan.
+ */
+static void
+ExecPrepareTuplestoreResult(SetExprState *sexpr,
+							ExprContext *econtext,
+							Tuplestorestate *resultStore,
+							TupleDesc resultDesc)
+{
+	sexpr->funcResultStore = resultStore;
 
 	/*
 	 * If function provided a tupdesc, cross-check it.  We only really need to
@@ -960,3 +1097,68 @@ tupledesc_match(TupleDesc dst_tupdesc, TupleDesc src_tupdesc)
 							   i + 1)));
 	}
 }
+
+static void
+slot_puttuple_offset (TupleTableSlot *scanslot, TupleDesc expectedDesc, AttrNumber scanslot_off,
+					  TupleDesc resultdesc, Datum result)
+{
+	if (result != 0)
+	{
+		HeapTupleHeader td = DatumGetHeapTupleHeader(result);
+
+		/*
+		 * tuplestore_puttuple needs a HeapTuple not a bare
+		 * HeapTupleHeader, but it doesn't need all the fields.
+		 */
+		HeapTupleData tmptup;
+		tmptup.t_len = HeapTupleHeaderGetDatumLength(td);
+		tmptup.t_data = td;
+
+		/* FIXME: seems we may be able to optimise the case where there is just one Function being scanned. Presently, this path causes the tuple to be read from disk, and it happens because we place results into a VirtualTupleSlot. In turn, this is needed because of the multiple-function ROWS FROM (...) case. In a single function case, we could perhaps simply pass on the returned TupleSlot, regardless of whether it has been read into memory. */
+		heap_deform_tuple (&tmptup, expectedDesc, &(scanslot->tts_values[scanslot_off]), &(scanslot->tts_isnull[scanslot_off]));
+	}
+	else
+	{
+		/* Ensure any remaining result cols are initialsed to NULL. */
+		for (int i = 0; i < expectedDesc->natts; i++)
+		{
+			scanslot->tts_values[scanslot_off + i] = (Datum) 0;
+			scanslot->tts_isnull[scanslot_off + i] = true;
+		}
+	}
+}
+
+static void
+slot_copyslots_offset (TupleTableSlot *scanslot, TupleDesc expectedDesc, AttrNumber scanslot_off,
+					   int natts, Datum *datums, bool *isnulls)
+{
+	int i;
+	for (i = 0; i < natts; i++)
+	{
+		if (i >= expectedDesc->natts)
+			break;
+		
+		scanslot->tts_values[scanslot_off + i] = datums[i];
+		scanslot->tts_isnull[scanslot_off + i] = isnulls[i];
+	}
+	
+	/* Ensure any remaining result cols are initialsed to NULL. */
+	for (; i < expectedDesc->natts; i++)
+	{
+		scanslot->tts_values[scanslot_off + i] = (Datum) 0;
+		scanslot->tts_isnull[scanslot_off + i] = true;
+	}
+}
+
+static void
+slot_copyslot_offset (TupleTableSlot *scanslot, TupleDesc expectedDesc, AttrNumber scanslot_off,
+					  TupleDesc resultdesc, TupleTableSlot *result)
+{
+	slot_copyslots_offset (scanslot, expectedDesc, scanslot_off, resultdesc->natts, &(result->tts_values[0]), &(result->tts_isnull[0]));
+}
+
+static void
+slot_putscalar_offset (TupleTableSlot *scanslot, TupleDesc expectedDesc, AttrNumber scanslot_off, Datum result, bool isNull)
+{
+	slot_copyslots_offset (scanslot, expectedDesc, scanslot_off, 1, &result, &isNull);
+}
diff --git a/src/backend/executor/nodeFunctionscan.c b/src/backend/executor/nodeFunctionscan.c
index 0370f2e..d5472ac 100644
--- a/src/backend/executor/nodeFunctionscan.c
+++ b/src/backend/executor/nodeFunctionscan.c
@@ -30,19 +30,6 @@
 #include "utils/memutils.h"
 
 
-/*
- * Runtime data for each function being scanned.
- */
-typedef struct FunctionScanPerFuncState
-{
-	SetExprState *setexpr;		/* state of the expression being evaluated */
-	TupleDesc	tupdesc;		/* desc of the function result type */
-	int			colcount;		/* expected number of result columns */
-	Tuplestorestate *tstore;	/* holds the function result set */
-	int64		rowcount;		/* # of rows in result set, -1 if not known */
-	TupleTableSlot *func_slot;	/* function result slot (or NULL) */
-} FunctionScanPerFuncState;
-
 static TupleTableSlot *FunctionNext(FunctionScanState *node);
 
 
@@ -62,8 +49,9 @@ FunctionNext(FunctionScanState *node)
 	EState	   *estate;
 	ScanDirection direction;
 	TupleTableSlot *scanslot;
+	MemoryContext oldcontext;
+	ExprDoneCond		doneCond;
 	bool		alldone;
-	int64		oldpos;
 	int			funcno;
 	int			att;
 
@@ -74,59 +62,34 @@ FunctionNext(FunctionScanState *node)
 	direction = estate->es_direction;
 	scanslot = node->ss.ss_ScanTupleSlot;
 
-	if (node->simple)
-	{
-		/*
-		 * Fast path for the trivial case: the function return type and scan
-		 * result type are the same, so we fetch the function result straight
-		 * into the scan result slot. No need to update ordinality or
-		 * rowcounts either.
-		 */
-		Tuplestorestate *tstore = node->funcstates[0].tstore;
-
-		/*
-		 * If first time through, read all tuples from function and put them
-		 * in a tuplestore. Subsequent calls just fetch tuples from
-		 * tuplestore.
-		 */
-		if (tstore == NULL)
-		{
-			node->funcstates[0].tstore = tstore =
-				ExecMakeTableFunctionResult(node->funcstates[0].setexpr,
-											node->ss.ps.ps_ExprContext,
-											node->argcontext,
-											node->funcstates[0].tupdesc,
-											node->eflags & EXEC_FLAG_BACKWARD);
-
-			/*
-			 * paranoia - cope if the function, which may have constructed the
-			 * tuplestore itself, didn't leave it pointing at the start. This
-			 * call is fast, so the overhead shouldn't be an issue.
-			 */
-			tuplestore_rescan(tstore);
-		}
+	ExecClearTuple(scanslot);
 
-		/*
-		 * Get the next tuple from tuplestore.
-		 */
-		(void) tuplestore_gettupleslot(tstore,
-									   ScanDirectionIsForward(direction),
-									   false,
-									   scanslot);
-		return scanslot;
+	/* Call SRFs, as well as plain expressions, in per-tuple context */
+	oldcontext = MemoryContextSwitchTo(node->ss.ps.ps_ExprContext->ecxt_per_tuple_memory);
+	
+	/*
+	 * Check to see if we're still projecting out tuples from a previous scan
+	 * tuple (because there is a function-returning-set in the projection
+	 * expressions). If not, indicate we are finished now.
+	 */
+	if (!node->pending_srf_tuples)
+	{
+		alldone = true;
+		goto return_resultslot;
 	}
 
 	/*
-	 * Increment or decrement ordinal counter before checking for end-of-data,
-	 * so that we can move off either end of the result by 1 (and no more than
-	 * 1) without losing correct count.  See PortalRunSelect for why we can
+	 * Assume no further tuples are produced unless an ExprMultipleResult is
+	 * encountered from a set returning function.
+	 */
+	node->pending_srf_tuples = false;
+
+	/*
+	 * Increment ordinal counter before checking for end-of-data.
+	 * See PortalRunSelect for why we can
 	 * assume that we won't be called repeatedly in the end-of-data state.
 	 */
-	oldpos = node->ordinal;
-	if (ScanDirectionIsForward(direction))
-		node->ordinal++;
-	else
-		node->ordinal--;
+	node->ordinal++;
 
 	/*
 	 * Main loop over functions.
@@ -141,87 +104,29 @@ FunctionNext(FunctionScanState *node)
 	for (funcno = 0; funcno < node->nfuncs; funcno++)
 	{
 		FunctionScanPerFuncState *fs = &node->funcstates[funcno];
-		int			i;
 
 		/*
-		 * If first time through, read all tuples from function and put them
-		 * in a tuplestore. Subsequent calls just fetch tuples from
-		 * tuplestore.
+		 * Read a tuples from function and put it in the scanslot.
 		 */
-		if (fs->tstore == NULL)
+		ExecMakeTableFunctionResult(fs,
+									node->ss.ps.ps_ExprContext,
+									node->argcontext,
+									scanslot,
+									att,
+									&doneCond);
+
+		if (doneCond != ExprEndResult)
 		{
-			fs->tstore =
-				ExecMakeTableFunctionResult(fs->setexpr,
-											node->ss.ps.ps_ExprContext,
-											node->argcontext,
-											fs->tupdesc,
-											node->eflags & EXEC_FLAG_BACKWARD);
-
-			/*
-			 * paranoia - cope if the function, which may have constructed the
-			 * tuplestore itself, didn't leave it pointing at the start. This
-			 * call is fast, so the overhead shouldn't be an issue.
-			 */
-			tuplestore_rescan(fs->tstore);
-		}
-
-		/*
-		 * Get the next tuple from tuplestore.
-		 *
-		 * If we have a rowcount for the function, and we know the previous
-		 * read position was out of bounds, don't try the read. This allows
-		 * backward scan to work when there are mixed row counts present.
-		 */
-		if (fs->rowcount != -1 && fs->rowcount < oldpos)
-			ExecClearTuple(fs->func_slot);
-		else
-			(void) tuplestore_gettupleslot(fs->tstore,
-										   ScanDirectionIsForward(direction),
-										   false,
-										   fs->func_slot);
-
-		if (TupIsNull(fs->func_slot))
-		{
-			/*
-			 * If we ran out of data for this function in the forward
-			 * direction then we now know how many rows it returned. We need
-			 * to know this in order to handle backwards scans. The row count
-			 * we store is actually 1+ the actual number, because we have to
-			 * position the tuplestore 1 off its end sometimes.
-			 */
-			if (ScanDirectionIsForward(direction) && fs->rowcount == -1)
-				fs->rowcount = node->ordinal;
-
-			/*
-			 * populate the result cols with nulls
-			 */
-			for (i = 0; i < fs->colcount; i++)
-			{
-				scanslot->tts_values[att] = (Datum) 0;
-				scanslot->tts_isnull[att] = true;
-				att++;
-			}
-		}
-		else
-		{
-			/*
-			 * we have a result, so just copy it to the result cols.
-			 */
-			slot_getallattrs(fs->func_slot);
-
-			for (i = 0; i < fs->colcount; i++)
-			{
-				scanslot->tts_values[att] = fs->func_slot->tts_values[i];
-				scanslot->tts_isnull[att] = fs->func_slot->tts_isnull[i];
-				att++;
-			}
-
 			/*
 			 * We're not done until every function result is exhausted; we pad
 			 * the shorter results with nulls until then.
 			 */
 			alldone = false;
 		}
+		if (doneCond == ExprMultipleResult)
+			node->pending_srf_tuples = true;
+
+		att += fs->colcount;
 	}
 
 	/*
@@ -233,6 +138,9 @@ FunctionNext(FunctionScanState *node)
 		scanslot->tts_isnull[att] = false;
 	}
 
+return_resultslot:
+	MemoryContextSwitchTo(oldcontext);
+	
 	/*
 	 * If alldone, we just return the previously-cleared scanslot.  Otherwise,
 	 * finish creating the virtual tuple.
@@ -353,14 +261,6 @@ ExecInitFunctionScan(FunctionScan *node, EState *estate, int eflags)
 										scanstate->ss.ps.ps_ExprContext,
 										&scanstate->ss.ps);
 
-		/*
-		 * Don't allocate the tuplestores; the actual calls to the functions
-		 * do that.  NULL means that we have not called the function yet (or
-		 * need to call it again after a rescan).
-		 */
-		fs->tstore = NULL;
-		fs->rowcount = -1;
-
 		/*
 		 * Now determine if the function returns a simple or composite type,
 		 * and build an appropriate tupdesc.  Note that in the composite case,
@@ -371,6 +271,12 @@ ExecInitFunctionScan(FunctionScan *node, EState *estate, int eflags)
 											&funcrettype,
 											&tupdesc);
 
+		/*
+		 * FIXME: we set funcReturnsTuple, but it is a slightly different
+		 * check to what type_is_rowtype() executes. Don't know if it is
+		 * a problem.
+		 */
+		
 		if (functypclass == TYPEFUNC_COMPOSITE ||
 			functypclass == TYPEFUNC_COMPOSITE_DOMAIN)
 		{
@@ -379,6 +285,7 @@ ExecInitFunctionScan(FunctionScan *node, EState *estate, int eflags)
 			Assert(tupdesc->natts >= colcount);
 			/* Must copy it out of typcache for safety */
 			tupdesc = CreateTupleDescCopy(tupdesc);
+			fs->setexpr->funcReturnsTuple = true;
 		}
 		else if (functypclass == TYPEFUNC_SCALAR)
 		{
@@ -393,6 +300,7 @@ ExecInitFunctionScan(FunctionScan *node, EState *estate, int eflags)
 			TupleDescInitEntryCollation(tupdesc,
 										(AttrNumber) 1,
 										exprCollation(funcexpr));
+			fs->setexpr->funcReturnsTuple = false;
 		}
 		else if (functypclass == TYPEFUNC_RECORD)
 		{
@@ -407,6 +315,7 @@ ExecInitFunctionScan(FunctionScan *node, EState *estate, int eflags)
 			 * case it doesn't.)
 			 */
 			BlessTupleDesc(tupdesc);
+			fs->setexpr->funcReturnsTuple = true;
 		}
 		else
 		{
@@ -414,21 +323,10 @@ ExecInitFunctionScan(FunctionScan *node, EState *estate, int eflags)
 			elog(ERROR, "function in FROM has unsupported return type");
 		}
 
-		fs->tupdesc = tupdesc;
+		fs->setexpr->funcResultDesc = tupdesc;
 		fs->colcount = colcount;
 
-		/*
-		 * We only need separate slots for the function results if we are
-		 * doing ordinality or multiple functions; otherwise, we'll fetch
-		 * function results directly into the scan slot.
-		 */
-		if (!scanstate->simple)
-		{
-			fs->func_slot = ExecInitExtraTupleSlot(estate, fs->tupdesc,
-												   &TTSOpsMinimalTuple);
-		}
-		else
-			fs->func_slot = NULL;
+		fs->tupdesc_checked = false;
 
 		natts += colcount;
 		i++;
@@ -443,7 +341,7 @@ ExecInitFunctionScan(FunctionScan *node, EState *estate, int eflags)
 	 */
 	if (scanstate->simple)
 	{
-		scan_tupdesc = CreateTupleDescCopy(scanstate->funcstates[0].tupdesc);
+		scan_tupdesc = CreateTupleDescCopy(scanstate->funcstates[0].setexpr->funcResultDesc);
 		scan_tupdesc->tdtypeid = RECORDOID;
 		scan_tupdesc->tdtypmod = -1;
 	}
@@ -458,7 +356,7 @@ ExecInitFunctionScan(FunctionScan *node, EState *estate, int eflags)
 
 		for (i = 0; i < nfuncs; i++)
 		{
-			TupleDesc	tupdesc = scanstate->funcstates[i].tupdesc;
+			TupleDesc	tupdesc = scanstate->funcstates[i].setexpr->funcResultDesc;
 			int			colcount = scanstate->funcstates[i].colcount;
 			int			j;
 
@@ -497,6 +395,11 @@ ExecInitFunctionScan(FunctionScan *node, EState *estate, int eflags)
 	 */
 	scanstate->ss.ps.qual =
 		ExecInitQual(node->scan.plan.qual, (PlanState *) scanstate);
+	
+	/*
+	 * Start out assuming there will be tuples returned.
+	 */
+	scanstate->pending_srf_tuples = true;
 
 	/*
 	 * Create a memory context that ExecMakeTableFunctionResult can use to
@@ -521,8 +424,6 @@ ExecInitFunctionScan(FunctionScan *node, EState *estate, int eflags)
 void
 ExecEndFunctionScan(FunctionScanState *node)
 {
-	int			i;
-
 	/*
 	 * Free the exprcontext
 	 */
@@ -534,23 +435,6 @@ ExecEndFunctionScan(FunctionScanState *node)
 	if (node->ss.ps.ps_ResultTupleSlot)
 		ExecClearTuple(node->ss.ps.ps_ResultTupleSlot);
 	ExecClearTuple(node->ss.ss_ScanTupleSlot);
-
-	/*
-	 * Release slots and tuplestore resources
-	 */
-	for (i = 0; i < node->nfuncs; i++)
-	{
-		FunctionScanPerFuncState *fs = &node->funcstates[i];
-
-		if (fs->func_slot)
-			ExecClearTuple(fs->func_slot);
-
-		if (fs->tstore != NULL)
-		{
-			tuplestore_end(node->funcstates[i].tstore);
-			fs->tstore = NULL;
-		}
-	}
 }
 
 /* ----------------------------------------------------------------
@@ -568,13 +452,6 @@ ExecReScanFunctionScan(FunctionScanState *node)
 
 	if (node->ss.ps.ps_ResultTupleSlot)
 		ExecClearTuple(node->ss.ps.ps_ResultTupleSlot);
-	for (i = 0; i < node->nfuncs; i++)
-	{
-		FunctionScanPerFuncState *fs = &node->funcstates[i];
-
-		if (fs->func_slot)
-			ExecClearTuple(fs->func_slot);
-	}
 
 	ExecScanReScan(&node->ss);
 
@@ -597,12 +474,11 @@ ExecReScanFunctionScan(FunctionScanState *node)
 
 			if (bms_overlap(chgparam, rtfunc->funcparams))
 			{
-				if (node->funcstates[i].tstore != NULL)
+				if (node->funcstates[i].setexpr->funcResultStore != NULL)
 				{
-					tuplestore_end(node->funcstates[i].tstore);
-					node->funcstates[i].tstore = NULL;
+					tuplestore_end(node->funcstates[i].setexpr->funcResultStore);
+					node->funcstates[i].setexpr->funcResultStore = NULL;
 				}
-				node->funcstates[i].rowcount = -1;
 			}
 			i++;
 		}
@@ -614,7 +490,16 @@ ExecReScanFunctionScan(FunctionScanState *node)
 	/* Make sure we rewind any remaining tuplestores */
 	for (i = 0; i < node->nfuncs; i++)
 	{
-		if (node->funcstates[i].tstore != NULL)
-			tuplestore_rescan(node->funcstates[i].tstore);
+		if (node->funcstates[i].setexpr->funcResultStore != NULL)
+			tuplestore_rescan (node->funcstates[i].setexpr->funcResultStore);
+
+		/* No matter what, we renew the ResultSetInfo structure */
+		if (node->funcstates[i].setexpr->fcinfo != NULL)
+			node->funcstates[i].setexpr->fcinfo->resultinfo = NULL;
 	}
+	
+	/*
+	 * Start out assuming there will be tuples returned.
+	 */
+	node->pending_srf_tuples = true;
 }
diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h
index 9be0b38..6d79394 100644
--- a/src/include/executor/executor.h
+++ b/src/include/executor/executor.h
@@ -405,11 +405,12 @@ extern bool ExecCheck(ExprState *state, ExprContext *context);
  */
 extern SetExprState *ExecInitTableFunctionResult(Expr *expr,
 												 ExprContext *econtext, PlanState *parent);
-extern Tuplestorestate *ExecMakeTableFunctionResult(SetExprState *setexpr,
+extern void ExecMakeTableFunctionResult(struct FunctionScanPerFuncState *fs,
 													ExprContext *econtext,
 													MemoryContext argContext,
-													TupleDesc expectedDesc,
-													bool randomAccess);
+													TupleTableSlot *scanslot,
+													AttrNumber scanslot_off,
+													ExprDoneCond *isDone);
 extern SetExprState *ExecInitFunctionResultSet(Expr *expr,
 											   ExprContext *econtext, PlanState *parent);
 extern Datum ExecMakeFunctionResultSet(SetExprState *fcache,
diff --git a/src/include/executor/nodeFunctionscan.h b/src/include/executor/nodeFunctionscan.h
index 4f7d60d..8fd572f 100644
--- a/src/include/executor/nodeFunctionscan.h
+++ b/src/include/executor/nodeFunctionscan.h
@@ -16,6 +16,16 @@
 
 #include "nodes/execnodes.h"
 
+/*
+ * Runtime data for each function being scanned.
+ */
+typedef struct FunctionScanPerFuncState
+{
+	SetExprState *setexpr;		/* state of the expression being evaluated */
+	int			colcount;		/* expected number of result columns */
+	bool		tupdesc_checked; /* has the return tupdesc been checked? */
+} FunctionScanPerFuncState;
+
 extern FunctionScanState *ExecInitFunctionScan(FunctionScan *node, EState *estate, int eflags);
 extern void ExecEndFunctionScan(FunctionScanState *node);
 extern void ExecReScanFunctionScan(FunctionScanState *node);
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 9ac7bc1..0ff58fc 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -1652,6 +1652,7 @@ typedef struct SubqueryScanState
  *		funcstates			per-function execution states (private in
  *							nodeFunctionscan.c)
  *		argcontext			memory context to evaluate function arguments in
+ *		pending_srf_tuples	still evaluating any SRFs?
  * ----------------
  */
 struct FunctionScanPerFuncState;
@@ -1666,6 +1667,7 @@ typedef struct FunctionScanState
 	int			nfuncs;
 	struct FunctionScanPerFuncState *funcstates;	/* array of length nfuncs */
 	MemoryContext argcontext;
+	bool		pending_srf_tuples;
 } FunctionScanState;
 
 /* ----------------
