From 6e07070ac1f2544ce8f0e455cc34b25144dd4a3e Mon Sep 17 00:00:00 2001
From: Andres Freund <andres@anarazel.de>
Date: Mon, 16 Jan 2017 12:40:13 -0800
Subject: [PATCH 2/2] Implement targetlist set returning functions in a new
 pipeline node.

---
 src/backend/commands/explain.c           |   5 +
 src/backend/executor/Makefile            |   4 +-
 src/backend/executor/execAmi.c           |   5 +
 src/backend/executor/execProcnode.c      |  14 ++
 src/backend/executor/execQual.c          |  85 ++++----
 src/backend/executor/nodeSetResult.c     | 322 +++++++++++++++++++++++++++++++
 src/backend/nodes/copyfuncs.c            |  19 ++
 src/backend/nodes/outfuncs.c             |  12 +-
 src/backend/nodes/readfuncs.c            |  16 ++
 src/backend/optimizer/path/allpaths.c    |   3 +
 src/backend/optimizer/plan/createplan.c  |  93 ++++++---
 src/backend/optimizer/plan/planner.c     |   4 +-
 src/backend/optimizer/plan/setrefs.c     |  21 ++
 src/backend/optimizer/plan/subselect.c   |   1 +
 src/backend/optimizer/util/pathnode.c    |  17 +-
 src/include/executor/executor.h          |   4 +
 src/include/executor/nodeSetResult.h     |  24 +++
 src/include/nodes/execnodes.h            |  15 ++
 src/include/nodes/nodes.h                |   3 +
 src/include/nodes/plannodes.h            |   7 +
 src/include/nodes/relation.h             |  11 +-
 src/include/optimizer/pathnode.h         |   2 +-
 src/test/regress/expected/aggregates.out |   2 +-
 src/test/regress/expected/limit.out      |   8 +-
 src/test/regress/expected/portals.out    |   8 +-
 src/test/regress/expected/subselect.out  |  13 +-
 src/test/regress/expected/tsrf.out       |   8 +-
 src/test/regress/expected/union.out      |   2 +-
 28 files changed, 631 insertions(+), 97 deletions(-)
 create mode 100644 src/backend/executor/nodeSetResult.c
 create mode 100644 src/include/executor/nodeSetResult.h

diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c
index ee7046c47b..a1a42f747d 100644
--- a/src/backend/commands/explain.c
+++ b/src/backend/commands/explain.c
@@ -852,6 +852,11 @@ ExplainNode(PlanState *planstate, List *ancestors,
 		case T_Result:
 			pname = sname = "Result";
 			break;
+
+		case T_SetResult:
+			pname = sname = "SetResult";
+			break;
+
 		case T_ModifyTable:
 			sname = "ModifyTable";
 			switch (((ModifyTable *) plan)->operation)
diff --git a/src/backend/executor/Makefile b/src/backend/executor/Makefile
index 51edd4c5e7..15587435d7 100644
--- a/src/backend/executor/Makefile
+++ b/src/backend/executor/Makefile
@@ -22,8 +22,8 @@ OBJS = execAmi.o execCurrent.o execGrouping.o execIndexing.o execJunk.o \
        nodeLimit.o nodeLockRows.o \
        nodeMaterial.o nodeMergeAppend.o nodeMergejoin.o nodeModifyTable.o \
        nodeNestloop.o nodeFunctionscan.o nodeRecursiveunion.o nodeResult.o \
-       nodeSamplescan.o nodeSeqscan.o nodeSetOp.o nodeSort.o nodeUnique.o \
-       nodeValuesscan.o nodeCtescan.o nodeWorktablescan.o \
+       nodeSamplescan.o nodeSeqscan.o nodeSetOp.o nodeSetResult.o nodeSort.o \
+       nodeUnique.o nodeValuesscan.o nodeCtescan.o nodeWorktablescan.o \
        nodeGroup.o nodeSubplan.o nodeSubqueryscan.o nodeTidscan.o \
        nodeForeignscan.o nodeWindowAgg.o tstoreReceiver.o tqueue.o spi.o
 
diff --git a/src/backend/executor/execAmi.c b/src/backend/executor/execAmi.c
index 3ea36979b3..c9c222f446 100644
--- a/src/backend/executor/execAmi.c
+++ b/src/backend/executor/execAmi.c
@@ -44,6 +44,7 @@
 #include "executor/nodeSamplescan.h"
 #include "executor/nodeSeqscan.h"
 #include "executor/nodeSetOp.h"
+#include "executor/nodeSetResult.h"
 #include "executor/nodeSort.h"
 #include "executor/nodeSubplan.h"
 #include "executor/nodeSubqueryscan.h"
@@ -130,6 +131,10 @@ ExecReScan(PlanState *node)
 			ExecReScanResult((ResultState *) node);
 			break;
 
+		case T_SetResultState:
+			ExecReScanSetResult((SetResultState *) node);
+			break;
+
 		case T_ModifyTableState:
 			ExecReScanModifyTable((ModifyTableState *) node);
 			break;
diff --git a/src/backend/executor/execProcnode.c b/src/backend/executor/execProcnode.c
index b8edd36470..f3cc706f13 100644
--- a/src/backend/executor/execProcnode.c
+++ b/src/backend/executor/execProcnode.c
@@ -106,6 +106,7 @@
 #include "executor/nodeSamplescan.h"
 #include "executor/nodeSeqscan.h"
 #include "executor/nodeSetOp.h"
+#include "executor/nodeSetResult.h"
 #include "executor/nodeSort.h"
 #include "executor/nodeSubplan.h"
 #include "executor/nodeSubqueryscan.h"
@@ -155,6 +156,11 @@ ExecInitNode(Plan *node, EState *estate, int eflags)
 												  estate, eflags);
 			break;
 
+		case T_SetResult:
+			result = (PlanState *) ExecInitSetResult((SetResult *) node,
+													 estate, eflags);
+			break;
+
 		case T_ModifyTable:
 			result = (PlanState *) ExecInitModifyTable((ModifyTable *) node,
 													   estate, eflags);
@@ -392,6 +398,10 @@ ExecProcNode(PlanState *node)
 			result = ExecResult((ResultState *) node);
 			break;
 
