From b06fbc7ad2cb2467a51432c01dd0d0b881667cb9 Mon Sep 17 00:00:00 2001
From: Tom Lane <tgl@sss.pgh.pa.us>
Date: Thu, 18 Jan 2001 07:12:37 +0000
Subject: [PATCH] Fix performance issue with qualifications on VIEWs: outer
 query should try to push restrictions on the view down into the view
 subquery, so that they can become indexscan quals or what-have-you rather
 than being applied at the top level of the subquery.  7.0 and before were
 able to do this, though in a much klugier way, and I'd hate to have anyone
 complaining that 7.1 is stupider than 7.0 ...

---
 src/backend/optimizer/path/allpaths.c | 63 +++++++++++++++++++++++++--
 src/backend/optimizer/plan/planner.c  | 59 ++++++++++++++++---------
 2 files changed, 97 insertions(+), 25 deletions(-)

diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c
index 4f7a0e570f..5e461cb3c1 100644
--- a/src/backend/optimizer/path/allpaths.c
+++ b/src/backend/optimizer/path/allpaths.c
@@ -8,13 +8,14 @@
  *
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/optimizer/path/allpaths.c,v 1.68 2000/12/14 22:30:43 tgl Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/optimizer/path/allpaths.c,v 1.69 2001/01/18 07:12:37 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
 
 #include "postgres.h"
 
+#include "optimizer/clauses.h"
 #include "optimizer/cost.h"
 #include "optimizer/geqo.h"
 #include "optimizer/pathnode.h"
@@ -23,6 +24,7 @@
 #include "optimizer/planner.h"
 #include "optimizer/prep.h"
 #include "parser/parsetree.h"
+#include "rewrite/rewriteManip.h"
 
 
 bool		enable_geqo = true;
