Skip to content

feat(optimizer): logical rewrite Filter(row_number() = 1) over PARTITION BY → Aggregate(FIRST_VALUE(... ORDER BY)) - #23824

Open
saadtajwar wants to merge 28 commits into
apache:mainfrom
saadtajwar:saadtajwar/logical-rewrite-filter-top1
Open

saadtajwar wants to merge 28 commits into
apache:mainfrom
saadtajwar:saadtajwar/logical-rewrite-filter-top1

Conversation

@saadtajwar

@saadtajwar saadtajwar commented Jul 22, 2026 •

Copy link
Copy Markdown
Contributor

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 BY partition keys with first_value(col ORDER BY ...), which can pick the winning row per group without a full sort. The rule is gated behind optimizer.enable_row_number_to_aggregate and defaults to off until nested-type GroupsAccumulator support 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 matches Filter(rn = 1 | rn <= 1 | rn < 2) over a single row_number() window with a non-empty PARTITION BY, and rewrites it to Projection -> Aggregate(first_value(...) ORDER BY ...) -> child, preserving the filter's output schema. It supports the direct Filter -> Window shape and Filter -> Projection -> Window when the projection passes the row_number column through unchanged.

A new config option datafusion.optimizer.enable_row_number_to_aggregate was added, and config docs and information_schema.slt are 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 in docs/source/user-guide/configs.md and appears in information_schema

@github-actions github-actions Bot added documentation Improvements or additions to documentation optimizer Optimizer rules sqllogictest SQL Logic Tests (.slt) common Related to common crate labels Jul 22, 2026
Comment thread datafusion/common/src/config.rs Resolved
let (name, op, val) = match (&**left, &**right) {
(
Expr::Column(Column { name, .. }),
Expr::Literal(ScalarValue::UInt64(Some(val)), _),

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.

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<'_>> {

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.

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 {

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.

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 {}

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.

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)

@github-actions github-actions Bot added the auto detected api change Auto detected API change label Jul 22, 2026
@github-actions github-actions Bot added the core Core DataFusion crate label Jul 23, 2026
@codecov-commenter

codecov-commenter commented Jul 23, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 77.12082% with 89 lines in your changes missing coverage. Please review.
✅ Project coverage is 81.44%. Comparing base (1f5be0e) to head (302b9f6).
⚠️ Report is 36 commits behind head on main.

Files with missing lines Patch % Lines
datafusion/optimizer/src/replace_filter_top1.rs 77.06% 47 Missing and 42 partials ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main   #23824      +/-   ##
==========================================
- Coverage   81.45%   81.44%   -0.01%     
==========================================
  Files        1118     1119       +1     
  Lines      399555   399944     +389     
  Branches   399555   399944     +389     
==========================================
+ Hits       325462   325754     +292     
- Misses      55097    55147      +50     
- Partials    18996    19043      +47     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@saadtajwar
saadtajwar marked this pull request as ready for review July 23, 2026 17:17
@saadtajwar

Copy link
Copy Markdown
Contributor Author

@zhuqi-lucas & @tohuya6 - ready for review for #23603! Looking forward to the feedback! 😁 🎉

@saadtajwar saadtajwar changed the title feat(optimizer): logical rewrite Filter(row_number() = 1) over PARTITION BY → Aggregate(FIRST_VALUE(... ORDER BY)) (DRAFT) feat(optimizer): logical rewrite Filter(row_number() = 1) over PARTITION BY → Aggregate(FIRST_VALUE(... ORDER BY)) Jul 23, 2026
@saadtajwar

Copy link
Copy Markdown
Contributor Author

Another note: I'll make a follow-up issue for this comment from @2010YOUY01 and address in a subsequent PR!

@saadtajwar

Copy link
Copy Markdown
Contributor Author

@zhuqi-lucas - thanks again for taking the time to review! Have pushed a couple of commits that should address your comments - please let me know your thoughts! Thanks!

@saadtajwar
saadtajwar requested a review from zhuqi-lucas August 9, 2026 16:57
@saadtajwar

Copy link
Copy Markdown
Contributor Author

@zhuqi-lucas - just wanted to follow up here! Thanks!

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

LGTM

@alamb

alamb commented Aug 27, 2026

Copy link
Copy Markdown
Contributor

@tohuya6 does this PR look good to you?

@alamb

alamb commented Oct 24, 2026

Copy link
Copy Markdown
Contributor

This PR appears to be waiting on the author to respond to reviewer feedback so marking as draft. Please mark ready to review when it is ready for another look

@alamb
alamb marked this pull request as draft October 24, 2026 10:44
@saadtajwar
saadtajwar marked this pull request as ready for review October 28, 2026 14:29
@saadtajwar
saadtajwar requested a review from alamb October 28, 2026 14:29
@saadtajwar

Copy link
Copy Markdown
Contributor Author

This PR appears to be waiting on the author to respond to reviewer feedback so marking as draft. Please mark ready to review when it is ready for another look

Hi @alamb - thanks for checking in here! Just fixed the merge conflict & marked the outstanding reviews as resolved - this should be good to go! Thanks in advance for your time! :)

@alamb

alamb commented Nov 2, 2026

Copy link
Copy Markdown
Contributor

@zhuqi-lucas do you think this one is ready to go?

@zhuqi-lucas

Copy link
Copy Markdown
Contributor

Thanks @alamb @tohuya6 , I think it's ready after resolve conflicts, and turning it on by default is tracked in #24761.

…tajwar/logical-rewrite-filter-top1

Resolve configs.md conflict by regenerating from merged config.
@saadtajwar

Copy link
Copy Markdown
Contributor Author

Thanks @alamb @tohuya6 , I think it's ready after resolve conflicts, and turning it on by default is tracked in #24761.

Thanks @zhuqi-lucas ! Just resolved those merge conflicts, and I'm happy to fast follow with a PR for #24761 when this is in! Appreciate you all :)

@github-actions github-actions Bot removed the auto detected api change Auto detected API change label Nov 3, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

common Related to common crate core Core DataFusion crate documentation Improvements or additions to documentation optimizer Optimizer rules sqllogictest SQL Logic Tests (.slt)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Logical rewrite: Filter(row_number() = 1) over PARTITION BY → Aggregate(FIRST_VALUE(... ORDER BY))

5 participants