Skip to content

refactor: move planning APIs to session crate#23842

Open
timsaucer wants to merge 7 commits into
apache:mainfrom
timsaucer:refactor/planner-contracts-session
Open

refactor: move planning APIs to session crate#23842
timsaucer wants to merge 7 commits into
apache:mainfrom
timsaucer:refactor/planner-contracts-session

Conversation

@timsaucer

Copy link
Copy Markdown
Member

Which issue does this PR close?

Rationale for this change

This PR unlocks using a query planner across the FFI boundary.

What changes are included in this PR?

Moves these traits to the datafusion-session crate:

  • QueryPlanner
  • PhysicalPlanner
  • ExtensionPlanner
  • PhysicalOptimizerRule
  • PhysicalOptimizerContext

Additionally changed the method signatures from taking &SessionState to &dyn Session.

Adds these methods on Session trait:

  • fn query_planner(&self) -> Arc<dyn QueryPlanner + Send + Sync>
  • fn optimize(&self, plan: &LogicalPlan) -> Result<LogicalPlan>
  • fn physical_optimizers(&self) -> &[Arc<dyn PhysicalOptimizerRule + Send + Sync>]
  • fn statistics_registry(&self) -> Option<&StatisticsRegistry>

Are these changes tested?

Unit tests are added for the new methods.

Are there any user-facing changes?

Yes, users must implement new methods for query_planner for their custom Session. This is not expected to impact many users, since it is most common to use the existing SessionState.

Move planner and physical optimizer interfaces into datafusion-session, convert planner session arguments to &dyn Session, and preserve existing public reexports.\n\nAI Disclosure: This code was written in part by an AI agent.
@timsaucer timsaucer self-assigned this Jul 23, 2026
@timsaucer timsaucer added the api change Changes the API exposed to users of the crate label Jul 23, 2026
@github-actions github-actions Bot added documentation Improvements or additions to documentation optimizer Optimizer rules core Core DataFusion crate ffi Changes to the ffi crate labels Jul 23, 2026
@timsaucer timsaucer changed the title Refactor/planner contracts session refactor: move planning APIs to session crate Jul 23, 2026
@github-actions

github-actions Bot commented Jul 23, 2026

Copy link
Copy Markdown

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
     Cloning apache/main
    Building datafusion v54.1.0 (current)
       Built [ 102.670s] (current)
     Parsing datafusion v54.1.0 (current)
      Parsed [   0.035s] (current)
    Building datafusion v54.1.0 (baseline)
       Built [ 102.259s] (baseline)
     Parsing datafusion v54.1.0 (baseline)
      Parsed [   0.034s] (baseline)
    Checking datafusion v54.1.0 -> v54.1.0 (no change; assume patch)
     Checked [   0.914s] 223 checks: 222 pass, 1 fail, 0 warn, 30 skip

--- failure trait_missing: pub trait removed or renamed ---

Description:
A publicly-visible trait cannot be imported by its prior path. A `pub use` may have been removed, or the trait itself may have been renamed or removed entirely.
        ref: https://doc.rust-lang.org/cargo/reference/semver.html#item-remove
       impl: https://github.com/obi1kenobi/cargo-semver-checks/tree/v0.49.0/src/lints/trait_missing.ron

Failed in:
  trait datafusion::physical_planner::PhysicalPlanner, previously in file /home/runner/work/datafusion/datafusion/target/semver-checks/git-apache_main/938e491179569ed166312dde33b8e39714eb69d5/datafusion/core/src/physical_planner.rs:126
  trait datafusion::execution::context::QueryPlanner, previously in file /home/runner/work/datafusion/datafusion/target/semver-checks/git-apache_main/938e491179569ed166312dde33b8e39714eb69d5/datafusion/core/src/execution/context/mod.rs:2192
  trait datafusion::physical_planner::ExtensionPlanner, previously in file /home/runner/work/datafusion/datafusion/target/semver-checks/git-apache_main/938e491179569ed166312dde33b8e39714eb69d5/datafusion/core/src/physical_planner.rs:158

     Summary semver requires new major version: 1 major and 0 minor checks failed
    Finished [ 208.252s] datafusion
    Building datafusion-datasource v54.1.0 (current)
       Built [  38.939s] (current)
     Parsing datafusion-datasource v54.1.0 (current)
      Parsed [   0.031s] (current)
    Building datafusion-datasource v54.1.0 (baseline)
       Built [  39.005s] (baseline)
     Parsing datafusion-datasource v54.1.0 (baseline)
      Parsed [   0.032s] (baseline)
    Checking datafusion-datasource v54.1.0 -> v54.1.0 (no change; assume patch)
     Checked [   0.349s] 223 checks: 223 pass, 30 skip
     Summary no semver update required
    Finished [  79.586s] datafusion-datasource
    Building datafusion-datasource-arrow v54.1.0 (current)
       Built [  39.169s] (current)
     Parsing datafusion-datasource-arrow v54.1.0 (current)
      Parsed [   0.012s] (current)
    Building datafusion-datasource-arrow v54.1.0 (baseline)
       Built [  38.630s] (baseline)
     Parsing datafusion-datasource-arrow v54.1.0 (baseline)
      Parsed [   0.013s] (baseline)
    Checking datafusion-datasource-arrow v54.1.0 -> v54.1.0 (no change; assume patch)
     Checked [   0.097s] 223 checks: 223 pass, 30 skip
     Summary no semver update required
    Finished [  79.151s] datafusion-datasource-arrow
    Building datafusion-ffi v54.1.0 (current)
       Built [  58.082s] (current)
     Parsing datafusion-ffi v54.1.0 (current)
      Parsed [   0.060s] (current)
    Building datafusion-ffi v54.1.0 (baseline)
       Built [  58.293s] (baseline)
     Parsing datafusion-ffi v54.1.0 (baseline)
      Parsed [   0.062s] (baseline)
    Checking datafusion-ffi v54.1.0 -> v54.1.0 (no change; assume patch)
     Checked [   0.323s] 223 checks: 223 pass, 30 skip
     Summary no semver update required
    Finished [ 118.352s] datafusion-ffi
    Building datafusion-physical-optimizer v54.1.0 (current)
       Built [  39.237s] (current)
     Parsing datafusion-physical-optimizer v54.1.0 (current)
      Parsed [   0.021s] (current)
    Building datafusion-physical-optimizer v54.1.0 (baseline)
       Built [  39.676s] (baseline)
     Parsing datafusion-physical-optimizer v54.1.0 (baseline)
      Parsed [   0.022s] (baseline)
    Checking datafusion-physical-optimizer v54.1.0 -> v54.1.0 (no change; assume patch)
     Checked [   0.164s] 223 checks: 222 pass, 1 fail, 0 warn, 30 skip

--- failure trait_missing: pub trait removed or renamed ---

Description:
A publicly-visible trait cannot be imported by its prior path. A `pub use` may have been removed, or the trait itself may have been renamed or removed entirely.
        ref: https://doc.rust-lang.org/cargo/reference/semver.html#item-remove
       impl: https://github.com/obi1kenobi/cargo-semver-checks/tree/v0.49.0/src/lints/trait_missing.ron

Failed in:
  trait datafusion_physical_optimizer::optimizer::PhysicalOptimizerContext, previously in file /home/runner/work/datafusion/datafusion/target/semver-checks/git-apache_main/938e491179569ed166312dde33b8e39714eb69d5/datafusion/physical-optimizer/src/optimizer.rs:53
  trait datafusion_physical_optimizer::PhysicalOptimizerContext, previously in file /home/runner/work/datafusion/datafusion/target/semver-checks/git-apache_main/938e491179569ed166312dde33b8e39714eb69d5/datafusion/physical-optimizer/src/optimizer.rs:53
  trait datafusion_physical_optimizer::optimizer::PhysicalOptimizerRule, previously in file /home/runner/work/datafusion/datafusion/target/semver-checks/git-apache_main/938e491179569ed166312dde33b8e39714eb69d5/datafusion/physical-optimizer/src/optimizer.rs:95
  trait datafusion_physical_optimizer::PhysicalOptimizerRule, previously in file /home/runner/work/datafusion/datafusion/target/semver-checks/git-apache_main/938e491179569ed166312dde33b8e39714eb69d5/datafusion/physical-optimizer/src/optimizer.rs:95

     Summary semver requires new major version: 1 major and 0 minor checks failed
    Finished [  80.274s] datafusion-physical-optimizer
    Building datafusion-session v54.1.0 (current)
       Built [  35.986s] (current)
     Parsing datafusion-session v54.1.0 (current)
      Parsed [   0.012s] (current)
    Building datafusion-session v54.1.0 (baseline)
       Built [  36.046s] (baseline)
     Parsing datafusion-session v54.1.0 (baseline)
      Parsed [   0.011s] (baseline)
    Checking datafusion-session v54.1.0 -> v54.1.0 (no change; assume patch)
     Checked [   0.238s] 223 checks: 222 pass, 1 fail, 0 warn, 30 skip

--- failure trait_method_added: pub trait method added ---

Description:
A non-sealed public trait added a new method without a default implementation, which breaks downstream implementations of the trait
        ref: https://doc.rust-lang.org/cargo/reference/semver.html#trait-new-item-no-default
       impl: https://github.com/obi1kenobi/cargo-semver-checks/tree/v0.49.0/src/lints/trait_method_added.ron

Failed in:
  trait method datafusion_session::session::Session::query_planner in file /home/runner/work/datafusion/datafusion/datafusion/session/src/session.rs:96
  trait method datafusion_session::Session::query_planner in file /home/runner/work/datafusion/datafusion/datafusion/session/src/session.rs:96

     Summary semver requires new major version: 1 major and 0 minor checks failed
    Finished [  73.413s] datafusion-session

@github-actions github-actions Bot added the auto detected api change Auto detected API change label Jul 23, 2026
Make query_planner a required Session method and provide a public UnsupportedQueryPlanner for sessions that do not expose one, including ForeignSession until planner FFI support is added.\n\nAI Disclosure: This code was written in part by an AI agent.
@codecov-commenter

codecov-commenter commented Jul 23, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 63.00000% with 74 lines in your changes missing coverage. Please review.
✅ Project coverage is 80.74%. Comparing base (8abc085) to head (21b6973).

Files with missing lines Patch % Lines
datafusion/core/src/physical_planner.rs 63.63% 50 Missing and 2 partials ⚠️
datafusion/core/src/execution/session_state.rs 50.00% 6 Missing ⚠️
...usion/core/src/datasource/listing_table_factory.rs 0.00% 3 Missing ⚠️
datafusion/datasource-arrow/src/file_format.rs 0.00% 3 Missing ⚠️
datafusion/datasource/src/url.rs 0.00% 3 Missing ⚠️
datafusion/session/src/physical_optimizer.rs 70.00% 3 Missing ⚠️
datafusion/session/src/planner.rs 50.00% 2 Missing ⚠️
datafusion/core/src/execution/context/mod.rs 0.00% 1 Missing ⚠️
datafusion/ffi/src/session/mod.rs 91.66% 0 Missing and 1 partial ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main   #23842      +/-   ##
==========================================
- Coverage   80.75%   80.74%   -0.02%     
==========================================
  Files        1089     1091       +2     
  Lines      369164   369340     +176     
  Branches   369164   369340     +176     
==========================================
+ Hits       298127   298223      +96     
- Misses      53282    53356      +74     
- Partials    17755    17761       +6     

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

@github-actions github-actions Bot added the datasource Changes to the datasource crate label Jul 23, 2026
Move test-only imports into their test modules instead of conditionally importing them into the parent modules.\n\nAI Disclosure: This code was written in part by an AI agent.
Comment on lines +578 to +581
fn query_planner(&self) -> Arc<dyn QueryPlanner + Send + Sync> {
Arc::new(UnsupportedQueryPlanner)
}

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will open a follow up PR immediately after this merges that will replace this with an FFI equivalent implementation.

Comment on lines +95 to +117
/// Return the query planner for this session.
fn query_planner(&self) -> Arc<dyn QueryPlanner + Send + Sync>;

/// Optimize a logical plan.
///
/// The default implementation returns the plan unchanged. Sessions that use
/// the default query planning implementation should override this method.
fn optimize(&self, plan: &LogicalPlan) -> Result<LogicalPlan> {
Ok(plan.clone())
}

/// Return the physical optimizer rules for this session.
///
/// The default implementation returns no rules. Sessions that use the
/// default physical planner should override this method.
fn physical_optimizers(&self) -> &[Arc<dyn PhysicalOptimizerRule + Send + Sync>] {
&[]
}

/// Return the optional statistics registry used during physical optimization.
fn statistics_registry(&self) -> Option<&StatisticsRegistry> {
None
}

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These are the new methods hoisted up to Session from SessionState

@timsaucer
timsaucer requested a review from Copilot July 23, 2026 16:46
@timsaucer

Copy link
Copy Markdown
Member Author

@milenkovicm this PR will unblock FFI_QueryPlanner !

@timsaucer
timsaucer marked this pull request as ready for review July 23, 2026 16:57

Copilot AI 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.

Pull request overview

This PR continues the ongoing refactor to make DataFusion’s planning and optimization extension points accessible via &dyn Session (including across FFI boundaries) by moving the relevant traits into the datafusion-session crate and preserving prior public paths via re-exports.

Changes:

  • Move planner contracts (QueryPlanner, PhysicalPlanner, ExtensionPlanner) and physical optimizer contracts (PhysicalOptimizerRule, PhysicalOptimizerContext) into datafusion-session, and update signatures to accept &dyn Session.
  • Extend the Session trait with query_planner and add defaulted hooks (optimize, physical_optimizers, statistics_registry) to expose planning/optimization behavior without downcasting.
  • Update core planning/optimization wiring, FFI ForeignSession, docs, tests, and examples; keep prior import paths working via re-exports.

Reviewed changes

Copilot reviewed 17 out of 18 changed files in this pull request and generated no comments.

Show a summary per file
File Description
docs/source/library-user-guide/upgrading/55.0.0.md Documents the trait moves, signature changes (&SessionState&dyn Session), re-exported paths, and new required Session::query_planner.
datafusion/session/src/session.rs Extends Session with query_planner plus defaulted optimization/optimizer/stats-registry hooks.
datafusion/session/src/planner.rs Introduces the planner trait definitions in datafusion-session, including UnsupportedQueryPlanner.
datafusion/session/src/physical_optimizer.rs Introduces physical optimizer traits in datafusion-session with optional statistics-registry support.
datafusion/session/src/lib.rs Exposes new planner and physical_optimizer modules and re-exports their public contracts.
datafusion/physical-optimizer/src/optimizer.rs Switches optimizer traits to re-export from datafusion-session for backward compatibility.
datafusion/physical-optimizer/Cargo.toml Adds datafusion-session dependency needed for the new re-export source.
datafusion/ffi/src/session/mod.rs Implements Session::query_planner for ForeignSession (currently UnsupportedQueryPlanner) and adds unit coverage for new default hooks.
datafusion/datasource/src/url.rs Updates test-only Session impls to satisfy new required query_planner.
datafusion/datasource-arrow/src/file_format.rs Updates test-only Session impls to satisfy new required query_planner.
datafusion/core/tests/user_defined/user_defined_plan.rs Updates custom planner/extension planner signatures to take &dyn Session.
datafusion/core/src/physical_planner.rs Re-exports planner traits from datafusion-session, updates planner/optimizer interfaces to use &dyn Session, and adapts optimizer context wiring.
datafusion/core/src/execution/session_state.rs Implements the expanded Session trait for SessionState and updates DefaultQueryPlanner signature to &dyn Session.
datafusion/core/src/execution/context/mod.rs Re-exports QueryPlanner from datafusion-session to preserve prior paths.
datafusion/core/src/datasource/listing_table_factory.rs Updates test-only Session impls to satisfy new required query_planner.
datafusion-examples/examples/relation_planner/table_sample.rs Updates example custom planner/extension planner signatures to &dyn Session.
datafusion-examples/examples/dataframe/cache_factory.rs Updates example custom planner/extension planner signatures to &dyn Session.
Cargo.lock Records the updated dependency graph (physical-optimizer now depends on datafusion-session).

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@timsaucer

Copy link
Copy Markdown
Member Author

Would any of you be willing to review this @andygrove @milenkovicm @gabotechs ? This hoists the QueryPlanner and it's needed traits up to datafusion-session crate. Once this is done, I can put up my PR for the FFI_QueryPlanner and hopefully in DF55 we can connect ballista and datafusion-distributed to datafusion-python!

caveat: distributed is still blocked by extension data on configs, but maybe there is something I can do on it.

@gabotechs

Copy link
Copy Markdown
Contributor

I can take a look next week!

caveat: distributed is still blocked by extension data on configs, but maybe there is something I can do on it.

This was addressed on datafusion-contrib/datafusion-distributed#550, let me know if that's not enough.

@timsaucer

Copy link
Copy Markdown
Member Author

This was addressed on datafusion-contrib/datafusion-distributed#550, let me know if that's not enough.

No, the issue is that only string key/value pairs can make it through FFI configs. I'm going to investigate to see if the python branch I have for datafusion-distributed can pass the internal config around in a different way other than getting them out of the session's config.

@alamb

alamb commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

Moves these traits to the datafusion-session crate:

I wonder if we should make a datafusion-api crate, or datafusion-traits crate 🤔

@gabotechs

gabotechs commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

No, the issue is that only string key/value pairs can make it through FFI configs. I'm going to investigate to see if the python branch I have for datafusion-distributed can pass the internal config around in a different way other than getting them out of the session's config.

Why would those extensions need to cross any FFI boundary? I'd imagine that in the Python API, we would build something like a DistributedSessionContext.new() that can be enriched with all the extensions, and planning + execution would happen under that DistributedSessionContext scope, where those extensions are automatically available, without crossing any FFI boundary.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

api change Changes the API exposed to users of the crate auto detected api change Auto detected API change core Core DataFusion crate datasource Changes to the datasource crate documentation Improvements or additions to documentation ffi Changes to the ffi crate optimizer Optimizer rules

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants