feat(optimizer): logical rewrite Filter(row_number() = 1) over PARTITION BY → Aggregate(FIRST_VALUE(... ORDER BY))#23824
Conversation
| /// efficient once the columnar nested-type `GroupsAccumulator` fast | ||
| /// path is available; enabling it without that support can route wide | ||
| /// payloads onto the slow per-group accumulator path. | ||
| pub enable_row_number_to_aggregate: bool, default = false |
There was a problem hiding this comment.
I assume we wanted a config for this - and can change the default to true and remove the "disabled by default..." comment when #23601 is closed out - but if not happy to remove!
| let (name, op, val) = match (&**left, &**right) { | ||
| ( | ||
| Expr::Column(Column { name, .. }), | ||
| Expr::Literal(ScalarValue::UInt64(Some(val)), _), |
There was a problem hiding this comment.
When I was testing it looked like the type was always UInt64 - is that a valid assumption? Or should I match on all int types?
|
|
||
| /// Recognize either `Filter -> Window` or `Filter -> Projection -> Window`, | ||
| /// where the window is a single `row_number()` with a non-empty `PARTITION BY`. | ||
| fn validate_window_input(input: &Arc<LogicalPlan>) -> Option<WindowTop1<'_>> { |
There was a problem hiding this comment.
I was a bit unsure about this function name/the general structure and return values 😅 happy to receive any feedback here!
| } | ||
|
|
||
| /// Unalias expression reference | ||
| fn unalias(mut expr: &Expr) -> &Expr { |
There was a problem hiding this comment.
BTW, looks like this unalias pattern is used in push_down_filter & eliminate_outer_join (although those two are recursive) - happy to consolidate this to a helper in a follow-up if we want!
| /// - window has a `PARTITION BY` clause | ||
| /// - gated behind the `optimizer.enable_row_number_to_aggregate` config option (off by default) | ||
| #[derive(Default, Debug)] | ||
| pub struct ReplaceFilterTop1 {} |
There was a problem hiding this comment.
Happy to rename this optimizer rule, I recognize that having a number in here might be a bit awkward (recurring theme throughout this PR of me not being good at naming things haha)
|
Thank you for opening this pull request! Reviewer note: cargo-semver-checks reported the current version number is not SemVer-compatible with the changes in this pull request (compared against the base branch). Details |
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #23824 +/- ##
==========================================
- Coverage 80.75% 80.74% -0.01%
==========================================
Files 1089 1090 +1
Lines 369164 369539 +375
Branches 369164 369539 +375
==========================================
+ Hits 298127 298402 +275
- Misses 53282 53338 +56
- Partials 17755 17799 +44 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
|
@zhuqi-lucas & @tohuya6 - ready for review for #23603! Looking forward to the feedback! 😁 🎉 |
|
Another note: I'll make a follow-up issue for this comment from @2010YOUY01 and address in a subsequent PR! |
Which issue does this PR close?
Rationale for this change
DF plans the common "top-1 per group" pattern (
ROW_NUMBER() OVER (PARTITION BY ... ORDER BY ...) WHERE rn = 1) as a full window sort followed by a filter - this is expensive on wide payloads because it materializes and ranks every row before discarding most.This PR adds a logical optimizer rule that recognizes the pattern and rewrites it to
GROUP BYpartition keys withfirst_value(col ORDER BY ...), which can pick the winning row per group without a full sort. The rule is gated behindoptimizer.enable_row_number_to_aggregateand defaults to off until nested-typeGroupsAccumulatorsupport lands in #23601/#23628, since the rewritten aggregate only becomes memory-efficient once that fast path exists.What changes are included in this PR?
Adds
ReplaceFilterTop1, a new logical optimizer rule that matchesFilter(rn = 1 | rn <= 1 | rn < 2)over a singlerow_number()window with a non-emptyPARTITION BY, and rewrites it toProjection -> Aggregate(first_value(...) ORDER BY ...) -> child, preserving the filter's output schema. It supports the directFilter -> Windowshape andFilter -> Projection -> Windowwhen the projection passes the row_number column through unchanged.A new config option
datafusion.optimizer.enable_row_number_to_aggregatewas added, and config docs andinformation_schema.sltare updated accordingly.Are these changes tested?
Yes
Are there any user-facing changes?
Yes, users can opt in with
SET datafusion.optimizer.enable_row_number_to_aggregate = true. The new setting is documented indocs/source/user-guide/configs.mdand appears ininformation_schema