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 e8bb63fa6ad Fix MutableJsonIndexImpl corrupting doc ID mapping on 
unpaired UTF-16 surrogates (#18738)
e8bb63fa6ad is described below

commit e8bb63fa6ad7e6c14a1c84f36a1c32e8a307034b
Author: Samuel Papin <[email protected]>
AuthorDate: Wed Jun 24 15:57:50 2026 -0400

    Fix MutableJsonIndexImpl corrupting doc ID mapping on unpaired UTF-16 
surrogates (#18738)
    
    Utf8.encodedLength throws on unpaired surrogates, aborting the posting-list
    build loop after _docIdMapping has already been extended. This leaves the 
two
    counters permanently out of sync, silently shifting all subsequent 
documents'
    json_match results by one.
    
    _bytesSize is only a memory heuristic, so a size calculation must never
    abort indexing. Wraps the calls in estimateLength(), which falls back to
    String.getBytes(UTF_8) — which never throws — for malformed strings.
    
    Fixes https://github.com/apache/pinot/issues/18737
    
    Co-authored-by: spapin <[email protected]>
---
 .../local/realtime/impl/json/MutableJsonIndexImpl.java   | 15 +++++++++++++--
 .../pinot/segment/local/segment/index/JsonIndexTest.java | 16 ++++++++++++++++
 2 files changed, 29 insertions(+), 2 deletions(-)

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 9e0583ab0bf..b9f1dbe515a 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
@@ -24,6 +24,7 @@ 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;
@@ -134,12 +135,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 += Utf8.encodedLength(key);
+          _bytesSize += estimateLength(key);
           return new RoaringBitmap();
         }).add(_nextFlattenedDocId);
         String keyValue = key + JsonIndexCreator.KEY_VALUE_SEPARATOR + 
entry.getValue();
         _postingListMap.computeIfAbsent(keyValue, k -> {
-          _bytesSize += Utf8.encodedLength(keyValue);
+          _bytesSize += estimateLength(keyValue);
           return new RoaringBitmap();
         }).add(_nextFlattenedDocId);
       }
@@ -147,6 +148,16 @@ 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/test/java/org/apache/pinot/segment/local/segment/index/JsonIndexTest.java
 
b/pinot-segment-local/src/test/java/org/apache/pinot/segment/local/segment/index/JsonIndexTest.java
index b8631bdc97b..b033e5dc6cc 100644
--- 
a/pinot-segment-local/src/test/java/org/apache/pinot/segment/local/segment/index/JsonIndexTest.java
+++ 
b/pinot-segment-local/src/test/java/org/apache/pinot/segment/local/segment/index/JsonIndexTest.java
@@ -98,6 +98,22 @@ public class JsonIndexTest implements 
PinotBuffersAfterMethodCheckRule {
     FileUtils.deleteDirectory(INDEX_DIR);
   }
 
+  @Test
+  public void testAddRecordWithUnpairedSurrogateDoesNotShiftDocIds()
+      throws IOException {
+    JsonIndexConfig jsonIndexConfig = getIndexConfig();
+    try (MutableJsonIndexImpl mutableJsonIndex =
+        new MutableJsonIndexImpl(jsonIndexConfig, "table__0__1", "col")) {
+      mutableJsonIndex.add("{\"name\":\"first\"}");
+      mutableJsonIndex.add("{\"name\":\"" + '\uD800' + "\"}");
+      mutableJsonIndex.add("{\"name\":\"third\"}");
+
+      assertDocIds(mutableJsonIndex, "name='first'", ids(0));
+      assertDocIds(mutableJsonIndex, "name='" + '\uD800' + "'", ids(1));
+      assertDocIds(mutableJsonIndex, "name='third'", ids(2));
+    }
+  }
+
   @Test
   public void testSmallIndex()
       throws Exception {


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

Reply via email to