Skip to content

feat: Add row-group-local RowSelection support to the push decoder - #10702

Open
haohuaijin wants to merge 8 commits into
apache:mainfrom
haohuaijin:push-decoder-row-group-selections
Open

feat: Add row-group-local RowSelection support to the push decoder#10702
haohuaijin wants to merge 8 commits into
apache:mainfrom
haohuaijin:push-decoder-row-group-selections

Conversation

@haohuaijin

Copy link
Copy Markdown
Contributor

Which issue does this PR close?

Rationale for this change

DataFusion makes row-group-local selection decisions (ParquetAccessPlan), but the reader APIs only accept selected row groups plus a single global RowSelection. Callers must concatenate per-row-group selections into one global selection, which arrow-rs then re-partitions back into per-row-group selections during decoding. This round trip is wasted work and loses each selection's representation (bitmap vs. selector).

What changes are included in this PR?

  • New public API on the push decoder: RowGroupSelection (a row group index plus an optional row-group-local RowSelection) and ParquetPushDecoderBuilder::with_row_group_selections. Entries decode in the supplied order, omitted row groups are skipped, None reads the whole row group, and each selection keeps its bitmap or selector representation.
  • Mutually exclusive with with_row_groups / with_row_selection: the setters share an internal state machine (RowGroupPlan) that reports conflicting combinations as an error from build() regardless of call order. The legacy API combination is unchanged.
  • build() validates per-row-group plans eagerly: out-of-bounds indices and selections longer than their row group are errors; shorter selections skip the trailing rows.
  • ParquetPushDecoder::into_builder preserves remaining local selections (still in local coordinates), so adaptive scans compose with the new API.
  • Minor behavior improvement: an out-of-bounds index from with_row_groups on the push decoder now returns a ParquetError during decoding instead of panicking.

The sync and async builders are unchanged; the async builder already delegates to the push decoder, so extending the API to it is a small follow-up if needed.

Are these changes tested?

Yes, new tests cover bitmap- and selector-backed local selections (including out-of-order row groups and short selections), skip/replace semantics, mutual exclusion in all four call orders, build-time validation, into_builder round-trips, and the unchanged legacy combination. All existing tests pass.

Are there any user-facing changes?

New public API: RowGroupSelection and ParquetPushDecoderBuilder::with_row_group_selections, with doc examples. No breaking changes; one behavior change: out-of-bounds with_row_groups indices on the push decoder now error during decoding instead of panicking.

@github-actions github-actions Bot added the parquet Changes to the parquet crate label Aug 15, 2026
@haohuaijin haohuaijin changed the title parquet: Add row-group-local RowSelection support to the push decoder feat: Add row-group-local RowSelection support to the push decoder Aug 15, 2026
@haohuaijin

Copy link
Copy Markdown
Contributor Author

In my mind the trick will be how to mesh the existing apis (with_row_groups and with_row_selection) and make sure we have a coherent documentation of what will happen if the user calls each one

This PR adds ParquetPushDecoderBuilder::with_row_group_selections so callers can supply row-group-local RowSelections directly. The new configuration is mutually exclusive with with_row_groups / with_row_selection: an internal state machine catches any conflicting combination in any call order and reports it as an error from build(), the legacy APIs keep their exact semantics, and each setter's docs spell out how it interacts with the others.

cc @alamb @zhuqi-lucas

@haohuaijin
haohuaijin force-pushed the push-decoder-row-group-selections branch from 02cae11 to a9157e1 Compare August 16, 2026 03:24

@zhuqi-lucas zhuqi-lucas left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @haohuaijin for this work!
From the DataFusion side: root fix for apache/datafusion#24352 / apache/datafusion#24355into_builder preserving local selections is exactly it. Core looks correct and well-tested; small notes inline. Happy to take the DataFusion migration (apache/datafusion#24358) once this lands.

Comment thread parquet/src/arrow/async_reader/mod.rs
Comment thread parquet/src/arrow/push_decoder/mod.rs
Comment thread parquet/src/arrow/push_decoder/remaining.rs Outdated
@haohuaijin

Copy link
Copy Markdown
Contributor Author

Thanks for you reviews @zhuqi-lucas, i update in c0369a6

@alamb alamb left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you @haohuaijin and @zhuqi-lucas -- I spent quite a while reading this one in detail; I think it is looking very good -- I had some comment and encapsulation nits which I would like to fix before merging but I don't think they are strictly required

Comment thread parquet/src/arrow/arrow_reader/mod.rs Outdated
Comment thread parquet/src/arrow/arrow_reader/mod.rs
Comment thread parquet/src/arrow/arrow_reader/mod.rs Outdated
Comment thread parquet/src/arrow/arrow_reader/mod.rs
Comment thread parquet/src/arrow/arrow_reader/mod.rs
Comment thread parquet/src/arrow/push_decoder/remaining.rs Outdated
Comment thread parquet/src/arrow/push_decoder/remaining.rs Outdated
Comment thread parquet/src/arrow/push_decoder/remaining.rs Outdated
Comment thread parquet/src/arrow/push_decoder/mod.rs
Comment thread parquet/src/arrow/push_decoder/mod.rs Outdated
@alamb

alamb commented Aug 18, 2026

Copy link
Copy Markdown
Contributor

I also ran cargo llvm-cov and it reports several of the cases in the decoder aren't covered. This might be resolved by refactoring some of the access plans into their own methods

@haohuaijin

haohuaijin commented Aug 19, 2026

Copy link
Copy Markdown
Contributor Author

Thanks for your details reviews and suggestion @alamb , i apply all suggestion and fix coverage issue.

alamb added a commit that referenced this pull request Aug 19, 2026
# Which issue does this PR close?

- Part of #10624.

# Rationale for this change

`RowSelection` exposes `row_count()` (selected rows) and
`skipped_row_count()` (skipped rows), but no way to get the total number
of rows a selection spans. Callers that need the total (e.g. to validate
a selection against a row group's row count, as #10702 does) must call
both methods, which iterates a selector-backed selection twice and
performs two popcounts on a mask-backed selection just to have them
cancel out.

# What changes are included in this PR?

Adds `RowSelection::total_row_count()`, which computes the total in a
single pass:

- selector-backed: one sum over the selectors
- mask-backed: `mask.len()`, O(1) with no popcount

# Are these changes tested?

Yes, a unit test covers both backings plus the empty selection.

# Are there any user-facing changes?

New public method `RowSelection::total_row_count()`. No changes to
existing APIs.

---------

Co-authored-by: Andrew Lamb <andrew@nerdnetworks.org>
@haohuaijin

Copy link
Copy Markdown
Contributor Author

update code to use #10704

MassivePizza pushed a commit to massive-com/arrow-rs that referenced this pull request Aug 19, 2026
# Which issue does this PR close?

- Part of apache#10624.

# Rationale for this change

`RowSelection` exposes `row_count()` (selected rows) and
`skipped_row_count()` (skipped rows), but no way to get the total number
of rows a selection spans. Callers that need the total (e.g. to validate
a selection against a row group's row count, as apache#10702 does) must call
both methods, which iterates a selector-backed selection twice and
performs two popcounts on a mask-backed selection just to have them
cancel out.

# What changes are included in this PR?

Adds `RowSelection::total_row_count()`, which computes the total in a
single pass:

- selector-backed: one sum over the selectors
- mask-backed: `mask.len()`, O(1) with no popcount

# Are these changes tested?

Yes, a unit test covers both backings plus the empty selection.

# Are there any user-facing changes?

New public method `RowSelection::total_row_count()`. No changes to
existing APIs.

---------

Co-authored-by: Andrew Lamb <andrew@nerdnetworks.org>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

parquet Changes to the parquet crate

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Support row-group-local row selections in the Parquet push decoder

3 participants