Push down outer qualification clauses into UNION and INTERSECT subqueries.
authorTom Lane <tgl@sss.pgh.pa.us>
Thu, 29 Aug 2002 16:03:49 +0000 (16:03 +0000)
committerTom Lane <tgl@sss.pgh.pa.us>
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 */