* Portions Copyright (c) 1994, Regents of the University of California
  *
  * IDENTIFICATION
- *   $PostgreSQL: pgsql/src/backend/optimizer/path/costsize.c,v 1.200 2008/10/21 20:42:52 tgl Exp $
+ *   $PostgreSQL: pgsql/src/backend/optimizer/path/costsize.c,v 1.201 2008/11/22 22:47:05 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
            break;
        case JOIN_SEMI:
            nrows = outer_rel->rows * jselec;
-           nrows *= pselec;
+           /* pselec not used */
            break;
        case JOIN_ANTI:
            nrows = outer_rel->rows * (1.0 - jselec);
 
  *
  *
  * IDENTIFICATION
- *   $PostgreSQL: pgsql/src/backend/optimizer/path/indxpath.c,v 1.233 2008/09/12 14:56:13 tgl Exp $
+ *   $PostgreSQL: pgsql/src/backend/optimizer/path/indxpath.c,v 1.234 2008/11/22 22:47:05 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
    switch (jointype)
    {
        case JOIN_INNER:
+       case JOIN_SEMI:
            isouterjoin = false;
            break;
        case JOIN_LEFT:
-       case JOIN_SEMI:
        case JOIN_ANTI:
            isouterjoin = true;
            break;
 
  *
  *
  * IDENTIFICATION
- *   $PostgreSQL: pgsql/src/backend/optimizer/path/joinpath.c,v 1.118 2008/10/04 21:56:53 tgl Exp $
+ *   $PostgreSQL: pgsql/src/backend/optimizer/path/joinpath.c,v 1.119 2008/11/22 22:47:06 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
    switch (jointype)
    {
        case JOIN_INNER:
+       case JOIN_SEMI:
        case JOIN_UNIQUE_OUTER:
        case JOIN_UNIQUE_INNER:
            isouterjoin = false;
            break;
        case JOIN_LEFT:
-       case JOIN_SEMI:
        case JOIN_ANTI:
            isouterjoin = true;
            break;
 
  *
  *
  * IDENTIFICATION
- *   $PostgreSQL: pgsql/src/backend/optimizer/path/joinrels.c,v 1.94 2008/08/17 19:40:11 tgl Exp $
+ *   $PostgreSQL: pgsql/src/backend/optimizer/path/joinrels.c,v 1.95 2008/11/22 22:47:06 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
            match_sjinfo = sjinfo;
            reversed = true;
        }
+       else if (sjinfo->jointype == JOIN_SEMI &&
+                bms_equal(sjinfo->syn_righthand, rel2->relids))
+       {
+           /*
+            * For a semijoin, we can join the RHS to anything else by
+            * unique-ifying the RHS.
+            */
+           if (match_sjinfo)
+               return false;   /* invalid join path */
+           match_sjinfo = sjinfo;
+           reversed = false;
+       }
+       else if (sjinfo->jointype == JOIN_SEMI &&
+                bms_equal(sjinfo->syn_righthand, rel1->relids))
+       {
+           /* Reversed semijoin case */
+           if (match_sjinfo)
+               return false;   /* invalid join path */
+           match_sjinfo = sjinfo;
+           reversed = true;
+       }
        else
        {
            /*----------
             * We assume that make_outerjoininfo() set things up correctly
             * so that we'll only match to some SJ if the join is valid.
             * Set flag here to check at bottom of loop.
+            *
+            * For a semijoin, assume it's okay if either side fully contains
+            * the RHS (per the unique-ification case above).
             *----------
             */
-           if (bms_overlap(rel1->relids, sjinfo->min_righthand) &&
+           if (sjinfo->jointype != JOIN_SEMI &&
+               bms_overlap(rel1->relids, sjinfo->min_righthand) &&
                bms_overlap(rel2->relids, sjinfo->min_righthand))
            {
                /* seems OK */
                Assert(!bms_overlap(joinrelids, sjinfo->min_lefthand));
            }
+           else if (sjinfo->jointype == JOIN_SEMI &&
+                    (bms_is_subset(sjinfo->syn_righthand, rel1->relids) ||
+                     bms_is_subset(sjinfo->syn_righthand, rel2->relids)))
+           {
+               /* seems OK */
+           }
            else
                is_valid_inner = false;
        }
                                 restrictlist);
            break;
        case JOIN_SEMI:
-           if (is_dummy_rel(rel1) || is_dummy_rel(rel2) ||
-               restriction_is_constant_false(restrictlist))
+           /*
+            * Do these steps only if we actually have a regular semijoin,
+            * as opposed to a case where we should unique-ify the RHS.
+            */
+           if (bms_is_subset(sjinfo->min_lefthand, rel1->relids) &&
+               bms_is_subset(sjinfo->min_righthand, rel2->relids))
            {
-               mark_dummy_rel(joinrel);
-               break;
+               if (is_dummy_rel(rel1) || is_dummy_rel(rel2) ||
+                   restriction_is_constant_false(restrictlist))
+               {
+                   mark_dummy_rel(joinrel);
+                   break;
+               }
+               add_paths_to_joinrel(root, joinrel, rel1, rel2,
+                                    JOIN_SEMI, sjinfo,
+                                    restrictlist);
            }
-           add_paths_to_joinrel(root, joinrel, rel1, rel2,
-                                JOIN_SEMI, sjinfo,
-                                restrictlist);
 
            /*
             * If we know how to unique-ify the RHS and one input rel is
 
  *
  *
  * IDENTIFICATION
- *   $PostgreSQL: pgsql/src/backend/optimizer/plan/initsplan.c,v 1.144 2008/10/25 19:51:32 tgl Exp $
+ *   $PostgreSQL: pgsql/src/backend/optimizer/plan/initsplan.c,v 1.145 2008/11/22 22:47:06 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
        maybe_equivalence = false;
        maybe_outer_join = false;
    }
-   else if (bms_overlap(relids, outerjoin_nonnullable) &&
-            (jointype != JOIN_SEMI ||
-             bms_nonempty_difference(relids, outerjoin_nonnullable)))
+   else if (bms_overlap(relids, outerjoin_nonnullable))
    {
        /*
         * The qual is attached to an outer join and mentions (some of the)
-        * rels on the nonnullable side, so it's not degenerate.  (For a
-        * JOIN_SEMI qual, we consider it non-degenerate only if it mentions
-        * both sides of the join --- if it mentions only one side, it can
-        * be pushed down.)
+        * rels on the nonnullable side, so it's not degenerate.
         *
         * We can't use such a clause to deduce equivalence (the left and
         * right sides might be unequal above the join because one of them has
    SpecialJoinInfo *sjinfo;
    Relids      qualscope;
    Relids      ojscope;
+   Relids      outerjoin_nonnullable;
    ListCell   *l;
 
    /*
                                fslink->jointype,
                                quals);
 
+   /* Treat as inner join if SEMI, outer join if ANTI */
    qualscope = bms_union(sjinfo->syn_lefthand, sjinfo->syn_righthand);
-   ojscope = bms_union(sjinfo->min_lefthand, sjinfo->min_righthand);
+   if (fslink->jointype == JOIN_SEMI)
+   {
+       ojscope = outerjoin_nonnullable = NULL;
+   }
+   else
+   {
+       Assert(fslink->jointype == JOIN_ANTI);
+       ojscope = bms_union(sjinfo->min_lefthand, sjinfo->min_righthand);
+       outerjoin_nonnullable = fslink->lefthand;
+   }
 
-   /* Distribute the join quals much as for a regular LEFT JOIN */
+   /* Distribute the join quals much as for a regular JOIN node */
    foreach(l, quals)
    {
        Node   *qual = (Node *) lfirst(l);
 
        distribute_qual_to_rels(root, qual,
                                false, below_outer_join, fslink->jointype,
-                               qualscope, ojscope, fslink->lefthand);
+                               qualscope, ojscope, outerjoin_nonnullable);
    }
 
    /* Now we can add the SpecialJoinInfo to join_info_list */
 
  * Portions Copyright (c) 1996-2008, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/include/nodes/nodes.h,v 1.214 2008/10/21 20:42:53 tgl Exp $
