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

lxy-9602 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 ded7cfb8 perf(read): reduce point-lookup metadata I/O (#285)
ded7cfb8 is described below

commit ded7cfb84005a6315f4778253411dc78f7fa4cb3
Author: wangyong9999 <[email protected]>
AuthorDate: Tue Sep 8 13:38:34 2026 +0800

    perf(read): reduce point-lookup metadata I/O (#285)
---
 src/paimon/core/utils/snapshot_manager.cpp         |   6 +-
 src/paimon/core/utils/snapshot_manager_test.cpp    |  28 +++++
 src/paimon/format/parquet/file_reader_wrapper.cpp  |  31 ++++-
 .../format/parquet/file_reader_wrapper_test.cpp    |  38 ++++++
 .../parquet/parquet_file_batch_reader_test.cpp     | 116 +++++++++++++++++++
 src/paimon/format/parquet/parquet_input_stream.h   | 127 +++++++++++++++++++++
 src/paimon/format/parquet/parquet_reader_builder.h |  15 +--
 7 files changed, 349 insertions(+), 12 deletions(-)

diff --git a/src/paimon/core/utils/snapshot_manager.cpp 
b/src/paimon/core/utils/snapshot_manager.cpp
index 3d38a695..9211d966 100644
--- a/src/paimon/core/utils/snapshot_manager.cpp
+++ b/src/paimon/core/utils/snapshot_manager.cpp
@@ -141,10 +141,6 @@ Result<std::optional<int64_t>> 
SnapshotManager::FindEarliest(
 Result<std::optional<int64_t>> SnapshotManager::FindLatest(
     const std::string& dir, const std::string& prefix,
     const std::function<std::string(int64_t)>& path_func) const {
-    PAIMON_ASSIGN_OR_RAISE(bool is_exist, fs_->Exists(dir));
-    if (!is_exist) {
-        return std::optional<int64_t>();
-    }
     std::optional<int64_t> snapshot_id = ReadHint(LATEST, dir);
     if (snapshot_id != std::nullopt && snapshot_id.value() > 0) {
         int64_t next_snapshot = snapshot_id.value() + 1;
@@ -155,6 +151,8 @@ Result<std::optional<int64_t>> SnapshotManager::FindLatest(
             return snapshot_id;
         }
     }
+    // A valid hint needs no parent-directory probe. The listing fallback 
checks
+    // directory existence itself, including tables without any snapshots yet.
     return FindByListFiles([](int64_t lhs, int64_t rhs) -> int64_t { return 
std::max(lhs, rhs); },
                            dir, prefix);
 }
diff --git a/src/paimon/core/utils/snapshot_manager_test.cpp 
b/src/paimon/core/utils/snapshot_manager_test.cpp
index a4794e0e..d1bc8309 100644
--- a/src/paimon/core/utils/snapshot_manager_test.cpp
+++ b/src/paimon/core/utils/snapshot_manager_test.cpp
@@ -20,6 +20,7 @@
 
 #include <filesystem>
 #include <limits>
+#include <vector>
 
 #include "gtest/gtest.h"
 #include "paimon/common/utils/path_util.h"
@@ -139,6 +140,33 @@ TEST(SnapshotManagerTest, TestPathNotExist) {
     ASSERT_EQ(snapshot, std::nullopt);
 }
 
+TEST(SnapshotManagerTest, LatestSnapshotWithStaleMissingOrInvalidHint) {
+    auto dir = UniqueTestDirectory::Create();
+    ASSERT_TRUE(dir);
+    auto fs = std::make_shared<LocalFileSystem>();
+    SnapshotManager mgr(fs, dir->Str());
+    ASSERT_OK(fs->Mkdirs(mgr.SnapshotDirectory()));
+    ASSERT_OK(fs->WriteFile(mgr.SnapshotPath(1), "{}", true));
+    ASSERT_OK(mgr.CommitLatestHint(1));
+
+    ASSERT_OK_AND_ASSIGN(std::optional<int64_t> latest, 
mgr.LatestSnapshotId());
+    ASSERT_EQ(latest, 1);
+
+    // A commit can publish its snapshot before updating the hint.
+    ASSERT_OK(fs->WriteFile(mgr.SnapshotPath(2), "{}", true));
+    ASSERT_OK_AND_ASSIGN(latest, mgr.LatestSnapshotId());
+    ASSERT_EQ(latest, 2);
+
+    const std::string hint_path = PathUtil::JoinPath(mgr.SnapshotDirectory(), 
"LATEST");
+    ASSERT_OK(fs->Delete(hint_path));
+    ASSERT_OK_AND_ASSIGN(latest, mgr.LatestSnapshotId());
+    ASSERT_EQ(latest, 2);
+
+    ASSERT_OK(fs->WriteFile(hint_path, "invalid", true));
+    ASSERT_OK_AND_ASSIGN(latest, mgr.LatestSnapshotId());
+    ASSERT_EQ(latest, 2);
+}
+
 TEST(SnapshotManagerTest, TestEarlierOrEqualTimeMillisExactMatch) {
     std::string test_data_path = paimon::test::GetDataDir() + 
"/orc/append_09.db/append_09";
     auto file_system = std::make_shared<LocalFileSystem>();
diff --git a/src/paimon/format/parquet/file_reader_wrapper.cpp 
b/src/paimon/format/parquet/file_reader_wrapper.cpp
index fb5b6574..f69b89d4 100644
--- a/src/paimon/format/parquet/file_reader_wrapper.cpp
+++ b/src/paimon/format/parquet/file_reader_wrapper.cpp
@@ -29,10 +29,12 @@
 #include "fmt/format.h"
 #include "paimon/common/utils/arrow/arrow_utils.h"
 #include "paimon/common/utils/math.h"
+#include "paimon/common/utils/scope_guard.h"
 #include "paimon/format/parquet/column_index_filter.h"
 #include "paimon/format/parquet/page_filtered_row_group_reader.h"
 #include "paimon/format/parquet/parquet_format_defs.h"
 #include "paimon/macros.h"
+#include "paimon/predicate/predicate_utils.h"
 #include "parquet/arrow/reader.h"
 #include "parquet/arrow/schema.h"
 #include "parquet/file_reader.h"
@@ -602,8 +604,35 @@ Result<RowRanges> 
FileReaderWrapper::CalculateFilteredRowRanges(
             return RowRanges::CreateSingle(row_count);
         }
 
+        auto page_index_reader = GetPageIndexReader();
+        if (!page_index_reader) {
+            return RowRanges::CreateSingle(row_count);
+        }
+        std::set<std::string> field_names;
+        PAIMON_RETURN_NOT_OK(PredicateUtils::GetAllNames(predicate, 
&field_names));
+        std::vector<int32_t> predicate_columns;
+        for (const auto& name : field_names) {
+            auto it = column_name_to_index.find(name);
+            if (it == column_name_to_index.end()) {
+                return Status::Invalid(
+                    fmt::format("column '{}' not found in 
column_name_to_index", name));
+            }
+            predicate_columns.push_back(it->second);
+        }
+        if (predicate_columns.empty()) {
+            return RowRanges::CreateSingle(row_count);
+        }
+
+        // Arrow otherwise reads the column-index envelope of every column, 
including
+        // potentially large min/max values in payload columns not used by the 
predicate.
+        const std::vector<int32_t> row_groups = {row_group_index};
+        ScopeGuard clear_hint([&]() { 
page_index_reader->WillNotNeed(row_groups); });
+        page_index_reader->WillNeed(row_groups, predicate_columns,
+                                    {/*column_index=*/true, 
/*offset_index=*/true});
+        // Keep this restricted reader separate: projected payload columns 
still need
+        // their offset indexes when the data reader is initialized later.
         return ColumnIndexFilter::CalculateRowRanges(predicate,
-                                                     
GetRowGroupPageIndexReader(row_group_index),
+                                                     
page_index_reader->RowGroup(row_group_index),
                                                      column_name_to_index, 
row_count);
     }
     
PAIMON_PARQUET_CATCH_AND_RETURN_STATUS("FileReaderWrapper::CalculateFilteredRowRanges")
diff --git a/src/paimon/format/parquet/file_reader_wrapper_test.cpp 
b/src/paimon/format/parquet/file_reader_wrapper_test.cpp
index 41dcde19..e2335107 100644
--- a/src/paimon/format/parquet/file_reader_wrapper_test.cpp
+++ b/src/paimon/format/parquet/file_reader_wrapper_test.cpp
@@ -46,9 +46,14 @@
 #include "paimon/fs/file_system.h"
 #include "paimon/fs/local/local_file_system.h"
 #include "paimon/memory/memory_pool.h"
+#include "paimon/predicate/literal.h"
+#include "paimon/predicate/predicate_builder.h"
 #include "paimon/record_batch.h"
 #include "paimon/testing/utils/testharness.h"
 #include "parquet/arrow/reader.h"
+#include "parquet/file_reader.h"
+#include "parquet/metadata.h"
+#include "parquet/page_index.h"
 #include "parquet/properties.h"
 
 namespace arrow {
@@ -273,6 +278,39 @@ TEST_F(FileReaderWrapperTest, NullFileReader) {
                         "file reader wrapper create failed. file reader is 
nullptr");
 }
 
+TEST_F(FileReaderWrapperTest, 
PredicateReadsOnlyItsPageIndexesAndKeepsPayloadReadable) {
+    std::string file_path = PathUtil::JoinPath(dir_->Str(), 
"predicate-index.parquet");
+    PrepareParquetFile(file_path, /*row_count=*/1000, 
/*enable_page_index=*/true);
+    ASSERT_OK_AND_ASSIGN(std::shared_ptr<InputStream> input, 
fs_->Open(file_path));
+    auto tracking_input = std::make_shared<ReadTrackingInputStream>(input);
+    ASSERT_OK_AND_ASSIGN(auto reader, 
PrepareReaderWrapperOnStream(tracking_input));
+    auto metadata = reader->GetFileReader()->parquet_reader()->metadata();
+    auto index_ranges =
+        
::parquet::PageIndexReader::DeterminePageIndexRangesInRowGroup(*metadata->RowGroup(0),
 {1});
+    ASSERT_TRUE(index_ranges.column_index.has_value());
+    ASSERT_TRUE(index_ranges.offset_index.has_value());
+    int64_t bytes_before = tracking_input->GetPositionalReadBytes();
+    auto predicate = PredicateBuilder::Equal(1, "col2", FieldType::INT, 
Literal(25));
+    ASSERT_OK_AND_ASSIGN(auto ranges, reader->CalculateFilteredRowRanges(
+                                          0, predicate, {{"col1", 0}, {"col2", 
1}, {"col3", 2}}));
+    ASSERT_EQ(10, ranges.RowCount());
+    ASSERT_EQ(index_ranges.column_index->length + 
index_ranges.offset_index->length,
+              tracking_input->GetPositionalReadBytes() - bytes_before);
+
+    // The predicate's read hint must not restrict subsequent payload 
offset-index reads.
+    ASSERT_OK(reader->PrepareForReading(
+        {TargetRowGroup(/*rg_index=*/0, /*is_partially_matched=*/true, 
ranges)}, {0, 1, 2}));
+    ASSERT_OK_AND_ASSIGN(auto batch, reader->Next());
+    ASSERT_TRUE(batch);
+    auto expected = PrepareArray(PrepareArrowSchema().second, 
/*record_batch_size=*/10,
+                                 /*offset=*/20);
+    auto expected_batch = arrow::RecordBatch::FromStructArray(expected);
+    ASSERT_TRUE(expected_batch.ok());
+    ASSERT_TRUE(batch->Equals(*expected_batch.ValueOrDie()));
+    ASSERT_OK_AND_ASSIGN(auto end, reader->Next());
+    ASSERT_FALSE(end);
+}
+
 TEST_F(FileReaderWrapperTest, Simple) {
     std::string file_path = PathUtil::JoinPath(dir_->Str(), "test.parquet");
     PrepareParquetFile(file_path, /*row_count=*/5500);
diff --git a/src/paimon/format/parquet/parquet_file_batch_reader_test.cpp 
b/src/paimon/format/parquet/parquet_file_batch_reader_test.cpp
index 3e4824e3..c52ceba9 100644
--- a/src/paimon/format/parquet/parquet_file_batch_reader_test.cpp
+++ b/src/paimon/format/parquet/parquet_file_batch_reader_test.cpp
@@ -343,6 +343,122 @@ TEST_F(ParquetFileBatchReaderTest, 
TestParquetMetadataCacheBypassesWhenGetUriFai
     ASSERT_EQ(0, cache->Size());
 }
 
+TEST_F(ParquetFileBatchReaderTest, TestPageIndexBytesSurviveReaderClose) {
+    WriteArray(file_path_, struct_array_, schema_, /*write_batch_size=*/1,
+               /*enable_dictionary=*/false, /*max_row_group_length=*/3,
+               /*max_page_size=*/1);
+    auto cache = 
std::make_shared<paimon::test::CountingRoutingCache>(CacheKind::DATA_FILE_FOOTER,
+                                                                      128 * 
1024 * 1024);
+    ASSERT_OK_AND_ASSIGN(std::shared_ptr<InputStream> raw_input, 
fs_->Open(file_path_));
+    ASSERT_OK_AND_ASSIGN(int64_t length, raw_input->Length());
+    auto raw = std::make_shared<ArrowInputStreamAdapter>(raw_input, length, 
pool_);
+    auto raw_reader = ::parquet::ParquetFileReader::Open(raw);
+    auto metadata = raw_reader->metadata();
+    int64_t expected_index_reads = 0;
+    std::weak_ptr<MemoryPool> cached_pool;
+    for (int32_t round = 0; round < 3; ++round) {
+        if (round == 2) {
+            cache->InvalidateAll();
+            ASSERT_TRUE(cached_pool.expired());
+        }
+        ASSERT_OK_AND_ASSIGN(std::shared_ptr<InputStream> input, 
fs_->Open(file_path_));
+        std::shared_ptr<MemoryPool> query_pool = GetMemoryPool();
+        auto stream = std::make_shared<ParquetInputStream>(input, length, 
pool_, query_pool, cache,
+                                                           file_path_);
+        stream->SetPageIndexRanges(*metadata);
+        for (int32_t rg = 0; rg < metadata->num_row_groups(); ++rg) {
+            auto ranges = 
::parquet::PageIndexReader::DeterminePageIndexRangesInRowGroup(
+                *metadata->RowGroup(rg), {});
+            for (const auto& range : {ranges.column_index, 
ranges.offset_index}) {
+                ASSERT_TRUE(range.has_value());
+                auto expected = raw->ReadAt(range->offset, range->length);
+                ASSERT_TRUE(expected.ok()) << expected.status().ToString();
+                auto actual = stream->ReadAt(range->offset, range->length);
+                ASSERT_TRUE(actual.ok()) << actual.status().ToString();
+                
ASSERT_TRUE(actual.ValueOrDie()->Equals(*expected.ValueOrDie()));
+                if (round != 1) {
+                    ++expected_index_reads;
+                }
+            }
+        }
+        ASSERT_EQ(expected_index_reads, cache->SupplierCallCount());
+        // Reader 2 serves all indexes from the shared cache without storage 
IO.
+        if (round == 1) {
+            ASSERT_EQ(0, stream->StorageReadBytes()->load());
+        } else {
+            ASSERT_GT(stream->StorageReadBytes()->load(), 0);
+        }
+        // Ordinary file bytes must still use storage, even on a cache hit 
round.
+        uint64_t bytes_before = stream->StorageReadBytes()->load();
+        auto magic = stream->ReadAt(0, 4);
+        ASSERT_TRUE(magic.ok()) << magic.status().ToString();
+        ASSERT_EQ("PAR1", magic.ValueOrDie()->ToString());
+        ASSERT_EQ(bytes_before + 4, stream->StorageReadBytes()->load());
+        ASSERT_EQ(expected_index_reads, cache->SupplierCallCount());
+        ASSERT_TRUE(stream->Close().ok());
+        if (round != 1) {
+            cached_pool = query_pool;
+        }
+        stream.reset();
+        query_pool.reset();
+        ASSERT_FALSE(cached_pool.expired());
+    }
+    cache->InvalidateAll();
+    ASSERT_TRUE(cached_pool.expired());
+}
+
+TEST_F(ParquetFileBatchReaderTest, 
TestCachedFooterKeepsAllocatorAliveUntilEviction) {
+    WriteArray(file_path_, struct_array_, schema_, /*write_batch_size=*/1,
+               /*enable_dictionary=*/false, /*max_row_group_length=*/3);
+    auto cache = 
std::make_shared<paimon::test::CountingRoutingCache>(CacheKind::DATA_FILE_FOOTER,
+                                                                      128 * 
1024 * 1024);
+    std::weak_ptr<MemoryPool> cached_pool;
+    {
+        std::shared_ptr<MemoryPool> query_pool = GetMemoryPool();
+        cached_pool = query_pool;
+        ParquetReaderBuilder builder({}, 10);
+        builder.WithMemoryPool(query_pool)->WithCache(cache);
+        ASSERT_OK_AND_ASSIGN(std::shared_ptr<InputStream> input, 
fs_->Open(file_path_));
+        ASSERT_OK_AND_ASSIGN(auto reader, builder.Build(input));
+    }
+    ASSERT_FALSE(cached_pool.expired());
+    cache->InvalidateAll();
+    ASSERT_TRUE(cached_pool.expired());
+}
+
+TEST_F(ParquetFileBatchReaderTest, TestPointReadReusesFooterAndPageIndexes) {
+    WriteArray(file_path_, struct_array_, schema_, /*write_batch_size=*/1,
+               /*enable_dictionary=*/false, /*max_row_group_length=*/3,
+               /*max_page_size=*/1);
+    auto cache = 
std::make_shared<paimon::test::CountingRoutingCache>(CacheKind::DATA_FILE_FOOTER,
+                                                                      128 * 
1024 * 1024);
+    auto projection = arrow::schema({schema_->GetFieldByName("f4"), 
schema_->GetFieldByName("f8")});
+    auto predicate = PredicateBuilder::Equal(0, "f4", FieldType::INT, 
Literal(300002));
+    int64_t cold_reads = 0;
+    std::shared_ptr<arrow::ChunkedArray> cold_result;
+    for (int32_t round = 0; round < 2; ++round) {
+        ASSERT_OK_AND_ASSIGN(std::shared_ptr<InputStream> input, 
fs_->Open(file_path_));
+        ParquetReaderBuilder builder({{PARQUET_READ_ENABLE_PAGE_INDEX_FILTER, 
"true"}}, 10);
+        builder.WithCache(cache);
+        ASSERT_OK_AND_ASSIGN(auto reader, builder.Build(input));
+        ArrowSchema c_schema;
+        ASSERT_TRUE(arrow::ExportSchema(*projection, &c_schema).ok());
+        ASSERT_OK(reader->SetReadSchema(&c_schema, predicate, std::nullopt));
+        ASSERT_OK_AND_ASSIGN(auto result,
+                             
paimon::test::ReadResultCollector::CollectResult(reader.get()));
+        ASSERT_EQ(1, result->length());
+        if (round == 0) {
+            cold_reads = cache->SupplierCallCount();
+            ASSERT_GT(cold_reads, 1);  // Footer plus actual page-index ranges.
+            cold_result = result;
+        } else {
+            ASSERT_TRUE(result->Equals(cold_result));
+            ASSERT_EQ(cold_reads, cache->SupplierCallCount());
+            ASSERT_GT(cache->GetCount(), cold_reads);
+        }
+    }
+}
+
 TEST_F(ParquetFileBatchReaderTest, 
TestReadBinaryWrittenFromBinaryAndLargeBinary) {
     auto check_binary_read_result = [&](const 
std::shared_ptr<arrow::DataType>& write_type,
                                         const std::string& file_name) {
diff --git a/src/paimon/format/parquet/parquet_input_stream.h 
b/src/paimon/format/parquet/parquet_input_stream.h
new file mode 100644
index 00000000..fbf45f24
--- /dev/null
+++ b/src/paimon/format/parquet/parquet_input_stream.h
@@ -0,0 +1,127 @@
+/*
+ * 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 <cstring>
+#include <limits>
+#include <map>
+#include <memory>
+#include <string>
+
+#include "paimon/cache/cache.h"
+#include "paimon/common/utils/arrow/arrow_input_stream_adapter.h"
+#include "paimon/common/utils/arrow/status_utils.h"
+#include "paimon/memory/memory_pool.h"
+#include "parquet/metadata.h"
+#include "parquet/page_index.h"
+
+namespace paimon::parquet {
+
+inline MemorySegment AllocateParquetCacheSegment(int32_t size,
+                                                 const 
std::shared_ptr<MemoryPool>& pool) {
+    struct PooledBytes {
+        PooledBytes(int32_t size, const std::shared_ptr<MemoryPool>& 
memory_pool)
+            : pool(memory_pool), bytes(size, memory_pool.get()) {}
+        // Destroy bytes before releasing their allocator.
+        std::shared_ptr<MemoryPool> pool;
+        Bytes bytes;
+    };
+    auto owner = std::make_shared<PooledBytes>(size, pool);
+    auto* bytes = &owner->bytes;
+    return MemorySegment::Wrap(std::shared_ptr<Bytes>(std::move(owner), 
bytes));
+}
+
+// Cache immutable page-index bytes, not Arrow readers: the latter borrow their
+// input stream, reader properties and decryptor from the current file reader.
+class ParquetInputStream : public ArrowInputStreamAdapter {
+ public:
+    ParquetInputStream(const std::shared_ptr<paimon::InputStream>& input, 
int64_t file_size,
+                       const std::shared_ptr<arrow::MemoryPool>& arrow_pool,
+                       const std::shared_ptr<MemoryPool>& pool, const 
std::shared_ptr<Cache>& cache,
+                       const std::string& file_uri)
+        : ArrowInputStreamAdapter(input, file_size, arrow_pool),
+          pool_(pool),
+          cache_(cache),
+          file_uri_(file_uri) {}
+
+    // Called before the stream is published to the file reader. Only index
+    // ranges described by its footer are eligible; data pages remain uncached.
+    void SetPageIndexRanges(const ::parquet::FileMetaData& metadata) {
+        for (int32_t rg = 0; rg < metadata.num_row_groups(); ++rg) {
+            auto ranges = 
::parquet::PageIndexReader::DeterminePageIndexRangesInRowGroup(
+                *metadata.RowGroup(rg), {});
+            for (const auto& range : {ranges.column_index, 
ranges.offset_index}) {
+                if (range.has_value()) {
+                    index_ranges_.emplace(range->offset, range->length);
+                }
+            }
+        }
+    }
+
+    using ArrowInputStreamAdapter::ReadAt;
+
+    arrow::Result<int64_t> ReadAt(int64_t position, int64_t nbytes, void* out) 
override {
+        if (!cache_ || file_uri_.empty() || nbytes <= 0 ||
+            nbytes > std::numeric_limits<int32_t>::max()) {
+            return ArrowInputStreamAdapter::ReadAt(position, nbytes, out);
+        }
+        auto range = index_ranges_.upper_bound(position);
+        if (range == index_ranges_.begin()) {
+            return ArrowInputStreamAdapter::ReadAt(position, nbytes, out);
+        }
+        --range;
+        if (position - range->first > range->second ||
+            nbytes > range->second - (position - range->first)) {
+            return ArrowInputStreamAdapter::ReadAt(position, nbytes, out);
+        }
+        auto key = CacheKey::ForKind(file_uri_, position, 
static_cast<int32_t>(nbytes),
+                                     CacheKind::DATA_FILE_FOOTER);
+        auto value = cache_->Get(
+            key,
+            [this, position,
+             nbytes](const std::shared_ptr<CacheKey>&) -> 
Result<std::shared_ptr<CacheValue>> {
+                MemorySegment segment =
+                    AllocateParquetCacheSegment(static_cast<int32_t>(nbytes), 
pool_);
+                PAIMON_ASSIGN_OR_RAISE_FROM_ARROW(
+                    int64_t size,
+                    ArrowInputStreamAdapter::ReadAt(position, nbytes, 
segment.MutableData()));
+                if (size != nbytes) {
+                    return Status::IOError("Short read of Parquet page index");
+                }
+                return std::make_shared<CacheValue>(segment, CacheCallback());
+            });
+        if (!value.ok()) {
+            return ToArrowStatus(value.status());
+        }
+        if (!value.value() || value.value()->GetSegment().Size() != nbytes) {
+            return arrow::Status::IOError("Invalid Parquet page-index cache 
value");
+        }
+        std::memcpy(out, value.value()->GetSegment().Data(), nbytes);
+        return nbytes;
+    }
+
+ private:
+    std::shared_ptr<MemoryPool> pool_;
+    std::shared_ptr<Cache> cache_;
+    std::string file_uri_;
+    std::map<int64_t, int64_t> index_ranges_;
+};
+
+}  // namespace paimon::parquet
diff --git a/src/paimon/format/parquet/parquet_reader_builder.h 
b/src/paimon/format/parquet/parquet_reader_builder.h
index e4d2ddb4..fad961ac 100644
--- a/src/paimon/format/parquet/parquet_reader_builder.h
+++ b/src/paimon/format/parquet/parquet_reader_builder.h
@@ -35,6 +35,7 @@
 #include "paimon/common/utils/arrow/mem_utils.h"
 #include "paimon/format/parquet/parquet_file_batch_reader.h"
 #include "paimon/format/parquet/parquet_format_defs.h"
+#include "paimon/format/parquet/parquet_input_stream.h"
 #include "paimon/format/read_hints.h"
 #include "paimon/format/reader_builder.h"
 #include "paimon/memory/memory_pool.h"
@@ -88,13 +89,14 @@ class ParquetReaderBuilder : public ReaderBuilder {
                     file_uri = std::move(file_uri_result).value();
                 }
             }
-            auto unique_input_stream =
-                std::make_unique<ArrowInputStreamAdapter>(path, file_length, 
arrow_pool_);
-            auto storage_read_bytes = unique_input_stream->StorageReadBytes();
-            std::shared_ptr<arrow::io::RandomAccessFile> input_stream(
-                std::move(unique_input_stream));
+            auto input_stream = std::make_shared<ParquetInputStream>(path, 
file_length, arrow_pool_,
+                                                                     pool_, 
cache_, file_uri);
+            auto storage_read_bytes = input_stream->StorageReadBytes();
             PAIMON_ASSIGN_OR_RAISE(std::shared_ptr<::parquet::FileMetaData> 
file_metadata,
                                    GetCachedParquetMetadata(input_stream, 
file_uri, arrow_pool_));
+            if (file_metadata) {
+                input_stream->SetPageIndexRanges(*file_metadata);
+            }
             return ParquetFileBatchReader::Create(
                 std::move(input_stream), options_, batch_size_, 
std::move(file_metadata),
                 std::move(storage_read_bytes), arrow_pool_, hints_);
@@ -123,8 +125,7 @@ class ParquetReaderBuilder : public ReaderBuilder {
         PAIMON_ASSIGN_OR_RAISE_FROM_ARROW(std::shared_ptr<arrow::Buffer> 
metadata_footer,
                                           output_stream->Finish());
 
-        MemorySegment segment =
-            MemorySegment::AllocateHeapMemory(metadata_footer->size(), 
pool_.get());
+        MemorySegment segment = 
AllocateParquetCacheSegment(metadata_footer->size(), pool_);
         std::memcpy(segment.MutableData(), metadata_footer->data(), 
metadata_footer->size());
         return segment;
     }

Reply via email to