Commit ad429fe3 authored by Tom Lane's avatar Tom Lane

Teach nodeMergejoin how to handle DESC and/or NULLS FIRST sort orders.

So far only tested by hacking the planner ...
parent 5b88b85c
...@@ -8,7 +8,7 @@ ...@@ -8,7 +8,7 @@
* *
* *
* IDENTIFICATION * 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 $
* *
*------------------------------------------------------------------------- *-------------------------------------------------------------------------
*/ */
...@@ -39,12 +39,13 @@ ...@@ -39,12 +39,13 @@
* therefore it should scan the outer relation first to find a * therefore it should scan the outer relation first to find a
* matching tuple and so on. * matching tuple and so on.
* *
* Therefore, when initializing the merge-join node, we look up the * Therefore, rather than directly executing the merge join clauses,
* associated sort operators. We assume the planner has seen to it * we evaluate the left and right key expressions separately and then
* that the inputs are correctly sorted by these operators. Rather * compare the columns one at a time (see MJCompare). The planner
* than directly executing the merge join clauses, we evaluate the * passes us enough information about the sort ordering of the inputs
* left and right key expressions separately and then compare the * to allow us to determine how to make the comparison. We may use the
* columns one at a time (see MJCompare). * 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 * Consider the above relations and suppose that the executor has
...@@ -104,19 +105,8 @@ ...@@ -104,19 +105,8 @@
/* /*
* Comparison strategies supported by MJCompare * Runtime data for each mergejoin clause
*
* 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.
*/ */
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 typedef struct MergeJoinClauseData
{ {
/* Executable expression trees */ /* Executable expression trees */
...@@ -136,7 +126,8 @@ typedef struct MergeJoinClauseData ...@@ -136,7 +126,8 @@ typedef struct MergeJoinClauseData
* The comparison strategy in use, and the lookup info to let us call the * The comparison strategy in use, and the lookup info to let us call the
* btree comparison support function. * 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; FmgrInfo cmpfinfo;
} MergeJoinClauseData; } MergeJoinClauseData;
...@@ -158,11 +149,11 @@ typedef struct MergeJoinClauseData ...@@ -158,11 +149,11 @@ typedef struct MergeJoinClauseData
* In addition to the expressions themselves, the planner passes the btree * In addition to the expressions themselves, the planner passes the btree
* opfamily OID, btree strategy number (BTLessStrategyNumber or * opfamily OID, btree strategy number (BTLessStrategyNumber or
* BTGreaterStrategyNumber), and nulls-first flag that identify the intended * 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 * equality operator in this opfamily, and the two inputs are guaranteed to be
* ordered in either increasing or decreasing (respectively) order according * ordered in either increasing or decreasing (respectively) order according
* to this opfamily. This allows us to obtain the needed comparison functions * to this opfamily, with nulls at the indicated end of the range. This
* from the opfamily. * allows us to obtain the needed comparison function from the opfamily.
*/ */
static MergeJoinClause static MergeJoinClause
MJExamineQuals(List *mergeclauses, MJExamineQuals(List *mergeclauses,
...@@ -193,11 +184,6 @@ MJExamineQuals(List *mergeclauses, ...@@ -193,11 +184,6 @@ MJExamineQuals(List *mergeclauses,
RegProcedure cmpproc; RegProcedure cmpproc;
AclResult aclresult; 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)) if (!IsA(qual, OpExpr))
elog(ERROR, "mergejoin clause is not an OpExpr"); elog(ERROR, "mergejoin clause is not an OpExpr");
...@@ -213,15 +199,19 @@ MJExamineQuals(List *mergeclauses, ...@@ -213,15 +199,19 @@ MJExamineQuals(List *mergeclauses,
&op_lefttype, &op_lefttype,
&op_righttype, &op_righttype,
&op_recheck); &op_recheck);
Assert(op_strategy == BTEqualStrategyNumber); if (op_strategy != BTEqualStrategyNumber) /* should not happen */
Assert(!op_recheck); 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) */ /* And get the matching support procedure (comparison function) */
cmpproc = get_opfamily_proc(opfamily, cmpproc = get_opfamily_proc(opfamily,
op_lefttype, op_lefttype,
op_righttype, op_righttype,
BTORDER_PROC); 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 */ /* Check permission to call cmp function */
aclresult = pg_proc_aclcheck(cmpproc, GetUserId(), ACL_EXECUTE); aclresult = pg_proc_aclcheck(cmpproc, GetUserId(), ACL_EXECUTE);
...@@ -232,6 +222,16 @@ MJExamineQuals(List *mergeclauses, ...@@ -232,6 +222,16 @@ MJExamineQuals(List *mergeclauses,
/* Set up the fmgr lookup information */ /* Set up the fmgr lookup information */
fmgr_info(cmpproc, &(clause->cmpfinfo)); 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++; iClause++;
} }
...@@ -324,10 +324,10 @@ MJEvalInnerValues(MergeJoinState *mergestate, TupleTableSlot *innerslot) ...@@ -324,10 +324,10 @@ MJEvalInnerValues(MergeJoinState *mergestate, TupleTableSlot *innerslot)
* MJEvalOuterValues and MJEvalInnerValues must already have been called * MJEvalOuterValues and MJEvalInnerValues must already have been called
* for the current outer and inner tuples, respectively. * for the current outer and inner tuples, respectively.
*/ */
static int static int32
MJCompare(MergeJoinState *mergestate) MJCompare(MergeJoinState *mergestate)
{ {
int result = 0; int32 result = 0;
bool nulleqnull = false; bool nulleqnull = false;
ExprContext *econtext = mergestate->js.ps.ps_ExprContext; ExprContext *econtext = mergestate->js.ps.ps_ExprContext;
int i; int i;
...@@ -348,26 +348,33 @@ MJCompare(MergeJoinState *mergestate) ...@@ -348,26 +348,33 @@ MJCompare(MergeJoinState *mergestate)
Datum fresult; Datum fresult;
/* /*
* Deal with null inputs. We treat NULL as sorting after non-NULL. * Deal with null inputs.
*/ */
if (clause->lisnull) if (clause->lisnull)
{ {
if (clause->risnull) if (clause->risnull)
{ {
nulleqnull = true; nulleqnull = true; /* NULL "=" NULL */
continue; continue;
} }
/* NULL > non-NULL */ if (clause->nulls_first)
result = 1; result = -1; /* NULL "<" NOT_NULL */
else
result = 1; /* NULL ">" NOT_NULL */
break; break;
} }
if (clause->risnull) if (clause->risnull)
{ {
/* non-NULL < NULL */ if (clause->nulls_first)
result = -1; result = 1; /* NOT_NULL ">" NULL */
else
result = -1; /* NOT_NULL "<" NULL */
break; break;
} }
/*
* OK to call the comparison function.
*/
InitFunctionCallInfoData(fcinfo, &(clause->cmpfinfo), 2, InitFunctionCallInfoData(fcinfo, &(clause->cmpfinfo), 2,
NULL, NULL); NULL, NULL);
fcinfo.arg[0] = clause->ldatum; fcinfo.arg[0] = clause->ldatum;
...@@ -377,46 +384,17 @@ MJCompare(MergeJoinState *mergestate) ...@@ -377,46 +384,17 @@ MJCompare(MergeJoinState *mergestate)
fresult = FunctionCallInvoke(&fcinfo); fresult = FunctionCallInvoke(&fcinfo);
if (fcinfo.isnull) if (fcinfo.isnull)
{ {
nulleqnull = true; nulleqnull = true; /* treat like NULL = NULL */
continue;
}
if (DatumGetInt32(fresult) == 0)
{
/* equal */
continue; continue;
} }
if (clause->cmpstrategy == MERGEFUNC_CMP) result = DatumGetInt32(fresult);
{
if (DatumGetInt32(fresult) < 0) if (clause->reverse)
{ result = -result;
/* less than */
result = -1; if (result != 0)
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; break;
} }
}
}
/* /*
* If we had any null comparison results or NULL-vs-NULL inputs, we do not * If we had any null comparison results or NULL-vs-NULL inputs, we do not
...@@ -581,7 +559,7 @@ ExecMergeJoin(MergeJoinState *node) ...@@ -581,7 +559,7 @@ ExecMergeJoin(MergeJoinState *node)
List *joinqual; List *joinqual;
List *otherqual; List *otherqual;
bool qualResult; bool qualResult;
int compareResult; int32 compareResult;
PlanState *innerPlan; PlanState *innerPlan;
TupleTableSlot *innerTupleSlot; TupleTableSlot *innerTupleSlot;
PlanState *outerPlan; PlanState *outerPlan;
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment