]> granicus.if.org Git - postgresql/blobdiff - src/backend/executor/nodeMergejoin.c
Update copyright notices for year 2012.
[postgresql] / src / backend / executor / nodeMergejoin.c
index be4a97574e5faaf1dc9b1c62e15b984fa22df7fe..f921847db906a85bc0159faae31cdf7609afd8f3 100644 (file)
@@ -3,12 +3,12 @@
  * nodeMergejoin.c
  *       routines supporting merge joins
  *
- * Portions Copyright (c) 1996-2005, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1996-2012, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
  *
  * IDENTIFICATION
- *       $PostgreSQL: pgsql/src/backend/executor/nodeMergejoin.c,v 1.72 2005/05/13 21:20:16 tgl Exp $
+ *       src/backend/executor/nodeMergejoin.c
  *
  *-------------------------------------------------------------------------
  */
  *             therefore it should scan the outer relation first to find a
  *             matching tuple and so on.
  *
- *             Therefore, when initializing the merge-join node, we look up the
- *             associated sort operators.  We assume the planner has seen to it
- *             that the inputs are correctly sorted by these operators.  Rather
- *             than directly executing the merge join clauses, we evaluate the
- *             left and right key expressions separately and then compare the
- *             columns one at a time (see MJCompare).
+ *             Therefore, rather than directly executing the merge join clauses,
+ *             we evaluate the left and right key expressions separately and then
+ *             compare the columns one at a time (see MJCompare).      The planner
+ *             passes us enough information about the sort ordering of the inputs
+ *             to allow us to determine how to make the comparison.  We may use the
+ *             appropriate btree comparison function, since Postgres' only notion
+ *             of ordering is specified by btree opfamilies.
  *
  *
  *             Consider the above relations and suppose that the executor has
  */
 #include "postgres.h"
 
-#include "access/heapam.h"
 #include "access/nbtree.h"
-#include "access/printtup.h"
-#include "catalog/pg_amop.h"
-#include "catalog/pg_operator.h"
 #include "executor/execdebug.h"
-#include "executor/execdefs.h"
 #include "executor/nodeMergejoin.h"
-#include "miscadmin.h"
-#include "utils/acl.h"
-#include "utils/catcache.h"
 #include "utils/lsyscache.h"
 #include "utils/memutils.h"
-#include "utils/syscache.h"
 
 
 /*
- * Comparison strategies supported by MJCompare
- *
- * XXX eventually should extend these to support descending-order sorts.
- * There are some tricky issues however about being sure we are on the same
- * page as the underlying sort or index as to which end NULLs sort to.
+ * States of the ExecMergeJoin state machine
  */
-typedef enum
-{
-       MERGEFUNC_LT,                           /* raw "<" operator */
-       MERGEFUNC_CMP                           /* -1 / 0 / 1 three-way comparator */
-} MergeFunctionKind;
+#define EXEC_MJ_INITIALIZE_OUTER               1
+#define EXEC_MJ_INITIALIZE_INNER               2
+#define EXEC_MJ_JOINTUPLES                             3
+#define EXEC_MJ_NEXTOUTER                              4
+#define EXEC_MJ_TESTOUTER                              5
+#define EXEC_MJ_NEXTINNER                              6
+#define EXEC_MJ_SKIP_TEST                              7
+#define EXEC_MJ_SKIPOUTER_ADVANCE              8
+#define EXEC_MJ_SKIPINNER_ADVANCE              9
+#define EXEC_MJ_ENDOUTER                               10
+#define EXEC_MJ_ENDINNER                               11
 
-/* Runtime data for each mergejoin clause */
+/*
+ * Runtime data for each mergejoin clause
+ */
 typedef struct MergeJoinClauseData
 {
        /* Executable expression trees */
-       ExprState          *lexpr;              /* left-hand (outer) input expression */
-       ExprState          *rexpr;              /* right-hand (inner) input expression */
+       ExprState  *lexpr;                      /* left-hand (outer) input expression */
+       ExprState  *rexpr;                      /* right-hand (inner) input expression */
+
        /*
         * If we have a current left or right input tuple, the values of the
         * expressions are loaded into these fields:
         */
-       Datum                   ldatum;         /* current left-hand value */
-       Datum                   rdatum;         /* current right-hand value */
-       bool                    lisnull;        /* and their isnull flags */
-       bool                    risnull;
-       /*
-        * Remember whether mergejoin operator is strict (usually it will be).
-        * NOTE: if it's not strict, we still assume it cannot return true for
-        * one null and one non-null input.
-        */
-       bool                    mergestrict;
+       Datum           ldatum;                 /* current left-hand value */
+       Datum           rdatum;                 /* current right-hand value */
+       bool            lisnull;                /* and their isnull flags */
+       bool            risnull;
+
        /*
-        * The comparison strategy in use, and the lookup info to let us call
-        * the needed comparison routines.  eqfinfo is the "=" operator itself.
-        * cmpfinfo is either the btree comparator or the "<" operator.
+        * Everything we need to know to compare the left and right values is
+        * stored here.
         */
-       MergeFunctionKind cmpstrategy;
-       FmgrInfo                eqfinfo;
-       FmgrInfo                cmpfinfo;
-} MergeJoinClauseData;
+       SortSupportData ssup;
+}      MergeJoinClauseData;
+
+/* Result type for MJEvalOuterValues and MJEvalInnerValues */
+typedef enum
+{
+       MJEVAL_MATCHABLE,                       /* normal, potentially matchable tuple */
+       MJEVAL_NONMATCHABLE,            /* tuple cannot join because it has a null */
+       MJEVAL_ENDOFJOIN                        /* end of input (physical or effective) */
+} MJEvalResult;
 
 
 #define MarkInnerTuple(innerTupleSlot, mergestate) \
@@ -165,34 +162,43 @@ typedef struct MergeJoinClauseData
  * we will need at runtime.  Each struct essentially tells us how to compare
  * the two expressions from the original clause.
  *
- * The best, most efficient way to compare two expressions is to use a btree
- * comparison support routine, since that requires only one function call
- * per comparison.  Hence we try to find a btree opclass that matches the
- * mergejoinable operator.  If we cannot find one, we'll have to call both
- * the "=" and (often) the "<" operator for each comparison.
+ * In addition to the expressions themselves, the planner passes the btree
+ * opfamily OID, collation OID, btree strategy number (BTLessStrategyNumber or
+ * BTGreaterStrategyNumber), and nulls-first flag that identify the intended
+ * sort ordering for each merge key.  The mergejoinable operator is an
+ * equality operator in the opfamily, and the two inputs are guaranteed to be
+ * ordered in either increasing or decreasing (respectively) order according
+ * to the opfamily and collation, with nulls at the indicated end of the range.
+ * This allows us to obtain the needed comparison function from the opfamily.
  */
 static MergeJoinClause
