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 d34dbf6e296 Fix set-mode FUNNELCOUNT merge with an empty segment's 
step sets (#20201)
d34dbf6e296 is described below

commit d34dbf6e29654ae4a2d9ad975540fe74cb0ac499
Author: Xiaotian (Jackie) Jiang <[email protected]>
AuthorDate: Tue Nov 10 20:42:00 2026 -0800

    Fix set-mode FUNNELCOUNT merge with an empty segment's step sets (#20201)
---
 .../function/funnel/SetMergeStrategy.java           | 21 ++++++++++++++++++---
 .../function/funnel/FunnelCountSerDeTest.java       | 17 +++++++++++++++++
 2 files changed, 35 insertions(+), 3 deletions(-)

diff --git 
a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/funnel/SetMergeStrategy.java
 
b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/funnel/SetMergeStrategy.java
index de8809a053d..e47f6b632bd 100644
--- 
a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/funnel/SetMergeStrategy.java
+++ 
b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/funnel/SetMergeStrategy.java
@@ -56,15 +56,30 @@ class SetMergeStrategy implements MergeStrategy<List<Set>> {
 
   @Override
   public List<Set> merge(List<Set> intermediateResult1, List<Set> 
intermediateResult2) {
-    // TODO: Fix merging typed step sets into empty ones. A segment without 
rows contributes empty `ObjectOpenHashSet`s.
-    //   When those are the first operand, the int, long, float or double 
values of the other side are boxed into
-    //   them, `setType` maps the result to the string set type, and 
serialization throws `ClassCastException`.
+    // A segment without rows contributes empty string sets whatever the 
stored type of the correlation column. Take the
+    // other side as a whole when one side has no values, so that the step 
sets of one list keep a single set type.
+    if (hasNoValues(intermediateResult2)) {
+      return intermediateResult1;
+    }
+    if (hasNoValues(intermediateResult1)) {
+      return intermediateResult2;
+    }
     for (int i = 0; i < _numSteps; i++) {
       intermediateResult1.get(i).addAll(intermediateResult2.get(i));
     }
     return intermediateResult1;
   }
 
+  /// Returns whether all step sets are empty, as they are for a segment 
without rows.
+  private static boolean hasNoValues(List<Set> stepsSets) {
+    for (Set stepSet : stepsSets) {
+      if (!stepSet.isEmpty()) {
+        return false;
+      }
+    }
+    return true;
+  }
+
   @Override
   public long[] extractFinalResult(List<Set> stepsSets) {
     long[] result = new long[_numSteps];
diff --git 
a/pinot-core/src/test/java/org/apache/pinot/core/query/aggregation/function/funnel/FunnelCountSerDeTest.java
 
b/pinot-core/src/test/java/org/apache/pinot/core/query/aggregation/function/funnel/FunnelCountSerDeTest.java
index fb41ca92b09..dc7d6bc5e86 100644
--- 
a/pinot-core/src/test/java/org/apache/pinot/core/query/aggregation/function/funnel/FunnelCountSerDeTest.java
+++ 
b/pinot-core/src/test/java/org/apache/pinot/core/query/aggregation/function/funnel/FunnelCountSerDeTest.java
@@ -30,6 +30,7 @@ import org.apache.pinot.common.CustomObject;
 import org.apache.pinot.common.request.Literal;
 import org.apache.pinot.common.request.context.ExpressionContext;
 import org.apache.pinot.common.request.context.FunctionContext;
+import org.apache.pinot.core.query.aggregation.AggregationResultHolder;
 import org.apache.pinot.core.query.aggregation.function.AggregationFunction;
 import 
org.apache.pinot.core.query.aggregation.function.AggregationFunction.SerializedResult;
 import org.roaringbitmap.RoaringBitmap;
@@ -83,6 +84,22 @@ public class FunnelCountSerDeTest {
         new long[]{1, 0});
   }
 
+  /// A segment without rows contributes empty step sets whatever the stored 
type of the correlation column. Merged on
+  /// either side of typed step sets, they leave the typed sets, which go out 
with the set type of that stored type.
+  @Test
+  public void testMergeSetStepsWithEmptySegment() {
+    AggregationFunction function = funnelCount("set");
+    AggregationResultHolder emptySegment = 
function.createAggregationResultHolder();
+    List intSteps = List.of(new IntOpenHashSet(new int[]{7}), new 
IntOpenHashSet());
+    byte[] intSets = HexFormat.of().parseHex("00000002" + "00000009"
+        + "00000008" + "0000000100000007"
+        + "00000004" + "00000000");
+    assertListSteps(function, (List) 
function.merge(function.extractAggregationResult(emptySegment), intSteps),
+        intSets, new long[]{1, 0});
+    assertListSteps(function, (List) function.merge(intSteps, 
function.extractAggregationResult(emptySegment)),
+        intSets, new long[]{1, 0});
+  }
+
   /// Step sketches go out as `[int size][int DATA_SKETCH_TYPE]`, then `[int 
length]` and the compact sketch bytes per
   /// step.
   @Test


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

Reply via email to