>From acc5949aaf61352ff3dd0cca2d7e921a8b8746d0 Mon Sep 17 00:00:00 2001
From: Andres Freund <andres@anarazel.de>
Date: Fri, 29 Jul 2016 18:51:02 -0700
Subject: [PATCH 2/3] Basic implementation of targetlist SRFs via ROWS FROM.

---
 src/backend/executor/execQual.c           |   7 +
 src/backend/nodes/copyfuncs.c             |   2 +
 src/backend/nodes/equalfuncs.c            |   1 +
 src/backend/nodes/outfuncs.c              |   2 +
 src/backend/nodes/readfuncs.c             |   2 +
 src/backend/optimizer/plan/initsplan.c    |   4 +
 src/backend/optimizer/plan/planner.c      |   8 +
 src/backend/optimizer/prep/prepjointree.c |   4 +
 src/backend/optimizer/util/clauses.c      | 581 ++++++++++++++++++++++++++++++
 src/backend/parser/analyze.c              |  10 +
 src/backend/parser/parse_func.c           |   5 +
 src/backend/parser/parse_oper.c           |   5 +
 src/include/nodes/parsenodes.h            |   6 +-
 src/include/optimizer/clauses.h           |   2 +
 src/include/parser/parse_node.h           |   1 +
 15 files changed, 639 insertions(+), 1 deletion(-)

diff --git a/src/backend/executor/execQual.c b/src/backend/executor/execQual.c
index 69bf65d..8896455 100644
--- a/src/backend/executor/execQual.c
+++ b/src/backend/executor/execQual.c
@@ -2420,6 +2420,13 @@ ExecEvalFunc(FuncExprState *fcache,
 	init_fcache(func->funcid, func->inputcollid, fcache,
 				econtext->ecxt_per_query_memory, true);
 
+	if (fcache->func.fn_retset)
+	{
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("set-valued function called in context that cannot accept a set")));
+	}
+
 	/*
 	 * We need to invoke ExecMakeFunctionResult if either the function itself
 	 * or any of its input expressions can return a set.  Otherwise, invoke
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 3244c76..8418b80 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -2150,6 +2150,7 @@ _copyRangeTblEntry(const RangeTblEntry *from)
 	COPY_BITMAPSET_FIELD(insertedCols);
 	COPY_BITMAPSET_FIELD(updatedCols);
 	COPY_NODE_FIELD(securityQuals);
+	COPY_NODE_FIELD(deps);
 
 	return newnode;
 }
@@ -2719,6 +2720,7 @@ _copyQuery(const Query *from)
 	COPY_SCALAR_FIELD(hasModifyingCTE);
 	COPY_SCALAR_FIELD(hasForUpdate);
 	COPY_SCALAR_FIELD(hasRowSecurity);
+	COPY_SCALAR_FIELD(hasTargetSRF);
 	COPY_NODE_FIELD(cteList);
 	COPY_NODE_FIELD(rtable);
 	COPY_NODE_FIELD(jointree);
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 1eb6799..b30bcc5 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -2459,6 +2459,7 @@ _equalRangeTblEntry(const RangeTblEntry *a, const RangeTblEntry *b)
 	COMPARE_BITMAPSET_FIELD(insertedCols);
 	COMPARE_BITMAPSET_FIELD(updatedCols);
 	COMPARE_NODE_FIELD(securityQuals);
+	COMPARE_NODE_FIELD(deps);
 
 	return true;
 }
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index acaf4ea..35eab0b 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -2675,6 +2675,7 @@ _outQuery(StringInfo str, const Query *node)
 	WRITE_BOOL_FIELD(hasModifyingCTE);
 	WRITE_BOOL_FIELD(hasForUpdate);
 	WRITE_BOOL_FIELD(hasRowSecurity);
+	WRITE_BOOL_FIELD(hasTargetSRF);
 	WRITE_NODE_FIELD(cteList);
 	WRITE_NODE_FIELD(rtable);
 	WRITE_NODE_FIELD(jointree);
@@ -2852,6 +2853,7 @@ _outRangeTblEntry(StringInfo str, const RangeTblEntry *node)
 	WRITE_BITMAPSET_FIELD(insertedCols);
 	WRITE_BITMAPSET_FIELD(updatedCols);
 	WRITE_NODE_FIELD(securityQuals);
+	WRITE_NODE_FIELD(deps);
 }
 
 static void
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index 94954dc..ca7d20c 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -244,6 +244,7 @@ _readQuery(void)
 	READ_BOOL_FIELD(hasModifyingCTE);
 	READ_BOOL_FIELD(hasForUpdate);
 	READ_BOOL_FIELD(hasRowSecurity);
+	READ_BOOL_FIELD(hasTargetSRF);
 	READ_NODE_FIELD(cteList);
 	READ_NODE_FIELD(rtable);
 	READ_NODE_FIELD(jointree);
@@ -1322,6 +1323,7 @@ _readRangeTblEntry(void)
 	READ_BITMAPSET_FIELD(insertedCols);
 	READ_BITMAPSET_FIELD(updatedCols);
 	READ_NODE_FIELD(securityQuals);
+	READ_NODE_FIELD(deps);
 
 	READ_DONE();
 }
diff --git a/src/backend/optimizer/plan/initsplan.c b/src/backend/optimizer/plan/initsplan.c
index 84ce6b3..ada34cc 100644
--- a/src/backend/optimizer/plan/initsplan.c
+++ b/src/backend/optimizer/plan/initsplan.c
@@ -339,6 +339,10 @@ extract_lateral_references(PlannerInfo *root, RelOptInfo *brel, Index rtindex)
 		return;					/* keep compiler quiet */
 	}
 
