This is an automated email from the ASF dual-hosted git repository.
xiangfu0 pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/pinot.git
The following commit(s) were added to refs/heads/master by this push:
new 1de06413cad [UUID 5/8] UUID aggregation, group-by and distinct (#18873)
1de06413cad is described below
commit 1de06413cadb84edd0d48bbbb6d458ca200fbd69
Author: Xiang Fu <[email protected]>
AuthorDate: Mon Aug 17 14:25:03 2026 -0700
[UUID 5/8] UUID aggregation, group-by and distinct (#18873)
* [UUID 5/8] UUID aggregation, group-by and distinct
Part 5/8 of splitting apache/pinot#18140 (logical UUID type). Rebased onto
latest master; stacked on uuid-split/04-sse-predicates-cast.
Downstream references use the UuidKey class merged in #18869.
* Return the converted UUID form for group keys in the DataTable reduce path
getConvertedKey had `case UUID` falling through to BYTES, returning the raw
byte[]. The other reduce path converts group keys via
ColumnDataType#convert,
and UUID is the one type whose converted form is not its stored bytes -- it
yields a java.util.UUID.
PredicateRowMatcher casts that directly (see #18872), so the byte[] made
GROUP BY ... HAVING over a UUID column fail with
"ClassCastException: class [B cannot be cast to class java.util.UUID".
Delegating to columnDataType.convert(...) keeps the two paths identical by
construction rather than by duplicated knowledge.
Covered by a new UuidAggregationTest integration test rather than a unit
test.
The unit-level BaseQueriesTest harness cannot reach this code, which is why
it
was uncovered; a query-level test goes through the real broker reduce.
Verified
by reverting the fix: testGroupByUuidColumnWithHaving and
testGroupByUuidColumnWithHavingReturningFinalResult both fail with the
ClassCastException and both pass with it.
The test also covers GROUP BY key rendering, DISTINCT (BytesDistinctTable no
longer hard-codes hex) and DISTINCTCOUNT / DISTINCTCOUNTHLL /
DISTINCTCOUNTBITMAP
over a UUID column.
* Cast directly to UuidKey in UuidToIdMap
Both callers key on UuidKey already
(NoDictionary{Single,Multi}ColumnGroupKey
Generator, via UuidKey.fromBytes), so UuidKey.fromObject accepted five input
types where exactly one is ever passed, and ran an instanceof chain per row
in
the group-by loop.
Casting directly matches the sibling maps -- DoubleToIdMap casts to double
--
and keeps the input type deterministic, which is what was asked for on the
equivalent PredicateRowMatcher branch in #18872.
* Hash UUID's stored bytes in distinct-count aggregations, not a canonical
string
The previous version rendered each UUID as its 36-char canonical string so
that
DISTINCTCOUNTHLL(uuidCol) would equal DISTINCTCOUNTHLL(CAST(uuidCol AS
STRING)).
No other logical type provides that guarantee: the scan path switches on the
stored type, so TIMESTAMP offers its raw millis and BOOLEAN its int, and
neither
matches a CAST to STRING. The UUID rendering was inventing a cross-type
equivalence at the cost of a String allocation per row in the aggregation
loop.
UUID now hashes its stored 16 bytes. Verified byte[] is content-hashed
rather
than identity-hashed by both HyperLogLog (clearspring MurmurHash) and
UltraLogLogUtils.OBJECT_FUNNEL (putBytes).
A minimal guard is still needed at each site, because unlike LONG or INT the
stored BYTES type is not a scalar case in this family: the scan path has no
`case BYTES`, and the dictionary path reads BYTES as serialized sketch
state.
- AggregationFunctionUtils: the three UUID blocks are gone; the BYTES guard
now
excludes UUID so it falls through to the scalar path, which offers
dictionary.get(i) -- the stored byte[] -- exactly as the scan path does.
- DistinctCountBitmap hashes Arrays.hashCode(bytes).
- DistinctCountThetaSketch cannot take scalar bytes (BYTES there means
"serialized sketch"), so it surfaces the stored hex rendering instead.
Replaces testUuidDistinctCountHllMatchesStringDistinctCountHll, which
asserted
the invariant being dropped, with one pinning the new behaviour.
* Drop UUID special-casing that the stored type already covers
Applying the rule that if TIMESTAMP and BIG_DECIMAL need no special
handling at
a site, UUID does not either -- their stored types carry them, and UUID's
should too.
Removed:
- NoDictionary{Single,Multi}ColumnGroupKeyGenerator, UuidToIdMap and its
ValueToIdMapFactory entry. `case BYTES` is already supported there and is
structurally identical to the UUID branch: same getBytesValuesSV(), same
loop, same map, differing only in wrapping UuidKey vs ByteArray. Both
yield
ByteArray downstream, which is what ColumnDataType.UUID#convert consumes,
so
this was a pure key-representation optimization -- two primitive longs
instead of a byte[] wrapper -- with no benchmark to justify 5 files and
+237/-36. UuidAggregationTest#testGroupByUuidColumn still passes,
confirming
group keys render identically without it.
- AnyValueAggregationFunction. Its switch is on getStoredType(), so
TIMESTAMP
already collapses to LONG (raw millis) and BOOLEAN to INT. UUID
collapsing to
BYTES is the consistent behaviour; the branch made it the odd one out.
- IntegerTupleSketchAggregationFunction. The UUID branch only produced a
friendlier error message; TIMESTAMP gets no such treatment when it is
equally
unusable there.
Kept, because the same rule shows they are needed:
- BytesDistinctTable: LongDistinctTable and BigDecimalDistinctTable already
render by ColumnDataType, and MultiColumnDistinctTable already calls
convertAndFormat. BytesDistinctTable hard-coding toHexString() was the
outlier; this brings it in line rather than special-casing UUID.
- The distinct-count guards and GroupByDataTableReducer, both verified
necessary by probe -- BYTES means "serialized sketch" in one and the group
key must convert in the other.
19 files / +958 -80 -> 12 files / +694 -39.
* Format DISTINCT rows in one pass in BytesDistinctTable
The previous version added every row holding a ByteArray, then walked the
whole
list again in formatRows() to rewrite row[0]. Two traversals and two writes
per
row where master did one.
The formatting now happens inline in addRows, with the ColumnDataType
passed in
-- the same shape LongDistinctTable already uses for its TIMESTAMP handling.
Not an extra String allocation either way: ColumnDataType#convertAndFormat
for
BYTES is ((ByteArray) value).toHexString(), the exact call master made, and
ByteArray#getBytes() returns the internal array without copying.
* Route UUID through the normal dispatch in DistinctCountBitmap
Replaces the three `if (dataType == DataType.UUID)` early-returns with a
narrowed guard on the serialized-bitmap branch:
if (storedType == DataType.BYTES && dataType != DataType.UUID)
UUID now falls through to aggregateSV/aggregateMV and the group-by variants
like any other type, handled by a `case BYTES` in each switch. That fixes MV
UUID, which the early-returns silently broke -- they called
getBytesValuesSV()
above the isSingleValue() dispatch, so an MV column took the SV accessor.
Seven switches needed the case, not six: the dictionary path in
convertToValueBitmap also throws on BYTES, which the integration test
caught --
DISTINCTCOUNTBITMAP(uuidColumn) failed with "Illegal data type for
DISTINCT_COUNT_BITMAP aggregation function: BYTES" until it was added.
DistinctCountHLL, DistinctCountHLLPlus, DistinctCountCPCSketch and
DistinctCountULL still use the early-return shape and need the same
treatment.
* Route UUID through the normal dispatch in DistinctCountHLL
Same shape as DistinctCountBitmap: the three `if (dataType ==
DataType.UUID)`
early-returns are gone, the serialized-HLL guard is narrowed to
if (storedType == DataType.BYTES && dataType != DataType.UUID)
and UUID falls through to aggregateSV/aggregateMV and the group-by variants,
handled by a `case BYTES` in each of the six switches. This fixes MV UUID,
which the early-returns broke by calling getBytesValuesSV() above the
isSingleValue() dispatch.
No seventh switch here: unlike DistinctCountBitmap, HLL's dictionary path
lives
in AggregationFunctionUtils#getDistinctCountHLLResult, which already
excludes
UUID from the serialized branch so it falls through to the scalar path.
* Route UUID through the normal dispatch in DistinctCountHLLPlus and ULL
Same shape as Bitmap and HLL: the three `if (dataType == DataType.UUID)`
early-returns are removed, each serialized-sketch guard is narrowed to
if (storedType == DataType.BYTES && dataType != DataType.UUID)
and UUID reaches the ordinary dispatch via a `case BYTES` in each switch.
HLLPlus has 6 switches (SV/MV x aggregate, groupBySV, groupByMV). ULL has 3:
it is single-value only, so all of its switches read getStringValuesSV and
the
BYTES cases mirror that -- no MV variants to add.
Both dictionary paths live in AggregationFunctionUtils, which already
excludes
UUID from the serialized branch, so neither needed a seventh switch.
* Read UUID as bytes in DistinctCountThetaSketch
extractValues no longer special-cases UUID: the block that materialized a
String[] / String[][] of hex renderings is gone, so a UUID column now
reports
its stored type (BYTES) and carries the raw byte[][] like any bytes column.
All the handling moved into the aggregateXXX methods. Each of the three
dispatch sites now reads the logical type from the block val set it already
has
and narrows its guard, so UUID takes the scalar path rather than the
serialized-sketch path, and a "case BYTES" in each of the six scalar
switches
feeds the stored bytes straight to UpdatableThetaSketch#update(byte[]) -- no
String allocated anywhere.
Same shape as Bitmap, HLL, HLLPlus and ULL. This also fixes MV UUID here for
the same reason: the work now happens inside the SV and MV branches instead
of
above them.
* Leave extractValues untouched in DistinctCountThetaSketch
The length parameter was only needed to size the hex String arrays that the
UUID block used to build. With that block gone, length was unused in the
body,
so the signature change and its three call sites were dead weight.
extractValues is now byte-identical to master: all UUID handling lives in
the
aggregateXXX methods.
* Route UUID through the normal dispatch in DistinctCountCPCSketch
Last of the six. Same shape as the others: the UUID early-returns are gone,
the
serialized-sketch guard is narrowed with `&& dataType != DataType.UUID`,
and a
`case BYTES` in each switch feeds the stored bytes to CpcSketch#update.
This one had 2 UUID blocks and 1 BYTES guard rather than 3 and 3 -- the
counts
differ per function, so they were verified rather than assumed.
No `if (dataType == DataType.UUID)` early-return remains anywhere in the
aggregation function package.
* Fix UUID aggregation dispatch for CPC and Theta sketches
Route UUID values through the single-value and multi-value switch branches
for aggregate and group-by paths while preserving serialized BYTES sketch
handling.
Add unit and ingestion/query coverage for UUID and multi-value serialized
sketches.
* Address UUID aggregation review feedback
* Clarify serialized aggregation BYTES handling
* Restore legacy serialized BYTES dispatch
* Simplify UUID aggregation coverage
* Address UUID aggregation review comments
---
.../function/AggregationFunctionUtils.java | 40 ++--
.../DistinctCountBitmapAggregationFunction.java | 76 +++++++-
.../DistinctCountCPCSketchAggregationFunction.java | 164 ++++++++++++++--
.../DistinctCountHLLAggregationFunction.java | 71 ++++++-
.../DistinctCountHLLPlusAggregationFunction.java | 71 ++++++-
...istinctCountThetaSketchAggregationFunction.java | 158 +++++++++++++--
.../DistinctCountULLAggregationFunction.java | 44 ++++-
.../query/distinct/table/BytesDistinctTable.java | 28 ++-
.../core/query/reduce/GroupByDataTableReducer.java | 3 +
.../tests/custom/UuidAggregationTest.java | 212 +++++++++++++++++++++
10 files changed, 783 insertions(+), 84 deletions(-)
diff --git
a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/AggregationFunctionUtils.java
b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/AggregationFunctionUtils.java
index 527b7817621..e4071beaa47 100644
---
a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/AggregationFunctionUtils.java
+++
b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/AggregationFunctionUtils.java
@@ -69,6 +69,7 @@ import org.apache.pinot.segment.local.utils.UltraLogLogUtils;
import org.apache.pinot.segment.spi.AggregationFunctionType;
import org.apache.pinot.segment.spi.SegmentContext;
import org.apache.pinot.segment.spi.datasource.DataSource;
+import org.apache.pinot.segment.spi.datasource.DataSourceMetadata;
import org.apache.pinot.segment.spi.index.reader.Dictionary;
import
org.apache.pinot.segment.spi.index.startree.AggregationFunctionColumnPair;
import org.apache.pinot.spi.data.FieldSpec;
@@ -610,23 +611,23 @@ public class AggregationFunctionUtils {
break;
case DISTINCTCOUNTHLL:
case DISTINCTCOUNTHLLMV:
- result =
getDistinctCountHLLResult(Objects.requireNonNull(dataSource.getDictionary()),
+ result = getDistinctCountHLLResult(dataSource,
(DistinctCountHLLAggregationFunction) aggregationFunction,
explainPlanName);
break;
case DISTINCTCOUNTRAWHLL:
case DISTINCTCOUNTRAWHLLMV:
- result =
getDistinctCountHLLResult(Objects.requireNonNull(dataSource.getDictionary()),
+ result = getDistinctCountHLLResult(dataSource,
((DistinctCountRawHLLAggregationFunction)
aggregationFunction).getDistinctCountHLLAggregationFunction(),
explainPlanName);
break;
case DISTINCTCOUNTHLLPLUS:
case DISTINCTCOUNTHLLPLUSMV:
- result =
getDistinctCountHLLPlusResult(Objects.requireNonNull(dataSource.getDictionary()),
+ result = getDistinctCountHLLPlusResult(dataSource,
(DistinctCountHLLPlusAggregationFunction) aggregationFunction,
explainPlanName);
break;
case DISTINCTCOUNTRAWHLLPLUS:
case DISTINCTCOUNTRAWHLLPLUSMV:
- result =
getDistinctCountHLLPlusResult(Objects.requireNonNull(dataSource.getDictionary()),
+ result = getDistinctCountHLLPlusResult(dataSource,
((DistinctCountRawHLLPlusAggregationFunction) aggregationFunction)
.getDistinctCountHLLPlusAggregationFunction(),
explainPlanName);
break;
@@ -642,7 +643,7 @@ public class AggregationFunctionUtils {
(DistinctCountSmartHLLPlusAggregationFunction)
aggregationFunction, explainPlanName);
break;
case DISTINCTCOUNTULL:
- result =
getDistinctCountULLResult(Objects.requireNonNull(dataSource.getDictionary()),
+ result = getDistinctCountULLResult(dataSource,
(DistinctCountULLAggregationFunction) aggregationFunction,
explainPlanName);
break;
case DISTINCTCOUNTSMARTULL:
@@ -650,7 +651,7 @@ public class AggregationFunctionUtils {
(DistinctCountSmartULLAggregationFunction) aggregationFunction,
explainPlanName);
break;
case DISTINCTCOUNTRAWULL:
- result =
getDistinctCountULLResult(Objects.requireNonNull(dataSource.getDictionary()),
+ result = getDistinctCountULLResult(dataSource,
(DistinctCountULLAggregationFunction) aggregationFunction,
explainPlanName);
break;
default:
@@ -799,10 +800,13 @@ public class AggregationFunctionUtils {
return hllPlus;
}
- private static HyperLogLog getDistinctCountHLLResult(Dictionary dictionary,
+ private static HyperLogLog getDistinctCountHLLResult(DataSource dataSource,
DistinctCountHLLAggregationFunction function, String explainPlanName) {
- if (dictionary.getValueType() == FieldSpec.DataType.BYTES) {
- // Treat BYTES value as serialized HyperLogLog
+ Dictionary dictionary = dataSource.getDictionary();
+ assert dictionary != null;
+ DataSourceMetadata metadata = dataSource.getDataSourceMetadata();
+ if (metadata.getDataType() == FieldSpec.DataType.BYTES) {
+ // Logical BYTES dictionary entries are serialized HyperLogLog objects.
try {
QueryThreadContext.checkTerminationAndSampleUsage(explainPlanName);
HyperLogLog hll =
ObjectSerDeUtils.HYPER_LOG_LOG_SER_DE.deserialize(dictionary.getBytesValue(0));
@@ -820,10 +824,13 @@ public class AggregationFunctionUtils {
}
}
- private static HyperLogLogPlus getDistinctCountHLLPlusResult(Dictionary
dictionary,
+ private static HyperLogLogPlus getDistinctCountHLLPlusResult(DataSource
dataSource,
DistinctCountHLLPlusAggregationFunction function, String
explainPlanName) {
- if (dictionary.getValueType() == FieldSpec.DataType.BYTES) {
- // Treat BYTES value as serialized HyperLogLogPlus
+ Dictionary dictionary = dataSource.getDictionary();
+ assert dictionary != null;
+ DataSourceMetadata metadata = dataSource.getDataSourceMetadata();
+ if (metadata.getDataType() == FieldSpec.DataType.BYTES) {
+ // Logical BYTES dictionary entries are serialized HyperLogLogPlus
objects.
try {
QueryThreadContext.checkTerminationAndSampleUsage(explainPlanName);
HyperLogLogPlus hllplus =
ObjectSerDeUtils.HYPER_LOG_LOG_PLUS_SER_DE.deserialize(dictionary.getBytesValue(0));
@@ -861,10 +868,13 @@ public class AggregationFunctionUtils {
}
}
- private static UltraLogLog getDistinctCountULLResult(Dictionary dictionary,
+ private static UltraLogLog getDistinctCountULLResult(DataSource dataSource,
DistinctCountULLAggregationFunction function, String explainPlanName) {
- if (dictionary.getValueType() == FieldSpec.DataType.BYTES) {
- // Treat BYTES value as serialized UltraLogLog and merge
+ Dictionary dictionary = dataSource.getDictionary();
+ assert dictionary != null;
+ DataSourceMetadata metadata = dataSource.getDataSourceMetadata();
+ if (metadata.getDataType() == FieldSpec.DataType.BYTES) {
+ // Logical BYTES dictionary entries are serialized UltraLogLog objects.
try {
QueryThreadContext.checkTerminationAndSampleUsage(explainPlanName);
UltraLogLog ull =
ObjectSerDeUtils.ULTRA_LOG_LOG_OBJECT_SER_DE.deserialize(dictionary.getBytesValue(0));
diff --git
a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/DistinctCountBitmapAggregationFunction.java
b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/DistinctCountBitmapAggregationFunction.java
index 4fe96b819db..e71632673b2 100644
---
a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/DistinctCountBitmapAggregationFunction.java
+++
b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/DistinctCountBitmapAggregationFunction.java
@@ -18,6 +18,7 @@
*/
package org.apache.pinot.core.query.aggregation.function;
+import java.util.Arrays;
import java.util.List;
import java.util.Map;
import javax.annotation.Nullable;
@@ -71,9 +72,9 @@ public class DistinctCountBitmapAggregationFunction extends
BaseSingleInputAggre
Map<ExpressionContext, BlockValSet> blockValSetMap) {
BlockValSet blockValSet = blockValSetMap.get(_expression);
- // Treat BYTES value as serialized RoaringBitmap
- DataType storedType = blockValSet.getValueType().getStoredType();
- if (storedType == DataType.BYTES) {
+ DataType dataType = blockValSet.getValueType();
+ if (dataType == DataType.BYTES) {
+ // Logical BYTES is a serialized RoaringBitmap and always uses the
single-value representation.
byte[][] bytesValues = blockValSet.getBytesValuesSV();
RoaringBitmap valueBitmap = aggregationResultHolder.getResult();
if (valueBitmap != null) {
@@ -90,6 +91,8 @@ public class DistinctCountBitmapAggregationFunction extends
BaseSingleInputAggre
return;
}
+ DataType storedType = dataType.getStoredType();
+
if (blockValSet.isSingleValue()) {
aggregateSV(length, aggregationResultHolder, blockValSet, storedType);
} else {
@@ -138,6 +141,12 @@ public class DistinctCountBitmapAggregationFunction
extends BaseSingleInputAggre
valueBitmap.add(stringValues[i].hashCode());
}
break;
+ case BYTES:
+ byte[][] bytesValues = blockValSet.getBytesValuesSV();
+ for (int i = 0; i < length; i++) {
+ valueBitmap.add(Arrays.hashCode(bytesValues[i]));
+ }
+ break;
default:
throw new IllegalStateException(
"Illegal data type for DISTINCT_COUNT_BITMAP aggregation function:
" + storedType);
@@ -198,6 +207,14 @@ public class DistinctCountBitmapAggregationFunction
extends BaseSingleInputAggre
}
}
break;
+ case BYTES:
+ byte[][][] bytesValues = blockValSet.getBytesValuesMV();
+ for (int i = 0; i < length; i++) {
+ for (byte[] value : bytesValues[i]) {
+ valueBitmap.add(Arrays.hashCode(value));
+ }
+ }
+ break;
default:
throw new IllegalStateException(
"Illegal data type for DISTINCT_COUNT_BITMAP aggregation function:
" + storedType);
@@ -209,9 +226,9 @@ public class DistinctCountBitmapAggregationFunction extends
BaseSingleInputAggre
Map<ExpressionContext, BlockValSet> blockValSetMap) {
BlockValSet blockValSet = blockValSetMap.get(_expression);
- // Treat BYTES value as serialized RoaringBitmap
- DataType storedType = blockValSet.getValueType().getStoredType();
- if (storedType == DataType.BYTES) {
+ DataType dataType = blockValSet.getValueType();
+ if (dataType == DataType.BYTES) {
+ // Logical BYTES is a serialized RoaringBitmap and always uses the
single-value representation.
byte[][] bytesValues = blockValSet.getBytesValuesSV();
for (int i = 0; i < length; i++) {
RoaringBitmap value = RoaringBitmapUtils.deserialize(bytesValues[i]);
@@ -226,6 +243,8 @@ public class DistinctCountBitmapAggregationFunction extends
BaseSingleInputAggre
return;
}
+ DataType storedType = dataType.getStoredType();
+
if (blockValSet.isSingleValue()) {
aggregateSVGroupBySV(length, groupKeyArray, groupByResultHolder,
blockValSet, storedType);
} else {
@@ -277,6 +296,12 @@ public class DistinctCountBitmapAggregationFunction
extends BaseSingleInputAggre
getValueBitmap(groupByResultHolder,
groupKeyArray[i]).add(stringValues[i].hashCode());
}
break;
+ case BYTES:
+ byte[][] bytesValues = blockValSet.getBytesValuesSV();
+ for (int i = 0; i < length; i++) {
+ getValueBitmap(groupByResultHolder,
groupKeyArray[i]).add(Arrays.hashCode(bytesValues[i]));
+ }
+ break;
default:
throw new IllegalStateException(
"Illegal data type for DISTINCT_COUNT_BITMAP aggregation function:
" + storedType);
@@ -339,6 +364,15 @@ public class DistinctCountBitmapAggregationFunction
extends BaseSingleInputAggre
}
}
break;
+ case BYTES:
+ byte[][][] bytesValues = blockValSet.getBytesValuesMV();
+ for (int i = 0; i < length; i++) {
+ RoaringBitmap bitmap = getValueBitmap(groupByResultHolder,
groupKeyArray[i]);
+ for (byte[] value : bytesValues[i]) {
+ bitmap.add(Arrays.hashCode(value));
+ }
+ }
+ break;
default:
throw new IllegalStateException(
"Illegal data type for DISTINCT_COUNT_BITMAP aggregation function:
" + storedType);
@@ -350,9 +384,9 @@ public class DistinctCountBitmapAggregationFunction extends
BaseSingleInputAggre
Map<ExpressionContext, BlockValSet> blockValSetMap) {
BlockValSet blockValSet = blockValSetMap.get(_expression);
- // Treat BYTES value as serialized RoaringBitmap
- DataType storedType = blockValSet.getValueType().getStoredType();
- if (storedType == DataType.BYTES) {
+ DataType dataType = blockValSet.getValueType();
+ if (dataType == DataType.BYTES) {
+ // Logical BYTES is a serialized RoaringBitmap and always uses the
single-value representation.
byte[][] bytesValues = blockValSet.getBytesValuesSV();
for (int i = 0; i < length; i++) {
RoaringBitmap value = RoaringBitmapUtils.deserialize(bytesValues[i]);
@@ -369,6 +403,8 @@ public class DistinctCountBitmapAggregationFunction extends
BaseSingleInputAggre
return;
}
+ DataType storedType = dataType.getStoredType();
+
if (blockValSet.isSingleValue()) {
aggregateSVGroupByMV(length, groupKeysArray, groupByResultHolder,
blockValSet, storedType);
} else {
@@ -420,6 +456,12 @@ public class DistinctCountBitmapAggregationFunction
extends BaseSingleInputAggre
setValueForGroupKeys(groupByResultHolder, groupKeysArray[i],
stringValues[i].hashCode());
}
break;
+ case BYTES:
+ byte[][] bytesValues = blockValSet.getBytesValuesSV();
+ for (int i = 0; i < length; i++) {
+ setValueForGroupKeys(groupByResultHolder, groupKeysArray[i],
Arrays.hashCode(bytesValues[i]));
+ }
+ break;
default:
throw new IllegalStateException(
"Illegal data type for DISTINCT_COUNT_BITMAP aggregation function:
" + storedType);
@@ -494,6 +536,17 @@ public class DistinctCountBitmapAggregationFunction
extends BaseSingleInputAggre
}
}
break;
+ case BYTES:
+ byte[][][] bytesValues = blockValSet.getBytesValuesMV();
+ for (int i = 0; i < length; i++) {
+ for (int groupKey : groupKeysArray[i]) {
+ RoaringBitmap bitmap = getValueBitmap(groupByResultHolder,
groupKey);
+ for (byte[] value : bytesValues[i]) {
+ bitmap.add(Arrays.hashCode(value));
+ }
+ }
+ }
+ break;
default:
throw new IllegalStateException(
"Illegal data type for DISTINCT_COUNT_BITMAP aggregation function:
" + storedType);
@@ -660,6 +713,11 @@ public class DistinctCountBitmapAggregationFunction
extends BaseSingleInputAggre
valueBitmap.add(dictionary.getStringValue(iterator.next()).hashCode());
}
break;
+ case BYTES:
+ while (iterator.hasNext()) {
+
valueBitmap.add(Arrays.hashCode(dictionary.getBytesValue(iterator.next())));
+ }
+ break;
default:
throw new IllegalStateException(
"Illegal data type for DISTINCT_COUNT_BITMAP aggregation function:
" + storedType);
diff --git
a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/DistinctCountCPCSketchAggregationFunction.java
b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/DistinctCountCPCSketchAggregationFunction.java
index ec273c066ab..8194222fb69 100644
---
a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/DistinctCountCPCSketchAggregationFunction.java
+++
b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/DistinctCountCPCSketchAggregationFunction.java
@@ -135,9 +135,9 @@ public class DistinctCountCPCSketchAggregationFunction
Map<ExpressionContext, BlockValSet> blockValSetMap) {
BlockValSet blockValSet = blockValSetMap.get(_expression);
- // Treat BYTES value as serialized CPC Sketch
- FieldSpec.DataType storedType = blockValSet.getValueType().getStoredType();
- if (storedType == DataType.BYTES) {
+ DataType dataType = blockValSet.getValueType();
+ if (dataType == DataType.BYTES) {
+ // Logical BYTES stores serialized CpcSketch objects in the single-value
representation.
byte[][] bytesValues = blockValSet.getBytesValuesSV();
try {
CpcSketchAccumulator cpcSketchAccumulator =
getAccumulator(aggregationResultHolder);
@@ -153,6 +153,16 @@ public class DistinctCountCPCSketchAggregationFunction
return;
}
+ DataType storedType = dataType.getStoredType();
+ if (blockValSet.isSingleValue()) {
+ aggregateSV(length, aggregationResultHolder, blockValSet, storedType);
+ } else {
+ aggregateMV(length, aggregationResultHolder, blockValSet, storedType);
+ }
+ }
+
+ protected void aggregateSV(int length, AggregationResultHolder
aggregationResultHolder, BlockValSet blockValSet,
+ DataType storedType) {
// For dictionary-encoded expression, store dictionary ids into the bitmap
Dictionary dictionary = blockValSet.isDictionaryEncoded() ?
blockValSet.getDictionary() : null;
if (dictionary != null) {
@@ -194,11 +204,44 @@ public class DistinctCountCPCSketchAggregationFunction
cpcSketch.update(stringValues[i]);
}
break;
+ case BYTES:
+ byte[][] bytesValues = blockValSet.getBytesValuesSV();
+ for (int i = 0; i < length; i++) {
+ cpcSketch.update(bytesValues[i]);
+ }
+ break;
+ default:
+ throw new IllegalStateException("Illegal data type for
DISTINCT_COUNT_CPC aggregation function: " + storedType);
+ }
+ }
+
+ protected void aggregateMV(int length, AggregationResultHolder
aggregationResultHolder, BlockValSet blockValSet,
+ DataType storedType) {
+ // For dictionary-encoded expression, store dictionary ids into the bitmap
+ Dictionary dictionary = blockValSet.isDictionaryEncoded() ?
blockValSet.getDictionary() : null;
+ if (dictionary != null) {
+ int[][] dictIds = blockValSet.getDictionaryIdsMV();
+ RoaringBitmap dictIdBitmap = getDictIdBitmap(aggregationResultHolder,
dictionary);
+ for (int i = 0; i < length; i++) {
+ dictIdBitmap.add(dictIds[i]);
+ }
+ return;
+ }
+
+ // For non-dictionary-encoded expression, store values into the CpcSketch
+ switch (storedType) {
+ case BYTES:
+ byte[][][] bytesValues = blockValSet.getBytesValuesMV();
+ CpcSketch cpcSketch = getCpcSketch(aggregationResultHolder);
+ for (int i = 0; i < length; i++) {
+ for (byte[] value : bytesValues[i]) {
+ cpcSketch.update(value);
+ }
+ }
+ break;
default:
throw new IllegalStateException("Illegal data type for
DISTINCT_COUNT_CPC aggregation function: " + storedType);
}
- CpcSketchAccumulator cpcSketchAccumulator =
getAccumulator(aggregationResultHolder);
- cpcSketchAccumulator.apply(cpcSketch);
}
@Override
@@ -206,9 +249,9 @@ public class DistinctCountCPCSketchAggregationFunction
Map<ExpressionContext, BlockValSet> blockValSetMap) {
BlockValSet blockValSet = blockValSetMap.get(_expression);
- // Treat BYTES value as serialized CPC Sketch
- DataType storedType = blockValSet.getValueType().getStoredType();
- if (storedType == FieldSpec.DataType.BYTES) {
+ DataType dataType = blockValSet.getValueType();
+ if (dataType == DataType.BYTES) {
+ // Logical BYTES stores serialized CpcSketch objects in the single-value
representation.
byte[][] bytesValues = blockValSet.getBytesValuesSV();
try {
CpcSketch[] sketches = deserializeSketches(bytesValues, length);
@@ -225,6 +268,16 @@ public class DistinctCountCPCSketchAggregationFunction
return;
}
+ DataType storedType = dataType.getStoredType();
+ if (blockValSet.isSingleValue()) {
+ aggregateSVGroupBySV(length, groupKeyArray, groupByResultHolder,
blockValSet, storedType);
+ } else {
+ aggregateMVGroupBySV(length, groupKeyArray, groupByResultHolder,
blockValSet, storedType);
+ }
+ }
+
+ protected void aggregateSVGroupBySV(int length, int[] groupKeyArray,
GroupByResultHolder groupByResultHolder,
+ BlockValSet blockValSet, DataType storedType) {
// For dictionary-encoded expression, store dictionary ids into the bitmap
Dictionary dictionary = blockValSet.isDictionaryEncoded() ?
blockValSet.getDictionary() : null;
if (dictionary != null) {
@@ -267,6 +320,40 @@ public class DistinctCountCPCSketchAggregationFunction
getCpcSketch(groupByResultHolder,
groupKeyArray[i]).update(stringValues[i]);
}
break;
+ case BYTES:
+ byte[][] bytesValues = blockValSet.getBytesValuesSV();
+ for (int i = 0; i < length; i++) {
+ getCpcSketch(groupByResultHolder,
groupKeyArray[i]).update(bytesValues[i]);
+ }
+ break;
+ default:
+ throw new IllegalStateException("Illegal data type for
DISTINCT_COUNT_CPC aggregation function: " + storedType);
+ }
+ }
+
+ protected void aggregateMVGroupBySV(int length, int[] groupKeyArray,
GroupByResultHolder groupByResultHolder,
+ BlockValSet blockValSet, DataType storedType) {
+ // For dictionary-encoded expression, store dictionary ids into the bitmap
+ Dictionary dictionary = blockValSet.isDictionaryEncoded() ?
blockValSet.getDictionary() : null;
+ if (dictionary != null) {
+ int[][] dictIds = blockValSet.getDictionaryIdsMV();
+ for (int i = 0; i < length; i++) {
+ getDictIdBitmap(groupByResultHolder, groupKeyArray[i],
dictionary).add(dictIds[i]);
+ }
+ return;
+ }
+
+ // For non-dictionary-encoded expression, store values into the CpcSketch
+ switch (storedType) {
+ case BYTES:
+ byte[][][] bytesValues = blockValSet.getBytesValuesMV();
+ for (int i = 0; i < length; i++) {
+ CpcSketch cpcSketch = getCpcSketch(groupByResultHolder,
groupKeyArray[i]);
+ for (byte[] value : bytesValues[i]) {
+ cpcSketch.update(value);
+ }
+ }
+ break;
default:
throw new IllegalStateException("Illegal data type for
DISTINCT_COUNT_CPC aggregation function: " + storedType);
}
@@ -277,11 +364,9 @@ public class DistinctCountCPCSketchAggregationFunction
Map<ExpressionContext, BlockValSet> blockValSetMap) {
BlockValSet blockValSet = blockValSetMap.get(_expression);
- // Treat BYTES value as serialized CPC Sketch
- DataType storedType = blockValSet.getValueType().getStoredType();
- boolean singleValue = blockValSet.isSingleValue();
-
- if (singleValue && storedType == DataType.BYTES) {
+ DataType dataType = blockValSet.getValueType();
+ if (dataType == DataType.BYTES) {
+ // Logical BYTES stores serialized CpcSketch objects in the single-value
representation.
byte[][] bytesValues = blockValSet.getBytesValuesSV();
try {
CpcSketch[] sketches = deserializeSketches(bytesValues, length);
@@ -298,6 +383,16 @@ public class DistinctCountCPCSketchAggregationFunction
return;
}
+ DataType storedType = dataType.getStoredType();
+ if (blockValSet.isSingleValue()) {
+ aggregateSVGroupByMV(length, groupKeysArray, groupByResultHolder,
blockValSet, storedType);
+ } else {
+ aggregateMVGroupByMV(length, groupKeysArray, groupByResultHolder,
blockValSet, storedType);
+ }
+ }
+
+ protected void aggregateSVGroupByMV(int length, int[][] groupKeysArray,
GroupByResultHolder groupByResultHolder,
+ BlockValSet blockValSet, DataType storedType) {
// For dictionary-encoded expression, store dictionary ids into the bitmap
Dictionary dictionary = blockValSet.isDictionaryEncoded() ?
blockValSet.getDictionary() : null;
if (dictionary != null) {
@@ -350,6 +445,47 @@ public class DistinctCountCPCSketchAggregationFunction
}
}
break;
+ case BYTES:
+ byte[][] bytesValues = blockValSet.getBytesValuesSV();
+ for (int i = 0; i < length; i++) {
+ for (int groupKey : groupKeysArray[i]) {
+ getCpcSketch(groupByResultHolder, groupKey).update(bytesValues[i]);
+ }
+ }
+ break;
+ default:
+ throw new IllegalStateException("Illegal data type for
DISTINCT_COUNT_CPC aggregation function: " + storedType);
+ }
+ }
+
+ protected void aggregateMVGroupByMV(int length, int[][] groupKeysArray,
GroupByResultHolder groupByResultHolder,
+ BlockValSet blockValSet, DataType storedType) {
+ // For dictionary-encoded expression, store dictionary ids into the bitmap
+ Dictionary dictionary = blockValSet.isDictionaryEncoded() ?
blockValSet.getDictionary() : null;
+ if (dictionary != null) {
+ int[][] dictIds = blockValSet.getDictionaryIdsMV();
+ for (int i = 0; i < length; i++) {
+ int[] rowDictIds = dictIds[i];
+ for (int groupKey : groupKeysArray[i]) {
+ getDictIdBitmap(groupByResultHolder, groupKey,
dictionary).add(rowDictIds);
+ }
+ }
+ return;
+ }
+
+ // For non-dictionary-encoded expression, store values into the CpcSketch
+ switch (storedType) {
+ case BYTES:
+ byte[][][] bytesValues = blockValSet.getBytesValuesMV();
+ for (int i = 0; i < length; i++) {
+ for (int groupKey : groupKeysArray[i]) {
+ CpcSketch cpcSketch = getCpcSketch(groupByResultHolder, groupKey);
+ for (byte[] value : bytesValues[i]) {
+ cpcSketch.update(value);
+ }
+ }
+ }
+ break;
default:
throw new IllegalStateException("Illegal data type for
DISTINCT_COUNT_CPC aggregation function: " + storedType);
}
@@ -524,6 +660,8 @@ public class DistinctCountCPCSketchAggregationFunction
private void addObjectToSketch(Object rawValue, CpcSketch sketch) {
if (rawValue instanceof String) {
sketch.update((String) rawValue);
+ } else if (rawValue instanceof byte[]) {
+ sketch.update((byte[]) rawValue);
} else if (rawValue instanceof Integer) {
sketch.update((Integer) rawValue);
} else if (rawValue instanceof Long) {
diff --git
a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/DistinctCountHLLAggregationFunction.java
b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/DistinctCountHLLAggregationFunction.java
index 07bfb6bd41a..1d68cde5958 100644
---
a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/DistinctCountHLLAggregationFunction.java
+++
b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/DistinctCountHLLAggregationFunction.java
@@ -81,9 +81,9 @@ public class DistinctCountHLLAggregationFunction extends
BaseSingleInputAggregat
Map<ExpressionContext, BlockValSet> blockValSetMap) {
BlockValSet blockValSet = blockValSetMap.get(_expression);
- // Treat BYTES value as serialized HyperLogLog
- DataType storedType = blockValSet.getValueType().getStoredType();
- if (storedType == DataType.BYTES) {
+ DataType dataType = blockValSet.getValueType();
+ if (dataType == DataType.BYTES) {
+ // Logical BYTES is a serialized HyperLogLog and always uses the
single-value representation.
byte[][] bytesValues = blockValSet.getBytesValuesSV();
try {
HyperLogLog hyperLogLog = aggregationResultHolder.getResult();
@@ -104,6 +104,8 @@ public class DistinctCountHLLAggregationFunction extends
BaseSingleInputAggregat
return;
}
+ DataType storedType = dataType.getStoredType();
+
if (blockValSet.isSingleValue()) {
aggregateSV(length, aggregationResultHolder, blockValSet, storedType);
} else {
@@ -159,6 +161,12 @@ public class DistinctCountHLLAggregationFunction extends
BaseSingleInputAggregat
hyperLogLog.offer(stringValues[i]);
}
break;
+ case BYTES:
+ byte[][] bytesValues = blockValSet.getBytesValuesSV();
+ for (int i = 0; i < length; i++) {
+ hyperLogLog.offer(bytesValues[i]);
+ }
+ break;
default:
throw new IllegalStateException("Illegal data type for
DISTINCT_COUNT_HLL aggregation function: " + storedType);
}
@@ -222,6 +230,14 @@ public class DistinctCountHLLAggregationFunction extends
BaseSingleInputAggregat
}
}
break;
+ case BYTES:
+ byte[][][] bytesValuesArray = blockValSet.getBytesValuesMV();
+ for (int i = 0; i < length; i++) {
+ for (byte[] value : bytesValuesArray[i]) {
+ hyperLogLog.offer(value);
+ }
+ }
+ break;
default:
throw new IllegalStateException("Illegal data type for
DISTINCT_COUNT_HLL aggregation function: " + storedType);
}
@@ -232,9 +248,9 @@ public class DistinctCountHLLAggregationFunction extends
BaseSingleInputAggregat
Map<ExpressionContext, BlockValSet> blockValSetMap) {
BlockValSet blockValSet = blockValSetMap.get(_expression);
- // Treat BYTES value as serialized HyperLogLog
- DataType storedType = blockValSet.getValueType().getStoredType();
- if (storedType == DataType.BYTES) {
+ DataType dataType = blockValSet.getValueType();
+ if (dataType == DataType.BYTES) {
+ // Logical BYTES is a serialized HyperLogLog and always uses the
single-value representation.
byte[][] bytesValues = blockValSet.getBytesValuesSV();
try {
for (int i = 0; i < length; i++) {
@@ -253,6 +269,8 @@ public class DistinctCountHLLAggregationFunction extends
BaseSingleInputAggregat
return;
}
+ DataType storedType = dataType.getStoredType();
+
if (blockValSet.isSingleValue()) {
aggregateSVGroupBySV(length, groupKeyArray, groupByResultHolder,
blockValSet, storedType);
} else {
@@ -307,6 +325,12 @@ public class DistinctCountHLLAggregationFunction extends
BaseSingleInputAggregat
getHyperLogLog(groupByResultHolder,
groupKeyArray[i]).offer(stringValues[i]);
}
break;
+ case BYTES:
+ byte[][] bytesValues = blockValSet.getBytesValuesSV();
+ for (int i = 0; i < length; i++) {
+ getHyperLogLog(groupByResultHolder,
groupKeyArray[i]).offer(bytesValues[i]);
+ }
+ break;
default:
throw new IllegalStateException("Illegal data type for
DISTINCT_COUNT_HLL aggregation function: " + storedType);
}
@@ -371,6 +395,15 @@ public class DistinctCountHLLAggregationFunction extends
BaseSingleInputAggregat
}
}
break;
+ case BYTES:
+ byte[][][] bytesValuesArray = blockValSet.getBytesValuesMV();
+ for (int i = 0; i < length; i++) {
+ HyperLogLog hyperLogLog = getHyperLogLog(groupByResultHolder,
groupKeyArray[i]);
+ for (byte[] value : bytesValuesArray[i]) {
+ hyperLogLog.offer(value);
+ }
+ }
+ break;
default:
throw new IllegalStateException("Illegal data type for
DISTINCT_COUNT_HLL aggregation function: " + storedType);
}
@@ -381,9 +414,9 @@ public class DistinctCountHLLAggregationFunction extends
BaseSingleInputAggregat
Map<ExpressionContext, BlockValSet> blockValSetMap) {
BlockValSet blockValSet = blockValSetMap.get(_expression);
- // Treat BYTES value as serialized HyperLogLog
- DataType storedType = blockValSet.getValueType().getStoredType();
- if (storedType == DataType.BYTES) {
+ DataType dataType = blockValSet.getValueType();
+ if (dataType == DataType.BYTES) {
+ // Logical BYTES is a serialized HyperLogLog and always uses the
single-value representation.
byte[][] bytesValues = blockValSet.getBytesValuesSV();
try {
for (int i = 0; i < length; i++) {
@@ -405,6 +438,8 @@ public class DistinctCountHLLAggregationFunction extends
BaseSingleInputAggregat
return;
}
+ DataType storedType = dataType.getStoredType();
+
if (blockValSet.isSingleValue()) {
aggregateSVGroupByMV(length, groupKeysArray, groupByResultHolder,
blockValSet, storedType);
} else {
@@ -459,6 +494,12 @@ public class DistinctCountHLLAggregationFunction extends
BaseSingleInputAggregat
setValueForGroupKeys(groupByResultHolder, groupKeysArray[i],
stringValues[i]);
}
break;
+ case BYTES:
+ byte[][] bytesValues = blockValSet.getBytesValuesSV();
+ for (int i = 0; i < length; i++) {
+ setValueForGroupKeys(groupByResultHolder, groupKeysArray[i],
bytesValues[i]);
+ }
+ break;
default:
throw new IllegalStateException("Illegal data type for
DISTINCT_COUNT_HLL aggregation function: " + storedType);
}
@@ -541,6 +582,18 @@ public class DistinctCountHLLAggregationFunction extends
BaseSingleInputAggregat
}
}
break;
+ case BYTES:
+ byte[][][] bytesValuesArray = blockValSet.getBytesValuesMV();
+ for (int i = 0; i < length; i++) {
+ byte[][] bytesValues = bytesValuesArray[i];
+ for (int groupKey : groupKeysArray[i]) {
+ HyperLogLog hyperLogLog = getHyperLogLog(groupByResultHolder,
groupKey);
+ for (byte[] value : bytesValues) {
+ hyperLogLog.offer(value);
+ }
+ }
+ }
+ break;
default:
throw new IllegalStateException("Illegal data type for
DISTINCT_COUNT_HLL aggregation function: " + storedType);
}
diff --git
a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/DistinctCountHLLPlusAggregationFunction.java
b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/DistinctCountHLLPlusAggregationFunction.java
index fd337f433a3..dfda4ed0bc7 100644
---
a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/DistinctCountHLLPlusAggregationFunction.java
+++
b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/DistinctCountHLLPlusAggregationFunction.java
@@ -93,9 +93,9 @@ public class DistinctCountHLLPlusAggregationFunction extends
BaseSingleInputAggr
Map<ExpressionContext, BlockValSet> blockValSetMap) {
BlockValSet blockValSet = blockValSetMap.get(_expression);
- // Treat BYTES value as serialized HyperLogLogPlus
- DataType storedType = blockValSet.getValueType().getStoredType();
- if (storedType == DataType.BYTES) {
+ DataType dataType = blockValSet.getValueType();
+ if (dataType == DataType.BYTES) {
+ // Logical BYTES is a serialized HyperLogLogPlus and always uses the
single-value representation.
byte[][] bytesValues = blockValSet.getBytesValuesSV();
try {
HyperLogLogPlus hyperLogLogPlus = aggregationResultHolder.getResult();
@@ -114,6 +114,8 @@ public class DistinctCountHLLPlusAggregationFunction
extends BaseSingleInputAggr
return;
}
+ DataType storedType = dataType.getStoredType();
+
if (blockValSet.isSingleValue()) {
aggregateSV(length, aggregationResultHolder, blockValSet, storedType);
} else {
@@ -164,6 +166,12 @@ public class DistinctCountHLLPlusAggregationFunction
extends BaseSingleInputAggr
hyperLogLogPlus.offer(stringValues[i]);
}
break;
+ case BYTES:
+ byte[][] bytesValues = blockValSet.getBytesValuesSV();
+ for (int i = 0; i < length; i++) {
+ hyperLogLogPlus.offer(bytesValues[i]);
+ }
+ break;
default:
throw new IllegalStateException(
"Illegal data type for DISTINCT_COUNT_HLL_PLUS aggregation
function: " + storedType);
@@ -226,6 +234,14 @@ public class DistinctCountHLLPlusAggregationFunction
extends BaseSingleInputAggr
}
}
break;
+ case BYTES:
+ byte[][][] bytesValuesArray = blockValSet.getBytesValuesMV();
+ for (int i = 0; i < length; i++) {
+ for (byte[] value : bytesValuesArray[i]) {
+ hyperLogLogPlus.offer(value);
+ }
+ }
+ break;
default:
throw new IllegalStateException(
"Illegal data type for DISTINCT_COUNT_HLL_PLUS aggregation
function: " + storedType);
@@ -237,9 +253,9 @@ public class DistinctCountHLLPlusAggregationFunction
extends BaseSingleInputAggr
Map<ExpressionContext, BlockValSet> blockValSetMap) {
BlockValSet blockValSet = blockValSetMap.get(_expression);
- // Treat BYTES value as serialized HyperLogLogPlus
- DataType storedType = blockValSet.getValueType().getStoredType();
- if (storedType == DataType.BYTES) {
+ DataType dataType = blockValSet.getValueType();
+ if (dataType == DataType.BYTES) {
+ // Logical BYTES is a serialized HyperLogLogPlus and always uses the
single-value representation.
byte[][] bytesValues = blockValSet.getBytesValuesSV();
try {
for (int i = 0; i < length; i++) {
@@ -258,6 +274,8 @@ public class DistinctCountHLLPlusAggregationFunction
extends BaseSingleInputAggr
return;
}
+ DataType storedType = dataType.getStoredType();
+
if (blockValSet.isSingleValue()) {
aggregateSVGroupBySV(length, groupKeyArray, groupByResultHolder,
blockValSet, storedType);
} else {
@@ -309,6 +327,12 @@ public class DistinctCountHLLPlusAggregationFunction
extends BaseSingleInputAggr
getHyperLogLogPlus(groupByResultHolder,
groupKeyArray[i]).offer(stringValues[i]);
}
break;
+ case BYTES:
+ byte[][] bytesValues = blockValSet.getBytesValuesSV();
+ for (int i = 0; i < length; i++) {
+ getHyperLogLogPlus(groupByResultHolder,
groupKeyArray[i]).offer(bytesValues[i]);
+ }
+ break;
default:
throw new IllegalStateException(
"Illegal data type for DISTINCT_COUNT_HLL_PLUS aggregation
function: " + storedType);
@@ -374,6 +398,15 @@ public class DistinctCountHLLPlusAggregationFunction
extends BaseSingleInputAggr
}
}
break;
+ case BYTES:
+ byte[][][] bytesValuesArray = blockValSet.getBytesValuesMV();
+ for (int i = 0; i < length; i++) {
+ HyperLogLogPlus hyperLogLogPlus =
getHyperLogLogPlus(groupByResultHolder, groupKeyArray[i]);
+ for (byte[] value : bytesValuesArray[i]) {
+ hyperLogLogPlus.offer(value);
+ }
+ }
+ break;
default:
throw new IllegalStateException(
"Illegal data type for DISTINCT_COUNT_HLL_PLUS aggregation
function: " + storedType);
@@ -385,9 +418,9 @@ public class DistinctCountHLLPlusAggregationFunction
extends BaseSingleInputAggr
Map<ExpressionContext, BlockValSet> blockValSetMap) {
BlockValSet blockValSet = blockValSetMap.get(_expression);
- // Treat BYTES value as serialized HyperLogLogPlus
- DataType storedType = blockValSet.getValueType().getStoredType();
- if (storedType == DataType.BYTES) {
+ DataType dataType = blockValSet.getValueType();
+ if (dataType == DataType.BYTES) {
+ // Logical BYTES is a serialized HyperLogLogPlus and always uses the
single-value representation.
byte[][] bytesValues = blockValSet.getBytesValuesSV();
try {
for (int i = 0; i < length; i++) {
@@ -409,6 +442,8 @@ public class DistinctCountHLLPlusAggregationFunction
extends BaseSingleInputAggr
return;
}
+ DataType storedType = dataType.getStoredType();
+
if (blockValSet.isSingleValue()) {
aggregateSVGroupByMV(length, groupKeysArray, groupByResultHolder,
blockValSet, storedType);
} else {
@@ -460,6 +495,12 @@ public class DistinctCountHLLPlusAggregationFunction
extends BaseSingleInputAggr
setValueForGroupKeys(groupByResultHolder, groupKeysArray[i],
stringValues[i]);
}
break;
+ case BYTES:
+ byte[][] bytesValues = blockValSet.getBytesValuesSV();
+ for (int i = 0; i < length; i++) {
+ setValueForGroupKeys(groupByResultHolder, groupKeysArray[i],
bytesValues[i]);
+ }
+ break;
default:
throw new IllegalStateException(
"Illegal data type for DISTINCT_COUNT_HLL_PLUS aggregation
function: " + storedType);
@@ -542,6 +583,18 @@ public class DistinctCountHLLPlusAggregationFunction
extends BaseSingleInputAggr
}
}
break;
+ case BYTES:
+ byte[][][] bytesValuesArray = blockValSet.getBytesValuesMV();
+ for (int i = 0; i < length; i++) {
+ byte[][] bytesValues = bytesValuesArray[i];
+ for (int groupKey : groupKeysArray[i]) {
+ HyperLogLogPlus hyperLogLogPlus =
getHyperLogLogPlus(groupByResultHolder, groupKey);
+ for (byte[] value : bytesValues) {
+ hyperLogLogPlus.offer(value);
+ }
+ }
+ }
+ break;
default:
throw new IllegalStateException(
"Illegal data type for DISTINCT_COUNT_HLL_PLUS aggregation
function: " + storedType);
diff --git
a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/DistinctCountThetaSketchAggregationFunction.java
b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/DistinctCountThetaSketchAggregationFunction.java
index a2859fe471e..9118d766124 100644
---
a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/DistinctCountThetaSketchAggregationFunction.java
+++
b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/DistinctCountThetaSketchAggregationFunction.java
@@ -196,7 +196,7 @@ public class DistinctCountThetaSketchAggregationFunction
if (valueTypes[0] != DataType.BYTES) {
List<UpdatableThetaSketch> updateSketches =
getUpdateSketches(aggregationResultHolder);
if (singleValues[0]) {
- switch (valueTypes[0]) {
+ switch (valueTypes[0].getStoredType()) {
case INT:
int[] intValues = (int[]) valueArrays[0];
if (_includeDefaultSketch) {
@@ -287,13 +287,31 @@ public class DistinctCountThetaSketchAggregationFunction
}
}
break;
+ case BYTES:
+ byte[][] bytesValues = (byte[][]) valueArrays[0];
+ if (_includeDefaultSketch) {
+ UpdatableThetaSketch defaultSketch = updateSketches.get(0);
+ for (int i = 0; i < length; i++) {
+ defaultSketch.update(bytesValues[i]);
+ }
+ }
+ for (int i = 0; i < numFilters; i++) {
+ FilterEvaluator filterEvaluator = _filterEvaluators.get(i);
+ UpdatableThetaSketch updateSketch = updateSketches.get(i + 1);
+ for (int j = 0; j < length; j++) {
+ if (filterEvaluator.evaluate(singleValues, valueTypes,
valueArrays, j)) {
+ updateSketch.update(bytesValues[j]);
+ }
+ }
+ }
+ break;
default:
throw new IllegalStateException(
"Illegal single-value data type for
DISTINCT_COUNT_THETA_SKETCH aggregation function: "
+ valueTypes[0]);
}
} else {
- switch (valueTypes[0]) {
+ switch (valueTypes[0].getStoredType()) {
case INT:
int[][] intValues = (int[][]) valueArrays[0];
if (_includeDefaultSketch) {
@@ -404,13 +422,35 @@ public class DistinctCountThetaSketchAggregationFunction
}
}
break;
+ case BYTES:
+ byte[][][] bytesValues = (byte[][][]) valueArrays[0];
+ if (_includeDefaultSketch) {
+ UpdatableThetaSketch defaultSketch = updateSketches.get(0);
+ for (int i = 0; i < length; i++) {
+ for (byte[] value : bytesValues[i]) {
+ defaultSketch.update(value);
+ }
+ }
+ }
+ for (int i = 0; i < numFilters; i++) {
+ FilterEvaluator filterEvaluator = _filterEvaluators.get(i);
+ UpdatableThetaSketch updateSketch = updateSketches.get(i + 1);
+ for (int j = 0; j < length; j++) {
+ if (filterEvaluator.evaluate(singleValues, valueTypes,
valueArrays, j)) {
+ for (byte[] value : bytesValues[j]) {
+ updateSketch.update(value);
+ }
+ }
+ }
+ }
+ break;
default:
throw new IllegalStateException(
"Illegal multi-value data type for DISTINCT_COUNT_THETA_SKETCH
aggregation function: " + valueTypes[0]);
}
}
} else {
- // Serialized sketch
+ // Logical BYTES stores serialized ThetaSketch objects in the
single-value representation.
List<ThetaSketchAccumulator> thetaSketchAccumulators =
getUnions(aggregationResultHolder);
ThetaSketch[] sketches = deserializeSketches((byte[][]) valueArrays[0],
length);
if (_includeDefaultSketch) {
@@ -444,7 +484,7 @@ public class DistinctCountThetaSketchAggregationFunction
// Main expression is always index 0
if (valueTypes[0] != DataType.BYTES) {
if (singleValues[0]) {
- switch (valueTypes[0]) {
+ switch (valueTypes[0].getStoredType()) {
case INT:
int[] intValues = (int[]) valueArrays[0];
for (int i = 0; i < length; i++) {
@@ -520,13 +560,29 @@ public class DistinctCountThetaSketchAggregationFunction
}
}
break;
+ case BYTES:
+ byte[][] bytesValues = (byte[][]) valueArrays[0];
+ for (int i = 0; i < length; i++) {
+ List<UpdatableThetaSketch> updateSketches =
+ getUpdateSketches(groupByResultHolder, groupKeyArray[i]);
+ byte[] value = bytesValues[i];
+ if (_includeDefaultSketch) {
+ updateSketches.get(0).update(value);
+ }
+ for (int j = 0; j < numFilters; j++) {
+ if (_filterEvaluators.get(j).evaluate(singleValues,
valueTypes, valueArrays, i)) {
+ updateSketches.get(j + 1).update(value);
+ }
+ }
+ }
+ break;
default:
throw new IllegalStateException(
"Illegal single-value data type for
DISTINCT_COUNT_THETA_SKETCH aggregation function: "
+ valueTypes[0]);
}
} else {
- switch (valueTypes[0]) {
+ switch (valueTypes[0].getStoredType()) {
case INT:
int[][] intValues = (int[][]) valueArrays[0];
for (int i = 0; i < length; i++) {
@@ -632,13 +688,35 @@ public class DistinctCountThetaSketchAggregationFunction
}
}
break;
+ case BYTES:
+ byte[][][] bytesValues = (byte[][][]) valueArrays[0];
+ for (int i = 0; i < length; i++) {
+ List<UpdatableThetaSketch> updateSketches =
+ getUpdateSketches(groupByResultHolder, groupKeyArray[i]);
+ byte[][] values = bytesValues[i];
+ if (_includeDefaultSketch) {
+ UpdatableThetaSketch defaultSketch = updateSketches.get(0);
+ for (byte[] value : values) {
+ defaultSketch.update(value);
+ }
+ }
+ for (int j = 0; j < numFilters; j++) {
+ if (_filterEvaluators.get(j).evaluate(singleValues,
valueTypes, valueArrays, i)) {
+ UpdatableThetaSketch updateSketch = updateSketches.get(j +
1);
+ for (byte[] value : values) {
+ updateSketch.update(value);
+ }
+ }
+ }
+ }
+ break;
default:
throw new IllegalStateException(
"Illegal multi-value data type for DISTINCT_COUNT_THETA_SKETCH
aggregation function: " + valueTypes[0]);
}
}
} else {
- // Serialized sketch
+ // Logical BYTES stores serialized ThetaSketch objects in the
single-value representation.
ThetaSketch[] sketches = deserializeSketches((byte[][]) valueArrays[0],
length);
for (int i = 0; i < length; i++) {
List<ThetaSketchAccumulator> thetaSketchAccumulators =
getUnions(groupByResultHolder, groupKeyArray[i]);
@@ -668,7 +746,7 @@ public class DistinctCountThetaSketchAggregationFunction
// Main expression is always index 0
if (valueTypes[0] != DataType.BYTES) {
if (singleValues[0]) {
- switch (valueTypes[0]) {
+ switch (valueTypes[0].getStoredType()) {
case INT:
int[] intValues = (int[]) valueArrays[0];
if (_includeDefaultSketch) {
@@ -769,13 +847,33 @@ public class DistinctCountThetaSketchAggregationFunction
}
}
break;
+ case BYTES:
+ byte[][] bytesValues = (byte[][]) valueArrays[0];
+ if (_includeDefaultSketch) {
+ for (int i = 0; i < length; i++) {
+ for (int groupKey : groupKeysArray[i]) {
+ getUpdateSketches(groupByResultHolder,
groupKey).get(0).update(bytesValues[i]);
+ }
+ }
+ }
+ for (int i = 0; i < numFilters; i++) {
+ FilterEvaluator filterEvaluator = _filterEvaluators.get(i);
+ for (int j = 0; j < length; j++) {
+ if (filterEvaluator.evaluate(singleValues, valueTypes,
valueArrays, j)) {
+ for (int groupKey : groupKeysArray[j]) {
+ getUpdateSketches(groupByResultHolder, groupKey).get(i +
1).update(bytesValues[j]);
+ }
+ }
+ }
+ }
+ break;
default:
throw new IllegalStateException(
"Illegal single-value data type for
DISTINCT_COUNT_THETA_SKETCH aggregation function: "
+ valueTypes[0]);
}
} else {
- switch (valueTypes[0]) {
+ switch (valueTypes[0].getStoredType()) {
case INT:
int[][] intValues = (int[][]) valueArrays[0];
if (_includeDefaultSketch) {
@@ -906,13 +1004,40 @@ public class DistinctCountThetaSketchAggregationFunction
}
}
break;
+ case BYTES:
+ byte[][][] bytesValues = (byte[][][]) valueArrays[0];
+ if (_includeDefaultSketch) {
+ for (int i = 0; i < length; i++) {
+ for (int groupKey : groupKeysArray[i]) {
+ UpdatableThetaSketch defaultSketch =
getUpdateSketches(groupByResultHolder, groupKey).get(0);
+ for (byte[] value : bytesValues[i]) {
+ defaultSketch.update(value);
+ }
+ }
+ }
+ }
+ for (int i = 0; i < numFilters; i++) {
+ FilterEvaluator filterEvaluator = _filterEvaluators.get(i);
+ for (int j = 0; j < length; j++) {
+ if (filterEvaluator.evaluate(singleValues, valueTypes,
valueArrays, j)) {
+ for (int groupKey : groupKeysArray[j]) {
+ UpdatableThetaSketch updateSketch =
+ getUpdateSketches(groupByResultHolder, groupKey).get(i
+ 1);
+ for (byte[] value : bytesValues[j]) {
+ updateSketch.update(value);
+ }
+ }
+ }
+ }
+ }
+ break;
default:
throw new IllegalStateException(
"Illegal multi-value data type for DISTINCT_COUNT_THETA_SKETCH
aggregation function: " + valueTypes[0]);
}
}
} else {
- // Serialized sketch
+ // Logical BYTES stores serialized ThetaSketch objects in the
single-value representation.
ThetaSketch[] sketches = deserializeSketches((byte[][]) valueArrays[0],
length);
if (_includeDefaultSketch) {
for (int i = 0; i < length; i++) {
@@ -1232,9 +1357,10 @@ public class DistinctCountThetaSketchAggregationFunction
for (int i = 0; i < numExpressions; i++) {
BlockValSet blockValSet = blockValSetMap.get(_inputExpressions.get(i));
boolean singleValue = blockValSet.isSingleValue();
- DataType storedType = blockValSet.getValueType().getStoredType();
+ DataType dataType = blockValSet.getValueType();
+ DataType storedType = dataType.getStoredType();
singleValues[i] = singleValue;
- valueTypes[i] = storedType;
+ valueTypes[i] = dataType;
if (singleValue) {
switch (storedType) {
case INT:
@@ -1275,6 +1401,9 @@ public class DistinctCountThetaSketchAggregationFunction
case STRING:
valueArrays[i] = blockValSet.getStringValuesMV();
break;
+ case BYTES:
+ valueArrays[i] = blockValSet.getBytesValuesMV();
+ break;
default:
throw new IllegalStateException();
}
@@ -1513,7 +1642,7 @@ public class DistinctCountThetaSketchAggregationFunction
_predicateEvaluator =
PredicateEvaluatorProvider.getPredicateEvaluator(_predicate, null, valueType,
null);
}
if (singleValue) {
- switch (valueType) {
+ switch (valueType.getStoredType()) {
case INT:
return _predicateEvaluator.applySV(((int[]) valueArray)[index]);
case LONG:
@@ -1530,7 +1659,7 @@ public class DistinctCountThetaSketchAggregationFunction
throw new IllegalStateException();
}
} else {
- switch (valueType) {
+ switch (valueType.getStoredType()) {
case INT:
int[] intValues = ((int[][]) valueArray)[index];
return _predicateEvaluator.applyMV(intValues, intValues.length);
@@ -1546,6 +1675,9 @@ public class DistinctCountThetaSketchAggregationFunction
case STRING:
String[] stringValues = ((String[][]) valueArray)[index];
return _predicateEvaluator.applyMV(stringValues,
stringValues.length);
+ case BYTES:
+ byte[][] bytesValues = ((byte[][][]) valueArray)[index];
+ return _predicateEvaluator.applyMV(bytesValues,
bytesValues.length);
default:
throw new IllegalStateException();
}
diff --git
a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/DistinctCountULLAggregationFunction.java
b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/DistinctCountULLAggregationFunction.java
index 5309a9713af..7ef3142ddb5 100644
---
a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/DistinctCountULLAggregationFunction.java
+++
b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/DistinctCountULLAggregationFunction.java
@@ -82,9 +82,9 @@ public class DistinctCountULLAggregationFunction extends
BaseSingleInputAggregat
Map<ExpressionContext, BlockValSet> blockValSetMap) {
BlockValSet blockValSet = blockValSetMap.get(_expression);
- // Treat BYTES value as serialized HyperLogLogPlus
- DataType storedType = blockValSet.getValueType().getStoredType();
- if (storedType == DataType.BYTES) {
+ DataType dataType = blockValSet.getValueType();
+ if (dataType == DataType.BYTES) {
+ // Logical BYTES is a serialized UltraLogLog and always uses the
single-value representation.
byte[][] bytesValues = blockValSet.getBytesValuesSV();
try {
UltraLogLog ull = aggregationResultHolder.getResult();
@@ -103,6 +103,8 @@ public class DistinctCountULLAggregationFunction extends
BaseSingleInputAggregat
return;
}
+ DataType storedType = dataType.getStoredType();
+
// For dictionary-encoded expression, store dictionary ids into the bitmap
Dictionary dictionary = blockValSet.isDictionaryEncoded() ?
blockValSet.getDictionary() : null;
if (dictionary != null) {
@@ -144,6 +146,12 @@ public class DistinctCountULLAggregationFunction extends
BaseSingleInputAggregat
UltraLogLogUtils.hashObject(stringValues[i]).ifPresent(ull::add);
}
break;
+ case BYTES:
+ byte[][] bytesValues = blockValSet.getBytesValuesSV();
+ for (int i = 0; i < length; i++) {
+ UltraLogLogUtils.hashObject(bytesValues[i]).ifPresent(ull::add);
+ }
+ break;
default:
throw new IllegalStateException(
"Illegal data type for DISTINCT_COUNT_ULL aggregation function: "
+ storedType);
@@ -155,9 +163,9 @@ public class DistinctCountULLAggregationFunction extends
BaseSingleInputAggregat
Map<ExpressionContext, BlockValSet> blockValSetMap) {
BlockValSet blockValSet = blockValSetMap.get(_expression);
- // Treat BYTES value as serialized UltraLogLogs
- DataType storedType = blockValSet.getValueType().getStoredType();
- if (storedType == DataType.BYTES) {
+ DataType dataType = blockValSet.getValueType();
+ if (dataType == DataType.BYTES) {
+ // Logical BYTES is a serialized UltraLogLog and always uses the
single-value representation.
byte[][] bytesValues = blockValSet.getBytesValuesSV();
try {
for (int i = 0; i < length; i++) {
@@ -176,6 +184,8 @@ public class DistinctCountULLAggregationFunction extends
BaseSingleInputAggregat
return;
}
+ DataType storedType = dataType.getStoredType();
+
// For dictionary-encoded expression, store dictionary ids into the bitmap
Dictionary dictionary = blockValSet.isDictionaryEncoded() ?
blockValSet.getDictionary() : null;
if (dictionary != null) {
@@ -223,6 +233,13 @@ public class DistinctCountULLAggregationFunction extends
BaseSingleInputAggregat
.ifPresent(getULL(groupByResultHolder, groupKeyArray[i])::add);
}
break;
+ case BYTES:
+ byte[][] bytesValues = blockValSet.getBytesValuesSV();
+ for (int i = 0; i < length; i++) {
+ UltraLogLogUtils.hashObject(bytesValues[i])
+ .ifPresent(getULL(groupByResultHolder, groupKeyArray[i])::add);
+ }
+ break;
default:
throw new IllegalStateException(
"Illegal data type for DISTINCT_COUNT_ULL aggregation function: "
+ storedType);
@@ -234,9 +251,9 @@ public class DistinctCountULLAggregationFunction extends
BaseSingleInputAggregat
Map<ExpressionContext, BlockValSet> blockValSetMap) {
BlockValSet blockValSet = blockValSetMap.get(_expression);
- // Treat BYTES value as serialized HyperLogLogPlus
- DataType storedType = blockValSet.getValueType().getStoredType();
- if (storedType == DataType.BYTES) {
+ DataType dataType = blockValSet.getValueType();
+ if (dataType == DataType.BYTES) {
+ // Logical BYTES is a serialized UltraLogLog and always uses the
single-value representation.
byte[][] bytesValues = blockValSet.getBytesValuesSV();
try {
for (int i = 0; i < length; i++) {
@@ -246,7 +263,6 @@ public class DistinctCountULLAggregationFunction extends
BaseSingleInputAggregat
if (ull != null) {
ull.add(value);
} else {
- // Create a new HyperLogLogPlus for the group
groupByResultHolder.setValueForKey(groupKey,
ObjectSerDeUtils.ULTRA_LOG_LOG_OBJECT_SER_DE.deserialize(bytesValues[i]));
}
@@ -258,6 +274,8 @@ public class DistinctCountULLAggregationFunction extends
BaseSingleInputAggregat
return;
}
+ DataType storedType = dataType.getStoredType();
+
// For dictionary-encoded expression, store dictionary ids into the bitmap
Dictionary dictionary = blockValSet.isDictionaryEncoded() ?
blockValSet.getDictionary() : null;
if (dictionary != null) {
@@ -300,6 +318,12 @@ public class DistinctCountULLAggregationFunction extends
BaseSingleInputAggregat
setValueForGroupKeys(groupByResultHolder, groupKeysArray[i],
stringValues[i]);
}
break;
+ case BYTES:
+ byte[][] bytesValues = blockValSet.getBytesValuesSV();
+ for (int i = 0; i < length; i++) {
+ setValueForGroupKeys(groupByResultHolder, groupKeysArray[i],
bytesValues[i]);
+ }
+ break;
default:
throw new IllegalStateException(
"Illegal data type for DISTINCT_COUNT_HLL_PLUS aggregation
function: " + storedType);
diff --git
a/pinot-core/src/main/java/org/apache/pinot/core/query/distinct/table/BytesDistinctTable.java
b/pinot-core/src/main/java/org/apache/pinot/core/query/distinct/table/BytesDistinctTable.java
index 386648b64f5..5a2565e49f6 100644
---
a/pinot-core/src/main/java/org/apache/pinot/core/query/distinct/table/BytesDistinctTable.java
+++
b/pinot-core/src/main/java/org/apache/pinot/core/query/distinct/table/BytesDistinctTable.java
@@ -32,11 +32,13 @@ import org.apache.pinot.common.datatable.DataTable;
import org.apache.pinot.common.request.context.OrderByExpressionContext;
import org.apache.pinot.common.response.broker.ResultTable;
import org.apache.pinot.common.utils.DataSchema;
+import org.apache.pinot.common.utils.DataSchema.ColumnDataType;
import org.apache.pinot.core.common.datatable.DataTableBuilder;
import org.apache.pinot.core.common.datatable.DataTableBuilderFactory;
import org.apache.pinot.spi.query.QueryThreadContext;
import org.apache.pinot.spi.utils.ByteArray;
import org.apache.pinot.spi.utils.CommonConstants;
+import org.apache.pinot.spi.utils.UuidUtils;
import org.roaringbitmap.RoaringBitmap;
@@ -290,9 +292,16 @@ public class BytesDistinctTable extends DistinctTable {
return new ResultTable(_dataSchema, rows);
}
- private static void addRows(ByteArray[] values, int length, List<Object[]>
rows) {
- for (int i = 0; i < length; i++) {
- rows.add(new Object[]{values[i].toHexString()});
+ private void addRows(ByteArray[] values, int length, List<Object[]> rows) {
+ ColumnDataType columnDataType = _dataSchema.getColumnDataType(0);
+ if (columnDataType == ColumnDataType.UUID) {
+ for (int i = 0; i < length; i++) {
+ rows.add(new Object[]{UuidUtils.toString(values[i])});
+ }
+ } else {
+ for (int i = 0; i < length; i++) {
+ rows.add(new Object[]{values[i].toHexString()});
+ }
}
}
@@ -311,9 +320,16 @@ public class BytesDistinctTable extends DistinctTable {
return new ResultTable(_dataSchema, rows);
}
- private static void addRows(HashSet<ByteArray> values, List<Object[]> rows) {
- for (ByteArray value : values) {
- rows.add(new Object[]{value.toHexString()});
+ private void addRows(HashSet<ByteArray> values, List<Object[]> rows) {
+ ColumnDataType columnDataType = _dataSchema.getColumnDataType(0);
+ if (columnDataType == ColumnDataType.UUID) {
+ for (ByteArray value : values) {
+ rows.add(new Object[]{UuidUtils.toString(value)});
+ }
+ } else {
+ for (ByteArray value : values) {
+ rows.add(new Object[]{value.toHexString()});
+ }
}
}
}
diff --git
a/pinot-core/src/main/java/org/apache/pinot/core/query/reduce/GroupByDataTableReducer.java
b/pinot-core/src/main/java/org/apache/pinot/core/query/reduce/GroupByDataTableReducer.java
index c1add8ca708..3c36ebdf920 100644
---
a/pinot-core/src/main/java/org/apache/pinot/core/query/reduce/GroupByDataTableReducer.java
+++
b/pinot-core/src/main/java/org/apache/pinot/core/query/reduce/GroupByDataTableReducer.java
@@ -63,6 +63,7 @@ import org.apache.pinot.core.util.trace.TraceRunnable;
import org.apache.pinot.spi.exception.EarlyTerminationException;
import org.apache.pinot.spi.exception.QueryErrorCode;
import org.apache.pinot.spi.query.QueryThreadContext;
+import org.apache.pinot.spi.utils.UuidUtils;
import org.roaringbitmap.RoaringBitmap;
@@ -531,6 +532,8 @@ public class GroupByDataTableReducer implements
DataTableReducer {
return dataTable.getString(rowId, colId);
case BYTES:
return dataTable.getBytes(rowId, colId).getBytes();
+ case UUID:
+ return UuidUtils.toUUID(dataTable.getBytes(rowId, colId));
default:
throw new IllegalStateException("Illegal column data type in group
key: " + columnDataType);
}
diff --git
a/pinot-integration-tests/src/test/java/org/apache/pinot/integration/tests/custom/UuidAggregationTest.java
b/pinot-integration-tests/src/test/java/org/apache/pinot/integration/tests/custom/UuidAggregationTest.java
new file mode 100644
index 00000000000..1b828a49998
--- /dev/null
+++
b/pinot-integration-tests/src/test/java/org/apache/pinot/integration/tests/custom/UuidAggregationTest.java
@@ -0,0 +1,212 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.pinot.integration.tests.custom;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import java.io.File;
+import java.util.List;
+import org.apache.avro.file.DataFileWriter;
+import org.apache.avro.generic.GenericData;
+import org.apache.pinot.spi.config.table.TableConfig;
+import org.apache.pinot.spi.config.table.TableType;
+import org.apache.pinot.spi.data.FieldSpec.DataType;
+import org.apache.pinot.spi.data.Schema;
+import org.apache.pinot.spi.utils.builder.TableConfigBuilder;
+import org.testng.annotations.Test;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertTrue;
+
+
+/// End-to-end UUID aggregation coverage over dictionary-encoded and raw SV/MV
columns.
+@Test(suiteName = "CustomClusterIntegrationTest")
+public class UuidAggregationTest extends CustomDataQueryClusterIntegrationTest
{
+ private static final String TABLE_NAME = "UuidAggregationTest";
+ private static final String UUID_DICT_SV_COLUMN = "uuidDictSv";
+ private static final String UUID_DICT_MV_COLUMN = "uuidDictMv";
+ private static final String UUID_RAW_SV_COLUMN = "uuidRawSv";
+ private static final String UUID_RAW_MV_COLUMN = "uuidRawMv";
+
+ private static final String UUID_0 = "550e8400-e29b-41d4-a716-446655440000";
+ private static final String UUID_0_HEX = "550e8400e29b41d4a716446655440000";
+ private static final String UUID_1 = "550e8400-e29b-41d4-a716-446655440001";
+ private static final String UUID_2 = "550e8400-e29b-41d4-a716-446655440002";
+ private static final String UUID_3 = "550e8400-e29b-41d4-a716-446655440003";
+
+ private static final List<String> UUID_SV_VALUES = List.of(UUID_0, UUID_0,
UUID_1, UUID_2);
+ private static final List<List<String>> UUID_MV_VALUES =
+ List.of(List.of(UUID_0, UUID_1), List.of(UUID_1, UUID_2),
List.of(UUID_0), List.of(UUID_3));
+
+ @Override
+ public String getTableName() {
+ return TABLE_NAME;
+ }
+
+ @Override
+ protected long getCountStarResult() {
+ return UUID_SV_VALUES.size();
+ }
+
+ @Override
+ public int getNumAvroFiles() {
+ return 1;
+ }
+
+ @Override
+ public TableConfig createOfflineTableConfig() {
+ return new
TableConfigBuilder(TableType.OFFLINE).setTableName(getTableName())
+ .setNoDictionaryColumns(List.of(UUID_RAW_SV_COLUMN,
UUID_RAW_MV_COLUMN)).build();
+ }
+
+ @Override
+ public Schema createSchema() {
+ return new Schema.SchemaBuilder().setSchemaName(getTableName())
+ .addSingleValueDimension(UUID_DICT_SV_COLUMN, DataType.UUID)
+ .addMultiValueDimension(UUID_DICT_MV_COLUMN, DataType.UUID)
+ .addSingleValueDimension(UUID_RAW_SV_COLUMN, DataType.UUID)
+ .addMultiValueDimension(UUID_RAW_MV_COLUMN, DataType.UUID)
+ .build();
+ }
+
+ @Override
+ public List<File> createAvroFiles()
+ throws Exception {
+ org.apache.avro.Schema uuidSchema =
org.apache.avro.Schema.create(org.apache.avro.Schema.Type.STRING);
+ org.apache.avro.Schema avroSchema =
org.apache.avro.Schema.createRecord("uuidRecord", null, null, false);
+ avroSchema.setFields(List.of(
+ new org.apache.avro.Schema.Field(UUID_DICT_SV_COLUMN, uuidSchema,
null, null),
+ new org.apache.avro.Schema.Field(UUID_DICT_MV_COLUMN,
org.apache.avro.Schema.createArray(uuidSchema), null,
+ null),
+ new org.apache.avro.Schema.Field(UUID_RAW_SV_COLUMN, uuidSchema, null,
null),
+ new org.apache.avro.Schema.Field(UUID_RAW_MV_COLUMN,
org.apache.avro.Schema.createArray(uuidSchema), null,
+ null)));
+
+ try (AvroFilesAndWriters avroFilesAndWriters =
createAvroFilesAndWriters(avroSchema)) {
+ DataFileWriter<GenericData.Record> writer =
avroFilesAndWriters.getWriters().get(0);
+ for (int i = 0; i < UUID_SV_VALUES.size(); i++) {
+ GenericData.Record record = new GenericData.Record(avroSchema);
+ record.put(UUID_DICT_SV_COLUMN, UUID_SV_VALUES.get(i));
+ record.put(UUID_DICT_MV_COLUMN, UUID_MV_VALUES.get(i));
+ record.put(UUID_RAW_SV_COLUMN, UUID_SV_VALUES.get(i));
+ record.put(UUID_RAW_MV_COLUMN, UUID_MV_VALUES.get(i));
+ writer.append(record);
+ }
+ return avroFilesAndWriters.getAvroFiles();
+ }
+ }
+
+ @Test
+ public void testGroupByHavingReturningFinalResult()
+ throws Exception {
+ setUseMultiStageQueryEngine(false);
+ JsonNode rows = query(String.format(
+ "SELECT %1$s, COUNT(*) FROM %2$s GROUP BY %1$s HAVING %1$s = '%3$s' "
+ + "OPTION(serverReturnFinalResult=true)",
+ UUID_DICT_SV_COLUMN, getTableName(), UUID_0_HEX));
+
+ assertEquals(rows.size(), 1, rows.toPrettyString());
+ assertEquals(rows.get(0).get(0).asText(), UUID_0, rows.toPrettyString());
+ assertEquals(rows.get(0).get(1).asLong(), 2L, rows.toPrettyString());
+ }
+
+ @Test(dataProvider = "useBothQueryEngines")
+ public void testDistinctOnUuidColumn(boolean useMultiStageQueryEngine)
+ throws Exception {
+ setUseMultiStageQueryEngine(useMultiStageQueryEngine);
+ JsonNode rows = query(String.format("SELECT DISTINCT %1$s FROM %2$s ORDER
BY %1$s", UUID_DICT_SV_COLUMN,
+ getTableName()));
+
+ assertEquals(rows.size(), 3, rows.toPrettyString());
+ for (int i = 0; i < rows.size(); i++) {
+ assertEquals(rows.get(i).get(0).asText(), List.of(UUID_0, UUID_1,
UUID_2).get(i), rows.toPrettyString());
+ }
+ }
+
+ @Test
+ public void testDistinctCountOnUuidColumns()
+ throws Exception {
+ setUseMultiStageQueryEngine(false);
+ for (String function : List.of("DISTINCTCOUNT", "DISTINCTCOUNTHLL",
"DISTINCTCOUNTHLLPLUS",
+ "DISTINCTCOUNTBITMAP", "DISTINCTCOUNTTHETASKETCH",
"DISTINCTCOUNTCPCSKETCH")) {
+ JsonNode rows = query(String.format("SELECT %1$s(%2$s), %1$s(%3$s),
%1$s(%4$s) FROM %5$s", function,
+ UUID_DICT_SV_COLUMN, UUID_RAW_SV_COLUMN, UUID_RAW_MV_COLUMN,
getTableName()));
+ assertCounts(rows.get(0), 3L, 3L, 4L);
+ }
+
+ // DISTINCTCOUNTULL currently supports only single-value inputs.
+ JsonNode rows = query(String.format("SELECT DISTINCTCOUNTULL(%s),
DISTINCTCOUNTULL(%s) FROM %s",
+ UUID_DICT_SV_COLUMN, UUID_RAW_SV_COLUMN, getTableName()));
+ assertCounts(rows.get(0), 3L, 3L);
+
+ rows = query(String.format(
+ "SELECT DISTINCTCOUNTTHETASKETCH(%1$s, '', '%1$s = ''%3$s''', '$1'), "
+ + "DISTINCTCOUNTTHETASKETCH(%2$s, '', '%2$s = ''%3$s''', '$1')
FROM %4$s",
+ UUID_RAW_SV_COLUMN, UUID_RAW_MV_COLUMN, UUID_0, getTableName()));
+ assertCounts(rows.get(0), 1L, 2L);
+ }
+
+ @Test
+ public void testCpcAndThetaGroupByUuidColumns()
+ throws Exception {
+ setUseMultiStageQueryEngine(false);
+ JsonNode rows = queryGroupBy(UUID_RAW_SV_COLUMN);
+ assertEquals(rows.size(), 3, rows.toPrettyString());
+ assertGroupRow(rows.get(0), UUID_0, 1L, 3L, 1L, 3L);
+ assertGroupRow(rows.get(1), UUID_1, 1L, 1L, 1L, 1L);
+ assertGroupRow(rows.get(2), UUID_2, 1L, 1L, 1L, 1L);
+
+ // UUID multi-value group keys require a dictionary, while the aggregate
input remains raw.
+ rows = queryGroupBy(UUID_DICT_MV_COLUMN);
+ assertEquals(rows.size(), 4, rows.toPrettyString());
+ assertGroupRow(rows.get(0), UUID_0, 2L, 2L, 2L, 2L);
+ assertGroupRow(rows.get(1), UUID_1, 1L, 3L, 1L, 3L);
+ assertGroupRow(rows.get(2), UUID_2, 1L, 2L, 1L, 2L);
+ assertGroupRow(rows.get(3), UUID_3, 1L, 1L, 1L, 1L);
+ }
+
+ private JsonNode queryGroupBy(String groupByColumn)
+ throws Exception {
+ return query(String.format(
+ "SELECT %1$s, DISTINCTCOUNTCPCSKETCH(%2$s),
DISTINCTCOUNTCPCSKETCH(%3$s), "
+ + "DISTINCTCOUNTTHETASKETCH(%2$s), DISTINCTCOUNTTHETASKETCH(%3$s) "
+ + "FROM %4$s GROUP BY %1$s ORDER BY %1$s",
+ groupByColumn, UUID_RAW_SV_COLUMN, UUID_RAW_MV_COLUMN,
getTableName()));
+ }
+
+ private JsonNode query(String sql)
+ throws Exception {
+ JsonNode response = postQuery(sql);
+ assertTrue(response.path("exceptions").isEmpty(), sql + " -> " +
response.toPrettyString());
+ return response.path("resultTable").path("rows");
+ }
+
+ private static void assertGroupRow(JsonNode row, String groupKey, long...
expectedCounts) {
+ assertEquals(row.get(0).asText(), groupKey, row.toPrettyString());
+ for (int i = 0; i < expectedCounts.length; i++) {
+ assertEquals(row.get(i + 1).asLong(), expectedCounts[i],
row.toPrettyString());
+ }
+ }
+
+ private static void assertCounts(JsonNode row, long... expectedCounts) {
+ assertEquals(row.size(), expectedCounts.length, row.toPrettyString());
+ for (int i = 0; i < expectedCounts.length; i++) {
+ assertEquals(row.get(i).asLong(), expectedCounts[i],
row.toPrettyString());
+ }
+ }
+}
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]