Teach nodeMergejoin how to handle DESC and/or NULLS FIRST sort orders.
authorTom Lane <tgl@sss.pgh.pa.us>
Thu, 11 Jan 2007 17:19:13 +0000 (17:19 +0000)
committerTom Lane <tgl@sss.pgh.pa.us>
Thu, 11 Jan 2007 17:19:13 +0000 (17:19 +0000)
So far only tested by hacking the planner ...

src/backend/executor/nodeMergejoin.c

index a5f08c28ef36a820d74333899d1e720752cc4d2f..6e820d7ad2a1d5b9d034cf94599028847d2b064a 100644 (file)
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *   $PostgreSQL: pgsql/src/backend/executor/nodeMergejoin.c,v 1.85 2007/01/10 18:06:02 tgl Exp $
+ *   $PostgreSQL: pgsql/src/backend/executor/nodeMergejoin.c,v 1.86 2007/01/11 17:19:13 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
  *     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
 
 
 /*
- * Comparison strategies supported by MJCompare
- *
- * XXX eventually should extend MJCompare 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.
+ * Runtime data for each mergejoin clause
  */
-typedef enum
-{
-   MERGEFUNC_CMP,              /* -1 / 0 / 1 three-way comparator */
-   MERGEFUNC_REV_CMP           /* same, reversing the sense of the result */
-} MergeFunctionKind;
-
-/* Runtime data for each mergejoin clause */
 typedef struct MergeJoinClauseData
 {
    /* Executable expression trees */
@@ -136,7 +126,8 @@ typedef struct MergeJoinClauseData
     * The comparison strategy in use, and the lookup info to let us call the
     * btree comparison support function.
     */
-   MergeFunctionKind cmpstrategy;
+   bool        reverse;        /* if true, negate the cmpfn's output */
+   bool        nulls_first;    /* if true, nulls sort low */
    FmgrInfo    cmpfinfo;
 } MergeJoinClauseData;
 
@@ -158,11 +149,11 @@ typedef struct MergeJoinClauseData
  * In addition to the expressions themselves, the planner passes the btree
  * opfamily OID, btree strategy number (BTLessStrategyNumber or
  * BTGreaterStrategyNumber), and nulls-first flag that identify the intended
- * merge semantics for each merge key.  The mergejoinable operator is an
+ * sort ordering for each merge key.  The mergejoinable operator is an
  * equality operator in this opfamily, and the two inputs are guaranteed to be
  * ordered in either increasing or decreasing (respectively) order according
