]> granicus.if.org Git - postgresql/commitdiff
Revise nodeMergejoin in light of example provided by Guillaume Smet.
authorTom Lane <tgl@sss.pgh.pa.us>
Fri, 13 May 2005 21:20:16 +0000 (21:20 +0000)
committerTom Lane <tgl@sss.pgh.pa.us>
Fri, 13 May 2005 21:20:16 +0000 (21:20 +0000)
When one side of the join has a NULL, we don't want to uselessly try
to match it against every remaining tuple of the other side.  While
at it, rewrite the comparison machinery to avoid multiple evaluations
of the left and right input expressions and to use a btree comparator
where available, instead of double operator calls.  Also revise the
state machine to eliminate redundant comparisons and hopefully make it
more readable too.

src/backend/executor/nodeMergejoin.c
src/include/executor/execdebug.h
src/include/executor/execdefs.h
src/include/nodes/execnodes.h

index fb1cbc3d59c8523fff277fc5638d3d25dfab2e49..be4a97574e5faaf1dc9b1c62e15b984fa22df7fe 100644 (file)
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *       $PostgreSQL: pgsql/src/backend/executor/nodeMergejoin.c,v 1.71 2005/05/06 17:24:54 tgl Exp $
+ *       $PostgreSQL: pgsql/src/backend/executor/nodeMergejoin.c,v 1.72 2005/05/13 21:20:16 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
  *             ExecEndMergeJoin                cleans up the node.
  *
  * NOTES
- *             Essential operation of the merge join algorithm is as follows:
  *
- *             Join {                                                                                             -
- *                     get initial outer and inner tuples                              INITIALIZE
- *                     Skip Inner                                                                              SKIPINNER
- *                     mark inner position                                                             JOINMARK
- *                     do forever {                                                                       -
- *                             while (outer == inner) {                                        JOINTEST
- *                                     join tuples                                                             JOINTUPLES
- *                                     advance inner position                                  NEXTINNER
- *                             }                                                                                          -
- *                             advance outer position                                          NEXTOUTER
- *                             if (outer == mark) {                                            TESTOUTER
- *                                     restore inner position to mark                  TESTOUTER
- *                                     continue                                                                   -
- *                             } else {                                                                           -
- *                                     Skip Outer                                                              SKIPOUTER
- *                                     mark inner position                                             JOINMARK
- *                             }                                                                                          -
- *                     }                                                                                                  -
- *             }                                                                                                          -
+ *             Merge-join is done by joining the inner and outer tuples satisfying
+ *             join clauses of the form ((= outerKey innerKey) ...).
+ *             The join clause list is provided by the query planner and may contain
+ *             more than one (= outerKey innerKey) clause (for composite sort key).
+ *
+ *             However, the query executor needs to know whether an outer
+ *             tuple is "greater/smaller" than an inner tuple so that it can
+ *             "synchronize" the two relations. For example, consider the following
+ *             relations:
+ *
+ *                             outer: (0 ^1 1 2 5 5 5 6 6 7)   current tuple: 1
+ *                             inner: (1 ^3 5 5 5 5 6)                 current tuple: 3
+ *
+ *             To continue the merge-join, the executor needs to scan both inner
+ *             and outer relations till the matching tuples 5. It needs to know
+ *             that currently inner tuple 3 is "greater" than outer tuple 1 and
+ *             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).
  *
- *             Skip Outer {                                                                            SKIPOUTER_BEGIN
- *                     if (inner == outer) Join Tuples                                 JOINTUPLES
- *                     while (outer < inner)                                                   SKIPOUTER_TEST
- *                             advance outer                                                           SKIPOUTER_ADVANCE
- *                     if (outer > inner)                                                              SKIPOUTER_TEST
- *                             Skip Inner                                                                      SKIPINNER
- *             }                                                                                                          -
  *
- *             Skip Inner {                                                                            SKIPINNER_BEGIN
- *                     if (inner == outer) Join Tuples                                 JOINTUPLES
- *                     while (outer > inner)                                                   SKIPINNER_TEST
- *                             advance inner                                                           SKIPINNER_ADVANCE
- *                     if (outer < inner)                                                              SKIPINNER_TEST
- *                             Skip Outer                                                                      SKIPOUTER
- *             }                                                                                                          -
+ *             Consider the above relations and suppose that the executor has
+ *             just joined the first outer "5" with the last inner "5". The
+ *             next step is of course to join the second outer "5" with all
+ *             the inner "5's". This requires repositioning the inner "cursor"
+ *             to point at the first inner "5". This is done by "marking" the
+ *             first inner 5 so we can restore the "cursor" to it before joining
+ *             with the second outer 5. The access method interface provides
+ *             routines to mark and restore to a tuple.
+ *
+ *
+ *             Essential operation of the merge join algorithm is as follows:
+ *
+ *             Join {
+ *                     get initial outer and inner tuples                              INITIALIZE
+ *                     do forever {
+ *                             while (outer != inner) {                                        SKIP_TEST
+ *                                     if (outer < inner)
+ *                                             advance outer                                           SKIPOUTER_ADVANCE
+ *                                     else
+ *                                             advance inner                                           SKIPINNER_ADVANCE
+ *                             }
+ *                             mark inner position                                                     SKIP_TEST
+ *                             do forever {
+ *                                     while (outer == inner) {
+ *                                             join tuples                                                     JOINTUPLES
+ *                                             advance inner position                          NEXTINNER
+ *                                     }
+ *                                     advance outer position                                  NEXTOUTER
+ *                                     if (outer == mark)                                              TESTOUTER
+ *                                             restore inner position to mark          TESTOUTER
+ *                                     else
+ *                                             break   // return to top of outer loop
+ *                             }
+ *                     }
+ *             }
  *
  *             The merge join operation is coded in the fashion
  *             of a state machine.  At each state, we do something and then
  *             proceed to another state.  This state is stored in the node's
  *             execution state information and is preserved across calls to
  *             ExecMergeJoin. -cim 10/31/89
- *
  */
 #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"
 
 
-static bool MergeCompare(List *eqQual, List *compareQual, ExprContext *econtext);
+/*
+ * 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.
+ */
+typedef enum
+{
+       MERGEFUNC_LT,                           /* raw "<" operator */
+       MERGEFUNC_CMP                           /* -1 / 0 / 1 three-way comparator */
+} MergeFunctionKind;
+
+/* 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 */
+       /*
+        * 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;
+       /*
+        * 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.
+        */
+       MergeFunctionKind cmpstrategy;
+       FmgrInfo                eqfinfo;
+       FmgrInfo                cmpfinfo;
+} MergeJoinClauseData;
+
 
 #define MarkInnerTuple(innerTupleSlot, mergestate) \
-( \
-       ExecCopySlot((mergestate)->mj_MarkedTupleSlot, \
-                                (innerTupleSlot)) \
-)
+       ExecCopySlot((mergestate)->mj_MarkedTupleSlot, (innerTupleSlot))
 
 
-/* ----------------------------------------------------------------
- *             MJFormSkipQuals
+/*
+ * MJExamineQuals
  *
- *             This takes the mergeclause which is a qualification of the
- *             form ((= expr expr) (= expr expr) ...) and forms new lists
- *             of the forms ((< expr expr) (< expr expr) ...) and
- *             ((> expr expr) (> expr expr) ...).      These lists will be used
- *             by ExecMergeJoin() to determine if we should skip tuples.
- *             (We expect there to be suitable operators because the "=" operators
- *             were marked mergejoinable; however, there might be a different
- *             one needed in each qual clause.)
- * ----------------------------------------------------------------
+ * This deconstructs the list of mergejoinable expressions, which is given
+ * to us by the planner in the form of a list of "leftexpr = rightexpr"
+ * expression trees in the order matching the sort columns of the inputs.
+ * We build an array of MergeJoinClause structs containing the information
+ * 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.
  */
-static void
-MJFormSkipQuals(List *qualList, List **ltQuals, List **gtQuals,
-                               PlanState *parent)
+static MergeJoinClause
+MJExamineQuals(List *qualList, PlanState *parent)
 {
-       List       *ltexprs,
-                          *gtexprs;
-       ListCell   *ltcdr,
-                          *gtcdr;
+       MergeJoinClause clauses;
+       int                     nClauses = list_length(qualList);
+       int                     iClause;
+       ListCell   *l;
 
-       /*
-        * Make modifiable copies of the qualList.
-        */
-       ltexprs = (List *) copyObject((Node *) qualList);
-       gtexprs = (List *) copyObject((Node *) qualList);
+       clauses = (MergeJoinClause) palloc0(nClauses * sizeof(MergeJoinClauseData));
 
-       /*
-        * Scan both lists in parallel, so that we can update the operators
-        * with the minimum number of syscache searches.
-        */
-       forboth(ltcdr, ltexprs, gtcdr, gtexprs)
+       iClause = 0;
+       foreach(l, qualList)
        {
-               OpExpr     *ltop = (OpExpr *) lfirst(ltcdr);
-               OpExpr     *gtop = (OpExpr *) lfirst(gtcdr);
+               OpExpr     *qual = (OpExpr *) lfirst(l);
+               MergeJoinClause clause = &clauses[iClause];
+               Oid                     ltop;
+               Oid                     gtop;
+               RegProcedure ltproc;
+               RegProcedure gtproc;
+               AclResult       aclresult;
+               CatCList   *catlist;
+               int                     i;
+
+               if (!IsA(qual, OpExpr))
+                       elog(ERROR, "mergejoin clause is not an OpExpr");
 
                /*
-                * The two ops should be identical, so use either one for lookup.
+                * Prepare the input expressions for execution.
                 */
-               if (!IsA(ltop, OpExpr))
-                       elog(ERROR, "mergejoin clause is not an OpExpr");
+               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 operators, and replace the data in the copied
-                * operator nodes.
+                * 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(ltop->opno,
-                                                         &ltop->opno,
-                                                         &gtop->opno,
-                                                         &ltop->opfuncid,
-                                                         &gtop->opfuncid);
+               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++)
+               {
+                       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 */
+                       }
+               }
+
+               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++;
        }
 
-       /*
-        * Prepare both lists for execution.
-        */
-       *ltQuals = (List *) ExecInitExpr((Expr *) ltexprs, parent);
-       *gtQuals = (List *) ExecInitExpr((Expr *) gtexprs, parent);
+       return clauses;
 }
 
-/* ----------------------------------------------------------------
- *             MergeCompare
+/*
+ * MJEvalOuterValues
  *
- *             Compare the keys according to 'compareQual' which is of the
- *             form: { (key1a > key2a) (key1b > key2b) ... }.
+ * 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.
  *
- *             (actually, it could also be of the form (key1a < key2a)...)
+ * We evaluate the values in OuterEContext, which can be reset each
+ * time we move to a new tuple.
+ */
+static bool
+MJEvalOuterValues(MergeJoinState *mergestate)
+{
+       ExprContext *econtext = mergestate->mj_OuterEContext;
+       bool            canmatch = true;
+       int                     i;
+       MemoryContext oldContext;
+
+       ResetExprContext(econtext);
+
+       oldContext = MemoryContextSwitchTo(econtext->ecxt_per_tuple_memory);
+
+       econtext->ecxt_outertuple = mergestate->mj_OuterTupleSlot;
+
+       for (i = 0; i < mergestate->mj_NumClauses; i++)
+       {
+               MergeJoinClause clause = &mergestate->mj_Clauses[i];
+
+               clause->ldatum = ExecEvalExpr(clause->lexpr, econtext,
+                                                                         &clause->lisnull, NULL);
+               if (clause->lisnull && clause->mergestrict)
+                       canmatch = false;
+       }
+
+       MemoryContextSwitchTo(oldContext);
+
+       return canmatch;
+}
+
+/*
+ * MJEvalInnerValues
  *
- *             This is different from calling ExecQual because ExecQual returns
- *             true only if ALL the comparison clauses are satisfied.
- *             However, there is an order of significance among the keys with
- *             the first keys being most significant. Therefore, the clauses
- *             are evaluated in order and the 'compareQual' is satisfied
- *             if (key1i > key2i) is true and (key1j = key2j) for 0 < j < i.
- *             We use the original mergeclause items to detect equality.
- * ----------------------------------------------------------------
+ * 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
-MergeCompare(List *eqQual, List *compareQual, ExprContext *econtext)
+MJEvalInnerValues(MergeJoinState *mergestate, TupleTableSlot *innerslot)
 {
-       bool            result;
+       ExprContext *econtext = mergestate->mj_InnerEContext;
+       bool            canmatch = true;
+       int                     i;
        MemoryContext oldContext;
-       ListCell   *clause;
-       ListCell   *eqclause;
 
-       /*
-        * Do expression eval in short-lived context.
-        */
+       ResetExprContext(econtext);
+
        oldContext = MemoryContextSwitchTo(econtext->ecxt_per_tuple_memory);
 