+	/* DIRTY hack time, add dependency for targetlist SRFs */
+	vars = list_concat(vars,
+					   pull_vars_of_level((Node *) rte->deps, 0));
+
 	if (vars == NIL)
 		return;					/* nothing to do */
 
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index b265628..ffc1c85 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -490,6 +490,14 @@ subquery_planner(PlannerGlobal *glob, Query *parse,
 	root->non_recursive_path = NULL;
 
 	/*
+	 * Convert SRFs in targetlist into FUNCTION rtes. As this, if applicable,
+	 * will move the move the main portion of the query into a subselect, this
+	 * has to be done early on in subquery_planner().
+	 */
+	if (parse->hasTargetSRF)
+		unsrfify(root);
+
+	/*
 	 * If there is a WITH list, process each WITH query and build an initplan
 	 * SubPlan structure for it.
 	 */
diff --git a/src/backend/optimizer/prep/prepjointree.c b/src/backend/optimizer/prep/prepjointree.c
index a334f15..0e06a98 100644
--- a/src/backend/optimizer/prep/prepjointree.c
+++ b/src/backend/optimizer/prep/prepjointree.c
@@ -1982,6 +1982,10 @@ replace_vars_in_jointree(Node *jtnode,
 						Assert(false);
 						break;
 				}
+
+				rte->deps = (List *)
+					pullup_replace_vars((Node *) rte->deps,
+										context);
 			}
 		}
 	}
diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c
index a69af7c..7e60694 100644
--- a/src/backend/optimizer/util/clauses.c
+++ b/src/backend/optimizer/util/clauses.c
@@ -36,6 +36,7 @@
 #include "optimizer/cost.h"
 #include "optimizer/planmain.h"
 #include "optimizer/prep.h"
+#include "optimizer/tlist.h"
 #include "optimizer/var.h"
 #include "parser/analyze.h"
 #include "parser/parse_agg.h"
@@ -94,6 +95,30 @@ typedef struct
 	bool		allow_restricted;
 } has_parallel_hazard_arg;
 
+typedef struct unsrfify_context
+{
+	PlannerInfo *root;
+	/* query being converted */
+	Query *outer_query;
+	/* created subquery */
+	Query *inner_query;
+	/* RT index of the above */
+	Index subquery_rti;
+
+	/* targetlist of the new subquery */
+	List *subquery_tlist;
+	List *subquery_colnames;
+
+	/* RTE for the currently generated function RTE */
+	RangeTblEntry *currte;
+	Index currti; /* and it's RT index */
+	/* current column number in function RTE */
+	int coloff;
+
+	/* current target's resname during expression iteration */
+	char *current_resname;
+} unsrfify_context;
+
 static bool contain_agg_clause_walker(Node *node, void *context);
 static bool get_agg_clause_costs_walker(Node *node,
 							get_agg_clause_costs_context *context);
