]> granicus.if.org Git - postgresql/blobdiff - src/backend/optimizer/path/joinrels.c
Implement SEMI and ANTI joins in the planner and executor. (Semijoins replace
[postgresql] / src / backend / optimizer / path / joinrels.c
index f63c3ab43ffc81b9ae4a197205a608d0485ba488..b517f09e003de8e20e8c66699d1ca28c284ce469 100644 (file)
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *       $PostgreSQL: pgsql/src/backend/optimizer/path/joinrels.c,v 1.92 2008/03/24 21:53:03 tgl Exp $
+ *       $PostgreSQL: pgsql/src/backend/optimizer/path/joinrels.c,v 1.93 2008/08/14 18:47:59 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -218,7 +218,7 @@ join_search_one_level(PlannerInfo *root, int level, List **joinrels)
                }
 
                /*----------
-                * When OJs or IN clauses are involved, there may be no legal way
+                * When special joins are involved, there may be no legal way
                 * to make an N-way join for some values of N.  For example consider
                 *
                 * SELECT ... FROM t1 WHERE
@@ -230,12 +230,11 @@ join_search_one_level(PlannerInfo *root, int level, List **joinrels)
                 * to accept failure at level 4 and go on to discover a workable
                 * bushy plan at level 5.
                 *
-                * However, if there are no such clauses then join_is_legal() should
+                * However, if there are no special joins then join_is_legal() should
                 * never fail, and so the following sanity check is useful.
                 *----------
                 */
-               if (result_rels == NIL &&
-                       root->oj_info_list == NIL && root->in_info_list == NIL)
+               if (result_rels == NIL && root->join_info_list == NIL)
                        elog(ERROR, "failed to build any %d-way joins", level);
        }
 
