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..86ef5ce 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,9 @@ static void ExecPrepareTuplestoreResult(SetExprState *sexpr,
 										Tuplestorestate *resultStore,
 										TupleDesc resultDesc);
 static void tupledesc_match(TupleDesc dst_tupdesc, TupleDesc src_tupdesc);
+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,33 +93,102 @@ 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. */
+			/*
+			 * Copy it to the result cols.
+			 */
+			slot_getallattrs(setexpr->funcResultSlot);
+			
+			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;
+		*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;
+	SetExprState *setexpr = fs->setexpr;
 	Oid			funcrettype;
 	bool		returnsTuple;
 	bool		returnsSet = false;
 	FunctionCallInfo fcinfo;
 	PgStat_FunctionCallUsage fcusage;
-	ReturnSetInfo rsinfo;
-	HeapTupleData tmptup;
+	ReturnSetInfo *rsinfo;
 	MemoryContext callerContext;
 	MemoryContext oldcontext;
-	bool		first_time = true;
+
+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)
+	{
+		ExecFetchFromTableFunctionTuplestore(setexpr, fs->tupdesc, resultslot, scanslot_off, isDone);
+		
+		/* No matter what, we are done here. */
+		return;
+	}
 
 	callerContext = CurrentMemoryContext;
 
@@ -130,18 +203,21 @@ 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;
-
-	fcinfo = palloc(SizeForFunctionCallInfo(list_length(setexpr->args)));
+	rsinfo = (ReturnSetInfo *) setexpr->fcinfo->resultinfo;
+	
+	if (rsinfo == NULL)
+	{
+		oldcontext = MemoryContextSwitchTo(econtext->ecxt_per_query_memory);
+		rsinfo = makeNode (ReturnSetInfo);
+		rsinfo->econtext = econtext;
+		rsinfo->expectedDesc = fs->tupdesc;
+		rsinfo->allowedModes = (int) (SFRM_ValuePerCall | SFRM_Materialize);
+		rsinfo->returnMode = SFRM_ValuePerCall;
+		setexpr->fcinfo->resultinfo = (Node *) rsinfo;
+		MemoryContextSwitchTo(oldcontext);
+	}
+
+	fcinfo = setexpr->fcinfo;
 
 	/*
 	 * Normally the passed expression tree will be a SetExprState, since the
@@ -162,23 +238,32 @@ ExecMakeTableFunctionResult(SetExprState *setexpr,
 		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
@@ -207,166 +292,161 @@ ExecMakeTableFunctionResult(SetExprState *setexpr,
 	MemoryContextSwitchTo(econtext->ecxt_per_tuple_memory);
 
 	/*
-	 * Loop to handle the ValuePerCall protocol (which is also the same
+	 * Handle the ValuePerCall protocol (which is also the same
 	 * behavior needed in the generic ExecEvalExpr path).
 	 */
