xiangfu0 commented on code in PR #19998:
URL: https://github.com/apache/pinot/pull/19998#discussion_r4344448386


##########
pinot-segment-spi/src/main/java/org/apache/pinot/segment/spi/index/metadata/MappedSegmentMetadataStore.java:
##########
@@ -0,0 +1,880 @@
+/**
+ * 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.spi.index.metadata;
+
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import com.google.common.collect.Interner;
+import com.google.common.collect.Interners;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.Closeable;
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.File;
+import java.io.IOException;
+import java.math.BigDecimal;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.AtomicMoveNotSupportedException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.StandardCopyOption;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.stream.Collectors;
+import java.util.zip.CRC32C;
+import javax.annotation.Nullable;
+import org.apache.commons.configuration2.Configuration;
+import org.apache.commons.configuration2.PropertiesConfiguration;
+import org.apache.commons.configuration2.ex.ConfigurationException;
+import org.apache.pinot.segment.spi.ColumnMetadata;
+import org.apache.pinot.segment.spi.V1Constants;
+import org.apache.pinot.segment.spi.index.IndexService;
+import org.apache.pinot.segment.spi.index.IndexType;
+import org.apache.pinot.segment.spi.memory.PinotDataBuffer;
+import org.apache.pinot.segment.spi.partition.PartitionFunction;
+import org.apache.pinot.segment.spi.store.ColumnIndexUtils;
+import org.apache.pinot.segment.spi.store.SegmentDirectoryPaths;
+import org.apache.pinot.segment.spi.utils.SegmentMetadataUtils;
+import org.apache.pinot.spi.config.table.FieldConfig.EncodingType;
+import org.apache.pinot.spi.data.ComplexFieldSpec;
+import org.apache.pinot.spi.data.FieldSpec;
+import org.apache.pinot.spi.env.CommonsConfigurationUtils;
+import org.apache.pinot.spi.utils.ByteArray;
+import org.apache.pinot.spi.utils.ColumnNameInterner;
+import org.apache.pinot.spi.utils.JsonUtils;
+
+/// Versioned, file-backed metadata for a sealed segment. The source 
`metadata.properties` remains the canonical
+/// representation for old readers and preprocessing.
+///
+/// The big-endian file starts with a 64-byte header containing the magic, 
format version, total length, source
+/// `metadata.properties` and `index_map` length/CRC fingerprints, payload 
CRC, segment-property blob reference,
+/// column count, and record-table bounds. Sorted columns occupy fixed 
180-byte records, followed by variable blobs;
+/// each blob reference is an absolute 32-bit offset and length. Numeric 
column statistics are read directly from the
+/// mapping. The column layout retains names and reusable [FieldSpec] 
references on heap.
+///
+/// A stale or corrupt sidecar is rebuilt from the canonical source and 
replaced locally, using atomic rename where
+/// supported. Change `VERSION` whenever the header, record, or blob encoding 
changes; old versions are rebuilt rather
+/// than migrated.
+final class MappedSegmentMetadataStore implements Closeable {
+  static final String FILE_NAME = "segment_metadata.bin";
+
+  private static final int MAGIC = 0x504d4431; // PMD1
+  private static final int VERSION = 3;
+  private static final int HEADER_SIZE = 64;
+  private static final int RECORD_SIZE = 180;
+
+  static final int NAME = 0;
+  static final int FIELD_SPEC = 8;
+  static final int TOTAL_DOCS = 16;
+  static final int CARDINALITY = 20;
+  static final int FLAGS = 24;
+  static final int ENCODING = 28;
+  static final int SHORTEST = 32;
+  static final int LONGEST = 36;
+  static final int TOTAL_ENTRIES = 40;
+  static final int MAX_MV = 44;
+  static final int MAX_ROW = 48;
+  static final int BITS = 52;
+  static final int MIN_TAG = 56;
+  static final int MIN_RAW = 60;
+  static final int MIN_BLOB = 68;
+  static final int MAX_TAG = 76;
+  static final int MAX_RAW = 80;
+  static final int MAX_BLOB = 88;
+  static final int PARTITION_FUNCTION = 96;
+  static final int PARTITIONS = 104;
+  static final int PARENT = 112;
+  static final int SPARSE_KEYS = 120;
+  static final int SPARSE_MULTI_VALUE_KEYS = 128;
+  static final int SPARSE_KEY_TYPES = 136;
+  static final int RAW_UNCOMPRESSED_SIZE = 144;
+  static final int DICTIONARY_UNCOMPRESSED_SIZE = 152;
+  static final int COMPRESSION_TYPE = 160;
+  static final int INDEXES = 168;
+  static final int INDEX_COUNT = 176;
+
+  static final int HAS_DICTIONARY = 1;
+  static final int SORTED = 1 << 1;
+  static final int NON_NULL = 1 << 2;
+  static final int INVALID_MIN_MAX = 1 << 3;
+  static final int ASCII = 1 << 4;
+  static final int AUTO_GENERATED = 1 << 5;
+
+  private static final int NO_VALUE = 0;
+  private static final int INT_VALUE = 1;
+  private static final int LONG_VALUE = 2;
+  private static final int FLOAT_VALUE = 3;
+  private static final int DOUBLE_VALUE = 4;
+  private static final int DECIMAL_VALUE = 5;
+  private static final int STRING_VALUE = 6;
+  private static final int BYTES_VALUE = 7;
+
+  private static final Interner<FieldSpec> FIELD_SPECS = 
Interners.newWeakInterner();
+  private static final Interner<ColumnLayout> COLUMN_LAYOUTS = 
Interners.newWeakInterner();
+
+  private final PinotDataBuffer _buffer;
+  private final ColumnLayout _columns;
+  private final int _tableOffset;
+  private final int _segmentConfigOffset;
+  private final int _segmentConfigLength;
+
+  private MappedSegmentMetadataStore(PinotDataBuffer buffer, ColumnLayout 
columns, int tableOffset,
+      int segmentConfigOffset, int segmentConfigLength) {
+    _buffer = buffer;
+    _columns = columns;
+    _tableOffset = tableOffset;
+    _segmentConfigOffset = segmentConfigOffset;
+    _segmentConfigLength = segmentConfigLength;
+  }
+
+  static MappedSegmentMetadataStore open(File indexDir)
+      throws IOException, ConfigurationException {
+    File source = SegmentDirectoryPaths.findMetadataFile(indexDir);
+    if (source == null) {
+      throw new IOException("Missing metadata.properties in " + indexDir);
+    }
+    File sidecar = new File(indexDir, FILE_NAME);
+    for (int attempt = 0; attempt < 3; attempt++) {
+      Fingerprint propertiesFingerprint = Fingerprint.of(source);
+      Fingerprint indexMapFingerprint = Fingerprint.of(findIndexMap(indexDir));
+      byte[] bytes = readValid(sidecar, propertiesFingerprint, 
indexMapFingerprint);
+      if (bytes == null) {
+        writeAtomically(sidecar, build(indexDir, propertiesFingerprint, 
indexMapFingerprint));
+        bytes = readValid(sidecar, propertiesFingerprint, indexMapFingerprint);
+        if (bytes == null) {
+          continue;
+        }
+      }
+      PinotDataBuffer buffer = PinotDataBuffer.mapFile(sidecar, true, 0, 
bytes.length, ByteOrder.BIG_ENDIAN,
+          "segment metadata");
+      boolean keepBuffer = false;
+      try {
+        // Another loader may atomically replace the sidecar between 
validation and mapFile(). Check the exact mapped
+        // bytes and the canonical inputs before publishing a view over this 
mapping.
+        if (!matchesMapped(buffer, bytes) || 
!propertiesFingerprint.sameAs(Fingerprint.of(source))
+            || 
!indexMapFingerprint.sameAs(Fingerprint.of(findIndexMap(indexDir)))) {
+          continue;
+        }
+        ByteBuffer header = ByteBuffer.wrap(bytes).order(ByteOrder.BIG_ENDIAN);
+        int count = header.getInt(52);
+        int tableOffset = header.getInt(56);
+        String[] names = new String[count];
+        FieldSpec[] fieldSpecs = new FieldSpec[count];
+        for (int i = 0; i < count; i++) {
+          int record = tableOffset + i * RECORD_SIZE;
+          names[i] = ColumnNameInterner.intern(readString(bytes, 
header.getInt(record + NAME),
+              header.getInt(record + NAME + 4)));
+          FieldSpec parsed = JsonUtils.stringToObject(readString(bytes, 
header.getInt(record + FIELD_SPEC),
+              header.getInt(record + FIELD_SPEC + 4)), FieldSpec.class);
+          fieldSpecs[i] = parsed instanceof ComplexFieldSpec ? parsed : 
FIELD_SPECS.intern(parsed);
+        }
+        ColumnLayout columns = COLUMN_LAYOUTS.intern(new ColumnLayout(names, 
fieldSpecs));
+        MappedSegmentMetadataStore store = new 
MappedSegmentMetadataStore(buffer, columns, tableOffset,
+            header.getInt(44), header.getInt(48));
+        keepBuffer = true;
+        return store;
+      } catch (IOException | RuntimeException e) {
+        throw new IOException("Invalid field specs in mapped segment metadata: 
" + sidecar, e);
+      } finally {
+        if (!keepBuffer) {
+          buffer.close();
+        }
+      }
+    }
+    throw new IOException("Metadata changed while mapping segment: " + 
indexDir);
+  }
+
+  private static boolean matchesMapped(PinotDataBuffer buffer, byte[] bytes) {
+    if (buffer.size() != bytes.length) {
+      return false;
+    }
+    byte[] block = new byte[Math.min(8192, bytes.length)];
+    for (int offset = 0; offset < bytes.length; offset += block.length) {
+      int length = Math.min(block.length, bytes.length - offset);
+      buffer.copyTo(offset, block, 0, length);
+      for (int i = 0; i < length; i++) {
+        if (block[i] != bytes[offset + i]) {
+          return false;
+        }
+      }
+    }
+    return true;
+  }
+
+  int size() {
+    return _columns._names.length;
+  }
+
+  String name(int ordinal) {
+    return _columns._names[ordinal];
+  }
+
+  int ordinal(String column) {
+    return Arrays.binarySearch(_columns._names, column);
+  }
+
+  int getInt(int ordinal, int offset) {
+    return _buffer.getInt((long) _tableOffset + (long) ordinal * RECORD_SIZE + 
offset);
+  }
+
+  long getLong(int ordinal, int offset) {
+    return _buffer.getLong((long) _tableOffset + (long) ordinal * RECORD_SIZE 
+ offset);
+  }
+
+  boolean flag(int ordinal, int flag) {
+    return (getInt(ordinal, FLAGS) & flag) != 0;
+  }
+
+  @Nullable
+  String string(int ordinal, int field) {
+    byte[] bytes = blob(ordinal, field);
+    return bytes != null ? new String(bytes, StandardCharsets.UTF_8) : null;
+  }
+
+  @Nullable
+  byte[] blob(int ordinal, int field) {
+    int offset = getInt(ordinal, field);
+    if (offset < 0) {
+      return null;
+    }
+    int length = getInt(ordinal, field + 4);
+    byte[] bytes = new byte[length];
+    _buffer.copyTo(offset, bytes);
+    return bytes;
+  }
+
+  FieldSpec fieldSpec(int ordinal) {
+    return _columns._fieldSpecs[ordinal];
+  }
+
+  @Nullable
+  Comparable<?> value(int ordinal, boolean minimum) {
+    int tag = getInt(ordinal, minimum ? MIN_TAG : MAX_TAG);
+    long raw = getLong(ordinal, minimum ? MIN_RAW : MAX_RAW);
+    int blobField = minimum ? MIN_BLOB : MAX_BLOB;
+    switch (tag) {
+      case NO_VALUE:
+        return null;
+      case INT_VALUE:
+        return (int) raw;
+      case LONG_VALUE:
+        return raw;
+      case FLOAT_VALUE:
+        return Float.intBitsToFloat((int) raw);
+      case DOUBLE_VALUE:
+        return Double.longBitsToDouble(raw);
+      case DECIMAL_VALUE:
+        return new BigDecimal(string(ordinal, blobField));

Review Comment:
   Minor: `validate()` bounds-checks the DECIMAL (and COMPRESSION_TYPE) blob 
but never parses it, so a CRC-valid-but-crafted sidecar with a non-numeric 
min/max blob passes validation and then throws `NumberFormatException` lazily 
from `getMinValue()`, outside the rebuild fallback. Not a real concern for the 
corruption threat model (CRC catches that), but you could parse/resolve these 
inside `validate()` so a bad sidecar rebuilds instead of failing a query.



##########
pinot-segment-spi/src/main/java/org/apache/pinot/segment/spi/index/metadata/MappedSegmentMetadataStore.java:
##########
@@ -0,0 +1,880 @@
+/**
+ * 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.spi.index.metadata;
+
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import com.google.common.collect.Interner;
+import com.google.common.collect.Interners;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.Closeable;
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.File;
+import java.io.IOException;
+import java.math.BigDecimal;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.AtomicMoveNotSupportedException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.StandardCopyOption;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.stream.Collectors;
+import java.util.zip.CRC32C;
+import javax.annotation.Nullable;
+import org.apache.commons.configuration2.Configuration;
+import org.apache.commons.configuration2.PropertiesConfiguration;
+import org.apache.commons.configuration2.ex.ConfigurationException;
+import org.apache.pinot.segment.spi.ColumnMetadata;
+import org.apache.pinot.segment.spi.V1Constants;
+import org.apache.pinot.segment.spi.index.IndexService;
+import org.apache.pinot.segment.spi.index.IndexType;
+import org.apache.pinot.segment.spi.memory.PinotDataBuffer;
+import org.apache.pinot.segment.spi.partition.PartitionFunction;
+import org.apache.pinot.segment.spi.store.ColumnIndexUtils;
+import org.apache.pinot.segment.spi.store.SegmentDirectoryPaths;
+import org.apache.pinot.segment.spi.utils.SegmentMetadataUtils;
+import org.apache.pinot.spi.config.table.FieldConfig.EncodingType;
+import org.apache.pinot.spi.data.ComplexFieldSpec;
+import org.apache.pinot.spi.data.FieldSpec;
+import org.apache.pinot.spi.env.CommonsConfigurationUtils;
+import org.apache.pinot.spi.utils.ByteArray;
+import org.apache.pinot.spi.utils.ColumnNameInterner;
+import org.apache.pinot.spi.utils.JsonUtils;
+
+/// Versioned, file-backed metadata for a sealed segment. The source 
`metadata.properties` remains the canonical
+/// representation for old readers and preprocessing.
+///
+/// The big-endian file starts with a 64-byte header containing the magic, 
format version, total length, source
+/// `metadata.properties` and `index_map` length/CRC fingerprints, payload 
CRC, segment-property blob reference,
+/// column count, and record-table bounds. Sorted columns occupy fixed 
180-byte records, followed by variable blobs;
+/// each blob reference is an absolute 32-bit offset and length. Numeric 
column statistics are read directly from the
+/// mapping. The column layout retains names and reusable [FieldSpec] 
references on heap.
+///
+/// A stale or corrupt sidecar is rebuilt from the canonical source and 
replaced locally, using atomic rename where
+/// supported. Change `VERSION` whenever the header, record, or blob encoding 
changes; old versions are rebuilt rather
+/// than migrated.
+final class MappedSegmentMetadataStore implements Closeable {
+  static final String FILE_NAME = "segment_metadata.bin";
+
+  private static final int MAGIC = 0x504d4431; // PMD1
+  private static final int VERSION = 3;
+  private static final int HEADER_SIZE = 64;
+  private static final int RECORD_SIZE = 180;
+
+  static final int NAME = 0;
+  static final int FIELD_SPEC = 8;
+  static final int TOTAL_DOCS = 16;
+  static final int CARDINALITY = 20;
+  static final int FLAGS = 24;
+  static final int ENCODING = 28;
+  static final int SHORTEST = 32;
+  static final int LONGEST = 36;
+  static final int TOTAL_ENTRIES = 40;
+  static final int MAX_MV = 44;
+  static final int MAX_ROW = 48;
+  static final int BITS = 52;
+  static final int MIN_TAG = 56;
+  static final int MIN_RAW = 60;
+  static final int MIN_BLOB = 68;
+  static final int MAX_TAG = 76;
+  static final int MAX_RAW = 80;
+  static final int MAX_BLOB = 88;
+  static final int PARTITION_FUNCTION = 96;
+  static final int PARTITIONS = 104;
+  static final int PARENT = 112;
+  static final int SPARSE_KEYS = 120;
+  static final int SPARSE_MULTI_VALUE_KEYS = 128;
+  static final int SPARSE_KEY_TYPES = 136;
+  static final int RAW_UNCOMPRESSED_SIZE = 144;
+  static final int DICTIONARY_UNCOMPRESSED_SIZE = 152;
+  static final int COMPRESSION_TYPE = 160;
+  static final int INDEXES = 168;
+  static final int INDEX_COUNT = 176;
+
+  static final int HAS_DICTIONARY = 1;
+  static final int SORTED = 1 << 1;
+  static final int NON_NULL = 1 << 2;
+  static final int INVALID_MIN_MAX = 1 << 3;
+  static final int ASCII = 1 << 4;
+  static final int AUTO_GENERATED = 1 << 5;
+
+  private static final int NO_VALUE = 0;
+  private static final int INT_VALUE = 1;
+  private static final int LONG_VALUE = 2;
+  private static final int FLOAT_VALUE = 3;
+  private static final int DOUBLE_VALUE = 4;
+  private static final int DECIMAL_VALUE = 5;
+  private static final int STRING_VALUE = 6;
+  private static final int BYTES_VALUE = 7;
+
+  private static final Interner<FieldSpec> FIELD_SPECS = 
Interners.newWeakInterner();
+  private static final Interner<ColumnLayout> COLUMN_LAYOUTS = 
Interners.newWeakInterner();
+
+  private final PinotDataBuffer _buffer;
+  private final ColumnLayout _columns;
+  private final int _tableOffset;
+  private final int _segmentConfigOffset;
+  private final int _segmentConfigLength;
+
+  private MappedSegmentMetadataStore(PinotDataBuffer buffer, ColumnLayout 
columns, int tableOffset,
+      int segmentConfigOffset, int segmentConfigLength) {
+    _buffer = buffer;
+    _columns = columns;
+    _tableOffset = tableOffset;
+    _segmentConfigOffset = segmentConfigOffset;
+    _segmentConfigLength = segmentConfigLength;
+  }
+
+  static MappedSegmentMetadataStore open(File indexDir)
+      throws IOException, ConfigurationException {
+    File source = SegmentDirectoryPaths.findMetadataFile(indexDir);
+    if (source == null) {
+      throw new IOException("Missing metadata.properties in " + indexDir);
+    }
+    File sidecar = new File(indexDir, FILE_NAME);
+    for (int attempt = 0; attempt < 3; attempt++) {
+      Fingerprint propertiesFingerprint = Fingerprint.of(source);
+      Fingerprint indexMapFingerprint = Fingerprint.of(findIndexMap(indexDir));

Review Comment:
   Minor: the fingerprint keys on the chosen index_map's content, but 
`findIndexMap()` prefers `v3/index_map` and falls back to the root map, and the 
zero-size handling differs between the two (`stagedIndexMap`). If the chosen 
file ever flips location but happens to share the same length+CRC as the 
previously fingerprinted file, a stale sidecar could be served. Very low 
probability and self-correcting, but mixing the chosen path (v3 vs root) into 
the fingerprint would close the gap.



##########
pinot-segment-spi/src/main/java/org/apache/pinot/segment/spi/index/metadata/SegmentMetadataImpl.java:
##########
@@ -483,21 +574,83 @@ public String getEndOffset() {
 
   @Override
   public TreeMap<String, ColumnMetadata> getColumnMetadataMap() {
-    return _columnMetadataMap;
+    if (_mappedStore == null) {
+      return _columnMetadataMap;
+    }
+    // Compatibility API for metadata/admin callers. The serving path uses 
column names and direct lookup instead.
+    TreeMap<String, ColumnMetadata> result = new TreeMap<>();
+    for (String column : getPhysicalColumnNames()) {
+      result.put(column, getColumnMetadataFor(column));
+    }
+    result.putAll(_virtualColumnMetadata);
+    return result;
+  }
+
+  /// Returns the physical column names without materializing a metadata map 
or Schema on mapped segments.
+  public Set<String> getPhysicalColumnNames() {
+    return _mappedStore != null ? _physicalColumnNames : 
_schema.getPhysicalColumnNames();
+  }
+
+  /// Indicates whether the immutable segment is reading its metadata from the 
binary mapping.
+  public boolean isMapped() {
+    return _mappedStore != null;
+  }
+
+  @Override
+  public ColumnMetadata getColumnMetadataFor(String column) {

Review Comment:
   Minor: `getColumnMetadataFor` now returns null for unknown columns and 
callers depend on that — worth adding `@Nullable` here (and on the 
`SegmentMetadata` interface declaration) to make the contract explicit.



##########
pinot-segment-spi/src/main/java/org/apache/pinot/segment/spi/index/metadata/MappedColumnMetadata.java:
##########
@@ -0,0 +1,363 @@
+/**
+ * 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.spi.index.metadata;
+
+import com.fasterxml.jackson.core.type.TypeReference;
+import java.io.ByteArrayInputStream;
+import java.io.DataInputStream;
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import javax.annotation.Nullable;
+import org.apache.pinot.segment.spi.ColumnMetadata;
+import org.apache.pinot.segment.spi.compression.ChunkCompressionType;
+import org.apache.pinot.segment.spi.index.IndexService;
+import org.apache.pinot.segment.spi.index.IndexType;
+import org.apache.pinot.segment.spi.partition.PartitionFunction;
+import org.apache.pinot.segment.spi.partition.PartitionFunctionFactory;
+import org.apache.pinot.spi.config.table.FieldConfig.EncodingType;
+import org.apache.pinot.spi.data.FieldSpec;
+import org.apache.pinot.spi.data.FieldSpec.DataType;
+import org.apache.pinot.spi.utils.JsonUtils;
+
+/// A small view of one fixed record in [MappedSegmentMetadataStore]. No 
per-column values are copied into this view.
+/// The segment owns the mapping; callers must not use the view after the 
segment directory is closed.
+public final class MappedColumnMetadata implements ColumnMetadata {
+  private final MappedSegmentMetadataStore _store;
+  private final int _ordinal;
+  @Nullable
+  private volatile PartitionFunction _partitionFunction;
+  @Nullable
+  private volatile Set<Integer> _partitions;
+  @Nullable
+  private volatile Bounds _bounds;
+
+  private static final class Bounds {
+    @Nullable
+    private final Comparable<?> _minimum;
+    @Nullable
+    private final Comparable<?> _maximum;
+
+    private Bounds(@Nullable Comparable<?> minimum, @Nullable Comparable<?> 
maximum) {
+      _minimum = minimum;
+      _maximum = maximum;
+    }
+  }
+
+  MappedColumnMetadata(MappedSegmentMetadataStore store, int ordinal) {
+    _store = store;
+    _ordinal = ordinal;
+  }
+
+  @Override
+  public FieldSpec getFieldSpec() {

Review Comment:
   Minor: the returned `FieldSpec` is interned and shared across segments of 
the table, and `FieldSpec` is mutable. The safety here rests on callers never 
mutating a spec obtained from mapped metadata (the `ColumnLayout` comment 
explains the hash stability but not this caller contract). A one-line note that 
the returned spec is shared/read-only would harden this against a future 
mutating caller.



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