This is an automated email from the ASF dual-hosted git repository.

JingsongLi pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/paimon-cpp.git


The following commit(s) were added to refs/heads/main by this push:
     new ba013b4  feat: add index file metadata and handler infrastructure (#78)
ba013b4 is described below

commit ba013b476c5bbc5cb27b338e47bb02e962828f59
Author: lszskye <[email protected]>
AuthorDate: Wed Jun 17 19:16:57 2026 -0700

    feat: add index file metadata and handler infrastructure (#78)
---
 src/paimon/core/index/deletion_vector_meta.h       |  97 +++++++
 .../core/index/deletion_vector_meta_test.cpp       |  46 +++
 src/paimon/core/index/global_index_meta.cpp        | 124 ++++++++
 src/paimon/core/index/global_index_meta.h          |  57 ++++
 src/paimon/core/index/index_file.h                 |  72 +++++
 src/paimon/core/index/index_file_handler.cpp       |  76 +++++
 src/paimon/core/index/index_file_handler.h         | 102 +++++++
 src/paimon/core/index/index_file_handler_test.cpp  | 320 +++++++++++++++++++++
 src/paimon/core/index/index_file_meta.h            | 183 ++++++++++++
 .../core/index/index_file_meta_serializer.cpp      | 112 ++++++++
 src/paimon/core/index/index_file_meta_serializer.h |  62 ++++
 .../core/index/index_file_meta_serializer_test.cpp | 146 ++++++++++
 .../core/index/index_file_meta_v1_deserializer.h   |  88 ++++++
 .../core/index/index_file_meta_v2_deserializer.h   |  84 ++++++
 .../core/index/index_file_meta_v3_deserializer.h   |  88 ++++++
 .../index/index_in_data_file_dir_path_factory.h    |  70 +++++
 .../index_in_data_file_dir_path_factory_test.cpp   |  84 ++++++
 src/paimon/core/index/index_path_factory.h         |  40 +++
 18 files changed, 1851 insertions(+)

diff --git a/src/paimon/core/index/deletion_vector_meta.h 
b/src/paimon/core/index/deletion_vector_meta.h
new file mode 100644
index 0000000..f951580
--- /dev/null
+++ b/src/paimon/core/index/deletion_vector_meta.h
@@ -0,0 +1,97 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+#include <cstdint>
+#include <memory>
+#include <optional>
+#include <string>
+#include <vector>
+
+#include "arrow/api.h"
+#include "fmt/core.h"
+#include "fmt/format.h"
+
+namespace paimon {
+/// Indicates the deletion vector info of member data_file_name, e.g., the 
length of dv.
+/// * DeletionVectorMeta is used when serialize to manifest file.
+class DeletionVectorMeta {
+ public:
+    static const std::shared_ptr<arrow::DataType>& DataType() {
+        static std::shared_ptr<arrow::DataType> schema = arrow::struct_(
+            {arrow::field("f0", arrow::utf8(), false), arrow::field("f1", 
arrow::int32(), false),
+             arrow::field("f2", arrow::int32(), false),
+             arrow::field("_CARDINALITY", arrow::int64(), true)});
+        return schema;
+    }
+    DeletionVectorMeta(const std::string& data_file_name, int32_t offset, 
int32_t length,
+                       const std::optional<int64_t>& cardinality)
+        : data_file_name_(data_file_name),
+          offset_(offset),
+          length_(length),
+          cardinality_(cardinality) {}
+
+    bool operator==(const DeletionVectorMeta& other) const {
+        if (this == &other) {
+            return true;
+        }
+        return data_file_name_ == other.data_file_name_ && offset_ == 
other.offset_ &&
+               length_ == other.length_ && cardinality_ == other.cardinality_;
+    }
+
+    bool TEST_Equal(const DeletionVectorMeta& other) const {
+        if (this == &other) {
+            return true;
+        }
+        // ignore data_file_name
+        return offset_ == other.offset_ && length_ == other.length_ &&
+               cardinality_ == other.cardinality_;
+    }
+
+    std::string ToString() const {
+        return fmt::format(
+            "DeletionVectorMeta{{data_file_name = {}, offset = {}, length = 
{}, cardinality = {}}}",
+            data_file_name_, offset_, length_,
+            cardinality_ == std::nullopt ? "null" : 
std::to_string(cardinality_.value()));
+    }
+
+    const std::string& GetDataFileName() const {
+        return data_file_name_;
+    }
+
+    int32_t GetOffset() const {
+        return offset_;
+    }
+
+    int32_t GetLength() const {
+        return length_;
+    }
+
+    std::optional<int64_t> GetCardinality() const {
+        return cardinality_;
+    }
+
+ private:
+    std::string data_file_name_;
+    int32_t offset_;
+    int32_t length_;
+    std::optional<int64_t> cardinality_;
+};
+}  // namespace paimon
diff --git a/src/paimon/core/index/deletion_vector_meta_test.cpp 
b/src/paimon/core/index/deletion_vector_meta_test.cpp
new file mode 100644
index 0000000..94230ea
--- /dev/null
+++ b/src/paimon/core/index/deletion_vector_meta_test.cpp
@@ -0,0 +1,46 @@
+/*
+ * 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.
+ */
+
+#include "paimon/core/index/deletion_vector_meta.h"
+
+#include "gtest/gtest.h"
+
+namespace paimon::test {
+TEST(DeletionVectorMetaTest, EqualityOperator) {
+    DeletionVectorMeta meta1("file1", 0, 100, 1000);
+    DeletionVectorMeta meta2("file1", 0, 100, 1000);
+    DeletionVectorMeta meta3("file2", 0, 100, 1000);
+
+    EXPECT_TRUE(meta1 == meta2);
+    EXPECT_FALSE(meta1 == meta3);
+}
+
+TEST(DeletionVectorMetaTest, ToString) {
+    DeletionVectorMeta meta("file1", 0, 100, 1000);
+    std::string expected =
+        "DeletionVectorMeta{data_file_name = file1, offset = 0, length = 100, 
cardinality = 1000}";
+    EXPECT_EQ(meta.ToString(), expected);
+
+    DeletionVectorMeta meta_null("file1", 0, 100, std::nullopt);
+    expected =
+        "DeletionVectorMeta{data_file_name = file1, offset = 0, length = 100, 
cardinality = null}";
+    EXPECT_EQ(meta_null.ToString(), expected);
+}
+
+}  // namespace paimon::test
diff --git a/src/paimon/core/index/global_index_meta.cpp 
b/src/paimon/core/index/global_index_meta.cpp
new file mode 100644
index 0000000..ffe05e3
--- /dev/null
+++ b/src/paimon/core/index/global_index_meta.cpp
@@ -0,0 +1,124 @@
+/*
+ * 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.
+ */
+
+#include "paimon/core/index/global_index_meta.h"
+
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "fmt/format.h"
+#include "fmt/ranges.h"
+#include "paimon/common/data/binary_array.h"
+#include "paimon/common/data/binary_row_writer.h"
+
+namespace paimon {
+GlobalIndexMeta::GlobalIndexMeta(int64_t _row_range_start, int64_t 
_row_range_end,
+                                 int32_t _index_field_id,
+                                 const std::optional<std::vector<int32_t>>& 
_extra_field_ids,
+                                 const std::shared_ptr<Bytes>& _index_meta)
+    : row_range_start(_row_range_start),
+      row_range_end(_row_range_end),
+      index_field_id(_index_field_id),
+      extra_field_ids(_extra_field_ids),
+      index_meta(_index_meta) {}
+
+bool GlobalIndexMeta::operator==(const GlobalIndexMeta& other) const {
+    if (this == &other) {
+        return true;
+    }
+    if ((index_meta && !other.index_meta) || (!index_meta && 
other.index_meta)) {
+        return false;
+    }
+    if (index_meta && other.index_meta && !(*index_meta == *other.index_meta)) 
{
+        return false;
+    }
+    return row_range_start == other.row_range_start && row_range_end == 
other.row_range_end &&
+           index_field_id == other.index_field_id && extra_field_ids == 
other.extra_field_ids;
+}
+
+std::string GlobalIndexMeta::ToString() const {
+    std::string extra_field_ids_str =
+        extra_field_ids == std::nullopt
+            ? "null"
+            : fmt::format("{}", fmt::join(extra_field_ids.value(), ", "));
+
+    std::string index_meta_str =
+        index_meta == nullptr ? "null" : std::string(index_meta->data(), 
index_meta->size());
+    return fmt::format(
+        "{{row_range_start={}, row_range_end={}, index_field_id={}, 
extra_field_ids={}, "
+        "index_meta={}}}",
+        row_range_start, row_range_end, index_field_id, extra_field_ids_str, 
index_meta_str);
+}
+
+BinaryRow GlobalIndexMeta::ToRow(MemoryPool* pool) const {
+    BinaryRow row(5);
+    BinaryRowWriter writer(&row, 32 * 1024, pool);
+    writer.WriteLong(0, row_range_start);
+    writer.WriteLong(1, row_range_end);
+    writer.WriteInt(2, index_field_id);
+    if (!extra_field_ids) {
+        writer.SetNullAt(3);
+    } else {
+        writer.WriteArray(3, 
BinaryArray::FromIntArray(extra_field_ids.value(), pool));
+    }
+    if (index_meta == nullptr) {
+        writer.SetNullAt(4);
+    } else {
+        writer.WriteBinary(4, *index_meta);
+    }
+    writer.Complete();
+    return row;
+}
+
+Result<GlobalIndexMeta> GlobalIndexMeta::FromRow(const InternalRow& row) {
+    int64_t row_range_start = row.GetLong(0);
+    int64_t row_range_end = row.GetLong(1);
+    int32_t index_field_id = row.GetInt(2);
+    std::optional<std::vector<int32_t>> extra_field_ids;
+    if (!row.IsNullAt(3)) {
+        std::shared_ptr<InternalArray> array = row.GetArray(3);
+        if (!array) {
+            return Status::Invalid("GlobalIndexMeta FromRow failed with 
nullptr extra field ids");
+        }
+        PAIMON_ASSIGN_OR_RAISE(extra_field_ids, array->ToIntArray());
+    }
+    std::shared_ptr<Bytes> index_meta;
+    if (!row.IsNullAt(4)) {
+        index_meta = row.GetBinary(4);
+        assert(index_meta);
+    }
+    return GlobalIndexMeta(row_range_start, row_range_end, index_field_id, 
extra_field_ids,
+                           index_meta);
+}
+
+const std::shared_ptr<arrow::DataType>& GlobalIndexMeta::DataType() {
+    static std::shared_ptr<arrow::DataType> schema = arrow::struct_({
+        arrow::field("_ROW_RANGE_START", arrow::int64(), /*nullable=*/false),
+        arrow::field("_ROW_RANGE_END", arrow::int64(), /*nullable=*/false),
+        arrow::field("_INDEX_FIELD_ID", arrow::int32(), /*nullable=*/false),
+        arrow::field("_EXTRA_FIELD_IDS",
+                     arrow::list(arrow::field("item", arrow::int32(), 
/*nullable=*/false)),
+                     /*nullable=*/true),
+        arrow::field("_INDEX_META", arrow::binary(), /*nullable=*/true),
+    });
+    return schema;
+}
+
+}  // namespace paimon
diff --git a/src/paimon/core/index/global_index_meta.h 
b/src/paimon/core/index/global_index_meta.h
new file mode 100644
index 0000000..a11cb82
--- /dev/null
+++ b/src/paimon/core/index/global_index_meta.h
@@ -0,0 +1,57 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+#include <memory>
+#include <optional>
+#include <string>
+#include <vector>
+
+#include "arrow/api.h"
+#include "fmt/format.h"
+#include "paimon/common/data/binary_row.h"
+#include "paimon/memory/bytes.h"
+namespace paimon {
+/// Schema for global index.
+struct GlobalIndexMeta {
+    static constexpr int32_t NUM_FIELDS = 5;
+
+    GlobalIndexMeta(int64_t _row_range_start, int64_t _row_range_end, int32_t 
_index_field_id,
+                    const std::optional<std::vector<int32_t>>& 
_extra_field_ids,
+                    const std::shared_ptr<Bytes>& _index_meta);
+
+    bool operator==(const GlobalIndexMeta& other) const;
+
+    std::string ToString() const;
+
+    BinaryRow ToRow(MemoryPool* pool) const;
+
+    static Result<GlobalIndexMeta> FromRow(const InternalRow& row);
+
+    static const std::shared_ptr<arrow::DataType>& DataType();
+
+    int64_t row_range_start;
+    int64_t row_range_end;
+    int32_t index_field_id;
+    std::optional<std::vector<int32_t>> extra_field_ids;
+    std::shared_ptr<Bytes> index_meta;
+};
+
+}  // namespace paimon
diff --git a/src/paimon/core/index/index_file.h 
b/src/paimon/core/index/index_file.h
new file mode 100644
index 0000000..e76c19d
--- /dev/null
+++ b/src/paimon/core/index/index_file.h
@@ -0,0 +1,72 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "paimon/core/index/index_path_factory.h"
+#include "paimon/fs/file_system.h"
+#include "paimon/result.h"
+
+namespace paimon {
+
+// Base index file
+class IndexFile {
+ public:
+    IndexFile(const std::shared_ptr<FileSystem>& fs,
+              const std::shared_ptr<IndexPathFactory>& path_factory)
+        : fs_(fs), path_factory_(path_factory) {}
+    virtual ~IndexFile() = default;
+
+    virtual std::string Path(const std::shared_ptr<IndexFileMeta>& file) const 
{
+        return path_factory_->ToPath(file);
+    }
+
+    virtual Result<uint64_t> FileSize(const std::shared_ptr<IndexFileMeta>& 
file) const {
+        return FileSize(Path(file));
+    }
+
+    virtual Result<uint64_t> FileSize(const std::string& file) const {
+        PAIMON_ASSIGN_OR_RAISE(std::unique_ptr<FileStatus> file_status, 
fs_->GetFileStatus(file));
+        return file_status->GetLen();
+    }
+
+    virtual void Delete(const std::shared_ptr<IndexFileMeta>& file) const {
+        // Deletion is best-effort
+        auto status = fs_->Delete(Path(file), /*recursive=*/false);
+        (void)status;
+    }
+
+    virtual Result<bool> Exists(const std::shared_ptr<IndexFileMeta>& file) 
const {
+        return fs_->Exists(Path(file));
+    }
+
+    virtual bool IsExternalPath() const {
+        return path_factory_->IsExternalPath();
+    }
+
+ protected:
+    std::shared_ptr<FileSystem> fs_;
+    std::shared_ptr<IndexPathFactory> path_factory_;
+};
+
+}  // namespace paimon
diff --git a/src/paimon/core/index/index_file_handler.cpp 
b/src/paimon/core/index/index_file_handler.cpp
new file mode 100644
index 0000000..9b45e74
--- /dev/null
+++ b/src/paimon/core/index/index_file_handler.cpp
@@ -0,0 +1,76 @@
+/*
+ * 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.
+ */
+
+#include "paimon/core/index/index_file_handler.h"
+
+#include <functional>
+#include <optional>
+
+#include "paimon/core/snapshot.h"
+#include "paimon/status.h"
+
+namespace paimon {
+
+Result<IndexFileHandler::IndexFileMetaGroups> IndexFileHandler::Scan(
+    const Snapshot& snapshot, const std::string& index_type,
+    const std::unordered_set<BinaryRow>& partitions) const {
+    IndexFileHandler::IndexFileMetaGroups result;
+    std::function<Result<bool>(const IndexManifestEntry&)> filter =
+        [&](const IndexManifestEntry& entry) -> bool {
+        if (entry.index_file->IndexType() == index_type &&
+            partitions.find(entry.partition) != partitions.end()) {
+            return true;
+        }
+        return false;
+    };
+
+    PAIMON_ASSIGN_OR_RAISE(std::vector<IndexManifestEntry> index_entries, 
Scan(snapshot, filter));
+    for (const auto& entry : index_entries) {
+        std::pair<BinaryRow, int32_t> key(entry.partition, entry.bucket);
+        result[key].push_back(entry.index_file);
+    }
+    return result;
+}
+
+Result<std::vector<IndexManifestEntry>> IndexFileHandler::Scan(
+    const Snapshot& snapshot, std::function<Result<bool>(const 
IndexManifestEntry&)> filter) const {
+    const std::optional<std::string>& index_manifest = 
snapshot.IndexManifest();
+    if (index_manifest == std::nullopt) {
+        return std::vector<IndexManifestEntry>();
+    }
+    std::vector<IndexManifestEntry> index_entries;
+    PAIMON_RETURN_NOT_OK(
+        index_manifest_file_->Read(index_manifest.value(), filter, 
&index_entries));
+    return index_entries;
+}
+
+Result<std::vector<std::shared_ptr<IndexFileMeta>>> IndexFileHandler::Scan(
+    const Snapshot& snapshot, const std::string& index_type, const BinaryRow& 
partition,
+    int32_t bucket) const {
+    PAIMON_ASSIGN_OR_RAISE(IndexFileHandler::IndexFileMetaGroups 
index_file_meta_groups,
+                           Scan(snapshot, index_type, {partition}));
+    std::pair<BinaryRow, int32_t> key(partition, bucket);
+    auto iter = index_file_meta_groups.find(key);
+    if (iter != index_file_meta_groups.end()) {
+        return iter->second;
+    }
+    return std::vector<std::shared_ptr<IndexFileMeta>>{};
+}
+
+}  // namespace paimon
diff --git a/src/paimon/core/index/index_file_handler.h 
b/src/paimon/core/index/index_file_handler.h
new file mode 100644
index 0000000..a84840a
--- /dev/null
+++ b/src/paimon/core/index/index_file_handler.h
@@ -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.
+ */
+
+#pragma once
+
+#include <cstdint>
+#include <memory>
+#include <string>
+#include <unordered_map>
+#include <unordered_set>
+#include <utility>
+#include <vector>
+
+#include "paimon/common/data/binary_row.h"
+#include "paimon/core/deletionvectors/deletion_vectors_index_file.h"
+#include "paimon/core/index/index_file_meta.h"
+#include "paimon/core/index/index_path_factory.h"
+#include "paimon/core/manifest/index_manifest_entry.h"
+#include "paimon/core/manifest/index_manifest_file.h"
+#include "paimon/core/snapshot.h"
+#include "paimon/core/utils/index_file_path_factories.h"
+#include "paimon/result.h"
+
+namespace paimon {
+class Snapshot;
+
+class IndexFileHandler {
+ public:
+    using IndexFileMetaGroups = std::unordered_map<std::pair<BinaryRow, 
int32_t>,
+                                                   
std::vector<std::shared_ptr<IndexFileMeta>>>;
+
+    IndexFileHandler(const std::shared_ptr<FileSystem>& fs,
+                     std::unique_ptr<IndexManifestFile>&& index_manifest_file,
+                     const std::shared_ptr<IndexFilePathFactories>& 
path_factories,
+                     bool dv_bitmap64, const std::shared_ptr<MemoryPool>& pool)
+        : fs_(fs),
+          index_manifest_file_(std::move(index_manifest_file)),
+          path_factories_(path_factories),
+          dv_bitmap64_(dv_bitmap64),
+          pool_(pool) {}
+
+    /// 1.Scan specified index_type index. 2.Cluster with partition & bucket.
+    Result<IndexFileMetaGroups> Scan(const Snapshot& snapshot, const 
std::string& index_type,
+                                     const std::unordered_set<BinaryRow>& 
partitions) const;
+
+    Result<std::vector<std::shared_ptr<IndexFileMeta>>> Scan(const Snapshot& 
snapshot,
+                                                             const 
std::string& index_type,
+                                                             const BinaryRow& 
partition,
+                                                             int32_t bucket) 
const;
+
+    /// Scan specified all typed index.
+    Result<std::vector<IndexManifestEntry>> Scan(
+        const Snapshot& snapshot,
+        std::function<Result<bool>(const IndexManifestEntry&)> filter) const;
+
+    Result<std::string> FilePath(const BinaryRow& partition, int32_t bucket,
+                                 const std::shared_ptr<IndexFileMeta>& file) 
const {
+        PAIMON_ASSIGN_OR_RAISE(std::shared_ptr<IndexPathFactory> factory,
+                               path_factories_->Get(partition, bucket));
+        return factory->ToPath(file);
+    }
+
+    Result<std::unique_ptr<DeletionVectorsIndexFile>> DvIndex(const BinaryRow& 
partition,
+                                                              int32_t bucket) 
const {
+        PAIMON_ASSIGN_OR_RAISE(std::shared_ptr<IndexPathFactory> 
index_path_factory,
+                               path_factories_->Get(partition, bucket));
+        return std::make_unique<DeletionVectorsIndexFile>(fs_, 
index_path_factory, dv_bitmap64_,
+                                                          pool_);
+    }
+
+    Result<std::map<std::string, std::shared_ptr<DeletionVector>>> 
ReadAllDeletionVectors(
+        const BinaryRow& partition, int32_t bucket,
+        const std::vector<std::shared_ptr<IndexFileMeta>>& file_metas) const {
+        PAIMON_ASSIGN_OR_RAISE(std::unique_ptr<DeletionVectorsIndexFile> 
dv_index,
+                               DvIndex(partition, bucket));
+        return dv_index->ReadAllDeletionVectors(file_metas);
+    }
+
+ private:
+    std::shared_ptr<FileSystem> fs_;
+    std::unique_ptr<IndexManifestFile> index_manifest_file_;
+    std::shared_ptr<IndexFilePathFactories> path_factories_;
+    bool dv_bitmap64_;
+    std::shared_ptr<MemoryPool> pool_;
+};
+}  // namespace paimon
diff --git a/src/paimon/core/index/index_file_handler_test.cpp 
b/src/paimon/core/index/index_file_handler_test.cpp
new file mode 100644
index 0000000..8236d18
--- /dev/null
+++ b/src/paimon/core/index/index_file_handler_test.cpp
@@ -0,0 +1,320 @@
+/*
+ * 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.
+ */
+
+#include "paimon/core/index/index_file_handler.h"
+
+#include <map>
+#include <optional>
+#include <variant>
+
+#include "gtest/gtest.h"
+#include "paimon/common/types/data_field.h"
+#include "paimon/common/utils/linked_hash_map.h"
+#include "paimon/common/utils/object_utils.h"
+#include "paimon/common/utils/path_util.h"
+#include "paimon/core/core_options.h"
+#include "paimon/core/deletionvectors/deletion_vectors_index_file.h"
+#include "paimon/core/index/deletion_vector_meta.h"
+#include "paimon/core/schema/schema_manager.h"
+#include "paimon/core/schema/table_schema.h"
+#include "paimon/core/snapshot.h"
+#include "paimon/core/utils/file_store_path_factory.h"
+#include "paimon/core/utils/snapshot_manager.h"
+#include "paimon/defs.h"
+#include "paimon/memory/memory_pool.h"
+#include "paimon/status.h"
+#include "paimon/testing/utils/binary_row_generator.h"
+#include "paimon/testing/utils/testharness.h"
+
+namespace paimon::test {
+class IndexFileHandlerTest : public testing::Test {
+ public:
+    void SetUp() override {
+        memory_pool_ = GetDefaultPool();
+    }
+
+    Result<std::unique_ptr<IndexFileHandler>> CreateIndexFileHandler(
+        const std::string& table_path, const CoreOptions& core_options) const {
+        SchemaManager schema_manager(core_options.GetFileSystem(), table_path);
+        PAIMON_ASSIGN_OR_RAISE(std::shared_ptr<TableSchema> table_schema,
+                               schema_manager.ReadSchema(/*schema_id=*/0));
+        auto schema = 
DataField::ConvertDataFieldsToArrowSchema(table_schema->Fields());
+        PAIMON_ASSIGN_OR_RAISE(std::vector<std::string> external_paths,
+                               core_options.CreateExternalPaths());
+        PAIMON_ASSIGN_OR_RAISE(std::optional<std::string> 
global_index_external_path,
+                               core_options.CreateGlobalIndexExternalPath());
+
+        PAIMON_ASSIGN_OR_RAISE(
+            std::shared_ptr<FileStorePathFactory> path_factory,
+            FileStorePathFactory::Create(
+                table_path, schema, table_schema->PartitionKeys(),
+                core_options.GetPartitionDefaultName(),
+                /*identifier=*/"orc", core_options.DataFilePrefix(),
+                core_options.LegacyPartitionNameEnabled(), external_paths,
+                global_index_external_path,
+                
/*index_file_in_data_file_dir=*/core_options.IndexFileInDataFileDir(),
+                memory_pool_));
+        PAIMON_ASSIGN_OR_RAISE(std::unique_ptr<IndexManifestFile> 
index_manifest_file,
+                               IndexManifestFile::Create(
+                                   core_options.GetFileSystem(), 
core_options.GetManifestFormat(),
+                                   core_options.GetManifestCompression(), 
path_factory,
+                                   core_options.GetBucket(), memory_pool_, 
core_options));
+        auto path_factories = 
std::make_shared<IndexFilePathFactories>(path_factory);
+        return std::make_unique<IndexFileHandler>(
+            core_options.GetFileSystem(), std::move(index_manifest_file), 
path_factories,
+            core_options.DeletionVectorsBitmap64(), memory_pool_);
+    }
+    std::shared_ptr<MemoryPool> memory_pool_;
+};
+
+TEST_F(IndexFileHandlerTest, TestFilePath) {
+    std::string table_path = paimon::test::GetDataDir() +
+                             
"/orc/pk_table_with_dv_cardinality.db/pk_table_with_dv_cardinality/";
+    {
+        // test without external path & index-file-in-data-file-dir" = false
+        ASSERT_OK_AND_ASSIGN(CoreOptions core_options, 
CoreOptions::FromMap({}));
+        ASSERT_OK_AND_ASSIGN(std::unique_ptr<IndexFileHandler> 
index_file_handler,
+                             CreateIndexFileHandler(table_path, core_options));
+        auto index_file_meta = std::make_shared<IndexFileMeta>(
+            /*index_type=*/"DELETION_VECTOR", /*file_name=*/"deletion-file", 
/*file_size=*/1000,
+            /*row_count=*/100, /*dv_ranges=*/std::nullopt, 
/*external_path=*/std::nullopt);
+        auto partition = BinaryRowGenerator::GenerateRow({10}, 
memory_pool_.get());
+        ASSERT_OK_AND_ASSIGN(std::string file_path, 
index_file_handler->FilePath(
+                                                        partition, 
/*bucket=*/1, index_file_meta));
+        ASSERT_EQ(file_path, table_path + "index/deletion-file");
+    }
+    {
+        // test with external path & index-file-in-data-file-dir" = false
+        ASSERT_OK_AND_ASSIGN(
+            CoreOptions core_options,
+            CoreOptions::FromMap({{Options::DATA_FILE_EXTERNAL_PATHS, 
"FILE:///tmp/"}}));
+        ASSERT_OK_AND_ASSIGN(std::unique_ptr<IndexFileHandler> 
index_file_handler,
+                             CreateIndexFileHandler(table_path, core_options));
+        auto index_file_meta = std::make_shared<IndexFileMeta>(
+            /*index_type=*/"DELETION_VECTOR", /*file_name=*/"deletion-file", 
/*file_size=*/1000,
+            /*row_count=*/100, /*dv_ranges=*/std::nullopt, 
/*external_path=*/std::nullopt);
+        auto partition = BinaryRowGenerator::GenerateRow({10}, 
memory_pool_.get());
+        ASSERT_OK_AND_ASSIGN(std::string file_path, 
index_file_handler->FilePath(
+                                                        partition, 
/*bucket=*/1, index_file_meta));
+        ASSERT_EQ(file_path, table_path + "index/deletion-file");
+    }
+    {
+        // test without external path & index-file-in-data-file-dir" = true
+        ASSERT_OK_AND_ASSIGN(
+            CoreOptions core_options,
+            CoreOptions::FromMap({{Options::INDEX_FILE_IN_DATA_FILE_DIR, 
"true"}}));
+        ASSERT_OK_AND_ASSIGN(std::unique_ptr<IndexFileHandler> 
index_file_handler,
+                             CreateIndexFileHandler(table_path, core_options));
+        auto index_file_meta = std::make_shared<IndexFileMeta>(
+            /*index_type=*/"DELETION_VECTOR", /*file_name=*/"deletion-file", 
/*file_size=*/1000,
+            /*row_count=*/100, /*dv_ranges=*/std::nullopt, 
/*external_path=*/std::nullopt);
+        auto partition = BinaryRowGenerator::GenerateRow({10}, 
memory_pool_.get());
+        ASSERT_OK_AND_ASSIGN(std::string file_path, 
index_file_handler->FilePath(
+                                                        partition, 
/*bucket=*/1, index_file_meta));
+        ASSERT_EQ(file_path, table_path + "f1=10/bucket-1/deletion-file");
+    }
+    {
+        // test with external path & index-file-in-data-file-dir" = true
+        ASSERT_OK_AND_ASSIGN(
+            CoreOptions core_options,
+            CoreOptions::FromMap({{Options::INDEX_FILE_IN_DATA_FILE_DIR, 
"true"},
+                                  {Options::DATA_FILE_EXTERNAL_PATHS, 
"FILE:///tmp/"}}));
+        ASSERT_OK_AND_ASSIGN(std::unique_ptr<IndexFileHandler> 
index_file_handler,
+                             CreateIndexFileHandler(table_path, core_options));
+        auto index_file_meta = std::make_shared<IndexFileMeta>(
+            /*index_type=*/"DELETION_VECTOR", /*file_name=*/"deletion-file", 
/*file_size=*/1000,
+            /*row_count=*/100, /*dv_ranges=*/std::nullopt,
+            /*external_path=*/"FILE:///tmp/f1=10/bucket-1/deletion-file");
+        auto partition = BinaryRowGenerator::GenerateRow({10}, 
memory_pool_.get());
+        ASSERT_OK_AND_ASSIGN(std::string file_path, 
index_file_handler->FilePath(
+                                                        partition, 
/*bucket=*/1, index_file_meta));
+        ASSERT_EQ(file_path, "FILE:///tmp/f1=10/bucket-1/deletion-file");
+    }
+}
+TEST_F(IndexFileHandlerTest, TestScan) {
+    std::string table_path = paimon::test::GetDataDir() +
+                             
"/orc/pk_table_with_dv_cardinality.db/pk_table_with_dv_cardinality/";
+
+    ASSERT_OK_AND_ASSIGN(CoreOptions core_options,
+                         CoreOptions::FromMap({{Options::MANIFEST_FORMAT, 
"orc"}}));
+    ASSERT_OK_AND_ASSIGN(std::unique_ptr<IndexFileHandler> index_file_handler,
+                         CreateIndexFileHandler(table_path, core_options));
+
+    SnapshotManager snapshot_manager(core_options.GetFileSystem(), table_path);
+    ASSERT_OK_AND_ASSIGN(Snapshot snapshot, 
snapshot_manager.LoadSnapshot(/*snapshot_id=*/4));
+
+    auto partition = BinaryRowGenerator::GenerateRow({10}, memory_pool_.get());
+    std::unordered_set<BinaryRow> partitions = {partition};
+    ASSERT_OK_AND_ASSIGN(
+        auto index_file_metas,
+        index_file_handler->Scan(
+            snapshot, 
std::string(DeletionVectorsIndexFile::DELETION_VECTORS_INDEX), partitions));
+    ASSERT_EQ(2, index_file_metas.size());
+
+    // check index metas equal
+    LinkedHashMap<std::string, DeletionVectorMeta> dv_meta_p10_b0;
+    dv_meta_p10_b0.insert_or_assign(
+        "data-0d0f29cc-63c6-4fab-a594-71bd7d06fcde-0.orc",
+        DeletionVectorMeta("data-0d0f29cc-63c6-4fab-a594-71bd7d06fcde-0.orc", 
/*offset=*/1,
+                           /*length=*/22, /*cardinality=*/1));
+    std::vector<std::shared_ptr<IndexFileMeta>> index_meta_p10_b0 = {
+        std::make_shared<IndexFileMeta>(
+            std::string(DeletionVectorsIndexFile::DELETION_VECTORS_INDEX),
+            "index-86356766-3238-46e6-990b-656cd7409eaa-0",
+            /*file_size=*/31, /*row_count=*/1, dv_meta_p10_b0, 
/*external_path=*/std::nullopt)};
+
+    LinkedHashMap<std::string, DeletionVectorMeta> dv_meta_p10_b1;
+    dv_meta_p10_b1.insert_or_assign(
+        "data-2ffe7ae9-2cf7-41e9-944b-2065585cde31-0.orc",
+        DeletionVectorMeta("data-2ffe7ae9-2cf7-41e9-944b-2065585cde31-0.orc", 
/*offset=*/1,
+                           /*length=*/24, /*cardinality=*/2));
+    std::vector<std::shared_ptr<IndexFileMeta>> index_meta_p10_b1 = {
+        std::make_shared<IndexFileMeta>(
+            std::string(DeletionVectorsIndexFile::DELETION_VECTORS_INDEX),
+            "index-86356766-3238-46e6-990b-656cd7409eaa-1",
+            /*file_size=*/33, /*row_count=*/1, dv_meta_p10_b1, 
/*external_path=*/std::nullopt)};
+    ASSERT_TRUE(
+        ObjectUtils::Equal(index_file_metas[std::make_pair(partition, 0)], 
index_meta_p10_b0));
+    ASSERT_TRUE(
+        ObjectUtils::Equal(index_file_metas[std::make_pair(partition, 1)], 
index_meta_p10_b1));
+
+    // test FilePath
+    ASSERT_OK_AND_ASSIGN(auto index_file_path, 
index_file_handler->FilePath(partition, /*bucket=*/0,
+                                                                            
index_meta_p10_b0[0]));
+    ASSERT_EQ(index_file_path,
+              PathUtil::JoinPath(table_path, "/index/" + 
index_meta_p10_b0[0]->FileName()));
+}
+
+TEST_F(IndexFileHandlerTest, Test09VersionScan) {
+    std::string table_path = paimon::test::GetDataDir() + 
"/orc/pk_09.db/pk_09/";
+    ASSERT_OK_AND_ASSIGN(CoreOptions core_options,
+                         CoreOptions::FromMap({{Options::MANIFEST_FORMAT, 
"orc"}}));
+    ASSERT_OK_AND_ASSIGN(std::unique_ptr<IndexFileHandler> index_file_handler,
+                         CreateIndexFileHandler(table_path, core_options));
+
+    SnapshotManager snapshot_manager(core_options.GetFileSystem(), table_path);
+    ASSERT_OK_AND_ASSIGN(Snapshot snapshot, 
snapshot_manager.LoadSnapshot(/*snapshot_id=*/6));
+
+    auto partition = BinaryRowGenerator::GenerateRow({10}, memory_pool_.get());
+    std::unordered_set<BinaryRow> partitions = {partition};
+    ASSERT_OK_AND_ASSIGN(
+        auto index_file_metas,
+        index_file_handler->Scan(
+            snapshot, 
std::string(DeletionVectorsIndexFile::DELETION_VECTORS_INDEX), partitions));
+
+    ASSERT_EQ(2, index_file_metas.size());
+
+    // check index metas equal
+    LinkedHashMap<std::string, DeletionVectorMeta> dv_meta_p10_b0;
+    dv_meta_p10_b0.insert_or_assign(
+        "data-1c7a85f1-55bd-424f-b503-34a33be0fb96-0.orc",
+        DeletionVectorMeta("data-1c7a85f1-55bd-424f-b503-34a33be0fb96-0.orc", 
/*offset=*/1,
+                           /*length=*/22, /*cardinality=*/std::nullopt));
+    dv_meta_p10_b0.insert_or_assign(
+        "data-980e82b4-2345-4976-bc1d-ea989fcdbffa-0.orc",
+        DeletionVectorMeta("data-980e82b4-2345-4976-bc1d-ea989fcdbffa-0.orc", 
/*offset=*/31,
+                           /*length=*/22, /*cardinality=*/std::nullopt));
+    std::vector<std::shared_ptr<IndexFileMeta>> index_meta_p10_b0 = {
+        std::make_shared<IndexFileMeta>(
+            std::string(DeletionVectorsIndexFile::DELETION_VECTORS_INDEX),
+            "index-7badd250-6c0b-49e9-8e40-2449ae9a2539-0",
+            /*file_size=*/61, /*row_count=*/2, dv_meta_p10_b0, 
/*external_path=*/std::nullopt)};
+
+    LinkedHashMap<std::string, DeletionVectorMeta> dv_meta_p10_b1;
+    dv_meta_p10_b1.insert_or_assign(
+        "data-6871b960-edd9-40fc-9859-aaca9ea205cf-0.orc",
+        DeletionVectorMeta("data-6871b960-edd9-40fc-9859-aaca9ea205cf-0.orc", 
/*offset=*/1,
+                           /*length=*/22, /*cardinality=*/std::nullopt));
+    std::vector<std::shared_ptr<IndexFileMeta>> index_meta_p10_b1 = {
+        std::make_shared<IndexFileMeta>(
+            std::string(DeletionVectorsIndexFile::DELETION_VECTORS_INDEX),
+            "index-7badd250-6c0b-49e9-8e40-2449ae9a2539-1",
+            /*file_size=*/31, /*row_count=*/1, dv_meta_p10_b1, 
/*external_path=*/std::nullopt)};
+    ASSERT_TRUE(
+        ObjectUtils::Equal(index_file_metas[std::make_pair(partition, 0)], 
index_meta_p10_b0));
+    ASSERT_TRUE(
+        ObjectUtils::Equal(index_file_metas[std::make_pair(partition, 1)], 
index_meta_p10_b1));
+}
+
+TEST_F(IndexFileHandlerTest, TestScanWithNoIndexManifest) {
+    std::string table_path = paimon::test::GetDataDir() + 
"/orc/pk_09.db/pk_09/";
+    ASSERT_OK_AND_ASSIGN(CoreOptions core_options,
+                         CoreOptions::FromMap({{Options::MANIFEST_FORMAT, 
"orc"}}));
+    ASSERT_OK_AND_ASSIGN(std::unique_ptr<IndexFileHandler> index_file_handler,
+                         CreateIndexFileHandler(table_path, core_options));
+
+    SnapshotManager snapshot_manager(core_options.GetFileSystem(), table_path);
+    ASSERT_OK_AND_ASSIGN(Snapshot snapshot, 
snapshot_manager.LoadSnapshot(/*snapshot_id=*/6));
+
+    Snapshot no_index_manifest_snapshot(
+        std::optional<int32_t>(snapshot.Version()), snapshot.Id(), 
snapshot.SchemaId(),
+        snapshot.BaseManifestList(), snapshot.BaseManifestListSize(), 
snapshot.DeltaManifestList(),
+        snapshot.DeltaManifestListSize(), snapshot.ChangelogManifestList(),
+        snapshot.ChangelogManifestListSize(), /*index_manifest=*/std::nullopt,
+        snapshot.CommitUser(), snapshot.CommitIdentifier(), 
snapshot.GetCommitKind(),
+        snapshot.TimeMillis(), snapshot.LogOffsets(), 
snapshot.TotalRecordCount(),
+        snapshot.DeltaRecordCount(), snapshot.ChangelogRecordCount(), 
snapshot.Watermark(),
+        snapshot.Statistics(), snapshot.Properties(), snapshot.NextRowId());
+
+    auto partition = BinaryRowGenerator::GenerateRow({10}, memory_pool_.get());
+    std::unordered_set<BinaryRow> partitions = {partition};
+    ASSERT_OK_AND_ASSIGN(
+        auto index_file_metas,
+        index_file_handler->Scan(no_index_manifest_snapshot,
+                                 
std::string(DeletionVectorsIndexFile::DELETION_VECTORS_INDEX),
+                                 partitions));
+    ASSERT_TRUE(index_file_metas.empty());
+
+    ASSERT_OK_AND_ASSIGN(
+        auto index_entries,
+        index_file_handler->Scan(no_index_manifest_snapshot,
+                                 [](const IndexManifestEntry&) -> Result<bool> 
{ return true; }));
+    ASSERT_TRUE(index_entries.empty());
+}
+
+TEST_F(IndexFileHandlerTest, 
TestScanByPartitionBucketAndReadAllDeletionVectors) {
+    std::string table_path = paimon::test::GetDataDir() +
+                             
"/orc/pk_table_with_dv_cardinality.db/pk_table_with_dv_cardinality/";
+
+    ASSERT_OK_AND_ASSIGN(CoreOptions core_options,
+                         CoreOptions::FromMap({{Options::MANIFEST_FORMAT, 
"orc"}}));
+    ASSERT_OK_AND_ASSIGN(std::unique_ptr<IndexFileHandler> index_file_handler,
+                         CreateIndexFileHandler(table_path, core_options));
+
+    SnapshotManager snapshot_manager(core_options.GetFileSystem(), table_path);
+    ASSERT_OK_AND_ASSIGN(Snapshot snapshot, 
snapshot_manager.LoadSnapshot(/*snapshot_id=*/4));
+
+    auto partition = BinaryRowGenerator::GenerateRow({10}, memory_pool_.get());
+    ASSERT_OK_AND_ASSIGN(
+        auto index_file_metas,
+        index_file_handler->Scan(
+            snapshot, 
std::string(DeletionVectorsIndexFile::DELETION_VECTORS_INDEX), partition,
+            /*bucket=*/0));
+    ASSERT_EQ(index_file_metas.size(), 1);
+
+    ASSERT_OK_AND_ASSIGN(auto deletion_vectors, 
index_file_handler->ReadAllDeletionVectors(
+                                                    partition, /*bucket=*/0, 
index_file_metas));
+    ASSERT_EQ(deletion_vectors.size(), 1);
+    
ASSERT_TRUE(deletion_vectors.find("data-0d0f29cc-63c6-4fab-a594-71bd7d06fcde-0.orc")
 !=
+                deletion_vectors.end());
+    
ASSERT_EQ(deletion_vectors["data-0d0f29cc-63c6-4fab-a594-71bd7d06fcde-0.orc"]->GetCardinality(),
+              1);
+}
+
+}  // namespace paimon::test
diff --git a/src/paimon/core/index/index_file_meta.h 
b/src/paimon/core/index/index_file_meta.h
new file mode 100644
index 0000000..3d495a3
--- /dev/null
+++ b/src/paimon/core/index/index_file_meta.h
@@ -0,0 +1,183 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+#include <memory>
+#include <string>
+
+#include "arrow/api.h"
+#include "fmt/core.h"
+#include "fmt/format.h"
+#include "fmt/ranges.h"
+#include "paimon/common/utils/linked_hash_map.h"
+#include "paimon/core/index/deletion_vector_meta.h"
+#include "paimon/core/index/global_index_meta.h"
+
+namespace paimon {
+/// Metadata of index file.
+class IndexFileMeta {
+ public:
+    static constexpr int32_t NUM_FIELDS = 7;
+
+    IndexFileMeta(const std::string& index_type, const std::string& file_name, 
int64_t file_size,
+                  int64_t row_count,
+                  const std::optional<LinkedHashMap<std::string, 
DeletionVectorMeta>>& dv_ranges,
+                  const std::optional<std::string>& external_path)
+        : IndexFileMeta(index_type, file_name, file_size, row_count, 
dv_ranges, external_path,
+                        /*global_index_meta=*/std::nullopt) {}
+
+    IndexFileMeta(const std::string& index_type, const std::string& file_name, 
int64_t file_size,
+                  int64_t row_count,
+                  const std::optional<LinkedHashMap<std::string, 
DeletionVectorMeta>>& dv_ranges,
+                  const std::optional<std::string>& external_path,
+                  const std::optional<GlobalIndexMeta>& global_index_meta)
+        : index_type_(index_type),
+          file_name_(file_name),
+          file_size_(file_size),
+          row_count_(row_count),
+          dv_ranges_(dv_ranges),
+          external_path_(external_path),
+          global_index_meta_(global_index_meta) {}
+
+    const std::string& IndexType() const {
+        return index_type_;
+    }
+
+    const std::string& FileName() const {
+        return file_name_;
+    }
+
+    int64_t FileSize() const {
+        return file_size_;
+    }
+
+    int64_t RowCount() const {
+        return row_count_;
+    }
+
+    const std::optional<LinkedHashMap<std::string, DeletionVectorMeta>>& 
DvRanges() const {
+        return dv_ranges_;
+    }
+
+    const std::optional<std::string>& ExternalPath() const {
+        return external_path_;
+    }
+
+    const std::optional<GlobalIndexMeta>& GetGlobalIndexMeta() const {
+        return global_index_meta_;
+    }
+
+    bool operator==(const IndexFileMeta& other) const {
+        if (this == &other) {
+            return true;
+        }
+        return index_type_ == other.index_type_ && file_name_ == 
other.file_name_ &&
+               file_size_ == other.file_size_ && row_count_ == 
other.row_count_ &&
+               dv_ranges_ == other.dv_ranges_ && external_path_ == 
other.external_path_ &&
+               global_index_meta_ == other.global_index_meta_;
+    }
+
+    bool TEST_Equal(const IndexFileMeta& other) const {
+        if (this == &other) {
+            return true;
+        }
+
+        if ((dv_ranges_ && !other.dv_ranges_) || (!dv_ranges_ && 
other.dv_ranges_)) {
+            return false;
+        }
+        if (dv_ranges_ && other.dv_ranges_) {
+            if (dv_ranges_.value().size() != other.dv_ranges_.value().size()) {
+                return false;
+            }
+            for (auto iter1 = dv_ranges_.value().begin(), iter2 = 
other.dv_ranges_.value().begin();
+                 iter1 != dv_ranges_.value().end() && iter2 != 
other.dv_ranges_.value().end();
+                 ++iter1, ++iter2) {
+                if (!iter1->second.TEST_Equal(iter2->second)) {
+                    return false;
+                }
+            }
+        }
+
+        if ((external_path_ && !other.external_path_) ||
+            (!external_path_ && other.external_path_)) {
+            return false;
+        }
+
+        // ignore file_name & file_size
+        return index_type_ == other.index_type_ && row_count_ == 
other.row_count_ &&
+               global_index_meta_ == other.global_index_meta_;
+    }
+
+    std::string ToString() const {
+        std::string dv_str = dv_ranges_ == std::nullopt ? "null" : 
Format(dv_ranges_.value());
+        std::string external_path_str =
+            external_path_ == std::nullopt ? "null" : external_path_.value();
+        std::string global_index_meta_str =
+            global_index_meta_ == std::nullopt ? "null" : 
global_index_meta_.value().ToString();
+        return fmt::format(
+            "IndexManifestEntry{{indexType={}, fileName={}, fileSize={}, 
rowCount={}, "
+            "dvRanges={}, externalPath={}, globalIndexMeta={}}}",
+            index_type_, file_name_, file_size_, row_count_, dv_str, 
external_path_str,
+            global_index_meta_str);
+    }
+
+    static const std::shared_ptr<arrow::DataType>& DataType() {
+        static std::shared_ptr<arrow::DataType> schema = arrow::struct_({
+            arrow::field("_INDEX_TYPE", arrow::utf8(), false),
+            arrow::field("_FILE_NAME", arrow::utf8(), false),
+            arrow::field("_FILE_SIZE", arrow::int64(), false),
+            arrow::field("_ROW_COUNT", arrow::int64(), false),
+            arrow::field("_DELETIONS_VECTORS_RANGES",
+                         arrow::list(arrow::field("item", 
DeletionVectorMeta::DataType(), true)),
+                         true),
+            arrow::field("_EXTERNAL_PATH", arrow::utf8(), true),
+            arrow::field("_GLOBAL_INDEX", GlobalIndexMeta::DataType(), true),
+        });
+        return schema;
+    }
+
+ private:
+    static std::string Format(const LinkedHashMap<std::string, 
DeletionVectorMeta>& val) {
+        std::string result = "{";
+        for (const auto& iter : val) {
+            result.append(fmt::format("{}: {};", iter.first, 
iter.second.ToString()));
+        }
+        if (!val.empty()) {
+            result.pop_back();
+        }
+        result.append("}");
+        return result;
+    }
+
+    std::string index_type_;
+    std::string file_name_;
+    int64_t file_size_ = 0;
+    int64_t row_count_ = 0;
+
+    /// Metadata only used by `DeletionVectorsIndexFile`, use LinkedHashMap to 
ensure that the
+    /// order of DeletionVectorRanges and the written DeletionVectors is 
consistent.
+    std::optional<LinkedHashMap<std::string, DeletionVectorMeta>> dv_ranges_;
+
+    std::optional<std::string> external_path_;
+
+    std::optional<GlobalIndexMeta> global_index_meta_;
+};
+
+}  // namespace paimon
diff --git a/src/paimon/core/index/index_file_meta_serializer.cpp 
b/src/paimon/core/index/index_file_meta_serializer.cpp
new file mode 100644
index 0000000..94fe445
--- /dev/null
+++ b/src/paimon/core/index/index_file_meta_serializer.cpp
@@ -0,0 +1,112 @@
+/*
+ * 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.
+ */
+#include "paimon/core/index/index_file_meta_serializer.h"
+
+#include "paimon/common/data/binary_array_writer.h"
+#include "paimon/common/data/binary_row_writer.h"
+#include "paimon/core/index/index_file_meta_v2_deserializer.h"
+
+namespace paimon {
+Result<BinaryRow> IndexFileMetaSerializer::ToRow(const 
std::shared_ptr<IndexFileMeta>& meta) const {
+    BinaryRow row(IndexFileMeta::NUM_FIELDS);
+    BinaryRowWriter writer(&row, 32 * 1024, pool_.get());
+    WriteIndexFileMeta(/*start_pos=*/0, meta, &writer, pool_.get());
+    writer.Complete();
+    return row;
+}
+
+Result<std::shared_ptr<IndexFileMeta>> IndexFileMetaSerializer::FromRow(
+    const InternalRow& row) const {
+    auto file_type = row.GetString(0);
+    auto file_name = row.GetString(1);
+    auto file_size = row.GetLong(2);
+    auto row_count = row.GetLong(3);
+    std::optional<LinkedHashMap<std::string, DeletionVectorMeta>> dv_ranges;
+    if (!row.IsNullAt(4)) {
+        dv_ranges = 
IndexFileMetaV2Deserializer::RowArrayDataToDvRanges(row.GetArray(4).get());
+    }
+    std::optional<std::string> external_path;
+    if (!row.IsNullAt(5)) {
+        external_path = row.GetString(5).ToString();
+    }
+    std::optional<GlobalIndexMeta> global_index_meta;
+    if (!row.IsNullAt(6)) {
+        std::shared_ptr<InternalRow> global_index_meta_row =
+            row.GetRow(6, GlobalIndexMeta::NUM_FIELDS);
+        assert(global_index_meta_row);
+        PAIMON_ASSIGN_OR_RAISE(global_index_meta, 
GlobalIndexMeta::FromRow(*global_index_meta_row));
+    }
+
+    return std::make_shared<IndexFileMeta>(file_type.ToString(), 
file_name.ToString(), file_size,
+                                           row_count, dv_ranges, 
external_path, global_index_meta);
+}
+
+void IndexFileMetaSerializer::WriteIndexFileMeta(int32_t start_pos,
+                                                 const 
std::shared_ptr<IndexFileMeta>& meta,
+                                                 BinaryRowWriter* writer, 
MemoryPool* pool) {
+    writer->WriteString(start_pos + 0, 
BinaryString::FromString(meta->IndexType(), pool));
+    writer->WriteString(start_pos + 1, 
BinaryString::FromString(meta->FileName(), pool));
+    writer->WriteLong(start_pos + 2, meta->FileSize());
+    writer->WriteLong(start_pos + 3, meta->RowCount());
+    const auto& dv_ranges = meta->DvRanges();
+    if (dv_ranges == std::nullopt) {
+        writer->SetNullAt(start_pos + 4);
+    } else {
+        auto array = DvRangesToRowArrayData(dv_ranges.value(), pool);
+        writer->WriteArray(start_pos + 4, array);
+    }
+    auto external_path = meta->ExternalPath();
+    if (external_path == std::nullopt) {
+        writer->SetNullAt(start_pos + 5);
+    } else {
+        writer->WriteString(start_pos + 5, 
BinaryString::FromString(external_path.value(), pool));
+    }
+    auto global_index_meta = meta->GetGlobalIndexMeta();
+    if (global_index_meta == std::nullopt) {
+        writer->SetNullAt(start_pos + 6);
+    } else {
+        writer->WriteRow(start_pos + 6, global_index_meta.value().ToRow(pool));
+    }
+}
+
+BinaryArray IndexFileMetaSerializer::DvRangesToRowArrayData(
+    const LinkedHashMap<std::string, DeletionVectorMeta>& dv_metas, 
MemoryPool* pool) {
+    BinaryArray array;
+    BinaryArrayWriter array_writer(&array, dv_metas.size(), 
/*element_size=*/8, pool);
+    int32_t pos = 0;
+    for (const auto& dv_meta : dv_metas) {
+        const auto& meta = dv_meta.second;
+        BinaryRow dv_data(4);
+        BinaryRowWriter writer(&dv_data, 1024, pool);
+        writer.WriteString(/*pos=*/0, 
BinaryString::FromString(meta.GetDataFileName(), pool));
+        writer.WriteInt(/*pos=*/1, meta.GetOffset());
+        writer.WriteInt(/*pos=*/2, meta.GetLength());
+        if (meta.GetCardinality()) {
+            writer.WriteLong(/*pos=*/3, meta.GetCardinality().value());
+        } else {
+            writer.SetNullAt(3);
+        }
+        writer.Complete();
+        array_writer.WriteRow(pos++, dv_data);
+    }
+    array_writer.Complete();
+    return array;
+}
+
+}  // namespace paimon
diff --git a/src/paimon/core/index/index_file_meta_serializer.h 
b/src/paimon/core/index/index_file_meta_serializer.h
new file mode 100644
index 0000000..6cac11a
--- /dev/null
+++ b/src/paimon/core/index/index_file_meta_serializer.h
@@ -0,0 +1,62 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+#include <cassert>
+#include <cstdint>
+#include <list>
+#include <memory>
+#include <optional>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "paimon/common/data/binary_array.h"
+#include "paimon/common/data/binary_row.h"
+#include "paimon/common/data/binary_string.h"
+#include "paimon/common/data/internal_array.h"
+#include "paimon/common/data/internal_row.h"
+#include "paimon/common/utils/linked_hash_map.h"
+#include "paimon/core/index/deletion_vector_meta.h"
+#include "paimon/core/index/index_file_meta.h"
+#include "paimon/core/utils/object_serializer.h"
+#include "paimon/result.h"
+
+namespace paimon {
+class MemoryPool;
+class BinaryRowWriter;
+/// An `ObjectSerializer` for `IndexFileMeta`.
+class IndexFileMetaSerializer : public 
ObjectSerializer<std::shared_ptr<IndexFileMeta>> {
+ public:
+    explicit IndexFileMetaSerializer(const std::shared_ptr<MemoryPool>& pool)
+        : 
ObjectSerializer<std::shared_ptr<IndexFileMeta>>(IndexFileMeta::DataType(), 
pool) {}
+
+    Result<BinaryRow> ToRow(const std::shared_ptr<IndexFileMeta>& meta) const 
override;
+
+    Result<std::shared_ptr<IndexFileMeta>> FromRow(const InternalRow& row) 
const override;
+
+    static void WriteIndexFileMeta(int32_t start_pos, const 
std::shared_ptr<IndexFileMeta>& meta,
+                                   BinaryRowWriter* writer, MemoryPool* pool);
+
+    static BinaryArray DvRangesToRowArrayData(
+        const LinkedHashMap<std::string, DeletionVectorMeta>& dv_metas, 
MemoryPool* pool);
+};
+
+}  // namespace paimon
diff --git a/src/paimon/core/index/index_file_meta_serializer_test.cpp 
b/src/paimon/core/index/index_file_meta_serializer_test.cpp
new file mode 100644
index 0000000..4db2ce3
--- /dev/null
+++ b/src/paimon/core/index/index_file_meta_serializer_test.cpp
@@ -0,0 +1,146 @@
+/*
+ * 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.
+ */
+
+#include "paimon/core/index/index_file_meta_serializer.h"
+
+#include <cstdlib>
+
+#include "gtest/gtest.h"
+#include "paimon/common/io/memory_segment_output_stream.h"
+#include "paimon/common/memory/memory_segment_utils.h"
+#include "paimon/core/deletionvectors/deletion_vectors_index_file.h"
+#include "paimon/core/index/index_file_meta.h"
+#include "paimon/io/byte_array_input_stream.h"
+#include "paimon/io/data_input_stream.h"
+#include "paimon/memory/bytes.h"
+#include "paimon/memory/memory_pool.h"
+#include "paimon/status.h"
+#include "paimon/testing/utils/testharness.h"
+
+namespace paimon::test {
+
+class IndexFileMetaSerializerTest : public testing::Test {
+ public:
+    void SetUp() override {
+        memory_pool_ = GetDefaultPool();
+    }
+
+ private:
+    std::shared_ptr<IndexFileMeta> GetRandomDeletionVectorIndexFile() {
+        LinkedHashMap<std::string, DeletionVectorMeta> deletion_vectors_ranges;
+        deletion_vectors_ranges.insert_or_assign(
+            "my_file_name1",
+            DeletionVectorMeta("my_file_name1", std::rand(), std::rand(), 
std::nullopt));
+        deletion_vectors_ranges.insert_or_assign(
+            "my_file_name2",
+            DeletionVectorMeta("my_file_name2", std::rand(), std::rand(), 
std::nullopt));
+        return std::make_shared<IndexFileMeta>(
+            std::string(DeletionVectorsIndexFile::DELETION_VECTORS_INDEX),
+            "deletion_vectors_index_file_name" + std::to_string(std::rand()), 
std::rand(), rand(),
+            deletion_vectors_ranges, /*external_path=*/std::nullopt);
+    }
+
+    const int32_t TRIES = 100;
+
+    std::shared_ptr<MemoryPool> memory_pool_;
+};
+
+TEST_F(IndexFileMetaSerializerTest, TestEqual) {
+    auto index_meta1 = GetRandomDeletionVectorIndexFile();
+    index_meta1->file_size_ = 10;
+    auto index_meta2 = GetRandomDeletionVectorIndexFile();
+    index_meta2->file_size_ = 20;
+    ASSERT_EQ(*index_meta1, *index_meta1);
+    ASSERT_TRUE(index_meta1->TEST_Equal(*index_meta1));
+    ASSERT_FALSE(*index_meta1 == *index_meta2);
+    ASSERT_FALSE(index_meta1->TEST_Equal(*index_meta2));
+}
+
+TEST_F(IndexFileMetaSerializerTest, TestToFromRow) {
+    IndexFileMetaSerializer serializer(memory_pool_);
+    for (int32_t i = 0; i < TRIES; i++) {
+        auto expected = GetRandomDeletionVectorIndexFile();
+        ASSERT_OK_AND_ASSIGN(BinaryRow row, serializer.ToRow(expected));
+        ASSERT_OK_AND_ASSIGN(std::shared_ptr<IndexFileMeta> actual, 
serializer.FromRow(row));
+        ASSERT_EQ(expected->ToString(), actual->ToString());
+        ASSERT_EQ(*expected, *actual);
+        ASSERT_TRUE(expected->TEST_Equal(*actual));
+    }
+}
+
+TEST_F(IndexFileMetaSerializerTest, TestToFromRowWithNullDeletionVectorMetas) {
+    IndexFileMetaSerializer serializer(memory_pool_);
+    auto expected = std::make_shared<IndexFileMeta>(
+        std::string(DeletionVectorsIndexFile::DELETION_VECTORS_INDEX),
+        "deletion_vectors_index_file_0", /*file_size=*/10,
+        /*row_count=*/5,
+        /*dv_ranges=*/std::nullopt, /*external_path=*/std::nullopt);
+    ASSERT_OK_AND_ASSIGN(BinaryRow row, serializer.ToRow(expected));
+    ASSERT_OK_AND_ASSIGN(std::shared_ptr<IndexFileMeta> actual, 
serializer.FromRow(row));
+    ASSERT_EQ(expected->ToString(), actual->ToString());
+    ASSERT_EQ(*expected, *actual);
+}
+
+TEST_F(IndexFileMetaSerializerTest, TestToFromRowWithGlobalIndex) {
+    auto bytes = std::make_shared<Bytes>("apple", memory_pool_.get());
+    IndexFileMetaSerializer serializer(memory_pool_);
+    GlobalIndexMeta global_index_meta(
+        /*row_range_start=*/10, /*row_range_end=*/50,
+        /*index_field_id=*/5, 
/*extra_field_ids=*/std::optional<std::vector<int32_t>>({0, 1}),
+        bytes);
+    {
+        auto expected =
+            std::make_shared<IndexFileMeta>("bitmap", "bitmap_index_file_0", 
/*file_size=*/10,
+                                            /*row_count=*/41, 
/*dv_ranges=*/std::nullopt,
+                                            /*external_path=*/std::nullopt, 
global_index_meta);
+        ASSERT_OK_AND_ASSIGN(BinaryRow row, serializer.ToRow(expected));
+        ASSERT_OK_AND_ASSIGN(std::shared_ptr<IndexFileMeta> actual, 
serializer.FromRow(row));
+        ASSERT_EQ(expected->ToString(), actual->ToString());
+        ASSERT_EQ(*expected, *actual);
+    }
+    {
+        // test external path
+        auto expected = std::make_shared<IndexFileMeta>(
+            "bitmap", "bitmap_index_file_0", /*file_size=*/10,
+            /*row_count=*/41, /*dv_ranges=*/std::nullopt,
+            /*external_path=*/"FILE:/tmp/external/bitmap_index_file_0", 
global_index_meta);
+        ASSERT_OK_AND_ASSIGN(BinaryRow row, serializer.ToRow(expected));
+        ASSERT_OK_AND_ASSIGN(std::shared_ptr<IndexFileMeta> actual, 
serializer.FromRow(row));
+        ASSERT_EQ(expected->ToString(), actual->ToString());
+        ASSERT_EQ(*expected, *actual);
+    }
+}
+
+TEST_F(IndexFileMetaSerializerTest, TestSerialize) {
+    IndexFileMetaSerializer serializer(memory_pool_);
+    auto expected = GetRandomDeletionVectorIndexFile();
+    for (int32_t i = 0; i < TRIES; i++) {
+        MemorySegmentOutputStream out(1024, memory_pool_);
+        ASSERT_OK(serializer.Serialize(expected, &out));
+        PAIMON_UNIQUE_PTR<Bytes> bytes = MemorySegmentUtils::CopyToBytes(
+            out.Segments(), 0, out.CurrentSize(), memory_pool_.get());
+        auto input_stream = 
std::make_shared<ByteArrayInputStream>(bytes->data(), bytes->size());
+        DataInputStream in(input_stream);
+        ASSERT_OK_AND_ASSIGN(std::shared_ptr<IndexFileMeta> actual, 
serializer.Deserialize(&in));
+        ASSERT_EQ(expected->ToString(), actual->ToString());
+        ASSERT_EQ(*expected, *actual);
+    }
+}
+
+}  // namespace paimon::test
diff --git a/src/paimon/core/index/index_file_meta_v1_deserializer.h 
b/src/paimon/core/index/index_file_meta_v1_deserializer.h
new file mode 100644
index 0000000..8eb4f74
--- /dev/null
+++ b/src/paimon/core/index/index_file_meta_v1_deserializer.h
@@ -0,0 +1,88 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+#include <memory>
+#include <string>
+#include <utility>
+
+#include "paimon/common/data/internal_array.h"
+#include "paimon/common/utils/linked_hash_map.h"
+#include "paimon/core/index/index_file_meta.h"
+#include "paimon/core/utils/object_serializer.h"
+
+namespace paimon {
+/// Serializer for `IndexFileMeta` with 0.9 version.
+class IndexFileMetaV1Deserializer : public 
ObjectSerializer<std::shared_ptr<IndexFileMeta>> {
+ public:
+    static const std::shared_ptr<arrow::DataType>& DataType() {
+        static std::shared_ptr<arrow::DataType> schema = arrow::struct_(
+            {arrow::field("_INDEX_TYPE", arrow::utf8(), false),
+             arrow::field("_FILE_NAME", arrow::utf8(), false),
+             arrow::field("_FILE_SIZE", arrow::int64(), false),
+             arrow::field("_ROW_COUNT", arrow::int64(), false),
+             arrow::field("_DELETIONS_VECTORS_RANGES",
+                          arrow::list(arrow::field(
+                              "item",
+                              arrow::struct_({arrow::field("f0", 
arrow::utf8(), false),
+                                              arrow::field("f1", 
arrow::int32(), false),
+                                              arrow::field("f2", 
arrow::int32(), false)}),
+                              true)),
+                          true)});
+        return schema;
+    }
+
+    explicit IndexFileMetaV1Deserializer(const std::shared_ptr<MemoryPool>& 
pool)
+        : ObjectSerializer<std::shared_ptr<IndexFileMeta>>(DataType(), pool) {}
+
+    Result<BinaryRow> ToRow(const std::shared_ptr<IndexFileMeta>& meta) const 
override {
+        assert(false);
+        return Status::Invalid("IndexFileMetaV1Deserializer to row is not 
valid");
+    }
+
+    Result<std::shared_ptr<IndexFileMeta>> FromRow(const InternalRow& row) 
const override {
+        auto file_type = row.GetString(0);
+        auto file_name = row.GetString(1);
+        auto file_size = row.GetLong(2);
+        auto row_count = row.GetLong(3);
+        std::optional<LinkedHashMap<std::string, DeletionVectorMeta>> 
dv_ranges;
+        if (!row.IsNullAt(4)) {
+            dv_ranges = RowArrayDataToDvRanges(row.GetArray(4).get());
+        }
+        return std::make_shared<IndexFileMeta>(file_type.ToString(), 
file_name.ToString(),
+                                               file_size, row_count, dv_ranges,
+                                               /*external_path=*/std::nullopt);
+    }
+
+ private:
+    static LinkedHashMap<std::string, DeletionVectorMeta> 
RowArrayDataToDvRanges(
+        const InternalArray* array_data) {
+        LinkedHashMap<std::string, DeletionVectorMeta> dv_metas;
+        for (int32_t i = 0; i < array_data->Size(); i++) {
+            auto row = array_data->GetRow(i, /*num_fields=*/3);
+            std::string file_name = row->GetString(0).ToString();
+            dv_metas.insert_or_assign(file_name, DeletionVectorMeta(file_name, 
row->GetInt(1),
+                                                                    
row->GetInt(2), std::nullopt));
+        }
+        return dv_metas;
+    }
+};
+
+}  // namespace paimon
diff --git a/src/paimon/core/index/index_file_meta_v2_deserializer.h 
b/src/paimon/core/index/index_file_meta_v2_deserializer.h
new file mode 100644
index 0000000..a5acd4e
--- /dev/null
+++ b/src/paimon/core/index/index_file_meta_v2_deserializer.h
@@ -0,0 +1,84 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+#include <memory>
+#include <string>
+
+#include "paimon/common/data/internal_array.h"
+#include "paimon/common/utils/linked_hash_map.h"
+#include "paimon/core/index/index_file_meta.h"
+#include "paimon/core/utils/object_serializer.h"
+
+namespace paimon {
+/// Serializer for `IndexFileMeta` with 1.2 version.
+class IndexFileMetaV2Deserializer : public 
ObjectSerializer<std::shared_ptr<IndexFileMeta>> {
+ public:
+    static const std::shared_ptr<arrow::DataType>& DataType() {
+        static std::shared_ptr<arrow::DataType> schema = arrow::struct_(
+            {arrow::field("_INDEX_TYPE", arrow::utf8(), false),
+             arrow::field("_FILE_NAME", arrow::utf8(), false),
+             arrow::field("_FILE_SIZE", arrow::int64(), false),
+             arrow::field("_ROW_COUNT", arrow::int64(), false),
+             arrow::field("_DELETIONS_VECTORS_RANGES",
+                          arrow::list(arrow::field("item", 
DeletionVectorMeta::DataType(), true)),
+                          true)});
+        return schema;
+    }
+
+    explicit IndexFileMetaV2Deserializer(const std::shared_ptr<MemoryPool>& 
pool)
+        : ObjectSerializer<std::shared_ptr<IndexFileMeta>>(DataType(), pool) {}
+
+    Result<BinaryRow> ToRow(const std::shared_ptr<IndexFileMeta>& meta) const 
override {
+        assert(false);
+        return Status::Invalid("IndexFileMetaV2Deserializer to row is not 
valid");
+    }
+
+    Result<std::shared_ptr<IndexFileMeta>> FromRow(const InternalRow& row) 
const override {
+        auto file_type = row.GetString(0);
+        auto file_name = row.GetString(1);
+        auto file_size = row.GetLong(2);
+        auto row_count = row.GetLong(3);
+        std::optional<LinkedHashMap<std::string, DeletionVectorMeta>> 
dv_ranges;
+        if (!row.IsNullAt(4)) {
+            dv_ranges = RowArrayDataToDvRanges(row.GetArray(4).get());
+        }
+        return std::make_shared<IndexFileMeta>(file_type.ToString(), 
file_name.ToString(),
+                                               file_size, row_count, dv_ranges,
+                                               /*external_path=*/std::nullopt);
+    }
+
+    static LinkedHashMap<std::string, DeletionVectorMeta> 
RowArrayDataToDvRanges(
+        const InternalArray* array_data) {
+        LinkedHashMap<std::string, DeletionVectorMeta> dv_metas;
+        for (int32_t i = 0; i < array_data->Size(); i++) {
+            auto row =
+                array_data->GetRow(i, 
/*num_fields=*/DeletionVectorMeta::DataType()->num_fields());
+            std::string file_name = row->GetString(0).ToString();
+            std::optional<int64_t> cardinality =
+                row->IsNullAt(3) ? std::nullopt : 
std::optional<int64_t>(row->GetLong(3));
+            dv_metas.insert_or_assign(file_name, DeletionVectorMeta(file_name, 
row->GetInt(1),
+                                                                    
row->GetInt(2), cardinality));
+        }
+        return dv_metas;
+    }
+};
+
+}  // namespace paimon
diff --git a/src/paimon/core/index/index_file_meta_v3_deserializer.h 
b/src/paimon/core/index/index_file_meta_v3_deserializer.h
new file mode 100644
index 0000000..005da56
--- /dev/null
+++ b/src/paimon/core/index/index_file_meta_v3_deserializer.h
@@ -0,0 +1,88 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+#include <cassert>
+#include <cstdint>
+#include <list>
+#include <memory>
+#include <optional>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "arrow/api.h"
+#include "paimon/common/data/internal_array.h"
+#include "paimon/common/data/internal_row.h"
+#include "paimon/common/utils/linked_hash_map.h"
+#include "paimon/core/index/deletion_vector_meta.h"
+#include "paimon/core/index/index_file_meta.h"
+#include "paimon/core/index/index_file_meta_v2_deserializer.h"
+#include "paimon/core/utils/object_serializer.h"
+#include "paimon/result.h"
+
+struct ArrowArray;
+
+namespace paimon {
+class MemoryPool;
+
+class IndexFileMetaV3Deserializer : public 
ObjectSerializer<std::shared_ptr<IndexFileMeta>> {
+ public:
+    static const std::shared_ptr<arrow::DataType>& DataType() {
+        static std::shared_ptr<arrow::DataType> schema = arrow::struct_({
+            arrow::field("_INDEX_TYPE", arrow::utf8(), false),
+            arrow::field("_FILE_NAME", arrow::utf8(), false),
+            arrow::field("_FILE_SIZE", arrow::int64(), false),
+            arrow::field("_ROW_COUNT", arrow::int64(), false),
+            arrow::field("_DELETIONS_VECTORS_RANGES",
+                         arrow::list(arrow::field("item", 
DeletionVectorMeta::DataType(), true)),
+                         true),
+            arrow::field("_EXTERNAL_PATH", arrow::utf8(), true),
+        });
+        return schema;
+    }
+
+    explicit IndexFileMetaV3Deserializer(const std::shared_ptr<MemoryPool>& 
pool)
+        : ObjectSerializer<std::shared_ptr<IndexFileMeta>>(DataType(), pool) {}
+
+    Result<BinaryRow> ToRow(const std::shared_ptr<IndexFileMeta>& meta) const 
override {
+        assert(false);
+        return Status::Invalid("IndexFileMetaV3Deserializer to row is not 
valid");
+    }
+
+    Result<std::shared_ptr<IndexFileMeta>> FromRow(const InternalRow& row) 
const override {
+        auto file_type = row.GetString(0);
+        auto file_name = row.GetString(1);
+        auto file_size = row.GetLong(2);
+        auto row_count = row.GetLong(3);
+        std::optional<LinkedHashMap<std::string, DeletionVectorMeta>> 
dv_ranges;
+        if (!row.IsNullAt(4)) {
+            dv_ranges = 
IndexFileMetaV2Deserializer::RowArrayDataToDvRanges(row.GetArray(4).get());
+        }
+        std::optional<std::string> external_path;
+        if (!row.IsNullAt(5)) {
+            external_path = row.GetString(5).ToString();
+        }
+        return std::make_shared<IndexFileMeta>(file_type.ToString(), 
file_name.ToString(),
+                                               file_size, row_count, 
dv_ranges, external_path);
+    }
+};
+
+}  // namespace paimon
diff --git a/src/paimon/core/index/index_in_data_file_dir_path_factory.h 
b/src/paimon/core/index/index_in_data_file_dir_path_factory.h
new file mode 100644
index 0000000..7339ddf
--- /dev/null
+++ b/src/paimon/core/index/index_in_data_file_dir_path_factory.h
@@ -0,0 +1,70 @@
+/*
+ * 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.
+ */
+
+#pragma once
+#include <atomic>
+#include <cstdint>
+#include <memory>
+#include <optional>
+#include <string>
+
+#include "paimon/common/utils/path_util.h"
+#include "paimon/core/index/index_file_meta.h"
+#include "paimon/core/index/index_path_factory.h"
+#include "paimon/core/io/data_file_path_factory.h"
+
+namespace paimon {
+/// Path factory to create an index path.
+class IndexInDataFileDirPathFactory : public IndexPathFactory {
+ public:
+    IndexInDataFileDirPathFactory(
+        const std::string& uuid, const std::shared_ptr<std::atomic<int32_t>>& 
index_file_count,
+        const std::shared_ptr<DataFilePathFactory>& data_file_path_factory)
+        : uuid_(uuid),
+          index_file_count_(index_file_count),
+          data_file_path_factory_(data_file_path_factory) {}
+
+    std::string NewPath() const override {
+        std::string name = IndexPathFactory::INDEX_PREFIX + uuid_ + "-" +
+                           std::to_string(index_file_count_->fetch_add(1));
+        return data_file_path_factory_->NewPathFromName(name);
+    }
+
+    std::string ToPath(const std::shared_ptr<IndexFileMeta>& file) const 
override {
+        if (file->ExternalPath() != std::nullopt) {
+            return file->ExternalPath().value();
+        }
+        return PathUtil::JoinPath(data_file_path_factory_->Parent(), 
file->FileName());
+    }
+
+    std::string ToPath(const std::string& file_name) const override {
+        return data_file_path_factory_->NewPathFromName(file_name);
+    }
+
+    bool IsExternalPath() const override {
+        return data_file_path_factory_->IsExternalPath();
+    }
+
+ private:
+    std::string uuid_;
+    std::shared_ptr<std::atomic<int32_t>> index_file_count_;
+    std::shared_ptr<DataFilePathFactory> data_file_path_factory_;
+};
+
+}  // namespace paimon
diff --git a/src/paimon/core/index/index_in_data_file_dir_path_factory_test.cpp 
b/src/paimon/core/index/index_in_data_file_dir_path_factory_test.cpp
new file mode 100644
index 0000000..fe49a46
--- /dev/null
+++ b/src/paimon/core/index/index_in_data_file_dir_path_factory_test.cpp
@@ -0,0 +1,84 @@
+/*
+ * 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.
+ */
+
+#include "paimon/core/index/index_in_data_file_dir_path_factory.h"
+
+#include <utility>
+#include <vector>
+
+#include "gtest/gtest.h"
+#include "paimon/common/fs/external_path_provider.h"
+#include "paimon/result.h"
+#include "paimon/status.h"
+#include "paimon/testing/utils/testharness.h"
+
+namespace paimon::test {
+
+TEST(IndexInDataFileDirPathFactoryTest, TestSimple) {
+    auto count = std::make_shared<std::atomic<int32_t>>();
+    count->store(0);
+
+    auto data_file_path_factory = std::make_shared<DataFilePathFactory>();
+    
ASSERT_OK(data_file_path_factory->Init(/*parent=*/"/tmp/p0=1/p1=0/bucket-0",
+                                           /*format_identifier=*/"txt",
+                                           /*data_file_prefix=*/"data-",
+                                           
/*external_path_provider=*/nullptr));
+    IndexInDataFileDirPathFactory factory(/*uuid=*/"uuid", count,
+                                          std::move(data_file_path_factory));
+
+    ASSERT_EQ(factory.NewPath(), "/tmp/p0=1/p1=0/bucket-0/index-uuid-0");
+    // test ToPath with IndexFileMeta
+    auto meta =
+        std::make_shared<IndexFileMeta>(/*index_type=*/"DELETION_VECTOR", 
"deletion_file",
+                                        /*file_size=*/500, /*row_count=*/1,
+                                        /*dv_ranges=*/std::nullopt, 
/*external_path=*/std::nullopt);
+    ASSERT_EQ(factory.ToPath(meta), "/tmp/p0=1/p1=0/bucket-0/deletion_file");
+
+    // test ToPath with file_name
+    ASSERT_EQ(factory.ToPath("bitmap.index"), 
"/tmp/p0=1/p1=0/bucket-0/bitmap.index");
+    // test external path
+    ASSERT_FALSE(factory.IsExternalPath());
+}
+
+TEST(IndexInDataFileDirPathFactoryTest, TestWithExternalPath) {
+    auto count = std::make_shared<std::atomic<int32_t>>();
+    count->store(1);
+
+    ASSERT_OK_AND_ASSIGN(
+        std::unique_ptr<ExternalPathProvider> external_path_provider,
+        ExternalPathProvider::Create({"/tmp/external_path/"}, 
"p0=1/p1=0/bucket-0"));
+
+    auto data_file_path_factory = std::make_shared<DataFilePathFactory>();
+    ASSERT_OK(
+        data_file_path_factory->Init(/*parent=*/"/tmp", 
/*format_identifier=*/"txt",
+                                     /*data_file_prefix=*/"data-",
+                                     
/*external_path_provider=*/std::move(external_path_provider)));
+    IndexInDataFileDirPathFactory factory(/*uuid=*/"uuid", count,
+                                          std::move(data_file_path_factory));
+    std::string external_path = 
"/tmp/external_path/p0=1/p1=0/bucket-0/index-uuid-1";
+    ASSERT_EQ(factory.NewPath(), external_path);
+    auto meta = std::make_shared<IndexFileMeta>(
+        /*index_type=*/"DELETION_VECTOR", "deletion_file",
+        /*file_size=*/500, /*row_count=*/1,
+        /*dv_ranges=*/std::nullopt, external_path);
+    ASSERT_EQ(factory.ToPath(meta), external_path);
+    ASSERT_TRUE(factory.IsExternalPath());
+}
+
+}  // namespace paimon::test
diff --git a/src/paimon/core/index/index_path_factory.h 
b/src/paimon/core/index/index_path_factory.h
new file mode 100644
index 0000000..3cf7063
--- /dev/null
+++ b/src/paimon/core/index/index_path_factory.h
@@ -0,0 +1,40 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+#include <memory>
+#include <string>
+
+#include "paimon/core/index/index_file_meta.h"
+
+namespace paimon {
+/// Path factory to create an index path.
+class IndexPathFactory {
+ public:
+    virtual ~IndexPathFactory() = default;
+
+    virtual std::string NewPath() const = 0;
+    virtual std::string ToPath(const std::string& file_name) const = 0;
+    virtual std::string ToPath(const std::shared_ptr<IndexFileMeta>& file) 
const = 0;
+    virtual bool IsExternalPath() const = 0;
+
+    static inline const char INDEX_PREFIX[] = "index-";
+};
+}  // namespace paimon

Reply via email to