Skip to content

Commit c171176

Browse files
committed
Repair bug in 8.2's new logic for planning outer joins: we have to allow joins
that overlap an outer join's min_righthand but aren't fully contained in it, to support joining within the RHS after having performed an outer join that can commute with this one. Aside from the direct fix in make_join_rel(), fix has_join_restriction() and GEQO's desirable_join() to consider this possibility. Per report from Ian Harding.
1 parent 849b070 commit c171176

File tree

4 files changed

+55
-23
lines changed

4 files changed

+55
-23
lines changed

src/backend/optimizer/README

+2-2
Original file line numberDiff line numberDiff line change
@@ -223,8 +223,8 @@ rels to the OJ's syntactic rels may be legal. Per identities 1 and 2,
223223
non-FULL joins can be freely associated into the lefthand side of an
224224
OJ, but in general they can't be associated into the righthand side.
225225
So the restriction enforced by make_join_rel is that a proposed join
226-
can't join across a RHS boundary (ie, join anything inside the RHS
227-
to anything else) unless the join validly implements some outer join.
226+
can't join a rel within or partly within an RHS boundary to one outside
227+
the boundary, unless the join validly implements some outer join.
228228
(To support use of identity 3, we have to allow cases where an apparent
229229
violation of a lower OJ's RHS is committed while forming an upper OJ.
230230
If this wouldn't in fact be legal, the upper OJ's minimum LHS or RHS

src/backend/optimizer/geqo/geqo_eval.c

+13-9
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
* Portions Copyright (c) 1996-2007, PostgreSQL Global Development Group
77
* Portions Copyright (c) 1994, Regents of the University of California
88
*
9-
* $PostgreSQL: pgsql/src/backend/optimizer/geqo/geqo_eval.c,v 1.83 2007/01/05 22:19:30 momjian Exp $
9+
* $PostgreSQL: pgsql/src/backend/optimizer/geqo/geqo_eval.c,v 1.84 2007/02/13 02:31:02 tgl Exp $
1010
*
1111
*-------------------------------------------------------------------------
1212
*/
@@ -182,7 +182,7 @@ gimme_tree(Gene *tour, int num_gene, GeqoEvalData *evaldata)
182182
* tour other than the one given. To the extent that the heuristics are
183183
* helpful, however, this will be a better plan than the raw tour.
184184
*
185-
* Also, when a join attempt fails (because of IN-clause constraints), we
185+
* Also, when a join attempt fails (because of OJ or IN constraints), we
186186
* may be able to recover and produce a workable plan, where the old code
187187
* just had to give up. This case acts the same as a false result from
188188
* desirable_join().
@@ -262,9 +262,9 @@ desirable_join(PlannerInfo *root,
262262
return true;
263263

264264
/*
265-
* Join if the rels are members of the same outer-join side. This is
266-
* needed to ensure that we can find a valid solution in a case where
267-
* an OJ contains a clauseless join.
265+
* Join if the rels overlap the same outer-join side and don't already
266+
* implement the outer join. This is needed to ensure that we can find a
267+
* valid solution in a case where an OJ contains a clauseless join.
268268
*/
269269
foreach(l, root->oj_info_list)
270270
{
@@ -273,11 +273,15 @@ desirable_join(PlannerInfo *root,
273273
/* ignore full joins --- other mechanisms preserve their ordering */
274274
if (ojinfo->is_full_join)
275275
continue;
276-
if (bms_is_subset(outer_rel->relids, ojinfo->min_righthand) &&
277-
bms_is_subset(inner_rel->relids, ojinfo->min_righthand))
276+
if (bms_overlap(outer_rel->relids, ojinfo->min_righthand) &&
277+
bms_overlap(inner_rel->relids, ojinfo->min_righthand) &&
278+
!bms_overlap(outer_rel->relids, ojinfo->min_lefthand) &&
279+
!bms_overlap(inner_rel->relids, ojinfo->min_lefthand))
278280
return true;
279-
if (bms_is_subset(outer_rel->relids, ojinfo->min_lefthand) &&
280-
bms_is_subset(inner_rel->relids, ojinfo->min_lefthand))
281+
if (bms_overlap(outer_rel->relids, ojinfo->min_lefthand) &&
282+
bms_overlap(inner_rel->relids, ojinfo->min_lefthand) &&
283+
!bms_overlap(outer_rel->relids, ojinfo->min_righthand) &&
284+
!bms_overlap(inner_rel->relids, ojinfo->min_righthand))
281285
return true;
282286
}
283287

src/backend/optimizer/path/joinrels.c

+30-9
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
*
99
*
1010
* IDENTIFICATION
11-
* $PostgreSQL: pgsql/src/backend/optimizer/path/joinrels.c,v 1.84 2007/01/20 20:45:39 tgl Exp $
11+
* $PostgreSQL: pgsql/src/backend/optimizer/path/joinrels.c,v 1.85 2007/02/13 02:31:03 tgl Exp $
1212
*
1313
*-------------------------------------------------------------------------
1414
*/
@@ -350,8 +350,9 @@ has_join_restriction(PlannerInfo *root, RelOptInfo *rel)
350350
/* ignore full joins --- other mechanisms preserve their ordering */
351351
if (ojinfo->is_full_join)
352352
continue;
353-
/* anything inside the RHS is definitely restricted */
354-
if (bms_is_subset(rel->relids, ojinfo->min_righthand))
353+
/* if it overlaps RHS and isn't yet joined to LHS, it's restricted */
354+
if (bms_overlap(rel->relids, ojinfo->min_righthand) &&
355+
!bms_overlap(rel->relids, ojinfo->min_lefthand))
355356
return true;
356357
/* if it's a proper subset of the LHS, it's also restricted */
357358
if (bms_is_subset(rel->relids, ojinfo->min_lefthand) &&
@@ -468,16 +469,36 @@ make_join_rel(PlannerInfo *root, RelOptInfo *rel1, RelOptInfo *rel2)
468469
/*----------
469470
* Otherwise, the proposed join overlaps the RHS but isn't
470471
* a valid implementation of this OJ. It might still be
471-
* a valid implementation of some other OJ, however. We have
472-
* to allow this to support the associative identity
473-
* (a LJ b on Pab) LJ c ON Pbc = a LJ (b LJ c ON Pbc) on Pab
472+
* a legal join, however. If both inputs overlap the RHS,
473+
* assume that it's OK. Since the inputs presumably got past
474+
* this function's checks previously, they can't overlap the
475+
* LHS and their violations of the RHS boundary must represent
476+
* OJs that have been determined to commute with this one.
477+
* We have to allow this to work correctly in cases like
478+
* (a LEFT JOIN (b JOIN (c LEFT JOIN d)))
479+
* when the c/d join has been determined to commute with the join
480+
* to a, and hence d is not part of min_righthand for the upper
481+
* join. It should be legal to join b to c/d but this will appear
482+
* as a violation of the upper join's RHS.
483+
* Furthermore, if one input overlaps the RHS and the other does
484+
* not, we should still allow the join if it is a valid
485+
* implementation of some other OJ. We have to allow this to
486+
* support the associative identity
487+
* (a LJ b on Pab) LJ c ON Pbc = a LJ (b LJ c ON Pbc) on Pab
474488
* since joining B directly to C violates the lower OJ's RHS.
475489
* We assume that make_outerjoininfo() set things up correctly
476-
* so that we'll only match to the upper OJ if the transformation
477-
* is valid. Set flag here to check at bottom of loop.
490+
* so that we'll only match to some OJ if the join is valid.
491+
* Set flag here to check at bottom of loop.
478492
*----------
479493
*/
480-
is_valid_inner = false;
494+
if (bms_overlap(rel1->relids, ojinfo->min_righthand) &&
495+
bms_overlap(rel2->relids, ojinfo->min_righthand))
496+
{
497+
/* seems OK */
498+
Assert(!bms_overlap(joinrelids, ojinfo->min_lefthand));
499+
}
500+
else
501+
is_valid_inner = false;
481502
}
482503
}
483504

src/backend/optimizer/plan/initsplan.c

+10-3
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
*
99
*
1010
* IDENTIFICATION
11-
* $PostgreSQL: pgsql/src/backend/optimizer/plan/initsplan.c,v 1.129 2007/02/01 19:10:26 momjian Exp $
11+
* $PostgreSQL: pgsql/src/backend/optimizer/plan/initsplan.c,v 1.130 2007/02/13 02:31:03 tgl Exp $
1212
*
1313
*-------------------------------------------------------------------------
1414
*/
@@ -370,7 +370,9 @@ deconstruct_recurse(PlannerInfo *root, Node *jtnode, bool below_outer_join,
370370

371371
/*
372372
* For an OJ, form the OuterJoinInfo now, because we need the OJ's
373-
* semantic scope (ojscope) to pass to distribute_qual_to_rels.
373+
* semantic scope (ojscope) to pass to distribute_qual_to_rels. But
374+
* we mustn't add it to oj_info_list just yet, because we don't want
375+
* distribute_qual_to_rels to think it is an outer join below us.
374376
*/
375377
if (j->jointype != JOIN_INNER)
376378
{
@@ -451,8 +453,13 @@ deconstruct_recurse(PlannerInfo *root, Node *jtnode, bool below_outer_join,
451453
* the caller, so that left_rels is always the nonnullable side. Hence
452454
* we need only distinguish the LEFT and FULL cases.
453455
*
454-
* The node should eventually be put into root->oj_info_list, but we
456+
* The node should eventually be appended to root->oj_info_list, but we
455457
* do not do that here.
458+
*
459+
* Note: we assume that this function is invoked bottom-up, so that
460+
* root->oj_info_list already contains entries for all outer joins that are
461+
* syntactically below this one; and indeed that oj_info_list is ordered
462+
* with syntactically lower joins listed first.
456463
*/
457464
static OuterJoinInfo *
458465
make_outerjoininfo(PlannerInfo *root,

0 commit comments

Comments
 (0)