@@ -99,12 +101,65 @@ set_base_rel_pathlists(Query *root)
 		if (rel->issubquery)
 		{
 			/* Subquery --- generate a separate plan for it */
+			List	   *upperrestrictlist;
+			List	   *lst;
 
 			/*
-			 * XXX for now, we just apply any restrict clauses that came
-			 * from the outer query as qpquals of the SubqueryScan node.
-			 * Later, think about pushing them down into the subquery itself.
+			 * If there are any restriction clauses that have been attached
+			 * to the subquery relation, consider pushing them down to become
+			 * HAVING quals of the subquery itself.  (Not WHERE clauses, since
+			 * they may refer to subquery outputs that are aggregate results.
+			 * But planner.c will transfer them into the subquery's WHERE if
+			 * they do not.)  This transformation is useful because it may
+			 * allow us to generate a better plan for the subquery than
+			 * evaluating all the subquery output rows and then filtering
+			 * them.
+			 *
+			 * Currently, 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.
+			 *
+			 * 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?
 			 */
+			upperrestrictlist = NIL;
+			foreach(lst, rel->baserestrictinfo)
+			{
+				RestrictInfo   *rinfo = (RestrictInfo *) lfirst(lst);
+				Node		   *clause = (Node *) rinfo->clause;
+
+				if (contain_subplans(clause))
+				{
+					/* Keep it in the upper query */
+					upperrestrictlist = lappend(upperrestrictlist, rinfo);
+				}
+				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,
+										rte->subquery->targetList,
+										CMD_SELECT, 0);
+					rte->subquery->havingQual =
+						make_and_qual(rte->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.
+					 */
+				}
+			}
+			rel->baserestrictinfo = upperrestrictlist;
 
 			/* Generate the plan for the subquery */
 			rel->subplan = subquery_planner(rte->subquery,
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index 5d845e9842..d2ec1b8a75 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/optimizer/plan/planner.c,v 1.98 2000/12/14 22:30:43 tgl Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/optimizer/plan/planner.c,v 1.99 2001/01/18 07:12:37 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -132,6 +132,7 @@ subquery_planner(Query *parse, double tuple_fraction)
 	List	   *saved_initplan = PlannerInitPlan;
 	int			saved_planid = PlannerPlanId;
 	Plan	   *plan;
+	List	   *newHaving;
 	List	   *lst;
 
 	/* Set up for a new level of subquery */
@@ -155,20 +156,6 @@ subquery_planner(Query *parse, double tuple_fraction)
 	parse->jointree = (FromExpr *)
 		preprocess_jointree(parse, (Node *) parse->jointree);
 
-	/*
-	 * A HAVING clause without aggregates is equivalent to a WHERE clause
-	 * (except it can only refer to grouped fields).  If there are no aggs
-	 * anywhere in the query, then we don't want to create an Agg plan
-	 * node, so merge the HAVING condition into WHERE.	(We used to
-	 * consider this an error condition, but it seems to be legal SQL.)
-	 */
-	if (parse->havingQual != NULL && !parse->hasAggs)
-	{
-		parse->jointree->quals = make_and_qual(parse->jointree->quals,
-											   parse->havingQual);
-		parse->havingQual = NULL;
-	}
-
 	/*
 	 * Do expression preprocessing on targetlist and quals.
 	 */
@@ -181,6 +168,37 @@ subquery_planner(Query *parse, double tuple_fraction)
 	parse->havingQual = preprocess_expression(parse, parse->havingQual,
 											  EXPRKIND_HAVING);
 
+	/*
+	 * A HAVING clause without aggregates is equivalent to a WHERE clause
+	 * (except it can only refer to grouped fields).  Transfer any agg-free
+	 * clauses of the HAVING qual into WHERE.  This may seem like wasting
+	 * cycles to cater to stupidly-written queries, but there are other
+	 * reasons for doing it.  Firstly, if the query contains no aggs at all,
+	 * then we aren't going to generate an Agg plan node, and so there'll be
+	 * no place to execute HAVING conditions; without this transfer, we'd
+	 * lose the HAVING condition entirely, which is wrong.  Secondly, when
+	 * we push down a qual condition into a sub-query, it's easiest to push
+	 * the qual into HAVING always, in case it contains aggs, and then let
+	 * this code sort it out.
+	 *
+	 * Note that both havingQual and parse->jointree->quals are in
+	 * implicitly-ANDed-list form at this point, even though they are
+	 * declared as Node *.  Also note that contain_agg_clause does not
+	 * recurse into sub-selects, which is exactly what we need here.
+	 */
+	newHaving = NIL;
+	foreach(lst, (List *) parse->havingQual)
+	{
+		Node   *havingclause = (Node *) lfirst(lst);
+
+		if (contain_agg_clause(havingclause))
+			newHaving = lappend(newHaving, havingclause);
+		else
+			parse->jointree->quals = (Node *)
+				lappend((List *) parse->jointree->quals, havingclause);
+	}
+	parse->havingQual = (Node *) newHaving;
+
 	/*
 	 * Do the main planning.  If we have an inherited target relation,
 	 * that needs special processing, else go straight to grouping_planner.
@@ -554,12 +572,6 @@ preprocess_expression(Query *parse, Node *expr, int kind)
 			 * Check for ungrouped variables passed to subplans.  Note we
 			 * do NOT do this for subplans in WHERE (or JOIN/ON); it's legal
 			 * there because WHERE is evaluated pre-GROUP.
-			 *
-			 * An interesting fine point: if subquery_planner reassigned a
-			 * HAVING qual into WHERE, then we will accept references to
-			 * ungrouped vars from subplans in the HAVING qual.  This is not
-			 * entirely consistent, but it doesn't seem particularly
-			 * harmful...
 			 */
 			check_subplans_for_ungrouped_vars(expr, parse);
 		}
@@ -1049,6 +1061,11 @@ grouping_planner(Query *parse, double tuple_fraction)
 										result_plan);
 		/* Note: Agg does not affect any existing sort order of the tuples */
 	}
+	else
+	{
+		/* If there are no Aggs, we shouldn't have any HAVING qual anymore */
+		Assert(parse->havingQual == NULL);
+	}
 
 	/*
 	 * If we were not able to make the plan come out in the right order,
-- 
2.24.1