-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Add plan optimizer pass that extracts join keys and uses them to construct pre-filters for inner joins #22996
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
Merged
rapids-bot
merged 87 commits into
NVIDIA:release/26.08
from
pentschev:cudf-polars/join-domain-prefilter
Jul 22, 2026
Merged
Changes from all commits
Commits
Show all changes
87 commits
Select commit
Hold shift + click to select a range
03ef8ad
Add dynamic join key prefilter planning
pentschev 6b71a95
Add generic derived join-domain prefilters
pentschev 09f0474
Reject boolean join prefilter key limits
pentschev 6f95d33
Consolidate optional config converters
pentschev 519cd1d
Remove legacy bloom prefilter threshold path
pentschev d1cd0a1
Merge remote-tracking branch 'upstream/main' into cudf-polars/join-pr…
pentschev 09a744e
Fix linting
pentschev 7da769c
Accept integer join prefilter thresholds
pentschev fd6ba23
Implement default in-place without additional variable
pentschev fbe886b
Document join prefilter key prefix limit
pentschev f01e62e
Use dataclass conversion for join prefilter trace
pentschev ffd5e8f
Remove redundant join prefilter decision state
pentschev 6bee038
Remove unused keyless prefilter skip
pentschev 47f2ce4
Assert matching join prefilter key counts
pentschev 97466a3
Track only join prefilter apply side
pentschev 543a73d
Remove unused prefilter partition trace
pentschev 0dfc80d
Clarify dynamic join collective IDs
pentschev 94e132c
Document actor trace extra metadata
pentschev 918417d
Fix docstring linting
pentschev 29b950e
Merge remote-tracking branch 'upstream/main' into cudf-polars/join-pr…
pentschev c75249d
Fix optional join prefilter default typing
pentschev 42871f0
Merge remote-tracking branch 'upstream/main' into cudf-polars/join-pr…
pentschev dc1ffb2
Merge branch 'cudf-polars/join-prefilter-core' into cudf-polars/join-…
pentschev 0dafc3e
Merge remote-tracking branch 'upstream/main' into cudf-polars/join-pr…
pentschev d81c198
Merge remote-tracking branch 'upstream/main' into cudf-polars/join-do…
pentschev 4cc6213
Use column expressions for join domain keys
pentschev c148219
Use shared DAG utilities for domain prefilter planning
pentschev fe17507
Restore optional float config conversion
pentschev 1c0e3df
Reanalyze rewritten join-domain subtrees
pentschev 702c6ad
Generalize default factory result typing
pentschev a37608d
Document join prefilter selection inputs
pentschev 6bc7559
Simplify join prefilter decision flow
pentschev 5ac7e79
Merge remote-tracking branch 'upstream/main' into cudf-polars/join-pr…
pentschev 903084d
Fix missing coverage
pentschev 4538aef
Fix one more missing coverage
pentschev c55aa6c
Merge remote-tracking branch 'upstream/main' into cudf-polars/join-pr…
pentschev 4a938fc
Merge branch 'cudf-polars/join-prefilter-core' into cudf-polars/join-…
pentschev d1936aa
Merge remote-tracking branch 'upstream/main' into cudf-polars/join-pr…
pentschev eab9726
Merge branch 'cudf-polars/join-prefilter-core' into cudf-polars/join-…
pentschev 47ac908
Merge remote-tracking branch 'upstream/main' into cudf-polars/join-do…
pentschev b3049c2
Add missing coverage for JOIN_DOMAIN_PREFILTER_TRACE
pentschev 1a0d1f5
Merge remote-tracking branch 'upstream/main' into cudf-polars/join-do…
pentschev 0b93f55
Simplify join-domain candidate selection
pentschev 0226a83
Track column bindings through domain subplans
pentschev 8bd9023
Keep domain prefilter replacement side-scoped
pentschev 9511c94
Format join-domain review updates
pentschev ae443dd
Trace bindings through filtering joins
pentschev 571181b
Check cheap domain guards first
pentschev cf59505
Inline target prefilter construction
pentschev 3a57f14
Clarify bound key projection
pentschev 4261279
Decouple join-domain prefilter configuration
pentschev ff32f43
Test nullable join-domain prefilters
pentschev c21cc13
Merge remote-tracking branch 'upstream/main' into cudf-polars/join-do…
pentschev 011ba87
Validate join-domain prefilter configuration
pentschev a5041ec
Merge remote-tracking branch 'upstream/main' into cudf-polars/join-do…
pentschev 7188a61
Use optional join-domain prefilter options
pentschev eabbdba
Merge remote-tracking branch 'upstream/main' into cudf-polars/join-do…
pentschev 9cf3029
Fix join-domain prefilter CI regressions
pentschev c07ca0b
Merge remote-tracking branch 'upstream/main' into cudf-polars/join-do…
pentschev fbc56ec
Merge remote-tracking branch 'upstream/main' into cudf-polars/join-do…
pentschev 920fdd9
Merge remote-tracking branch 'upstream/main' into pull-request/22996
wence- a5f919b
New optimize_with_stats stage in lowering
wence- 3c6adf8
Introduce utility to map value domains of columns
wence- 135af45
Use column_domain_bindings in select_column_targets
wence- 7e58320
WIP: gather plan facts for join prefilter in one place
wence- 4ae2b15
Refactor semijoin pushdown candidates
wence- 4c2e988
docstring
wence- 6da9b87
fixup
wence- ccc9503
More refactoring
wence- 7c8393a
Disambiguate column lineage with edges
wence- f9926f8
Remove Cache nodes when lowering to streaming engine
wence- 408619f
Merge branch 'main' into pull-request/22996
wence- e26e1ac
Remove treatment of Cache nodes in lowering and raise if we see one
wence- 97e693a
Correctly handle pushdown through self-joins
wence- 3a46fcf
Add high-level description of what is going on
wence- 7904b20
Actually test we get the right answers
wence- 8817181
Assert optimised query in way independent of Polars version
wence- 6f16f9f
Assert more correctness in tests
wence- 1ce2e59
Remove inappropriate intimacy in tests
wence- f69f2f2
Update docstring with better explanation
wence- 68d01bd
Remove double checking of source being None
wence- 7df367b
Rename to join filter pushdown, provide more explanation
wence- 5c24361
Rename module
wence- 9638a58
Finish renaming, add more explanatory comments
wence- 3d9bfe4
Final renaming
wence- 641832f
Merge branch 'release/26.08' into cudf-polars/join-domain-prefilter
wence- 51303be
Merge branch 'release/26.08' into cudf-polars/join-domain-prefilter
wence- File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
143 changes: 143 additions & 0 deletions
143
python/cudf_polars/cudf_polars/dsl/utils/column_domain.py
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,143 @@ | ||
| # SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. | ||
| # SPDX-License-Identifier: Apache-2.0 | ||
|
|
||
| """Utilities for tracking column value domains between IR nodes.""" | ||
|
|
||
| from __future__ import annotations | ||
|
|
||
| from dataclasses import dataclass | ||
| from functools import singledispatch | ||
| from typing import TYPE_CHECKING | ||
|
|
||
| from cudf_polars.dsl import expr | ||
| from cudf_polars.dsl.ir import ( | ||
| Distinct, | ||
| Filter, | ||
| GroupBy, | ||
| HStack, | ||
| Join, | ||
| Projection, | ||
| Select, | ||
| Slice, | ||
| Sort, | ||
| ) | ||
|
|
||
| if TYPE_CHECKING: | ||
| from collections.abc import Mapping | ||
|
|
||
| from cudf_polars.dsl.ir import IR | ||
|
|
||
| __all__ = [ | ||
| "ColumnBinding", | ||
| "ColumnLineage", | ||
| "ColumnRef", | ||
| "column_domain_bindings", | ||
| ] | ||
|
|
||
|
|
||
| @dataclass(frozen=True) | ||
| class ColumnBinding: | ||
| """A direct binding to a named column on a specific child edge.""" | ||
|
|
||
| child_index: int | ||
| name: str | ||
|
|
||
|
|
||
| @dataclass(frozen=True) | ||
| class ColumnRef: | ||
| """A named column produced by an IR node.""" | ||
|
|
||
| node: IR | ||
| name: str | ||
|
|
||
|
|
||
| @dataclass(frozen=True) | ||
| class ColumnLineage: | ||
| """Persistent value-domain lineage, sharing suffixes across DAG branches.""" | ||
|
|
||
| column: ColumnRef | ||
| source: ColumnLineage | None = None | ||
| source_child_index: int | None = None | ||
| """Child edge leading to ``source``, or None if there is no source.""" | ||
|
|
||
|
|
||
| @singledispatch | ||
| def column_domain_bindings(node: IR) -> Mapping[str, ColumnBinding]: | ||
| """ | ||
| Map output columns to child columns containing their value domains. | ||
|
|
||
| For every ``output_name -> ColumnBinding(child_index, input_name)`` binding, | ||
| every value appearing in ``node[output_name]`` is guaranteed to appear in | ||
| ``node.children[child_index][input_name]``. Row order, multiplicity, and | ||
| cardinality are not preserved. | ||
|
|
||
| If a name in ``node.schema`` does not appear in the mapping it means | ||
| that it was not possible to derive a relationship between the domain of | ||
| the output and input values for that column. | ||
| """ | ||
| return {} | ||
|
|
||
|
|
||
| @column_domain_bindings.register(Select) | ||
| def _(node: Select) -> Mapping[str, ColumnBinding]: | ||
| return { | ||
| item.name: ColumnBinding(0, item.value.name) | ||
| for item in node.exprs | ||
| if isinstance(item.value, expr.Col) | ||
| } | ||
|
|
||
|
|
||
| @column_domain_bindings.register(HStack) | ||
| def _(node: HStack) -> Mapping[str, ColumnBinding]: | ||
| child = node.children[0] | ||
| replaced = {item.name for item in node.columns} | ||
| return { | ||
| name: ColumnBinding(0, name) for name in child.schema if name not in replaced | ||
| } | { | ||
| item.name: ColumnBinding(0, item.value.name) | ||
| for item in node.columns | ||
| if isinstance(item.value, expr.Col) | ||
| } | ||
|
|
||
|
|
||
| @column_domain_bindings.register(GroupBy) | ||
| def _(node: GroupBy) -> Mapping[str, ColumnBinding]: | ||
| return { | ||
| key.name: ColumnBinding(0, key.value.name) | ||
| for key in node.keys | ||
| if isinstance(key.value, expr.Col) | ||
| } | ||
|
|
||
|
|
||
| @column_domain_bindings.register(Join) | ||
| def _(node: Join) -> Mapping[str, ColumnBinding]: | ||
| left, right = node.children | ||
| how = node.options[0] | ||
| if how in ("Semi", "Anti"): | ||
| return { | ||
| name: ColumnBinding(0, name) for name in node.schema if name in left.schema | ||
| } | ||
| if how != "Inner": | ||
| return {} | ||
|
|
||
| bindings = {name: ColumnBinding(0, name) for name in left.schema} | ||
| suffix = node.options[3] | ||
| for name in right.schema: | ||
| output_name = f"{name}{suffix}" if name in left.schema else name | ||
| if output_name in node.schema: | ||
| bindings[output_name] = ColumnBinding(1, name) | ||
| return bindings | ||
|
|
||
|
|
||
| @column_domain_bindings.register(Distinct) | ||
| @column_domain_bindings.register(Filter) | ||
| @column_domain_bindings.register(Projection) | ||
| @column_domain_bindings.register(Slice) | ||
| @column_domain_bindings.register(Sort) | ||
| def _( | ||
| node: Distinct | Filter | Projection | Slice | Sort, | ||
| ) -> Mapping[str, ColumnBinding]: | ||
| child = node.children[0] | ||
| return { | ||
| name: ColumnBinding(0, name) for name in node.schema if name in child.schema | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
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 don't think any Quent things should be considered a blocker right now, but I'll need to check on this.
On
main, we emit two plans, which show up in the DAG view as the Logical and Physical plans. With this change, I think we emitplan, which comes fromoptimized(lowering.optimized)ir:lowering.lowered, with ties back tolowering.optimizedvialogical_op_by_id)and we don't emit the original, unlowered plan anywhere. We can emit as many or few plans as we want here. Quent is flexible.
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.
OK, so
optimizedis the "logical" plan we now consider for execution. Andloweredis the thing we actually execute. I suppose we want a stage from "input -> optimized" too?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.
Possibly, if we think it'd be valuable to see what the optimizer changed. But we can handle that later.