+		case T_SetResultState:
+			result = ExecSetResult((SetResultState *) node);
+			break;
+
 		case T_ModifyTableState:
 			result = ExecModifyTable((ModifyTableState *) node);
 			break;
@@ -634,6 +644,10 @@ ExecEndNode(PlanState *node)
 			ExecEndResult((ResultState *) node);
 			break;
 
+		case T_SetResultState:
+			ExecEndSetResult((SetResultState *) node);
+			break;
+
 		case T_ModifyTableState:
 			ExecEndModifyTable((ModifyTableState *) node);
 			break;
diff --git a/src/backend/executor/execQual.c b/src/backend/executor/execQual.c
index bf007b7efd..475efedad2 100644
--- a/src/backend/executor/execQual.c
+++ b/src/backend/executor/execQual.c
@@ -104,10 +104,6 @@ static void ExecPrepareTuplestoreResult(FuncExprState *fcache,
 							Tuplestorestate *resultStore,
 							TupleDesc resultDesc);
 static void tupledesc_match(TupleDesc dst_tupdesc, TupleDesc src_tupdesc);
-static Datum ExecMakeFunctionResult(FuncExprState *fcache,
-					   ExprContext *econtext,
-					   bool *isNull,
-					   ExprDoneCond *isDone);
 static Datum ExecMakeFunctionResultNoSets(FuncExprState *fcache,
 							 ExprContext *econtext,
 							 bool *isNull, ExprDoneCond *isDone);
@@ -1681,7 +1677,7 @@ tupledesc_match(TupleDesc dst_tupdesc, TupleDesc src_tupdesc)
  * This function handles the most general case, wherein the function or
  * one of its arguments can return a set.
  */
-static Datum
+Datum
 ExecMakeFunctionResult(FuncExprState *fcache,
 					   ExprContext *econtext,
 					   bool *isNull,
@@ -1702,6 +1698,32 @@ restart:
 	check_stack_depth();
 
 	/*
+	 * Initialize function cache if first time through. Unfortunately the
+	 * parent can be either an FuncExpr or OpExpr.  This is a bit ugly.
+	 */
+	if (fcache->func.fn_oid == InvalidOid)
+	{
+		if (IsA(fcache->xprstate.expr, FuncExpr))
+		{
+			FuncExpr   *func = (FuncExpr *) fcache->xprstate.expr;
+
+			init_fcache(func->funcid, func->inputcollid, fcache,
+						econtext->ecxt_per_query_memory, true);
+		}
+		else if (IsA(fcache->xprstate.expr, OpExpr))
+		{
+			OpExpr	   *op = (OpExpr *) fcache->xprstate.expr;
+
+			init_fcache(op->opfuncid, op->inputcollid, fcache,
+						econtext->ecxt_per_query_memory, true);
+		}
+		else
+		{
+			elog(ERROR, "unexpected type");
+		}
+	}
+
+	/*
 	 * 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.
@@ -2423,24 +2445,18 @@ ExecEvalFunc(FuncExprState *fcache,
 
 	/* Initialize function lookup info */
 	init_fcache(func->funcid, func->inputcollid, fcache,
-				econtext->ecxt_per_query_memory, true);
+				econtext->ecxt_per_query_memory, false);
 
-	/*
-	 * We need to invoke ExecMakeFunctionResult if either the function itself
-	 * or any of its input expressions can return a set.  Otherwise, invoke
-	 * ExecMakeFunctionResultNoSets.  In either case, change the evalfunc
-	 * pointer to go directly there on subsequent uses.
-	 */
-	if (fcache->func.fn_retset || expression_returns_set((Node *) func->args))
+	if (expression_returns_set((Node *) func->args) ||
+		fcache->func.fn_retset)
 	{
-		fcache->xprstate.evalfunc = (ExprStateEvalFunc) ExecMakeFunctionResult;
-		return ExecMakeFunctionResult(fcache, econtext, isNull, isDone);
-	}
-	else
-	{
-		fcache->xprstate.evalfunc = (ExprStateEvalFunc) ExecMakeFunctionResultNoSets;
-		return ExecMakeFunctionResultNoSets(fcache, econtext, isNull, isDone);
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("set-valued function called in context that cannot accept a set")));
 	}
+
+	fcache->xprstate.evalfunc = (ExprStateEvalFunc) ExecMakeFunctionResultNoSets;
+	return ExecMakeFunctionResultNoSets(fcache, econtext, isNull, isDone);
 }
 
 /* ----------------------------------------------------------------
@@ -2458,24 +2474,23 @@ ExecEvalOper(FuncExprState *fcache,
 
 	/* Initialize function lookup info */
 	init_fcache(op->opfuncid, op->inputcollid, fcache,
-				econtext->ecxt_per_query_memory, true);
+				econtext->ecxt_per_query_memory, false);
 
-	/*
-	 * We need to invoke ExecMakeFunctionResult if either the function itself
-	 * or any of its input expressions can return a set.  Otherwise, invoke
-	 * ExecMakeFunctionResultNoSets.  In either case, change the evalfunc
-	 * pointer to go directly there on subsequent uses.
-	 */
-	if (fcache->func.fn_retset || expression_returns_set((Node *) op->args))
+	/* should never get here */
+	if (expression_returns_set((Node *) op->args) ||
+		fcache->func.fn_retset)
 	{
-		fcache->xprstate.evalfunc = (ExprStateEvalFunc) ExecMakeFunctionResult;
-		return ExecMakeFunctionResult(fcache, econtext, isNull, isDone);
-	}
-	else
-	{
-		fcache->xprstate.evalfunc = (ExprStateEvalFunc) ExecMakeFunctionResultNoSets;
-		return ExecMakeFunctionResultNoSets(fcache, econtext, isNull, isDone);
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("set-valued function called in context that cannot accept a set")));
 	}
