1
0
mirror of https://github.com/postgres/postgres.git synced 2025-05-03 22:24:49 +03:00

Adjust the definition of is_pushed_down so that it's always true for INNER

JOIN quals, just like WHERE quals, even if they reference every one of the
join's relations.  Now that we can reorder outer and inner joins, it's
possible for such a qual to end up being assigned to an outer join plan node,
and we mustn't have it treated as a join qual rather than a filter qual for
the node.  (If it were, the join could produce null-extended rows that it
shouldn't.)  Per bug report from Pelle Johansson.
This commit is contained in:
Tom Lane 2007-02-16 20:57:26 +00:00
parent 36e24e8d17
commit 33b41590df
2 changed files with 51 additions and 47 deletions

View File

@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/optimizer/plan/initsplan.c,v 1.123.2.3 2007/02/13 02:31:12 tgl Exp $
* $PostgreSQL: pgsql/src/backend/optimizer/plan/initsplan.c,v 1.123.2.4 2007/02/16 20:57:26 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -45,7 +45,6 @@ static OuterJoinInfo *make_outerjoininfo(PlannerInfo *root,
Relids left_rels, Relids right_rels,
bool is_full_join, Node *clause);
static void distribute_qual_to_rels(PlannerInfo *root, Node *clause,
bool is_pushed_down,
bool is_deduced,
bool below_outer_join,
Relids qualscope,
@ -286,12 +285,11 @@ deconstruct_recurse(PlannerInfo *root, Node *jtnode, bool below_outer_join,
}
/*
* Now process the top-level quals. These are always marked as
* "pushed down", since they clearly didn't come from a JOIN expr.
* Now process the top-level quals.
*/
foreach(l, (List *) f->quals)
distribute_qual_to_rels(root, (Node *) lfirst(l),
true, false, below_outer_join,
false, below_outer_join,
*qualscope, NULL, NULL);
}
else if (IsA(jtnode, JoinExpr))
@ -392,7 +390,7 @@ deconstruct_recurse(PlannerInfo *root, Node *jtnode, bool below_outer_join,
/* Process the qual clauses */
foreach(qual, (List *) j->quals)
distribute_qual_to_rels(root, (Node *) lfirst(qual),
false, false, below_outer_join,
false, below_outer_join,
*qualscope, ojscope, nonnullable_rels);
/* Now we can add the OuterJoinInfo to oj_info_list */
@ -603,8 +601,6 @@ make_outerjoininfo(PlannerInfo *root,
* equijoined vars.
*
* 'clause': the qual clause to be distributed
* '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)
* '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.
@ -622,7 +618,6 @@ make_outerjoininfo(PlannerInfo *root,
*/
static void
distribute_qual_to_rels(PlannerInfo *root, Node *clause,
bool is_pushed_down,
bool is_deduced,
bool below_outer_join,
Relids qualscope,
@ -630,6 +625,7 @@ distribute_qual_to_rels(PlannerInfo *root, Node *clause,
Relids outerjoin_nonnullable)
{
Relids relids;
bool is_pushed_down;
bool outerjoin_delayed;
bool pseudoconstant = false;
bool maybe_equijoin;
@ -697,17 +693,37 @@ distribute_qual_to_rels(PlannerInfo *root, Node *clause,
root->hasPseudoConstantQuals = true;
/* if not below outer join, push it to top of tree */
if (!below_outer_join)
{
relids = get_relids_in_jointree((Node *) root->parse->jointree);
is_pushed_down = true;
}
}
}
}
/*
/*----------
* Check to see if clause application must be delayed by outer-join
* considerations.
*
* A word about is_pushed_down: we mark the qual as "pushed down" if
* it is (potentially) applicable at a level different from its original
* syntactic level. This flag is used to distinguish OUTER JOIN ON quals
* from other quals pushed down to the same joinrel. The rules are:
* WHERE quals and INNER JOIN quals: is_pushed_down = true.
* Non-degenerate OUTER JOIN quals: is_pushed_down = false.
* Degenerate OUTER JOIN quals: is_pushed_down = true.
* A "degenerate" OUTER JOIN qual is one that doesn't mention the
* non-nullable side, and hence can be pushed down into the nullable side
* without changing the join result. It is correct to treat it as a
* regular filter condition at the level where it is evaluated.
*
* Note: it is not immediately obvious that a simple boolean is enough
* for this: if for some reason we were to attach a degenerate qual to
* its original join level, it would need to be treated as an outer join
* qual there. However, this cannot happen, because all the rels the
* clause mentions must be in the outer join's min_righthand, therefore
* the join it needs must be formed before the outer join; and we always
* attach quals to the lowest level where they can be evaluated. But
* if we were ever to re-introduce a mechanism for delaying evaluation
* of "expensive" quals, this area would need work.
*----------
*/
if (is_deduced)
{
@ -720,6 +736,7 @@ distribute_qual_to_rels(PlannerInfo *root, Node *clause,
Assert(bms_equal(relids, qualscope));
Assert(!ojscope);
Assert(!pseudoconstant);
is_pushed_down = true;
/* Needn't feed it back for more deductions */
outerjoin_delayed = false;
maybe_equijoin = false;
@ -729,19 +746,16 @@ distribute_qual_to_rels(PlannerInfo *root, Node *clause,
{
/*
* The qual is attached to an outer join and mentions (some of the)
* rels on the nonnullable side. Force the qual to be evaluated
* exactly at the level of joining corresponding to the outer join. We
* cannot let it get pushed down into the nonnullable side, since then
* we'd produce no output rows, rather than the intended single
* null-extended row, for any nonnullable-side rows failing the qual.
*
* Note: an outer-join qual that mentions only nullable-side rels can
* be pushed down into the nullable side without changing the join
* result, so we treat it the same as an ordinary inner-join qual,
* except for not setting maybe_equijoin (see below).
* rels on the nonnullable side, so it's not degenerate. Force the
* qual to be evaluated exactly at the level of joining corresponding
* to the outer join. We cannot let it get pushed down into the
* nonnullable side, since then we'd produce no output rows, rather
* than the intended single null-extended row, for any
* nonnullable-side rows failing the qual.
*/
Assert(ojscope);
relids = ojscope;
is_pushed_down = false;
outerjoin_delayed = true;
Assert(!pseudoconstant);
@ -760,7 +774,10 @@ distribute_qual_to_rels(PlannerInfo *root, Node *clause,
else
{
/*
* For a non-outer-join qual, we can evaluate the qual as soon as (1)
* Normal qual clause or degenerate outer-join clause. Either way,
* we can mark it as pushed-down.
*
* For a pushed-down qual, we can evaluate the qual as soon as (1)
* we have all the rels it mentions, and (2) we are at or above any
* outer joins that can null any of these rels and are below the
* syntactic location of the given qual. We must enforce (2) because
@ -784,6 +801,7 @@ distribute_qual_to_rels(PlannerInfo *root, Node *clause,
*/
bool found_some;
is_pushed_down = true;
outerjoin_delayed = false;
do {
ListCell *l;
@ -846,17 +864,6 @@ distribute_qual_to_rels(PlannerInfo *root, Node *clause,
maybe_outer_join = false;
}
/*
* Mark the qual as "pushed down" if it can be applied at a level below
* its original syntactic level. This allows us to distinguish original
* JOIN/ON quals from higher-level quals pushed down to the same joinrel.
* A qual originating from WHERE is always considered "pushed down". Note
* that for an outer-join qual, we have to compare to ojscope not
* qualscope.
*/
if (!is_pushed_down)
is_pushed_down = !bms_equal(relids, ojscope ? ojscope : qualscope);
/*
* Build the RestrictInfo node itself.
*/
@ -1161,12 +1168,9 @@ process_implied_equality(PlannerInfo *root,
/*
* Push the new clause into all the appropriate restrictinfo lists.
*
* Note: we mark the qual "pushed down" to ensure that it can never be
* taken for an original JOIN/ON clause.
*/
distribute_qual_to_rels(root, (Node *) clause,
true, true, false, relids, NULL, NULL);
true, false, relids, NULL, NULL);
}
/*

View File

@ -7,7 +7,7 @@
* Portions Copyright (c) 1996-2006, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
* $PostgreSQL: pgsql/src/include/nodes/relation.h,v 1.128 2006/10/04 00:30:09 momjian Exp $
* $PostgreSQL: pgsql/src/include/nodes/relation.h,v 1.128.2.1 2007/02/16 20:57:26 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -697,14 +697,14 @@ typedef struct HashPath
* join, we prevent it from being evaluated below the outer join's joinrel.
* When we do form the outer join's joinrel, we still need to distinguish
* those quals that are actually in that join's JOIN/ON condition from those
* that appeared higher in the tree and were pushed down to the join rel
* that appeared elsewhere in the tree and were pushed down to the join rel
* because they used no other rels. That's what the is_pushed_down flag is
* for; it tells us that a qual came from a point above the join of the
* set of base rels listed in required_relids. A clause that originally came
* from WHERE will *always* have its is_pushed_down flag set; a clause that
* came from an INNER JOIN condition, but doesn't use all the rels being
* joined, will also have is_pushed_down set because it will get attached to
* some lower joinrel.
* for; it tells us that a qual is not an OUTER JOIN qual for the set of base
* rels listed in required_relids. A clause that originally came from WHERE
* or an INNER JOIN condition will *always* have its is_pushed_down flag set.
* It's possible for an OUTER JOIN clause to be marked is_pushed_down too,
* if we decide that it can be pushed down into the nullable side of the join.
* In that case it acts as a plain filter qual for wherever it gets evaluated.
*
* When application of a qual must be delayed by outer join, we also mark it
* with outerjoin_delayed = true. This isn't redundant with required_relids