- * to this opfamily.  This allows us to obtain the needed comparison functions
- * from the opfamily.
+ * to this opfamily, 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 *mergeclauses,
@@ -193,11 +184,6 @@ MJExamineQuals(List *mergeclauses,
        RegProcedure cmpproc;
        AclResult   aclresult;
 
-       /* Later we'll support both ascending and descending sort... */
-       Assert(opstrategy == BTLessStrategyNumber);
-       clause->cmpstrategy = MERGEFUNC_CMP;
-       Assert(!nulls_first);
-
        if (!IsA(qual, OpExpr))
            elog(ERROR, "mergejoin clause is not an OpExpr");
 
@@ -213,15 +199,19 @@ MJExamineQuals(List *mergeclauses,
                                   &op_lefttype,
                                   &op_righttype,
                                   &op_recheck);
-       Assert(op_strategy == BTEqualStrategyNumber);
-       Assert(!op_recheck);
+       if (op_strategy != BTEqualStrategyNumber)   /* should not happen */
+           elog(ERROR, "cannot merge using non-equality operator %u",
+                qual->opno);
+       Assert(!op_recheck);    /* never true for btree */
 
        /* And get the matching support procedure (comparison function) */
        cmpproc = get_opfamily_proc(opfamily,
                                    op_lefttype,
                                    op_righttype,
                                    BTORDER_PROC);
-       Assert(RegProcedureIsValid(cmpproc));
+       if (!RegProcedureIsValid(cmpproc))          /* should not happen */
+           elog(ERROR, "missing support function %d(%u,%u) in opfamily %u",
+                BTORDER_PROC, op_lefttype, op_righttype, opfamily);
 
        /* Check permission to call cmp function */
        aclresult = pg_proc_aclcheck(cmpproc, GetUserId(), ACL_EXECUTE);
@@ -232,6 +222,16 @@ MJExamineQuals(List *mergeclauses,
        /* Set up the fmgr lookup information */
        fmgr_info(cmpproc, &(clause->cmpfinfo));
 
+       /* Fill the additional comparison-strategy flags */
+       if (opstrategy == BTLessStrategyNumber)
+           clause->reverse = false;
+       else if (opstrategy == BTGreaterStrategyNumber)
+           clause->reverse = true;
+       else                    /* planner screwed up */
+           elog(ERROR, "unsupported mergejoin strategy %d", opstrategy);
+
+       clause->nulls_first = nulls_first;
+
        iClause++;
    }
 
@@ -324,10 +324,10 @@ MJEvalInnerValues(MergeJoinState *mergestate, TupleTableSlot *innerslot)
  * MJEvalOuterValues and MJEvalInnerValues must already have been called
  * for the current outer and inner tuples, respectively.
  */
-static int
+static int32
 MJCompare(MergeJoinState *mergestate)
 {
-   int         result = 0;
+   int32       result = 0;
    bool        nulleqnull = false;
    ExprContext *econtext = mergestate->js.ps.ps_ExprContext;
    int         i;
@@ -348,26 +348,33 @@ MJCompare(MergeJoinState *mergestate)
        Datum       fresult;
 
        /*
-        * Deal with null inputs.  We treat NULL as sorting after non-NULL.
+        * Deal with null inputs.
         */
        if (clause->lisnull)
        {
            if (clause->risnull)
            {
-               nulleqnull = true;
+               nulleqnull = true;              /* NULL "=" NULL */
                continue;
            }
-           /* NULL > non-NULL */
-           result = 1;
+           if (clause->nulls_first)
+               result = -1;                    /* NULL "<" NOT_NULL */
+           else
+               result = 1;                     /* NULL ">" NOT_NULL */
            break;
        }
        if (clause->risnull)
        {
-           /* non-NULL < NULL */
-           result = -1;
+           if (clause->nulls_first)
+               result = 1;                     /* NOT_NULL ">" NULL */
+           else
+               result = -1;                    /* NOT_NULL "<" NULL */
            break;
        }
 
+       /*
+        * OK to call the comparison function.
+        */
        InitFunctionCallInfoData(fcinfo, &(clause->cmpfinfo), 2,
                                 NULL, NULL);
        fcinfo.arg[0] = clause->ldatum;
@@ -377,45 +384,16 @@ MJCompare(MergeJoinState *mergestate)
        fresult = FunctionCallInvoke(&fcinfo);
        if (fcinfo.isnull)
        {
-           nulleqnull = true;
-           continue;
-       }
-       if (DatumGetInt32(fresult) == 0)
-       {
-           /* equal */
+           nulleqnull = true;                  /* treat like NULL = NULL */
            continue;
        }
-       if (clause->cmpstrategy == MERGEFUNC_CMP)
-       {
-           if (DatumGetInt32(fresult) < 0)
-           {
-               /* less than */
-               result = -1;
-               break;
-           }
-           else
-           {
-               /* greater than */
-               result = 1;
-               break;
-           }
-       }
-       else
-       {
-           /* reverse the sort order */
-           if (DatumGetInt32(fresult) > 0)
-           {
-               /* less than */
-               result = -1;
-               break;
-           }
-           else
-           {
-               /* greater than */
-               result = 1;
-               break;
-           }
-       }
+       result = DatumGetInt32(fresult);
+
+       if (clause->reverse)
+           result = -result;
+
+       if (result != 0)
+           break;
    }
 
    /*
@@ -581,7 +559,7 @@ ExecMergeJoin(MergeJoinState *node)
    List       *joinqual;
    List       *otherqual;
    bool        qualResult;
-   int         compareResult;
+   int32       compareResult;
    PlanState  *innerPlan;
    TupleTableSlot *innerTupleSlot;
    PlanState  *outerPlan;