-	for (;;)
+	Datum		result;
+
+	/* Call the function or expression one time */
+	if (!setexpr->elidedFuncState)
 	{
-		Datum		result;
+		pgstat_init_function_usage(fcinfo, &fcusage);
 
-		CHECK_FOR_INTERRUPTS();
+		fcinfo->isnull = false;
+		rsinfo->isDone = ExprSingleResult;
+		result = FunctionCallInvoke(fcinfo);
 
+		pgstat_end_function_usage(&fcusage,
+								  rsinfo->isDone != ExprMultipleResult);
+	}
+	else
+	{
+		result =
+			ExecEvalExpr(setexpr->elidedFuncState, econtext, &fcinfo->isnull);
+		rsinfo->isDone = ExprSingleResult;
+	}
+
+	/* Which protocol does function want to use? */
+	if (rsinfo->returnMode == SFRM_ValuePerCall)
+	{
 		/*
-		 * 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.
+		 * Check for end of result set.
 		 */
-		ResetExprContext(econtext);
-
-		/* Call the function or expression one time */
-		if (!setexpr->elidedFuncState)
-		{
-			pgstat_init_function_usage(fcinfo, &fcusage);
-
-			fcinfo->isnull = false;
-			rsinfo.isDone = ExprSingleResult;
-			result = FunctionCallInvoke(fcinfo);
-
-			pgstat_end_function_usage(&fcusage,
-									  rsinfo.isDone != ExprMultipleResult);
-		}
+		if (rsinfo->isDone == ExprEndResult)
+			goto no_function_result;
 		else
-		{
-			result =
-				ExecEvalExpr(setexpr->elidedFuncState, econtext, &fcinfo->isnull);
-			rsinfo.isDone = ExprSingleResult;
-		}
-
-		/* Which protocol does function want to use? */
-		if (rsinfo.returnMode == SFRM_ValuePerCall)
 		{
 			/*
-			 * Check for end of result set.
-			 */
-			if (rsinfo.isDone == ExprEndResult)
-				break;
-
-			/*
-			 * If first time through, build tuplestore for result.  For a
-			 * scalar function result type, also make a suitable tupdesc.
+			 * Save the current argument values to re-use on the next call.
 			 */
-			if (first_time)
+			if (*isDone == ExprMultipleResult)
 			{
-				oldcontext = MemoryContextSwitchTo(econtext->ecxt_per_query_memory);
-				tupstore = tuplestore_begin_heap(randomAccess, false, work_mem);
-				rsinfo.setResult = tupstore;
-				if (!returnsTuple)
+				setexpr->setArgsValid = true;
+				/* Register cleanup callback if we didn't already */
+				if (!setexpr->shutdown_reg)
 				{
-					tupdesc = CreateTemplateTupleDesc(1);
-					TupleDescInitEntry(tupdesc,
-									   (AttrNumber) 1,
-									   "column",
-									   funcrettype,
-									   -1,
-									   0);
-					rsinfo.setDesc = tupdesc;
+					RegisterExprContextCallback(econtext,
+												ShutdownSetExpr,
+												PointerGetDatum(setexpr));
+					setexpr->shutdown_reg = true;
 				}
+			}
+		}
+
+		HeapTupleHeader td = NULL;
+
+		/*
+		 * Obtain a suitable tupdesc, when we first encounter a non-NULL result.
+		 */
+		if (fs->returned_tupdesc == NULL)
+		{
+			if (!returnsTuple)
+			{
+				/*
+				 * This is the first non-NULL result from the
+				 * function.  Use the type info embedded in the
+				 * rowtype Datum to look up the needed tupdesc.  Make
+				 * a copy for the query.
+				 */
+				// FIXME: is this a too-long lived context?
+				oldcontext = MemoryContextSwitchTo(econtext->ecxt_per_query_memory);
+				fs->returned_tupdesc = CreateTemplateTupleDesc(1);
+				TupleDescInitEntry(fs->tupdesc,
+								   (AttrNumber) 1,
+								   "column",
+								   funcrettype,
+								   -1,
+								   0);
+				MemoryContextSwitchTo(oldcontext);
+			}
+			else if (!fcinfo->isnull)
+			{
+				td = DatumGetHeapTupleHeader(result);
+				 
+				/*
+				 * This is the first non-NULL result from the
+				 * function.  Use the type info embedded in the
+				 * rowtype Datum to look up the needed tupdesc.  Make
+				 * a copy for the query.
+				 */
+				// FIXME: is this a too-long lived context?
+				oldcontext = MemoryContextSwitchTo(econtext->ecxt_per_query_memory);
+				fs->returned_tupdesc = lookup_rowtype_tupdesc_copy(HeapTupleHeaderGetTypeId(td),
+												   HeapTupleHeaderGetTypMod(td));
 				MemoryContextSwitchTo(oldcontext);
 			}
+		}
 
-			/*
-			 * Store current resultset item.
-			 */
-			if (returnsTuple)
+		/*
+		 * Store current resultset item.
+		 */
+		if (returnsTuple)
+		{
+			if (!fcinfo->isnull)
 			{
-				if (!fcinfo->isnull)
-				{
-					HeapTupleHeader td = DatumGetHeapTupleHeader(result);
-
-					if (tupdesc == NULL)
-					{
-						/*
-						 * This is the first non-NULL result from the
-						 * function.  Use the type info embedded in the
-						 * rowtype Datum to look up the needed tupdesc.  Make
-						 * a copy for the query.
-						 */
-						oldcontext = MemoryContextSwitchTo(econtext->ecxt_per_query_memory);
-						tupdesc = 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")));
-					}
-
-					/*
-					 * tuplestore_puttuple needs a HeapTuple not a bare
-					 * HeapTupleHeader, but it doesn't need all the fields.
-					 */
-					tmptup.t_len = HeapTupleHeaderGetDatumLength(td);
-					tmptup.t_data = td;
-
-					tuplestore_puttuple(tupstore, &tmptup);
-				}
-				else
-				{
-					/*
-					 * NULL result from a tuple-returning function; expand it
-					 * to a row of all nulls.  We rely on the expectedDesc to
-					 * form such rows.  (Note: this would be problematic if
-					 * tuplestore_putvalues saved the tdtypeid/tdtypmod from
-					 * 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);
-				}
+				if (td == NULL)
+					td = DatumGetHeapTupleHeader(result);
+				
+				/*
+				 * Verify all later returned rows have same subtype;
+				 * necessary in case the type is RECORD.
+				 */
+				if (HeapTupleHeaderGetTypeId(td) != fs->returned_tupdesc->tdtypeid ||
+					HeapTupleHeaderGetTypMod(td) != fs->returned_tupdesc->tdtypmod)
+					ereport(ERROR,
+							(errcode(ERRCODE_DATATYPE_MISMATCH),
+							 errmsg("rows returned by function are not all of the same row type")));
+
+				slot_puttuple_offset (resultslot, fs->tupdesc, scanslot_off, rsinfo->setDesc, result);
 			}
 			else
 			{
-				/* Scalar-type case: just store the function result */
-				tuplestore_putvalues(tupstore, tupdesc, &result, &fcinfo->isnull);
+				/*
+				 * NULL result from a tuple-returning function; expand it
+				 * to a row of all nulls.  We rely on the expectedDesc to
+				 * form such rows.  (Note: this would be problematic if
+				 * tuplestore_putvalues saved the tdtypeid/tdtypmod from
+				 * the provided descriptor, since that might not match
+				 * what we get from the function itself.  But it doesn't.)
+				 */
+				slot_puttuple_offset (resultslot, fs->tupdesc, scanslot_off, rsinfo->setDesc, 0);
 			}
-
-			/*
-			 * Are we done?
-			 */
-			if (rsinfo.isDone != ExprMultipleResult)
-				break;
 		}
-		else if (rsinfo.returnMode == SFRM_Materialize)
+		else
 		{
-			/* 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;
+			/* Scalar-type case: just store the function result */
+			slot_putscalar_offset (resultslot, fs->tupdesc, scanslot_off, result, fcinfo->isnull);
 		}
-		else
+	}
+	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("unrecognized table-function returnMode: %d",
-							(int) rsinfo.returnMode)));
-
-		first_time = false;
+					 errmsg("table-function protocol for materialize mode was not followed")));
+		/* prepare to return values from the tuplestore */
+		ExecPrepareTuplestoreResult(setexpr, econtext,
+									rsinfo->setResult,
+									rsinfo->setDesc);
+		/* Done evaluating the set result */
+		goto restart;
 	}
