@@ -83,6 +83,7 @@ use datafusion_expr::{
83
83
use datafusion_physical_expr:: aggregate:: { AggregateExprBuilder , AggregateFunctionExpr } ;
84
84
use datafusion_physical_expr:: expressions:: Literal ;
85
85
use datafusion_physical_expr:: LexOrdering ;
86
+ use datafusion_physical_plan:: execution_plan:: InvariantLevel ;
86
87
use datafusion_physical_plan:: placeholder_row:: PlaceholderRowExec ;
87
88
use datafusion_physical_plan:: unnest:: ListUnnest ;
88
89
use datafusion_sql:: utils:: window_expr_common_partition_keys;
@@ -1875,6 +1876,10 @@ impl DefaultPhysicalPlanner {
1875
1876
displayable( plan. as_ref( ) ) . indent( true )
1876
1877
) ;
1877
1878
1879
+ // This runs once before any optimization,
1880
+ // to verify that the plan fulfills the base requirements.
1881
+ InvariantChecker ( InvariantLevel :: Always ) . check ( & plan) ?;
1882
+
1878
1883
let mut new_plan = Arc :: clone ( & plan) ;
1879
1884
for optimizer in optimizers {
1880
1885
let before_schema = new_plan. schema ( ) ;
@@ -1884,9 +1889,9 @@ impl DefaultPhysicalPlanner {
1884
1889
DataFusionError :: Context ( optimizer. name ( ) . to_string ( ) , Box :: new ( e) )
1885
1890
} ) ?;
1886
1891
1887
- // confirm optimizer change did not violate invariants
1888
- let mut validator = InvariantChecker :: new ( optimizer) ;
1889
- validator . check ( & new_plan, before_schema) ?;
1892
+ // This only checks the schema in release build, and performs additional checks in debug mode.
1893
+ OptimizationInvariantChecker :: new ( optimizer)
1894
+ . check ( & new_plan, before_schema) ?;
1890
1895
1891
1896
trace ! (
1892
1897
"Optimized physical plan by {}:\n {}\n " ,
@@ -1895,6 +1900,11 @@ impl DefaultPhysicalPlanner {
1895
1900
) ;
1896
1901
observer ( new_plan. as_ref ( ) , optimizer. as_ref ( ) )
1897
1902
}
1903
+
1904
+ // This runs once after all optimizer runs are complete,
1905
+ // to verify that the plan is executable.
1906
+ InvariantChecker ( InvariantLevel :: Executable ) . check ( & new_plan) ?;
1907
+
1898
1908
debug ! (
1899
1909
"Optimized physical plan:\n {}\n " ,
1900
1910
displayable( new_plan. as_ref( ) ) . indent( false )
@@ -2002,22 +2012,21 @@ fn tuple_err<T, R>(value: (Result<T>, Result<R>)) -> Result<(T, R)> {
2002
2012
}
2003
2013
}
2004
2014
2005
- /// Confirms that a given [`PhysicalOptimizerRule`] run
2006
- /// did not violate the [`ExecutionPlan`] invariants.
2007
- struct InvariantChecker < ' a > {
2015
+ struct OptimizationInvariantChecker < ' a > {
2008
2016
rule : & ' a Arc < dyn PhysicalOptimizerRule + Send + Sync > ,
2009
2017
}
2010
2018
2011
- impl < ' a > InvariantChecker < ' a > {
2012
- /// Create an [`InvariantChecker`] .
2019
+ impl < ' a > OptimizationInvariantChecker < ' a > {
2020
+ /// Create an [`OptimizationInvariantChecker`] that performs checking per tule .
2013
2021
pub fn new ( rule : & ' a Arc < dyn PhysicalOptimizerRule + Send + Sync > ) -> Self {
2014
2022
Self { rule }
2015
2023
}
2016
2024
2017
2025
/// Checks that the plan change is permitted, returning an Error if not.
2018
2026
///
2027
+ /// Conditionally performs schema checks per [PhysicalOptimizerRule::schema_check].
2019
2028
/// In debug mode, this recursively walks the entire physical plan
2020
- /// and performs [`ExecutionPlan::check_node_invariants `].
2029
+ /// and performs [`ExecutionPlan::check_invariants `].
2021
2030
pub fn check (
2022
2031
& mut self ,
2023
2032
plan : & Arc < dyn ExecutionPlan > ,
@@ -2032,19 +2041,48 @@ impl<'a> InvariantChecker<'a> {
2032
2041
) ?
2033
2042
}
2034
2043
2035
- // check invariants per ExecutionPlan extension
2044
+ // check invariants per each ExecutionPlan node
2036
2045
#[ cfg( debug_assertions) ]
2037
2046
plan. visit ( self ) ?;
2038
2047
2039
2048
Ok ( ( ) )
2040
2049
}
2041
2050
}
2042
2051
2043
- impl < ' n > TreeNodeVisitor < ' n > for InvariantChecker < ' _ > {
2052
+ impl < ' n > TreeNodeVisitor < ' n > for OptimizationInvariantChecker < ' _ > {
2044
2053
type Node = Arc < dyn ExecutionPlan > ;
2045
2054
2046
2055
fn f_down ( & mut self , node : & ' n Self :: Node ) -> Result < TreeNodeRecursion > {
2047
- node. check_node_invariants ( ) . map_err ( |e| e. context ( format ! ( "Invariant for ExecutionPlan node '{}' failed for PhysicalOptimizer rule '{}'" , node. name( ) , self . rule. name( ) ) ) ) ?;
2056
+ // Checks for the more permissive `InvariantLevel::Always`.
2057
+ // Plans are not guarenteed to be executable after each physical optimizer run.
2058
+ node. check_invariants ( InvariantLevel :: Always ) . map_err ( |e| e. context ( format ! ( "Invariant for ExecutionPlan node '{}' failed for PhysicalOptimizer rule '{}'" , node. name( ) , self . rule. name( ) ) ) ) ?;
2059
+ Ok ( TreeNodeRecursion :: Continue )
2060
+ }
2061
+ }
2062
+
2063
+ /// Check [`ExecutionPlan`] invariants per [`InvariantLevel`].
2064
+ struct InvariantChecker ( InvariantLevel ) ;
2065
+
2066
+ impl InvariantChecker {
2067
+ /// Checks that the plan is executable, returning an Error if not.
2068
+ pub fn check ( & mut self , plan : & Arc < dyn ExecutionPlan > ) -> Result < ( ) > {
2069
+ // check invariants per each ExecutionPlan node
2070
+ plan. visit ( self ) ?;
2071
+
2072
+ Ok ( ( ) )
2073
+ }
2074
+ }
2075
+
2076
+ impl < ' n > TreeNodeVisitor < ' n > for InvariantChecker {
2077
+ type Node = Arc < dyn ExecutionPlan > ;
2078
+
2079
+ fn f_down ( & mut self , node : & ' n Self :: Node ) -> Result < TreeNodeRecursion > {
2080
+ node. check_invariants ( self . 0 ) . map_err ( |e| {
2081
+ e. context ( format ! (
2082
+ "Invariant for ExecutionPlan node '{}' failed" ,
2083
+ node. name( )
2084
+ ) )
2085
+ } ) ?;
2048
2086
Ok ( TreeNodeRecursion :: Continue )
2049
2087
}
2050
2088
}
@@ -2864,15 +2902,18 @@ digraph {
2864
2902
}
2865
2903
}
2866
2904
2867
- /// Extension Node which fails invariant checks
2905
+ /// Extension Node which fails the [`OptimizationInvariantChecker`].
2868
2906
#[ derive( Debug ) ]
2869
2907
struct InvariantFailsExtensionNode ;
2870
2908
impl ExecutionPlan for InvariantFailsExtensionNode {
2871
2909
fn name ( & self ) -> & str {
2872
2910
"InvariantFailsExtensionNode"
2873
2911
}
2874
- fn check_node_invariants ( & self ) -> Result < ( ) > {
2875
- plan_err ! ( "extension node failed it's user-defined invariant check" )
2912
+ fn check_invariants ( & self , check : InvariantLevel ) -> Result < ( ) > {
2913
+ match check {
2914
+ InvariantLevel :: Always => plan_err ! ( "extension node failed it's user-defined always-invariant check" ) ,
2915
+ InvariantLevel :: Executable => panic ! ( "the OptimizationInvariantChecker should not be checking for executableness" ) ,
2916
+ }
2876
2917
}
2877
2918
fn schema ( & self ) -> SchemaRef {
2878
2919
Arc :: new ( Schema :: empty ( ) )
@@ -2926,7 +2967,7 @@ digraph {
2926
2967
}
2927
2968
2928
2969
#[ test]
2929
- fn test_invariant_checker ( ) -> Result < ( ) > {
2970
+ fn test_optimization_invariant_checker ( ) -> Result < ( ) > {
2930
2971
let rule: Arc < dyn PhysicalOptimizerRule + Send + Sync > =
2931
2972
Arc :: new ( OptimizerRuleWithSchemaCheck ) ;
2932
2973
@@ -2940,37 +2981,119 @@ digraph {
2940
2981
2941
2982
// Test: check should pass with same schema
2942
2983
let equal_schema = ok_plan. schema ( ) ;
2943
- InvariantChecker :: new ( & rule) . check ( & ok_plan, equal_schema) ?;
2984
+ OptimizationInvariantChecker :: new ( & rule) . check ( & ok_plan, equal_schema) ?;
2944
2985
2945
2986
// Test: should fail with schema changed
2946
2987
let different_schema =
2947
2988
Arc :: new ( Schema :: new ( vec ! [ Field :: new( "a" , DataType :: Boolean , false ) ] ) ) ;
2948
- let expected_err = InvariantChecker :: new ( & rule)
2989
+ let expected_err = OptimizationInvariantChecker :: new ( & rule)
2949
2990
. check ( & ok_plan, different_schema)
2950
2991
. unwrap_err ( ) ;
2951
2992
assert ! ( expected_err. to_string( ) . contains( "PhysicalOptimizer rule 'OptimizerRuleWithSchemaCheck' failed, due to generate a different schema" ) ) ;
2952
2993
2953
2994
// Test: should fail when extension node fails it's own invariant check
2954
2995
let failing_node: Arc < dyn ExecutionPlan > = Arc :: new ( InvariantFailsExtensionNode ) ;
2955
- let expected_err = InvariantChecker :: new ( & rule)
2996
+ let expected_err = OptimizationInvariantChecker :: new ( & rule)
2956
2997
. check ( & failing_node, ok_plan. schema ( ) )
2957
2998
. unwrap_err ( ) ;
2958
2999
assert ! ( expected_err
2959
3000
. to_string( )
2960
- . contains( "extension node failed it's user-defined invariant check" ) ) ;
3001
+ . contains( "extension node failed it's user-defined always- invariant check" ) ) ;
2961
3002
2962
3003
// Test: should fail when descendent extension node fails
2963
3004
let failing_node: Arc < dyn ExecutionPlan > = Arc :: new ( InvariantFailsExtensionNode ) ;
2964
3005
let invalid_plan = ok_node. with_new_children ( vec ! [
2965
3006
Arc :: clone( & child) . with_new_children( vec![ Arc :: clone( & failing_node) ] ) ?,
2966
3007
Arc :: clone( & child) ,
2967
3008
] ) ?;
2968
- let expected_err = InvariantChecker :: new ( & rule)
3009
+ let expected_err = OptimizationInvariantChecker :: new ( & rule)
2969
3010
. check ( & invalid_plan, ok_plan. schema ( ) )
2970
3011
. unwrap_err ( ) ;
2971
3012
assert ! ( expected_err
2972
3013
. to_string( )
2973
- . contains( "extension node failed it's user-defined invariant check" ) ) ;
3014
+ . contains( "extension node failed it's user-defined always-invariant check" ) ) ;
3015
+
3016
+ Ok ( ( ) )
3017
+ }
3018
+
3019
+ /// Extension Node which fails the [`InvariantChecker`]
3020
+ /// if, and only if, [`InvariantLevel::Executable`]
3021
+ #[ derive( Debug ) ]
3022
+ struct ExecutableInvariantFails ;
3023
+ impl ExecutionPlan for ExecutableInvariantFails {
3024
+ fn name ( & self ) -> & str {
3025
+ "ExecutableInvariantFails"
3026
+ }
3027
+ fn check_invariants ( & self , check : InvariantLevel ) -> Result < ( ) > {
3028
+ match check {
3029
+ InvariantLevel :: Always => Ok ( ( ) ) ,
3030
+ InvariantLevel :: Executable => plan_err ! (
3031
+ "extension node failed it's user-defined executable-invariant check"
3032
+ ) ,
3033
+ }
3034
+ }
3035
+ fn schema ( & self ) -> SchemaRef {
3036
+ Arc :: new ( Schema :: empty ( ) )
3037
+ }
3038
+ fn with_new_children (
3039
+ self : Arc < Self > ,
3040
+ _children : Vec < Arc < dyn ExecutionPlan > > ,
3041
+ ) -> Result < Arc < dyn ExecutionPlan > > {
3042
+ unimplemented ! ( )
3043
+ }
3044
+ fn as_any ( & self ) -> & dyn Any {
3045
+ unimplemented ! ( )
3046
+ }
3047
+ fn children ( & self ) -> Vec < & Arc < dyn ExecutionPlan > > {
3048
+ vec ! [ ]
3049
+ }
3050
+ fn properties ( & self ) -> & PlanProperties {
3051
+ unimplemented ! ( )
3052
+ }
3053
+ fn execute (
3054
+ & self ,
3055
+ _partition : usize ,
3056
+ _context : Arc < TaskContext > ,
3057
+ ) -> Result < SendableRecordBatchStream > {
3058
+ unimplemented ! ( )
3059
+ }
3060
+ }
3061
+ impl DisplayAs for ExecutableInvariantFails {
3062
+ fn fmt_as ( & self , _t : DisplayFormatType , f : & mut fmt:: Formatter ) -> fmt:: Result {
3063
+ write ! ( f, "{}" , self . name( ) )
3064
+ }
3065
+ }
3066
+
3067
+ #[ test]
3068
+ fn test_invariant_checker_levels ( ) -> Result < ( ) > {
3069
+ // plan that passes the always-invariant, but fails the executable check
3070
+ let plan: Arc < dyn ExecutionPlan > = Arc :: new ( ExecutableInvariantFails ) ;
3071
+
3072
+ // Test: check should pass with less stringent Always check
3073
+ InvariantChecker ( InvariantLevel :: Always ) . check ( & plan) ?;
3074
+
3075
+ // Test: should fail the executable check
3076
+ let expected_err = InvariantChecker ( InvariantLevel :: Executable )
3077
+ . check ( & plan)
3078
+ . unwrap_err ( ) ;
3079
+ assert ! ( expected_err. to_string( ) . contains(
3080
+ "extension node failed it's user-defined executable-invariant check"
3081
+ ) ) ;
3082
+
3083
+ // Test: should fail when descendent extension node fails
3084
+ let failing_node: Arc < dyn ExecutionPlan > = Arc :: new ( ExecutableInvariantFails ) ;
3085
+ let ok_node: Arc < dyn ExecutionPlan > = Arc :: new ( OkExtensionNode ( vec ! [ ] ) ) ;
3086
+ let child = Arc :: clone ( & ok_node) ;
3087
+ let plan = ok_node. with_new_children ( vec ! [
3088
+ Arc :: clone( & child) . with_new_children( vec![ Arc :: clone( & failing_node) ] ) ?,
3089
+ Arc :: clone( & child) ,
3090
+ ] ) ?;
3091
+ let expected_err = InvariantChecker ( InvariantLevel :: Executable )
3092
+ . check ( & plan)
3093
+ . unwrap_err ( ) ;
3094
+ assert ! ( expected_err. to_string( ) . contains(
3095
+ "extension node failed it's user-defined executable-invariant check"
3096
+ ) ) ;
2974
3097
2975
3098
Ok ( ( ) )
2976
3099
}
0 commit comments