-       /*
-        * for each pair of clauses, test them until our compare conditions
-        * are satisfied. if we reach the end of the list, none of our key
-        * greater-than conditions were satisfied so we return false.
-        */
-       result = false;                         /* assume 'false' result */
+       econtext->ecxt_innertuple = innerslot;
+
+       for (i = 0; i < mergestate->mj_NumClauses; i++)
+       {
+               MergeJoinClause clause = &mergestate->mj_Clauses[i];
+
+               clause->rdatum = ExecEvalExpr(clause->rexpr, econtext,
+                                                                         &clause->risnull, NULL);
+               if (clause->risnull && clause->mergestrict)
+                       canmatch = false;
+       }
+
+       MemoryContextSwitchTo(oldContext);
+
+       return canmatch;
+}
+
+/*
+ * MJCompare
+ *
+ * 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.
+ *
+ * MJEvalOuterValues and MJEvalInnerValues must already have been called
+ * for the current outer and inner tuples, respectively.
+ */
+static int
+MJCompare(MergeJoinState *mergestate)
+{
+       int                     result = 0;
+       bool            nulleqnull = false;
+       ExprContext *econtext = mergestate->js.ps.ps_ExprContext;
+       int                     i;
+       MemoryContext oldContext;
+       FunctionCallInfoData fcinfo;
 
        /*
-        * We can't run out of one list before the other
+        * Call the comparison functions in short-lived context, in case they
+        * leak memory.
         */
-       Assert(list_length(compareQual) == list_length(eqQual));
+       ResetExprContext(econtext);
 
-       forboth(clause, compareQual, eqclause, eqQual)
+       oldContext = MemoryContextSwitchTo(econtext->ecxt_per_tuple_memory);
+
+       for (i = 0; i < mergestate->mj_NumClauses; i++)
        {
-               ExprState  *clauseexpr = (ExprState *) lfirst(clause);
-               ExprState  *eqclauseexpr = (ExprState *) lfirst(eqclause);
-               Datum           const_value;
-               bool            isNull;
+               MergeJoinClause clause = &mergestate->mj_Clauses[i];
+               Datum           fresult;
 
                /*
-                * first test if our compare clause is satisfied. if so then
-                * return true.
+                * Deal with null inputs.  We treat NULL as sorting after non-NULL.
                 *
-                * A NULL result is considered false.
+                * 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.
                 */
-               const_value = ExecEvalExpr(clauseexpr, econtext, &isNull, NULL);
-
-               if (DatumGetBool(const_value) && !isNull)
+               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)
                {
-                       result = true;
+                       /* non-NULL < NULL */
+                       result = -1;
                        break;
                }
 
-               /*-----------
-                * ok, the compare clause failed so we test if the keys are
-                * equal... if key1 != key2, we return false. otherwise
-                * key1 = key2 so we move on to the next pair of keys.
-                *-----------
-                */
-               const_value = ExecEvalExpr(eqclauseexpr, econtext, &isNull, NULL);
-
-               if (!DatumGetBool(const_value) || isNull)
-                       break;                          /* return false */
+               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;
+                       }
+               }
        }
 
+       /*
+        * 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 (nulleqnull && result == 0)
+               result = 1;
+
        MemoryContextSwitchTo(oldContext);
 
        return result;
@@ -287,68 +585,16 @@ ExecMergeTupleDump(MergeJoinState *mergestate)
 
 /* ----------------------------------------------------------------
  *             ExecMergeJoin
- *
- * old comments
- *             Details of the merge-join routines:
- *
- *             (1) ">" and "<" operators
- *
- *             Merge-join is done by joining the inner and outer tuples satisfying
- *             the join clauses of the form ((= outerKey innerKey) ...).
- *             The join clauses is provided by the query planner and may contain
- *             more than one (= outerKey innerKey) clauses (for composite key).
- *
- *             However, the query executor needs to know whether an outer
- *             tuple is "greater/smaller" than an inner tuple so that it can
- *             "synchronize" the two relations. For e.g., consider the following
- *             relations:
- *
- *                             outer: (0 ^1 1 2 5 5 5 6 6 7)   current tuple: 1
- *                             inner: (1 ^3 5 5 5 5 6)                 current tuple: 3
- *
- *             To continue the merge-join, the executor needs to scan both inner
- *             and outer relations till the matching tuples 5. It needs to know
- *             that currently inner tuple 3 is "greater" than outer tuple 1 and
- *             therefore it should scan the outer relation first to find a
- *             matching tuple and so on.
- *
- *             Therefore, when initializing the merge-join node, the executor
- *             creates the "greater/smaller" clause by substituting the "="
- *             operator in the join clauses with the corresponding ">" operator.
- *             The opposite "smaller/greater" clause is formed by substituting "<".
- *
- *             Note: prior to v6.5, the relational clauses were formed using the
- *             sort op used to sort the inner relation, which of course would fail
- *             if the outer and inner keys were of different data types.
- *             In the current code, we instead assume that operators named "<" and ">"
- *             will do the right thing.  This should be true since the mergejoin "="
- *             operator's pg_operator entry will have told the planner to sort by
- *             "<" for each of the left and right sides.
- *
- *             (2) repositioning inner "cursor"
- *
- *             Consider the above relations and suppose that the executor has
- *             just joined the first outer "5" with the last inner "5". The
- *             next step is of course to join the second outer "5" with all
- *             the inner "5's". This requires repositioning the inner "cursor"
- *             to point at the first inner "5". This is done by "marking" the
- *             first inner 5 and restore the "cursor" to it before joining
- *             with the second outer 5. The access method interface provides
- *             routines to mark and restore to a tuple.
  * ----------------------------------------------------------------
  */
 TupleTableSlot *
 ExecMergeJoin(MergeJoinState *node)
 {
        EState     *estate;
-       ScanDirection direction;
-       List       *innerSkipQual;
-       List       *outerSkipQual;
-       List       *mergeclauses;
        List       *joinqual;
        List       *otherqual;
        bool            qualResult;
-       bool            compareResult;
+       int                     compareResult;
        PlanState  *innerPlan;
        TupleTableSlot *innerTupleSlot;
        PlanState  *outerPlan;
@@ -361,11 +607,9 @@ ExecMergeJoin(MergeJoinState *node)
         * get information from node
         */
        estate = node->js.ps.state;
-       direction = estate->es_direction;
        innerPlan = innerPlanState(node);
        outerPlan = outerPlanState(node);
        econtext = node->js.ps.ps_ExprContext;
-       mergeclauses = node->mergeclauses;
        joinqual = node->js.joinqual;
        otherqual = node->js.ps.qual;
 
@@ -396,17 +640,6 @@ ExecMergeJoin(MergeJoinState *node)
                        break;
        }
 