+	else
+		ereport(ERROR,
+				(errcode(ERRCODE_E_R_I_E_SRF_PROTOCOL_VIOLATED),
+				 errmsg("unrecognized table-function returnMode: %d",
+						(int) rsinfo->returnMode)));
 
 no_function_result:
 
@@ -376,20 +456,11 @@ no_function_result:
 	 * 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)
+	if (rsinfo->setResult == NULL)
 	{
-		MemoryContextSwitchTo(econtext->ecxt_per_query_memory);
-		tupstore = tuplestore_begin_heap(randomAccess, false, work_mem);
-		rsinfo.setResult = tupstore;
 		if (!returnsSet)
 		{
-			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);
+			slot_puttuple_offset (resultslot, fs->tupdesc, scanslot_off, rsinfo->setDesc, 0);
 		}
 	}
 
@@ -397,23 +468,25 @@ 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)
+	if (rsinfo->setDesc)
 	{
-		tupledesc_match(expectedDesc, rsinfo.setDesc);
+		tupledesc_match(fs->tupdesc, 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);
+		//if (rsinfo->setDesc->tdrefcount == -1)
+		//	FreeTupleDesc(rsinfo->setDesc);
+		// FIXME: work out when to release this...
 	}
+	
+	*isDone = rsinfo->isDone;
 
 	MemoryContextSwitchTo(callerContext);
 
