Jackie-Jiang commented on code in PR #19217:
URL: https://github.com/apache/pinot/pull/19217#discussion_r3762480823


##########
pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/IntegerTupleSketchAggregationFunction.java:
##########
@@ -156,11 +157,14 @@ public void aggregate(int length, AggregationResultHolder 
aggregationResultHolde
     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) -> {
+          TupleIntSketchAccumulator tupleIntSketchAccumulator = 
getAccumulator(aggregationResultHolder);
+          for (int i = from; i < to; i++) {
+            tupleIntSketchAccumulator.apply(deserializeSketch(bytesValues[i]));
+          }

Review Comment:
   Correct, and it was a real hole. `forEachNotNull` calls the consumer with 
`(0, 0)` for a zero-length block, so creating the accumulator inside the range 
was not enough — the range itself can be empty, and the holder was marked as 
aggregated for an input that aggregated nothing.
   
   The range now returns early when `to == from`, before the accumulator exists.
   
   `testZeroLengthBlockLeavesTheHolderUntouched` pins it, and I mutation-tested 
the guard: removing it fails that test. Nothing else covered the case, so thank 
you — this would have shipped.



##########
pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/IntegerTupleSketchAggregationFunction.java:
##########
@@ -208,12 +211,13 @@ public void aggregateGroupByMV(int length, int[][] 
groupKeysArray, GroupByResult
     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++) {
+            for (int groupKey : groupKeysArray[i]) {
+              getAccumulator(groupByResultHolder, 
groupKey).apply(deserializeSketch(bytesValues[i]));
+            }
           }

Review Comment:
   Correct, and a regression I introduced. The previous code deserialized each 
row once into an array before the loop; moving deserialization into the range 
put it inside the group-key loop, so a row belonging to N group keys was 
heapified N times.
   
   Now deserialized once per row, above the inner loop, and reused for every 
group key that row belongs to. The single-value group-by path was already once 
per row and is unchanged.



##########
pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/AggregationFunctionFactory.java:
##########
@@ -481,13 +481,16 @@ public static AggregationFunction 
getAggregationFunction(FunctionContext functio
             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,

Review Comment:
   Agreed, and it is the honest gap in this PR. The coverage here is direct 
construction, so it does not prove the factory plumbing reaches server 
aggregation or that the rendered value survives to the broker.
   
   I have not added end-to-end cases, because the existing tuple sketch 
fixtures carry no null `BYTES` rows and building that fixture is a larger piece 
than this change. Tracking it on #19218 rather than claiming coverage this PR 
does not have — the description now says the unit test is the only coverage and 
why the contract test cannot reach this family.



-- 
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]

Reply via email to