From acc5949aaf61352ff3dd0cca2d7e921a8b8746d0 Mon Sep 17 00:00:00 2001
From: Andres Freund
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