]> granicus.if.org Git - postgresql/commitdiff
Rewrite make_outerjoininfo's construction of min_lefthand and min_righthand
authorTom Lane <tgl@sss.pgh.pa.us>
Fri, 31 Aug 2007 01:44:14 +0000 (01:44 +0000)
committerTom Lane <tgl@sss.pgh.pa.us>
Fri, 31 Aug 2007 01:44:14 +0000 (01:44 +0000)
sets for outer joins, in the light of bug #3588 and additional thought and
experimentation.  The original methodology was fatally flawed for nests of
more than two outer joins: it got the relationships between adjacent joins
right, but didn't always come to the right conclusions about whether a join
could be interchanged with one two or more levels below it.  This was largely
caused by a mistaken idea that we should use the min_lefthand + min_righthand
sets of a sub-join as the minimum left or right input set of an upper join
when we conclude that the sub-join can't commute with the upper one.  If
there's a still-lower join that the sub-join *can* commute with, this method
led us to think that that one could commute with the topmost join; which it
can't.  Another problem (not directly connected to bug #3588) was that
make_outerjoininfo's processing-order-dependent method for enforcing outer
join identity #3 didn't work right: if we decided that join A could safely
commute with lower join B, we dropped all information about sub-joins under B
that join A could perhaps not safely commute with, because we removed B's
entire min_righthand from A's.

To fix, make an explicit computation of all inner join combinations that occur
below an outer join, and add to that the full syntactic relsets of any lower
outer joins that we determine it can't commute with.  This method gives much
more direct enforcement of the outer join rearrangement identities, and it
turns out not to cost a lot of additional bookkeeping.

Thanks to Richard Harris for the bug report and test case.

src/backend/nodes/copyfuncs.c
src/backend/nodes/equalfuncs.c
src/backend/nodes/outfuncs.c
src/backend/optimizer/plan/initsplan.c
src/include/nodes/relation.h
src/test/regress/expected/join.out
src/test/regress/expected/join_1.out
src/test/regress/sql/join.sql

index a9cb0416c345d2bf7036dfae063c60796fd3b1ac..6aa7e5590877600715331bdb0e058c1d66cbedc9 100644 (file)
@@ -15,7 +15,7 @@
  * Portions Copyright (c) 1994, Regents of the University of California
  *
  * IDENTIFICATION
- *       $PostgreSQL: pgsql/src/backend/nodes/copyfuncs.c,v 1.353.2.1 2007/05/22 23:24:08 tgl Exp $
+ *       $PostgreSQL: pgsql/src/backend/nodes/copyfuncs.c,v 1.353.2.2 2007/08/31 01:44:14 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -1316,6 +1316,8 @@ _copyOuterJoinInfo(OuterJoinInfo *from)
 
        COPY_BITMAPSET_FIELD(min_lefthand);
        COPY_BITMAPSET_FIELD(min_righthand);
+       COPY_BITMAPSET_FIELD(syn_lefthand);
+       COPY_BITMAPSET_FIELD(syn_righthand);
        COPY_SCALAR_FIELD(is_full_join);
        COPY_SCALAR_FIELD(lhs_strict);
        COPY_SCALAR_FIELD(delay_upper_joins);
index 13be0e0f9c84b697b1bc5359f514ba7abc790532..0979e2b8716b720a3dfeb827a6e9caeae7458584 100644 (file)
@@ -18,7 +18,7 @@
  * Portions Copyright (c) 1994, Regents of the University of California
  *
  * IDENTIFICATION
