-
Notifications
You must be signed in to change notification settings - Fork 1.6k
Add TableProvider::scan_with_args
to support pushdown sorting
#17273
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
lots of warnings to resolve, and I'm not sure my handling of Vec<Vec> is correct... but I wanted to post to get the initial sketch out there |
40c9a11
to
04272f0
Compare
@@ -138,6 +138,7 @@ impl MemTable { | |||
) -> Result<Self> { | |||
let schema = t.schema(); | |||
let constraints = t.constraints(); | |||
#[expect(deprecated)] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We could just not deprecate the existing method to (1) reduce the diff here and (2) make sure we want to commit to the new method before deprecating the old one
} = args; | ||
let filters = filters.unwrap_or_default(); | ||
let unsupported_filters = self | ||
.supports_filters_pushdown(&filters.iter().collect_vec())? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm tempted to also do something about supports_filters_pushdown
. I think it could be folded into scan()
(and somewhat already set it up that way since scan_with_args
gets to return unsupported/inexact filters).
I would rework the filter pushdown optimizer so that it pushes all filters into the TableScan structure (without caring about supported / unsupported) and then then when we go from logical -> physical plan we apply a FilterExec for any non-exact filters and a ProjectionExec for the projections.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I recommend doing this as a follow on PR -- and the key consideration would be how disruptive it would be to existing users vs the benefit existing users and new users would get
let known_file_ordering = self.try_create_output_ordering()?; | ||
let desired_file_ordering = match args.preferred_ordering() { | ||
Some(ordering) if !ordering.is_empty() => { | ||
// Prefer the ordering requested by the query to any inherint file ordering | ||
create_ordering(&self.table_schema, &[ordering.to_vec()])? | ||
.first() | ||
.cloned() | ||
} | ||
Some(_) | None => { | ||
// If the query did not request a specific ordering, fall back to any inherent file ordering | ||
known_file_ordering.first().cloned() | ||
} | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not too sure about correctness here. The Vec<Vec<SortExpr>>
is a bit wonky. I think the answer is to add tests / benchmarks proving that the "correct" ordering for files is being chosen.
The actual sort optimization is not working. |
Okay this mostly works other than the fact that This test illustrates what happens:
That said we (Pydantic) don't use that function (nor do we use ListingTable) so just the refactor to make the sorting information available to TableProvider is good enough for us. To get that optimization to everyone using ListingTable / |
I think one proposal that may work is:
|
} | ||
|
||
/// Attempts to push sort expressions down to a TableScan | ||
fn try_push_sort_to_table_scan( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is another place to pay attention
I am out this next week on vacation, but I'll be around and will try and review this PR but it may take me a few days |
I can't remember who,but there were other people who have asked about pushing down order by into scans that might be interested as well. Maybe @suremarc remembers 🤔 |
476d764
to
c352b07
Compare
I've now implemented this but the diff is much larger (full disclosure: I gave Claude the plan, coached it through some mistakes but had it do most of the work, the output looks good to me in general though but needs more human review by me and others). I propose that if we want to move forward this top level PR get some review and if we agree on the big picture I'll split this up into:
I can make an |
TableProvider::scan_with_args
to support pushdown sorting
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you for this PR @adriangb
There are multiple things going on in this PR:
- Update TableProvider API
- Pushdown sorts
- rework how filter pushdown works during physical planning
It might help to split them up into separate PRs
Also in terms of pushdown sort, I feel like it would really help to add an example how how to use it, or even better find some way to use that information to improve one of the built in features of DataFusion (so more users could benefit)
However, I can't think of a good improvement at the moment.
Maybe just an example showing how to use it in a custom table provdider / extend the existing "custom file format" example
@@ -171,6 +172,34 @@ pub trait TableProvider: Debug + Sync + Send { | |||
limit: Option<usize>, | |||
) -> Result<Arc<dyn ExecutionPlan>>; | |||
|
|||
async fn scan_with_args( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this is a great new API and makes a lot of sense as it allows us to iterate / expand the scan API over time with less API disruption
It is also consistent with ScalarUDFImpl::invoke_with_args
To merge this PR I think we should:
- document this function (perhaps move the docs from
scan
here and then direct people to usescan_with_args
with new TableProviders - Migrate all existing code in Datafusion to use
scan_with_args
- Consider deprecating
scan
(maybe we can do this as a follow on PR)
} = args; | ||
let filters = filters.unwrap_or_default(); | ||
let unsupported_filters = self | ||
.supports_filters_pushdown(&filters.iter().collect_vec())? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I recommend doing this as a follow on PR -- and the key consideration would be how disruptive it would be to existing users vs the benefit existing users and new users would get
plan: Arc<dyn ExecutionPlan>, | ||
// Remaining filters that were not completely evaluated during `scan_with_args()`. | ||
// These were previously referred to as "unsupported filters" or "inexact filters". | ||
filters: Vec<Expr>, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this is confusing as the "can the filters be supported" is currently a separate API. I think we should have one or the other but not both.
I realize that scan_with_args
is basically the "create the appropriate physical plan" so logically it makes sense here.
Yep agreed, that's why I laid out a plan in #17273 (comment):
Agreed as well, that's why I've kept this "larger" PR so we can see the e2e change. My hope is that this e2e change can demonstrate a performance improvement using It sounds like you're generally on board with the vision here, so I'll start the work of splitting this PR up. |
This commit adds a new optional field `preferred_ordering` to the `TableScan` logical plan node to support sort pushdown optimizations. Changes include: - Add `preferred_ordering: Option<Vec<SortExpr>>` field to `TableScan` struct - Add `try_new_with_preferred_ordering` constructor method - Update all `TableScan` constructors throughout the codebase to include the new field - Update `Debug`, `PartialEq`, `Hash`, and `PartialOrd` implementations - Update pattern matching in optimizer and other modules The preferred_ordering field is currently not used by any optimization rules but provides the foundation for future sort pushdown implementations. This is part 2 of 2 PRs split from apache#17273 as requested in apache#17273 (comment) 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <[email protected]>
I'll close this PR for now and focus on the breakouts |
I've made an epic to track this work: #17348 |
Thanks -- sorry I have been away. I am back now and will be working through the review backlog |
This commit adds a new optional field `preferred_ordering` to the `TableScan` logical plan node to support sort pushdown optimizations. Changes include: - Add `preferred_ordering: Option<Vec<SortExpr>>` field to `TableScan` struct - Add `try_new_with_preferred_ordering` constructor method - Update all `TableScan` constructors throughout the codebase to include the new field - Update `Debug`, `PartialEq`, `Hash`, and `PartialOrd` implementations - Update pattern matching in optimizer and other modules The preferred_ordering field is currently not used by any optimization rules but provides the foundation for future sort pushdown implementations. This is part 2 of 2 PRs split from apache#17273 as requested in apache#17273 (comment) 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <[email protected]>
This commit adds a new optional field `preferred_ordering` to the `TableScan` logical plan node to support sort pushdown optimizations. Changes include: - Add `preferred_ordering: Option<Vec<SortExpr>>` field to `TableScan` struct - Add `try_new_with_preferred_ordering` constructor method - Update all `TableScan` constructors throughout the codebase to include the new field - Update `Debug`, `PartialEq`, `Hash`, and `PartialOrd` implementations - Update pattern matching in optimizer and other modules The preferred_ordering field is currently not used by any optimization rules but provides the foundation for future sort pushdown implementations. This is part 2 of 2 PRs split from apache#17273 as requested in apache#17273 (comment) 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <[email protected]>
I think the refactor to
scan()
is overall positive: there's already several arguments, it's conceivable we want to add more or allow the TableProvider to return more information.