yashmayya commented on code in PR #18334:
URL: https://github.com/apache/pinot/pull/18334#discussion_r3598646315
##########
pinot-core/src/main/java/org/apache/pinot/core/plan/AggregationPlanNode.java:
##########
@@ -115,17 +115,38 @@ public Operator<AggregationResultsBlock>
buildNonFilteredAggOperator() {
boolean hasNullValues = _queryContext.isNullHandlingEnabled() &&
hasNullValues(aggregationFunctions);
if (!hasNullValues) {
+ DataSource[] dataSources = new DataSource[aggregationFunctions.length];
+ for (int i = 0; i < aggregationFunctions.length; i++) {
+ List<?> inputExpressions =
aggregationFunctions[i].getInputExpressions();
+ if (!inputExpressions.isEmpty()) {
+ String column = ((ExpressionContext)
inputExpressions.get(0)).getIdentifier();
+ dataSources[i] = _indexSegment.getDataSource(column,
_queryContext.getSchema());
+ }
+ }
+
// Priority 2: Check if non-scan based aggregation is feasible
if (filterOperator.isResultMatchingAll() && isFitForNonScanBasedPlan()) {
- DataSource[] dataSources = new DataSource[aggregationFunctions.length];
+ return new NonScanBasedAggregationOperator(_queryContext, dataSources,
numTotalDocs);
+ }
+
+ if (filterOperator.isResultMatchingAll()) {
+ boolean anyResolved = false;
+ Object[] preAggregatedResults = new
Object[aggregationFunctions.length];
for (int i = 0; i < aggregationFunctions.length; i++) {
- List<?> inputExpressions =
aggregationFunctions[i].getInputExpressions();
- if (!inputExpressions.isEmpty()) {
- String column = ((ExpressionContext)
inputExpressions.get(0)).getIdentifier();
- dataSources[i] = _indexSegment.getDataSource(column,
_queryContext.getSchema());
+ Object resolved =
AggregationFunctionUtils.getAggregationResult(aggregationFunctions[i],
Review Comment:
`getAggregationResult()` never returns null - for non-metadata functions it
throws (the `default` in the util), and for `DISTINCT*` without a dictionary it
NPEs on `requireNonNull(getDictionary())`. So `resolved != null` never actually
skips anything.
And this branch is only reached when `isFitForNonScanBasedPlan()` is false,
i.e. at least one function *isn't* metadata-resolvable - so the loop is
guaranteed to hit one of those throws. Net effect: `SELECT SUM(col) FROM t` (no
filter) throws here instead of falling through to the scan path.
I think you need a per-function feasibility check before resolving - the
checks already in `isFitForNonScanBasedPlan()` are exactly that - and only
resolve the ones that pass, leaving the rest null.
##########
pinot-core/src/main/java/org/apache/pinot/core/plan/AggregationPlanNode.java:
##########
@@ -115,17 +115,38 @@ public Operator<AggregationResultsBlock>
buildNonFilteredAggOperator() {
boolean hasNullValues = _queryContext.isNullHandlingEnabled() &&
hasNullValues(aggregationFunctions);
if (!hasNullValues) {
+ DataSource[] dataSources = new DataSource[aggregationFunctions.length];
+ for (int i = 0; i < aggregationFunctions.length; i++) {
+ List<?> inputExpressions =
aggregationFunctions[i].getInputExpressions();
+ if (!inputExpressions.isEmpty()) {
+ String column = ((ExpressionContext)
inputExpressions.get(0)).getIdentifier();
+ dataSources[i] = _indexSegment.getDataSource(column,
_queryContext.getSchema());
+ }
+ }
+
// Priority 2: Check if non-scan based aggregation is feasible
if (filterOperator.isResultMatchingAll() && isFitForNonScanBasedPlan()) {
- DataSource[] dataSources = new DataSource[aggregationFunctions.length];
+ return new NonScanBasedAggregationOperator(_queryContext, dataSources,
numTotalDocs);
+ }
+
+ if (filterOperator.isResultMatchingAll()) {
+ boolean anyResolved = false;
+ Object[] preAggregatedResults = new
Object[aggregationFunctions.length];
for (int i = 0; i < aggregationFunctions.length; i++) {
- List<?> inputExpressions =
aggregationFunctions[i].getInputExpressions();
- if (!inputExpressions.isEmpty()) {
- String column = ((ExpressionContext)
inputExpressions.get(0)).getIdentifier();
- dataSources[i] = _indexSegment.getDataSource(column,
_queryContext.getSchema());
+ Object resolved =
AggregationFunctionUtils.getAggregationResult(aggregationFunctions[i],
+ dataSources[i], numTotalDocs,
NonScanBasedAggregationOperator.EXPLAIN_NAME);
+ if (resolved != null) {
+ preAggregatedResults[i] = resolved;
+ anyResolved = true;
}
}
- return new NonScanBasedAggregationOperator(_queryContext, dataSources,
numTotalDocs);
+
+ if (anyResolved) {
+ // build aggregation info for all functions (including those that
cannot be resolved from metadata)
+ aggregationInfo =
AggregationFunctionUtils.buildAggregationInfoWithoutStarTree(_segmentContext,
_queryContext,
Review Comment:
Couple of things on the resulting operator:
1. `buildAggregationInfoWithoutStarTree` collects every function's columns,
so the metadata-resolved columns still get projected/scanned - we only skip the
per-row `aggregate()`, not the I/O. So the win is narrower than "avoid the
scan".
2. Resolution runs here at plan-build time (dictionary scans, HLL merges),
whereas the full non-scan path does it lazily in `getNextBlock()`. Might be
worth deferring to keep planning cheap and the two paths consistent.
##########
pinot-core/src/main/java/org/apache/pinot/core/plan/AggregationPlanNode.java:
##########
@@ -115,17 +115,38 @@ public Operator<AggregationResultsBlock>
buildNonFilteredAggOperator() {
boolean hasNullValues = _queryContext.isNullHandlingEnabled() &&
hasNullValues(aggregationFunctions);
if (!hasNullValues) {
+ DataSource[] dataSources = new DataSource[aggregationFunctions.length];
+ for (int i = 0; i < aggregationFunctions.length; i++) {
+ List<?> inputExpressions =
aggregationFunctions[i].getInputExpressions();
+ if (!inputExpressions.isEmpty()) {
+ String column = ((ExpressionContext)
inputExpressions.get(0)).getIdentifier();
+ dataSources[i] = _indexSegment.getDataSource(column,
_queryContext.getSchema());
Review Comment:
This loop used to live inside the `isFitForNonScanBasedPlan()` block, which
guaranteed every arg was a plain identifier. Now it runs for every query, but
`getIdentifier()` returns null for non-identifier args and `getDataSource(null,
schema)` throws (`checkState(fieldSpec != null, ...)`).
So `SELECT SUM(a+b) FROM t` (any agg over an expression) now fails at plan
time - and since this is above the `isResultMatchingAll()` checks, even
filtered queries hit it. Worth guarding on `getType() == IDENTIFIER`.
##########
pinot-core/src/main/java/org/apache/pinot/core/plan/AggregationPlanNode.java:
##########
@@ -115,17 +115,38 @@ public Operator<AggregationResultsBlock>
buildNonFilteredAggOperator() {
boolean hasNullValues = _queryContext.isNullHandlingEnabled() &&
hasNullValues(aggregationFunctions);
if (!hasNullValues) {
+ DataSource[] dataSources = new DataSource[aggregationFunctions.length];
+ for (int i = 0; i < aggregationFunctions.length; i++) {
+ List<?> inputExpressions =
aggregationFunctions[i].getInputExpressions();
+ if (!inputExpressions.isEmpty()) {
+ String column = ((ExpressionContext)
inputExpressions.get(0)).getIdentifier();
+ dataSources[i] = _indexSegment.getDataSource(column,
_queryContext.getSchema());
+ }
+ }
+
// Priority 2: Check if non-scan based aggregation is feasible
if (filterOperator.isResultMatchingAll() && isFitForNonScanBasedPlan()) {
- DataSource[] dataSources = new DataSource[aggregationFunctions.length];
+ return new NonScanBasedAggregationOperator(_queryContext, dataSources,
numTotalDocs);
+ }
+
+ if (filterOperator.isResultMatchingAll()) {
+ boolean anyResolved = false;
+ Object[] preAggregatedResults = new
Object[aggregationFunctions.length];
for (int i = 0; i < aggregationFunctions.length; i++) {
- List<?> inputExpressions =
aggregationFunctions[i].getInputExpressions();
- if (!inputExpressions.isEmpty()) {
- String column = ((ExpressionContext)
inputExpressions.get(0)).getIdentifier();
- dataSources[i] = _indexSegment.getDataSource(column,
_queryContext.getSchema());
+ Object resolved =
AggregationFunctionUtils.getAggregationResult(aggregationFunctions[i],
+ dataSources[i], numTotalDocs,
NonScanBasedAggregationOperator.EXPLAIN_NAME);
Review Comment:
Minor: this tags the work as `AGGREGATE_NO_SCAN`, but the operator that
actually runs here is `AggregationOperator` (`AGGREGATE`), so the usage
sampling / termination checks get labeled with the wrong operator name. Also,
making `EXPLAIN_NAME` public just to pass this constant is a bit of a smell -
could pass the caller's own name or a neutral label.
##########
pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/AggregationFunctionUtils.java:
##########
@@ -501,4 +519,340 @@ public static String
getResultColumnName(AggregationFunction aggregationFunction
}
return columnName;
}
+
+ /**
+ * Gets the aggregation result without scanning the segment.
+ * This is used for non-scan based aggregation operator.
+ * @param aggregationFunction
+ * @param dataSource
+ * @param numTotalDocs
+ * @return
+ */
+ public static Object getAggregationResult(AggregationFunction
aggregationFunction, DataSource dataSource,
Review Comment:
For the partial path to work, this needs to return null when a function
can't be resolved from metadata. Right now it either throws (`default`, L623)
or NPEs (`requireNonNull(getDictionary())`, and the `MINSTRING`/`MAXSTRING`
asserts) whenever the dictionary/metadata isn't present.
Also the Javadoc `@param`/`@return` are empty and `explainName` isn't
documented.
##########
pinot-core/src/main/java/org/apache/pinot/core/operator/query/AggregationOperator.java:
##########
@@ -67,7 +74,7 @@ protected AggregationResultsBlock getNextBlock() {
if (_useStarTree) {
aggregationExecutor = new
StarTreeAggregationExecutor(_aggregationFunctions);
} else {
- aggregationExecutor = new
DefaultAggregationExecutor(_aggregationFunctions);
+ aggregationExecutor = new
DefaultAggregationExecutor(_aggregationFunctions, _preAggregatedResults);
Review Comment:
`_preAggregatedResults` is only wired into `DefaultAggregationExecutor`; the
star-tree branch above silently drops it. Safe today because the star-tree path
returns earlier in the plan node, but it's fragile - and
`StarTreeAggregationExecutor` overrides `aggregate()` but inherits
`getResult()`, so if it were ever constructed with pre-aggregated results the
two would disagree. An assert (`_preAggregatedResults == null` when star-tree)
or a short comment would help.
##########
pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/AggregationFunctionUtils.java:
##########
@@ -501,4 +519,340 @@ public static String
getResultColumnName(AggregationFunction aggregationFunction
}
return columnName;
}
+
+ /**
+ * Gets the aggregation result without scanning the segment.
+ * This is used for non-scan based aggregation operator.
+ * @param aggregationFunction
+ * @param dataSource
+ * @param numTotalDocs
+ * @return
+ */
+ public static Object getAggregationResult(AggregationFunction
aggregationFunction, DataSource dataSource,
+ int numTotalDocs, String explainName) {
+ Object result;
+ switch (aggregationFunction.getType()) {
+ case COUNT:
+ result = (long) numTotalDocs;
+ break;
+ case MIN:
+ case MINMV:
+ result = getMinValueNumeric(dataSource);
+ break;
+ case MINLONG:
+ result = getMinValueLong(dataSource);
+ break;
+ case MINSTRING:
+ assert dataSource.getDictionary() != null;
+ result = dataSource.getDictionary().getMinVal();
+ break;
+ case MAX:
+ case MAXMV:
+ result = getMaxValueNumeric(dataSource);
+ break;
+ case MAXLONG:
+ result = getMaxValueLong(dataSource);
+ break;
+ case MAXSTRING:
+ assert dataSource.getDictionary() != null;
+ result = dataSource.getDictionary().getMaxVal();
+ break;
+ case MINMAXRANGE:
+ case MINMAXRANGEMV:
+ result = new MinMaxRangePair(getMinValueNumeric(dataSource),
getMaxValueNumeric(dataSource));
+ break;
+ case DISTINCTCOUNT:
+ case DISTINCTSUM:
+ case DISTINCTAVG:
+ case DISTINCTCOUNTMV:
+ case DISTINCTSUMMV:
+ case DISTINCTAVGMV:
+ result =
getDistinctValueSet(Objects.requireNonNull(dataSource.getDictionary()),
explainName);
+ break;
+ case DISTINCTCOUNTOFFHEAP:
+ result = ((DistinctCountOffHeapAggregationFunction)
aggregationFunction).extractAggregationResult(
+ Objects.requireNonNull(dataSource.getDictionary()));
+ break;
+ case DISTINCTCOUNTHLL:
+ case DISTINCTCOUNTHLLMV:
+ result =
getDistinctCountHLLResult(Objects.requireNonNull(dataSource.getDictionary()),
+ (DistinctCountHLLAggregationFunction) aggregationFunction,
explainName);
+ break;
+ case DISTINCTCOUNTRAWHLL:
+ case DISTINCTCOUNTRAWHLLMV:
+ result =
getDistinctCountHLLResult(Objects.requireNonNull(dataSource.getDictionary()),
+ ((DistinctCountRawHLLAggregationFunction)
aggregationFunction).getDistinctCountHLLAggregationFunction(),
+ explainName);
+ break;
+ case DISTINCTCOUNTHLLPLUS:
+ case DISTINCTCOUNTHLLPLUSMV:
+ result =
getDistinctCountHLLPlusResult(Objects.requireNonNull(dataSource.getDictionary()),
+ (DistinctCountHLLPlusAggregationFunction) aggregationFunction,
explainName);
+ break;
+ case DISTINCTCOUNTRAWHLLPLUS:
+ case DISTINCTCOUNTRAWHLLPLUSMV:
+ result =
getDistinctCountHLLPlusResult(Objects.requireNonNull(dataSource.getDictionary()),
+ ((DistinctCountRawHLLPlusAggregationFunction) aggregationFunction)
+ .getDistinctCountHLLPlusAggregationFunction(), explainName);
+ break;
+ case SEGMENTPARTITIONEDDISTINCTCOUNT:
+ result = (long)
Objects.requireNonNull(dataSource.getDictionary()).length();
+ break;
+ case DISTINCTCOUNTSMARTHLL:
+ result =
getDistinctCountSmartHLLResult(Objects.requireNonNull(dataSource.getDictionary()),
+ (DistinctCountSmartHLLAggregationFunction) aggregationFunction,
explainName);
+ break;
+ case DISTINCTCOUNTSMARTHLLPLUS:
+ result =
getDistinctCountSmartHLLPlusResult(Objects.requireNonNull(dataSource.getDictionary()),
+ (DistinctCountSmartHLLPlusAggregationFunction)
aggregationFunction, explainName);
+ break;
+ case DISTINCTCOUNTULL:
+ result =
getDistinctCountULLResult(Objects.requireNonNull(dataSource.getDictionary()),
+ (DistinctCountULLAggregationFunction) aggregationFunction,
explainName);
+ break;
+ case DISTINCTCOUNTSMARTULL:
+ result =
getDistinctCountSmartULLResult(Objects.requireNonNull(dataSource.getDictionary()),
+ (DistinctCountSmartULLAggregationFunction) aggregationFunction,
explainName);
+ break;
+ case DISTINCTCOUNTRAWULL:
+ result =
getDistinctCountULLResult(Objects.requireNonNull(dataSource.getDictionary()),
+ (DistinctCountULLAggregationFunction) aggregationFunction,
explainName);
+ break;
+ default:
+ throw new IllegalStateException(
+ "Non-scan based aggregation operator does not support function
type: " + aggregationFunction.getType());
+ }
+
+ return result;
+ }
+
+ private static Double getMinValueNumeric(DataSource dataSource) {
+ Dictionary dictionary = dataSource.getDictionary();
+ if (dictionary != null) {
+ return toDouble(dictionary.getMinVal());
+ }
+ return toDouble(dataSource.getDataSourceMetadata().getMinValue());
+ }
+
+ private static Long getMinValueLong(DataSource dataSource) {
+ FieldSpec.DataType dataType =
dataSource.getDataSourceMetadata().getDataType().getStoredType();
+ Preconditions.checkArgument(
+ dataType == FieldSpec.DataType.LONG || dataType ==
FieldSpec.DataType.INT,
+ "MINLONG aggregation function can only be applied to columns of
integer types");
+ Dictionary dictionary = dataSource.getDictionary();
+ if (dictionary != null) {
+ return ((Number) dictionary.getMinVal()).longValue();
+ }
+ return ((Number)
dataSource.getDataSourceMetadata().getMinValue()).longValue();
+ }
+
+ private static Double getMaxValueNumeric(DataSource dataSource) {
+ Dictionary dictionary = dataSource.getDictionary();
+ if (dictionary != null) {
+ return toDouble(dictionary.getMaxVal());
+ }
+ return toDouble(dataSource.getDataSourceMetadata().getMaxValue());
+ }
+
+ private static Long getMaxValueLong(DataSource dataSource) {
+ FieldSpec.DataType dataType =
dataSource.getDataSourceMetadata().getDataType().getStoredType();
+ Preconditions.checkArgument(
+ dataType == FieldSpec.DataType.LONG || dataType ==
FieldSpec.DataType.INT,
+ "MAXLONG aggregation function can only be applied to columns of
integer types");
+ Dictionary dictionary = dataSource.getDictionary();
+ if (dictionary != null) {
+ return ((Number) dictionary.getMaxVal()).longValue();
+ }
+ return ((Number)
dataSource.getDataSourceMetadata().getMaxValue()).longValue();
+ }
+
+ private static Double toDouble(Comparable<?> value) {
+ if (value instanceof Double) {
+ return (Double) value;
+ } else if (value instanceof Number) {
+ return ((Number) value).doubleValue();
+ } else {
+ return Double.parseDouble(value.toString());
+ }
+ }
+
+ private static Set getDistinctValueSet(Dictionary dictionary, String
explainName) {
+ int dictionarySize = dictionary.length();
+ switch (dictionary.getValueType()) {
+ case INT:
+ IntOpenHashSet intSet = new IntOpenHashSet(dictionarySize);
+ for (int dictId = 0; dictId < dictionarySize; dictId++) {
+
QueryThreadContext.checkTerminationAndSampleUsagePeriodically(dictId,
explainName);
+ intSet.add(dictionary.getIntValue(dictId));
+ }
+ return intSet;
+ case LONG:
+ LongOpenHashSet longSet = new LongOpenHashSet(dictionarySize);
+ for (int dictId = 0; dictId < dictionarySize; dictId++) {
+
QueryThreadContext.checkTerminationAndSampleUsagePeriodically(dictId,
explainName);
+ longSet.add(dictionary.getLongValue(dictId));
+ }
+ return longSet;
+ case FLOAT:
+ FloatOpenHashSet floatSet = new FloatOpenHashSet(dictionarySize);
+ for (int dictId = 0; dictId < dictionarySize; dictId++) {
+
QueryThreadContext.checkTerminationAndSampleUsagePeriodically(dictId,
explainName);
+ floatSet.add(dictionary.getFloatValue(dictId));
+ }
+ return floatSet;
+ case DOUBLE:
+ DoubleOpenHashSet doubleSet = new DoubleOpenHashSet(dictionarySize);
+ for (int dictId = 0; dictId < dictionarySize; dictId++) {
+
QueryThreadContext.checkTerminationAndSampleUsagePeriodically(dictId,
explainName);
+ doubleSet.add(dictionary.getDoubleValue(dictId));
+ }
+ return doubleSet;
+ case BIG_DECIMAL:
+ ObjectOpenHashSet<BigDecimal> bigDecimalSet = new
ObjectOpenHashSet<>(dictionarySize);
+ for (int dictId = 0; dictId < dictionarySize; dictId++) {
+
QueryThreadContext.checkTerminationAndSampleUsagePeriodically(dictId,
explainName);
+ bigDecimalSet.add(dictionary.getBigDecimalValue(dictId));
+ }
+ return bigDecimalSet;
+ case STRING:
+ ObjectOpenHashSet<String> stringSet = new
ObjectOpenHashSet<>(dictionarySize);
+ for (int dictId = 0; dictId < dictionarySize; dictId++) {
+
QueryThreadContext.checkTerminationAndSampleUsagePeriodically(dictId,
explainName);
+ stringSet.add(dictionary.getStringValue(dictId));
+ }
+ return stringSet;
+ case BYTES:
+ ObjectOpenHashSet<ByteArray> bytesSet = new
ObjectOpenHashSet<>(dictionarySize);
+ for (int dictId = 0; dictId < dictionarySize; dictId++) {
+
QueryThreadContext.checkTerminationAndSampleUsagePeriodically(dictId,
explainName);
+ bytesSet.add(new ByteArray(dictionary.getBytesValue(dictId)));
+ }
+ return bytesSet;
+ default:
+ throw new IllegalStateException();
+ }
+ }
+
+ private static HyperLogLog getDistinctValueHLL(Dictionary dictionary, int
log2m, String explainName) {
+ HyperLogLog hll = new HyperLogLog(log2m);
+ int length = dictionary.length();
+ for (int i = 0; i < length; i++) {
+ QueryThreadContext.checkTerminationAndSampleUsagePeriodically(i,
explainName);
+ hll.offer(dictionary.get(i));
+ }
+ return hll;
+ }
+
+ private static UltraLogLog getDistinctValueULL(Dictionary dictionary, int p,
String explainName) {
+ UltraLogLog ull = UltraLogLog.create(p);
+ int length = dictionary.length();
+ for (int i = 0; i < length; i++) {
+ QueryThreadContext.checkTerminationAndSampleUsagePeriodically(i,
explainName);
+ Object value = dictionary.get(i);
+ UltraLogLogUtils.hashObject(value).ifPresent(ull::add);
+ }
+ return ull;
+ }
+
+ private static HyperLogLogPlus getDistinctValueHLLPlus(Dictionary
dictionary, int p, int sp, String explainName) {
+ HyperLogLogPlus hllPlus = new HyperLogLogPlus(p, sp);
+ int length = dictionary.length();
+ for (int i = 0; i < length; i++) {
+ QueryThreadContext.checkTerminationAndSampleUsagePeriodically(i,
explainName);
+ hllPlus.offer(dictionary.get(i));
+ }
+ return hllPlus;
+ }
+
+ private static HyperLogLog getDistinctCountHLLResult(Dictionary dictionary,
+ DistinctCountHLLAggregationFunction function, String explainName) {
+ if (dictionary.getValueType() == FieldSpec.DataType.BYTES) {
+ // Treat BYTES value as serialized HyperLogLog
+ try {
+ QueryThreadContext.checkTerminationAndSampleUsage(explainName);
+ HyperLogLog hll =
ObjectSerDeUtils.HYPER_LOG_LOG_SER_DE.deserialize(dictionary.getBytesValue(0));
+ int length = dictionary.length();
+ for (int i = 1; i < length; i++) {
+ QueryThreadContext.checkTerminationAndSampleUsagePeriodically(i,
explainName);
+
hll.addAll(ObjectSerDeUtils.HYPER_LOG_LOG_SER_DE.deserialize(dictionary.getBytesValue(i)));
+ }
+ return hll;
+ } catch (Exception e) {
+ throw new RuntimeException("Caught exception while merging
HyperLogLogs", e);
+ }
+ } else {
+ return getDistinctValueHLL(dictionary, function.getLog2m(), explainName);
+ }
+ }
+
+ private static HyperLogLogPlus getDistinctCountHLLPlusResult(Dictionary
dictionary,
+ DistinctCountHLLPlusAggregationFunction function, String explainName) {
+ if (dictionary.getValueType() == FieldSpec.DataType.BYTES) {
+ // Treat BYTES value as serialized HyperLogLogPlus
+ try {
+ QueryThreadContext.checkTerminationAndSampleUsage(explainName);
+ HyperLogLogPlus hllplus =
ObjectSerDeUtils.HYPER_LOG_LOG_PLUS_SER_DE.deserialize(dictionary.getBytesValue(0));
+ int length = dictionary.length();
+ for (int i = 1; i < length; i++) {
+ QueryThreadContext.checkTerminationAndSampleUsagePeriodically(i,
explainName);
+
hllplus.addAll(ObjectSerDeUtils.HYPER_LOG_LOG_PLUS_SER_DE.deserialize(dictionary.getBytesValue(i)));
+ }
+ return hllplus;
+ } catch (Exception e) {
+ throw new RuntimeException("Caught exception while merging
HyperLogLogPluses", e);
+ }
+ } else {
+ return getDistinctValueHLLPlus(dictionary, function.getP(),
function.getSp(), explainName);
+ }
+ }
+
+ private static Object getDistinctCountSmartHLLResult(Dictionary dictionary,
+ DistinctCountSmartHLLAggregationFunction function, String
explainPlanName) {
Review Comment:
nit: `explainPlanName` here - every other method in this file calls it
`explainName`.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: [email protected]
For queries about this service, please contact Infrastructure at:
[email protected]
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]