Skip to content

GH-3596: Add RowRanges.Builder for incremental construction from selected row indices#3597

Merged
wgtmac merged 9 commits into
apache:masterfrom
peter-toth:GH-3596-row-ranges-builder
Jul 14, 2026
Merged

GH-3596: Add RowRanges.Builder for incremental construction from selected row indices#3597
wgtmac merged 9 commits into
apache:masterfrom
peter-toth:GH-3596-row-ranges-builder

Conversation

@peter-toth

@peter-toth peter-toth commented Jun 5, 2026

Copy link
Copy Markdown
Contributor

Rationale for this change

This PR is based on @mbutrovich's previous work.

Opening up APIs needed by a later materialization feature in Spark. External readers need to assemble a RowRanges incrementally from a stream of selected row indices (e.g. produced by a downstream filter or join) without having to know page boundaries ahead of time.

Since RowRanges is the type external readers must work with, this PR also promotes it out of the org.apache.parquet.internal.* package into a supported location.

What changes are included in this PR?

  • Adds a RowRanges.Builder for incremental construction. It accepts a strictly-increasing sequence of selected rows via addSelectedRow(long rowIndex) — or ranges via addSelectedRange(long from, long to), with addSelectedRow delegating to it — and coalesces consecutive/adjacent entries into Ranges. Out-of-order, overlapping, duplicate, negative, or from > to calls throw IllegalArgumentException. build() returns an independent snapshot, so the builder can be reused afterwards. Builder is final with a private constructor.
  • Moves RowRanges (with Range and Builder) from org.apache.parquet.internal.filter2.columnindex to org.apache.parquet.filter2.columnindex.
  • Preserves backward compatibility for the one released public API that referenced the old type: a deprecated org.apache.parquet.internal.filter2.columnindex.RowRanges subclass is kept, and ParquetFileReader.readFilteredRowGroup(int, RowRanges) gains a deprecated overload (taking the old internal type) that delegates to the new one. Both are marked for removal in 2.0.
  • Reimplements RowRanges.create(...) on top of the new Builder and keeps it on the deprecated internal.RowRanges (preserving the released internal.RowRanges.create(...) signature), so the public RowRanges no longer references the internal OffsetIndex; ColumnIndexFilter now builds via the public builder.
  • Other internal-only surfaces (RowRanges statics, Range, ColumnIndexFilter return types) are relocated without a bridge, as the internal package carries no compatibility guarantee.

Are these changes tested?

Yes. TestRowRanges covers single/multiple/coalesced ranges, the empty builder case, the out-of-order/duplicate/negative rejection paths, addSelectedRange coalescing and overlap/from > to rejection, the Long.MAX_VALUE edge case, and that build() returns a snapshot independent of the builder. Existing TestColumnIndexFilter and TestParquetFileReaderRowRanges continue to pass against the relocated type.

Are there any user-facing changes?

Yes. RowRanges (and Range/Builder) is now available at the supported package org.apache.parquet.filter2.columnindex. The old org.apache.parquet.internal.filter2.columnindex.RowRanges and ParquetFileReader.readFilteredRowGroup(int, <internal> RowRanges) remain as deprecated shims and will be removed in 2.0.

Closes #3596

…m selected row indices

### Rationale for this change

Opening up APIs needed by a later materialization feature in Spark. External readers need to assemble a RowRanges incrementally from a stream of selected row indices (e.g. produced by a downstream filter or join) without having to know page boundaries ahead of time.

### What changes are included in this PR?

Adds a Builder to RowRanges that takes a strictly-increasing sequence of selected row indices via addSelected(long) and coalesces consecutive indices into Range entries. Out-of-order or duplicate calls throw IllegalArgumentException.

### Are these changes tested?

Yes. TestRowRanges covers single/multiple/coalesced ranges, the empty builder case, and the out-of-order/duplicate rejection paths.

### Are there any user-facing changes?

No.

Closes apache#3596

Co-authored-by: Matt Butrovich <mbutrovich@gmail.com>
@peter-toth peter-toth force-pushed the GH-3596-row-ranges-builder branch from e11f3d9 to 58857df Compare June 5, 2026 13:56
@peter-toth

Copy link
Copy Markdown
Contributor Author

@wgtmac, could you please take a look at this PR?

@wgtmac wgtmac left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Codex review, focused only on API design.

@wgtmac

wgtmac commented Jun 30, 2026

Copy link
Copy Markdown
Member

Do you have any advice on this new API? @gszadovszky

Peter Toth added 6 commits June 30, 2026 14:42
…dRowGroup compatibility

Recreate org.apache.parquet.internal.filter2.columnindex.RowRanges as a
deprecated subclass of the relocated org.apache.parquet.filter2.columnindex.RowRanges
so the released ParquetFileReader#readFilteredRowGroup(int, RowRanges) signature
keeps linking. Add a deprecated readFilteredRowGroup overload taking the old
internal type that delegates to the new one, and drop the japicmp exclusions for
that method now that it is compatible. Other internal-only surfaces (RowRanges
statics, Range, ColumnIndexFilter return types) remain relocated without a bridge.
ParquetFileReader lives in the downstream parquet-hadoop module, so a {@link}
to it cannot be resolved from parquet-column's javadoc. Use a {@code} reference
instead.
@peter-toth

Copy link
Copy Markdown
Contributor Author

@wgtmac, @gszadovszky, please let me know how to proceed with this PR.

@peter-toth

Copy link
Copy Markdown
Contributor Author

Thanks @wgtmac. I'm a bit confused, so let me lay out how things stand now:

How old and new relate. The real RowRanges implementation now lives at the new public location org.apache.parquet.filter2.columnindex.RowRanges. The old org.apache.parquet.internal.filter2.columnindex.RowRanges is kept as a thin deprecated subclass of it (Old extends New), purely so the released signatures keep linking.

The method you commented on. That overload is the deprecated one — readFilteredRowGroup(int, internal.RowRanges) is retained (and @Deprecated) as a shim that delegates to the new readFilteredRowGroup(int, public.RowRanges). So we already keep the original method; it's not going away until 2.0.

Why the excludes exist. They're not caused by readFilteredRowGroup — that signature is preserved, so japicmp is happy with it. The excludes are for the internal package itself: making Old an empty subclass moved its members (statics, Range) into New, and ColumnIndexFilter.calculateRowRanges now returns the public RowRanges. Since internal.* carries no backward-compat guarantee, those are intentional and that's what the three excludes cover.

On dropping the excludes. Reversing the inheritance (New extends Old) would indeed let us avoid the excludes now — but I think it just moves the cost to 2.0: when we delete the old parent, New loses its superclass and all the inherited members, so we'd have to pull them up into New and add fresh excludes for the class we actually keep. The current direction makes Old a deletable leaf, so in 2.0 these three excludes just get removed along with it — no new ones.

The other option is to fully separate New from Old (no shared type), but then ColumnIndexFilter and the range-algebra helpers would have to be duplicated to produce the new type (or we add an Old → New conversion layer), which felt worse than three internal-package excludes.

Do you have a concrete approach in mind? I'm happy to explore it.

@peter-toth

Copy link
Copy Markdown
Contributor Author

@wgtmac , any suggestion on the above? Do you think the current implementation is fine or shall try to fully separate old and new RowRanges?

@wgtmac wgtmac left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Sorry for the delay. Now it overall looks good. I just have two more comments. Let me know what you think. Thanks!

Peter Toth added 2 commits July 11, 2026 09:34
Check rowIndex <= runEnd before evaluating runEnd + 1, so the
strictly-increasing guard keeps working when runEnd is Long.MAX_VALUE
(runEnd + 1 would otherwise overflow to Long.MIN_VALUE and let a
follow-up index start a bogus new run).
… move it to internal.RowRanges

Adds a range-level Builder.addSelectedRange(from, to) (addSelectedRow now
delegates to it), so RowRanges can be built from page ranges through the
public API. create() is reimplemented on top of the builder and moved to
the deprecated internal.RowRanges shim, where the released 1.17
internal.RowRanges.create(...) signature lived. The public RowRanges no
longer references the internal OffsetIndex, and ColumnIndexFilter now builds
via the public builder rather than a deprecated method.

Behavior note: the builder rejects overlapping ranges instead of silently
unioning them (as the old add() did). Offset-index pages never overlap, so
the filter path is unaffected; TestRowRanges#testCreate is updated to feed
realistic adjacent (non-overlapping) page ranges.

@wgtmac wgtmac left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Thanks @peter-toth, now it looks good to me. It would be good to wait for @gszadovszky to take a look in case I missed anything.

@gszadovszky gszadovszky 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.

It looks good to me, too.
Thank you @peter-toth and @wgtmac!

@wgtmac wgtmac merged commit 06ed267 into apache:master Jul 14, 2026
5 checks passed
@peter-toth

Copy link
Copy Markdown
Contributor Author

Thank you @wgtmac and @gszadovszky for the review!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add a RowRanges.Builder for incremental construction from selected row indices

3 participants