1
0
mirror of https://github.com/postgres/postgres.git synced 2025-07-28 23:42:10 +03:00

Create a "sort support" interface API for faster sorting.

This patch creates an API whereby a btree index opclass can optionally
provide non-SQL-callable support functions for sorting.  In the initial
patch, we only use this to provide a directly-callable comparator function,
which can be invoked with a bit less overhead than the traditional
SQL-callable comparator.  While that should be of value in itself, the real
reason for doing this is to provide a datatype-extensible framework for
more aggressive optimizations, as in Peter Geoghegan's recent work.

Robert Haas and Tom Lane
This commit is contained in:
Tom Lane
2011-12-07 00:18:38 -05:00
parent d2a662182e
commit c6e3ac11b6
30 changed files with 869 additions and 421 deletions

View File

@ -38,10 +38,8 @@
#include "postgres.h"
#include "access/nbtree.h"
#include "executor/execdebug.h"
#include "executor/nodeMergeAppend.h"
#include "utils/lsyscache.h"
/*
* It gets quite confusing having a heap array (indexed by integers) which
@ -128,38 +126,18 @@ ExecInitMergeAppend(MergeAppend *node, EState *estate, int eflags)
* initialize sort-key information
*/
mergestate->ms_nkeys = node->numCols;
mergestate->ms_scankeys = palloc0(sizeof(ScanKeyData) * node->numCols);
mergestate->ms_sortkeys = palloc0(sizeof(SortSupportData) * node->numCols);
for (i = 0; i < node->numCols; i++)
{
Oid sortFunction;
bool reverse;
int flags;
SortSupport sortKey = mergestate->ms_sortkeys + i;
if (!get_compare_function_for_ordering_op(node->sortOperators[i],
&sortFunction, &reverse))
elog(ERROR, "operator %u is not a valid ordering operator",
node->sortOperators[i]);
sortKey->ssup_cxt = CurrentMemoryContext;
sortKey->ssup_collation = node->collations[i];
sortKey->ssup_nulls_first = node->nullsFirst[i];
sortKey->ssup_attno = node->sortColIdx[i];
/* We use btree's conventions for encoding directionality */
flags = 0;
if (reverse)
flags |= SK_BT_DESC;
if (node->nullsFirst[i])
flags |= SK_BT_NULLS_FIRST;
/*
* We needn't fill in sk_strategy or sk_subtype since these scankeys
* will never be passed to an index.
*/
ScanKeyEntryInitialize(&mergestate->ms_scankeys[i],
flags,
node->sortColIdx[i],
InvalidStrategy,
InvalidOid,
node->collations[i],
sortFunction,
(Datum) 0);
PrepareSortSupportFromOrderingOp(node->sortOperators[i], sortKey);
}
/*
@ -298,45 +276,22 @@ heap_compare_slots(MergeAppendState *node, SlotNumber slot1, SlotNumber slot2)
for (nkey = 0; nkey < node->ms_nkeys; nkey++)
{
ScanKey scankey = node->ms_scankeys + nkey;
AttrNumber attno = scankey->sk_attno;
SortSupport sortKey = node->ms_sortkeys + nkey;
AttrNumber attno = sortKey->ssup_attno;
Datum datum1,
datum2;
bool isNull1,
isNull2;
int32 compare;
int compare;
datum1 = slot_getattr(s1, attno, &isNull1);
datum2 = slot_getattr(s2, attno, &isNull2);
if (isNull1)
{
if (isNull2)
continue; /* NULL "=" NULL */
else if (scankey->sk_flags & SK_BT_NULLS_FIRST)
return -1; /* NULL "<" NOT_NULL */
else
return 1; /* NULL ">" NOT_NULL */
}
else if (isNull2)
{
if (scankey->sk_flags & SK_BT_NULLS_FIRST)
return 1; /* NOT_NULL ">" NULL */
else
return -1; /* NOT_NULL "<" NULL */
}
else
{
compare = DatumGetInt32(FunctionCall2Coll(&scankey->sk_func,
scankey->sk_collation,
datum1, datum2));
if (compare != 0)
{
if (scankey->sk_flags & SK_BT_DESC)
compare = -compare;
return compare;
}
}
compare = ApplySortComparator(datum1, isNull1,
datum2, isNull2,
sortKey);
if (compare != 0)
return compare;
}
return 0;
}

