}
tuplesort_empty = !tuplesort_getdatum(state->tuplesort, true,
- &ts_val, &ts_isnull);
+ &ts_val, &ts_isnull, NULL);
Assert(tuplesort_empty || !ts_isnull);
if (!tuplesort_empty)
{
if (aggstate->sort_in)
{
- if (!tuplesort_gettupleslot(aggstate->sort_in, true, aggstate->sort_slot))
+ if (!tuplesort_gettupleslot(aggstate->sort_in, true, aggstate->sort_slot,
+ NULL))
return NULL;
slot = aggstate->sort_slot;
}
MemoryContext workcontext = aggstate->tmpcontext->ecxt_per_tuple_memory;
MemoryContext oldContext;
bool isDistinct = (pertrans->numDistinctCols > 0);
+ Datum newAbbrevVal = (Datum) 0;
+ Datum oldAbbrevVal = (Datum) 0;
FunctionCallInfo fcinfo = &pertrans->transfn_fcinfo;
Datum *newVal;
bool *isNull;
*/
while (tuplesort_getdatum(pertrans->sortstates[aggstate->current_set],
- true, newVal, isNull))
+ true, newVal, isNull, &newAbbrevVal))
{
/*
* Clear and select the working context for evaluation of the equality
haveOldVal &&
((oldIsNull && *isNull) ||
(!oldIsNull && !*isNull &&
+ oldAbbrevVal == newAbbrevVal &&
DatumGetBool(FunctionCall2(&pertrans->equalfns[0],
oldVal, *newVal)))))
{
pfree(DatumGetPointer(oldVal));
/* and remember the new one for subsequent equality checks */
oldVal = *newVal;
+ oldAbbrevVal = newAbbrevVal;
oldIsNull = *isNull;
haveOldVal = true;
}
TupleTableSlot *slot2 = pertrans->uniqslot;
int numTransInputs = pertrans->numTransInputs;
int numDistinctCols = pertrans->numDistinctCols;
+ Datum newAbbrevVal = (Datum) 0;
+ Datum oldAbbrevVal = (Datum) 0;
bool haveOldValue = false;
int i;
ExecClearTuple(slot2);
while (tuplesort_gettupleslot(pertrans->sortstates[aggstate->current_set],
- true, slot1))
+ true, slot1, &newAbbrevVal))
{
/*
* Extract the first numTransInputs columns as datums to pass to the
if (numDistinctCols == 0 ||
!haveOldValue ||
+ newAbbrevVal != oldAbbrevVal ||
!execTuplesMatch(slot1, slot2,
numDistinctCols,
pertrans->sortColIdx,
slot2 = slot1;
slot1 = tmpslot;
+ /* avoid execTuplesMatch() calls by reusing abbreviated keys */
+ oldAbbrevVal = newAbbrevVal;
haveOldValue = true;
}
}
slot = node->ss.ps.ps_ResultTupleSlot;
(void) tuplesort_gettupleslot(tuplesortstate,
ScanDirectionIsForward(dir),
- slot);
+ slot, NULL);
return slot;
}
elog(ERROR, "missing row in percentile_disc");
}
- if (!tuplesort_getdatum(osastate->sortstate, true, &val, &isnull))
+ if (!tuplesort_getdatum(osastate->sortstate, true, &val, &isnull, NULL))
elog(ERROR, "missing row in percentile_disc");
/*
if (!tuplesort_skiptuples(osastate->sortstate, first_row, true))
elog(ERROR, "missing row in percentile_cont");
- if (!tuplesort_getdatum(osastate->sortstate, true, &first_val, &isnull))
+ if (!tuplesort_getdatum(osastate->sortstate, true, &first_val, &isnull, NULL))
elog(ERROR, "missing row in percentile_cont");
if (isnull)
PG_RETURN_NULL();
}
else
{
- if (!tuplesort_getdatum(osastate->sortstate, true, &second_val, &isnull))
+ if (!tuplesort_getdatum(osastate->sortstate, true, &second_val, &isnull, NULL))
elog(ERROR, "missing row in percentile_cont");
if (isnull)
if (!tuplesort_skiptuples(osastate->sortstate, target_row - rownum - 1, true))
elog(ERROR, "missing row in percentile_disc");
- if (!tuplesort_getdatum(osastate->sortstate, true, &val, &isnull))
+ if (!tuplesort_getdatum(osastate->sortstate, true, &val, &isnull, NULL))
elog(ERROR, "missing row in percentile_disc");
rownum = target_row;
if (!tuplesort_skiptuples(osastate->sortstate, first_row - rownum - 1, true))
elog(ERROR, "missing row in percentile_cont");
- if (!tuplesort_getdatum(osastate->sortstate, true, &first_val, &isnull) || isnull)
+ if (!tuplesort_getdatum(osastate->sortstate, true, &first_val,
+ &isnull, NULL) || isnull)
elog(ERROR, "missing row in percentile_cont");
rownum = first_row;
/* Fetch second_row if needed */
if (second_row > rownum)
{
- if (!tuplesort_getdatum(osastate->sortstate, true, &second_val, &isnull) || isnull)
+ if (!tuplesort_getdatum(osastate->sortstate, true, &second_val,
+ &isnull, NULL) || isnull)
elog(ERROR, "missing row in percentile_cont");
rownum++;
}
int64 last_val_freq = 0;
bool last_val_is_mode = false;
FmgrInfo *equalfn;
+ Datum abbrev_val = (Datum) 0;
+ Datum last_abbrev_val = (Datum) 0;
bool shouldfree;
Assert(AggCheckCallContext(fcinfo, NULL) == AGG_CONTEXT_AGGREGATE);
tuplesort_performsort(osastate->sortstate);
/* Scan tuples and count frequencies */
- while (tuplesort_getdatum(osastate->sortstate, true, &val, &isnull))
+ while (tuplesort_getdatum(osastate->sortstate, true, &val, &isnull, &abbrev_val))
{
/* we don't expect any nulls, but ignore them if found */
if (isnull)
mode_val = last_val = val;
mode_freq = last_val_freq = 1;
last_val_is_mode = true;
+ last_abbrev_val = abbrev_val;
}
- else if (DatumGetBool(FunctionCall2(equalfn, val, last_val)))
+ else if (abbrev_val == last_abbrev_val &&
+ DatumGetBool(FunctionCall2(equalfn, val, last_val)))
{
/* value equal to previous value, count it */
if (last_val_is_mode)
if (shouldfree && !last_val_is_mode)
pfree(DatumGetPointer(last_val));
last_val = val;
+ /* avoid equality function calls by reusing abbreviated keys */
+ last_abbrev_val = abbrev_val;
last_val_freq = 1;
last_val_is_mode = false;
}
tuplesort_performsort(osastate->sortstate);
/* iterate till we find the hypothetical row */
- while (tuplesort_gettupleslot(osastate->sortstate, true, slot))
+ while (tuplesort_gettupleslot(osastate->sortstate, true, slot, NULL))
{
bool isnull;
Datum d = slot_getattr(slot, nargs + 1, &isnull);
int64 duplicate_count = 0;
OSAPerGroupState *osastate;
int numDistinctCols;
+ Datum abbrevVal = (Datum) 0;
+ Datum abbrevOld = (Datum) 0;
AttrNumber *sortColIdx;
FmgrInfo *equalfns;
TupleTableSlot *slot;
slot2 = extraslot;
/* iterate till we find the hypothetical row */
- while (tuplesort_gettupleslot(osastate->sortstate, true, slot))
+ while (tuplesort_gettupleslot(osastate->sortstate, true, slot, &abbrevVal))
{
bool isnull;
Datum d = slot_getattr(slot, nargs + 1, &isnull);
/* count non-distinct tuples */
if (!TupIsNull(slot2) &&
+ abbrevVal == abbrevOld &&
execTuplesMatch(slot, slot2,
numDistinctCols,
sortColIdx,
tmpslot = slot2;
slot2 = slot;
slot = tmpslot;
+ /* avoid execTuplesMatch() calls by reusing abbreviated keys */
+ abbrevOld = abbrevVal;
rank++;
* converter it won't expect NULL values, and cost model is not
* required to account for NULL, so in that case we avoid calling
* converter and just set datum1 to zeroed representation (to be
- * consistent).
+ * consistent, and to support cheap inequality tests for NULL
+ * abbreviated keys).
*/
stup.datum1 = original;
}
if (isNull || state->datumTypeByVal)
{
- stup.datum1 = val;
+ /*
+ * Set datum1 to zeroed representation for NULLs (to be consistent, and
+ * to support cheap inequality tests for NULL abbreviated keys).
+ */
+ stup.datum1 = !isNull ? val : (Datum) 0;
stup.isnull1 = isNull;
stup.tuple = NULL; /* no separate storage */
}
* Fetch the next tuple in either forward or back direction.
* If successful, put tuple in slot and return TRUE; else, clear the slot
* and return FALSE.
+ *
+ * Caller may optionally be passed back abbreviated value (on TRUE return
+ * value) when abbreviation was used, which can be used to cheaply avoid
+ * equality checks that might otherwise be required. Caller can safely make a
+ * determination of "non-equal tuple" based on simple binary inequality. A
+ * NULL value in leading attribute will set abbreviated value to zeroed
+ * representation, which caller may rely on in abbreviated inequality check.
*/
bool
tuplesort_gettupleslot(Tuplesortstate *state, bool forward,
- TupleTableSlot *slot)
+ TupleTableSlot *slot, Datum *abbrev)
{
MemoryContext oldcontext = MemoryContextSwitchTo(state->sortcontext);
SortTuple stup;
if (stup.tuple)
{
+ /* Record abbreviated key for caller */
+ if (state->sortKeys->abbrev_converter && abbrev)
+ *abbrev = stup.datum1;
+
ExecStoreMinimalTuple((MinimalTuple) stup.tuple, slot, should_free);
return true;
}
*
* If the Datum is pass-by-ref type, the returned value is freshly palloc'd
* and is now owned by the caller.
+ *
+ * Caller may optionally be passed back abbreviated value (on TRUE return
+ * value) when abbreviation was used, which can be used to cheaply avoid
+ * equality checks that might otherwise be required. Caller can safely make a
+ * determination of "non-equal tuple" based on simple binary inequality. A
+ * NULL value will have a zeroed abbreviated value representation, which caller
+ * may rely on in abbreviated inequality check.
*/
bool
tuplesort_getdatum(Tuplesortstate *state, bool forward,
- Datum *val, bool *isNull)
+ Datum *val, bool *isNull, Datum *abbrev)
{
MemoryContext oldcontext = MemoryContextSwitchTo(state->sortcontext);
SortTuple stup;
return false;
}
+ /* Record abbreviated key for caller */
+ if (state->sortKeys->abbrev_converter && abbrev)
+ *abbrev = stup.datum1;
+
if (stup.isnull1 || state->datumTypeByVal)
{
*val = stup.datum1;
Assert(state->status == TSS_BUILDRUNS);
Assert(state->memtupcount == 0);
- /*
- * If we produced only one initial run (quite likely if the total data
- * volume is between 1X and 2X workMem), we can just use that tape as the
- * finished output, rather than doing a useless merge. (This obvious
- * optimization is not in Knuth's algorithm.)
- */
- if (state->currentRun == 1)
- {
- state->result_tape = state->tp_tapenum[state->destTape];
- /* must freeze and rewind the finished output tape */
- LogicalTapeFreeze(state->tapeset, state->result_tape);
- state->status = TSS_SORTEDONTAPE;
- return;
- }
-
if (state->sortKeys != NULL && state->sortKeys->abbrev_converter != NULL)
{
/*
state->sortKeys->abbrev_full_comparator = NULL;
}
+ /*
+ * If we produced only one initial run (quite likely if the total data
+ * volume is between 1X and 2X workMem), we can just use that tape as the
+ * finished output, rather than doing a useless merge. (This obvious
+ * optimization is not in Knuth's algorithm.)
+ */
+ if (state->currentRun == 1)
+ {
+ state->result_tape = state->tp_tapenum[state->destTape];
+ /* must freeze and rewind the finished output tape */
+ LogicalTapeFreeze(state->tapeset, state->result_tape);
+ state->status = TSS_SORTEDONTAPE;
+ return;
+ }
+
/* End of step D2: rewind all output tapes to prepare for merging */
for (tapenum = 0; tapenum < state->tapeRange; tapenum++)
LogicalTapeRewind(state->tapeset, tapenum, false);
* converter it won't expect NULL values, and cost model is not
* required to account for NULL, so in that case we avoid calling
* converter and just set datum1 to zeroed representation (to be
- * consistent).
+ * consistent, and to support cheap inequality tests for NULL
+ * abbreviated keys).
*/
stup->datum1 = original;
}
* converter it won't expect NULL values, and cost model is not
* required to account for NULL, so in that case we avoid calling
* converter and just set datum1 to zeroed representation (to be
- * consistent).
+ * consistent, and to support cheap inequality tests for NULL
+ * abbreviated keys).
*/
stup->datum1 = original;
}
* converter it won't expect NULL values, and cost model is not
* required to account for NULL, so in that case we avoid calling
* converter and just set datum1 to zeroed representation (to be
- * consistent).
+ * consistent, and to support cheap inequality tests for NULL
+ * abbreviated keys).
*/
stup->datum1 = original;
}
extern void tuplesort_performsort(Tuplesortstate *state);
extern bool tuplesort_gettupleslot(Tuplesortstate *state, bool forward,
- TupleTableSlot *slot);
+ TupleTableSlot *slot, Datum *abbrev);
extern HeapTuple tuplesort_getheaptuple(Tuplesortstate *state, bool forward,
bool *should_free);
extern IndexTuple tuplesort_getindextuple(Tuplesortstate *state, bool forward,
bool *should_free);
extern bool tuplesort_getdatum(Tuplesortstate *state, bool forward,
- Datum *val, bool *isNull);
+ Datum *val, bool *isNull, Datum *abbrev);
extern bool tuplesort_skiptuples(Tuplesortstate *state, int64 ntuples,
bool forward);