Skip to content

chore: remove as_any from ExecutionPlan#21263

Merged
timsaucer merged 17 commits intoapache:mainfrom
timsaucer:chore/remove-as-any-execution-plan
Apr 3, 2026
Merged

chore: remove as_any from ExecutionPlan#21263
timsaucer merged 17 commits intoapache:mainfrom
timsaucer:chore/remove-as-any-execution-plan

Conversation

@timsaucer
Copy link
Copy Markdown
Member

@timsaucer timsaucer commented Mar 30, 2026

Which issue does this PR close?

This is a follow on to #20812 and #21209 but treats ExecutionPlan.

Rationale for this change

This PR reduces the amount of boilerplate code that users need to write for execution plans.

What changes are included in this PR?

Now that we have trait upcasting since rust 1.86, we no longer need every implementation of these functions to have the as_any function that returns &self. This PR makes Any an supertrait and makes the appropriate casts when necessary.

I have also implemented functions is and downcast_ref on the trait object for ExecutionPlan and applied this same pattern to the udf, udaf, and udwf implementations. This allows for a clean downcasting and type checking.

Are these changes tested?

Existing unit tests.

Are there any user-facing changes?

Yes, the users simply need to remove the as_any function. The upgrade guide is updated.

@github-actions github-actions bot added documentation Improvements or additions to documentation optimizer Optimizer rules core Core DataFusion crate substrait Changes to the substrait crate catalog Related to the catalog crate proto Related to proto crate datasource Changes to the datasource crate ffi Changes to the ffi crate physical-plan Changes to the physical-plan crate labels Mar 30, 2026
timsaucer and others added 6 commits March 30, 2026 16:29
When upcasting to `&dyn Any` from an `Arc<dyn Trait>`, using
`(&plan as &dyn Any)` gives an Any for the Arc, not the inner trait
object. Document that `.as_ref() as &dyn Any` is required instead.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@timsaucer timsaucer marked this pull request as ready for review March 31, 2026 17:47
Copy link
Copy Markdown
Contributor

@alamb alamb left a comment

Choose a reason for hiding this comment

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

Thanks @timsaucer

The basic idea here is great -- however, this pattern of calling .as_any().downcast_ref()... is super common over the codebase and changing it to (scan.as_ref() as &dyn Any) is pretty 🤮

So in other words, I am not sure this PR is a good as it makes the code harder to read (at least for me). Is there anyway to avoid this (like maybe keeping the as_any as a default impl perhaps?)


