From 13ae5901afc06a347f7112b3a7c455050eac422c Mon Sep 17 00:00:00 2001 From: Tim Saucer Date: Fri, 27 Mar 2026 21:05:03 -0400 Subject: [PATCH 01/14] Remove as_any function from execution plans and use trait upcasting --- .../adapter_serialization.rs | 7 +- .../custom_data_source/custom_datasource.rs | 4 - .../examples/data_io/parquet_exec_visitor.rs | 4 +- .../memory_pool_execution_plan.rs | 5 - .../proto/composed_extension_codec.rs | 18 ++-- .../proto/expression_deduplication.rs | 4 +- .../examples/relation_planner/table_sample.rs | 5 - datafusion/catalog/src/memory/table.rs | 4 - .../core/src/datasource/listing/table.rs | 4 +- datafusion/core/src/physical_planner.rs | 25 +---- datafusion/core/src/test_util/parquet.rs | 3 +- .../core/tests/custom_sources_cases/mod.rs | 9 +- .../provider_filter_pushdown.rs | 4 - .../tests/custom_sources_cases/statistics.rs | 4 - .../core/tests/fuzz_cases/aggregate_fuzz.rs | 4 +- datafusion/core/tests/fuzz_cases/once_exec.rs | 5 - .../memory_limit/repartition_mem_limit.rs | 3 +- .../core/tests/parquet/file_statistics.rs | 15 ++- datafusion/core/tests/parquet/utils.rs | 4 +- .../aggregate_statistics.rs | 11 +- .../enforce_distribution.rs | 13 +-- .../physical_optimizer/filter_pushdown.rs | 4 +- .../physical_optimizer/join_selection.rs | 57 +++------- .../physical_optimizer/projection_pushdown.rs | 20 ++-- .../physical_optimizer/pushdown_utils.rs | 4 - .../tests/physical_optimizer/test_utils.rs | 9 -- datafusion/core/tests/sql/explain_analyze.rs | 17 +-- .../tests/user_defined/insert_operation.rs | 8 +- .../tests/user_defined/user_defined_plan.rs | 6 +- datafusion/datasource/src/sink.rs | 4 - datafusion/datasource/src/source.rs | 4 - datafusion/ffi/src/execution_plan.rs | 28 ++--- .../ffi/src/proto/physical_extension_codec.rs | 4 +- datafusion/ffi/src/tests/async_provider.rs | 4 - datafusion/ffi/tests/ffi_execution_plan.rs | 2 +- .../src/aggregate_statistics.rs | 9 +- .../src/combine_partial_final_agg.rs | 7 +- .../src/enforce_distribution.rs | 65 ++++++----- .../src/enforce_sorting/mod.rs | 23 ++-- .../src/enforce_sorting/sort_pushdown.rs | 45 +++++--- .../physical-optimizer/src/ensure_coop.rs | 4 - .../src/hash_join_buffering.rs | 14 ++- .../physical-optimizer/src/join_selection.rs | 102 +++++++++--------- .../physical-optimizer/src/limit_pushdown.rs | 9 +- .../src/limit_pushdown_past_window.rs | 21 ++-- .../src/limited_distinct_aggregation.rs | 14 ++- .../src/output_requirements.rs | 16 +-- .../src/projection_pushdown.rs | 57 +++++----- .../physical-optimizer/src/pushdown_sort.rs | 4 +- .../physical-optimizer/src/sanity_checker.rs | 4 +- .../src/topk_aggregation.rs | 16 ++- .../src/topk_repartition.rs | 15 ++- .../src/update_aggr_exprs.rs | 5 +- datafusion/physical-optimizer/src/utils.rs | 17 +-- .../physical-plan/src/aggregates/mod.rs | 9 -- datafusion/physical-plan/src/analyze.rs | 5 - datafusion/physical-plan/src/async_func.rs | 5 - datafusion/physical-plan/src/buffer.rs | 5 - .../physical-plan/src/coalesce_batches.rs | 5 - .../physical-plan/src/coalesce_partitions.rs | 5 - datafusion/physical-plan/src/coop.rs | 5 - datafusion/physical-plan/src/display.rs | 4 - datafusion/physical-plan/src/empty.rs | 5 - .../physical-plan/src/execution_plan.rs | 18 +--- datafusion/physical-plan/src/explain.rs | 5 - datafusion/physical-plan/src/filter.rs | 5 - .../physical-plan/src/joins/cross_join.rs | 6 +- .../physical-plan/src/joins/hash_join/exec.rs | 6 +- .../src/joins/nested_loop_join.rs | 5 - .../src/joins/piecewise_merge_join/exec.rs | 4 - .../src/joins/sort_merge_join/exec.rs | 5 - .../src/joins/symmetric_hash_join.rs | 5 - datafusion/physical-plan/src/limit.rs | 9 -- datafusion/physical-plan/src/memory.rs | 4 - .../physical-plan/src/placeholder_row.rs | 5 - datafusion/physical-plan/src/projection.rs | 33 +++--- .../physical-plan/src/recursive_query.rs | 4 - .../physical-plan/src/repartition/mod.rs | 6 +- .../physical-plan/src/sorts/partial_sort.rs | 5 - datafusion/physical-plan/src/sorts/sort.rs | 11 +- .../src/sorts/sort_preserving_merge.rs | 8 -- datafusion/physical-plan/src/streaming.rs | 5 - datafusion/physical-plan/src/test.rs | 5 - datafusion/physical-plan/src/test/exec.rs | 25 ----- datafusion/physical-plan/src/union.rs | 18 +--- datafusion/physical-plan/src/unnest.rs | 6 +- .../src/windows/bounded_window_agg_exec.rs | 9 +- .../src/windows/window_agg_exec.rs | 5 - datafusion/physical-plan/src/work_table.rs | 4 - datafusion/proto/src/physical_plan/mod.rs | 2 +- .../tests/cases/roundtrip_physical_plan.rs | 19 ++-- .../substrait/src/physical_plan/producer.rs | 3 +- .../custom-table-providers.md | 12 --- .../library-user-guide/upgrading/54.0.0.md | 42 +++++--- 94 files changed, 449 insertions(+), 671 deletions(-) diff --git a/datafusion-examples/examples/custom_data_source/adapter_serialization.rs b/datafusion-examples/examples/custom_data_source/adapter_serialization.rs index a2cd187fee067..a05801da52e2a 100644 --- a/datafusion-examples/examples/custom_data_source/adapter_serialization.rs +++ b/datafusion-examples/examples/custom_data_source/adapter_serialization.rs @@ -32,6 +32,7 @@ //! of the `PhysicalExtensionCodec` interception pattern. Both plan and expression //! serialization route through the codec, enabling interception at every node in the tree. +use std::any::Any; use std::fmt::Debug; use std::sync::Arc; @@ -317,7 +318,7 @@ impl PhysicalProtoConverterExtension for AdapterPreservingCodec { extension_codec: &dyn PhysicalExtensionCodec, ) -> Result { // Check if this is a DataSourceExec with adapter - if let Some(exec) = plan.as_any().downcast_ref::() + if let Some(exec) = (plan.as_ref() as &dyn Any).downcast_ref::() && let Some(config) = exec.data_source().as_any().downcast_ref::() && let Some(adapter_factory) = &config.expr_adapter_factory @@ -481,7 +482,7 @@ fn inject_adapter_into_plan( plan: Arc, adapter_factory: Arc, ) -> Result> { - if let Some(exec) = plan.as_any().downcast_ref::() + if let Some(exec) = (plan.as_ref() as &dyn Any).downcast_ref::() && let Some(config) = exec.data_source().as_any().downcast_ref::() { let new_config = FileScanConfigBuilder::from(config.clone()) @@ -497,7 +498,7 @@ fn inject_adapter_into_plan( fn verify_adapter_in_plan(plan: &Arc, label: &str) -> bool { // Walk the plan tree to find DataSourceExec with adapter fn check_plan(plan: &dyn ExecutionPlan) -> bool { - if let Some(exec) = plan.as_any().downcast_ref::() + if let Some(exec) = (plan as &dyn Any).downcast_ref::() && let Some(config) = exec.data_source().as_any().downcast_ref::() && config.expr_adapter_factory.is_some() diff --git a/datafusion-examples/examples/custom_data_source/custom_datasource.rs b/datafusion-examples/examples/custom_data_source/custom_datasource.rs index 71e589dcf6e88..0b441b9d7c8d2 100644 --- a/datafusion-examples/examples/custom_data_source/custom_datasource.rs +++ b/datafusion-examples/examples/custom_data_source/custom_datasource.rs @@ -235,10 +235,6 @@ impl ExecutionPlan for CustomExec { "CustomExec" } - fn as_any(&self) -> &dyn Any { - self - } - fn properties(&self) -> &Arc { &self.cache } diff --git a/datafusion-examples/examples/data_io/parquet_exec_visitor.rs b/datafusion-examples/examples/data_io/parquet_exec_visitor.rs index 47caf9480df93..10c2666dcac7a 100644 --- a/datafusion-examples/examples/data_io/parquet_exec_visitor.rs +++ b/datafusion-examples/examples/data_io/parquet_exec_visitor.rs @@ -17,6 +17,7 @@ //! See `main.rs` for how to run it. +use std::any::Any; use std::sync::Arc; use datafusion::datasource::file_format::parquet::ParquetFormat; @@ -104,7 +105,8 @@ impl ExecutionPlanVisitor for ParquetExecVisitor { /// or `post_visit` (visit each node after its children/inputs) fn pre_visit(&mut self, plan: &dyn ExecutionPlan) -> Result { // If needed match on a specific `ExecutionPlan` node type - if let Some(data_source_exec) = plan.as_any().downcast_ref::() + if let Some(data_source_exec) = + (plan as &dyn Any).downcast_ref::() && let Some((file_config, _)) = data_source_exec.downcast_to_file_source::() { diff --git a/datafusion-examples/examples/execution_monitoring/memory_pool_execution_plan.rs b/datafusion-examples/examples/execution_monitoring/memory_pool_execution_plan.rs index 1440347d4413d..dc374c7e02fe5 100644 --- a/datafusion-examples/examples/execution_monitoring/memory_pool_execution_plan.rs +++ b/datafusion-examples/examples/execution_monitoring/memory_pool_execution_plan.rs @@ -43,7 +43,6 @@ use datafusion::physical_plan::{ }; use datafusion::prelude::*; use futures::stream::{StreamExt, TryStreamExt}; -use std::any::Any; use std::fmt; use std::sync::Arc; @@ -226,10 +225,6 @@ impl ExecutionPlan for BufferingExecutionPlan { "BufferingExecutionPlan" } - fn as_any(&self) -> &dyn Any { - self - } - fn schema(&self) -> SchemaRef { self.schema.clone() } diff --git a/datafusion-examples/examples/proto/composed_extension_codec.rs b/datafusion-examples/examples/proto/composed_extension_codec.rs index df3d58b7bfb81..d0590a44a4913 100644 --- a/datafusion-examples/examples/proto/composed_extension_codec.rs +++ b/datafusion-examples/examples/proto/composed_extension_codec.rs @@ -103,10 +103,6 @@ impl ExecutionPlan for ParentExec { "ParentExec" } - fn as_any(&self) -> &dyn Any { - self - } - fn properties(&self) -> &Arc { unreachable!() } @@ -161,7 +157,10 @@ impl PhysicalExtensionCodec for ParentPhysicalExtensionCodec { } fn try_encode(&self, node: Arc, buf: &mut Vec) -> Result<()> { - if node.as_any().downcast_ref::().is_some() { + if (node.as_ref() as &dyn Any) + .downcast_ref::() + .is_some() + { buf.extend_from_slice("ParentExec".as_bytes()); Ok(()) } else { @@ -188,10 +187,6 @@ impl ExecutionPlan for ChildExec { "ChildExec" } - fn as_any(&self) -> &dyn Any { - self - } - fn properties(&self) -> &Arc { unreachable!() } @@ -244,7 +239,10 @@ impl PhysicalExtensionCodec for ChildPhysicalExtensionCodec { } fn try_encode(&self, node: Arc, buf: &mut Vec) -> Result<()> { - if node.as_any().downcast_ref::().is_some() { + if (node.as_ref() as &dyn Any) + .downcast_ref::() + .is_some() + { buf.extend_from_slice("ChildExec".as_bytes()); Ok(()) } else { diff --git a/datafusion-examples/examples/proto/expression_deduplication.rs b/datafusion-examples/examples/proto/expression_deduplication.rs index 0dec807f8043a..1b074f8897623 100644 --- a/datafusion-examples/examples/proto/expression_deduplication.rs +++ b/datafusion-examples/examples/proto/expression_deduplication.rs @@ -32,6 +32,7 @@ //! This demonstrates the decorator pattern enabled by the `PhysicalExtensionCodec` trait, //! where all expression serialization/deserialization routes through the codec methods. +use std::any::Any; use std::collections::HashMap; use std::fmt::Debug; use std::sync::{Arc, RwLock}; @@ -124,7 +125,8 @@ pub async fn expression_deduplication() -> Result<()> { // Step 5: check that we deduplicated expressions println!("Step 5: Checking for deduplicated expressions..."); - let Some(filter_exec) = deserialized_plan.as_any().downcast_ref::() + let Some(filter_exec) = + (deserialized_plan.as_ref() as &dyn Any).downcast_ref::() else { panic!("Deserialized plan is not a FilterExec"); }; diff --git a/datafusion-examples/examples/relation_planner/table_sample.rs b/datafusion-examples/examples/relation_planner/table_sample.rs index 04e5efd9706a6..42342e5f1a641 100644 --- a/datafusion-examples/examples/relation_planner/table_sample.rs +++ b/datafusion-examples/examples/relation_planner/table_sample.rs @@ -80,7 +80,6 @@ //! ``` use std::{ - any::Any, fmt::{self, Debug, Formatter}, hash::{Hash, Hasher}, pin::Pin, @@ -682,10 +681,6 @@ impl ExecutionPlan for SampleExec { "SampleExec" } - fn as_any(&self) -> &dyn Any { - self - } - fn properties(&self) -> &Arc { &self.cache } diff --git a/datafusion/catalog/src/memory/table.rs b/datafusion/catalog/src/memory/table.rs index 9b91062657a07..a0a34a8f6d6ad 100644 --- a/datafusion/catalog/src/memory/table.rs +++ b/datafusion/catalog/src/memory/table.rs @@ -594,10 +594,6 @@ impl ExecutionPlan for DmlResultExec { "DmlResultExec" } - fn as_any(&self) -> &dyn Any { - self - } - fn schema(&self) -> SchemaRef { Arc::clone(&self.schema) } diff --git a/datafusion/core/src/datasource/listing/table.rs b/datafusion/core/src/datasource/listing/table.rs index 5dd11739c1f57..681451f8d14a1 100644 --- a/datafusion/core/src/datasource/listing/table.rs +++ b/datafusion/core/src/datasource/listing/table.rs @@ -107,6 +107,8 @@ impl ListingTableConfigExt for ListingTableConfig { #[cfg(test)] mod tests { + use std::any::Any; + #[cfg(feature = "parquet")] use crate::datasource::file_format::parquet::ParquetFormat; use crate::datasource::listing::table::ListingTableConfigExt; @@ -404,7 +406,7 @@ mod tests { .await .expect("Empty execution plan"); - assert!(scan.as_any().is::()); + assert!((scan.as_ref() as &dyn Any).is::()); assert_eq!( columns(&scan.schema()), vec!["a".to_owned(), "p1".to_owned()] diff --git a/datafusion/core/src/physical_planner.rs b/datafusion/core/src/physical_planner.rs index e25969903521c..68fcc9a03a661 100644 --- a/datafusion/core/src/physical_planner.rs +++ b/datafusion/core/src/physical_planner.rs @@ -3624,8 +3624,7 @@ mod tests { .build()?; let execution_plan = plan(&logical_plan).await?; - let final_hash_agg = execution_plan - .as_any() + let final_hash_agg = (execution_plan.as_ref() as &dyn Any) .downcast_ref::() .expect("hash aggregate"); assert_eq!( @@ -3652,8 +3651,7 @@ mod tests { .build()?; let execution_plan = plan(&logical_plan).await?; - let final_hash_agg = execution_plan - .as_any() + let final_hash_agg = (execution_plan.as_ref() as &dyn Any) .downcast_ref::() .expect("hash aggregate"); assert_eq!( @@ -3788,7 +3786,7 @@ mod tests { .unwrap(); let plan = plan(&logical_plan).await.unwrap(); - if let Some(plan) = plan.as_any().downcast_ref::() { + if let Some(plan) = (plan.as_ref() as &dyn Any).downcast_ref::() { let stringified_plans = plan.stringified_plans(); assert!(stringified_plans.len() >= 4); assert!( @@ -3856,7 +3854,7 @@ mod tests { .handle_explain(&explain, &ctx.state()) .await .unwrap(); - if let Some(plan) = plan.as_any().downcast_ref::() { + if let Some(plan) = (plan.as_ref() as &dyn Any).downcast_ref::() { let stringified_plans = plan.stringified_plans(); assert_eq!(stringified_plans.len(), 1); assert_eq!(stringified_plans[0].plan.as_str(), "Test Err"); @@ -3996,10 +3994,6 @@ mod tests { } /// Return a reference to Any that can be used for downcasting - fn as_any(&self) -> &dyn Any { - self - } - fn properties(&self) -> &Arc { &self.cache } @@ -4162,9 +4156,6 @@ digraph { fn schema(&self) -> SchemaRef { Arc::new(Schema::empty()) } - fn as_any(&self) -> &dyn Any { - unimplemented!() - } fn children(&self) -> Vec<&Arc> { self.0.iter().collect::>() } @@ -4217,9 +4208,6 @@ digraph { ) -> Result> { unimplemented!() } - fn as_any(&self) -> &dyn Any { - unimplemented!() - } fn children(&self) -> Vec<&Arc> { unimplemented!() } @@ -4344,9 +4332,6 @@ digraph { ) -> Result> { unimplemented!() } - fn as_any(&self) -> &dyn Any { - unimplemented!() - } fn children(&self) -> Vec<&Arc> { vec![] } @@ -4758,6 +4743,6 @@ digraph { .unwrap(); assert_eq!(plan.schema(), schema); - assert!(plan.as_any().is::()); + assert!((plan.as_ref() as &dyn Any).is::()); } } diff --git a/datafusion/core/src/test_util/parquet.rs b/datafusion/core/src/test_util/parquet.rs index 53084fd9a0df5..307d7e6672744 100644 --- a/datafusion/core/src/test_util/parquet.rs +++ b/datafusion/core/src/test_util/parquet.rs @@ -196,7 +196,8 @@ impl TestParquetFile { /// Recursively searches for DataSourceExec and returns the metrics /// on the first one it finds pub fn parquet_metrics(plan: &Arc) -> Option { - if let Some(data_source_exec) = plan.as_any().downcast_ref::() + if let Some(data_source_exec) = + (plan.as_ref() as &dyn std::any::Any).downcast_ref::() && data_source_exec .downcast_to_file_source::() .is_some() diff --git a/datafusion/core/tests/custom_sources_cases/mod.rs b/datafusion/core/tests/custom_sources_cases/mod.rs index 6919d9794b29e..e1225136e2836 100644 --- a/datafusion/core/tests/custom_sources_cases/mod.rs +++ b/datafusion/core/tests/custom_sources_cases/mod.rs @@ -157,10 +157,6 @@ impl ExecutionPlan for CustomExecutionPlan { Self::static_name() } - fn as_any(&self) -> &dyn Any { - self - } - fn properties(&self) -> &Arc { &self.cache } @@ -335,7 +331,10 @@ async fn optimizers_catch_all_statistics() { #[expect(clippy::needless_pass_by_value)] fn contains_place_holder_exec(plan: Arc) -> bool { - if plan.as_any().is::() { + if (plan.as_ref() as &dyn Any) + .downcast_ref::() + .is_some() + { true } else if plan.children().len() != 1 { false diff --git a/datafusion/core/tests/custom_sources_cases/provider_filter_pushdown.rs b/datafusion/core/tests/custom_sources_cases/provider_filter_pushdown.rs index 8078b0a7ec158..82774c8b44dde 100644 --- a/datafusion/core/tests/custom_sources_cases/provider_filter_pushdown.rs +++ b/datafusion/core/tests/custom_sources_cases/provider_filter_pushdown.rs @@ -109,10 +109,6 @@ impl ExecutionPlan for CustomPlan { Self::static_name() } - fn as_any(&self) -> &dyn std::any::Any { - self - } - fn properties(&self) -> &Arc { &self.cache } diff --git a/datafusion/core/tests/custom_sources_cases/statistics.rs b/datafusion/core/tests/custom_sources_cases/statistics.rs index 561c6b3b246ff..63b5398b7072a 100644 --- a/datafusion/core/tests/custom_sources_cases/statistics.rs +++ b/datafusion/core/tests/custom_sources_cases/statistics.rs @@ -155,10 +155,6 @@ impl ExecutionPlan for StatisticsValidation { Self::static_name() } - fn as_any(&self) -> &dyn Any { - self - } - fn properties(&self) -> &Arc { &self.cache } diff --git a/datafusion/core/tests/fuzz_cases/aggregate_fuzz.rs b/datafusion/core/tests/fuzz_cases/aggregate_fuzz.rs index d64223abdb767..ada0628c22ccc 100644 --- a/datafusion/core/tests/fuzz_cases/aggregate_fuzz.rs +++ b/datafusion/core/tests/fuzz_cases/aggregate_fuzz.rs @@ -547,7 +547,9 @@ async fn verify_ordered_aggregate(frame: &DataFrame, expected_sort: bool) { type Node = Arc; fn f_down(&mut self, node: &'n Self::Node) -> Result { - if let Some(exec) = node.as_any().downcast_ref::() { + if let Some(exec) = + (node.as_ref() as &dyn std::any::Any).downcast_ref::() + { if self.expected_sort { assert!(matches!( exec.input_order_mode(), diff --git a/datafusion/core/tests/fuzz_cases/once_exec.rs b/datafusion/core/tests/fuzz_cases/once_exec.rs index eed172f09f994..403e377a690e2 100644 --- a/datafusion/core/tests/fuzz_cases/once_exec.rs +++ b/datafusion/core/tests/fuzz_cases/once_exec.rs @@ -24,7 +24,6 @@ use datafusion_physical_plan::execution_plan::{Boundedness, EmissionType}; use datafusion_physical_plan::{ DisplayAs, DisplayFormatType, ExecutionPlan, PlanProperties, }; -use std::any::Any; use std::fmt::{Debug, Formatter}; use std::sync::{Arc, Mutex}; @@ -80,10 +79,6 @@ impl ExecutionPlan for OnceExec { Self::static_name() } - fn as_any(&self) -> &dyn Any { - self - } - fn properties(&self) -> &Arc { &self.cache } diff --git a/datafusion/core/tests/memory_limit/repartition_mem_limit.rs b/datafusion/core/tests/memory_limit/repartition_mem_limit.rs index b21bffebaf95e..efff9527bda6a 100644 --- a/datafusion/core/tests/memory_limit/repartition_mem_limit.rs +++ b/datafusion/core/tests/memory_limit/repartition_mem_limit.rs @@ -15,6 +15,7 @@ // specific language governing permissions and limitations // under the License. +use std::any::Any; use std::sync::Arc; use arrow::array::{ArrayRef, Int32Array, RecordBatch}; @@ -74,7 +75,7 @@ async fn test_repartition_memory_limit() { let mut metrics = None; Arc::clone(&plan) .transform_down(|node| { - if node.as_any().is::() { + if (node.as_ref() as &dyn Any).is::() { metrics = node.metrics(); } Ok(Transformed::no(node)) diff --git a/datafusion/core/tests/parquet/file_statistics.rs b/datafusion/core/tests/parquet/file_statistics.rs index fdefdafa00aa4..e9167bfaba879 100644 --- a/datafusion/core/tests/parquet/file_statistics.rs +++ b/datafusion/core/tests/parquet/file_statistics.rs @@ -15,6 +15,7 @@ // specific language governing permissions and limitations // under the License. +use std::any::Any; use std::fs; use std::sync::Arc; @@ -88,7 +89,7 @@ async fn check_stats_precision_with_filter_pushdown() { .unwrap(); assert!( - optimized_exec.as_any().is::(), + (optimized_exec.as_ref() as &dyn Any).is::(), "Sanity check that the pushdown did what we expected" ); // Scan with filter pushdown, stats are inexact @@ -196,7 +197,9 @@ async fn list_files_with_session_level_cache() { //Session 1 first time list files assert_eq!(get_list_file_cache_size(&state1), 0); let exec1 = table1.scan(&state1, None, &[], None).await.unwrap(); - let data_source_exec = exec1.as_any().downcast_ref::().unwrap(); + let data_source_exec = (exec1.as_ref() as &dyn Any) + .downcast_ref::() + .unwrap(); let data_source = data_source_exec.data_source(); let parquet1 = data_source .as_any() @@ -212,7 +215,9 @@ async fn list_files_with_session_level_cache() { //check session 1 cache result not show in session 2 assert_eq!(get_list_file_cache_size(&state2), 0); let exec2 = table2.scan(&state2, None, &[], None).await.unwrap(); - let data_source_exec = exec2.as_any().downcast_ref::().unwrap(); + let data_source_exec = (exec2.as_ref() as &dyn Any) + .downcast_ref::() + .unwrap(); let data_source = data_source_exec.data_source(); let parquet2 = data_source .as_any() @@ -228,7 +233,9 @@ async fn list_files_with_session_level_cache() { //check session 1 cache result not show in session 2 assert_eq!(get_list_file_cache_size(&state1), 1); let exec3 = table1.scan(&state1, None, &[], None).await.unwrap(); - let data_source_exec = exec3.as_any().downcast_ref::().unwrap(); + let data_source_exec = (exec3.as_ref() as &dyn Any) + .downcast_ref::() + .unwrap(); let data_source = data_source_exec.data_source(); let parquet3 = data_source .as_any() diff --git a/datafusion/core/tests/parquet/utils.rs b/datafusion/core/tests/parquet/utils.rs index e5e0026ec1f16..a18882a324edb 100644 --- a/datafusion/core/tests/parquet/utils.rs +++ b/datafusion/core/tests/parquet/utils.rs @@ -21,6 +21,7 @@ use datafusion::datasource::physical_plan::ParquetSource; use datafusion::datasource::source::DataSourceExec; use datafusion_physical_plan::metrics::MetricsSet; use datafusion_physical_plan::{ExecutionPlan, ExecutionPlanVisitor, accept}; +use std::any::Any; /// Find the metrics from the first DataSourceExec encountered in the plan #[derive(Debug)] @@ -47,7 +48,8 @@ impl MetricsFinder { impl ExecutionPlanVisitor for MetricsFinder { type Error = std::convert::Infallible; fn pre_visit(&mut self, plan: &dyn ExecutionPlan) -> Result { - if let Some(data_source_exec) = plan.as_any().downcast_ref::() + if let Some(data_source_exec) = + (plan as &dyn Any).downcast_ref::() && data_source_exec .downcast_to_file_source::() .is_some() diff --git a/datafusion/core/tests/physical_optimizer/aggregate_statistics.rs b/datafusion/core/tests/physical_optimizer/aggregate_statistics.rs index 850f9d187780b..168bf8a13bddc 100644 --- a/datafusion/core/tests/physical_optimizer/aggregate_statistics.rs +++ b/datafusion/core/tests/physical_optimizer/aggregate_statistics.rs @@ -15,6 +15,7 @@ // specific language governing permissions and limitations // under the License. +use std::any::Any; use std::sync::Arc; use crate::physical_optimizer::test_utils::TestAggregate; @@ -83,7 +84,7 @@ async fn assert_count_optim_success( let optimized = AggregateStatistics::new().optimize(Arc::clone(&plan), &config)?; // A ProjectionExec is a sign that the count optimization was applied - assert!(optimized.as_any().is::()); + assert!((optimized.as_ref() as &dyn Any).is::()); // run both the optimized and nonoptimized plan let optimized_result = @@ -280,7 +281,7 @@ async fn test_count_inexact_stat() -> Result<()> { let optimized = AggregateStatistics::new().optimize(Arc::new(final_agg), &conf)?; // check that the original ExecutionPlan was not replaced - assert!(optimized.as_any().is::()); + assert!((optimized.as_ref() as &dyn Any).is::()); Ok(()) } @@ -324,7 +325,7 @@ async fn test_count_with_nulls_inexact_stat() -> Result<()> { let optimized = AggregateStatistics::new().optimize(Arc::new(final_agg), &conf)?; // check that the original ExecutionPlan was not replaced - assert!(optimized.as_any().is::()); + assert!((optimized.as_ref() as &dyn Any).is::()); Ok(()) } @@ -526,7 +527,7 @@ async fn test_count_distinct_optimization() -> Result<()> { if case.expect_optimized { assert!( - optimized.as_any().is::(), + (optimized.as_ref() as &dyn Any).is::(), "'{}': expected ProjectionExec", case.name ); @@ -544,7 +545,7 @@ async fn test_count_distinct_optimization() -> Result<()> { } } else { assert!( - optimized.as_any().is::(), + (optimized.as_ref() as &dyn Any).is::(), "'{}': expected AggregateExec (not optimized)", case.name ); diff --git a/datafusion/core/tests/physical_optimizer/enforce_distribution.rs b/datafusion/core/tests/physical_optimizer/enforce_distribution.rs index 3a6106c45356f..6665553cc3978 100644 --- a/datafusion/core/tests/physical_optimizer/enforce_distribution.rs +++ b/datafusion/core/tests/physical_optimizer/enforce_distribution.rs @@ -15,6 +15,7 @@ // specific language governing permissions and limitations // under the License. +use std::any::Any; use std::fmt::Debug; use std::ops::Deref; use std::sync::Arc; @@ -174,10 +175,6 @@ impl ExecutionPlan for SortRequiredExec { "SortRequiredExec" } - fn as_any(&self) -> &dyn std::any::Any { - self - } - fn properties(&self) -> &Arc { &self.cache } @@ -275,10 +272,6 @@ impl ExecutionPlan for SinglePartitionMaintainsOrderExec { "SinglePartitionMaintainsOrderExec" } - fn as_any(&self) -> &dyn std::any::Any { - self - } - fn properties(&self) -> &Arc { &self.cache } @@ -3899,9 +3892,7 @@ fn test_replace_order_preserving_variants_with_fetch() -> Result<()> { let result = replace_order_preserving_variants(dist_context)?; // Verify the plan was transformed to CoalescePartitionsExec - result - .plan - .as_any() + (result.plan.as_ref() as &dyn Any) .downcast_ref::() .expect("Expected CoalescePartitionsExec"); diff --git a/datafusion/core/tests/physical_optimizer/filter_pushdown.rs b/datafusion/core/tests/physical_optimizer/filter_pushdown.rs index 9f3dffd23004d..4169df723bfaa 100644 --- a/datafusion/core/tests/physical_optimizer/filter_pushdown.rs +++ b/datafusion/core/tests/physical_optimizer/filter_pushdown.rs @@ -15,6 +15,7 @@ // specific language governing permissions and limitations // under the License. +use std::any::Any; use std::sync::{Arc, LazyLock}; use arrow::{ @@ -4323,8 +4324,7 @@ async fn test_hashjoin_dynamic_filter_pushdown_is_used() { .unwrap(); // Get the HashJoinExec to check the dynamic filter - let hash_join = plan - .as_any() + let hash_join = (plan.as_ref() as &dyn Any) .downcast_ref::() .expect("Plan should be HashJoinExec"); diff --git a/datafusion/core/tests/physical_optimizer/join_selection.rs b/datafusion/core/tests/physical_optimizer/join_selection.rs index 1c94a7bd1e91c..5ea7859218d21 100644 --- a/datafusion/core/tests/physical_optimizer/join_selection.rs +++ b/datafusion/core/tests/physical_optimizer/join_selection.rs @@ -232,8 +232,7 @@ async fn test_join_with_swap() { .optimize(join, &ConfigOptions::new()) .unwrap(); - let swapping_projection = optimized_join - .as_any() + let swapping_projection = (optimized_join.as_ref() as &dyn Any) .downcast_ref::() .expect("A proj is required to swap columns back to their original order"); @@ -245,9 +244,7 @@ async fn test_join_with_swap() { assert_eq!(proj_expr.alias, "small_col"); assert_col_expr(&proj_expr.expr, "small_col", 0); - let swapped_join = swapping_projection - .input() - .as_any() + let swapped_join = (swapping_projection.input().as_ref() as &dyn Any) .downcast_ref::() .expect("The type of the plan should not be changed"); @@ -295,8 +292,7 @@ async fn test_left_join_no_swap() { .optimize(join, &ConfigOptions::new()) .unwrap(); - let swapped_join = optimized_join - .as_any() + let swapped_join = (optimized_join.as_ref() as &dyn Any) .downcast_ref::() .expect("The type of the plan should not be changed"); @@ -346,8 +342,7 @@ async fn test_join_with_swap_semi() { .optimize(Arc::new(join), &ConfigOptions::new()) .unwrap(); - let swapped_join = optimized_join - .as_any() + let swapped_join = (optimized_join.as_ref() as &dyn Any) .downcast_ref::() .expect( "A proj is not required to swap columns back to their original order", @@ -402,8 +397,7 @@ async fn test_join_with_swap_mark() { .optimize(Arc::new(join), &ConfigOptions::new()) .unwrap(); - let swapped_join = optimized_join - .as_any() + let swapped_join = (optimized_join.as_ref() as &dyn Any) .downcast_ref::() .expect( "A proj is not required to swap columns back to their original order", @@ -534,8 +528,7 @@ async fn test_join_no_swap() { .optimize(join, &ConfigOptions::new()) .unwrap(); - let swapped_join = optimized_join - .as_any() + let swapped_join = (optimized_join.as_ref() as &dyn Any) .downcast_ref::() .expect("The type of the plan should not be changed"); @@ -583,8 +576,7 @@ async fn test_nl_join_with_swap(join_type: JoinType) { .optimize(join, &ConfigOptions::new()) .unwrap(); - let swapping_projection = optimized_join - .as_any() + let swapping_projection = (optimized_join.as_ref() as &dyn Any) .downcast_ref::() .expect("A proj is required to swap columns back to their original order"); @@ -596,9 +588,7 @@ async fn test_nl_join_with_swap(join_type: JoinType) { assert_eq!(proj_expr.alias, "small_col"); assert_col_expr(&proj_expr.expr, "small_col", 0); - let swapped_join = swapping_projection - .input() - .as_any() + let swapped_join = (swapping_projection.input().as_ref() as &dyn Any) .downcast_ref::() .expect("The type of the plan should not be changed"); @@ -664,8 +654,7 @@ async fn test_nl_join_with_swap_no_proj(join_type: JoinType) { ) .unwrap(); - let swapped_join = optimized_join - .as_any() + let swapped_join = (optimized_join.as_ref() as &dyn Any) .downcast_ref::() .expect("The type of the plan should not be changed"); @@ -759,7 +748,7 @@ async fn test_hash_join_swap_on_joins_with_projections( let swapped = join .swap_inputs(PartitionMode::Partitioned) .expect("swap_hash_join must support joins with projections"); - let swapped_join = swapped.as_any().downcast_ref::().expect( + let swapped_join = (swapped.as_ref() as &dyn Any).downcast_ref::().expect( "ProjectionExec won't be added above if HashJoinExec contains embedded projection", ); @@ -925,19 +914,15 @@ fn check_join_partition_mode( .unwrap(); if !is_swapped { - let swapped_join = optimized_join - .as_any() + let swapped_join = (optimized_join.as_ref() as &dyn Any) .downcast_ref::() .expect("The type of the plan should not be changed"); assert_eq!(*swapped_join.partition_mode(), expected_mode); } else { - let swapping_projection = optimized_join - .as_any() + let swapping_projection = (optimized_join.as_ref() as &dyn Any) .downcast_ref::() .expect("A proj is required to swap columns back to their original order"); - let swapped_join = swapping_projection - .input() - .as_any() + let swapped_join = (swapping_projection.input().as_ref() as &dyn Any) .downcast_ref::() .expect("The type of the plan should not be changed"); @@ -1049,10 +1034,6 @@ impl ExecutionPlan for UnboundedExec { Self::static_name() } - fn as_any(&self) -> &dyn Any { - self - } - fn properties(&self) -> &Arc { &self.cache } @@ -1164,10 +1145,6 @@ impl ExecutionPlan for StatisticsExec { Self::static_name() } - fn as_any(&self) -> &dyn Any { - self - } - fn properties(&self) -> &Arc { &self.cache } @@ -1595,10 +1572,10 @@ async fn test_join_with_maybe_swap_unbounded_case(t: TestCase) -> Result<()> { JoinSelection::new().optimize(Arc::clone(&join), &ConfigOptions::new())?; // If swap did happen - let projection_added = optimized_join_plan.as_any().is::(); + let projection_added = + (optimized_join_plan.as_ref() as &dyn Any).is::(); let plan = if projection_added { - let proj = optimized_join_plan - .as_any() + let proj = (optimized_join_plan.as_ref() as &dyn Any) .downcast_ref::() .expect("A proj is required to swap columns back to their original order"); Arc::::clone(proj.input()) @@ -1612,7 +1589,7 @@ async fn test_join_with_maybe_swap_unbounded_case(t: TestCase) -> Result<()> { join_type, mode, .. - }) = plan.as_any().downcast_ref::() + }) = (plan.as_ref() as &dyn Any).downcast_ref::() { let left_changed = Arc::ptr_eq(left, &right_exec); let right_changed = Arc::ptr_eq(right, &left_exec); diff --git a/datafusion/core/tests/physical_optimizer/projection_pushdown.rs b/datafusion/core/tests/physical_optimizer/projection_pushdown.rs index d3448062d1062..6d7922f682432 100644 --- a/datafusion/core/tests/physical_optimizer/projection_pushdown.rs +++ b/datafusion/core/tests/physical_optimizer/projection_pushdown.rs @@ -15,6 +15,7 @@ // specific language governing permissions and limitations // under the License. +use std::any::Any; use std::sync::Arc; use arrow::compute::SortOptions; @@ -513,9 +514,7 @@ fn test_memory_after_projection() -> Result<()> { ); assert_eq!( - after_optimize - .clone() - .as_any() + (after_optimize.clone().as_ref() as &dyn Any) .downcast_ref::() .unwrap() .data_source() @@ -598,8 +597,7 @@ fn test_streaming_table_after_projection() -> Result<()> { let after_optimize = ProjectionPushdown::new().optimize(projection, &ConfigOptions::new())?; - let result = after_optimize - .as_any() + let result = (after_optimize.as_ref() as &dyn Any) .downcast_ref::() .unwrap(); assert_eq!( @@ -791,8 +789,7 @@ fn test_output_req_after_projection() -> Result<()> { .into(), ); assert_eq!( - after_optimize - .as_any() + (after_optimize.as_ref() as &dyn Any) .downcast_ref::() .unwrap() .required_input_ordering()[0] @@ -804,8 +801,7 @@ fn test_output_req_after_projection() -> Result<()> { Arc::new(Column::new("new_a", 1)), Arc::new(Column::new("b", 2)), ]; - if let Distribution::HashPartitioned(vec) = after_optimize - .as_any() + if let Distribution::HashPartitioned(vec) = (after_optimize.as_ref() as &dyn Any) .downcast_ref::() .unwrap() .required_input_distribution()[0] @@ -1035,8 +1031,7 @@ fn test_join_after_projection() -> Result<()> { assert_eq!( expected_filter_col_ind, - after_optimize - .as_any() + (after_optimize.as_ref() as &dyn Any) .downcast_ref::() .unwrap() .filter() @@ -1399,8 +1394,7 @@ fn test_repartition_after_projection() -> Result<()> { ); assert_eq!( - after_optimize - .as_any() + (after_optimize.as_ref() as &dyn Any) .downcast_ref::() .unwrap() .partitioning() diff --git a/datafusion/core/tests/physical_optimizer/pushdown_utils.rs b/datafusion/core/tests/physical_optimizer/pushdown_utils.rs index ce2cb04b64a5f..e8c02d4efcfd4 100644 --- a/datafusion/core/tests/physical_optimizer/pushdown_utils.rs +++ b/datafusion/core/tests/physical_optimizer/pushdown_utils.rs @@ -490,10 +490,6 @@ impl ExecutionPlan for TestNode { "TestInsertExec" } - fn as_any(&self) -> &dyn Any { - self - } - fn properties(&self) -> &Arc { self.input.properties() } diff --git a/datafusion/core/tests/physical_optimizer/test_utils.rs b/datafusion/core/tests/physical_optimizer/test_utils.rs index 8d9e7b68b8c96..4b6db1abc45d8 100644 --- a/datafusion/core/tests/physical_optimizer/test_utils.rs +++ b/datafusion/core/tests/physical_optimizer/test_utils.rs @@ -17,7 +17,6 @@ //! Test utilities for physical optimizer tests -use std::any::Any; use std::fmt::{Display, Formatter}; use std::sync::{Arc, LazyLock}; @@ -452,10 +451,6 @@ impl ExecutionPlan for RequirementsTestExec { "RequiredInputOrderingExec" } - fn as_any(&self) -> &dyn Any { - self - } - fn properties(&self) -> &Arc { self.input.properties() } @@ -927,10 +922,6 @@ impl ExecutionPlan for TestScan { "TestScan" } - fn as_any(&self) -> &dyn Any { - self - } - fn properties(&self) -> &Arc { &self.plan_properties } diff --git a/datafusion/core/tests/sql/explain_analyze.rs b/datafusion/core/tests/sql/explain_analyze.rs index b8dc6ab59ceec..589a018c05dbe 100644 --- a/datafusion/core/tests/sql/explain_analyze.rs +++ b/datafusion/core/tests/sql/explain_analyze.rs @@ -18,6 +18,7 @@ use super::*; use insta::assert_snapshot; use rstest::rstest; +use std::any::Any; use datafusion::config::ConfigOptions; use datafusion::physical_plan::display::DisplayableExecutionPlan; @@ -139,14 +140,14 @@ async fn explain_analyze_baseline_metrics() { use datafusion::physical_plan; use datafusion::physical_plan::sorts; - plan.as_any().downcast_ref::().is_some() - || plan.as_any().downcast_ref::().is_some() - || plan.as_any().downcast_ref::().is_some() - || plan.as_any().downcast_ref::().is_some() - || plan.as_any().downcast_ref::().is_some() - || plan.as_any().downcast_ref::().is_some() - || plan.as_any().downcast_ref::().is_some() - || plan.as_any().downcast_ref::().is_some() + (plan as &dyn Any).downcast_ref::().is_some() + || (plan as &dyn Any).downcast_ref::().is_some() + || (plan as &dyn Any).downcast_ref::().is_some() + || (plan as &dyn Any).downcast_ref::().is_some() + || (plan as &dyn Any).downcast_ref::().is_some() + || (plan as &dyn Any).downcast_ref::().is_some() + || (plan as &dyn Any).downcast_ref::().is_some() + || (plan as &dyn Any).downcast_ref::().is_some() } // Validate that the recorded elapsed compute time was more than diff --git a/datafusion/core/tests/user_defined/insert_operation.rs b/datafusion/core/tests/user_defined/insert_operation.rs index 2a2aed82f0af3..c032ce20f9026 100644 --- a/datafusion/core/tests/user_defined/insert_operation.rs +++ b/datafusion/core/tests/user_defined/insert_operation.rs @@ -58,7 +58,9 @@ async fn insert_operation_is_passed_correctly_to_table_provider() { async fn assert_insert_op(ctx: &SessionContext, sql: &str, insert_op: InsertOp) { let df = ctx.sql(sql).await.unwrap(); let plan = df.create_physical_plan().await.unwrap(); - let exec = plan.as_any().downcast_ref::().unwrap(); + let exec = (plan.as_ref() as &dyn Any) + .downcast_ref::() + .unwrap(); assert_eq!(exec.op, insert_op); } @@ -158,10 +160,6 @@ impl ExecutionPlan for TestInsertExec { "TestInsertExec" } - fn as_any(&self) -> &dyn Any { - self - } - fn properties(&self) -> &Arc { &self.plan_properties } diff --git a/datafusion/core/tests/user_defined/user_defined_plan.rs b/datafusion/core/tests/user_defined/user_defined_plan.rs index 6e4ed69e508d3..505468a19cd37 100644 --- a/datafusion/core/tests/user_defined/user_defined_plan.rs +++ b/datafusion/core/tests/user_defined/user_defined_plan.rs @@ -60,7 +60,7 @@ use std::fmt::Debug; use std::hash::Hash; use std::task::{Context, Poll}; -use std::{any::Any, collections::BTreeMap, fmt, sync::Arc}; +use std::{collections::BTreeMap, fmt, sync::Arc}; use arrow::array::{Array, ArrayRef, StringViewArray}; use arrow::{ @@ -706,10 +706,6 @@ impl ExecutionPlan for TopKExec { } /// Return a reference to Any that can be used for downcasting - fn as_any(&self) -> &dyn Any { - self - } - fn properties(&self) -> &Arc { &self.cache } diff --git a/datafusion/datasource/src/sink.rs b/datafusion/datasource/src/sink.rs index 155c951fe5756..d438f2de92a0d 100644 --- a/datafusion/datasource/src/sink.rs +++ b/datafusion/datasource/src/sink.rs @@ -171,10 +171,6 @@ impl ExecutionPlan for DataSinkExec { } /// Return a reference to Any that can be used for downcasting - fn as_any(&self) -> &dyn Any { - self - } - fn properties(&self) -> &Arc { &self.cache } diff --git a/datafusion/datasource/src/source.rs b/datafusion/datasource/src/source.rs index 73fc46f634acc..81e15d0a2a092 100644 --- a/datafusion/datasource/src/source.rs +++ b/datafusion/datasource/src/source.rs @@ -285,10 +285,6 @@ impl ExecutionPlan for DataSourceExec { "DataSourceExec" } - fn as_any(&self) -> &dyn Any { - self - } - fn properties(&self) -> &Arc { &self.cache } diff --git a/datafusion/ffi/src/execution_plan.rs b/datafusion/ffi/src/execution_plan.rs index eba16d9390787..811bd365787f5 100644 --- a/datafusion/ffi/src/execution_plan.rs +++ b/datafusion/ffi/src/execution_plan.rs @@ -214,7 +214,8 @@ fn pass_runtime_to_children( runtime: &Handle, ) -> Result>> { let mut updated_children = false; - let plan_is_foreign = plan.as_any().is::(); + let plan_is_foreign = + (plan.as_ref() as &dyn std::any::Any).is::(); let children = plan .children() @@ -233,7 +234,9 @@ fn pass_runtime_to_children( // `ForeignExecutionPlan`. In this case wrap the plan in a `ForeignExecutionPlan` // because when we call `with_new_children` below it will extract the // FFI plan that does contain the runtime. - if plan_is_foreign && !child.as_any().is::() { + if plan_is_foreign + && !(child.as_ref() as &dyn std::any::Any).is::() + { updated_children = true; let ffi_child = FFI_ExecutionPlan::new(child, Some(runtime.clone())); let foreign_child = ForeignExecutionPlan::try_from(ffi_child); @@ -255,7 +258,9 @@ impl FFI_ExecutionPlan { pub fn new(mut plan: Arc, runtime: Option) -> Self { // Note to developers: `pass_runtime_to_children` relies on the logic here to // get the underlying FFI plan during calls to `new_with_children`. - if let Some(plan) = plan.as_any().downcast_ref::() { + if let Some(plan) = + (plan.as_ref() as &dyn std::any::Any).downcast_ref::() + { return plan.plan.clone(); } @@ -369,10 +374,6 @@ impl ExecutionPlan for ForeignExecutionPlan { &self.name } - fn as_any(&self) -> &dyn std::any::Any { - self - } - fn properties(&self) -> &Arc { &self.properties } @@ -482,10 +483,6 @@ pub mod tests { "empty-exec" } - fn as_any(&self) -> &dyn std::any::Any { - self - } - fn properties(&self) -> &Arc { &self.props } @@ -610,14 +607,17 @@ pub mod tests { // Verify local libraries can be downcast to their original let foreign_plan: Arc = (&ffi_plan).try_into().unwrap(); - assert!(foreign_plan.as_any().downcast_ref::().is_some()); + assert!( + (foreign_plan.as_ref() as &dyn std::any::Any) + .downcast_ref::() + .is_some() + ); // Verify different library markers generate foreign providers ffi_plan.library_marker_id = crate::mock_foreign_marker_id; let foreign_plan: Arc = (&ffi_plan).try_into().unwrap(); assert!( - foreign_plan - .as_any() + (foreign_plan.as_ref() as &dyn std::any::Any) .downcast_ref::() .is_some() ); diff --git a/datafusion/ffi/src/proto/physical_extension_codec.rs b/datafusion/ffi/src/proto/physical_extension_codec.rs index ad4a9a0ae75b3..8fee9159357ed 100644 --- a/datafusion/ffi/src/proto/physical_extension_codec.rs +++ b/datafusion/ffi/src/proto/physical_extension_codec.rs @@ -466,7 +466,7 @@ pub(crate) mod tests { ) -> Result<()> { buf.push(Self::MAGIC_NUMBER); - let Some(_) = node.as_any().downcast_ref::() else { + let Some(_) = (node.as_ref() as &dyn Any).downcast_ref::() else { return exec_err!("TestExtensionCodec only expects EmptyExec"); }; @@ -588,7 +588,7 @@ pub(crate) mod tests { let returned_exec = foreign_codec.try_decode(&bytes, &input_execs, ctx.task_ctx().as_ref())?; - assert!(returned_exec.as_any().is::()); + assert!((returned_exec.as_ref() as &dyn Any).is::()); Ok(()) } diff --git a/datafusion/ffi/src/tests/async_provider.rs b/datafusion/ffi/src/tests/async_provider.rs index e9fa31a7fc6ed..0097872d4970d 100644 --- a/datafusion/ffi/src/tests/async_provider.rs +++ b/datafusion/ffi/src/tests/async_provider.rs @@ -191,10 +191,6 @@ impl ExecutionPlan for AsyncTestExecutionPlan { "async test execution plan" } - fn as_any(&self) -> &dyn Any { - self - } - fn properties(&self) -> &Arc { &self.properties } diff --git a/datafusion/ffi/tests/ffi_execution_plan.rs b/datafusion/ffi/tests/ffi_execution_plan.rs index d81f947dc80ed..8944bfd0c7bdc 100644 --- a/datafusion/ffi/tests/ffi_execution_plan.rs +++ b/datafusion/ffi/tests/ffi_execution_plan.rs @@ -58,7 +58,7 @@ mod tests { let child_plan: Arc = (&child_plan) .try_into() .expect("should be able create plan"); - assert!(child_plan.as_any().is::()); + assert!((child_plan.as_ref() as &dyn std::any::Any).is::()); let grandchild_plan = generate_local_plan(); diff --git a/datafusion/physical-optimizer/src/aggregate_statistics.rs b/datafusion/physical-optimizer/src/aggregate_statistics.rs index 5caee8b047d83..a5066cd039034 100644 --- a/datafusion/physical-optimizer/src/aggregate_statistics.rs +++ b/datafusion/physical-optimizer/src/aggregate_statistics.rs @@ -25,6 +25,7 @@ use datafusion_physical_plan::placeholder_row::PlaceholderRowExec; use datafusion_physical_plan::projection::{ProjectionExec, ProjectionExpr}; use datafusion_physical_plan::udaf::{AggregateFunctionExpr, StatisticsArgs}; use datafusion_physical_plan::{ExecutionPlan, expressions}; +use std::any::Any; use std::sync::Arc; use crate::PhysicalOptimizerRule; @@ -50,8 +51,7 @@ impl PhysicalOptimizerRule for AggregateStatistics { config: &ConfigOptions, ) -> Result> { if let Some(partial_agg_exec) = take_optimizable(&*plan) { - let partial_agg_exec = partial_agg_exec - .as_any() + let partial_agg_exec = (partial_agg_exec.as_ref() as &dyn Any) .downcast_ref::() .expect("take_optimizable() ensures that this is a AggregateExec"); let stats = partial_agg_exec.input().partition_statistics(None)?; @@ -115,13 +115,14 @@ impl PhysicalOptimizerRule for AggregateStatistics { /// We would have preferred to return a casted ref to AggregateExec but the recursion requires /// the `ExecutionPlan.children()` method that returns an owned reference. fn take_optimizable(node: &dyn ExecutionPlan) -> Option> { - if let Some(final_agg_exec) = node.as_any().downcast_ref::() + if let Some(final_agg_exec) = (node as &dyn Any).downcast_ref::() && final_agg_exec.mode().input_mode() == AggregateInputMode::Partial && final_agg_exec.group_expr().is_empty() { let mut child = Arc::clone(final_agg_exec.input()); loop { - if let Some(partial_agg_exec) = child.as_any().downcast_ref::() + if let Some(partial_agg_exec) = + (child.as_ref() as &dyn Any).downcast_ref::() && partial_agg_exec.mode().input_mode() == AggregateInputMode::Raw && partial_agg_exec.group_expr().is_empty() && partial_agg_exec.filter_expr().iter().all(|e| e.is_none()) diff --git a/datafusion/physical-optimizer/src/combine_partial_final_agg.rs b/datafusion/physical-optimizer/src/combine_partial_final_agg.rs index 860406118c1b7..cd1266f0eeb99 100644 --- a/datafusion/physical-optimizer/src/combine_partial_final_agg.rs +++ b/datafusion/physical-optimizer/src/combine_partial_final_agg.rs @@ -18,6 +18,7 @@ //! CombinePartialFinalAggregate optimizer rule checks the adjacent Partial and Final AggregateExecs //! and try to combine them if necessary +use std::any::Any; use std::sync::Arc; use datafusion_common::error::Result; @@ -54,7 +55,9 @@ impl PhysicalOptimizerRule for CombinePartialFinalAggregate { ) -> Result> { plan.transform_down(|plan| { // Check if the plan is AggregateExec - let Some(agg_exec) = plan.as_any().downcast_ref::() else { + let Some(agg_exec) = + (plan.as_ref() as &dyn Any).downcast_ref::() + else { return Ok(Transformed::no(plan)); }; @@ -67,7 +70,7 @@ impl PhysicalOptimizerRule for CombinePartialFinalAggregate { // Check if the input is AggregateExec let Some(input_agg_exec) = - agg_exec.input().as_any().downcast_ref::() + (agg_exec.input().as_ref() as &dyn Any).downcast_ref::() else { return Ok(Transformed::no(plan)); }; diff --git a/datafusion/physical-optimizer/src/enforce_distribution.rs b/datafusion/physical-optimizer/src/enforce_distribution.rs index 504197a2ded5e..63105f9c0fdc5 100644 --- a/datafusion/physical-optimizer/src/enforce_distribution.rs +++ b/datafusion/physical-optimizer/src/enforce_distribution.rs @@ -21,6 +21,7 @@ //! according to the configuration), this rule increases partition counts in //! the physical plan. +use std::any::Any; use std::fmt::Debug; use std::sync::Arc; @@ -294,7 +295,7 @@ pub fn adjust_input_keys_ordering( mode, .. }, - ) = plan.as_any().downcast_ref::() + ) = (plan.as_ref() as &dyn Any).downcast_ref::() { match mode { PartitionMode::Partitioned => { @@ -339,7 +340,7 @@ pub fn adjust_input_keys_ordering( } } } else if let Some(CrossJoinExec { left, .. }) = - plan.as_any().downcast_ref::() + (plan.as_ref() as &dyn Any).downcast_ref::() { let left_columns_len = left.schema().fields().len(); // Push down requirements to the right side @@ -355,7 +356,7 @@ pub fn adjust_input_keys_ordering( sort_options, null_equality, .. - }) = plan.as_any().downcast_ref::() + }) = (plan.as_ref() as &dyn Any).downcast_ref::() { let join_constructor = |new_conditions: ( Vec<(PhysicalExprRef, PhysicalExprRef)>, @@ -379,7 +380,9 @@ pub fn adjust_input_keys_ordering( &join_constructor, ) .map(Transformed::yes); - } else if let Some(aggregate_exec) = plan.as_any().downcast_ref::() { + } else if let Some(aggregate_exec) = + (plan.as_ref() as &dyn Any).downcast_ref::() + { if !requirements.data.is_empty() { if aggregate_exec.mode() == &AggregateMode::FinalPartitioned { return reorder_aggregate_keys(requirements, aggregate_exec) @@ -391,7 +394,9 @@ pub fn adjust_input_keys_ordering( // Keep everything unchanged return Ok(Transformed::no(requirements)); } - } else if let Some(proj) = plan.as_any().downcast_ref::() { + } else if let Some(proj) = + (plan.as_ref() as &dyn Any).downcast_ref::() + { let expr = proj.expr(); // For Projection, we need to transform the requirements to the columns before the Projection // And then to push down the requirements @@ -407,12 +412,15 @@ pub fn adjust_input_keys_ordering( // Can not satisfy, clear the current requirements and generate new empty requirements requirements.data.clear(); } - } else if plan.as_any().downcast_ref::().is_some() - || plan - .as_any() + } else if (plan.as_ref() as &dyn Any) + .downcast_ref::() + .is_some() + || (plan.as_ref() as &dyn Any) .downcast_ref::() .is_some() - || plan.as_any().downcast_ref::().is_some() + || (plan.as_ref() as &dyn Any) + .downcast_ref::() + .is_some() { requirements.data.clear(); } else { @@ -484,7 +492,8 @@ pub fn reorder_aggregate_keys( && agg_exec.group_expr().null_expr().is_empty() && !physical_exprs_equal(&output_exprs, parent_required) && let Some(positions) = expected_expr_positions(&output_exprs, parent_required) - && let Some(agg_exec) = agg_exec.input().as_any().downcast_ref::() + && let Some(agg_exec) = + (agg_exec.input().as_ref() as &dyn Any).downcast_ref::() && *agg_exec.mode() == AggregateMode::Partial { let group_exprs = agg_exec.group_expr().expr(); @@ -563,11 +572,13 @@ fn shift_right_required( let new_right_required = parent_required .iter() .filter_map(|r| { - r.as_any().downcast_ref::().and_then(|col| { - col.index() - .checked_sub(left_columns_len) - .map(|index| Arc::new(Column::new(col.name(), index)) as _) - }) + (r.as_ref() as &dyn Any) + .downcast_ref::() + .and_then(|col| { + col.index() + .checked_sub(left_columns_len) + .map(|index| Arc::new(Column::new(col.name(), index)) as _) + }) }) .collect::>(); @@ -598,7 +609,7 @@ fn shift_right_required( pub fn reorder_join_keys_to_inputs( plan: Arc, ) -> Result> { - let plan_any = plan.as_any(); + let plan_any = plan.as_ref() as &dyn Any; if let Some( exec @ HashJoinExec { left, @@ -1067,7 +1078,7 @@ pub fn replace_order_preserving_variants( ); return Ok(context); } else if let Some(repartition) = - context.plan.as_any().downcast_ref::() + (context.plan.as_ref() as &dyn Any).downcast_ref::() && repartition.preserve_order() { context.plan = Arc::new(RepartitionExec::try_new( @@ -1226,7 +1237,7 @@ pub fn ensure_distribution( children, } = remove_dist_changing_operators(dist_context)?; - if let Some(exec) = plan.as_any().downcast_ref::() { + if let Some(exec) = (plan.as_ref() as &dyn Any).downcast_ref::() { if let Some(updated_window) = get_best_fitting_window( exec.window_expr(), exec.input(), @@ -1234,7 +1245,8 @@ pub fn ensure_distribution( )? { plan = updated_window; } - } else if let Some(exec) = plan.as_any().downcast_ref::() + } else if let Some(exec) = + (plan.as_ref() as &dyn Any).downcast_ref::() && let Some(updated_window) = get_best_fitting_window( exec.window_expr(), exec.input(), @@ -1273,11 +1285,10 @@ pub fn ensure_distribution( // // CollectLeft/CollectRight modes are safe because one side is collected // to a single partition which eliminates partition-to-partition mapping. - let is_partitioned_join = plan - .as_any() + let is_partitioned_join = (plan.as_ref() as &dyn Any) .downcast_ref::() .is_some_and(|join| join.mode == PartitionMode::Partitioned) - || plan.as_any().is::(); + || (plan.as_ref() as &dyn Any).is::(); let repartition_status_flags = get_repartition_requirement_status(&plan, batch_size, should_use_estimates)?; @@ -1326,7 +1337,7 @@ pub fn ensure_distribution( // aggregates from different partitions are correctly combined. let requires_grouping_id = matches!(&requirement, Distribution::HashPartitioned(exprs) if exprs.iter().any(|expr| { - expr.as_any() + (expr.as_ref() as &dyn Any) .downcast_ref::() .is_some_and(|col| col.name() == Aggregate::INTERNAL_GROUPING_ID) }) @@ -1406,7 +1417,7 @@ pub fn ensure_distribution( child = add_sort_above_with_check( child, sort_req, - plan.as_any() + (plan.as_ref() as &dyn Any) .downcast_ref::() .map(|output| output.fetch()) .unwrap_or(None), @@ -1435,7 +1446,7 @@ pub fn ensure_distribution( } Distribution::UnspecifiedDistribution => { // Since ordering is lost, trying to preserve ordering is pointless - if !maintains || plan.as_any().is::() { + if !maintains || (plan.as_ref() as &dyn Any).is::() { child = replace_order_preserving_variants(child)?; } } @@ -1451,7 +1462,7 @@ pub fn ensure_distribution( .map(|c| Arc::clone(&c.plan)) .collect::>(); - plan = if plan.as_any().is::() + plan = if (plan.as_ref() as &dyn Any).is::() && !config.optimizer.prefer_existing_union && can_interleave(children_plans.iter()) { @@ -1496,7 +1507,7 @@ pub type DistributionContext = PlanContext; fn update_children(mut dist_context: DistributionContext) -> Result { for child_context in dist_context.children.iter_mut() { - let child_plan_any = child_context.plan.as_any(); + let child_plan_any = child_context.plan.as_ref() as &dyn Any; child_context.data = if let Some(repartition) = child_plan_any.downcast_ref::() { !matches!( diff --git a/datafusion/physical-optimizer/src/enforce_sorting/mod.rs b/datafusion/physical-optimizer/src/enforce_sorting/mod.rs index 5f1b3613143e4..704857f21fc51 100644 --- a/datafusion/physical-optimizer/src/enforce_sorting/mod.rs +++ b/datafusion/physical-optimizer/src/enforce_sorting/mod.rs @@ -38,6 +38,7 @@ pub mod replace_with_order_preserving_variants; pub mod sort_pushdown; +use std::any::Any; use std::sync::Arc; use crate::PhysicalOptimizerRule; @@ -265,7 +266,7 @@ impl PhysicalOptimizerRule for EnforceSorting { fn replace_with_partial_sort( plan: Arc, ) -> Result> { - let plan_any = plan.as_any(); + let plan_any = plan.as_ref() as &dyn Any; let Some(sort_plan) = plan_any.downcast_ref::() else { return Ok(plan); }; @@ -509,7 +510,7 @@ pub fn ensure_sorting( child = add_sort_above( child, req, - plan.as_any() + (plan.as_ref() as &dyn Any) .downcast_ref::() .map(|output| output.fetch()) .unwrap_or(None), @@ -555,7 +556,8 @@ pub fn ensure_sorting( fn analyze_immediate_sort_removal( mut node: PlanWithCorrespondingSort, ) -> Result> { - let Some(sort_exec) = node.plan.as_any().downcast_ref::() else { + let Some(sort_exec) = (node.plan.as_ref() as &dyn Any).downcast_ref::() + else { return Ok(Transformed::no(node)); }; let sort_input = sort_exec.input(); @@ -620,7 +622,7 @@ fn adjust_window_sort_removal( )?; window_tree.children.push(child_node); - let plan = window_tree.plan.as_any(); + let plan = window_tree.plan.as_ref() as &dyn Any; let child_plan = &window_tree.children[0].plan; let (window_expr, new_window) = if let Some(exec) = plan.downcast_ref::() { @@ -706,7 +708,9 @@ fn remove_bottleneck_in_subplan( .collect::>()?; } let mut new_reqs = requirements.update_plan_from_children()?; - if let Some(repartition) = new_reqs.plan.as_any().downcast_ref::() { + if let Some(repartition) = + (new_reqs.plan.as_ref() as &dyn Any).downcast_ref::() + { let input_partitioning = repartition.input().output_partitioning(); // We can remove this repartitioning operator if it is now a no-op: let mut can_remove = input_partitioning.eq(repartition.partitioning()); @@ -744,7 +748,7 @@ fn remove_corresponding_sort_from_sub_plan( requires_single_partition: bool, ) -> Result { // A `SortExec` is always at the bottom of the tree. - if let Some(sort_exec) = node.plan.as_any().downcast_ref::() { + if let Some(sort_exec) = (node.plan.as_ref() as &dyn Any).downcast_ref::() { // Do not remove sorts with fetch: if sort_exec.fetch().is_none() { node = node.children.swap_remove(0); @@ -778,7 +782,7 @@ fn remove_corresponding_sort_from_sub_plan( node.children = node.children.swap_remove(0).children; node.plan = Arc::clone(node.plan.children().swap_remove(0)); } else if let Some(repartition) = - node.plan.as_any().downcast_ref::() + (node.plan.as_ref() as &dyn Any).downcast_ref::() { node.plan = Arc::new(RepartitionExec::try_new( Arc::clone(&node.children[0].plan), @@ -811,9 +815,10 @@ fn remove_corresponding_sort_from_sub_plan( fn get_sort_exprs( sort_any: &Arc, ) -> Result<(&LexOrdering, Option)> { - if let Some(sort_exec) = sort_any.as_any().downcast_ref::() { + if let Some(sort_exec) = (sort_any.as_ref() as &dyn Any).downcast_ref::() { Ok((sort_exec.expr(), sort_exec.fetch())) - } else if let Some(spm) = sort_any.as_any().downcast_ref::() + } else if let Some(spm) = + (sort_any.as_ref() as &dyn Any).downcast_ref::() { Ok((spm.expr(), spm.fetch())) } else { diff --git a/datafusion/physical-optimizer/src/enforce_sorting/sort_pushdown.rs b/datafusion/physical-optimizer/src/enforce_sorting/sort_pushdown.rs index 23af981b35318..d631b4ffca7f4 100644 --- a/datafusion/physical-optimizer/src/enforce_sorting/sort_pushdown.rs +++ b/datafusion/physical-optimizer/src/enforce_sorting/sort_pushdown.rs @@ -15,6 +15,7 @@ // specific language governing permissions and limitations // under the License. +use std::any::Any; use std::fmt::Debug; use std::sync::Arc; @@ -280,7 +281,8 @@ fn pushdown_requirement_to_children( } RequirementsCompatibility::NonCompatible => Ok(None), } - } else if let Some(sort_exec) = plan.as_any().downcast_ref::() { + } else if let Some(sort_exec) = (plan.as_ref() as &dyn Any).downcast_ref::() + { let Some(sort_ordering) = sort_exec.properties().output_ordering().cloned() else { return internal_err!("SortExec should have output ordering"); @@ -319,7 +321,9 @@ fn pushdown_requirement_to_children( // `UnionExec` does not have real sort requirements for its input, we // just propagate the sort requirements down: Ok(Some(vec![Some(parent_required); plan.children().len()])) - } else if let Some(smj) = plan.as_any().downcast_ref::() { + } else if let Some(smj) = + (plan.as_ref() as &dyn Any).downcast_ref::() + { let left_columns_len = smj.left().schema().fields().len(); let parent_ordering: Vec = parent_required .first() @@ -354,14 +358,16 @@ fn pushdown_requirement_to_children( Ok(None) } } - } else if let Some(aggregate_exec) = plan.as_any().downcast_ref::() { + } else if let Some(aggregate_exec) = + (plan.as_ref() as &dyn Any).downcast_ref::() + { handle_aggregate_pushdown(aggregate_exec, parent_required) } else if maintains_input_order.is_empty() || !maintains_input_order.iter().any(|o| *o) - || plan.as_any().is::() - || plan.as_any().is::() + || (plan.as_ref() as &dyn Any).is::() + || (plan.as_ref() as &dyn Any).is::() // TODO: Add support for Projection push down - || plan.as_any().is::() + || (plan.as_ref() as &dyn Any).is::() || pushdown_would_violate_requirements(&parent_required, plan.as_ref()) { // If the current plan is a leaf node or can not maintain any of the input ordering, can not pushed down requirements. @@ -383,7 +389,9 @@ fn pushdown_requirement_to_children( // ordering requirement invalidates requirement of sort preserving merge exec. Ok(None) } - } else if let Some(hash_join) = plan.as_any().downcast_ref::() { + } else if let Some(hash_join) = + (plan.as_ref() as &dyn Any).downcast_ref::() + { handle_hash_join(hash_join, parent_required) } else { handle_custom_pushdown(plan, parent_required, &maintains_input_order) @@ -430,7 +438,8 @@ fn handle_aggregate_pushdown( for req in parent_requirement { // Sort above AggregateExec should reference its output columns. Map each // output group-by column to its original input expression. - let Some(column) = req.expr.as_any().downcast_ref::() else { + let Some(column) = (req.expr.as_ref() as &dyn Any).downcast_ref::() + else { return Ok(None); }; if column.index() >= group_input_exprs.len() { @@ -604,14 +613,13 @@ fn expr_source_side( let mut right_ordering = ordering.clone(); let (mut valid_left, mut valid_right) = (true, true); for (left, right) in ordering.iter_mut().zip(right_ordering.iter_mut()) { - let col = left.expr.as_any().downcast_ref::()?; + let col = (left.expr.as_ref() as &dyn Any).downcast_ref::()?; let eq_class = eq_group.get_equivalence_class(&left.expr); if col.index() < left_columns_len { if valid_right { valid_right = eq_class.is_some_and(|cls| { for expr in cls.iter() { - if expr - .as_any() + if (expr.as_ref() as &dyn Any) .downcast_ref::() .is_some_and(|c| c.index() >= left_columns_len) { @@ -625,8 +633,7 @@ fn expr_source_side( } else if valid_left { valid_left = eq_class.is_some_and(|cls| { for expr in cls.iter() { - if expr - .as_any() + if (expr.as_ref() as &dyn Any) .downcast_ref::() .is_some_and(|c| c.index() < left_columns_len) { @@ -652,11 +659,11 @@ fn expr_source_side( } JoinType::LeftSemi | JoinType::LeftAnti => ordering .iter() - .all(|e| e.expr.as_any().is::()) + .all(|e| (e.expr.as_ref() as &dyn Any).is::()) .then_some((JoinSide::Left, ordering)), JoinType::RightSemi | JoinType::RightAnti => ordering .iter() - .all(|e| e.expr.as_any().is::()) + .all(|e| (e.expr.as_ref() as &dyn Any).is::()) .then_some((JoinSide::Right, ordering)), } } @@ -739,7 +746,9 @@ fn handle_custom_pushdown( let updated_columns = req .expr .transform_up(|expr| { - if let Some(col) = expr.as_any().downcast_ref::() { + if let Some(col) = + (expr.as_ref() as &dyn Any).downcast_ref::() + { let new_index = col.index() - sub_offset; Ok(Transformed::yes(Arc::new(Column::new( child_schema.field(new_index).name(), @@ -822,7 +831,9 @@ fn handle_hash_join( let updated_columns = req .expr .transform_up(|expr| { - if let Some(col) = expr.as_any().downcast_ref::() { + if let Some(col) = + (expr.as_ref() as &dyn Any).downcast_ref::() + { let index = projected_indices[col.index()].index; Ok(Transformed::yes(Arc::new(Column::new( child_schema.field(index).name(), diff --git a/datafusion/physical-optimizer/src/ensure_coop.rs b/datafusion/physical-optimizer/src/ensure_coop.rs index e783c0a9e776a..102e21a4853a4 100644 --- a/datafusion/physical-optimizer/src/ensure_coop.rs +++ b/datafusion/physical-optimizer/src/ensure_coop.rs @@ -272,7 +272,6 @@ mod tests { SendableRecordBatchStream, execution_plan::{Boundedness, EmissionType}, }; - use std::any::Any; #[derive(Debug)] struct DummyExec { @@ -323,9 +322,6 @@ mod tests { fn name(&self) -> &str { &self.name } - fn as_any(&self) -> &dyn Any { - self - } fn properties(&self) -> &Arc { &self.properties } diff --git a/datafusion/physical-optimizer/src/hash_join_buffering.rs b/datafusion/physical-optimizer/src/hash_join_buffering.rs index 3c29b46c0fa64..b88273b4dd27c 100644 --- a/datafusion/physical-optimizer/src/hash_join_buffering.rs +++ b/datafusion/physical-optimizer/src/hash_join_buffering.rs @@ -22,6 +22,7 @@ use datafusion_common::tree_node::{Transformed, TransformedResult, TreeNode}; use datafusion_physical_plan::ExecutionPlan; use datafusion_physical_plan::buffer::BufferExec; use datafusion_physical_plan::joins::HashJoinExec; +use std::any::Any; use std::sync::Arc; /// Looks for all the [HashJoinExec]s in the plan and places a [BufferExec] node with the @@ -64,14 +65,18 @@ impl PhysicalOptimizerRule for HashJoinBuffering { } plan.transform_down(|plan| { - let Some(node) = plan.as_any().downcast_ref::() else { + let Some(node) = (plan.as_ref() as &dyn Any).downcast_ref::() + else { return Ok(Transformed::no(plan)); }; let plan = Arc::clone(&plan); Ok(Transformed::yes( if HashJoinExec::probe_side() == JoinSide::Left { // Do not stack BufferExec nodes together. - if node.left.as_any().downcast_ref::().is_some() { + if (node.left.as_ref() as &dyn Any) + .downcast_ref::() + .is_some() + { return Ok(Transformed::no(plan)); } plan.with_new_children(vec![ @@ -80,7 +85,10 @@ impl PhysicalOptimizerRule for HashJoinBuffering { ])? } else { // Do not stack BufferExec nodes together. - if node.right.as_any().downcast_ref::().is_some() { + if (node.right.as_ref() as &dyn Any) + .downcast_ref::() + .is_some() + { return Ok(Transformed::no(plan)); } plan.with_new_children(vec![ diff --git a/datafusion/physical-optimizer/src/join_selection.rs b/datafusion/physical-optimizer/src/join_selection.rs index c796638dd1237..f9a8cf0891ee2 100644 --- a/datafusion/physical-optimizer/src/join_selection.rs +++ b/datafusion/physical-optimizer/src/join_selection.rs @@ -38,6 +38,7 @@ use datafusion_physical_plan::joins::{ StreamJoinPartitionMode, SymmetricHashJoinExec, }; use datafusion_physical_plan::{ExecutionPlan, ExecutionPlanProperties}; +use std::any::Any; use std::sync::Arc; /// The [`JoinSelection`] rule tries to modify a given plan so that it can @@ -286,56 +287,61 @@ fn statistical_join_selection_subrule( plan: Arc, config: &ConfigOptions, ) -> Result>> { - let transformed = - if let Some(hash_join) = plan.as_any().downcast_ref::() { - match hash_join.partition_mode() { - PartitionMode::Auto => try_collect_left(hash_join, false, config)? - .map_or_else( - || partitioned_hash_join(hash_join, config).map(Some), - |v| Ok(Some(v)), - )?, - PartitionMode::CollectLeft => try_collect_left(hash_join, true, config)? - .map_or_else( - || partitioned_hash_join(hash_join, config).map(Some), - |v| Ok(Some(v)), - )?, - PartitionMode::Partitioned => { - let left = hash_join.left(); - let right = hash_join.right(); - // Don't swap null-aware anti joins as they have specific side requirements - if hash_join.join_type().supports_swap() - && !hash_join.null_aware - && should_swap_join_order(&**left, &**right, config)? - { - hash_join - .swap_inputs(PartitionMode::Partitioned) - .map(Some)? - } else { - None - } + let transformed = if let Some(hash_join) = + (plan.as_ref() as &dyn Any).downcast_ref::() + { + match hash_join.partition_mode() { + PartitionMode::Auto => try_collect_left(hash_join, false, config)? + .map_or_else( + || partitioned_hash_join(hash_join, config).map(Some), + |v| Ok(Some(v)), + )?, + PartitionMode::CollectLeft => try_collect_left(hash_join, true, config)? + .map_or_else( + || partitioned_hash_join(hash_join, config).map(Some), + |v| Ok(Some(v)), + )?, + PartitionMode::Partitioned => { + let left = hash_join.left(); + let right = hash_join.right(); + // Don't swap null-aware anti joins as they have specific side requirements + if hash_join.join_type().supports_swap() + && !hash_join.null_aware + && should_swap_join_order(&**left, &**right, config)? + { + hash_join + .swap_inputs(PartitionMode::Partitioned) + .map(Some)? + } else { + None } } - } else if let Some(cross_join) = plan.as_any().downcast_ref::() { - let left = cross_join.left(); - let right = cross_join.right(); - if should_swap_join_order(&**left, &**right, config)? { - cross_join.swap_inputs().map(Some)? - } else { - None - } - } else if let Some(nl_join) = plan.as_any().downcast_ref::() { - let left = nl_join.left(); - let right = nl_join.right(); - if nl_join.join_type().supports_swap() - && should_swap_join_order(&**left, &**right, config)? - { - nl_join.swap_inputs().map(Some)? - } else { - None - } + } + } else if let Some(cross_join) = + (plan.as_ref() as &dyn Any).downcast_ref::() + { + let left = cross_join.left(); + let right = cross_join.right(); + if should_swap_join_order(&**left, &**right, config)? { + cross_join.swap_inputs().map(Some)? } else { None - }; + } + } else if let Some(nl_join) = + (plan.as_ref() as &dyn Any).downcast_ref::() + { + let left = nl_join.left(); + let right = nl_join.right(); + if nl_join.join_type().supports_swap() + && should_swap_join_order(&**left, &**right, config)? + { + nl_join.swap_inputs().map(Some)? + } else { + None + } + } else { + None + }; Ok(if let Some(transformed) = transformed { Transformed::yes(transformed) @@ -369,7 +375,7 @@ fn hash_join_convert_symmetric_subrule( config_options: &ConfigOptions, ) -> Result> { // Check if the current plan node is a HashJoinExec. - if let Some(hash_join) = input.as_any().downcast_ref::() { + if let Some(hash_join) = (input.as_ref() as &dyn Any).downcast_ref::() { let left_unbounded = hash_join.left.boundedness().is_unbounded(); let left_incremental = matches!( hash_join.left.pipeline_behavior(), @@ -508,7 +514,7 @@ pub fn hash_join_swap_subrule( mut input: Arc, _config_options: &ConfigOptions, ) -> Result> { - if let Some(hash_join) = input.as_any().downcast_ref::() + if let Some(hash_join) = (input.as_ref() as &dyn Any).downcast_ref::() && hash_join.left.boundedness().is_unbounded() && !hash_join.right.boundedness().is_unbounded() && !hash_join.null_aware // Don't swap null-aware anti joins diff --git a/datafusion/physical-optimizer/src/limit_pushdown.rs b/datafusion/physical-optimizer/src/limit_pushdown.rs index 0b85eb805bba1..fdf0730ecbf14 100644 --- a/datafusion/physical-optimizer/src/limit_pushdown.rs +++ b/datafusion/physical-optimizer/src/limit_pushdown.rs @@ -60,6 +60,7 @@ //! //! Reference implementation in Hash Join: +use std::any::Any; use std::fmt::Debug; use std::sync::Arc; @@ -325,7 +326,9 @@ pub(crate) fn pushdown_limits( /// Extracts limit information from the [`ExecutionPlan`] if it is a /// [`GlobalLimitExec`] or a [`LocalLimitExec`]. fn extract_limit(plan: &Arc) -> Option { - if let Some(global_limit) = plan.as_any().downcast_ref::() { + if let Some(global_limit) = + (plan.as_ref() as &dyn Any).downcast_ref::() + { Some(LimitInfo { input: Arc::clone(global_limit.input()), fetch: global_limit.fetch(), @@ -333,7 +336,7 @@ fn extract_limit(plan: &Arc) -> Option { preserve_order: global_limit.required_ordering().is_some(), }) } else { - plan.as_any() + (plan.as_ref() as &dyn Any) .downcast_ref::() .map(|local_limit| LimitInfo { input: Arc::clone(local_limit.input()), @@ -346,7 +349,7 @@ fn extract_limit(plan: &Arc) -> Option { /// Checks if the given plan combines input partitions. fn combines_input_partitions(plan: &Arc) -> bool { - let plan = plan.as_any(); + let plan = plan.as_ref() as &dyn Any; plan.is::() || plan.is::() } diff --git a/datafusion/physical-optimizer/src/limit_pushdown_past_window.rs b/datafusion/physical-optimizer/src/limit_pushdown_past_window.rs index fdb5cfa6003b4..13d4de8e1bbdb 100644 --- a/datafusion/physical-optimizer/src/limit_pushdown_past_window.rs +++ b/datafusion/physical-optimizer/src/limit_pushdown_past_window.rs @@ -31,6 +31,7 @@ use datafusion_physical_plan::sorts::sort::SortExec; use datafusion_physical_plan::sorts::sort_preserving_merge::SortPreservingMergeExec; use datafusion_physical_plan::windows::{BoundedWindowAggExec, WindowUDFExpr}; use datafusion_physical_plan::{ExecutionPlan, ExecutionPlanProperties}; +use std::any::Any; use std::cmp; use std::sync::Arc; @@ -104,7 +105,9 @@ impl PhysicalOptimizerRule for LimitPushPastWindows { } // grow the limit if we hit a window function - if let Some(window) = node.as_any().downcast_ref::() { + if let Some(window) = + (node.as_ref() as &dyn Any).downcast_ref::() + { phase = Phase::Apply; if !grow_limit(window, &mut ctx) { return reset(node, &mut ctx); @@ -123,7 +126,9 @@ impl PhysicalOptimizerRule for LimitPushPastWindows { if !node.supports_limit_pushdown() { return reset(node, &mut ctx); } - if let Some(part) = node.as_any().downcast_ref::() { + if let Some(part) = + (node.as_ref() as &dyn Any).downcast_ref::() + { let output = part.partitioning().partition_count(); let input = part.input().output_partitioning().partition_count(); if output < input { @@ -185,7 +190,9 @@ fn apply_limit( node: &Arc, ctx: &mut TraverseState, ) -> Option>> { - if !node.as_any().is::() && !node.as_any().is::() { + if !(node.as_ref() as &dyn Any).is::() + && !(node.as_ref() as &dyn Any).is::() + { return None; } let latest = ctx.limit.take(); @@ -202,17 +209,19 @@ fn apply_limit( } fn get_limit(node: &Arc, ctx: &mut TraverseState) -> bool { - if let Some(limit) = node.as_any().downcast_ref::() { + if let Some(limit) = (node.as_ref() as &dyn Any).downcast_ref::() { ctx.reset_limit(limit.fetch().map(|fetch| fetch + limit.skip())); return true; } // In distributed execution, GlobalLimitExec becomes LocalLimitExec // per partition. Handle it the same way (LocalLimitExec has no skip). - if let Some(limit) = node.as_any().downcast_ref::() { + if let Some(limit) = (node.as_ref() as &dyn Any).downcast_ref::() { ctx.reset_limit(Some(limit.fetch())); return true; } - if let Some(limit) = node.as_any().downcast_ref::() { + if let Some(limit) = + (node.as_ref() as &dyn Any).downcast_ref::() + { ctx.reset_limit(limit.fetch()); return true; } diff --git a/datafusion/physical-optimizer/src/limited_distinct_aggregation.rs b/datafusion/physical-optimizer/src/limited_distinct_aggregation.rs index 7c1fc2a039d21..2a32e17d6af10 100644 --- a/datafusion/physical-optimizer/src/limited_distinct_aggregation.rs +++ b/datafusion/physical-optimizer/src/limited_distinct_aggregation.rs @@ -18,6 +18,7 @@ //! A special-case optimizer rule that pushes limit into a grouped aggregation //! which has no aggregate expressions or sorting requirements +use std::any::Any; use std::sync::Arc; use datafusion_physical_plan::aggregates::{AggregateExec, LimitOptions}; @@ -69,10 +70,13 @@ impl LimitedDistinctAggregation { let mut global_skip: usize = 0; let children: Vec>; let mut is_global_limit = false; - if let Some(local_limit) = plan.as_any().downcast_ref::() { + if let Some(local_limit) = + (plan.as_ref() as &dyn Any).downcast_ref::() + { limit = local_limit.fetch(); children = local_limit.children().into_iter().cloned().collect(); - } else if let Some(global_limit) = plan.as_any().downcast_ref::() + } else if let Some(global_limit) = + (plan.as_ref() as &dyn Any).downcast_ref::() { global_fetch = global_limit.fetch(); global_fetch?; @@ -104,10 +108,12 @@ impl LimitedDistinctAggregation { if !rewrite_applicable { return Ok(Transformed::no(plan)); } - if let Some(aggr) = plan.as_any().downcast_ref::() { + if let Some(aggr) = + (plan.as_ref() as &dyn Any).downcast_ref::() + { if found_match_aggr && let Some(parent_aggr) = - match_aggr.as_any().downcast_ref::() + (match_aggr.as_ref() as &dyn Any).downcast_ref::() && !parent_aggr.group_expr().eq(aggr.group_expr()) { // a partial and final aggregation with different groupings disqualifies diff --git a/datafusion/physical-optimizer/src/output_requirements.rs b/datafusion/physical-optimizer/src/output_requirements.rs index f709c9e17ce01..afaae1bac6e26 100644 --- a/datafusion/physical-optimizer/src/output_requirements.rs +++ b/datafusion/physical-optimizer/src/output_requirements.rs @@ -22,6 +22,7 @@ //! Since the `OutputRequirementExec` operator is only a helper operator, it //! shouldn't occur in the final plan (i.e. the executed plan). +use std::any::Any; use std::sync::Arc; use crate::PhysicalOptimizerRule; @@ -198,10 +199,6 @@ impl ExecutionPlan for OutputRequirementExec { "OutputRequirementExec" } - fn as_any(&self) -> &dyn std::any::Any { - self - } - fn properties(&self) -> &Arc { &self.cache } @@ -340,8 +337,8 @@ impl PhysicalOptimizerRule for OutputRequirements { RuleMode::Add => require_top_ordering(plan), RuleMode::Remove => plan .transform_up(|plan| { - if let Some(sort_req) = - plan.as_any().downcast_ref::() + if let Some(sort_req) = (plan.as_ref() as &dyn Any) + .downcast_ref::() { Ok(Transformed::yes(sort_req.input())) } else { @@ -389,7 +386,8 @@ fn require_top_ordering_helper( // Global ordering defines desired ordering in the final result. if children.len() != 1 { Ok((plan, false)) - } else if let Some(sort_exec) = plan.as_any().downcast_ref::() { + } else if let Some(sort_exec) = (plan.as_ref() as &dyn Any).downcast_ref::() + { // In case of constant columns, output ordering of the `SortExec` would // be an empty set. Therefore; we check the sort expression field to // assign the requirements. @@ -407,7 +405,9 @@ fn require_top_ordering_helper( )) as _, true, )) - } else if let Some(spm) = plan.as_any().downcast_ref::() { + } else if let Some(spm) = + (plan.as_ref() as &dyn Any).downcast_ref::() + { let reqs = OrderingRequirements::from(spm.expr().clone()); let fetch = spm.fetch(); Ok(( diff --git a/datafusion/physical-optimizer/src/projection_pushdown.rs b/datafusion/physical-optimizer/src/projection_pushdown.rs index 44d0926a8b250..d8004366063a4 100644 --- a/datafusion/physical-optimizer/src/projection_pushdown.rs +++ b/datafusion/physical-optimizer/src/projection_pushdown.rs @@ -23,6 +23,7 @@ use crate::PhysicalOptimizerRule; use arrow::datatypes::{Fields, Schema, SchemaRef}; use datafusion_common::alias::AliasGenerator; +use std::any::Any; use std::collections::HashSet; use std::sync::Arc; @@ -65,7 +66,7 @@ impl PhysicalOptimizerRule for ProjectionPushdown { let alias_generator = AliasGenerator::new(); let plan = plan .transform_up(|plan| { - match plan.as_any().downcast_ref::() { + match (plan.as_ref() as &dyn Any).downcast_ref::() { None => Ok(Transformed::no(plan)), Some(hash_join) => try_push_down_join_filter( Arc::clone(&plan), @@ -244,7 +245,7 @@ fn minimize_join_filter( ) -> JoinFilter { let mut used_columns = HashSet::new(); expr.apply(|expr| { - if let Some(col) = expr.as_any().downcast_ref::() { + if let Some(col) = (expr.as_ref() as &dyn Any).downcast_ref::() { used_columns.insert(col.index()); } Ok(TreeNodeRecursion::Continue) @@ -267,19 +268,21 @@ fn minimize_join_filter( .collect::(); let final_expr = expr - .transform_up(|expr| match expr.as_any().downcast_ref::() { - None => Ok(Transformed::no(expr)), - Some(column) => { - let new_idx = used_columns - .iter() - .filter(|idx| **idx < column.index()) - .count(); - let new_column = Column::new(column.name(), new_idx); - Ok(Transformed::yes( - Arc::new(new_column) as Arc - )) - } - }) + .transform_up( + |expr| match (expr.as_ref() as &dyn Any).downcast_ref::() { + None => Ok(Transformed::no(expr)), + Some(column) => { + let new_idx = used_columns + .iter() + .filter(|idx| **idx < column.index()) + .count(); + let new_column = Column::new(column.name(), new_idx); + Ok(Transformed::yes( + Arc::new(new_column) as Arc + )) + } + }, + ) .expect("Closure cannot fail"); JoinFilter::new( @@ -380,7 +383,7 @@ impl<'a> JoinFilterRewriter<'a> { // executed against the filter schema. let new_idx = self.join_side_projections.len(); let rewritten_expr = expr.transform_up(|expr| { - Ok(match expr.as_any().downcast_ref::() { + Ok(match (expr.as_ref() as &dyn Any).downcast_ref::() { None => Transformed::no(expr), Some(column) => { let intermediate_column = @@ -414,17 +417,19 @@ impl<'a> JoinFilterRewriter<'a> { join_side: JoinSide, ) -> Result { let mut result = false; - expr.apply(|expr| match expr.as_any().downcast_ref::() { - None => Ok(TreeNodeRecursion::Continue), - Some(c) => { - let column_index = &self.intermediate_column_indices[c.index()]; - if column_index.side == join_side { - result = true; - return Ok(TreeNodeRecursion::Stop); + expr.apply( + |expr| match (expr.as_ref() as &dyn Any).downcast_ref::() { + None => Ok(TreeNodeRecursion::Continue), + Some(c) => { + let column_index = &self.intermediate_column_indices[c.index()]; + if column_index.side == join_side { + result = true; + return Ok(TreeNodeRecursion::Stop); + } + Ok(TreeNodeRecursion::Continue) } - Ok(TreeNodeRecursion::Continue) - } - })?; + }, + )?; Ok(result) } diff --git a/datafusion/physical-optimizer/src/pushdown_sort.rs b/datafusion/physical-optimizer/src/pushdown_sort.rs index 1fa15492d2a92..7cd6ccc3e7aea 100644 --- a/datafusion/physical-optimizer/src/pushdown_sort.rs +++ b/datafusion/physical-optimizer/src/pushdown_sort.rs @@ -56,6 +56,7 @@ use datafusion_common::tree_node::{Transformed, TransformedResult, TreeNode}; use datafusion_physical_plan::ExecutionPlan; use datafusion_physical_plan::SortOrderPushdownResult; use datafusion_physical_plan::sorts::sort::SortExec; +use std::any::Any; use std::sync::Arc; /// A PhysicalOptimizerRule that attempts to push down sort requirements to data sources. @@ -84,7 +85,8 @@ impl PhysicalOptimizerRule for PushdownSort { // Use transform_down to find and optimize all SortExec nodes (including nested ones) plan.transform_down(|plan: Arc| { // Check if this is a SortExec - let Some(sort_exec) = plan.as_any().downcast_ref::() else { + let Some(sort_exec) = (plan.as_ref() as &dyn Any).downcast_ref::() + else { return Ok(Transformed::no(plan)); }; diff --git a/datafusion/physical-optimizer/src/sanity_checker.rs b/datafusion/physical-optimizer/src/sanity_checker.rs index 43e6dcbb4417c..da1f0f47249df 100644 --- a/datafusion/physical-optimizer/src/sanity_checker.rs +++ b/datafusion/physical-optimizer/src/sanity_checker.rs @@ -21,6 +21,7 @@ //! infinite input(s). In addition, it will check if all order and //! distribution requirements of a plan are satisfied by its children. +use std::any::Any; use std::sync::Arc; use datafusion_common::Result; @@ -89,7 +90,8 @@ pub fn check_finiteness_requirements( input: &dyn ExecutionPlan, optimizer_options: &OptimizerOptions, ) -> Result<()> { - if let Some(exec) = input.as_any().downcast_ref::() + if let Some(exec) = + (input.as_ref() as &dyn Any).downcast_ref::() && !(optimizer_options.allow_symmetric_joins_without_pruning || (exec.check_if_order_information_available()? && is_prunable(exec))) { diff --git a/datafusion/physical-optimizer/src/topk_aggregation.rs b/datafusion/physical-optimizer/src/topk_aggregation.rs index cec6bd70a2089..bbcff3d5ac4e5 100644 --- a/datafusion/physical-optimizer/src/topk_aggregation.rs +++ b/datafusion/physical-optimizer/src/topk_aggregation.rs @@ -17,6 +17,7 @@ //! An optimizer rule that detects aggregate operations that could use a limited bucket count +use std::any::Any; use std::sync::Arc; use crate::PhysicalOptimizerRule; @@ -93,14 +94,14 @@ impl TopKAggregation { } fn transform_sort(plan: &Arc) -> Option> { - let sort = plan.as_any().downcast_ref::()?; + let sort = (plan.as_ref() as &dyn Any).downcast_ref::()?; let children = sort.children(); let child = children.into_iter().exactly_one().ok()?; let order = sort.properties().output_ordering()?; let order = order.iter().exactly_one().ok()?; let order_desc = order.options.descending; - let order = order.expr.as_any().downcast_ref::()?; + let order = (order.expr.as_ref() as &dyn Any).downcast_ref::()?; let mut cur_col_name = order.name().to_string(); let limit = sort.fetch()?; @@ -109,16 +110,21 @@ impl TopKAggregation { if !cardinality_preserved { return Ok(Transformed::no(plan)); } - if let Some(aggr) = plan.as_any().downcast_ref::() { + if let Some(aggr) = + (plan.as_ref() as &dyn Any).downcast_ref::() + { // either we run into an Aggregate and transform it match Self::transform_agg(aggr, &cur_col_name, order_desc, limit) { None => cardinality_preserved = false, Some(plan) => return Ok(Transformed::yes(plan)), } - } else if let Some(proj) = plan.as_any().downcast_ref::() { + } else if let Some(proj) = + (plan.as_ref() as &dyn Any).downcast_ref::() + { // track renames due to successive projections for proj_expr in proj.expr() { - let Some(src_col) = proj_expr.expr.as_any().downcast_ref::() + let Some(src_col) = + (proj_expr.expr.as_ref() as &dyn Any).downcast_ref::() else { continue; }; diff --git a/datafusion/physical-optimizer/src/topk_repartition.rs b/datafusion/physical-optimizer/src/topk_repartition.rs index 9f9878012849e..21f812799541d 100644 --- a/datafusion/physical-optimizer/src/topk_repartition.rs +++ b/datafusion/physical-optimizer/src/topk_repartition.rs @@ -48,6 +48,7 @@ use crate::PhysicalOptimizerRule; use datafusion_common::Result; use datafusion_common::config::ConfigOptions; use datafusion_common::tree_node::{Transformed, TransformedResult, TreeNode}; +use std::any::Any; use std::sync::Arc; // CoalesceBatchesExec is deprecated on main (replaced by arrow-rs BatchCoalescer), // but older DataFusion versions may still insert it between SortExec and RepartitionExec. @@ -82,7 +83,8 @@ impl PhysicalOptimizerRule for TopKRepartition { } plan.transform_down(|node| { // Match SortExec with fetch (TopK) - let Some(sort_exec) = node.as_any().downcast_ref::() else { + let Some(sort_exec) = (node.as_ref() as &dyn Any).downcast_ref::() + else { return Ok(Transformed::no(node)); }; let Some(fetch) = sort_exec.fetch() else { @@ -91,7 +93,7 @@ impl PhysicalOptimizerRule for TopKRepartition { // The child might be a CoalesceBatchesExec; look through it let sort_input = sort_exec.input(); - let sort_any = sort_input.as_any(); + let sort_any = sort_input.as_ref() as &dyn Any; let (repart_parent, repart_exec) = if let Some(rp) = sort_any.downcast_ref::() { @@ -101,7 +103,9 @@ impl PhysicalOptimizerRule for TopKRepartition { // There's a CoalesceBatchesExec between TopK & RepartitionExec // in this case we will need to reconstruct both nodes let cb_input = cb_exec.input(); - let Some(rp) = cb_input.as_any().downcast_ref::() else { + let Some(rp) = + (cb_input.as_ref() as &dyn Any).downcast_ref::() + else { return Ok(Transformed::no(node)); }; (Some(Arc::clone(sort_input)), rp) @@ -133,7 +137,10 @@ impl PhysicalOptimizerRule for TopKRepartition { // Don't push if the input to the repartition is already bounded // (e.g., another TopK), as it would be redundant. let repart_input = repart_exec.input(); - if repart_input.as_any().downcast_ref::().is_some() { + if (repart_input.as_ref() as &dyn Any) + .downcast_ref::() + .is_some() + { return Ok(Transformed::no(node)); } diff --git a/datafusion/physical-optimizer/src/update_aggr_exprs.rs b/datafusion/physical-optimizer/src/update_aggr_exprs.rs index 67127c2a238f9..c8e18407c970b 100644 --- a/datafusion/physical-optimizer/src/update_aggr_exprs.rs +++ b/datafusion/physical-optimizer/src/update_aggr_exprs.rs @@ -18,6 +18,7 @@ //! An optimizer rule that checks ordering requirements of aggregate expressions //! and modifies the expressions to work more efficiently if possible. +use std::any::Any; use std::sync::Arc; use datafusion_common::config::ConfigOptions; @@ -78,7 +79,9 @@ impl PhysicalOptimizerRule for OptimizeAggregateOrder { _config: &ConfigOptions, ) -> Result> { plan.transform_up(|plan| { - if let Some(aggr_exec) = plan.as_any().downcast_ref::() { + if let Some(aggr_exec) = + (plan.as_ref() as &dyn Any).downcast_ref::() + { // Final stage implementations do not rely on ordering -- those // ordering fields may be pruned out by first stage aggregates. // Hence, necessary information for proper merge is added during diff --git a/datafusion/physical-optimizer/src/utils.rs b/datafusion/physical-optimizer/src/utils.rs index 13a1745216e83..bd6db0c7d8df9 100644 --- a/datafusion/physical-optimizer/src/utils.rs +++ b/datafusion/physical-optimizer/src/utils.rs @@ -15,6 +15,7 @@ // specific language governing permissions and limitations // under the License. +use std::any::Any; use std::sync::Arc; use datafusion_common::Result; @@ -79,37 +80,39 @@ pub fn add_sort_above_with_check( /// Checks whether the given operator is a [`SortExec`]. pub fn is_sort(plan: &Arc) -> bool { - plan.as_any().is::() + (plan.as_ref() as &dyn Any).is::() } /// Checks whether the given operator is a window; /// i.e. either a [`WindowAggExec`] or a [`BoundedWindowAggExec`]. pub fn is_window(plan: &Arc) -> bool { - plan.as_any().is::() || plan.as_any().is::() + (plan.as_ref() as &dyn Any).is::() + || (plan.as_ref() as &dyn Any).is::() } /// Checks whether the given operator is a [`UnionExec`]. pub fn is_union(plan: &Arc) -> bool { - plan.as_any().is::() + (plan.as_ref() as &dyn Any).is::() } /// Checks whether the given operator is a [`SortPreservingMergeExec`]. pub fn is_sort_preserving_merge(plan: &Arc) -> bool { - plan.as_any().is::() + (plan.as_ref() as &dyn Any).is::() } /// Checks whether the given operator is a [`CoalescePartitionsExec`]. pub fn is_coalesce_partitions(plan: &Arc) -> bool { - plan.as_any().is::() + (plan.as_ref() as &dyn Any).is::() } /// Checks whether the given operator is a [`RepartitionExec`]. pub fn is_repartition(plan: &Arc) -> bool { - plan.as_any().is::() + (plan.as_ref() as &dyn Any).is::() } /// Checks whether the given operator is a limit; /// i.e. either a [`LocalLimitExec`] or a [`GlobalLimitExec`]. pub fn is_limit(plan: &Arc) -> bool { - plan.as_any().is::() || plan.as_any().is::() + (plan.as_ref() as &dyn Any).is::() + || (plan.as_ref() as &dyn Any).is::() } diff --git a/datafusion/physical-plan/src/aggregates/mod.rs b/datafusion/physical-plan/src/aggregates/mod.rs index 2b9d8698183de..a7d7098a69c96 100644 --- a/datafusion/physical-plan/src/aggregates/mod.rs +++ b/datafusion/physical-plan/src/aggregates/mod.rs @@ -17,7 +17,6 @@ //! Aggregates functionalities -use std::any::Any; use std::sync::Arc; use super::{DisplayAs, ExecutionPlanProperties, PlanProperties}; @@ -1341,10 +1340,6 @@ impl ExecutionPlan for AggregateExec { } /// Return a reference to Any that can be used for down-casting - fn as_any(&self) -> &dyn Any { - self - } - fn properties(&self) -> &Arc { &self.cache } @@ -2498,10 +2493,6 @@ mod tests { "TestYieldingExec" } - fn as_any(&self) -> &dyn Any { - self - } - fn properties(&self) -> &Arc { &self.cache } diff --git a/datafusion/physical-plan/src/analyze.rs b/datafusion/physical-plan/src/analyze.rs index 845908fdd2187..491a0872a2f97 100644 --- a/datafusion/physical-plan/src/analyze.rs +++ b/datafusion/physical-plan/src/analyze.rs @@ -17,7 +17,6 @@ //! Defines the ANALYZE operator -use std::any::Any; use std::sync::Arc; use super::stream::{RecordBatchReceiverStream, RecordBatchStreamAdapter}; @@ -138,10 +137,6 @@ impl ExecutionPlan for AnalyzeExec { } /// Return a reference to Any that can be used for downcasting - fn as_any(&self) -> &dyn Any { - self - } - fn properties(&self) -> &Arc { &self.cache } diff --git a/datafusion/physical-plan/src/async_func.rs b/datafusion/physical-plan/src/async_func.rs index abfe870f52665..23f2b9ba1ed5f 100644 --- a/datafusion/physical-plan/src/async_func.rs +++ b/datafusion/physical-plan/src/async_func.rs @@ -37,7 +37,6 @@ use datafusion_physical_expr_common::physical_expr::PhysicalExpr; use futures::Stream; use futures::stream::StreamExt; use log::trace; -use std::any::Any; use std::pin::Pin; use std::sync::Arc; use std::task::{Context, Poll, ready}; @@ -158,10 +157,6 @@ impl ExecutionPlan for AsyncFuncExec { "async_func" } - fn as_any(&self) -> &dyn Any { - self - } - fn properties(&self) -> &Arc { &self.cache } diff --git a/datafusion/physical-plan/src/buffer.rs b/datafusion/physical-plan/src/buffer.rs index 31e38419b32fa..0cc4a1d71814e 100644 --- a/datafusion/physical-plan/src/buffer.rs +++ b/datafusion/physical-plan/src/buffer.rs @@ -43,7 +43,6 @@ use datafusion_physical_expr_common::physical_expr::PhysicalExpr; use datafusion_physical_expr_common::sort_expr::PhysicalSortExpr; use futures::{Stream, StreamExt, TryStreamExt}; use pin_project_lite::pin_project; -use std::any::Any; use std::fmt; use std::pin::Pin; use std::sync::Arc; @@ -153,10 +152,6 @@ impl ExecutionPlan for BufferExec { "BufferExec" } - fn as_any(&self) -> &dyn Any { - self - } - fn properties(&self) -> &Arc { &self.properties } diff --git a/datafusion/physical-plan/src/coalesce_batches.rs b/datafusion/physical-plan/src/coalesce_batches.rs index 3e8bfc7f81724..2bf046f03b6cf 100644 --- a/datafusion/physical-plan/src/coalesce_batches.rs +++ b/datafusion/physical-plan/src/coalesce_batches.rs @@ -17,7 +17,6 @@ //! [`CoalesceBatchesExec`] combines small batches into larger batches. -use std::any::Any; use std::pin::Pin; use std::sync::Arc; use std::task::{Context, Poll}; @@ -168,10 +167,6 @@ impl ExecutionPlan for CoalesceBatchesExec { } /// Return a reference to Any that can be used for downcasting - fn as_any(&self) -> &dyn Any { - self - } - fn properties(&self) -> &Arc { &self.cache } diff --git a/datafusion/physical-plan/src/coalesce_partitions.rs b/datafusion/physical-plan/src/coalesce_partitions.rs index 5ea3589f22b3e..9290d725165e9 100644 --- a/datafusion/physical-plan/src/coalesce_partitions.rs +++ b/datafusion/physical-plan/src/coalesce_partitions.rs @@ -18,7 +18,6 @@ //! Defines the merge plan for executing partitions in parallel and then merging the results //! into a single partition -use std::any::Any; use std::sync::Arc; use super::metrics::{BaselineMetrics, ExecutionPlanMetricsSet, MetricsSet}; @@ -143,10 +142,6 @@ impl ExecutionPlan for CoalescePartitionsExec { } /// Return a reference to Any that can be used for downcasting - fn as_any(&self) -> &dyn Any { - self - } - fn properties(&self) -> &Arc { &self.cache } diff --git a/datafusion/physical-plan/src/coop.rs b/datafusion/physical-plan/src/coop.rs index 129b30d4b4199..fe6a3bc3d5678 100644 --- a/datafusion/physical-plan/src/coop.rs +++ b/datafusion/physical-plan/src/coop.rs @@ -75,7 +75,6 @@ use datafusion_common::tree_node::TreeNodeRecursion; use datafusion_physical_expr::PhysicalExpr; #[cfg(datafusion_coop = "tokio_fallback")] use futures::Future; -use std::any::Any; use std::pin::Pin; use std::sync::Arc; use std::task::{Context, Poll}; @@ -262,10 +261,6 @@ impl ExecutionPlan for CooperativeExec { "CooperativeExec" } - fn as_any(&self) -> &dyn Any { - self - } - fn schema(&self) -> Arc { self.input.schema() } diff --git a/datafusion/physical-plan/src/display.rs b/datafusion/physical-plan/src/display.rs index 964eb152e8096..756a68b1a958d 100644 --- a/datafusion/physical-plan/src/display.rs +++ b/datafusion/physical-plan/src/display.rs @@ -1199,10 +1199,6 @@ mod tests { "TestStatsExecPlan" } - fn as_any(&self) -> &dyn std::any::Any { - self - } - fn properties(&self) -> &Arc { unimplemented!() } diff --git a/datafusion/physical-plan/src/empty.rs b/datafusion/physical-plan/src/empty.rs index 078bc4b8d064b..8103695ad08fa 100644 --- a/datafusion/physical-plan/src/empty.rs +++ b/datafusion/physical-plan/src/empty.rs @@ -17,7 +17,6 @@ //! EmptyRelation with produce_one_row=false execution plan -use std::any::Any; use std::sync::Arc; use crate::memory::MemoryStream; @@ -112,10 +111,6 @@ impl ExecutionPlan for EmptyExec { } /// Return a reference to Any that can be used for downcasting - fn as_any(&self) -> &dyn Any { - self - } - fn properties(&self) -> &Arc { &self.cache } diff --git a/datafusion/physical-plan/src/execution_plan.rs b/datafusion/physical-plan/src/execution_plan.rs index 117fd917396af..922787e32751e 100644 --- a/datafusion/physical-plan/src/execution_plan.rs +++ b/datafusion/physical-plan/src/execution_plan.rs @@ -93,7 +93,7 @@ use futures::stream::{StreamExt, TryStreamExt}; /// /// [`datafusion-examples`]: https://github.com/apache/datafusion/tree/main/datafusion-examples /// [`memory_pool_execution_plan.rs`]: https://github.com/apache/datafusion/blob/main/datafusion-examples/examples/execution_monitoring/memory_pool_execution_plan.rs -pub trait ExecutionPlan: Debug + DisplayAs + Send + Sync { +pub trait ExecutionPlan: Any + Debug + DisplayAs + Send + Sync { /// Short name for the ExecutionPlan, such as 'DataSourceExec'. /// /// Implementation note: this method can just proxy to @@ -117,10 +117,6 @@ pub trait ExecutionPlan: Debug + DisplayAs + Send + Sync { } } - /// Returns the execution plan as [`Any`] so that it can be - /// downcast to a specific implementation. - fn as_any(&self) -> &dyn Any; - /// Get the schema for this execution plan fn schema(&self) -> SchemaRef { Arc::clone(self.properties().schema()) @@ -1609,10 +1605,6 @@ mod tests { Self::static_name() } - fn as_any(&self) -> &dyn Any { - self - } - fn properties(&self) -> &Arc { unimplemented!() } @@ -1682,10 +1674,6 @@ mod tests { "MyRenamedEmptyExec" } - fn as_any(&self) -> &dyn Any { - self - } - fn properties(&self) -> &Arc { unimplemented!() } @@ -1746,10 +1734,6 @@ mod tests { "MultiExprExec" } - fn as_any(&self) -> &dyn Any { - self - } - fn properties(&self) -> &Arc { unimplemented!() } diff --git a/datafusion/physical-plan/src/explain.rs b/datafusion/physical-plan/src/explain.rs index fa684f3483a83..617a1a6cdaf53 100644 --- a/datafusion/physical-plan/src/explain.rs +++ b/datafusion/physical-plan/src/explain.rs @@ -17,7 +17,6 @@ //! Defines the EXPLAIN operator -use std::any::Any; use std::sync::Arc; use super::{DisplayAs, PlanProperties, SendableRecordBatchStream}; @@ -109,10 +108,6 @@ impl ExecutionPlan for ExplainExec { } /// Return a reference to Any that can be used for downcasting - fn as_any(&self) -> &dyn Any { - self - } - fn properties(&self) -> &Arc { &self.cache } diff --git a/datafusion/physical-plan/src/filter.rs b/datafusion/physical-plan/src/filter.rs index bce1b2f95085c..3e053341a1446 100644 --- a/datafusion/physical-plan/src/filter.rs +++ b/datafusion/physical-plan/src/filter.rs @@ -15,7 +15,6 @@ // specific language governing permissions and limitations // under the License. -use std::any::Any; use std::pin::Pin; use std::sync::Arc; use std::task::{Context, Poll, ready}; @@ -520,10 +519,6 @@ impl ExecutionPlan for FilterExec { } /// Return a reference to Any that can be used for downcasting - fn as_any(&self) -> &dyn Any { - self - } - fn properties(&self) -> &Arc { &self.cache } diff --git a/datafusion/physical-plan/src/joins/cross_join.rs b/datafusion/physical-plan/src/joins/cross_join.rs index b64de91d95997..3027fb130f087 100644 --- a/datafusion/physical-plan/src/joins/cross_join.rs +++ b/datafusion/physical-plan/src/joins/cross_join.rs @@ -18,7 +18,7 @@ //! Defines the cross join plan for loading the left side of the cross join //! and producing batches in parallel for the right partitions -use std::{any::Any, sync::Arc, task::Poll}; +use std::{sync::Arc, task::Poll}; use super::utils::{ BatchSplitter, BatchTransformer, BuildProbeJoinMetrics, NoopBatchTransformer, @@ -271,10 +271,6 @@ impl ExecutionPlan for CrossJoinExec { "CrossJoinExec" } - fn as_any(&self) -> &dyn Any { - self - } - fn properties(&self) -> &Arc { &self.cache } diff --git a/datafusion/physical-plan/src/joins/hash_join/exec.rs b/datafusion/physical-plan/src/joins/hash_join/exec.rs index 8f0b5181e0618..9667069a09a32 100644 --- a/datafusion/physical-plan/src/joins/hash_join/exec.rs +++ b/datafusion/physical-plan/src/joins/hash_join/exec.rs @@ -20,7 +20,7 @@ use std::fmt; use std::mem::size_of; use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering}; use std::sync::{Arc, OnceLock}; -use std::{any::Any, vec}; +use std::vec; use crate::ExecutionPlanProperties; use crate::execution_plan::{ @@ -1179,10 +1179,6 @@ impl ExecutionPlan for HashJoinExec { "HashJoinExec" } - fn as_any(&self) -> &dyn Any { - self - } - fn properties(&self) -> &Arc { &self.cache } diff --git a/datafusion/physical-plan/src/joins/nested_loop_join.rs b/datafusion/physical-plan/src/joins/nested_loop_join.rs index 1582556b01e11..cdfe3a33ecbe9 100644 --- a/datafusion/physical-plan/src/joins/nested_loop_join.rs +++ b/datafusion/physical-plan/src/joins/nested_loop_join.rs @@ -17,7 +17,6 @@ //! [`NestedLoopJoinExec`]: joins without equijoin (equality predicates). -use std::any::Any; use std::fmt::Formatter; use std::ops::{BitOr, ControlFlow}; use std::sync::Arc; @@ -535,10 +534,6 @@ impl ExecutionPlan for NestedLoopJoinExec { "NestedLoopJoinExec" } - fn as_any(&self) -> &dyn Any { - self - } - fn properties(&self) -> &Arc { &self.cache } diff --git a/datafusion/physical-plan/src/joins/piecewise_merge_join/exec.rs b/datafusion/physical-plan/src/joins/piecewise_merge_join/exec.rs index fb1c4b160528d..2b20089f8e221 100644 --- a/datafusion/physical-plan/src/joins/piecewise_merge_join/exec.rs +++ b/datafusion/physical-plan/src/joins/piecewise_merge_join/exec.rs @@ -500,10 +500,6 @@ impl ExecutionPlan for PiecewiseMergeJoinExec { "PiecewiseMergeJoinExec" } - fn as_any(&self) -> &dyn std::any::Any { - self - } - fn properties(&self) -> &Arc { &self.cache } diff --git a/datafusion/physical-plan/src/joins/sort_merge_join/exec.rs b/datafusion/physical-plan/src/joins/sort_merge_join/exec.rs index e2c169e5580df..3f309431614a4 100644 --- a/datafusion/physical-plan/src/joins/sort_merge_join/exec.rs +++ b/datafusion/physical-plan/src/joins/sort_merge_join/exec.rs @@ -19,7 +19,6 @@ //! A Sort-Merge join plan consumes two sorted children plans and produces //! joined output by given join type and other options. -use std::any::Any; use std::fmt::Formatter; use std::sync::Arc; @@ -420,10 +419,6 @@ impl ExecutionPlan for SortMergeJoinExec { "SortMergeJoinExec" } - fn as_any(&self) -> &dyn Any { - self - } - fn properties(&self) -> &Arc { &self.cache } diff --git a/datafusion/physical-plan/src/joins/symmetric_hash_join.rs b/datafusion/physical-plan/src/joins/symmetric_hash_join.rs index f31cd8d446de2..dbfdf94426782 100644 --- a/datafusion/physical-plan/src/joins/symmetric_hash_join.rs +++ b/datafusion/physical-plan/src/joins/symmetric_hash_join.rs @@ -25,7 +25,6 @@ //! This plan uses the [`OneSideHashJoiner`] object to facilitate join calculations //! for both its children. -use std::any::Any; use std::fmt::{self, Debug}; use std::mem::{size_of, size_of_val}; use std::sync::Arc; @@ -423,10 +422,6 @@ impl ExecutionPlan for SymmetricHashJoinExec { "SymmetricHashJoinExec" } - fn as_any(&self) -> &dyn Any { - self - } - fn properties(&self) -> &Arc { &self.cache } diff --git a/datafusion/physical-plan/src/limit.rs b/datafusion/physical-plan/src/limit.rs index 5632ef234af66..51bef5d24bd2d 100644 --- a/datafusion/physical-plan/src/limit.rs +++ b/datafusion/physical-plan/src/limit.rs @@ -17,7 +17,6 @@ //! Defines the LIMIT plan -use std::any::Any; use std::pin::Pin; use std::sync::Arc; use std::task::{Context, Poll}; @@ -155,10 +154,6 @@ impl ExecutionPlan for GlobalLimitExec { } /// Return a reference to Any that can be used for downcasting - fn as_any(&self) -> &dyn Any { - self - } - fn properties(&self) -> &Arc { &self.cache } @@ -347,10 +342,6 @@ impl ExecutionPlan for LocalLimitExec { } /// Return a reference to Any that can be used for downcasting - fn as_any(&self) -> &dyn Any { - self - } - fn properties(&self) -> &Arc { &self.cache } diff --git a/datafusion/physical-plan/src/memory.rs b/datafusion/physical-plan/src/memory.rs index d607f2b440f66..3b91990588513 100644 --- a/datafusion/physical-plan/src/memory.rs +++ b/datafusion/physical-plan/src/memory.rs @@ -300,10 +300,6 @@ impl ExecutionPlan for LazyMemoryExec { "LazyMemoryExec" } - fn as_any(&self) -> &dyn Any { - self - } - fn schema(&self) -> SchemaRef { Arc::clone(&self.schema) } diff --git a/datafusion/physical-plan/src/placeholder_row.rs b/datafusion/physical-plan/src/placeholder_row.rs index eaa895c821837..ae8e73cd74ade 100644 --- a/datafusion/physical-plan/src/placeholder_row.rs +++ b/datafusion/physical-plan/src/placeholder_row.rs @@ -17,7 +17,6 @@ //! EmptyRelation produce_one_row=true execution plan -use std::any::Any; use std::sync::Arc; use crate::coop::cooperative; @@ -130,10 +129,6 @@ impl ExecutionPlan for PlaceholderRowExec { } /// Return a reference to Any that can be used for downcasting - fn as_any(&self) -> &dyn Any { - self - } - fn properties(&self) -> &Arc { &self.cache } diff --git a/datafusion/physical-plan/src/projection.rs b/datafusion/physical-plan/src/projection.rs index cd80277156fcb..c19aa2cc83409 100644 --- a/datafusion/physical-plan/src/projection.rs +++ b/datafusion/physical-plan/src/projection.rs @@ -283,10 +283,6 @@ impl ExecutionPlan for ProjectionExec { } /// Return a reference to Any that can be used for downcasting - fn as_any(&self) -> &dyn Any { - self - } - fn properties(&self) -> &Arc { &self.cache } @@ -732,20 +728,21 @@ pub fn try_pushdown_through_join( pub fn remove_unnecessary_projections( plan: Arc, ) -> Result>> { - let maybe_modified = - if let Some(projection) = plan.as_any().downcast_ref::() { - // If the projection does not cause any change on the input, we can - // safely remove it: - if is_projection_removable(projection) { - return Ok(Transformed::yes(Arc::clone(projection.input()))); - } - // If it does, check if we can push it under its child(ren): - projection - .input() - .try_swapping_with_projection(projection)? - } else { - return Ok(Transformed::no(plan)); - }; + let maybe_modified = if let Some(projection) = + (plan.as_ref() as &dyn Any).downcast_ref::() + { + // If the projection does not cause any change on the input, we can + // safely remove it: + if is_projection_removable(projection) { + return Ok(Transformed::yes(Arc::clone(projection.input()))); + } + // If it does, check if we can push it under its child(ren): + projection + .input() + .try_swapping_with_projection(projection)? + } else { + return Ok(Transformed::no(plan)); + }; Ok(maybe_modified.map_or_else(|| Transformed::no(plan), Transformed::yes)) } diff --git a/datafusion/physical-plan/src/recursive_query.rs b/datafusion/physical-plan/src/recursive_query.rs index 049aa9563d52e..35b787759441c 100644 --- a/datafusion/physical-plan/src/recursive_query.rs +++ b/datafusion/physical-plan/src/recursive_query.rs @@ -141,10 +141,6 @@ impl ExecutionPlan for RecursiveQueryExec { "RecursiveQueryExec" } - fn as_any(&self) -> &dyn Any { - self - } - fn properties(&self) -> &Arc { &self.cache } diff --git a/datafusion/physical-plan/src/repartition/mod.rs b/datafusion/physical-plan/src/repartition/mod.rs index d3020c2756fff..8eefb2cbcdd75 100644 --- a/datafusion/physical-plan/src/repartition/mod.rs +++ b/datafusion/physical-plan/src/repartition/mod.rs @@ -23,7 +23,7 @@ use std::fmt::{Debug, Formatter}; use std::pin::Pin; use std::sync::Arc; use std::task::{Context, Poll}; -use std::{any::Any, vec}; +use std::vec; use super::common::SharedMemoryReservation; use super::metrics::{self, ExecutionPlanMetricsSet, MetricBuilder, MetricsSet}; @@ -902,10 +902,6 @@ impl ExecutionPlan for RepartitionExec { } /// Return a reference to Any that can be used for downcasting - fn as_any(&self) -> &dyn Any { - self - } - fn properties(&self) -> &Arc { &self.cache } diff --git a/datafusion/physical-plan/src/sorts/partial_sort.rs b/datafusion/physical-plan/src/sorts/partial_sort.rs index 127998601fba8..28b8745235918 100644 --- a/datafusion/physical-plan/src/sorts/partial_sort.rs +++ b/datafusion/physical-plan/src/sorts/partial_sort.rs @@ -51,7 +51,6 @@ //! The plan concats incoming data with such last rows of previous input //! and continues partial sorting of the segments. -use std::any::Any; use std::fmt::Debug; use std::pin::Pin; use std::sync::Arc; @@ -260,10 +259,6 @@ impl ExecutionPlan for PartialSortExec { "PartialSortExec" } - fn as_any(&self) -> &dyn Any { - self - } - fn properties(&self) -> &Arc { &self.cache } diff --git a/datafusion/physical-plan/src/sorts/sort.rs b/datafusion/physical-plan/src/sorts/sort.rs index bdf08823a29d1..a7744573cf9e5 100644 --- a/datafusion/physical-plan/src/sorts/sort.rs +++ b/datafusion/physical-plan/src/sorts/sort.rs @@ -1176,10 +1176,6 @@ impl ExecutionPlan for SortExec { } } - fn as_any(&self) -> &dyn Any { - self - } - fn properties(&self) -> &Arc { &self.cache } @@ -1246,8 +1242,7 @@ impl ExecutionPlan for SortExec { fn reset_state(self: Arc) -> Result> { let children = self.children().into_iter().cloned().collect(); let new_sort = self.with_new_children(children)?; - let mut new_sort = new_sort - .as_any() + let mut new_sort = (new_sort.as_ref() as &dyn Any) .downcast_ref::() .expect("cloned 1 lines above this line, we know the type") .clone(); @@ -1509,10 +1504,6 @@ mod tests { Self::static_name() } - fn as_any(&self) -> &dyn Any { - self - } - fn properties(&self) -> &Arc { &self.cache } diff --git a/datafusion/physical-plan/src/sorts/sort_preserving_merge.rs b/datafusion/physical-plan/src/sorts/sort_preserving_merge.rs index 1e60c391f50d1..b77cced35504b 100644 --- a/datafusion/physical-plan/src/sorts/sort_preserving_merge.rs +++ b/datafusion/physical-plan/src/sorts/sort_preserving_merge.rs @@ -17,7 +17,6 @@ //! [`SortPreservingMergeExec`] merges multiple sorted streams into one sorted stream. -use std::any::Any; use std::sync::Arc; use crate::common::spawn_buffered; @@ -235,10 +234,6 @@ impl ExecutionPlan for SortPreservingMergeExec { } /// Return a reference to Any that can be used for downcasting - fn as_any(&self) -> &dyn Any { - self - } - fn properties(&self) -> &Arc { &self.cache } @@ -1418,9 +1413,6 @@ mod tests { fn name(&self) -> &'static str { Self::static_name() } - fn as_any(&self) -> &dyn Any { - self - } fn properties(&self) -> &Arc { &self.cache } diff --git a/datafusion/physical-plan/src/streaming.rs b/datafusion/physical-plan/src/streaming.rs index 5a1206629ac7b..250eb59f19b87 100644 --- a/datafusion/physical-plan/src/streaming.rs +++ b/datafusion/physical-plan/src/streaming.rs @@ -17,7 +17,6 @@ //! Generic plans for deferred execution: [`StreamingTableExec`] and [`PartitionStream`] -use std::any::Any; use std::fmt::Debug; use std::sync::Arc; @@ -234,10 +233,6 @@ impl ExecutionPlan for StreamingTableExec { "StreamingTableExec" } - fn as_any(&self) -> &dyn Any { - self - } - fn properties(&self) -> &Arc { &self.cache } diff --git a/datafusion/physical-plan/src/test.rs b/datafusion/physical-plan/src/test.rs index 0630b8f174563..4c4724e4dcc4f 100644 --- a/datafusion/physical-plan/src/test.rs +++ b/datafusion/physical-plan/src/test.rs @@ -17,7 +17,6 @@ //! Utilities for testing datafusion-physical-plan -use std::any::Any; use std::collections::HashMap; use std::fmt; use std::fmt::{Debug, Formatter}; @@ -133,10 +132,6 @@ impl ExecutionPlan for TestMemoryExec { "DataSourceExec" } - fn as_any(&self) -> &dyn Any { - self - } - fn properties(&self) -> &Arc { &self.cache } diff --git a/datafusion/physical-plan/src/test/exec.rs b/datafusion/physical-plan/src/test/exec.rs index 5458fa7ab8264..200223b9b660a 100644 --- a/datafusion/physical-plan/src/test/exec.rs +++ b/datafusion/physical-plan/src/test/exec.rs @@ -28,7 +28,6 @@ use crate::{ }; use std::sync::atomic::{AtomicUsize, Ordering}; use std::{ - any::Any, pin::Pin, sync::{Arc, Weak}, task::{Context, Poll}, @@ -189,10 +188,6 @@ impl ExecutionPlan for MockExec { Self::static_name() } - fn as_any(&self) -> &dyn Any { - self - } - fn properties(&self) -> &Arc { &self.cache } @@ -426,10 +421,6 @@ impl ExecutionPlan for BarrierExec { Self::static_name() } - fn as_any(&self) -> &dyn Any { - self - } - fn properties(&self) -> &Arc { &self.cache } @@ -569,10 +560,6 @@ impl ExecutionPlan for ErrorExec { Self::static_name() } - fn as_any(&self) -> &dyn Any { - self - } - fn properties(&self) -> &Arc { &self.cache } @@ -666,10 +653,6 @@ impl ExecutionPlan for StatisticsExec { Self::static_name() } - fn as_any(&self) -> &dyn Any { - self - } - fn properties(&self) -> &Arc { &self.cache } @@ -776,10 +759,6 @@ impl ExecutionPlan for BlockingExec { Self::static_name() } - fn as_any(&self) -> &dyn Any { - self - } - fn properties(&self) -> &Arc { &self.cache } @@ -929,10 +908,6 @@ impl ExecutionPlan for PanicExec { Self::static_name() } - fn as_any(&self) -> &dyn Any { - self - } - fn properties(&self) -> &Arc { &self.cache } diff --git a/datafusion/physical-plan/src/union.rs b/datafusion/physical-plan/src/union.rs index eb16375a2d647..89dfe5757045c 100644 --- a/datafusion/physical-plan/src/union.rs +++ b/datafusion/physical-plan/src/union.rs @@ -23,8 +23,8 @@ use std::borrow::Borrow; use std::pin::Pin; +use std::sync::Arc; use std::task::{Context, Poll}; -use std::{any::Any, sync::Arc}; use super::{ ColumnStatistics, DisplayAs, DisplayFormatType, ExecutionPlan, @@ -225,10 +225,6 @@ impl ExecutionPlan for UnionExec { } /// Return a reference to Any that can be used for downcasting - fn as_any(&self) -> &dyn Any { - self - } - fn properties(&self) -> &Arc { &self.cache } @@ -593,10 +589,6 @@ impl ExecutionPlan for InterleaveExec { } /// Return a reference to Any that can be used for downcasting - fn as_any(&self) -> &dyn Any { - self - } - fn properties(&self) -> &Arc { &self.cache } @@ -906,6 +898,8 @@ fn stats_union(mut left: Statistics, right: Statistics) -> Statistics { #[cfg(test)] mod tests { + use std::any::Any; + use super::*; use crate::collect; use crate::test::{self, TestMemoryExec}; @@ -1410,8 +1404,7 @@ mod tests { let union_plan = UnionExec::try_new(vec![memory_exec1, memory_exec2])?; // Downcast to verify it's a UnionExec - let union = union_plan - .as_any() + let union = (&union_plan as &dyn Any) .downcast_ref::() .expect("Expected UnionExec"); @@ -1451,8 +1444,7 @@ mod tests { Arc::new(TestMemoryExec::try_new(&[], Arc::clone(&schema), None)?); let union = UnionExec::try_new(vec![input1, input2])?; - let union = union - .as_any() + let union = (&union as &dyn Any) .downcast_ref::() .expect("expected UnionExec for multiple inputs"); diff --git a/datafusion/physical-plan/src/unnest.rs b/datafusion/physical-plan/src/unnest.rs index a70b292120334..c774ff09af33c 100644 --- a/datafusion/physical-plan/src/unnest.rs +++ b/datafusion/physical-plan/src/unnest.rs @@ -18,8 +18,8 @@ //! Define a plan for unnesting values in columns that contain a list type. use std::cmp::{self, Ordering}; +use std::sync::Arc; use std::task::{Poll, ready}; -use std::{any::Any, sync::Arc}; use super::metrics::{ self, BaselineMetrics, ExecutionPlanMetricsSet, MetricBuilder, MetricCategory, @@ -230,10 +230,6 @@ impl ExecutionPlan for UnnestExec { "UnnestExec" } - fn as_any(&self) -> &dyn Any { - self - } - fn properties(&self) -> &Arc { &self.cache } diff --git a/datafusion/physical-plan/src/windows/bounded_window_agg_exec.rs b/datafusion/physical-plan/src/windows/bounded_window_agg_exec.rs index d0c44c659c20d..6d82e89d42285 100644 --- a/datafusion/physical-plan/src/windows/bounded_window_agg_exec.rs +++ b/datafusion/physical-plan/src/windows/bounded_window_agg_exec.rs @@ -20,7 +20,6 @@ //! the input data seen so far), which makes it appropriate when processing //! infinite inputs. -use std::any::Any; use std::cmp::{Ordering, min}; use std::collections::VecDeque; use std::pin::Pin; @@ -313,10 +312,6 @@ impl ExecutionPlan for BoundedWindowAggExec { } /// Return a reference to Any that can be used for downcasting - fn as_any(&self) -> &dyn Any { - self - } - fn properties(&self) -> &Arc { &self.cache } @@ -1266,6 +1261,7 @@ pub(crate) fn get_last_row_batch(batch: &RecordBatch) -> Result { #[cfg(test)] mod tests { + use std::any::Any; use std::pin::Pin; use std::sync::Arc; use std::task::{Context, Poll}; @@ -1864,8 +1860,7 @@ mod tests { let input: Arc = Arc::new(TestMemoryExec::try_new(&[], Arc::clone(&schema), None)?); let plan = bounded_window_exec_pb_latent_range(input, 1, "hash", "sn")?; - let plan = plan - .as_any() + let plan = (plan.as_ref() as &dyn Any) .downcast_ref::() .expect("expected BoundedWindowAggExec"); diff --git a/datafusion/physical-plan/src/windows/window_agg_exec.rs b/datafusion/physical-plan/src/windows/window_agg_exec.rs index c9958c875c6b6..5098c84034062 100644 --- a/datafusion/physical-plan/src/windows/window_agg_exec.rs +++ b/datafusion/physical-plan/src/windows/window_agg_exec.rs @@ -17,7 +17,6 @@ //! Stream and channel implementations for window function expressions. -use std::any::Any; use std::pin::Pin; use std::sync::Arc; use std::task::{Context, Poll}; @@ -214,10 +213,6 @@ impl ExecutionPlan for WindowAggExec { } /// Return a reference to Any that can be used for downcasting - fn as_any(&self) -> &dyn Any { - self - } - fn properties(&self) -> &Arc { &self.cache } diff --git a/datafusion/physical-plan/src/work_table.rs b/datafusion/physical-plan/src/work_table.rs index c2ef6bf071c43..0855dbf2fd635 100644 --- a/datafusion/physical-plan/src/work_table.rs +++ b/datafusion/physical-plan/src/work_table.rs @@ -178,10 +178,6 @@ impl ExecutionPlan for WorkTableExec { "WorkTableExec" } - fn as_any(&self) -> &dyn Any { - self - } - fn properties(&self) -> &Arc { &self.cache } diff --git a/datafusion/proto/src/physical_plan/mod.rs b/datafusion/proto/src/physical_plan/mod.rs index 0f37e9ad3f942..4e37e9f9528e5 100644 --- a/datafusion/proto/src/physical_plan/mod.rs +++ b/datafusion/proto/src/physical_plan/mod.rs @@ -333,7 +333,7 @@ impl protobuf::PhysicalPlanNode { Self: Sized, { let plan_clone = Arc::clone(&plan); - let plan = plan.as_any(); + let plan = plan.as_ref() as &dyn Any; if let Some(exec) = plan.downcast_ref::() { return protobuf::PhysicalPlanNode::try_from_explain_exec(exec, codec); diff --git a/datafusion/proto/tests/cases/roundtrip_physical_plan.rs b/datafusion/proto/tests/cases/roundtrip_physical_plan.rs index 10b50930b469b..c1788d2f38ab7 100644 --- a/datafusion/proto/tests/cases/roundtrip_physical_plan.rs +++ b/datafusion/proto/tests/cases/roundtrip_physical_plan.rs @@ -968,8 +968,7 @@ fn roundtrip_parquet_exec_attaches_cached_reader_factory_after_roundtrip() -> Re let roundtripped = roundtrip_test_and_return(exec_plan, &ctx, &codec, &proto_converter)?; - let data_source = roundtripped - .as_any() + let data_source = (roundtripped.as_ref() as &dyn Any) .downcast_ref::() .ok_or_else(|| { internal_datafusion_err!("Expected DataSourceExec after roundtrip") @@ -1657,8 +1656,7 @@ fn roundtrip_csv_sink() -> Result<()> { &proto_converter, )?; - let roundtrip_plan = roundtrip_plan - .as_any() + let roundtrip_plan = (roundtrip_plan.as_ref() as &dyn Any) .downcast_ref::() .unwrap(); let csv_sink = roundtrip_plan @@ -2522,7 +2520,9 @@ fn roundtrip_hash_table_lookup_expr_to_lit() -> Result<()> { // The deserialized plan should have lit(true) instead of HashTableLookupExpr // Verify the filter predicate is a Literal(true) - let result_filter = result.as_any().downcast_ref::().unwrap(); + let result_filter = (result.as_ref() as &dyn Any) + .downcast_ref::() + .unwrap(); let predicate = result_filter.predicate(); let literal = predicate.as_any().downcast_ref::().unwrap(); assert_eq!(*literal.value(), ScalarValue::Boolean(Some(true))); @@ -2818,8 +2818,7 @@ fn test_expression_deduplication_arc_sharing() -> Result<()> { )?; // Get the projection from the result - let projection = result_plan - .as_any() + let projection = (result_plan.as_ref() as &dyn Any) .downcast_ref::() .expect("Expected ProjectionExec"); @@ -2927,8 +2926,7 @@ fn test_deduplication_within_plan_deserialization() -> Result<()> { )?; // Check that the plan was deserialized correctly with deduplication - let projection1 = plan1 - .as_any() + let projection1 = (plan1.as_ref() as &dyn Any) .downcast_ref::() .expect("Expected ProjectionExec"); let exprs1: Vec<_> = projection1.expr().iter().collect(); @@ -2947,8 +2945,7 @@ fn test_deduplication_within_plan_deserialization() -> Result<()> { )?; // Check that the second plan was also deserialized correctly - let projection2 = plan2 - .as_any() + let projection2 = (plan2.as_ref() as &dyn Any) .downcast_ref::() .expect("Expected ProjectionExec"); let exprs2: Vec<_> = projection2.expr().iter().collect(); diff --git a/datafusion/substrait/src/physical_plan/producer.rs b/datafusion/substrait/src/physical_plan/producer.rs index 7a2da70352b00..3d30704c73d5d 100644 --- a/datafusion/substrait/src/physical_plan/producer.rs +++ b/datafusion/substrait/src/physical_plan/producer.rs @@ -51,7 +51,8 @@ pub fn to_substrait_rel( HashMap, ), ) -> Result> { - if let Some(data_source_exec) = plan.as_any().downcast_ref::() + if let Some(data_source_exec) = + (plan as &dyn std::any::Any).downcast_ref::() && let Some((file_config, _)) = data_source_exec.downcast_to_file_source::() { diff --git a/docs/source/library-user-guide/custom-table-providers.md b/docs/source/library-user-guide/custom-table-providers.md index 70b6be3ae2ab1..9e5fd605d75cd 100644 --- a/docs/source/library-user-guide/custom-table-providers.md +++ b/docs/source/library-user-guide/custom-table-providers.md @@ -100,10 +100,6 @@ impl ExecutionPlan for CustomExec { "CustomExec" } - fn as_any(&self) -> &dyn Any { - self - } - fn schema(&self) -> SchemaRef { self.projected_schema.clone() } @@ -231,10 +227,6 @@ The `scan` method of the `TableProvider` returns a `Result &dyn Any { -# self -# } -# # fn schema(&self) -> SchemaRef { # self.projected_schema.clone() # } @@ -431,10 +423,6 @@ This will allow you to use the custom table provider in DataFusion. For example, # "CustomExec" # } # -# fn as_any(&self) -> &dyn Any { -# self -# } -# # fn schema(&self) -> SchemaRef { # self.projected_schema.clone() # } diff --git a/docs/source/library-user-guide/upgrading/54.0.0.md b/docs/source/library-user-guide/upgrading/54.0.0.md index d8758e1342485..e7270efda3f3e 100644 --- a/docs/source/library-user-guide/upgrading/54.0.0.md +++ b/docs/source/library-user-guide/upgrading/54.0.0.md @@ -124,13 +124,13 @@ let mut stats = Arc::unwrap_or_clone(plan.partition_statistics(None)?); stats.column_statistics[0].min_value = ...; ``` -### Remove `as_any` from `ScalarUDFImpl`, `AggregateUDFImpl`, and `WindowUDFImpl` +### Remove `as_any` from `ScalarUDFImpl`, `AggregateUDFImpl`, `WindowUDFImpl`, and `ExecutionPlan` Now that we have a more recent minimum version of Rust, we can take advantage of -trait upcasting for UDFs. This reduces the amount of boilerplate code that -users need to do to create a UDF. In your implementations of `ScalarUDFImpl`, -`AggregateUDFImpl`, and `WindowUDFImpl`, you can simply remove the `as_any` -function. The below diffs are examples from the associated PRs. +trait upcasting. This reduces the amount of boilerplate code that +users need to implement. In your implementations of `ScalarUDFImpl`, +`AggregateUDFImpl`, `WindowUDFImpl`, and `ExecutionPlan`, you can simply remove +the `as_any` function. The below diffs are examples from the associated PRs. **Scalar UDFs:** @@ -180,27 +180,41 @@ function. The below diffs are examples from the associated PRs. } ``` -If you have a function that is downcasting a UDF, you can replace +**Execution Plans:** + +```diff + impl ExecutionPlan for MyExec { +- fn as_any(&self) -> &dyn Any { +- self +- } +- + fn name(&self) -> &'static str { + "MyExec" + } + + ... + } +``` + +If you have code that is downcasting, you can replace the call to `.as_any()` with `.as_ref() as &dyn Any`. For example **Before:** ```rust,ignore -let is_async = func - .inner() +let exec = plan .as_any() - .downcast_ref::() - .is_some(); + .downcast_ref::() + .unwrap(); ``` **After:** ```rust,ignore -let is_async = (func - .inner() +let exec = (plan .as_ref() as &dyn Any) - .downcast_ref::() - .is_some(); + .downcast_ref::() + .unwrap(); ``` ### Avro API and timestamp decoding changes From c6e9e5318e5f5ceddd61d4d84b51f397cf7f1d42 Mon Sep 17 00:00:00 2001 From: Tim Saucer Date: Tue, 31 Mar 2026 07:30:25 -0400 Subject: [PATCH 02/14] we already had &dyn so no need for as_ref() --- datafusion/physical-optimizer/src/sanity_checker.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/datafusion/physical-optimizer/src/sanity_checker.rs b/datafusion/physical-optimizer/src/sanity_checker.rs index da1f0f47249df..c5b05bb54bea2 100644 --- a/datafusion/physical-optimizer/src/sanity_checker.rs +++ b/datafusion/physical-optimizer/src/sanity_checker.rs @@ -90,8 +90,7 @@ pub fn check_finiteness_requirements( input: &dyn ExecutionPlan, optimizer_options: &OptimizerOptions, ) -> Result<()> { - if let Some(exec) = - (input.as_ref() as &dyn Any).downcast_ref::() + if let Some(exec) = (input as &dyn Any).downcast_ref::() && !(optimizer_options.allow_symmetric_joins_without_pruning || (exec.check_if_order_information_available()? && is_prunable(exec))) { From 5562887a62e8695d24973fd1d8d3ef62baf11f39 Mon Sep 17 00:00:00 2001 From: Tim Saucer Date: Tue, 31 Mar 2026 10:16:00 -0400 Subject: [PATCH 03/14] Update unit tests --- datafusion/physical-plan/src/union.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/datafusion/physical-plan/src/union.rs b/datafusion/physical-plan/src/union.rs index 89dfe5757045c..0c4605b067f4c 100644 --- a/datafusion/physical-plan/src/union.rs +++ b/datafusion/physical-plan/src/union.rs @@ -1404,7 +1404,7 @@ mod tests { let union_plan = UnionExec::try_new(vec![memory_exec1, memory_exec2])?; // Downcast to verify it's a UnionExec - let union = (&union_plan as &dyn Any) + let union = (union_plan.as_ref() as &dyn Any) .downcast_ref::() .expect("Expected UnionExec"); @@ -1444,7 +1444,7 @@ mod tests { Arc::new(TestMemoryExec::try_new(&[], Arc::clone(&schema), None)?); let union = UnionExec::try_new(vec![input1, input2])?; - let union = (&union as &dyn Any) + let union = (union.as_ref() as &dyn Any) .downcast_ref::() .expect("expected UnionExec for multiple inputs"); From cde29a530c38a6f957865b581f234b3481615433 Mon Sep 17 00:00:00 2001 From: Tim Saucer Date: Tue, 31 Mar 2026 10:27:28 -0400 Subject: [PATCH 04/14] Add warning about Arc downcasting pitfall in upgrade guide When upcasting to `&dyn Any` from an `Arc`, 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) --- .../library-user-guide/upgrading/54.0.0.md | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/docs/source/library-user-guide/upgrading/54.0.0.md b/docs/source/library-user-guide/upgrading/54.0.0.md index e7270efda3f3e..918c475798359 100644 --- a/docs/source/library-user-guide/upgrading/54.0.0.md +++ b/docs/source/library-user-guide/upgrading/54.0.0.md @@ -217,6 +217,23 @@ let exec = (plan .unwrap(); ``` +> **Warning:** This applies to all the traits above (`ScalarUDFImpl`, +> `AggregateUDFImpl`, `WindowUDFImpl`, and `ExecutionPlan`). If you hold +> the trait object behind an `Arc` (e.g. `Arc`), be +> careful with the cast. Writing `(&plan as &dyn Any)` will give you an +> `Any` reference to the **`Arc` itself**, not the underlying trait object, +> so the downcast will fail. You must dereference through the `Arc` first: +> +> ```rust,ignore +> // WRONG – downcasts the Arc, not the inner trait object +> let exec = (&plan as &dyn Any) +> .downcast_ref::(); // always None +> +> // CORRECT – downcasts the inner dyn ExecutionPlan +> let exec = (plan.as_ref() as &dyn Any) +> .downcast_ref::(); // works as expected +> ``` + ### Avro API and timestamp decoding changes DataFusion has switched to use `arrow-avro` (see [#17861]) when reading avro files From 7a4b65382e0eb632e105ff9f8796672fc2b671f1 Mon Sep 17 00:00:00 2001 From: Tim Saucer Date: Tue, 31 Mar 2026 11:02:06 -0400 Subject: [PATCH 05/14] Use Any::is() instead of downcast_ref().is_some() in tests Co-Authored-By: Claude Opus 4.6 (1M context) --- .../core/tests/custom_sources_cases/mod.rs | 5 +---- datafusion/core/tests/sql/explain_analyze.rs | 17 +++++++++-------- 2 files changed, 10 insertions(+), 12 deletions(-) diff --git a/datafusion/core/tests/custom_sources_cases/mod.rs b/datafusion/core/tests/custom_sources_cases/mod.rs index e1225136e2836..6745126b3c911 100644 --- a/datafusion/core/tests/custom_sources_cases/mod.rs +++ b/datafusion/core/tests/custom_sources_cases/mod.rs @@ -331,10 +331,7 @@ async fn optimizers_catch_all_statistics() { #[expect(clippy::needless_pass_by_value)] fn contains_place_holder_exec(plan: Arc) -> bool { - if (plan.as_ref() as &dyn Any) - .downcast_ref::() - .is_some() - { + if (plan.as_ref() as &dyn Any).is::() { true } else if plan.children().len() != 1 { false diff --git a/datafusion/core/tests/sql/explain_analyze.rs b/datafusion/core/tests/sql/explain_analyze.rs index 589a018c05dbe..62b6f6487edd5 100644 --- a/datafusion/core/tests/sql/explain_analyze.rs +++ b/datafusion/core/tests/sql/explain_analyze.rs @@ -140,14 +140,15 @@ async fn explain_analyze_baseline_metrics() { use datafusion::physical_plan; use datafusion::physical_plan::sorts; - (plan as &dyn Any).downcast_ref::().is_some() - || (plan as &dyn Any).downcast_ref::().is_some() - || (plan as &dyn Any).downcast_ref::().is_some() - || (plan as &dyn Any).downcast_ref::().is_some() - || (plan as &dyn Any).downcast_ref::().is_some() - || (plan as &dyn Any).downcast_ref::().is_some() - || (plan as &dyn Any).downcast_ref::().is_some() - || (plan as &dyn Any).downcast_ref::().is_some() + (plan as &dyn Any).is::() + || (plan as &dyn Any).is::() + || (plan as &dyn Any).is::() + || (plan as &dyn Any).is::() + || (plan as &dyn Any).is::() + || (plan as &dyn Any) + .is::() + || (plan as &dyn Any).is::() + || (plan as &dyn Any).is::() } // Validate that the recorded elapsed compute time was more than From da69a2618e6f7556faa96cd4bd145e186b4f6943 Mon Sep 17 00:00:00 2001 From: Tim Saucer Date: Tue, 31 Mar 2026 16:40:17 -0400 Subject: [PATCH 06/14] Add downcast_ref and is methods to dyn ExecutionPlan Adds an inherent impl on `dyn ExecutionPlan` providing `downcast_ref` and `is` methods, so callers can write `plan.downcast_ref::()` instead of `(plan.as_ref() as &dyn Any).downcast_ref::()`. Both methods work correctly whether `plan` is `&dyn ExecutionPlan` or `Arc` via auto-deref. Updates call sites in tests and the 54.0.0 upgrade guide accordingly. Co-Authored-By: Claude Sonnet 4.6 --- .../core/tests/custom_sources_cases/mod.rs | 2 +- datafusion/core/tests/sql/explain_analyze.rs | 18 +++++----- .../physical-plan/src/execution_plan.rs | 20 +++++++++++ .../library-user-guide/upgrading/54.0.0.md | 34 +++++++++---------- 4 files changed, 46 insertions(+), 28 deletions(-) diff --git a/datafusion/core/tests/custom_sources_cases/mod.rs b/datafusion/core/tests/custom_sources_cases/mod.rs index 6745126b3c911..c01e65beddd3d 100644 --- a/datafusion/core/tests/custom_sources_cases/mod.rs +++ b/datafusion/core/tests/custom_sources_cases/mod.rs @@ -331,7 +331,7 @@ async fn optimizers_catch_all_statistics() { #[expect(clippy::needless_pass_by_value)] fn contains_place_holder_exec(plan: Arc) -> bool { - if (plan.as_ref() as &dyn Any).is::() { + if plan.is::() { true } else if plan.children().len() != 1 { false diff --git a/datafusion/core/tests/sql/explain_analyze.rs b/datafusion/core/tests/sql/explain_analyze.rs index 62b6f6487edd5..8ab0d150a7272 100644 --- a/datafusion/core/tests/sql/explain_analyze.rs +++ b/datafusion/core/tests/sql/explain_analyze.rs @@ -18,7 +18,6 @@ use super::*; use insta::assert_snapshot; use rstest::rstest; -use std::any::Any; use datafusion::config::ConfigOptions; use datafusion::physical_plan::display::DisplayableExecutionPlan; @@ -140,15 +139,14 @@ async fn explain_analyze_baseline_metrics() { use datafusion::physical_plan; use datafusion::physical_plan::sorts; - (plan as &dyn Any).is::() - || (plan as &dyn Any).is::() - || (plan as &dyn Any).is::() - || (plan as &dyn Any).is::() - || (plan as &dyn Any).is::() - || (plan as &dyn Any) - .is::() - || (plan as &dyn Any).is::() - || (plan as &dyn Any).is::() + plan.is::() + || plan.is::() + || plan.is::() + || plan.is::() + || plan.is::() + || plan.is::() + || plan.is::() + || plan.is::() } // Validate that the recorded elapsed compute time was more than diff --git a/datafusion/physical-plan/src/execution_plan.rs b/datafusion/physical-plan/src/execution_plan.rs index 922787e32751e..1a67ea0ded11b 100644 --- a/datafusion/physical-plan/src/execution_plan.rs +++ b/datafusion/physical-plan/src/execution_plan.rs @@ -791,6 +791,26 @@ pub trait ExecutionPlan: Any + Debug + DisplayAs + Send + Sync { } } +impl dyn ExecutionPlan { + /// Returns `true` if the plan is of type `T`. + /// + /// Prefer this over `downcast_ref::().is_some()`. Works correctly when + /// called on `Arc` via auto-deref. + pub fn is(&self) -> bool { + (self as &dyn Any).is::() + } + + /// Attempts to downcast this plan to a concrete type `T`, returning `None` + /// if the plan is not of that type. + /// + /// Works correctly when called on `Arc` via auto-deref, + /// unlike `(&arc as &dyn Any).downcast_ref::()` which would attempt to + /// downcast the `Arc` itself. + pub fn downcast_ref(&self) -> Option<&T> { + (self as &dyn Any).downcast_ref() + } +} + /// [`ExecutionPlan`] Invariant Level /// /// What set of assertions ([Invariant]s) holds for a particular `ExecutionPlan` diff --git a/docs/source/library-user-guide/upgrading/54.0.0.md b/docs/source/library-user-guide/upgrading/54.0.0.md index 918c475798359..a9eb02645cf44 100644 --- a/docs/source/library-user-guide/upgrading/54.0.0.md +++ b/docs/source/library-user-guide/upgrading/54.0.0.md @@ -196,8 +196,8 @@ the `as_any` function. The below diffs are examples from the associated PRs. } ``` -If you have code that is downcasting, you can replace -the call to `.as_any()` with `.as_ref() as &dyn Any`. For example +If you have code that is downcasting an `ExecutionPlan`, you can use the +new `downcast_ref` and `is` methods defined directly on `dyn ExecutionPlan`: **Before:** @@ -211,27 +211,27 @@ let exec = plan **After:** ```rust,ignore -let exec = (plan - .as_ref() as &dyn Any) - .downcast_ref::() - .unwrap(); +let exec = plan.downcast_ref::().unwrap(); ``` -> **Warning:** This applies to all the traits above (`ScalarUDFImpl`, -> `AggregateUDFImpl`, `WindowUDFImpl`, and `ExecutionPlan`). If you hold -> the trait object behind an `Arc` (e.g. `Arc`), be -> careful with the cast. Writing `(&plan as &dyn Any)` will give you an -> `Any` reference to the **`Arc` itself**, not the underlying trait object, -> so the downcast will fail. You must dereference through the `Arc` first: +These methods work correctly whether `plan` is `&dyn ExecutionPlan` or +`Arc` (Rust auto-derefs through the `Arc`). + +> **Warning:** For the other traits (`ScalarUDFImpl`, `AggregateUDFImpl`, +> `WindowUDFImpl`), no equivalent helper methods exist yet. If you hold +> the trait object behind an `Arc`, be careful with manual casts. +> Writing `(&plan as &dyn Any)` gives you an `Any` reference to the +> **`Arc` itself**, not the underlying trait object, so the downcast will +> fail. You must dereference through the `Arc` first: > > ```rust,ignore > // WRONG – downcasts the Arc, not the inner trait object -> let exec = (&plan as &dyn Any) -> .downcast_ref::(); // always None +> let udf = (&udf as &dyn Any) +> .downcast_ref::(); // always None > -> // CORRECT – downcasts the inner dyn ExecutionPlan -> let exec = (plan.as_ref() as &dyn Any) -> .downcast_ref::(); // works as expected +> // CORRECT – downcasts the inner dyn ScalarUDFImpl +> let udf = (udf.as_ref() as &dyn Any) +> .downcast_ref::(); // works as expected > ``` ### Avro API and timestamp decoding changes From ec73f0264ca06c014286092ce97ab8dd60757f25 Mon Sep 17 00:00:00 2001 From: Tim Saucer Date: Tue, 31 Mar 2026 16:45:56 -0400 Subject: [PATCH 07/14] Add downcast_ref and is methods to dyn ScalarUDFImpl, AggregateUDFImpl, WindowUDFImpl Mirrors the same inherent impl pattern added for dyn ExecutionPlan. Callers can now write `udf.downcast_ref::()` instead of `(udf.as_ref() as &dyn Any).downcast_ref::()`, 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 --- datafusion/expr/src/udaf.rs | 19 +++++++++++ datafusion/expr/src/udf.rs | 19 +++++++++++ datafusion/expr/src/udwf.rs | 19 +++++++++++ .../library-user-guide/upgrading/54.0.0.md | 34 +++++++------------ 4 files changed, 69 insertions(+), 22 deletions(-) diff --git a/datafusion/expr/src/udaf.rs b/datafusion/expr/src/udaf.rs index 4659470656878..54957c273abcc 100644 --- a/datafusion/expr/src/udaf.rs +++ b/datafusion/expr/src/udaf.rs @@ -907,6 +907,25 @@ pub trait AggregateUDFImpl: Debug + DynEq + DynHash + Send + Sync + Any { } } +impl dyn AggregateUDFImpl { + /// Returns `true` if the implementation is of type `T`. + /// + /// Works correctly when called on `Arc` via auto-deref. + pub fn is(&self) -> bool { + (self as &dyn Any).is::() + } + + /// Attempts to downcast to a concrete type `T`, returning `None` if the + /// implementation is not of that type. + /// + /// Works correctly when called on `Arc` via auto-deref, + /// unlike `(&arc as &dyn Any).downcast_ref::()` which would attempt to + /// downcast the `Arc` itself. + pub fn downcast_ref(&self) -> Option<&T> { + (self as &dyn Any).downcast_ref() + } +} + impl PartialEq for dyn AggregateUDFImpl { fn eq(&self, other: &Self) -> bool { self.dyn_eq(other) diff --git a/datafusion/expr/src/udf.rs b/datafusion/expr/src/udf.rs index 418dc9660614a..fd56f40ccbfdf 100644 --- a/datafusion/expr/src/udf.rs +++ b/datafusion/expr/src/udf.rs @@ -984,6 +984,25 @@ pub trait ScalarUDFImpl: Debug + DynEq + DynHash + Send + Sync + Any { } } +impl dyn ScalarUDFImpl { + /// Returns `true` if the implementation is of type `T`. + /// + /// Works correctly when called on `Arc` via auto-deref. + pub fn is(&self) -> bool { + (self as &dyn Any).is::() + } + + /// Attempts to downcast to a concrete type `T`, returning `None` if the + /// implementation is not of that type. + /// + /// Works correctly when called on `Arc` via auto-deref, + /// unlike `(&arc as &dyn Any).downcast_ref::()` which would attempt to + /// downcast the `Arc` itself. + pub fn downcast_ref(&self) -> Option<&T> { + (self as &dyn Any).downcast_ref() + } +} + /// ScalarUDF that adds an alias to the underlying function. It is better to /// implement [`ScalarUDFImpl`], which supports aliases, directly if possible. #[derive(Debug, PartialEq, Eq, Hash)] diff --git a/datafusion/expr/src/udwf.rs b/datafusion/expr/src/udwf.rs index d2f7f13348046..5a5daca28a918 100644 --- a/datafusion/expr/src/udwf.rs +++ b/datafusion/expr/src/udwf.rs @@ -431,6 +431,25 @@ pub trait WindowUDFImpl: Debug + DynEq + DynHash + Send + Sync + Any { } } +impl dyn WindowUDFImpl { + /// Returns `true` if the implementation is of type `T`. + /// + /// Works correctly when called on `Arc` via auto-deref. + pub fn is(&self) -> bool { + (self as &dyn Any).is::() + } + + /// Attempts to downcast to a concrete type `T`, returning `None` if the + /// implementation is not of that type. + /// + /// Works correctly when called on `Arc` via auto-deref, + /// unlike `(&arc as &dyn Any).downcast_ref::()` which would attempt to + /// downcast the `Arc` itself. + pub fn downcast_ref(&self) -> Option<&T> { + (self as &dyn Any).downcast_ref() + } +} + /// the effect this function will have on the limit pushdown pub enum LimitEffect { /// Does not affect the limit (i.e. this is causal) diff --git a/docs/source/library-user-guide/upgrading/54.0.0.md b/docs/source/library-user-guide/upgrading/54.0.0.md index a9eb02645cf44..7fb1c4ece79ef 100644 --- a/docs/source/library-user-guide/upgrading/54.0.0.md +++ b/docs/source/library-user-guide/upgrading/54.0.0.md @@ -196,43 +196,33 @@ the `as_any` function. The below diffs are examples from the associated PRs. } ``` -If you have code that is downcasting an `ExecutionPlan`, you can use the -new `downcast_ref` and `is` methods defined directly on `dyn ExecutionPlan`: +If you have code that is downcasting, you can use the new `downcast_ref` +and `is` methods defined directly on each trait object: **Before:** ```rust,ignore -let exec = plan - .as_any() - .downcast_ref::() - .unwrap(); +let exec = plan.as_any().downcast_ref::().unwrap(); +let udf = scalar_udf.as_any().downcast_ref::().unwrap(); ``` **After:** ```rust,ignore let exec = plan.downcast_ref::().unwrap(); +let udf = scalar_udf.downcast_ref::().unwrap(); ``` -These methods work correctly whether `plan` is `&dyn ExecutionPlan` or -`Arc` (Rust auto-derefs through the `Arc`). +These methods are available on `dyn ExecutionPlan`, `dyn ScalarUDFImpl`, +`dyn AggregateUDFImpl`, and `dyn WindowUDFImpl`. They work correctly +whether the value is a bare reference or behind an `Arc` (Rust +auto-derefs through the `Arc`). -> **Warning:** For the other traits (`ScalarUDFImpl`, `AggregateUDFImpl`, -> `WindowUDFImpl`), no equivalent helper methods exist yet. If you hold -> the trait object behind an `Arc`, be careful with manual casts. +> **Warning:** Do not cast an `Arc` directly to `&dyn Any`. > Writing `(&plan as &dyn Any)` gives you an `Any` reference to the > **`Arc` itself**, not the underlying trait object, so the downcast will -> fail. You must dereference through the `Arc` first: -> -> ```rust,ignore -> // WRONG – downcasts the Arc, not the inner trait object -> let udf = (&udf as &dyn Any) -> .downcast_ref::(); // always None -> -> // CORRECT – downcasts the inner dyn ScalarUDFImpl -> let udf = (udf.as_ref() as &dyn Any) -> .downcast_ref::(); // works as expected -> ``` +> always return `None`. Use the `downcast_ref` method above instead, or +> dereference through the `Arc` first with `plan.as_ref() as &dyn Any`. ### Avro API and timestamp decoding changes From 0a2881f14ef81e24e367f50305ed8982286d86cb Mon Sep 17 00:00:00 2001 From: Tim Saucer Date: Tue, 31 Mar 2026 19:43:11 -0400 Subject: [PATCH 08/14] Use ExecutionPlan::downcast_ref and is() instead of as &dyn Any patterns Replaces all `(plan.as_ref() as &dyn Any).downcast_ref::()` and `(plan as &dyn Any).is::()` call sites with the new inherent methods `plan.downcast_ref::()` and `plan.is::()` added to `dyn ExecutionPlan`. Also removes the now-unused `use std::any::Any` imports. Co-Authored-By: Claude Sonnet 4.6 --- .../adapter_serialization.rs | 7 +- .../examples/data_io/parquet_exec_visitor.rs | 4 +- .../proto/expression_deduplication.rs | 5 +- .../core/src/datasource/listing/table.rs | 3 +- datafusion/core/src/physical_planner.rs | 6 +- .../memory_limit/repartition_mem_limit.rs | 3 +- .../core/tests/parquet/file_statistics.rs | 2 +- datafusion/core/tests/parquet/utils.rs | 4 +- .../aggregate_statistics.rs | 11 +- .../physical_optimizer/join_selection.rs | 53 ++++++--- .../src/aggregate_statistics.rs | 5 +- .../src/combine_partial_final_agg.rs | 8 +- .../src/enforce_distribution.rs | 104 ++++++++---------- .../src/enforce_sorting/mod.rs | 31 +++--- .../src/enforce_sorting/sort_pushdown.rs | 21 ++-- .../src/hash_join_buffering.rs | 3 +- .../physical-optimizer/src/join_selection.rs | 17 +-- .../physical-optimizer/src/limit_pushdown.rs | 4 +- .../src/limit_pushdown_past_window.rs | 21 +--- .../src/limited_distinct_aggregation.rs | 16 +-- .../src/output_requirements.rs | 7 +- .../src/projection_pushdown.rs | 16 ++- .../physical-optimizer/src/pushdown_sort.rs | 4 +- .../physical-optimizer/src/sanity_checker.rs | 3 +- .../src/topk_aggregation.rs | 10 +- .../src/topk_repartition.rs | 7 +- .../src/update_aggr_exprs.rs | 5 +- datafusion/physical-optimizer/src/utils.rs | 17 ++- datafusion/physical-plan/src/projection.rs | 30 +++-- datafusion/physical-plan/src/sorts/sort.rs | 4 +- datafusion/physical-plan/src/union.rs | 8 +- .../src/windows/bounded_window_agg_exec.rs | 4 +- 32 files changed, 187 insertions(+), 256 deletions(-) diff --git a/datafusion-examples/examples/custom_data_source/adapter_serialization.rs b/datafusion-examples/examples/custom_data_source/adapter_serialization.rs index a05801da52e2a..df1dacf23a51c 100644 --- a/datafusion-examples/examples/custom_data_source/adapter_serialization.rs +++ b/datafusion-examples/examples/custom_data_source/adapter_serialization.rs @@ -32,7 +32,6 @@ //! of the `PhysicalExtensionCodec` interception pattern. Both plan and expression //! serialization route through the codec, enabling interception at every node in the tree. -use std::any::Any; use std::fmt::Debug; use std::sync::Arc; @@ -318,7 +317,7 @@ impl PhysicalProtoConverterExtension for AdapterPreservingCodec { extension_codec: &dyn PhysicalExtensionCodec, ) -> Result { // Check if this is a DataSourceExec with adapter - if let Some(exec) = (plan.as_ref() as &dyn Any).downcast_ref::() + if let Some(exec) = plan.downcast_ref::() && let Some(config) = exec.data_source().as_any().downcast_ref::() && let Some(adapter_factory) = &config.expr_adapter_factory @@ -482,7 +481,7 @@ fn inject_adapter_into_plan( plan: Arc, adapter_factory: Arc, ) -> Result> { - if let Some(exec) = (plan.as_ref() as &dyn Any).downcast_ref::() + if let Some(exec) = plan.downcast_ref::() && let Some(config) = exec.data_source().as_any().downcast_ref::() { let new_config = FileScanConfigBuilder::from(config.clone()) @@ -498,7 +497,7 @@ fn inject_adapter_into_plan( fn verify_adapter_in_plan(plan: &Arc, label: &str) -> bool { // Walk the plan tree to find DataSourceExec with adapter fn check_plan(plan: &dyn ExecutionPlan) -> bool { - if let Some(exec) = (plan as &dyn Any).downcast_ref::() + if let Some(exec) = plan.downcast_ref::() && let Some(config) = exec.data_source().as_any().downcast_ref::() && config.expr_adapter_factory.is_some() diff --git a/datafusion-examples/examples/data_io/parquet_exec_visitor.rs b/datafusion-examples/examples/data_io/parquet_exec_visitor.rs index 10c2666dcac7a..d1951b2d9904d 100644 --- a/datafusion-examples/examples/data_io/parquet_exec_visitor.rs +++ b/datafusion-examples/examples/data_io/parquet_exec_visitor.rs @@ -17,7 +17,6 @@ //! See `main.rs` for how to run it. -use std::any::Any; use std::sync::Arc; use datafusion::datasource::file_format::parquet::ParquetFormat; @@ -105,8 +104,7 @@ impl ExecutionPlanVisitor for ParquetExecVisitor { /// or `post_visit` (visit each node after its children/inputs) fn pre_visit(&mut self, plan: &dyn ExecutionPlan) -> Result { // If needed match on a specific `ExecutionPlan` node type - if let Some(data_source_exec) = - (plan as &dyn Any).downcast_ref::() + if let Some(data_source_exec) = plan.downcast_ref::() && let Some((file_config, _)) = data_source_exec.downcast_to_file_source::() { diff --git a/datafusion-examples/examples/proto/expression_deduplication.rs b/datafusion-examples/examples/proto/expression_deduplication.rs index 1b074f8897623..9f3940c82d003 100644 --- a/datafusion-examples/examples/proto/expression_deduplication.rs +++ b/datafusion-examples/examples/proto/expression_deduplication.rs @@ -32,7 +32,6 @@ //! This demonstrates the decorator pattern enabled by the `PhysicalExtensionCodec` trait, //! where all expression serialization/deserialization routes through the codec methods. -use std::any::Any; use std::collections::HashMap; use std::fmt::Debug; use std::sync::{Arc, RwLock}; @@ -125,9 +124,7 @@ pub async fn expression_deduplication() -> Result<()> { // Step 5: check that we deduplicated expressions println!("Step 5: Checking for deduplicated expressions..."); - let Some(filter_exec) = - (deserialized_plan.as_ref() as &dyn Any).downcast_ref::() - else { + let Some(filter_exec) = deserialized_plan.downcast_ref::() else { panic!("Deserialized plan is not a FilterExec"); }; let predicate = Arc::clone(filter_exec.predicate()); diff --git a/datafusion/core/src/datasource/listing/table.rs b/datafusion/core/src/datasource/listing/table.rs index 681451f8d14a1..d14ec1f56dce2 100644 --- a/datafusion/core/src/datasource/listing/table.rs +++ b/datafusion/core/src/datasource/listing/table.rs @@ -107,7 +107,6 @@ impl ListingTableConfigExt for ListingTableConfig { #[cfg(test)] mod tests { - use std::any::Any; #[cfg(feature = "parquet")] use crate::datasource::file_format::parquet::ParquetFormat; @@ -406,7 +405,7 @@ mod tests { .await .expect("Empty execution plan"); - assert!((scan.as_ref() as &dyn Any).is::()); + assert!(scan.is::()); assert_eq!( columns(&scan.schema()), vec!["a".to_owned(), "p1".to_owned()] diff --git a/datafusion/core/src/physical_planner.rs b/datafusion/core/src/physical_planner.rs index 68fcc9a03a661..b2db3cf91eb22 100644 --- a/datafusion/core/src/physical_planner.rs +++ b/datafusion/core/src/physical_planner.rs @@ -3786,7 +3786,7 @@ mod tests { .unwrap(); let plan = plan(&logical_plan).await.unwrap(); - if let Some(plan) = (plan.as_ref() as &dyn Any).downcast_ref::() { + if let Some(plan) = plan.downcast_ref::() { let stringified_plans = plan.stringified_plans(); assert!(stringified_plans.len() >= 4); assert!( @@ -3854,7 +3854,7 @@ mod tests { .handle_explain(&explain, &ctx.state()) .await .unwrap(); - if let Some(plan) = (plan.as_ref() as &dyn Any).downcast_ref::() { + if let Some(plan) = plan.downcast_ref::() { let stringified_plans = plan.stringified_plans(); assert_eq!(stringified_plans.len(), 1); assert_eq!(stringified_plans[0].plan.as_str(), "Test Err"); @@ -4743,6 +4743,6 @@ digraph { .unwrap(); assert_eq!(plan.schema(), schema); - assert!((plan.as_ref() as &dyn Any).is::()); + assert!(plan.is::()); } } diff --git a/datafusion/core/tests/memory_limit/repartition_mem_limit.rs b/datafusion/core/tests/memory_limit/repartition_mem_limit.rs index efff9527bda6a..27bcd33926e99 100644 --- a/datafusion/core/tests/memory_limit/repartition_mem_limit.rs +++ b/datafusion/core/tests/memory_limit/repartition_mem_limit.rs @@ -15,7 +15,6 @@ // specific language governing permissions and limitations // under the License. -use std::any::Any; use std::sync::Arc; use arrow::array::{ArrayRef, Int32Array, RecordBatch}; @@ -75,7 +74,7 @@ async fn test_repartition_memory_limit() { let mut metrics = None; Arc::clone(&plan) .transform_down(|node| { - if (node.as_ref() as &dyn Any).is::() { + if node.is::() { metrics = node.metrics(); } Ok(Transformed::no(node)) diff --git a/datafusion/core/tests/parquet/file_statistics.rs b/datafusion/core/tests/parquet/file_statistics.rs index e9167bfaba879..0d68c9f935054 100644 --- a/datafusion/core/tests/parquet/file_statistics.rs +++ b/datafusion/core/tests/parquet/file_statistics.rs @@ -89,7 +89,7 @@ async fn check_stats_precision_with_filter_pushdown() { .unwrap(); assert!( - (optimized_exec.as_ref() as &dyn Any).is::(), + optimized_exec.is::(), "Sanity check that the pushdown did what we expected" ); // Scan with filter pushdown, stats are inexact diff --git a/datafusion/core/tests/parquet/utils.rs b/datafusion/core/tests/parquet/utils.rs index a18882a324edb..77bc808f1ea08 100644 --- a/datafusion/core/tests/parquet/utils.rs +++ b/datafusion/core/tests/parquet/utils.rs @@ -21,7 +21,6 @@ use datafusion::datasource::physical_plan::ParquetSource; use datafusion::datasource::source::DataSourceExec; use datafusion_physical_plan::metrics::MetricsSet; use datafusion_physical_plan::{ExecutionPlan, ExecutionPlanVisitor, accept}; -use std::any::Any; /// Find the metrics from the first DataSourceExec encountered in the plan #[derive(Debug)] @@ -48,8 +47,7 @@ impl MetricsFinder { impl ExecutionPlanVisitor for MetricsFinder { type Error = std::convert::Infallible; fn pre_visit(&mut self, plan: &dyn ExecutionPlan) -> Result { - if let Some(data_source_exec) = - (plan as &dyn Any).downcast_ref::() + if let Some(data_source_exec) = plan.downcast_ref::() && data_source_exec .downcast_to_file_source::() .is_some() diff --git a/datafusion/core/tests/physical_optimizer/aggregate_statistics.rs b/datafusion/core/tests/physical_optimizer/aggregate_statistics.rs index 168bf8a13bddc..808e163b08369 100644 --- a/datafusion/core/tests/physical_optimizer/aggregate_statistics.rs +++ b/datafusion/core/tests/physical_optimizer/aggregate_statistics.rs @@ -15,7 +15,6 @@ // specific language governing permissions and limitations // under the License. -use std::any::Any; use std::sync::Arc; use crate::physical_optimizer::test_utils::TestAggregate; @@ -84,7 +83,7 @@ async fn assert_count_optim_success( let optimized = AggregateStatistics::new().optimize(Arc::clone(&plan), &config)?; // A ProjectionExec is a sign that the count optimization was applied - assert!((optimized.as_ref() as &dyn Any).is::()); + assert!(optimized.is::()); // run both the optimized and nonoptimized plan let optimized_result = @@ -281,7 +280,7 @@ async fn test_count_inexact_stat() -> Result<()> { let optimized = AggregateStatistics::new().optimize(Arc::new(final_agg), &conf)?; // check that the original ExecutionPlan was not replaced - assert!((optimized.as_ref() as &dyn Any).is::()); + assert!(optimized.is::()); Ok(()) } @@ -325,7 +324,7 @@ async fn test_count_with_nulls_inexact_stat() -> Result<()> { let optimized = AggregateStatistics::new().optimize(Arc::new(final_agg), &conf)?; // check that the original ExecutionPlan was not replaced - assert!((optimized.as_ref() as &dyn Any).is::()); + assert!(optimized.is::()); Ok(()) } @@ -527,7 +526,7 @@ async fn test_count_distinct_optimization() -> Result<()> { if case.expect_optimized { assert!( - (optimized.as_ref() as &dyn Any).is::(), + optimized.is::(), "'{}': expected ProjectionExec", case.name ); @@ -545,7 +544,7 @@ async fn test_count_distinct_optimization() -> Result<()> { } } else { assert!( - (optimized.as_ref() as &dyn Any).is::(), + optimized.is::(), "'{}': expected AggregateExec (not optimized)", case.name ); diff --git a/datafusion/core/tests/physical_optimizer/join_selection.rs b/datafusion/core/tests/physical_optimizer/join_selection.rs index 5ea7859218d21..1327a2ac960ca 100644 --- a/datafusion/core/tests/physical_optimizer/join_selection.rs +++ b/datafusion/core/tests/physical_optimizer/join_selection.rs @@ -18,7 +18,6 @@ use insta::assert_snapshot; use std::sync::Arc; use std::{ - any::Any, pin::Pin, task::{Context, Poll}, }; @@ -232,7 +231,8 @@ async fn test_join_with_swap() { .optimize(join, &ConfigOptions::new()) .unwrap(); - let swapping_projection = (optimized_join.as_ref() as &dyn Any) + let swapping_projection = optimized_join + .as_ref() .downcast_ref::() .expect("A proj is required to swap columns back to their original order"); @@ -244,7 +244,9 @@ async fn test_join_with_swap() { assert_eq!(proj_expr.alias, "small_col"); assert_col_expr(&proj_expr.expr, "small_col", 0); - let swapped_join = (swapping_projection.input().as_ref() as &dyn Any) + let swapped_join = swapping_projection + .input() + .as_ref() .downcast_ref::() .expect("The type of the plan should not be changed"); @@ -292,7 +294,8 @@ async fn test_left_join_no_swap() { .optimize(join, &ConfigOptions::new()) .unwrap(); - let swapped_join = (optimized_join.as_ref() as &dyn Any) + let swapped_join = optimized_join + .as_ref() .downcast_ref::() .expect("The type of the plan should not be changed"); @@ -342,7 +345,8 @@ async fn test_join_with_swap_semi() { .optimize(Arc::new(join), &ConfigOptions::new()) .unwrap(); - let swapped_join = (optimized_join.as_ref() as &dyn Any) + let swapped_join = optimized_join + .as_ref() .downcast_ref::() .expect( "A proj is not required to swap columns back to their original order", @@ -397,7 +401,8 @@ async fn test_join_with_swap_mark() { .optimize(Arc::new(join), &ConfigOptions::new()) .unwrap(); - let swapped_join = (optimized_join.as_ref() as &dyn Any) + let swapped_join = optimized_join + .as_ref() .downcast_ref::() .expect( "A proj is not required to swap columns back to their original order", @@ -528,7 +533,8 @@ async fn test_join_no_swap() { .optimize(join, &ConfigOptions::new()) .unwrap(); - let swapped_join = (optimized_join.as_ref() as &dyn Any) + let swapped_join = optimized_join + .as_ref() .downcast_ref::() .expect("The type of the plan should not be changed"); @@ -576,7 +582,8 @@ async fn test_nl_join_with_swap(join_type: JoinType) { .optimize(join, &ConfigOptions::new()) .unwrap(); - let swapping_projection = (optimized_join.as_ref() as &dyn Any) + let swapping_projection = optimized_join + .as_ref() .downcast_ref::() .expect("A proj is required to swap columns back to their original order"); @@ -588,7 +595,9 @@ async fn test_nl_join_with_swap(join_type: JoinType) { assert_eq!(proj_expr.alias, "small_col"); assert_col_expr(&proj_expr.expr, "small_col", 0); - let swapped_join = (swapping_projection.input().as_ref() as &dyn Any) + let swapped_join = swapping_projection + .input() + .as_ref() .downcast_ref::() .expect("The type of the plan should not be changed"); @@ -654,7 +663,8 @@ async fn test_nl_join_with_swap_no_proj(join_type: JoinType) { ) .unwrap(); - let swapped_join = (optimized_join.as_ref() as &dyn Any) + let swapped_join = optimized_join + .as_ref() .downcast_ref::() .expect("The type of the plan should not be changed"); @@ -748,7 +758,10 @@ async fn test_hash_join_swap_on_joins_with_projections( let swapped = join .swap_inputs(PartitionMode::Partitioned) .expect("swap_hash_join must support joins with projections"); - let swapped_join = (swapped.as_ref() as &dyn Any).downcast_ref::().expect( + let swapped_join = swapped + .as_ref() + .downcast_ref::() + .expect( "ProjectionExec won't be added above if HashJoinExec contains embedded projection", ); @@ -914,15 +927,19 @@ fn check_join_partition_mode( .unwrap(); if !is_swapped { - let swapped_join = (optimized_join.as_ref() as &dyn Any) + let swapped_join = optimized_join + .as_ref() .downcast_ref::() .expect("The type of the plan should not be changed"); assert_eq!(*swapped_join.partition_mode(), expected_mode); } else { - let swapping_projection = (optimized_join.as_ref() as &dyn Any) + let swapping_projection = optimized_join + .as_ref() .downcast_ref::() .expect("A proj is required to swap columns back to their original order"); - let swapped_join = (swapping_projection.input().as_ref() as &dyn Any) + let swapped_join = swapping_projection + .input() + .as_ref() .downcast_ref::() .expect("The type of the plan should not be changed"); @@ -1572,10 +1589,10 @@ async fn test_join_with_maybe_swap_unbounded_case(t: TestCase) -> Result<()> { JoinSelection::new().optimize(Arc::clone(&join), &ConfigOptions::new())?; // If swap did happen - let projection_added = - (optimized_join_plan.as_ref() as &dyn Any).is::(); + let projection_added = optimized_join_plan.as_ref().is::(); let plan = if projection_added { - let proj = (optimized_join_plan.as_ref() as &dyn Any) + let proj = optimized_join_plan + .as_ref() .downcast_ref::() .expect("A proj is required to swap columns back to their original order"); Arc::::clone(proj.input()) @@ -1589,7 +1606,7 @@ async fn test_join_with_maybe_swap_unbounded_case(t: TestCase) -> Result<()> { join_type, mode, .. - }) = (plan.as_ref() as &dyn Any).downcast_ref::() + }) = plan.as_ref().downcast_ref::() { let left_changed = Arc::ptr_eq(left, &right_exec); let right_changed = Arc::ptr_eq(right, &left_exec); diff --git a/datafusion/physical-optimizer/src/aggregate_statistics.rs b/datafusion/physical-optimizer/src/aggregate_statistics.rs index a5066cd039034..909f6420cb126 100644 --- a/datafusion/physical-optimizer/src/aggregate_statistics.rs +++ b/datafusion/physical-optimizer/src/aggregate_statistics.rs @@ -115,14 +115,13 @@ impl PhysicalOptimizerRule for AggregateStatistics { /// We would have preferred to return a casted ref to AggregateExec but the recursion requires /// the `ExecutionPlan.children()` method that returns an owned reference. fn take_optimizable(node: &dyn ExecutionPlan) -> Option> { - if let Some(final_agg_exec) = (node as &dyn Any).downcast_ref::() + if let Some(final_agg_exec) = node.downcast_ref::() && final_agg_exec.mode().input_mode() == AggregateInputMode::Partial && final_agg_exec.group_expr().is_empty() { let mut child = Arc::clone(final_agg_exec.input()); loop { - if let Some(partial_agg_exec) = - (child.as_ref() as &dyn Any).downcast_ref::() + if let Some(partial_agg_exec) = child.downcast_ref::() && partial_agg_exec.mode().input_mode() == AggregateInputMode::Raw && partial_agg_exec.group_expr().is_empty() && partial_agg_exec.filter_expr().iter().all(|e| e.is_none()) diff --git a/datafusion/physical-optimizer/src/combine_partial_final_agg.rs b/datafusion/physical-optimizer/src/combine_partial_final_agg.rs index cd1266f0eeb99..74e938e75ed64 100644 --- a/datafusion/physical-optimizer/src/combine_partial_final_agg.rs +++ b/datafusion/physical-optimizer/src/combine_partial_final_agg.rs @@ -18,7 +18,6 @@ //! CombinePartialFinalAggregate optimizer rule checks the adjacent Partial and Final AggregateExecs //! and try to combine them if necessary -use std::any::Any; use std::sync::Arc; use datafusion_common::error::Result; @@ -55,9 +54,7 @@ impl PhysicalOptimizerRule for CombinePartialFinalAggregate { ) -> Result> { plan.transform_down(|plan| { // Check if the plan is AggregateExec - let Some(agg_exec) = - (plan.as_ref() as &dyn Any).downcast_ref::() - else { + let Some(agg_exec) = plan.downcast_ref::() else { return Ok(Transformed::no(plan)); }; @@ -69,8 +66,7 @@ impl PhysicalOptimizerRule for CombinePartialFinalAggregate { } // Check if the input is AggregateExec - let Some(input_agg_exec) = - (agg_exec.input().as_ref() as &dyn Any).downcast_ref::() + let Some(input_agg_exec) = agg_exec.input().downcast_ref::() else { return Ok(Transformed::no(plan)); }; diff --git a/datafusion/physical-optimizer/src/enforce_distribution.rs b/datafusion/physical-optimizer/src/enforce_distribution.rs index 63105f9c0fdc5..98d8dd49790a9 100644 --- a/datafusion/physical-optimizer/src/enforce_distribution.rs +++ b/datafusion/physical-optimizer/src/enforce_distribution.rs @@ -295,7 +295,7 @@ pub fn adjust_input_keys_ordering( mode, .. }, - ) = (plan.as_ref() as &dyn Any).downcast_ref::() + ) = plan.as_ref().downcast_ref::() { match mode { PartitionMode::Partitioned => { @@ -340,7 +340,7 @@ pub fn adjust_input_keys_ordering( } } } else if let Some(CrossJoinExec { left, .. }) = - (plan.as_ref() as &dyn Any).downcast_ref::() + plan.as_ref().downcast_ref::() { let left_columns_len = left.schema().fields().len(); // Push down requirements to the right side @@ -356,7 +356,7 @@ pub fn adjust_input_keys_ordering( sort_options, null_equality, .. - }) = (plan.as_ref() as &dyn Any).downcast_ref::() + }) = plan.as_ref().downcast_ref::() { let join_constructor = |new_conditions: ( Vec<(PhysicalExprRef, PhysicalExprRef)>, @@ -380,9 +380,7 @@ pub fn adjust_input_keys_ordering( &join_constructor, ) .map(Transformed::yes); - } else if let Some(aggregate_exec) = - (plan.as_ref() as &dyn Any).downcast_ref::() - { + } else if let Some(aggregate_exec) = plan.as_ref().downcast_ref::() { if !requirements.data.is_empty() { if aggregate_exec.mode() == &AggregateMode::FinalPartitioned { return reorder_aggregate_keys(requirements, aggregate_exec) @@ -394,9 +392,7 @@ pub fn adjust_input_keys_ordering( // Keep everything unchanged return Ok(Transformed::no(requirements)); } - } else if let Some(proj) = - (plan.as_ref() as &dyn Any).downcast_ref::() - { + } else if let Some(proj) = plan.as_ref().downcast_ref::() { let expr = proj.expr(); // For Projection, we need to transform the requirements to the columns before the Projection // And then to push down the requirements @@ -412,15 +408,9 @@ pub fn adjust_input_keys_ordering( // Can not satisfy, clear the current requirements and generate new empty requirements requirements.data.clear(); } - } else if (plan.as_ref() as &dyn Any) - .downcast_ref::() - .is_some() - || (plan.as_ref() as &dyn Any) - .downcast_ref::() - .is_some() - || (plan.as_ref() as &dyn Any) - .downcast_ref::() - .is_some() + } else if plan.as_ref().is::() + || plan.as_ref().is::() + || plan.as_ref().is::() { requirements.data.clear(); } else { @@ -492,8 +482,7 @@ pub fn reorder_aggregate_keys( && agg_exec.group_expr().null_expr().is_empty() && !physical_exprs_equal(&output_exprs, parent_required) && let Some(positions) = expected_expr_positions(&output_exprs, parent_required) - && let Some(agg_exec) = - (agg_exec.input().as_ref() as &dyn Any).downcast_ref::() + && let Some(agg_exec) = agg_exec.input().as_ref().downcast_ref::() && *agg_exec.mode() == AggregateMode::Partial { let group_exprs = agg_exec.group_expr().expr(); @@ -609,7 +598,6 @@ fn shift_right_required( pub fn reorder_join_keys_to_inputs( plan: Arc, ) -> Result> { - let plan_any = plan.as_ref() as &dyn Any; if let Some( exec @ HashJoinExec { left, @@ -618,7 +606,7 @@ pub fn reorder_join_keys_to_inputs( mode, .. }, - ) = plan_any.downcast_ref::() + ) = plan.as_ref().downcast_ref::() { if *mode == PartitionMode::Partitioned { let (join_keys, positions) = reorder_current_join_keys( @@ -650,7 +638,7 @@ pub fn reorder_join_keys_to_inputs( sort_options, null_equality, .. - }) = plan_any.downcast_ref::() + }) = plan.as_ref().downcast_ref::() { let (join_keys, positions) = reorder_current_join_keys( extract_join_keys(on), @@ -1078,7 +1066,7 @@ pub fn replace_order_preserving_variants( ); return Ok(context); } else if let Some(repartition) = - (context.plan.as_ref() as &dyn Any).downcast_ref::() + context.plan.as_ref().downcast_ref::() && repartition.preserve_order() { context.plan = Arc::new(RepartitionExec::try_new( @@ -1237,7 +1225,7 @@ pub fn ensure_distribution( children, } = remove_dist_changing_operators(dist_context)?; - if let Some(exec) = (plan.as_ref() as &dyn Any).downcast_ref::() { + if let Some(exec) = plan.as_ref().downcast_ref::() { if let Some(updated_window) = get_best_fitting_window( exec.window_expr(), exec.input(), @@ -1245,8 +1233,7 @@ pub fn ensure_distribution( )? { plan = updated_window; } - } else if let Some(exec) = - (plan.as_ref() as &dyn Any).downcast_ref::() + } else if let Some(exec) = plan.as_ref().downcast_ref::() && let Some(updated_window) = get_best_fitting_window( exec.window_expr(), exec.input(), @@ -1285,10 +1272,11 @@ pub fn ensure_distribution( // // CollectLeft/CollectRight modes are safe because one side is collected // to a single partition which eliminates partition-to-partition mapping. - let is_partitioned_join = (plan.as_ref() as &dyn Any) + let is_partitioned_join = plan + .as_ref() .downcast_ref::() .is_some_and(|join| join.mode == PartitionMode::Partitioned) - || (plan.as_ref() as &dyn Any).is::(); + || plan.as_ref().is::(); let repartition_status_flags = get_repartition_requirement_status(&plan, batch_size, should_use_estimates)?; @@ -1417,7 +1405,7 @@ pub fn ensure_distribution( child = add_sort_above_with_check( child, sort_req, - (plan.as_ref() as &dyn Any) + plan.as_ref() .downcast_ref::() .map(|output| output.fetch()) .unwrap_or(None), @@ -1446,7 +1434,7 @@ pub fn ensure_distribution( } Distribution::UnspecifiedDistribution => { // Since ordering is lost, trying to preserve ordering is pointless - if !maintains || (plan.as_ref() as &dyn Any).is::() { + if !maintains || plan.as_ref().is::() { child = replace_order_preserving_variants(child)?; } } @@ -1462,7 +1450,7 @@ pub fn ensure_distribution( .map(|c| Arc::clone(&c.plan)) .collect::>(); - plan = if (plan.as_ref() as &dyn Any).is::() + plan = if plan.as_ref().is::() && !config.optimizer.prefer_existing_union && can_interleave(children_plans.iter()) { @@ -1507,31 +1495,33 @@ pub type DistributionContext = PlanContext; fn update_children(mut dist_context: DistributionContext) -> Result { for child_context in dist_context.children.iter_mut() { - let child_plan_any = child_context.plan.as_ref() as &dyn Any; - child_context.data = - if let Some(repartition) = child_plan_any.downcast_ref::() { - !matches!( - repartition.partitioning(), - Partitioning::UnknownPartitioning(_) - ) - } else { - child_plan_any.is::() - || child_plan_any.is::() - || child_context.plan.children().is_empty() - || child_context.children[0].data - || child_context - .plan - .required_input_distribution() - .iter() - .zip(child_context.children.iter()) - .any(|(required_dist, child_context)| { - child_context.data - && matches!( - required_dist, - Distribution::UnspecifiedDistribution - ) - }) - } + child_context.data = if let Some(repartition) = child_context + .plan + .as_ref() + .downcast_ref::() + { + !matches!( + repartition.partitioning(), + Partitioning::UnknownPartitioning(_) + ) + } else { + child_context.plan.as_ref().is::() + || child_context.plan.as_ref().is::() + || child_context.plan.children().is_empty() + || child_context.children[0].data + || child_context + .plan + .required_input_distribution() + .iter() + .zip(child_context.children.iter()) + .any(|(required_dist, child_context)| { + child_context.data + && matches!( + required_dist, + Distribution::UnspecifiedDistribution + ) + }) + } } dist_context.data = false; diff --git a/datafusion/physical-optimizer/src/enforce_sorting/mod.rs b/datafusion/physical-optimizer/src/enforce_sorting/mod.rs index 704857f21fc51..99101a9ab4f51 100644 --- a/datafusion/physical-optimizer/src/enforce_sorting/mod.rs +++ b/datafusion/physical-optimizer/src/enforce_sorting/mod.rs @@ -38,7 +38,6 @@ pub mod replace_with_order_preserving_variants; pub mod sort_pushdown; -use std::any::Any; use std::sync::Arc; use crate::PhysicalOptimizerRule; @@ -266,8 +265,7 @@ impl PhysicalOptimizerRule for EnforceSorting { fn replace_with_partial_sort( plan: Arc, ) -> Result> { - let plan_any = plan.as_ref() as &dyn Any; - let Some(sort_plan) = plan_any.downcast_ref::() else { + let Some(sort_plan) = plan.as_ref().downcast_ref::() else { return Ok(plan); }; @@ -510,7 +508,7 @@ pub fn ensure_sorting( child = add_sort_above( child, req, - (plan.as_ref() as &dyn Any) + plan.as_ref() .downcast_ref::() .map(|output| output.fetch()) .unwrap_or(None), @@ -556,8 +554,7 @@ pub fn ensure_sorting( fn analyze_immediate_sort_removal( mut node: PlanWithCorrespondingSort, ) -> Result> { - let Some(sort_exec) = (node.plan.as_ref() as &dyn Any).downcast_ref::() - else { + let Some(sort_exec) = node.plan.as_ref().downcast_ref::() else { return Ok(Transformed::no(node)); }; let sort_input = sort_exec.input(); @@ -622,15 +619,18 @@ fn adjust_window_sort_removal( )?; window_tree.children.push(child_node); - let plan = window_tree.plan.as_ref() as &dyn Any; let child_plan = &window_tree.children[0].plan; let (window_expr, new_window) = - if let Some(exec) = plan.downcast_ref::() { + if let Some(exec) = window_tree.plan.as_ref().downcast_ref::() { let window_expr = exec.window_expr(); let new_window = get_best_fitting_window(window_expr, child_plan, &exec.partition_keys())?; (window_expr, new_window) - } else if let Some(exec) = plan.downcast_ref::() { + } else if let Some(exec) = window_tree + .plan + .as_ref() + .downcast_ref::() + { let window_expr = exec.window_expr(); let new_window = get_best_fitting_window(window_expr, child_plan, &exec.partition_keys())?; @@ -708,9 +708,7 @@ fn remove_bottleneck_in_subplan( .collect::>()?; } let mut new_reqs = requirements.update_plan_from_children()?; - if let Some(repartition) = - (new_reqs.plan.as_ref() as &dyn Any).downcast_ref::() - { + if let Some(repartition) = new_reqs.plan.as_ref().downcast_ref::() { let input_partitioning = repartition.input().output_partitioning(); // We can remove this repartitioning operator if it is now a no-op: let mut can_remove = input_partitioning.eq(repartition.partitioning()); @@ -748,7 +746,7 @@ fn remove_corresponding_sort_from_sub_plan( requires_single_partition: bool, ) -> Result { // A `SortExec` is always at the bottom of the tree. - if let Some(sort_exec) = (node.plan.as_ref() as &dyn Any).downcast_ref::() { + if let Some(sort_exec) = node.plan.as_ref().downcast_ref::() { // Do not remove sorts with fetch: if sort_exec.fetch().is_none() { node = node.children.swap_remove(0); @@ -782,7 +780,7 @@ fn remove_corresponding_sort_from_sub_plan( node.children = node.children.swap_remove(0).children; node.plan = Arc::clone(node.plan.children().swap_remove(0)); } else if let Some(repartition) = - (node.plan.as_ref() as &dyn Any).downcast_ref::() + node.plan.as_ref().downcast_ref::() { node.plan = Arc::new(RepartitionExec::try_new( Arc::clone(&node.children[0].plan), @@ -815,10 +813,9 @@ fn remove_corresponding_sort_from_sub_plan( fn get_sort_exprs( sort_any: &Arc, ) -> Result<(&LexOrdering, Option)> { - if let Some(sort_exec) = (sort_any.as_ref() as &dyn Any).downcast_ref::() { + if let Some(sort_exec) = sort_any.as_ref().downcast_ref::() { Ok((sort_exec.expr(), sort_exec.fetch())) - } else if let Some(spm) = - (sort_any.as_ref() as &dyn Any).downcast_ref::() + } else if let Some(spm) = sort_any.as_ref().downcast_ref::() { Ok((spm.expr(), spm.fetch())) } else { diff --git a/datafusion/physical-optimizer/src/enforce_sorting/sort_pushdown.rs b/datafusion/physical-optimizer/src/enforce_sorting/sort_pushdown.rs index d631b4ffca7f4..c54053926924f 100644 --- a/datafusion/physical-optimizer/src/enforce_sorting/sort_pushdown.rs +++ b/datafusion/physical-optimizer/src/enforce_sorting/sort_pushdown.rs @@ -281,8 +281,7 @@ fn pushdown_requirement_to_children( } RequirementsCompatibility::NonCompatible => Ok(None), } - } else if let Some(sort_exec) = (plan.as_ref() as &dyn Any).downcast_ref::() - { + } else if let Some(sort_exec) = plan.as_ref().downcast_ref::() { let Some(sort_ordering) = sort_exec.properties().output_ordering().cloned() else { return internal_err!("SortExec should have output ordering"); @@ -321,9 +320,7 @@ fn pushdown_requirement_to_children( // `UnionExec` does not have real sort requirements for its input, we // just propagate the sort requirements down: Ok(Some(vec![Some(parent_required); plan.children().len()])) - } else if let Some(smj) = - (plan.as_ref() as &dyn Any).downcast_ref::() - { + } else if let Some(smj) = plan.as_ref().downcast_ref::() { let left_columns_len = smj.left().schema().fields().len(); let parent_ordering: Vec = parent_required .first() @@ -358,16 +355,14 @@ fn pushdown_requirement_to_children( Ok(None) } } - } else if let Some(aggregate_exec) = - (plan.as_ref() as &dyn Any).downcast_ref::() - { + } else if let Some(aggregate_exec) = plan.as_ref().downcast_ref::() { handle_aggregate_pushdown(aggregate_exec, parent_required) } else if maintains_input_order.is_empty() || !maintains_input_order.iter().any(|o| *o) - || (plan.as_ref() as &dyn Any).is::() - || (plan.as_ref() as &dyn Any).is::() + || plan.as_ref().is::() + || plan.as_ref().is::() // TODO: Add support for Projection push down - || (plan.as_ref() as &dyn Any).is::() + || plan.as_ref().is::() || pushdown_would_violate_requirements(&parent_required, plan.as_ref()) { // If the current plan is a leaf node or can not maintain any of the input ordering, can not pushed down requirements. @@ -389,9 +384,7 @@ fn pushdown_requirement_to_children( // ordering requirement invalidates requirement of sort preserving merge exec. Ok(None) } - } else if let Some(hash_join) = - (plan.as_ref() as &dyn Any).downcast_ref::() - { + } else if let Some(hash_join) = plan.as_ref().downcast_ref::() { handle_hash_join(hash_join, parent_required) } else { handle_custom_pushdown(plan, parent_required, &maintains_input_order) diff --git a/datafusion/physical-optimizer/src/hash_join_buffering.rs b/datafusion/physical-optimizer/src/hash_join_buffering.rs index b88273b4dd27c..bddc654ef6b01 100644 --- a/datafusion/physical-optimizer/src/hash_join_buffering.rs +++ b/datafusion/physical-optimizer/src/hash_join_buffering.rs @@ -65,8 +65,7 @@ impl PhysicalOptimizerRule for HashJoinBuffering { } plan.transform_down(|plan| { - let Some(node) = (plan.as_ref() as &dyn Any).downcast_ref::() - else { + let Some(node) = plan.downcast_ref::() else { return Ok(Transformed::no(plan)); }; let plan = Arc::clone(&plan); diff --git a/datafusion/physical-optimizer/src/join_selection.rs b/datafusion/physical-optimizer/src/join_selection.rs index f9a8cf0891ee2..0b8c070c37591 100644 --- a/datafusion/physical-optimizer/src/join_selection.rs +++ b/datafusion/physical-optimizer/src/join_selection.rs @@ -38,7 +38,6 @@ use datafusion_physical_plan::joins::{ StreamJoinPartitionMode, SymmetricHashJoinExec, }; use datafusion_physical_plan::{ExecutionPlan, ExecutionPlanProperties}; -use std::any::Any; use std::sync::Arc; /// The [`JoinSelection`] rule tries to modify a given plan so that it can @@ -287,9 +286,7 @@ fn statistical_join_selection_subrule( plan: Arc, config: &ConfigOptions, ) -> Result>> { - let transformed = if let Some(hash_join) = - (plan.as_ref() as &dyn Any).downcast_ref::() - { + let transformed = if let Some(hash_join) = plan.downcast_ref::() { match hash_join.partition_mode() { PartitionMode::Auto => try_collect_left(hash_join, false, config)? .map_or_else( @@ -317,9 +314,7 @@ fn statistical_join_selection_subrule( } } } - } else if let Some(cross_join) = - (plan.as_ref() as &dyn Any).downcast_ref::() - { + } else if let Some(cross_join) = plan.downcast_ref::() { let left = cross_join.left(); let right = cross_join.right(); if should_swap_join_order(&**left, &**right, config)? { @@ -327,9 +322,7 @@ fn statistical_join_selection_subrule( } else { None } - } else if let Some(nl_join) = - (plan.as_ref() as &dyn Any).downcast_ref::() - { + } else if let Some(nl_join) = plan.downcast_ref::() { let left = nl_join.left(); let right = nl_join.right(); if nl_join.join_type().supports_swap() @@ -375,7 +368,7 @@ fn hash_join_convert_symmetric_subrule( config_options: &ConfigOptions, ) -> Result> { // Check if the current plan node is a HashJoinExec. - if let Some(hash_join) = (input.as_ref() as &dyn Any).downcast_ref::() { + if let Some(hash_join) = input.downcast_ref::() { let left_unbounded = hash_join.left.boundedness().is_unbounded(); let left_incremental = matches!( hash_join.left.pipeline_behavior(), @@ -514,7 +507,7 @@ pub fn hash_join_swap_subrule( mut input: Arc, _config_options: &ConfigOptions, ) -> Result> { - if let Some(hash_join) = (input.as_ref() as &dyn Any).downcast_ref::() + if let Some(hash_join) = input.downcast_ref::() && hash_join.left.boundedness().is_unbounded() && !hash_join.right.boundedness().is_unbounded() && !hash_join.null_aware // Don't swap null-aware anti joins diff --git a/datafusion/physical-optimizer/src/limit_pushdown.rs b/datafusion/physical-optimizer/src/limit_pushdown.rs index fdf0730ecbf14..cfdc3c1e00036 100644 --- a/datafusion/physical-optimizer/src/limit_pushdown.rs +++ b/datafusion/physical-optimizer/src/limit_pushdown.rs @@ -326,9 +326,7 @@ pub(crate) fn pushdown_limits( /// Extracts limit information from the [`ExecutionPlan`] if it is a /// [`GlobalLimitExec`] or a [`LocalLimitExec`]. fn extract_limit(plan: &Arc) -> Option { - if let Some(global_limit) = - (plan.as_ref() as &dyn Any).downcast_ref::() - { + if let Some(global_limit) = plan.downcast_ref::() { Some(LimitInfo { input: Arc::clone(global_limit.input()), fetch: global_limit.fetch(), diff --git a/datafusion/physical-optimizer/src/limit_pushdown_past_window.rs b/datafusion/physical-optimizer/src/limit_pushdown_past_window.rs index 13d4de8e1bbdb..1c2979a09193a 100644 --- a/datafusion/physical-optimizer/src/limit_pushdown_past_window.rs +++ b/datafusion/physical-optimizer/src/limit_pushdown_past_window.rs @@ -31,7 +31,6 @@ use datafusion_physical_plan::sorts::sort::SortExec; use datafusion_physical_plan::sorts::sort_preserving_merge::SortPreservingMergeExec; use datafusion_physical_plan::windows::{BoundedWindowAggExec, WindowUDFExpr}; use datafusion_physical_plan::{ExecutionPlan, ExecutionPlanProperties}; -use std::any::Any; use std::cmp; use std::sync::Arc; @@ -105,9 +104,7 @@ impl PhysicalOptimizerRule for LimitPushPastWindows { } // grow the limit if we hit a window function - if let Some(window) = - (node.as_ref() as &dyn Any).downcast_ref::() - { + if let Some(window) = node.as_ref().downcast_ref::() { phase = Phase::Apply; if !grow_limit(window, &mut ctx) { return reset(node, &mut ctx); @@ -126,9 +123,7 @@ impl PhysicalOptimizerRule for LimitPushPastWindows { if !node.supports_limit_pushdown() { return reset(node, &mut ctx); } - if let Some(part) = - (node.as_ref() as &dyn Any).downcast_ref::() - { + if let Some(part) = node.as_ref().downcast_ref::() { let output = part.partitioning().partition_count(); let input = part.input().output_partitioning().partition_count(); if output < input { @@ -190,9 +185,7 @@ fn apply_limit( node: &Arc, ctx: &mut TraverseState, ) -> Option>> { - if !(node.as_ref() as &dyn Any).is::() - && !(node.as_ref() as &dyn Any).is::() - { + if !node.as_ref().is::() && !node.as_ref().is::() { return None; } let latest = ctx.limit.take(); @@ -209,19 +202,17 @@ fn apply_limit( } fn get_limit(node: &Arc, ctx: &mut TraverseState) -> bool { - if let Some(limit) = (node.as_ref() as &dyn Any).downcast_ref::() { + if let Some(limit) = node.as_ref().downcast_ref::() { ctx.reset_limit(limit.fetch().map(|fetch| fetch + limit.skip())); return true; } // In distributed execution, GlobalLimitExec becomes LocalLimitExec // per partition. Handle it the same way (LocalLimitExec has no skip). - if let Some(limit) = (node.as_ref() as &dyn Any).downcast_ref::() { + if let Some(limit) = node.as_ref().downcast_ref::() { ctx.reset_limit(Some(limit.fetch())); return true; } - if let Some(limit) = - (node.as_ref() as &dyn Any).downcast_ref::() - { + if let Some(limit) = node.as_ref().downcast_ref::() { ctx.reset_limit(limit.fetch()); return true; } diff --git a/datafusion/physical-optimizer/src/limited_distinct_aggregation.rs b/datafusion/physical-optimizer/src/limited_distinct_aggregation.rs index 2a32e17d6af10..852dc2a2a9434 100644 --- a/datafusion/physical-optimizer/src/limited_distinct_aggregation.rs +++ b/datafusion/physical-optimizer/src/limited_distinct_aggregation.rs @@ -18,7 +18,6 @@ //! A special-case optimizer rule that pushes limit into a grouped aggregation //! which has no aggregate expressions or sorting requirements -use std::any::Any; use std::sync::Arc; use datafusion_physical_plan::aggregates::{AggregateExec, LimitOptions}; @@ -70,14 +69,10 @@ impl LimitedDistinctAggregation { let mut global_skip: usize = 0; let children: Vec>; let mut is_global_limit = false; - if let Some(local_limit) = - (plan.as_ref() as &dyn Any).downcast_ref::() - { + if let Some(local_limit) = plan.downcast_ref::() { limit = local_limit.fetch(); children = local_limit.children().into_iter().cloned().collect(); - } else if let Some(global_limit) = - (plan.as_ref() as &dyn Any).downcast_ref::() - { + } else if let Some(global_limit) = plan.downcast_ref::() { global_fetch = global_limit.fetch(); global_fetch?; global_skip = global_limit.skip(); @@ -108,12 +103,9 @@ impl LimitedDistinctAggregation { if !rewrite_applicable { return Ok(Transformed::no(plan)); } - if let Some(aggr) = - (plan.as_ref() as &dyn Any).downcast_ref::() - { + if let Some(aggr) = plan.downcast_ref::() { if found_match_aggr - && let Some(parent_aggr) = - (match_aggr.as_ref() as &dyn Any).downcast_ref::() + && let Some(parent_aggr) = match_aggr.downcast_ref::() && !parent_aggr.group_expr().eq(aggr.group_expr()) { // a partial and final aggregation with different groupings disqualifies diff --git a/datafusion/physical-optimizer/src/output_requirements.rs b/datafusion/physical-optimizer/src/output_requirements.rs index afaae1bac6e26..6992cd96c7cc2 100644 --- a/datafusion/physical-optimizer/src/output_requirements.rs +++ b/datafusion/physical-optimizer/src/output_requirements.rs @@ -386,8 +386,7 @@ fn require_top_ordering_helper( // Global ordering defines desired ordering in the final result. if children.len() != 1 { Ok((plan, false)) - } else if let Some(sort_exec) = (plan.as_ref() as &dyn Any).downcast_ref::() - { + } else if let Some(sort_exec) = plan.downcast_ref::() { // In case of constant columns, output ordering of the `SortExec` would // be an empty set. Therefore; we check the sort expression field to // assign the requirements. @@ -405,9 +404,7 @@ fn require_top_ordering_helper( )) as _, true, )) - } else if let Some(spm) = - (plan.as_ref() as &dyn Any).downcast_ref::() - { + } else if let Some(spm) = plan.downcast_ref::() { let reqs = OrderingRequirements::from(spm.expr().clone()); let fetch = spm.fetch(); Ok(( diff --git a/datafusion/physical-optimizer/src/projection_pushdown.rs b/datafusion/physical-optimizer/src/projection_pushdown.rs index d8004366063a4..b0095fc6b8c87 100644 --- a/datafusion/physical-optimizer/src/projection_pushdown.rs +++ b/datafusion/physical-optimizer/src/projection_pushdown.rs @@ -65,15 +65,13 @@ impl PhysicalOptimizerRule for ProjectionPushdown { ) -> Result> { let alias_generator = AliasGenerator::new(); let plan = plan - .transform_up(|plan| { - match (plan.as_ref() as &dyn Any).downcast_ref::() { - None => Ok(Transformed::no(plan)), - Some(hash_join) => try_push_down_join_filter( - Arc::clone(&plan), - hash_join, - &alias_generator, - ), - } + .transform_up(|plan| match plan.downcast_ref::() { + None => Ok(Transformed::no(plan)), + Some(hash_join) => try_push_down_join_filter( + Arc::clone(&plan), + hash_join, + &alias_generator, + ), }) .map(|t| t.data)?; diff --git a/datafusion/physical-optimizer/src/pushdown_sort.rs b/datafusion/physical-optimizer/src/pushdown_sort.rs index 7cd6ccc3e7aea..20c53ef31645f 100644 --- a/datafusion/physical-optimizer/src/pushdown_sort.rs +++ b/datafusion/physical-optimizer/src/pushdown_sort.rs @@ -56,7 +56,6 @@ use datafusion_common::tree_node::{Transformed, TransformedResult, TreeNode}; use datafusion_physical_plan::ExecutionPlan; use datafusion_physical_plan::SortOrderPushdownResult; use datafusion_physical_plan::sorts::sort::SortExec; -use std::any::Any; use std::sync::Arc; /// A PhysicalOptimizerRule that attempts to push down sort requirements to data sources. @@ -85,8 +84,7 @@ impl PhysicalOptimizerRule for PushdownSort { // Use transform_down to find and optimize all SortExec nodes (including nested ones) plan.transform_down(|plan: Arc| { // Check if this is a SortExec - let Some(sort_exec) = (plan.as_ref() as &dyn Any).downcast_ref::() - else { + let Some(sort_exec) = plan.downcast_ref::() else { return Ok(Transformed::no(plan)); }; diff --git a/datafusion/physical-optimizer/src/sanity_checker.rs b/datafusion/physical-optimizer/src/sanity_checker.rs index c5b05bb54bea2..40c6245d894d4 100644 --- a/datafusion/physical-optimizer/src/sanity_checker.rs +++ b/datafusion/physical-optimizer/src/sanity_checker.rs @@ -21,7 +21,6 @@ //! infinite input(s). In addition, it will check if all order and //! distribution requirements of a plan are satisfied by its children. -use std::any::Any; use std::sync::Arc; use datafusion_common::Result; @@ -90,7 +89,7 @@ pub fn check_finiteness_requirements( input: &dyn ExecutionPlan, optimizer_options: &OptimizerOptions, ) -> Result<()> { - if let Some(exec) = (input as &dyn Any).downcast_ref::() + if let Some(exec) = input.downcast_ref::() && !(optimizer_options.allow_symmetric_joins_without_pruning || (exec.check_if_order_information_available()? && is_prunable(exec))) { diff --git a/datafusion/physical-optimizer/src/topk_aggregation.rs b/datafusion/physical-optimizer/src/topk_aggregation.rs index bbcff3d5ac4e5..38e25e5a396c3 100644 --- a/datafusion/physical-optimizer/src/topk_aggregation.rs +++ b/datafusion/physical-optimizer/src/topk_aggregation.rs @@ -94,7 +94,7 @@ impl TopKAggregation { } fn transform_sort(plan: &Arc) -> Option> { - let sort = (plan.as_ref() as &dyn Any).downcast_ref::()?; + let sort = plan.downcast_ref::()?; let children = sort.children(); let child = children.into_iter().exactly_one().ok()?; @@ -110,17 +110,13 @@ impl TopKAggregation { if !cardinality_preserved { return Ok(Transformed::no(plan)); } - if let Some(aggr) = - (plan.as_ref() as &dyn Any).downcast_ref::() - { + if let Some(aggr) = plan.downcast_ref::() { // either we run into an Aggregate and transform it match Self::transform_agg(aggr, &cur_col_name, order_desc, limit) { None => cardinality_preserved = false, Some(plan) => return Ok(Transformed::yes(plan)), } - } else if let Some(proj) = - (plan.as_ref() as &dyn Any).downcast_ref::() - { + } else if let Some(proj) = plan.downcast_ref::() { // track renames due to successive projections for proj_expr in proj.expr() { let Some(src_col) = diff --git a/datafusion/physical-optimizer/src/topk_repartition.rs b/datafusion/physical-optimizer/src/topk_repartition.rs index 21f812799541d..10b4b44d85d3e 100644 --- a/datafusion/physical-optimizer/src/topk_repartition.rs +++ b/datafusion/physical-optimizer/src/topk_repartition.rs @@ -83,8 +83,7 @@ impl PhysicalOptimizerRule for TopKRepartition { } plan.transform_down(|node| { // Match SortExec with fetch (TopK) - let Some(sort_exec) = (node.as_ref() as &dyn Any).downcast_ref::() - else { + let Some(sort_exec) = node.downcast_ref::() else { return Ok(Transformed::no(node)); }; let Some(fetch) = sort_exec.fetch() else { @@ -103,9 +102,7 @@ impl PhysicalOptimizerRule for TopKRepartition { // There's a CoalesceBatchesExec between TopK & RepartitionExec // in this case we will need to reconstruct both nodes let cb_input = cb_exec.input(); - let Some(rp) = - (cb_input.as_ref() as &dyn Any).downcast_ref::() - else { + let Some(rp) = cb_input.downcast_ref::() else { return Ok(Transformed::no(node)); }; (Some(Arc::clone(sort_input)), rp) diff --git a/datafusion/physical-optimizer/src/update_aggr_exprs.rs b/datafusion/physical-optimizer/src/update_aggr_exprs.rs index c8e18407c970b..2430918e2c2db 100644 --- a/datafusion/physical-optimizer/src/update_aggr_exprs.rs +++ b/datafusion/physical-optimizer/src/update_aggr_exprs.rs @@ -18,7 +18,6 @@ //! An optimizer rule that checks ordering requirements of aggregate expressions //! and modifies the expressions to work more efficiently if possible. -use std::any::Any; use std::sync::Arc; use datafusion_common::config::ConfigOptions; @@ -79,9 +78,7 @@ impl PhysicalOptimizerRule for OptimizeAggregateOrder { _config: &ConfigOptions, ) -> Result> { plan.transform_up(|plan| { - if let Some(aggr_exec) = - (plan.as_ref() as &dyn Any).downcast_ref::() - { + if let Some(aggr_exec) = plan.downcast_ref::() { // Final stage implementations do not rely on ordering -- those // ordering fields may be pruned out by first stage aggregates. // Hence, necessary information for proper merge is added during diff --git a/datafusion/physical-optimizer/src/utils.rs b/datafusion/physical-optimizer/src/utils.rs index bd6db0c7d8df9..6038469c2c264 100644 --- a/datafusion/physical-optimizer/src/utils.rs +++ b/datafusion/physical-optimizer/src/utils.rs @@ -15,7 +15,6 @@ // specific language governing permissions and limitations // under the License. -use std::any::Any; use std::sync::Arc; use datafusion_common::Result; @@ -80,39 +79,37 @@ pub fn add_sort_above_with_check( /// Checks whether the given operator is a [`SortExec`]. pub fn is_sort(plan: &Arc) -> bool { - (plan.as_ref() as &dyn Any).is::() + plan.as_ref().is::() } /// Checks whether the given operator is a window; /// i.e. either a [`WindowAggExec`] or a [`BoundedWindowAggExec`]. pub fn is_window(plan: &Arc) -> bool { - (plan.as_ref() as &dyn Any).is::() - || (plan.as_ref() as &dyn Any).is::() + plan.as_ref().is::() || plan.as_ref().is::() } /// Checks whether the given operator is a [`UnionExec`]. pub fn is_union(plan: &Arc) -> bool { - (plan.as_ref() as &dyn Any).is::() + plan.as_ref().is::() } /// Checks whether the given operator is a [`SortPreservingMergeExec`]. pub fn is_sort_preserving_merge(plan: &Arc) -> bool { - (plan.as_ref() as &dyn Any).is::() + plan.as_ref().is::() } /// Checks whether the given operator is a [`CoalescePartitionsExec`]. pub fn is_coalesce_partitions(plan: &Arc) -> bool { - (plan.as_ref() as &dyn Any).is::() + plan.as_ref().is::() } /// Checks whether the given operator is a [`RepartitionExec`]. pub fn is_repartition(plan: &Arc) -> bool { - (plan.as_ref() as &dyn Any).is::() + plan.as_ref().is::() } /// Checks whether the given operator is a limit; /// i.e. either a [`LocalLimitExec`] or a [`GlobalLimitExec`]. pub fn is_limit(plan: &Arc) -> bool { - (plan.as_ref() as &dyn Any).is::() - || (plan.as_ref() as &dyn Any).is::() + plan.as_ref().is::() || plan.as_ref().is::() } diff --git a/datafusion/physical-plan/src/projection.rs b/datafusion/physical-plan/src/projection.rs index c19aa2cc83409..990af26e01796 100644 --- a/datafusion/physical-plan/src/projection.rs +++ b/datafusion/physical-plan/src/projection.rs @@ -34,7 +34,6 @@ use crate::filter_pushdown::{ }; use crate::joins::utils::{ColumnIndex, JoinFilter, JoinOn, JoinOnRef}; use crate::{DisplayFormatType, ExecutionPlan, PhysicalExpr, check_if_same_properties}; -use std::any::Any; use std::collections::HashMap; use std::pin::Pin; use std::sync::Arc; @@ -728,21 +727,20 @@ pub fn try_pushdown_through_join( pub fn remove_unnecessary_projections( plan: Arc, ) -> Result>> { - let maybe_modified = if let Some(projection) = - (plan.as_ref() as &dyn Any).downcast_ref::() - { - // If the projection does not cause any change on the input, we can - // safely remove it: - if is_projection_removable(projection) { - return Ok(Transformed::yes(Arc::clone(projection.input()))); - } - // If it does, check if we can push it under its child(ren): - projection - .input() - .try_swapping_with_projection(projection)? - } else { - return Ok(Transformed::no(plan)); - }; + let maybe_modified = + if let Some(projection) = plan.as_ref().downcast_ref::() { + // If the projection does not cause any change on the input, we can + // safely remove it: + if is_projection_removable(projection) { + return Ok(Transformed::yes(Arc::clone(projection.input()))); + } + // If it does, check if we can push it under its child(ren): + projection + .input() + .try_swapping_with_projection(projection)? + } else { + return Ok(Transformed::no(plan)); + }; Ok(maybe_modified.map_or_else(|| Transformed::no(plan), Transformed::yes)) } diff --git a/datafusion/physical-plan/src/sorts/sort.rs b/datafusion/physical-plan/src/sorts/sort.rs index a7744573cf9e5..5114004653cec 100644 --- a/datafusion/physical-plan/src/sorts/sort.rs +++ b/datafusion/physical-plan/src/sorts/sort.rs @@ -19,7 +19,6 @@ //! It will do in-memory sorting if it has enough memory budget //! but spills to disk if needed. -use std::any::Any; use std::fmt; use std::fmt::{Debug, Formatter}; use std::sync::Arc; @@ -1242,7 +1241,8 @@ impl ExecutionPlan for SortExec { fn reset_state(self: Arc) -> Result> { let children = self.children().into_iter().cloned().collect(); let new_sort = self.with_new_children(children)?; - let mut new_sort = (new_sort.as_ref() as &dyn Any) + let mut new_sort = new_sort + .as_ref() .downcast_ref::() .expect("cloned 1 lines above this line, we know the type") .clone(); diff --git a/datafusion/physical-plan/src/union.rs b/datafusion/physical-plan/src/union.rs index 0c4605b067f4c..f67f1fefc5bdb 100644 --- a/datafusion/physical-plan/src/union.rs +++ b/datafusion/physical-plan/src/union.rs @@ -898,8 +898,6 @@ fn stats_union(mut left: Statistics, right: Statistics) -> Statistics { #[cfg(test)] mod tests { - use std::any::Any; - use super::*; use crate::collect; use crate::test::{self, TestMemoryExec}; @@ -1404,7 +1402,8 @@ mod tests { let union_plan = UnionExec::try_new(vec![memory_exec1, memory_exec2])?; // Downcast to verify it's a UnionExec - let union = (union_plan.as_ref() as &dyn Any) + let union = union_plan + .as_ref() .downcast_ref::() .expect("Expected UnionExec"); @@ -1444,7 +1443,8 @@ mod tests { Arc::new(TestMemoryExec::try_new(&[], Arc::clone(&schema), None)?); let union = UnionExec::try_new(vec![input1, input2])?; - let union = (union.as_ref() as &dyn Any) + let union = union + .as_ref() .downcast_ref::() .expect("expected UnionExec for multiple inputs"); diff --git a/datafusion/physical-plan/src/windows/bounded_window_agg_exec.rs b/datafusion/physical-plan/src/windows/bounded_window_agg_exec.rs index 6d82e89d42285..fe01b2d2cbb7a 100644 --- a/datafusion/physical-plan/src/windows/bounded_window_agg_exec.rs +++ b/datafusion/physical-plan/src/windows/bounded_window_agg_exec.rs @@ -1261,7 +1261,6 @@ pub(crate) fn get_last_row_batch(batch: &RecordBatch) -> Result { #[cfg(test)] mod tests { - use std::any::Any; use std::pin::Pin; use std::sync::Arc; use std::task::{Context, Poll}; @@ -1860,7 +1859,8 @@ mod tests { let input: Arc = Arc::new(TestMemoryExec::try_new(&[], Arc::clone(&schema), None)?); let plan = bounded_window_exec_pb_latent_range(input, 1, "hash", "sn")?; - let plan = (plan.as_ref() as &dyn Any) + let plan = plan + .as_ref() .downcast_ref::() .expect("expected BoundedWindowAggExec"); From 5c8a15457b2939f5fc17334920014e05c5b87468 Mon Sep 17 00:00:00 2001 From: Tim Saucer Date: Wed, 1 Apr 2026 07:14:15 -0400 Subject: [PATCH 09/14] Use ExecutionPlan::downcast_ref and is() instead of as &dyn Any patterns Replaces remaining `(x.as_ref() as &dyn Any).downcast_ref::()` and `(x as &dyn std::any::Any).downcast_ref::()` 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::().is_some()` to `.is::()` where applicable. Removes now-unused `std::any::Any` imports. Co-Authored-By: Claude Sonnet 4.6 --- .../proto/composed_extension_codec.rs | 11 +--- datafusion/core/src/physical_planner.rs | 4 +- datafusion/core/src/test_util/parquet.rs | 3 +- .../core/tests/fuzz_cases/aggregate_fuzz.rs | 4 +- .../core/tests/parquet/file_statistics.rs | 13 ++--- .../enforce_distribution.rs | 4 +- .../physical_optimizer/filter_pushdown.rs | 3 +- .../physical_optimizer/join_selection.rs | 34 +++--------- .../physical_optimizer/projection_pushdown.rs | 16 +++--- .../tests/user_defined/insert_operation.rs | 4 +- datafusion/ffi/src/execution_plan.rs | 23 ++------ .../ffi/src/proto/physical_extension_codec.rs | 4 +- datafusion/ffi/tests/ffi_execution_plan.rs | 2 +- .../src/aggregate_statistics.rs | 3 +- .../src/enforce_distribution.rs | 50 ++++++++--------- .../src/enforce_sorting/mod.rs | 53 ++++++++----------- .../src/enforce_sorting/sort_pushdown.rs | 14 ++--- .../src/hash_join_buffering.rs | 11 +--- .../physical-optimizer/src/limit_pushdown.rs | 3 +- .../src/limit_pushdown_past_window.rs | 12 ++--- .../src/output_requirements.rs | 5 +- .../src/topk_repartition.rs | 5 +- datafusion/physical-optimizer/src/utils.rs | 14 ++--- datafusion/physical-plan/src/projection.rs | 27 +++++----- datafusion/physical-plan/src/sorts/sort.rs | 1 - datafusion/physical-plan/src/union.rs | 2 - .../src/windows/bounded_window_agg_exec.rs | 1 - .../tests/cases/roundtrip_physical_plan.rs | 16 +++--- .../substrait/src/physical_plan/producer.rs | 3 +- 29 files changed, 127 insertions(+), 218 deletions(-) diff --git a/datafusion-examples/examples/proto/composed_extension_codec.rs b/datafusion-examples/examples/proto/composed_extension_codec.rs index d0590a44a4913..ae9503dd87b19 100644 --- a/datafusion-examples/examples/proto/composed_extension_codec.rs +++ b/datafusion-examples/examples/proto/composed_extension_codec.rs @@ -32,7 +32,6 @@ //! DeltaScan //! ``` -use std::any::Any; use std::fmt::Debug; use std::sync::Arc; @@ -157,10 +156,7 @@ impl PhysicalExtensionCodec for ParentPhysicalExtensionCodec { } fn try_encode(&self, node: Arc, buf: &mut Vec) -> Result<()> { - if (node.as_ref() as &dyn Any) - .downcast_ref::() - .is_some() - { + if node.is::() { buf.extend_from_slice("ParentExec".as_bytes()); Ok(()) } else { @@ -239,10 +235,7 @@ impl PhysicalExtensionCodec for ChildPhysicalExtensionCodec { } fn try_encode(&self, node: Arc, buf: &mut Vec) -> Result<()> { - if (node.as_ref() as &dyn Any) - .downcast_ref::() - .is_some() - { + if node.is::() { buf.extend_from_slice("ChildExec".as_bytes()); Ok(()) } else { diff --git a/datafusion/core/src/physical_planner.rs b/datafusion/core/src/physical_planner.rs index b2db3cf91eb22..2d51a92cf1aeb 100644 --- a/datafusion/core/src/physical_planner.rs +++ b/datafusion/core/src/physical_planner.rs @@ -3624,7 +3624,7 @@ mod tests { .build()?; let execution_plan = plan(&logical_plan).await?; - let final_hash_agg = (execution_plan.as_ref() as &dyn Any) + let final_hash_agg = execution_plan .downcast_ref::() .expect("hash aggregate"); assert_eq!( @@ -3651,7 +3651,7 @@ mod tests { .build()?; let execution_plan = plan(&logical_plan).await?; - let final_hash_agg = (execution_plan.as_ref() as &dyn Any) + let final_hash_agg = execution_plan .downcast_ref::() .expect("hash aggregate"); assert_eq!( diff --git a/datafusion/core/src/test_util/parquet.rs b/datafusion/core/src/test_util/parquet.rs index 307d7e6672744..c53495421307b 100644 --- a/datafusion/core/src/test_util/parquet.rs +++ b/datafusion/core/src/test_util/parquet.rs @@ -196,8 +196,7 @@ impl TestParquetFile { /// Recursively searches for DataSourceExec and returns the metrics /// on the first one it finds pub fn parquet_metrics(plan: &Arc) -> Option { - if let Some(data_source_exec) = - (plan.as_ref() as &dyn std::any::Any).downcast_ref::() + if let Some(data_source_exec) = plan.downcast_ref::() && data_source_exec .downcast_to_file_source::() .is_some() diff --git a/datafusion/core/tests/fuzz_cases/aggregate_fuzz.rs b/datafusion/core/tests/fuzz_cases/aggregate_fuzz.rs index ada0628c22ccc..4726e7c4aca5c 100644 --- a/datafusion/core/tests/fuzz_cases/aggregate_fuzz.rs +++ b/datafusion/core/tests/fuzz_cases/aggregate_fuzz.rs @@ -547,9 +547,7 @@ async fn verify_ordered_aggregate(frame: &DataFrame, expected_sort: bool) { type Node = Arc; fn f_down(&mut self, node: &'n Self::Node) -> Result { - if let Some(exec) = - (node.as_ref() as &dyn std::any::Any).downcast_ref::() - { + if let Some(exec) = node.downcast_ref::() { if self.expected_sort { assert!(matches!( exec.input_order_mode(), diff --git a/datafusion/core/tests/parquet/file_statistics.rs b/datafusion/core/tests/parquet/file_statistics.rs index 0d68c9f935054..c96df5c50998f 100644 --- a/datafusion/core/tests/parquet/file_statistics.rs +++ b/datafusion/core/tests/parquet/file_statistics.rs @@ -15,7 +15,6 @@ // specific language governing permissions and limitations // under the License. -use std::any::Any; use std::fs; use std::sync::Arc; @@ -197,9 +196,7 @@ async fn list_files_with_session_level_cache() { //Session 1 first time list files assert_eq!(get_list_file_cache_size(&state1), 0); let exec1 = table1.scan(&state1, None, &[], None).await.unwrap(); - let data_source_exec = (exec1.as_ref() as &dyn Any) - .downcast_ref::() - .unwrap(); + let data_source_exec = exec1.downcast_ref::().unwrap(); let data_source = data_source_exec.data_source(); let parquet1 = data_source .as_any() @@ -215,9 +212,7 @@ async fn list_files_with_session_level_cache() { //check session 1 cache result not show in session 2 assert_eq!(get_list_file_cache_size(&state2), 0); let exec2 = table2.scan(&state2, None, &[], None).await.unwrap(); - let data_source_exec = (exec2.as_ref() as &dyn Any) - .downcast_ref::() - .unwrap(); + let data_source_exec = exec2.downcast_ref::().unwrap(); let data_source = data_source_exec.data_source(); let parquet2 = data_source .as_any() @@ -233,9 +228,7 @@ async fn list_files_with_session_level_cache() { //check session 1 cache result not show in session 2 assert_eq!(get_list_file_cache_size(&state1), 1); let exec3 = table1.scan(&state1, None, &[], None).await.unwrap(); - let data_source_exec = (exec3.as_ref() as &dyn Any) - .downcast_ref::() - .unwrap(); + let data_source_exec = exec3.downcast_ref::().unwrap(); let data_source = data_source_exec.data_source(); let parquet3 = data_source .as_any() diff --git a/datafusion/core/tests/physical_optimizer/enforce_distribution.rs b/datafusion/core/tests/physical_optimizer/enforce_distribution.rs index 6665553cc3978..4aea2a052aeb4 100644 --- a/datafusion/core/tests/physical_optimizer/enforce_distribution.rs +++ b/datafusion/core/tests/physical_optimizer/enforce_distribution.rs @@ -15,7 +15,6 @@ // specific language governing permissions and limitations // under the License. -use std::any::Any; use std::fmt::Debug; use std::ops::Deref; use std::sync::Arc; @@ -3892,7 +3891,8 @@ fn test_replace_order_preserving_variants_with_fetch() -> Result<()> { let result = replace_order_preserving_variants(dist_context)?; // Verify the plan was transformed to CoalescePartitionsExec - (result.plan.as_ref() as &dyn Any) + result + .plan .downcast_ref::() .expect("Expected CoalescePartitionsExec"); diff --git a/datafusion/core/tests/physical_optimizer/filter_pushdown.rs b/datafusion/core/tests/physical_optimizer/filter_pushdown.rs index 4169df723bfaa..8f880e28486c6 100644 --- a/datafusion/core/tests/physical_optimizer/filter_pushdown.rs +++ b/datafusion/core/tests/physical_optimizer/filter_pushdown.rs @@ -15,7 +15,6 @@ // specific language governing permissions and limitations // under the License. -use std::any::Any; use std::sync::{Arc, LazyLock}; use arrow::{ @@ -4324,7 +4323,7 @@ async fn test_hashjoin_dynamic_filter_pushdown_is_used() { .unwrap(); // Get the HashJoinExec to check the dynamic filter - let hash_join = (plan.as_ref() as &dyn Any) + let hash_join = plan .downcast_ref::() .expect("Plan should be HashJoinExec"); diff --git a/datafusion/core/tests/physical_optimizer/join_selection.rs b/datafusion/core/tests/physical_optimizer/join_selection.rs index 1327a2ac960ca..ee22e7b616433 100644 --- a/datafusion/core/tests/physical_optimizer/join_selection.rs +++ b/datafusion/core/tests/physical_optimizer/join_selection.rs @@ -232,7 +232,6 @@ async fn test_join_with_swap() { .unwrap(); let swapping_projection = optimized_join - .as_ref() .downcast_ref::() .expect("A proj is required to swap columns back to their original order"); @@ -246,7 +245,6 @@ async fn test_join_with_swap() { let swapped_join = swapping_projection .input() - .as_ref() .downcast_ref::() .expect("The type of the plan should not be changed"); @@ -295,7 +293,6 @@ async fn test_left_join_no_swap() { .unwrap(); let swapped_join = optimized_join - .as_ref() .downcast_ref::() .expect("The type of the plan should not be changed"); @@ -345,12 +342,9 @@ async fn test_join_with_swap_semi() { .optimize(Arc::new(join), &ConfigOptions::new()) .unwrap(); - let swapped_join = optimized_join - .as_ref() - .downcast_ref::() - .expect( - "A proj is not required to swap columns back to their original order", - ); + let swapped_join = optimized_join.downcast_ref::().expect( + "A proj is not required to swap columns back to their original order", + ); assert_eq!(swapped_join.schema().fields().len(), 1); assert_eq!( @@ -401,12 +395,9 @@ async fn test_join_with_swap_mark() { .optimize(Arc::new(join), &ConfigOptions::new()) .unwrap(); - let swapped_join = optimized_join - .as_ref() - .downcast_ref::() - .expect( - "A proj is not required to swap columns back to their original order", - ); + let swapped_join = optimized_join.downcast_ref::().expect( + "A proj is not required to swap columns back to their original order", + ); assert_eq!(swapped_join.schema().fields().len(), 2); assert_eq!( @@ -534,7 +525,6 @@ async fn test_join_no_swap() { .unwrap(); let swapped_join = optimized_join - .as_ref() .downcast_ref::() .expect("The type of the plan should not be changed"); @@ -583,7 +573,6 @@ async fn test_nl_join_with_swap(join_type: JoinType) { .unwrap(); let swapping_projection = optimized_join - .as_ref() .downcast_ref::() .expect("A proj is required to swap columns back to their original order"); @@ -597,7 +586,6 @@ async fn test_nl_join_with_swap(join_type: JoinType) { let swapped_join = swapping_projection .input() - .as_ref() .downcast_ref::() .expect("The type of the plan should not be changed"); @@ -664,7 +652,6 @@ async fn test_nl_join_with_swap_no_proj(join_type: JoinType) { .unwrap(); let swapped_join = optimized_join - .as_ref() .downcast_ref::() .expect("The type of the plan should not be changed"); @@ -759,7 +746,6 @@ async fn test_hash_join_swap_on_joins_with_projections( .swap_inputs(PartitionMode::Partitioned) .expect("swap_hash_join must support joins with projections"); let swapped_join = swapped - .as_ref() .downcast_ref::() .expect( "ProjectionExec won't be added above if HashJoinExec contains embedded projection", @@ -928,18 +914,15 @@ fn check_join_partition_mode( if !is_swapped { let swapped_join = optimized_join - .as_ref() .downcast_ref::() .expect("The type of the plan should not be changed"); assert_eq!(*swapped_join.partition_mode(), expected_mode); } else { let swapping_projection = optimized_join - .as_ref() .downcast_ref::() .expect("A proj is required to swap columns back to their original order"); let swapped_join = swapping_projection .input() - .as_ref() .downcast_ref::() .expect("The type of the plan should not be changed"); @@ -1589,10 +1572,9 @@ async fn test_join_with_maybe_swap_unbounded_case(t: TestCase) -> Result<()> { JoinSelection::new().optimize(Arc::clone(&join), &ConfigOptions::new())?; // If swap did happen - let projection_added = optimized_join_plan.as_ref().is::(); + let projection_added = optimized_join_plan.is::(); let plan = if projection_added { let proj = optimized_join_plan - .as_ref() .downcast_ref::() .expect("A proj is required to swap columns back to their original order"); Arc::::clone(proj.input()) @@ -1606,7 +1588,7 @@ async fn test_join_with_maybe_swap_unbounded_case(t: TestCase) -> Result<()> { join_type, mode, .. - }) = plan.as_ref().downcast_ref::() + }) = plan.downcast_ref::() { let left_changed = Arc::ptr_eq(left, &right_exec); let right_changed = Arc::ptr_eq(right, &left_exec); diff --git a/datafusion/core/tests/physical_optimizer/projection_pushdown.rs b/datafusion/core/tests/physical_optimizer/projection_pushdown.rs index 6d7922f682432..a85ce8d080d16 100644 --- a/datafusion/core/tests/physical_optimizer/projection_pushdown.rs +++ b/datafusion/core/tests/physical_optimizer/projection_pushdown.rs @@ -15,7 +15,6 @@ // specific language governing permissions and limitations // under the License. -use std::any::Any; use std::sync::Arc; use arrow::compute::SortOptions; @@ -514,7 +513,8 @@ fn test_memory_after_projection() -> Result<()> { ); assert_eq!( - (after_optimize.clone().as_ref() as &dyn Any) + after_optimize + .clone() .downcast_ref::() .unwrap() .data_source() @@ -597,9 +597,7 @@ fn test_streaming_table_after_projection() -> Result<()> { let after_optimize = ProjectionPushdown::new().optimize(projection, &ConfigOptions::new())?; - let result = (after_optimize.as_ref() as &dyn Any) - .downcast_ref::() - .unwrap(); + let result = after_optimize.downcast_ref::().unwrap(); assert_eq!( result.partition_schema(), &Arc::new(Schema::new(vec![ @@ -789,7 +787,7 @@ fn test_output_req_after_projection() -> Result<()> { .into(), ); assert_eq!( - (after_optimize.as_ref() as &dyn Any) + after_optimize .downcast_ref::() .unwrap() .required_input_ordering()[0] @@ -801,7 +799,7 @@ fn test_output_req_after_projection() -> Result<()> { Arc::new(Column::new("new_a", 1)), Arc::new(Column::new("b", 2)), ]; - if let Distribution::HashPartitioned(vec) = (after_optimize.as_ref() as &dyn Any) + if let Distribution::HashPartitioned(vec) = after_optimize .downcast_ref::() .unwrap() .required_input_distribution()[0] @@ -1031,7 +1029,7 @@ fn test_join_after_projection() -> Result<()> { assert_eq!( expected_filter_col_ind, - (after_optimize.as_ref() as &dyn Any) + after_optimize .downcast_ref::() .unwrap() .filter() @@ -1394,7 +1392,7 @@ fn test_repartition_after_projection() -> Result<()> { ); assert_eq!( - (after_optimize.as_ref() as &dyn Any) + after_optimize .downcast_ref::() .unwrap() .partitioning() diff --git a/datafusion/core/tests/user_defined/insert_operation.rs b/datafusion/core/tests/user_defined/insert_operation.rs index c032ce20f9026..2bab79df424b6 100644 --- a/datafusion/core/tests/user_defined/insert_operation.rs +++ b/datafusion/core/tests/user_defined/insert_operation.rs @@ -58,9 +58,7 @@ async fn insert_operation_is_passed_correctly_to_table_provider() { async fn assert_insert_op(ctx: &SessionContext, sql: &str, insert_op: InsertOp) { let df = ctx.sql(sql).await.unwrap(); let plan = df.create_physical_plan().await.unwrap(); - let exec = (plan.as_ref() as &dyn Any) - .downcast_ref::() - .unwrap(); + let exec = plan.downcast_ref::().unwrap(); assert_eq!(exec.op, insert_op); } diff --git a/datafusion/ffi/src/execution_plan.rs b/datafusion/ffi/src/execution_plan.rs index 811bd365787f5..4733a1e9b9c28 100644 --- a/datafusion/ffi/src/execution_plan.rs +++ b/datafusion/ffi/src/execution_plan.rs @@ -214,8 +214,7 @@ fn pass_runtime_to_children( runtime: &Handle, ) -> Result>> { let mut updated_children = false; - let plan_is_foreign = - (plan.as_ref() as &dyn std::any::Any).is::(); + let plan_is_foreign = plan.is::(); let children = plan .children() @@ -234,9 +233,7 @@ fn pass_runtime_to_children( // `ForeignExecutionPlan`. In this case wrap the plan in a `ForeignExecutionPlan` // because when we call `with_new_children` below it will extract the // FFI plan that does contain the runtime. - if plan_is_foreign - && !(child.as_ref() as &dyn std::any::Any).is::() - { + if plan_is_foreign && !child.is::() { updated_children = true; let ffi_child = FFI_ExecutionPlan::new(child, Some(runtime.clone())); let foreign_child = ForeignExecutionPlan::try_from(ffi_child); @@ -258,9 +255,7 @@ impl FFI_ExecutionPlan { pub fn new(mut plan: Arc, runtime: Option) -> Self { // Note to developers: `pass_runtime_to_children` relies on the logic here to // get the underlying FFI plan during calls to `new_with_children`. - if let Some(plan) = - (plan.as_ref() as &dyn std::any::Any).downcast_ref::() - { + if let Some(plan) = plan.downcast_ref::() { return plan.plan.clone(); } @@ -607,19 +602,11 @@ pub mod tests { // Verify local libraries can be downcast to their original let foreign_plan: Arc = (&ffi_plan).try_into().unwrap(); - assert!( - (foreign_plan.as_ref() as &dyn std::any::Any) - .downcast_ref::() - .is_some() - ); + assert!(foreign_plan.is::()); // Verify different library markers generate foreign providers ffi_plan.library_marker_id = crate::mock_foreign_marker_id; let foreign_plan: Arc = (&ffi_plan).try_into().unwrap(); - assert!( - (foreign_plan.as_ref() as &dyn std::any::Any) - .downcast_ref::() - .is_some() - ); + assert!(foreign_plan.is::()); } } diff --git a/datafusion/ffi/src/proto/physical_extension_codec.rs b/datafusion/ffi/src/proto/physical_extension_codec.rs index 8fee9159357ed..b7cd1167f8f9d 100644 --- a/datafusion/ffi/src/proto/physical_extension_codec.rs +++ b/datafusion/ffi/src/proto/physical_extension_codec.rs @@ -466,7 +466,7 @@ pub(crate) mod tests { ) -> Result<()> { buf.push(Self::MAGIC_NUMBER); - let Some(_) = (node.as_ref() as &dyn Any).downcast_ref::() else { + let Some(_) = node.downcast_ref::() else { return exec_err!("TestExtensionCodec only expects EmptyExec"); }; @@ -588,7 +588,7 @@ pub(crate) mod tests { let returned_exec = foreign_codec.try_decode(&bytes, &input_execs, ctx.task_ctx().as_ref())?; - assert!((returned_exec.as_ref() as &dyn Any).is::()); + assert!(returned_exec.is::()); Ok(()) } diff --git a/datafusion/ffi/tests/ffi_execution_plan.rs b/datafusion/ffi/tests/ffi_execution_plan.rs index 8944bfd0c7bdc..4911566f4f90b 100644 --- a/datafusion/ffi/tests/ffi_execution_plan.rs +++ b/datafusion/ffi/tests/ffi_execution_plan.rs @@ -58,7 +58,7 @@ mod tests { let child_plan: Arc = (&child_plan) .try_into() .expect("should be able create plan"); - assert!((child_plan.as_ref() as &dyn std::any::Any).is::()); + assert!(child_plan.is::()); let grandchild_plan = generate_local_plan(); diff --git a/datafusion/physical-optimizer/src/aggregate_statistics.rs b/datafusion/physical-optimizer/src/aggregate_statistics.rs index 909f6420cb126..75da1873263d8 100644 --- a/datafusion/physical-optimizer/src/aggregate_statistics.rs +++ b/datafusion/physical-optimizer/src/aggregate_statistics.rs @@ -25,7 +25,6 @@ use datafusion_physical_plan::placeholder_row::PlaceholderRowExec; use datafusion_physical_plan::projection::{ProjectionExec, ProjectionExpr}; use datafusion_physical_plan::udaf::{AggregateFunctionExpr, StatisticsArgs}; use datafusion_physical_plan::{ExecutionPlan, expressions}; -use std::any::Any; use std::sync::Arc; use crate::PhysicalOptimizerRule; @@ -51,7 +50,7 @@ impl PhysicalOptimizerRule for AggregateStatistics { config: &ConfigOptions, ) -> Result> { if let Some(partial_agg_exec) = take_optimizable(&*plan) { - let partial_agg_exec = (partial_agg_exec.as_ref() as &dyn Any) + let partial_agg_exec = partial_agg_exec .downcast_ref::() .expect("take_optimizable() ensures that this is a AggregateExec"); let stats = partial_agg_exec.input().partition_statistics(None)?; diff --git a/datafusion/physical-optimizer/src/enforce_distribution.rs b/datafusion/physical-optimizer/src/enforce_distribution.rs index 98d8dd49790a9..ea47c12030393 100644 --- a/datafusion/physical-optimizer/src/enforce_distribution.rs +++ b/datafusion/physical-optimizer/src/enforce_distribution.rs @@ -295,7 +295,7 @@ pub fn adjust_input_keys_ordering( mode, .. }, - ) = plan.as_ref().downcast_ref::() + ) = plan.downcast_ref::() { match mode { PartitionMode::Partitioned => { @@ -339,8 +339,7 @@ pub fn adjust_input_keys_ordering( requirements.data.clear(); } } - } else if let Some(CrossJoinExec { left, .. }) = - plan.as_ref().downcast_ref::() + } else if let Some(CrossJoinExec { left, .. }) = plan.downcast_ref::() { let left_columns_len = left.schema().fields().len(); // Push down requirements to the right side @@ -356,7 +355,7 @@ pub fn adjust_input_keys_ordering( sort_options, null_equality, .. - }) = plan.as_ref().downcast_ref::() + }) = plan.downcast_ref::() { let join_constructor = |new_conditions: ( Vec<(PhysicalExprRef, PhysicalExprRef)>, @@ -380,7 +379,7 @@ pub fn adjust_input_keys_ordering( &join_constructor, ) .map(Transformed::yes); - } else if let Some(aggregate_exec) = plan.as_ref().downcast_ref::() { + } else if let Some(aggregate_exec) = plan.downcast_ref::() { if !requirements.data.is_empty() { if aggregate_exec.mode() == &AggregateMode::FinalPartitioned { return reorder_aggregate_keys(requirements, aggregate_exec) @@ -392,7 +391,7 @@ pub fn adjust_input_keys_ordering( // Keep everything unchanged return Ok(Transformed::no(requirements)); } - } else if let Some(proj) = plan.as_ref().downcast_ref::() { + } else if let Some(proj) = plan.downcast_ref::() { let expr = proj.expr(); // For Projection, we need to transform the requirements to the columns before the Projection // And then to push down the requirements @@ -408,9 +407,9 @@ pub fn adjust_input_keys_ordering( // Can not satisfy, clear the current requirements and generate new empty requirements requirements.data.clear(); } - } else if plan.as_ref().is::() - || plan.as_ref().is::() - || plan.as_ref().is::() + } else if plan.is::() + || plan.is::() + || plan.is::() { requirements.data.clear(); } else { @@ -482,7 +481,7 @@ pub fn reorder_aggregate_keys( && agg_exec.group_expr().null_expr().is_empty() && !physical_exprs_equal(&output_exprs, parent_required) && let Some(positions) = expected_expr_positions(&output_exprs, parent_required) - && let Some(agg_exec) = agg_exec.input().as_ref().downcast_ref::() + && let Some(agg_exec) = agg_exec.input().downcast_ref::() && *agg_exec.mode() == AggregateMode::Partial { let group_exprs = agg_exec.group_expr().expr(); @@ -606,7 +605,7 @@ pub fn reorder_join_keys_to_inputs( mode, .. }, - ) = plan.as_ref().downcast_ref::() + ) = plan.downcast_ref::() { if *mode == PartitionMode::Partitioned { let (join_keys, positions) = reorder_current_join_keys( @@ -638,7 +637,7 @@ pub fn reorder_join_keys_to_inputs( sort_options, null_equality, .. - }) = plan.as_ref().downcast_ref::() + }) = plan.downcast_ref::() { let (join_keys, positions) = reorder_current_join_keys( extract_join_keys(on), @@ -1065,8 +1064,7 @@ pub fn replace_order_preserving_variants( CoalescePartitionsExec::new(child_plan).with_fetch(context.plan.fetch()), ); return Ok(context); - } else if let Some(repartition) = - context.plan.as_ref().downcast_ref::() + } else if let Some(repartition) = context.plan.downcast_ref::() && repartition.preserve_order() { context.plan = Arc::new(RepartitionExec::try_new( @@ -1225,7 +1223,7 @@ pub fn ensure_distribution( children, } = remove_dist_changing_operators(dist_context)?; - if let Some(exec) = plan.as_ref().downcast_ref::() { + if let Some(exec) = plan.downcast_ref::() { if let Some(updated_window) = get_best_fitting_window( exec.window_expr(), exec.input(), @@ -1233,7 +1231,7 @@ pub fn ensure_distribution( )? { plan = updated_window; } - } else if let Some(exec) = plan.as_ref().downcast_ref::() + } else if let Some(exec) = plan.downcast_ref::() && let Some(updated_window) = get_best_fitting_window( exec.window_expr(), exec.input(), @@ -1273,10 +1271,9 @@ pub fn ensure_distribution( // CollectLeft/CollectRight modes are safe because one side is collected // to a single partition which eliminates partition-to-partition mapping. let is_partitioned_join = plan - .as_ref() .downcast_ref::() .is_some_and(|join| join.mode == PartitionMode::Partitioned) - || plan.as_ref().is::(); + || plan.is::(); let repartition_status_flags = get_repartition_requirement_status(&plan, batch_size, should_use_estimates)?; @@ -1405,8 +1402,7 @@ pub fn ensure_distribution( child = add_sort_above_with_check( child, sort_req, - plan.as_ref() - .downcast_ref::() + plan.downcast_ref::() .map(|output| output.fetch()) .unwrap_or(None), )?; @@ -1434,7 +1430,7 @@ pub fn ensure_distribution( } Distribution::UnspecifiedDistribution => { // Since ordering is lost, trying to preserve ordering is pointless - if !maintains || plan.as_ref().is::() { + if !maintains || plan.is::() { child = replace_order_preserving_variants(child)?; } } @@ -1450,7 +1446,7 @@ pub fn ensure_distribution( .map(|c| Arc::clone(&c.plan)) .collect::>(); - plan = if plan.as_ref().is::() + plan = if plan.is::() && !config.optimizer.prefer_existing_union && can_interleave(children_plans.iter()) { @@ -1495,18 +1491,16 @@ pub type DistributionContext = PlanContext; fn update_children(mut dist_context: DistributionContext) -> Result { for child_context in dist_context.children.iter_mut() { - child_context.data = if let Some(repartition) = child_context - .plan - .as_ref() - .downcast_ref::() + child_context.data = if let Some(repartition) = + child_context.plan.downcast_ref::() { !matches!( repartition.partitioning(), Partitioning::UnknownPartitioning(_) ) } else { - child_context.plan.as_ref().is::() - || child_context.plan.as_ref().is::() + child_context.plan.is::() + || child_context.plan.is::() || child_context.plan.children().is_empty() || child_context.children[0].data || child_context diff --git a/datafusion/physical-optimizer/src/enforce_sorting/mod.rs b/datafusion/physical-optimizer/src/enforce_sorting/mod.rs index 99101a9ab4f51..729a6b3121a83 100644 --- a/datafusion/physical-optimizer/src/enforce_sorting/mod.rs +++ b/datafusion/physical-optimizer/src/enforce_sorting/mod.rs @@ -265,7 +265,7 @@ impl PhysicalOptimizerRule for EnforceSorting { fn replace_with_partial_sort( plan: Arc, ) -> Result> { - let Some(sort_plan) = plan.as_ref().downcast_ref::() else { + let Some(sort_plan) = plan.downcast_ref::() else { return Ok(plan); }; @@ -508,8 +508,7 @@ pub fn ensure_sorting( child = add_sort_above( child, req, - plan.as_ref() - .downcast_ref::() + plan.downcast_ref::() .map(|output| output.fetch()) .unwrap_or(None), ); @@ -554,7 +553,7 @@ pub fn ensure_sorting( fn analyze_immediate_sort_removal( mut node: PlanWithCorrespondingSort, ) -> Result> { - let Some(sort_exec) = node.plan.as_ref().downcast_ref::() else { + let Some(sort_exec) = node.plan.downcast_ref::() else { return Ok(Transformed::no(node)); }; let sort_input = sort_exec.input(); @@ -620,24 +619,21 @@ fn adjust_window_sort_removal( window_tree.children.push(child_node); let child_plan = &window_tree.children[0].plan; - let (window_expr, new_window) = - if let Some(exec) = window_tree.plan.as_ref().downcast_ref::() { - let window_expr = exec.window_expr(); - let new_window = - get_best_fitting_window(window_expr, child_plan, &exec.partition_keys())?; - (window_expr, new_window) - } else if let Some(exec) = window_tree - .plan - .as_ref() - .downcast_ref::() - { - let window_expr = exec.window_expr(); - let new_window = - get_best_fitting_window(window_expr, child_plan, &exec.partition_keys())?; - (window_expr, new_window) - } else { - return plan_err!("Expected WindowAggExec or BoundedWindowAggExec"); - }; + let (window_expr, new_window) = if let Some(exec) = + window_tree.plan.downcast_ref::() + { + let window_expr = exec.window_expr(); + let new_window = + get_best_fitting_window(window_expr, child_plan, &exec.partition_keys())?; + (window_expr, new_window) + } else if let Some(exec) = window_tree.plan.downcast_ref::() { + let window_expr = exec.window_expr(); + let new_window = + get_best_fitting_window(window_expr, child_plan, &exec.partition_keys())?; + (window_expr, new_window) + } else { + return plan_err!("Expected WindowAggExec or BoundedWindowAggExec"); + }; window_tree.plan = if let Some(new_window) = new_window { // We were able to change the window to accommodate the input, use it: @@ -708,7 +704,7 @@ fn remove_bottleneck_in_subplan( .collect::>()?; } let mut new_reqs = requirements.update_plan_from_children()?; - if let Some(repartition) = new_reqs.plan.as_ref().downcast_ref::() { + if let Some(repartition) = new_reqs.plan.downcast_ref::() { let input_partitioning = repartition.input().output_partitioning(); // We can remove this repartitioning operator if it is now a no-op: let mut can_remove = input_partitioning.eq(repartition.partitioning()); @@ -746,7 +742,7 @@ fn remove_corresponding_sort_from_sub_plan( requires_single_partition: bool, ) -> Result { // A `SortExec` is always at the bottom of the tree. - if let Some(sort_exec) = node.plan.as_ref().downcast_ref::() { + if let Some(sort_exec) = node.plan.downcast_ref::() { // Do not remove sorts with fetch: if sort_exec.fetch().is_none() { node = node.children.swap_remove(0); @@ -779,9 +775,7 @@ fn remove_corresponding_sort_from_sub_plan( if is_sort_preserving_merge(&node.plan) { node.children = node.children.swap_remove(0).children; node.plan = Arc::clone(node.plan.children().swap_remove(0)); - } else if let Some(repartition) = - node.plan.as_ref().downcast_ref::() - { + } else if let Some(repartition) = node.plan.downcast_ref::() { node.plan = Arc::new(RepartitionExec::try_new( Arc::clone(&node.children[0].plan), repartition.properties().output_partitioning().clone(), @@ -813,10 +807,9 @@ fn remove_corresponding_sort_from_sub_plan( fn get_sort_exprs( sort_any: &Arc, ) -> Result<(&LexOrdering, Option)> { - if let Some(sort_exec) = sort_any.as_ref().downcast_ref::() { + if let Some(sort_exec) = sort_any.downcast_ref::() { Ok((sort_exec.expr(), sort_exec.fetch())) - } else if let Some(spm) = sort_any.as_ref().downcast_ref::() - { + } else if let Some(spm) = sort_any.downcast_ref::() { Ok((spm.expr(), spm.fetch())) } else { plan_err!("Given ExecutionPlan is not a SortExec or a SortPreservingMergeExec") diff --git a/datafusion/physical-optimizer/src/enforce_sorting/sort_pushdown.rs b/datafusion/physical-optimizer/src/enforce_sorting/sort_pushdown.rs index c54053926924f..a3e135657433b 100644 --- a/datafusion/physical-optimizer/src/enforce_sorting/sort_pushdown.rs +++ b/datafusion/physical-optimizer/src/enforce_sorting/sort_pushdown.rs @@ -281,7 +281,7 @@ fn pushdown_requirement_to_children( } RequirementsCompatibility::NonCompatible => Ok(None), } - } else if let Some(sort_exec) = plan.as_ref().downcast_ref::() { + } else if let Some(sort_exec) = plan.downcast_ref::() { let Some(sort_ordering) = sort_exec.properties().output_ordering().cloned() else { return internal_err!("SortExec should have output ordering"); @@ -320,7 +320,7 @@ fn pushdown_requirement_to_children( // `UnionExec` does not have real sort requirements for its input, we // just propagate the sort requirements down: Ok(Some(vec![Some(parent_required); plan.children().len()])) - } else if let Some(smj) = plan.as_ref().downcast_ref::() { + } else if let Some(smj) = plan.downcast_ref::() { let left_columns_len = smj.left().schema().fields().len(); let parent_ordering: Vec = parent_required .first() @@ -355,14 +355,14 @@ fn pushdown_requirement_to_children( Ok(None) } } - } else if let Some(aggregate_exec) = plan.as_ref().downcast_ref::() { + } else if let Some(aggregate_exec) = plan.downcast_ref::() { handle_aggregate_pushdown(aggregate_exec, parent_required) } else if maintains_input_order.is_empty() || !maintains_input_order.iter().any(|o| *o) - || plan.as_ref().is::() - || plan.as_ref().is::() + || plan.is::() + || plan.is::() // TODO: Add support for Projection push down - || plan.as_ref().is::() + || plan.is::() || pushdown_would_violate_requirements(&parent_required, plan.as_ref()) { // If the current plan is a leaf node or can not maintain any of the input ordering, can not pushed down requirements. @@ -384,7 +384,7 @@ fn pushdown_requirement_to_children( // ordering requirement invalidates requirement of sort preserving merge exec. Ok(None) } - } else if let Some(hash_join) = plan.as_ref().downcast_ref::() { + } else if let Some(hash_join) = plan.downcast_ref::() { handle_hash_join(hash_join, parent_required) } else { handle_custom_pushdown(plan, parent_required, &maintains_input_order) diff --git a/datafusion/physical-optimizer/src/hash_join_buffering.rs b/datafusion/physical-optimizer/src/hash_join_buffering.rs index bddc654ef6b01..7a198cac13fc9 100644 --- a/datafusion/physical-optimizer/src/hash_join_buffering.rs +++ b/datafusion/physical-optimizer/src/hash_join_buffering.rs @@ -22,7 +22,6 @@ use datafusion_common::tree_node::{Transformed, TransformedResult, TreeNode}; use datafusion_physical_plan::ExecutionPlan; use datafusion_physical_plan::buffer::BufferExec; use datafusion_physical_plan::joins::HashJoinExec; -use std::any::Any; use std::sync::Arc; /// Looks for all the [HashJoinExec]s in the plan and places a [BufferExec] node with the @@ -72,10 +71,7 @@ impl PhysicalOptimizerRule for HashJoinBuffering { Ok(Transformed::yes( if HashJoinExec::probe_side() == JoinSide::Left { // Do not stack BufferExec nodes together. - if (node.left.as_ref() as &dyn Any) - .downcast_ref::() - .is_some() - { + if node.left.is::() { return Ok(Transformed::no(plan)); } plan.with_new_children(vec![ @@ -84,10 +80,7 @@ impl PhysicalOptimizerRule for HashJoinBuffering { ])? } else { // Do not stack BufferExec nodes together. - if (node.right.as_ref() as &dyn Any) - .downcast_ref::() - .is_some() - { + if node.right.is::() { return Ok(Transformed::no(plan)); } plan.with_new_children(vec![ diff --git a/datafusion/physical-optimizer/src/limit_pushdown.rs b/datafusion/physical-optimizer/src/limit_pushdown.rs index cfdc3c1e00036..f14f299440df3 100644 --- a/datafusion/physical-optimizer/src/limit_pushdown.rs +++ b/datafusion/physical-optimizer/src/limit_pushdown.rs @@ -334,8 +334,7 @@ fn extract_limit(plan: &Arc) -> Option { preserve_order: global_limit.required_ordering().is_some(), }) } else { - (plan.as_ref() as &dyn Any) - .downcast_ref::() + plan.downcast_ref::() .map(|local_limit| LimitInfo { input: Arc::clone(local_limit.input()), fetch: Some(local_limit.fetch()), diff --git a/datafusion/physical-optimizer/src/limit_pushdown_past_window.rs b/datafusion/physical-optimizer/src/limit_pushdown_past_window.rs index 1c2979a09193a..092570b051979 100644 --- a/datafusion/physical-optimizer/src/limit_pushdown_past_window.rs +++ b/datafusion/physical-optimizer/src/limit_pushdown_past_window.rs @@ -104,7 +104,7 @@ impl PhysicalOptimizerRule for LimitPushPastWindows { } // grow the limit if we hit a window function - if let Some(window) = node.as_ref().downcast_ref::() { + if let Some(window) = node.downcast_ref::() { phase = Phase::Apply; if !grow_limit(window, &mut ctx) { return reset(node, &mut ctx); @@ -123,7 +123,7 @@ impl PhysicalOptimizerRule for LimitPushPastWindows { if !node.supports_limit_pushdown() { return reset(node, &mut ctx); } - if let Some(part) = node.as_ref().downcast_ref::() { + if let Some(part) = node.downcast_ref::() { let output = part.partitioning().partition_count(); let input = part.input().output_partitioning().partition_count(); if output < input { @@ -185,7 +185,7 @@ fn apply_limit( node: &Arc, ctx: &mut TraverseState, ) -> Option>> { - if !node.as_ref().is::() && !node.as_ref().is::() { + if !node.is::() && !node.is::() { return None; } let latest = ctx.limit.take(); @@ -202,17 +202,17 @@ fn apply_limit( } fn get_limit(node: &Arc, ctx: &mut TraverseState) -> bool { - if let Some(limit) = node.as_ref().downcast_ref::() { + if let Some(limit) = node.downcast_ref::() { ctx.reset_limit(limit.fetch().map(|fetch| fetch + limit.skip())); return true; } // In distributed execution, GlobalLimitExec becomes LocalLimitExec // per partition. Handle it the same way (LocalLimitExec has no skip). - if let Some(limit) = node.as_ref().downcast_ref::() { + if let Some(limit) = node.downcast_ref::() { ctx.reset_limit(Some(limit.fetch())); return true; } - if let Some(limit) = node.as_ref().downcast_ref::() { + if let Some(limit) = node.downcast_ref::() { ctx.reset_limit(limit.fetch()); return true; } diff --git a/datafusion/physical-optimizer/src/output_requirements.rs b/datafusion/physical-optimizer/src/output_requirements.rs index 6992cd96c7cc2..81df6f943c15e 100644 --- a/datafusion/physical-optimizer/src/output_requirements.rs +++ b/datafusion/physical-optimizer/src/output_requirements.rs @@ -22,7 +22,6 @@ //! Since the `OutputRequirementExec` operator is only a helper operator, it //! shouldn't occur in the final plan (i.e. the executed plan). -use std::any::Any; use std::sync::Arc; use crate::PhysicalOptimizerRule; @@ -337,9 +336,7 @@ impl PhysicalOptimizerRule for OutputRequirements { RuleMode::Add => require_top_ordering(plan), RuleMode::Remove => plan .transform_up(|plan| { - if let Some(sort_req) = (plan.as_ref() as &dyn Any) - .downcast_ref::() - { + if let Some(sort_req) = plan.downcast_ref::() { Ok(Transformed::yes(sort_req.input())) } else { Ok(Transformed::no(plan)) diff --git a/datafusion/physical-optimizer/src/topk_repartition.rs b/datafusion/physical-optimizer/src/topk_repartition.rs index 10b4b44d85d3e..93df27b7b16de 100644 --- a/datafusion/physical-optimizer/src/topk_repartition.rs +++ b/datafusion/physical-optimizer/src/topk_repartition.rs @@ -134,10 +134,7 @@ impl PhysicalOptimizerRule for TopKRepartition { // Don't push if the input to the repartition is already bounded // (e.g., another TopK), as it would be redundant. let repart_input = repart_exec.input(); - if (repart_input.as_ref() as &dyn Any) - .downcast_ref::() - .is_some() - { + if repart_input.is::() { return Ok(Transformed::no(node)); } diff --git a/datafusion/physical-optimizer/src/utils.rs b/datafusion/physical-optimizer/src/utils.rs index 6038469c2c264..a6b01637c970e 100644 --- a/datafusion/physical-optimizer/src/utils.rs +++ b/datafusion/physical-optimizer/src/utils.rs @@ -79,37 +79,37 @@ pub fn add_sort_above_with_check( /// Checks whether the given operator is a [`SortExec`]. pub fn is_sort(plan: &Arc) -> bool { - plan.as_ref().is::() + plan.is::() } /// Checks whether the given operator is a window; /// i.e. either a [`WindowAggExec`] or a [`BoundedWindowAggExec`]. pub fn is_window(plan: &Arc) -> bool { - plan.as_ref().is::() || plan.as_ref().is::() + plan.is::() || plan.is::() } /// Checks whether the given operator is a [`UnionExec`]. pub fn is_union(plan: &Arc) -> bool { - plan.as_ref().is::() + plan.is::() } /// Checks whether the given operator is a [`SortPreservingMergeExec`]. pub fn is_sort_preserving_merge(plan: &Arc) -> bool { - plan.as_ref().is::() + plan.is::() } /// Checks whether the given operator is a [`CoalescePartitionsExec`]. pub fn is_coalesce_partitions(plan: &Arc) -> bool { - plan.as_ref().is::() + plan.is::() } /// Checks whether the given operator is a [`RepartitionExec`]. pub fn is_repartition(plan: &Arc) -> bool { - plan.as_ref().is::() + plan.is::() } /// Checks whether the given operator is a limit; /// i.e. either a [`LocalLimitExec`] or a [`GlobalLimitExec`]. pub fn is_limit(plan: &Arc) -> bool { - plan.as_ref().is::() || plan.as_ref().is::() + plan.is::() || plan.is::() } diff --git a/datafusion/physical-plan/src/projection.rs b/datafusion/physical-plan/src/projection.rs index 990af26e01796..9cc75a68fefcf 100644 --- a/datafusion/physical-plan/src/projection.rs +++ b/datafusion/physical-plan/src/projection.rs @@ -727,20 +727,19 @@ pub fn try_pushdown_through_join( pub fn remove_unnecessary_projections( plan: Arc, ) -> Result>> { - let maybe_modified = - if let Some(projection) = plan.as_ref().downcast_ref::() { - // If the projection does not cause any change on the input, we can - // safely remove it: - if is_projection_removable(projection) { - return Ok(Transformed::yes(Arc::clone(projection.input()))); - } - // If it does, check if we can push it under its child(ren): - projection - .input() - .try_swapping_with_projection(projection)? - } else { - return Ok(Transformed::no(plan)); - }; + let maybe_modified = if let Some(projection) = plan.downcast_ref::() { + // If the projection does not cause any change on the input, we can + // safely remove it: + if is_projection_removable(projection) { + return Ok(Transformed::yes(Arc::clone(projection.input()))); + } + // If it does, check if we can push it under its child(ren): + projection + .input() + .try_swapping_with_projection(projection)? + } else { + return Ok(Transformed::no(plan)); + }; Ok(maybe_modified.map_or_else(|| Transformed::no(plan), Transformed::yes)) } diff --git a/datafusion/physical-plan/src/sorts/sort.rs b/datafusion/physical-plan/src/sorts/sort.rs index 5114004653cec..583bfa29b04ad 100644 --- a/datafusion/physical-plan/src/sorts/sort.rs +++ b/datafusion/physical-plan/src/sorts/sort.rs @@ -1242,7 +1242,6 @@ impl ExecutionPlan for SortExec { let children = self.children().into_iter().cloned().collect(); let new_sort = self.with_new_children(children)?; let mut new_sort = new_sort - .as_ref() .downcast_ref::() .expect("cloned 1 lines above this line, we know the type") .clone(); diff --git a/datafusion/physical-plan/src/union.rs b/datafusion/physical-plan/src/union.rs index f67f1fefc5bdb..20295b7e6fac9 100644 --- a/datafusion/physical-plan/src/union.rs +++ b/datafusion/physical-plan/src/union.rs @@ -1403,7 +1403,6 @@ mod tests { // Downcast to verify it's a UnionExec let union = union_plan - .as_ref() .downcast_ref::() .expect("Expected UnionExec"); @@ -1444,7 +1443,6 @@ mod tests { let union = UnionExec::try_new(vec![input1, input2])?; let union = union - .as_ref() .downcast_ref::() .expect("expected UnionExec for multiple inputs"); diff --git a/datafusion/physical-plan/src/windows/bounded_window_agg_exec.rs b/datafusion/physical-plan/src/windows/bounded_window_agg_exec.rs index fe01b2d2cbb7a..14f8ce5e95ffd 100644 --- a/datafusion/physical-plan/src/windows/bounded_window_agg_exec.rs +++ b/datafusion/physical-plan/src/windows/bounded_window_agg_exec.rs @@ -1860,7 +1860,6 @@ mod tests { Arc::new(TestMemoryExec::try_new(&[], Arc::clone(&schema), None)?); let plan = bounded_window_exec_pb_latent_range(input, 1, "hash", "sn")?; let plan = plan - .as_ref() .downcast_ref::() .expect("expected BoundedWindowAggExec"); diff --git a/datafusion/proto/tests/cases/roundtrip_physical_plan.rs b/datafusion/proto/tests/cases/roundtrip_physical_plan.rs index c1788d2f38ab7..74bc122969275 100644 --- a/datafusion/proto/tests/cases/roundtrip_physical_plan.rs +++ b/datafusion/proto/tests/cases/roundtrip_physical_plan.rs @@ -968,7 +968,7 @@ fn roundtrip_parquet_exec_attaches_cached_reader_factory_after_roundtrip() -> Re let roundtripped = roundtrip_test_and_return(exec_plan, &ctx, &codec, &proto_converter)?; - let data_source = (roundtripped.as_ref() as &dyn Any) + let data_source = roundtripped .downcast_ref::() .ok_or_else(|| { internal_datafusion_err!("Expected DataSourceExec after roundtrip") @@ -1656,9 +1656,7 @@ fn roundtrip_csv_sink() -> Result<()> { &proto_converter, )?; - let roundtrip_plan = (roundtrip_plan.as_ref() as &dyn Any) - .downcast_ref::() - .unwrap(); + let roundtrip_plan = roundtrip_plan.downcast_ref::().unwrap(); let csv_sink = roundtrip_plan .sink() .as_any() @@ -2520,9 +2518,7 @@ fn roundtrip_hash_table_lookup_expr_to_lit() -> Result<()> { // The deserialized plan should have lit(true) instead of HashTableLookupExpr // Verify the filter predicate is a Literal(true) - let result_filter = (result.as_ref() as &dyn Any) - .downcast_ref::() - .unwrap(); + let result_filter = result.downcast_ref::().unwrap(); let predicate = result_filter.predicate(); let literal = predicate.as_any().downcast_ref::().unwrap(); assert_eq!(*literal.value(), ScalarValue::Boolean(Some(true))); @@ -2818,7 +2814,7 @@ fn test_expression_deduplication_arc_sharing() -> Result<()> { )?; // Get the projection from the result - let projection = (result_plan.as_ref() as &dyn Any) + let projection = result_plan .downcast_ref::() .expect("Expected ProjectionExec"); @@ -2926,7 +2922,7 @@ fn test_deduplication_within_plan_deserialization() -> Result<()> { )?; // Check that the plan was deserialized correctly with deduplication - let projection1 = (plan1.as_ref() as &dyn Any) + let projection1 = plan1 .downcast_ref::() .expect("Expected ProjectionExec"); let exprs1: Vec<_> = projection1.expr().iter().collect(); @@ -2945,7 +2941,7 @@ fn test_deduplication_within_plan_deserialization() -> Result<()> { )?; // Check that the second plan was also deserialized correctly - let projection2 = (plan2.as_ref() as &dyn Any) + let projection2 = plan2 .downcast_ref::() .expect("Expected ProjectionExec"); let exprs2: Vec<_> = projection2.expr().iter().collect(); diff --git a/datafusion/substrait/src/physical_plan/producer.rs b/datafusion/substrait/src/physical_plan/producer.rs index 3d30704c73d5d..17ca99ceff6e4 100644 --- a/datafusion/substrait/src/physical_plan/producer.rs +++ b/datafusion/substrait/src/physical_plan/producer.rs @@ -51,8 +51,7 @@ pub fn to_substrait_rel( HashMap, ), ) -> Result> { - if let Some(data_source_exec) = - (plan as &dyn std::any::Any).downcast_ref::() + if let Some(data_source_exec) = plan.downcast_ref::() && let Some((file_config, _)) = data_source_exec.downcast_to_file_source::() { From c36b282635d20ebca3d507cda8cea5b57137e037 Mon Sep 17 00:00:00 2001 From: Tim Saucer Date: Wed, 1 Apr 2026 07:30:27 -0400 Subject: [PATCH 10/14] Clean up remaining ExecutionPlan as &dyn Any patterns 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 --- datafusion/physical-optimizer/src/limit_pushdown.rs | 2 -- datafusion/physical-optimizer/src/topk_repartition.rs | 7 +++---- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/datafusion/physical-optimizer/src/limit_pushdown.rs b/datafusion/physical-optimizer/src/limit_pushdown.rs index f14f299440df3..c5fa0cc3ee78c 100644 --- a/datafusion/physical-optimizer/src/limit_pushdown.rs +++ b/datafusion/physical-optimizer/src/limit_pushdown.rs @@ -60,7 +60,6 @@ //! //! Reference implementation in Hash Join: -use std::any::Any; use std::fmt::Debug; use std::sync::Arc; @@ -346,7 +345,6 @@ fn extract_limit(plan: &Arc) -> Option { /// Checks if the given plan combines input partitions. fn combines_input_partitions(plan: &Arc) -> bool { - let plan = plan.as_ref() as &dyn Any; plan.is::() || plan.is::() } diff --git a/datafusion/physical-optimizer/src/topk_repartition.rs b/datafusion/physical-optimizer/src/topk_repartition.rs index 93df27b7b16de..115bdc3cb535f 100644 --- a/datafusion/physical-optimizer/src/topk_repartition.rs +++ b/datafusion/physical-optimizer/src/topk_repartition.rs @@ -48,7 +48,6 @@ use crate::PhysicalOptimizerRule; use datafusion_common::Result; use datafusion_common::config::ConfigOptions; use datafusion_common::tree_node::{Transformed, TransformedResult, TreeNode}; -use std::any::Any; use std::sync::Arc; // CoalesceBatchesExec is deprecated on main (replaced by arrow-rs BatchCoalescer), // but older DataFusion versions may still insert it between SortExec and RepartitionExec. @@ -92,13 +91,13 @@ impl PhysicalOptimizerRule for TopKRepartition { // The child might be a CoalesceBatchesExec; look through it let sort_input = sort_exec.input(); - let sort_any = sort_input.as_ref() as &dyn Any; let (repart_parent, repart_exec) = if let Some(rp) = - sort_any.downcast_ref::() + sort_input.downcast_ref::() { // found a RepartitionExec, use it (None, rp) - } else if let Some(cb_exec) = sort_any.downcast_ref::() { + } else if let Some(cb_exec) = sort_input.downcast_ref::() + { // There's a CoalesceBatchesExec between TopK & RepartitionExec // in this case we will need to reconstruct both nodes let cb_input = cb_exec.input(); From 3fa186947fa7099360f6b6d393e5e2aff3d6f49c Mon Sep 17 00:00:00 2001 From: Tim Saucer Date: Wed, 1 Apr 2026 07:38:27 -0400 Subject: [PATCH 11/14] Use ScalarUDFImpl::downcast_ref and is() instead of as &dyn Any patterns Replaces `(func.inner().as_ref() as &dyn Any).downcast_ref::()` and `.is::()` call sites with the new inherent methods on `dyn ScalarUDFImpl`. Also simplifies `.downcast_ref::().is_some()` to `.is::()` and removes now-unused `std::any::Any` imports. Co-Authored-By: Claude Sonnet 4.6 --- datafusion/expr/src/udf.rs | 2 +- .../ffi/src/proto/logical_extension_codec.rs | 2 +- .../ffi/src/proto/physical_extension_codec.rs | 4 ++-- datafusion/ffi/src/udf/mod.rs | 18 +++--------------- datafusion/functions/src/core/getfield.rs | 5 +---- datafusion/functions/src/math/log.rs | 6 +----- datafusion/functions/src/math/power.rs | 6 +----- .../physical-expr/src/async_scalar_function.rs | 7 ++++--- .../physical-expr/src/scalar_function.rs | 12 +++--------- .../tests/cases/roundtrip_physical_plan.rs | 2 +- 10 files changed, 18 insertions(+), 46 deletions(-) diff --git a/datafusion/expr/src/udf.rs b/datafusion/expr/src/udf.rs index fd56f40ccbfdf..ddbfb65df0536 100644 --- a/datafusion/expr/src/udf.rs +++ b/datafusion/expr/src/udf.rs @@ -360,7 +360,7 @@ impl ScalarUDF { /// Return true if this function is an async function pub fn as_async(&self) -> Option<&AsyncScalarUDF> { - (self.inner().as_ref() as &dyn Any).downcast_ref::() + self.inner().downcast_ref::() } /// Returns placement information for this function. diff --git a/datafusion/ffi/src/proto/logical_extension_codec.rs b/datafusion/ffi/src/proto/logical_extension_codec.rs index 19fc73b368cda..ca111ac73ff61 100644 --- a/datafusion/ffi/src/proto/logical_extension_codec.rs +++ b/datafusion/ffi/src/proto/logical_extension_codec.rs @@ -659,7 +659,7 @@ mod tests { let returned_udf = foreign_codec.try_decode_udf(udf.name(), &bytes)?; - assert!((returned_udf.inner().as_ref() as &dyn Any).is::()); + assert!(returned_udf.inner().is::()); Ok(()) } diff --git a/datafusion/ffi/src/proto/physical_extension_codec.rs b/datafusion/ffi/src/proto/physical_extension_codec.rs index b7cd1167f8f9d..abbbb543bce6b 100644 --- a/datafusion/ffi/src/proto/physical_extension_codec.rs +++ b/datafusion/ffi/src/proto/physical_extension_codec.rs @@ -493,7 +493,7 @@ pub(crate) mod tests { buf.push(Self::MAGIC_NUMBER); let udf = node.inner(); - if !(udf.as_ref() as &dyn Any).is::() { + if !udf.is::() { return exec_err!("TestExtensionCodec only expects Abs UDF"); }; @@ -609,7 +609,7 @@ pub(crate) mod tests { let returned_udf = foreign_codec.try_decode_udf(udf.name(), &bytes)?; - assert!((returned_udf.inner().as_ref() as &dyn Any).is::()); + assert!(returned_udf.inner().is::()); Ok(()) } diff --git a/datafusion/ffi/src/udf/mod.rs b/datafusion/ffi/src/udf/mod.rs index 766c7dece3d86..fc26b2b098b0a 100644 --- a/datafusion/ffi/src/udf/mod.rs +++ b/datafusion/ffi/src/udf/mod.rs @@ -15,7 +15,6 @@ // specific language governing permissions and limitations // under the License. -use std::any::Any; use std::ffi::c_void; use std::hash::{Hash, Hasher}; use std::sync::Arc; @@ -231,9 +230,7 @@ impl Clone for FFI_ScalarUDF { impl From> for FFI_ScalarUDF { fn from(udf: Arc) -> Self { - if let Some(udf) = - (udf.inner().as_ref() as &dyn Any).downcast_ref::() - { + if let Some(udf) = udf.inner().downcast_ref::() { return udf.udf.clone(); } @@ -433,7 +430,6 @@ impl ScalarUDFImpl for ForeignScalarUDF { #[cfg(test)] mod tests { use super::*; - use std::any::Any; #[test] fn test_round_trip_scalar_udf() -> Result<()> { @@ -460,20 +456,12 @@ mod tests { // Verify local libraries can be downcast to their original let foreign_udf: Arc = (&ffi_udf).into(); - assert!( - (foreign_udf.as_ref() as &dyn Any) - .downcast_ref::() - .is_some() - ); + assert!(foreign_udf.is::()); // Verify different library markers generate foreign providers ffi_udf.library_marker_id = crate::mock_foreign_marker_id; let foreign_udf: Arc = (&ffi_udf).into(); - assert!( - (foreign_udf.as_ref() as &dyn Any) - .downcast_ref::() - .is_some() - ); + assert!(foreign_udf.is::()); Ok(()) } diff --git a/datafusion/functions/src/core/getfield.rs b/datafusion/functions/src/core/getfield.rs index 247c1493b1347..e1fce4ee6c835 100644 --- a/datafusion/functions/src/core/getfield.rs +++ b/datafusion/functions/src/core/getfield.rs @@ -15,7 +15,6 @@ // specific language governing permissions and limitations // under the License. -use std::any::Any; use std::sync::Arc; use arrow::array::{ @@ -440,9 +439,7 @@ impl ScalarUDFImpl for GetFieldFunc { func, args: inner_args, }) = current_expr - && (func.inner().as_ref() as &dyn Any) - .downcast_ref::() - .is_some() + && func.inner().is::() { // Store this level's path arguments (all except the first, which is base/nested call) path_args_stack.push(&inner_args[1..]); diff --git a/datafusion/functions/src/math/log.rs b/datafusion/functions/src/math/log.rs index ae336786a5d80..ac94f78e0c723 100644 --- a/datafusion/functions/src/math/log.rs +++ b/datafusion/functions/src/math/log.rs @@ -17,8 +17,6 @@ //! Math function: `log()`. -use std::any::Any; - use super::power::PowerFunc; use crate::utils::calculate_binary_math; @@ -399,9 +397,7 @@ impl ScalarUDFImpl for LogFunc { /// Returns true if the function is `PowerFunc` fn is_pow(func: &ScalarUDF) -> bool { - (func.inner().as_ref() as &dyn Any) - .downcast_ref::() - .is_some() + func.inner().is::() } #[cfg(test)] diff --git a/datafusion/functions/src/math/power.rs b/datafusion/functions/src/math/power.rs index bac970c95d085..ea7c85a33782e 100644 --- a/datafusion/functions/src/math/power.rs +++ b/datafusion/functions/src/math/power.rs @@ -16,8 +16,6 @@ // under the License. //! Math function: `power()`. -use std::any::Any; - use super::log::LogFunc; use crate::utils::{calculate_binary_decimal_math, calculate_binary_math}; @@ -601,9 +599,7 @@ impl ScalarUDFImpl for PowerFunc { /// Return true if this function call is a call to `Log` fn is_log(func: &ScalarUDF) -> bool { - (func.inner().as_ref() as &dyn Any) - .downcast_ref::() - .is_some() + func.inner().is::() } #[cfg(test)] diff --git a/datafusion/physical-expr/src/async_scalar_function.rs b/datafusion/physical-expr/src/async_scalar_function.rs index 4fc79822b5966..9f58d7c77a831 100644 --- a/datafusion/physical-expr/src/async_scalar_function.rs +++ b/datafusion/physical-expr/src/async_scalar_function.rs @@ -100,8 +100,7 @@ impl AsyncFuncExpr { /// Return the ideal batch size for this function pub fn ideal_batch_size(&self) -> Result> { if let Some(expr) = self.func.as_any().downcast_ref::() - && let Some(udf) = - (expr.fun().inner().as_ref() as &dyn Any).downcast_ref::() + && let Some(udf) = expr.fun().inner().downcast_ref::() { return Ok(udf.ideal_batch_size()); } @@ -125,7 +124,9 @@ impl AsyncFuncExpr { ); }; - let Some(async_udf) = (scalar_function_expr.fun().inner().as_ref() as &dyn Any) + let Some(async_udf) = scalar_function_expr + .fun() + .inner() .downcast_ref::() else { return not_impl_err!( diff --git a/datafusion/physical-expr/src/scalar_function.rs b/datafusion/physical-expr/src/scalar_function.rs index ef800b1439c3f..5e6e0aa97f003 100644 --- a/datafusion/physical-expr/src/scalar_function.rs +++ b/datafusion/physical-expr/src/scalar_function.rs @@ -46,7 +46,7 @@ use datafusion_expr::sort_properties::ExprProperties; use datafusion_expr::type_coercion::functions::fields_with_udf; use datafusion_expr::{ ColumnarValue, ExpressionPlacement, ReturnFieldArgs, ScalarFunctionArgs, ScalarUDF, - Volatility, expr_vec_fmt, + ScalarUDFImpl, Volatility, expr_vec_fmt, }; /// Physical expression of a scalar function @@ -169,16 +169,10 @@ impl ScalarFunctionExpr { /// Otherwise returns `Some(ScalarFunctionExpr)`. pub fn try_downcast_func(expr: &dyn PhysicalExpr) -> Option<&ScalarFunctionExpr> where - T: 'static, + T: ScalarUDFImpl, { match expr.as_any().downcast_ref::() { - Some(scalar_expr) - if (scalar_expr.fun().inner().as_ref() as &dyn Any) - .downcast_ref::() - .is_some() => - { - Some(scalar_expr) - } + Some(scalar_expr) if scalar_expr.fun().inner().is::() => Some(scalar_expr), _ => None, } } diff --git a/datafusion/proto/tests/cases/roundtrip_physical_plan.rs b/datafusion/proto/tests/cases/roundtrip_physical_plan.rs index 74bc122969275..f1f0f7fd6c73c 100644 --- a/datafusion/proto/tests/cases/roundtrip_physical_plan.rs +++ b/datafusion/proto/tests/cases/roundtrip_physical_plan.rs @@ -1279,7 +1279,7 @@ impl PhysicalExtensionCodec for UDFExtensionCodec { fn try_encode_udf(&self, node: &ScalarUDF, buf: &mut Vec) -> Result<()> { let binding = node.inner(); - if let Some(udf) = (binding.as_ref() as &dyn Any).downcast_ref::() { + if let Some(udf) = binding.downcast_ref::() { let proto = MyRegexUdfNode { pattern: udf.pattern.clone(), }; From fa7fa2d4bc0b5328fd1b34972cc55bec14c5c90b Mon Sep 17 00:00:00 2001 From: Tim Saucer Date: Wed, 1 Apr 2026 07:47:50 -0400 Subject: [PATCH 12/14] Use AggregateUDFImpl::downcast_ref and is() instead of as &dyn Any patterns Replaces `(udaf.inner().as_ref() as &dyn Any).downcast_ref::()` and `.is::()` call sites with the new inherent methods on `dyn AggregateUDFImpl`. Removes now-unused `std::any::Any` imports. Co-Authored-By: Claude Sonnet 4.6 --- .../ffi/src/proto/logical_extension_codec.rs | 2 +- .../ffi/src/proto/physical_extension_codec.rs | 4 ++-- datafusion/ffi/src/udaf/mod.rs | 18 +++--------------- .../tests/cases/roundtrip_physical_plan.rs | 3 +-- 4 files changed, 7 insertions(+), 20 deletions(-) diff --git a/datafusion/ffi/src/proto/logical_extension_codec.rs b/datafusion/ffi/src/proto/logical_extension_codec.rs index ca111ac73ff61..5de54629afef7 100644 --- a/datafusion/ffi/src/proto/logical_extension_codec.rs +++ b/datafusion/ffi/src/proto/logical_extension_codec.rs @@ -680,7 +680,7 @@ mod tests { let returned_udf = foreign_codec.try_decode_udaf(udf.name(), &bytes)?; - assert!((returned_udf.inner().as_ref() as &dyn Any).is::()); + assert!(returned_udf.inner().is::()); Ok(()) } diff --git a/datafusion/ffi/src/proto/physical_extension_codec.rs b/datafusion/ffi/src/proto/physical_extension_codec.rs index abbbb543bce6b..a6fbc36aa6fdf 100644 --- a/datafusion/ffi/src/proto/physical_extension_codec.rs +++ b/datafusion/ffi/src/proto/physical_extension_codec.rs @@ -520,7 +520,7 @@ pub(crate) mod tests { buf.push(Self::MAGIC_NUMBER); let udf = node.inner(); - let Some(_udf) = (udf.as_ref() as &dyn Any).downcast_ref::() else { + let Some(_udf) = udf.downcast_ref::() else { return exec_err!("TestExtensionCodec only expects Sum UDAF"); }; @@ -630,7 +630,7 @@ pub(crate) mod tests { let returned_udf = foreign_codec.try_decode_udaf(udf.name(), &bytes)?; - assert!((returned_udf.inner().as_ref() as &dyn Any).is::()); + assert!(returned_udf.inner().is::()); Ok(()) } diff --git a/datafusion/ffi/src/udaf/mod.rs b/datafusion/ffi/src/udaf/mod.rs index d3cb3ca7bb704..57f8282605866 100644 --- a/datafusion/ffi/src/udaf/mod.rs +++ b/datafusion/ffi/src/udaf/mod.rs @@ -15,7 +15,6 @@ // specific language governing permissions and limitations // under the License. -use std::any::Any; use std::ffi::c_void; use std::hash::{Hash, Hasher}; use std::sync::Arc; @@ -372,9 +371,7 @@ impl Clone for FFI_AggregateUDF { impl From> for FFI_AggregateUDF { fn from(udaf: Arc) -> Self { - if let Some(udaf) = - (udaf.inner().as_ref() as &dyn Any).downcast_ref::() - { + if let Some(udaf) = udaf.inner().downcast_ref::() { return udaf.udaf.clone(); } @@ -644,7 +641,6 @@ impl From for FFI_AggregateOrderSensitivity { #[cfg(test)] mod tests { - use std::any::Any; use std::collections::HashMap; use arrow::datatypes::Schema; @@ -855,20 +851,12 @@ mod tests { // Verify local libraries can be downcast to their original let foreign_udaf: Arc = (&ffi_udaf).into(); - assert!( - (foreign_udaf.as_ref() as &dyn Any) - .downcast_ref::() - .is_some() - ); + assert!(foreign_udaf.is::()); // Verify different library markers generate foreign providers ffi_udaf.library_marker_id = crate::mock_foreign_marker_id; let foreign_udaf: Arc = (&ffi_udaf).into(); - assert!( - (foreign_udaf.as_ref() as &dyn Any) - .downcast_ref::() - .is_some() - ); + assert!(foreign_udaf.is::()); Ok(()) } diff --git a/datafusion/proto/tests/cases/roundtrip_physical_plan.rs b/datafusion/proto/tests/cases/roundtrip_physical_plan.rs index f1f0f7fd6c73c..df924b7645a42 100644 --- a/datafusion/proto/tests/cases/roundtrip_physical_plan.rs +++ b/datafusion/proto/tests/cases/roundtrip_physical_plan.rs @@ -1306,8 +1306,7 @@ impl PhysicalExtensionCodec for UDFExtensionCodec { fn try_encode_udaf(&self, node: &AggregateUDF, buf: &mut Vec) -> Result<()> { let binding = node.inner(); - if let Some(udf) = (binding.as_ref() as &dyn Any).downcast_ref::() - { + if let Some(udf) = binding.downcast_ref::() { let proto = MyAggregateUdfNode { result: udf.result.clone(), }; From 02457bdb03a5847f96d71bd688a340a33f4bcb2c Mon Sep 17 00:00:00 2001 From: Tim Saucer Date: Wed, 1 Apr 2026 07:51:07 -0400 Subject: [PATCH 13/14] Use WindowUDFImpl::downcast_ref and is() instead of as &dyn Any patterns Replaces `(udwf.inner().as_ref() as &dyn Any).downcast_ref::()` and `.is::()` call sites with the new inherent methods on `dyn WindowUDFImpl`. Removes now-unused `std::any::Any` imports. Co-Authored-By: Claude Sonnet 4.6 --- .../ffi/src/proto/logical_extension_codec.rs | 3 +-- .../ffi/src/proto/physical_extension_codec.rs | 5 ++--- datafusion/ffi/src/udwf/mod.rs | 16 +++------------- .../proto/tests/cases/roundtrip_physical_plan.rs | 2 +- 4 files changed, 7 insertions(+), 19 deletions(-) diff --git a/datafusion/ffi/src/proto/logical_extension_codec.rs b/datafusion/ffi/src/proto/logical_extension_codec.rs index 5de54629afef7..a5be8588d23e3 100644 --- a/datafusion/ffi/src/proto/logical_extension_codec.rs +++ b/datafusion/ffi/src/proto/logical_extension_codec.rs @@ -489,7 +489,6 @@ impl LogicalExtensionCodec for ForeignLogicalExtensionCodec { #[cfg(test)] mod tests { - use std::any::Any; use std::sync::Arc; use arrow::array::record_batch; @@ -704,7 +703,7 @@ mod tests { let returned_udf = foreign_codec.try_decode_udwf(udf.name(), &bytes)?; - assert!((returned_udf.inner().as_ref() as &dyn Any).is::()); + assert!(returned_udf.inner().is::()); Ok(()) } diff --git a/datafusion/ffi/src/proto/physical_extension_codec.rs b/datafusion/ffi/src/proto/physical_extension_codec.rs index a6fbc36aa6fdf..882da7c084746 100644 --- a/datafusion/ffi/src/proto/physical_extension_codec.rs +++ b/datafusion/ffi/src/proto/physical_extension_codec.rs @@ -410,7 +410,6 @@ impl PhysicalExtensionCodec for ForeignPhysicalExtensionCodec { #[cfg(test)] pub(crate) mod tests { - use std::any::Any; use std::sync::Arc; use arrow_schema::{DataType, Field, Schema}; @@ -550,7 +549,7 @@ pub(crate) mod tests { buf.push(Self::MAGIC_NUMBER); let udf = node.inner(); - let Some(udf) = (udf.as_ref() as &dyn Any).downcast_ref::() else { + let Some(udf) = udf.downcast_ref::() else { return exec_err!("TestExtensionCodec only expects Rank UDWF"); }; @@ -654,7 +653,7 @@ pub(crate) mod tests { let returned_udf = foreign_codec.try_decode_udwf(udf.name(), &bytes)?; - assert!((returned_udf.inner().as_ref() as &dyn Any).is::()); + assert!(returned_udf.inner().is::()); Ok(()) } diff --git a/datafusion/ffi/src/udwf/mod.rs b/datafusion/ffi/src/udwf/mod.rs index 7b5183f7f6ea1..b7f6baac4c376 100644 --- a/datafusion/ffi/src/udwf/mod.rs +++ b/datafusion/ffi/src/udwf/mod.rs @@ -222,9 +222,7 @@ impl Clone for FFI_WindowUDF { impl From> for FFI_WindowUDF { fn from(udf: Arc) -> Self { - if let Some(udwf) = (udf.inner().as_ref() as &dyn std::any::Any) - .downcast_ref::() - { + if let Some(udwf) = udf.inner().downcast_ref::() { return udwf.udf.clone(); } @@ -474,20 +472,12 @@ mod tests { // Verify local libraries can be downcast to their original let foreign_udwf: Arc = (&ffi_udwf).into(); - assert!( - (foreign_udwf.as_ref() as &dyn std::any::Any) - .downcast_ref::() - .is_some() - ); + assert!(foreign_udwf.is::()); // Verify different library markers generate foreign providers ffi_udwf.library_marker_id = crate::mock_foreign_marker_id; let foreign_udwf: Arc = (&ffi_udwf).into(); - assert!( - (foreign_udwf.as_ref() as &dyn std::any::Any) - .downcast_ref::() - .is_some() - ); + assert!(foreign_udwf.is::()); Ok(()) } diff --git a/datafusion/proto/tests/cases/roundtrip_physical_plan.rs b/datafusion/proto/tests/cases/roundtrip_physical_plan.rs index df924b7645a42..15639bcd25bdd 100644 --- a/datafusion/proto/tests/cases/roundtrip_physical_plan.rs +++ b/datafusion/proto/tests/cases/roundtrip_physical_plan.rs @@ -1333,7 +1333,7 @@ impl PhysicalExtensionCodec for UDFExtensionCodec { fn try_encode_udwf(&self, node: &WindowUDF, buf: &mut Vec) -> Result<()> { let binding = node.inner(); - if let Some(udwf) = (binding.as_ref() as &dyn Any).downcast_ref::() { + if let Some(udwf) = binding.downcast_ref::() { let proto = CustomUDWFNode { payload: udwf.payload.clone(), }; From 9233df3ec9ea3b03d1b1d7c29250106e716346b7 Mon Sep 17 00:00:00 2001 From: Tim Saucer Date: Wed, 1 Apr 2026 09:15:54 -0400 Subject: [PATCH 14/14] We got a little too carried away with the 'as &dyn Any' pattern and applied it to expressions --- .../src/enforce_sorting/sort_pushdown.rs | 24 ++++---- .../src/projection_pushdown.rs | 55 +++++++++---------- .../src/topk_aggregation.rs | 6 +- 3 files changed, 37 insertions(+), 48 deletions(-) diff --git a/datafusion/physical-optimizer/src/enforce_sorting/sort_pushdown.rs b/datafusion/physical-optimizer/src/enforce_sorting/sort_pushdown.rs index a3e135657433b..3677f3a45b599 100644 --- a/datafusion/physical-optimizer/src/enforce_sorting/sort_pushdown.rs +++ b/datafusion/physical-optimizer/src/enforce_sorting/sort_pushdown.rs @@ -15,7 +15,6 @@ // specific language governing permissions and limitations // under the License. -use std::any::Any; use std::fmt::Debug; use std::sync::Arc; @@ -431,8 +430,7 @@ fn handle_aggregate_pushdown( for req in parent_requirement { // Sort above AggregateExec should reference its output columns. Map each // output group-by column to its original input expression. - let Some(column) = (req.expr.as_ref() as &dyn Any).downcast_ref::() - else { + let Some(column) = req.expr.as_any().downcast_ref::() else { return Ok(None); }; if column.index() >= group_input_exprs.len() { @@ -606,13 +604,14 @@ fn expr_source_side( let mut right_ordering = ordering.clone(); let (mut valid_left, mut valid_right) = (true, true); for (left, right) in ordering.iter_mut().zip(right_ordering.iter_mut()) { - let col = (left.expr.as_ref() as &dyn Any).downcast_ref::()?; + let col = left.expr.as_any().downcast_ref::()?; let eq_class = eq_group.get_equivalence_class(&left.expr); if col.index() < left_columns_len { if valid_right { valid_right = eq_class.is_some_and(|cls| { for expr in cls.iter() { - if (expr.as_ref() as &dyn Any) + if expr + .as_any() .downcast_ref::() .is_some_and(|c| c.index() >= left_columns_len) { @@ -626,7 +625,8 @@ fn expr_source_side( } else if valid_left { valid_left = eq_class.is_some_and(|cls| { for expr in cls.iter() { - if (expr.as_ref() as &dyn Any) + if expr + .as_any() .downcast_ref::() .is_some_and(|c| c.index() < left_columns_len) { @@ -652,11 +652,11 @@ fn expr_source_side( } JoinType::LeftSemi | JoinType::LeftAnti => ordering .iter() - .all(|e| (e.expr.as_ref() as &dyn Any).is::()) + .all(|e| e.expr.as_any().is::()) .then_some((JoinSide::Left, ordering)), JoinType::RightSemi | JoinType::RightAnti => ordering .iter() - .all(|e| (e.expr.as_ref() as &dyn Any).is::()) + .all(|e| e.expr.as_any().is::()) .then_some((JoinSide::Right, ordering)), } } @@ -739,9 +739,7 @@ fn handle_custom_pushdown( let updated_columns = req .expr .transform_up(|expr| { - if let Some(col) = - (expr.as_ref() as &dyn Any).downcast_ref::() - { + if let Some(col) = expr.as_any().downcast_ref::() { let new_index = col.index() - sub_offset; Ok(Transformed::yes(Arc::new(Column::new( child_schema.field(new_index).name(), @@ -824,9 +822,7 @@ fn handle_hash_join( let updated_columns = req .expr .transform_up(|expr| { - if let Some(col) = - (expr.as_ref() as &dyn Any).downcast_ref::() - { + if let Some(col) = expr.as_any().downcast_ref::() { let index = projected_indices[col.index()].index; Ok(Transformed::yes(Arc::new(Column::new( child_schema.field(index).name(), diff --git a/datafusion/physical-optimizer/src/projection_pushdown.rs b/datafusion/physical-optimizer/src/projection_pushdown.rs index b0095fc6b8c87..7d8bf7aa47326 100644 --- a/datafusion/physical-optimizer/src/projection_pushdown.rs +++ b/datafusion/physical-optimizer/src/projection_pushdown.rs @@ -23,7 +23,6 @@ use crate::PhysicalOptimizerRule; use arrow::datatypes::{Fields, Schema, SchemaRef}; use datafusion_common::alias::AliasGenerator; -use std::any::Any; use std::collections::HashSet; use std::sync::Arc; @@ -243,7 +242,7 @@ fn minimize_join_filter( ) -> JoinFilter { let mut used_columns = HashSet::new(); expr.apply(|expr| { - if let Some(col) = (expr.as_ref() as &dyn Any).downcast_ref::() { + if let Some(col) = expr.as_any().downcast_ref::() { used_columns.insert(col.index()); } Ok(TreeNodeRecursion::Continue) @@ -266,21 +265,19 @@ fn minimize_join_filter( .collect::(); let final_expr = expr - .transform_up( - |expr| match (expr.as_ref() as &dyn Any).downcast_ref::() { - None => Ok(Transformed::no(expr)), - Some(column) => { - let new_idx = used_columns - .iter() - .filter(|idx| **idx < column.index()) - .count(); - let new_column = Column::new(column.name(), new_idx); - Ok(Transformed::yes( - Arc::new(new_column) as Arc - )) - } - }, - ) + .transform_up(|expr| match expr.as_any().downcast_ref::() { + None => Ok(Transformed::no(expr)), + Some(column) => { + let new_idx = used_columns + .iter() + .filter(|idx| **idx < column.index()) + .count(); + let new_column = Column::new(column.name(), new_idx); + Ok(Transformed::yes( + Arc::new(new_column) as Arc + )) + } + }) .expect("Closure cannot fail"); JoinFilter::new( @@ -381,7 +378,7 @@ impl<'a> JoinFilterRewriter<'a> { // executed against the filter schema. let new_idx = self.join_side_projections.len(); let rewritten_expr = expr.transform_up(|expr| { - Ok(match (expr.as_ref() as &dyn Any).downcast_ref::() { + Ok(match expr.as_any().downcast_ref::() { None => Transformed::no(expr), Some(column) => { let intermediate_column = @@ -415,19 +412,17 @@ impl<'a> JoinFilterRewriter<'a> { join_side: JoinSide, ) -> Result { let mut result = false; - expr.apply( - |expr| match (expr.as_ref() as &dyn Any).downcast_ref::() { - None => Ok(TreeNodeRecursion::Continue), - Some(c) => { - let column_index = &self.intermediate_column_indices[c.index()]; - if column_index.side == join_side { - result = true; - return Ok(TreeNodeRecursion::Stop); - } - Ok(TreeNodeRecursion::Continue) + expr.apply(|expr| match expr.as_any().downcast_ref::() { + None => Ok(TreeNodeRecursion::Continue), + Some(c) => { + let column_index = &self.intermediate_column_indices[c.index()]; + if column_index.side == join_side { + result = true; + return Ok(TreeNodeRecursion::Stop); } - }, - )?; + Ok(TreeNodeRecursion::Continue) + } + })?; Ok(result) } diff --git a/datafusion/physical-optimizer/src/topk_aggregation.rs b/datafusion/physical-optimizer/src/topk_aggregation.rs index 38e25e5a396c3..581edd86cd0aa 100644 --- a/datafusion/physical-optimizer/src/topk_aggregation.rs +++ b/datafusion/physical-optimizer/src/topk_aggregation.rs @@ -17,7 +17,6 @@ //! An optimizer rule that detects aggregate operations that could use a limited bucket count -use std::any::Any; use std::sync::Arc; use crate::PhysicalOptimizerRule; @@ -101,7 +100,7 @@ impl TopKAggregation { let order = sort.properties().output_ordering()?; let order = order.iter().exactly_one().ok()?; let order_desc = order.options.descending; - let order = (order.expr.as_ref() as &dyn Any).downcast_ref::()?; + let order = order.expr.as_any().downcast_ref::()?; let mut cur_col_name = order.name().to_string(); let limit = sort.fetch()?; @@ -119,8 +118,7 @@ impl TopKAggregation { } else if let Some(proj) = plan.downcast_ref::() { // track renames due to successive projections for proj_expr in proj.expr() { - let Some(src_col) = - (proj_expr.expr.as_ref() as &dyn Any).downcast_ref::() + let Some(src_col) = proj_expr.expr.as_any().downcast_ref::() else { continue; };