- *       $PostgreSQL: pgsql/src/backend/nodes/equalfuncs.c,v 1.287.2.1 2007/05/22 23:24:08 tgl Exp $
+ *       $PostgreSQL: pgsql/src/backend/nodes/equalfuncs.c,v 1.287.2.2 2007/08/31 01:44:14 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -612,6 +612,8 @@ _equalOuterJoinInfo(OuterJoinInfo *a, OuterJoinInfo *b)
 {
        COMPARE_BITMAPSET_FIELD(min_lefthand);
        COMPARE_BITMAPSET_FIELD(min_righthand);
+       COMPARE_BITMAPSET_FIELD(syn_lefthand);
+       COMPARE_BITMAPSET_FIELD(syn_righthand);
        COMPARE_SCALAR_FIELD(is_full_join);
        COMPARE_SCALAR_FIELD(lhs_strict);
        COMPARE_SCALAR_FIELD(delay_upper_joins);
index 3d02e3fada795ad79fa840dbec1cee8078805574..ac6787688d777302127724e51ea04681d41add8f 100644 (file)
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *       $PostgreSQL: pgsql/src/backend/nodes/outfuncs.c,v 1.285.2.3 2007/07/17 01:21:55 tgl Exp $
+ *       $PostgreSQL: pgsql/src/backend/nodes/outfuncs.c,v 1.285.2.4 2007/08/31 01:44:14 tgl Exp $
  *
  * NOTES
  *       Every node type that can appear in stored rules' parsetrees *must*
@@ -1291,6 +1291,8 @@ _outOuterJoinInfo(StringInfo str, OuterJoinInfo *node)
 
        WRITE_BITMAPSET_FIELD(min_lefthand);
        WRITE_BITMAPSET_FIELD(min_righthand);
+       WRITE_BITMAPSET_FIELD(syn_lefthand);
+       WRITE_BITMAPSET_FIELD(syn_righthand);
        WRITE_BOOL_FIELD(is_full_join);
        WRITE_BOOL_FIELD(lhs_strict);
        WRITE_BOOL_FIELD(delay_upper_joins);
index 0b315f2cd7df324891f67f67019d77be7f59faa0..f991f30e130648368a64878d54313eeb1790b781 100644 (file)
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *       $PostgreSQL: pgsql/src/backend/optimizer/plan/initsplan.c,v 1.123.2.5 2007/05/22 23:24:08 tgl Exp $
+ *       $PostgreSQL: pgsql/src/backend/optimizer/plan/initsplan.c,v 1.123.2.6 2007/08/31 01:44:14 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -40,9 +40,11 @@ int                  join_collapse_limit;
 static void add_vars_to_targetlist(PlannerInfo *root, List *vars,
                                           Relids where_needed);
 static List *deconstruct_recurse(PlannerInfo *root, Node *jtnode,
-                                       bool below_outer_join, Relids *qualscope);
+                                       bool below_outer_join,
+                                       Relids *qualscope, Relids *inner_join_rels);
 static OuterJoinInfo *make_outerjoininfo(PlannerInfo *root,
                                   Relids left_rels, Relids right_rels,
+                                  Relids inner_join_rels,
                                   bool is_full_join, Node *clause);
 static void distribute_qual_to_rels(PlannerInfo *root, Node *clause,
                                                bool is_deduced,
@@ -206,13 +208,14 @@ List *
 deconstruct_jointree(PlannerInfo *root)
 {
        Relids          qualscope;
+       Relids          inner_join_rels;
 
        /* Start recursion at top of jointree */
        Assert(root->parse->jointree != NULL &&
                   IsA(root->parse->jointree, FromExpr));
 
        return deconstruct_recurse(root, (Node *) root->parse->jointree, false,
-                                                          &qualscope);
+                                                          &qualscope, &inner_join_rels);
 }
 
 /*
@@ -226,20 +229,24 @@ deconstruct_jointree(PlannerInfo *root)
  * Outputs:
  *     *qualscope gets the set of base Relids syntactically included in this
  *             jointree node (do not modify or free this, as it may also be pointed
- *             to by RestrictInfo nodes)
+ *             to by RestrictInfo and OuterJoinInfo nodes)
+ *     *inner_join_rels gets the set of base Relids syntactically included in
+ *             inner joins appearing at or below this jointree node (do not modify
+ *             or free this, either)
  *     Return value is the appropriate joinlist for this jointree node
  *
  * In addition, entries will be added to root->oj_info_list for outer joins.
  */
 static List *
 deconstruct_recurse(PlannerInfo *root, Node *jtnode, bool below_outer_join,
-                                       Relids *qualscope)
+                                       Relids *qualscope, Relids *inner_join_rels)
 {
        List       *joinlist;
 
        if (jtnode == NULL)
        {
                *qualscope = NULL;
+               *inner_join_rels = NULL;
                return NIL;
        }
        if (IsA(jtnode, RangeTblRef))
@@ -248,6 +255,8 @@ deconstruct_recurse(PlannerInfo *root, Node *jtnode, bool below_outer_join,
 
                /* No quals to deal with, just return correct result */
                *qualscope = bms_make_singleton(varno);
+               /* A single baserel does not create an inner join */
+               *inner_join_rels = NULL;
                joinlist = list_make1(jtnode);
        }
        else if (IsA(jtnode, FromExpr))
@@ -263,6 +272,7 @@ deconstruct_recurse(PlannerInfo *root, Node *jtnode, bool below_outer_join,
                 * subproblems, since that won't lengthen the joinlist anyway.
                 */
                *qualscope = NULL;
+               *inner_join_rels = NULL;
                joinlist = NIL;
                remaining = list_length(f->fromlist);
                foreach(l, f->fromlist)
@@ -273,7 +283,8 @@ deconstruct_recurse(PlannerInfo *root, Node *jtnode, bool below_outer_join,
 
                        sub_joinlist = deconstruct_recurse(root, lfirst(l),
                                                                                           below_outer_join,
-                                                                                          &sub_qualscope);
+                                                                                          &sub_qualscope,
+                                                                                          inner_join_rels);
                        *qualscope = bms_add_members(*qualscope, sub_qualscope);
                        sub_members = list_length(sub_joinlist);
                        remaining--;
@@ -284,6 +295,16 @@ deconstruct_recurse(PlannerInfo *root, Node *jtnode, bool below_outer_join,
                                joinlist = lappend(joinlist, sub_joinlist);
                }
 
+               /*
+                * A FROM with more than one list element is an inner join subsuming
+                * all below it, so we should report inner_join_rels = qualscope.
+                * If there was exactly one element, we should (and already did) report
+                * whatever its inner_join_rels were.  If there were no elements
+                * (is that possible?) the initialization before the loop fixed it.
+                */
+               if (list_length(f->fromlist) > 1)
+                       *inner_join_rels = *qualscope;
+
                /*
                 * Now process the top-level quals.
                 */
@@ -297,6 +318,8 @@ deconstruct_recurse(PlannerInfo *root, Node *jtnode, bool below_outer_join,
                JoinExpr   *j = (JoinExpr *) jtnode;
                Relids          leftids,
                                        rightids,
+                                       left_inners,
+                                       right_inners,
                                        nonnullable_rels,
                                        ojscope;
                List       *leftjoinlist,
@@ -321,32 +344,35 @@ deconstruct_recurse(PlannerInfo *root, Node *jtnode, bool below_outer_join,
                        case JOIN_INNER:
                                leftjoinlist = deconstruct_recurse(root, j->larg,
                                                                                                   below_outer_join,
-                                                                                                  &leftids);
+                                                                                                  &leftids, &left_inners);
                                rightjoinlist = deconstruct_recurse(root, j->rarg,
                                                                                                        below_outer_join,
-                                                                                                       &rightids);
+                                                                                                       &rightids, &right_inners);
                                *qualscope = bms_union(leftids, rightids);
+                               *inner_join_rels = *qualscope;
                                /* Inner join adds no restrictions for quals */
                                nonnullable_rels = NULL;
                                break;
                        case JOIN_LEFT:
                                leftjoinlist = deconstruct_recurse(root, j->larg,
                                                                                                   below_outer_join,
-                                                                                                  &leftids);
+                                                                                                  &leftids, &left_inners);
                                rightjoinlist = deconstruct_recurse(root, j->rarg,
                                                                                                        true,
-                                                                                                       &rightids);
+                                                                                                       &rightids, &right_inners);
                                *qualscope = bms_union(leftids, rightids);
