From f18baaeb55803c179e54a4c592532ed27cf8a815 Mon Sep 17 00:00:00 2001 From: Andres Freund Date: Fri, 29 Jul 2016 18:51:02 -0700 Subject: [PATCH 5/6] 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 | 2 + 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 | 549 ++++++++++++++++++++++++++++++ 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 + src/test/regress/expected/aggregates.out | 21 +- src/test/regress/expected/limit.out | 72 ++-- src/test/regress/expected/portals.out | 12 +- src/test/regress/expected/rangefuncs.out | 10 +- src/test/regress/expected/subselect.out | 29 +- src/test/regress/expected/tsrf.out | 15 +- src/test/regress/expected/union.out | 8 +- src/test/regress/output/misc.source | 18 +- 23 files changed, 716 insertions(+), 78 deletions(-) diff --git a/src/backend/executor/execQual.c b/src/backend/executor/execQual.c index 79589d0..d9e2797 100644 --- a/src/backend/executor/execQual.c +++ b/src/backend/executor/execQual.c @@ -2074,6 +2074,13 @@ ExecEvalFunc(FuncExprState *fcache, ExecInitFcache(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 292ab6c..589fc27 100644 --- a/src/backend/nodes/copyfuncs.c +++ b/src/backend/nodes/copyfuncs.c @@ -2167,6 +2167,7 @@ _copyRangeTblEntry(const RangeTblEntry *from) COPY_BITMAPSET_FIELD(insertedCols); COPY_BITMAPSET_FIELD(updatedCols); COPY_NODE_FIELD(securityQuals); + COPY_NODE_FIELD(deps); return newnode; } @@ -2749,6 +2750,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 f69dc8e..b24e623 100644 --- a/src/backend/nodes/equalfuncs.c +++ b/src/backend/nodes/equalfuncs.c @@ -927,6 +927,7 @@ _equalQuery(const Query *a, const Query *b) COMPARE_SCALAR_FIELD(hasModifyingCTE); COMPARE_SCALAR_FIELD(hasForUpdate); COMPARE_SCALAR_FIELD(hasRowSecurity); + COMPARE_SCALAR_FIELD(hasTargetSRF); COMPARE_NODE_FIELD(cteList); COMPARE_NODE_FIELD(rtable); COMPARE_NODE_FIELD(jointree); @@ -2480,6 +2481,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 bea295b..86cd575 100644 --- a/src/backend/nodes/outfuncs.c +++ b/src/backend/nodes/outfuncs.c @@ -2688,6 +2688,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); @@ -2865,6 +2866,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 70bb73d..174b02f 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); @@ -1338,6 +1339,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 174210b..986c92b 100644 --- a/src/backend/optimizer/plan/planner.c +++ b/src/backend/optimizer/plan/planner.c @@ -505,6 +505,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 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 3830bc9..9c502bd 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" @@ -95,6 +96,30 @@ typedef struct char max_interesting; /* worst proparallel hazard of interest */ } max_parallel_hazard_context; +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); @@ -2390,6 +2415,530 @@ 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; + Oid funcrettype; + /* FIXME: used in places it shouldn't */ + char *funcname = get_func_name(expr->funcid); + bool asrecord; + + funcrettype = exprType(node); + + asrecord = type_is_rowtype(funcrettype); + + 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 = 1; + rtfunc->funcasrecord = asrecord; + + rte->functions = lappend(rte->functions, rtfunc); + + 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); + } + } + 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; + bool groupContainsSRF = 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; + } + + Assert(outer_query->commandType != CMD_UPDATE); + + 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; + } + } + + /* check whether sorting has to be performed before/after SRF processing */ + foreach(lc, root->parse->groupClause) + { + SortGroupClause *sgc = lfirst(lc); + Node *groupExpr = get_sortgroupclause_expr(sgc, root->parse->targetList); + + if (expression_returns_set(groupExpr)) + { + groupContainsSRF = 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); + + inner_query->jointree = outer_query->jointree; + + /* + * Transfer group / window computation to child, unless referencing SRF + * output. + */ + if (!groupContainsSRF) + { + inner_query->hasAggs = outer_query->hasAggs; + outer_query->hasAggs = false; /* moved to subquery */ + } + else + { + inner_query->hasAggs = false; + } + + 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, unless referencing + * SRF output. + */ + if (!groupContainsSRF && list_length(outer_query->groupClause) > 0) + { + 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 cf5bc86..f81db37 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 f26e651..969db8c 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) */ @@ -879,6 +881,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 9abef37..7fb5005 100644 --- a/src/include/optimizer/clauses.h +++ b/src/include/optimizer/clauses.h @@ -79,6 +79,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; diff --git a/src/test/regress/expected/aggregates.out b/src/test/regress/expected/aggregates.out index 45208a6..b791572 100644 --- a/src/test/regress/expected/aggregates.out +++ b/src/test/regress/expected/aggregates.out @@ -814,16 +814,19 @@ select max(unique2) from tenk1 order by max(unique2)+1; explain (costs off) select max(unique2), generate_series(1,3) as g from tenk1 order by g desc; - QUERY PLAN ---------------------------------------------------------------------- + QUERY PLAN +--------------------------------------------------------------------------------------- Sort - Sort Key: (generate_series(1, 3)) DESC - InitPlan 1 (returns $0) - -> Limit - -> Index Only Scan Backward using tenk1_unique2 on tenk1 - Index Cond: (unique2 IS NOT NULL) - -> Result -(7 rows) + Sort Key: generate_series.generate_series DESC + -> Nested Loop + -> Subquery Scan on srf + -> Result + InitPlan 1 (returns $0) + -> Limit + -> Index Only Scan Backward using tenk1_unique2 on tenk1 + Index Cond: (unique2 IS NOT NULL) + -> Function Scan on generate_series +(10 rows) select max(unique2), generate_series(1,3) as g from tenk1 order by g desc; max | g diff --git a/src/test/regress/expected/limit.out b/src/test/regress/expected/limit.out index 9c3eecf..45f0c38 100644 --- a/src/test/regress/expected/limit.out +++ b/src/test/regress/expected/limit.out @@ -208,13 +208,20 @@ select currval('testseq'); explain (verbose, costs off) select unique1, unique2, generate_series(1,10) from tenk1 order by unique2 limit 7; - QUERY PLAN ----------------------------------------------------------- + QUERY PLAN +--------------------------------------------------------------------------- Limit - Output: unique1, unique2, (generate_series(1, 10)) - -> Index Scan using tenk1_unique2 on public.tenk1 - Output: unique1, unique2, generate_series(1, 10) -(4 rows) + Output: srf.unique1, srf.unique2, generate_series.generate_series + -> Nested Loop + Output: srf.unique1, srf.unique2, generate_series.generate_series + -> Subquery Scan on srf + Output: srf.unique1, srf.unique2, srf.* + -> Index Scan using tenk1_unique2 on public.tenk1 + Output: tenk1.unique1, tenk1.unique2 + -> Function Scan on pg_catalog.generate_series + Output: generate_series.generate_series + Function Call: generate_series(1, 10) +(11 rows) select unique1, unique2, generate_series(1,10) from tenk1 order by unique2 limit 7; @@ -232,18 +239,23 @@ select unique1, unique2, generate_series(1,10) explain (verbose, costs off) select unique1, unique2, generate_series(1,10) from tenk1 order by tenthous limit 7; - QUERY PLAN --------------------------------------------------------------------- + QUERY PLAN +-------------------------------------------------------------------------------------- Limit - Output: unique1, unique2, (generate_series(1, 10)), tenthous - -> Result - Output: unique1, unique2, generate_series(1, 10), tenthous - -> Sort - Output: unique1, unique2, tenthous - Sort Key: tenk1.tenthous - -> Seq Scan on public.tenk1 - Output: unique1, unique2, tenthous -(9 rows) + Output: srf.unique1, srf.unique2, generate_series.generate_series, srf."..." + -> Nested Loop + Output: srf.unique1, srf.unique2, generate_series.generate_series, srf."..." + -> Subquery Scan on srf + Output: srf.unique1, srf.unique2, srf."...", srf.* + -> Sort + Output: tenk1.unique1, tenk1.unique2, tenk1.tenthous + Sort Key: tenk1.tenthous + -> Seq Scan on public.tenk1 + Output: tenk1.unique1, tenk1.unique2, tenk1.tenthous + -> Function Scan on pg_catalog.generate_series + Output: generate_series.generate_series + Function Call: generate_series(1, 10) +(14 rows) select unique1, unique2, generate_series(1,10) from tenk1 order by tenthous limit 7; @@ -261,11 +273,12 @@ select unique1, unique2, generate_series(1,10) -- use of random() is to keep planner from folding the expressions together explain (verbose, costs off) select generate_series(0,2) as s1, generate_series((random()*.1)::int,2) as s2; - QUERY PLAN ------------------------------------------------------------------------------------------------------- - Result - Output: generate_series(0, 2), generate_series(((random() * '0.1'::double precision))::integer, 2) -(2 rows) + QUERY PLAN +------------------------------------------------------------------------------------------------------------- + Function Scan on generate_series + Output: generate_series.generate_series, generate_series.generate_series_1 + Function Call: generate_series(0, 2), generate_series(((random() * '0.1'::double precision))::integer, 2) +(3 rows) select generate_series(0,2) as s1, generate_series((random()*.1)::int,2) as s2; s1 | s2 @@ -278,14 +291,15 @@ select generate_series(0,2) as s1, generate_series((random()*.1)::int,2) as s2; explain (verbose, costs off) select generate_series(0,2) as s1, generate_series((random()*.1)::int,2) as s2 order by s2 desc; - QUERY PLAN ------------------------------------------------------------------------------------------------------------- + QUERY PLAN +------------------------------------------------------------------------------------------------------------------- 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 - Output: generate_series(0, 2), generate_series(((random() * '0.1'::double precision))::integer, 2) -(5 rows) + Output: generate_series.generate_series, generate_series.generate_series_1 + Sort Key: generate_series.generate_series_1 DESC + -> Function Scan on generate_series + Output: generate_series.generate_series, generate_series.generate_series_1 + Function Call: generate_series(0, 2), generate_series(((random() * '0.1'::double precision))::integer, 2) +(6 rows) select generate_series(0,2) as s1, generate_series((random()*.1)::int,2) as s2 order by s2 desc; diff --git a/src/test/regress/expected/portals.out b/src/test/regress/expected/portals.out index 3ae918a..7530bb8 100644 --- a/src/test/regress/expected/portals.out +++ b/src/test/regress/expected/portals.out @@ -1320,16 +1320,16 @@ fetch backward all in c1; rollback; begin; explain (costs off) declare c2 cursor for select generate_series(1,3) as g; - QUERY PLAN ------------- - Result + QUERY PLAN +---------------------------------- + Function Scan on generate_series (1 row) explain (costs off) declare c2 scroll cursor for select generate_series(1,3) as g; - QUERY PLAN --------------- + QUERY PLAN +---------------------------------------- Materialize - -> Result + -> Function Scan on generate_series (2 rows) declare c2 scroll cursor for select generate_series(1,3) as g; diff --git a/src/test/regress/expected/rangefuncs.out b/src/test/regress/expected/rangefuncs.out index 249dc67..635aa50 100644 --- a/src/test/regress/expected/rangefuncs.out +++ b/src/test/regress/expected/rangefuncs.out @@ -2080,12 +2080,10 @@ SELECT *, END) FROM (VALUES (1,''), (2,'0000000049404'), (3,'FROM 10000000876')) v(id, str); - id | str | lower -----+------------------+------------------ - 1 | | - 2 | 0000000049404 | 49404 - 3 | FROM 10000000876 | from 10000000876 -(3 rows) + id | str | lower +----+---------------+------- + 2 | 0000000049404 | 49404 +(1 row) -- check whole-row-Var handling in nested lateral functions (bug #11703) create function extractq2(t int8_tbl) returns int8 as $$ diff --git a/src/test/regress/expected/subselect.out b/src/test/regress/expected/subselect.out index 0fc93d9..569784d 100644 --- a/src/test/regress/expected/subselect.out +++ b/src/test/regress/expected/subselect.out @@ -807,24 +807,31 @@ select * from int4_tbl where explain (verbose, costs off) select * from int4_tbl o where (f1, f1) in (select f1, generate_series(1,2) / 10 g from int4_tbl i group by f1); - QUERY PLAN ----------------------------------------------------------------- - Hash Semi Join + QUERY PLAN +---------------------------------------------------------------------------- + Nested Loop Semi Join Output: o.f1 - Hash Cond: (o.f1 = "ANY_subquery".f1) + Join Filter: (o.f1 = "ANY_subquery".f1) -> Seq Scan on public.int4_tbl o Output: o.f1 - -> Hash + -> Materialize Output: "ANY_subquery".f1, "ANY_subquery".g -> Subquery Scan on "ANY_subquery" Output: "ANY_subquery".f1, "ANY_subquery".g Filter: ("ANY_subquery".f1 = "ANY_subquery".g) - -> HashAggregate - Output: i.f1, (generate_series(1, 2) / 10) - Group Key: i.f1 - -> Seq Scan on public.int4_tbl i - Output: i.f1 -(15 rows) + -> Nested Loop + Output: srf.f1, (generate_series.generate_series / 10) + -> Subquery Scan on srf + Output: srf.f1, srf.* + -> HashAggregate + Output: i.f1 + Group Key: i.f1 + -> Seq Scan on public.int4_tbl i + Output: i.f1 + -> Function Scan on pg_catalog.generate_series + Output: generate_series.generate_series + Function Call: generate_series(1, 2) +(22 rows) select * from int4_tbl o where (f1, f1) in (select f1, generate_series(1,2) / 10 g from int4_tbl i group by f1); diff --git a/src/test/regress/expected/tsrf.out b/src/test/regress/expected/tsrf.out index f520a91..4a7ab6d 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 @@ -43,7 +43,16 @@ SELECT generate_series(1, generate_series(1, 3)); -- srf, with two SRF arguments SELECT generate_series(generate_series(1,3), generate_series(2, 4)); -ERROR: functions and operators can take at most one set argument + generate_series +----------------- + 1 + 2 + 2 + 3 + 3 + 4 +(6 rows) + CREATE TABLE few(id int, dataa text, datab text); INSERT INTO few VALUES(1, 'a', 'foo'),(2, 'a', 'bar'),(3, 'b', 'bar'); -- SRF output order of sorting is maintained, if SRF is not referenced diff --git a/src/test/regress/expected/union.out b/src/test/regress/expected/union.out index 016571b..04e9765 100644 --- a/src/test/regress/expected/union.out +++ b/src/test/regress/expected/union.out @@ -622,16 +622,16 @@ SELECT * FROM SELECT 2 AS t, 4 AS x) ss WHERE x < 4 ORDER BY x; - QUERY PLAN --------------------------------------------------------- + QUERY PLAN +--------------------------------------------------------------- Sort Sort Key: ss.x -> Subquery Scan on ss Filter: (ss.x < 4) -> HashAggregate - Group Key: (1), (generate_series(1, 10)) + Group Key: (1), generate_series.generate_series -> Append - -> Result + -> Function Scan on generate_series -> Result (9 rows) diff --git a/src/test/regress/output/misc.source b/src/test/regress/output/misc.source index 5c88aad..d8d87cf 100644 --- a/src/test/regress/output/misc.source +++ b/src/test/regress/output/misc.source @@ -511,7 +511,7 @@ SELECT p.name, name(p.hobbies), name(equipment(p.hobbies)) FROM ONLY person p; name | name | name -------+-------------+--------------- mike | posthacking | advil - mike | posthacking | peet's coffee + mike | | peet's coffee joe | basketball | hightops sally | basketball | hightops (4 rows) @@ -523,11 +523,11 @@ SELECT p.name, name(p.hobbies), name(equipment(p.hobbies)) FROM person* p; name | name | name -------+-------------+--------------- mike | posthacking | advil - mike | posthacking | peet's coffee + mike | | peet's coffee joe | basketball | hightops sally | basketball | hightops jeff | posthacking | advil - jeff | posthacking | peet's coffee + jeff | | peet's coffee (6 rows) -- @@ -538,7 +538,7 @@ SELECT name(equipment(p.hobbies)), p.name, name(p.hobbies) FROM ONLY person p; name | name | name ---------------+-------+------------- advil | mike | posthacking - peet's coffee | mike | posthacking + peet's coffee | mike | hightops | joe | basketball hightops | sally | basketball (4 rows) @@ -547,18 +547,18 @@ SELECT (p.hobbies).equipment.name, p.name, name(p.hobbies) FROM person* p; name | name | name ---------------+-------+------------- advil | mike | posthacking - peet's coffee | mike | posthacking + peet's coffee | mike | hightops | joe | basketball hightops | sally | basketball advil | jeff | posthacking - peet's coffee | jeff | posthacking + peet's coffee | jeff | (6 rows) SELECT (p.hobbies).equipment.name, name(p.hobbies), p.name FROM ONLY person p; name | name | name ---------------+-------------+------- advil | posthacking | mike - peet's coffee | posthacking | mike + peet's coffee | | mike hightops | basketball | joe hightops | basketball | sally (4 rows) @@ -567,11 +567,11 @@ SELECT name(equipment(p.hobbies)), name(p.hobbies), p.name FROM person* p; name | name | name ---------------+-------------+------- advil | posthacking | mike - peet's coffee | posthacking | mike + peet's coffee | | mike hightops | basketball | joe hightops | basketball | sally advil | posthacking | jeff - peet's coffee | posthacking | jeff + peet's coffee | | jeff (6 rows) SELECT user_relns() AS user_relns -- 2.9.3