@@ -31,6 +31,7 @@ use arrow::compute::kernels::boolean::{not, or_kleene};
3131use arrow:: compute:: kernels:: cmp:: eq as arrow_eq;
3232use arrow:: datatypes:: * ;
3333
34+ use datafusion_common:: utils:: { normalize_float_zero, normalize_float_zero_scalar} ;
3435use datafusion_common:: {
3536 DFSchema , Result , ScalarValue , assert_or_internal_err, exec_err,
3637} ;
@@ -82,6 +83,27 @@ fn supports_arrow_eq(dt: &DataType) -> bool {
8283 }
8384}
8485
86+ fn normalize_in_list_float_zero_value ( value : ColumnarValue ) -> ColumnarValue {
87+ match value {
88+ ColumnarValue :: Array ( array)
89+ if is_float_or_dictionary_float ( array. data_type ( ) ) =>
90+ {
91+ ColumnarValue :: Array ( normalize_float_zero ( & array) )
92+ }
93+ ColumnarValue :: Scalar ( scalar) => {
94+ ColumnarValue :: Scalar ( normalize_float_zero_scalar ( scalar) )
95+ }
96+ value => value,
97+ }
98+ }
99+
100+ fn is_float_or_dictionary_float ( mut data_type : & DataType ) -> bool {
101+ while let DataType :: Dictionary ( _, value_type) = data_type {
102+ data_type = value_type;
103+ }
104+ data_type. is_floating ( )
105+ }
106+
85107/// Evaluates the list of expressions into an array, flattening any dictionaries
86108fn evaluate_list (
87109 list : & [ Arc < dyn PhysicalExpr > ] ,
@@ -370,12 +392,15 @@ impl PhysicalExpr for InListExpr {
370392 // Use Arrow's vectorized eq kernel for types it supports (primitive,
371393 // boolean, string, binary, dictionary), falling back to row-by-row
372394 // comparator for unsupported types (nested, RunEndEncoded, etc.).
373- let value = value. into_array ( num_rows) ?;
395+ // Normalize the left side once for the whole list. Doing this
396+ // outside `compare_one` avoids rescanning it for every item.
397+ let value =
398+ normalize_in_list_float_zero_value ( value) . into_array ( num_rows) ?;
374399 let lhs_supports_arrow_eq = supports_arrow_eq ( value. data_type ( ) ) ;
375400
376401 // Helper: compare value against a single list expression
377402 let compare_one = |expr : & Arc < dyn PhysicalExpr > | -> Result < BooleanArray > {
378- match expr. evaluate ( batch) ? {
403+ match normalize_in_list_float_zero_value ( expr. evaluate ( batch) ?) {
379404 ColumnarValue :: Array ( array) => {
380405 if lhs_supports_arrow_eq
381406 && supports_arrow_eq ( array. data_type ( ) )
@@ -3364,6 +3389,111 @@ mod tests {
33643389 Ok ( ( ) )
33653390 }
33663391
3392+ #[ test]
3393+ fn test_in_list_with_columns_float_signed_zero ( ) -> Result < ( ) > {
3394+ let schema = Schema :: new ( vec ! [
3395+ Field :: new( "a" , DataType :: Float64 , false ) ,
3396+ Field :: new( "b" , DataType :: Float64 , false ) ,
3397+ ] ) ;
3398+ let batch = RecordBatch :: try_new (
3399+ Arc :: new ( schema. clone ( ) ) ,
3400+ vec ! [
3401+ Arc :: new( Float64Array :: from( vec![ 0.0 , -0.0 , 1.0 ] ) ) ,
3402+ Arc :: new( Float64Array :: from( vec![ -0.0 , 0.0 , 2.0 ] ) ) ,
3403+ ] ,
3404+ ) ?;
3405+
3406+ let expr = make_in_list_with_columns (
3407+ col ( "a" , & schema) ?,
3408+ vec ! [ col( "b" , & schema) ?] ,
3409+ false ,
3410+ ) ;
3411+ let result = expr. evaluate ( & batch) ?. into_array ( batch. num_rows ( ) ) ?;
3412+ assert_eq ! (
3413+ as_boolean_array( & result) ,
3414+ & BooleanArray :: from( vec![ true , true , false ] )
3415+ ) ;
3416+ Ok ( ( ) )
3417+ }
3418+
3419+ #[ test]
3420+ fn test_in_list_with_columns_float_scalar_signed_zero ( ) -> Result < ( ) > {
3421+ let schema = Schema :: new ( vec ! [ Field :: new( "a" , DataType :: Float32 , false ) ] ) ;
3422+ let batch = RecordBatch :: try_new (
3423+ Arc :: new ( schema. clone ( ) ) ,
3424+ vec ! [ Arc :: new( Float32Array :: from( vec![ 0.0 , -0.0 , 1.0 ] ) ) ] ,
3425+ ) ?;
3426+ let list = vec ! [ lit( ScalarValue :: Float32 ( Some ( -0.0 ) ) ) ] ;
3427+
3428+ for ( negated, expected) in [
3429+ ( false , BooleanArray :: from ( vec ! [ true , true , false ] ) ) ,
3430+ ( true , BooleanArray :: from ( vec ! [ false , false , true ] ) ) ,
3431+ ] {
3432+ let expr =
3433+ make_in_list_with_columns ( col ( "a" , & schema) ?, list. clone ( ) , negated) ;
3434+ let result = expr. evaluate ( & batch) ?. into_array ( batch. num_rows ( ) ) ?;
3435+ assert_eq ! ( as_boolean_array( & result) , & expected) ;
3436+ }
3437+
3438+ // A scalar left-hand side is normalized before it is broadcast.
3439+ let expr = make_in_list_with_columns (
3440+ lit ( ScalarValue :: Float32 ( Some ( -0.0 ) ) ) ,
3441+ vec ! [ col( "a" , & schema) ?] ,
3442+ false ,
3443+ ) ;
3444+ let result = expr. evaluate ( & batch) ?. into_array ( batch. num_rows ( ) ) ?;
3445+ assert_eq ! (
3446+ as_boolean_array( & result) ,
3447+ & BooleanArray :: from( vec![ true , true , false ] )
3448+ ) ;
3449+
3450+ Ok ( ( ) )
3451+ }
3452+
3453+ #[ test]
3454+ fn test_in_list_with_columns_dictionary_float_signed_zero ( ) -> Result < ( ) > {
3455+ let left: ArrayRef = Arc :: new ( DictionaryArray :: try_new (
3456+ Int8Array :: from ( vec ! [ 0 , 1 , 2 ] ) ,
3457+ Arc :: new ( Float64Array :: from ( vec ! [ 0.0 , -0.0 , 1.0 ] ) ) ,
3458+ ) ?) ;
3459+ let right: ArrayRef = Arc :: new ( DictionaryArray :: try_new (
3460+ Int8Array :: from ( vec ! [ 0 , 1 , 2 ] ) ,
3461+ Arc :: new ( Float64Array :: from ( vec ! [ -0.0 , 0.0 , 2.0 ] ) ) ,
3462+ ) ?) ;
3463+ let data_type = left. data_type ( ) . clone ( ) ;
3464+ let schema = Schema :: new ( vec ! [
3465+ Field :: new( "a" , data_type. clone( ) , false ) ,
3466+ Field :: new( "b" , data_type, false ) ,
3467+ ] ) ;
3468+ let batch = RecordBatch :: try_new ( Arc :: new ( schema. clone ( ) ) , vec ! [ left, right] ) ?;
3469+
3470+ for ( negated, expected) in [
3471+ ( false , BooleanArray :: from ( vec ! [ true , true , false ] ) ) ,
3472+ ( true , BooleanArray :: from ( vec ! [ false , false , true ] ) ) ,
3473+ ] {
3474+ let expr = make_in_list_with_columns (
3475+ col ( "a" , & schema) ?,
3476+ vec ! [ col( "b" , & schema) ?] ,
3477+ negated,
3478+ ) ;
3479+ let result = expr. evaluate ( & batch) ?. into_array ( batch. num_rows ( ) ) ?;
3480+ assert_eq ! ( as_boolean_array( & result) , & expected) ;
3481+ }
3482+
3483+ let scalar = lit ( ScalarValue :: Dictionary (
3484+ Box :: new ( DataType :: Int8 ) ,
3485+ Box :: new ( ScalarValue :: Float64 ( Some ( -0.0 ) ) ) ,
3486+ ) ) ;
3487+ let expr = make_in_list_with_columns ( col ( "a" , & schema) ?, vec ! [ scalar] , false ) ;
3488+ let result = expr. evaluate ( & batch) ?. into_array ( batch. num_rows ( ) ) ?;
3489+ assert_eq ! (
3490+ as_boolean_array( & result) ,
3491+ & BooleanArray :: from( vec![ true , true , false ] )
3492+ ) ;
3493+
3494+ Ok ( ( ) )
3495+ }
3496+
33673497 /// Tests that short-circuit evaluation produces correct results.
33683498 /// When all rows match after the first list item, remaining items
33693499 /// should be skipped without affecting correctness.
@@ -3889,6 +4019,29 @@ mod tests {
38894019 Ok ( ( ) )
38904020 }
38914021
4022+ #[ test]
4023+ fn test_try_new_from_array_dict_haystack_float64_signed_zero ( ) -> Result < ( ) > {
4024+ // One value beyond the branchless limit selects the hash-set strategy.
4025+ let list_len =
4026+ <Float64Type as branchless_filter:: BranchlessFilterType >:: MAX_LIST_LEN + 1 ;
4027+ let mut list_values = vec ! [ Some ( -0.0 ) ] ;
4028+ list_values. extend ( ( 1 ..list_len) . map ( |value| Some ( value as f64 ) ) ) ;
4029+ let haystack = make_f64_dict_array ( list_values) ;
4030+ let needles: ArrayRef = Arc :: new ( Float64Array :: from ( vec ! [ 0.0 , -0.0 , -1.0 ] ) ) ;
4031+ let expected = BooleanArray :: from ( vec ! [ true , true , false ] ) ;
4032+
4033+ assert_eq ! (
4034+ eval_in_list_from_array( Arc :: clone( & needles) , Arc :: clone( & haystack) ) ?,
4035+ expected
4036+ ) ;
4037+ assert_eq ! (
4038+ eval_in_list_from_array( wrap_in_dict( needles) , haystack) ?,
4039+ expected
4040+ ) ;
4041+
4042+ Ok ( ( ) )
4043+ }
4044+
38924045 #[ test]
38934046 fn test_try_new_from_array_type_mismatch_rejects ( ) -> Result < ( ) > {
38944047 let schema = Schema :: new ( vec ! [ Field :: new( "a" , DataType :: Int32 , false ) ] ) ;
0 commit comments