@@ -439,9 +439,305 @@ diff --git a/cpp/cmake_modules/BuildUtils.cmake b/cpp/cmake_modules/BuildUtils.c
439439diff --git a/cpp/src/arrow/io/interfaces.h b/cpp/src/arrow/io/interfaces.h
440440--- a/cpp/src/arrow/io/interfaces.h
441441+++ b/cpp/src/arrow/io/interfaces.h
442- @@ -211 ,7 +211 ,7 @@
442+ @@ -210 ,7 +210 ,7 @@
443443 /// \brief Advance or skip stream indicated number of bytes
444444 /// \param[in] nbytes the number to move forward
445445 /// \return Status
446446- Status Advance(int64_t nbytes);
447447+ virtual Status Advance(int64_t nbytes);
448+
449+ /// \brief Return zero-copy string_view to upcoming bytes.
450+ ///
451+ --- a/cpp/src/parquet/arrow/reader.cc
452+ +++ b/cpp/src/parquet/arrow/reader.cc
453+ @@ -254,6 +254,11 @@
454+ return GetColumn(i, AllRowGroupsFactory(), out);
455+ }
456+
457+ + ::arrow::Status GetColumn(
458+ + int i, const std::vector<int>& column_indices,
459+ + FileColumnIteratorFactory iterator_factory,
460+ + std::unique_ptr<ColumnReader>* out) override;
461+ +
462+ Status GetSchema(std::shared_ptr<::arrow::Schema>* out) override {
463+ return FromParquetSchema(reader_->metadata()->schema(), reader_properties_,
464+ reader_->metadata()->key_value_metadata(), out);
465+ @@ -493,10 +498,40 @@
466+
467+ ::arrow::Status BuildArray(int64_t length_upper_bound,
468+ std::shared_ptr<::arrow::ChunkedArray>* out) final {
469+ + if (!out_) {
470+ + BEGIN_PARQUET_CATCH_EXCEPTIONS
471+ + RETURN_NOT_OK(
472+ + TransferColumnData(record_reader_.get(), field_, descr_, ctx_->pool, &out_));
473+ + END_PARQUET_CATCH_EXCEPTIONS
474+ + }
475+ *out = out_;
476+ return Status::OK();
477+ }
478+
479+ + std::vector<int> LeafColumnIndices() const final {
480+ + return {input_->column_index()};
481+ + }
482+ +
483+ + ::arrow::Status ResetLeaf(int col_idx, int64_t reserve) final {
484+ + if (col_idx != input_->column_index()) return Status::OK();
485+ + BEGIN_PARQUET_CATCH_EXCEPTIONS
486+ + out_ = nullptr;
487+ + record_reader_->Reset();
488+ + record_reader_->Reserve(reserve);
489+ + return Status::OK();
490+ + END_PARQUET_CATCH_EXCEPTIONS
491+ + }
492+ +
493+ + int64_t SkipRecords(int col_idx, int64_t num_records) final {
494+ + if (col_idx != input_->column_index() || num_records <= 0) return 0;
495+ + return record_reader_->SkipRecords(num_records);
496+ + }
497+ +
498+ + int64_t ReadRecords(int col_idx, int64_t num_records) final {
499+ + if (col_idx != input_->column_index() || num_records <= 0) return 0;
500+ + return record_reader_->ReadRecords(num_records);
501+ + }
502+ +
503+ const std::shared_ptr<Field> field() override { return field_; }
504+
505+ private:
506+ @@ -532,6 +567,22 @@
507+ return storage_reader_->LoadBatch(number_of_records);
508+ }
509+
510+ + std::vector<int> LeafColumnIndices() const final {
511+ + return storage_reader_->LeafColumnIndices();
512+ + }
513+ +
514+ + ::arrow::Status ResetLeaf(int col_idx, int64_t reserve) final {
515+ + return storage_reader_->ResetLeaf(col_idx, reserve);
516+ + }
517+ +
518+ + int64_t SkipRecords(int col_idx, int64_t num_records) final {
519+ + return storage_reader_->SkipRecords(col_idx, num_records);
520+ + }
521+ +
522+ + int64_t ReadRecords(int col_idx, int64_t num_records) final {
523+ + return storage_reader_->ReadRecords(col_idx, num_records);
524+ + }
525+ +
526+ Status BuildArray(int64_t length_upper_bound,
527+ std::shared_ptr<ChunkedArray>* out) override {
528+ std::shared_ptr<ChunkedArray> storage;
529+ @@ -576,6 +627,22 @@
530+ return item_reader_->LoadBatch(number_of_records);
531+ }
532+
533+ + std::vector<int> LeafColumnIndices() const final {
534+ + return item_reader_->LeafColumnIndices();
535+ + }
536+ +
537+ + ::arrow::Status ResetLeaf(int col_idx, int64_t reserve) final {
538+ + return item_reader_->ResetLeaf(col_idx, reserve);
539+ + }
540+ +
541+ + int64_t SkipRecords(int col_idx, int64_t num_records) final {
542+ + return item_reader_->SkipRecords(col_idx, num_records);
543+ + }
544+ +
545+ + int64_t ReadRecords(int col_idx, int64_t num_records) final {
546+ + return item_reader_->ReadRecords(col_idx, num_records);
547+ + }
548+ +
549+ virtual ::arrow::Result<std::shared_ptr<ChunkedArray>> AssembleArray(
550+ std::shared_ptr<ArrayData> data) {
551+ if (field_->type()->id() == ::arrow::Type::MAP) {
552+ @@ -709,6 +776,39 @@
553+ }
554+ return Status::OK();
555+ }
556+ +
557+ + std::vector<int> LeafColumnIndices() const override {
558+ + std::vector<int> indices;
559+ + for (const std::unique_ptr<ColumnReaderImpl>& reader : children_) {
560+ + std::vector<int> child_indices = reader->LeafColumnIndices();
561+ + indices.insert(indices.end(), child_indices.begin(), child_indices.end());
562+ + }
563+ + return indices;
564+ + }
565+ +
566+ + ::arrow::Status ResetLeaf(int col_idx, int64_t reserve) override {
567+ + for (const std::unique_ptr<ColumnReaderImpl>& reader : children_) {
568+ + RETURN_NOT_OK(reader->ResetLeaf(col_idx, reserve));
569+ + }
570+ + return Status::OK();
571+ + }
572+ +
573+ + int64_t SkipRecords(int col_idx, int64_t num_records) override {
574+ + int64_t skipped = 0;
575+ + for (const std::unique_ptr<ColumnReaderImpl>& reader : children_) {
576+ + skipped += reader->SkipRecords(col_idx, num_records);
577+ + }
578+ + return skipped;
579+ + }
580+ +
581+ + int64_t ReadRecords(int col_idx, int64_t num_records) override {
582+ + int64_t read = 0;
583+ + for (const std::unique_ptr<ColumnReaderImpl>& reader : children_) {
584+ + read += reader->ReadRecords(col_idx, num_records);
585+ + }
586+ + return read;
587+ + }
588+ +
589+ Status BuildArray(int64_t length_upper_bound,
590+ std::shared_ptr<ChunkedArray>* out) override;
591+ Status GetDefLevels(const int16_t** data, int64_t* length) override;
592+ @@ -1228,6 +1328,23 @@
593+ std::unique_ptr<ColumnReaderImpl> result;
594+ RETURN_NOT_OK(GetReader(manifest_.schema_fields[i], ctx, &result));
595+ *out = std::move(result);
596+ + return Status::OK();
597+ + }
598+ +
599+ + ::arrow::Status FileReaderImpl::GetColumn(
600+ + int i, const std::vector<int>& column_indices,
601+ + FileColumnIteratorFactory iterator_factory,
602+ + std::unique_ptr<ColumnReader>* out) {
603+ + RETURN_NOT_OK(BoundsCheckColumn(i));
604+ + auto ctx = std::make_shared<ReaderContext>();
605+ + ctx->reader = reader_.get();
606+ + ctx->pool = pool_;
607+ + ctx->iterator_factory = iterator_factory;
608+ + ctx->filter_leaves = true;
609+ + ctx->included_leaves = VectorToSharedSet(column_indices);
610+ + std::unique_ptr<ColumnReaderImpl> result;
611+ + RETURN_NOT_OK(GetReader(manifest_.schema_fields[i], ctx, &result));
612+ + *out = std::move(result);
613+ return Status::OK();
614+ }
615+
616+ --- a/cpp/src/parquet/arrow/reader.h
617+ +++ b/cpp/src/parquet/arrow/reader.h
618+ @@ -21,6 +21,7 @@
619+ // N.B. we don't include async_generator.h as it's relatively heavy
620+ #include <functional>
621+ #include <memory>
622+ + #include <utility>
623+ #include <vector>
624+
625+ #include "parquet/file_reader.h"
626+ @@ -48,9 +49,13 @@
627+
628+ class ColumnChunkReader;
629+ class ColumnReader;
630+ + class FileColumnIterator;
631+ struct SchemaManifest;
632+ class RowGroupReader;
633+
634+ + using FileColumnIteratorFactory =
635+ + std::function<FileColumnIterator*(int, ParquetFileReader*)>;
636+ +
637+ /// \brief Arrow read adapter class for deserializing Parquet files as Arrow row batches.
638+ ///
639+ /// This interfaces caters for different use cases and thus provides different
640+ @@ -136,6 +141,27 @@
641+ // The indicated column index is relative to the schema
642+ virtual ::arrow::Status GetColumn(int i, std::unique_ptr<ColumnReader>* out) = 0;
643+
644+ + /// \brief Return a ColumnReader with a custom FileColumnIteratorFactory
645+ + /// and leaf column filtering.
646+ + ///
647+ + /// This allows callers to customize page reading behavior (e.g., setting
648+ + /// data_page_filter for page-level skipping) and to select only specific
649+ + /// leaf columns within a nested field. The factory is called once per leaf
650+ + /// column included in column_indices.
651+ + ///
652+ + /// \param i top-level field index (same as GetColumn(int i, ...))
653+ + /// \param column_indices leaf column indices to include (enables sub-column
654+ + /// projection within nested types)
655+ + /// \param iterator_factory factory to create FileColumnIterator per leaf
656+ + /// \param[out] out the ColumnReader (may be nullptr if all leaves are pruned)
657+ + virtual ::arrow::Status GetColumn(
658+ + int i, const std::vector<int>& column_indices,
659+ + FileColumnIteratorFactory iterator_factory,
660+ + std::unique_ptr<ColumnReader>* out) {
661+ + return ::arrow::Status::NotImplemented(
662+ + "GetColumn with factory not implemented");
663+ + }
664+ +
665+ /// \brief Return arrow schema for all the columns.
666+ virtual ::arrow::Status GetSchema(std::shared_ptr<::arrow::Schema>* out) = 0;
667+
668+ @@ -316,6 +342,43 @@
669+ // the data available in the file.
670+ virtual ::arrow::Status NextBatch(int64_t batch_size,
671+ std::shared_ptr<::arrow::ChunkedArray>* out) = 0;
672+ +
673+ + /// \brief Leaf column indices covered by this (sub)tree, in leaf order.
674+ + ///
675+ + /// Used to drive per-leaf row filtering: after page-level skipping each leaf
676+ + /// lives in its own compressed coordinate space, so callers must reset and
677+ + /// skip/read each leaf independently rather than in lockstep.
678+ + virtual std::vector<int> LeafColumnIndices() const { return {}; }
679+ +
680+ + /// \brief Reset the leaf identified by col_idx and reserve space for
681+ + /// `reserve` records (in that leaf's post-page-filter compressed space).
682+ + /// Must be called before SkipRecords()/ReadRecords() for that leaf, and
683+ + /// followed by BuildArray() to get the result.
684+ + virtual ::arrow::Status ResetLeaf(int col_idx, int64_t reserve) {
685+ + return ::arrow::Status::NotImplemented("ResetLeaf not implemented");
686+ + }
687+ +
688+ + /// \brief Skip num_records on the leaf identified by col_idx and return the
689+ + /// number of records actually skipped. Returns 0 when num_records <= 0 or
690+ + /// col_idx does not belong to this (sub)tree. May throw ParquetException on a
691+ + /// decode error; callers convert it to Status at the public boundary.
692+ + virtual int64_t SkipRecords(int col_idx, int64_t num_records) { return 0; }
693+ +
694+ + /// \brief Read num_records on the leaf identified by col_idx and return the
695+ + /// number of records actually read. Values accumulate across successive calls
696+ + /// until BuildArray() is called. Returns 0 when num_records <= 0 or col_idx
697+ + /// does not belong to this (sub)tree. May throw ParquetException on a decode
698+ + /// error; callers convert it to Status at the public boundary.
699+ + virtual int64_t ReadRecords(int col_idx, int64_t num_records) { return 0; }
700+ +
701+ + /// \brief Build the Arrow array from previously loaded data.
702+ + /// For leaf readers, calls TransferColumnData if not already done.
703+ + /// For nested readers, assembles the nested array from child arrays.
704+ + virtual ::arrow::Status BuildArray(
705+ + int64_t length_upper_bound,
706+ + std::shared_ptr<::arrow::ChunkedArray>* out) {
707+ + return ::arrow::Status::NotImplemented("BuildArray not implemented");
708+ + }
709+ };
710+
711+ /// \brief Experimental helper class for bindings (like Python) that struggle
712+ --- a/cpp/src/parquet/arrow/reader_internal.h
713+ +++ b/cpp/src/parquet/arrow/reader_internal.h
714+ @@ -26,6 +26,7 @@
715+ #include <utility>
716+ #include <vector>
717+
718+ + #include "parquet/arrow/reader.h"
719+ #include "parquet/arrow/schema.h"
720+ #include "parquet/column_reader.h"
721+ #include "parquet/file_reader.h"
722+ @@ -70,7 +71,10 @@
723+
724+ virtual ~FileColumnIterator() {}
725+
726+ - std::unique_ptr<::parquet::PageReader> NextChunk() {
727+ + /// \brief Fetch the PageReader for the next row group in this iterator's
728+ + /// range. Virtual so subclasses can decorate the returned PageReader, e.g.
729+ + /// to install a data_page_filter for I/O-level page skipping.
730+ + virtual std::unique_ptr<::parquet::PageReader> NextChunk() {
731+ if (row_groups_.empty()) {
732+ return nullptr;
733+ }
734+ @@ -95,9 +99,6 @@
735+ std::deque<int> row_groups_;
736+ };
737+
738+ - using FileColumnIteratorFactory =
739+ - std::function<FileColumnIterator*(int, ParquetFileReader*)>;
740+ -
741+ Status TransferColumnData(::parquet::internal::RecordReader* reader,
742+ const std::shared_ptr<::arrow::Field>& value_field,
743+ const ColumnDescriptor* descr, ::arrow::MemoryPool* pool,
0 commit comments