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

xiangfu0 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 c3cd3c70ad2 Fix TDigest accumulator serialization through generic 
serdes for capacity-preserving state (#19017)
c3cd3c70ad2 is described below

commit c3cd3c70ad264665555b7204fc973e4adeb09c7d
Author: Xiang Fu <[email protected]>
AuthorDate: Wed Jul 22 04:29:25 2026 -0700

    Fix TDigest accumulator serialization through generic serdes for 
capacity-preserving state (#19017)
    
    * Fix TDigest accumulator serialization through generic serdes for 
capacity-preserving state
    
    PercentileTDigestAccumulator now overrides byteSize()/asBytes() to emit its
    mixed-capacity-safe serialize() bytes, so ObjectSerDeUtils.TDIGEST_SER_DE 
and
    CustomSerDeUtils.TDIGEST_SER_DE stay readable by plain 
MergingDigest.fromBytes
    readers (percentileRawTDigest final results previously emitted verbose bytes
    with more centroids than a fresh reader allocates, failing with
    ArrayIndexOutOfBoundsException). Also routes percentileSmartTDigest through 
the
    accumulator, completing the #18996 routing.
    
    * Address review comments: null-safe merge, pending write-through in 
asBytes, static test imports
---
 .../PercentileSmartTDigestAggregationFunction.java | 34 +++++---
 .../function/PercentileTDigestAccumulator.java     | 45 +++++++++--
 .../PercentileTDigestAggregationFunction.java      |  9 +--
 ...ercentileRawTDigestAggregationFunctionTest.java | 90 ++++++++++++++++++++++
 ...centileSmartTDigestAggregationFunctionTest.java | 70 +++++++++++++++++
 .../PercentileTDigestAggregationFunctionTest.java  | 18 +++++
 6 files changed, 241 insertions(+), 25 deletions(-)

diff --git 
a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/PercentileSmartTDigestAggregationFunction.java
 
b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/PercentileSmartTDigestAggregationFunction.java
index 24ffd615ec5..539b3a7eea0 100644
--- 
a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/PercentileSmartTDigestAggregationFunction.java
+++ 
b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/PercentileSmartTDigestAggregationFunction.java
@@ -25,6 +25,7 @@ import it.unimi.dsi.fastutil.doubles.DoubleListIterator;
 import java.util.Arrays;
 import java.util.List;
 import java.util.Map;
+import javax.annotation.Nullable;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.pinot.common.CustomObject;
 import org.apache.pinot.common.request.context.ExpressionContext;
@@ -184,7 +185,7 @@ public class PercentileSmartTDigestAggregationFunction 
extends NullableSingleInp
   }
 
   private TDigest convertValueListToTDigest(DoubleArrayList valueList) {
-    TDigest tDigest = TDigest.createMergingDigest(_compression);
+    TDigest tDigest = new PercentileTDigestAccumulator(_compression);
     DoubleListIterator iterator = valueList.iterator();
     while (iterator.hasNext()) {
       tDigest.add(iterator.nextDouble());
@@ -265,12 +266,19 @@ public class PercentileSmartTDigestAggregationFunction 
extends NullableSingleInp
   }
 
   @Override
-  public Object merge(Object intermediateResult1, Object intermediateResult2) {
-    if (intermediateResult1 instanceof TDigest) {
-      return mergeIntoTDigest((TDigest) intermediateResult1, 
intermediateResult2);
+  @Nullable
+  public Object merge(@Nullable Object intermediateResult1, @Nullable Object 
intermediateResult2) {
+    if (intermediateResult1 == null) {
+      return intermediateResult2;
     }
-    if (intermediateResult2 instanceof TDigest) {
-      return mergeIntoTDigest((TDigest) intermediateResult2, 
intermediateResult1);
+    if (intermediateResult2 == null) {
+      return intermediateResult1;
+    }
+    if (intermediateResult1 instanceof PercentileTDigestAccumulator) {
+      return mergeIntoAccumulator((PercentileTDigestAccumulator) 
intermediateResult1, intermediateResult2);
+    }
+    if (intermediateResult2 instanceof PercentileTDigestAccumulator) {
+      return mergeIntoAccumulator((PercentileTDigestAccumulator) 
intermediateResult2, intermediateResult1);
     }
     DoubleArrayList valueList1 = (DoubleArrayList) intermediateResult1;
     DoubleArrayList valueList2 = (DoubleArrayList) intermediateResult2;
@@ -278,17 +286,18 @@ public class PercentileSmartTDigestAggregationFunction 
extends NullableSingleInp
     return valueList1.size() > _threshold ? 
convertValueListToTDigest(valueList1) : valueList1;
   }
 
-  private static TDigest mergeIntoTDigest(TDigest tDigest, Object 
intermediateResult) {
+  private static PercentileTDigestAccumulator 
mergeIntoAccumulator(PercentileTDigestAccumulator accumulator,
+      Object intermediateResult) {
     if (intermediateResult instanceof TDigest) {
-      tDigest.add((TDigest) intermediateResult);
+      accumulator.add((TDigest) intermediateResult);
     } else {
       DoubleArrayList valueList = (DoubleArrayList) intermediateResult;
       DoubleListIterator iterator = valueList.iterator();
       while (iterator.hasNext()) {
-        tDigest.add(iterator.nextDouble());
+        accumulator.add(iterator.nextDouble());
       }
     }
-    return tDigest;
+    return accumulator;
   }
 
   @Override
@@ -309,6 +318,11 @@ public class PercentileSmartTDigestAggregationFunction 
extends NullableSingleInp
 
   @Override
   public Object deserializeIntermediateResult(CustomObject customObject) {
+    if (customObject.getType() == 
ObjectSerDeUtils.ObjectType.TDigest.getValue()) {
+      // Generic TDigest deserialization returns a plain MergingDigest. Keep 
this function's TDigest intermediates as
+      // accumulators so subsequent merges retain the capacity-preserving 
serialization path.
+      return 
PercentileTDigestAccumulator.forSerializedTDigest(customObject.getBuffer());
+    }
     return ObjectSerDeUtils.deserialize(customObject);
   }
 
diff --git 
a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/PercentileTDigestAccumulator.java
 
b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/PercentileTDigestAccumulator.java
index 569cc269167..b0fdac2e49d 100644
--- 
a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/PercentileTDigestAccumulator.java
+++ 
b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/PercentileTDigestAccumulator.java
@@ -37,8 +37,9 @@ import java.util.List;
 /// its explicit capacity is required to decode an oversized compact digest.
 ///
 /// The accumulator implements [TDigest] so process-local combine and 
reduction can retain the primitive state.
-/// Quantile queries and CDFs read the primitive state directly. Centroid 
iteration and serialization use the
-/// canonical library implementation returned from [#toTDigest()].
+/// Quantile queries and CDFs read the primitive state directly. Centroid 
iteration uses the canonical library
+/// implementation returned from [#toTDigest()], while [#byteSize()] and 
[#asBytes(ByteBuffer)] emit [#serialize()]
+/// bytes directly so generic `TDigest` serializers preserve 
capacity-preserving state.
 ///
 /// Serialized group state keeps its first digest pending, and allocates 
primitive centroid buffers only when another
 /// input must be merged. The raw-value buffer remains unallocated for 
serialized state until a raw value is added.
@@ -476,9 +477,25 @@ final class PercentileTDigestAccumulator extends TDigest {
     return _compression;
   }
 
+  /// Returns the length of the [#serialize()] bytes rather than the 
materialized [MergingDigest] byte size, so
+  /// generic `TDigest` serializers ([#byteSize()] followed by 
[#asBytes(ByteBuffer)]) emit the capacity-preserving
+  /// encoding. Re-encoding through a materialized [MergingDigest] can produce 
a verbose digest with more centroids
+  /// than a freshly allocated [MergingDigest] of the same compression can 
hold, which readers reject with
+  /// [ArrayIndexOutOfBoundsException]. The length is computed without 
materializing the bytes by mirroring the
+  /// [#serialize()] branches; the mutations here (flush, capacity 
normalization) are idempotent, so the following
+  /// [#asBytes(ByteBuffer)] call writes exactly this many bytes.
   @Override
   public int byteSize() {
-    return toTDigest().byteSize();
+    if (_pendingSerializedTDigest != null) {
+      return _pendingSerializedTDigest.length;
+    }
+    flush();
+    if (requiresCapacityPreservingEncoding()) {
+      checkCapacityPreservingCentroidCount();
+      return SMALL_HEADER_SIZE + SMALL_CENTROID_SIZE * _numCentroids;
+    }
+    normalizeCentroidCapacity();
+    return VERBOSE_HEADER_SIZE + VERBOSE_CENTROID_SIZE * _numCentroids;
   }
 
   @Override
@@ -486,9 +503,17 @@ final class PercentileTDigestAccumulator extends TDigest {
     return toTDigest().smallByteSize();
   }
 
+  /// Writes the [#serialize()] bytes; see [#byteSize()]. Bytes are always 
written in big-endian order (the t-digest
+  /// wire order) regardless of the destination buffer's byte order, unlike 
[MergingDigest#asBytes(ByteBuffer)].
   @Override
   public void asBytes(ByteBuffer buffer) {
-    toTDigest().asBytes(buffer);
+    if (_pendingSerializedTDigest != null) {
+      // Same validation as serialize(), but write through without the 
defensive clone.
+      getSerializedTotalWeight(_pendingSerializedTDigest);
+      buffer.put(_pendingSerializedTDigest);
+      return;
+    }
+    buffer.put(serialize());
   }
 
   @Override
@@ -610,10 +635,7 @@ final class PercentileTDigestAccumulator extends TDigest {
   }
 
   private byte[] toCapacityPreservingBytes() {
-    if (_numCentroids > Short.MAX_VALUE) {
-      throw new IllegalStateException("TDigest has too many centroids for 
capacity-preserving encoding: "
-          + _numCentroids);
-    }
+    checkCapacityPreservingCentroidCount();
     int mainCapacity = Math.min(Short.MAX_VALUE, Math.max(_numCentroids, 
_serializedMainCapacity));
     long defaultBufferCapacity = 
Math.multiplyExact(DEFAULT_MERGE_BUFFER_MULTIPLIER, (long) 
Math.ceil(_compression));
     int bufferCapacity = Math.toIntExact(Math.min(Short.MAX_VALUE,
@@ -633,6 +655,13 @@ final class PercentileTDigestAccumulator extends TDigest {
     return buffer.array();
   }
 
+  private void checkCapacityPreservingCentroidCount() {
+    if (_numCentroids > Short.MAX_VALUE) {
+      throw new IllegalStateException("TDigest has too many centroids for 
capacity-preserving encoding: "
+          + _numCentroids);
+    }
+  }
+
   private void flush() {
     if (_numRawValues == 0) {
       return;
diff --git 
a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/PercentileTDigestAggregationFunction.java
 
b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/PercentileTDigestAggregationFunction.java
index 8fd195c6c89..eacf5d91f5a 100644
--- 
a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/PercentileTDigestAggregationFunction.java
+++ 
b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/PercentileTDigestAggregationFunction.java
@@ -300,13 +300,8 @@ public class PercentileTDigestAggregationFunction extends 
NullableSingleInputAgg
 
   @Override
   public SerializedIntermediateResult serializeIntermediateResult(TDigest 
tDigest) {
-    byte[] bytes;
-    if (tDigest instanceof PercentileTDigestAccumulator) {
-      bytes = ((PercentileTDigestAccumulator) tDigest).serialize();
-    } else {
-      bytes = ObjectSerDeUtils.TDIGEST_SER_DE.serialize(tDigest);
-    }
-    return new 
SerializedIntermediateResult(ObjectSerDeUtils.ObjectType.TDigest.getValue(), 
bytes);
+    return new 
SerializedIntermediateResult(ObjectSerDeUtils.ObjectType.TDigest.getValue(),
+        ObjectSerDeUtils.TDIGEST_SER_DE.serialize(tDigest));
   }
 
   @Override
diff --git 
a/pinot-core/src/test/java/org/apache/pinot/core/query/aggregation/function/PercentileRawTDigestAggregationFunctionTest.java
 
b/pinot-core/src/test/java/org/apache/pinot/core/query/aggregation/function/PercentileRawTDigestAggregationFunctionTest.java
new file mode 100644
index 00000000000..911374148b5
--- /dev/null
+++ 
b/pinot-core/src/test/java/org/apache/pinot/core/query/aggregation/function/PercentileRawTDigestAggregationFunctionTest.java
@@ -0,0 +1,90 @@
+/**
+ * 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 com.tdunning.math.stats.MergingDigest;
+import com.tdunning.math.stats.TDigest;
+import java.nio.ByteBuffer;
+import org.apache.pinot.common.CustomObject;
+import org.apache.pinot.common.request.context.ExpressionContext;
+import org.apache.pinot.core.common.ObjectSerDeUtils;
+import org.apache.pinot.spi.utils.BytesUtils;
+import org.testng.annotations.Test;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertTrue;
+
+
+/// Tests that the final result of `percentileRawTDigest` stays readable by a 
plain t-digest
+/// [MergingDigest#fromBytes] reader when the intermediate 
[PercentileTDigestAccumulator] holds
+/// capacity-preserving state (a digest with more centroids than a freshly 
allocated
+/// [MergingDigest] of the same compression can hold).
+public class PercentileRawTDigestAggregationFunctionTest {
+  private static final ExpressionContext EXPRESSION = 
ExpressionContext.forIdentifier("col");
+
+  @Test
+  public void testFinalResultReadableByPlainReaderForCapacityPreservingState() 
{
+    int numCentroids = 51;
+    double compression = 20.0;
+    byte[] small = createSmallUnitCentroidDigest(numCentroids, compression, 
60, 100);
+
+    // Sanity: the input itself is readable by a plain t-digest reader.
+    TDigest direct = MergingDigest.fromBytes(ByteBuffer.wrap(small));
+    assertEquals(direct.size(), numCentroids);
+
+    PercentileRawTDigestAggregationFunction function =
+        new PercentileRawTDigestAggregationFunction(EXPRESSION, 50.0, (int) 
compression, false);
+    TDigest intermediateResult = function.deserializeIntermediateResult(
+        new CustomObject(ObjectSerDeUtils.ObjectType.TDigest.getValue(), 
ByteBuffer.wrap(small)));
+    assertTrue(intermediateResult instanceof PercentileTDigestAccumulator);
+
+    byte[] serialized = 
BytesUtils.toBytes(function.extractFinalResult(intermediateResult).toString());
+
+    // Client side: a plain t-digest reader must be able to read the emitted 
bytes without losing
+    // state. Before the fix this threw ArrayIndexOutOfBoundsException because 
the final result was
+    // re-encoded as a verbose digest with more centroids than the reader 
allocates.
+    TDigest roundTripped = 
MergingDigest.fromBytes(ByteBuffer.wrap(serialized));
+    assertEquals(roundTripped.size(), numCentroids);
+    assertEquals(roundTripped.quantile(0.0), 0.0);
+    assertEquals(roundTripped.quantile(0.5), (numCentroids - 1.0) / 2.0, 1.0);
+    assertEquals(roundTripped.quantile(1.0), numCentroids - 1.0);
+  }
+
+  /// SMALL-encoded digest (encoding 2) with explicit main/buffer capacities, 
unit-weight centroids
+  /// at means 0..numCentroids-1. This is the layout t-digest's `asSmallBytes` 
produces and
+  /// `MergingDigest.fromBytes` accepts regardless of the default capacity for 
the compression.
+  /// Package-private: shared with 
[PercentileSmartTDigestAggregationFunctionTest].
+  static byte[] createSmallUnitCentroidDigest(int numCentroids, double 
compression, int mainCapacity,
+      int bufferCapacity) {
+    ByteBuffer buffer = ByteBuffer.allocate(Integer.BYTES + 2 * Double.BYTES + 
Float.BYTES + 3 * Short.BYTES
+        + 2 * Float.BYTES * numCentroids);
+    buffer.putInt(2);
+    buffer.putDouble(0.0);
+    buffer.putDouble(numCentroids - 1.0);
+    buffer.putFloat((float) compression);
+    buffer.putShort((short) mainCapacity);
+    buffer.putShort((short) bufferCapacity);
+    buffer.putShort((short) numCentroids);
+    for (int i = 0; i < numCentroids; i++) {
+      buffer.putFloat(1.0f);
+      buffer.putFloat(i);
+    }
+    return buffer.array();
+  }
+}
diff --git 
a/pinot-core/src/test/java/org/apache/pinot/core/query/aggregation/function/PercentileSmartTDigestAggregationFunctionTest.java
 
b/pinot-core/src/test/java/org/apache/pinot/core/query/aggregation/function/PercentileSmartTDigestAggregationFunctionTest.java
index 68a180ea886..4cb07afd996 100644
--- 
a/pinot-core/src/test/java/org/apache/pinot/core/query/aggregation/function/PercentileSmartTDigestAggregationFunctionTest.java
+++ 
b/pinot-core/src/test/java/org/apache/pinot/core/query/aggregation/function/PercentileSmartTDigestAggregationFunctionTest.java
@@ -18,8 +18,78 @@
  */
 package org.apache.pinot.core.query.aggregation.function;
 
+import com.tdunning.math.stats.MergingDigest;
+import com.tdunning.math.stats.TDigest;
+import it.unimi.dsi.fastutil.doubles.DoubleArrayList;
+import java.nio.ByteBuffer;
+import java.util.List;
+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.core.common.ObjectSerDeUtils;
+import org.testng.annotations.Test;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertTrue;
+
 
 public class PercentileSmartTDigestAggregationFunctionTest {
+  private static final ExpressionContext EXPRESSION = 
ExpressionContext.forIdentifier("col");
+
+  /// Exercises the complete capacity-preserving intermediate lifecycle: 
pass-through serde, materialization by
+  /// merging another digest, and raw-list merging in both argument orders.
+  @Test
+  public void testCapacityPreservingIntermediateLifecycle() {
+    int numCentroids = 51;
+    byte[] small = 
PercentileRawTDigestAggregationFunctionTest.createSmallUnitCentroidDigest(numCentroids,
 20.0, 60,
+        100);
+    PercentileSmartTDigestAggregationFunction function = createFunction();
+
+    Object intermediateResult = function.deserializeIntermediateResult(
+        new CustomObject(ObjectSerDeUtils.ObjectType.TDigest.getValue(), 
ByteBuffer.wrap(small)));
+    assertTrue(intermediateResult instanceof PercentileTDigestAccumulator);
+
+    AggregationFunction.SerializedIntermediateResult passThrough =
+        function.serializeIntermediateResult(intermediateResult);
+    assertEquals(passThrough.getType(), 
ObjectSerDeUtils.ObjectType.TDigest.getValue());
+    // Before the fix this was a verbose encoding with 51 centroids, which a 
plain
+    // MergingDigest.fromBytes reader rejects with 
ArrayIndexOutOfBoundsException.
+    
assertEquals(MergingDigest.fromBytes(ByteBuffer.wrap(passThrough.getBytes())).size(),
 numCentroids);
+
+    byte[] empty = 
ObjectSerDeUtils.TDIGEST_SER_DE.serialize(TDigest.createMergingDigest(20.0));
+    Object merged = function.merge(
+        intermediateResult,
+        function.deserializeIntermediateResult(
+            new CustomObject(ObjectSerDeUtils.ObjectType.TDigest.getValue(), 
ByteBuffer.wrap(empty))));
+    byte[] serialized = 
function.serializeIntermediateResult(merged).getBytes();
+    assertEquals(ByteBuffer.wrap(serialized).getInt(), 2, "Expected small 
(capacity-preserving) encoding");
+    assertEquals(((TDigest) merged).byteSize(), serialized.length);
+    TDigest roundTripped = 
MergingDigest.fromBytes(ByteBuffer.wrap(serialized));
+    assertEquals(roundTripped.size(), numCentroids);
+    assertEquals(roundTripped.quantile(0.5), (numCentroids - 1.0) / 2.0, 1.0);
+
+    DoubleArrayList values = new DoubleArrayList(new double[]{0.0, 25.0, 
50.0});
+    assertEquals(((TDigest) function.merge(merged, values)).size(), 
numCentroids + 3L);
+    Object reverseOrder = function.merge(new DoubleArrayList(values), 
function.deserializeIntermediateResult(
+        new CustomObject(ObjectSerDeUtils.ObjectType.TDigest.getValue(), 
ByteBuffer.wrap(small))));
+    assertTrue(reverseOrder instanceof PercentileTDigestAccumulator);
+    assertEquals(((TDigest) reverseOrder).size(), numCentroids + 3L);
+  }
+
+  @Test
+  public void testMergeWithNullIntermediateResult() {
+    PercentileSmartTDigestAggregationFunction function = createFunction();
+    DoubleArrayList valueList = new DoubleArrayList(new double[]{1.0, 2.0});
+    assertEquals(function.merge(null, valueList), valueList);
+    assertEquals(function.merge(valueList, null), valueList);
+    assertEquals(function.merge(null, null), null);
+  }
+
+  private static PercentileSmartTDigestAggregationFunction createFunction() {
+    return new PercentileSmartTDigestAggregationFunction(
+        List.of(EXPRESSION, 
ExpressionContext.forLiteral(Literal.doubleValue(50.0)),
+            
ExpressionContext.forLiteral(Literal.stringValue("THRESHOLD=1;COMPRESSION=20"))),
 false);
+  }
 
   public static class WithHighThreshold extends 
AbstractPercentileAggregationFunctionTest {
     @Override
diff --git 
a/pinot-core/src/test/java/org/apache/pinot/core/query/aggregation/function/PercentileTDigestAggregationFunctionTest.java
 
b/pinot-core/src/test/java/org/apache/pinot/core/query/aggregation/function/PercentileTDigestAggregationFunctionTest.java
index d94ec545cf8..75aa822abf3 100644
--- 
a/pinot-core/src/test/java/org/apache/pinot/core/query/aggregation/function/PercentileTDigestAggregationFunctionTest.java
+++ 
b/pinot-core/src/test/java/org/apache/pinot/core/query/aggregation/function/PercentileTDigestAggregationFunctionTest.java
@@ -457,6 +457,24 @@ public class PercentileTDigestAggregationFunctionTest {
     }
   }
 
+  @Test
+  public void testCapacityPreservingByteSizeRejectsUnencodableCentroidCount()
+      throws ReflectiveOperationException {
+    PercentileTDigestAccumulator accumulator = 
PercentileTDigestAccumulator.forReduction(20.0);
+    int unencodableCentroidCount = Short.MAX_VALUE + 1;
+    Field numCentroidsField = 
PercentileTDigestAccumulator.class.getDeclaredField("_numCentroids");
+    numCentroidsField.setAccessible(true);
+    numCentroidsField.setInt(accumulator, unencodableCentroidCount);
+    Field serializedMainCapacityField =
+        
PercentileTDigestAccumulator.class.getDeclaredField("_serializedMainCapacity");
+    serializedMainCapacityField.setAccessible(true);
+    serializedMainCapacityField.setInt(accumulator, unencodableCentroidCount);
+
+    IllegalStateException exception = 
Assert.expectThrows(IllegalStateException.class, accumulator::byteSize);
+    Assert.assertEquals(exception.getMessage(),
+        "TDigest has too many centroids for capacity-preserving encoding: " + 
unencodableCentroidCount);
+  }
+
   @Test
   public void 
testSerializedGroupByMVSharedInputUsesCentroidCountForInitialCapacity()
       throws ReflectiveOperationException {


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

Reply via email to