Push down outer qualification clauses into UNION and INTERSECT subqueries.
authorTom Lane
Thu, 29 Aug 2002 16:03:49 +0000 (16:03 +0000)
committerTom Lane
Thu, 29 Aug 2002 16:03:49 +0000 (16:03 +0000)
Per pghackers discussion from back around 1-August.

src/backend/optimizer/path/allpaths.c
src/backend/optimizer/prep/prepunion.c
src/include/optimizer/prep.h

index 64e1c059dad64637fd7ff5d68f73e75a68b06dc4..c1ee656b514bc95663594a242463fdc418fa705c 100644 (file)
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *   $Header: /cvsroot/pgsql/src/backend/optimizer/path/allpaths.c,v 1.86 2002/06/20 20:29:29 momjian Exp $
+ *   $Header: /cvsroot/pgsql/src/backend/optimizer/path/allpaths.c,v 1.87 2002/08/29 16:03:48 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -46,6 +46,11 @@ static void set_function_pathlist(Query *root, RelOptInfo *rel,
                        RangeTblEntry *rte);
 static RelOptInfo *make_one_rel_by_joins(Query *root, int levels_needed,
                      List *initial_rels);
+static bool subquery_is_pushdown_safe(Query *subquery, Query *topquery);
+static bool recurse_pushdown_safe(Node *setOp, Query *topquery);
+static void subquery_push_qual(Query *subquery, Index rti, Node *qual);
+static void recurse_push_qual(Node *setOp, Query *topquery,
+                             Index rti, Node *qual);
 
 
 /*
@@ -297,31 +302,11 @@ set_subquery_pathlist(Query *root, RelOptInfo *rel,
     * generate a better plan for the subquery than evaluating all the
     * subquery output rows and then filtering them.
     *
-    * There are several cases where we cannot push down clauses:
-    *
-    * 1. If the subquery contains set ops (UNION/INTERSECT/EXCEPT) we do not
-    * push down any qual clauses, since the planner doesn't support quals
-    * at the top level of a setop.  (With suitable analysis we could try
-    * to push the quals down into the component queries of the setop, but
-    * getting it right seems nontrivial.  Work on this later.)
-    *
-    * 2. If the subquery has a LIMIT clause or a DISTINCT ON clause, we must
-    * not push down any quals, since that could change the set of rows
-    * returned.  (Actually, we could push down quals into a DISTINCT ON
-    * subquery if they refer only to DISTINCT-ed output columns, but
-    * checking that seems more work than it's worth.  In any case, a
-    * plain DISTINCT is safe to push down past.)
-    *
-    * 3. If the subquery has any functions returning sets in its target list,
-    * we do not push down any quals, since the quals
-    * might refer to those tlist items, which would mean we'd introduce
-    * functions-returning-sets into the subquery's WHERE/HAVING quals.
-    * (It'd be sufficient to not push down quals that refer to those
-    * particular tlist items, but that's much clumsier to check.)
-    *
-    * 4. We do not push down clauses that contain subselects, mainly because
-    * I'm not sure it will work correctly (the subplan hasn't yet
-    * transformed sublinks to subselects).
+    * There are several cases where we cannot push down clauses.
+    * Restrictions involving the subquery are checked by
+    * subquery_is_pushdown_safe().  Also, we do not push down clauses that
+    * contain subselects, mainly because I'm not sure it will work correctly
+    * (the subplan hasn't yet transformed sublinks to subselects).
     *
     * Non-pushed-down clauses will get evaluated as qpquals of the
     * SubqueryScan node.
@@ -329,11 +314,8 @@ set_subquery_pathlist(Query *root, RelOptInfo *rel,
     * XXX Are there any cases where we want to make a policy decision not to
     * push down, because it'd result in a worse plan?
     */
-   if (subquery->setOperations == NULL &&
-       subquery->limitOffset == NULL &&
-       subquery->limitCount == NULL &&
-       !has_distinct_on_clause(subquery) &&
-       !expression_returns_set((Node *) subquery->targetList))
+   if (rel->baserestrictinfo != NIL &&
+       subquery_is_pushdown_safe(subquery, subquery))
    {
        /* OK to consider pushing down individual quals */
        List       *upperrestrictlist = NIL;
@@ -351,25 +333,8 @@ set_subquery_pathlist(Query *root, RelOptInfo *rel,
            }
            else
            {
-               /*
-                * We need to replace Vars in the clause (which must refer
-                * to outputs of the subquery) with copies of the
-                * subquery's targetlist expressions.  Note that at this
-                * point, any uplevel Vars in the clause should have been
-                * replaced with Params, so they need no work.
-                */
-               clause = ResolveNew(clause, rti, 0,
-                                   subquery->targetList,
-                                   CMD_SELECT, 0);
-               subquery->havingQual = make_and_qual(subquery->havingQual,
-                                                    clause);
-
-               /*
-                * We need not change the subquery's hasAggs or
-                * hasSublinks flags, since we can't be pushing down any
-                * aggregates that weren't there before, and we don't push
-                * down subselects at all.
-                */
+               /* Push it down */
+               subquery_push_qual(subquery, rti, clause);
            }
        }
        rel->baserestrictinfo = upperrestrictlist;