-MJExamineQuals(List *qualList, PlanState *parent)
+MJExamineQuals(List *mergeclauses,
+                          Oid *mergefamilies,
+                          Oid *mergecollations,
+                          int *mergestrategies,
+                          bool *mergenullsfirst,
+                          PlanState *parent)
 {
        MergeJoinClause clauses;
-       int                     nClauses = list_length(qualList);
+       int                     nClauses = list_length(mergeclauses);
        int                     iClause;
-       ListCell   *l;
+       ListCell   *cl;
 
        clauses = (MergeJoinClause) palloc0(nClauses * sizeof(MergeJoinClauseData));
 
        iClause = 0;
-       foreach(l, qualList)
+       foreach(cl, mergeclauses)
        {
-               OpExpr     *qual = (OpExpr *) lfirst(l);
+               OpExpr     *qual = (OpExpr *) lfirst(cl);
                MergeJoinClause clause = &clauses[iClause];
-               Oid                     ltop;
-               Oid                     gtop;
-               RegProcedure ltproc;
-               RegProcedure gtproc;
-               AclResult       aclresult;
-               CatCList   *catlist;
-               int                     i;
+               Oid                     opfamily = mergefamilies[iClause];
+               Oid                     collation = mergecollations[iClause];
+               StrategyNumber opstrategy = mergestrategies[iClause];
+               bool            nulls_first = mergenullsfirst[iClause];
+               int                     op_strategy;
+               Oid                     op_lefttype;
+               Oid                     op_righttype;
+               Oid                     sortfunc;
 
                if (!IsA(qual, OpExpr))
                        elog(ERROR, "mergejoin clause is not an OpExpr");
@@ -203,78 +209,50 @@ MJExamineQuals(List *qualList, PlanState *parent)
                clause->lexpr = ExecInitExpr((Expr *) linitial(qual->args), parent);
                clause->rexpr = ExecInitExpr((Expr *) lsecond(qual->args), parent);
 
-               /*
-                * Check permission to call the mergejoinable operator.
-                * For predictability, we check this even if we end up not using it.
-                */
-               aclresult = pg_proc_aclcheck(qual->opfuncid, GetUserId(), ACL_EXECUTE);
-               if (aclresult != ACLCHECK_OK)
-                       aclcheck_error(aclresult, ACL_KIND_PROC,
-                                                  get_func_name(qual->opfuncid));
-
-               /* Set up the fmgr lookup information */
-               fmgr_info(qual->opfuncid, &(clause->eqfinfo));
-
-               /* And remember strictness */
-               clause->mergestrict = clause->eqfinfo.fn_strict;
-
-               /*
-                * Lookup the comparison operators that go with the mergejoinable
-                * top-level operator.  (This will elog if the operator isn't
-                * mergejoinable, which would be the planner's mistake.)
-                */
-               op_mergejoin_crossops(qual->opno,
-                                                         &ltop,
-                                                         &gtop,
-                                                         &ltproc,
-                                                         &gtproc);
-
-               clause->cmpstrategy = MERGEFUNC_LT;
-
-               /*
-                * Look for a btree opclass including all three operators.
-                * This is much like SelectSortFunction except we insist on
-                * matching all the operators provided, and it can be a cross-type
-                * opclass.
-                *
-                * XXX for now, insist on forward sort so that NULLs can be counted
-                * on to be high.
-                */
-               catlist = SearchSysCacheList(AMOPOPID, 1,
-                                                                        ObjectIdGetDatum(qual->opno),
-                                                                        0, 0, 0);
-
-               for (i = 0; i < catlist->n_members; i++)
+               /* Set up sort support data */
+               clause->ssup.ssup_cxt = CurrentMemoryContext;
+               clause->ssup.ssup_collation = collation;
+               if (opstrategy == BTLessStrategyNumber)
+                       clause->ssup.ssup_reverse = false;
+               else if (opstrategy == BTGreaterStrategyNumber)
+                       clause->ssup.ssup_reverse = true;
+               else    /* planner screwed up */
+                       elog(ERROR, "unsupported mergejoin strategy %d", opstrategy);
+               clause->ssup.ssup_nulls_first = nulls_first;
+
+               /* Extract the operator's declared left/right datatypes */
+               get_op_opfamily_properties(qual->opno, opfamily, false,
+                                                                  &op_strategy,
+                                                                  &op_lefttype,
+                                                                  &op_righttype);
+               if (op_strategy != BTEqualStrategyNumber)               /* should not happen */
+                       elog(ERROR, "cannot merge using non-equality operator %u",
+                                qual->opno);
+
+               /* And get the matching support or comparison function */
+               sortfunc = get_opfamily_proc(opfamily,
+                                                                        op_lefttype,
+                                                                        op_righttype,
+                                                                        BTSORTSUPPORT_PROC);
+               if (OidIsValid(sortfunc))
                {
-                       HeapTuple       tuple = &catlist->members[i]->tuple;
-                       Form_pg_amop aform = (Form_pg_amop) GETSTRUCT(tuple);
-                       Oid                     opcid = aform->amopclaid;
-
-                       if (aform->amopstrategy != BTEqualStrategyNumber)
-                               continue;
-                       if (!opclass_is_btree(opcid))
-                               continue;
-                       if (get_op_opclass_strategy(ltop, opcid) == BTLessStrategyNumber &&
-                               get_op_opclass_strategy(gtop, opcid) == BTGreaterStrategyNumber)
-                       {
-                               clause->cmpstrategy = MERGEFUNC_CMP;
-                               ltproc = get_opclass_proc(opcid, aform->amopsubtype,
-                                                                                 BTORDER_PROC);
-                               Assert(RegProcedureIsValid(ltproc));
-                               break;                          /* done looking */
-                       }
+                       /* The sort support function should provide a comparator */
+                       OidFunctionCall1(sortfunc, PointerGetDatum(&clause->ssup));
+                       Assert(clause->ssup.comparator != NULL);
+               }
+               else
+               {
+                       /* opfamily doesn't provide sort support, get comparison func */
+                       sortfunc = get_opfamily_proc(opfamily,
+                                                                                op_lefttype,
+                                                                                op_righttype,
+                                                                                BTORDER_PROC);
+                       if (!OidIsValid(sortfunc))              /* should not happen */
+                               elog(ERROR, "missing support function %d(%u,%u) in opfamily %u",
+                                        BTORDER_PROC, op_lefttype, op_righttype, opfamily);
+                       /* We'll use a shim to call the old-style btree comparator */
+                       PrepareSortSupportComparisonShim(sortfunc, &clause->ssup);
                }
-
-               ReleaseSysCacheList(catlist);
-
-               /* Check permission to call "<" operator or cmp function */
-               aclresult = pg_proc_aclcheck(ltproc, GetUserId(), ACL_EXECUTE);
-               if (aclresult != ACLCHECK_OK)
-                       aclcheck_error(aclresult, ACL_KIND_PROC,
-                                                  get_func_name(ltproc));
-
-               /* Set up the fmgr lookup information */
-               fmgr_info(ltproc, &(clause->cmpfinfo));
 
                iClause++;
        }
@@ -288,19 +266,34 @@ MJExamineQuals(List *qualList, PlanState *parent)
  * Compute the values of the mergejoined expressions for the current
  * outer tuple.  We also detect whether it's impossible for the current
  * outer tuple to match anything --- this is true if it yields a NULL
- * input for any strict mergejoin operator.
+ * input, since we assume mergejoin operators are strict.  If the NULL
+ * is in the first join column, and that column sorts nulls last, then
+ * we can further conclude that no following tuple can match anything
+ * either, since they must all have nulls in the first column. However,
+ * that case is only interesting if we're not in FillOuter mode, else
+ * we have to visit all the tuples anyway.
+ *
+ * For the convenience of callers, we also make this routine responsible
+ * for testing for end-of-input (null outer tuple), and returning
+ * MJEVAL_ENDOFJOIN when that's seen.  This allows the same code to be used
+ * for both real end-of-input and the effective end-of-input represented by
+ * a first-column NULL.
  *
  * We evaluate the values in OuterEContext, which can be reset each
  * time we move to a new tuple.
  */
-static bool
+static MJEvalResult
 MJEvalOuterValues(MergeJoinState *mergestate)
 {
        ExprContext *econtext = mergestate->mj_OuterEContext;
-       bool            canmatch = true;
+       MJEvalResult result = MJEVAL_MATCHABLE;
        int                     i;
        MemoryContext oldContext;
 
+       /* Check for end of outer subplan */
+       if (TupIsNull(mergestate->mj_OuterTupleSlot))
+               return MJEVAL_ENDOFJOIN;
+
        ResetExprContext(econtext);
 
        oldContext = MemoryContextSwitchTo(econtext->ecxt_per_tuple_memory);
@@ -313,30 +306,41 @@ MJEvalOuterValues(MergeJoinState *mergestate)
 
                clause->ldatum = ExecEvalExpr(clause->lexpr, econtext,
                                                                          &clause->lisnull, NULL);
-               if (clause->lisnull && clause->mergestrict)
-                       canmatch = false;
+               if (clause->lisnull)
+               {
+                       /* match is impossible; can we end the join early? */
+                       if (i == 0 && !clause->ssup.ssup_nulls_first &&
+                               !mergestate->mj_FillOuter)
+                               result = MJEVAL_ENDOFJOIN;
+                       else if (result == MJEVAL_MATCHABLE)
+                               result = MJEVAL_NONMATCHABLE;
+               }
        }
 
        MemoryContextSwitchTo(oldContext);
 
-       return canmatch;
+       return result;
 }
 
 /*
  * MJEvalInnerValues
  *
- * Same as above, but for the inner tuple.  Here, we have to be prepared
+ * Same as above, but for the inner tuple.     Here, we have to be prepared
  * to load data from either the true current inner, or the marked inner,
  * so caller must tell us which slot to load from.
  */
-static bool
+static MJEvalResult
 MJEvalInnerValues(MergeJoinState *mergestate, TupleTableSlot *innerslot)
 {
        ExprContext *econtext = mergestate->mj_InnerEContext;
-       bool            canmatch = true;
+       MJEvalResult result = MJEVAL_MATCHABLE;
        int                     i;
        MemoryContext oldContext;
 
+       /* Check for end of inner subplan */
+       if (TupIsNull(innerslot))
+               return MJEVAL_ENDOFJOIN;
+
        ResetExprContext(econtext);
 
        oldContext = MemoryContextSwitchTo(econtext->ecxt_per_tuple_memory);
@@ -349,13 +353,20 @@ MJEvalInnerValues(MergeJoinState *mergestate, TupleTableSlot *innerslot)
 
                clause->rdatum = ExecEvalExpr(clause->rexpr, econtext,
                                                                          &clause->risnull, NULL);
-               if (clause->risnull && clause->mergestrict)
-                       canmatch = false;
+               if (clause->risnull)
+               {
+                       /* match is impossible; can we end the join early? */
+                       if (i == 0 && !clause->ssup.ssup_nulls_first &&
+                               !mergestate->mj_FillInner)
+                               result = MJEVAL_ENDOFJOIN;
+                       else if (result == MJEVAL_MATCHABLE)
+                               result = MJEVAL_NONMATCHABLE;
+               }
        }
 
        MemoryContextSwitchTo(oldContext);
 
-       return canmatch;
+       return result;
 }
 
 /*
@@ -363,7 +374,7 @@ MJEvalInnerValues(MergeJoinState *mergestate, TupleTableSlot *innerslot)
  *
  * Compare the mergejoinable values of the current two input tuples
  * and return 0 if they are equal (ie, the mergejoin equalities all
- * succeed), +1 if outer > inner, -1 if outer < inner.
+ * succeed), >0 if outer > inner, <0 if outer < inner.
  *
  * MJEvalOuterValues and MJEvalInnerValues must already have been called
  * for the current outer and inner tuples, respectively.
@@ -376,11 +387,10 @@ MJCompare(MergeJoinState *mergestate)
        ExprContext *econtext = mergestate->js.ps.ps_ExprContext;
        int                     i;
        MemoryContext oldContext;
-       FunctionCallInfoData fcinfo;
 
        /*
-        * Call the comparison functions in short-lived context, in case they
-        * leak memory.
+        * Call the comparison functions in short-lived context, in case they leak
+        * memory.
         */
        ResetExprContext(econtext);
 