fn f_down(&mut self, node: &'n Self::Node) -> Result<TreeNodeRecursion> {
if let Some(exec) = node.as_any().downcast_ref::<AggregateExec>() {
if let Some(exec) =
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.

I will be honest that the previous syntax is quite a bit nicer 🤔

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.

You're right. Moved to draft, at least for the time being.

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.

Thank you for the push back @alamb. If you take another look, I think we now have a very clean downcast. I also updated the UDFs at the same time and applied the pattern. They have many fewer places where we do downcast_ref or is so it wasn't so noticeable.

@timsaucer timsaucer marked this pull request as draft March 31, 2026 20:19
timsaucer and others added 3 commits March 31, 2026 16:40
Adds an inherent impl on `dyn ExecutionPlan` providing `downcast_ref<T>`
and `is<T>` methods, so callers can write `plan.downcast_ref::<MyExec>()`
instead of `(plan.as_ref() as &dyn Any).downcast_ref::<MyExec>()`. Both
methods work correctly whether `plan` is `&dyn ExecutionPlan` or
`Arc<dyn ExecutionPlan>` via auto-deref.

Updates call sites in tests and the 54.0.0 upgrade guide accordingly.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…l, WindowUDFImpl

Mirrors the same inherent impl pattern added for dyn ExecutionPlan.
Callers can now write `udf.downcast_ref::<MyUdf>()` instead of
`(udf.as_ref() as &dyn Any).downcast_ref::<MyUdf>()`, and it works
correctly whether the value is a bare reference or behind an Arc.

Updates the 54.0.0 upgrade guide accordingly.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replaces all `(plan.as_ref() as &dyn Any).downcast_ref::<T>()` and
`(plan as &dyn Any).is::<T>()` call sites with the new inherent methods
`plan.downcast_ref::<T>()` and `plan.is::<T>()` added to `dyn ExecutionPlan`.
Also removes the now-unused `use std::any::Any` imports.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@github-actions github-actions bot added the logical-expr Logical plan and expressions label Mar 31, 2026
timsaucer and others added 5 commits April 1, 2026 07:14
Replaces remaining `(x.as_ref() as &dyn Any).downcast_ref::<T>()` and
`(x as &dyn std::any::Any).downcast_ref::<T>()` patterns with the new
inherent methods on `dyn ExecutionPlan`. Also cleans up redundant
`.as_ref()` calls before `.downcast_ref()` / `.is()` on Arc values,
and simplifies `.downcast_ref::<T>().is_some()` to `.is::<T>()` where
applicable. Removes now-unused `std::any::Any` imports.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Fixes two missed ExecutionPlan patterns:
- topk_repartition.rs: remove intermediate as &dyn Any variable
- limit_pushdown.rs: remove intermediate as &dyn Any variable

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replaces `(func.inner().as_ref() as &dyn Any).downcast_ref::<T>()` and
`.is::<T>()` call sites with the new inherent methods on
`dyn ScalarUDFImpl`. Also simplifies `.downcast_ref::<T>().is_some()`
to `.is::<T>()` and removes now-unused `std::any::Any` imports.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…tterns

Replaces `(udaf.inner().as_ref() as &dyn Any).downcast_ref::<T>()` and
`.is::<T>()` call sites with the new inherent methods on
`dyn AggregateUDFImpl`. Removes now-unused `std::any::Any` imports.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replaces `(udwf.inner().as_ref() as &dyn Any).downcast_ref::<T>()` and
`.is::<T>()` call sites with the new inherent methods on
`dyn WindowUDFImpl`. Removes now-unused `std::any::Any` imports.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@github-actions github-actions bot added physical-expr Changes to the physical-expr crates functions Changes to functions implementation labels Apr 1, 2026
pub fn try_downcast_func<T>(expr: &dyn PhysicalExpr) -> Option<&ScalarFunctionExpr>
where
T: 'static,
T: ScalarUDFImpl,
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 may look like it's changing behavior because we're changing the generic but if you look at the usage, the downcast must be to a ScalarUDFImpl so this is valid IMO.

@timsaucer timsaucer marked this pull request as ready for review April 1, 2026 13:16
Copy link
Copy Markdown
Contributor

@alamb alamb left a comment

Choose a reason for hiding this comment

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

This is much nicer

}

impl dyn ScalarUDFImpl {
/// Returns `true` if the implementation is of type `T`.
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.

this is nice

}
```

If you have code that is downcasting, you can use the new `downcast_ref`
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.

🎉

whether the value is a bare reference or behind an `Arc` (Rust
auto-derefs through the `Arc`).

> **Warning:** Do not cast an `Arc<dyn Trait>` directly to `&dyn Any`.
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.

is this meant to have > in front of it?

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.

Yes, so it renders like this:

Screenshot 2026-04-02 at 2 31 26 PM

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.

TIL!

@alamb alamb added the api change Changes the API exposed to users of the crate label Apr 2, 2026
@timsaucer timsaucer enabled auto-merge April 3, 2026 12:00
@timsaucer timsaucer disabled auto-merge April 3, 2026 12:00
@timsaucer timsaucer added this pull request to the merge queue Apr 3, 2026
Merged via the queue into apache:main with commit dd8760d Apr 3, 2026
35 checks passed
@timsaucer timsaucer deleted the chore/remove-as-any-execution-plan branch April 3, 2026 13:33
github-merge-queue bot pushed a commit that referenced this pull request Apr 12, 2026
…d CatalogProviderList (#21346)

## Which issue does this PR close?

This is a follow on to #20812 #21209 #21263 but treats `TableProvider`,
`SchemaProvider`, `CatalogProvider`, and `CatalogProviderList`.

## Rationale for this change

This PR reduces the amount of boilerplate code that users need to write
for catalogs through table providers.

## What changes are included in this PR?

Now that we have [trait
upcasting](https://blog.rust-lang.org/2025/04/03/Rust-1.86.0/) since
rust 1.86, we no longer need every implementation of these functions to
have the as_any function that returns &self. This PR makes Any an
supertrait and makes the appropriate casts when necessary.

## Are these changes tested?

Existing unit tests.

## Are there any user-facing changes?

Yes, the users simply need to remove the `as_any` function. The upgrade
guide is updated.

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
coderfender pushed a commit to coderfender/datafusion that referenced this pull request Apr 14, 2026
…d CatalogProviderList (apache#21346)

## Which issue does this PR close?

This is a follow on to apache#20812 apache#21209 apache#21263 but treats `TableProvider`,
`SchemaProvider`, `CatalogProvider`, and `CatalogProviderList`.

## Rationale for this change

This PR reduces the amount of boilerplate code that users need to write
for catalogs through table providers.

## What changes are included in this PR?

Now that we have [trait
upcasting](https://blog.rust-lang.org/2025/04/03/Rust-1.86.0/) since
rust 1.86, we no longer need every implementation of these functions to
have the as_any function that returns &self. This PR makes Any an
supertrait and makes the appropriate casts when necessary.

## Are these changes tested?

Existing unit tests.

## Are there any user-facing changes?

Yes, the users simply need to remove the `as_any` function. The upgrade
guide is updated.

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
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 catalog Related to the catalog crate core Core DataFusion crate datasource Changes to the datasource crate documentation Improvements or additions to documentation ffi Changes to the ffi crate functions Changes to functions implementation logical-expr Logical plan and expressions optimizer Optimizer rules physical-expr Changes to the physical-expr crates physical-plan Changes to the physical-plan crate proto Related to proto crate substrait Changes to the substrait crate

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants