Copilot commented on code in PR #19168:
URL: https://github.com/apache/pinot/pull/19168#discussion_r3726692954


##########
pinot-spi/src/main/java/org/apache/pinot/spi/utils/MapUtils.java:
##########
@@ -183,13 +186,87 @@ public static Map<String, Object> 
deserializeMap(ByteBuffer byteBuffer) {
     return map;
   }
 
+  /// Deserializes only the value for the requested key from a length-prefixed 
MAP frame.
+  /// Non-matching keys and values are skipped without allocating byte arrays 
or invoking Jackson.
+  ///
+  /// @param bytes Serialized MAP frame
+  /// @param key Key whose value should be deserialized
+  /// @return Deserialized value, or `null` if the key is missing, has a null 
value, or cannot be deserialized
+  @Nullable
+  public static Object deserializeMapValue(byte[] bytes, String key) {
+    return deserializeMapValue(ByteBuffer.wrap(bytes), key);
+  }
+
+  /// Variant of [#deserializeMapValue(byte[], String)] that reads from the 
supplied buffer without copying the
+  /// complete MAP frame.
+  ///
+  /// Consumes the buffer from its current position and forces 
[ByteOrder#BIG_ENDIAN] on it — the write path frames
+  /// lengths through a big-endian [ByteBuffer], while an off-heap view 
inherits the platform's native order.
+  @Nullable
+  public static Object deserializeMapValue(ByteBuffer byteBuffer, String key) {
+    byteBuffer.order(ByteOrder.BIG_ENDIAN);
+    int size = byteBuffer.getInt();
+    if (size == 0) {
+      return null;
+    }
+    byte[] keyBytes = Utf8Utils.encode(key);
+    int keyBytesLength = keyBytes.length;
+    for (int i = 0; i < size; i++) {
+      int keyLength = byteBuffer.getInt();
+      // Bounds-check up front so the absolute gets below are provably in 
range, and so a truncated frame still
+      // surfaces as BufferUnderflowException rather than 
IndexOutOfBoundsException.
+      checkLength(byteBuffer, keyLength);
+      // Compare through absolute gets so a length mismatch or a differing 
byte skips the rest of the key outright,
+      // rather than walking it one relative get at a time just to advance the 
position.
+      boolean matches = keyLength == keyBytesLength;
+      if (matches) {
+        int keyOffset = byteBuffer.position();
+        for (int j = 0; j < keyLength; j++) {
+          if (byteBuffer.get(keyOffset + j) != keyBytes[j]) {
+            matches = false;
+            break;
+          }
+        }
+      }
+      byteBuffer.position(byteBuffer.position() + keyLength);
+
+      int valueLength = byteBuffer.getInt();
+      if (!matches) {
+        skip(byteBuffer, valueLength);
+        continue;
+      }
+      // Keys within a frame are unique - the write path iterates a Map - so 
the first match is the only match and
+      // the remaining entries never need to be scanned.
+      byte[] valueBytes = new byte[valueLength];
+      byteBuffer.get(valueBytes);

Review Comment:
   `deserializeMapValue(ByteBuffer, ...)` allocates `new byte[valueLength]` 
without validating `valueLength` against the remaining buffer. For 
malformed/truncated frames this can throw `NegativeArraySizeException` / OOME 
instead of the intended `BufferUnderflowException` behavior used by the skip 
path.



##########
pinot-spi/src/main/java/org/apache/pinot/spi/utils/MapUtils.java:
##########
@@ -183,13 +186,87 @@ public static Map<String, Object> 
deserializeMap(ByteBuffer byteBuffer) {
     return map;
   }
 
+  /// Deserializes only the value for the requested key from a length-prefixed 
MAP frame.
+  /// Non-matching keys and values are skipped without allocating byte arrays 
or invoking Jackson.
+  ///
+  /// @param bytes Serialized MAP frame
+  /// @param key Key whose value should be deserialized
+  /// @return Deserialized value, or `null` if the key is missing, has a null 
value, or cannot be deserialized
+  @Nullable
+  public static Object deserializeMapValue(byte[] bytes, String key) {

Review Comment:
   The Javadoc for `deserializeMapValue` says it returns null when the value 
"cannot be deserialized", but malformed/truncated frames will currently throw 
`BufferUnderflowException` (similar to `deserializeMap`). Consider documenting 
that exception explicitly so callers don't treat it as a missing-key `null`.



##########
pinot-segment-local/src/test/java/org/apache/pinot/segment/local/segment/index/map/MapKeyIndexReaderTest.java:
##########
@@ -0,0 +1,102 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.pinot.segment.local.segment.index.map;
+
+import java.util.Map;
+import javax.annotation.Nullable;
+import org.apache.pinot.segment.spi.index.reader.ForwardIndexReader;
+import org.apache.pinot.segment.spi.index.reader.ForwardIndexReaderContext;
+import org.apache.pinot.spi.data.DimensionFieldSpec;
+import org.apache.pinot.spi.data.FieldSpec;
+import org.apache.pinot.spi.data.FieldSpec.DataType;
+import org.testng.annotations.Test;
+
+import static org.testng.Assert.assertEquals;
+
+
+/// Covers [MapKeyIndexReader] over both shapes of underlying reader: one that 
implements the selective
+/// [ForwardIndexReader#getMapValue] override (as the mutable forward index 
does), and one that only implements
+/// [ForwardIndexReader#getMap] and therefore falls through to the default. 
Both must agree.
+public class MapKeyIndexReaderTest {
+  private static final Map<String, Object> MAP =
+      Map.of("k8s.workload.name", "pinot-server", "k8s.workload.replicas", 3);
+
+  @Test
+  public void testSelectiveReader() {
+    assertReaderBehavior(new SelectiveReader());
+  }
+
+  /// The immutable sparse-key path inherits the default `getMapValue`. It has 
to keep working unchanged.
+  @Test
+  public void testReaderWithoutSelectiveOverride() {
+    assertReaderBehavior(new FullMapOnlyReader());
+  }
+
+  private static void assertReaderBehavior(ForwardIndexReader reader) {

Review Comment:
   This test uses the raw `ForwardIndexReader` type, which loses compile-time 
type safety and introduces avoidable unchecked warnings. It can be 
parameterized with `ForwardIndexReaderContext` here.



##########
pinot-segment-local/src/test/java/org/apache/pinot/segment/local/segment/index/map/MapKeyIndexReaderTest.java:
##########
@@ -0,0 +1,102 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.pinot.segment.local.segment.index.map;
+
+import java.util.Map;
+import javax.annotation.Nullable;
+import org.apache.pinot.segment.spi.index.reader.ForwardIndexReader;
+import org.apache.pinot.segment.spi.index.reader.ForwardIndexReaderContext;
+import org.apache.pinot.spi.data.DimensionFieldSpec;
+import org.apache.pinot.spi.data.FieldSpec;
+import org.apache.pinot.spi.data.FieldSpec.DataType;
+import org.testng.annotations.Test;
+
+import static org.testng.Assert.assertEquals;
+
+
+/// Covers [MapKeyIndexReader] over both shapes of underlying reader: one that 
implements the selective
+/// [ForwardIndexReader#getMapValue] override (as the mutable forward index 
does), and one that only implements
+/// [ForwardIndexReader#getMap] and therefore falls through to the default. 
Both must agree.
+public class MapKeyIndexReaderTest {
+  private static final Map<String, Object> MAP =
+      Map.of("k8s.workload.name", "pinot-server", "k8s.workload.replicas", 3);
+
+  @Test
+  public void testSelectiveReader() {
+    assertReaderBehavior(new SelectiveReader());
+  }
+
+  /// The immutable sparse-key path inherits the default `getMapValue`. It has 
to keep working unchanged.
+  @Test
+  public void testReaderWithoutSelectiveOverride() {
+    assertReaderBehavior(new FullMapOnlyReader());
+  }
+
+  private static void assertReaderBehavior(ForwardIndexReader reader) {
+    FieldSpec stringSpec = new DimensionFieldSpec("value", DataType.STRING, 
true);
+    assertEquals(new MapKeyIndexReader(reader, "k8s.workload.name", 
stringSpec).getString(0, null), "pinot-server");
+
+    // A key that is absent from the map resolves to the field spec's default 
null value, not to null.
+    assertEquals(new MapKeyIndexReader(reader, "missing", 
stringSpec).getString(0, null),
+        stringSpec.getDefaultNullValue());
+
+    FieldSpec intSpec = new DimensionFieldSpec("value", DataType.INT, true);
+    assertEquals(new MapKeyIndexReader(reader, "k8s.workload.replicas", 
intSpec).getInt(0, null), 3);
+  }
+
+  /// Mirrors the mutable forward index: answers a single key without 
materializing the map.
+  private static class SelectiveReader extends BaseReader {
+    @Override
+    @Nullable
+    public Object getMapValue(int docId, ForwardIndexReaderContext context, 
String key) {
+      return MAP.get(key);
+    }
+  }
+
+  /// Mirrors a reader that only knows how to hand back the whole map.
+  private static class FullMapOnlyReader extends BaseReader {
+  }
+
+  @SuppressWarnings("rawtypes")
+  private abstract static class BaseReader implements ForwardIndexReader {

Review Comment:
   `BaseReader` implements `ForwardIndexReader` as a raw type and suppresses 
the warning. Using `ForwardIndexReader<ForwardIndexReaderContext>` avoids the 
suppression and keeps overrides type-checked.



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