Copilot commented on code in PR #19302:
URL: https://github.com/apache/pinot/pull/19302#discussion_r3809348131


##########
pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/DistinctCountULLAggregationFunction.java:
##########
@@ -105,6 +105,15 @@ public void aggregate(int length, AggregationResultHolder 
aggregationResultHolde
 
     DataType storedType = dataType.getStoredType();
 
+    if (blockValSet.isSingleValue()) {
+      aggregateSV(length, aggregationResultHolder, blockValSet, storedType);
+    } else {
+      aggregateMV(length, aggregationResultHolder, blockValSet, storedType);
+    }

Review Comment:
   This query-semantics change is covered only by direct aggregator unit calls, 
so it does not verify planner-to-segment dispatch with an actual MV forward 
index. `UuidAggregationTest` already provides dictionary and raw MV columns and 
currently excludes MV from its `DISTINCTCOUNTULL` query; extend that 
integration test to query both MV columns (including grouped execution).



##########
pinot-core/src/test/java/org/apache/pinot/core/query/aggregation/function/DistinctCountULLAggregationFunctionTest.java:
##########
@@ -34,28 +42,82 @@ public void testCanUseStarTreeDefaultP() {
     DistinctCountULLAggregationFunction function = new 
DistinctCountULLAggregationFunction(
         List.of(ExpressionContext.forIdentifier("col")));
 
-    Assert.assertTrue(function.canUseStarTree(Map.of()));
-    
Assert.assertTrue(function.canUseStarTree(Map.of(Constants.HLLPLUS_ULL_P_KEY, 
"12")));
-    
Assert.assertTrue(function.canUseStarTree(Map.of(Constants.HLLPLUS_ULL_P_KEY, 
12)));
-    
Assert.assertFalse(function.canUseStarTree(Map.of(Constants.HLLPLUS_ULL_P_KEY, 
16)));
+    assertTrue(function.canUseStarTree(Map.of()));
+    assertTrue(function.canUseStarTree(Map.of(Constants.HLLPLUS_ULL_P_KEY, 
"12")));
+    assertTrue(function.canUseStarTree(Map.of(Constants.HLLPLUS_ULL_P_KEY, 
12)));
+    assertFalse(function.canUseStarTree(Map.of(Constants.HLLPLUS_ULL_P_KEY, 
16)));
 
     function = new 
DistinctCountULLAggregationFunction(List.of(ExpressionContext.forIdentifier("col"),
         ExpressionContext.forLiteral(Literal.intValue(12))));
 
-    Assert.assertTrue(function.canUseStarTree(Map.of()));
-    
Assert.assertTrue(function.canUseStarTree(Map.of(Constants.HLLPLUS_ULL_P_KEY, 
"12")));
-    
Assert.assertTrue(function.canUseStarTree(Map.of(Constants.HLLPLUS_ULL_P_KEY, 
12)));
-    
Assert.assertFalse(function.canUseStarTree(Map.of(Constants.HLLPLUS_ULL_P_KEY, 
"16")));
+    assertTrue(function.canUseStarTree(Map.of()));
+    assertTrue(function.canUseStarTree(Map.of(Constants.HLLPLUS_ULL_P_KEY, 
"12")));
+    assertTrue(function.canUseStarTree(Map.of(Constants.HLLPLUS_ULL_P_KEY, 
12)));
+    assertFalse(function.canUseStarTree(Map.of(Constants.HLLPLUS_ULL_P_KEY, 
"16")));
   }
 
   @Test
   public void testCanUseStarTreeCustomP() {
     DistinctCountULLAggregationFunction function = new 
DistinctCountULLAggregationFunction(
         List.of(ExpressionContext.forIdentifier("col"), 
ExpressionContext.forLiteral(Literal.stringValue("16"))));
 
-    Assert.assertFalse(function.canUseStarTree(Map.of()));
-    
Assert.assertFalse(function.canUseStarTree(Map.of(Constants.HLLPLUS_ULL_P_KEY, 
"12")));
-    
Assert.assertTrue(function.canUseStarTree(Map.of(Constants.HLLPLUS_ULL_P_KEY, 
16)));
-    
Assert.assertTrue(function.canUseStarTree(Map.of(Constants.HLLPLUS_ULL_P_KEY, 
"16")));
+    assertFalse(function.canUseStarTree(Map.of()));
+    assertFalse(function.canUseStarTree(Map.of(Constants.HLLPLUS_ULL_P_KEY, 
"12")));
+    assertTrue(function.canUseStarTree(Map.of(Constants.HLLPLUS_ULL_P_KEY, 
16)));
+    assertTrue(function.canUseStarTree(Map.of(Constants.HLLPLUS_ULL_P_KEY, 
"16")));
+  }
+
+  private static final ExpressionContext COLUMN = 
ExpressionContext.forIdentifier("column");
+  private static final long[][] MV_ROWS = {{1L, 2L}, {3L, 4L}, {5L, 6L}, {1L, 
3L}};
+  private static final long[] FLATTENED = {1L, 2L, 3L, 4L, 5L, 6L, 1L, 3L};
+
+  private static DistinctCountULLAggregationFunction create() {
+    return new DistinctCountULLAggregationFunction(List.of(COLUMN));
+  }
+
+  private static Map<ExpressionContext, BlockValSet> mvBlock() {
+    return Map.of(COLUMN, SyntheticBlockValSets.LongMV.create(null, MV_ROWS));

Review Comment:
   The new tests only exercise the non-dictionary `LONG` branch, while this 
change adds separate dictionary handling and six stored-type cases. A broken 
`getDictionaryIdsMV()` path or any of the other five accessors would therefore 
go undetected. Please add a TestNG data provider/real fixtures covering 
dictionary-encoded MV input and each supported stored type across the three 
entry points.



##########
pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/SegmentPartitionedDistinctCountAggregationFunction.java:
##########
@@ -72,7 +72,14 @@ public GroupByResultHolder createGroupByResultHolder(int 
initialCapacity, int ma
   public void aggregate(int length, AggregationResultHolder 
aggregationResultHolder,
       Map<ExpressionContext, BlockValSet> blockValSetMap) {
     BlockValSet blockValSet = blockValSetMap.get(_expression);
+    if (blockValSet.isSingleValue()) {
+      aggregateSV(length, aggregationResultHolder, blockValSet);
+    } else {
+      aggregateMV(length, aggregationResultHolder, blockValSet);

Review Comment:
   The new MV dispatch is tested only by calling this class directly. Add a 
custom-cluster integration query over a real MV column for aggregate, 
group-by-SV, and group-by-MV shapes; otherwise reader/planner integration 
regressions can pass these unit tests while the SQL feature still fails end to 
end.
   
   This issue also appears on line 75 of the same file.



##########
pinot-core/src/test/java/org/apache/pinot/core/query/aggregation/function/SegmentPartitionedDistinctCountAggregationFunctionTest.java:
##########
@@ -0,0 +1,98 @@
+/**
+ * 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.pinot.common.request.context.ExpressionContext;
+import org.apache.pinot.core.common.BlockValSet;
+import org.apache.pinot.core.common.SyntheticBlockValSets;
+import org.apache.pinot.core.query.aggregation.AggregationResultHolder;
+import org.apache.pinot.core.query.aggregation.groupby.GroupByResultHolder;
+import 
org.apache.pinot.core.query.aggregation.groupby.ObjectGroupByResultHolder;
+import org.testng.annotations.Test;
+
+import static org.testng.Assert.assertEquals;
+
+
+/// Multi-value column support for `SEGMENTPARTITIONEDDISTINCTCOUNT`.
+///
+/// This function used to read the single-value accessors unconditionally, so 
a multi-value column was not something
+/// it could aggregate at all. It now branches on 
`BlockValSet.isSingleValue()` the way the HyperLogLog and bitmap
+/// families already did. Its counts are exact, so the answers are asserted 
directly rather than as estimates.
+public class SegmentPartitionedDistinctCountAggregationFunctionTest {
+  private static final ExpressionContext COLUMN = 
ExpressionContext.forIdentifier("column");
+  private static final long[][] MV_ROWS = {{1L, 2L}, {3L, 4L}, {5L, 6L}, {1L, 
3L}};
+  private static final long[] FLATTENED = {1L, 2L, 3L, 4L, 5L, 6L, 1L, 3L};
+
+  private static SegmentPartitionedDistinctCountAggregationFunction create() {
+    return new 
SegmentPartitionedDistinctCountAggregationFunction(List.of(COLUMN));
+  }
+
+  private static Map<ExpressionContext, BlockValSet> mvBlock() {
+    return Map.of(COLUMN, SyntheticBlockValSets.LongMV.create(null, MV_ROWS));

Review Comment:
   This fixture covers only the non-dictionary `LONG`/hash-set branch. The 
implementation has materially different dictionary and `INT` bitmap paths plus 
FLOAT, DOUBLE, STRING, and BYTES sets, so errors in those new branches are 
currently invisible. Please make these tests data-driven over all supported 
stored types and include a real dictionary-encoded MV case for each aggregation 
shape.



##########
pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/SegmentPartitionedDistinctCountAggregationFunction.java:
##########
@@ -298,6 +491,97 @@ public void aggregateGroupByMV(int length, int[][] 
groupKeysArray, GroupByResult
     }
   }
 
+  protected void aggregateMVGroupByMV(int length, int[][] groupKeysArray, 
GroupByResultHolder groupByResultHolder,
+      BlockValSet blockValSet) {
+    // For dictionary-encoded expression, store dictionary ids into a 
RoaringBitmap
+    if (blockValSet.isDictionaryEncoded()) {
+      int[][] dictIds = blockValSet.getDictionaryIdsMV();
+      for (int i = 0; i < length; i++) {
+        int[] rowDictIds = dictIds[i];
+        for (int groupKey : groupKeysArray[i]) {
+          for (int dictId : rowDictIds) {
+            setIntValueForGroup(groupByResultHolder, groupKey, dictId);
+          }
+        }
+      }
+      return;
+    }
+
+    // For non-dictionary-encoded expression, store INT values into a 
RoaringBitmap, other types into an OpenHashSet
+    DataType storedType = blockValSet.getValueType().getStoredType();
+    switch (storedType) {
+      case INT:
+        int[][] intValues = blockValSet.getIntValuesMV();
+        for (int i = 0; i < length; i++) {
+          int[] intRow = intValues[i];
+          for (int groupKey : groupKeysArray[i]) {
+            for (int value : intRow) {
+              setIntValueForGroup(groupByResultHolder, groupKey, value);
+            }
+          }
+        }
+        break;
+      case LONG:
+        long[][] longValues = blockValSet.getLongValuesMV();
+        for (int i = 0; i < length; i++) {
+          long[] longRow = longValues[i];
+          for (int groupKey : groupKeysArray[i]) {
+            for (long value : longRow) {
+              setLongValueForGroup(groupByResultHolder, groupKey, value);
+            }
+          }
+        }
+        break;
+      case FLOAT:
+        float[][] floatValues = blockValSet.getFloatValuesMV();
+        for (int i = 0; i < length; i++) {
+          float[] floatRow = floatValues[i];
+          for (int groupKey : groupKeysArray[i]) {
+            for (float value : floatRow) {
+              setFloatValueForGroup(groupByResultHolder, groupKey, value);
+            }
+          }
+        }
+        break;
+      case DOUBLE:
+        double[][] doubleValues = blockValSet.getDoubleValuesMV();
+        for (int i = 0; i < length; i++) {
+          double[] doubleRow = doubleValues[i];
+          for (int groupKey : groupKeysArray[i]) {
+            for (double value : doubleRow) {
+              setDoubleValueForGroup(groupByResultHolder, groupKey, value);
+            }
+          }
+        }
+        break;
+      case STRING:
+        String[][] stringValues = blockValSet.getStringValuesMV();
+        for (int i = 0; i < length; i++) {
+          String[] stringRow = stringValues[i];
+          for (int groupKey : groupKeysArray[i]) {
+            for (String value : stringRow) {
+              setStringValueForGroup(groupByResultHolder, groupKey, value);
+            }
+          }
+        }
+        break;
+      case BYTES:
+        byte[][][] bytesValues = blockValSet.getBytesValuesMV();
+        for (int i = 0; i < length; i++) {
+          byte[][] bytesRow = bytesValues[i];
+          for (int groupKey : groupKeysArray[i]) {
+            for (byte[] value : bytesRow) {
+              setBytesValueForGroup(groupByResultHolder, groupKey, new 
ByteArray(value));
+            }
+          }
+        }

Review Comment:
   This allocates a new `ByteArray` for every `(groupKey, value)` pair. For MV 
group-by rows, allocation grows as `groups × byte values`, although the same 
immutable wrapper can be shared across all groups (as the SV helper already 
does). Iterate `bytesRow` first, create one wrapper per value, then add it to 
each group.



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