+ * $PostgreSQL: pgsql/src/include/nodes/nodes.h,v 1.215 2008/11/22 22:47:06 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
 
 /*
  * OUTER joins are those for which pushed-down quals must behave differently
- * from the join's own quals.  This is in fact everything except INNER joins.
- * However, this macro must also exclude the JOIN_UNIQUE symbols since those
- * are temporary proxies for what will eventually be an INNER join.
+ * from the join's own quals.  This is in fact everything except INNER and
+ * SEMI joins.  However, this macro must also exclude the JOIN_UNIQUE symbols
+ * since those are temporary proxies for what will eventually be an INNER
+ * join.
  *
- * Note: in some places it is preferable to treat JOIN_SEMI as not being
- * an outer join, since it doesn't produce null-extended rows.  Be aware
- * of that distinction when deciding whether to use this macro.
+ * Note: semijoins are a hybrid case, but we choose to treat them as not
+ * being outer joins.  This is okay principally because the SQL syntax makes
+ * it impossible to have a pushed-down qual that refers to the inner relation
+ * of a semijoin; so there is no strong need to distinguish join quals from
+ * pushed-down quals.  This is convenient because for almost all purposes,
+ * quals attached to a semijoin can be treated the same as innerjoin quals.
  */
 #define IS_OUTER_JOIN(jointype) \
-   ((jointype) > JOIN_INNER && (jointype) < JOIN_UNIQUE_OUTER)
+   (((1 << (jointype)) & \
+     ((1 << JOIN_LEFT) | \
+      (1 << JOIN_FULL) | \
+      (1 << JOIN_RIGHT) | \
+      (1 << JOIN_ANTI))) != 0)
 
 #endif   /* NODES_H */