Commit 2e1254e7 authored by Tom Lane's avatar Tom Lane

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).
parent 4ff20326
...@@ -8,7 +8,7 @@ ...@@ -8,7 +8,7 @@
* *
* *
* IDENTIFICATION * IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/optimizer/plan/initsplan.c,v 1.108 2005/07/02 23:00:41 tgl Exp $ * $PostgreSQL: pgsql/src/backend/optimizer/plan/initsplan.c,v 1.109 2005/09/28 21:17:02 tgl Exp $
* *
*------------------------------------------------------------------------- *-------------------------------------------------------------------------
*/ */
...@@ -38,7 +38,8 @@ static void mark_baserels_for_outer_join(PlannerInfo *root, Relids rels, ...@@ -38,7 +38,8 @@ static void mark_baserels_for_outer_join(PlannerInfo *root, Relids rels,
Relids outerrels); Relids outerrels);
static void distribute_qual_to_rels(PlannerInfo *root, Node *clause, static void distribute_qual_to_rels(PlannerInfo *root, Node *clause,
bool is_pushed_down, bool is_pushed_down,
bool isdeduced, bool is_deduced,
bool below_outer_join,
Relids outerjoin_nonnullable, Relids outerjoin_nonnullable,
Relids qualscope); Relids qualscope);
static void add_vars_to_targetlist(PlannerInfo *root, List *vars, static void add_vars_to_targetlist(PlannerInfo *root, List *vars,
...@@ -174,6 +175,10 @@ add_vars_to_targetlist(PlannerInfo *root, List *vars, Relids where_needed) ...@@ -174,6 +175,10 @@ add_vars_to_targetlist(PlannerInfo *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)
...@@ -189,7 +194,8 @@ add_vars_to_targetlist(PlannerInfo *root, List *vars, Relids where_needed) ...@@ -189,7 +194,8 @@ add_vars_to_targetlist(PlannerInfo *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(PlannerInfo *root, Node *jtnode) distribute_quals_to_rels(PlannerInfo *root, Node *jtnode,
bool below_outer_join)
{ {
Relids result = NULL; Relids result = NULL;
...@@ -214,7 +220,8 @@ distribute_quals_to_rels(PlannerInfo *root, Node *jtnode) ...@@ -214,7 +220,8 @@ distribute_quals_to_rels(PlannerInfo *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(PlannerInfo *root, Node *jtnode) ...@@ -223,7 +230,8 @@ distribute_quals_to_rels(PlannerInfo *root, Node *jtnode)
*/ */
foreach(l, (List *) f->quals) foreach(l, (List *) f->quals)
distribute_qual_to_rels(root, (Node *) lfirst(l), distribute_qual_to_rels(root, (Node *) lfirst(l),
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(PlannerInfo *root, Node *jtnode) ...@@ -247,27 +255,47 @@ distribute_quals_to_rels(PlannerInfo *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(PlannerInfo *root, Node *jtnode) ...@@ -280,16 +308,20 @@ distribute_quals_to_rels(PlannerInfo *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)
...@@ -357,7 +389,9 @@ mark_baserels_for_outer_join(PlannerInfo *root, Relids rels, Relids outerrels) ...@@ -357,7 +389,9 @@ mark_baserels_for_outer_join(PlannerInfo *root, Relids rels, Relids outerrels)
* 'clause': the qual clause to be distributed * 'clause': the qual clause to be distributed
* 'is_pushed_down': if TRUE, force the clause to be marked 'is_pushed_down' * 'is_pushed_down': if TRUE, force the clause to be marked 'is_pushed_down'
* (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 * 'is_deduced': 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
...@@ -369,7 +403,8 @@ mark_baserels_for_outer_join(PlannerInfo *root, Relids rels, Relids outerrels) ...@@ -369,7 +403,8 @@ mark_baserels_for_outer_join(PlannerInfo *root, Relids rels, Relids outerrels)
static void static void
distribute_qual_to_rels(PlannerInfo *root, Node *clause, distribute_qual_to_rels(PlannerInfo *root, Node *clause,
bool is_pushed_down, bool is_pushed_down,
bool isdeduced, bool is_deduced,
bool below_outer_join,
Relids outerjoin_nonnullable, Relids outerjoin_nonnullable,
Relids qualscope) Relids qualscope)
{ {
...@@ -406,7 +441,7 @@ distribute_qual_to_rels(PlannerInfo *root, Node *clause, ...@@ -406,7 +441,7 @@ distribute_qual_to_rels(PlannerInfo *root, Node *clause,
* Check to see if clause application must be delayed by outer-join * Check to see if clause application must be delayed by outer-join
* considerations. * considerations.
*/ */
if (isdeduced) if (is_deduced)
{ {
/* /*
* If the qual came from implied-equality deduction, we always * If the qual came from implied-equality deduction, we always
...@@ -432,14 +467,18 @@ distribute_qual_to_rels(PlannerInfo *root, Node *clause, ...@@ -432,14 +467,18 @@ distribute_qual_to_rels(PlannerInfo *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 maybe_equijoin (see below).
*/ */
relids = qualscope; relids = qualscope;
/* /*
* We can't use such a clause to deduce equijoin (the left and * We can't use such a clause to deduce equijoin (the left and
* right sides might be unequal above the join because one of * right sides might be unequal above the join because one of
* them has gone to NULL) ... but we might be able to use it * them has gone to NULL) ... but we might be able to use it
* for more limited purposes. * for more limited purposes. Note: for the current uses of
* deductions from an outer-join clause, it seems safe to make
* the deductions even when the clause is below a higher-level
* outer join; so we do not check below_outer_join here.
*/ */
maybe_equijoin = false; maybe_equijoin = false;
maybe_outer_join = true; maybe_outer_join = true;
...@@ -473,8 +512,19 @@ distribute_qual_to_rels(PlannerInfo *root, Node *clause, ...@@ -473,8 +512,19 @@ distribute_qual_to_rels(PlannerInfo *root, Node *clause,
if (bms_is_subset(addrelids, relids)) if (bms_is_subset(addrelids, relids))
{ {
/* Qual is not affected by any outer-join restriction */ /*
maybe_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)
maybe_equijoin = true;
else
maybe_equijoin = false;
} }
else else
{ {
...@@ -543,7 +593,7 @@ distribute_qual_to_rels(PlannerInfo *root, Node *clause, ...@@ -543,7 +593,7 @@ distribute_qual_to_rels(PlannerInfo *root, Node *clause,
* redundancy will be detected when the join clause is moved * redundancy will be detected when the join clause is moved
* into a join rel's restriction list.) * into a join rel's restriction list.)
*/ */
if (!isdeduced || if (!is_deduced ||
!qual_is_redundant(root, restrictinfo, !qual_is_redundant(root, restrictinfo,
rel->baserestrictinfo)) rel->baserestrictinfo))
{ {
...@@ -810,7 +860,7 @@ process_implied_equality(PlannerInfo *root, ...@@ -810,7 +860,7 @@ process_implied_equality(PlannerInfo *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);
} }
/* /*
......
...@@ -14,7 +14,7 @@ ...@@ -14,7 +14,7 @@
* *
* *
* IDENTIFICATION * IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/optimizer/plan/planmain.c,v 1.87 2005/08/27 22:13:43 tgl Exp $ * $PostgreSQL: pgsql/src/backend/optimizer/plan/planmain.c,v 1.88 2005/09/28 21:17:02 tgl Exp $
* *
*------------------------------------------------------------------------- *-------------------------------------------------------------------------
*/ */
...@@ -155,7 +155,7 @@ query_planner(PlannerInfo *root, List *tlist, double tuple_fraction, ...@@ -155,7 +155,7 @@ query_planner(PlannerInfo *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 *) parse->jointree); (void) distribute_quals_to_rels(root, (Node *) parse->jointree, false);
/* /*
* Use the completed lists of equijoined keys to deduce any implied * Use the completed lists of equijoined keys to deduce any implied
......
...@@ -7,7 +7,7 @@ ...@@ -7,7 +7,7 @@
* Portions Copyright (c) 1996-2005, PostgreSQL Global Development Group * Portions Copyright (c) 1996-2005, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California * Portions Copyright (c) 1994, Regents of the University of California
* *
* $PostgreSQL: pgsql/src/include/optimizer/planmain.h,v 1.88 2005/08/27 22:13:44 tgl Exp $ * $PostgreSQL: pgsql/src/include/optimizer/planmain.h,v 1.89 2005/09/28 21:17:02 tgl Exp $
* *
*------------------------------------------------------------------------- *-------------------------------------------------------------------------
*/ */
...@@ -67,7 +67,8 @@ extern bool is_projection_capable_plan(Plan *plan); ...@@ -67,7 +67,8 @@ extern bool is_projection_capable_plan(Plan *plan);
*/ */
extern void add_base_rels_to_query(PlannerInfo *root, Node *jtnode); extern void add_base_rels_to_query(PlannerInfo *root, Node *jtnode);
extern void build_base_rel_tlists(PlannerInfo *root, List *final_tlist); extern void build_base_rel_tlists(PlannerInfo *root, List *final_tlist);
extern Relids distribute_quals_to_rels(PlannerInfo *root, Node *jtnode); extern Relids distribute_quals_to_rels(PlannerInfo *root, Node *jtnode,
bool below_outer_join);
extern void process_implied_equality(PlannerInfo *root, extern void process_implied_equality(PlannerInfo *root,
Node *item1, Node *item2, Node *item1, Node *item2,
Oid sortop1, Oid sortop2, Oid sortop1, Oid sortop2,
......
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