+
+	/* should never get here */
+	Assert(!expression_returns_set((Node *) op->args));
+	Assert(!fcache->func.fn_retset);
+
+	fcache->xprstate.evalfunc = (ExprStateEvalFunc) ExecMakeFunctionResultNoSets;
+	return ExecMakeFunctionResultNoSets(fcache, econtext, isNull, isDone);
 }
 
 /* ----------------------------------------------------------------
diff --git a/src/backend/executor/nodeSetResult.c b/src/backend/executor/nodeSetResult.c
new file mode 100644
index 0000000000..55a9789632
--- /dev/null
+++ b/src/backend/executor/nodeSetResult.c
@@ -0,0 +1,322 @@
+/*-------------------------------------------------------------------------
+ *
+ * nodeSetResult.c
+ *	  support for evaluating targetlist set returning functions
+ *
+ * DESCRIPTION
+ *
+ *		SetResult nodes are inserted by the planner to evaluate set returning
+ *		functions in the targetlist. It's guaranteed that all set returning
+ *		functions are directly at the top level of the targetlist, i.e. there
+ *		can't be inside a more complex expressions.  If that'd otherwise be
+ *		the case, the planner adds additional SetResult nodes.
+ *
+ * Portions Copyright (c) 1996-2017, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ *	  src/backend/executor/nodeSetResult.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "executor/executor.h"
+#include "executor/nodeSetResult.h"
+#include "utils/memutils.h"
+
+
+static TupleTableSlot *
+ExecProjectSRF(SetResultState *node, bool continuing);
+
+
+/* ----------------------------------------------------------------
+ *		ExecSetResult(node)
+ *
+ *		returns the tuples from the outer plan which satisfy the
+ *		qualification clause.  Since result nodes with right
+ *		subtrees are never planned, we ignore the right subtree
+ *		entirely (for now).. -cim 10/7/89
+ *
+ *		The qualification containing only constant clauses are
+ *		checked first before any processing is done. It always returns
+ *		'nil' if the constant qualification is not satisfied.
+ * ----------------------------------------------------------------
+ */
+TupleTableSlot *
+ExecSetResult(SetResultState *node)
+{
+	TupleTableSlot *outerTupleSlot;
+	TupleTableSlot *resultSlot;
+	PlanState  *outerPlan;
+	ExprContext *econtext;
+
+	econtext = node->ps.ps_ExprContext;
+
+	/*
+	 * 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 so, try to project another one.
+	 */
+	if (node->pending_srf_tuples)
+	{
+		resultSlot = ExecProjectSRF(node, true);
+
+		if (resultSlot != NULL)
+			return resultSlot;
+	}
+
+	/*
+	 * Reset per-tuple memory context to free any expression evaluation
+	 * storage allocated in the previous tuple cycle.  Note this can't happen
+	 * until we're done projecting out tuples from a scan tuple.
+	 */
+	ResetExprContext(econtext);
+
+	/*
+	 * if input_done is true then it means that we were asked to return a
+	 * constant tuple and we already did the last time ExecSetResult() was
+	 * called. Either way, now we are through.
+	 */
+	while (!node->input_done)
+	{
+		outerPlan = outerPlanState(node);
+
+		if (outerPlan != NULL)
+		{
+			/*
+			 * Retrieve tuples from the outer plan until there are no more.
+			 */
+			outerTupleSlot = ExecProcNode(outerPlan);
+
+			if (TupIsNull(outerTupleSlot))
+				return NULL;
+
+			/*
+			 * Prepare to compute projection expressions, which will expect to
+			 * access the input tuples as varno OUTER.
+			 */
+			econtext->ecxt_outertuple = outerTupleSlot;
+		}
+		else
+		{
+			/*
+			 * If we don't have an outer plan, then we are just generating the
+			 * results from a constant target list.  Do it only once.
+			 */
+			node->input_done = true;
+		}
+
+		resultSlot = ExecProjectSRF(node, false);
+
+		/*
+		 * Return the tuple unless the projection produced now rows (due to an
+		 * empty set), in which case we must loop back to see if there are
+		 * more outerPlan tuples.
+		 */
+		if (resultSlot)
+			return resultSlot;
+	}
+
+	return NULL;
+}
+
+/* ----------------------------------------------------------------
+ *		ExecProjectSRF
+ *
+ *		Project a targetlist containing one or more set returning functions.
+ *
+ *		continuing is to be set to true if we're continuing to project rows
+ *		for the same input tuple.
+ *
+ *		Returns NULL if no output tuple has been produced.
+ *
+ * ----------------------------------------------------------------
+ */
+static TupleTableSlot *
+ExecProjectSRF(SetResultState *node, bool continuing)
+{
+	TupleTableSlot *resultSlot = node->ps.ps_ResultTupleSlot;
+	ExprContext *econtext = node->ps.ps_ExprContext;
+	ListCell *lc;
+	int argno;
+	bool hasresult;
+	bool hassrf = false PG_USED_FOR_ASSERTS_ONLY;
+
+	ExecClearTuple(resultSlot);
+
+	/*
+	 * Assume no further tuples are produces unless an ExprMultipleResult is
+	 * encountered from a set returning function.
+	 */
+	node->pending_srf_tuples = false;
+
+	hasresult = false;
+	argno = 0;
+	foreach(lc, node->ps.targetlist)
+	{
+		GenericExprState *gstate = (GenericExprState *) lfirst(lc);
+		ExprDoneCond  *isdone = &node->elemdone[argno];
+		Datum *result = &resultSlot->tts_values[argno];
+		bool *isnull = &resultSlot->tts_isnull[argno];
+
+		if (continuing && *isdone == ExprEndResult)
+		{
+			/*
+			 * If we're continuing to project output rows from a source tuple,
+			 * return NULLs once the SRF has been exhausted.
+			 */
+			*result = 0;
+			*isnull = true;
+			hassrf = true;
+		}
+		else if (IsA(gstate->arg, FuncExprState) &&
+				 ((FuncExpr *) gstate->arg->expr)->funcretset)
+		{
+			/*
+			 * Evaluate SRF - possibly continuing previously started output.
+			 */
+			*result = ExecMakeFunctionResult((FuncExprState *) gstate->arg,
+											 econtext, isnull, isdone);
+
+			if (node->elemdone[argno] != ExprEndResult)
+				hasresult = true;
+			if (node->elemdone[argno] == ExprMultipleResult)
+				node->pending_srf_tuples = true;
+			hassrf = true;
+		}
+		else
+		{
+			*result = ExecEvalExpr(gstate->arg, econtext, isnull, NULL);
+			*isdone = ExprSingleResult;
+		}
+
+		argno++;
+	}
+
+	/* SetResult should not be used if there's no SRFs */
+	Assert(hassrf);
+
+	/*
+	 * If all the SRFs returned EndResult, we consider that as no result being
+	 * produced.
+	 */
+	if (hasresult)
+	{
+		ExecStoreVirtualTuple(resultSlot);
+		return resultSlot;
+	}
+
+	return NULL;
+}
+
+/* ----------------------------------------------------------------
+ *		ExecInitResult
+ *
+ *		Creates the run-time state information for the SetResult node
+ *		produced by the planner and initializes outer relations
+ *		(child nodes).
+ * ----------------------------------------------------------------
+ */
+SetResultState *
+ExecInitSetResult(SetResult *node, EState *estate, int eflags)
+{
+	SetResultState *state;
+
+	/* check for unsupported flags */
+	Assert(!(eflags & (EXEC_FLAG_MARK | EXEC_FLAG_BACKWARD)) ||
+		   outerPlan(node) != NULL);
+
+	/*
+	 * create state structure
+	 */
+	state = makeNode(SetResultState);
+	state->ps.plan = (Plan *) node;
+	state->ps.state = estate;
+
+	state->input_done = false;
+	state->pending_srf_tuples = false;
+
+	/*
+	 * Miscellaneous initialization
+	 *
+	 * create expression context for node
+	 */
+	ExecAssignExprContext(estate, &state->ps);
+
+	/*
+	 * tuple table initialization
+	 */
+	ExecInitResultTupleSlot(estate, &state->ps);
+
+	/*
+	 * initialize child expressions
+	 */
+	state->ps.targetlist = (List *)
+		ExecInitExpr((Expr *) node->plan.targetlist,
+					 (PlanState *) state);
+	state->ps.qual = (List *)
+		ExecInitExpr((Expr *) node->plan.qual,
+					 (PlanState *) state);
+
+	/*
+	 * initialize child nodes
+	 */
+	outerPlanState(state) = ExecInitNode(outerPlan(node), estate, eflags);
+
+	/*
+	 * we don't use inner plan
+	 */
+	Assert(innerPlan(node) == NULL);
+
+	/*
+	 * initialize tuple type and projection info
+	 */
+	ExecAssignResultTypeFromTL(&state->ps);
+
+	state->nelems = list_length(node->plan.targetlist);
+	state->elemdone = palloc(sizeof(ExprDoneCond) * state->nelems);
+
+	return state;
+}
+
+/* ----------------------------------------------------------------
+ *		ExecEndSetResult
+ *
+ *		frees up storage allocated through C routines
+ * ----------------------------------------------------------------
+ */
+void
+ExecEndSetResult(SetResultState *node)
+{
+	/*
+	 * Free the exprcontext
+	 */
+	ExecFreeExprContext(&node->ps);
+
+	/*
+	 * clean out the tuple table
+	 */
+	ExecClearTuple(node->ps.ps_ResultTupleSlot);
+
+	/*
+	 * shut down subplans
+	 */
+	ExecEndNode(outerPlanState(node));
+}
+
+void
+ExecReScanSetResult(SetResultState *node)
+{
+	node->input_done = false;
+	node->pending_srf_tuples = false;
+
+	/*
+	 * If chgParam of subnode is not null then plan will be re-scanned by
+	 * first ExecProcNode.
+	 */
+	if (node->ps.lefttree &&
+		node->ps.lefttree->chgParam == NULL)
+		ExecReScan(node->ps.lefttree);
+}
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 7107bbf164..37fbb35455 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -166,6 +166,22 @@ _copyResult(const Result *from)
 }
 
 /*
+ * _copySetResult
+ */
+static SetResult *
+_copySetResult(const SetResult *from)
+{
+	SetResult	   *newnode = makeNode(SetResult);
+
+	/*
+	 * copy node superclass fields
+	 */
+	CopyPlanFields((const Plan *) from, (Plan *) newnode);
+
+	return newnode;
+}
+
+/*
  * _copyModifyTable
  */
 static ModifyTable *