-       if (ScanDirectionIsForward(direction))
-       {
-               outerSkipQual = node->mj_OuterSkipQual;
-               innerSkipQual = node->mj_InnerSkipQual;
-       }
-       else
-       {
-               outerSkipQual = node->mj_InnerSkipQual;
-               innerSkipQual = node->mj_OuterSkipQual;
-       }
-
        /*
         * Check to see if we're still projecting out tuples from a previous
         * join tuple (because there is a function-returning-set in the
@@ -436,31 +669,28 @@ ExecMergeJoin(MergeJoinState *node)
         */
        for (;;)
        {
+               MJ_dump(node);
+
                /*
                 * get the current state of the join and do things accordingly.
-                * Note: The join states are highlighted with 32-* comments for
-                * improved readability.
                 */
-               MJ_dump(node);
-
                switch (node->mj_JoinState)
                {
                                /*
-                                * EXEC_MJ_INITIALIZE means that this is the first time
+                                * EXEC_MJ_INITIALIZE_OUTER means that this is the first time
                                 * ExecMergeJoin() has been called and so we have to fetch
-                                * the first tuple for both outer and inner subplans. If
-                                * we fail to get a tuple here, then that subplan is
-                                * empty, and we either end the join or go to one of the
-                                * fill-remaining-tuples states.
+                                * 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:
-                               MJ_printf("ExecMergeJoin: EXEC_MJ_INITIALIZE\n");
+                       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: outer subplan is empty\n");
+                                       MJ_printf("ExecMergeJoin: nothing in outer subplan\n");
                                        if (doFillInner)
                                        {
                                                /*
@@ -476,18 +706,34 @@ ExecMergeJoin(MergeJoinState *node)
                                        return NULL;
                                }
 
+                               /* Compute join values and check for unmatchability */
+                               if (!MJEvalOuterValues(node) && !doFillOuter)
+                               {
+                                       /* 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;
+                               }
+                               break;
+
+                       case EXEC_MJ_INITIALIZE_INNER:
+                               MJ_printf("ExecMergeJoin: EXEC_MJ_INITIALIZE_INNER\n");
+
                                innerTupleSlot = ExecProcNode(innerPlan);
                                node->mj_InnerTupleSlot = innerTupleSlot;
                                if (TupIsNull(innerTupleSlot))
                                {
-                                       MJ_printf("ExecMergeJoin: inner subplan is empty\n");
+                                       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 this tuple before advancing
+                                                * state to emit first tuple before advancing
                                                 * outer.
                                                 */
                                                node->mj_JoinState = EXEC_MJ_ENDINNER;
@@ -498,65 +744,35 @@ ExecMergeJoin(MergeJoinState *node)
                                        return NULL;
                                }
 
-                               /*
-                                * OK, we have the initial tuples.      Begin by skipping
-                                * unmatched inner tuples.
-                                */
-                               node->mj_JoinState = EXEC_MJ_SKIPINNER_BEGIN;
-                               break;
-
-                               /*
-                                * EXEC_MJ_JOINMARK means we have just found a new outer
-                                * tuple and a possible matching inner tuple. This is the
-                                * case after the INITIALIZE, SKIPOUTER or SKIPINNER
-                                * states.
-                                */
-                       case EXEC_MJ_JOINMARK:
-                               MJ_printf("ExecMergeJoin: EXEC_MJ_JOINMARK\n");
-
-                               ExecMarkPos(innerPlan);
-
-                               MarkInnerTuple(node->mj_InnerTupleSlot, node);
-
-                               node->mj_JoinState = EXEC_MJ_JOINTEST;
-                               break;
-
-                               /*
-                                * EXEC_MJ_JOINTEST means we have two tuples which might
-                                * satisfy the merge clause, so we test them.
-                                *
-                                * If they do satisfy, then we join them and move on to the
-                                * next inner tuple (EXEC_MJ_JOINTUPLES).
-                                *
-                                * If they do not satisfy then advance to next outer tuple.
-                                */
-                       case EXEC_MJ_JOINTEST:
-                               MJ_printf("ExecMergeJoin: EXEC_MJ_JOINTEST\n");
-
-                               ResetExprContext(econtext);
-
-                               outerTupleSlot = node->mj_OuterTupleSlot;
-                               econtext->ecxt_outertuple = outerTupleSlot;
-                               innerTupleSlot = node->mj_InnerTupleSlot;
-                               econtext->ecxt_innertuple = innerTupleSlot;
-
-                               qualResult = ExecQual(mergeclauses, econtext, false);
-                               MJ_DEBUG_QUAL(mergeclauses, qualResult);
-
-                               if (qualResult)
-                                       node->mj_JoinState = EXEC_MJ_JOINTUPLES;
+                               /* 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
-                                       node->mj_JoinState = EXEC_MJ_NEXTOUTER;
+                               {
+                                       /*
+                                        * OK, we have the initial tuples.      Begin by skipping
+                                        * non-matching tuples.
+                                        */
+                                       node->mj_JoinState = EXEC_MJ_SKIP_TEST;
+                               }
                                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_NEXT_INNER).
+                                * proceed to get the next inner tuple (EXEC_MJ_NEXTINNER).
                                 */
                        case EXEC_MJ_JOINTUPLES:
                                MJ_printf("ExecMergeJoin: EXEC_MJ_JOINTUPLES\n");
 