-	/* All done, pass back the tuplestore */
-	return rsinfo.setResult;
+	/* All done, result is in the tuplestore */
 }
 
 
@@ -486,7 +559,7 @@ ExecMakeFunctionResultSet(SetExprState *fcache,
 	Datum		result;
 	FunctionCallInfo fcinfo;
 	PgStat_FunctionCallUsage fcusage;
-	ReturnSetInfo rsinfo;
+	ReturnSetInfo *rsinfo;
 	bool		callit;
 	int			i;
 
@@ -569,16 +642,23 @@ restart:
 	 */
 
 	/* Prepare a resultinfo node for communication. */
-	fcinfo->resultinfo = (Node *) &rsinfo;
-	rsinfo.type = T_ReturnSetInfo;
-	rsinfo.econtext = econtext;
-	rsinfo.expectedDesc = fcache->funcResultDesc;
-	rsinfo.allowedModes = (int) (SFRM_ValuePerCall | SFRM_Materialize);
-	/* note we do not set SFRM_Materialize_Random or _Preferred */
-	rsinfo.returnMode = SFRM_ValuePerCall;
-	/* isDone is filled below */
-	rsinfo.setResult = NULL;
-	rsinfo.setDesc = NULL;
+	rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	
+	if (rsinfo == NULL)
+	{
+		MemoryContext oldcontext = MemoryContextSwitchTo(econtext->ecxt_per_query_memory);
+		rsinfo = makeNode (ReturnSetInfo);
+		rsinfo->type = T_ReturnSetInfo;
+		rsinfo->econtext = econtext;
+		rsinfo->expectedDesc = fcache->funcResultDesc;
+		rsinfo->allowedModes = (int) (SFRM_ValuePerCall | SFRM_Materialize);
+		rsinfo->returnMode = SFRM_ValuePerCall;
+		/* isDone is filled below */
+		rsinfo->setResult = NULL;
+		rsinfo->setDesc = NULL;
+		fcinfo->resultinfo = (Node *) rsinfo;
+		MemoryContextSwitchTo(oldcontext);
+	}
 
 	/*
 	 * If function is strict, and there are any NULL arguments, skip calling
@@ -602,13 +682,13 @@ restart:
 		pgstat_init_function_usage(fcinfo, &fcusage);
 
 		fcinfo->isnull = false;
-		rsinfo.isDone = ExprSingleResult;
+		rsinfo->isDone = ExprSingleResult;
 		result = FunctionCallInvoke(fcinfo);
 		*isNull = fcinfo->isnull;
-		*isDone = rsinfo.isDone;
+		*isDone = rsinfo->isDone;
 
 		pgstat_end_function_usage(&fcusage,
-								  rsinfo.isDone != ExprMultipleResult);
+								  rsinfo->isDone != ExprMultipleResult);
 	}
 	else
 	{
@@ -619,7 +699,7 @@ restart:
 	}
 
 	/* Which protocol does function want to use? */
-	if (rsinfo.returnMode == SFRM_ValuePerCall)
+	if (rsinfo->returnMode == SFRM_ValuePerCall)
 	{
 		if (*isDone != ExprEndResult)
 		{
@@ -640,19 +720,20 @@ restart:
 			}
 		}
 	}