@@ -4413,6 +4429,9 @@ copyObject(const void *from)
 		case T_Result:
 			retval = _copyResult(from);
 			break;
+		case T_SetResult:
+			retval = _copySetResult(from);
+			break;
 		case T_ModifyTable:
 			retval = _copyModifyTable(from);
 			break;
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 73fdc9706d..6a1b9a4536 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -327,6 +327,14 @@ _outResult(StringInfo str, const Result *node)
 }
 
 static void
+_outSetResult(StringInfo str, const SetResult *node)
+{
+	WRITE_NODE_TYPE("SETRESULT");
+
+	_outPlanInfo(str, (const Plan *) node);
+}
+
+static void
 _outModifyTable(StringInfo str, const ModifyTable *node)
 {
 	WRITE_NODE_TYPE("MODIFYTABLE");
@@ -1805,7 +1813,6 @@ _outProjectionPath(StringInfo str, const ProjectionPath *node)
 
 	WRITE_NODE_FIELD(subpath);
 	WRITE_BOOL_FIELD(dummypp);
-	WRITE_BOOL_FIELD(srfpp);
 }
 
 static void
@@ -3362,6 +3369,9 @@ outNode(StringInfo str, const void *obj)
 			case T_Result:
 				_outResult(str, obj);
 				break;
+			case T_SetResult:
+				_outSetResult(str, obj);
+				break;
 			case T_ModifyTable:
 				_outModifyTable(str, obj);
 				break;
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index e02dd94f05..f47b841947 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -1483,6 +1483,20 @@ _readResult(void)
 	READ_DONE();
 }
 
+
+/*
+ * _readSetResult
+ */
+static SetResult *
+_readSetResult(void)
+{
+	READ_LOCALS_NO_FIELDS(SetResult);
+
+	ReadCommonPlan(&local_node->plan);
+
+	READ_DONE();
+}
+
 /*
  * _readModifyTable
  */
@@ -2450,6 +2464,8 @@ parseNodeString(void)
 		return_value = _readPlan();
 	else if (MATCH("RESULT", 6))
 		return_value = _readResult();
