Skip to content

[rust] Add predicates as part of filter pushdown - #3847

Merged
leekeiabstraction merged 1 commit into
apache:mainfrom
leekeiabstraction:rust-predicate-filter-pushdown
Aug 7, 2026
Merged

[rust] Add predicates as part of filter pushdown#3847
leekeiabstraction merged 1 commit into
apache:mainfrom
leekeiabstraction:rust-predicate-filter-pushdown

Conversation

@leekeiabstraction

@leekeiabstraction leekeiabstraction commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Closes [rust] predicate/proto wiring #3846.
  • Adds fluss::predicate: an unresolved predicate tree (Predicate::Leaf/Compound) and a col() builder, the first piece of server-side filter pushdown for Rust log scans.
  • Leaves reference columns by name and hold raw literals; binding to schema field ids and column types happens later, when the predicate is attached to a scan.
  • and/or flatten into n-ary nodes to match the wire representation. The protocol has no negation node, so negation is expressed with ne/not_in.
  • No behaviour change yet — nothing constructs or sends a predicate until the encoder lands.

Test Plan

  • cargo test -p fluss-rs --lib — 592 passed, including 6 new tests covering leaf construction, literal conversion, combinator flattening and the protocol wire codes.
  • cargo fmt / cargo clippy -p fluss-rs --lib clean, apart from expected dead_code on the wire-code helpers the encoder will consume.

🤖 AI-assisted changes - reviewed by human developer

@leekeiabstraction
leekeiabstraction force-pushed the rust-predicate-filter-pushdown branch from f82f8c4 to 4ca085d Compare August 4, 2026 20:58
@leekeiabstraction

Copy link
Copy Markdown
Contributor Author

@charlesdong1991 @fresh-borzoni Appreciate a review here please 🙏

@fresh-borzoni fresh-borzoni 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.

@leekeiabstraction Thank you, left one question, PTAL

//! let p = col("age").gt(30i64).and(col("name").starts_with("A"));
//! ```
//!
//! The server prunes whole Arrow batches by their statistics, so a filtered scan

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.

On the Java side, TableScan throws when the log format is not Arrow, and throws again if a filter is set on a batch scanner. The same code written against this API would silently do nothing instead, which is a hard thing to discover.
Should we mirror those checks when the predicate is attached to a scan?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Agreed, I've added an additional doc line

Setting a predicate on a scan is only supported on log scans over tables with the ARROW log format.

The behaviour to reject for batch scan or non-arrow log will be implemented in PR for #3844


#[test]
fn wire_codes_match_the_protocol() {
assert_eq!(LeafFunction::Equal.code(), 0);

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.

why do we test only 5 out if 13?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Nice catch, addressed.

Add the client-side predicate tree and `col()` builder that server-side
filter pushdown will encode onto the FetchLog request. Column names and
literals stay unresolved until the predicate is bound to a scan schema.
@leekeiabstraction
leekeiabstraction force-pushed the rust-predicate-filter-pushdown branch from 4ca085d to 4475ac2 Compare August 6, 2026 20:45
@leekeiabstraction

Copy link
Copy Markdown
Contributor Author

@fresh-borzoni TY for the review. Addressed your comments, PTAL 🙏

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

looks good to me!

}

/// `column IN (values...)`. An empty `values` matches nothing.
pub fn is_in<V: Into<Literal>>(self, values: impl IntoIterator<Item = V>) -> Predicate {

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.

when i check PredicateBuilder, it emits a real in leaf when >20 literals, otherwise or on equals due to performance reason, maybe nice to add a note not to revisit later

}

#[test]
fn builds_leaf_predicates() {

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.

we likely have no need to test all functions since it's quite straightforward, but fyi here

@fresh-borzoni fresh-borzoni 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.

@leekeiabstraction LGTM, thank you

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.

[rust] predicate/proto wiring

3 participants