1
0
mirror of https://github.com/postgres/postgres.git synced 2025-07-14 08:21:07 +03:00

Further fixes for degenerate outer join clauses.

Further testing revealed that commit f69b4b9495 was still a few
bricks shy of a load: minor tweaking of the previous test cases resulted
in the same wrong-outer-join-order problem coming back.  After study
I concluded that my previous changes in make_outerjoininfo() were just
accidentally masking the problem, and should be reverted in favor of
forcing syntactic join order whenever an upper outer join's predicate
doesn't mention a lower outer join's LHS.  This still allows the
chained-outer-joins style that is the normally optimizable case.

I also tightened things up some more in join_is_legal().  It seems to me
on review that what's really happening in the exception case where we
ignore a mismatched special join is that we're allowing the proposed join
to associate into the RHS of the outer join we're comparing it to.  As
such, we should *always* insist that the proposed join be a left join,
which eliminates a bunch of rather dubious argumentation.  The case where
we weren't enforcing that was the one that was already known buggy anyway
(it had a violatable Assert before the aforesaid commit) so it hardly
deserves a lot of deference.

Back-patch to all active branches, like the previous patch.  The added
regression test case failed in all branches back to 9.1, and I think it's
only an unrelated change in costing calculations that kept 9.0 from
choosing a broken plan.
This commit is contained in:
Tom Lane
2015-08-06 15:35:27 -04:00
parent df0a67f754
commit 8703059c6b
5 changed files with 153 additions and 68 deletions

View File

@ -1128,17 +1128,6 @@ make_outerjoininfo(PlannerInfo *root,
min_righthand = bms_int_members(bms_union(clause_relids, inner_join_rels),
right_rels);
/*
* If we have a degenerate join clause that doesn't mention any RHS rels,
* force the min RHS to be the syntactic RHS; otherwise we can end up
* making serious errors, like putting the LHS on the wrong side of an
* outer join. It seems to be safe to not do this when we have a
* contribution from inner_join_rels, though; that's enough to pin the SJ
* to occur at a reasonable place in the tree.
*/
if (bms_is_empty(min_righthand))
min_righthand = bms_copy(right_rels);
/*
* Now check previous outer joins for ordering restrictions.
*/
@ -1181,9 +1170,15 @@ make_outerjoininfo(PlannerInfo *root,
* For a lower OJ in our RHS, if our join condition does not use the
* lower join's RHS and the lower OJ's join condition is strict, we
* can interchange the ordering of the two OJs; otherwise we must add
* the lower OJ's full syntactic relset to min_righthand. Also, we
* must preserve ordering anyway if either the current join or the
* lower OJ is either a semijoin or an antijoin.
* the lower OJ's full syntactic relset to min_righthand.
*
* Also, if our join condition does not use the lower join's LHS
* either, force the ordering to be preserved. Otherwise we can end
* up with SpecialJoinInfos with identical min_righthands, which can
* confuse join_is_legal (see discussion in backend/optimizer/README).
*
* Also, we must preserve ordering anyway if either the current join
* or the lower OJ is either a semijoin or an antijoin.
*
* Here, we have to consider that "our join condition" includes any
* clauses that syntactically appeared above the lower OJ and below
@ -1199,6 +1194,7 @@ make_outerjoininfo(PlannerInfo *root,
if (bms_overlap(right_rels, otherinfo->syn_righthand))
{
if (bms_overlap(clause_relids, otherinfo->syn_righthand) ||
!bms_overlap(clause_relids, otherinfo->min_lefthand) ||
jointype == JOIN_SEMI ||
jointype == JOIN_ANTI ||
otherinfo->jointype == JOIN_SEMI ||
@ -1238,10 +1234,12 @@ make_outerjoininfo(PlannerInfo *root,
* If we found nothing to put in min_lefthand, punt and make it the full
* LHS, to avoid having an empty min_lefthand which will confuse later
* processing. (We don't try to be smart about such cases, just correct.)
* We already forced min_righthand nonempty, so nothing to do for that.
* Likewise for min_righthand.
*/
if (bms_is_empty(min_lefthand))
min_lefthand = bms_copy(left_rels);
if (bms_is_empty(min_righthand))
min_righthand = bms_copy(right_rels);
/* Now they'd better be nonempty */
Assert(!bms_is_empty(min_lefthand));