@@ -547,7 +512,183 @@ make_one_rel_by_joins(Query *root, int levels_needed, List *initial_rels)
 }
 
 /*****************************************************************************
+ *         PUSHING QUALS DOWN INTO SUBQUERIES
+ *****************************************************************************/
+
+/*
+ * subquery_is_pushdown_safe - is a subquery safe for pushing down quals?
+ *
+ * subquery is the particular component query being checked.  topquery
+ * is the top component of a set-operations tree (the same Query if no
+ * set-op is involved).
+ *
+ * Conditions checked here:
  *
+ * 1. If the subquery has a LIMIT clause or a DISTINCT ON clause, we must
+ * not push down any quals, since that could change the set of rows
+ * returned.  (Actually, we could push down quals into a DISTINCT ON
+ * subquery if they refer only to DISTINCT-ed output columns, but
+ * checking that seems more work than it's worth.  In any case, a
+ * plain DISTINCT is safe to push down past.)
+ *
+ * 2. If the subquery has any functions returning sets in its target list,
+ * we do not push down any quals, since the quals
+ * might refer to those tlist items, which would mean we'd introduce
+ * functions-returning-sets into the subquery's WHERE/HAVING quals.
+ * (It'd be sufficient to not push down quals that refer to those
+ * particular tlist items, but that's much clumsier to check.)
+ *
+ * 3. If the subquery contains EXCEPT or EXCEPT ALL set ops we cannot push
+ * quals into it, because that would change the results.  For subqueries
+ * using UNION/UNION ALL/INTERSECT/INTERSECT ALL, we can push the quals
+ * into each component query, so long as all the component queries share
+ * identical output types.  (That restriction could probably be relaxed,
+ * but it would take much more code to include type coercion code into
+ * the quals, and I'm also concerned about possible semantic gotchas.)
+ */
+static bool
+subquery_is_pushdown_safe(Query *subquery, Query *topquery)
+{
+   SetOperationStmt *topop;
+
+   /* Check points 1 and 2 */
+   if (subquery->limitOffset != NULL ||
+       subquery->limitCount != NULL ||
+       has_distinct_on_clause(subquery) ||
+       expression_returns_set((Node *) subquery->targetList))
+       return false;
+
+   /* Are we at top level, or looking at a setop component? */
+   if (subquery == topquery)
+   {
+       /* Top level, so check any component queries */
+       if (subquery->setOperations != NULL)
+           if (!recurse_pushdown_safe(subquery->setOperations, topquery))
+               return false;
+   }
+   else
+   {
+       /* Setop component must not have more components (too weird) */
+       if (subquery->setOperations != NULL)
+           return false;
+       /* Setop component output types must match top level */
+       topop = (SetOperationStmt *) topquery->setOperations;
+       Assert(topop && IsA(topop, SetOperationStmt));
+       if (!tlist_same_datatypes(subquery->targetList,
+                                 topop->colTypes,
+                                 true))
+           return false;
+
+   }
+   return true;
+}
+
+/*
+ * Helper routine to recurse through setOperations tree
+ */
+static bool
+recurse_pushdown_safe(Node *setOp, Query *topquery)
+{
+   if (IsA(setOp, RangeTblRef))
+   {
+       RangeTblRef *rtr = (RangeTblRef *) setOp;
+       RangeTblEntry *rte = rt_fetch(rtr->rtindex, topquery->rtable);
+       Query      *subquery = rte->subquery;
+
+       Assert(subquery != NULL);
+       return subquery_is_pushdown_safe(subquery, topquery);
+   }
+   else if (IsA(setOp, SetOperationStmt))
+   {
+       SetOperationStmt *op = (SetOperationStmt *) setOp;
+
+       /* EXCEPT is no good */
+       if (op->op == SETOP_EXCEPT)
+           return false;
+       /* Else recurse */
+       if (!recurse_pushdown_safe(op->larg, topquery))
+           return false;
+       if (!recurse_pushdown_safe(op->rarg, topquery))
+           return false;
+   }
+   else
+   {
+       elog(ERROR, "recurse_pushdown_safe: unexpected node %d",
+            (int) nodeTag(setOp));
+   }
+   return true;
+}
+
+/*
+ * subquery_push_qual - push down a qual that we have determined is safe
+ */
+static void
+subquery_push_qual(Query *subquery, Index rti, Node *qual)
+{
+   if (subquery->setOperations != NULL)
+   {
+       /* Recurse to push it separately to each component query */
+       recurse_push_qual(subquery->setOperations, subquery, rti, qual);
+   }
+   else
+   {
+       /*
+        * We need to replace Vars in the qual (which must refer
+        * to outputs of the subquery) with copies of the
+        * subquery's targetlist expressions.  Note that at this
+        * point, any uplevel Vars in the qual should have been
+        * replaced with Params, so they need no work.
+        *
+        * This step also ensures that when we are pushing into a setop
+        * tree, each component query gets its own copy of the qual.
+        */
+       qual = ResolveNew(qual, rti, 0,
+                         subquery->targetList,
+                         CMD_SELECT, 0);
+       subquery->havingQual = make_and_qual(subquery->havingQual,
+                                            qual);
+
+       /*
+        * We need not change the subquery's hasAggs or
+        * hasSublinks flags, since we can't be pushing down any
+        * aggregates that weren't there before, and we don't push
+        * down subselects at all.
+        */
+   }
+}
+
+/*
+ * Helper routine to recurse through setOperations tree
+ */
+static void
+recurse_push_qual(Node *setOp, Query *topquery,
+                 Index rti, Node *qual)
+{
+   if (IsA(setOp, RangeTblRef))
+   {
+       RangeTblRef *rtr = (RangeTblRef *) setOp;
+       RangeTblEntry *rte = rt_fetch(rtr->rtindex, topquery->rtable);
+       Query      *subquery = rte->subquery;
+
+       Assert(subquery != NULL);
+       subquery_push_qual(subquery, rti, qual);
+   }
+   else if (IsA(setOp, SetOperationStmt))
+   {
+       SetOperationStmt *op = (SetOperationStmt *) setOp;
+
+       recurse_push_qual(op->larg, topquery, rti, qual);
+       recurse_push_qual(op->rarg, topquery, rti, qual);
+   }
+   else
+   {
+       elog(ERROR, "recurse_push_qual: unexpected node %d",
+            (int) nodeTag(setOp));
+   }
+}
+
+/*****************************************************************************
+ *         DEBUG SUPPORT
  *****************************************************************************/
 
 #ifdef OPTIMIZER_DEBUG