-	else if (rsinfo.returnMode == SFRM_Materialize)
+	else if (rsinfo->returnMode == SFRM_Materialize)
 	{
 		/* check we're on the same page as the function author */
-		if (rsinfo.isDone != ExprSingleResult)
+		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)
+		if (rsinfo->setResult != NULL)
 		{
 			/* prepare to return values from the tuplestore */
 			ExecPrepareTuplestoreResult(fcache, econtext,
-										rsinfo.setResult,
-										rsinfo.setDesc);
+										rsinfo->setResult,
+										rsinfo->setDesc);
+
 			/* loop back to top to start returning from tuplestore */
 			goto restart;
 		}
@@ -665,7 +746,7 @@ restart:
 		ereport(ERROR,
 				(errcode(ERRCODE_E_R_I_E_SRF_PROTOCOL_VIOLATED),
 				 errmsg("unrecognized table-function returnMode: %d",
-						(int) rsinfo.returnMode)));
+						(int) rsinfo->returnMode)));
 
 	return result;
 }
@@ -712,6 +793,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)
@@ -804,6 +886,12 @@ ShutdownSetExpr(Datum arg)
 
 	/* Clear any active set-argument state */
 	sexpr->setArgsValid = false;
+	
+	if (sexpr->fcinfo->resultinfo != NULL)
+	{
+		pfree (sexpr->fcinfo->resultinfo);
+		sexpr->fcinfo->resultinfo = NULL;
+	}
 
 	/* execUtils will deregister the callback... */
 	sexpr->shutdown_reg = false;
@@ -960,3 +1048,67 @@ 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;
+
+		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..34054b7 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);
 
 
@@ -61,59 +48,42 @@ FunctionNext(FunctionScanState *node)
 {
 	EState	   *estate;
 	ScanDirection direction;
-	TupleTableSlot *scanslot;
+	TupleTableSlot *resultslot;
+	MemoryContext oldcontext;
+	ExprDoneCond		doneCond;
 	bool		alldone;
 	int64		oldpos;
 	int			funcno;
 	int			att;
 
+	// FIXME: assert not backwards
+
 	/*
 	 * get information from the estate and scan state
 	 */
 	estate = node->ss.ps.state;
 	direction = estate->es_direction;
-	scanslot = node->ss.ss_ScanTupleSlot;
+	resultslot = node->ss.ss_ScanTupleSlot;
 
+	ExecClearTuple(resultslot);
+
+	/* Call SRFs, as well as plain expressions, in per-tuple context */
+	oldcontext = MemoryContextSwitchTo(node->ss.ps.ps_ExprContext->ecxt_per_tuple_memory);
+	
 	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);
-		}
-
-		/*
-		 * Get the next tuple from tuplestore.
+		 * Read tuple from function and put it in the scanslot.
 		 */
-		(void) tuplestore_gettupleslot(tstore,
-									   ScanDirectionIsForward(direction),
-									   false,
-									   scanslot);
-		return scanslot;
+		ExecMakeTableFunctionResult(&node->funcstates[0],
+									node->ss.ps.ps_ExprContext,
+									node->argcontext,
+									resultslot,
+									0, &doneCond);
+
+		alldone = (doneCond == ExprEndResult);
+		
+		goto return_resultslot;
 	}
 
 	/*
@@ -135,93 +105,31 @@ FunctionNext(FunctionScanState *node)
 	 * return types), and then copy the values to scanslot (which matches the
 	 * scan result type), setting the ordinal column (if any) as well.
 	 */
