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

SteNicholas 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 ec854151 chore(build): reformat arrow.diff patch sections (#217)
ec854151 is described below

commit ec8541515c0be97056271ac8cc272b6588dd94ea
Author: Yonghao Fang <[email protected]>
AuthorDate: Wed Aug 19 22:10:55 2026 +0800

    chore(build): reformat arrow.diff patch sections (#217)
---
 cmake_modules/arrow.diff | 1524 +++++++++++++++++++++++-----------------------
 1 file changed, 766 insertions(+), 758 deletions(-)

diff --git a/cmake_modules/arrow.diff b/cmake_modules/arrow.diff
index f86f36e8..ce63af35 100644
--- a/cmake_modules/arrow.diff
+++ b/cmake_modules/arrow.diff
@@ -1,22 +1,197 @@
-diff --git a/cpp/src/parquet/arrow/schema.cc b/cpp/src/parquet/arrow/schema.cc
-index ec3890a41f..943f69bb6c 100644
---- a/cpp/src/parquet/arrow/schema.cc
-+++ b/cpp/src/parquet/arrow/schema.cc
-@@ -178,7 +178,7 @@ static Status GetTimestampMetadata(const 
::arrow::TimestampType& type,
+diff --git a/cpp/cmake_modules/BuildUtils.cmake 
b/cpp/cmake_modules/BuildUtils.cmake
+index e7523add27..e079a1ad41 100644
+--- a/cpp/cmake_modules/BuildUtils.cmake
++++ b/cpp/cmake_modules/BuildUtils.cmake
+@@ -112,7 +112,7 @@ function(arrow_create_merged_static_lib output_target)
+     execute_process(COMMAND ${LIBTOOL_MACOS} -V
+                     OUTPUT_VARIABLE LIBTOOL_V_OUTPUT
+                     OUTPUT_STRIP_TRAILING_WHITESPACE)
+-    if(NOT "${LIBTOOL_V_OUTPUT}" MATCHES ".*cctools-([0-9.]+).*")
++    if(NOT "${LIBTOOL_V_OUTPUT}" MATCHES ".*cctools(_ld)?-([0-9.]+).*")
+       message(FATAL_ERROR "libtool found appears to be the incompatible GNU 
libtool: ${LIBTOOL_MACOS}"
+       )
+     endif()
+diff --git a/cpp/cmake_modules/ThirdpartyToolchain.cmake 
b/cpp/cmake_modules/ThirdpartyToolchain.cmake
+index 8cb3ec83f5..0765df8fa8 100644
+--- a/cpp/cmake_modules/ThirdpartyToolchain.cmake
++++ b/cpp/cmake_modules/ThirdpartyToolchain.cmake
+@@ -983,6 +983,11 @@ if(CMAKE_TOOLCHAIN_FILE)
+   list(APPEND EP_COMMON_CMAKE_ARGS 
-DCMAKE_TOOLCHAIN_FILE=${CMAKE_TOOLCHAIN_FILE})
+ endif()
 
-   // The user is explicitly asking for Impala int96 encoding, there is no
-   // logical type.
--  if (arrow_properties.support_deprecated_int96_timestamps()) {
-+  if (arrow_properties.support_deprecated_int96_timestamps() && target_unit 
== ::arrow::TimeUnit::NANO) {
-     *physical_type = ParquetType::INT96;
-     return Status::OK();
-   }
++# Compatibility with bundled dependencies that require old CMake versions.
++if(CMAKE_VERSION VERSION_GREATER_EQUAL "3.30")
++  list(APPEND EP_COMMON_CMAKE_ARGS -DCMAKE_POLICY_VERSION_MINIMUM=3.5)
++endif()
++
+ # and crosscompiling emulator (for try_run() )
+ if(CMAKE_CROSSCOMPILING_EMULATOR)
+   string(REPLACE ";" ${EP_LIST_SEPARATOR} EP_CMAKE_CROSSCOMPILING_EMULATOR
+@@ -1716,6 +1721,7 @@ macro(build_thrift)
+       -DWITH_JAVASCRIPT=OFF
+       -DWITH_LIBEVENT=OFF
+       -DWITH_NODEJS=OFF
++      -DWITH_OPENSSL=OFF
+       -DWITH_PYTHON=OFF
+       -DWITH_QT5=OFF
+       -DWITH_ZLIB=OFF)
+diff --git a/cpp/src/arrow/io/interfaces.h b/cpp/src/arrow/io/interfaces.h
+index b36c38c6d4..f974a33073 100644
+--- a/cpp/src/arrow/io/interfaces.h
++++ b/cpp/src/arrow/io/interfaces.h
+@@ -210,7 +210,7 @@ class ARROW_EXPORT InputStream : virtual public 
FileInterface, virtual public Re
+   /// \brief Advance or skip stream indicated number of bytes
+   /// \param[in] nbytes the number to move forward
+   /// \return Status
+-  Status Advance(int64_t nbytes);
++  virtual Status Advance(int64_t nbytes);
 
+   /// \brief Return zero-copy string_view to upcoming bytes.
+   ///
 diff --git a/cpp/src/parquet/arrow/reader.cc b/cpp/src/parquet/arrow/reader.cc
-index 285e2a5973..aa6f92f077 100644
+index 285e2a5973..db919d7ef8 100644
 --- a/cpp/src/parquet/arrow/reader.cc
 +++ b/cpp/src/parquet/arrow/reader.cc
-@@ -1013,25 +1013,32 @@ Status FileReaderImpl::GetRecordBatchReader(const 
std::vector<int>& row_groups,
+@@ -254,6 +254,11 @@ class FileReaderImpl : public FileReader {
+     return GetColumn(i, AllRowGroupsFactory(), out);
+   }
+
++  ::arrow::Status GetColumn(
++      int i, const std::vector<int>& column_indices,
++      FileColumnIteratorFactory iterator_factory,
++      std::unique_ptr<ColumnReader>* out) override;
++
+   Status GetSchema(std::shared_ptr<::arrow::Schema>* out) override {
+     return FromParquetSchema(reader_->metadata()->schema(), 
reader_properties_,
+                              reader_->metadata()->key_value_metadata(), out);
+@@ -493,10 +498,40 @@ class LeafReader : public ColumnReaderImpl {
+
+   ::arrow::Status BuildArray(int64_t length_upper_bound,
+                              std::shared_ptr<::arrow::ChunkedArray>* out) 
final {
++    if (!out_) {
++      BEGIN_PARQUET_CATCH_EXCEPTIONS
++      RETURN_NOT_OK(
++          TransferColumnData(record_reader_.get(), field_, descr_, 
ctx_->pool, &out_));
++      END_PARQUET_CATCH_EXCEPTIONS
++    }
+     *out = out_;
+     return Status::OK();
+   }
+
++  std::vector<int> LeafColumnIndices() const final {
++    return {input_->column_index()};
++  }
++
++  ::arrow::Status ResetLeaf(int col_idx, int64_t reserve) final {
++    if (col_idx != input_->column_index()) return Status::OK();
++    BEGIN_PARQUET_CATCH_EXCEPTIONS
++    out_ = nullptr;
++    record_reader_->Reset();
++    record_reader_->Reserve(reserve);
++    return Status::OK();
++    END_PARQUET_CATCH_EXCEPTIONS
++  }
++
++  int64_t SkipRecords(int col_idx, int64_t num_records) final {
++    if (col_idx != input_->column_index() || num_records <= 0) return 0;
++    return record_reader_->SkipRecords(num_records);
++  }
++
++  int64_t ReadRecords(int col_idx, int64_t num_records) final {
++    if (col_idx != input_->column_index() || num_records <= 0) return 0;
++    return record_reader_->ReadRecords(num_records);
++  }
++
+   const std::shared_ptr<Field> field() override { return field_; }
+
+  private:
+@@ -532,6 +567,22 @@ class ExtensionReader : public ColumnReaderImpl {
+     return storage_reader_->LoadBatch(number_of_records);
+   }
+
++  std::vector<int> LeafColumnIndices() const final {
++    return storage_reader_->LeafColumnIndices();
++  }
++
++  ::arrow::Status ResetLeaf(int col_idx, int64_t reserve) final {
++    return storage_reader_->ResetLeaf(col_idx, reserve);
++  }
++
++  int64_t SkipRecords(int col_idx, int64_t num_records) final {
++    return storage_reader_->SkipRecords(col_idx, num_records);
++  }
++
++  int64_t ReadRecords(int col_idx, int64_t num_records) final {
++    return storage_reader_->ReadRecords(col_idx, num_records);
++  }
++
+   Status BuildArray(int64_t length_upper_bound,
+                     std::shared_ptr<ChunkedArray>* out) override {
+     std::shared_ptr<ChunkedArray> storage;
+@@ -576,6 +627,22 @@ class ListReader : public ColumnReaderImpl {
+     return item_reader_->LoadBatch(number_of_records);
+   }
+
++  std::vector<int> LeafColumnIndices() const final {
++    return item_reader_->LeafColumnIndices();
++  }
++
++  ::arrow::Status ResetLeaf(int col_idx, int64_t reserve) final {
++    return item_reader_->ResetLeaf(col_idx, reserve);
++  }
++
++  int64_t SkipRecords(int col_idx, int64_t num_records) final {
++    return item_reader_->SkipRecords(col_idx, num_records);
++  }
++
++  int64_t ReadRecords(int col_idx, int64_t num_records) final {
++    return item_reader_->ReadRecords(col_idx, num_records);
++  }
++
+   virtual ::arrow::Result<std::shared_ptr<ChunkedArray>> AssembleArray(
+       std::shared_ptr<ArrayData> data) {
+     if (field_->type()->id() == ::arrow::Type::MAP) {
+@@ -709,6 +776,39 @@ class PARQUET_NO_EXPORT StructReader : public 
ColumnReaderImpl {
+     }
+     return Status::OK();
+   }
++
++  std::vector<int> LeafColumnIndices() const override {
++    std::vector<int> indices;
++    for (const std::unique_ptr<ColumnReaderImpl>& reader : children_) {
++      std::vector<int> child_indices = reader->LeafColumnIndices();
++      indices.insert(indices.end(), child_indices.begin(), 
child_indices.end());
++    }
++    return indices;
++  }
++
++  ::arrow::Status ResetLeaf(int col_idx, int64_t reserve) override {
++    for (const std::unique_ptr<ColumnReaderImpl>& reader : children_) {
++      RETURN_NOT_OK(reader->ResetLeaf(col_idx, reserve));
++    }
++    return Status::OK();
++  }
++
++  int64_t SkipRecords(int col_idx, int64_t num_records) override {
++    int64_t skipped = 0;
++    for (const std::unique_ptr<ColumnReaderImpl>& reader : children_) {
++      skipped += reader->SkipRecords(col_idx, num_records);
++    }
++    return skipped;
++  }
++
++  int64_t ReadRecords(int col_idx, int64_t num_records) override {
++    int64_t read = 0;
++    for (const std::unique_ptr<ColumnReaderImpl>& reader : children_) {
++      read += reader->ReadRecords(col_idx, num_records);
++    }
++    return read;
++  }
++
+   Status BuildArray(int64_t length_upper_bound,
+                     std::shared_ptr<ChunkedArray>* out) override;
+   Status GetDefLevels(const int16_t** data, int64_t* length) override;
+@@ -1013,25 +1113,32 @@ Status FileReaderImpl::GetRecordBatchReader(const 
std::vector<int>& row_groups,
      return Status::OK();
    }
 
@@ -55,581 +230,49 @@ index 285e2a5973..aa6f92f077 100644
 
          RETURN_NOT_OK(::arrow::internal::OptionalParallelFor(
              reader_properties_.use_threads(), 
static_cast<int>(readers.size()),
-diff --git a/cpp/src/parquet/arrow/writer.cc b/cpp/src/parquet/arrow/writer.cc
-index 4fd7ef1b47..87326a54f1 100644
---- a/cpp/src/parquet/arrow/writer.cc
-+++ b/cpp/src/parquet/arrow/writer.cc
-@@ -314,6 +314,14 @@ class FileWriterImpl : public FileWriter {
-     return Status::OK();
-   }
+@@ -1224,6 +1331,23 @@ Status FileReaderImpl::GetColumn(int i, 
FileColumnIteratorFactory iterator_facto
+   return Status::OK();
+ }
 
-+  int64_t GetBufferedSize() override {
-+    if (row_group_writer_ == nullptr) {
-+      return 0;
-+    }
-+    return row_group_writer_->total_compressed_bytes() +
-+      row_group_writer_->total_compressed_bytes_written();
-+  }
++::arrow::Status FileReaderImpl::GetColumn(
++    int i, const std::vector<int>& column_indices,
++    FileColumnIteratorFactory iterator_factory,
++    std::unique_ptr<ColumnReader>* out) {
++  RETURN_NOT_OK(BoundsCheckColumn(i));
++  auto ctx = std::make_shared<ReaderContext>();
++  ctx->reader = reader_.get();
++  ctx->pool = pool_;
++  ctx->iterator_factory = iterator_factory;
++  ctx->filter_leaves = true;
++  ctx->included_leaves = VectorToSharedSet(column_indices);
++  std::unique_ptr<ColumnReaderImpl> result;
++  RETURN_NOT_OK(GetReader(manifest_.schema_fields[i], ctx, &result));
++  *out = std::move(result);
++  return Status::OK();
++}
 +
-   Status Close() override {
-     if (!closed_) {
-       // Make idempotent
-@@ -418,10 +426,13 @@ class FileWriterImpl : public FileWriter {
+ Status FileReaderImpl::ReadRowGroups(const std::vector<int>& row_groups,
+                                      const std::vector<int>& column_indices,
+                                      std::shared_ptr<Table>* out) {
+diff --git a/cpp/src/parquet/arrow/reader.h b/cpp/src/parquet/arrow/reader.h
+index 6e46ca43f7..e86ff0ef52 100644
+--- a/cpp/src/parquet/arrow/reader.h
++++ b/cpp/src/parquet/arrow/reader.h
+@@ -21,6 +21,7 @@
+ // N.B. we don't include async_generator.h as it's relatively heavy
+ #include <functional>
+ #include <memory>
++#include <utility>
+ #include <vector>
 
-     // Max number of rows allowed in a row group.
-     const int64_t max_row_group_length = 
this->properties().max_row_group_length();
-+    const int64_t max_row_group_size = 
this->properties().max_row_group_size();
+ #include "parquet/file_reader.h"
+@@ -48,9 +49,13 @@ namespace arrow {
 
-     // Initialize a new buffered row group writer if necessary.
-     if (row_group_writer_ == nullptr || !row_group_writer_->buffered() ||
--        row_group_writer_->num_rows() >= max_row_group_length) {
-+        row_group_writer_->num_rows() >= max_row_group_length ||
-+        (row_group_writer_->total_compressed_bytes_written() +
-+         row_group_writer_->total_compressed_bytes() >= max_row_group_size)) {
-       RETURN_NOT_OK(NewBufferedRowGroup());
-     }
-
-diff --git a/cpp/src/parquet/arrow/writer.h b/cpp/src/parquet/arrow/writer.h
-index 4a1a033a7b..0f13d05e44 100644
---- a/cpp/src/parquet/arrow/writer.h
-+++ b/cpp/src/parquet/arrow/writer.h
-@@ -138,6 +138,9 @@ class PARQUET_EXPORT FileWriter {
-   /// option in this case.
-   virtual ::arrow::Status WriteRecordBatch(const ::arrow::RecordBatch& batch) 
= 0;
-
-+  /// \brief Return the buffered size in bytes.
-+  virtual int64_t GetBufferedSize() = 0;
-+
-   /// \brief Write the footer and close the file.
-   virtual ::arrow::Status Close() = 0;
-   virtual ~FileWriter();
-diff --git a/cpp/src/parquet/properties.h b/cpp/src/parquet/properties.h
-index 4d3acb491e..3906ff3c59 100644
---- a/cpp/src/parquet/properties.h
-+++ b/cpp/src/parquet/properties.h
-@@ -139,6 +139,7 @@ static constexpr bool DEFAULT_IS_DICTIONARY_ENABLED = true;
- static constexpr int64_t DEFAULT_DICTIONARY_PAGE_SIZE_LIMIT = 
kDefaultDataPageSize;
- static constexpr int64_t DEFAULT_WRITE_BATCH_SIZE = 1024;
- static constexpr int64_t DEFAULT_MAX_ROW_GROUP_LENGTH = 1024 * 1024;
-+static constexpr int64_t DEFAULT_MAX_ROW_GROUP_SIZE = 128 * 1024 * 1024;
- static constexpr bool DEFAULT_ARE_STATISTICS_ENABLED = true;
- static constexpr int64_t DEFAULT_MAX_STATISTICS_SIZE = 4096;
- static constexpr Encoding::type DEFAULT_ENCODING = Encoding::UNKNOWN;
-@@ -232,6 +233,7 @@ class PARQUET_EXPORT WriterProperties {
-           dictionary_pagesize_limit_(DEFAULT_DICTIONARY_PAGE_SIZE_LIMIT),
-           write_batch_size_(DEFAULT_WRITE_BATCH_SIZE),
-           max_row_group_length_(DEFAULT_MAX_ROW_GROUP_LENGTH),
-+          max_row_group_size_(DEFAULT_MAX_ROW_GROUP_SIZE),
-           pagesize_(kDefaultDataPageSize),
-           version_(ParquetVersion::PARQUET_2_6),
-           data_page_version_(ParquetDataPageVersion::V1),
-@@ -244,6 +246,7 @@ class PARQUET_EXPORT WriterProperties {
-           dictionary_pagesize_limit_(properties.dictionary_pagesize_limit()),
-           write_batch_size_(properties.write_batch_size()),
-           max_row_group_length_(properties.max_row_group_length()),
-+          max_row_group_size_(properties.max_row_group_size()),
-           pagesize_(properties.data_pagesize()),
-           version_(properties.version()),
-           data_page_version_(properties.data_page_version()),
-@@ -321,6 +324,13 @@ class PARQUET_EXPORT WriterProperties {
-       return this;
-     }
-
-+    /// Specify the max bytes size to put in a single row group.
-+    /// Default 128 M.
-+    Builder* max_row_group_size(int64_t max_row_group_size) {
-+      max_row_group_size_ = max_row_group_size;
-+      return this;
-+    }
-+
-     /// Specify the data page size.
-     /// Default 1MB.
-     Builder* data_pagesize(int64_t pg_size) {
-@@ -664,7 +674,7 @@ class PARQUET_EXPORT WriterProperties {
-
-       return std::shared_ptr<WriterProperties>(new WriterProperties(
-           pool_, dictionary_pagesize_limit_, write_batch_size_, 
max_row_group_length_,
--          pagesize_, version_, created_by_, page_checksum_enabled_,
-+          max_row_group_size_, pagesize_, version_, created_by_, 
page_checksum_enabled_,
-           std::move(file_encryption_properties_), default_column_properties_,
-           column_properties, data_page_version_, store_decimal_as_integer_,
-           std::move(sorting_columns_)));
-@@ -675,6 +685,7 @@ class PARQUET_EXPORT WriterProperties {
-     int64_t dictionary_pagesize_limit_;
-     int64_t write_batch_size_;
-     int64_t max_row_group_length_;
-+    int64_t max_row_group_size_;
-     int64_t pagesize_;
-     ParquetVersion::type version_;
-     ParquetDataPageVersion data_page_version_;
-@@ -705,6 +716,8 @@ class PARQUET_EXPORT WriterProperties {
-
-   inline int64_t max_row_group_length() const { return max_row_group_length_; 
}
-
-+  inline int64_t max_row_group_size() const { return max_row_group_size_; }
-+
-   inline int64_t data_pagesize() const { return pagesize_; }
-
-   inline ParquetDataPageVersion data_page_version() const {
-@@ -810,7 +823,7 @@ class PARQUET_EXPORT WriterProperties {
-  private:
-   explicit WriterProperties(
-       MemoryPool* pool, int64_t dictionary_pagesize_limit, int64_t 
write_batch_size,
--      int64_t max_row_group_length, int64_t pagesize, ParquetVersion::type 
version,
-+      int64_t max_row_group_length, int64_t max_row_group_size, int64_t 
pagesize, ParquetVersion::type version,
-       const std::string& created_by, bool page_write_checksum_enabled,
-       std::shared_ptr<FileEncryptionProperties> file_encryption_properties,
-       const ColumnProperties& default_column_properties,
-@@ -821,6 +834,7 @@ class PARQUET_EXPORT WriterProperties {
-         dictionary_pagesize_limit_(dictionary_pagesize_limit),
-         write_batch_size_(write_batch_size),
-         max_row_group_length_(max_row_group_length),
-+        max_row_group_size_(max_row_group_size),
-         pagesize_(pagesize),
-         parquet_data_page_version_(data_page_version),
-         parquet_version_(version),
-@@ -836,6 +850,7 @@ class PARQUET_EXPORT WriterProperties {
-   int64_t dictionary_pagesize_limit_;
-   int64_t write_batch_size_;
-   int64_t max_row_group_length_;
-+  int64_t max_row_group_size_;
-   int64_t pagesize_;
-   ParquetDataPageVersion parquet_data_page_version_;
-   ParquetVersion::type parquet_version_;
-
---- a/cpp/src/parquet/file_reader.h
-+++ b/cpp/src/parquet/file_reader.h
-@@ -210,6 +210,17 @@
-   ::arrow::Future<> WhenBuffered(const std::vector<int>& row_groups,
-                                  const std::vector<int>& column_indices) 
const;
-
-+  /// Pre-buffer arbitrary byte ranges (e.g., page-level ranges from 
OffsetIndex).
-+  /// Unlike PreBuffer(), this does NOT set the column bitmap, so
-+  /// GetColumnPageReader will use CachedInputStream (page-level cache path).
-+  void PreBufferRanges(const std::vector<::arrow::io::ReadRange>& ranges,
-+                       const ::arrow::io::IOContext& ctx,
-+                       const ::arrow::io::CacheOptions& options);
-+
-+  /// Wait for arbitrary byte ranges to be pre-buffered.
-+  ::arrow::Future<> WhenBufferedRanges(
-+      const std::vector<::arrow::io::ReadRange>& ranges) const;
-+
-  private:
-   // Holds a pointer to an instance of Contents implementation
-   std::unique_ptr<Contents> contents_;
-
---- a/cpp/src/parquet/file_reader.cc
-+++ b/cpp/src/parquet/file_reader.cc
-@@ -207,6 +207,117 @@
-   return {col_start, col_length};
- }
-
-+// CachedInputStream: InputStream adapter that reads through ReadRangeCache 
with
-+// zero-cost skip for non-cached pages. Used for page-level caching where only
-+// specific pages are pre-buffered.
-+//
-+// Key behavior:
-+// - Read(): On cache hit, returns cached data. On cache miss, returns 
zero-filled
-+//   buffer (zero I/O). This makes InputStream::Advance() (which calls Read() 
and
-+//   discards) effectively free for skipped pages.
-+// - Peek(): Always falls back to source on cache miss, because PageReader 
uses
-+//   Peek() to read Thrift page headers (~30 bytes) which must have real data.
-+class CachedInputStream : public ::arrow::io::InputStream {
-+ public:
-+  CachedInputStream(
-+      std::shared_ptr<::arrow::io::internal::ReadRangeCache> cache,
-+      std::shared_ptr<ArrowInputFile> source,
-+      int64_t offset, int64_t length)
-+      : cache_(std::move(cache)),
-+        source_(std::move(source)),
-+        base_offset_(offset),
-+        length_(length) {}
-+
-+  ::arrow::Status Close() override {
-+    closed_ = true;
-+    return ::arrow::Status::OK();
-+  }
-+
-+  bool closed() const override { return closed_; }
-+
-+  ::arrow::Result<int64_t> Tell() const override { return position_; }
-+
-+  ::arrow::Result<std::string_view> Peek(int64_t nbytes) override {
-+    int64_t to_read = std::min(nbytes, length_ - position_);
-+    if (to_read <= 0) {
-+      return std::string_view();
-+    }
-+    ::arrow::io::ReadRange range{base_offset_ + position_, to_read};
-+    auto result = cache_->Read(range);
-+    if (result.ok()) {
-+      peek_buffer_ = *result;
-+    } else {
-+      // Peek is used for Thrift page headers (~30 bytes) — must read real 
data
-+      ARROW_ASSIGN_OR_RAISE(peek_buffer_,
-+                            source_->ReadAt(range.offset, range.length));
-+    }
-+    return std::string_view(
-+        reinterpret_cast<const char*>(peek_buffer_->data()),
-+        static_cast<size_t>(peek_buffer_->size()));
-+  }
-+
-+  ::arrow::Result<int64_t> Read(int64_t nbytes, void* out) override {
-+    int64_t to_read = std::min(nbytes, length_ - position_);
-+    if (to_read <= 0) return 0;
-+    ::arrow::io::ReadRange range{base_offset_ + position_, to_read};
-+    auto result = cache_->Read(range);
-+    if (result.ok()) {
-+      auto& buf = *result;
-+      memcpy(out, buf->data(), static_cast<size_t>(buf->size()));
-+      position_ += buf->size();
-+      return buf->size();
-+    }
-+    // Cache miss: fall back to real I/O from source
-+    ARROW_ASSIGN_OR_RAISE(auto buf, source_->ReadAt(range.offset, 
range.length));
-+    memcpy(out, buf->data(), static_cast<size_t>(buf->size()));
-+    position_ += buf->size();
-+    return buf->size();
-+  }
-+
-+  ::arrow::Result<std::shared_ptr<::arrow::Buffer>> Read(int64_t nbytes) 
override {
-+    int64_t to_read = std::min(nbytes, length_ - position_);
-+    if (to_read <= 0) {
-+      return std::make_shared<::arrow::Buffer>(nullptr, 0);
-+    }
-+    ::arrow::io::ReadRange range{base_offset_ + position_, to_read};
-+    auto result = cache_->Read(range);
-+    if (result.ok()) {
-+      position_ += (*result)->size();
-+      return *result;
-+    }
-+    // Cache miss: fall back to real I/O from source
-+    ARROW_ASSIGN_OR_RAISE(auto buf, source_->ReadAt(range.offset, 
range.length));
-+    position_ += buf->size();
-+    return std::shared_ptr<::arrow::Buffer>(std::move(buf));
-+  }
-+
-+  // Override Advance to avoid real I/O for skipped pages.
-+  // The default InputStream::Advance() calls Read() and discards the result,
-+  // which would trigger source_->ReadAt() on cache miss — defeating 
page-level
-+  // I/O skipping via data_page_filter. Since Advance() is only used to skip
-+  // over data that will not be consumed, we can safely just move the 
position.
-+  ::arrow::Status Advance(int64_t nbytes) override {
-+    if (nbytes <= 0) {
-+      return ::arrow::Status::OK();
-+    }
-+    int64_t remaining = length_ - position_;
-+    if (remaining <= 0) {
-+      return ::arrow::Status::OK();
-+    }
-+    position_ += std::min(nbytes, remaining);
-+    return ::arrow::Status::OK();
-+  }
-+
-+ private:
-+  std::shared_ptr<::arrow::io::internal::ReadRangeCache> cache_;
-+  std::shared_ptr<ArrowInputFile> source_;
-+  int64_t base_offset_;
-+  int64_t length_;
-+  int64_t position_ = 0;
-+  bool closed_ = false;
-+  std::shared_ptr<::arrow::Buffer> peek_buffer_;
-+};
-+
- // RowGroupReader::Contents implementation for the Parquet file specification
- class SerializedRowGroup : public RowGroupReader::Contents {
-  public:
-@@ -242,6 +343,11 @@
-       // segments.
-       PARQUET_ASSIGN_OR_THROW(auto buffer, cached_source_->Read(col_range));
-       stream = std::make_shared<::arrow::io::BufferReader>(buffer);
-+    } else if (cached_source_) {
-+      // Page-level caching: read through cache with fallback to source.
-+      // Advance() is zero-cost for skipped pages via data_page_filter.
-+      stream = std::make_shared<CachedInputStream>(
-+          cached_source_, source_, col_range.offset, col_range.length);
-     } else {
-       stream = properties_.GetStream(source_, col_range.offset, 
col_range.length);
-     }
-@@ -417,6 +523,26 @@
-     return cached_source_->WaitFor(ranges);
-   }
-
-+  void PreBufferRanges(const std::vector<::arrow::io::ReadRange>& ranges,
-+                       const ::arrow::io::IOContext& ctx,
-+                       const ::arrow::io::CacheOptions& options) {
-+    cached_source_ =
-+        std::make_shared<::arrow::io::internal::ReadRangeCache>(source_, ctx, 
options);
-+    // Do NOT set prebuffered_column_chunks_ bitmap — GetColumnPageReader will
-+    // use CachedInputStream path instead of full-chunk BufferReader path.
-+    prebuffered_column_chunks_.clear();
-+    PARQUET_THROW_NOT_OK(cached_source_->Cache(ranges));
-+  }
-+
-+  ::arrow::Future<> WhenBufferedRanges(
-+      const std::vector<::arrow::io::ReadRange>& ranges) const {
-+    if (!cached_source_) {
-+      return ::arrow::Status::Invalid(
-+          "Must call PreBufferRanges before WhenBufferedRanges");
-+    }
-+    return cached_source_->WaitFor(ranges);
-+  }
-+
-   // Metadata/footer parsing. Divided up to separate sync/async paths, and to 
use
-   // exceptions for error handling (with the async path converting to 
Future/Status).
-
-@@ -911,6 +1037,22 @@
-   return file->WhenBuffered(row_groups, column_indices);
- }
-
-+void ParquetFileReader::PreBufferRanges(
-+    const std::vector<::arrow::io::ReadRange>& ranges,
-+    const ::arrow::io::IOContext& ctx,
-+    const ::arrow::io::CacheOptions& options) {
-+  SerializedFile* file =
-+      ::arrow::internal::checked_cast<SerializedFile*>(contents_.get());
-+  file->PreBufferRanges(ranges, ctx, options);
-+}
-+
-+::arrow::Future<> ParquetFileReader::WhenBufferedRanges(
-+    const std::vector<::arrow::io::ReadRange>& ranges) const {
-+  SerializedFile* file =
-+      ::arrow::internal::checked_cast<SerializedFile*>(contents_.get());
-+  return file->WhenBufferedRanges(ranges);
-+}
-+
- // ----------------------------------------------------------------------
- // File metadata helpers
-
-diff --git a/cpp/cmake_modules/ThirdpartyToolchain.cmake 
b/cpp/cmake_modules/ThirdpartyToolchain.cmake
---- a/cpp/cmake_modules/ThirdpartyToolchain.cmake
-+++ b/cpp/cmake_modules/ThirdpartyToolchain.cmake
-@@ -981,6 +981,11 @@ if(CMAKE_TOOLCHAIN_FILE)
-   list(APPEND EP_COMMON_CMAKE_ARGS 
-DCMAKE_TOOLCHAIN_FILE=${CMAKE_TOOLCHAIN_FILE})
- endif()
-
-+# Compatibility with bundled dependencies that require old CMake versions.
-+if(CMAKE_VERSION VERSION_GREATER_EQUAL "3.30")
-+  list(APPEND EP_COMMON_CMAKE_ARGS -DCMAKE_POLICY_VERSION_MINIMUM=3.5)
-+endif()
-+
- # and crosscompiling emulator (for try_run() )
- if(CMAKE_CROSSCOMPILING_EMULATOR)
-   string(REPLACE ";" ${EP_LIST_SEPARATOR} EP_CMAKE_CROSSCOMPILING_EMULATOR
-@@ -1720,6 +1725,7 @@ macro(build_thrift)
-       -DWITH_JAVASCRIPT=OFF
-       -DWITH_LIBEVENT=OFF
-       -DWITH_NODEJS=OFF
-+      -DWITH_OPENSSL=OFF
-       -DWITH_PYTHON=OFF
-       -DWITH_QT5=OFF
-       -DWITH_ZLIB=OFF)
-diff --git a/cpp/cmake_modules/BuildUtils.cmake 
b/cpp/cmake_modules/BuildUtils.cmake
---- a/cpp/cmake_modules/BuildUtils.cmake
-+++ b/cpp/cmake_modules/BuildUtils.cmake
-@@ -112,7 +112,7 @@ function(arrow_create_merged_static_lib output_target)
-     execute_process(COMMAND ${LIBTOOL_MACOS} -V
-                     OUTPUT_VARIABLE LIBTOOL_V_OUTPUT
-                     OUTPUT_STRIP_TRAILING_WHITESPACE)
--    if(NOT "${LIBTOOL_V_OUTPUT}" MATCHES ".*cctools-([0-9.]+).*")
-+    if(NOT "${LIBTOOL_V_OUTPUT}" MATCHES ".*cctools(_ld)?-([0-9.]+).*")
-       message(FATAL_ERROR "libtool found appears to be the incompatible GNU 
libtool: ${LIBTOOL_MACOS}"
-       )
-     endif()
-
-diff --git a/cpp/src/arrow/io/interfaces.h b/cpp/src/arrow/io/interfaces.h
---- a/cpp/src/arrow/io/interfaces.h
-+++ b/cpp/src/arrow/io/interfaces.h
-@@ -210,7 +210,7 @@
-   /// \brief Advance or skip stream indicated number of bytes
-   /// \param[in] nbytes the number to move forward
-   /// \return Status
--  Status Advance(int64_t nbytes);
-+  virtual Status Advance(int64_t nbytes);
-
-   /// \brief Return zero-copy string_view to upcoming bytes.
-   ///
---- a/cpp/src/parquet/arrow/reader.cc
-+++ b/cpp/src/parquet/arrow/reader.cc
-@@ -254,6 +254,11 @@
-     return GetColumn(i, AllRowGroupsFactory(), out);
-   }
-
-+  ::arrow::Status GetColumn(
-+      int i, const std::vector<int>& column_indices,
-+      FileColumnIteratorFactory iterator_factory,
-+      std::unique_ptr<ColumnReader>* out) override;
-+
-   Status GetSchema(std::shared_ptr<::arrow::Schema>* out) override {
-     return FromParquetSchema(reader_->metadata()->schema(), 
reader_properties_,
-                              reader_->metadata()->key_value_metadata(), out);
-@@ -493,10 +498,40 @@
-
-   ::arrow::Status BuildArray(int64_t length_upper_bound,
-                              std::shared_ptr<::arrow::ChunkedArray>* out) 
final {
-+    if (!out_) {
-+      BEGIN_PARQUET_CATCH_EXCEPTIONS
-+      RETURN_NOT_OK(
-+          TransferColumnData(record_reader_.get(), field_, descr_, 
ctx_->pool, &out_));
-+      END_PARQUET_CATCH_EXCEPTIONS
-+    }
-     *out = out_;
-     return Status::OK();
-   }
-
-+  std::vector<int> LeafColumnIndices() const final {
-+    return {input_->column_index()};
-+  }
-+
-+  ::arrow::Status ResetLeaf(int col_idx, int64_t reserve) final {
-+    if (col_idx != input_->column_index()) return Status::OK();
-+    BEGIN_PARQUET_CATCH_EXCEPTIONS
-+    out_ = nullptr;
-+    record_reader_->Reset();
-+    record_reader_->Reserve(reserve);
-+    return Status::OK();
-+    END_PARQUET_CATCH_EXCEPTIONS
-+  }
-+
-+  int64_t SkipRecords(int col_idx, int64_t num_records) final {
-+    if (col_idx != input_->column_index() || num_records <= 0) return 0;
-+    return record_reader_->SkipRecords(num_records);
-+  }
-+
-+  int64_t ReadRecords(int col_idx, int64_t num_records) final {
-+    if (col_idx != input_->column_index() || num_records <= 0) return 0;
-+    return record_reader_->ReadRecords(num_records);
-+  }
-+
-   const std::shared_ptr<Field> field() override { return field_; }
-
-  private:
-@@ -532,6 +567,22 @@
-     return storage_reader_->LoadBatch(number_of_records);
-   }
-
-+  std::vector<int> LeafColumnIndices() const final {
-+    return storage_reader_->LeafColumnIndices();
-+  }
-+
-+  ::arrow::Status ResetLeaf(int col_idx, int64_t reserve) final {
-+    return storage_reader_->ResetLeaf(col_idx, reserve);
-+  }
-+
-+  int64_t SkipRecords(int col_idx, int64_t num_records) final {
-+    return storage_reader_->SkipRecords(col_idx, num_records);
-+  }
-+
-+  int64_t ReadRecords(int col_idx, int64_t num_records) final {
-+    return storage_reader_->ReadRecords(col_idx, num_records);
-+  }
-+
-   Status BuildArray(int64_t length_upper_bound,
-                     std::shared_ptr<ChunkedArray>* out) override {
-     std::shared_ptr<ChunkedArray> storage;
-@@ -576,6 +627,22 @@
-     return item_reader_->LoadBatch(number_of_records);
-   }
-
-+  std::vector<int> LeafColumnIndices() const final {
-+    return item_reader_->LeafColumnIndices();
-+  }
-+
-+  ::arrow::Status ResetLeaf(int col_idx, int64_t reserve) final {
-+    return item_reader_->ResetLeaf(col_idx, reserve);
-+  }
-+
-+  int64_t SkipRecords(int col_idx, int64_t num_records) final {
-+    return item_reader_->SkipRecords(col_idx, num_records);
-+  }
-+
-+  int64_t ReadRecords(int col_idx, int64_t num_records) final {
-+    return item_reader_->ReadRecords(col_idx, num_records);
-+  }
-+
-   virtual ::arrow::Result<std::shared_ptr<ChunkedArray>> AssembleArray(
-       std::shared_ptr<ArrayData> data) {
-     if (field_->type()->id() == ::arrow::Type::MAP) {
-@@ -709,6 +776,39 @@
-     }
-     return Status::OK();
-   }
-+
-+  std::vector<int> LeafColumnIndices() const override {
-+    std::vector<int> indices;
-+    for (const std::unique_ptr<ColumnReaderImpl>& reader : children_) {
-+      std::vector<int> child_indices = reader->LeafColumnIndices();
-+      indices.insert(indices.end(), child_indices.begin(), 
child_indices.end());
-+    }
-+    return indices;
-+  }
-+
-+  ::arrow::Status ResetLeaf(int col_idx, int64_t reserve) override {
-+    for (const std::unique_ptr<ColumnReaderImpl>& reader : children_) {
-+      RETURN_NOT_OK(reader->ResetLeaf(col_idx, reserve));
-+    }
-+    return Status::OK();
-+  }
-+
-+  int64_t SkipRecords(int col_idx, int64_t num_records) override {
-+    int64_t skipped = 0;
-+    for (const std::unique_ptr<ColumnReaderImpl>& reader : children_) {
-+      skipped += reader->SkipRecords(col_idx, num_records);
-+    }
-+    return skipped;
-+  }
-+
-+  int64_t ReadRecords(int col_idx, int64_t num_records) override {
-+    int64_t read = 0;
-+    for (const std::unique_ptr<ColumnReaderImpl>& reader : children_) {
-+      read += reader->ReadRecords(col_idx, num_records);
-+    }
-+    return read;
-+  }
-+
-   Status BuildArray(int64_t length_upper_bound,
-                     std::shared_ptr<ChunkedArray>* out) override;
-   Status GetDefLevels(const int16_t** data, int64_t* length) override;
-@@ -1228,6 +1328,23 @@
-   std::unique_ptr<ColumnReaderImpl> result;
-   RETURN_NOT_OK(GetReader(manifest_.schema_fields[i], ctx, &result));
-   *out = std::move(result);
-+  return Status::OK();
-+}
-+
-+::arrow::Status FileReaderImpl::GetColumn(
-+    int i, const std::vector<int>& column_indices,
-+    FileColumnIteratorFactory iterator_factory,
-+    std::unique_ptr<ColumnReader>* out) {
-+  RETURN_NOT_OK(BoundsCheckColumn(i));
-+  auto ctx = std::make_shared<ReaderContext>();
-+  ctx->reader = reader_.get();
-+  ctx->pool = pool_;
-+  ctx->iterator_factory = iterator_factory;
-+  ctx->filter_leaves = true;
-+  ctx->included_leaves = VectorToSharedSet(column_indices);
-+  std::unique_ptr<ColumnReaderImpl> result;
-+  RETURN_NOT_OK(GetReader(manifest_.schema_fields[i], ctx, &result));
-+  *out = std::move(result);
-   return Status::OK();
- }
-
---- a/cpp/src/parquet/arrow/reader.h
-+++ b/cpp/src/parquet/arrow/reader.h
-@@ -21,6 +21,7 @@
- // N.B. we don't include async_generator.h as it's relatively heavy
- #include <functional>
- #include <memory>
-+#include <utility>
- #include <vector>
-
- #include "parquet/file_reader.h"
-@@ -48,9 +49,13 @@
-
- class ColumnChunkReader;
- class ColumnReader;
-+class FileColumnIterator;
- struct SchemaManifest;
- class RowGroupReader;
+ class ColumnChunkReader;
+ class ColumnReader;
++class FileColumnIterator;
+ struct SchemaManifest;
+ class RowGroupReader;
 
 +using FileColumnIteratorFactory =
 +    std::function<FileColumnIterator*(int, ParquetFileReader*)>;
@@ -637,7 +280,7 @@ diff --git a/cpp/src/arrow/io/interfaces.h 
b/cpp/src/arrow/io/interfaces.h
  /// \brief Arrow read adapter class for deserializing Parquet files as Arrow 
row batches.
  ///
  /// This interfaces caters for different use cases and thus provides different
-@@ -136,6 +141,27 @@
+@@ -136,6 +141,27 @@ class PARQUET_EXPORT FileReader {
    // The indicated column index is relative to the schema
    virtual ::arrow::Status GetColumn(int i, std::unique_ptr<ColumnReader>* 
out) = 0;
 
@@ -665,7 +308,7 @@ diff --git a/cpp/src/arrow/io/interfaces.h 
b/cpp/src/arrow/io/interfaces.h
    /// \brief Return arrow schema for all the columns.
    virtual ::arrow::Status GetSchema(std::shared_ptr<::arrow::Schema>* out) = 
0;
 
-@@ -316,6 +342,43 @@
+@@ -316,6 +342,43 @@ class PARQUET_EXPORT ColumnReader {
    // the data available in the file.
    virtual ::arrow::Status NextBatch(int64_t batch_size,
                                      std::shared_ptr<::arrow::ChunkedArray>* 
out) = 0;
@@ -698,50 +341,269 @@ diff --git a/cpp/src/arrow/io/interfaces.h 
b/cpp/src/arrow/io/interfaces.h
 +  /// error; callers convert it to Status at the public boundary.
 +  virtual int64_t ReadRecords(int col_idx, int64_t num_records) { return 0; }
 +
-+  /// \brief Build the Arrow array from previously loaded data.
-+  /// For leaf readers, calls TransferColumnData if not already done.
-+  /// For nested readers, assembles the nested array from child arrays.
-+  virtual ::arrow::Status BuildArray(
-+      int64_t length_upper_bound,
-+      std::shared_ptr<::arrow::ChunkedArray>* out) {
-+    return ::arrow::Status::NotImplemented("BuildArray not implemented");
-+  }
- };
-
- /// \brief Experimental helper class for bindings (like Python) that struggle
---- a/cpp/src/parquet/arrow/reader_internal.h
-+++ b/cpp/src/parquet/arrow/reader_internal.h
-@@ -26,6 +26,7 @@
- #include <utility>
- #include <vector>
-
-+#include "parquet/arrow/reader.h"
- #include "parquet/arrow/schema.h"
- #include "parquet/column_reader.h"
- #include "parquet/file_reader.h"
-@@ -70,7 +71,10 @@
++  /// \brief Build the Arrow array from previously loaded data.
++  /// For leaf readers, calls TransferColumnData if not already done.
++  /// For nested readers, assembles the nested array from child arrays.
++  virtual ::arrow::Status BuildArray(
++      int64_t length_upper_bound,
++      std::shared_ptr<::arrow::ChunkedArray>* out) {
++    return ::arrow::Status::NotImplemented("BuildArray not implemented");
++  }
+ };
+
+ /// \brief Experimental helper class for bindings (like Python) that struggle
+diff --git a/cpp/src/parquet/arrow/reader_internal.h 
b/cpp/src/parquet/arrow/reader_internal.h
+index cf9dbb8657..9216f18289 100644
+--- a/cpp/src/parquet/arrow/reader_internal.h
++++ b/cpp/src/parquet/arrow/reader_internal.h
+@@ -26,6 +26,7 @@
+ #include <utility>
+ #include <vector>
+
++#include "parquet/arrow/reader.h"
+ #include "parquet/arrow/schema.h"
+ #include "parquet/column_reader.h"
+ #include "parquet/file_reader.h"
+@@ -70,7 +71,10 @@ class FileColumnIterator {
+
+   virtual ~FileColumnIterator() {}
+
+-  std::unique_ptr<::parquet::PageReader> NextChunk() {
++  /// \brief Fetch the PageReader for the next row group in this iterator's
++  /// range. Virtual so subclasses can decorate the returned PageReader, e.g.
++  /// to install a data_page_filter for I/O-level page skipping.
++  virtual std::unique_ptr<::parquet::PageReader> NextChunk() {
+     if (row_groups_.empty()) {
+       return nullptr;
+     }
+@@ -95,9 +99,6 @@ class FileColumnIterator {
+   std::deque<int> row_groups_;
+ };
+
+-using FileColumnIteratorFactory =
+-    std::function<FileColumnIterator*(int, ParquetFileReader*)>;
+-
+ Status TransferColumnData(::parquet::internal::RecordReader* reader,
+                           const std::shared_ptr<::arrow::Field>& value_field,
+                           const ColumnDescriptor* descr, ::arrow::MemoryPool* 
pool,
+diff --git a/cpp/src/parquet/arrow/schema.cc b/cpp/src/parquet/arrow/schema.cc
+index ec3890a41f..943f69bb6c 100644
+--- a/cpp/src/parquet/arrow/schema.cc
++++ b/cpp/src/parquet/arrow/schema.cc
+@@ -178,7 +178,7 @@ static Status GetTimestampMetadata(const 
::arrow::TimestampType& type,
+
+   // The user is explicitly asking for Impala int96 encoding, there is no
+   // logical type.
+-  if (arrow_properties.support_deprecated_int96_timestamps()) {
++  if (arrow_properties.support_deprecated_int96_timestamps() && target_unit 
== ::arrow::TimeUnit::NANO) {
+     *physical_type = ParquetType::INT96;
+     return Status::OK();
+   }
+diff --git a/cpp/src/parquet/arrow/writer.cc b/cpp/src/parquet/arrow/writer.cc
+index 4fd7ef1b47..87326a54f1 100644
+--- a/cpp/src/parquet/arrow/writer.cc
++++ b/cpp/src/parquet/arrow/writer.cc
+@@ -314,6 +314,14 @@ class FileWriterImpl : public FileWriter {
+     return Status::OK();
+   }
+
++  int64_t GetBufferedSize() override {
++    if (row_group_writer_ == nullptr) {
++      return 0;
++    }
++    return row_group_writer_->total_compressed_bytes() +
++      row_group_writer_->total_compressed_bytes_written();
++  }
++
+   Status Close() override {
+     if (!closed_) {
+       // Make idempotent
+@@ -418,10 +426,13 @@ class FileWriterImpl : public FileWriter {
+
+     // Max number of rows allowed in a row group.
+     const int64_t max_row_group_length = 
this->properties().max_row_group_length();
++    const int64_t max_row_group_size = 
this->properties().max_row_group_size();
+
+     // Initialize a new buffered row group writer if necessary.
+     if (row_group_writer_ == nullptr || !row_group_writer_->buffered() ||
+-        row_group_writer_->num_rows() >= max_row_group_length) {
++        row_group_writer_->num_rows() >= max_row_group_length ||
++        (row_group_writer_->total_compressed_bytes_written() +
++         row_group_writer_->total_compressed_bytes() >= max_row_group_size)) {
+       RETURN_NOT_OK(NewBufferedRowGroup());
+     }
+
+diff --git a/cpp/src/parquet/arrow/writer.h b/cpp/src/parquet/arrow/writer.h
+index 4a1a033a7b..0f13d05e44 100644
+--- a/cpp/src/parquet/arrow/writer.h
++++ b/cpp/src/parquet/arrow/writer.h
+@@ -138,6 +138,9 @@ class PARQUET_EXPORT FileWriter {
+   /// option in this case.
+   virtual ::arrow::Status WriteRecordBatch(const ::arrow::RecordBatch& batch) 
= 0;
+
++  /// \brief Return the buffered size in bytes.
++  virtual int64_t GetBufferedSize() = 0;
++
+   /// \brief Write the footer and close the file.
+   virtual ::arrow::Status Close() = 0;
+   virtual ~FileWriter();
+diff --git a/cpp/src/parquet/column_reader.cc 
b/cpp/src/parquet/column_reader.cc
+index ebf9515f27..0abc7d2320 100644
+--- a/cpp/src/parquet/column_reader.cc
++++ b/cpp/src/parquet/column_reader.cc
+@@ -208,6 +208,39 @@ ReaderProperties default_reader_properties() {
+   return default_reader_properties;
+ }
+
++void PageReader::set_data_page_read_plan(
++    int64_t first_data_page_offset,
++    std::vector<DataPageReadPlanEntry> data_pages) {
++  if (data_page_filter_) {
++    throw ParquetException(
++        "data_page_filter and data_page_read_plan cannot be enabled 
together");
++  }
++  if (first_data_page_offset < 0) {
++    throw ParquetException("Invalid negative first data page offset");
++  }
++
++  int64_t previous_end = first_data_page_offset;
++  int32_t previous_ordinal = -1;
++  for (const auto& page : data_pages) {
++    int64_t page_end;
++    if (page.page_ordinal < 0 || page.offset < first_data_page_offset ||
++        page.compressed_page_size <= 0 ||
++        AddWithOverflow(page.offset, page.compressed_page_size, &page_end)) {
++      throw ParquetException("Invalid data page read plan entry");
++    }
++    if (page.offset < previous_end || page.page_ordinal <= previous_ordinal) {
++      throw ParquetException("Data page read plan entries must be ordered");
++    }
++    previous_end = page_end;
++    previous_ordinal = page.page_ordinal;
++  }
++
++  data_page_read_plan_enabled_ = true;
++  first_data_page_offset_ = first_data_page_offset;
++  data_page_read_plan_ = std::move(data_pages);
++  next_data_page_ = 0;
++}
++
+ namespace {
+
+ // Extracts encoded statistics from V1 and V2 data page headers
+@@ -430,9 +463,43 @@ std::shared_ptr<Page> SerializedPageReader::NextPage() {
+
+   // Loop here because there may be unhandled page types that we skip until
+   // finding a page that we do know what to do with
+-  while (seen_num_values_ < total_num_values_) {
++  while (data_page_read_plan_enabled_ || seen_num_values_ < 
total_num_values_) {
++    const DataPageReadPlanEntry* planned_data_page = nullptr;
++    uint32_t page_header_limit = max_page_header_size_;
++
++    if (data_page_read_plan_enabled_) {
++      if (next_data_page_ >= data_page_read_plan_.size()) {
++        return nullptr;
++      }
++
++      PARQUET_ASSIGN_OR_THROW(int64_t current_position, stream_->Tell());
++      if (current_position < first_data_page_offset_) {
++        page_header_limit = static_cast<uint32_t>(std::min<int64_t>(
++            page_header_limit, first_data_page_offset_ - current_position));
++      } else {
++        planned_data_page = &data_page_read_plan_[next_data_page_];
++        if (current_position > planned_data_page->offset) {
++          throw ParquetException("Data page read plan points behind stream 
position");
++        }
++        PARQUET_THROW_NOT_OK(
++            stream_->Advance(planned_data_page->offset - current_position));
++        PARQUET_ASSIGN_OR_THROW(int64_t target_position, stream_->Tell());
++        if (target_position != planned_data_page->offset) {
++          throw ParquetException("Failed to seek to planned data page");
++        }
++        page_ordinal_ = planned_data_page->page_ordinal;
++        page_header_limit = static_cast<uint32_t>(std::min<int64_t>(
++            page_header_limit, planned_data_page->compressed_page_size));
++      }
++    }
++
++    if (page_header_limit == 0) {
++      throw ParquetException("No bytes available for page header");
++    }
++
+     uint32_t header_size = 0;
+-    uint32_t allowed_page_size = kDefaultPageHeaderSize;
++    uint32_t allowed_page_size =
++        std::min<uint32_t>(kDefaultPageHeaderSize, page_header_limit);
 
-   virtual ~FileColumnIterator() {}
+     // Page headers can be very large because of page statistics
+     // We try to deserialize a larger buffer progressively
+@@ -458,11 +525,12 @@ std::shared_ptr<Page> SerializedPageReader::NextPage() {
+         // Failed to deserialize. Double the allowed page header size and try 
again
+         std::stringstream ss;
+         ss << e.what();
+-        allowed_page_size *= 2;
+-        if (allowed_page_size > max_page_header_size_) {
++        if (allowed_page_size >= page_header_limit) {
+           ss << "Deserializing page header failed.\n";
+           throw ParquetException(ss.str());
+         }
++        allowed_page_size =
++            std::min<uint32_t>(allowed_page_size * 2, page_header_limit);
+       }
+     }
+     // Advance the stream offset
+@@ -474,6 +542,20 @@ std::shared_ptr<Page> SerializedPageReader::NextPage() {
+       throw ParquetException("Invalid page header");
+     }
 
--  std::unique_ptr<::parquet::PageReader> NextChunk() {
-+  /// \brief Fetch the PageReader for the next row group in this iterator's
-+  /// range. Virtual so subclasses can decorate the returned PageReader, e.g.
-+  /// to install a data_page_filter for I/O-level page skipping.
-+  virtual std::unique_ptr<::parquet::PageReader> NextChunk() {
-     if (row_groups_.empty()) {
-       return nullptr;
++    const PageType::type page_type = LoadEnumSafe(&current_page_header_.type);
++    if (planned_data_page != nullptr) {
++      if (page_type != PageType::DATA_PAGE && page_type != 
PageType::DATA_PAGE_V2) {
++        throw ParquetException("Data page read plan points to a non-data 
page");
++      }
++      int64_t total_compressed_size;
++      if (AddWithOverflow(static_cast<int64_t>(header_size),
++                          static_cast<int64_t>(compressed_len),
++                          &total_compressed_size) ||
++          total_compressed_size != planned_data_page->compressed_page_size) {
++        throw ParquetException("Planned data page size does not match page 
header");
++      }
++    }
++
+     EncodedStatistics data_page_statistics;
+     if (ShouldSkipPage(&data_page_statistics)) {
+       PARQUET_THROW_NOT_OK(stream_->Advance(compressed_len));
+@@ -494,8 +576,6 @@ std::shared_ptr<Page> SerializedPageReader::NextPage() {
+       ParquetException::EofException(ss.str());
      }
-@@ -95,9 +99,6 @@
-   std::deque<int> row_groups_;
- };
 
--using FileColumnIteratorFactory =
--    std::function<FileColumnIterator*(int, ParquetFileReader*)>;
+-    const PageType::type page_type = LoadEnumSafe(&current_page_header_.type);
 -
- Status TransferColumnData(::parquet::internal::RecordReader* reader,
-                           const std::shared_ptr<::arrow::Field>& value_field,
-                           const ColumnDescriptor* descr, ::arrow::MemoryPool* 
pool,
+     if (properties_.page_checksum_verification() && 
current_page_header_.__isset.crc &&
+         PageCanUseChecksum(page_type)) {
+       // verify crc
+@@ -534,6 +614,9 @@ std::shared_ptr<Page> SerializedPageReader::NextPage() {
+                                               
LoadEnumSafe(&dict_header.encoding),
+                                               is_sorted);
+     } else if (page_type == PageType::DATA_PAGE) {
++      if (planned_data_page != nullptr) {
++        ++next_data_page_;
++      }
+       ++page_ordinal_;
+       const format::DataPageHeader& header = 
current_page_header_.data_page_header;
+       page_buffer =
+@@ -545,6 +628,9 @@ std::shared_ptr<Page> SerializedPageReader::NextPage() {
+           LoadEnumSafe(&header.repetition_level_encoding), uncompressed_len,
+           std::move(data_page_statistics));
+     } else if (page_type == PageType::DATA_PAGE_V2) {
++      if (planned_data_page != nullptr) {
++        ++next_data_page_;
++      }
+       ++page_ordinal_;
+       const format::DataPageHeaderV2& header = 
current_page_header_.data_page_header_v2;
+
 diff --git a/cpp/src/parquet/column_reader.h b/cpp/src/parquet/column_reader.h
+index 29e1b2a25e..386e574644 100644
 --- a/cpp/src/parquet/column_reader.h
 +++ b/cpp/src/parquet/column_reader.h
 @@ -76,6 +76,18 @@ struct PARQUET_EXPORT DataPageStats {
@@ -797,156 +659,302 @@ diff --git a/cpp/src/parquet/column_reader.h 
b/cpp/src/parquet/column_reader.h
  };
 
  class PARQUET_EXPORT ColumnReader {
-diff --git a/cpp/src/parquet/column_reader.cc 
b/cpp/src/parquet/column_reader.cc
---- a/cpp/src/parquet/column_reader.cc
-+++ b/cpp/src/parquet/column_reader.cc
-@@ -207,6 +207,39 @@ ReaderProperties default_reader_properties() {
-   return default_reader_properties;
+diff --git a/cpp/src/parquet/file_reader.cc b/cpp/src/parquet/file_reader.cc
+index 3e9eeea6c6..671ebe4644 100644
+--- a/cpp/src/parquet/file_reader.cc
++++ b/cpp/src/parquet/file_reader.cc
+@@ -207,6 +207,117 @@ const RowGroupMetaData* RowGroupReader::metadata() const 
{ return contents_->met
+   return {col_start, col_length};
  }
 
-+void PageReader::set_data_page_read_plan(
-+    int64_t first_data_page_offset,
-+    std::vector<DataPageReadPlanEntry> data_pages) {
-+  if (data_page_filter_) {
-+    throw ParquetException(
-+        "data_page_filter and data_page_read_plan cannot be enabled 
together");
++// CachedInputStream: InputStream adapter that reads through ReadRangeCache 
with
++// zero-cost skip for non-cached pages. Used for page-level caching where only
++// specific pages are pre-buffered.
++//
++// Key behavior:
++// - Read(): On cache hit, returns cached data. On cache miss, returns 
zero-filled
++//   buffer (zero I/O). This makes InputStream::Advance() (which calls Read() 
and
++//   discards) effectively free for skipped pages.
++// - Peek(): Always falls back to source on cache miss, because PageReader 
uses
++//   Peek() to read Thrift page headers (~30 bytes) which must have real data.
++class CachedInputStream : public ::arrow::io::InputStream {
++ public:
++  CachedInputStream(
++      std::shared_ptr<::arrow::io::internal::ReadRangeCache> cache,
++      std::shared_ptr<ArrowInputFile> source,
++      int64_t offset, int64_t length)
++      : cache_(std::move(cache)),
++        source_(std::move(source)),
++        base_offset_(offset),
++        length_(length) {}
++
++  ::arrow::Status Close() override {
++    closed_ = true;
++    return ::arrow::Status::OK();
 +  }
-+  if (first_data_page_offset < 0) {
-+    throw ParquetException("Invalid negative first data page offset");
++
++  bool closed() const override { return closed_; }
++
++  ::arrow::Result<int64_t> Tell() const override { return position_; }
++
++  ::arrow::Result<std::string_view> Peek(int64_t nbytes) override {
++    int64_t to_read = std::min(nbytes, length_ - position_);
++    if (to_read <= 0) {
++      return std::string_view();
++    }
++    ::arrow::io::ReadRange range{base_offset_ + position_, to_read};
++    auto result = cache_->Read(range);
++    if (result.ok()) {
++      peek_buffer_ = *result;
++    } else {
++      // Peek is used for Thrift page headers (~30 bytes) — must read real 
data
++      ARROW_ASSIGN_OR_RAISE(peek_buffer_,
++                            source_->ReadAt(range.offset, range.length));
++    }
++    return std::string_view(
++        reinterpret_cast<const char*>(peek_buffer_->data()),
++        static_cast<size_t>(peek_buffer_->size()));
++  }
++
++  ::arrow::Result<int64_t> Read(int64_t nbytes, void* out) override {
++    int64_t to_read = std::min(nbytes, length_ - position_);
++    if (to_read <= 0) return 0;
++    ::arrow::io::ReadRange range{base_offset_ + position_, to_read};
++    auto result = cache_->Read(range);
++    if (result.ok()) {
++      auto& buf = *result;
++      memcpy(out, buf->data(), static_cast<size_t>(buf->size()));
++      position_ += buf->size();
++      return buf->size();
++    }
++    // Cache miss: fall back to real I/O from source
++    ARROW_ASSIGN_OR_RAISE(auto buf, source_->ReadAt(range.offset, 
range.length));
++    memcpy(out, buf->data(), static_cast<size_t>(buf->size()));
++    position_ += buf->size();
++    return buf->size();
++  }
++
++  ::arrow::Result<std::shared_ptr<::arrow::Buffer>> Read(int64_t nbytes) 
override {
++    int64_t to_read = std::min(nbytes, length_ - position_);
++    if (to_read <= 0) {
++      return std::make_shared<::arrow::Buffer>(nullptr, 0);
++    }
++    ::arrow::io::ReadRange range{base_offset_ + position_, to_read};
++    auto result = cache_->Read(range);
++    if (result.ok()) {
++      position_ += (*result)->size();
++      return *result;
++    }
++    // Cache miss: fall back to real I/O from source
++    ARROW_ASSIGN_OR_RAISE(auto buf, source_->ReadAt(range.offset, 
range.length));
++    position_ += buf->size();
++    return std::shared_ptr<::arrow::Buffer>(std::move(buf));
++  }
++
++  // Override Advance to avoid real I/O for skipped pages.
++  // The default InputStream::Advance() calls Read() and discards the result,
++  // which would trigger source_->ReadAt() on cache miss — defeating 
page-level
++  // I/O skipping via data_page_filter. Since Advance() is only used to skip
++  // over data that will not be consumed, we can safely just move the 
position.
++  ::arrow::Status Advance(int64_t nbytes) override {
++    if (nbytes <= 0) {
++      return ::arrow::Status::OK();
++    }
++    int64_t remaining = length_ - position_;
++    if (remaining <= 0) {
++      return ::arrow::Status::OK();
++    }
++    position_ += std::min(nbytes, remaining);
++    return ::arrow::Status::OK();
++  }
++
++ private:
++  std::shared_ptr<::arrow::io::internal::ReadRangeCache> cache_;
++  std::shared_ptr<ArrowInputFile> source_;
++  int64_t base_offset_;
++  int64_t length_;
++  int64_t position_ = 0;
++  bool closed_ = false;
++  std::shared_ptr<::arrow::Buffer> peek_buffer_;
++};
++
+ // RowGroupReader::Contents implementation for the Parquet file specification
+ class SerializedRowGroup : public RowGroupReader::Contents {
+  public:
+@@ -242,6 +353,11 @@ class SerializedRowGroup : public 
RowGroupReader::Contents {
+       // segments.
+       PARQUET_ASSIGN_OR_THROW(auto buffer, cached_source_->Read(col_range));
+       stream = std::make_shared<::arrow::io::BufferReader>(buffer);
++    } else if (cached_source_) {
++      // Page-level caching: read through cache with fallback to source.
++      // Advance() is zero-cost for skipped pages via data_page_filter.
++      stream = std::make_shared<CachedInputStream>(
++          cached_source_, source_, col_range.offset, col_range.length);
+     } else {
+       stream = properties_.GetStream(source_, col_range.offset, 
col_range.length);
+     }
+@@ -417,6 +533,26 @@ class SerializedFile : public ParquetFileReader::Contents 
{
+     return cached_source_->WaitFor(ranges);
+   }
+
++  void PreBufferRanges(const std::vector<::arrow::io::ReadRange>& ranges,
++                       const ::arrow::io::IOContext& ctx,
++                       const ::arrow::io::CacheOptions& options) {
++    cached_source_ =
++        std::make_shared<::arrow::io::internal::ReadRangeCache>(source_, ctx, 
options);
++    // Do NOT set prebuffered_column_chunks_ bitmap — GetColumnPageReader will
++    // use CachedInputStream path instead of full-chunk BufferReader path.
++    prebuffered_column_chunks_.clear();
++    PARQUET_THROW_NOT_OK(cached_source_->Cache(ranges));
 +  }
 +
-+  int64_t previous_end = first_data_page_offset;
-+  int32_t previous_ordinal = -1;
-+  for (const auto& page : data_pages) {
-+    int64_t page_end;
-+    if (page.page_ordinal < 0 || page.offset < first_data_page_offset ||
-+        page.compressed_page_size <= 0 ||
-+        AddWithOverflow(page.offset, page.compressed_page_size, &page_end)) {
-+      throw ParquetException("Invalid data page read plan entry");
-+    }
-+    if (page.offset < previous_end || page.page_ordinal <= previous_ordinal) {
-+      throw ParquetException("Data page read plan entries must be ordered");
++  ::arrow::Future<> WhenBufferedRanges(
++      const std::vector<::arrow::io::ReadRange>& ranges) const {
++    if (!cached_source_) {
++      return ::arrow::Status::Invalid(
++          "Must call PreBufferRanges before WhenBufferedRanges");
 +    }
-+    previous_end = page_end;
-+    previous_ordinal = page.page_ordinal;
++    return cached_source_->WaitFor(ranges);
 +  }
 +
-+  data_page_read_plan_enabled_ = true;
-+  first_data_page_offset_ = first_data_page_offset;
-+  data_page_read_plan_ = std::move(data_pages);
-+  next_data_page_ = 0;
+   // Metadata/footer parsing. Divided up to separate sync/async paths, and to 
use
+   // exceptions for error handling (with the async path converting to 
Future/Status).
+
+@@ -911,6 +1047,22 @@ void ParquetFileReader::PreBuffer(const 
std::vector<int>& row_groups,
+   return file->WhenBuffered(row_groups, column_indices);
+ }
+
++void ParquetFileReader::PreBufferRanges(
++    const std::vector<::arrow::io::ReadRange>& ranges,
++    const ::arrow::io::IOContext& ctx,
++    const ::arrow::io::CacheOptions& options) {
++  SerializedFile* file =
++      ::arrow::internal::checked_cast<SerializedFile*>(contents_.get());
++  file->PreBufferRanges(ranges, ctx, options);
 +}
 +
- namespace {
++::arrow::Future<> ParquetFileReader::WhenBufferedRanges(
++    const std::vector<::arrow::io::ReadRange>& ranges) const {
++  SerializedFile* file =
++      ::arrow::internal::checked_cast<SerializedFile*>(contents_.get());
++  return file->WhenBufferedRanges(ranges);
++}
++
+ // ----------------------------------------------------------------------
+ // File metadata helpers
 
- // Extracts encoded statistics from V1 and V2 data page headers
-@@ -430,9 +463,43 @@ std::shared_ptr<Page> SerializedPageReader::NextPage() {
+diff --git a/cpp/src/parquet/file_reader.h b/cpp/src/parquet/file_reader.h
+index b59b59f95c..657a438a3a 100644
+--- a/cpp/src/parquet/file_reader.h
++++ b/cpp/src/parquet/file_reader.h
+@@ -210,6 +210,17 @@ class PARQUET_EXPORT ParquetFileReader {
+   ::arrow::Future<> WhenBuffered(const std::vector<int>& row_groups,
+                                  const std::vector<int>& column_indices) 
const;
 
-   // Loop here because there may be unhandled page types that we skip until
-   // finding a page that we do know what to do with
--  while (seen_num_values_ < total_num_values_) {
-+  while (data_page_read_plan_enabled_ || seen_num_values_ < 
total_num_values_) {
-+    const DataPageReadPlanEntry* planned_data_page = nullptr;
-+    uint32_t page_header_limit = max_page_header_size_;
-+
-+    if (data_page_read_plan_enabled_) {
-+      if (next_data_page_ >= data_page_read_plan_.size()) {
-+        return nullptr;
-+      }
++  /// Pre-buffer arbitrary byte ranges (e.g., page-level ranges from 
OffsetIndex).
++  /// Unlike PreBuffer(), this does NOT set the column bitmap, so
++  /// GetColumnPageReader will use CachedInputStream (page-level cache path).
++  void PreBufferRanges(const std::vector<::arrow::io::ReadRange>& ranges,
++                       const ::arrow::io::IOContext& ctx,
++                       const ::arrow::io::CacheOptions& options);
 +
-+      PARQUET_ASSIGN_OR_THROW(int64_t current_position, stream_->Tell());
-+      if (current_position < first_data_page_offset_) {
-+        page_header_limit = static_cast<uint32_t>(std::min<int64_t>(
-+            page_header_limit, first_data_page_offset_ - current_position));
-+      } else {
-+        planned_data_page = &data_page_read_plan_[next_data_page_];
-+        if (current_position > planned_data_page->offset) {
-+          throw ParquetException("Data page read plan points behind stream 
position");
-+        }
-+        PARQUET_THROW_NOT_OK(
-+            stream_->Advance(planned_data_page->offset - current_position));
-+        PARQUET_ASSIGN_OR_THROW(int64_t target_position, stream_->Tell());
-+        if (target_position != planned_data_page->offset) {
-+          throw ParquetException("Failed to seek to planned data page");
-+        }
-+        page_ordinal_ = planned_data_page->page_ordinal;
-+        page_header_limit = static_cast<uint32_t>(std::min<int64_t>(
-+            page_header_limit, planned_data_page->compressed_page_size));
-+      }
-+    }
++  /// Wait for arbitrary byte ranges to be pre-buffered.
++  ::arrow::Future<> WhenBufferedRanges(
++      const std::vector<::arrow::io::ReadRange>& ranges) const;
 +
-+    if (page_header_limit == 0) {
-+      throw ParquetException("No bytes available for page header");
+  private:
+   // Holds a pointer to an instance of Contents implementation
+   std::unique_ptr<Contents> contents_;
+diff --git a/cpp/src/parquet/properties.h b/cpp/src/parquet/properties.h
+index 4d3acb491e..3906ff3c59 100644
+--- a/cpp/src/parquet/properties.h
++++ b/cpp/src/parquet/properties.h
+@@ -139,6 +139,7 @@ static constexpr bool DEFAULT_IS_DICTIONARY_ENABLED = true;
+ static constexpr int64_t DEFAULT_DICTIONARY_PAGE_SIZE_LIMIT = 
kDefaultDataPageSize;
+ static constexpr int64_t DEFAULT_WRITE_BATCH_SIZE = 1024;
+ static constexpr int64_t DEFAULT_MAX_ROW_GROUP_LENGTH = 1024 * 1024;
++static constexpr int64_t DEFAULT_MAX_ROW_GROUP_SIZE = 128 * 1024 * 1024;
+ static constexpr bool DEFAULT_ARE_STATISTICS_ENABLED = true;
+ static constexpr int64_t DEFAULT_MAX_STATISTICS_SIZE = 4096;
+ static constexpr Encoding::type DEFAULT_ENCODING = Encoding::UNKNOWN;
+@@ -232,6 +233,7 @@ class PARQUET_EXPORT WriterProperties {
+           dictionary_pagesize_limit_(DEFAULT_DICTIONARY_PAGE_SIZE_LIMIT),
+           write_batch_size_(DEFAULT_WRITE_BATCH_SIZE),
+           max_row_group_length_(DEFAULT_MAX_ROW_GROUP_LENGTH),
++          max_row_group_size_(DEFAULT_MAX_ROW_GROUP_SIZE),
+           pagesize_(kDefaultDataPageSize),
+           version_(ParquetVersion::PARQUET_2_6),
+           data_page_version_(ParquetDataPageVersion::V1),
+@@ -244,6 +246,7 @@ class PARQUET_EXPORT WriterProperties {
+           dictionary_pagesize_limit_(properties.dictionary_pagesize_limit()),
+           write_batch_size_(properties.write_batch_size()),
+           max_row_group_length_(properties.max_row_group_length()),
++          max_row_group_size_(properties.max_row_group_size()),
+           pagesize_(properties.data_pagesize()),
+           version_(properties.version()),
+           data_page_version_(properties.data_page_version()),
+@@ -321,6 +324,13 @@ class PARQUET_EXPORT WriterProperties {
+       return this;
+     }
+
++    /// Specify the max bytes size to put in a single row group.
++    /// Default 128 M.
++    Builder* max_row_group_size(int64_t max_row_group_size) {
++      max_row_group_size_ = max_row_group_size;
++      return this;
 +    }
 +
-     uint32_t header_size = 0;
--    uint32_t allowed_page_size = kDefaultPageHeaderSize;
-+    uint32_t allowed_page_size =
-+        std::min<uint32_t>(kDefaultPageHeaderSize, page_header_limit);
+     /// Specify the data page size.
+     /// Default 1MB.
+     Builder* data_pagesize(int64_t pg_size) {
+@@ -664,7 +674,7 @@ class PARQUET_EXPORT WriterProperties {
 
-     // Page headers can be very large because of page statistics
-     // We try to deserialize a larger buffer progressively
-@@ -458,11 +525,12 @@ std::shared_ptr<Page> SerializedPageReader::NextPage() {
-         // Failed to deserialize. Double the allowed page header size and try 
again
-         std::stringstream ss;
-         ss << e.what();
--        allowed_page_size *= 2;
--        if (allowed_page_size > max_page_header_size_) {
-+        if (allowed_page_size >= page_header_limit) {
-           ss << "Deserializing page header failed.\n";
-           throw ParquetException(ss.str());
-         }
-+        allowed_page_size =
-+            std::min<uint32_t>(allowed_page_size * 2, page_header_limit);
-       }
-     }
-     // Advance the stream offset
-@@ -474,6 +542,20 @@ std::shared_ptr<Page> SerializedPageReader::NextPage() {
-       throw ParquetException("Invalid page header");
-     }
+       return std::shared_ptr<WriterProperties>(new WriterProperties(
+           pool_, dictionary_pagesize_limit_, write_batch_size_, 
max_row_group_length_,
+-          pagesize_, version_, created_by_, page_checksum_enabled_,
++          max_row_group_size_, pagesize_, version_, created_by_, 
page_checksum_enabled_,
+           std::move(file_encryption_properties_), default_column_properties_,
+           column_properties, data_page_version_, store_decimal_as_integer_,
+           std::move(sorting_columns_)));
+@@ -675,6 +685,7 @@ class PARQUET_EXPORT WriterProperties {
+     int64_t dictionary_pagesize_limit_;
+     int64_t write_batch_size_;
+     int64_t max_row_group_length_;
++    int64_t max_row_group_size_;
+     int64_t pagesize_;
+     ParquetVersion::type version_;
+     ParquetDataPageVersion data_page_version_;
+@@ -705,6 +716,8 @@ class PARQUET_EXPORT WriterProperties {
 
-+    const PageType::type page_type = LoadEnumSafe(&current_page_header_.type);
-+    if (planned_data_page != nullptr) {
-+      if (page_type != PageType::DATA_PAGE && page_type != 
PageType::DATA_PAGE_V2) {
-+        throw ParquetException("Data page read plan points to a non-data 
page");
-+      }
-+      int64_t total_compressed_size;
-+      if (AddWithOverflow(static_cast<int64_t>(header_size),
-+                          static_cast<int64_t>(compressed_len),
-+                          &total_compressed_size) ||
-+          total_compressed_size != planned_data_page->compressed_page_size) {
-+        throw ParquetException("Planned data page size does not match page 
header");
-+      }
-+    }
+   inline int64_t max_row_group_length() const { return max_row_group_length_; 
}
+
++  inline int64_t max_row_group_size() const { return max_row_group_size_; }
 +
-     EncodedStatistics data_page_statistics;
-     if (ShouldSkipPage(&data_page_statistics)) {
-       PARQUET_THROW_NOT_OK(stream_->Advance(compressed_len));
-@@ -494,8 +576,6 @@ std::shared_ptr<Page> SerializedPageReader::NextPage() {
-       ParquetException::EofException(ss.str());
-     }
+   inline int64_t data_pagesize() const { return pagesize_; }
 
--    const PageType::type page_type = LoadEnumSafe(&current_page_header_.type);
--
-     if (properties_.page_checksum_verification() && 
current_page_header_.__isset.crc &&
-         PageCanUseChecksum(page_type)) {
-       // verify crc
-@@ -534,6 +614,9 @@ std::shared_ptr<Page> SerializedPageReader::NextPage() {
-                                               
LoadEnumSafe(&dict_header.encoding),
-                                               is_sorted);
-     } else if (page_type == PageType::DATA_PAGE) {
-+      if (planned_data_page != nullptr) {
-+        ++next_data_page_;
-+      }
-       ++page_ordinal_;
-       const format::DataPageHeader& header = 
current_page_header_.data_page_header;
-       page_buffer =
-@@ -545,6 +628,9 @@ std::shared_ptr<Page> SerializedPageReader::NextPage() {
-           LoadEnumSafe(&header.repetition_level_encoding), uncompressed_len,
-           std::move(data_page_statistics));
-     } else if (page_type == PageType::DATA_PAGE_V2) {
-+      if (planned_data_page != nullptr) {
-+        ++next_data_page_;
-+      }
-       ++page_ordinal_;
-       const format::DataPageHeaderV2& header = 
current_page_header_.data_page_header_v2;
+   inline ParquetDataPageVersion data_page_version() const {
+@@ -810,7 +823,7 @@ class PARQUET_EXPORT WriterProperties {
+  private:
+   explicit WriterProperties(
+       MemoryPool* pool, int64_t dictionary_pagesize_limit, int64_t 
write_batch_size,
+-      int64_t max_row_group_length, int64_t pagesize, ParquetVersion::type 
version,
++      int64_t max_row_group_length, int64_t max_row_group_size, int64_t 
pagesize, ParquetVersion::type version,
+       const std::string& created_by, bool page_write_checksum_enabled,
+       std::shared_ptr<FileEncryptionProperties> file_encryption_properties,
+       const ColumnProperties& default_column_properties,
+@@ -821,6 +834,7 @@ class PARQUET_EXPORT WriterProperties {
+         dictionary_pagesize_limit_(dictionary_pagesize_limit),
+         write_batch_size_(write_batch_size),
+         max_row_group_length_(max_row_group_length),
++        max_row_group_size_(max_row_group_size),
+         pagesize_(pagesize),
+         parquet_data_page_version_(data_page_version),
+         parquet_version_(version),
+@@ -836,6 +850,7 @@ class PARQUET_EXPORT WriterProperties {
+   int64_t dictionary_pagesize_limit_;
+   int64_t write_batch_size_;
+   int64_t max_row_group_length_;
++  int64_t max_row_group_size_;
+   int64_t pagesize_;
+   ParquetDataPageVersion parquet_data_page_version_;
+   ParquetVersion::type parquet_version_;

Reply via email to