+	else if (MATCH("SETRESULT", 9))
+		return_value = _readSetResult();
 	else if (MATCH("MODIFYTABLE", 11))
 		return_value = _readModifyTable();
 	else if (MATCH("APPEND", 6))
diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c
index 46d7d064d4..1708e8062c 100644
--- a/src/backend/optimizer/path/allpaths.c
+++ b/src/backend/optimizer/path/allpaths.c
@@ -2976,6 +2976,9 @@ print_path(PlannerInfo *root, Path *path, int indent)
 		case T_ResultPath:
 			ptype = "Result";
 			break;
+		case T_SetResultPath:
+			ptype = "SetResult";
+			break;
 		case T_MaterialPath:
 			ptype = "Material";
 			subpath = ((MaterialPath *) path)->subpath;
diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c
index 875de739a8..78f9d1b4c3 100644
--- a/src/backend/optimizer/plan/createplan.c
+++ b/src/backend/optimizer/plan/createplan.c
@@ -81,6 +81,7 @@ static Plan *create_join_plan(PlannerInfo *root, JoinPath *best_path);
 static Plan *create_append_plan(PlannerInfo *root, AppendPath *best_path);
 static Plan *create_merge_append_plan(PlannerInfo *root, MergeAppendPath *best_path);
 static Result *create_result_plan(PlannerInfo *root, ResultPath *best_path);
+static SetResult *create_set_result_plan(PlannerInfo *root, SetProjectionPath *best_path);
 static Material *create_material_plan(PlannerInfo *root, MaterialPath *best_path,
 					 int flags);
 static Plan *create_unique_plan(PlannerInfo *root, UniquePath *best_path,
@@ -264,6 +265,7 @@ static SetOp *make_setop(SetOpCmd cmd, SetOpStrategy strategy, Plan *lefttree,
 		   long numGroups);
 static LockRows *make_lockrows(Plan *lefttree, List *rowMarks, int epqParam);
 static Result *make_result(List *tlist, Node *resconstantqual, Plan *subplan);
+static SetResult *make_set_result(List *tlist, Plan *subplan);
 static ModifyTable *make_modifytable(PlannerInfo *root,
 				 CmdType operation, bool canSetTag,
 				 Index nominalRelation,
@@ -392,6 +394,10 @@ create_plan_recurse(PlannerInfo *root, Path *best_path, int flags)
 												   (ResultPath *) best_path);
 			}
 			break;
+		case T_SetResult:
+			plan = (Plan *) create_set_result_plan(root,
+											(SetProjectionPath *) best_path);
+			break;
 		case T_Material:
 			plan = (Plan *) create_material_plan(root,
 												 (MaterialPath *) best_path,
@@ -1142,6 +1148,44 @@ create_result_plan(PlannerInfo *root, ResultPath *best_path)
 }
 
 /*
+ * create_set_result_plan
+ *	  Create a SetResult plan for 'best_path'.
+ *
+ *	  Returns a Plan node.
+ */
+static SetResult *
+create_set_result_plan(PlannerInfo *root, SetProjectionPath *best_path)
+{
+	SetResult  *plan;
+	Plan	   *subplan;
+	List	   *tlist;
+
+	/*
+	 * XXX Possibly-temporary hack: if the subpath is a dummy ResultPath,
+	 * don't bother with it, just make a SetResult with no input.  This avoids
+	 * an extra Result plan node when doing "SELECT srf()".  Depending on what
+	 * we decide about the desired plan structure for SRF-expanding nodes,
+	 * this optimization might have to go away, and in any case it'll probably
+	 * look a good bit different.
+	 */
+	if (IsA(best_path->subpath, ResultPath) &&
+		((ResultPath *) best_path->subpath)->path.pathtarget->exprs == NIL &&
+		((ResultPath *) best_path->subpath)->quals == NIL)
+		subplan = NULL;
+	else
+		/* Since we intend to project, we don't need to constrain child tlist */
+		subplan = create_plan_recurse(root, best_path->subpath, 0);
+
+	tlist = build_path_tlist(root, &best_path->path);
+
+	plan = make_set_result(tlist, subplan);
+
+	copy_generic_path_info(&plan->plan, (Path *) best_path);
+
+	return plan;
+}
+
+/*
  * create_material_plan
  *	  Create a Material plan for 'best_path' and (recursively) plans
  *	  for its subpaths.
@@ -1421,21 +1465,8 @@ create_projection_plan(PlannerInfo *root, ProjectionPath *best_path)
 	Plan	   *subplan;
 	List	   *tlist;
 
-	/*
-	 * XXX Possibly-temporary hack: if the subpath is a dummy ResultPath,
-	 * don't bother with it, just make a Result with no input.  This avoids an
-	 * extra Result plan node when doing "SELECT srf()".  Depending on what we
-	 * decide about the desired plan structure for SRF-expanding nodes, this
-	 * optimization might have to go away, and in any case it'll probably look
-	 * a good bit different.
-	 */
-	if (IsA(best_path->subpath, ResultPath) &&
-		((ResultPath *) best_path->subpath)->path.pathtarget->exprs == NIL &&
-		((ResultPath *) best_path->subpath)->quals == NIL)
-		subplan = NULL;
-	else
-		/* Since we intend to project, we don't need to constrain child tlist */
-		subplan = create_plan_recurse(root, best_path->subpath, 0);
+	/* Since we intend to project, we don't need to constrain child tlist */
+	subplan = create_plan_recurse(root, best_path->subpath, 0);
 
 	tlist = build_path_tlist(root, &best_path->path);
 
@@ -1454,9 +1485,8 @@ create_projection_plan(PlannerInfo *root, ProjectionPath *best_path)
 	 * creation, but that would add expense to creating Paths we might end up
 	 * not using.)
 	 */