+                               /*
+                                * Set the next state machine state.  The right things will
+                                * happen whether we return this join tuple or just fall
+                                * through to continue the state machine execution.
+                                */
                                node->mj_JoinState = EXEC_MJ_NEXTINNER;
 
                                /*
@@ -568,11 +784,16 @@ ExecMergeJoin(MergeJoinState *node)
                                 * (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 before checking the
-                                * merge qual.  One per tuple should be sufficient.  Also,
-                                * the econtext's tuple pointers were set up before
-                                * checking the merge qual, so we needn't do it again.
+                                * 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;
@@ -669,7 +890,12 @@ ExecMergeJoin(MergeJoinState *node)
                                }
 
                                /*
-                                * now we get the next inner tuple, if any
+                                * 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.
                                 */
                                innerTupleSlot = ExecProcNode(innerPlan);
                                node->mj_InnerTupleSlot = innerTupleSlot;
@@ -677,9 +903,32 @@ ExecMergeJoin(MergeJoinState *node)
                                node->mj_MatchedInner = false;
 
                                if (TupIsNull(innerTupleSlot))
+                               {
                                        node->mj_JoinState = EXEC_MJ_NEXTOUTER;
+                                       break;
+                               }
+
+                               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);
+
+                               if (compareResult == 0)
+                                       node->mj_JoinState = EXEC_MJ_JOINTUPLES;
                                else
-                                       node->mj_JoinState = EXEC_MJ_JOINTEST;
+                               {
+                                       Assert(compareResult < 0);
+                                       node->mj_JoinState = EXEC_MJ_NEXTOUTER;
+                               }
                                break;
 
                                /*-------------------------------------------
@@ -692,7 +941,8 @@ ExecMergeJoin(MergeJoinState *node)
                                 *                                7             7
                                 *
                                 * we know we just bumped into the
-                                * first inner tuple > current outer tuple
+                                * first inner tuple > current outer tuple (or possibly
+                                * the end of the inner stream)
                                 * so get a new outer tuple and then
                                 * proceed to test it against the marked tuple
                                 * (EXEC_MJ_TESTOUTER)
@@ -773,7 +1023,17 @@ ExecMergeJoin(MergeJoinState *node)
                                        return NULL;
                                }
 
-                               node->mj_JoinState = EXEC_MJ_TESTOUTER;
+                               /* Compute join values and check for unmatchability */
+                               if (!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;
+                               }
                                break;
 
                                /*--------------------------------------------------------
@@ -781,7 +1041,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 (EXEC_MJ_JOINTEST)
+                                * new outer tuples with the inner tuples.
                                 *
                                 * This is the case when
                                 *                                                outer inner
@@ -791,11 +1051,13 @@ ExecMergeJoin(MergeJoinState *node)
                                 *                                                      6         8  - inner tuple
                                 *                                                      7        12
                                 *
-                                *                              new outer tuple = marked tuple
+                                *                              new outer tuple == marked tuple
                                 *
-                                *              If the outer tuple fails the test, then we know we have
-                                *              to proceed to skip outer tuples until outer >= inner
-                                *              (EXEC_MJ_SKIPOUTER).
+                                * If the outer tuple fails the test, then we are done
+                                * with the marked tuples, and we have to look for a
+                                * match to the current inner tuple.  So we will
+                                * proceed to skip outer tuples until outer >= inner
+                                * (EXEC_MJ_SKIP_TEST).
                                 *
                                 *              This is the case when
                                 *
@@ -805,8 +1067,7 @@ ExecMergeJoin(MergeJoinState *node)
                                 *               new outer tuple -      6         8  - inner tuple
                                 *                                                      7        12
                                 *
-                                *
-                                *               new outer tuple > marked tuple
+                                *                              new outer tuple > marked tuple
                                 *
                                 *---------------------------------------------------------
                                 */
@@ -814,27 +1075,21 @@ ExecMergeJoin(MergeJoinState *node)
                                MJ_printf("ExecMergeJoin: EXEC_MJ_TESTOUTER\n");
 
                                /*
-                                * here we compare the outer tuple with the marked inner
+                                * here we must compare the outer tuple with the marked inner
                                 * tuple
                                 */
-                               ResetExprContext(econtext);
-
-                               outerTupleSlot = node->mj_OuterTupleSlot;
-                               econtext->ecxt_outertuple = outerTupleSlot;
                                innerTupleSlot = node->mj_MarkedTupleSlot;
-                               econtext->ecxt_innertuple = innerTupleSlot;
+                               (void) MJEvalInnerValues(node, innerTupleSlot);
 
-                               qualResult = ExecQual(mergeclauses, econtext, false);
-                               MJ_DEBUG_QUAL(mergeclauses, qualResult);
+                               compareResult = MJCompare(node);
+                               MJ_DEBUG_COMPARE(compareResult);
 
-                               if (qualResult)
+                               if (compareResult == 0)
                                {
                                        /*
                                         * the merge clause matched so now we restore the
-                                        * inner scan position to the first mark, and loop
-                                        * back to JOINTEST.  Actually, since we know the
-                                        * mergeclause matches, we can skip JOINTEST and go
-                                        * straight to JOINTUPLES.
+                                        * 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
@@ -846,25 +1101,36 @@ ExecMergeJoin(MergeJoinState *node)
                                         * the extra joinquals.
                                         */
                                        ExecRestrPos(innerPlan);
+
+                                       /*
+                                        * ExecRestrPos really should give us back a new Slot,
+                                        * but since it doesn't, use the marked slot.
+                                        */
+                                       node->mj_InnerTupleSlot = innerTupleSlot;
+                                       /* we need not do MJEvalInnerValues again */
+
                                        node->mj_JoinState = EXEC_MJ_JOINTUPLES;
                                }
                                else
                                {
                                        /* ----------------
-                                        *      if the inner tuple was nil and the new outer
-                                        *      tuple didn't match the marked outer tuple then
-                                        *      we have the case:
+                                        *      if the new outer tuple didn't match the marked inner
+                                        *      tuple then we have a case like:
                                         *
                                         *                       outer inner
                                         *                         4     4      - marked tuple
                                         * new outer - 5         4
-                                        *                         6    nil - inner tuple
+                                        *                         6     5      - inner tuple
                                         *                         7
                                         *
                                         *      which means that all subsequent outer tuples will be
-                                        *      larger than our marked inner tuples.  So we're done.
+                                        *      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.
                                         * ----------------
                                         */
+                                       Assert(compareResult > 0);
                                        innerTupleSlot = node->mj_InnerTupleSlot;
                                        if (TupIsNull(innerTupleSlot))
                                        {
@@ -881,14 +1147,17 @@ ExecMergeJoin(MergeJoinState *node)
                                                return NULL;
                                        }
 
+                                       /* reload comparison data for current inner */
+                                       (void) MJEvalInnerValues(node, innerTupleSlot);
+
                                        /* continue on to skip outer tuples */
-                                       node->mj_JoinState = EXEC_MJ_SKIPOUTER_BEGIN;
+                                       node->mj_JoinState = EXEC_MJ_SKIP_TEST;
                                }
                                break;
 
                                /*----------------------------------------------------------
-                                * EXEC_MJ_SKIPOUTER means skip over tuples in the outer plan
-                                * until we find an outer tuple >= current inner tuple.
+                                * EXEC_MJ_SKIP means compare tuples and if they do not
+                                * match, skip whichever is lesser.
                                 *
                                 * For example:
                                 *
@@ -902,83 +1171,42 @@ ExecMergeJoin(MergeJoinState *node)
                                 * we have to advance the outer scan
                                 * until we find the outer 8.
                                 *
-                                * To avoid redundant tests, we divide this into three
-                                * sub-states: BEGIN, TEST, ADVANCE.
+                                * On the other hand:
+                                *
+                                *                              outer inner
+                                *                                5             5
+                                *                                5             5
+                                * outer tuple - 12             8  - inner tuple
+                                *                               14    10
+                                *                               17    12
+                                *
+                                * we have to advance the inner scan
+                                * until we find the inner 12.
                                 *----------------------------------------------------------
                                 */