-	ExecClearTuple(scanslot);
 	att = 0;
 	alldone = true;
 	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)
-		{
-			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
+		ExecMakeTableFunctionResult(fs,
+									node->ss.ps.ps_ExprContext,
+									node->argcontext,
+									resultslot,
+									att,
+									&doneCond);
+
+		if (doneCond != ExprEndResult)
 		{
-			/*
-			 * 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;
 		}
+		att += fs->colcount;
 	}
 
 	/*
@@ -229,18 +137,23 @@ FunctionNext(FunctionScanState *node)
 	 */
 	if (node->ordinality)
 	{
-		scanslot->tts_values[att] = Int64GetDatumFast(node->ordinal);
-		scanslot->tts_isnull[att] = false;
+		resultslot->tts_values[att] = Int64GetDatumFast(node->ordinal);
+		resultslot->tts_isnull[att] = false;
 	}
 
+return_resultslot:
+	MemoryContextSwitchTo(oldcontext);
+	
+	if (alldone)
+		return NULL;
+	
 	/*
 	 * If alldone, we just return the previously-cleared scanslot.  Otherwise,
 	 * finish creating the virtual tuple.
 	 */
-	if (!alldone)
-		ExecStoreVirtualTuple(scanslot);
+	ExecStoreVirtualTuple(resultslot);
 
-	return scanslot;
+	return resultslot;
 }
 
 /*
@@ -353,14 +266,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,
@@ -416,19 +321,7 @@ ExecInitFunctionScan(FunctionScan *node, EState *estate, int eflags)
 
 		fs->tupdesc = 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->returned_tupdesc = NULL; /* will be initialzied during FunctionNext */
 
 		natts += colcount;
 		i++;
@@ -521,8 +414,6 @@ ExecInitFunctionScan(FunctionScan *node, EState *estate, int eflags)
 void
 ExecEndFunctionScan(FunctionScanState *node)
 {
-	int			i;
-
 	/*
 	 * Free the exprcontext
 	 */
@@ -534,23 +425,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;
-		}
-	}
 }
 
 /* ----------------------------------------------------------------
@@ -571,9 +445,12 @@ ExecReScanFunctionScan(FunctionScanState *node)
 	for (i = 0; i < node->nfuncs; i++)
 	{
 		FunctionScanPerFuncState *fs = &node->funcstates[i];
-
-		if (fs->func_slot)
-			ExecClearTuple(fs->func_slot);
+		
+		if (fs->returned_tupdesc != NULL)
+		{
+			FreeTupleDesc (fs->returned_tupdesc);
+			fs->returned_tupdesc = NULL;
+		}
 	}
 
 	ExecScanReScan(&node->ss);
@@ -597,12 +474,7 @@ ExecReScanFunctionScan(FunctionScanState *node)
 
 			if (bms_overlap(chgparam, rtfunc->funcparams))
 			{
-				if (node->funcstates[i].tstore != NULL)
-				{
-					tuplestore_end(node->funcstates[i].tstore);
-					node->funcstates[i].tstore = NULL;
-				}
-				node->funcstates[i].rowcount = -1;
+				// FIXME: trigger something...!
 			}
 			i++;
 		}
@@ -614,7 +486,6 @@ 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);
+		// FIXME: trigger rewind
 	}
 }
diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h
index d056fd6..c40f6e6 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..9362952 100644
--- a/src/include/executor/nodeFunctionscan.h
+++ b/src/include/executor/nodeFunctionscan.h
@@ -16,6 +16,17 @@
 
 #include "nodes/execnodes.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 */
+	TupleDesc	returned_tupdesc;		/* desc of the function's last-returned result type */
+} FunctionScanPerFuncState;
+
 extern FunctionScanState *ExecInitFunctionScan(FunctionScan *node, EState *estate, int eflags);
 extern void ExecEndFunctionScan(FunctionScanState *node);
 extern void ExecReScanFunctionScan(FunctionScanState *node);