@@ -389,135 +399,35 @@ MJCompare(MergeJoinState *mergestate)
        for (i = 0; i < mergestate->mj_NumClauses; i++)
        {
                MergeJoinClause clause = &mergestate->mj_Clauses[i];
-               Datum           fresult;
 
                /*
-                * Deal with null inputs.  We treat NULL as sorting after non-NULL.
-                *
-                * If both inputs are NULL, and the comparison function isn't
-                * strict, then we call it and check for a true result (this allows
-                * operators that behave like IS NOT DISTINCT to be mergejoinable).
-                * If the function is strict or returns false, we temporarily
-                * pretend NULL == NULL and contine checking remaining columns.
+                * Special case for NULL-vs-NULL, else use standard comparison.
                 */
-               if (clause->lisnull)
-               {
-                       if (clause->risnull)
-                       {
-                               if (!clause->eqfinfo.fn_strict)
-                               {
-                                       InitFunctionCallInfoData(fcinfo, &(clause->eqfinfo), 2,
-                                                                                        NULL, NULL);
-                                       fcinfo.arg[0] = clause->ldatum;
-                                       fcinfo.arg[1] = clause->rdatum;
-                                       fcinfo.argnull[0] = true;
-                                       fcinfo.argnull[1] = true;
-                                       fresult = FunctionCallInvoke(&fcinfo);
-                                       if (!fcinfo.isnull && DatumGetBool(fresult))
-                                       {
-                                               /* treat nulls as really equal */
-                                               continue;
-                                       }
-                               }
-                               nulleqnull = true;
-                               continue;
-                       }
-                       /* NULL > non-NULL */
-                       result = 1;
-                       break;
-               }
-               if (clause->risnull)
+               if (clause->lisnull && clause->risnull)
                {
-                       /* non-NULL < NULL */
-                       result = -1;
-                       break;
+                       nulleqnull = true;              /* NULL "=" NULL */
+                       continue;
                }
 
-               if (clause->cmpstrategy == MERGEFUNC_LT)
-               {
-                       InitFunctionCallInfoData(fcinfo, &(clause->eqfinfo), 2,
-                                                                        NULL, NULL);
-                       fcinfo.arg[0] = clause->ldatum;
-                       fcinfo.arg[1] = clause->rdatum;
-                       fcinfo.argnull[0] = false;
-                       fcinfo.argnull[1] = false;
-                       fresult = FunctionCallInvoke(&fcinfo);
-                       if (fcinfo.isnull)
-                       {
-                               nulleqnull = true;
-                               continue;
-                       }
-                       else if (DatumGetBool(fresult))
-                       {
-                               /* equal */
-                               continue;
-                       }
-                       InitFunctionCallInfoData(fcinfo, &(clause->cmpfinfo), 2,
-                                                                        NULL, NULL);
-                       fcinfo.arg[0] = clause->ldatum;
-                       fcinfo.arg[1] = clause->rdatum;
-                       fcinfo.argnull[0] = false;
-                       fcinfo.argnull[1] = false;
-                       fresult = FunctionCallInvoke(&fcinfo);
-                       if (fcinfo.isnull)
-                       {
-                               nulleqnull = true;
-                               continue;
-                       }
-                       else if (DatumGetBool(fresult))
-                       {
-                               /* less than */
-                               result = -1;
-                               break;
-                       }
-                       else
-                       {
-                               /* greater than */
-                               result = 1;
-                               break;
-                       }
-               }
-               else                                    /* must be MERGEFUNC_CMP */
-               {
-                       InitFunctionCallInfoData(fcinfo, &(clause->cmpfinfo), 2,
-                                                                        NULL, NULL);
-                       fcinfo.arg[0] = clause->ldatum;
-                       fcinfo.arg[1] = clause->rdatum;
-                       fcinfo.argnull[0] = false;
-                       fcinfo.argnull[1] = false;
-                       fresult = FunctionCallInvoke(&fcinfo);
-                       if (fcinfo.isnull)
-                       {
-                               nulleqnull = true;
-                               continue;
-                       }
-                       else if (DatumGetInt32(fresult) == 0)
-                       {
-                               /* equal */
-                               continue;
-                       }
-                       else if (DatumGetInt32(fresult) < 0)
-                       {
-                               /* less than */
-                               result = -1;
-                               break;
-                       }
-                       else
-                       {
-                               /* greater than */
-                               result = 1;
-                               break;
-                       }
-               }
+               result = ApplySortComparator(clause->ldatum, clause->lisnull,
+                                                                        clause->rdatum, clause->risnull,
+                                                                        &clause->ssup);
+
+               if (result != 0)
+                       break;
        }
 
        /*
-        * If we had any null comparison results or NULL-vs-NULL inputs,
-        * we do not want to report that the tuples are equal.  Instead,
-        * if result is still 0, change it to +1.  This will result in
-        * advancing the inner side of the join.
+        * If we had any NULL-vs-NULL inputs, we do not want to report that the
+        * tuples are equal.  Instead, if result is still 0, change it to +1.
+        * This will result in advancing the inner side of the join.
+        *
+        * Likewise, if there was a constant-false joinqual, do not report
+        * equality.  We have to check this as part of the mergequals, else the
+        * rescan logic will do the wrong thing.
         */
-       if (nulleqnull && result == 0)
+       if (result == 0 &&
+               (nulleqnull || mergestate->mj_ConstFalseJoin))
                result = 1;
 
        MemoryContextSwitchTo(oldContext);
@@ -525,6 +435,116 @@ MJCompare(MergeJoinState *mergestate)
        return result;
 }
 
+
+/*
+ * Generate a fake join tuple with nulls for the inner tuple,
+ * and return it if it passes the non-join quals.
+ */
+static TupleTableSlot *
+MJFillOuter(MergeJoinState *node)
+{
+       ExprContext *econtext = node->js.ps.ps_ExprContext;
+       List       *otherqual = node->js.ps.qual;
+
+       ResetExprContext(econtext);
+
+       econtext->ecxt_outertuple = node->mj_OuterTupleSlot;
+       econtext->ecxt_innertuple = node->mj_NullInnerTupleSlot;
+
+       if (ExecQual(otherqual, econtext, false))
+       {
+               /*
+                * qualification succeeded.  now form the desired projection tuple and
+                * return the slot containing it.
+                */
+               TupleTableSlot *result;
+               ExprDoneCond isDone;
+
+               MJ_printf("ExecMergeJoin: returning outer fill tuple\n");
+
+               result = ExecProject(node->js.ps.ps_ProjInfo, &isDone);
+
+               if (isDone != ExprEndResult)
+               {
+                       node->js.ps.ps_TupFromTlist =
+                               (isDone == ExprMultipleResult);
+                       return result;
+               }
+       }
+       else
+               InstrCountFiltered2(node, 1);
+
+       return NULL;
+}
+
+/*
+ * Generate a fake join tuple with nulls for the outer tuple,
+ * and return it if it passes the non-join quals.
+ */
+static TupleTableSlot *
+MJFillInner(MergeJoinState *node)
+{
+       ExprContext *econtext = node->js.ps.ps_ExprContext;
+       List       *otherqual = node->js.ps.qual;
+
+       ResetExprContext(econtext);
+
+       econtext->ecxt_outertuple = node->mj_NullOuterTupleSlot;
+       econtext->ecxt_innertuple = node->mj_InnerTupleSlot;
+
+       if (ExecQual(otherqual, econtext, false))
+       {
+               /*
+                * qualification succeeded.  now form the desired projection tuple and
+                * return the slot containing it.
+                */
+               TupleTableSlot *result;
+               ExprDoneCond isDone;
+
+               MJ_printf("ExecMergeJoin: returning inner fill tuple\n");
+
+               result = ExecProject(node->js.ps.ps_ProjInfo, &isDone);
+
+               if (isDone != ExprEndResult)
+               {
+                       node->js.ps.ps_TupFromTlist =
+                               (isDone == ExprMultipleResult);
+                       return result;
+               }
+       }
+       else
+               InstrCountFiltered2(node, 1);
+
+       return NULL;
+}
+
+
+/*
+ * Check that a qual condition is constant true or constant false.
+ * If it is constant false (or null), set *is_const_false to TRUE.
+ *
+ * Constant true would normally be represented by a NIL list, but we allow an
+ * actual bool Const as well.  We do expect that the planner will have thrown
+ * away any non-constant terms that have been ANDed with a constant false.
+ */
+static bool
+check_constant_qual(List *qual, bool *is_const_false)
+{
+       ListCell   *lc;
+
+       foreach(lc, qual)
+       {
+               Const      *con = (Const *) lfirst(lc);
+
+               if (!con || !IsA(con, Const))
+                       return false;
+               if (con->constisnull || !DatumGetBool(con->constvalue))
+                       *is_const_false = true;
+       }
+       return true;
+}
+
+
 /* ----------------------------------------------------------------
  *             ExecMergeTupleDump
  *
@@ -590,7 +610,6 @@ ExecMergeTupleDump(MergeJoinState *mergestate)
 TupleTableSlot *
 ExecMergeJoin(MergeJoinState *node)
 {
-       EState     *estate;
        List       *joinqual;
        List       *otherqual;
        bool            qualResult;
@@ -606,44 +625,18 @@ ExecMergeJoin(MergeJoinState *node)
        /*
         * get information from node
         */
-       estate = node->js.ps.state;
        innerPlan = innerPlanState(node);
        outerPlan = outerPlanState(node);
        econtext = node->js.ps.ps_ExprContext;
        joinqual = node->js.joinqual;
        otherqual = node->js.ps.qual;
