1717
1818use std:: sync:: Arc ;
1919
20- use arrow:: array:: { ArrayRef , AsArray , Float16Array , Float32Array , Float64Array } ;
20+ use arrow:: array:: builder:: NullBufferBuilder ;
21+ use arrow:: array:: { Array , ArrayRef , AsArray , PrimitiveArray } ;
2122use arrow:: datatypes:: DataType :: { Float16 , Float32 , Float64 } ;
22- use arrow:: datatypes:: { DataType , Float16Type , Float32Type , Float64Type } ;
23+ use arrow:: datatypes:: {
24+ ArrowPrimitiveType , DataType , Float16Type , Float32Type , Float64Type ,
25+ } ;
2326use datafusion_common:: types:: { NativeType , logical_float64} ;
2427use datafusion_common:: { Result , ScalarValue , exec_err, utils:: take_function_args} ;
2528use datafusion_expr:: {
2629 Coercion , ColumnarValue , Documentation , ScalarFunctionArgs , ScalarUDFImpl , Signature ,
2730 TypeSignature , TypeSignatureClass , Volatility ,
2831} ;
2932use datafusion_macros:: user_doc;
33+ use num_traits:: Float ;
3034
3135#[ user_doc(
3236 doc_section( label = "Math Functions" ) ,
@@ -148,46 +152,80 @@ fn scalar_is_nan(scalar: &ScalarValue) -> bool {
148152/// - otherwise -> output is x (which may itself be NULL)
149153fn nanvl ( args : & [ ArrayRef ] ) -> Result < ArrayRef > {
150154 match args[ 0 ] . data_type ( ) {
151- Float64 => {
152- let x = args[ 0 ] . as_primitive :: < Float64Type > ( ) ;
153- let y = args[ 1 ] . as_primitive :: < Float64Type > ( ) ;
154- let result: Float64Array = x
155- . iter ( )
156- . zip ( y. iter ( ) )
157- . map ( |( x_value, y_value) | match x_value {
158- Some ( x_value) if x_value. is_nan ( ) => y_value,
159- _ => x_value,
160- } )
161- . collect ( ) ;
162- Ok ( Arc :: new ( result) as ArrayRef )
163- }
164- Float32 => {
165- let x = args[ 0 ] . as_primitive :: < Float32Type > ( ) ;
166- let y = args[ 1 ] . as_primitive :: < Float32Type > ( ) ;
167- let result: Float32Array = x
155+ Float64 => Ok ( Arc :: new ( nanvl_impl :: < Float64Type > (
156+ args[ 0 ] . as_primitive ( ) ,
157+ args[ 1 ] . as_primitive ( ) ,
158+ ) ) ) ,
159+ Float32 => Ok ( Arc :: new ( nanvl_impl :: < Float32Type > (
160+ args[ 0 ] . as_primitive ( ) ,
161+ args[ 1 ] . as_primitive ( ) ,
162+ ) ) ) ,
163+ Float16 => Ok ( Arc :: new ( nanvl_impl :: < Float16Type > (
164+ args[ 0 ] . as_primitive ( ) ,
165+ args[ 1 ] . as_primitive ( ) ,
166+ ) ) ) ,
167+ other => exec_err ! ( "Unsupported data type {other:?} for function nanvl" ) ,
168+ }
169+ }
170+
171+ /// Element-wise `nanvl`: selects `y[i]` where `x[i]` is `NaN`, otherwise `x[i]`
172+ /// (a null `x` selects `x`, i.e. propagates null).
173+ ///
174+ /// This produces output identical to collecting an iterator of `Option`s but
175+ /// splits out a null-free fast path that iterates the raw value slices,
176+ /// skipping per-element validity checks and `Option` handling. The null-aware
177+ /// path builds its null buffer lazily via [`NullBufferBuilder`].
178+ fn nanvl_impl < T > ( x : & PrimitiveArray < T > , y : & PrimitiveArray < T > ) -> PrimitiveArray < T >
179+ where
180+ T : ArrowPrimitiveType ,
181+ T :: Native : Float ,
182+ {
183+ let xv = x. values ( ) ;
184+ let yv = y. values ( ) ;
185+
186+ match ( x. nulls ( ) , y. nulls ( ) ) {
187+ // No nulls in either input means no nulls in the output, so we can
188+ // iterate values directly and avoid the null bookkeeping entirely.
189+ ( None , None ) => {
190+ let values: Vec < T :: Native > = xv
168191 . iter ( )
169- . zip ( y. iter ( ) )
170- . map ( |( x_value, y_value) | match x_value {
171- Some ( x_value) if x_value. is_nan ( ) => y_value,
172- _ => x_value,
173- } )
192+ . zip ( yv. iter ( ) )
193+ . map (
194+ |( & x_value, & y_value) | {
195+ if x_value. is_nan ( ) { y_value } else { x_value }
196+ } ,
197+ )
174198 . collect ( ) ;
175- Ok ( Arc :: new ( result ) as ArrayRef )
199+ PrimitiveArray :: < T > :: new ( values . into ( ) , None )
176200 }
177- Float16 => {
178- let x = args[ 0 ] . as_primitive :: < Float16Type > ( ) ;
179- let y = args[ 1 ] . as_primitive :: < Float16Type > ( ) ;
180- let result: Float16Array = x
181- . iter ( )
182- . zip ( y. iter ( ) )
183- . map ( |( x_value, y_value) | match x_value {
184- Some ( x_value) if x_value. is_nan ( ) => y_value,
185- _ => x_value,
186- } )
187- . collect ( ) ;
188- Ok ( Arc :: new ( result) as ArrayRef )
201+ _ => {
202+ let len = x. len ( ) ;
203+ let mut nulls = NullBufferBuilder :: new ( len) ;
204+ let mut values = Vec :: with_capacity ( len) ;
205+ for i in 0 ..len {
206+ // `y` is only consulted when `x` is a (non-null) NaN, matching
207+ // the original short-circuiting match.
208+ if x. is_valid ( i) {
209+ let x_value = xv[ i] ;
210+ if x_value. is_nan ( ) {
211+ if y. is_valid ( i) {
212+ values. push ( yv[ i] ) ;
213+ nulls. append_non_null ( ) ;
214+ } else {
215+ values. push ( T :: Native :: default ( ) ) ;
216+ nulls. append_null ( ) ;
217+ }
218+ } else {
219+ values. push ( x_value) ;
220+ nulls. append_non_null ( ) ;
221+ }
222+ } else {
223+ values. push ( T :: Native :: default ( ) ) ;
224+ nulls. append_null ( ) ;
225+ }
226+ }
227+ PrimitiveArray :: < T > :: new ( values. into ( ) , nulls. finish ( ) )
189228 }
190- other => exec_err ! ( "Unsupported data type {other:?} for function nanvl" ) ,
191229 }
192230}
193231
@@ -197,7 +235,7 @@ mod test {
197235
198236 use crate :: math:: nanvl:: nanvl;
199237
200- use arrow:: array:: { ArrayRef , Float32Array , Float64Array } ;
238+ use arrow:: array:: { Array , ArrayRef , Float32Array , Float64Array } ;
201239 use datafusion_common:: cast:: { as_float32_array, as_float64_array} ;
202240
203241 #[ test]
@@ -235,4 +273,91 @@ mod test {
235273 assert_eq ! ( floats. value( 2 ) , 3.0 ) ;
236274 assert ! ( floats. value( 3 ) . is_nan( ) ) ;
237275 }
276+
277+ #[ test]
278+ fn test_nanvl_f64_with_nulls ( ) {
279+ // Covers the null-aware path and null propagation:
280+ // - x null -> null (regardless of y)
281+ // - x NaN, y non-null -> y
282+ // - x NaN, y null -> null
283+ // - x non-NaN -> x
284+ let args: Vec < ArrayRef > = vec ! [
285+ Arc :: new( Float64Array :: from( vec![
286+ None ,
287+ Some ( f64 :: NAN ) ,
288+ Some ( f64 :: NAN ) ,
289+ Some ( 2.5 ) ,
290+ ] ) ) , // x
291+ Arc :: new( Float64Array :: from( vec![
292+ Some ( 9.0 ) ,
293+ Some ( 6.0 ) ,
294+ None ,
295+ Some ( 7.0 ) ,
296+ ] ) ) , // y
297+ ] ;
298+
299+ let result = nanvl ( & args) . expect ( "failed to initialize function nanvl" ) ;
300+ let floats =
301+ as_float64_array ( & result) . expect ( "failed to initialize function nanvl" ) ;
302+
303+ assert_eq ! ( floats. len( ) , 4 ) ;
304+ assert ! ( floats. is_null( 0 ) ) ;
305+ assert_eq ! ( floats. value( 1 ) , 6.0 ) ;
306+ assert ! ( floats. is_null( 2 ) ) ;
307+ assert_eq ! ( floats. value( 3 ) , 2.5 ) ;
308+ }
309+
310+ #[ test]
311+ fn test_nanvl_f64_only_y_nulls ( ) {
312+ // `x` has no nulls, `y` does:
313+ // - x non-NaN -> x
314+ // - x NaN, y non-null -> y
315+ // - x NaN, y null -> null (propagated from y)
316+ let args: Vec < ArrayRef > = vec ! [
317+ Arc :: new( Float64Array :: from( vec![ 1.0 , f64 :: NAN , f64 :: NAN , 4.0 ] ) ) , // x
318+ Arc :: new( Float64Array :: from( vec![
319+ Some ( 5.0 ) ,
320+ Some ( 6.0 ) ,
321+ None ,
322+ Some ( 8.0 ) ,
323+ ] ) ) , // y
324+ ] ;
325+
326+ let result = nanvl ( & args) . expect ( "failed to initialize function nanvl" ) ;
327+ let floats =
328+ as_float64_array ( & result) . expect ( "failed to initialize function nanvl" ) ;
329+
330+ assert_eq ! ( floats. len( ) , 4 ) ;
331+ assert_eq ! ( floats. value( 0 ) , 1.0 ) ;
332+ assert_eq ! ( floats. value( 1 ) , 6.0 ) ;
333+ assert ! ( floats. is_null( 2 ) ) ;
334+ assert_eq ! ( floats. value( 3 ) , 4.0 ) ;
335+ }
336+
337+ #[ test]
338+ fn test_nanvl_f64_only_x_nulls ( ) {
339+ // `x` has nulls, `y` does not:
340+ // - x null -> null (propagated from x)
341+ // - x NaN -> y
342+ // - x non-NaN -> x
343+ let args: Vec < ArrayRef > = vec ! [
344+ Arc :: new( Float64Array :: from( vec![
345+ None ,
346+ Some ( f64 :: NAN ) ,
347+ Some ( 3.0 ) ,
348+ None ,
349+ ] ) ) , // x
350+ Arc :: new( Float64Array :: from( vec![ 5.0 , 6.0 , 7.0 , 8.0 ] ) ) , // y
351+ ] ;
352+
353+ let result = nanvl ( & args) . expect ( "failed to initialize function nanvl" ) ;
354+ let floats =
355+ as_float64_array ( & result) . expect ( "failed to initialize function nanvl" ) ;
356+
357+ assert_eq ! ( floats. len( ) , 4 ) ;
358+ assert ! ( floats. is_null( 0 ) ) ;
359+ assert_eq ! ( floats. value( 1 ) , 6.0 ) ;
360+ assert_eq ! ( floats. value( 2 ) , 3.0 ) ;
361+ assert ! ( floats. is_null( 3 ) ) ;
362+ }
238363}
0 commit comments