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:
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
|
Reference in New Issue
Block a user