-                       case EXEC_MJ_SKIPOUTER_BEGIN:
-                               MJ_printf("ExecMergeJoin: EXEC_MJ_SKIPOUTER_BEGIN\n");
+                       case EXEC_MJ_SKIP_TEST:
+                               MJ_printf("ExecMergeJoin: EXEC_MJ_SKIP_TEST\n");
 
                                /*
                                 * before we advance, make sure the current tuples do not
                                 * satisfy the mergeclauses.  If they do, then we update
-                                * the marked tuple and go join them.
+                                * the marked tuple position and go join them.
                                 */
-                               ResetExprContext(econtext);
+                               compareResult = MJCompare(node);
+                               MJ_DEBUG_COMPARE(compareResult);
 
-                               outerTupleSlot = node->mj_OuterTupleSlot;
-                               econtext->ecxt_outertuple = outerTupleSlot;
-                               innerTupleSlot = node->mj_InnerTupleSlot;
-                               econtext->ecxt_innertuple = innerTupleSlot;
-
-                               qualResult = ExecQual(mergeclauses, econtext, false);
-                               MJ_DEBUG_QUAL(mergeclauses, qualResult);
-
-                               if (qualResult)
+                               if (compareResult == 0)
                                {
                                        ExecMarkPos(innerPlan);
 
-                                       MarkInnerTuple(innerTupleSlot, node);
+                                       MarkInnerTuple(node->mj_InnerTupleSlot, node);
 
                                        node->mj_JoinState = EXEC_MJ_JOINTUPLES;
-                                       break;
                                }
-
-                               node->mj_JoinState = EXEC_MJ_SKIPOUTER_TEST;
-                               break;
-
-                       case EXEC_MJ_SKIPOUTER_TEST:
-                               MJ_printf("ExecMergeJoin: EXEC_MJ_SKIPOUTER_TEST\n");
-
-                               /*
-                                * ok, now test the skip qualification
-                                */
-                               outerTupleSlot = node->mj_OuterTupleSlot;
-                               econtext->ecxt_outertuple = outerTupleSlot;
-                               innerTupleSlot = node->mj_InnerTupleSlot;
-                               econtext->ecxt_innertuple = innerTupleSlot;
-
-                               compareResult = MergeCompare(mergeclauses,
-                                                                                        outerSkipQual,
-                                                                                        econtext);
-
-                               MJ_DEBUG_MERGE_COMPARE(outerSkipQual, compareResult);
-
-                               /*
-                                * compareResult is true as long as we should continue
-                                * skipping outer tuples.
-                                */
-                               if (compareResult)
-                               {
+                               else if (compareResult < 0)
                                        node->mj_JoinState = EXEC_MJ_SKIPOUTER_ADVANCE;
-                                       break;
-                               }
-
-                               /*
-                                * now check the inner skip qual to see if we should now
-                                * skip inner tuples... if we fail the inner skip qual,
-                                * then we know we have a new pair of matching tuples.
-                                */
-                               compareResult = MergeCompare(mergeclauses,
-                                                                                        innerSkipQual,
-                                                                                        econtext);
-
-                               MJ_DEBUG_MERGE_COMPARE(innerSkipQual, compareResult);
-
-                               if (compareResult)
-                                       node->mj_JoinState = EXEC_MJ_SKIPINNER_BEGIN;
-                               else
-                                       node->mj_JoinState = EXEC_MJ_JOINMARK;
+                               else                    /* compareResult > 0 */
+                                       node->mj_JoinState = EXEC_MJ_SKIPINNER_ADVANCE;
                                break;
 
                                /*
@@ -1057,105 +1285,16 @@ ExecMergeJoin(MergeJoinState *node)
                                        return NULL;
                                }
 
-                               /*
-                                * otherwise test the new tuple against the skip qual.
-                                */
-                               node->mj_JoinState = EXEC_MJ_SKIPOUTER_TEST;
-                               break;
-
-                               /*-----------------------------------------------------------
-                                * EXEC_MJ_SKIPINNER means skip over tuples in the inner plan
-                                * until we find an inner tuple >= current outer tuple.
-                                *
-                                * For example:
-                                *
-                                *                              outer inner
-                                *                                5             5
-                                *                                5             5
-                                * outer tuple - 12             8  - inner tuple
-                                *                               14    10
-                                *                               17    12
-                                *
-                                * we have to advance the inner scan
-                                * until we find the inner 12.
-                                *
-                                * To avoid redundant tests, we divide this into three
-                                * sub-states: BEGIN, TEST, ADVANCE.
-                                *-------------------------------------------------------
-                                */
-                       case EXEC_MJ_SKIPINNER_BEGIN:
-                               MJ_printf("ExecMergeJoin: EXEC_MJ_SKIPINNER_BEGIN\n");
-
-                               /*
-                                * before we advance, make sure the current tuples do not
-                                * satisfy the mergeclauses.  If they do, then we update
-                                * the marked tuple and go join them.
-                                */
-                               ResetExprContext(econtext);
-
-                               outerTupleSlot = node->mj_OuterTupleSlot;
-                               econtext->ecxt_outertuple = outerTupleSlot;
-                               innerTupleSlot = node->mj_InnerTupleSlot;
-                               econtext->ecxt_innertuple = innerTupleSlot;
-
-                               qualResult = ExecQual(mergeclauses, econtext, false);
-                               MJ_DEBUG_QUAL(mergeclauses, qualResult);
-
-                               if (qualResult)
-                               {
-                                       ExecMarkPos(innerPlan);
-
-                                       MarkInnerTuple(innerTupleSlot, node);
-
-                                       node->mj_JoinState = EXEC_MJ_JOINTUPLES;
-                                       break;
-                               }
-
-                               node->mj_JoinState = EXEC_MJ_SKIPINNER_TEST;
-                               break;
-
-                       case EXEC_MJ_SKIPINNER_TEST:
-                               MJ_printf("ExecMergeJoin: EXEC_MJ_SKIPINNER_TEST\n");
-
-                               /*
-                                * ok, now test the skip qualification
-                                */
-                               outerTupleSlot = node->mj_OuterTupleSlot;
-                               econtext->ecxt_outertuple = outerTupleSlot;
-                               innerTupleSlot = node->mj_InnerTupleSlot;
-                               econtext->ecxt_innertuple = innerTupleSlot;
-
-                               compareResult = MergeCompare(mergeclauses,
-                                                                                        innerSkipQual,
-                                                                                        econtext);
-
-                               MJ_DEBUG_MERGE_COMPARE(innerSkipQual, compareResult);
-
-                               /*
-                                * compareResult is true as long as we should continue
-                                * skipping inner tuples.
-                                */
-                               if (compareResult)
+                               /* Compute join values and check for unmatchability */
+                               if (!MJEvalOuterValues(node))
                                {
-                                       node->mj_JoinState = EXEC_MJ_SKIPINNER_ADVANCE;
+                                       /* Stay in same state to fetch next outer tuple */
+                                       node->mj_JoinState = EXEC_MJ_SKIPOUTER_ADVANCE;
                                        break;
                                }
 