@@ -2251,6 +2276,562 @@ rowtype_field_matches(Oid rowtypeid, int fieldnum,
 	return true;
 }
 
+/*
+ * Push down expression into the subquery, return resno of targetlist entry.
+ */
+static int
+unsrfify_push_expr_to_subquery(Expr *expr, Index sortgroupref,
+							   unsrfify_context *context)
+{
+	ListCell *tc;
+	int resno = 1;
+	char *resname = context->current_resname;
+	TargetEntry *new_te;
+
+	/*
+	 * Check whether we already moved this expression to subquery, if so,
+	 * reuse.
+	 */
+	foreach(tc, context->subquery_tlist)
+	{
+		TargetEntry *te = (TargetEntry *) lfirst(tc);
+		Expr *oldexpr = te->expr;
+
+		if (equal(oldexpr, expr))
+		{
+			if (sortgroupref > 0)
+			{
+				if (te->ressortgroupref != sortgroupref &&
+					te->ressortgroupref > 0)
+				{
+					/* FIXME: might happen with duplicate expressions? */
+					elog(ERROR, "non-unique ressortgroupref?");
+				}
+				else
+				{
+					te->ressortgroupref = sortgroupref;
+					return resno;
+				}
+			}
+			return resno;
+		}
+		resno++;
+	}
+
+	/* XXX */
+	if (!resname)
+		resname = "...";
+
+	Assert(resno == list_length(context->subquery_tlist) + 1);
+
+	new_te = makeTargetEntry((Expr *) copyObject(expr),
+							 resno, resname , false);
+	new_te->ressortgroupref = sortgroupref;
+	context->subquery_tlist = lappend(context->subquery_tlist, new_te);
+	context->subquery_colnames = lappend(context->subquery_colnames,
+									makeString(context->current_resname));
+
+	return resno;
+}
+
+/*
+ * Change target list to reference subquery.
+ *
+ * TargetEntry's that dont't contain a set returning function are pushed down
+ * entirely, others are modified to have relevant expressions refer to (new)
+ * entries in the subquery targetlist.
+ */
+static Node *
+unsrfify_reference_subquery_mutator(Node *node, unsrfify_context *context)
+{
+	check_stack_depth();
+
+	if (node == NULL)
+		return NULL;
+
+	switch (nodeTag(node))
+	{
+		case T_TargetEntry:
+			{
+				TargetEntry *te = (TargetEntry *) node;
+
+				/*
+				 * Note that we're intentionally pushing down sortgrouprefs,
+				 * that way grouping et al will work. It's more than a bit
+				 * debatable though to do this unconditionally: We'll
+				 * currently end up with sortgrouprefs in both top-level and
+				 * subquery.
+				 */
+
+				/* XXX: naming here isn't great */
+				if (!te->resname)
+					context->current_resname = "...";
+				else
+					context->current_resname = pstrdup(te->resname);
+
+				/* if expression doesn't return set, push down entirely */
+				if (!expression_returns_set((Node *) te->expr))
+				{
+					AttrNumber resno =
+						unsrfify_push_expr_to_subquery(te->expr,
+													   te->ressortgroupref,
+													   context);
+					te = flatCopyTargetEntry(te);
+
+					te->expr = (Expr *) makeVar(context->subquery_rti,
+												resno,
+												exprType((Node *) te->expr),
+												exprTypmod((Node *) te->expr),
+												exprCollation((Node *) te->expr),
+												0);
+				}
+				else
+				{
+					te = (TargetEntry *)
+						expression_tree_mutator((Node *) te,
+												unsrfify_reference_subquery_mutator,
+												(void *) context);
+				}
+
+				context->current_resname = NULL;
+				return (Node *) te;
+			}
+			break;
+			/* Anything additional? */
+		case T_Var:
+		case T_Aggref:
+		case T_GroupingFunc:
+		case T_WindowFunc:
+		case T_Param /* ? */:
+			/*
+			 * Vars, aggrefs, groupingfuncs, ... come from the subquery in
+			 * which the main query is being moved. For each reference in the
+			 * main targetlist - containing the reference to the SRF and such
+			 * - move the underlying clause as a separate TargetEntry into the
+			 * subquery, and reference that.
+			 *
+			 * Note that varlevelsup for expressions in the subquery is later
+			 * adjusted with IncrementVarSublevelsUp, together with the other
+			 * expressions in the subquery.
+			 */
+			{
+				AttrNumber resno =
+					unsrfify_push_expr_to_subquery((Expr *) node, 0, context);
+
+				return (Node *) makeVar(context->subquery_rti,
+										resno,
+										exprType(node),
+										exprTypmod(node),
+										exprCollation(node),
+										0);
+			}
+			return node;
+		default:
+			break;
+	}
+
+	return expression_tree_mutator(node, unsrfify_reference_subquery_mutator,
+								   (void *) context);
+}
+
+static Node *
+unsrfify_implement_srfs_mutator(Node *node, unsrfify_context *context)
+{
+	check_stack_depth();
+
+	if (node == NULL)
+		return NULL;
+	switch (nodeTag(node))
+	{
+		case T_OpExpr:
+			{
+				OpExpr   *expr = (OpExpr *) node;
+
+				if (expr->opretset)
+				{
+					/*
+					 * TODO: Hrmpf, implement. And why is there not a single
+					 * test for this :(
+					 */
+					ereport(ERROR,
+							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+							 errmsg("XXX: SETOF record returning operators are not supported")));
+				}
+			}
+			break;
+
+		case T_FuncExpr:
+			{
+				FuncExpr   *expr = (FuncExpr *) node;
+
+				/*
+				 * For set returning functions, move them to the current
+				 * level's ROWS FROM expression, and add a Var referencing
+				 * that expressions result.
+				 */
+				if (expr->funcretset)
+				{
+					RangeTblEntry *old_currte;
+					Index old_currti;
+					int old_coloff;
+
+					/*
+					 * Process set-returning arguments to set-returning
+					 * functions as a separate ROWS FROM expression, again
+					 * laterally joined to this.
+					 */
+					old_currte = context->currte;
+					old_currti = context->currti;
+					old_coloff = context->coloff;
+
+					context->currte = NULL;
+					context->currti = 0;
+					context->coloff = 0;
+
+					expr->args = (List *)
+						expression_tree_mutator((Node *) expr->args,
+												unsrfify_implement_srfs_mutator,
+												(void *) context);
+					context->currte = old_currte;
+					context->currti = old_currti;
+					context->coloff = old_coloff;
+
+				}
+				else
+				{
+					expr->args = (List *)
+						expression_tree_mutator((Node *) expr->args,
+												unsrfify_implement_srfs_mutator,
+												(void *) context);
+				}
+
+				if (expr->funcretset)
+				{
+					RangeTblEntry *rte;
+					RangeTblFunction *rtfunc;
+					RangeTblRef *rtf;
+					Index rti;
+					TypeFuncClass functypclass;
+					TupleDesc tupdesc;
+					Oid funcrettype;
+					/* FIXME: used in places it shouldn't */
+					char *funcname = get_func_name(expr->funcid);
+					int i;
+
+					functypclass = get_expr_result_type(node,
+														&funcrettype,
+														&tupdesc);
+
+					if (functypclass == TYPEFUNC_COMPOSITE)
+					{
+						/* Composite data type, e.g. a table's row type */
+						Assert(tupdesc);
+					}
+					else if (functypclass == TYPEFUNC_SCALAR)
+					{
+						/* Base data type, i.e. scalar */
+						tupdesc = CreateTemplateTupleDesc(1, false);
+						TupleDescInitEntry(tupdesc,
+										   (AttrNumber) 1,
+										   funcname,
+										   funcrettype,
+										   -1,
+										   0);
+					}
+					else if (functypclass == TYPEFUNC_RECORD)
+					{
+						/* Add ROWS FROM() feature to support this? */
+						ereport(ERROR,
+								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+								 errmsg("XXX: SETOF record returning functions are not allowed in target list")));
+					}
+					else
+					{
+						Assert(false);
+					}
+
+					if (context->currte == NULL)
+					{
+						Alias	   *eref;
+
+						rte = makeNode(RangeTblEntry);
+						rte->rtekind = RTE_FUNCTION;
+						rte->lateral = true;
+						rte->inh = false;
+						rte->inFromCl = true;
+
+						eref = makeAlias(funcname, NIL);
+
+						rte->eref = eref;
+
+						rte->funcordinality = false;
+
+						/*
+						 * DIRTY hack time: add LATERAL dependency to the
+						 * subquery containing the original query. That forces
+						 * the planner to evaluate the subquery first
+						 * (i.e. nestloop subquery to SRF, not the other way
+						 * round), persisting the output ordering of the SRF.
+						 */
+						rte->deps = list_make1(makeVar(context->subquery_rti, 0, RECORDOID, -1, InvalidOid, 0));
+
+						context->outer_query->rtable =
+							lappend(context->outer_query->rtable, rte);
+
+						rti = list_length(context->outer_query->rtable);
+
+						rtf = makeNode(RangeTblRef);
+						rtf->rtindex = rti;
+
+						context->outer_query->jointree->fromlist =
+							lappend(context->outer_query->jointree->fromlist, rtf);
+
+						context->currte = rte;
+						context->currti = rti;
+					}
+					else
+					{
+						rte = context->currte;
+						rti = context->currti;
+					}
+
+					/* add SRF RTE */
+					rtfunc = makeNode(RangeTblFunction);
+					rtfunc->funcexpr = (Node *) expr;
+					rtfunc->funccolcount = tupdesc ? tupdesc->natts : 1;
+
+					rte->functions = lappend(rte->functions, rtfunc);
+
+					if (functypclass == TYPEFUNC_SCALAR)
+					{
+						rte->eref->colnames = lappend(rte->eref->colnames,
+													  makeString(funcname));
+
+						/* replace reference to RTE */
+						return (Node *) makeVar(rti,
+												++context->coloff,
+												funcrettype,
+												exprTypmod(node),
+												expr->funccollid,
+												0);
+					}
+					else
+					{
+						/*
+						 * targetlist SRFs returning a composite type have all
+						 * columns in one field. ROWS FROM returns all columns
+						 * separately. Construct a ROW(a,b,c, ...) expression,
+						 * referring to the ROWS FROM expression output.
+						 */
+						RowExpr *row = makeNode(RowExpr);
+
+						row->row_typeid = funcrettype;
+
+						for (i = 0; i < tupdesc->natts; i++)
+						{
+							Form_pg_attribute attr = tupdesc->attrs[i];
+							Var *var = makeVar(rti,
+											   ++context->coloff,
+											   attr->atttypid,
+											   attr->atttypmod,
+											   attr->attcollation,
+											   0);
+							row->args = lappend(row->args, var);
+							row->colnames = lappend(row->colnames,
+								makeString(pstrdup(NameStr(attr->attname))));
+							rte->eref->colnames = lappend(rte->eref->colnames,
+														  makeString(pstrdup(NameStr(attr->attname))));
+						}
+
+						return (Node *) row;
+					}
+				}
+			}
+			break;
+		default:
+			break;
+	}
+
+	return expression_tree_mutator(node, unsrfify_implement_srfs_mutator,
+								   (void *) context);
+}
+
+/*
+ * Implement set-returning-functions in the targetlist using ROWS FROM() in
+ * the from list.
+ */
+void
+unsrfify(PlannerInfo *root)
+{
+	unsrfify_context context;
+	Query *outer_query = root->parse;
+	List *outerOldTlist = root->parse->targetList;
+	bool sortContainsSRF = false;
+	Query *inner_query;
+	RangeTblEntry *rte;
+	RangeTblRef *rtf;
+	ListCell *lc;
+
+	/* skip work if targetlist doesn't contain an SRF */
+	if (!expression_returns_set((Node *) root->parse->targetList))
+	{
+		return;
+	}
+
+	inner_query = makeNode(Query);
+	rte = makeNode(RangeTblEntry);
+	rtf = makeNode(RangeTblRef);
+
+	memset(&context, 0, sizeof(context));
+	context.root = root;
+	context.outer_query = outer_query;
+	context.inner_query = inner_query;
+
+	/* check whether sorting has to be performed before/after SRF processing */
+	foreach(lc, root->parse->sortClause)
+	{
+		SortGroupClause *sgc = lfirst(lc);
+		Node *sortExpr = get_sortgroupclause_expr(sgc, root->parse->targetList);
+
+		if (expression_returns_set(sortExpr))
+		{
+			sortContainsSRF = true;
+			break;
+		}
+	}
+
+	/*
+	 * Move main query processing into a subquery. Otherwise aggregates will
+	 * possibly process more rows, due to the SRF expanding the result set. We
+	 * could perform this work conditionally, but that seems like an
+	 * unnecessary complication.
+	 *
+	 * If the query has an order-by, but that order-by does not reference SRF
+	 * output, then SRF expansion should happen after the sort, for two
+	 * reasons: Firstly, to process fewer rows. Secondly, to have less
+	 * confusing results, if the output of the SRF are sorted.
+	 */
+	rte->rtekind = RTE_SUBQUERY;
+	rte->subquery = inner_query;
+	rte->security_barrier = false;
+	context.subquery_rti = list_length(outer_query->rtable) + 1;
+	rtf->rtindex = context.subquery_rti;
+
+	inner_query->commandType = CMD_SELECT;
+	inner_query->querySource = QSRC_TARGETLIST_SRF;
+	inner_query->canSetTag = true;
+
+	/*
+	 * Copy the range-table, without resetting it on the outside. If the outer
+	 * query is a data-modifying one, resultRelation needs to point to the
+	 * actually modified table.  XXX: But that doesn't work at all for
+	 * UPDATEs, because there expand_targetlist() will add Vars pointing to
+	 * the result relation.
+	 */
+	inner_query->rtable = copyObject(outer_query->rtable);
+
+	if (outer_query->commandType == CMD_UPDATE)
+		elog(ERROR, "what does this SRF mean anyway?");
+
+	inner_query->jointree = outer_query->jointree;
+
+	inner_query->hasAggs = outer_query->hasAggs;
+	outer_query->hasAggs = false; /* moved to subquery */
+
+	inner_query->hasWindowFuncs = outer_query->hasWindowFuncs; /* FIXME */
+	outer_query->hasWindowFuncs = false;
+
+	/* can still be present in outer query */
+	inner_query->hasSubLinks = outer_query->hasSubLinks;
+
+	/*
+	 * CTEs stay on outer level, IncrementVarSublevelsUp adjusts ctelevelsup.
+	 */
+	inner_query->hasRecursive = false;
+	inner_query->hasModifyingCTE = false;
+
+	inner_query->hasForUpdate = false;
+
+	inner_query->hasRowSecurity = outer_query->hasRowSecurity;
+
+	/* we've expanded everything */
+	outer_query->hasTargetSRF = false;
+
+	outer_query->rtable = lappend(outer_query->rtable, rte);
+
+	outer_query->jointree = makeFromExpr(list_make1(rtf), NULL);
+
+	/* targetlist is set later */
+
+	 /* not modifying */
+	inner_query->onConflict = NULL;
+	inner_query->returningList = NIL;
+
+	/* transfer group / window related clauses to child */
+	inner_query->groupClause = outer_query->groupClause;
+	outer_query->groupClause = NIL;
+
+	inner_query->groupingSets = outer_query->groupingSets;
+	outer_query->groupingSets = NIL;
+
+	inner_query->havingQual = outer_query->havingQual;
+	outer_query->havingQual = NULL;
+
+	inner_query->windowClause = outer_query->windowClause;
+	outer_query->windowClause = NIL;
+
+	/* DISTINCT [ON] is computed outside */
+
+	/* sort is computed in sub query, unless referencing SRF output */
+	/* XXX: what about combinations with DISTINCT? */
+	if (!sortContainsSRF && list_length(outer_query->sortClause) > 0)
+	{
+		inner_query->sortClause = outer_query->sortClause;
+		outer_query->sortClause = NIL;
+	}
+
+
+	/* limit is processed after SRF expansion */
+
+	/* XXX: where should row marks be processed? */
+
+	/* XXX: where should set operations be processed? */
+	inner_query->setOperations = outer_query->setOperations;
+	outer_query->setOperations = NULL;
+
+	/* constraints should stay on top level */
+
+	/* XXX: where should WITH CHECK options be processed? */
+
+	/*
+	 * Update the outer query's targetlist to reference subquery for all
+	 * Vars, Aggs and such.
+	 */
+	outer_query->targetList = (List *)
+		unsrfify_reference_subquery_mutator((Node *) outerOldTlist,
+											&context);
+	/*
+	 * Now convert all targetlist SRFs into FUNCTION RTEs.
+	 */
+	outer_query->targetList = (List *)
+		unsrfify_implement_srfs_mutator((Node *) outer_query->targetList,
+										&context);
+
+
+	rte->eref = makeAlias("srf", context.subquery_colnames);
+
+	inner_query->targetList = context.subquery_tlist;
+
+	/*
+	 * varlevelsup for expression not local to the query (i.e. varlevelsup >
+	 * 0) have to be increased by one, to adjust for the additional layer of
+	 * subquery added. Do so after the above processing populating the
+	 * subselect's targetlist, to avoid having to deal with varlevelsup in
+	 * multiple places.
+	 */
+	IncrementVarSublevelsUp((Node *) inner_query, 1, 1);
+}
+
 
 /*--------------------
  * eval_const_expressions
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index eac86cc..4e0d095 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -418,6 +418,7 @@ transformDeleteStmt(ParseState *pstate, DeleteStmt *stmt)
 	qry->hasSubLinks = pstate->p_hasSubLinks;
 	qry->hasWindowFuncs = pstate->p_hasWindowFuncs;
 	qry->hasAggs = pstate->p_hasAggs;
+	qry->hasTargetSRF = pstate->p_hasTargetSRF;
 	if (pstate->p_hasAggs)
 		parseCheckAggregates(pstate, qry);
 
@@ -820,6 +821,7 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
 	qry->jointree = makeFromExpr(pstate->p_joinlist, NULL);
 
 	qry->hasSubLinks = pstate->p_hasSubLinks;
+	qry->hasTargetSRF = pstate->p_hasTargetSRF;
 
 	assign_query_collations(pstate, qry);
 
@@ -1232,6 +1234,7 @@ transformSelectStmt(ParseState *pstate, SelectStmt *stmt)
 	qry->hasSubLinks = pstate->p_hasSubLinks;
 	qry->hasWindowFuncs = pstate->p_hasWindowFuncs;
 	qry->hasAggs = pstate->p_hasAggs;
+	qry->hasTargetSRF = pstate->p_hasTargetSRF;
 	if (pstate->p_hasAggs || qry->groupClause || qry->groupingSets || qry->havingQual)
 		parseCheckAggregates(pstate, qry);
 
@@ -1463,6 +1466,11 @@ transformValuesClause(ParseState *pstate, SelectStmt *stmt)
 
 	qry->hasSubLinks = pstate->p_hasSubLinks;
 
+	if (pstate->p_hasTargetSRF)
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("set-valued function called in context that cannot accept a set")));
+
 	assign_query_collations(pstate, qry);
 
 	return qry;
@@ -1692,6 +1700,7 @@ transformSetOperationStmt(ParseState *pstate, SelectStmt *stmt)
 	qry->hasSubLinks = pstate->p_hasSubLinks;
 	qry->hasWindowFuncs = pstate->p_hasWindowFuncs;
 	qry->hasAggs = pstate->p_hasAggs;
+	qry->hasTargetSRF = pstate->p_hasTargetSRF;
 	if (pstate->p_hasAggs || qry->groupClause || qry->groupingSets || qry->havingQual)
 		parseCheckAggregates(pstate, qry);
 
@@ -2171,6 +2180,7 @@ transformUpdateStmt(ParseState *pstate, UpdateStmt *stmt)
 	qry->jointree = makeFromExpr(pstate->p_joinlist, qual);
 
 	qry->hasSubLinks = pstate->p_hasSubLinks;
+	qry->hasTargetSRF = pstate->p_hasTargetSRF;
 
 	assign_query_collations(pstate, qry);
 
diff --git a/src/backend/parser/parse_func.c b/src/backend/parser/parse_func.c
index 61af484..770903d 100644
--- a/src/backend/parser/parse_func.c
+++ b/src/backend/parser/parse_func.c
@@ -625,6 +625,11 @@ ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs,
 									  exprLocation((Node *) llast(fargs)))));
 	}
 
+	if (retset)
+	{
+		pstate->p_hasTargetSRF = true;
+	}
+
 	/* build the appropriate output structure */
 	if (fdresult == FUNCDETAIL_NORMAL)
 	{
diff --git a/src/backend/parser/parse_oper.c b/src/backend/parser/parse_oper.c
index e913d05..0a1a0f1 100644
--- a/src/backend/parser/parse_oper.c
+++ b/src/backend/parser/parse_oper.c
@@ -841,6 +841,11 @@ make_op(ParseState *pstate, List *opname, Node *ltree, Node *rtree,
 
 	ReleaseSysCache(tup);
 
+	if (result->opretset)
+	{
+		pstate->p_hasTargetSRF = true;
+	}
+
 	return (Expr *) result;
 }
 
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 1481fff..2d3081e 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -32,7 +32,8 @@ typedef enum QuerySource
 	QSRC_PARSER,				/* added by parse analysis (now unused) */
 	QSRC_INSTEAD_RULE,			/* added by unconditional INSTEAD rule */
 	QSRC_QUAL_INSTEAD_RULE,		/* added by conditional INSTEAD rule */
-	QSRC_NON_INSTEAD_RULE		/* added by non-INSTEAD rule */
+	QSRC_NON_INSTEAD_RULE,		/* added by non-INSTEAD rule */
+	QSRC_TARGETLIST_SRF			/* added by targetlist SRF processing */
 } QuerySource;
 
 /* Sort ordering options for ORDER BY and CREATE INDEX */