@@ -337,89 +336,98 @@ make_rels_by_clauseless_joins(PlannerInfo *root,
  * (We could simplify the API by computing joinrelids locally, but this
  * would be redundant work in the normal path through make_join_rel.)
  *
- * On success, *jointype_p is set to the required join type.
+ * On success, *sjinfo_p is set to NULL if this is to be a plain inner join,
+ * else it's set to point to the associated SpecialJoinInfo node.  Also,
+ * *reversed_p is set TRUE if the given relations need to be swapped to
+ * match the SpecialJoinInfo node.
  */
 static bool
 join_is_legal(PlannerInfo *root, RelOptInfo *rel1, RelOptInfo *rel2,
-                         Relids joinrelids, JoinType *jointype_p)
+                         Relids joinrelids,
+                         SpecialJoinInfo **sjinfo_p, bool *reversed_p)
 {
-       JoinType        jointype;
+       SpecialJoinInfo *match_sjinfo;
+       bool            reversed;
        bool            is_valid_inner;
        ListCell   *l;
 
        /*
-        * Ensure *jointype_p is set on failure return.  This is just to suppress
-        * uninitialized-variable warnings from overly anal compilers.
+        * Ensure output params are set on failure return.  This is just to
+        * suppress uninitialized-variable warnings from overly anal compilers.
         */
-       *jointype_p = JOIN_INNER;
+       *sjinfo_p = NULL;
+       *reversed_p = false;
 
        /*
-        * If we have any outer joins, the proposed join might be illegal; and in
-        * any case we have to determine its join type.  Scan the OJ list for
-        * conflicts.
+        * If we have any special joins, the proposed join might be illegal; and
+        * in any case we have to determine its join type.  Scan the join info
+        * list for conflicts.
         */
-       jointype = JOIN_INNER;          /* default if no match to an OJ */
+       match_sjinfo = NULL;
+       reversed = false;
        is_valid_inner = true;
 
-       foreach(l, root->oj_info_list)
+       foreach(l, root->join_info_list)
        {
-               OuterJoinInfo *ojinfo = (OuterJoinInfo *) lfirst(l);
+               SpecialJoinInfo *sjinfo = (SpecialJoinInfo *) lfirst(l);
 
                /*
-                * This OJ is not relevant unless its RHS overlaps the proposed join.
-                * (Check this first as a fast path for dismissing most irrelevant OJs
-                * quickly.)
+                * This special join is not relevant unless its RHS overlaps the
+                * proposed join.  (Check this first as a fast path for dismissing
+                * most irrelevant SJs quickly.)
                 */
-               if (!bms_overlap(ojinfo->min_righthand, joinrelids))
+               if (!bms_overlap(sjinfo->min_righthand, joinrelids))
                        continue;
 
                /*
                 * Also, not relevant if proposed join is fully contained within RHS
                 * (ie, we're still building up the RHS).
                 */
-               if (bms_is_subset(joinrelids, ojinfo->min_righthand))
+               if (bms_is_subset(joinrelids, sjinfo->min_righthand))
                        continue;
 
                /*
-                * Also, not relevant if OJ is already done within either input.
+                * Also, not relevant if SJ is already done within either input.
                 */
-               if (bms_is_subset(ojinfo->min_lefthand, rel1->relids) &&
-                       bms_is_subset(ojinfo->min_righthand, rel1->relids))
+               if (bms_is_subset(sjinfo->min_lefthand, rel1->relids) &&
+                       bms_is_subset(sjinfo->min_righthand, rel1->relids))
                        continue;
-               if (bms_is_subset(ojinfo->min_lefthand, rel2->relids) &&
-                       bms_is_subset(ojinfo->min_righthand, rel2->relids))
+               if (bms_is_subset(sjinfo->min_lefthand, rel2->relids) &&
+                       bms_is_subset(sjinfo->min_righthand, rel2->relids))
                        continue;
 
                /*
                 * If one input contains min_lefthand and the other contains
-                * min_righthand, then we can perform the OJ at this join.
+                * min_righthand, then we can perform the SJ at this join.
                 *
-                * Barf if we get matches to more than one OJ (is that possible?)
+                * Barf if we get matches to more than one SJ (is that possible?)
                 */
-               if (bms_is_subset(ojinfo->min_lefthand, rel1->relids) &&
-                       bms_is_subset(ojinfo->min_righthand, rel2->relids))
+               if (bms_is_subset(sjinfo->min_lefthand, rel1->relids) &&
+                       bms_is_subset(sjinfo->min_righthand, rel2->relids))
                {
-                       if (jointype != JOIN_INNER)
+                       if (match_sjinfo)
                                return false;   /* invalid join path */
-                       jointype = ojinfo->is_full_join ? JOIN_FULL : JOIN_LEFT;
+                       match_sjinfo = sjinfo;
+                       reversed = false;
                }
-               else if (bms_is_subset(ojinfo->min_lefthand, rel2->relids) &&
-                                bms_is_subset(ojinfo->min_righthand, rel1->relids))
+               else if (bms_is_subset(sjinfo->min_lefthand, rel2->relids) &&
+                                bms_is_subset(sjinfo->min_righthand, rel1->relids))
                {
-                       if (jointype != JOIN_INNER)
+                       if (match_sjinfo)
                                return false;   /* invalid join path */
-                       jointype = ojinfo->is_full_join ? JOIN_FULL : JOIN_RIGHT;
+                       match_sjinfo = sjinfo;
+                       reversed = true;
                }
                else
                {
                        /*----------
                         * Otherwise, the proposed join overlaps the RHS but isn't
-                        * a valid implementation of this OJ.  It might still be
+                        * a valid implementation of this SJ.  It might still be
                         * a legal join, however.  If both inputs overlap the RHS,
                         * assume that it's OK.  Since the inputs presumably got past
                         * this function's checks previously, they can't overlap the
                         * LHS and their violations of the RHS boundary must represent
-                        * OJs that have been determined to commute with this one.
+                        * SJs that have been determined to commute with this one.
                         * We have to allow this to work correctly in cases like
                         *              (a LEFT JOIN (b JOIN (c LEFT JOIN d)))
                         * when the c/d join has been determined to commute with the join
@@ -428,105 +436,33 @@ join_is_legal(PlannerInfo *root, RelOptInfo *rel1, RelOptInfo *rel2,
                         * as a violation of the upper join's RHS.
                         * Furthermore, if one input overlaps the RHS and the other does
                         * not, we should still allow the join if it is a valid
-                        * implementation of some other OJ.  We have to allow this to
+                        * implementation of some other SJ.  We have to allow this to
                         * support the associative identity
                         *              (a LJ b on Pab) LJ c ON Pbc = a LJ (b LJ c ON Pbc) on Pab
-                        * since joining B directly to C violates the lower OJ's RHS.
+                        * since joining B directly to C violates the lower SJ's RHS.
                         * We assume that make_outerjoininfo() set things up correctly
-                        * so that we'll only match to some OJ if the join is valid.
+                        * so that we'll only match to some SJ if the join is valid.
                         * Set flag here to check at bottom of loop.
                         *----------
                         */
-                       if (bms_overlap(rel1->relids, ojinfo->min_righthand) &&
-                               bms_overlap(rel2->relids, ojinfo->min_righthand))
+                       if (bms_overlap(rel1->relids, sjinfo->min_righthand) &&
+                               bms_overlap(rel2->relids, sjinfo->min_righthand))
                        {
                                /* seems OK */
-                               Assert(!bms_overlap(joinrelids, ojinfo->min_lefthand));
+                               Assert(!bms_overlap(joinrelids, sjinfo->min_lefthand));
                        }
                        else
                                is_valid_inner = false;
                }
        }
 
-       /* Fail if violated some OJ's RHS and didn't match to another OJ */
-       if (jointype == JOIN_INNER && !is_valid_inner)
+       /* Fail if violated some SJ's RHS and didn't match to another SJ */
+       if (match_sjinfo == NULL && !is_valid_inner)
                return false;                   /* invalid join path */
 
-       /*
-        * Similarly, if we are implementing IN clauses as joins, check for
-        * illegal join path and detect whether we need a non-default join type.
-        */
-       foreach(l, root->in_info_list)
-       {
-               InClauseInfo *ininfo = (InClauseInfo *) lfirst(l);
-
-               /*
-                * This IN clause is not relevant unless its RHS overlaps the proposed
-                * join.  (Check this first as a fast path for dismissing most
-                * irrelevant INs quickly.)
-                */
-               if (!bms_overlap(ininfo->righthand, joinrelids))
-                       continue;
-
-               /*
-                * If we are still building the IN clause's RHS, then this IN clause
-                * isn't relevant yet.
-                */
-               if (bms_is_subset(joinrelids, ininfo->righthand))
-                       continue;
-
-               /*
-                * Cannot join if proposed join contains rels not in the RHS *and*
-                * contains only part of the RHS.  We must build the complete RHS
-                * (subselect's join) before it can be joined to rels outside the
-                * subselect.
-                */
-               if (!bms_is_subset(ininfo->righthand, joinrelids))
-                       return false;
-
-               /*
-                * At this point we are considering a join of the IN's RHS to some
-                * other rel(s).
-                *
-                * If we already joined IN's RHS to any other rels in either input
-                * path, then this join is not constrained (the necessary work was
-                * done at the lower level where that join occurred).
-                */
-               if (bms_is_subset(ininfo->righthand, rel1->relids) &&
-                       !bms_equal(ininfo->righthand, rel1->relids))
-                       continue;
-               if (bms_is_subset(ininfo->righthand, rel2->relids) &&
-                       !bms_equal(ininfo->righthand, rel2->relids))
-                       continue;
-
-               /*
-                * JOIN_IN technique will work if outerrel includes LHS and innerrel
-                * is exactly RHS; conversely JOIN_REVERSE_IN handles RHS/LHS.
-                *
-                * JOIN_UNIQUE_OUTER will work if outerrel is exactly RHS; conversely
-                * JOIN_UNIQUE_INNER will work if innerrel is exactly RHS.
-                *
-                * But none of these will work if we already found an OJ or another IN
-                * that needs to trigger here.
-                */
-               if (jointype != JOIN_INNER)
-                       return false;
-               if (bms_is_subset(ininfo->lefthand, rel1->relids) &&
-                       bms_equal(ininfo->righthand, rel2->relids))
-                       jointype = JOIN_IN;
-               else if (bms_is_subset(ininfo->lefthand, rel2->relids) &&
-                                bms_equal(ininfo->righthand, rel1->relids))
-                       jointype = JOIN_REVERSE_IN;
-               else if (bms_equal(ininfo->righthand, rel1->relids))
-                       jointype = JOIN_UNIQUE_OUTER;
-               else if (bms_equal(ininfo->righthand, rel2->relids))
-                       jointype = JOIN_UNIQUE_INNER;
-               else
-                       return false;           /* invalid join path */
-       }
-
-       /* Join is valid */
-       *jointype_p = jointype;
+       /* Otherwise, it's a valid join */
+       *sjinfo_p = match_sjinfo;
+       *reversed_p = reversed;
        return true;
 }
 