-                               /*
-                                * now check the outer skip qual to see if we should now
-                                * skip outer tuples... if we fail the outer skip qual,
-                                * then we know we have a new pair of matching tuples.
-                                */
-                               compareResult = MergeCompare(mergeclauses,
-                                                                                        outerSkipQual,
-                                                                                        econtext);
-
-                               MJ_DEBUG_MERGE_COMPARE(outerSkipQual, compareResult);
-
-                               if (compareResult)
-                                       node->mj_JoinState = EXEC_MJ_SKIPOUTER_BEGIN;
-                               else
-                                       node->mj_JoinState = EXEC_MJ_JOINMARK;
+                               /* Test the new tuple against the current inner */
+                               node->mj_JoinState = EXEC_MJ_SKIP_TEST;
                                break;
 
                                /*
@@ -1234,16 +1373,22 @@ ExecMergeJoin(MergeJoinState *node)
                                        return NULL;
                                }
 
-                               /*
-                                * otherwise test the new tuple against the skip qual.
-                                */
-                               node->mj_JoinState = EXEC_MJ_SKIPINNER_TEST;
+                               /* 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;
+                               }
+
+                               /* 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.
+                                * null-fill any remaing unmatched inner tuples.
                                 */
                        case EXEC_MJ_ENDOUTER:
                                MJ_printf("ExecMergeJoin: EXEC_MJ_ENDOUTER\n");
@@ -1410,6 +1555,15 @@ 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.
+        */
+       mergestate->mj_OuterEContext = CreateExprContext(estate);
+       mergestate->mj_InnerEContext = CreateExprContext(estate);
+
        /*
         * initialize child expressions
         */
@@ -1423,9 +1577,7 @@ ExecInitMergeJoin(MergeJoin *node, EState *estate)
        mergestate->js.joinqual = (List *)
                ExecInitExpr((Expr *) node->join.joinqual,
                                         (PlanState *) mergestate);
-       mergestate->mergeclauses = (List *)
-               ExecInitExpr((Expr *) node->mergeclauses,
-                                        (PlanState *) mergestate);
+       /* mergeclauses are handled below */
 
        /*
         * initialize child nodes
@@ -1498,23 +1650,16 @@ ExecInitMergeJoin(MergeJoin *node, EState *estate)
        ExecAssignProjectionInfo(&mergestate->js.ps);
 
        /*
-        * form merge skip qualifications
+        * preprocess the merge clauses
         */
-       MJFormSkipQuals(node->mergeclauses,
-                                       &mergestate->mj_OuterSkipQual,
-                                       &mergestate->mj_InnerSkipQual,
-                                       (PlanState *) mergestate);
-
-       MJ_printf("\nExecInitMergeJoin: OuterSkipQual is ");
-       MJ_nodeDisplay(mergestate->mj_OuterSkipQual);
-       MJ_printf("\nExecInitMergeJoin: InnerSkipQual is ");
-       MJ_nodeDisplay(mergestate->mj_InnerSkipQual);
-       MJ_printf("\n");
+       mergestate->mj_NumClauses = list_length(node->mergeclauses);
+       mergestate->mj_Clauses = MJExamineQuals(node->mergeclauses,
+                                                                                       (PlanState *) mergestate);
 
        /*
         * initialize join state
         */
-       mergestate->mj_JoinState = EXEC_MJ_INITIALIZE;
+       mergestate->mj_JoinState = EXEC_MJ_INITIALIZE_OUTER;
        mergestate->js.ps.ps_TupFromTlist = false;
        mergestate->mj_MatchedOuter = false;
        mergestate->mj_MatchedInner = false;
