raghavyadav01 commented on code in PR #19608:
URL: https://github.com/apache/pinot/pull/19608#discussion_r4118369177


##########
pinot-core/src/main/java/org/apache/pinot/core/operator/blocks/ProjectionBlock.java:
##########
@@ -61,15 +70,99 @@ public BlockValSet getBlockValueSet(ExpressionContext 
expression) {
   public BlockValSet getBlockValueSet(String column) {
     DataSource dataSource = _dataSourceMap.get(column);
     // An OPEN_STRUCT parent is only a handle for per-key resolution — it has 
no forward index, so DataFetcher does
-    // not register it and it cannot be read as a column. Reject it here 
rather than letting the missing
-    // ColumnValueReader surface as an NPE.
-    if (dataSource instanceof OpenStructDataSource) {
-      throw new BadQueryRequestException(
-          "OPEN_STRUCT column: " + column + " cannot be selected directly; use 
" + column + "['key']");
+    // not register it and it cannot be read through the block cache. Assemble 
its document here instead, which is
+    // what the storage layer's contract defers to the query layer. Without 
this `SELECT col` and, worse, `SELECT *`
+    // both failed outright on any table carrying one.
+    if (dataSource instanceof OpenStructDataSource openStructDataSource) {
+      return openStructDocuments(column, openStructDataSource);
     }
     return new ProjectionBlockValSet(_dataBlockCache, column, dataSource);
   }
 
+  /// The column's whole document per row, as JSON text.
+  ///
+  /// Assembled through [OpenStructDataSource#openMapValueReader()], the 
reconstruction the storage layer already
+  /// owns and the seal path already uses. Going key by key over 
[OpenStructDataSource#getDataSources()] instead
+  /// reads only the materialized keys -- sparse keys share one JSON column 
and have no DataSource of their own --
+  /// so every unmaterialized key would silently vanish from the document.
+  private BlockValSet openStructDocuments(String column, OpenStructDataSource 
openStructDataSource) {
+    int numDocs = getNumDocs();
+    int[] docIds = getDocIds();
+    String[] documents = new String[numDocs];
+    try (MapValueReader reader = openStructDataSource.openMapValueReader()) {
+      for (int i = 0; i < numDocs; i++) {
+        Map<String, Object> document = reader.getMapValue(docIds[i]);
+        documents[i] = document == null ? "{}" : 
JsonUtils.objectToString(renderDocument(document));
+      }
+    } catch (IOException e) {
+      throw new RuntimeException("Failed to read OPEN_STRUCT column: " + 
column, e);
+    }
+    return new OpenStructDocumentBlockValSet(documents);
+  }
+
+  /// The document as it should read back: nested, and with no key spelled 
twice.
+  ///
+  /// A key nested inside an object is materialized under its path -- 
`configApi.timeTaken` -- while the object it
+  /// came from stays in the document whole, so the reconstruction carries the 
same value both ways. The object is
+  /// the shape the source had, so it wins and the paths into it are dropped. 
`.` is an ordinary key character with
+  /// no escape, and that is exactly what makes the container's own entry the 
thing that disambiguates: a dotted key
+  /// whose prefix is not itself a key was never a path, so it stays a key 
spelled with a dot.
+  private static Map<String, Object> renderDocument(Map<String, Object> 
document) {
+    Map<String, Object> rendered = new LinkedHashMap<>(document.size());
+    for (Map.Entry<String, Object> entry : document.entrySet()) {
+      if (!isPathIntoPresentObject(entry.getKey(), document)) {
+        rendered.put(entry.getKey(), renderValue(entry.getValue()));
+      }
+    }
+    return rendered;
+  }
+
+  /// Whether `key` is a path into an object that the document also carries 
whole. `configApi.timeTaken` is, when
+  /// `configApi` is a key; a key the document literally spells with a dot is 
not, because no prefix of it is a key.
+  private static boolean isPathIntoPresentObject(String key, Map<String, 
Object> document) {
+    int dot = key.indexOf(OpenStructKeyFlattener.PATH_SEPARATOR);
+    while (dot >= 0) {
+      if (document.get(key.substring(0, dot)) instanceof Map) {

Review Comment:
   You are right, and this one was mine — fixed in c10fc97.
   
   The guard could not fire, for exactly the reason you give: the container 
arrives as JSON **text**, so `document.get(prefix)` is a `String` and the 
`instanceof Map` check never matched. It only worked on the path where the blob 
is parsed back into real maps, which is not the native one.
   
   And your read of the tests is the part that stung: they stubbed 
`openMapValueReader()` with hand-built `LinkedHashMap`s, a shape reconstruction 
never produces. I had a `jsonOrText` helper doing this unwrap in an earlier 
revision and dropped it when I moved the assembly onto `openMapValueReader()`; 
the stubs were what let that go unnoticed.
   
   Two changes:
   
   - `renderValue` unwraps a `String` that parses as an object or an array, 
recursively, so a container nested two deep comes back nested rather than 
escaped.
   - the dedup check no longer asks only whether a prefix *is* a container — it 
asks whether that container actually holds the rest of the path. Without that, 
a string that merely looks like JSON could swallow a key genuinely spelled with 
a dot.
   
   New tests use the real shape (container as text alongside its dotted 
leaves), plus cases for recursive unwrapping, a JSON-looking string that must 
not swallow `a.b`, non-JSON text staying text, and a leaf its container does 
not hold being kept rather than dropped. Correcting the old fixture also 
surfaced that the container in it was missing one of its own leaves, which the 
stricter check now catches.



##########
pinot-segment-local/src/main/java/org/apache/pinot/segment/local/segment/index/openstruct/ImmutableOpenStructDataSource.java:
##########
@@ -106,7 +115,33 @@ public DataSource getDataSource(String key) {
       return new NullDataSource(getValueFieldSpec(key), 
getDataSourceMetadata().getNumDocs());
     }
     return _sparseKeyDataSourceCache.computeIfAbsent(key,
-        k -> new SparseKeyDataSource(getValueFieldSpec(k), _sparseBlobReader));
+        k -> new SparseKeyDataSource(getValueFieldSpec(k), _sparseBlobReader, 
maxNumValues(k)));
+  }
+
+  /// Field spec for a key's values, with an undeclared sparse key's shape 
taken from the segment's sparse
+  /// multi-value manifest. Which tier a key lands on is a tuning decision, so 
it must not decide the key's
+  /// shape: without this, the same rows would report `STRING[]` on a segment 
that materialized the key and a
+  /// scalar `STRING` holding `["a","b"]` on one that put it in the blob, and 
a query fanning out over both
+  /// would see two shapes for one column.
+  @Override
+  public FieldSpec getValueFieldSpec(String key) {
+    FieldSpec childFieldSpec = _fieldSpec.getChildFieldSpec(key);
+    if (childFieldSpec != null) {
+      return childFieldSpec;
+    }
+    boolean singleValue = _sparseMultiValueKeys == null || 
!_sparseMultiValueKeys.containsKey(key);
+    return new DimensionFieldSpec(key, FieldSpec.DataType.STRING, singleValue);

Review Comment:
   Not deliberate — shape got persisted and type did not, and you are right 
that fix 5 is what made the disagreement visible.
   
   To your question of whether it is worth persisting alongside the shape in 
the same manifest: I think yes, and it is the same manifest and the same write 
site (`OpenStructColumnSplitter` L823-834 already walks the sparse keys and has 
the inferred type in hand), so it is a small change rather than a new mechanism.
   
   I would rather not fold it into this PR — it changes what 
`SPARSE_MULTI_VALUE_KEYS` carries, which is a format change and deserves its 
own review and its own consuming/sealed parity test, and nothing here regresses 
without it. Happy to do it as the immediate follow-up unless you would rather 
see it land together.



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