index f41466dbd5c7f3691b9a3fa6ae8a463b43b83bf5..c7bf00abf5fea910d6bb782b4666980de6d7ed36 100644 (file)
@@ -14,7 +14,7 @@
  *
  *
  * IDENTIFICATION
- *   $Header: /cvsroot/pgsql/src/backend/optimizer/prep/prepunion.c,v 1.75 2002/08/02 18:15:06 tgl Exp $
+ *   $Header: /cvsroot/pgsql/src/backend/optimizer/prep/prepunion.c,v 1.76 2002/08/29 16:03:48 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -66,7 +66,6 @@ static List *generate_setop_tlist(List *colTypes, int flag,
 static List *generate_append_tlist(List *colTypes, bool flag,
                     List *input_plans,
                     List *refnames_tlist);
-static bool tlist_same_datatypes(List *tlist, List *colTypes, bool junkOK);
 static Node *adjust_inherited_attrs_mutator(Node *node,
                               adjust_inherited_attrs_context *context);
 
@@ -579,7 +578,7 @@ generate_append_tlist(List *colTypes, bool flag,
  * Resjunk columns are ignored if junkOK is true; otherwise presence of
  * a resjunk column will always cause a 'false' result.
  */
-static bool
+bool
 tlist_same_datatypes(List *tlist, List *colTypes, bool junkOK)
 {
    List       *i;
index 736bb4a4ee0db42634eaab0f81e732af0a963e72..1bb64af3ae5edc21af4e775a7f393c2cd1017039 100644 (file)
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $Id: prep.h,v 1.32 2002/06/20 20:29:51 momjian Exp $
+ * $Id: prep.h,v 1.33 2002/08/29 16:03:49 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -43,4 +43,6 @@ extern Node *adjust_inherited_attrs(Node *node,
                       Index old_rt_index, Oid old_relid,
                       Index new_rt_index, Oid new_relid);
 
+extern bool tlist_same_datatypes(List *tlist, List *colTypes, bool junkOK);
+
 #endif   /* PREP_H */