@@ -540,14 +476,16 @@ join_is_legal(PlannerInfo *root, RelOptInfo *rel1, RelOptInfo *rel2,
  *        pairs of rels that add up to the same set of base rels.)
  *
  * NB: will return NULL if attempted join is not valid.  This can happen
- * when working with outer joins, or with IN clauses that have been turned
- * into joins.
+ * when working with outer joins, or with IN or EXISTS clauses that have been
+ * turned into joins.
  */
 RelOptInfo *
 make_join_rel(PlannerInfo *root, RelOptInfo *rel1, RelOptInfo *rel2)
 {
        Relids          joinrelids;
-       JoinType        jointype;
+       SpecialJoinInfo *sjinfo;
+       bool            reversed;
+       SpecialJoinInfo sjinfo_data;
        RelOptInfo *joinrel;
        List       *restrictlist;
 
@@ -558,18 +496,48 @@ make_join_rel(PlannerInfo *root, RelOptInfo *rel1, RelOptInfo *rel2)
        joinrelids = bms_union(rel1->relids, rel2->relids);
 
        /* Check validity and determine join type. */
-       if (!join_is_legal(root, rel1, rel2, joinrelids, &jointype))
+       if (!join_is_legal(root, rel1, rel2, joinrelids,
+                                          &sjinfo, &reversed))
        {
                /* invalid join path */
                bms_free(joinrelids);
                return NULL;
        }
 
+       /* Swap rels if needed to match the join info. */
+       if (reversed)
+       {
+               RelOptInfo *trel = rel1;
+
+               rel1 = rel2;
+               rel2 = trel;
+       }
+
+       /*
+        * If it's a plain inner join, then we won't have found anything in
+        * join_info_list.  Make up a SpecialJoinInfo so that selectivity
+        * estimation functions will know what's being joined.
+        */
+       if (sjinfo == NULL)
+       {
+               sjinfo = &sjinfo_data;
+               sjinfo->type = T_SpecialJoinInfo;
+               sjinfo->min_lefthand = rel1->relids;
+               sjinfo->min_righthand = rel2->relids;
+               sjinfo->syn_lefthand = rel1->relids;
+               sjinfo->syn_righthand = rel2->relids;
+               sjinfo->jointype = JOIN_INNER;
+               /* we don't bother trying to make the remaining fields valid */
+               sjinfo->lhs_strict = false;
+               sjinfo->delay_upper_joins = false;
+               sjinfo->join_quals = NIL;
+       }
+
        /*
         * Find or build the join RelOptInfo, and compute the restrictlist that
         * goes with this particular joining.
         */
-       joinrel = build_join_rel(root, joinrelids, rel1, rel2, jointype,
+       joinrel = build_join_rel(root, joinrelids, rel1, rel2, sjinfo,
                                                         &restrictlist);
 
        /*
@@ -589,8 +557,11 @@ make_join_rel(PlannerInfo *root, RelOptInfo *rel1, RelOptInfo *rel2)
         * previously computed paths and mark the join as dummy.  (We do it
         * this way since it's conceivable that dummy-ness of a multi-element
         * join might only be noticeable for certain construction paths.)
+        *
+        * We need only consider the jointypes that appear in join_info_list,
+        * plus JOIN_INNER.
         */
-       switch (jointype)
+       switch (sjinfo->jointype)
        {
                case JOIN_INNER:
                        if (is_dummy_rel(rel1) || is_dummy_rel(rel2))
@@ -598,9 +569,11 @@ make_join_rel(PlannerInfo *root, RelOptInfo *rel1, RelOptInfo *rel2)
                                mark_dummy_join(joinrel);
                                break;
                        }
-                       add_paths_to_joinrel(root, joinrel, rel1, rel2, JOIN_INNER,
+                       add_paths_to_joinrel(root, joinrel, rel1, rel2,
+                                                                JOIN_INNER, sjinfo,
                                                                 restrictlist);
-                       add_paths_to_joinrel(root, joinrel, rel2, rel1, JOIN_INNER,
+                       add_paths_to_joinrel(root, joinrel, rel2, rel1,
+                                                                JOIN_INNER, sjinfo,
                                                                 restrictlist);
                        break;
                case JOIN_LEFT:
@@ -609,9 +582,11 @@ make_join_rel(PlannerInfo *root, RelOptInfo *rel1, RelOptInfo *rel2)
                                mark_dummy_join(joinrel);
                                break;
                        }
-                       add_paths_to_joinrel(root, joinrel, rel1, rel2, JOIN_LEFT,
+                       add_paths_to_joinrel(root, joinrel, rel1, rel2,
+                                                                JOIN_LEFT, sjinfo,
                                                                 restrictlist);
-                       add_paths_to_joinrel(root, joinrel, rel2, rel1, JOIN_RIGHT,
+                       add_paths_to_joinrel(root, joinrel, rel2, rel1,
+                                                                JOIN_RIGHT, sjinfo,
                                                                 restrictlist);
                        break;
                case JOIN_FULL:
@@ -620,75 +595,53 @@ make_join_rel(PlannerInfo *root, RelOptInfo *rel1, RelOptInfo *rel2)
                                mark_dummy_join(joinrel);
                                break;
                        }
-                       add_paths_to_joinrel(root, joinrel, rel1, rel2, JOIN_FULL,
-                                                                restrictlist);
-                       add_paths_to_joinrel(root, joinrel, rel2, rel1, JOIN_FULL,
-                                                                restrictlist);
-                       break;
-               case JOIN_RIGHT:
-                       if (is_dummy_rel(rel2))
-                       {
-                               mark_dummy_join(joinrel);
-                               break;
-                       }
-                       add_paths_to_joinrel(root, joinrel, rel1, rel2, JOIN_RIGHT,
-                                                                restrictlist);
-                       add_paths_to_joinrel(root, joinrel, rel2, rel1, JOIN_LEFT,
-                                                                restrictlist);
-                       break;
-               case JOIN_IN:
-                       if (is_dummy_rel(rel1) || is_dummy_rel(rel2))
-                       {
-                               mark_dummy_join(joinrel);
-                               break;
-                       }
-                       add_paths_to_joinrel(root, joinrel, rel1, rel2, JOIN_IN,
+                       add_paths_to_joinrel(root, joinrel, rel1, rel2,
+                                                                JOIN_FULL, sjinfo,
                                                                 restrictlist);
-                       /* REVERSE_IN isn't supported by joinpath.c */
-                       add_paths_to_joinrel(root, joinrel, rel1, rel2, JOIN_UNIQUE_INNER,
-                                                                restrictlist);
-                       add_paths_to_joinrel(root, joinrel, rel2, rel1, JOIN_UNIQUE_OUTER,
+                       add_paths_to_joinrel(root, joinrel, rel2, rel1,
+                                                                JOIN_FULL, sjinfo,
                                                                 restrictlist);
                        break;
-               case JOIN_REVERSE_IN:
+               case JOIN_SEMI:
                        if (is_dummy_rel(rel1) || is_dummy_rel(rel2))
                        {
                                mark_dummy_join(joinrel);
                                break;
                        }
-                       /* REVERSE_IN isn't supported by joinpath.c */
-                       add_paths_to_joinrel(root, joinrel, rel2, rel1, JOIN_IN,
-                                                                restrictlist);
-                       add_paths_to_joinrel(root, joinrel, rel1, rel2, JOIN_UNIQUE_OUTER,
+                       add_paths_to_joinrel(root, joinrel, rel1, rel2,
+                                                                JOIN_SEMI, sjinfo,
                                                                 restrictlist);
-                       add_paths_to_joinrel(root, joinrel, rel2, rel1, JOIN_UNIQUE_INNER,
-                                                                restrictlist);
-                       break;
-               case JOIN_UNIQUE_OUTER:
-                       if (is_dummy_rel(rel1) || is_dummy_rel(rel2))
+
+                       /*
+                        * If we know how to unique-ify the RHS and one input rel is
+                        * exactly the RHS (not a superset) we can consider unique-ifying
+                        * it and then doing a regular join.
+                        */
+                       if (bms_equal(sjinfo->syn_righthand, rel2->relids) &&
+                               create_unique_path(root, rel2, rel2->cheapest_total_path,
+                                                                  sjinfo) != NULL)
                        {
-                               mark_dummy_join(joinrel);
-                               break;
+                               add_paths_to_joinrel(root, joinrel, rel1, rel2,
+                                                                        JOIN_UNIQUE_INNER, sjinfo,
+                                                                        restrictlist);
+                               add_paths_to_joinrel(root, joinrel, rel2, rel1,
+                                                                        JOIN_UNIQUE_OUTER, sjinfo,
+                                                                        restrictlist);
                        }
-                       add_paths_to_joinrel(root, joinrel, rel1, rel2, JOIN_UNIQUE_OUTER,
-                                                                restrictlist);
-                       add_paths_to_joinrel(root, joinrel, rel2, rel1, JOIN_UNIQUE_INNER,
-                                                                restrictlist);
                        break;
-               case JOIN_UNIQUE_INNER:
-                       if (is_dummy_rel(rel1) || is_dummy_rel(rel2))
+               case JOIN_ANTI:
+                       if (is_dummy_rel(rel1))
                        {
                                mark_dummy_join(joinrel);
                                break;
                        }
-                       add_paths_to_joinrel(root, joinrel, rel1, rel2, JOIN_UNIQUE_INNER,
-                                                                restrictlist);
-                       add_paths_to_joinrel(root, joinrel, rel2, rel1, JOIN_UNIQUE_OUTER,
+                       add_paths_to_joinrel(root, joinrel, rel1, rel2,
+                                                                JOIN_ANTI, sjinfo,
                                                                 restrictlist);
                        break;
                default:
-                       elog(ERROR, "unrecognized join type: %d",
-                                (int) jointype);
+                       /* other values not expected here */
+                       elog(ERROR, "unrecognized join type: %d", (int) sjinfo->jointype);
                        break;
        }
 
@@ -701,7 +654,7 @@ make_join_rel(PlannerInfo *root, RelOptInfo *rel1, RelOptInfo *rel2)
 /*
  * have_join_order_restriction
  *             Detect whether the two relations should be joined to satisfy
- *             a join-order restriction arising from outer joins or IN clauses.
+ *             a join-order restriction arising from special joins.
  *
  * In practice this is always used with have_relevant_joinclause(), and so
  * could be merged with that function, but it seems clearer to separate the
@@ -709,8 +662,8 @@ make_join_rel(PlannerInfo *root, RelOptInfo *rel1, RelOptInfo *rel2)
  * a clauseless join must be performed to satisfy join-order restrictions.
  *
  * Note: this is only a problem if one side of a degenerate outer join
- * contains multiple rels, or a clauseless join is required within an IN's
- * RHS; else we will find a join path via the "last ditch" case in
+ * contains multiple rels, or a clauseless join is required within an
+ * IN/EXISTS RHS; else we will find a join path via the "last ditch" case in
  * join_search_one_level().  We could dispense with this test if we were
  * willing to try bushy plans in the "last ditch" case, but that seems much
  * less efficient.
@@ -730,23 +683,23 @@ have_join_order_restriction(PlannerInfo *root,
         * Also, the two rels could represent a clauseless join that has to be
         * completed to build up the LHS or RHS of an outer join.
         */
-       foreach(l, root->oj_info_list)
+       foreach(l, root->join_info_list)
        {
-               OuterJoinInfo *ojinfo = (OuterJoinInfo *) lfirst(l);
+               SpecialJoinInfo *sjinfo = (SpecialJoinInfo *) lfirst(l);
 
                /* ignore full joins --- other mechanisms handle them */
-               if (ojinfo->is_full_join)
+               if (sjinfo->jointype == JOIN_FULL)
                        continue;
 
-               /* Can we perform the OJ with these rels? */
-               if (bms_is_subset(ojinfo->min_lefthand, rel1->relids) &&
-                       bms_is_subset(ojinfo->min_righthand, rel2->relids))
+               /* Can we perform the SJ with these rels? */
+               if (bms_is_subset(sjinfo->min_lefthand, rel1->relids) &&
+                       bms_is_subset(sjinfo->min_righthand, rel2->relids))
                {
                        result = true;
                        break;
                }
-               if (bms_is_subset(ojinfo->min_lefthand, rel2->relids) &&
-                       bms_is_subset(ojinfo->min_righthand, rel1->relids))
+               if (bms_is_subset(sjinfo->min_lefthand, rel2->relids) &&
+                       bms_is_subset(sjinfo->min_righthand, rel1->relids))
                {
                        result = true;
                        break;
@@ -754,63 +707,19 @@ have_join_order_restriction(PlannerInfo *root,
 
                /*
                 * Might we need to join these rels to complete the RHS?  We have to
-                * use "overlap" tests since either rel might include a lower OJ that
+                * use "overlap" tests since either rel might include a lower SJ that
                 * has been proven to commute with this one.
                 */
-               if (bms_overlap(ojinfo->min_righthand, rel1->relids) &&
-                       bms_overlap(ojinfo->min_righthand, rel2->relids))
+               if (bms_overlap(sjinfo->min_righthand, rel1->relids) &&
+                       bms_overlap(sjinfo->min_righthand, rel2->relids))
                {
                        result = true;
                        break;
                }
 
                /* Likewise for the LHS. */
-               if (bms_overlap(ojinfo->min_lefthand, rel1->relids) &&
-                       bms_overlap(ojinfo->min_lefthand, rel2->relids))
-               {
-                       result = true;
-                       break;
-               }
-       }
-
-       /*
-        * Similarly, we need to allow a join that completes a degenerate
-        * IN-clause, or one that builds up its LHS or RHS.
-        */
-       foreach(l, root->in_info_list)
-       {
-               InClauseInfo *ininfo = (InClauseInfo *) lfirst(l);
-
-               /* Can we perform the IN with these rels? */
-               if (bms_is_subset(ininfo->lefthand, rel1->relids) &&
-                       bms_is_subset(ininfo->righthand, rel2->relids))
-               {
-                       result = true;
-                       break;
-               }
-               if (bms_is_subset(ininfo->lefthand, rel2->relids) &&
-                       bms_is_subset(ininfo->righthand, rel1->relids))
-               {
-                       result = true;
-                       break;
-               }
-
-               /*
-                * Might we need to join these rels to complete the RHS?  It's
-                * probably overkill to test "overlap", since we never join part of an
-                * IN's RHS to anything else, but may as well keep the coding similar
-                * to the OJ case.
-                */
-               if (bms_overlap(ininfo->righthand, rel1->relids) &&
-                       bms_overlap(ininfo->righthand, rel2->relids))
-               {
-                       result = true;
-                       break;
-               }
-
-               /* Likewise for the LHS. */
-               if (bms_overlap(ininfo->lefthand, rel1->relids) &&
-                       bms_overlap(ininfo->lefthand, rel2->relids))
+               if (bms_overlap(sjinfo->min_lefthand, rel1->relids) &&
+                       bms_overlap(sjinfo->min_lefthand, rel2->relids))
                {
                        result = true;
                        break;
@@ -852,37 +761,22 @@ has_join_restriction(PlannerInfo *root, RelOptInfo *rel)
 {
        ListCell   *l;
 
-       foreach(l, root->oj_info_list)
+       foreach(l, root->join_info_list)
        {
-               OuterJoinInfo *ojinfo = (OuterJoinInfo *) lfirst(l);
+               SpecialJoinInfo *sjinfo = (SpecialJoinInfo *) lfirst(l);
 
                /* ignore full joins --- other mechanisms preserve their ordering */
-               if (ojinfo->is_full_join)
-                       continue;
-
-               /* ignore if OJ is already contained in rel */
-               if (bms_is_subset(ojinfo->min_lefthand, rel->relids) &&
-                       bms_is_subset(ojinfo->min_righthand, rel->relids))
+               if (sjinfo->jointype == JOIN_FULL)
                        continue;
 
-               /* restricted if it overlaps LHS or RHS, but doesn't contain OJ */
-               if (bms_overlap(ojinfo->min_lefthand, rel->relids) ||
-                       bms_overlap(ojinfo->min_righthand, rel->relids))
-                       return true;
-       }
-
-       foreach(l, root->in_info_list)
-       {
-               InClauseInfo *ininfo = (InClauseInfo *) lfirst(l);
-
-               /* ignore if IN is already contained in rel */
-               if (bms_is_subset(ininfo->lefthand, rel->relids) &&
-                       bms_is_subset(ininfo->righthand, rel->relids))
+               /* ignore if SJ is already contained in rel */
+               if (bms_is_subset(sjinfo->min_lefthand, rel->relids) &&
+                       bms_is_subset(sjinfo->min_righthand, rel->relids))
                        continue;
 
-               /* restricted if it overlaps LHS or RHS, but doesn't contain IN */
-               if (bms_overlap(ininfo->lefthand, rel->relids) ||
-                       bms_overlap(ininfo->righthand, rel->relids))
+               /* restricted if it overlaps LHS or RHS, but doesn't contain SJ */
+               if (bms_overlap(sjinfo->min_lefthand, rel->relids) ||
+                       bms_overlap(sjinfo->min_righthand, rel->relids))
                        return true;
        }
 
@@ -922,12 +816,14 @@ has_legal_joinclause(PlannerInfo *root, RelOptInfo *rel)
                if (have_relevant_joinclause(root, rel, rel2))
                {
                        Relids          joinrelids;
-                       JoinType        jointype;
+                       SpecialJoinInfo *sjinfo;
+                       bool            reversed;
 
                        /* join_is_legal needs relids of the union */
                        joinrelids = bms_union(rel->relids, rel2->relids);
 
-                       if (join_is_legal(root, rel, rel2, joinrelids, &jointype))
+                       if (join_is_legal(root, rel, rel2, joinrelids,
+                                                         &sjinfo, &reversed))
                        {
                                /* Yes, this will work */
                                bms_free(joinrelids);