diff --git a/src/backend/executor/nodeRecursiveunion.c b/src/backend/executor/nodeRecursiveunion.c index 620414a1ed..50f59ff0b9 100644 --- a/src/backend/executor/nodeRecursiveunion.c +++ b/src/backend/executor/nodeRecursiveunion.c @@ -104,7 +104,9 @@ ExecRecursiveUnion(PlanState *pstate) /* Each non-duplicate tuple goes to the working table ... */ tuplestore_puttupleslot(node->working_table, slot); /* ... and to the caller */ - return slot; + if (!node->iterating) { + return slot; + } } node->recursing = true; } @@ -116,8 +118,33 @@ ExecRecursiveUnion(PlanState *pstate) if (TupIsNull(slot)) { /* Done if there's nothing in the intermediate table */ - if (node->intermediate_empty) + if (node->intermediate_empty) { + if (!node->iterating) { + return NULL; + } + + if (!TupIsNull(pstate->ps_ResultTupleSlot)) { + tuplestore_select_read_pointer(node->working_table, 1); + (void) tuplestore_gettupleslot(node->working_table, true, true, pstate->ps_ResultTupleSlot); + tuplestore_select_read_pointer(node->working_table, 0); + return pstate->ps_ResultTupleSlot; + } else { + tuplestore_alloc_read_pointer(node->working_table, 0); + + tuplestore_rescan(node->working_table); + + tuplestore_copy_read_pointer(node->working_table, 0, 1); + tuplestore_select_read_pointer(node->working_table, 1); + + tuplestore_gettupleslot(node->working_table, true, true, pstate->ps_ResultTupleSlot); + tuplestore_select_read_pointer(node->working_table, 0); + return pstate->ps_ResultTupleSlot; + } + + return slot; + break; + } /* done with old working table ... */ tuplestore_end(node->working_table); @@ -153,10 +180,16 @@ ExecRecursiveUnion(PlanState *pstate) node->intermediate_empty = false; tuplestore_puttupleslot(node->intermediate_table, slot); /* ... and return it */ - return slot; + if (!node->iterating) { + return slot; + } } - return NULL; + if (node->iterating) { + return pstate->ps_ResultTupleSlot; + } else { + return NULL; + } } /* ---------------------------------------------------------------- @@ -188,6 +221,7 @@ ExecInitRecursiveUnion(RecursiveUnion *node, EState *estate, int eflags) /* initialize processing state */ rustate->recursing = false; + rustate->iterating = node->iterative; rustate->intermediate_empty = true; rustate->working_table = tuplestore_begin_heap(false, false, work_mem); rustate->intermediate_table = tuplestore_begin_heap(false, false, work_mem); @@ -233,6 +267,11 @@ ExecInitRecursiveUnion(RecursiveUnion *node, EState *estate, int eflags) */ ExecInitResultTypeTL(&rustate->ps); + if (rustate->iterating) { + /* XXX FIX */ + rustate->ps.ps_ResultTupleSlot = ExecAllocTableSlot(&estate->es_tupleTable, NULL, &TTSOpsMinimalTuple); + } + /* * Initialize result tuple type. (Note: we have to set up the result type * before initializing child nodes, because nodeWorktablescan.c expects it diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c index 491452ae2d..89ccc82fd6 100644 --- a/src/backend/nodes/copyfuncs.c +++ b/src/backend/nodes/copyfuncs.c @@ -2556,6 +2556,7 @@ _copyWithClause(const WithClause *from) COPY_NODE_FIELD(ctes); COPY_SCALAR_FIELD(recursive); + COPY_SCALAR_FIELD(iterative); COPY_LOCATION_FIELD(location); return newnode; @@ -2599,6 +2600,7 @@ _copyCommonTableExpr(const CommonTableExpr *from) COPY_NODE_FIELD(ctequery); COPY_LOCATION_FIELD(location); COPY_SCALAR_FIELD(cterecursive); + COPY_SCALAR_FIELD(cteiterative); COPY_SCALAR_FIELD(cterefcount); COPY_NODE_FIELD(ctecolnames); COPY_NODE_FIELD(ctecoltypes); @@ -3066,6 +3068,7 @@ _copyQuery(const Query *from) COPY_SCALAR_FIELD(hasSubLinks); COPY_SCALAR_FIELD(hasDistinctOn); COPY_SCALAR_FIELD(hasRecursive); + COPY_SCALAR_FIELD(hasIterative); COPY_SCALAR_FIELD(hasModifyingCTE); COPY_SCALAR_FIELD(hasForUpdate); COPY_SCALAR_FIELD(hasRowSecurity); diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c index bb1565467d..e6da740d6d 100644 --- a/src/backend/nodes/outfuncs.c +++ b/src/backend/nodes/outfuncs.c @@ -468,6 +468,7 @@ _outRecursiveUnion(StringInfo str, const RecursiveUnion *node) WRITE_OID_ARRAY(dupOperators, node->numCols); WRITE_OID_ARRAY(dupCollations, node->numCols); WRITE_LONG_FIELD(numGroups); + WRITE_BOOL_FIELD(iterative); } static void @@ -2254,6 +2255,7 @@ _outPlannerInfo(StringInfo str, const PlannerInfo *node) WRITE_BOOL_FIELD(hasHavingQual); WRITE_BOOL_FIELD(hasPseudoConstantQuals); WRITE_BOOL_FIELD(hasRecursion); + WRITE_BOOL_FIELD(hasIteration); WRITE_INT_FIELD(wt_param_id); WRITE_BITMAPSET_FIELD(curOuterRels); WRITE_NODE_FIELD(curOuterParams); @@ -2942,6 +2944,7 @@ _outQuery(StringInfo str, const Query *node) WRITE_BOOL_FIELD(hasSubLinks); WRITE_BOOL_FIELD(hasDistinctOn); WRITE_BOOL_FIELD(hasRecursive); + WRITE_BOOL_FIELD(hasIterative); WRITE_BOOL_FIELD(hasModifyingCTE); WRITE_BOOL_FIELD(hasForUpdate); WRITE_BOOL_FIELD(hasRowSecurity); @@ -3042,6 +3045,7 @@ _outWithClause(StringInfo str, const WithClause *node) WRITE_NODE_FIELD(ctes); WRITE_BOOL_FIELD(recursive); + WRITE_BOOL_FIELD(iterative); WRITE_LOCATION_FIELD(location); } @@ -3056,6 +3060,7 @@ _outCommonTableExpr(StringInfo str, const CommonTableExpr *node) WRITE_NODE_FIELD(ctequery); WRITE_LOCATION_FIELD(location); WRITE_BOOL_FIELD(cterecursive); + WRITE_BOOL_FIELD(cteiterative); WRITE_INT_FIELD(cterefcount); WRITE_NODE_FIELD(ctecolnames); WRITE_NODE_FIELD(ctecoltypes); diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c index 644e0f5fc7..2333b7f2bf 100644 --- a/src/backend/nodes/readfuncs.c +++ b/src/backend/nodes/readfuncs.c @@ -260,6 +260,7 @@ _readQuery(void) READ_BOOL_FIELD(hasSubLinks); READ_BOOL_FIELD(hasDistinctOn); READ_BOOL_FIELD(hasRecursive); + READ_BOOL_FIELD(hasIterative); READ_BOOL_FIELD(hasModifyingCTE); READ_BOOL_FIELD(hasForUpdate); READ_BOOL_FIELD(hasRowSecurity); diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c index 255f56b827..26d215b6ec 100644 --- a/src/backend/optimizer/path/allpaths.c +++ b/src/backend/optimizer/path/allpaths.c @@ -2302,7 +2302,7 @@ set_subquery_pathlist(PlannerInfo *root, RelOptInfo *rel, /* Generate a subroot and Paths for the subquery */ rel->subroot = subquery_planner(root->glob, subquery, root, - false, tuple_fraction); + false, tuple_fraction, false); /* Isolate the params needed by this specific subplan */ rel->subplan_params = root->plan_params; diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c index 9941dfe65e..7586edc6c7 100644 --- a/src/backend/optimizer/plan/createplan.c +++ b/src/backend/optimizer/plan/createplan.c @@ -214,7 +214,8 @@ static RecursiveUnion *make_recursive_union(List *tlist, Plan *righttree, int wtParam, List *distinctList, - long numGroups); + long numGroups, + bool iterate); static BitmapAnd *make_bitmap_and(List *bitmapplans); static BitmapOr *make_bitmap_or(List *bitmapplans); static NestLoop *make_nestloop(List *tlist, @@ -2588,7 +2589,8 @@ create_recursiveunion_plan(PlannerInfo *root, RecursiveUnionPath *best_path) rightplan, best_path->wtParam, best_path->distinctList, - numGroups); + numGroups, + best_path->iterative); copy_generic_path_info(&plan->plan, (Path *) best_path); @@ -5549,7 +5551,8 @@ make_recursive_union(List *tlist, Plan *righttree, int wtParam, List *distinctList, - long numGroups) + long numGroups, + bool iterate) { RecursiveUnion *node = makeNode(RecursiveUnion); Plan *plan = &node->plan; @@ -5595,6 +5598,7 @@ make_recursive_union(List *tlist, node->dupCollations = dupCollations; } node->numGroups = numGroups; + node->iterative = iterate; return node; } diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c index e664eb18c0..c191325bf2 100644 --- a/src/backend/optimizer/plan/planner.c +++ b/src/backend/optimizer/plan/planner.c @@ -315,6 +315,7 @@ standard_planner(Query *parse, const char *query_string, int cursorOptions, glob->lastPlanNodeId = 0; glob->transientPlan = false; glob->dependsOnRole = false; + glob->iterativePlan = parse->hasIterative; /* * Assess whether it's feasible to use parallel mode for this query. We @@ -403,7 +404,7 @@ standard_planner(Query *parse, const char *query_string, int cursorOptions, /* primary planning entry point (may recurse for subqueries) */ root = subquery_planner(glob, parse, NULL, - false, tuple_fraction); + false, tuple_fraction, parse->hasIterative); /* Select best Path and turn it into a Plan */ final_rel = fetch_upper_rel(root, UPPERREL_FINAL, NULL); @@ -596,7 +597,7 @@ standard_planner(Query *parse, const char *query_string, int cursorOptions, PlannerInfo * subquery_planner(PlannerGlobal *glob, Query *parse, PlannerInfo *parent_root, - bool hasRecursion, double tuple_fraction) + bool hasRecursion, double tuple_fraction, bool hasIteration) { PlannerInfo *root; List *newWithCheckOptions; @@ -630,6 +631,7 @@ subquery_planner(PlannerGlobal *glob, Query *parse, root->qual_security_level = 0; root->inhTargetKind = INHKIND_NONE; root->hasRecursion = hasRecursion; + root->hasIteration = hasIteration; if (hasRecursion) root->wt_param_id = assign_special_exec_param(root); else diff --git a/src/backend/optimizer/plan/subselect.c b/src/backend/optimizer/plan/subselect.c index b02fcb9bfe..272334ea5b 100644 --- a/src/backend/optimizer/plan/subselect.c +++ b/src/backend/optimizer/plan/subselect.c @@ -219,7 +219,7 @@ make_subplan(PlannerInfo *root, Query *orig_subquery, /* Generate Paths for the subquery */ subroot = subquery_planner(root->glob, subquery, root, - false, tuple_fraction); + false, tuple_fraction, subquery->hasIterative); /* Isolate the params needed by this specific subplan */ plan_params = root->plan_params; @@ -266,7 +266,7 @@ make_subplan(PlannerInfo *root, Query *orig_subquery, /* Generate Paths for the ANY subquery; we'll need all rows */ subroot = subquery_planner(root->glob, subquery, root, - false, 0.0); + false, 0.0, false); /* Isolate the params needed by this specific subplan */ plan_params = root->plan_params; @@ -917,7 +917,7 @@ SS_process_ctes(PlannerInfo *root) */ subroot = subquery_planner(root->glob, subquery, root, - cte->cterecursive, 0.0); + cte->cterecursive, 0.0, (cte->cteiterative || root->hasIteration)); /* * Since the current query level doesn't yet contain any RTEs, it diff --git a/src/backend/optimizer/prep/prepunion.c b/src/backend/optimizer/prep/prepunion.c index 951aed80e7..b8eedbcc47 100644 --- a/src/backend/optimizer/prep/prepunion.c +++ b/src/backend/optimizer/prep/prepunion.c @@ -239,7 +239,8 @@ recurse_set_operations(Node *setOp, PlannerInfo *root, subroot = rel->subroot = subquery_planner(root->glob, subquery, root, false, - root->tuple_fraction); + root->tuple_fraction, + root->hasIteration); /* * It should not be possible for the primitive query to contain any diff --git a/src/backend/optimizer/util/pathnode.c b/src/backend/optimizer/util/pathnode.c index e991385059..f5db21f344 100644 --- a/src/backend/optimizer/util/pathnode.c +++ b/src/backend/optimizer/util/pathnode.c @@ -3422,6 +3422,7 @@ create_recursiveunion_path(PlannerInfo *root, pathnode->distinctList = distinctList; pathnode->wtParam = wtParam; pathnode->numGroups = numGroups; + pathnode->iterative = (root->hasIteration || root->parse->hasIterative); cost_recursive_union(&pathnode->path, leftpath, rightpath); diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c index eee9c33637..6d21f7dc39 100644 --- a/src/backend/parser/analyze.c +++ b/src/backend/parser/analyze.c @@ -406,6 +406,7 @@ transformDeleteStmt(ParseState *pstate, DeleteStmt *stmt) if (stmt->withClause) { qry->hasRecursive = stmt->withClause->recursive; + qry->hasIterative = stmt->withClause->iterative; qry->cteList = transformWithClause(pstate, stmt->withClause); qry->hasModifyingCTE = pstate->p_hasModifyingCTE; } @@ -492,6 +493,7 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt) if (stmt->withClause) { qry->hasRecursive = stmt->withClause->recursive; + qry->hasIterative = stmt->withClause->iterative; qry->cteList = transformWithClause(pstate, stmt->withClause); qry->hasModifyingCTE = pstate->p_hasModifyingCTE; } @@ -1199,6 +1201,7 @@ transformSelectStmt(ParseState *pstate, SelectStmt *stmt) if (stmt->withClause) { qry->hasRecursive = stmt->withClause->recursive; + qry->hasIterative = stmt->withClause->iterative; qry->cteList = transformWithClause(pstate, stmt->withClause); qry->hasModifyingCTE = pstate->p_hasModifyingCTE; } @@ -1360,6 +1363,7 @@ transformValuesClause(ParseState *pstate, SelectStmt *stmt) if (stmt->withClause) { qry->hasRecursive = stmt->withClause->recursive; + qry->hasIterative = stmt->withClause->iterative; qry->cteList = transformWithClause(pstate, stmt->withClause); qry->hasModifyingCTE = pstate->p_hasModifyingCTE; } @@ -1644,6 +1648,7 @@ transformSetOperationStmt(ParseState *pstate, SelectStmt *stmt) if (withClause) { qry->hasRecursive = withClause->recursive; + qry->hasIterative = withClause->iterative; qry->cteList = transformWithClause(pstate, withClause); qry->hasModifyingCTE = pstate->p_hasModifyingCTE; } @@ -2239,6 +2244,7 @@ transformUpdateStmt(ParseState *pstate, UpdateStmt *stmt) if (stmt->withClause) { qry->hasRecursive = stmt->withClause->recursive; + qry->hasIterative = stmt->withClause->iterative; qry->cteList = transformWithClause(pstate, stmt->withClause); qry->hasModifyingCTE = pstate->p_hasModifyingCTE; } diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index 3c78f2d1b5..fc14c06fdf 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -662,7 +662,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); IDENTITY_P IF_P ILIKE IMMEDIATE IMMUTABLE IMPLICIT_P IMPORT_P IN_P INCLUDE INCLUDING INCREMENT INDEX INDEXES INHERIT INHERITS INITIALLY INLINE_P INNER_P INOUT INPUT_P INSENSITIVE INSERT INSTEAD INT_P INTEGER - INTERSECT INTERVAL INTO INVOKER IS ISNULL ISOLATION + INTERSECT INTERVAL INTO INVOKER IS ISNULL ISOLATION ITERATIVE JOIN @@ -11559,6 +11559,7 @@ with_clause: $$ = makeNode(WithClause); $$->ctes = $2; $$->recursive = false; + $$->iterative = false; $$->location = @1; } | WITH_LA cte_list @@ -11566,6 +11567,7 @@ with_clause: $$ = makeNode(WithClause); $$->ctes = $2; $$->recursive = false; + $$->iterative = false; $$->location = @1; } | WITH RECURSIVE cte_list @@ -11573,6 +11575,15 @@ with_clause: $$ = makeNode(WithClause); $$->ctes = $3; $$->recursive = true; + $$->iterative = false; + $$->location = @1; + } + | WITH ITERATIVE cte_list + { + $$ = makeNode(WithClause); + $$->ctes = $3; + $$->recursive = true; + $$->iterative = true; $$->location = @1; } ; @@ -15387,6 +15398,7 @@ unreserved_keyword: | INSTEAD | INVOKER | ISOLATION + | ITERATIVE | KEY | LABEL | LANGUAGE diff --git a/src/backend/parser/parse_cte.c b/src/backend/parser/parse_cte.c index 1fca7485ca..cb696220e6 100644 --- a/src/backend/parser/parse_cte.c +++ b/src/backend/parser/parse_cte.c @@ -135,6 +135,7 @@ transformWithClause(ParseState *pstate, WithClause *withClause) } cte->cterecursive = false; + cte->cteiterative = withClause->iterative; cte->cterefcount = 0; if (!IsA(cte->ctequery, SelectStmt)) diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h index 4fee043bb2..088265ba02 100644 --- a/src/include/nodes/execnodes.h +++ b/src/include/nodes/execnodes.h @@ -1266,6 +1266,7 @@ typedef struct MergeAppendState * RecursiveUnionState is used for performing a recursive union. * * recursing T when we're done scanning the non-recursive term + * iterating T when we're done scanning the non-recursive term * intermediate_empty T if intermediate_table is currently empty * working_table working table (to be scanned by recursive term) * intermediate_table current recursive output (next generation of WT) @@ -1275,6 +1276,7 @@ typedef struct RecursiveUnionState { PlanState ps; /* its first field is NodeTag */ bool recursing; + bool iterating; bool intermediate_empty; Tuplestorestate *working_table; Tuplestorestate *intermediate_table; diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h index 5e1ffafb91..551e62c1c1 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -128,6 +128,7 @@ typedef struct Query bool hasSubLinks; /* has subquery SubLink */ bool hasDistinctOn; /* distinctClause is from DISTINCT ON */ bool hasRecursive; /* WITH RECURSIVE was specified */ + bool hasIterative; /* WITH ITERATIVE was specified */ bool hasModifyingCTE; /* has INSERT/UPDATE/DELETE in WITH */ bool hasForUpdate; /* FOR [KEY] UPDATE/SHARE was specified */ bool hasRowSecurity; /* rewriter has applied some RLS policy */ @@ -1396,7 +1397,8 @@ typedef struct WithClause { NodeTag type; List *ctes; /* list of CommonTableExprs */ - bool recursive; /* true = WITH RECURSIVE */ + bool recursive; /* true = WITH RECURSIVE (or ITERATIVE) */ + bool iterative; /* true = WITH ITERATIVE */ int location; /* token location, or -1 if unknown */ } WithClause; @@ -1455,6 +1457,7 @@ typedef struct CommonTableExpr int location; /* token location, or -1 if unknown */ /* These fields are set during parse analysis: */ bool cterecursive; /* is this CTE actually recursive? */ + bool cteiterative; /* is this CTE iterative? */ int cterefcount; /* number of RTEs referencing this CTE * (excluding internal self-references) */ List *ctecolnames; /* list of output column names */ diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h index 1c83772d62..e8df896ffc 100644 --- a/src/include/nodes/pathnodes.h +++ b/src/include/nodes/pathnodes.h @@ -147,6 +147,7 @@ typedef struct PlannerGlobal char maxParallelHazard; /* worst PROPARALLEL hazard level */ PartitionDirectory partition_directory; /* partition descriptors */ + bool iterativePlan; } PlannerGlobal; /* macro for fetching the Plan associated with a SubPlan node */ @@ -348,6 +349,7 @@ struct PlannerInfo bool hasPseudoConstantQuals; /* true if any RestrictInfo has * pseudoconstant = true */ bool hasRecursion; /* true if planning a recursive WITH item */ + bool hasIteration; /* true if planning an iteration-optimized recursive WITH item */ /* These fields are used only when hasRecursion is true: */ int wt_param_id; /* PARAM_EXEC ID for the work table */ @@ -1787,6 +1789,7 @@ typedef struct RecursiveUnionPath List *distinctList; /* SortGroupClauses identifying target cols */ int wtParam; /* ID of Param representing work table */ double numGroups; /* estimated number of groups in input */ + bool iterative; /* WITH ITERATIVE */ } RecursiveUnionPath; /* diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h index 55f363f70c..b58f82f82b 100644 --- a/src/include/nodes/plannodes.h +++ b/src/include/nodes/plannodes.h @@ -303,6 +303,7 @@ typedef struct RecursiveUnion Oid *dupOperators; /* equality operators to compare with */ Oid *dupCollations; long numGroups; /* estimated number of groups in input */ + bool iterative; /* WITH ITERATIVE */ } RecursiveUnion; /* ---------------- diff --git a/src/include/optimizer/planner.h b/src/include/optimizer/planner.h index beb7dbbcbe..2521bc79a2 100644 --- a/src/include/optimizer/planner.h +++ b/src/include/optimizer/planner.h @@ -44,7 +44,9 @@ extern PlannedStmt *standard_planner(Query *parse, const char *query_string, extern PlannerInfo *subquery_planner(PlannerGlobal *glob, Query *parse, PlannerInfo *parent_root, - bool hasRecursion, double tuple_fraction); + bool hasRecursion, double tuple_fraction, bool hasIterate); + +extern bool is_dummy_plan(Plan *plan); extern RowMarkType select_rowmark_type(RangeTblEntry *rte, LockClauseStrength strength); diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h index 08f22ce211..8a76c43b9b 100644 --- a/src/include/parser/kwlist.h +++ b/src/include/parser/kwlist.h @@ -220,6 +220,7 @@ PG_KEYWORD("invoker", INVOKER, UNRESERVED_KEYWORD) PG_KEYWORD("is", IS, TYPE_FUNC_NAME_KEYWORD) PG_KEYWORD("isnull", ISNULL, TYPE_FUNC_NAME_KEYWORD) PG_KEYWORD("isolation", ISOLATION, UNRESERVED_KEYWORD) +PG_KEYWORD("iterative", ITERATIVE, UNRESERVED_KEYWORD) PG_KEYWORD("join", JOIN, TYPE_FUNC_NAME_KEYWORD) PG_KEYWORD("key", KEY, UNRESERVED_KEYWORD) PG_KEYWORD("label", LABEL, UNRESERVED_KEYWORD)