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