chore: remove as_any from ExecutionPlan#21263
Conversation
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>
alamb
left a comment
There was a problem hiding this comment.
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) = |
There was a problem hiding this comment.
I will be honest that the previous syntax is quite a bit nicer 🤔
There was a problem hiding this comment.
You're right. Moved to draft, at least for the time being.
There was a problem hiding this comment.
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.
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>
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>
| pub fn try_downcast_func<T>(expr: &dyn PhysicalExpr) -> Option<&ScalarFunctionExpr> | ||
| where | ||
| T: 'static, | ||
| T: ScalarUDFImpl, |
There was a problem hiding this comment.
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.
…pplied it to expressions
| } | ||
|
|
||
| impl dyn ScalarUDFImpl { | ||
| /// Returns `true` if the implementation is of type `T`. |
| } | ||
| ``` | ||
|
|
||
| If you have code that is downcasting, you can use the new `downcast_ref` |
| 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`. |
There was a problem hiding this comment.
is this meant to have > in front of it?
…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>
…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>
## Which issue does this PR close? This is a follow on to apache#20812 and apache#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](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. 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. --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…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>
apache#23154) ## Which issue does this PR close? N/A. This is a follow-up to apache#22559 and apache#21263, but there is no dedicated issue for this specific bug. ## Rationale for this change apache#21263 removed `ExecutionPlan::as_any()` now that `ExecutionPlan` has `Any` as a supertrait. Most call sites were migrated from code like this: ```rust plan.as_any().downcast_ref::<SomeExec>() ``` to the new helper: ```rust plan.downcast_ref::<SomeExec>() ``` That distinction matters after apache#22559. `ExecutionPlan::downcast_ref` is now the public downcast operation for execution plans: it follows `ExecutionPlan::downcast_delegate()` before falling back to raw `Any`. This lets wrapper plans keep their own concrete type private while still presenting the wrapped plan's normal public identity. For example, tracing or instrumentation wrappers can implement `downcast_delegate()` so callers that ask "is this a `FilterExec` / `EmptyExec` / `ProjectionExec`?" get the same answer they would have received without the wrapper. The wrapper remains visible only to code that intentionally performs a raw concrete-type `Any` check. This problem came up while trying to instrument distributed plans through [`datafusion-distributed`](https://github.com/datafusion-contrib/datafusion-distributed). In that setting, physical plans may be wrapped for tracing/instrumentation and then serialized for distributed execution. The wrapper is meant to be transparent for normal plan inspection, but serialization still needs to recognize the delegated built-in plan underneath it. The physical plan proto serializer still had one leftover mechanical migration from the old `as_any()` world: ```rust let plan = plan.as_ref() as &dyn Any; ``` That line bypasses `ExecutionPlan::downcast_ref` entirely. As a result, a delegating wrapper around a built-in execution plan is not serialized as the built-in plan. Instead, the serializer sees only the wrapper's concrete type, fails all built-in `Exec` checks, and falls through to the extension codec. With the default extension codec this produces an unsupported-plan error, even though the wrapped plan itself is serializable. This is particularly relevant for transparent wrappers such as [`InstrumentedExec`](https://github.com/datafusion-contrib/datafusion-tracing/blob/main/datafusion-tracing/src/instrumented_exec.rs) in [`datafusion-contrib/datafusion-tracing`](https://github.com/datafusion-contrib/datafusion-tracing), which intentionally delegate public downcasts to the wrapped plan. ## What changes are included in this PR? - Changes physical plan proto serialization to bind the plan as `&dyn ExecutionPlan` instead of `&dyn Any`. - Leaves the existing serializer downcast chain intact, so each `plan.downcast_ref::<...>()` now dispatches through the `ExecutionPlan` helper and therefore honors `downcast_delegate()`. - Adds a regression test with a small wrapper execution plan that delegates public downcasts to an inner `EmptyExec`. - Audited other `as_ref() as &dyn Any` patterns. The remaining hits are either non-`ExecutionPlan` traits, `PhysicalExpr` checks, UDF tests, or the intentional raw `Any` fallback inside the `ExecutionPlan` helper itself. ## Are these changes tested? Yes. ```bash cargo fmt --all cargo fmt --all --check cargo test -p datafusion-proto --test proto_integration serialize_uses_downcast_delegate cargo clippy --all-targets --all-features -- -D warnings ``` ## Are there any user-facing changes? Yes, as a bug fix. Physical plan proto serialization now honors the documented `ExecutionPlan::downcast_delegate()` behavior. Transparent wrapper plans can serialize as their delegated built-in execution plan when appropriate instead of requiring an extension codec for the wrapper itself.

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
isanddowncast_refon 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_anyfunction. The upgrade guide is updated.