Skip to content

refactor: pass SubqueryContext explicitly through planner traits#23649

Draft
timsaucer wants to merge 4 commits into
apache:mainfrom
timsaucer:refactor/subquery-context-trait
Draft

refactor: pass SubqueryContext explicitly through planner traits#23649
timsaucer wants to merge 4 commits into
apache:mainfrom
timsaucer:refactor/subquery-context-trait

Conversation

@timsaucer

@timsaucer timsaucer commented Jul 16, 2026

Copy link
Copy Markdown
Member

Which issue does this PR close?

Rationale for this change

When we turn logical plans into physical plans, we have a work around to get the context for scalar subqueries. Specifically we clone the session and mutate the execution_props where the subquery context currently sits. This is even called out in the code with comments:

            // Ideally, the subquery state would live in a dedicated planning
            // context rather than in `ExecutionProps`. It's here because
            // `create_physical_expr` only receives `&ExecutionProps`.

The real reason for this is that we want to expose the QueryPlanner via FFI and currently it depends upon downcasts from Session to SessionState. That does not work for the FFI crate where we have a different Session implementation. Since we cannot do the downcast, we cannot use the work around in the current code.

This PR is designed to plumb through the required context properly rather than rely on manipulating the execution properties.

What changes are included in this PR?

The major change here is to remove subquery_indexes and subquery_results from ExecutionProps and put them into their own struct SubqueryContext. Everything else is just refactoring and plumbing to align around this split of the data.

Are these changes tested?

  • Existing unit tests all pass.
  • Added additional unit test to cover user defined expressions.

Are there any user-facing changes?

Yes, this adds a single parameter into create_physical_expr, the subquery context. The migration guide demonstrates how to add a default context in for users, if necessary.

AI Disclosure: This code was written in part by an AI agent.
@github-actions github-actions Bot added documentation Improvements or additions to documentation logical-expr Logical plan and expressions physical-expr Changes to the physical-expr crates optimizer Optimizer rules core Core DataFusion crate catalog Related to the catalog crate functions Changes to functions implementation datasource Changes to the datasource crate labels Jul 16, 2026
@github-actions

github-actions Bot commented Jul 16, 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.0.0 (current)
       Built [ 105.219s] (current)
     Parsing datafusion v54.0.0 (current)
      Parsed [   0.036s] (current)
    Building datafusion v54.0.0 (baseline)
       Built [ 104.345s] (baseline)
     Parsing datafusion v54.0.0 (baseline)
      Parsed [   0.037s] (baseline)
    Checking datafusion v54.0.0 -> v54.0.0 (no change; assume patch)
     Checked [   0.972s] 223 checks: 221 pass, 2 fail, 0 warn, 30 skip

--- failure function_parameter_count_changed: pub fn parameter count changed ---

Description:
A publicly-visible function now takes a different number of parameters.
        ref: https://doc.rust-lang.org/cargo/reference/semver.html#fn-change-arity
       impl: https://github.com/obi1kenobi/cargo-semver-checks/tree/v0.48.0/src/lints/function_parameter_count_changed.ron

Failed in:
  datafusion::physical_planner::create_window_expr_with_name now takes 5 parameters instead of 4, in /home/runner/work/datafusion/datafusion/datafusion/core/src/physical_planner.rs:2523
  datafusion::physical_planner::create_window_expr now takes 4 parameters instead of 3, in /home/runner/work/datafusion/datafusion/datafusion/core/src/physical_planner.rs:2604

--- failure trait_method_parameter_count_changed: pub trait method parameter count changed ---

Description:
A trait method now takes a different number of parameters.
        ref: https://doc.rust-lang.org/cargo/reference/semver.html#trait-item-signature
       impl: https://github.com/obi1kenobi/cargo-semver-checks/tree/v0.48.0/src/lints/trait_method_parameter_count_changed.ron

Failed in:
  PhysicalPlanner::create_physical_expr now takes 4 instead of 3 parameters, in file /home/runner/work/datafusion/datafusion/datafusion/core/src/physical_planner.rs:147
  ExtensionPlanner::plan_extension now takes 6 instead of 5 parameters, in file /home/runner/work/datafusion/datafusion/datafusion/core/src/physical_planner.rs:174
  ExtensionPlanner::plan_table_scan now takes 4 instead of 3 parameters, in file /home/runner/work/datafusion/datafusion/datafusion/core/src/physical_planner.rs:251

     Summary semver requires new major version: 2 major and 0 minor checks failed
    Finished [ 212.282s] datafusion
    Building datafusion-catalog v54.0.0 (current)
       Built [  36.453s] (current)
     Parsing datafusion-catalog v54.0.0 (current)
      Parsed [   0.026s] (current)
    Building datafusion-catalog v54.0.0 (baseline)
       Built [  36.639s] (baseline)
     Parsing datafusion-catalog v54.0.0 (baseline)
      Parsed [   0.028s] (baseline)
    Checking datafusion-catalog v54.0.0 -> v54.0.0 (no change; assume patch)
     Checked [   0.204s] 223 checks: 223 pass, 30 skip
     Summary no semver update required
    Finished [  74.343s] datafusion-catalog
    Building datafusion-catalog-listing v54.0.0 (current)
       Built [  43.043s] (current)
     Parsing datafusion-catalog-listing v54.0.0 (current)
      Parsed [   0.011s] (current)
    Building datafusion-catalog-listing v54.0.0 (baseline)
       Built [  43.059s] (baseline)
     Parsing datafusion-catalog-listing v54.0.0 (baseline)
      Parsed [   0.015s] (baseline)
    Checking datafusion-catalog-listing v54.0.0 -> v54.0.0 (no change; assume patch)
     Checked [   0.118s] 223 checks: 223 pass, 30 skip
     Summary no semver update required
    Finished [  87.617s] datafusion-catalog-listing
    Building datafusion-datasource v54.0.0 (current)
       Built [  35.344s] (current)
     Parsing datafusion-datasource v54.0.0 (current)
      Parsed [   0.032s] (current)
    Building datafusion-datasource v54.0.0 (baseline)
       Built [  35.463s] (baseline)
     Parsing datafusion-datasource v54.0.0 (baseline)
      Parsed [   0.034s] (baseline)
    Checking datafusion-datasource v54.0.0 -> v54.0.0 (no change; assume patch)
     Checked [   0.351s] 223 checks: 223 pass, 30 skip
     Summary no semver update required
    Finished [  72.279s] datafusion-datasource
    Building datafusion-expr v54.0.0 (current)
       Built [  25.019s] (current)
     Parsing datafusion-expr v54.0.0 (current)
      Parsed [   0.079s] (current)
    Building datafusion-expr v54.0.0 (baseline)
       Built [  25.079s] (baseline)
     Parsing datafusion-expr v54.0.0 (baseline)
      Parsed [   0.079s] (baseline)
    Checking datafusion-expr v54.0.0 -> v54.0.0 (no change; assume patch)
     Checked [   1.815s] 223 checks: 222 pass, 1 fail, 0 warn, 30 skip

--- failure struct_pub_field_missing: pub struct's pub field removed or renamed ---

Description:
A publicly-visible struct has at least one public field that is no longer available under its prior name. It 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.48.0/src/lints/struct_pub_field_missing.ron

Failed in:
  field subquery_indexes of struct ExecutionProps, previously in file /home/runner/work/datafusion/datafusion/target/semver-checks/git-apache_main/194a357a000890b6004c42f42887c7512d11be8d/datafusion/expr/src/execution_props.rs:69
  field subquery_results of struct ExecutionProps, previously in file /home/runner/work/datafusion/datafusion/target/semver-checks/git-apache_main/194a357a000890b6004c42f42887c7512d11be8d/datafusion/expr/src/execution_props.rs:72

     Summary semver requires new major version: 1 major and 0 minor checks failed
    Finished [  53.162s] datafusion-expr
    Building datafusion-functions-nested v54.0.0 (current)
       Built [  33.575s] (current)
     Parsing datafusion-functions-nested v54.0.0 (current)
      Parsed [   0.039s] (current)
    Building datafusion-functions-nested v54.0.0 (baseline)
       Built [  33.488s] (baseline)
     Parsing datafusion-functions-nested v54.0.0 (baseline)
      Parsed [   0.043s] (baseline)
    Checking datafusion-functions-nested v54.0.0 -> v54.0.0 (no change; assume patch)
     Checked [   0.284s] 223 checks: 223 pass, 30 skip
     Summary no semver update required
    Finished [  68.547s] datafusion-functions-nested
    Building datafusion-optimizer v54.0.0 (current)
       Built [  25.382s] (current)
     Parsing datafusion-optimizer v54.0.0 (current)
      Parsed [   0.031s] (current)
    Building datafusion-optimizer v54.0.0 (baseline)
       Built [  25.357s] (baseline)
     Parsing datafusion-optimizer v54.0.0 (baseline)
      Parsed [   0.034s] (baseline)
    Checking datafusion-optimizer v54.0.0 -> v54.0.0 (no change; assume patch)
     Checked [   0.240s] 223 checks: 223 pass, 30 skip
     Summary no semver update required
    Finished [  51.921s] datafusion-optimizer
    Building datafusion-physical-expr v54.0.0 (current)
       Built [  27.219s] (current)
     Parsing datafusion-physical-expr v54.0.0 (current)
      Parsed [   0.047s] (current)
    Building datafusion-physical-expr v54.0.0 (baseline)
       Built [  27.033s] (baseline)
     Parsing datafusion-physical-expr v54.0.0 (baseline)
      Parsed [   0.050s] (baseline)
    Checking datafusion-physical-expr v54.0.0 -> v54.0.0 (no change; assume patch)
     Checked [   0.497s] 223 checks: 221 pass, 2 fail, 0 warn, 30 skip

