tarun11Mavani commented on code in PR #19093:
URL: https://github.com/apache/pinot/pull/19093#discussion_r4023964659


##########
pinot-segment-local/src/main/java/org/apache/pinot/segment/local/segment/index/openstruct/OpenStructIndexType.java:
##########
@@ -88,6 +89,36 @@ public void validate(FieldIndexConfigs indexConfigs, 
FieldSpec fieldSpec, TableC
           fieldSpec.getName());
       validatePerKeyIndexes(config);
       validateIgnoredKeys(config, fieldSpec);
+      if (fieldSpec instanceof ComplexFieldSpec) {
+        validateChildFieldSpecTypes((ComplexFieldSpec) fieldSpec);
+      }
+    }
+  }
+
+  /// Rejects a declared child key type that the consuming and sealed-build 
paths cannot coerce a
+  /// value to (e.g. STRUCT, LIST, nested OPEN_STRUCT, UNKNOWN — 
[ColumnDataType#fromDataTypeSV] or
+  /// the resulting [ColumnDataType#toPinotDataType] throws for these). 
Catching this at config
+  /// validation, rather than surfacing it as an uncaught exception on the 
first row ingested for
+  /// such a key, keeps a bad declared type from taking down the whole 
consuming thread.
+  private void validateChildFieldSpecTypes(ComplexFieldSpec fieldSpec) {
+    Map<String, FieldSpec> childFieldSpecs = fieldSpec.getChildFieldSpecs();
+    if (childFieldSpecs == null) {
+      return;
+    }
+    for (Map.Entry<String, FieldSpec> entry : childFieldSpecs.entrySet()) {
+      FieldSpec.DataType storedType = 
entry.getValue().getDataType().getStoredType();
+      Preconditions.checkState(isCoercible(storedType),
+          "OPEN_STRUCT column '%s': child key '%s' declares type '%s', which 
cannot be coerced for indexing",
+          fieldSpec.getName(), entry.getKey(), storedType);
+    }
+  }
+
+  private static boolean isCoercible(FieldSpec.DataType storedType) {
+    try {

Review Comment:
    isCoercible now calls FieldSpec.getDefaultNullValue(DIMENSION, storedType, 
null) directly — the exact call allocateKeyColumn makes — instead of the looser 
ColumnDataType conversion. 



##########
pinot-segment-local/src/main/java/org/apache/pinot/segment/local/segment/index/openstruct/ImmutableOpenStructDataSource.java:
##########
@@ -140,6 +144,141 @@ public JsonIndexReader getSparseJsonIndex() {
     return _sparseDataSource != null ? _sparseDataSource.getJsonIndex() : null;
   }
 
+  @SuppressWarnings("unchecked")
+  @Nullable
+  @Override
+  public Map<String, Object> getMapValue(int docId) {
+    Map<String, Object> result = null;
+
+    for (Map.Entry<String, DataSource> entry : _perKeyDataSources.entrySet()) {
+      Object value = readValue(entry.getKey(), entry.getValue(), docId);
+      if (value != null) {
+        if (result == null) {
+          result = new HashMap<>();
+        }
+        result.put(entry.getKey(), value);
+      }
+    }
+
+    if (_sparseDataSource != null) {
+      Object sparseValue = readValue(_fieldSpec.getName(), _sparseDataSource, 
docId);
+      if (sparseValue instanceof String) {
+        String json = (String) sparseValue;
+        if (!json.isEmpty()) {
+          try {
+            Map<String, Object> sparseMap = JsonUtils.stringToObject(json, 
Map.class);
+            if (result == null) {
+              result = new HashMap<>();
+            }
+            result.putAll(sparseMap);
+          } catch (IOException e) {
+            throw new RuntimeException("Failed to parse sparse JSON at docId " 
+ docId, e);
+          }
+        }
+      }
+    }
+
+    return result;
+  }
+
+  /// Reads the value of `key` at `docId`, or `null` when the doc is null or 
the column has no
+  /// forward index. Delegates the null-vector check and the dictionary/raw 
per-type read dispatch to
+  /// [PinotSegmentColumnReader] rather than re-deriving them here, so this 
path cannot drift from the
+  /// reader every other column read in the engine already goes through. 
OPEN_STRUCT child columns are
+  /// always single-valued, hence the 0 maxNumValuesPerMVEntry.
+  @Nullable
+  private static Object readValue(String key, DataSource dataSource, int 
docId) {
+    ForwardIndexReader<?> fwdReader = dataSource.getForwardIndex();
+    if (fwdReader == null) {
+      return null;
+    }
+    try (PinotSegmentColumnReader reader = new PinotSegmentColumnReader(key, 
fwdReader, dataSource.getDictionary(),
+        dataSource.getNullValueVector(), 0)) {
+      return reader.isNull(docId) ? null : reader.getValue(docId);
+    } catch (Exception e) {
+      throw new RuntimeException("Failed to read value from OPEN_STRUCT key 
forward index", e);
+    }
+  }
+
+  @Override
+  public MapValueReader openMapValueReader() {
+    return new CachingMapValueReader();
+  }
+
+  /// Caches one [PinotSegmentColumnReader] per key for the life of the 
reader, instead of the
+  /// per-call construct-and-close [#readValue]/[#getMapValue] does. For a 
raw, chunk-compressed
+  /// column (e.g. the sparse blob), a fresh reader per doc means a fresh 
decompression buffer and,
+  /// depending on access order, redundant re-decompression of the same chunk; 
reusing the reader
+  /// across a sequential scan lets it carry its decoded-chunk state forward. 
Not thread-safe — for
+  /// one single-threaded scan only, per 
[OpenStructDataSource#openMapValueReader()].
+  private final class CachingMapValueReader implements MapValueReader {
+    private final Map<String, PinotSegmentColumnReader> _readers = new 
HashMap<>();
+
+    @SuppressWarnings("unchecked")
+    @Nullable
+    @Override
+    public Map<String, Object> getMapValue(int docId) {
+      Map<String, Object> result = null;
+
+      for (Map.Entry<String, DataSource> entry : 
_perKeyDataSources.entrySet()) {
+        Object value = readValue(entry.getKey(), entry.getValue(), docId);
+        if (value != null) {
+          if (result == null) {
+            result = new HashMap<>();
+          }
+          result.put(entry.getKey(), value);
+        }
+      }
+
+      if (_sparseDataSource != null) {
+        Object sparseValue = readValue(_fieldSpec.getName(), 
_sparseDataSource, docId);
+        if (sparseValue instanceof String) {
+          String json = (String) sparseValue;
+          if (!json.isEmpty()) {
+            try {
+              Map<String, Object> sparseMap = JsonUtils.stringToObject(json, 
Map.class);
+              if (result == null) {
+                result = new HashMap<>();
+              }
+              result.putAll(sparseMap);
+            } catch (IOException e) {
+              throw new RuntimeException("Failed to parse sparse JSON at docId 
" + docId, e);
+            }
+          }
+        }
+      }
+
+      return result;
+    }
+
+    @Nullable
+    private Object readValue(String key, DataSource dataSource, int docId) {
+      PinotSegmentColumnReader reader = _readers.computeIfAbsent(key, k -> 
createReader(k, dataSource));
+      if (reader == null) {
+        return null;
+      }
+      return reader.isNull(docId) ? null : reader.getValue(docId);
+    }
+
+    @Nullable
+    private PinotSegmentColumnReader createReader(String key, DataSource 
dataSource) {
+      ForwardIndexReader<?> fwdReader = dataSource.getForwardIndex();
+      if (fwdReader == null) {
+        return null;
+      }
+      return new PinotSegmentColumnReader(key, fwdReader, 
dataSource.getDictionary(),
+          dataSource.getNullValueVector(), 0);
+    }
+
+    @Override
+    public void close()
+        throws IOException {
+      for (PinotSegmentColumnReader reader : _readers.values()) {

Review Comment:
   done. 



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