@@ -1140,6 +1140,12 @@ impl OptimizerRule for PushDownFilter {
1140
1140
} )
1141
1141
}
1142
1142
LogicalPlan :: Extension ( extension_plan) => {
1143
+ // This check prevents the Filter from being removed when the extension node has no children,
1144
+ // so we return the original Filter unchanged.
1145
+ if extension_plan. node . inputs ( ) . is_empty ( ) {
1146
+ filter. input = Arc :: new ( LogicalPlan :: Extension ( extension_plan) ) ;
1147
+ return Ok ( Transformed :: no ( LogicalPlan :: Filter ( filter) ) ) ;
1148
+ }
1143
1149
let prevent_cols =
1144
1150
extension_plan. node . prevent_predicate_push_down_columns ( ) ;
1145
1151
@@ -3786,4 +3792,83 @@ Projection: a, b
3786
3792
\n TableScan: test";
3787
3793
assert_optimized_plan_eq ( plan, expected_after)
3788
3794
}
3795
+
3796
+ #[ test]
3797
+ fn test_push_down_filter_to_user_defined_node ( ) -> Result < ( ) > {
3798
+ // Define a custom user-defined logical node
3799
+ #[ derive( Debug , Hash , Eq , PartialEq ) ]
3800
+ struct TestUserNode {
3801
+ schema : DFSchemaRef ,
3802
+ }
3803
+
3804
+ impl PartialOrd for TestUserNode {
3805
+ fn partial_cmp ( & self , _other : & Self ) -> Option < Ordering > {
3806
+ None
3807
+ }
3808
+ }
3809
+
3810
+ impl TestUserNode {
3811
+ fn new ( ) -> Self {
3812
+ let schema = Arc :: new (
3813
+ DFSchema :: new_with_metadata (
3814
+ vec ! [ ( None , Field :: new( "a" , DataType :: Int64 , false ) . into( ) ) ] ,
3815
+ Default :: default ( ) ,
3816
+ )
3817
+ . unwrap ( ) ,
3818
+ ) ;
3819
+
3820
+ Self { schema }
3821
+ }
3822
+ }
3823
+
3824
+ impl UserDefinedLogicalNodeCore for TestUserNode {
3825
+ fn name ( & self ) -> & str {
3826
+ "test_node"
3827
+ }
3828
+
3829
+ fn inputs ( & self ) -> Vec < & LogicalPlan > {
3830
+ vec ! [ ]
3831
+ }
3832
+
3833
+ fn schema ( & self ) -> & DFSchemaRef {
3834
+ & self . schema
3835
+ }
3836
+
3837
+ fn expressions ( & self ) -> Vec < Expr > {
3838
+ vec ! [ ]
3839
+ }
3840
+
3841
+ fn fmt_for_explain ( & self , f : & mut Formatter ) -> std:: fmt:: Result {
3842
+ write ! ( f, "TestUserNode" )
3843
+ }
3844
+
3845
+ fn with_exprs_and_inputs (
3846
+ & self ,
3847
+ exprs : Vec < Expr > ,
3848
+ inputs : Vec < LogicalPlan > ,
3849
+ ) -> Result < Self > {
3850
+ assert ! ( exprs. is_empty( ) ) ;
3851
+ assert ! ( inputs. is_empty( ) ) ;
3852
+ Ok ( Self {
3853
+ schema : Arc :: clone ( & self . schema ) ,
3854
+ } )
3855
+ }
3856
+ }
3857
+
3858
+ // Create a node and build a plan with a filter
3859
+ let node = LogicalPlan :: Extension ( Extension {
3860
+ node : Arc :: new ( TestUserNode :: new ( ) ) ,
3861
+ } ) ;
3862
+
3863
+ let plan = LogicalPlanBuilder :: from ( node) . filter ( lit ( false ) ) ?. build ( ) ?;
3864
+
3865
+ // Check the original plan format (not part of the test assertions)
3866
+ let expected_before = "Filter: Boolean(false)\
3867
+ \n TestUserNode";
3868
+ assert_eq ! ( format!( "{plan}" ) , expected_before) ;
3869
+
3870
+ // Check that the filter is pushed down to the user-defined node
3871
+ let expected_after = "Filter: Boolean(false)\n TestUserNode" ;
3872
+ assert_optimized_plan_eq ( plan, expected_after)
3873
+ }
3789
3874
}
0 commit comments