--- failure function_parameter_count_changed: pub fn parameter count changed ---

Description:
A publicly-visible function now takes a different number of parameters.
        ref: https://doc.rust-lang.org/cargo/reference/semver.html#fn-change-arity
       impl: https://github.com/obi1kenobi/cargo-semver-checks/tree/v0.48.0/src/lints/function_parameter_count_changed.ron

Failed in:
  datafusion_physical_expr::create_physical_sort_exprs now takes 4 parameters instead of 3, in /home/runner/work/datafusion/datafusion/datafusion/physical-expr/src/physical_expr.rs:219
  datafusion_physical_expr::create_physical_sort_expr now takes 4 parameters instead of 3, in /home/runner/work/datafusion/datafusion/datafusion/physical-expr/src/physical_expr.rs:202
  datafusion_physical_expr::planner::create_physical_exprs now takes 4 parameters instead of 3, in /home/runner/work/datafusion/datafusion/datafusion/physical-expr/src/planner.rs:704
  datafusion_physical_expr::create_physical_exprs now takes 4 parameters instead of 3, in /home/runner/work/datafusion/datafusion/datafusion/physical-expr/src/planner.rs:704
  datafusion_physical_expr::create_physical_partitioning now takes 4 parameters instead of 3, in /home/runner/work/datafusion/datafusion/datafusion/physical-expr/src/physical_expr.rs:236
  datafusion_physical_expr::planner::create_physical_expr now takes 4 parameters instead of 3, in /home/runner/work/datafusion/datafusion/datafusion/physical-expr/src/planner.rs:126
  datafusion_physical_expr::create_physical_expr now takes 4 parameters instead of 3, in /home/runner/work/datafusion/datafusion/datafusion/physical-expr/src/planner.rs:126

--- failure method_parameter_count_changed: pub method parameter count changed ---

Description:
A publicly-visible method now takes a different number of parameters, not counting the receiver (self) parameter.
        ref: https://doc.rust-lang.org/cargo/reference/semver.html#fn-change-arity
       impl: https://github.com/obi1kenobi/cargo-semver-checks/tree/v0.48.0/src/lints/method_parameter_count_changed.ron

Failed in:
  datafusion_physical_expr::aggregate::LoweredAggregateBuilder::new takes 4 parameters in /home/runner/work/datafusion/datafusion/target/semver-checks/git-apache_main/194a357a000890b6004c42f42887c7512d11be8d/datafusion/physical-expr/src/aggregate.rs:434, but now takes 5 parameters in /home/runner/work/datafusion/datafusion/datafusion/physical-expr/src/aggregate.rs:439

     Summary semver requires new major version: 2 major and 0 minor checks failed
    Finished [  55.751s] datafusion-physical-expr

@github-actions github-actions Bot added the auto detected api change Auto detected API change label Jul 16, 2026
Replace broad uses of expression lowering with explicit language about creating physical expressions, and correct the migration guide PR link.

AI Disclosure: This code was written in part by an AI agent.

@timsaucer timsaucer left a comment

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've tried to mark all of the places in the code that I think are significant, which are few. The rest is just adding in an extra parameter to function calls and plumbing that through as necessary.

Comment on lines -475 to -478
let mut owned = session_state.clone();
owned.execution_props_mut().subquery_indexes = index_map;
owned.execution_props_mut().subquery_results = results.clone();
let session_state = Cow::Owned(owned);

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.

This is the work around we are removing.

Comment on lines +4017 to +4039
#[tokio::test]
async fn scalar_subquery_in_extension_expr_plans() -> Result<()> {
let subquery = LogicalPlanBuilder::empty(true)
.project(vec![lit(42_i32)])?
.build()?;
let logical_plan = LogicalPlan::Extension(Extension {
node: Arc::new(NoOpExtensionNode {
expressions: vec![scalar_subquery(Arc::new(subquery))],
..Default::default()
}),
});
let planner = DefaultPhysicalPlanner::with_extension_planners(vec![Arc::new(
ExpressionExtensionPlanner,
)]);

let plan = planner
.create_physical_plan(&logical_plan, &make_session_state())
.await?;

assert_contains!(format!("{plan:?}"), "ScalarSubqueryExec");
Ok(())
}

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.

This test is necessary and demonstrates extension nodes that need the subquery context piped through properly.

Comment on lines -93 to -94
subquery_indexes: HashMap::new(),
subquery_results: ScalarSubqueryResults::default(),

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.

This is the core of the change to this PR. We move these values out of the ExecutionProps and into their own struct, SubqueryContext

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

Labels

auto detected api change Auto detected API change catalog Related to the catalog crate core Core DataFusion crate datasource Changes to the datasource crate documentation Improvements or additions to documentation functions Changes to functions implementation logical-expr Logical plan and expressions optimizer Optimizer rules physical-expr Changes to the physical-expr crates

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant