Commit b06fbc7a authored by Tom Lane's avatar Tom Lane

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 ...
parent 7705581e
......@@ -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,
......
......@@ -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,
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment