Skip to content

feat(core): RangeFilterExec — sorted-input per-partition range filter (purely additive) - #2262

Merged
avantgardnerio merged 3 commits into
apache:mainfrom
avantgardnerio:brent/rangefilterexec
Aug 9, 2026
Merged

feat(core): RangeFilterExec — sorted-input per-partition range filter (purely additive)#2262
avantgardnerio merged 3 commits into
apache:mainfrom
avantgardnerio:brent/rangefilterexec

Conversation

@avantgardnerio

@avantgardnerio avantgardnerio commented Aug 8, 2026

Copy link
Copy Markdown
Contributor

Summary

Extracted from #2223 (working parallel-windows acceleration PR).

Purely additive. Adds RangeFilterExec alongside PerPartitionFilterExec. No in-tree callers, no mutations. Follow-up PRs wire it into the parallel-window path.

RangeFilterExec is the sorted-input specialisation of PerPartitionFilterExec:

  • Applies a half-open [lo, hi) predicate per input partition, widened by a lower/upper halo (for RANGE-frame boundary context in bounded window ops).
  • Fast path when the input advertises ordering on routing_expr ascending: batch min/max bound the range, so batches entirely inside/outside the window skip filter_record_batchRecordBatch::slice (zero-copy) or Arc-clone pass-through.
  • Falls back to filter_record_batch when the input isn't sorted or has nulls in the routing column.

Design notes

  • Bounds are late-bound via resolve_bounds(). Wire encode refuses before resolution — over-the-wire plans always carry resolved bounds. Two constructors: try_new_pending (rule path — bounds resolved later by the scheduler) and try_new_resolved (wire decode + task-restriction path).
  • Primitive-generic APIs: halos and bounds use ScalarValue at the API + proto surface so future KLL migration to non-Float64 numeric primitives doesn't break callers. Internal impl is Float64-only today; errors loudly on other numeric types.
  • Scope: RFE is a pure per-partition filter. Range-partitioning concerns (cuts → per-partition ranges, task-slice mapping) belong to the scheduler in follow-up work; not in this PR.

Test plan

  • cargo test -p ballista-core --lib -- range_filter — 22 tests (fast/slow paths, halo widening, sorted-key detection, null handling, pending/resolved constructors, resolve validation)
  • Serde roundtrip + pending-refuses-to-serialize tests
  • cargo test --workspace — 1200 pass / 0 fail / 8 ignored
  • cargo clippy --all-targets clean
  • cargo fmt --all --check clean
  • RUSTDOCFLAGS='-D warnings' cargo doc --workspace --no-deps clean

… (purely additive)

Adds `RangeFilterExec`: filter that applies a per-input-partition
half-open range predicate, widened by the operator's halo. Sibling of
`PerPartitionFilterExec` (which handles arbitrary predicates); RFE
specialises to `[lo, hi)` ranges over a `routing_expr` and gets a
fast path for sorted inputs (binary-search slice + Arc-clone
pass-through when a batch is entirely inside/outside the window).

- Bounds are late-bound via `resolve_bounds`; wire encode refuses
  before resolution. Constructors: `try_new_pending`, `try_new_resolved`.
- Halos + bounds use `ScalarValue` at the API + proto surface (generic
  over Arrow primitives); internal impl is Float64-only today and
  errors on non-Float64 until KLL widens support.
- No in-tree callers — this is purely additive.
- Includes serde roundtrip + fast/slow path unit tests.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@avantgardnerio

Copy link
Copy Markdown
Contributor Author

@phillipleblanc FYI

DataType was only referenced textually in the test module (via
`DataType::Float64` in schema builders); the non-test build treated
the top-level import as unused, papered over by a `#[allow(dead_code)]
fn _touch_datatype(_: DataType)`. Move the import into `mod tests`
and remove the workaround.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Comment thread ballista/core/src/execution_plans/mod.rs Outdated
Comment thread ballista/core/src/execution_plans/range_filter.rs

@andygrove andygrove 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 @avantgardnerio. LGTM with a couple of nits.

…ignatures render as rustdoc links

Both aliases were reachable only through structural spelling — `Vec<(Option<ScalarValue>, Option<ScalarValue>)>` for the raw form, `Vec<(Option<f64>, Option<f64>)>` for the widened form — because `range_filter` is a private module and neither alias was `pub`-re-exported. Rustdoc rendered `try_new_resolved` / `raw_bounds` / `widened_bounds` signatures with bare unlinkable names as a result. Bumping both to `pub` and re-exporting from `execution_plans` restores the signature → alias-page links.

Addresses andygrove's review nits on apache#2262.
@avantgardnerio
avantgardnerio merged commit 526918f into apache:main Aug 9, 2026
23 checks passed
@avantgardnerio
avantgardnerio deleted the brent/rangefilterexec branch August 9, 2026 15:21
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.

3 participants