@@ -1577,7 +1722,7 @@ ExecReScanMergeJoin(MergeJoinState *node, ExprContext *exprCtxt)
 {
        ExecClearTuple(node->mj_MarkedTupleSlot);
 
-       node->mj_JoinState = EXEC_MJ_INITIALIZE;
+       node->mj_JoinState = EXEC_MJ_INITIALIZE_OUTER;
        node->js.ps.ps_TupFromTlist = false;
        node->mj_MatchedOuter = false;
        node->mj_MatchedInner = false;
index 58a987107de5f56fbdb91a9332b06cd215a9c5e6..9a6969ecefc7eaa1fa7fba6a82809002f0228137 100644 (file)
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2005, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/include/executor/execdebug.h,v 1.26 2005/03/16 21:38:09 tgl Exp $
+ * $PostgreSQL: pgsql/src/include/executor/execdebug.h,v 1.27 2005/05/13 21:20:16 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -258,17 +258,14 @@ extern int        NIndexTupleInserted;
 #define MJ2_printf(s, p1, p2)                  printf(s, p1, p2)
 #define MJ_debugtup(slot)                              debugtup(slot, NULL)
 #define MJ_dump(state)                                 ExecMergeTupleDump(state)
+#define MJ_DEBUG_COMPARE(res) \
+  MJ1_printf("  MJCompare() returns %d\n", (res))
 #define MJ_DEBUG_QUAL(clause, res) \
   MJ2_printf("  ExecQual(%s, econtext) returns %s\n", \
-                        CppAsString(clause), T_OR_F(res));
-
-#define MJ_DEBUG_MERGE_COMPARE(qual, res) \
-  MJ2_printf("  MergeCompare(mergeclauses, %s, ...) returns %s\n", \
-                        CppAsString(qual), T_OR_F(res));
-
+                        CppAsString(clause), T_OR_F(res))
 #define MJ_DEBUG_PROC_NODE(slot) \
   MJ2_printf("  %s = ExecProcNode(...) returns %s\n", \
-                        CppAsString(slot), NULL_OR_TUPLE(slot));
+                        CppAsString(slot), NULL_OR_TUPLE(slot))
 
 #else
 
@@ -278,8 +275,8 @@ extern int  NIndexTupleInserted;
 #define MJ2_printf(s, p1, p2)
 #define MJ_debugtup(slot)
 #define MJ_dump(state)
+#define MJ_DEBUG_COMPARE(res)
 #define MJ_DEBUG_QUAL(clause, res)
-#define MJ_DEBUG_MERGE_COMPARE(qual, res)
 #define MJ_DEBUG_PROC_NODE(slot)
 #endif   /* EXEC_MERGEJOINDEBUG */
 
index 100049fdf83305c0a9aaae850ba1fb818c203d14..e8ef7a47fd0f1df0a7033543ef6e23d80f968416 100644 (file)
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2005, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/include/executor/execdefs.h,v 1.17 2004/12/31 22:03:29 pgsql Exp $
+ * $PostgreSQL: pgsql/src/include/executor/execdefs.h,v 1.18 2005/05/13 21:20:16 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
  *             Merge Join states
  * ----------------
  */
-#define EXEC_MJ_INITIALIZE                             1
-#define EXEC_MJ_JOINMARK                               2
-#define EXEC_MJ_JOINTEST                               3
-#define EXEC_MJ_JOINTUPLES                             4
-#define EXEC_MJ_NEXTOUTER                              5
-#define EXEC_MJ_TESTOUTER                              6
-#define EXEC_MJ_NEXTINNER                              7
-#define EXEC_MJ_SKIPOUTER_BEGIN                        8
-#define EXEC_MJ_SKIPOUTER_TEST                 9
-#define EXEC_MJ_SKIPOUTER_ADVANCE              10
-#define EXEC_MJ_SKIPINNER_BEGIN                        11
-#define EXEC_MJ_SKIPINNER_TEST                 12
-#define EXEC_MJ_SKIPINNER_ADVANCE              13
-#define EXEC_MJ_ENDOUTER                               14
-#define EXEC_MJ_ENDINNER                               15
+#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
 
 #endif   /* EXECDEFS_H */
index 2d74d25a3615874b67b74204bd9c257cc46135ee..9d47c17ad23f687eb267765e69be24b4ab6806e8 100644 (file)
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2005, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/include/nodes/execnodes.h,v 1.131 2005/05/05 03:37:23 tgl Exp $
+ * $PostgreSQL: pgsql/src/include/nodes/execnodes.h,v 1.132 2005/05/13 21:20:16 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -1028,24 +1028,28 @@ typedef struct NestLoopState
 /* ----------------
  *      MergeJoinState information
  *
- *             OuterSkipQual      outerKey1 < innerKey1 ...
- *             InnerSkipQual      outerKey1 > innerKey1 ...
- *             JoinState                  current "state" of join. see executor.h
+ *             NumClauses                 number of mergejoinable join clauses
+ *             Clauses                    info for each mergejoinable clause
+ *             JoinState                  current "state" of join.  see execdefs.h
  *             MatchedOuter       true if found a join match for current outer tuple
  *             MatchedInner       true if found a join match for current inner tuple
- *             OuterTupleSlot     pointer to slot in tuple table for cur outer tuple
- *             InnerTupleSlot     pointer to slot in tuple table for cur inner tuple
- *             MarkedTupleSlot    pointer to slot in tuple table for marked tuple
+ *             OuterTupleSlot     slot in tuple table for cur outer tuple
+ *             InnerTupleSlot     slot in tuple table for cur inner tuple
+ *             MarkedTupleSlot    slot in tuple table for marked tuple
  *             NullOuterTupleSlot prepared null tuple for right outer joins
  *             NullInnerTupleSlot prepared null tuple for left outer joins
+ *             OuterEContext      workspace for computing outer tuple's join values
+ *             InnerEContext      workspace for computing inner tuple's join values
  * ----------------
  */
+/* private in nodeMergejoin.c: */
+typedef struct MergeJoinClauseData *MergeJoinClause;
+
 typedef struct MergeJoinState
 {
        JoinState       js;                             /* its first field is NodeTag */
-       List       *mergeclauses;       /* list of ExprState nodes */
-       List       *mj_OuterSkipQual;           /* list of ExprState nodes */
-       List       *mj_InnerSkipQual;           /* list of ExprState nodes */
+       int                     mj_NumClauses;
+       MergeJoinClause mj_Clauses;     /* array of length mj_NumClauses */
        int                     mj_JoinState;
        bool            mj_MatchedOuter;
        bool            mj_MatchedInner;
@@ -1054,6 +1058,8 @@ typedef struct MergeJoinState
        TupleTableSlot *mj_MarkedTupleSlot;
        TupleTableSlot *mj_NullOuterTupleSlot;
        TupleTableSlot *mj_NullInnerTupleSlot;
+       ExprContext *mj_OuterEContext;
+       ExprContext *mj_InnerEContext;
 } MergeJoinState;
 
 /* ----------------