spapin opened a new issue, #18737:
URL: https://github.com/apache/pinot/issues/18737

   ## Bug description
   
   A JSON value containing an unpaired UTF-16 surrogate corrupts the JSON index 
of a consuming (mutable) segment.
   
   > **What's a surrogate?** Java represents characters above U+FFFF (emoji, 
some CJK, etc.) as a *pair* of `char` values called surrogates. An *unpaired* 
surrogate is one half of that pair with its partner missing — it's not valid 
text and not encodable as UTF-8. It commonly appears when a string is truncated 
in the middle of such a character.
   
   ## Why it happens
   
   For each document, `MutableJsonIndexImpl.addFlattenedRecords` does two steps:
   
   1. **Step 1** — appends one entry per flattened record to `_docIdMapping`.
   2. **Step 2** — loops over those records to build the posting lists, 
incrementing `_nextFlattenedDocId` as it goes.
   
   Step 2 calls `Utf8.encodedLength(...)` only to track an approximate memory 
size — and that call **throws** on an unpaired surrogate. The throw aborts step 
2 partway, *after* step 1 has already grown `_docIdMapping`. Result: 
`_docIdMapping` is left permanently one entry longer than `_nextFlattenedDocId`.
   
   ## Effect
   
   With the two counters out of sync, every later document's posting-list 
entries map back to the *wrong* real doc id, so `json_match()` on that segment 
returns rows shifted by one. `MutableSegmentImpl` swallows the exception, so 
it's **completely silent** — no query error, no log warning — and only heals 
once the segment is committed and the immutable index is rebuilt from scratch.
   
   ## Reproduction
   
   Insert these three records in order into a JSON-indexed column on a 
consuming (realtime) segment:
   
   ```
   {"name": "first"}
   {"name": "\uD800"}   // unpaired high surrogate
   {"name": "third"}
   ```
   
   Expected results:
   - `json_match(col, '"$.name = ''first''")` → doc 0
   - `json_match(col, '"$.name = ''\uD800''")` → doc 1
   - `json_match(col, '"$.name = ''third''")` → doc 2
   
   Actual results after the corrupted document:
   - `json_match(col, '"$.name = ''third''")` → doc 3 (off by one)
   
   ## Suggested fix
   
   `_bytesSize` is only a memory-accounting heuristic, so a byte-length 
calculation should never be able to break indexing. The fix wraps the two 
`Utf8.encodedLength` calls in a helper that catches `IllegalArgumentException` 
and falls back to `String.getBytes(UTF_8)` — which never throws — when the 
string is malformed. This keeps the allocation-free fast path for well-formed 
strings and only pays for the allocation on the rare malformed input where the 
exact count doesn't matter anyway.
   
   ## Environment
   
   - Component: `pinot-segment-local` / `MutableJsonIndexImpl`
   - Index type: JSON index (mutable/realtime segment)
   - Trigger: any JSON column value containing an unpaired UTF-16 surrogate 
character


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