Use appendrel planning logic for top-level UNION ALL structures.
authorTom Lane <[email protected]>
Mon, 8 Nov 2010 20:15:02 +0000 (15:15 -0500)
committerTom Lane <[email protected]>
Mon, 8 Nov 2010 20:15:02 +0000 (15:15 -0500)
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
src/backend/optimizer/prep/prepjointree.c
src/include/optimizer/prep.h

index 07301c77fbf4f2550367ea8ff58c685462835223..620888cbb86a9eddf29112ac32f815c9d4a4bd63 100644 (file)
@@ -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
index e337751328bf0b05e3025a2ecc302f754b766855..9c99f0fb5c23389e15b46569518fc609577e64d1 100644 (file)
@@ -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.
index f8dd5428ee47a74637d37f5a293d2acff117eae4..9f8e379c24baca7fde7529e285d1fe88aeec66e9 100644 (file)
@@ -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);