-
-       switch (node->js.jointype)
-       {
-               case JOIN_INNER:
-               case JOIN_IN:
-                       doFillOuter = false;
-                       doFillInner = false;
-                       break;
-               case JOIN_LEFT:
-                       doFillOuter = true;
-                       doFillInner = false;
-                       break;
-               case JOIN_FULL:
-                       doFillOuter = true;
-                       doFillInner = true;
-                       break;
-               case JOIN_RIGHT:
-                       doFillOuter = false;
-                       doFillInner = true;
-                       break;
-               default:
-                       elog(ERROR, "unrecognized join type: %d",
-                                (int) node->js.jointype);
-                       doFillOuter = false;    /* keep compiler quiet */
-                       doFillInner = false;
-                       break;
-       }
+       doFillOuter = node->mj_FillOuter;
+       doFillInner = node->mj_FillInner;
 
        /*
-        * Check to see if we're still projecting out tuples from a previous
-        * join tuple (because there is a function-returning-set in the
-        * projection expressions).  If so, try to project another one.
+        * Check to see if we're still projecting out tuples from a previous join
+        * tuple (because there is a function-returning-set in the projection
+        * expressions).  If so, try to project another one.
         */
        if (node->js.ps.ps_TupFromTlist)
        {
@@ -659,8 +652,8 @@ ExecMergeJoin(MergeJoinState *node)
 
        /*
         * Reset per-tuple memory context to free any expression evaluation
-        * storage allocated in the previous tuple cycle.  Note this can't
-        * happen until we're done projecting out tuples from a join tuple.
+        * storage allocated in the previous tuple cycle.  Note this can't happen
+        * until we're done projecting out tuples from a join tuple.
         */
        ResetExprContext(econtext);
 
@@ -678,44 +671,56 @@ ExecMergeJoin(MergeJoinState *node)
                {
                                /*
                                 * EXEC_MJ_INITIALIZE_OUTER means that this is the first time
-                                * ExecMergeJoin() has been called and so we have to fetch
-                                * the first matchable tuple for both outer and inner subplans.
-                                * We do the outer side in INITIALIZE_OUTER state, then
-                                * advance to INITIALIZE_INNER state for the inner subplan.
+                                * ExecMergeJoin() has been called and so we have to fetch the
+                                * first matchable tuple for both outer and inner subplans. We
+                                * do the outer side in INITIALIZE_OUTER state, then advance
+                                * to INITIALIZE_INNER state for the inner subplan.
                                 */
                        case EXEC_MJ_INITIALIZE_OUTER:
                                MJ_printf("ExecMergeJoin: EXEC_MJ_INITIALIZE_OUTER\n");
 
                                outerTupleSlot = ExecProcNode(outerPlan);
                                node->mj_OuterTupleSlot = outerTupleSlot;
-                               if (TupIsNull(outerTupleSlot))
-                               {
-                                       MJ_printf("ExecMergeJoin: nothing in outer subplan\n");
-                                       if (doFillInner)
-                                       {
-                                               /*
-                                                * Need to emit right-join tuples for remaining
-                                                * inner tuples.  We set MatchedInner = true to
-                                                * force the ENDOUTER state to advance inner.
-                                                */
-                                               node->mj_JoinState = EXEC_MJ_ENDOUTER;
-                                               node->mj_MatchedInner = true;
-                                               break;
-                                       }
-                                       /* Otherwise we're done. */
-                                       return NULL;
-                               }
 
                                /* Compute join values and check for unmatchability */
-                               if (!MJEvalOuterValues(node) && !doFillOuter)
+                               switch (MJEvalOuterValues(node))
                                {
-                                       /* Stay in same state to fetch next outer tuple */
-                                       node->mj_JoinState = EXEC_MJ_INITIALIZE_OUTER;
-                               }
-                               else
-                               {
-                                       /* OK to go get the first inner tuple */
-                                       node->mj_JoinState = EXEC_MJ_INITIALIZE_INNER;
+                                       case MJEVAL_MATCHABLE:
+                                               /* OK to go get the first inner tuple */
+                                               node->mj_JoinState = EXEC_MJ_INITIALIZE_INNER;
+                                               break;
+                                       case MJEVAL_NONMATCHABLE:
+                                               /* Stay in same state to fetch next outer tuple */
+                                               if (doFillOuter)
+                                               {
+                                                       /*
+                                                        * Generate a fake join tuple with nulls for the
+                                                        * inner tuple, and return it if it passes the
+                                                        * non-join quals.
+                                                        */
+                                                       TupleTableSlot *result;
+
+                                                       result = MJFillOuter(node);
+                                                       if (result)
+                                                               return result;
+                                               }
+                                               break;
+                                       case MJEVAL_ENDOFJOIN:
+                                               /* No more outer tuples */
+                                               MJ_printf("ExecMergeJoin: nothing in outer subplan\n");
+                                               if (doFillInner)
+                                               {
+                                                       /*
+                                                        * Need to emit right-join tuples for remaining
+                                                        * inner tuples. We set MatchedInner = true to
+                                                        * force the ENDOUTER state to advance inner.
+                                                        */
+                                                       node->mj_JoinState = EXEC_MJ_ENDOUTER;
+                                                       node->mj_MatchedInner = true;
+                                                       break;
+                                               }
+                                               /* Otherwise we're done. */
+                                               return NULL;
                                }
                                break;
 
@@ -724,46 +729,62 @@ ExecMergeJoin(MergeJoinState *node)
 
                                innerTupleSlot = ExecProcNode(innerPlan);
                                node->mj_InnerTupleSlot = innerTupleSlot;
-                               if (TupIsNull(innerTupleSlot))
+
+                               /* Compute join values and check for unmatchability */
+                               switch (MJEvalInnerValues(node, innerTupleSlot))
                                {
-                                       MJ_printf("ExecMergeJoin: nothing in inner subplan\n");
-                                       if (doFillOuter)
-                                       {
+                                       case MJEVAL_MATCHABLE:
+
                                                /*
-                                                * Need to emit left-join tuples for all outer
-                                                * tuples, including the one we just fetched.  We
-                                                * set MatchedOuter = false to force the ENDINNER
-                                                * state to emit first tuple before advancing
-                                                * outer.
+                                                * OK, we have the initial tuples.      Begin by skipping
+                                                * non-matching tuples.
                                                 */
-                                               node->mj_JoinState = EXEC_MJ_ENDINNER;
-                                               node->mj_MatchedOuter = false;
+                                               node->mj_JoinState = EXEC_MJ_SKIP_TEST;
                                                break;
-                                       }
-                                       /* Otherwise we're done. */
-                                       return NULL;
-                               }
+                                       case MJEVAL_NONMATCHABLE:
+                                               /* Mark before advancing, if wanted */
+                                               if (node->mj_ExtraMarks)
+                                                       ExecMarkPos(innerPlan);
+                                               /* Stay in same state to fetch next inner tuple */
+                                               if (doFillInner)
+                                               {
+                                                       /*
+                                                        * Generate a fake join tuple with nulls for the
+                                                        * outer tuple, and return it if it passes the
+                                                        * non-join quals.
+                                                        */
+                                                       TupleTableSlot *result;
 
-                               /* Compute join values and check for unmatchability */
-                               if (!MJEvalInnerValues(node, innerTupleSlot) && !doFillInner)
-                               {
-                                       /* Stay in same state to fetch next inner tuple */
-                                       node->mj_JoinState = EXEC_MJ_INITIALIZE_INNER;
-                               }
-                               else
-                               {
-                                       /*
-                                        * OK, we have the initial tuples.      Begin by skipping
-                                        * non-matching tuples.
-                                        */
-                                       node->mj_JoinState = EXEC_MJ_SKIP_TEST;
+                                                       result = MJFillInner(node);
+                                                       if (result)
+                                                               return result;
+                                               }
+                                               break;
+                                       case MJEVAL_ENDOFJOIN:
+                                               /* No more inner tuples */
+                                               MJ_printf("ExecMergeJoin: nothing in inner subplan\n");
+                                               if (doFillOuter)
+                                               {
+                                                       /*
+                                                        * Need to emit left-join tuples for all outer
+                                                        * tuples, including the one we just fetched.  We
+                                                        * set MatchedOuter = false to force the ENDINNER
+                                                        * state to emit first tuple before advancing
+                                                        * outer.
+                                                        */
+                                                       node->mj_JoinState = EXEC_MJ_ENDINNER;
+                                                       node->mj_MatchedOuter = false;
+                                                       break;
+                                               }
+                                               /* Otherwise we're done. */
+                                               return NULL;
                                }
                                break;
 
                                /*
-                                * EXEC_MJ_JOINTUPLES means we have two tuples which
-                                * satisfied the merge clause so we join them and then
-                                * proceed to get the next inner tuple (EXEC_MJ_NEXTINNER).
+                                * EXEC_MJ_JOINTUPLES means we have two tuples which satisfied
+                                * the merge clause so we join them and then proceed to get
+                                * the next inner tuple (EXEC_MJ_NEXTINNER).
                                 */
                        case EXEC_MJ_JOINTUPLES:
                                MJ_printf("ExecMergeJoin: EXEC_MJ_JOINTUPLES\n");
@@ -776,39 +797,47 @@ ExecMergeJoin(MergeJoinState *node)
                                node->mj_JoinState = EXEC_MJ_NEXTINNER;
 
                                /*
-                                * Check the extra qual conditions to see if we actually
-                                * want to return this join tuple.      If not, can proceed
-                                * with merge.  We must distinguish the additional
-                                * joinquals (which must pass to consider the tuples
-                                * "matched" for outer-join logic) from the otherquals
-                                * (which must pass before we actually return the tuple).
+                                * Check the extra qual conditions to see if we actually want
+                                * to return this join tuple.  If not, can proceed with merge.
+                                * We must distinguish the additional joinquals (which must
+                                * pass to consider the tuples "matched" for outer-join logic)
+                                * from the otherquals (which must pass before we actually
+                                * return the tuple).
                                 *
                                 * We don't bother with a ResetExprContext here, on the
-                                * assumption that we just did one while checking the
-                                * merge qual.  One per tuple should be sufficient.  We
-                                * do have to set up the econtext links to the tuples
-                                * for ExecQual to use.
+                                * assumption that we just did one while checking the merge
+                                * qual.  One per tuple should be sufficient.  We do have to
+                                * set up the econtext links to the tuples for ExecQual to
+                                * use.
                                 */
                                outerTupleSlot = node->mj_OuterTupleSlot;
                                econtext->ecxt_outertuple = outerTupleSlot;
                                innerTupleSlot = node->mj_InnerTupleSlot;
                                econtext->ecxt_innertuple = innerTupleSlot;
 
-                               if (node->js.jointype == JOIN_IN &&
-                                       node->mj_MatchedOuter)
-                                       qualResult = false;
-                               else
-                               {
-                                       qualResult = (joinqual == NIL ||
-                                                                 ExecQual(joinqual, econtext, false));
-                                       MJ_DEBUG_QUAL(joinqual, qualResult);
-                               }
+                               qualResult = (joinqual == NIL ||
+                                                         ExecQual(joinqual, econtext, false));
+                               MJ_DEBUG_QUAL(joinqual, qualResult);
 
                                if (qualResult)
                                {
                                        node->mj_MatchedOuter = true;
                                        node->mj_MatchedInner = true;
 
+                                       /* In an antijoin, we never return a matched tuple */
+                                       if (node->js.jointype == JOIN_ANTI)
+                                       {
+                                               node->mj_JoinState = EXEC_MJ_NEXTOUTER;
+                                               break;
+                                       }
+
+                                       /*
+                                        * In a semijoin, we'll consider returning the first
+                                        * match, but after that we're done with this outer tuple.
+                                        */
+                                       if (node->js.jointype == JOIN_SEMI)
+                                               node->mj_JoinState = EXEC_MJ_NEXTOUTER;
+
                                        qualResult = (otherqual == NIL ||
                                                                  ExecQual(otherqual, econtext, false));
                                        MJ_DEBUG_QUAL(otherqual, qualResult);
@@ -817,8 +846,7 @@ ExecMergeJoin(MergeJoinState *node)
                                        {
                                                /*
                                                 * qualification succeeded.  now form the desired
-                                                * projection tuple and return the slot containing
-                                                * it.
+                                                * projection tuple and return the slot containing it.
                                                 */
                                                TupleTableSlot *result;
                                                ExprDoneCond isDone;
@@ -835,13 +863,17 @@ ExecMergeJoin(MergeJoinState *node)
                                                        return result;
                                                }
                                        }
+                                       else
+                                               InstrCountFiltered2(node, 1);
                                }
+                               else
+                                       InstrCountFiltered1(node, 1);
                                break;
 
                                /*
-                                * EXEC_MJ_NEXTINNER means advance the inner scan to the
-                                * next tuple. If the tuple is not nil, we then proceed to
-                                * test it against the join qualification.
+                                * EXEC_MJ_NEXTINNER means advance the inner scan to the next
+                                * tuple. If the tuple is not nil, we then proceed to test it
+                                * against the join qualification.
                                 *
                                 * Before advancing, we check to see if we must emit an
                                 * outer-join fill tuple for this inner tuple.
@@ -853,81 +885,78 @@ ExecMergeJoin(MergeJoinState *node)
                                {
                                        /*
                                         * Generate a fake join tuple with nulls for the outer
-                                        * tuple, and return it if it passes the non-join
-                                        * quals.
+                                        * tuple, and return it if it passes the non-join quals.
                                         */
-                                       node->mj_MatchedInner = true;           /* do it only once */
+                                       TupleTableSlot *result;
 
-                                       ResetExprContext(econtext);
-
-                                       outerTupleSlot = node->mj_NullOuterTupleSlot;
-                                       econtext->ecxt_outertuple = outerTupleSlot;
-                                       innerTupleSlot = node->mj_InnerTupleSlot;
-                                       econtext->ecxt_innertuple = innerTupleSlot;
-
-                                       if (ExecQual(otherqual, econtext, false))
-                                       {
-                                               /*
-                                                * qualification succeeded.  now form the desired
-                                                * projection tuple and return the slot containing
-                                                * it.
-                                                */
-                                               TupleTableSlot *result;
-                                               ExprDoneCond isDone;
-
-                                               MJ_printf("ExecMergeJoin: returning fill tuple\n");
-
-                                               result = ExecProject(node->js.ps.ps_ProjInfo,
-                                                                                        &isDone);
+                                       node->mj_MatchedInner = true;           /* do it only once */
 
-                                               if (isDone != ExprEndResult)
-                                               {
-                                                       node->js.ps.ps_TupFromTlist =
-                                                               (isDone == ExprMultipleResult);
-                                                       return result;
-                                               }
-                                       }
+                                       result = MJFillInner(node);
+                                       if (result)
+                                               return result;
                                }
 
                                /*
-                                * now we get the next inner tuple, if any.  If there's
-                                * none, advance to next outer tuple (which may be able
-                                * to join to previously marked tuples).
+                                * now we get the next inner tuple, if any.  If there's none,
+                                * advance to next outer tuple (which may be able to join to
+                                * previously marked tuples).
                                 *
-                                * If we find one but it cannot join to anything, stay
-                                * in NEXTINNER state to fetch the next one.
+                                * NB: must NOT do "extraMarks" here, since we may need to
+                                * return to previously marked tuples.
                                 */
                                innerTupleSlot = ExecProcNode(innerPlan);
                                node->mj_InnerTupleSlot = innerTupleSlot;
                                MJ_DEBUG_PROC_NODE(innerTupleSlot);
                                node->mj_MatchedInner = false;
 
