@@ -1415,14 +1415,39 @@ fn build_predicate_expression(
14151415 . unwrap_or_else ( || unhandled_hook. handle ( expr) ) ;
14161416 }
14171417 if let Some ( not) = expr_any. downcast_ref :: < phys_expr:: NotExpr > ( ) {
1418- // match !col (don't do so recursively)
14191418 if let Some ( col) = not. arg ( ) . as_any ( ) . downcast_ref :: < phys_expr:: Column > ( ) {
14201419 return build_single_column_expr ( col, schema, required_columns, true )
14211420 . unwrap_or_else ( || unhandled_hook. handle ( expr) ) ;
1422- } else {
1421+ }
1422+
1423+ let inner_expr = build_predicate_expression (
1424+ not. arg ( ) ,
1425+ schema,
1426+ required_columns,
1427+ unhandled_hook,
1428+ ) ;
1429+
1430+ // Only apply NOT if the inner expression is NOT a true literal
1431+ // (because true literals may come from unhandled cases)
1432+ if is_always_true ( & inner_expr) {
1433+ // Conservative approach: if inner returns true (possibly unhandled),
1434+ // then NOT should also return true (unhandled) to be safe
14231435 return unhandled_hook. handle ( expr) ;
14241436 }
1437+
1438+ // Handle other boolean literals
1439+ if let Some ( literal) = inner_expr. as_any ( ) . downcast_ref :: < phys_expr:: Literal > ( ) {
1440+ if let ScalarValue :: Boolean ( Some ( val) ) = literal. value ( ) {
1441+ return Arc :: new ( phys_expr:: Literal :: new ( ScalarValue :: Boolean ( Some (
1442+ !val,
1443+ ) ) ) ) ;
1444+ }
1445+ }
1446+
1447+ // Apply NOT to the result
1448+ return Arc :: new ( phys_expr:: NotExpr :: new ( inner_expr) ) ;
14251449 }
1450+
14261451 if let Some ( in_list) = expr_any. downcast_ref :: < phys_expr:: InListExpr > ( ) {
14271452 if !in_list. list ( ) . is_empty ( )
14281453 && in_list. list ( ) . len ( ) <= MAX_LIST_VALUE_SIZE_REWRITE
@@ -1868,7 +1893,7 @@ mod tests {
18681893
18691894 use super :: * ;
18701895 use datafusion_common:: test_util:: batches_to_string;
1871- use datafusion_expr:: { and, col, lit, or} ;
1896+ use datafusion_expr:: { and, col, lit, not , or} ;
18721897 use insta:: assert_snapshot;
18731898
18741899 use arrow:: array:: Decimal128Array ;
@@ -4422,7 +4447,7 @@ mod tests {
44224447 true ,
44234448 // s1 ["AB", "A\u{10ffff}\u{10ffff}\u{10ffff}"] ==> some rows could pass (must keep)
44244449 true ,
4425- // s1 ["A\u{10ffff}\u{10ffff}", "A\u{10ffff}\u{10ffff}"] ==> no row match. (min, max) maybe truncate
4450+ // s1 ["A\u{10ffff}\u{10ffff}", "A\u{10ffff}\u{10ffff}"] ==> no row match. (min, max) maybe truncate
44264451 // original (min, max) maybe ("A\u{10ffff}\u{10ffff}\u{10ffff}", "A\u{10ffff}\u{10ffff}\u{10ffff}\u{10ffff}")
44274452 true ,
44284453 ] ;
@@ -5175,4 +5200,66 @@ mod tests {
51755200 "c1_null_count@2 != row_count@3 AND c1_min@0 <= a AND a <= c1_max@1" ;
51765201 assert_eq ! ( res. to_string( ) , expected) ;
51775202 }
5203+
5204+ #[ test]
5205+ fn test_not_expression_unhandled_inner_true ( ) -> Result < ( ) > {
5206+ // Test case: when inner expression returns true (unhandled),
5207+ // NOT should also return true (unhandled) for safety
5208+ let schema = Schema :: new ( vec ! [ Field :: new( "c1" , DataType :: Int32 , false ) ] ) ;
5209+
5210+ // NOT(c1) for Int32 returns true because build_single_column_expr
5211+ // only handles boolean columns, so non-boolean columns fall back to unhandled_hook
5212+ let expr = not ( col ( "c1" ) ) ;
5213+ let predicate_expr =
5214+ test_build_predicate_expression ( & expr, & schema, & mut RequiredColumns :: new ( ) ) ;
5215+ assert_eq ! ( predicate_expr. to_string( ) , "true" ) ;
5216+ Ok ( ( ) )
5217+ }
5218+
5219+ #[ test]
5220+ fn test_not_expression_boolean_literal_handling ( ) -> Result < ( ) > {
5221+ let schema = Schema :: empty ( ) ;
5222+
5223+ // NOT(false) -> true
5224+ let expr = not ( lit ( false ) ) ;
5225+ let predicate_expr =
5226+ test_build_predicate_expression ( & expr, & schema, & mut RequiredColumns :: new ( ) ) ;
5227+ assert_eq ! ( predicate_expr. to_string( ) , "true" ) ;
5228+
5229+ // NOT(true) -> true (conservatively)
5230+ let expr = not ( lit ( true ) ) ;
5231+ let predicate_expr =
5232+ test_build_predicate_expression ( & expr, & schema, & mut RequiredColumns :: new ( ) ) ;
5233+ assert_eq ! ( predicate_expr. to_string( ) , "true" ) ;
5234+
5235+ Ok ( ( ) )
5236+ }
5237+
5238+ #[ test]
5239+ fn test_not_expression_wraps_complex_expressions ( ) -> Result < ( ) > {
5240+ let schema = Schema :: new ( vec ! [ Field :: new( "c1" , DataType :: Int32 , false ) ] ) ;
5241+
5242+ let expr = not ( col ( "c1" ) . gt ( lit ( 5 ) ) ) ;
5243+ let predicate_expr =
5244+ test_build_predicate_expression ( & expr, & schema, & mut RequiredColumns :: new ( ) ) ;
5245+
5246+ let result_str = predicate_expr. to_string ( ) ;
5247+ assert_eq ! (
5248+ result_str,
5249+ "NOT c1_null_count@1 != row_count@2 AND c1_max@0 > 5"
5250+ ) ;
5251+
5252+ // NOT(c1 = 10)
5253+ let expr = not ( col ( "c1" ) . eq ( lit ( 10 ) ) ) ;
5254+ let predicate_expr =
5255+ test_build_predicate_expression ( & expr, & schema, & mut RequiredColumns :: new ( ) ) ;
5256+
5257+ let result_str = predicate_expr. to_string ( ) ;
5258+ assert_eq ! (
5259+ result_str,
5260+ "NOT c1_null_count@2 != row_count@3 AND c1_min@0 <= 10 AND 10 <= c1_max@1"
5261+ ) ;
5262+
5263+ Ok ( ( ) )
5264+ }
51785265}
0 commit comments