@@ -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} ;
@@ -81,6 +82,27 @@ fn supports_arrow_eq(dt: &DataType) -> bool {
8182 }
8283}
8384
85+ fn normalize_in_list_float_zero_value ( value : ColumnarValue ) -> ColumnarValue {
86+ match value {
87+ ColumnarValue :: Array ( array)
88+ if is_float_or_dictionary_float ( array. data_type ( ) ) =>
89+ {
90+ ColumnarValue :: Array ( normalize_float_zero ( & array) )
91+ }
92+ ColumnarValue :: Scalar ( scalar) => {
93+ ColumnarValue :: Scalar ( normalize_float_zero_scalar ( scalar) )
94+ }
95+ value => value,
96+ }
97+ }
98+
99+ fn is_float_or_dictionary_float ( mut data_type : & DataType ) -> bool {
100+ while let DataType :: Dictionary ( _, value_type) = data_type {
101+ data_type = value_type;
102+ }
103+ data_type. is_floating ( )
104+ }
105+
84106/// Evaluates the list of expressions into an array, flattening any dictionaries
85107fn evaluate_list (
86108 list : & [ Arc < dyn PhysicalExpr > ] ,
@@ -369,12 +391,15 @@ impl PhysicalExpr for InListExpr {
369391 // Use Arrow's vectorized eq kernel for types it supports (primitive,
370392 // boolean, string, binary, dictionary), falling back to row-by-row
371393 // comparator for unsupported types (nested, RunEndEncoded, etc.).
372- let value = value. into_array ( num_rows) ?;
394+ // Normalize the left side once for the whole list. Doing this
395+ // outside `compare_one` avoids rescanning it for every item.
396+ let value =
397+ normalize_in_list_float_zero_value ( value) . into_array ( num_rows) ?;
373398 let lhs_supports_arrow_eq = supports_arrow_eq ( value. data_type ( ) ) ;
374399
375400 // Helper: compare value against a single list expression
376401 let compare_one = |expr : & Arc < dyn PhysicalExpr > | -> Result < BooleanArray > {
377- match expr. evaluate ( batch) ? {
402+ match normalize_in_list_float_zero_value ( expr. evaluate ( batch) ?) {
378403 ColumnarValue :: Array ( array) => {
379404 if lhs_supports_arrow_eq
380405 && supports_arrow_eq ( array. data_type ( ) )
@@ -3363,6 +3388,111 @@ mod tests {
33633388 Ok ( ( ) )
33643389 }
33653390
3391+ #[ test]
3392+ fn test_in_list_with_columns_float_signed_zero ( ) -> Result < ( ) > {
3393+ let schema = Schema :: new ( vec ! [
3394+ Field :: new( "a" , DataType :: Float64 , false ) ,
3395+ Field :: new( "b" , DataType :: Float64 , false ) ,
3396+ ] ) ;
3397+ let batch = RecordBatch :: try_new (
3398+ Arc :: new ( schema. clone ( ) ) ,
3399+ vec ! [
3400+ Arc :: new( Float64Array :: from( vec![ 0.0 , -0.0 , 1.0 ] ) ) ,
3401+ Arc :: new( Float64Array :: from( vec![ -0.0 , 0.0 , 2.0 ] ) ) ,
3402+ ] ,
3403+ ) ?;
3404+
3405+ let expr = make_in_list_with_columns (
3406+ col ( "a" , & schema) ?,
3407+ vec ! [ col( "b" , & schema) ?] ,
3408+ false ,
3409+ ) ;
3410+ let result = expr. evaluate ( & batch) ?. into_array ( batch. num_rows ( ) ) ?;
3411+ assert_eq ! (
3412+ as_boolean_array( & result) ,
3413+ & BooleanArray :: from( vec![ true , true , false ] )
3414+ ) ;
3415+ Ok ( ( ) )
3416+ }
3417+
3418+ #[ test]
3419+ fn test_in_list_with_columns_float_scalar_signed_zero ( ) -> Result < ( ) > {
3420+ let schema = Schema :: new ( vec ! [ Field :: new( "a" , DataType :: Float32 , false ) ] ) ;
3421+ let batch = RecordBatch :: try_new (
3422+ Arc :: new ( schema. clone ( ) ) ,
3423+ vec ! [ Arc :: new( Float32Array :: from( vec![ 0.0 , -0.0 , 1.0 ] ) ) ] ,
3424+ ) ?;
3425+ let list = vec ! [ lit( ScalarValue :: Float32 ( Some ( -0.0 ) ) ) ] ;
3426+
3427+ for ( negated, expected) in [
3428+ ( false , BooleanArray :: from ( vec ! [ true , true , false ] ) ) ,
3429+ ( true , BooleanArray :: from ( vec ! [ false , false , true ] ) ) ,
3430+ ] {
3431+ let expr =
3432+ make_in_list_with_columns ( col ( "a" , & schema) ?, list. clone ( ) , negated) ;
3433+ let result = expr. evaluate ( & batch) ?. into_array ( batch. num_rows ( ) ) ?;
3434+ assert_eq ! ( as_boolean_array( & result) , & expected) ;
3435+ }
3436+
3437+ // A scalar left-hand side is normalized before it is broadcast.
3438+ let expr = make_in_list_with_columns (
3439+ lit ( ScalarValue :: Float32 ( Some ( -0.0 ) ) ) ,
3440+ vec ! [ col( "a" , & schema) ?] ,
3441+ false ,
3442+ ) ;
3443+ let result = expr. evaluate ( & batch) ?. into_array ( batch. num_rows ( ) ) ?;
3444+ assert_eq ! (
3445+ as_boolean_array( & result) ,
3446+ & BooleanArray :: from( vec![ true , true , false ] )
3447+ ) ;
3448+
3449+ Ok ( ( ) )
3450+ }
3451+
3452+ #[ test]
3453+ fn test_in_list_with_columns_dictionary_float_signed_zero ( ) -> Result < ( ) > {
3454+ let left: ArrayRef = Arc :: new ( DictionaryArray :: try_new (
3455+ Int8Array :: from ( vec ! [ 0 , 1 , 2 ] ) ,
3456+ Arc :: new ( Float64Array :: from ( vec ! [ 0.0 , -0.0 , 1.0 ] ) ) ,
3457+ ) ?) ;
3458+ let right: ArrayRef = Arc :: new ( DictionaryArray :: try_new (
3459+ Int8Array :: from ( vec ! [ 0 , 1 , 2 ] ) ,
3460+ Arc :: new ( Float64Array :: from ( vec ! [ -0.0 , 0.0 , 2.0 ] ) ) ,
3461+ ) ?) ;
3462+ let data_type = left. data_type ( ) . clone ( ) ;
3463+ let schema = Schema :: new ( vec ! [
3464+ Field :: new( "a" , data_type. clone( ) , false ) ,
3465+ Field :: new( "b" , data_type, false ) ,
3466+ ] ) ;
3467+ let batch = RecordBatch :: try_new ( Arc :: new ( schema. clone ( ) ) , vec ! [ left, right] ) ?;
3468+
3469+ for ( negated, expected) in [
3470+ ( false , BooleanArray :: from ( vec ! [ true , true , false ] ) ) ,
3471+ ( true , BooleanArray :: from ( vec ! [ false , false , true ] ) ) ,
3472+ ] {
3473+ let expr = make_in_list_with_columns (
3474+ col ( "a" , & schema) ?,
3475+ vec ! [ col( "b" , & schema) ?] ,
3476+ negated,
3477+ ) ;
3478+ let result = expr. evaluate ( & batch) ?. into_array ( batch. num_rows ( ) ) ?;
3479+ assert_eq ! ( as_boolean_array( & result) , & expected) ;
3480+ }
3481+
3482+ let scalar = lit ( ScalarValue :: Dictionary (
3483+ Box :: new ( DataType :: Int8 ) ,
3484+ Box :: new ( ScalarValue :: Float64 ( Some ( -0.0 ) ) ) ,
3485+ ) ) ;
3486+ let expr = make_in_list_with_columns ( col ( "a" , & schema) ?, vec ! [ scalar] , false ) ;
3487+ let result = expr. evaluate ( & batch) ?. into_array ( batch. num_rows ( ) ) ?;
3488+ assert_eq ! (
3489+ as_boolean_array( & result) ,
3490+ & BooleanArray :: from( vec![ true , true , false ] )
3491+ ) ;
3492+
3493+ Ok ( ( ) )
3494+ }
3495+
33663496 /// Tests that short-circuit evaluation produces correct results.
33673497 /// When all rows match after the first list item, remaining items
33683498 /// should be skipped without affecting correctness.
@@ -3871,6 +4001,29 @@ mod tests {
38714001 Ok ( ( ) )
38724002 }
38734003
4004+ #[ test]
4005+ fn test_try_new_from_array_dict_haystack_float64_signed_zero ( ) -> Result < ( ) > {
4006+ // One value beyond the branchless limit selects the hash-set strategy.
4007+ let list_len =
4008+ <Float64Type as branchless_filter:: BranchlessFilterType >:: MAX_LIST_LEN + 1 ;
4009+ let mut list_values = vec ! [ Some ( -0.0 ) ] ;
4010+ list_values. extend ( ( 1 ..list_len) . map ( |value| Some ( value as f64 ) ) ) ;
4011+ let haystack = make_f64_dict_array ( list_values) ;
4012+ let needles: ArrayRef = Arc :: new ( Float64Array :: from ( vec ! [ 0.0 , -0.0 , -1.0 ] ) ) ;
4013+ let expected = BooleanArray :: from ( vec ! [ true , true , false ] ) ;
4014+
4015+ assert_eq ! (
4016+ eval_in_list_from_array( Arc :: clone( & needles) , Arc :: clone( & haystack) ) ?,
4017+ expected
4018+ ) ;
4019+ assert_eq ! (
4020+ eval_in_list_from_array( wrap_in_dict( needles) , haystack) ?,
4021+ expected
4022+ ) ;
4023+
4024+ Ok ( ( ) )
4025+ }
4026+
38744027 #[ test]
38754028 fn test_try_new_from_array_type_mismatch_rejects ( ) -> Result < ( ) > {
38764029 let schema = Schema :: new ( vec ! [ Field :: new( "a" , DataType :: Int32 , false ) ] ) ;
0 commit comments