View File

@ -95,8 +95,6 @@
#include "access/nbtree.h"
#include "executor/execdebug.h"
#include "executor/nodeMergejoin.h"
#include "miscadmin.h"
#include "utils/acl.h"
#include "utils/lsyscache.h"
#include "utils/memutils.h"
@ -135,13 +133,10 @@ typedef struct MergeJoinClauseData
bool risnull;
/*
* The comparison strategy in use, and the lookup info to let us call the
* btree comparison support function, and the collation to use.
* Everything we need to know to compare the left and right values is
* stored here.
*/
bool reverse; /* if true, negate the cmpfn's output */
bool nulls_first; /* if true, nulls sort low */
FmgrInfo cmpfinfo;
Oid collation;
SortSupportData ssup;
} MergeJoinClauseData;
/* Result type for MJEvalOuterValues and MJEvalInnerValues */
@ -203,8 +198,7 @@ MJExamineQuals(List *mergeclauses,
int op_strategy;
Oid op_lefttype;
Oid op_righttype;
RegProcedure cmpproc;
AclResult aclresult;
Oid sortfunc;
if (!IsA(qual, OpExpr))
elog(ERROR, "mergejoin clause is not an OpExpr");
@ -215,6 +209,17 @@ MJExamineQuals(List *mergeclauses,
clause->lexpr = ExecInitExpr((Expr *) linitial(qual->args), parent);
clause->rexpr = ExecInitExpr((Expr *) lsecond(qual->args), parent);
/* Set up sort support data */
clause->ssup.ssup_cxt = CurrentMemoryContext;
clause->ssup.ssup_collation = collation;
if (opstrategy == BTLessStrategyNumber)
clause->ssup.ssup_reverse = false;
else if (opstrategy == BTGreaterStrategyNumber)
clause->ssup.ssup_reverse = true;
else /* planner screwed up */
elog(ERROR, "unsupported mergejoin strategy %d", opstrategy);
clause->ssup.ssup_nulls_first = nulls_first;
/* Extract the operator's declared left/right datatypes */
get_op_opfamily_properties(qual->opno, opfamily, false,
&op_strategy,
@ -224,36 +229,30 @@ MJExamineQuals(List *mergeclauses,
elog(ERROR, "cannot merge using non-equality operator %u",
qual->opno);
/* And get the matching support procedure (comparison function) */
cmpproc = get_opfamily_proc(opfamily,
op_lefttype,
op_righttype,
BTORDER_PROC);
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);
if (aclresult != ACLCHECK_OK)
aclcheck_error(aclresult, ACL_KIND_PROC,
get_func_name(cmpproc));
/* 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;
/* ... and the collation too */
clause->collation = collation;
/* And get the matching support or comparison function */
sortfunc = get_opfamily_proc(opfamily,
op_lefttype,
op_righttype,
BTSORTSUPPORT_PROC);
if (OidIsValid(sortfunc))
{
/* The sort support function should provide a comparator */
OidFunctionCall1(sortfunc, PointerGetDatum(&clause->ssup));
Assert(clause->ssup.comparator != NULL);
}
else
{
/* opfamily doesn't provide sort support, get comparison func */
sortfunc = get_opfamily_proc(opfamily,
op_lefttype,
op_righttype,
BTORDER_PROC);
if (!OidIsValid(sortfunc)) /* should not happen */
elog(ERROR, "missing support function %d(%u,%u) in opfamily %u",
BTORDER_PROC, op_lefttype, op_righttype, opfamily);
/* We'll use a shim to call the old-style btree comparator */
PrepareSortSupportComparisonShim(sortfunc, &clause->ssup);
}
iClause++;
}
@ -310,7 +309,8 @@ MJEvalOuterValues(MergeJoinState *mergestate)
if (clause->lisnull)
{
/* match is impossible; can we end the join early? */
if (i == 0 && !clause->nulls_first && !mergestate->mj_FillOuter)
if (i == 0 && !clause->ssup.ssup_nulls_first &&
!mergestate->mj_FillOuter)
result = MJEVAL_ENDOFJOIN;
else if (result == MJEVAL_MATCHABLE)
result = MJEVAL_NONMATCHABLE;
@ -356,7 +356,8 @@ MJEvalInnerValues(MergeJoinState *mergestate, TupleTableSlot *innerslot)
if (clause->risnull)
{
/* match is impossible; can we end the join early? */
if (i == 0 && !clause->nulls_first && !mergestate->mj_FillInner)
if (i == 0 && !clause->ssup.ssup_nulls_first &&
!mergestate->mj_FillInner)
result = MJEVAL_ENDOFJOIN;
else if (result == MJEVAL_MATCHABLE)
result = MJEVAL_NONMATCHABLE;
@ -373,20 +374,19 @@ MJEvalInnerValues(MergeJoinState *mergestate, TupleTableSlot *innerslot)
*
* Compare the mergejoinable values of the current two input tuples
* and return 0 if they are equal (ie, the mergejoin equalities all
* succeed), +1 if outer > inner, -1 if outer < inner.
* succeed), >0 if outer > inner, <0 if outer < inner.
*
* MJEvalOuterValues and MJEvalInnerValues must already have been called
* for the current outer and inner tuples, respectively.
*/
static int32
static int
MJCompare(MergeJoinState *mergestate)
{
int32 result = 0;
int result = 0;
bool nulleqnull = false;
ExprContext *econtext = mergestate->js.ps.ps_ExprContext;
int i;
MemoryContext oldContext;
FunctionCallInfoData fcinfo;
/*
* Call the comparison functions in short-lived context, in case they leak
@ -399,62 +399,28 @@ MJCompare(MergeJoinState *mergestate)
for (i = 0; i < mergestate->mj_NumClauses; i++)
{
MergeJoinClause clause = &mergestate->mj_Clauses[i];
Datum fresult;
/*
* Deal with null inputs.
* Special case for NULL-vs-NULL, else use standard comparison.
*/
if (clause->lisnull)
if (clause->lisnull && clause->risnull)
{
if (clause->risnull)
{
nulleqnull = true; /* NULL "=" NULL */
continue;
}
if (clause->nulls_first)
result = -1; /* NULL "<" NOT_NULL */
else
result = 1; /* NULL ">" NOT_NULL */
break;
}
if (clause->risnull)
{
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,
clause->collation, 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; /* treat like NULL = NULL */
nulleqnull = true; /* NULL "=" NULL */
continue;
}
result = DatumGetInt32(fresult);
if (clause->reverse)
result = -result;
result = ApplySortComparator(clause->ldatum, clause->lisnull,
clause->rdatum, clause->risnull,
&clause->ssup);
if (result != 0)
break;
}
/*
* If we had any null comparison results or NULL-vs-NULL inputs, we do not
* want to report that the tuples are equal. Instead, if result is still
* 0, change it to +1. This will result in advancing the inner side of
* the join.
* If we had any NULL-vs-NULL inputs, we do not want to report that the
* tuples are equal. Instead, if result is still 0, change it to +1.
* This will result in advancing the inner side of the join.
*
* Likewise, if there was a constant-false joinqual, do not report
* equality. We have to check this as part of the mergequals, else the
@ -647,7 +613,7 @@ ExecMergeJoin(MergeJoinState *node)
List *joinqual;
List *otherqual;
bool qualResult;
int32 compareResult;
int compareResult;
PlanState *innerPlan;
TupleTableSlot *innerTupleSlot;
PlanState *outerPlan;