-                               if (TupIsNull(innerTupleSlot))
+                               /* Compute join values and check for unmatchability */
+                               switch (MJEvalInnerValues(node, innerTupleSlot))
                                {
-                                       node->mj_JoinState = EXEC_MJ_NEXTOUTER;
-                                       break;
-                               }
+                                       case MJEVAL_MATCHABLE:
 
-                               if (!MJEvalInnerValues(node, innerTupleSlot))
-                                       break;          /* stay in NEXTINNER state */
+                                               /*
+                                                * Test the new inner tuple to see if it matches
+                                                * outer.
+                                                *
+                                                * If they do match, then we join them and move on to
+                                                * the next inner tuple (EXEC_MJ_JOINTUPLES).
+                                                *
+                                                * If they do not match then advance to next outer
+                                                * tuple.
+                                                */
+                                               compareResult = MJCompare(node);
+                                               MJ_DEBUG_COMPARE(compareResult);
 
-                               /*
-                                * Test the new inner tuple to see if it matches outer.
-                                *
-                                * If they do match, then we join them and move on to the
-                                * next inner tuple (EXEC_MJ_JOINTUPLES).
-                                *
-                                * If they do not match then advance to next outer tuple.
-                                */
-                               compareResult = MJCompare(node);
-                               MJ_DEBUG_COMPARE(compareResult);
+                                               if (compareResult == 0)
+                                                       node->mj_JoinState = EXEC_MJ_JOINTUPLES;
+                                               else
+                                               {
+                                                       Assert(compareResult < 0);
+                                                       node->mj_JoinState = EXEC_MJ_NEXTOUTER;
+                                               }
+                                               break;
+                                       case MJEVAL_NONMATCHABLE:
 
-                               if (compareResult == 0)
-                                       node->mj_JoinState = EXEC_MJ_JOINTUPLES;
-                               else
-                               {
-                                       Assert(compareResult < 0);
-                                       node->mj_JoinState = EXEC_MJ_NEXTOUTER;
+                                               /*
+                                                * It contains a NULL and hence can't match any outer
+                                                * tuple, so we can skip the comparison and assume the
+                                                * new tuple is greater than current outer.
+                                                */
+                                               node->mj_JoinState = EXEC_MJ_NEXTOUTER;
+                                               break;
+                                       case MJEVAL_ENDOFJOIN:
+
+                                               /*
+                                                * No more inner tuples.  However, this might be only
+                                                * effective and not physical end of inner plan, so
+                                                * force mj_InnerTupleSlot to null to make sure we
+                                                * don't fetch more inner tuples.  (We need this hack
+                                                * because we are not transiting to a state where the
+                                                * inner plan is assumed to be exhausted.)
+                                                */
+                                               node->mj_InnerTupleSlot = NULL;
+                                               node->mj_JoinState = EXEC_MJ_NEXTOUTER;
+                                               break;
                                }
                                break;
 
@@ -958,40 +987,15 @@ ExecMergeJoin(MergeJoinState *node)
                                {
                                        /*
                                         * Generate a fake join tuple with nulls for the inner
-                                        * tuple, and return it if it passes the non-join
-                                        * quals.
+                                        * tuple, and return it if it passes the non-join quals.
                                         */
-                                       node->mj_MatchedOuter = true;           /* do it only once */
+                                       TupleTableSlot *result;
 
-                                       ResetExprContext(econtext);
-
-                                       outerTupleSlot = node->mj_OuterTupleSlot;
-                                       econtext->ecxt_outertuple = outerTupleSlot;
-                                       innerTupleSlot = node->mj_NullInnerTupleSlot;
-                                       econtext->ecxt_innertuple = innerTupleSlot;
-
-                                       if (ExecQual(otherqual, econtext, false))
-                                       {
-                                               /*
-                                                * qualification succeeded.  now form the desired
-                                                * projection tuple and return the slot containing
-                                                * it.
-                                                */
-                                               TupleTableSlot *result;
-                                               ExprDoneCond isDone;
-
-                                               MJ_printf("ExecMergeJoin: returning fill tuple\n");
-
-                                               result = ExecProject(node->js.ps.ps_ProjInfo,
-                                                                                        &isDone);
+                                       node->mj_MatchedOuter = true;           /* do it only once */
 
-                                               if (isDone != ExprEndResult)
-                                               {
-                                                       node->js.ps.ps_TupFromTlist =
-                                                               (isDone == ExprMultipleResult);
-                                                       return result;
-                                               }
-                                       }
+                                       result = MJFillOuter(node);
+                                       if (result)
+                                               return result;
                                }
 
                                /*
@@ -1002,37 +1006,32 @@ ExecMergeJoin(MergeJoinState *node)
                                MJ_DEBUG_PROC_NODE(outerTupleSlot);
                                node->mj_MatchedOuter = false;
 
-                               /*
-                                * if the outer tuple is null then we are done with the
-                                * join, unless we have inner tuples we need to null-fill.
-                                */
-                               if (TupIsNull(outerTupleSlot))
-                               {
-                                       MJ_printf("ExecMergeJoin: end of outer subplan\n");
-                                       innerTupleSlot = node->mj_InnerTupleSlot;
-                                       if (doFillInner && !TupIsNull(innerTupleSlot))
-                                       {
-                                               /*
-                                                * Need to emit right-join tuples for remaining
-                                                * inner tuples.
-                                                */
-                                               node->mj_JoinState = EXEC_MJ_ENDOUTER;
-                                               break;
-                                       }
-                                       /* Otherwise we're done. */
-                                       return NULL;
-                               }
-
                                /* Compute join values and check for unmatchability */
-                               if (!MJEvalOuterValues(node))
+                               switch (MJEvalOuterValues(node))
                                {
-                                       /* Stay in same state to fetch next outer tuple */
-                                       node->mj_JoinState = EXEC_MJ_NEXTOUTER;
-                               }
-                               else
-                               {
-                                       /* Go test the tuple */
-                                       node->mj_JoinState = EXEC_MJ_TESTOUTER;
+                                       case MJEVAL_MATCHABLE:
+                                               /* Go test the new tuple against the marked tuple */
+                                               node->mj_JoinState = EXEC_MJ_TESTOUTER;
+                                               break;
+                                       case MJEVAL_NONMATCHABLE:
+                                               /* Can't match, so fetch next outer tuple */
+                                               node->mj_JoinState = EXEC_MJ_NEXTOUTER;
+                                               break;
+                                       case MJEVAL_ENDOFJOIN:
+                                               /* No more outer tuples */
+                                               MJ_printf("ExecMergeJoin: end of outer subplan\n");
+                                               innerTupleSlot = node->mj_InnerTupleSlot;
+                                               if (doFillInner && !TupIsNull(innerTupleSlot))
+                                               {
+                                                       /*
+                                                        * Need to emit right-join tuples for remaining
+                                                        * inner tuples.
+                                                        */
+                                                       node->mj_JoinState = EXEC_MJ_ENDOUTER;
+                                                       break;
+                                               }
+                                               /* Otherwise we're done. */
+                                               return NULL;
                                }
                                break;
 
@@ -1041,7 +1040,7 @@ ExecMergeJoin(MergeJoinState *node)
                                 * tuple satisfy the merge clause then we know we have
                                 * duplicates in the outer scan so we have to restore the
                                 * inner scan to the marked tuple and proceed to join the
-                                * new outer tuples with the inner tuples.
+                                * new outer tuple with the inner tuples.
                                 *
                                 * This is the case when
                                 *                                                outer inner
@@ -1075,8 +1074,9 @@ ExecMergeJoin(MergeJoinState *node)
                                MJ_printf("ExecMergeJoin: EXEC_MJ_TESTOUTER\n");
 
                                /*
-                                * here we must compare the outer tuple with the marked inner
-                                * tuple
+                                * Here we must compare the outer tuple with the marked inner
+                                * tuple.  (We can ignore the result of MJEvalInnerValues,
+                                * since the marked inner tuple is certainly matchable.)
                                 */
                                innerTupleSlot = node->mj_MarkedTupleSlot;
                                (void) MJEvalInnerValues(node, innerTupleSlot);
@@ -1087,24 +1087,29 @@ ExecMergeJoin(MergeJoinState *node)
                                if (compareResult == 0)
                                {
                                        /*
-                                        * the merge clause matched so now we restore the
-                                        * inner scan position to the first mark, and go join
-                                        * that tuple (and any following ones) to the new outer.
+                                        * the merge clause matched so now we restore the inner
+                                        * scan position to the first mark, and go join that tuple
+                                        * (and any following ones) to the new outer.
                                         *
                                         * NOTE: we do not need to worry about the MatchedInner
-                                        * state for the rescanned inner tuples.  We know all
-                                        * of them will match this new outer tuple and
-                                        * therefore won't be emitted as fill tuples.  This
-                                        * works *only* because we require the extra joinquals
-                                        * to be nil when doing a right or full join ---
-                                        * otherwise some of the rescanned tuples might fail
-                                        * the extra joinquals.
+                                        * state for the rescanned inner tuples.  We know all of
+                                        * them will match this new outer tuple and therefore
+                                        * won't be emitted as fill tuples.  This works *only*
+                                        * because we require the extra joinquals to be constant
+                                        * when doing a right or full join --- otherwise some of
+                                        * the rescanned tuples might fail the extra joinquals.
+                                        * This obviously won't happen for a constant-true extra
+                                        * joinqual, while the constant-false case is handled by
+                                        * forcing the merge clause to never match, so we never
+                                        * get here.
                                         */
                                        ExecRestrPos(innerPlan);
 
                                        /*
-                                        * ExecRestrPos really should give us back a new Slot,
-                                        * but since it doesn't, use the marked slot.
+                                        * ExecRestrPos probably should give us back a new Slot,
+                                        * but since it doesn't, use the marked slot.  (The
+                                        * previously returned mj_InnerTupleSlot cannot be assumed
+                                        * to hold the required tuple.)
                                         */
                                        node->mj_InnerTupleSlot = innerTupleSlot;
                                        /* we need not do MJEvalInnerValues again */
@@ -1126,32 +1131,43 @@ ExecMergeJoin(MergeJoinState *node)
                                         *      which means that all subsequent outer tuples will be
                                         *      larger than our marked inner tuples.  So we need not
                                         *      revisit any of the marked tuples but can proceed to
-                                        *      look for a match to the current inner.  If there's
-                                        *      no more inners, we are done.
+                                        *      look for a match to the current inner.  If there's
+                                        *      no more inners, no more matches are possible.
                                         * ----------------
                                         */
                                        Assert(compareResult > 0);
                                        innerTupleSlot = node->mj_InnerTupleSlot;
-                                       if (TupIsNull(innerTupleSlot))
+
+                                       /* reload comparison data for current inner */
+                                       switch (MJEvalInnerValues(node, innerTupleSlot))
                                        {
-                                               if (doFillOuter)
-                                               {
+                                               case MJEVAL_MATCHABLE:
+                                                       /* proceed to compare it to the current outer */
+                                                       node->mj_JoinState = EXEC_MJ_SKIP_TEST;
+                                                       break;
+                                               case MJEVAL_NONMATCHABLE:
+
                                                        /*
-                                                        * Need to emit left-join tuples for remaining
-                                                        * outer tuples.
+                                                        * current inner can't possibly match any outer;
+                                                        * better to advance the inner scan than the
+                                                        * outer.
                                                         */
-                                                       node->mj_JoinState = EXEC_MJ_ENDINNER;
+                                                       node->mj_JoinState = EXEC_MJ_SKIPINNER_ADVANCE;
                                                        break;
-                                               }
-                                               /* Otherwise we're done. */
-                                               return NULL;
+                                               case MJEVAL_ENDOFJOIN:
+                                                       /* No more inner tuples */
+                                                       if (doFillOuter)
+                                                       {
+                                                               /*
+                                                                * Need to emit left-join tuples for remaining
+                                                                * outer tuples.
+                                                                */
+                                                               node->mj_JoinState = EXEC_MJ_ENDINNER;
+                                                               break;
+                                                       }
+                                                       /* Otherwise we're done. */
+                                                       return NULL;
                                        }
-
-                                       /* reload comparison data for current inner */
-                                       (void) MJEvalInnerValues(node, innerTupleSlot);
-
-                                       /* continue on to skip outer tuples */
-                                       node->mj_JoinState = EXEC_MJ_SKIP_TEST;
                                }
                                break;
 
@@ -1189,8 +1205,8 @@ ExecMergeJoin(MergeJoinState *node)
 
                                /*
                                 * before we advance, make sure the current tuples do not
-                                * satisfy the mergeclauses.  If they do, then we update
-                                * the marked tuple position and go join them.
+                                * satisfy the mergeclauses.  If they do, then we update the
+                                * marked tuple position and go join them.
                                 */
                                compareResult = MJCompare(node);
                                MJ_DEBUG_COMPARE(compareResult);
@@ -1205,11 +1221,15 @@ ExecMergeJoin(MergeJoinState *node)
                                }
                                else if (compareResult < 0)
                                        node->mj_JoinState = EXEC_MJ_SKIPOUTER_ADVANCE;
-                               else                    /* compareResult > 0 */
+                               else
+                                       /* compareResult > 0 */
                                        node->mj_JoinState = EXEC_MJ_SKIPINNER_ADVANCE;
                                break;
 
                                /*
+                                * SKIPOUTER_ADVANCE: advance over an outer tuple that is
+                                * known not to join to any inner tuple.
+                                *
                                 * Before advancing, we check to see if we must emit an
                                 * outer-join fill tuple for this outer tuple.
                                 */
@@ -1220,40 +1240,15 @@ ExecMergeJoin(MergeJoinState *node)
                                {
                                        /*
                                         * Generate a fake join tuple with nulls for the inner
-                                        * tuple, and return it if it passes the non-join
-                                        * quals.
+                                        * tuple, and return it if it passes the non-join quals.
                                         */
-                                       node->mj_MatchedOuter = true;           /* do it only once */
+                                       TupleTableSlot *result;
 
-                                       ResetExprContext(econtext);
-
-                                       outerTupleSlot = node->mj_OuterTupleSlot;
-                                       econtext->ecxt_outertuple = outerTupleSlot;
-                                       innerTupleSlot = node->mj_NullInnerTupleSlot;
-                                       econtext->ecxt_innertuple = innerTupleSlot;
-
-                                       if (ExecQual(otherqual, econtext, false))
-                                       {
-                                               /*
-                                                * qualification succeeded.  now form the desired
-                                                * projection tuple and return the slot containing
-                                                * it.
-                                                */
-                                               TupleTableSlot *result;
-                                               ExprDoneCond isDone;
-
-                                               MJ_printf("ExecMergeJoin: returning fill tuple\n");
-
-                                               result = ExecProject(node->js.ps.ps_ProjInfo,
-                                                                                        &isDone);
+                                       node->mj_MatchedOuter = true;           /* do it only once */
 
-                                               if (isDone != ExprEndResult)
-                                               {
-                                                       node->js.ps.ps_TupFromTlist =
-                                                               (isDone == ExprMultipleResult);
-                                                       return result;
-                                               }
-                                       }
+                                       result = MJFillOuter(node);
+                                       if (result)
+                                               return result;
                                }
 
                                /*
@@ -1264,40 +1259,39 @@ ExecMergeJoin(MergeJoinState *node)
                                MJ_DEBUG_PROC_NODE(outerTupleSlot);
                                node->mj_MatchedOuter = false;
 
-                               /*
-                                * if the outer tuple is null then we are done with the
-                                * join, unless we have inner tuples we need to null-fill.
-                                */
-                               if (TupIsNull(outerTupleSlot))
-                               {
-                                       MJ_printf("ExecMergeJoin: end of outer subplan\n");
-                                       innerTupleSlot = node->mj_InnerTupleSlot;
-                                       if (doFillInner && !TupIsNull(innerTupleSlot))
-                                       {
-                                               /*
-                                                * Need to emit right-join tuples for remaining
-                                                * inner tuples.
-                                                */
-                                               node->mj_JoinState = EXEC_MJ_ENDOUTER;
-                                               break;
-                                       }
-                                       /* Otherwise we're done. */
-                                       return NULL;
-                               }
-
                                /* Compute join values and check for unmatchability */
