Jackie-Jiang commented on code in PR #18756:
URL: https://github.com/apache/pinot/pull/18756#discussion_r4201800026


##########
pinot-spi/src/main/java/org/apache/pinot/spi/utils/JsonUtils.java:
##########
@@ -783,6 +783,115 @@ public static List<Map<String, String>> flatten(String 
jsonString, JsonIndexConf
     }
   }
 
+  /// Flattens an already-parsed JSON value ({@link Map} / {@link List} / 
{@link JsonNode}) for the JSON index, avoiding
+  /// the string tokenization that {@link #flatten(String, JsonIndexConfig)} 
performs. Used by the realtime JSON index
+  /// when the source value is already a parsed object (e.g. cached on the 
`GenericRow` before it is serialized to a
+  /// string for the forward index), so the document is parsed once at 
ingestion instead of being serialized and
+  /// re-parsed here. The result must match {@link #flatten(String, 
JsonIndexConfig)} on the serialized form (both go
+  /// through `DEFAULT_MAPPER`). A {@link String} input is delegated to {@link 
#flatten(String, JsonIndexConfig)}.
+  public static List<Map<String, String>> flattenParsed(Object jsonValue, 
JsonIndexConfig jsonIndexConfig)
+      throws IOException {
+    if (jsonValue instanceof String) {
+      return flatten((String) jsonValue, jsonIndexConfig);
+    }
+    JsonNode jsonNode = jsonValue instanceof JsonNode ? (JsonNode) jsonValue : 
DEFAULT_MAPPER.valueToTree(jsonValue);

Review Comment:
   [P1] Preserve the original numeric representation when converting a Map
   
   Jackson's default valueToTree() strips BigDecimal trailing zeros before 
normalizeBigDecimalNodes() reparses the leaf. This is reachable through the 
standard JSON reader, which converts integers beyond long range into 
BigDecimal. For {"jsonCol":{"a":123456789012345678900}}, the forward value 
retains the exact integer, but the cached tree contains 
1.234567890123456789E+20, which is subsequently parsed as a double and indexed 
as 1.2345678901234568E20. Exact JSON_MATCH equality therefore differs between 
the consuming segment and the immutable index rebuilt from its forward values. 
A smaller example is Map.of("a", new BigDecimal("2.0")): the indexed text 
changes from "2.0" to "2".
   
   Please preserve the original decimal representation when converting Map/List 
inputs, or use the existing string path for affected values. Add a focused 
regression without Float/binary leaves: the new mixed-Map test includes a 
Float, forcing the entire document through fallback and masking this defect.



##########
pinot-segment-local/src/main/java/org/apache/pinot/segment/local/recordtransformer/DataTypeTransformer.java:
##########
@@ -131,8 +167,39 @@ public void transform(GenericRow record) {
             && value instanceof CharSequence) {
           validateCanonicalUuidPrimaryKey(column, value.toString());
         }
-        value = DataTypeTransformerUtils.transformValue(column, value, 
entry.getValue());
+        // For a JSON column with a JSON index, cache the parsed value so the 
index flattens it directly instead of
+        // re-parsing the string the value is serialized into for the forward 
index. The parsed value is cached AFTER
+        // the forward value is written (below), because putValue invalidates 
the parsed-JSON cache: a later transformer
+        // that rewrites the value (e.g. sanitization trimming an over-length 
JSON string) must not leave a stale node.
+        Object parsedToCache = null;
+        if (value != null && !_jsonCacheColumns.isEmpty() && 
_jsonCacheColumns.contains(column)) {
+          if (value instanceof Map || value instanceof List || value 
instanceof JsonNode) {
+            // Already-parsed JSON: serialize it for the forward index (below) 
and cache the parsed value for the index.
+            parsedToCache = value;

Review Comment:
   [P1] Preserve the canonical Map ordering used by the forward value
   
   This caches the original Map, but the conversion below serializes Map inputs 
through MapUtils.toString(), which sorts keys. flattenParsed() instead uses an 
unsorted mapper, and flattening overwrites entries when a dotted key and a 
nested path produce the same flattened key. For a LinkedHashMap with a.b=2 
inserted before a={b:1}, the existing sorted string path indexes .a.b as "2", 
while the cached path indexes it as "1". JSON_MATCH on "$.a.b" can consequently 
return different results before and after segment sealing.
   
   Please preserve the canonical serialization order in the parsed path and its 
fallback, or fall back for colliding paths. Regression coverage should compare 
against the actual forward string produced by DataTypeTransformer/MapUtils, 
rather than only JsonUtils.objectToString().



##########
pinot-spi/src/main/java/org/apache/pinot/spi/utils/JsonUtils.java:
##########
@@ -783,6 +783,115 @@ public static List<Map<String, String>> flatten(String 
jsonString, JsonIndexConf
     }
   }
 
+  /// Flattens an already-parsed JSON value ({@link Map} / {@link List} / 
{@link JsonNode}) for the JSON index, avoiding
+  /// the string tokenization that {@link #flatten(String, JsonIndexConfig)} 
performs. Used by the realtime JSON index
+  /// when the source value is already a parsed object (e.g. cached on the 
`GenericRow` before it is serialized to a
+  /// string for the forward index), so the document is parsed once at 
ingestion instead of being serialized and
+  /// re-parsed here. The result must match {@link #flatten(String, 
JsonIndexConfig)} on the serialized form (both go
+  /// through `DEFAULT_MAPPER`). A {@link String} input is delegated to {@link 
#flatten(String, JsonIndexConfig)}.
+  public static List<Map<String, String>> flattenParsed(Object jsonValue, 
JsonIndexConfig jsonIndexConfig)
+      throws IOException {
+    if (jsonValue instanceof String) {
+      return flatten((String) jsonValue, jsonIndexConfig);
+    }
+    JsonNode jsonNode = jsonValue instanceof JsonNode ? (JsonNode) jsonValue : 
DEFAULT_MAPPER.valueToTree(jsonValue);
+    int classification = classifyForFlatten(jsonNode);
+    if (classification == FLATTEN_UNSAFE) {
+      // A leaf renders differently than the string path (e.g. a Float or 
byte[] from a non-JSON RecordReader). Fall
+      // back to serialize+reparse so the flattened records are byte-for-byte 
identical. Rare: the JSON decoders never
+      // produce these types.
+      return flatten(objectToString(jsonValue), jsonIndexConfig);

Review Comment:
   [P2] Reuse the existing forward-index string on fallback
   
   For a document containing Float or byte[] leaves, DataTypeTransformer has 
already serialized the document for the forward index. This path then builds a 
complete tree with valueToTree(), classifies it, serializes the original 
document again, and finally performs the original parse/flatten. Ordinary Avro 
records/maps can contain these leaf types, so those rows retain all previous 
work and add another tree conversion plus a redundant whole-document 
serialization.
   
   Please pass the already-produced canonical column string to the fallback, or 
decline the optimization before doing the full tree conversion for unsupported 
values. Include an Avro-style float/binary payload in performance coverage; the 
current integer/string Map benchmark does not exercise this path.



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