From 947d0c862c895618a874344322e7b07c9df05cb2 Mon Sep 17 00:00:00 2001 From: Tom Lane Date: Mon, 8 Nov 2010 15:15:02 -0500 Subject: [PATCH] Use appendrel planning logic for top-level UNION ALL structures. Formerly, we could convert a UNION ALL structure inside a subquery-in-FROM into an appendrel, as a side effect of pulling up the subquery into its parent; but top-level UNION ALL always caused use of plan_set_operations(). That didn't matter too much because you got an Append-based plan either way. However, now that the appendrel code can do things with MergeAppend, it's worthwhile to hack up the top-level case so it also uses appendrels. This is a bit of a stopgap; but going much further than this will require a major rewrite of the planner's set-operations support, which I'm not prepared to undertake now. For the moment let's grab the low-hanging fruit. --- src/backend/optimizer/plan/planner.c | 11 +- src/backend/optimizer/prep/prepjointree.c | 121 ++++++++++++++++++++-- src/include/optimizer/prep.h | 1 + 3 files changed, 121 insertions(+), 12 deletions(-) diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c index 07301c77fbf..620888cbb86 100644 --- a/src/backend/optimizer/plan/planner.c +++ b/src/backend/optimizer/plan/planner.c @@ -341,12 +341,21 @@ subquery_planner(PlannerGlobal *glob, Query *parse, inline_set_returning_functions(root); /* - * Check to see if any subqueries in the rangetable can be merged into + * Check to see if any subqueries in the jointree can be merged into * this query. */ parse->jointree = (FromExpr *) pull_up_subqueries(root, (Node *) parse->jointree, NULL, NULL); + /* + * If this is a simple UNION ALL query, flatten it into an appendrel. + * We do this now because it requires applying pull_up_subqueries to the + * leaf queries of the UNION ALL, which weren't touched above because they + * weren't referenced by the jointree (they will be after we do this). + */ + if (parse->setOperations) + flatten_simple_union_all(root); + /* * Detect whether any rangetable entries are RTE_JOIN kind; if not, we can * avoid the expense of doing flatten_join_alias_vars(). Also check for diff --git a/src/backend/optimizer/prep/prepjointree.c b/src/backend/optimizer/prep/prepjointree.c index e337751328b..9c99f0fb5c2 100644 --- a/src/backend/optimizer/prep/prepjointree.c +++ b/src/backend/optimizer/prep/prepjointree.c @@ -7,6 +7,7 @@ * pull_up_sublinks * inline_set_returning_functions * pull_up_subqueries + * flatten_simple_union_all * do expression preprocessing (including flattening JOIN alias vars) * reduce_outer_joins * @@ -868,11 +869,6 @@ pull_up_simple_union_all(PlannerInfo *root, Node *jtnode, RangeTblEntry *rte) int rtoffset; List *rtable; - /* - * Append the subquery rtable entries to upper query. - */ - rtoffset = list_length(root->parse->rtable); - /* * Append child RTEs to parent rtable. * @@ -881,6 +877,7 @@ pull_up_simple_union_all(PlannerInfo *root, Node *jtnode, RangeTblEntry *rte) * because any such vars must refer to stuff above the level of the query * we are pulling into. */ + rtoffset = list_length(root->parse->rtable); rtable = copyObject(subquery->rtable); IncrementVarSublevelsUp_rtable(rtable, -1, 1); root->parse->rtable = list_concat(root->parse->rtable, rtable); @@ -888,7 +885,7 @@ pull_up_simple_union_all(PlannerInfo *root, Node *jtnode, RangeTblEntry *rte) /* * Recursively scan the subquery's setOperations tree and add * AppendRelInfo nodes for leaf subqueries to the parent's - * append_rel_list. + * append_rel_list. Also apply pull_up_subqueries to the leaf subqueries. */ Assert(subquery->setOperations); pull_up_union_leaf_queries(subquery->setOperations, root, varno, subquery, @@ -905,14 +902,20 @@ pull_up_simple_union_all(PlannerInfo *root, Node *jtnode, RangeTblEntry *rte) /* * pull_up_union_leaf_queries -- recursive guts of pull_up_simple_union_all * - * Note that setOpQuery is the Query containing the setOp node, whose rtable - * is where to look up the RTE if setOp is a RangeTblRef. This is *not* the - * same as root->parse, which is the top-level Query we are pulling up into. + * Build an AppendRelInfo for each leaf query in the setop tree, and then + * apply pull_up_subqueries to the leaf query. + * + * Note that setOpQuery is the Query containing the setOp node, whose tlist + * contains references to all the setop output columns. When called from + * pull_up_simple_union_all, this is *not* the same as root->parse, which is + * the parent Query we are pulling up into. * * parentRTindex is the appendrel parent's index in root->parse->rtable. * - * The child RTEs have already been copied to the parent. childRToffset - * tells us where in the parent's range table they were copied. + * The child RTEs have already been copied to the parent. childRToffset + * tells us where in the parent's range table they were copied. When called + * from flatten_simple_union_all, childRToffset is 0 since the child RTEs + * were already in root->parse->rtable and no RT index adjustment is needed. */ static void pull_up_union_leaf_queries(Node *setOp, PlannerInfo *root, int parentRTindex, @@ -1418,6 +1421,102 @@ pullup_replace_vars_callback(Var *var, return newnode; } + +/* + * flatten_simple_union_all + * Try to optimize top-level UNION ALL structure into an appendrel + * + * If a query's setOperations tree consists entirely of simple UNION ALL + * operations, flatten it into an append relation, which we can process more + * intelligently than the general setops case. Otherwise, do nothing. + * + * In most cases, this can succeed only for a top-level query, because for a + * subquery in FROM, the parent query's invocation of pull_up_subqueries would + * already have flattened the UNION via pull_up_simple_union_all. But there + * are a few cases we can support here but not in that code path, for example + * when the subquery also contains ORDER BY. + */ +void +flatten_simple_union_all(PlannerInfo *root) +{ + Query *parse = root->parse; + SetOperationStmt *topop; + Node *leftmostjtnode; + int leftmostRTI; + RangeTblEntry *leftmostRTE; + int childRTI; + RangeTblEntry *childRTE; + RangeTblRef *rtr; + + /* Shouldn't be called unless query has setops */ + topop = (SetOperationStmt *) parse->setOperations; + Assert(topop && IsA(topop, SetOperationStmt)); + + /* Can't optimize away a recursive UNION */ + if (root->hasRecursion) + return; + + /* + * Recursively check the tree of set operations. If not all UNION ALL + * with identical column types, punt. + */ + if (!is_simple_union_all_recurse((Node *) topop, parse, topop->colTypes)) + return; + + /* + * Locate the leftmost leaf query in the setops tree. The upper query's + * Vars all refer to this RTE (see transformSetOperationStmt). + */ + leftmostjtnode = topop->larg; + while (leftmostjtnode && IsA(leftmostjtnode, SetOperationStmt)) + leftmostjtnode = ((SetOperationStmt *) leftmostjtnode)->larg; + Assert(leftmostjtnode && IsA(leftmostjtnode, RangeTblRef)); + leftmostRTI = ((RangeTblRef *) leftmostjtnode)->rtindex; + leftmostRTE = rt_fetch(leftmostRTI, parse->rtable); + Assert(leftmostRTE->rtekind == RTE_SUBQUERY); + + /* + * Make a copy of the leftmost RTE and add it to the rtable. This copy + * will represent the leftmost leaf query in its capacity as a member + * of the appendrel. The original will represent the appendrel as a + * whole. (We must do things this way because the upper query's Vars + * have to be seen as referring to the whole appendrel.) + */ + childRTE = copyObject(leftmostRTE); + parse->rtable = lappend(parse->rtable, childRTE); + childRTI = list_length(parse->rtable); + + /* Modify the setops tree to reference the child copy */ + ((RangeTblRef *) leftmostjtnode)->rtindex = childRTI; + + /* Modify the formerly-leftmost RTE to mark it as an appendrel parent */ + leftmostRTE->inh = true; + + /* + * Form a RangeTblRef for the appendrel, and insert it into FROM. The top + * Query of a setops tree should have had an empty FromClause initially. + */ + rtr = makeNode(RangeTblRef); + rtr->rtindex = leftmostRTI; + Assert(parse->jointree->fromlist == NIL); + parse->jointree->fromlist = list_make1(rtr); + + /* + * Now pretend the query has no setops. We must do this before trying + * to do subquery pullup, because of Assert in pull_up_simple_subquery. + */ + parse->setOperations = NULL; + + /* + * Build AppendRelInfo information, and apply pull_up_subqueries to the + * leaf queries of the UNION ALL. (We must do that now because they + * weren't previously referenced by the jointree, and so were missed by + * the main invocation of pull_up_subqueries.) + */ + pull_up_union_leaf_queries((Node *) topop, root, leftmostRTI, parse, 0); +} + + /* * reduce_outer_joins * Attempt to reduce outer joins to plain inner joins. diff --git a/src/include/optimizer/prep.h b/src/include/optimizer/prep.h index f8dd5428ee4..9f8e379c24b 100644 --- a/src/include/optimizer/prep.h +++ b/src/include/optimizer/prep.h @@ -26,6 +26,7 @@ extern void inline_set_returning_functions(PlannerInfo *root); extern Node *pull_up_subqueries(PlannerInfo *root, Node *jtnode, JoinExpr *lowest_outer_join, AppendRelInfo *containing_appendrel); +extern void flatten_simple_union_all(PlannerInfo *root); extern void reduce_outer_joins(PlannerInfo *root); extern Relids get_relids_in_jointree(Node *jtnode, bool include_joins); extern Relids get_relids_for_join(PlannerInfo *root, int joinrelid);