mirror of https://github.com/postgres/postgres
Push down outer qualification clauses into UNION and INTERSECT subqueries.
Per pghackers discussion from back around 1-August.
This commit is contained in:
parent
d2236800ee
commit
0201dac1c3
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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 */
|
||||
|
|
Loading…
Reference in New Issue