-	if (!best_path->srfpp &&
-		(is_projection_capable_path(best_path->subpath) ||
-		 tlist_same_exprs(tlist, subplan->targetlist)))
+	if (is_projection_capable_path(best_path->subpath) ||
+		tlist_same_exprs(tlist, subplan->targetlist))
 	{
 		/* Don't need a separate Result, just assign tlist to subplan */
 		plan = subplan;
@@ -6041,6 +6071,25 @@ make_result(List *tlist,
 }
 
 /*
+ * make_set_result
+ *	  Build a SetResult plan node
+ */
+static SetResult *
+make_set_result(List *tlist,
+				Plan *subplan)
+{
+	SetResult  *node = makeNode(SetResult);
+	Plan	   *plan = &node->plan;
+
+	plan->targetlist = tlist;
+	plan->qual = NIL;
+	plan->lefttree = subplan;
+	plan->righttree = NULL;
+
+	return node;
+}
+
+/*
  * make_modifytable
  *	  Build a ModifyTable plan node
  */
@@ -6206,17 +6255,15 @@ is_projection_capable_path(Path *path)
 			 * projection to its dummy path.
 			 */
 			return IS_DUMMY_PATH(path);
-		case T_Result:
 
+		case T_SetResult:
 			/*
 			 * If the path is doing SRF evaluation, claim it can't project, so
 			 * we don't jam a new tlist into it and thereby break the property
 			 * that the SRFs appear at top level.
 			 */
-			if (IsA(path, ProjectionPath) &&
-				((ProjectionPath *) path)->srfpp)
-				return false;
-			break;
+			return false;
+
 		default:
 			break;
 	}
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index 70870bbbe0..a208f511d9 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -5245,7 +5245,7 @@ adjust_paths_for_srfs(PlannerInfo *root, RelOptInfo *rel,
 
 			/* If this level doesn't contain SRFs, do regular projection */
 			if (contains_srfs)
-				newpath = (Path *) create_srf_projection_path(root,
+				newpath = (Path *) create_set_projection_path(root,
 															  rel,
 															  newpath,
 															  thistarget);
@@ -5278,7 +5278,7 @@ adjust_paths_for_srfs(PlannerInfo *root, RelOptInfo *rel,
 
 			/* If this level doesn't contain SRFs, do regular projection */
 			if (contains_srfs)
-				newpath = (Path *) create_srf_projection_path(root,
+				newpath = (Path *) create_set_projection_path(root,
 															  rel,
 															  newpath,
 															  thistarget);
diff --git a/src/backend/optimizer/plan/setrefs.c b/src/backend/optimizer/plan/setrefs.c
index 413a0d9da2..e77312d6af 100644
--- a/src/backend/optimizer/plan/setrefs.c
+++ b/src/backend/optimizer/plan/setrefs.c
@@ -733,6 +733,27 @@ set_plan_refs(PlannerInfo *root, Plan *plan, int rtoffset)
 					fix_scan_expr(root, splan->resconstantqual, rtoffset);
 			}
 			break;
+
+		case T_SetResult:
+			{
+				SetResult *splan = (SetResult *) plan;
+
+				/*
+				 * SetResult may or may not have a subplan; if not, it's more
+				 * like a scan node than an upper node.
+				 */
+				if (splan->plan.lefttree != NULL)
+					set_upper_references(root, plan, rtoffset);
+				else
+				{
+					splan->plan.targetlist =
+						fix_scan_list(root, splan->plan.targetlist, rtoffset);
+					splan->plan.qual =
+						fix_scan_list(root, splan->plan.qual, rtoffset);
+				}
+			}
+			break;
+
 		case T_ModifyTable:
 			{
 				ModifyTable *splan = (ModifyTable *) plan;
diff --git a/src/backend/optimizer/plan/subselect.c b/src/backend/optimizer/plan/subselect.c
index aad0b684ed..ad8b75b4d9 100644
--- a/src/backend/optimizer/plan/subselect.c
+++ b/src/backend/optimizer/plan/subselect.c
@@ -2680,6 +2680,7 @@ finalize_plan(PlannerInfo *root, Plan *plan, Bitmapset *valid_params,
 							  &context);
 			break;
 
+		case T_SetResult:
 		case T_Hash:
 		case T_Material:
 		case T_Sort:
diff --git a/src/backend/optimizer/util/pathnode.c b/src/backend/optimizer/util/pathnode.c
index aa635fd057..2e30af20af 100644
--- a/src/backend/optimizer/util/pathnode.c
+++ b/src/backend/optimizer/util/pathnode.c
@@ -2227,9 +2227,6 @@ create_projection_path(PlannerInfo *root,
 			(cpu_tuple_cost + target->cost.per_tuple) * subpath->rows;
 	}
 
-	/* Assume no SRFs around */
-	pathnode->srfpp = false;
-
 	return pathnode;
 }
 
@@ -2333,17 +2330,17 @@ apply_projection_to_path(PlannerInfo *root,
  * 'subpath' is the path representing the source of data
  * 'target' is the PathTarget to be computed
  */
-ProjectionPath *
-create_srf_projection_path(PlannerInfo *root,
+SetProjectionPath *
+create_set_projection_path(PlannerInfo *root,
 						   RelOptInfo *rel,
 						   Path *subpath,
 						   PathTarget *target)
 {
-	ProjectionPath *pathnode = makeNode(ProjectionPath);
+	SetProjectionPath *pathnode = makeNode(SetProjectionPath);
 	double		tlist_rows;
 	ListCell   *lc;
 
-	pathnode->path.pathtype = T_Result;
+	pathnode->path.pathtype = T_SetResult;
 	pathnode->path.parent = rel;
 	pathnode->path.pathtarget = target;
 	/* For now, assume we are above any joins, so no parameterization */
@@ -2353,15 +2350,11 @@ create_srf_projection_path(PlannerInfo *root,
 		subpath->parallel_safe &&
 		is_parallel_safe(root, (Node *) target->exprs);
 	pathnode->path.parallel_workers = subpath->parallel_workers;
-	/* Projection does not change the sort order */
+	/* Projection does not change the sort order XXX? */
 	pathnode->path.pathkeys = subpath->pathkeys;
 
 	pathnode->subpath = subpath;
 
-	/* Always need the Result node */
-	pathnode->dummypp = false;
-	pathnode->srfpp = true;
-
 	/*
 	 * Estimate number of rows produced by SRFs for each row of input; if
 	 * there's more than one in this node, use the maximum.
diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h
index b9c7f72903..59fae35ab5 100644
--- a/src/include/executor/executor.h
+++ b/src/include/executor/executor.h
@@ -262,6 +262,10 @@ extern int	ExecTargetListLength(List *targetlist);
 extern int	ExecCleanTargetListLength(List *targetlist);
 extern TupleTableSlot *ExecProject(ProjectionInfo *projInfo,
 			ExprDoneCond *isDone);
+extern Datum ExecMakeFunctionResult(FuncExprState *fcache,
+					   ExprContext *econtext,
+					   bool *isNull,
+					   ExprDoneCond *isDone);
 
 /*
  * prototypes from functions in execScan.c
diff --git a/src/include/executor/nodeSetResult.h b/src/include/executor/nodeSetResult.h
new file mode 100644
index 0000000000..f51cf32956
--- /dev/null
+++ b/src/include/executor/nodeSetResult.h
@@ -0,0 +1,24 @@
+/*-------------------------------------------------------------------------
+ *
+ * nodeSetResult.h
+ *
+ *
+ *
+ * Portions Copyright (c) 1996-2017, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/executor/nodeSetResult.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef NODESETRESULT_H
+#define NODESETRESULT_H
+
+#include "nodes/execnodes.h"
+
+extern SetResultState *ExecInitSetResult(SetResult *node, EState *estate, int eflags);
+extern TupleTableSlot *ExecSetResult(SetResultState *node);
+extern void ExecEndSetResult(SetResultState *node);
+extern void ExecReScanSetResult(SetResultState *node);
+
+#endif   /* NODESETRESULT_H */
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index ce13bf7635..69de3ebbd9 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -1129,6 +1129,21 @@ typedef struct ResultState
 	bool		rs_checkqual;	/* do we need to check the qual? */
 } ResultState;
 
+
+/* ----------------
+ *	 SetResultState information
+ * ----------------
+ */
+typedef struct SetResultState
+{
+	PlanState	ps;				/* its first field is NodeTag */
+	int			nelems;
+	ExprDoneCond *elemdone;
+	bool		input_done;		/* done reading source tuple? */
+	bool		pending_srf_tuples; /* evaluating srfs in tlist? */
+} SetResultState;
+
+
 /* ----------------
  *	 ModifyTableState information
  * ----------------
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index 4c4319bcab..be397fb138 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -43,6 +43,7 @@ typedef enum NodeTag
 	 */
 	T_Plan,
 	T_Result,
+	T_SetResult,
 	T_ModifyTable,
 	T_Append,
 	T_MergeAppend,
@@ -91,6 +92,7 @@ typedef enum NodeTag
 	 */
 	T_PlanState,
 	T_ResultState,
+	T_SetResultState,
 	T_ModifyTableState,
 	T_AppendState,
 	T_MergeAppendState,
@@ -245,6 +247,7 @@ typedef enum NodeTag
 	T_UniquePath,
 	T_GatherPath,
 	T_ProjectionPath,
+	T_SetProjectionPath,
 	T_SortPath,
 	T_GroupPath,
 	T_UpperUniquePath,
diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h
index 6810f8c099..3405f018fc 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -176,6 +176,13 @@ typedef struct Result
 	Node	   *resconstantqual;
 } Result;
 
+
+typedef struct SetResult
+{
+	Plan		plan;
+} SetResult;
+
+
 /* ----------------
  *	 ModifyTable node -
  *		Apply rows produced by subplan(s) to result table(s),
diff --git a/src/include/nodes/relation.h b/src/include/nodes/relation.h
index de4092d679..50fa79926a 100644
--- a/src/include/nodes/relation.h
+++ b/src/include/nodes/relation.h
@@ -1293,10 +1293,19 @@ typedef struct ProjectionPath
 	Path		path;
 	Path	   *subpath;		/* path representing input source */
 	bool		dummypp;		/* true if no separate Result is needed */
-	bool		srfpp;			/* true if SRFs are being evaluated here */
 } ProjectionPath;
 
 /*
+ * SetProjectionPath represents an evaluation of a targetlist set returning
+ * function.
+ */
+typedef struct SetProjectionPath
+{
+	Path		path;
+	Path	   *subpath;		/* path representing input source */
+} SetProjectionPath;
+
+/*
  * SortPath represents an explicit sort step
  *
  * The sort keys are, by definition, the same as path.pathkeys.
diff --git a/src/include/optimizer/pathnode.h b/src/include/optimizer/pathnode.h
index c11c59df23..9cbd87c0a2 100644
--- a/src/include/optimizer/pathnode.h
+++ b/src/include/optimizer/pathnode.h
@@ -144,7 +144,7 @@ extern Path *apply_projection_to_path(PlannerInfo *root,
 						 RelOptInfo *rel,
 						 Path *path,
 						 PathTarget *target);
-extern ProjectionPath *create_srf_projection_path(PlannerInfo *root,
+extern SetProjectionPath *create_set_projection_path(PlannerInfo *root,
 						   RelOptInfo *rel,
 						   Path *subpath,
 						   PathTarget *target);
diff --git a/src/test/regress/expected/aggregates.out b/src/test/regress/expected/aggregates.out
index b71d81ee21..c7a87a25a9 100644
--- a/src/test/regress/expected/aggregates.out
+++ b/src/test/regress/expected/aggregates.out
@@ -822,7 +822,7 @@ explain (costs off)
      ->  Limit
            ->  Index Only Scan Backward using tenk1_unique2 on tenk1
                  Index Cond: (unique2 IS NOT NULL)
-   ->  Result
+   ->  SetResult
          ->  Result
 (8 rows)
 
diff --git a/src/test/regress/expected/limit.out b/src/test/regress/expected/limit.out
index a7ded3ad05..f3124394a3 100644
--- a/src/test/regress/expected/limit.out
+++ b/src/test/regress/expected/limit.out
@@ -212,7 +212,7 @@ select unique1, unique2, generate_series(1,10)
 -------------------------------------------------------------------------------------------------------------------------------------------------------------
  Limit
    Output: unique1, unique2, (generate_series(1, 10))
-   ->  Result
+   ->  SetResult
          Output: unique1, unique2, generate_series(1, 10)
          ->  Index Scan using tenk1_unique2 on public.tenk1
                Output: unique1, unique2, two, four, ten, twenty, hundred, thousand, twothousand, fivethous, tenthous, odd, even, stringu1, stringu2, string4
@@ -238,7 +238,7 @@ select unique1, unique2, generate_series(1,10)
 --------------------------------------------------------------------
  Limit
    Output: unique1, unique2, (generate_series(1, 10)), tenthous
-   ->  Result
+   ->  SetResult
          Output: unique1, unique2, generate_series(1, 10), tenthous
          ->  Sort
                Output: unique1, unique2, tenthous
@@ -265,7 +265,7 @@ explain (verbose, costs off)
 select generate_series(0,2) as s1, generate_series((random()*.1)::int,2) as s2;
                                               QUERY PLAN                                              
 ------------------------------------------------------------------------------------------------------
- Result
+ SetResult
    Output: generate_series(0, 2), generate_series(((random() * '0.1'::double precision))::integer, 2)
 (2 rows)
 
@@ -285,7 +285,7 @@ order by s2 desc;
  Sort
    Output: (generate_series(0, 2)), (generate_series(((random() * '0.1'::double precision))::integer, 2))
    Sort Key: (generate_series(((random() * '0.1'::double precision))::integer, 2)) DESC
-   ->  Result
+   ->  SetResult
          Output: generate_series(0, 2), generate_series(((random() * '0.1'::double precision))::integer, 2)
 (5 rows)
 
diff --git a/src/test/regress/expected/portals.out b/src/test/regress/expected/portals.out
index 3ae918a63c..b49fa17eb3 100644
--- a/src/test/regress/expected/portals.out
+++ b/src/test/regress/expected/portals.out
@@ -1322,14 +1322,14 @@ begin;
 explain (costs off) declare c2 cursor for select generate_series(1,3) as g;
  QUERY PLAN 
 ------------
- Result
+ SetResult
 (1 row)
 
 explain (costs off) declare c2 scroll cursor for select generate_series(1,3) as g;
-  QUERY PLAN  
---------------
+   QUERY PLAN    
+-----------------
  Materialize
-   ->  Result
+   ->  SetResult
 (2 rows)
 
 declare c2 scroll cursor for select generate_series(1,3) as g;
diff --git a/src/test/regress/expected/subselect.out b/src/test/regress/expected/subselect.out
index 3ed089aa46..0215c9a663 100644
--- a/src/test/regress/expected/subselect.out
+++ b/src/test/regress/expected/subselect.out
@@ -821,7 +821,7 @@ select * from int4_tbl o where (f1, f1) in
                Filter: ("ANY_subquery".f1 = "ANY_subquery".g)
                ->  Result
                      Output: i.f1, ((generate_series(1, 2)) / 10)
-                     ->  Result
+                     ->  SetResult
                            Output: i.f1, generate_series(1, 2)
                            ->  HashAggregate
                                  Output: i.f1
@@ -903,7 +903,7 @@ select * from
  Subquery Scan on ss
    Output: x, u
    Filter: tattle(ss.x, 8)
-   ->  Result
+   ->  SetResult
          Output: 9, unnest('{1,2,3,11,12,13}'::integer[])
 (5 rows)
 
@@ -934,10 +934,11 @@ select * from
   where tattle(x, 8);
                      QUERY PLAN                     
 ----------------------------------------------------
- Result
+ SetResult
    Output: 9, unnest('{1,2,3,11,12,13}'::integer[])
-   One-Time Filter: tattle(9, 8)
-(3 rows)
+   ->  Result
+         One-Time Filter: tattle(9, 8)
+(4 rows)
 
 select * from
   (select 9 as x, unnest(array[1,2,3,11,12,13]) as u) ss
@@ -963,7 +964,7 @@ select * from
  Subquery Scan on ss
    Output: x, u
    Filter: tattle(ss.x, ss.u)
-   ->  Result
+   ->  SetResult
          Output: 9, unnest('{1,2,3,11,12,13}'::integer[])
 (5 rows)
 
diff --git a/src/test/regress/expected/tsrf.out b/src/test/regress/expected/tsrf.out
index f257537925..8c47f0f668 100644
--- a/src/test/regress/expected/tsrf.out
+++ b/src/test/regress/expected/tsrf.out
@@ -25,8 +25,8 @@ SELECT generate_series(1, 2), generate_series(1,4);
 -----------------+-----------------
                1 |               1
                2 |               2
-               1 |               3
-               2 |               4
+                 |               3
+                 |               4
 (4 rows)
 
 -- srf, with SRF argument
@@ -127,15 +127,15 @@ SELECT few.dataa, count(*), min(id), max(id), unnest('{1,1,3}'::int[]) FROM few
 SELECT few.dataa, count(*), min(id), max(id), unnest('{1,1,3}'::int[]) FROM few WHERE few.id = 1 GROUP BY few.dataa, unnest('{1,1,3}'::int[]);
  dataa | count | min | max | unnest 
 -------+-------+-----+-----+--------
- a     |     2 |   1 |   1 |      1
  a     |     1 |   1 |   1 |      3
+ a     |     2 |   1 |   1 |      1
 (2 rows)
 
 SELECT few.dataa, count(*), min(id), max(id), unnest('{1,1,3}'::int[]) FROM few WHERE few.id = 1 GROUP BY few.dataa, 5;
  dataa | count | min | max | unnest 
 -------+-------+-----+-----+--------
- a     |     2 |   1 |   1 |      1
  a     |     1 |   1 |   1 |      3
+ a     |     2 |   1 |   1 |      1
 (2 rows)
 
 -- check HAVING works when GROUP BY does [not] reference SRF output
diff --git a/src/test/regress/expected/union.out b/src/test/regress/expected/union.out
index 67f5fc4361..743d0bd0ed 100644
--- a/src/test/regress/expected/union.out
+++ b/src/test/regress/expected/union.out
@@ -636,7 +636,7 @@ ORDER BY x;
          ->  HashAggregate
                Group Key: (1), (generate_series(1, 10))
                ->  Append
-                     ->  Result
+                     ->  SetResult
                      ->  Result
 (9 rows)
 
-- 
2.11.0.22.g8d7a455.dirty