@@ -122,6 +123,7 @@ typedef struct Query
 	bool		hasModifyingCTE;	/* has INSERT/UPDATE/DELETE in WITH */
 	bool		hasForUpdate;	/* FOR [KEY] UPDATE/SHARE was specified */
 	bool		hasRowSecurity; /* rewriter has applied some RLS policy */
+	bool		hasTargetSRF;	/* has SRF in target list */
 
 	List	   *cteList;		/* WITH list (of CommonTableExpr's) */
 
@@ -871,6 +873,8 @@ typedef struct RangeTblEntry
 	Bitmapset  *insertedCols;	/* columns needing INSERT permission */
 	Bitmapset  *updatedCols;	/* columns needing UPDATE permission */
 	List	   *securityQuals;	/* any security barrier quals to apply */
+
+	List		*deps;
 } RangeTblEntry;
 
 /*
diff --git a/src/include/optimizer/clauses.h b/src/include/optimizer/clauses.h
index be7c639..71d4e12 100644
--- a/src/include/optimizer/clauses.h
+++ b/src/include/optimizer/clauses.h
@@ -78,6 +78,8 @@ extern int	NumRelids(Node *clause);
 extern void CommuteOpExpr(OpExpr *clause);
 extern void CommuteRowCompareExpr(RowCompareExpr *clause);
 
+extern void unsrfify(PlannerInfo *root);
+
 extern Node *eval_const_expressions(PlannerInfo *root, Node *node);
 
 extern Node *estimate_expression_value(PlannerInfo *root, Node *node);
diff --git a/src/include/parser/parse_node.h b/src/include/parser/parse_node.h
index e3e359c..c0eec33 100644
--- a/src/include/parser/parse_node.h
+++ b/src/include/parser/parse_node.h
@@ -152,6 +152,7 @@ struct ParseState
 	bool		p_hasWindowFuncs;
 	bool		p_hasSubLinks;
 	bool		p_hasModifyingCTE;
+	bool		p_hasTargetSRF;
 	bool		p_is_insert;
 	bool		p_locked_from_parent;
 	Relation	p_target_relation;
-- 
2.8.1