-                               if (!MJEvalOuterValues(node))
+                               switch (MJEvalOuterValues(node))
                                {
-                                       /* Stay in same state to fetch next outer tuple */
-                                       node->mj_JoinState = EXEC_MJ_SKIPOUTER_ADVANCE;
-                                       break;
+                                       case MJEVAL_MATCHABLE:
+                                               /* Go test the new tuple against the current inner */
+                                               node->mj_JoinState = EXEC_MJ_SKIP_TEST;
+                                               break;
+                                       case MJEVAL_NONMATCHABLE:
+                                               /* Can't match, so fetch next outer tuple */
+                                               node->mj_JoinState = EXEC_MJ_SKIPOUTER_ADVANCE;
+                                               break;
+                                       case MJEVAL_ENDOFJOIN:
+                                               /* No more outer tuples */
+                                               MJ_printf("ExecMergeJoin: end of outer subplan\n");
+                                               innerTupleSlot = node->mj_InnerTupleSlot;
+                                               if (doFillInner && !TupIsNull(innerTupleSlot))
+                                               {
+                                                       /*
+                                                        * Need to emit right-join tuples for remaining
+                                                        * inner tuples.
+                                                        */
+                                                       node->mj_JoinState = EXEC_MJ_ENDOUTER;
+                                                       break;
+                                               }
+                                               /* Otherwise we're done. */
+                                               return NULL;
                                }
-
-                               /* Test the new tuple against the current inner */
-                               node->mj_JoinState = EXEC_MJ_SKIP_TEST;
                                break;
 
                                /*
+                                * SKIPINNER_ADVANCE: advance over an inner tuple that is
+                                * known not to join to any outer tuple.
+                                *
                                 * Before advancing, we check to see if we must emit an
                                 * outer-join fill tuple for this inner tuple.
                                 */
@@ -1308,42 +1302,21 @@ ExecMergeJoin(MergeJoinState *node)
                                {
                                        /*
                                         * Generate a fake join tuple with nulls for the outer
-                                        * tuple, and return it if it passes the non-join
-                                        * quals.
+                                        * tuple, and return it if it passes the non-join quals.
                                         */
-                                       node->mj_MatchedInner = true;           /* do it only once */
-
-                                       ResetExprContext(econtext);
-
-                                       outerTupleSlot = node->mj_NullOuterTupleSlot;
-                                       econtext->ecxt_outertuple = outerTupleSlot;
-                                       innerTupleSlot = node->mj_InnerTupleSlot;
-                                       econtext->ecxt_innertuple = innerTupleSlot;
-
-                                       if (ExecQual(otherqual, econtext, false))
-                                       {
-                                               /*
-                                                * qualification succeeded.  now form the desired
-                                                * projection tuple and return the slot containing
-                                                * it.
-                                                */
-                                               TupleTableSlot *result;
-                                               ExprDoneCond isDone;
+                                       TupleTableSlot *result;
 
-                                               MJ_printf("ExecMergeJoin: returning fill tuple\n");
-
-                                               result = ExecProject(node->js.ps.ps_ProjInfo,
-                                                                                        &isDone);
+                                       node->mj_MatchedInner = true;           /* do it only once */
 
-                                               if (isDone != ExprEndResult)
-                                               {
-                                                       node->js.ps.ps_TupFromTlist =
-                                                               (isDone == ExprMultipleResult);
-                                                       return result;
-                                               }
-                                       }
+                                       result = MJFillInner(node);
+                                       if (result)
+                                               return result;
                                }
 
+                               /* Mark before advancing, if wanted */
+                               if (node->mj_ExtraMarks)
+                                       ExecMarkPos(innerPlan);
+
                                /*
                                 * now we get the next inner tuple, if any
                                 */
@@ -1352,43 +1325,43 @@ ExecMergeJoin(MergeJoinState *node)
                                MJ_DEBUG_PROC_NODE(innerTupleSlot);
                                node->mj_MatchedInner = false;
 
-                               /*
-                                * if the inner tuple is null then we are done with the
-                                * join, unless we have outer tuples we need to null-fill.
-                                */
-                               if (TupIsNull(innerTupleSlot))
+                               /* Compute join values and check for unmatchability */
+                               switch (MJEvalInnerValues(node, innerTupleSlot))
                                {
-                                       MJ_printf("ExecMergeJoin: end of inner subplan\n");
-                                       outerTupleSlot = node->mj_OuterTupleSlot;
-                                       if (doFillOuter && !TupIsNull(outerTupleSlot))
-                                       {
+                                       case MJEVAL_MATCHABLE:
+                                               /* proceed to compare it to the current outer */
+                                               node->mj_JoinState = EXEC_MJ_SKIP_TEST;
+                                               break;
+                                       case MJEVAL_NONMATCHABLE:
+
                                                /*
-                                                * Need to emit left-join tuples for remaining
-                                                * outer tuples.
+                                                * current inner can't possibly match any outer;
+                                                * better to advance the inner scan than the outer.
                                                 */
-                                               node->mj_JoinState = EXEC_MJ_ENDINNER;
+                                               node->mj_JoinState = EXEC_MJ_SKIPINNER_ADVANCE;
                                                break;
-                                       }
-                                       /* Otherwise we're done. */
-                                       return NULL;
-                               }
-
-                               /* Compute join values and check for unmatchability */
-                               if (!MJEvalInnerValues(node, innerTupleSlot))
-                               {
-                                       /* Stay in same state to fetch next inner tuple */
-                                       node->mj_JoinState = EXEC_MJ_SKIPINNER_ADVANCE;
-                                       break;
+                                       case MJEVAL_ENDOFJOIN:
+                                               /* No more inner tuples */
+                                               MJ_printf("ExecMergeJoin: end of inner subplan\n");
+                                               outerTupleSlot = node->mj_OuterTupleSlot;
+                                               if (doFillOuter && !TupIsNull(outerTupleSlot))
+                                               {
+                                                       /*
+                                                        * Need to emit left-join tuples for remaining
+                                                        * outer tuples.
+                                                        */
+                                                       node->mj_JoinState = EXEC_MJ_ENDINNER;
+                                                       break;
+                                               }
+                                               /* Otherwise we're done. */
+                                               return NULL;
                                }
-
-                               /* Test the new tuple against the current outer */
-                               node->mj_JoinState = EXEC_MJ_SKIP_TEST;
                                break;
 
                                /*
-                                * EXEC_MJ_ENDOUTER means we have run out of outer tuples,
-                                * but are doing a right/full join and therefore must
-                                * null-fill any remaing unmatched inner tuples.
+                                * EXEC_MJ_ENDOUTER means we have run out of outer tuples, but
+                                * are doing a right/full join and therefore must null-fill
+                                * any remaing unmatched inner tuples.
                                 */
                        case EXEC_MJ_ENDOUTER:
                                MJ_printf("ExecMergeJoin: EXEC_MJ_ENDOUTER\n");
@@ -1399,42 +1372,21 @@ ExecMergeJoin(MergeJoinState *node)
                                {
                                        /*
                                         * Generate a fake join tuple with nulls for the outer
-                                        * tuple, and return it if it passes the non-join
-                                        * quals.
+                                        * tuple, and return it if it passes the non-join quals.
                                         */
-                                       node->mj_MatchedInner = true;           /* do it only once */
-
-                                       ResetExprContext(econtext);
-
-                                       outerTupleSlot = node->mj_NullOuterTupleSlot;
-                                       econtext->ecxt_outertuple = outerTupleSlot;
-                                       innerTupleSlot = node->mj_InnerTupleSlot;
-                                       econtext->ecxt_innertuple = innerTupleSlot;
+                                       TupleTableSlot *result;
 
-                                       if (ExecQual(otherqual, econtext, false))
-                                       {
-                                               /*
-                                                * qualification succeeded.  now form the desired
-                                                * projection tuple and return the slot containing
-                                                * it.
-                                                */
-                                               TupleTableSlot *result;
-                                               ExprDoneCond isDone;
-
-                                               MJ_printf("ExecMergeJoin: returning fill tuple\n");
-
-                                               result = ExecProject(node->js.ps.ps_ProjInfo,
-                                                                                        &isDone);
+                                       node->mj_MatchedInner = true;           /* do it only once */
 
-                                               if (isDone != ExprEndResult)
-                                               {
-                                                       node->js.ps.ps_TupFromTlist =
-                                                               (isDone == ExprMultipleResult);
-                                                       return result;
-                                               }
-                                       }
+                                       result = MJFillInner(node);
+                                       if (result)
+                                               return result;
                                }
 
+                               /* Mark before advancing, if wanted */
+                               if (node->mj_ExtraMarks)
+                                       ExecMarkPos(innerPlan);
+
                                /*
                                 * now we get the next inner tuple, if any
                                 */
@@ -1453,9 +1405,9 @@ ExecMergeJoin(MergeJoinState *node)
                                break;
 
                                /*
-                                * EXEC_MJ_ENDINNER means we have run out of inner tuples,
-                                * but are doing a left/full join and therefore must null-
-                                * fill any remaing unmatched outer tuples.
+                                * EXEC_MJ_ENDINNER means we have run out of inner tuples, but
+                                * are doing a left/full join and therefore must null- fill
+                                * any remaing unmatched outer tuples.
                                 */
                        case EXEC_MJ_ENDINNER:
                                MJ_printf("ExecMergeJoin: EXEC_MJ_ENDINNER\n");
