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 14b564428ee Make Utf8Utils.encodedLength agree with encode() on
unpaired surrogates (#20153)
14b564428ee is described below
commit 14b564428ee6d939728e3111ffb2f13c957afa18
Author: Xiaotian (Jackie) Jiang <[email protected]>
AuthorDate: Wed Nov 4 22:42:05 2026 -0800
Make Utf8Utils.encodedLength agree with encode() on unpaired surrogates
(#20153)
---
.../indexsegment/mutable/MutableSegmentImpl.java | 4 +-
.../converter/stats/CompactedColumnStatistics.java | 4 +-
.../stats/CompactedNoDictColumnStatistics.java | 6 +-
.../dictionary/StringOnHeapMutableDictionary.java | 5 +-
.../impl/forward/CLPMutableForwardIndex.java | 4 +-
.../impl/forward/SameValueMutableForwardIndex.java | 4 +-
.../realtime/impl/json/MutableJsonIndexImpl.java | 17 +----
.../SchemaConformingTransformer.java | 4 +-
.../creator/impl/SegmentDictionaryCreator.java | 2 +-
.../impl/openstruct/OpenStructColumnSplitter.java | 4 +-
.../stats/NoDictColumnStatisticsCollector.java | 4 +-
.../stats/StringColumnPreIndexStatsCollector.java | 6 +-
.../segment/index/loader/ForwardIndexHandler.java | 4 +-
.../defaultcolumn/DefaultColumnStatistics.java | 4 +-
.../PartitionIdVirtualColumnProvider.java | 4 +-
.../local/startree/StarTreeBuilderUtils.java | 4 +-
.../stats/MutableNoDictColumnStatisticsTest.java | 6 +-
.../defaultcolumn/DefaultColumnStatisticsTest.java | 6 +-
.../index/readers/ImmutableDictionaryTest.java | 4 +-
.../pinot/server/api/resources/TablesResource.java | 4 +-
.../java/org/apache/pinot/spi/utils/Utf8Utils.java | 78 +++++++++++++---------
.../org/apache/pinot/spi/utils/Utf8UtilsTest.java | 58 ++++++++++++++--
22 files changed, 144 insertions(+), 92 deletions(-)
diff --git
a/pinot-segment-local/src/main/java/org/apache/pinot/segment/local/indexsegment/mutable/MutableSegmentImpl.java
b/pinot-segment-local/src/main/java/org/apache/pinot/segment/local/indexsegment/mutable/MutableSegmentImpl.java
index 832b1c1cbd7..3920c0e2faf 100644
---
a/pinot-segment-local/src/main/java/org/apache/pinot/segment/local/indexsegment/mutable/MutableSegmentImpl.java
+++
b/pinot-segment-local/src/main/java/org/apache/pinot/segment/local/indexsegment/mutable/MutableSegmentImpl.java
@@ -19,7 +19,6 @@
package org.apache.pinot.segment.local.indexsegment.mutable;
import com.google.common.base.Preconditions;
-import com.google.common.base.Utf8;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import it.unimi.dsi.fastutil.booleans.BooleanArrayList;
@@ -126,6 +125,7 @@ import org.apache.pinot.spi.utils.BooleanUtils;
import org.apache.pinot.spi.utils.ByteArray;
import org.apache.pinot.spi.utils.FixedIntArray;
import org.apache.pinot.spi.utils.MapUtils;
+import org.apache.pinot.spi.utils.Utf8Utils;
import org.apache.pinot.spi.utils.UuidUtils;
import org.apache.pinot.spi.utils.builder.TableNameBuilder;
import org.roaringbitmap.BatchIterator;
@@ -1720,7 +1720,7 @@ public class MutableSegmentImpl implements MutableSegment
{
switch (dataType) {
case STRING: {
for (Object value : values) {
- rowLength += Utf8.encodedLength((String) value);
+ rowLength += Utf8Utils.encodedLength((String) value);
}
_varByteMVMaxRowLengthInBytes =
Math.max(_varByteMVMaxRowLengthInBytes, rowLength);
diff --git
a/pinot-segment-local/src/main/java/org/apache/pinot/segment/local/realtime/converter/stats/CompactedColumnStatistics.java
b/pinot-segment-local/src/main/java/org/apache/pinot/segment/local/realtime/converter/stats/CompactedColumnStatistics.java
index a1464e584fc..9d2f33dc639 100644
---
a/pinot-segment-local/src/main/java/org/apache/pinot/segment/local/realtime/converter/stats/CompactedColumnStatistics.java
+++
b/pinot-segment-local/src/main/java/org/apache/pinot/segment/local/realtime/converter/stats/CompactedColumnStatistics.java
@@ -19,7 +19,6 @@
package org.apache.pinot.segment.local.realtime.converter.stats;
import com.google.common.base.Preconditions;
-import com.google.common.base.Utf8;
import it.unimi.dsi.fastutil.ints.IntIterator;
import it.unimi.dsi.fastutil.ints.IntOpenHashSet;
import java.math.BigDecimal;
@@ -31,6 +30,7 @@ import org.apache.pinot.segment.spi.index.reader.Dictionary;
import org.apache.pinot.spi.data.FieldSpec.DataType;
import org.apache.pinot.spi.utils.BigDecimalUtils;
import org.apache.pinot.spi.utils.ByteArray;
+import org.apache.pinot.spi.utils.Utf8Utils;
import org.roaringbitmap.RoaringBitmap;
@@ -237,7 +237,7 @@ public class CompactedColumnStatistics extends
MutableColumnStatistics {
maxValue = values[_cardinality - 1];
isAscii = true;
for (String value : values) {
- int length = Utf8.encodedLength(value);
+ int length = Utf8Utils.encodedLength(value);
minElementLength = Math.min(minElementLength, length);
maxElementLength = Math.max(maxElementLength, length);
if (isAscii) {
diff --git
a/pinot-segment-local/src/main/java/org/apache/pinot/segment/local/realtime/converter/stats/CompactedNoDictColumnStatistics.java
b/pinot-segment-local/src/main/java/org/apache/pinot/segment/local/realtime/converter/stats/CompactedNoDictColumnStatistics.java
index ff96b76f58b..25fd626072d 100644
---
a/pinot-segment-local/src/main/java/org/apache/pinot/segment/local/realtime/converter/stats/CompactedNoDictColumnStatistics.java
+++
b/pinot-segment-local/src/main/java/org/apache/pinot/segment/local/realtime/converter/stats/CompactedNoDictColumnStatistics.java
@@ -19,13 +19,13 @@
package org.apache.pinot.segment.local.realtime.converter.stats;
import com.google.common.base.Preconditions;
-import com.google.common.base.Utf8;
import java.math.BigDecimal;
import javax.annotation.Nullable;
import org.apache.pinot.segment.spi.datasource.DataSource;
import org.apache.pinot.spi.data.FieldSpec.DataType;
import org.apache.pinot.spi.utils.BigDecimalUtils;
import org.apache.pinot.spi.utils.ByteArray;
+import org.apache.pinot.spi.utils.Utf8Utils;
import org.roaringbitmap.IntIterator;
import org.roaringbitmap.RoaringBitmap;
@@ -259,7 +259,7 @@ public class CompactedNoDictColumnStatistics extends
MutableNoDictColumnStatisti
if (maxString == null || value.compareTo(maxString) > 0) {
maxString = value;
}
- int length = Utf8.encodedLength(value);
+ int length = Utf8Utils.encodedLength(value);
minElementLength = Math.min(minElementLength, length);
maxElementLength = Math.max(maxElementLength, length);
if (isAscii) {
@@ -406,7 +406,7 @@ public class CompactedNoDictColumnStatistics extends
MutableNoDictColumnStatisti
case BIG_DECIMAL:
return BigDecimalUtils.byteSize((BigDecimal) value);
case STRING:
- return Utf8.encodedLength((String) value);
+ return Utf8Utils.encodedLength((String) value);
case BYTES:
return ((ByteArray) value).length();
default:
diff --git
a/pinot-segment-local/src/main/java/org/apache/pinot/segment/local/realtime/impl/dictionary/StringOnHeapMutableDictionary.java
b/pinot-segment-local/src/main/java/org/apache/pinot/segment/local/realtime/impl/dictionary/StringOnHeapMutableDictionary.java
index ad8ef63cd5a..721667c6027 100644
---
a/pinot-segment-local/src/main/java/org/apache/pinot/segment/local/realtime/impl/dictionary/StringOnHeapMutableDictionary.java
+++
b/pinot-segment-local/src/main/java/org/apache/pinot/segment/local/realtime/impl/dictionary/StringOnHeapMutableDictionary.java
@@ -18,7 +18,6 @@
*/
package org.apache.pinot.segment.local.realtime.impl.dictionary;
-import com.google.common.base.Utf8;
import it.unimi.dsi.fastutil.ints.IntOpenHashSet;
import it.unimi.dsi.fastutil.ints.IntSet;
import it.unimi.dsi.fastutil.ints.IntSets;
@@ -181,11 +180,11 @@ public class StringOnHeapMutableDictionary extends
BaseOnHeapMutableDictionary {
@Override
public int getValueSize(int dictId) {
- return Utf8.encodedLength(getStringValue(dictId));
+ return Utf8Utils.encodedLength(getStringValue(dictId));
}
private void updateStats(String value) {
- int utf8Length = Utf8.encodedLength(value);
+ int utf8Length = Utf8Utils.encodedLength(value);
if (_min == null) {
_min = value;
_max = value;
diff --git
a/pinot-segment-local/src/main/java/org/apache/pinot/segment/local/realtime/impl/forward/CLPMutableForwardIndex.java
b/pinot-segment-local/src/main/java/org/apache/pinot/segment/local/realtime/impl/forward/CLPMutableForwardIndex.java
index 2341a6e6471..c618bc4c43a 100644
---
a/pinot-segment-local/src/main/java/org/apache/pinot/segment/local/realtime/impl/forward/CLPMutableForwardIndex.java
+++
b/pinot-segment-local/src/main/java/org/apache/pinot/segment/local/realtime/impl/forward/CLPMutableForwardIndex.java
@@ -18,7 +18,6 @@
*/
package org.apache.pinot.segment.local.realtime.impl.forward;
-import com.google.common.base.Utf8;
import com.yscope.clp.compressorfrontend.BuiltInVariableHandlingRuleVersions;
import com.yscope.clp.compressorfrontend.EncodedMessage;
import com.yscope.clp.compressorfrontend.MessageDecoder;
@@ -32,6 +31,7 @@ import
org.apache.pinot.segment.spi.index.mutable.MutableDictionary;
import org.apache.pinot.segment.spi.index.mutable.MutableForwardIndex;
import org.apache.pinot.segment.spi.memory.PinotDataBufferMemoryManager;
import org.apache.pinot.spi.data.FieldSpec.DataType;
+import org.apache.pinot.spi.utils.Utf8Utils;
public class CLPMutableForwardIndex implements MutableForwardIndex {
@@ -116,7 +116,7 @@ public class CLPMutableForwardIndex implements
MutableForwardIndex {
String[] dictVars;
Long[] encodedVars;
- int length = Utf8.encodedLength(value);
+ int length = Utf8Utils.encodedLength(value);
_lengthOfShortestElement = Math.min(_lengthOfShortestElement, length);
_lengthOfLongestElement = Math.max(_lengthOfLongestElement, length);
if (_isAscii) {
diff --git
a/pinot-segment-local/src/main/java/org/apache/pinot/segment/local/realtime/impl/forward/SameValueMutableForwardIndex.java
b/pinot-segment-local/src/main/java/org/apache/pinot/segment/local/realtime/impl/forward/SameValueMutableForwardIndex.java
index 2e01a391fff..9d0659f9ed4 100644
---
a/pinot-segment-local/src/main/java/org/apache/pinot/segment/local/realtime/impl/forward/SameValueMutableForwardIndex.java
+++
b/pinot-segment-local/src/main/java/org/apache/pinot/segment/local/realtime/impl/forward/SameValueMutableForwardIndex.java
@@ -18,11 +18,11 @@
*/
package org.apache.pinot.segment.local.realtime.impl.forward;
-import com.google.common.base.Utf8;
import java.io.IOException;
import javax.annotation.Nullable;
import org.apache.pinot.segment.spi.index.mutable.MutableForwardIndex;
import org.apache.pinot.spi.data.FieldSpec.DataType;
+import org.apache.pinot.spi.utils.Utf8Utils;
/// SameValueMutableForwardIndex is used to wrap any MutableForwardIndex, but
store the same value. This is done to
@@ -36,7 +36,7 @@ public class SameValueMutableForwardIndex implements
MutableForwardIndex {
public SameValueMutableForwardIndex(Object actualValue, MutableForwardIndex
delegate) {
_actualValue = actualValue.toString();
_actualValues = new Object[]{_actualValue};
- _valueLength = Utf8.encodedLength(_actualValue);
+ _valueLength = Utf8Utils.encodedLength(_actualValue);
_delegate = delegate;
}
diff --git
a/pinot-segment-local/src/main/java/org/apache/pinot/segment/local/realtime/impl/json/MutableJsonIndexImpl.java
b/pinot-segment-local/src/main/java/org/apache/pinot/segment/local/realtime/impl/json/MutableJsonIndexImpl.java
index f9fde3c6ea3..0cc6ff6d358 100644
---
a/pinot-segment-local/src/main/java/org/apache/pinot/segment/local/realtime/impl/json/MutableJsonIndexImpl.java
+++
b/pinot-segment-local/src/main/java/org/apache/pinot/segment/local/realtime/impl/json/MutableJsonIndexImpl.java
@@ -19,12 +19,10 @@
package org.apache.pinot.segment.local.realtime.impl.json;
import com.google.common.base.Preconditions;
-import com.google.common.base.Utf8;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import it.unimi.dsi.fastutil.ints.IntArrayList;
import it.unimi.dsi.fastutil.ints.IntList;
import java.io.IOException;
-import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
@@ -59,6 +57,7 @@ import org.apache.pinot.spi.data.FieldSpec;
import org.apache.pinot.spi.exception.BadQueryRequestException;
import org.apache.pinot.spi.query.QueryThreadContext;
import org.apache.pinot.spi.utils.JsonUtils;
+import org.apache.pinot.spi.utils.Utf8Utils;
import org.apache.pinot.sql.parsers.CalciteSqlParser;
import org.roaringbitmap.IntConsumer;
import org.roaringbitmap.RoaringBitmap;
@@ -150,12 +149,12 @@ public class MutableJsonIndexImpl implements
MutableJsonIndex {
// Put both key and key-value into the posting list. Key is useful for
checking if a key exists in the json.
String key = entry.getKey();
_postingListMap.computeIfAbsent(key, k -> {
- _bytesSize += estimateLength(key);
+ _bytesSize += Utf8Utils.encodedLength(key);
return new RoaringBitmap();
}).add(_nextFlattenedDocId);
String keyValue = key + JsonIndexCreator.KEY_VALUE_SEPARATOR +
entry.getValue();
_postingListMap.computeIfAbsent(keyValue, k -> {
- _bytesSize += estimateLength(keyValue);
+ _bytesSize += Utf8Utils.encodedLength(keyValue);
return new RoaringBitmap();
}).add(_nextFlattenedDocId);
}
@@ -163,16 +162,6 @@ public class MutableJsonIndexImpl implements
MutableJsonIndex {
}
}
- private static int estimateLength(String value) {
- // Utf8.encodedLength counts bytes without allocating but throws on
malformed strings (e.g. unpaired surrogates).
- // _bytesSize is only a heuristic, so fall back to getBytes (which never
throws) instead of failing.
- try {
- return Utf8.encodedLength(value);
- } catch (IllegalArgumentException e) {
- return value.getBytes(StandardCharsets.UTF_8).length;
- }
- }
-
@Override
public MutableRoaringBitmap getMatchingDocIds(String filterString) {
FilterContext filter;
diff --git
a/pinot-segment-local/src/main/java/org/apache/pinot/segment/local/recordtransformer/SchemaConformingTransformer.java
b/pinot-segment-local/src/main/java/org/apache/pinot/segment/local/recordtransformer/SchemaConformingTransformer.java
index 51183eca92f..518d28d2c56 100644
---
a/pinot-segment-local/src/main/java/org/apache/pinot/segment/local/recordtransformer/SchemaConformingTransformer.java
+++
b/pinot-segment-local/src/main/java/org/apache/pinot/segment/local/recordtransformer/SchemaConformingTransformer.java
@@ -20,7 +20,6 @@ package org.apache.pinot.segment.local.recordtransformer;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.google.common.base.Preconditions;
-import com.google.common.base.Utf8;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
@@ -48,6 +47,7 @@ import org.apache.pinot.spi.metrics.PinotMeter;
import org.apache.pinot.spi.recordtransformer.RecordTransformer;
import org.apache.pinot.spi.stream.StreamDataDecoderImpl;
import org.apache.pinot.spi.utils.JsonUtils;
+import org.apache.pinot.spi.utils.Utf8Utils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -140,7 +140,7 @@ public class SchemaConformingTransformer implements
RecordTransformer {
unindexableExtrasFieldName != null ?
getAndValidateExtrasFieldType(schema, unindexableExtrasFieldName) : null;
_mergedTextIndexFieldSpec =
schema.getDimensionSpec(_transformerConfig.getMergedTextIndexField());
_schemaTree = validateSchemaAndCreateTree(schema, _transformerConfig);
- _jsonKeyValueSeparatorByteCount =
Utf8.encodedLength(_transformerConfig.getJsonKeyValueSeparator());
+ _jsonKeyValueSeparatorByteCount =
Utf8Utils.encodedLength(_transformerConfig.getJsonKeyValueSeparator());
_continueOnError = ingestionConfig.isContinueOnError();
_serverMetrics = ServerMetrics.get();
_throttledLogger = new ThrottledLogger(_logger, ingestionConfig);
diff --git
a/pinot-segment-local/src/main/java/org/apache/pinot/segment/local/segment/creator/impl/SegmentDictionaryCreator.java
b/pinot-segment-local/src/main/java/org/apache/pinot/segment/local/segment/creator/impl/SegmentDictionaryCreator.java
index e0849ce369d..9717e34d0fb 100644
---
a/pinot-segment-local/src/main/java/org/apache/pinot/segment/local/segment/creator/impl/SegmentDictionaryCreator.java
+++
b/pinot-segment-local/src/main/java/org/apache/pinot/segment/local/segment/creator/impl/SegmentDictionaryCreator.java
@@ -490,7 +490,7 @@ public class SegmentDictionaryCreator implements
IndexCreator {
_totalUncompressedValueSizeInBytes +=
BigDecimalUtils.byteSize((BigDecimal) value);
break;
case STRING:
- _totalUncompressedValueSizeInBytes +=
Utf8Utils.encodedLengthWithReplacement((String) value);
+ _totalUncompressedValueSizeInBytes += Utf8Utils.encodedLength((String)
value);
break;
case BYTES:
_totalUncompressedValueSizeInBytes += ((ByteArray)
value).getBytes().length;
diff --git
a/pinot-segment-local/src/main/java/org/apache/pinot/segment/local/segment/creator/impl/openstruct/OpenStructColumnSplitter.java
b/pinot-segment-local/src/main/java/org/apache/pinot/segment/local/segment/creator/impl/openstruct/OpenStructColumnSplitter.java
index 1ca8783bb4a..babcd5be606 100644
---
a/pinot-segment-local/src/main/java/org/apache/pinot/segment/local/segment/creator/impl/openstruct/OpenStructColumnSplitter.java
+++
b/pinot-segment-local/src/main/java/org/apache/pinot/segment/local/segment/creator/impl/openstruct/OpenStructColumnSplitter.java
@@ -19,7 +19,6 @@
package org.apache.pinot.segment.local.segment.creator.impl.openstruct;
import com.google.common.base.Preconditions;
-import com.google.common.base.Utf8;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
@@ -74,6 +73,7 @@ import org.apache.pinot.spi.data.OpenStructNaming;
import org.apache.pinot.spi.data.OpenStructTypeInference;
import org.apache.pinot.spi.utils.JsonUtils;
import org.apache.pinot.spi.utils.PinotDataType;
+import org.apache.pinot.spi.utils.Utf8Utils;
import org.roaringbitmap.RoaringBitmap;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -748,7 +748,7 @@ public class OpenStructColumnSplitter implements
ColumnarOpenStructIndexCreator
try {
String json = JsonUtils.objectToString(sparseEntries);
jsonPerDoc[docId] = json;
- maxLen = Math.max(maxLen, Utf8.encodedLength(json));
+ maxLen = Math.max(maxLen, Utf8Utils.encodedLength(json));
} catch (IOException e) {
throw new RuntimeException("Failed to serialize sparse entries for
docId " + docId, e);
}
diff --git
a/pinot-segment-local/src/main/java/org/apache/pinot/segment/local/segment/creator/impl/stats/NoDictColumnStatisticsCollector.java
b/pinot-segment-local/src/main/java/org/apache/pinot/segment/local/segment/creator/impl/stats/NoDictColumnStatisticsCollector.java
index 6810d55e010..adac748fe6c 100644
---
a/pinot-segment-local/src/main/java/org/apache/pinot/segment/local/segment/creator/impl/stats/NoDictColumnStatisticsCollector.java
+++
b/pinot-segment-local/src/main/java/org/apache/pinot/segment/local/segment/creator/impl/stats/NoDictColumnStatisticsCollector.java
@@ -19,7 +19,6 @@
package org.apache.pinot.segment.local.segment.creator.impl.stats;
import com.clearspring.analytics.stream.cardinality.HyperLogLogPlus;
-import com.google.common.base.Utf8;
import java.math.BigDecimal;
import java.util.HashSet;
import java.util.Set;
@@ -32,6 +31,7 @@ import org.apache.pinot.spi.data.FieldSpec.DataType;
import org.apache.pinot.spi.utils.BigDecimalUtils;
import org.apache.pinot.spi.utils.ByteArray;
import org.apache.pinot.spi.utils.CommonConstants;
+import org.apache.pinot.spi.utils.Utf8Utils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -205,7 +205,7 @@ public class NoDictColumnStatisticsCollector extends
AbstractColumnStatisticsCol
case BIG_DECIMAL:
return BigDecimalUtils.byteSize((BigDecimal) value);
case STRING:
- return Utf8.encodedLength((String) value);
+ return Utf8Utils.encodedLength((String) value);
case BYTES:
return ((byte[]) value).length;
default:
diff --git
a/pinot-segment-local/src/main/java/org/apache/pinot/segment/local/segment/creator/impl/stats/StringColumnPreIndexStatsCollector.java
b/pinot-segment-local/src/main/java/org/apache/pinot/segment/local/segment/creator/impl/stats/StringColumnPreIndexStatsCollector.java
index 26a168d4986..b4f7fb50b11 100644
---
a/pinot-segment-local/src/main/java/org/apache/pinot/segment/local/segment/creator/impl/stats/StringColumnPreIndexStatsCollector.java
+++
b/pinot-segment-local/src/main/java/org/apache/pinot/segment/local/segment/creator/impl/stats/StringColumnPreIndexStatsCollector.java
@@ -19,7 +19,6 @@
package org.apache.pinot.segment.local.segment.creator.impl.stats;
import com.google.common.annotations.VisibleForTesting;
-import com.google.common.base.Utf8;
import com.yscope.clp.compressorfrontend.BuiltInVariableHandlingRuleVersions;
import com.yscope.clp.compressorfrontend.EncodedMessage;
import com.yscope.clp.compressorfrontend.MessageEncoder;
@@ -32,6 +31,7 @@ import
org.apache.pinot.segment.spi.creator.StatsCollectorConfig;
import org.apache.pinot.segment.spi.partition.PartitionFunction;
import org.apache.pinot.spi.config.table.FieldConfig;
import org.apache.pinot.spi.data.FieldSpec;
+import org.apache.pinot.spi.utils.Utf8Utils;
@@ -73,7 +73,7 @@ public class StringColumnPreIndexStatsCollector extends
AbstractColumnStatistics
if (_clpStatsCollector != null) {
_clpStatsCollector.collect(value);
}
- int length = Utf8.encodedLength(value);
+ int length = Utf8Utils.encodedLength(value);
if (_values.add(value)) {
_minLength = Math.min(_minLength, length);
_maxLength = Math.max(_maxLength, length);
@@ -96,7 +96,7 @@ public class StringColumnPreIndexStatsCollector extends
AbstractColumnStatistics
if (isPartitionEnabled()) {
updatePartition(value);
}
- int length = Utf8.encodedLength(value);
+ int length = Utf8Utils.encodedLength(value);
if (_isAscii) {
_isAscii = length == value.length();
}
diff --git
a/pinot-segment-local/src/main/java/org/apache/pinot/segment/local/segment/index/loader/ForwardIndexHandler.java
b/pinot-segment-local/src/main/java/org/apache/pinot/segment/local/segment/index/loader/ForwardIndexHandler.java
index 88355ba28de..41cc548a58d 100644
---
a/pinot-segment-local/src/main/java/org/apache/pinot/segment/local/segment/index/loader/ForwardIndexHandler.java
+++
b/pinot-segment-local/src/main/java/org/apache/pinot/segment/local/segment/index/loader/ForwardIndexHandler.java
@@ -1424,12 +1424,12 @@ public class ForwardIndexHandler extends
BaseIndexHandler {
case STRING:
if (singleValue) {
String value = forwardIndex.getString(docId, context);
- rowLength = Utf8Utils.encodedLengthWithReplacement(value);
+ rowLength = Utf8Utils.encodedLength(value);
stats.addElement(rowLength, isAscii(value));
} else {
int numValues = forwardIndex.getStringMV(docId, stringBuffer,
context);
for (int i = 0; i < numValues; i++) {
- int valueLength =
Utf8Utils.encodedLengthWithReplacement(stringBuffer[i]);
+ int valueLength = Utf8Utils.encodedLength(stringBuffer[i]);
rowLength += valueLength;
stats.addElement(valueLength, isAscii(stringBuffer[i]));
}
diff --git
a/pinot-segment-local/src/main/java/org/apache/pinot/segment/local/segment/index/loader/defaultcolumn/DefaultColumnStatistics.java
b/pinot-segment-local/src/main/java/org/apache/pinot/segment/local/segment/index/loader/defaultcolumn/DefaultColumnStatistics.java
index 199195b4239..6abdb8b7c1f 100644
---
a/pinot-segment-local/src/main/java/org/apache/pinot/segment/local/segment/index/loader/defaultcolumn/DefaultColumnStatistics.java
+++
b/pinot-segment-local/src/main/java/org/apache/pinot/segment/local/segment/index/loader/defaultcolumn/DefaultColumnStatistics.java
@@ -18,7 +18,6 @@
*/
package org.apache.pinot.segment.local.segment.index.loader.defaultcolumn;
-import com.google.common.base.Utf8;
import java.math.BigDecimal;
import java.util.Set;
import javax.annotation.Nullable;
@@ -28,6 +27,7 @@ import org.apache.pinot.spi.data.FieldSpec;
import org.apache.pinot.spi.data.FieldSpec.DataType;
import org.apache.pinot.spi.utils.BigDecimalUtils;
import org.apache.pinot.spi.utils.ByteArray;
+import org.apache.pinot.spi.utils.Utf8Utils;
/// Column statistics for a default-value column.
@@ -68,7 +68,7 @@ public class DefaultColumnStatistics implements
ColumnStatistics {
case STRING:
String stringValue = (String) value;
_valueSet = new String[]{stringValue};
- length = Utf8.encodedLength(stringValue);
+ length = Utf8Utils.encodedLength(stringValue);
isAscii = length == stringValue.length();
break;
case BYTES:
diff --git
a/pinot-segment-local/src/main/java/org/apache/pinot/segment/local/segment/virtualcolumn/PartitionIdVirtualColumnProvider.java
b/pinot-segment-local/src/main/java/org/apache/pinot/segment/local/segment/virtualcolumn/PartitionIdVirtualColumnProvider.java
index 9431617d1fe..f88eaa0ddcb 100644
---
a/pinot-segment-local/src/main/java/org/apache/pinot/segment/local/segment/virtualcolumn/PartitionIdVirtualColumnProvider.java
+++
b/pinot-segment-local/src/main/java/org/apache/pinot/segment/local/segment/virtualcolumn/PartitionIdVirtualColumnProvider.java
@@ -18,7 +18,6 @@
*/
package org.apache.pinot.segment.local.segment.virtualcolumn;
-import com.google.common.base.Utf8;
import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap;
import java.math.BigDecimal;
import java.nio.charset.StandardCharsets;
@@ -37,6 +36,7 @@ import
org.apache.pinot.segment.spi.index.reader.ForwardIndexReaderContext;
import org.apache.pinot.segment.spi.index.reader.InvertedIndexReader;
import org.apache.pinot.spi.data.FieldSpec;
import org.apache.pinot.spi.data.FieldSpec.DataType;
+import org.apache.pinot.spi.utils.Utf8Utils;
/// Virtual column provider that returns the partition information of the
segment.
@@ -212,7 +212,7 @@ public class PartitionIdVirtualColumnProvider implements
VirtualColumnProvider {
@Override
public int getValueSize(int dictId) {
- return Utf8.encodedLength(_values.get(dictId));
+ return Utf8Utils.encodedLength(_values.get(dictId));
}
@Override
diff --git
a/pinot-segment-local/src/main/java/org/apache/pinot/segment/local/startree/StarTreeBuilderUtils.java
b/pinot-segment-local/src/main/java/org/apache/pinot/segment/local/startree/StarTreeBuilderUtils.java
index ad2a5ad5d84..ca41f1d5742 100644
---
a/pinot-segment-local/src/main/java/org/apache/pinot/segment/local/startree/StarTreeBuilderUtils.java
+++
b/pinot-segment-local/src/main/java/org/apache/pinot/segment/local/startree/StarTreeBuilderUtils.java
@@ -19,7 +19,6 @@
package org.apache.pinot.segment.local.startree;
import com.fasterxml.jackson.databind.JsonNode;
-import com.google.common.base.Utf8;
import java.io.File;
import java.io.IOException;
import java.nio.ByteOrder;
@@ -51,6 +50,7 @@ import
org.apache.pinot.segment.spi.utils.SegmentMetadataUtils;
import org.apache.pinot.spi.config.table.StarTreeIndexConfig;
import org.apache.pinot.spi.data.Schema;
import org.apache.pinot.spi.utils.CommonConstants;
+import org.apache.pinot.spi.utils.Utf8Utils;
import static java.nio.charset.StandardCharsets.UTF_8;
@@ -147,7 +147,7 @@ public class StarTreeBuilderUtils {
for (String dimension : dimensions) {
headerSizeInBytes += Integer.BYTES; // For dimension index
headerSizeInBytes += Integer.BYTES; // For length of dimension name
- headerSizeInBytes += Utf8.encodedLength(dimension); // For dimension name
+ headerSizeInBytes += Utf8Utils.encodedLength(dimension); // For
dimension name
}
headerSizeInBytes += Integer.BYTES; // For number of nodes.
diff --git
a/pinot-segment-local/src/test/java/org/apache/pinot/segment/local/realtime/converter/stats/MutableNoDictColumnStatisticsTest.java
b/pinot-segment-local/src/test/java/org/apache/pinot/segment/local/realtime/converter/stats/MutableNoDictColumnStatisticsTest.java
index 22ae84fb6b3..45baf0032bc 100644
---
a/pinot-segment-local/src/test/java/org/apache/pinot/segment/local/realtime/converter/stats/MutableNoDictColumnStatisticsTest.java
+++
b/pinot-segment-local/src/test/java/org/apache/pinot/segment/local/realtime/converter/stats/MutableNoDictColumnStatisticsTest.java
@@ -18,7 +18,6 @@
*/
package org.apache.pinot.segment.local.realtime.converter.stats;
-import com.google.common.base.Utf8;
import java.math.BigDecimal;
import org.apache.pinot.segment.spi.datasource.DataSource;
import org.apache.pinot.segment.spi.datasource.DataSourceMetadata;
@@ -28,6 +27,7 @@ import org.apache.pinot.spi.data.FieldSpec;
import org.apache.pinot.spi.data.FieldSpec.DataType;
import org.apache.pinot.spi.utils.BigDecimalUtils;
import org.apache.pinot.spi.utils.ByteArray;
+import org.apache.pinot.spi.utils.Utf8Utils;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;
@@ -304,12 +304,12 @@ public class MutableNoDictColumnStatisticsTest {
when(metadata.getMinValue()).thenReturn("hello");
when(metadata.getMaxValue()).thenReturn("héllo");
when(metadata.getMaxNumValuesPerMVEntry()).thenReturn(0);
-
when(metadata.getMaxRowLengthInBytes()).thenReturn(Utf8.encodedLength("héllo"));
+
when(metadata.getMaxRowLengthInBytes()).thenReturn(Utf8Utils.encodedLength("héllo"));
MutableForwardIndex forwardIndex = mock(MutableForwardIndex.class);
when(forwardIndex.isSingleValue()).thenReturn(true);
when(forwardIndex.getLengthOfShortestElement()).thenReturn(5);
-
when(forwardIndex.getLengthOfLongestElement()).thenReturn(Utf8.encodedLength("héllo"));
+
when(forwardIndex.getLengthOfLongestElement()).thenReturn(Utf8Utils.encodedLength("héllo"));
when(forwardIndex.isAscii()).thenReturn(false);
when(forwardIndex.getString(0)).thenReturn("hello");
when(forwardIndex.getString(1)).thenReturn("héllo");
diff --git
a/pinot-segment-local/src/test/java/org/apache/pinot/segment/local/segment/index/loader/defaultcolumn/DefaultColumnStatisticsTest.java
b/pinot-segment-local/src/test/java/org/apache/pinot/segment/local/segment/index/loader/defaultcolumn/DefaultColumnStatisticsTest.java
index 9ff9db8055f..175e7a084cc 100644
---
a/pinot-segment-local/src/test/java/org/apache/pinot/segment/local/segment/index/loader/defaultcolumn/DefaultColumnStatisticsTest.java
+++
b/pinot-segment-local/src/test/java/org/apache/pinot/segment/local/segment/index/loader/defaultcolumn/DefaultColumnStatisticsTest.java
@@ -18,13 +18,13 @@
*/
package org.apache.pinot.segment.local.segment.index.loader.defaultcolumn;
-import com.google.common.base.Utf8;
import java.math.BigDecimal;
import java.util.Arrays;
import org.apache.pinot.spi.data.DimensionFieldSpec;
import org.apache.pinot.spi.data.FieldSpec;
import org.apache.pinot.spi.data.FieldSpec.DataType;
import org.apache.pinot.spi.utils.BigDecimalUtils;
+import org.apache.pinot.spi.utils.Utf8Utils;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;
@@ -92,7 +92,7 @@ public class DefaultColumnStatisticsTest {
assertFalse(stats.isAscii());
break;
case STRING:
- expectedLength = Utf8.encodedLength((String)
fieldSpec.getDefaultNullValue());
+ expectedLength = Utf8Utils.encodedLength((String)
fieldSpec.getDefaultNullValue());
assertTrue(stats.isAscii()); // default "null" is ASCII
break;
case BYTES:
@@ -148,7 +148,7 @@ public class DefaultColumnStatisticsTest {
int expectedLength;
switch (storedType) {
case STRING:
- expectedLength = Utf8.encodedLength((String)
fieldSpec.getDefaultNullValue());
+ expectedLength = Utf8Utils.encodedLength((String)
fieldSpec.getDefaultNullValue());
assertTrue(stats.isAscii()); // default "null" is ASCII
break;
case BYTES:
diff --git
a/pinot-segment-local/src/test/java/org/apache/pinot/segment/local/segment/index/readers/ImmutableDictionaryTest.java
b/pinot-segment-local/src/test/java/org/apache/pinot/segment/local/segment/index/readers/ImmutableDictionaryTest.java
index b4da5465a45..e4b0d86175c 100644
---
a/pinot-segment-local/src/test/java/org/apache/pinot/segment/local/segment/index/readers/ImmutableDictionaryTest.java
+++
b/pinot-segment-local/src/test/java/org/apache/pinot/segment/local/segment/index/readers/ImmutableDictionaryTest.java
@@ -18,7 +18,6 @@
*/
package org.apache.pinot.segment.local.segment.index.readers;
-import com.google.common.base.Utf8;
import it.unimi.dsi.fastutil.doubles.DoubleOpenHashSet;
import it.unimi.dsi.fastutil.floats.FloatOpenHashSet;
import it.unimi.dsi.fastutil.ints.IntOpenHashSet;
@@ -49,6 +48,7 @@ import org.apache.pinot.spi.utils.BigDecimalUtils;
import org.apache.pinot.spi.utils.ByteArray;
import org.apache.pinot.spi.utils.BytesUtils;
import org.apache.pinot.spi.utils.FALFInterner;
+import org.apache.pinot.spi.utils.Utf8Utils;
import org.testng.Assert;
import org.testng.annotations.AfterClass;
import org.testng.annotations.BeforeClass;
@@ -440,7 +440,7 @@ public class ImmutableDictionaryTest implements
PinotBuffersAfterMethodCheckRule
for (int i = 0; i < NUM_VALUES; i++) {
assertEquals(stringDictionary.get(i), _stringValues[i]);
assertEquals(stringDictionary.getStringValue(i), _stringValues[i]);
- assertEquals(stringDictionary.getValueSize(i),
Utf8.encodedLength(_stringValues[i]));
+ assertEquals(stringDictionary.getValueSize(i),
Utf8Utils.encodedLength(_stringValues[i]));
assertEquals(stringDictionary.indexOf(_stringValues[i]), i);
diff --git
a/pinot-server/src/main/java/org/apache/pinot/server/api/resources/TablesResource.java
b/pinot-server/src/main/java/org/apache/pinot/server/api/resources/TablesResource.java
index 1a0288d406c..82bfa64f570 100644
---
a/pinot-server/src/main/java/org/apache/pinot/server/api/resources/TablesResource.java
+++
b/pinot-server/src/main/java/org/apache/pinot/server/api/resources/TablesResource.java
@@ -21,7 +21,6 @@ package org.apache.pinot.server.api.resources;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.google.common.base.Preconditions;
-import com.google.common.base.Utf8;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiKeyAuthDefinition;
import io.swagger.annotations.ApiOperation;
@@ -132,6 +131,7 @@ import org.apache.pinot.spi.utils.ByteArray;
import org.apache.pinot.spi.utils.CommonConstants;
import
org.apache.pinot.spi.utils.CommonConstants.Helix.StateModel.SegmentStateModel;
import org.apache.pinot.spi.utils.JsonUtils;
+import org.apache.pinot.spi.utils.Utf8Utils;
import org.apache.pinot.spi.utils.builder.TableNameBuilder;
import org.roaringbitmap.buffer.MutableRoaringBitmap;
import org.slf4j.Logger;
@@ -272,7 +272,7 @@ public class TablesResource {
// For raw STRING/BYTES/BIG_DECIMAL column, set the
columnLength as the length of the max value.
Comparable<?> maxValue = columnMetadata.getMaxValue();
if (maxValue instanceof String) {
- columnLength = Utf8.encodedLength((String) maxValue);
+ columnLength = Utf8Utils.encodedLength((String) maxValue);
} else if (maxValue instanceof ByteArray) {
columnLength = ((ByteArray) maxValue).length();
} else if (maxValue instanceof BigDecimal) {
diff --git a/pinot-spi/src/main/java/org/apache/pinot/spi/utils/Utf8Utils.java
b/pinot-spi/src/main/java/org/apache/pinot/spi/utils/Utf8Utils.java
index ea1e8604735..bd7bc1c525c 100644
--- a/pinot-spi/src/main/java/org/apache/pinot/spi/utils/Utf8Utils.java
+++ b/pinot-spi/src/main/java/org/apache/pinot/spi/utils/Utf8Utils.java
@@ -18,45 +18,57 @@
*/
package org.apache.pinot.spi.utils;
-import com.google.common.base.Utf8;
+import java.lang.invoke.MethodHandles;
+import java.lang.invoke.VarHandle;
+import java.nio.ByteOrder;
import java.nio.charset.StandardCharsets;
+/// UTF-8 helpers whose sizing and encoding agree: [#encodedLength] returns
exactly the length of the array that
+/// [#encode] produces for the same string.
public class Utf8Utils {
private Utf8Utils() {
}
+ // Byte order is irrelevant: isAscii() only ORs the bytes together
+ private static final VarHandle LONG_VIEW =
+ MethodHandles.byteArrayViewVarHandle(long[].class,
ByteOrder.nativeOrder());
+
public static byte[] encode(String s) {
return s.getBytes(StandardCharsets.UTF_8);
}
+ /// Returns the number of bytes in the UTF-8 encoding of `s`, which is
exactly `encode(s).length`. An unpaired
+ /// surrogate counts as the single replacement byte that [String#getBytes]
writes for it, so this never throws.
public static int encodedLength(String s) {
- return Utf8.encodedLength(s);
- }
-
- /// Returns the UTF-8 byte length using the same malformed UTF-16
replacement behavior as [String#getBytes()].
- /// Unlike [#encodedLength(String)], this method does not reject unpaired
surrogate code units.
- public static int encodedLengthWithReplacement(String s) {
- int length = 0;
- int index = 0;
- while (index < s.length()) {
- char character = s.charAt(index);
- if (character <= 0x7f) {
- length++;
- } else if (character <= 0x7ff) {
- length += 2;
- } else if (Character.isHighSurrogate(character) && index + 1 < s.length()
- && Character.isLowSurrogate(s.charAt(index + 1))) {
- length += 4;
- index++;
- } else if (Character.isSurrogate(character)) {
- length++;
- } else {
- length += 3;
+ int utf16Length = s.length();
+ // Fast path for the common all-ASCII prefix, one byte per char
+ int i = 0;
+ while (i < utf16Length && s.charAt(i) < 0x80) {
+ i++;
+ }
+ // Every char counts one byte up front. Add 1 for a 2-byte char
(branch-free: (0x7f - c) >>> 31 is 1 iff c >= 0x80),
+ // 2 for a 3-byte char or a surrogate pair, and nothing for an unpaired
surrogate, which encodes as the single
+ // replacement byte '?'. The surrogate ranges are compared inline instead
of through Character.isSurrogate() and
+ // similar helpers: C2 folds adjacent range checks that guard uncommon
traps into one unsigned comparison only when
+ // both sit in the same method, so a check inside an inlined helper stays
a separate compare-and-branch per char,
+ // which measured 1.6x slower on CJK text.
+ int utf8Length = utf16Length;
+ for (; i < utf16Length; i++) {
+ char c = s.charAt(i);
+ if (c < 0x800) {
+ utf8Length += (0x7f - c) >>> 31;
+ } else if (c < Character.MIN_SURROGATE || c > Character.MAX_SURROGATE) {
+ utf8Length += 2;
+ } else if (c <= Character.MAX_HIGH_SURROGATE && i + 1 < utf16Length) {
+ char next = s.charAt(i + 1);
+ if (next >= Character.MIN_LOW_SURROGATE && next <=
Character.MAX_LOW_SURROGATE) {
+ utf8Length += 2;
+ i++;
+ }
}
- index++;
}
- return length;
+ return utf8Length;
}
public static String decode(byte[] bytes) {
@@ -64,11 +76,17 @@ public class Utf8Utils {
}
public static boolean isAscii(byte[] bytes) {
- int or = 0;
- for (byte b : bytes) {
- // Do not check within loop because most values are ASCII
- or |= b;
+ int length = bytes.length;
+ int loopBound = length & ~0x7;
+ long or = 0;
+ int i = 0;
+ for (; i < loopBound; i += 8) {
+ or |= (long) LONG_VIEW.get(bytes, i);
+ }
+ for (; i < length; i++) {
+ or |= bytes[i];
}
- return (or & 0x80) == 0;
+ // Do not check within the loops because most values are ASCII
+ return (or & 0x8080808080808080L) == 0;
}
}
diff --git
a/pinot-spi/src/test/java/org/apache/pinot/spi/utils/Utf8UtilsTest.java
b/pinot-spi/src/test/java/org/apache/pinot/spi/utils/Utf8UtilsTest.java
index 4e2e3943445..0fafd2c201e 100644
--- a/pinot-spi/src/test/java/org/apache/pinot/spi/utils/Utf8UtilsTest.java
+++ b/pinot-spi/src/test/java/org/apache/pinot/spi/utils/Utf8UtilsTest.java
@@ -18,19 +18,65 @@
*/
package org.apache.pinot.spi.utils;
-import java.nio.charset.StandardCharsets;
+import java.util.Arrays;
+import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;
import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertFalse;
+import static org.testng.Assert.assertTrue;
-/// Tests UTF-8 sizing contracts used by segment metadata and compression
statistics.
+/// Tests [Utf8Utils].
public class Utf8UtilsTest {
+ @DataProvider
+ public Object[][] encodedLengths() {
+ return new Object[][]{
+ {"", 0},
+ {"ascii", 5},
+ // Two-byte characters
+ {"caf\u00e9", 5},
+ {"\u00e9\u00e9", 4},
+ // Three-byte characters
+ {"\u4e2d\u6587", 6},
+ {"a\u20acb", 5},
+ // Surrogate pairs encode to four bytes
+ {"\uD83D\uDE00", 4},
+ {"a\uD83D\uDE00b", 6},
+ // Unpaired surrogates encode to a single replacement byte
+ {"\uD800", 1},
+ {"abc\uD800", 4},
+ {"a\uD800b", 3},
+ {"\uDC00", 1},
+ {"\uDC00\uD800", 2},
+ {"\uD800\uD83D\uDE00", 5}
+ };
+ }
+
+ @Test(dataProvider = "encodedLengths")
+ public void testEncodedLength(String s, int expectedLength) {
+ assertEquals(Utf8Utils.encodedLength(s), expectedLength);
+ assertEquals(Utf8Utils.encode(s).length, expectedLength);
+ }
+
@Test
- public void testEncodedLengthWithReplacementMatchesJdkEncoding() {
- for (String value : new String[]{"ascii", "caf\u00e9", "\uD83D\uDE00",
"\uD800", "\uDC00", "a\uD800b"}) {
- assertEquals(Utf8Utils.encodedLengthWithReplacement(value),
value.getBytes(StandardCharsets.UTF_8).length,
- "Unexpected serialized UTF-8 length for: " + value);
+ public void testIsAscii() {
+ // Cover the empty array and every tail length around the 8-byte stride
+ for (int length = 0; length <= 24; length++) {
+ byte[] bytes = new byte[length];
+ Arrays.fill(bytes, (byte) 'a');
+ assertTrue(Utf8Utils.isAscii(bytes), "length: " + length);
+ for (int i = 0; i < length; i++) {
+ bytes[i] = 0x7f;
+ assertTrue(Utf8Utils.isAscii(bytes), "length: " + length + ", index: "
+ i);
+ bytes[i] = (byte) 0x80;
+ assertFalse(Utf8Utils.isAscii(bytes), "length: " + length + ", index:
" + i);
+ bytes[i] = (byte) 0xff;
+ assertFalse(Utf8Utils.isAscii(bytes), "length: " + length + ", index:
" + i);
+ bytes[i] = 'a';
+ }
}
+ assertTrue(Utf8Utils.isAscii(Utf8Utils.encode("ascii")));
+ assertFalse(Utf8Utils.isAscii(Utf8Utils.encode("caf\u00e9")));
}
}
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]