Repair planning bug introduced in 7.4: outer-join ON clauses that referenced

only the inner-side relation would be considered as potential equijoin clauses,
which is wrong because the condition doesn't necessarily hold above the point
of the outer join.  Per test case from Kevin Grittner (bug#1916).
This commit is contained in:
Tom Lane 2005-09-28 21:17:50 +00:00
parent 6a2c659b31
commit 196c9d289e
3 changed files with 67 additions and 19 deletions

View File

@ -8,7 +8,7 @@
* *
* *
* IDENTIFICATION * IDENTIFICATION
* $Header: /cvsroot/pgsql/src/backend/optimizer/plan/initsplan.c,v 1.91.2.2 2004/02/27 21:42:10 tgl Exp $ * $Header: /cvsroot/pgsql/src/backend/optimizer/plan/initsplan.c,v 1.91.2.3 2005/09/28 21:17:48 tgl Exp $
* *
*------------------------------------------------------------------------- *-------------------------------------------------------------------------
*/ */
@ -38,6 +38,7 @@ static void mark_baserels_for_outer_join(Query *root, Relids rels,
static void distribute_qual_to_rels(Query *root, Node *clause, static void distribute_qual_to_rels(Query *root, Node *clause,
bool ispusheddown, bool ispusheddown,
bool isdeduced, bool isdeduced,
bool below_outer_join,
Relids outerjoin_nonnullable, Relids outerjoin_nonnullable,
Relids qualscope); Relids qualscope);
static void add_vars_to_targetlist(Query *root, List *vars, static void add_vars_to_targetlist(Query *root, List *vars,
@ -173,6 +174,10 @@ add_vars_to_targetlist(Query *root, List *vars, Relids where_needed)
* with outerjoinset information, to aid in proper positioning of qual * with outerjoinset information, to aid in proper positioning of qual
* clauses that appear above outer joins. * clauses that appear above outer joins.
* *
* jtnode is the jointree node currently being examined. below_outer_join
* is TRUE if this node is within the nullable side of a higher-level outer
* join.
*
* NOTE: when dealing with inner joins, it is appropriate to let a qual clause * NOTE: when dealing with inner joins, it is appropriate to let a qual clause
* be evaluated at the lowest level where all the variables it mentions are * be evaluated at the lowest level where all the variables it mentions are
* available. However, we cannot push a qual down into the nullable side(s) * available. However, we cannot push a qual down into the nullable side(s)
@ -188,7 +193,8 @@ add_vars_to_targetlist(Query *root, List *vars, Relids where_needed)
* internal convenience; no outside callers pay attention to the result. * internal convenience; no outside callers pay attention to the result.
*/ */
Relids Relids
distribute_quals_to_rels(Query *root, Node *jtnode) distribute_quals_to_rels(Query *root, Node *jtnode,
bool below_outer_join)
{ {
Relids result = NULL; Relids result = NULL;
@ -214,7 +220,8 @@ distribute_quals_to_rels(Query *root, Node *jtnode)
{ {
result = bms_add_members(result, result = bms_add_members(result,
distribute_quals_to_rels(root, distribute_quals_to_rels(root,
lfirst(l))); lfirst(l),
below_outer_join));
} }
/* /*
@ -223,7 +230,8 @@ distribute_quals_to_rels(Query *root, Node *jtnode)
*/ */
foreach(qual, (List *) f->quals) foreach(qual, (List *) f->quals)
distribute_qual_to_rels(root, (Node *) lfirst(qual), distribute_qual_to_rels(root, (Node *) lfirst(qual),
true, false, NULL, result); true, false, below_outer_join,
NULL, result);
} }
else if (IsA(jtnode, JoinExpr)) else if (IsA(jtnode, JoinExpr))
{ {
@ -247,27 +255,47 @@ distribute_quals_to_rels(Query *root, Node *jtnode)
* rels from being pushed down below this level. (It's okay for * rels from being pushed down below this level. (It's okay for
* upper quals to be pushed down to the outer side, however.) * upper quals to be pushed down to the outer side, however.)
*/ */
leftids = distribute_quals_to_rels(root, j->larg);
rightids = distribute_quals_to_rels(root, j->rarg);
result = bms_union(leftids, rightids);
nonnullable_rels = nullable_rels = NULL;
switch (j->jointype) switch (j->jointype)
{ {
case JOIN_INNER: case JOIN_INNER:
leftids = distribute_quals_to_rels(root, j->larg,
below_outer_join);
rightids = distribute_quals_to_rels(root, j->rarg,
below_outer_join);
result = bms_union(leftids, rightids);
/* Inner join adds no restrictions for quals */ /* Inner join adds no restrictions for quals */
nonnullable_rels = NULL;
nullable_rels = NULL;
break; break;
case JOIN_LEFT: case JOIN_LEFT:
leftids = distribute_quals_to_rels(root, j->larg,
below_outer_join);
rightids = distribute_quals_to_rels(root, j->rarg,
true);
result = bms_union(leftids, rightids);
nonnullable_rels = leftids; nonnullable_rels = leftids;
nullable_rels = rightids; nullable_rels = rightids;
break; break;
case JOIN_FULL: case JOIN_FULL:
leftids = distribute_quals_to_rels(root, j->larg,
true);
rightids = distribute_quals_to_rels(root, j->rarg,
true);
result = bms_union(leftids, rightids);
/* each side is both outer and inner */ /* each side is both outer and inner */
nonnullable_rels = result; nonnullable_rels = result;
nullable_rels = result; nullable_rels = result;
break; break;
case JOIN_RIGHT: case JOIN_RIGHT:
leftids = distribute_quals_to_rels(root, j->larg,
true);
rightids = distribute_quals_to_rels(root, j->rarg,
below_outer_join);
result = bms_union(leftids, rightids);
nonnullable_rels = rightids; nonnullable_rels = rightids;
nullable_rels = leftids; nullable_rels = leftids;
break; break;
@ -280,16 +308,20 @@ distribute_quals_to_rels(Query *root, Node *jtnode)
ereport(ERROR, ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED), (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("UNION JOIN is not implemented"))); errmsg("UNION JOIN is not implemented")));
nonnullable_rels = NULL; /* keep compiler quiet */
nullable_rels = NULL;
break; break;
default: default:
elog(ERROR, "unrecognized join type: %d", elog(ERROR, "unrecognized join type: %d",
(int) j->jointype); (int) j->jointype);
nonnullable_rels = NULL; /* keep compiler quiet */
nullable_rels = NULL;
break; break;
} }
foreach(qual, (List *) j->quals) foreach(qual, (List *) j->quals)
distribute_qual_to_rels(root, (Node *) lfirst(qual), distribute_qual_to_rels(root, (Node *) lfirst(qual),
false, false, false, false, below_outer_join,
nonnullable_rels, result); nonnullable_rels, result);
if (nullable_rels != NULL) if (nullable_rels != NULL)
@ -358,6 +390,8 @@ mark_baserels_for_outer_join(Query *root, Relids rels, Relids outerrels)
* 'ispusheddown': if TRUE, force the clause to be marked 'ispusheddown' * 'ispusheddown': if TRUE, force the clause to be marked 'ispusheddown'
* (this indicates the clause came from a FromExpr, not a JoinExpr) * (this indicates the clause came from a FromExpr, not a JoinExpr)
* 'isdeduced': TRUE if the qual came from implied-equality deduction * 'isdeduced': TRUE if the qual came from implied-equality deduction
* 'below_outer_join': TRUE if the qual is from a JOIN/ON that is below the
* nullable side of a higher-level outer join.
* 'outerjoin_nonnullable': NULL if not an outer-join qual, else the set of * 'outerjoin_nonnullable': NULL if not an outer-join qual, else the set of
* baserels appearing on the outer (nonnullable) side of the join * baserels appearing on the outer (nonnullable) side of the join
* 'qualscope': set of baserels the qual's syntactic scope covers * 'qualscope': set of baserels the qual's syntactic scope covers
@ -370,6 +404,7 @@ static void
distribute_qual_to_rels(Query *root, Node *clause, distribute_qual_to_rels(Query *root, Node *clause,
bool ispusheddown, bool ispusheddown,
bool isdeduced, bool isdeduced,
bool below_outer_join,
Relids outerjoin_nonnullable, Relids outerjoin_nonnullable,
Relids qualscope) Relids qualscope)
{ {
@ -448,7 +483,8 @@ distribute_qual_to_rels(Query *root, Node *clause,
* *
* Note: an outer-join qual that mentions only nullable-side rels can * Note: an outer-join qual that mentions only nullable-side rels can
* be pushed down into the nullable side without changing the join * be pushed down into the nullable side without changing the join
* result, so we treat it the same as an ordinary inner-join qual. * result, so we treat it the same as an ordinary inner-join qual,
* except for not setting can_be_equijoin (see below).
*/ */
relids = qualscope; relids = qualscope;
can_be_equijoin = false; can_be_equijoin = false;
@ -482,8 +518,19 @@ distribute_qual_to_rels(Query *root, Node *clause,
if (bms_is_subset(addrelids, relids)) if (bms_is_subset(addrelids, relids))
{ {
/* Qual is not affected by any outer-join restriction */ /*
can_be_equijoin = true; * Qual is not delayed by any lower outer-join restriction.
* If it is not itself below or within an outer join, we
* can consider it "valid everywhere", so consider feeding
* it to the equijoin machinery. (If it is within an outer
* join, we can't consider it "valid everywhere": once the
* contained variables have gone to NULL, we'd be asserting
* things like NULL = NULL, which is not true.)
*/
if (!below_outer_join && outerjoin_nonnullable == NULL)
can_be_equijoin = true;
else
can_be_equijoin = false;
} }
else else
{ {
@ -788,7 +835,7 @@ process_implied_equality(Query *root,
* taken for an original JOIN/ON clause. * taken for an original JOIN/ON clause.
*/ */
distribute_qual_to_rels(root, (Node *) clause, distribute_qual_to_rels(root, (Node *) clause,
true, true, NULL, relids); true, true, false, NULL, relids);
} }
/* /*

View File

@ -14,7 +14,7 @@
* *
* *
* IDENTIFICATION * IDENTIFICATION
* $Header: /cvsroot/pgsql/src/backend/optimizer/plan/planmain.c,v 1.78 2003/08/04 02:40:01 momjian Exp $ * $Header: /cvsroot/pgsql/src/backend/optimizer/plan/planmain.c,v 1.78.4.1 2005/09/28 21:17:49 tgl Exp $
* *
*------------------------------------------------------------------------- *-------------------------------------------------------------------------
*/ */
@ -133,7 +133,7 @@ query_planner(Query *root, List *tlist, double tuple_fraction,
*/ */
build_base_rel_tlists(root, tlist); build_base_rel_tlists(root, tlist);
(void) distribute_quals_to_rels(root, (Node *) root->jointree); (void) distribute_quals_to_rels(root, (Node *) root->jointree, false);
/* /*
* Use the completed lists of equijoined keys to deduce any implied * Use the completed lists of equijoined keys to deduce any implied

View File

@ -7,7 +7,7 @@
* Portions Copyright (c) 1996-2003, PostgreSQL Global Development Group * Portions Copyright (c) 1996-2003, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California * Portions Copyright (c) 1994, Regents of the University of California
* *
* $Id: planmain.h,v 1.75 2003/08/08 21:42:50 momjian Exp $ * $Id: planmain.h,v 1.75.4.1 2005/09/28 21:17:50 tgl Exp $
* *
*------------------------------------------------------------------------- *-------------------------------------------------------------------------
*/ */
@ -57,7 +57,8 @@ extern Result *make_result(List *tlist, Node *resconstantqual, Plan *subplan);
*/ */
extern void add_base_rels_to_query(Query *root, Node *jtnode); extern void add_base_rels_to_query(Query *root, Node *jtnode);
extern void build_base_rel_tlists(Query *root, List *final_tlist); extern void build_base_rel_tlists(Query *root, List *final_tlist);
extern Relids distribute_quals_to_rels(Query *root, Node *jtnode); extern Relids distribute_quals_to_rels(Query *root, Node *jtnode,
bool below_outer_join);
extern void process_implied_equality(Query *root, extern void process_implied_equality(Query *root,
Node *item1, Node *item2, Node *item1, Node *item2,
Oid sortop1, Oid sortop2, Oid sortop1, Oid sortop2,