@@ -1466,40 +1418,15 @@ ExecMergeJoin(MergeJoinState *node)
                                {
                                        /*
                                         * Generate a fake join tuple with nulls for the inner
-                                        * tuple, and return it if it passes the non-join
-                                        * quals.
+                                        * tuple, and return it if it passes the non-join quals.
                                         */
-                                       node->mj_MatchedOuter = true;           /* do it only once */
-
-                                       ResetExprContext(econtext);
+                                       TupleTableSlot *result;
 
-                                       outerTupleSlot = node->mj_OuterTupleSlot;
-                                       econtext->ecxt_outertuple = outerTupleSlot;
-                                       innerTupleSlot = node->mj_NullInnerTupleSlot;
-                                       econtext->ecxt_innertuple = innerTupleSlot;
-
-                                       if (ExecQual(otherqual, econtext, false))
-                                       {
-                                               /*
-                                                * qualification succeeded.  now form the desired
-                                                * projection tuple and return the slot containing
-                                                * it.
-                                                */
-                                               TupleTableSlot *result;
-                                               ExprDoneCond isDone;
-
-                                               MJ_printf("ExecMergeJoin: returning fill tuple\n");
-
-                                               result = ExecProject(node->js.ps.ps_ProjInfo,
-                                                                                        &isDone);
+                                       node->mj_MatchedOuter = true;           /* do it only once */
 
-                                               if (isDone != ExprEndResult)
-                                               {
-                                                       node->js.ps.ps_TupFromTlist =
-                                                               (isDone == ExprMultipleResult);
-                                                       return result;
-                                               }
-                                       }
+                                       result = MJFillOuter(node);
+                                       if (result)
+                                               return result;
                                }
 
                                /*
@@ -1534,10 +1461,13 @@ ExecMergeJoin(MergeJoinState *node)
  * ----------------------------------------------------------------
  */
 MergeJoinState *
-ExecInitMergeJoin(MergeJoin *node, EState *estate)
+ExecInitMergeJoin(MergeJoin *node, EState *estate, int eflags)
 {
        MergeJoinState *mergestate;
 
+       /* check for unsupported flags */
+       Assert(!(eflags & (EXEC_FLAG_BACKWARD | EXEC_FLAG_MARK)));
+
        MJ1_printf("ExecInitMergeJoin: %s\n",
                           "initializing node");
 
@@ -1556,10 +1486,9 @@ ExecInitMergeJoin(MergeJoin *node, EState *estate)
        ExecAssignExprContext(estate, &mergestate->js.ps);
 
        /*
-        * we need two additional econtexts in which we can compute the
-        * join expressions from the left and right input tuples.  The
-        * node's regular econtext won't do because it gets reset too
-        * often.
+        * we need two additional econtexts in which we can compute the join
+        * expressions from the left and right input tuples.  The node's regular
+        * econtext won't do because it gets reset too often.
         */
        mergestate->mj_OuterEContext = CreateExprContext(estate);
        mergestate->mj_InnerEContext = CreateExprContext(estate);
@@ -1577,15 +1506,33 @@ ExecInitMergeJoin(MergeJoin *node, EState *estate)
        mergestate->js.joinqual = (List *)
                ExecInitExpr((Expr *) node->join.joinqual,
                                         (PlanState *) mergestate);
+       mergestate->mj_ConstFalseJoin = false;
        /* mergeclauses are handled below */
 
        /*
         * initialize child nodes
+        *
+        * inner child must support MARK/RESTORE.
         */
-       outerPlanState(mergestate) = ExecInitNode(outerPlan(node), estate);
-       innerPlanState(mergestate) = ExecInitNode(innerPlan(node), estate);
+       outerPlanState(mergestate) = ExecInitNode(outerPlan(node), estate, eflags);
+       innerPlanState(mergestate) = ExecInitNode(innerPlan(node), estate,
+                                                                                         eflags | EXEC_FLAG_MARK);
 
-#define MERGEJOIN_NSLOTS 4
+       /*
+        * For certain types of inner child nodes, it is advantageous to issue
+        * MARK every time we advance past an inner tuple we will never return to.
+        * For other types, MARK on a tuple we cannot return to is a waste of
+        * cycles.      Detect which case applies and set mj_ExtraMarks if we want to
+        * issue "unnecessary" MARK calls.
+        *
+        * Currently, only Material wants the extra MARKs, and it will be helpful
+        * only if eflags doesn't specify REWIND.
+        */
+       if (IsA(innerPlan(node), Material) &&
+               (eflags & EXEC_FLAG_REWIND) == 0)
+               mergestate->mj_ExtraMarks = true;
+       else
+               mergestate->mj_ExtraMarks = false;
 
        /*
         * tuple table initialization
@@ -1594,46 +1541,56 @@ ExecInitMergeJoin(MergeJoin *node, EState *estate)
 
        mergestate->mj_MarkedTupleSlot = ExecInitExtraTupleSlot(estate);
        ExecSetSlotDescriptor(mergestate->mj_MarkedTupleSlot,
-                                                 ExecGetResultType(innerPlanState(mergestate)),
-                                                 false);
+                                                 ExecGetResultType(innerPlanState(mergestate)));
 
        switch (node->join.jointype)
        {
                case JOIN_INNER:
-               case JOIN_IN:
+               case JOIN_SEMI:
+                       mergestate->mj_FillOuter = false;
+                       mergestate->mj_FillInner = false;
                        break;
                case JOIN_LEFT:
+               case JOIN_ANTI:
+                       mergestate->mj_FillOuter = true;
+                       mergestate->mj_FillInner = false;
                        mergestate->mj_NullInnerTupleSlot =
                                ExecInitNullTupleSlot(estate,
-                                                 ExecGetResultType(innerPlanState(mergestate)));
+                                                         ExecGetResultType(innerPlanState(mergestate)));
                        break;
                case JOIN_RIGHT:
+                       mergestate->mj_FillOuter = false;
+                       mergestate->mj_FillInner = true;
                        mergestate->mj_NullOuterTupleSlot =
                                ExecInitNullTupleSlot(estate,
-                                                 ExecGetResultType(outerPlanState(mergestate)));
+                                                         ExecGetResultType(outerPlanState(mergestate)));
 
                        /*
-                        * Can't handle right or full join with non-nil extra
+                        * Can't handle right or full join with non-constant extra
                         * joinclauses.  This should have been caught by planner.
                         */
-                       if (node->join.joinqual != NIL)
+                       if (!check_constant_qual(node->join.joinqual,
+                                                                        &mergestate->mj_ConstFalseJoin))
                                ereport(ERROR,
                                                (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
                                                 errmsg("RIGHT JOIN is only supported with merge-joinable join conditions")));
                        break;
                case JOIN_FULL:
+                       mergestate->mj_FillOuter = true;
+                       mergestate->mj_FillInner = true;
                        mergestate->mj_NullOuterTupleSlot =
                                ExecInitNullTupleSlot(estate,
-                                                 ExecGetResultType(outerPlanState(mergestate)));
+                                                         ExecGetResultType(outerPlanState(mergestate)));
                        mergestate->mj_NullInnerTupleSlot =
                                ExecInitNullTupleSlot(estate,
-                                                 ExecGetResultType(innerPlanState(mergestate)));
+                                                         ExecGetResultType(innerPlanState(mergestate)));
 
                        /*
-                        * Can't handle right or full join with non-nil extra
-                        * joinclauses.
+                        * Can't handle right or full join with non-constant extra
+                        * joinclauses.  This should have been caught by planner.
                         */
-                       if (node->join.joinqual != NIL)
+                       if (!check_constant_qual(node->join.joinqual,
+                                                                        &mergestate->mj_ConstFalseJoin))
                                ereport(ERROR,
                                                (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
                                                 errmsg("FULL JOIN is only supported with merge-joinable join conditions")));
@@ -1647,13 +1604,17 @@ ExecInitMergeJoin(MergeJoin *node, EState *estate)
         * initialize tuple type and projection info
         */
        ExecAssignResultTypeFromTL(&mergestate->js.ps);
-       ExecAssignProjectionInfo(&mergestate->js.ps);
+       ExecAssignProjectionInfo(&mergestate->js.ps, NULL);
 
        /*
         * preprocess the merge clauses
         */
        mergestate->mj_NumClauses = list_length(node->mergeclauses);
        mergestate->mj_Clauses = MJExamineQuals(node->mergeclauses,
+                                                                                       node->mergeFamilies,
+                                                                                       node->mergeCollations,
+                                                                                       node->mergeStrategies,
+                                                                                       node->mergeNullsFirst,
                                                                                        (PlanState *) mergestate);
 
        /*
@@ -1675,14 +1636,6 @@ ExecInitMergeJoin(MergeJoin *node, EState *estate)
        return mergestate;
 }
 
-int
-ExecCountSlotsMergeJoin(MergeJoin *node)
-{
-       return ExecCountSlotsNode(outerPlan((Plan *) node)) +
-               ExecCountSlotsNode(innerPlan((Plan *) node)) +
-               MERGEJOIN_NSLOTS;
-}
-
 /* ----------------------------------------------------------------
  *             ExecEndMergeJoin
  *
@@ -1718,7 +1671,7 @@ ExecEndMergeJoin(MergeJoinState *node)
 }
 
 void
-ExecReScanMergeJoin(MergeJoinState *node, ExprContext *exprCtxt)
+ExecReScanMergeJoin(MergeJoinState *node)
 {
        ExecClearTuple(node->mj_MarkedTupleSlot);
 
@@ -1730,12 +1683,12 @@ ExecReScanMergeJoin(MergeJoinState *node, ExprContext *exprCtxt)
        node->mj_InnerTupleSlot = NULL;
 
        /*
-        * if chgParam of subnodes is not null then plans will be re-scanned
-        * by first ExecProcNode.
+        * if chgParam of subnodes is not null then plans will be re-scanned by
+        * first ExecProcNode.
         */
-       if (((PlanState *) node)->lefttree->chgParam == NULL)
-               ExecReScan(((PlanState *) node)->lefttree, exprCtxt);
-       if (((PlanState *) node)->righttree->chgParam == NULL)
-               ExecReScan(((PlanState *) node)->righttree, exprCtxt);
+       if (node->js.ps.lefttree->chgParam == NULL)
+               ExecReScan(node->js.ps.lefttree);
+       if (node->js.ps.righttree->chgParam == NULL)
+               ExecReScan(node->js.ps.righttree);
 
 }