+                               *inner_join_rels = bms_union(left_inners, right_inners);
                                nonnullable_rels = leftids;
                                break;
                        case JOIN_FULL:
                                leftjoinlist = deconstruct_recurse(root, j->larg,
                                                                                                   true,
-                                                                                                  &leftids);
+                                                                                                  &leftids, &left_inners);
                                rightjoinlist = deconstruct_recurse(root, j->rarg,
                                                                                                        true,
-                                                                                                       &rightids);
+                                                                                                       &rightids, &right_inners);
                                *qualscope = bms_union(leftids, rightids);
+                               *inner_join_rels = bms_union(left_inners, right_inners);
                                /* each side is both outer and inner */
                                nonnullable_rels = *qualscope;
                                break;
@@ -354,11 +380,12 @@ deconstruct_recurse(PlannerInfo *root, Node *jtnode, bool below_outer_join,
                                /* notice we switch leftids and rightids */
                                leftjoinlist = deconstruct_recurse(root, j->larg,
                                                                                                   true,
-                                                                                                  &rightids);
+                                                                                                  &rightids, &right_inners);
                                rightjoinlist = deconstruct_recurse(root, j->rarg,
                                                                                                        below_outer_join,
-                                                                                                       &leftids);
+                                                                                                       &leftids, &left_inners);
                                *qualscope = bms_union(leftids, rightids);
+                               *inner_join_rels = bms_union(left_inners, right_inners);
                                nonnullable_rels = leftids;
                                break;
                        default:
