Repository navigation
feat: physical optimizer rules declare whether they run before or after enforcement - #26979
Open
jayzhan211 wants to merge 3 commits into
Open
jayzhan211 wants to merge 3 commits into
jayzhan211 wants to merge 3 commits into
Conversation
jayzhan211
marked this pull request as ready for review
November 1, 2026 01:37
Contributor
Author
|
Another idea is 3 phase design, analyzer → enforcement → optimizer 🤔 |
Contributor
Author
|
I'll try 3 phase design, turn it to draft |
jayzhan211
marked this pull request as draft
November 1, 2026 02:06
jayzhan211
force-pushed
the
physical-optimizer-phase
branch
from
November 1, 2026 03:13
a7169d6 to
a86d79d
Compare
|
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 |
jayzhan211
marked this pull request as ready for review
November 1, 2026 03:26
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
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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.
Which issue does this PR close?
Rationale for this change
The physical plan meets the distribution and ordering requirements of its operators only after
EnsureRequirements. Whether a rule is correct depends on which side of it the rule runs, but nothing records or checks that: the optimizer contract inoptimizer.rs(every rule returns a valid plan, and the rules can be reordered) listsEnsureRequirementsandOutputRequirementsas exceptions and says nothing about the other rules that run before enforcement, and the planner's final check (#26461) finds the boundary by looking for the nameEnsureRequirements. A rule on the wrong side can return wrong results that the final check does not catch: withLimitPushdownbefore enforcement, 11 sqllogictest queries return wrong rows (measurements).This PR records the side on each rule, with one required method on the existing trait, and has the planner check it.
What changes are included in this PR?
Three commits:
PhysicalOptimizerRule::phase(), a required method, returnsPhysicalOptimizerPhase::Analyzerfor a rule that runs while the requirements are only declared, orPhysicalOptimizerPhase::Optimizerfor a rule that receives a plan meeting them and must return one. The eight default rules up to and includingEnsureRequirementsreturnAnalyzer;OutputRequirementsandFilterPushdownreturn the phase of their mode, so they stay one type each. The planner checks the order once per plan and fails planning when an optimizer rule runs before an analyzer rule, with an error that names both rules and their positions. The final check from Check the final physical plan in the planner and name the rule that broke it #26461 finds the boundary by phase instead of by the nameEnsureRequirements.FFI_PhysicalOptimizerRuleandFFI_SessionRefkeep their layouts, so they stay compatible with DataFusion 55 (fix(ffi): prevent recursive session physical planning #24492 keptFFI_SessionRefcompatible on purpose); a rule received through FFI reportsOptimizer.PhysicalOptimizerPhase::Enforcement, forEnsureRequirements. With two phases, an analyzer rule placed right afterEnsureRequirementspasses the order check and silently gives worse plans (for exampleWindowTopNkeeps the window's sort). With three, the planner requires Analyzer, then Enforcement, then Optimizer, and the final check names the enforcement rule, or no rule when there is none, instead of whichever analyzer rule comes last. This commit needs agreement on the extra phase and can be dropped without changing the first two. If the planner later runs enforcement itself, as EPIC: Separate optimizer rules from correctness transformation rules #26481 plans, this variant goes away with it.The default list, its order, the rule names and
EXPLAIN VERBOSEoutput do not change.Open question on naming: most rules in the
Analyzerphase are performance rules, unlike the logical analyzer, soBeforeEnforcement,EnforcementandAfterEnforcementwould describe the phases by position instead. I keptAnalyzerandOptimizerbecause #26481 and the discussion use them.Compared with #26757:
phase())phase(), required, on the existing traitPhysicalOptimizerPhaseandPhysicalOptimizerRule::phasePhysicalAnalyzerRule,PhysicalAnalyzer, session and builder methods, two FFI types, three rule typesphase()phase()(compile error); an optimizer rule before enforcement, an analyzer rule after it (commit 3),EnsureRequirementsrun again after optimizer rules, and a rule received through FFI placed before enforcement (planning errors)Optimizer, so the order check passes there without checking anythingSessionthat doesn't return the analyzer rules skips them for plans that need no enforcement (plans that need it fail the final check)FFI_SessionRefgains a field, with no version checkWhat is the testing strategy for this PR?
explain.sltchanges.physical_planner.rs: an optimizer rule in front of the default rules fails planning and the error names both rules and their positions; the same rule declaredAnalyzerplans;LimitPushdownmoved beforeEnsureRequirements, an analyzer rule appended withwith_physical_optimizer_rule, andEnsureRequirementsappended again after the optimizer rules each fail planning; with commit 3, an analyzer rule right afterEnsureRequirementsfails planning.sanity_checker.rs: the final check names the enforcement rule when it leaves a requirement unmet, and names no rule when the list has analyzer rules but no enforcement rule; the existing Check the final physical plan in the planner and name the rule that broke it #26461 tests pass unchanged.optimizer.rs: every default rule declares the phase that matches its position.ffi: a rule received through FFI reportsOptimizer, and a rule that comes back to its own library keeps its phase.sort_pushdown.sltinserts a test-only rule beforeEnsureRequirements. It failed planning with the new error until it declaredAnalyzer, which is the change downstream rules placed before enforcement need.Are there any user-facing changes?
Yes, documented in the 56.0.0 upgrade guide. Every
PhysicalOptimizerRuleimplementation must addphase(); most returnPhysicalOptimizerPhase::Optimizer, and a wrapper that only observes another rule returns that rule's phase. A rule list that puts a rule on the wrong side ofEnsureRequirements, or runsEnsureRequirementsagain after optimizer rules, fails planning with an error that names both rules. A rule received through FFI reportsOptimizer; to run one before enforcement, wrap it in a local rule that returnsAnalyzer.