This is an automated email from the ASF dual-hosted git repository.

Jackie-Jiang 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 1c78e5d47ff Give the integer tuple sketch functions the query's null 
handling option (#19217)
1c78e5d47ff is described below

commit 1c78e5d47ffd708bf2da187492e3272a1bd0225e
Author: Xiaotian (Jackie) Jiang <[email protected]>
AuthorDate: Wed Aug 12 09:30:02 2026 -0700

    Give the integer tuple sketch functions the query's null handling option 
(#19217)
---
 .../aggregation/function/AggregationFunction.java  |   5 +-
 .../function/AggregationFunctionFactory.java       |  11 +-
 ...ValueIntegerTupleSketchAggregationFunction.java |   8 +-
 ...CountIntegerTupleSketchAggregationFunction.java |   5 +-
 .../IntegerTupleSketchAggregationFunction.java     |  81 +++++++------
 ...aluesIntegerTupleSketchAggregationFunction.java |  10 +-
 .../IntegerTupleSketchAggregationFunctionTest.java |   4 +-
 .../IntegerTupleSketchNullHandlingTest.java        | 132 +++++++++++++++++++++
 8 files changed, 204 insertions(+), 52 deletions(-)

diff --git 
a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/AggregationFunction.java
 
b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/AggregationFunction.java
index 7569aa03967..45208aa6e02 100644
--- 
a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/AggregationFunction.java
+++ 
b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/AggregationFunction.java
@@ -100,9 +100,8 @@ import org.apache.pinot.segment.spi.AggregationFunctionType;
 ///      null rows and fold the column's default value into the aggregate 
whatever the query asked for: the
 ///      sketch-backed distinct counts (`DISTINCTCOUNTBITMAP`, 
`DISTINCTCOUNTHLL`, `DISTINCTCOUNTTHETASKETCH`,
 ///      `DISTINCTCOUNTCPCSKETCH`, `FASTHLL`, 
`SEGMENTPARTITIONEDDISTINCTCOUNT` and the raw and smart variants of
-///      each), the tuple and frequency sketches, `HISTOGRAM`, `IDSET`, 
`STUNION`, the array sums, and the funnel
-///      family. Whether a function takes the option is visible at its 
construction site, which is the reliable way
-///      to tell.
+///      each), the frequency sketches, `HISTOGRAM`, `IDSET`, `STUNION`, the 
array sums, and the funnel family.
+///      Whether a function takes the option is visible at its construction 
site, which is the reliable way to tell.
 ///      The family names are not a safe shorthand for this, in either 
direction. The exact distinct functions
 ///      (`DISTINCTCOUNT`, `DISTINCTSUM`, `DISTINCTAVG`, 
`DISTINCTCOUNTOFFHEAP`) do take the option and skip null
 ///      rows through their shared base, as do the variance, 
standard-deviation and covariance functions and the
diff --git 
a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/AggregationFunctionFactory.java
 
b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/AggregationFunctionFactory.java
index 5616244d770..cc6efe8a17d 100644
--- 
a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/AggregationFunctionFactory.java
+++ 
b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/AggregationFunctionFactory.java
@@ -481,13 +481,16 @@ public class AggregationFunctionFactory {
             return new FourthMomentAggregationFunction(arguments, 
FourthMomentAggregationFunction.Type.MOMENT);
           case DISTINCTCOUNTTUPLESKETCH:
             // mode actually doesn't matter here because we only care about 
keys, not values
-            return new 
DistinctCountIntegerTupleSketchAggregationFunction(arguments, 
IntegerSummary.Mode.Sum);
+            return new 
DistinctCountIntegerTupleSketchAggregationFunction(arguments, 
IntegerSummary.Mode.Sum,
+                nullHandlingEnabled);
           case DISTINCTCOUNTRAWINTEGERSUMTUPLESKETCH:
-            return new IntegerTupleSketchAggregationFunction(arguments, 
IntegerSummary.Mode.Sum);
+            return new IntegerTupleSketchAggregationFunction(arguments, 
IntegerSummary.Mode.Sum, nullHandlingEnabled);
           case SUMVALUESINTEGERSUMTUPLESKETCH:
-            return new 
SumValuesIntegerTupleSketchAggregationFunction(arguments, 
IntegerSummary.Mode.Sum);
+            return new 
SumValuesIntegerTupleSketchAggregationFunction(arguments, 
IntegerSummary.Mode.Sum,
+                nullHandlingEnabled);
           case AVGVALUEINTEGERSUMTUPLESKETCH:
-            return new 
AvgValueIntegerTupleSketchAggregationFunction(arguments, 
IntegerSummary.Mode.Sum);
+            return new 
AvgValueIntegerTupleSketchAggregationFunction(arguments, 
IntegerSummary.Mode.Sum,
+                nullHandlingEnabled);
           case PINOTPARENTAGGEXPRMAX:
             return new ParentExprMinMaxAggregationFunction(arguments, true);
           case PINOTPARENTAGGEXPRMIN:
diff --git 
a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/AvgValueIntegerTupleSketchAggregationFunction.java
 
b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/AvgValueIntegerTupleSketchAggregationFunction.java
index fba88d643cf..f279a157aae 100644
--- 
a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/AvgValueIntegerTupleSketchAggregationFunction.java
+++ 
b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/AvgValueIntegerTupleSketchAggregationFunction.java
@@ -32,8 +32,9 @@ import org.apache.pinot.segment.spi.AggregationFunctionType;
 public class AvgValueIntegerTupleSketchAggregationFunction
     extends IntegerTupleSketchAggregationFunction {
 
-  public AvgValueIntegerTupleSketchAggregationFunction(List<ExpressionContext> 
arguments, IntegerSummary.Mode mode) {
-    super(arguments, mode);
+  public AvgValueIntegerTupleSketchAggregationFunction(List<ExpressionContext> 
arguments, IntegerSummary.Mode mode,
+      boolean nullHandlingEnabled) {
+    super(arguments, mode, nullHandlingEnabled);
   }
 
   // TODO if extra aggregation modes are supported, make this switch
@@ -51,7 +52,8 @@ public class AvgValueIntegerTupleSketchAggregationFunction
   @Nullable
   @Override
   public Comparable extractFinalResult(@Nullable TupleIntSketchAccumulator 
accumulator) {
-    // A null intermediate result means nothing was aggregated, and there is 
nothing to average
+    // A null intermediate result means nothing was aggregated, and so does an 
empty sketch, which the retained-entry
+    // check below already answers NULL for. The average of nothing is NULL in 
either mode.
     if (accumulator == null) {
       return null;
     }
diff --git 
a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/DistinctCountIntegerTupleSketchAggregationFunction.java
 
b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/DistinctCountIntegerTupleSketchAggregationFunction.java
index c65cd9ed7fa..4e071706eb4 100644
--- 
a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/DistinctCountIntegerTupleSketchAggregationFunction.java
+++ 
b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/DistinctCountIntegerTupleSketchAggregationFunction.java
@@ -30,8 +30,8 @@ import org.apache.pinot.segment.spi.AggregationFunctionType;
 public class DistinctCountIntegerTupleSketchAggregationFunction extends 
IntegerTupleSketchAggregationFunction {
 
   public 
DistinctCountIntegerTupleSketchAggregationFunction(List<ExpressionContext> 
arguments,
-      IntegerSummary.Mode mode) {
-    super(arguments, mode);
+      IntegerSummary.Mode mode, boolean nullHandlingEnabled) {
+    super(arguments, mode, nullHandlingEnabled);
   }
 
   // TODO if extra aggregation modes are supported, make this switch
@@ -47,6 +47,7 @@ public class 
DistinctCountIntegerTupleSketchAggregationFunction extends IntegerT
 
   @Override
   public Comparable extractFinalResult(@Nullable TupleIntSketchAccumulator 
accumulator) {
+    // Nothing aggregated: an empty sketch estimates to 0 in either mode, so 
the counting answer is the same
     if (accumulator == null) {
       return 0L;
     }
diff --git 
a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/IntegerTupleSketchAggregationFunction.java
 
b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/IntegerTupleSketchAggregationFunction.java
index 83ae908cfa8..7591272c0d5 100644
--- 
a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/IntegerTupleSketchAggregationFunction.java
+++ 
b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/IntegerTupleSketchAggregationFunction.java
@@ -96,15 +96,16 @@ import org.apache.pinot.spi.utils.CommonConstants;
 ///     )
 @SuppressWarnings({"rawtypes"})
 public class IntegerTupleSketchAggregationFunction
-    extends BaseSingleInputAggregationFunction<TupleIntSketchAccumulator, 
Comparable> {
+    extends NullableSingleInputAggregationFunction<TupleIntSketchAccumulator, 
Comparable> {
   private static final int DEFAULT_ACCUMULATOR_THRESHOLD = 2;
   final ExpressionContext _expressionContext;
   final IntegerSummarySetOperations _setOps;
   protected int _accumulatorThreshold = DEFAULT_ACCUMULATOR_THRESHOLD;
   protected int _nominalEntries;
 
-  public IntegerTupleSketchAggregationFunction(List<ExpressionContext> 
arguments, IntegerSummary.Mode mode) {
-    super(arguments.get(0));
+  public IntegerTupleSketchAggregationFunction(List<ExpressionContext> 
arguments, IntegerSummary.Mode mode,
+      boolean nullHandlingEnabled) {
+    super(arguments.get(0), nullHandlingEnabled);
 
     Preconditions.checkArgument(arguments.size() <= 2,
         "Tuple Sketch Aggregation Function expects at most 2 arguments, got: 
%s", arguments.size());
@@ -156,11 +157,19 @@ public class IntegerTupleSketchAggregationFunction
     if (storedType == FieldSpec.DataType.BYTES) {
       byte[][] bytesValues = blockValSet.getBytesValuesSV();
       try {
-        TupleIntSketchAccumulator tupleIntSketchAccumulator = 
getAccumulator(aggregationResultHolder);
-        TupleSketch<IntegerSummary>[] sketches = 
deserializeSketches(bytesValues, length);
-        for (TupleSketch<IntegerSummary> sketch : sketches) {
-          tupleIntSketchAccumulator.apply(sketch);
-        }
+        // The accumulator is created inside the range, so an all-null block 
leaves the holder untouched and
+        // extractFinalResult sees the null that means nothing was aggregated
+        forEachNotNull(length, blockValSet, (from, to) -> {
+          // An empty range still reaches here, for a zero-length block. 
Creating the accumulator for it would mark
+          // the holder as aggregated and lose the signal this whole 
arrangement exists to carry.
+          if (to == from) {
+            return;
+          }
+          TupleIntSketchAccumulator tupleIntSketchAccumulator = 
getAccumulator(aggregationResultHolder);
+          for (int i = from; i < to; i++) {
+            tupleIntSketchAccumulator.apply(deserializeSketch(bytesValues[i]));
+          }
+        });
       } catch (Exception e) {
         throw new RuntimeException("Caught exception while aggregating Tuple 
Sketches", e);
       }
@@ -181,12 +190,11 @@ public class IntegerTupleSketchAggregationFunction
     if (storedType == FieldSpec.DataType.BYTES) {
       byte[][] bytesValues = blockValSet.getBytesValuesSV();
       try {
-        TupleSketch<IntegerSummary>[] sketches = 
deserializeSketches(bytesValues, length);
-        for (int i = 0; i < length; i++) {
-          TupleIntSketchAccumulator tupleIntSketchAccumulator = 
getAccumulator(groupByResultHolder, groupKeyArray[i]);
-          TupleSketch<IntegerSummary> sketch = sketches[i];
-          tupleIntSketchAccumulator.apply(sketch);
-        }
+        forEachNotNull(length, blockValSet, (from, to) -> {
+          for (int i = from; i < to; i++) {
+            getAccumulator(groupByResultHolder, 
groupKeyArray[i]).apply(deserializeSketch(bytesValues[i]));
+          }
+        });
       } catch (Exception e) {
         throw new RuntimeException("Caught exception while aggregating Tuple 
Sketches", e);
       }
@@ -208,12 +216,15 @@ public class IntegerTupleSketchAggregationFunction
     if (singleValue && storedType == FieldSpec.DataType.BYTES) {
       byte[][] bytesValues = 
blockValSetMap.get(_expression).getBytesValuesSV();
       try {
-        TupleSketch<IntegerSummary>[] sketches = 
deserializeSketches(bytesValues, length);
-        for (int i = 0; i < length; i++) {
-          for (int groupKey : groupKeysArray[i]) {
-            getAccumulator(groupByResultHolder, groupKey).apply(sketches[i]);
+        forEachNotNull(length, blockValSet, (from, to) -> {
+          for (int i = from; i < to; i++) {
+            // Deserialized once per row, not once per group key the row 
belongs to
+            TupleSketch<IntegerSummary> sketch = 
deserializeSketch(bytesValues[i]);
+            for (int groupKey : groupKeysArray[i]) {
+              getAccumulator(groupByResultHolder, groupKey).apply(sketch);
+            }
           }
-        }
+        });
       } catch (Exception e) {
         throw new RuntimeException("Caught exception while aggregating Tuple 
Sketches", e);
       }
@@ -224,12 +235,15 @@ public class IntegerTupleSketchAggregationFunction
   }
 
   @Override
+  @Nullable
   public TupleIntSketchAccumulator 
extractAggregationResult(AggregationResultHolder aggregationResultHolder) {
-    TupleIntSketchAccumulator result = aggregationResultHolder.getResult();
-    if (result == null) {
-      return new TupleIntSketchAccumulator(_setOps, _nominalEntries, 
_accumulatorThreshold);
-    }
-    return result;
+    return aggregationResultHolder.getResult();
+  }
+
+  /// The accumulator an untouched holder stands for, built where the 
disabled-mode answer is rendered rather than
+  /// substituted during extraction.
+  TupleIntSketchAccumulator emptyAccumulator() {
+    return new TupleIntSketchAccumulator(_setOps, _nominalEntries, 
_accumulatorThreshold);
   }
 
   @Nullable
@@ -278,8 +292,13 @@ public class IntegerTupleSketchAggregationFunction
   @Nullable
   @Override
   public Comparable extractFinalResult(@Nullable TupleIntSketchAccumulator 
accumulator) {
+    // A null intermediate result means nothing was aggregated. With null 
handling enabled that is NULL; with it
+    // disabled the answer stays what it has always been, the serialized empty 
sketch.
     if (accumulator == null) {
-      return null;
+      if (_nullHandlingEnabled) {
+        return null;
+      }
+      accumulator = emptyAccumulator();
     }
     accumulator.setNominalEntries(_nominalEntries);
     accumulator.setSetOperations(_setOps);
@@ -327,15 +346,9 @@ public class IntegerTupleSketchAggregationFunction
     return accumulator;
   }
 
-  /// Deserializes the sketches from the bytes.
-  @SuppressWarnings({"unchecked"})
-  private TupleSketch<IntegerSummary>[] deserializeSketches(byte[][] 
serializedSketches, int length) {
-    TupleSketch<IntegerSummary>[] sketches = new TupleSketch[length];
-    for (int i = 0; i < length; i++) {
-      sketches[i] =
-          
TupleSketch.heapifySketch(MemorySegment.ofArray(serializedSketches[i]), new 
IntegerSummaryDeserializer());
-    }
-    return sketches;
+  /// Deserializes a single serialized sketch, so a row that is skipped as 
null is never heapified.
+  private TupleSketch<IntegerSummary> deserializeSketch(byte[] 
serializedSketch) {
+    return TupleSketch.heapifySketch(MemorySegment.ofArray(serializedSketch), 
new IntegerSummaryDeserializer());
   }
 
   /// Helper class to wrap the tuple-sketch parameters.  The initial values 
for the parameters are set to the
diff --git 
a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/SumValuesIntegerTupleSketchAggregationFunction.java
 
b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/SumValuesIntegerTupleSketchAggregationFunction.java
index fb819290df5..d3b8c1bed8c 100644
--- 
a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/SumValuesIntegerTupleSketchAggregationFunction.java
+++ 
b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/SumValuesIntegerTupleSketchAggregationFunction.java
@@ -31,8 +31,9 @@ import org.apache.pinot.segment.spi.AggregationFunctionType;
 
 public class SumValuesIntegerTupleSketchAggregationFunction extends 
IntegerTupleSketchAggregationFunction {
 
-  public 
SumValuesIntegerTupleSketchAggregationFunction(List<ExpressionContext> 
arguments, IntegerSummary.Mode mode) {
-    super(arguments, mode);
+  public 
SumValuesIntegerTupleSketchAggregationFunction(List<ExpressionContext> 
arguments, IntegerSummary.Mode mode,
+      boolean nullHandlingEnabled) {
+    super(arguments, mode, nullHandlingEnabled);
   }
 
   // TODO if extra aggregation modes are supported, make this switch
@@ -49,9 +50,10 @@ public class SumValuesIntegerTupleSketchAggregationFunction 
extends IntegerTuple
   @Nullable
   @Override
   public Comparable extractFinalResult(@Nullable TupleIntSketchAccumulator 
accumulator) {
-    // A null intermediate result means nothing was aggregated, and there is 
nothing to sum
+    // A null intermediate result means nothing was aggregated. With null 
handling enabled there is nothing to sum,
+    // so the answer is NULL; with it disabled it stays what an empty sketch 
summed to, which is zero.
     if (accumulator == null) {
-      return null;
+      return _nullHandlingEnabled ? null : 0L;
     }
     double retainedTotal = 0L;
     accumulator.setNominalEntries(_nominalEntries);
diff --git 
a/pinot-core/src/test/java/org/apache/pinot/core/query/aggregation/function/IntegerTupleSketchAggregationFunctionTest.java
 
b/pinot-core/src/test/java/org/apache/pinot/core/query/aggregation/function/IntegerTupleSketchAggregationFunctionTest.java
index d486bf620e4..20c1eb7d836 100644
--- 
a/pinot-core/src/test/java/org/apache/pinot/core/query/aggregation/function/IntegerTupleSketchAggregationFunctionTest.java
+++ 
b/pinot-core/src/test/java/org/apache/pinot/core/query/aggregation/function/IntegerTupleSketchAggregationFunctionTest.java
@@ -34,7 +34,7 @@ public class IntegerTupleSketchAggregationFunctionTest {
   public void testCanUseStarTreeDefaultK() {
     IntegerTupleSketchAggregationFunction function =
         new 
IntegerTupleSketchAggregationFunction(List.of(ExpressionContext.forIdentifier("col")),
-            IntegerSummary.Mode.Sum);
+            IntegerSummary.Mode.Sum, false);
 
     Assert.assertTrue(function.canUseStarTree(Map.of()));
     
Assert.assertTrue(function.canUseStarTree(Map.of(Constants.THETA_TUPLE_SKETCH_NOMINAL_ENTRIES,
 "16384")));
@@ -46,7 +46,7 @@ public class IntegerTupleSketchAggregationFunctionTest {
   public void testCanUseCustomK() {
     IntegerTupleSketchAggregationFunction function = new 
IntegerTupleSketchAggregationFunction(
         List.of(ExpressionContext.forIdentifier("col"), 
ExpressionContext.forLiteral(Literal.intValue(32768))),
-        IntegerSummary.Mode.Sum);
+        IntegerSummary.Mode.Sum, false);
 
     // Default StarTree lgK = 14 / K=16384
     Assert.assertFalse(function.canUseStarTree(Map.of()));
diff --git 
a/pinot-core/src/test/java/org/apache/pinot/core/query/aggregation/function/IntegerTupleSketchNullHandlingTest.java
 
b/pinot-core/src/test/java/org/apache/pinot/core/query/aggregation/function/IntegerTupleSketchNullHandlingTest.java
new file mode 100644
index 00000000000..bbb227b272e
--- /dev/null
+++ 
b/pinot-core/src/test/java/org/apache/pinot/core/query/aggregation/function/IntegerTupleSketchNullHandlingTest.java
@@ -0,0 +1,132 @@
+/**
+ * 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.core.query.aggregation.function;
+
+import java.util.List;
+import java.util.Map;
+import org.apache.datasketches.tuple.aninteger.IntegerSummary;
+import org.apache.datasketches.tuple.aninteger.IntegerTupleSketch;
+import org.apache.pinot.common.request.context.ExpressionContext;
+import org.apache.pinot.core.common.BlockValSet;
+import org.apache.pinot.core.common.ObjectSerDeUtils;
+import org.apache.pinot.core.common.SyntheticBlockValSets;
+import org.apache.pinot.core.query.aggregation.AggregationResultHolder;
+import org.apache.pinot.segment.local.customobject.TupleIntSketchAccumulator;
+import org.roaringbitmap.RoaringBitmap;
+import org.testng.annotations.Test;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertNotNull;
+import static org.testng.Assert.assertNull;
+
+
+/// Null handling for the integer tuple sketch family, whose input is always a 
column of serialized sketches.
+///
+/// [AggregationFunctionNullContractTest] cannot reach these functions: its 
synthetic `BYTES` block supplies empty
+/// byte arrays, which are not deserializable sketches. They are pinned in its 
skip list, so this is the only place
+/// their null behaviour is checked.
+public class IntegerTupleSketchNullHandlingTest {
+  private static final ExpressionContext COLUMN = 
ExpressionContext.forIdentifier("column");
+  private static final int NUM_DOCS = 4;
+
+  private static byte[][] serializedSketches() {
+    byte[][] values = new byte[NUM_DOCS][];
+    for (int i = 0; i < NUM_DOCS; i++) {
+      IntegerTupleSketch sketch = new IntegerTupleSketch(4, 
IntegerSummary.Mode.Sum);
+      sketch.update(i, 1);
+      values[i] = 
ObjectSerDeUtils.DATA_SKETCH_INT_TUPLE_SER_DE.serialize(sketch.compact());
+    }
+    return values;
+  }
+
+  private static Map<ExpressionContext, BlockValSet> block(RoaringBitmap 
nullBitmap) {
+    return Map.of(COLUMN, SyntheticBlockValSets.Bytes.create(nullBitmap, 
serializedSketches()));
+  }
+
+  private static RoaringBitmap allNull() {
+    RoaringBitmap bitmap = new RoaringBitmap();
+    bitmap.add(0L, NUM_DOCS);
+    return bitmap;
+  }
+
+  private static TupleIntSketchAccumulator 
aggregate(IntegerTupleSketchAggregationFunction function,
+      RoaringBitmap nullBitmap) {
+    AggregationResultHolder resultHolder = 
function.createAggregationResultHolder();
+    function.aggregate(NUM_DOCS, resultHolder, block(nullBitmap));
+    return function.extractAggregationResult(resultHolder);
+  }
+
+  private static IntegerTupleSketchAggregationFunction raw(boolean 
nullHandlingEnabled) {
+    return new IntegerTupleSketchAggregationFunction(List.of(COLUMN), 
IntegerSummary.Mode.Sum, nullHandlingEnabled);
+  }
+
+  /// Only the rows that carry a sketch are deserialized and unioned.
+  @Test
+  public void testNullRowsAreSkipped() {
+    TupleIntSketchAccumulator accumulator = aggregate(raw(true), 
RoaringBitmap.bitmapOf(1, 3));
+
+    assertNotNull(accumulator);
+    assertEquals(accumulator.getResult().getRetainedEntries(), 2);
+  }
+
+  /// Nothing aggregated leaves the holder untouched, which is how the state 
reaches `extractFinalResult`.
+  @Test
+  public void testEveryRowNullYieldsNoIntermediateResult() {
+    assertNull(aggregate(raw(true), allNull()));
+  }
+
+  /// A zero-length block still reaches the range callback, and must not mark 
the holder as aggregated.
+  @Test
+  public void testZeroLengthBlockLeavesTheHolderUntouched() {
+    IntegerTupleSketchAggregationFunction function = raw(true);
+    AggregationResultHolder resultHolder = 
function.createAggregationResultHolder();
+    function.aggregate(0, resultHolder, block(null));
+
+    assertNull(function.extractAggregationResult(resultHolder));
+  }
+
+  /// With the option enabled each function gives its own answer for an empty 
input.
+  @Test
+  public void testEmptyInputAnswersPerFunctionWhenEnabled() {
+    assertNull(raw(true).extractFinalResult(null));
+    assertEquals(new 
DistinctCountIntegerTupleSketchAggregationFunction(List.of(COLUMN), 
IntegerSummary.Mode.Sum, true)
+        .extractFinalResult(null), 0L);
+    assertNull(new 
SumValuesIntegerTupleSketchAggregationFunction(List.of(COLUMN), 
IntegerSummary.Mode.Sum, true)
+        .extractFinalResult(null));
+    assertNull(new 
AvgValueIntegerTupleSketchAggregationFunction(List.of(COLUMN), 
IntegerSummary.Mode.Sum, true)
+        .extractFinalResult(null));
+  }
+
+  /// With the option disabled each function renders what its empty 
accumulator has always rendered: the raw variant
+  /// a serialized empty sketch, the counting and summing variants zero, the 
average `NULL` for want of entries.
+  @Test
+  public void testEmptyInputRendersTheIdentityWhenDisabled() {
+    IntegerTupleSketchAggregationFunction rawFunction = raw(false);
+    Comparable<?> rendered = rawFunction.extractFinalResult(null);
+    assertNotNull(rendered);
+    assertEquals(rendered, 
rawFunction.extractFinalResult(rawFunction.emptyAccumulator()));
+
+    assertEquals(new 
DistinctCountIntegerTupleSketchAggregationFunction(List.of(COLUMN), 
IntegerSummary.Mode.Sum,
+        false).extractFinalResult(null), 0L);
+    assertEquals(new 
SumValuesIntegerTupleSketchAggregationFunction(List.of(COLUMN), 
IntegerSummary.Mode.Sum, false)
+        .extractFinalResult(null), 0L);
+    assertNull(new 
AvgValueIntegerTupleSketchAggregationFunction(List.of(COLUMN), 
IntegerSummary.Mode.Sum, false)
+        .extractFinalResult(null));
+  }
+}


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to