@@ -377,8 +404,11 @@ deconstruct_recurse(PlannerInfo *root, Node *jtnode, bool below_outer_join,
                 */
                if (j->jointype != JOIN_INNER)
                {
-                       ojinfo = make_outerjoininfo(root, leftids, rightids,
-                                                                               (j->jointype == JOIN_FULL), j->quals);
+                       ojinfo = make_outerjoininfo(root,
+                                                                               leftids, rightids,
+                                                                               *inner_join_rels,
+                                                                               (j->jointype == JOIN_FULL),
+                                                                               j->quals);
                        ojscope = bms_union(ojinfo->min_lefthand, ojinfo->min_righthand);
                }
                else
@@ -447,6 +477,7 @@ deconstruct_recurse(PlannerInfo *root, Node *jtnode, bool below_outer_join,
  * Inputs:
  *     left_rels: the base Relids syntactically on outer side of join
  *     right_rels: the base Relids syntactically on inner side of join
+ *     inner_join_rels: base Relids participating in inner joins below this one
  *     is_full_join: what it says
  *     clause: the outer join's join condition
  *
@@ -459,17 +490,19 @@ deconstruct_recurse(PlannerInfo *root, Node *jtnode, bool below_outer_join,
  *
  * Note: we assume that this function is invoked bottom-up, so that
  * root->oj_info_list already contains entries for all outer joins that are
- * syntactically below this one; and indeed that oj_info_list is ordered
- * with syntactically lower joins listed first.
+ * syntactically below this one.
  */
 static OuterJoinInfo *
 make_outerjoininfo(PlannerInfo *root,
                                   Relids left_rels, Relids right_rels,
+                                  Relids inner_join_rels,
                                   bool is_full_join, Node *clause)
 {
        OuterJoinInfo *ojinfo = makeNode(OuterJoinInfo);
        Relids          clause_relids;
        Relids          strict_relids;
+       Relids          min_lefthand;
+       Relids          min_righthand;
        ListCell   *l;
 
        /*
@@ -499,6 +532,8 @@ make_outerjoininfo(PlannerInfo *root,
        ojinfo->delay_upper_joins = false;
 
        /* If it's a full join, no need to be very smart */
+       ojinfo->syn_lefthand = left_rels;
+       ojinfo->syn_righthand = right_rels;
        ojinfo->is_full_join = is_full_join;
        if (is_full_join)
        {
@@ -523,21 +558,17 @@ make_outerjoininfo(PlannerInfo *root,
        ojinfo->lhs_strict = bms_overlap(strict_relids, left_rels);
 
        /*
-        * Required LHS is basically the LHS rels mentioned in the clause... but
-        * if there aren't any, 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 may have to add more
-        * rels based on lower outer joins; see below.
+        * Required LHS always includes the LHS rels mentioned in the clause.
+        * We may have to add more rels based on lower outer joins; see below.
         */
-       ojinfo->min_lefthand = bms_intersect(clause_relids, left_rels);
-       if (bms_is_empty(ojinfo->min_lefthand))
-               ojinfo->min_lefthand = bms_copy(left_rels);
+       min_lefthand = bms_intersect(clause_relids, left_rels);
 
        /*
-        * Required RHS is normally the full set of RHS rels.  Sometimes we can
-        * exclude some, see below.
+        * Similarly for required RHS.  But here, we must also include any lower
+        * inner joins, to ensure we don't try to commute with any of them.
         */
-       ojinfo->min_righthand = bms_copy(right_rels);
+       min_righthand = bms_int_members(bms_union(clause_relids, inner_join_rels),
+                                                                       right_rels);
 
        foreach(l, root->oj_info_list)
        {
@@ -550,23 +581,29 @@ make_outerjoininfo(PlannerInfo *root,
                /*
                 * For a lower OJ in our LHS, if our join condition uses the lower
                 * join's RHS and is not strict for that rel, we must preserve the
-                * ordering of the two OJs, so add lower OJ's full required relset to
-                * min_lefthand.
+                * ordering of the two OJs, so add lower OJ's full syntactic relset to
+                * min_lefthand.  (We must use its full syntactic relset, not just
+                * its min_lefthand + min_righthand.  This is because there might
+                * be other OJs below this one that this one can commute with,
+                * but we cannot commute with them if we don't with this one.)
+                *
+                * Note: I believe we have to insist on being strict for at least one
+                * rel in the lower OJ's min_righthand, not its whole syn_righthand.
                 */
-               if (bms_overlap(ojinfo->min_lefthand, otherinfo->min_righthand) &&
+               if (bms_overlap(left_rels, otherinfo->syn_righthand) &&
                        !bms_overlap(strict_relids, otherinfo->min_righthand))
                {
-                       ojinfo->min_lefthand = bms_add_members(ojinfo->min_lefthand,
-                                                                                                  otherinfo->min_lefthand);
-                       ojinfo->min_lefthand = bms_add_members(ojinfo->min_lefthand,
-                                                                                                  otherinfo->min_righthand);
+                       min_lefthand = bms_add_members(min_lefthand,
+                                                                                  otherinfo->syn_lefthand);
+                       min_lefthand = bms_add_members(min_lefthand,
+                                                                                  otherinfo->syn_righthand);
                }
 
                /*
                 * 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, so exclude the lower
-                * RHS from our min_righthand.
+                * can interchange the ordering of the two OJs; otherwise we must
+                * add lower OJ's full syntactic relset to min_righthand. 
                 *
                 * Here, we have to consider that "our join condition" includes
                 * any clauses that syntactically appeared above the lower OJ and
@@ -579,20 +616,38 @@ make_outerjoininfo(PlannerInfo *root,
                 * effect is therefore sufficiently represented by the
                 * delay_upper_joins flag saved for us by distribute_qual_to_rels.
                 */
-               if (bms_overlap(ojinfo->min_righthand, otherinfo->min_righthand) &&
-                       !bms_overlap(clause_relids, otherinfo->min_righthand) &&
-                       otherinfo->lhs_strict && !otherinfo->delay_upper_joins)
+               if (bms_overlap(right_rels, otherinfo->syn_righthand))
                {
-                       ojinfo->min_righthand = bms_del_members(ojinfo->min_righthand,
-                                                                                                       otherinfo->min_righthand);
+                       if (bms_overlap(clause_relids, otherinfo->syn_righthand) ||
+                               !otherinfo->lhs_strict || otherinfo->delay_upper_joins)
+                       {
+                               min_righthand = bms_add_members(min_righthand,
+                                                                                               otherinfo->syn_lefthand);
+                               min_righthand = bms_add_members(min_righthand,
+                                                                                               otherinfo->syn_righthand);
+                       }
                }
        }
 
-       /* Neither set should be empty, else we might get confused later */
-       Assert(!bms_is_empty(ojinfo->min_lefthand));
-       Assert(!bms_is_empty(ojinfo->min_righthand));
+       /*
+        * 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.)
+        * 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));
+       Assert(!bms_is_empty(min_righthand));
        /* Shouldn't overlap either */
-       Assert(!bms_overlap(ojinfo->min_lefthand, ojinfo->min_righthand));
+       Assert(!bms_overlap(min_lefthand, min_righthand));
+
+       ojinfo->min_lefthand = min_lefthand;
+       ojinfo->min_righthand = min_righthand;
 
        return ojinfo;
 }
index b1cedcc850a2a2b00107ae2418d94a4b2fe01d3c..34727773e0667fc63866622ce22333b00c179aaa 100644 (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.2.3 2007/05/22 23:24:09 tgl Exp $
+ * $PostgreSQL: pgsql/src/include/nodes/relation.h,v 1.128.2.4 2007/08/31 01:44:14 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -852,6 +852,11 @@ typedef struct InnerIndexscanInfo
  * It is not valid for either min_lefthand or min_righthand to be empty sets;
  * if they were, this would break the logic that enforces join order.
  *
+ * syn_lefthand and syn_righthand are the sets of base relids that are
+ * syntactically below this outer join.  (These are needed to help compute
+ * min_lefthand and min_righthand for higher joins, but are not used
+ * thereafter.)
+ *
  * delay_upper_joins is set TRUE if we detect a pushed-down clause that has
  * to be evaluated after this join is formed (because it references the RHS).
  * Any outer joins that have such a clause and this join in their RHS cannot
@@ -869,6 +874,8 @@ typedef struct OuterJoinInfo
        NodeTag         type;
        Relids          min_lefthand;   /* base relids in minimum LHS for join */
        Relids          min_righthand;  /* base relids in minimum RHS for join */
+       Relids          syn_lefthand;   /* base relids syntactically within LHS */
+       Relids          syn_righthand;  /* base relids syntactically within RHS */
        bool            is_full_join;   /* it's a FULL OUTER JOIN */
        bool            lhs_strict;             /* joinclause is strict for some LHS rel */
        bool            delay_upper_joins;      /* can't commute with upper RHS */
index 3b7bdd9cbb321f9b0aa125b0b169746947ab1321..3dd4f5ed7d01218181c764e5cd1c67969f0de514 100644 (file)
@@ -2266,3 +2266,27 @@ select * from tt5,tt6 where tt5.f1 = tt6.f1 and tt5.f1 = tt5.f2 - tt6.f2;
   1 | 10 |  1 |  9
 (1 row)
 
+--
+-- regression test for problems of the sort depicted in bug #3588
+--
+create temp table xx (pkxx int);
+create temp table yy (pkyy int, pkxx int);
+insert into xx values (1);
+insert into xx values (2);
+insert into xx values (3);
+insert into yy values (101, 1);
+insert into yy values (201, 2);
+insert into yy values (301, NULL);
+select yy.pkyy as yy_pkyy, yy.pkxx as yy_pkxx, yya.pkyy as yya_pkyy,
+       xxa.pkxx as xxa_pkxx, xxb.pkxx as xxb_pkxx
+from yy
+     left join (SELECT * FROM yy where pkyy = 101) as yya ON yy.pkyy = yya.pkyy
+     left join xx xxa on yya.pkxx = xxa.pkxx
+     left join xx xxb on coalesce (xxa.pkxx, 1) = xxb.pkxx;
+ yy_pkyy | yy_pkxx | yya_pkyy | xxa_pkxx | xxb_pkxx 
+---------+---------+----------+----------+----------
+     101 |       1 |      101 |        1 |        1
+     201 |       2 |          |          |        1
+     301 |         |          |          |        1
+(3 rows)
+
index f1bf4b9af269b25781478148296430ddbcfeecaf..9e10c73acb7b96b1b9353d60b185fa63e3142464 100644 (file)
@@ -2266,3 +2266,27 @@ select * from tt5,tt6 where tt5.f1 = tt6.f1 and tt5.f1 = tt5.f2 - tt6.f2;
   1 | 10 |  1 |  9
 (1 row)
 
+--
+-- regression test for problems of the sort depicted in bug #3588
+--
+create temp table xx (pkxx int);
+create temp table yy (pkyy int, pkxx int);
+insert into xx values (1);
+insert into xx values (2);
+insert into xx values (3);
+insert into yy values (101, 1);
+insert into yy values (201, 2);
+insert into yy values (301, NULL);
+select yy.pkyy as yy_pkyy, yy.pkxx as yy_pkxx, yya.pkyy as yya_pkyy,
+       xxa.pkxx as xxa_pkxx, xxb.pkxx as xxb_pkxx
+from yy
+     left join (SELECT * FROM yy where pkyy = 101) as yya ON yy.pkyy = yya.pkyy
+     left join xx xxa on yya.pkxx = xxa.pkxx
+     left join xx xxb on coalesce (xxa.pkxx, 1) = xxb.pkxx;
+ yy_pkyy | yy_pkxx | yya_pkyy | xxa_pkxx | xxb_pkxx 
+---------+---------+----------+----------+----------
+     101 |       1 |      101 |        1 |        1
+     201 |       2 |          |          |        1
+     301 |         |          |          |        1
+(3 rows)
+
index ad8957cd6e3a3675e2471460df34880cf1bb06b8..4efb3c7283626153d9555667eb83a0e93fc8614c 100644 (file)
@@ -440,3 +440,25 @@ insert into tt6 values(1, 2);
 insert into tt6 values(2, 9);
 
 select * from tt5,tt6 where tt5.f1 = tt6.f1 and tt5.f1 = tt5.f2 - tt6.f2;
+
+--
+-- regression test for problems of the sort depicted in bug #3588
+--
+
+create temp table xx (pkxx int);
+create temp table yy (pkyy int, pkxx int);
+
+insert into xx values (1);
+insert into xx values (2);
+insert into xx values (3);
+
+insert into yy values (101, 1);
+insert into yy values (201, 2);
+insert into yy values (301, NULL);
+
+select yy.pkyy as yy_pkyy, yy.pkxx as yy_pkxx, yya.pkyy as yya_pkyy,
+       xxa.pkxx as xxa_pkxx, xxb.pkxx as xxb_pkxx
+from yy
+     left join (SELECT * FROM yy where pkyy = 101) as yya ON yy.pkyy = yya.pkyy
+     left join xx xxa on yya.pkxx = xxa.pkxx
+     left join xx xxb on coalesce (xxa.pkxx, 1) = xxb.pkxx;