1818use std:: str:: from_utf8_unchecked;
1919use std:: sync:: Arc ;
2020
21- use arrow:: array:: { Array , ArrayRef , NullBufferBuilder , StringArray , StringBuilder } ;
21+ use arrow:: array:: { Array , ArrayAccessor , ArrayRef , StringArray , StringBuilder } ;
2222use arrow:: buffer:: { Buffer , OffsetBuffer } ;
2323use arrow:: datatypes:: DataType ;
2424use arrow:: {
@@ -111,21 +111,40 @@ impl ScalarUDFImpl for SparkHex {
111111 }
112112}
113113
114+ #[ inline]
115+ fn append_hex_bytes (
116+ values : & mut Vec < u8 > ,
117+ bytes : & [ u8 ] ,
118+ case : HexCase ,
119+ ) -> Result < i32 , DataFusionError > {
120+ let additional = bytes
121+ . len ( )
122+ . checked_mul ( 2 )
123+ . ok_or_else ( || exec_datafusion_err ! ( "hex output size overflow" ) ) ?;
124+ values. try_reserve ( additional) . map_err ( |e| {
125+ exec_datafusion_err ! ( "failed to reserve {additional} bytes for hex output: {e}" )
126+ } ) ?;
127+ encode_bytes_into ( bytes, case, values) ;
128+ i32:: try_from ( values. len ( ) )
129+ . map_err ( |_| exec_datafusion_err ! ( "hex output exceeds i32 offset range" ) )
130+ }
131+
114132/// Generic hex encoding for byte array types
115- fn hex_encode_bytes < ' a , I , T > (
116- iter : I ,
133+ fn hex_encode_bytes < ' a , A , T > (
134+ array : & A ,
117135 lowercase : bool ,
118- len : usize ,
119136) -> Result < ArrayRef , DataFusionError >
120137where
121- I : Iterator < Item = Option < T > > ,
122- T : AsRef < [ u8 ] > + ' a ,
138+ A : ArrayAccessor < Item = & ' a T > ,
139+ T : AsRef < [ u8 ] > + ? Sized + ' a ,
123140{
124141 let case = if lowercase {
125142 HexCase :: Lower
126143 } else {
127144 HexCase :: Upper
128145 } ;
146+ let len = array. len ( ) ;
147+ let nulls = array. nulls ( ) . cloned ( ) ;
129148
130149 // Write hex digits directly into one growing value buffer, tracking offsets
131150 // ourselves. Each input byte becomes exactly two output bytes, so there is
@@ -134,30 +153,25 @@ where
134153 let mut values: Vec < u8 > = Vec :: with_capacity ( len * 64 ) ;
135154 let mut offsets: Vec < i32 > = Vec :: with_capacity ( len + 1 ) ;
136155 offsets. push ( 0 ) ;
137- let mut nulls = NullBufferBuilder :: new ( len) ;
138156
139- for v in iter {
140- if let Some ( b) = v {
141- let bytes = b. as_ref ( ) ;
142- let additional = bytes
143- . len ( )
144- . checked_mul ( 2 )
145- . ok_or_else ( || exec_datafusion_err ! ( "hex output size overflow" ) ) ?;
146- values. try_reserve ( additional) . map_err ( |e| {
147- exec_datafusion_err ! (
148- "failed to reserve {additional} bytes for hex output: {e}"
149- )
150- } ) ?;
151- encode_bytes_into ( bytes, case, & mut values) ;
152- nulls. append_non_null ( ) ;
153- } else {
154- nulls. append_null ( ) ;
157+ if let Some ( ref nulls) = nulls {
158+ for i in 0 ..len {
159+ if nulls. is_valid ( i) {
160+ // SAFETY: `i` is in bounds and the validity buffer marks it valid.
161+ let bytes = unsafe { array. value_unchecked ( i) } . as_ref ( ) ;
162+ offsets. push ( append_hex_bytes ( & mut values, bytes, case) ?) ;
163+ } else {
164+ offsets. push ( i32:: try_from ( values. len ( ) ) . map_err ( |_| {
165+ exec_datafusion_err ! ( "hex output exceeds i32 offset range" )
166+ } ) ?) ;
167+ }
168+ }
169+ } else {
170+ for i in 0 ..len {
171+ // SAFETY: `i` is in bounds and no null buffer means every value is valid.
172+ let bytes = unsafe { array. value_unchecked ( i) } . as_ref ( ) ;
173+ offsets. push ( append_hex_bytes ( & mut values, bytes, case) ?) ;
155174 }
156- offsets. push (
157- i32:: try_from ( values. len ( ) ) . map_err ( |_| {
158- exec_datafusion_err ! ( "hex output exceeds i32 offset range" )
159- } ) ?,
160- ) ;
161175 }
162176
163177 // SAFETY: the value buffer contains only ASCII hex digits (valid UTF-8) and
@@ -168,7 +182,7 @@ where
168182 StringArray :: new_unchecked (
169183 OffsetBuffer :: new ( offsets. into ( ) ) ,
170184 Buffer :: from_vec ( values) ,
171- nulls. finish ( ) ,
185+ nulls,
172186 )
173187 } ;
174188 Ok ( Arc :: new ( array) )
@@ -227,51 +241,27 @@ pub fn compute_hex(
227241 }
228242 DataType :: Utf8 => {
229243 let array = as_string_array ( array) ;
230- Ok ( ColumnarValue :: Array ( hex_encode_bytes (
231- array. iter ( ) ,
232- lowercase,
233- array. len ( ) ,
234- ) ?) )
244+ Ok ( ColumnarValue :: Array ( hex_encode_bytes ( & array, lowercase) ?) )
235245 }
236246 DataType :: Utf8View => {
237247 let array = as_string_view_array ( array) ?;
238- Ok ( ColumnarValue :: Array ( hex_encode_bytes (
239- array. iter ( ) ,
240- lowercase,
241- array. len ( ) ,
242- ) ?) )
248+ Ok ( ColumnarValue :: Array ( hex_encode_bytes ( & array, lowercase) ?) )
243249 }
244250 DataType :: LargeUtf8 => {
245251 let array = as_largestring_array ( array) ;
246- Ok ( ColumnarValue :: Array ( hex_encode_bytes (
247- array. iter ( ) ,
248- lowercase,
249- array. len ( ) ,
250- ) ?) )
252+ Ok ( ColumnarValue :: Array ( hex_encode_bytes ( & array, lowercase) ?) )
251253 }
252254 DataType :: Binary => {
253255 let array = as_binary_array ( array) ?;
254- Ok ( ColumnarValue :: Array ( hex_encode_bytes (
255- array. iter ( ) ,
256- lowercase,
257- array. len ( ) ,
258- ) ?) )
256+ Ok ( ColumnarValue :: Array ( hex_encode_bytes ( & array, lowercase) ?) )
259257 }
260258 DataType :: LargeBinary => {
261259 let array = as_large_binary_array ( array) ?;
262- Ok ( ColumnarValue :: Array ( hex_encode_bytes (
263- array. iter ( ) ,
264- lowercase,
265- array. len ( ) ,
266- ) ?) )
260+ Ok ( ColumnarValue :: Array ( hex_encode_bytes ( & array, lowercase) ?) )
267261 }
268262 DataType :: FixedSizeBinary ( _) => {
269263 let array = as_fixed_size_binary_array ( array) ?;
270- Ok ( ColumnarValue :: Array ( hex_encode_bytes (
271- array. iter ( ) ,
272- lowercase,
273- array. len ( ) ,
274- ) ?) )
264+ Ok ( ColumnarValue :: Array ( hex_encode_bytes ( & array, lowercase) ?) )
275265 }
276266 DataType :: Dictionary ( key_type, _) => {
277267 if * * key_type != DataType :: Int32 {
@@ -291,27 +281,27 @@ pub fn compute_hex(
291281 }
292282 DataType :: Utf8 => {
293283 let arr = as_string_array ( dict_values) ;
294- hex_encode_bytes ( arr. iter ( ) , lowercase, arr . len ( ) ) ?
284+ hex_encode_bytes ( & arr, lowercase) ?
295285 }
296286 DataType :: LargeUtf8 => {
297287 let arr = as_largestring_array ( dict_values) ;
298- hex_encode_bytes ( arr. iter ( ) , lowercase, arr . len ( ) ) ?
288+ hex_encode_bytes ( & arr, lowercase) ?
299289 }
300290 DataType :: Utf8View => {
301291 let arr = as_string_view_array ( dict_values) ?;
302- hex_encode_bytes ( arr. iter ( ) , lowercase, arr . len ( ) ) ?
292+ hex_encode_bytes ( & arr, lowercase) ?
303293 }
304294 DataType :: Binary => {
305295 let arr = as_binary_array ( dict_values) ?;
306- hex_encode_bytes ( arr. iter ( ) , lowercase, arr . len ( ) ) ?
296+ hex_encode_bytes ( & arr, lowercase) ?
307297 }
308298 DataType :: LargeBinary => {
309299 let arr = as_large_binary_array ( dict_values) ?;
310- hex_encode_bytes ( arr. iter ( ) , lowercase, arr . len ( ) ) ?
300+ hex_encode_bytes ( & arr, lowercase) ?
311301 }
312302 DataType :: FixedSizeBinary ( _) => {
313303 let arr = as_fixed_size_binary_array ( dict_values) ?;
314- hex_encode_bytes ( arr. iter ( ) , lowercase, arr . len ( ) ) ?
304+ hex_encode_bytes ( & arr, lowercase) ?
315305 }
316306 _ => {
317307 return exec_err ! (
@@ -465,7 +455,8 @@ mod test {
465455 // is reachable only via `spark_sha2_hex`, which has no in-workspace
466456 // caller, so it otherwise has no coverage. Drive it directly here.
467457 let input = StringArray :: from ( vec ! [ Some ( "hi" ) , Some ( "bye" ) , None , Some ( "rust" ) ] ) ;
468- let result = super :: hex_encode_bytes ( input. iter ( ) , true , input. len ( ) ) . unwrap ( ) ;
458+ let input_ref = & input;
459+ let result = super :: hex_encode_bytes ( & input_ref, true ) . unwrap ( ) ;
469460 let result = as_string_array ( & result) ;
470461
471462 let expected =
@@ -495,6 +486,56 @@ mod test {
495486 assert_eq ! ( strings. value( 0 ) , expected) ;
496487 }
497488
489+ #[ test]
490+ fn test_spark_hex_binary_no_nulls ( ) {
491+ let input = BinaryArray :: from ( vec ! [
492+ b"" . as_slice( ) ,
493+ b"\x00 \x7f \x80 \xff " . as_slice( ) ,
494+ b"DataFusion" . as_slice( ) ,
495+ ] ) ;
496+
497+ let result = super :: spark_hex ( & [ ColumnarValue :: Array ( Arc :: new ( input) ) ] ) . unwrap ( ) ;
498+ let array = match result {
499+ ColumnarValue :: Array ( array) => array,
500+ _ => panic ! ( "Expected array" ) ,
501+ } ;
502+ let strings = as_string_array ( & array) ;
503+
504+ assert_eq ! ( strings. nulls( ) , None ) ;
505+ assert_eq ! (
506+ strings,
507+ & StringArray :: from( vec![ "" , "007F80FF" , "44617461467573696F6E" ] )
508+ ) ;
509+ }
510+
511+ #[ test]
512+ fn test_spark_hex_binary_reuses_input_nulls ( ) {
513+ let input = BinaryArray :: from ( vec ! [
514+ Some ( b"skip" . as_slice( ) ) ,
515+ None ,
516+ Some ( b"\x00 \xff " . as_slice( ) ) ,
517+ Some ( b"hex" . as_slice( ) ) ,
518+ None ,
519+ ] )
520+ . slice ( 1 , 4 ) ;
521+ let input_nulls = input. nulls ( ) . unwrap ( ) . clone ( ) ;
522+
523+ let result = super :: spark_hex ( & [ ColumnarValue :: Array ( Arc :: new ( input) ) ] ) . unwrap ( ) ;
524+ let array = match result {
525+ ColumnarValue :: Array ( array) => array,
526+ _ => panic ! ( "Expected array" ) ,
527+ } ;
528+ let strings = as_string_array ( & array) ;
529+ let output_nulls = strings. nulls ( ) . unwrap ( ) ;
530+
531+ assert_eq ! ( output_nulls, & input_nulls) ;
532+ assert ! ( output_nulls. inner( ) . ptr_eq( input_nulls. inner( ) ) ) ;
533+ assert_eq ! (
534+ strings,
535+ & StringArray :: from( vec![ None , Some ( "00FF" ) , Some ( "686578" ) , None ] )
536+ ) ;
537+ }
538+
498539 #[ test]
499540 fn test_spark_hex_int64 ( ) {
500541 let int_array = Int64Array :: from ( vec ! [ Some ( 1 ) , Some ( 2 ) , None , Some ( 3 ) ] ) ;
@@ -540,4 +581,25 @@ mod test {
540581
541582 assert_eq ! ( & expected, result) ;
542583 }
584+
585+ #[ test]
586+ fn test_dict_binary_values_null ( ) {
587+ let keys = Int32Array :: from ( vec ! [ Some ( 0 ) , None , Some ( 1 ) ] ) ;
588+ let vals = BinaryArray :: from ( vec ! [ Some ( b"hi" . as_slice( ) ) , None ] ) ;
589+ // [b"hi", null, null]
590+ let dict = DictionaryArray :: new ( keys, Arc :: new ( vals) ) ;
591+
592+ let result = super :: spark_hex ( & [ ColumnarValue :: Array ( Arc :: new ( dict) ) ] ) . unwrap ( ) ;
593+ let result = match result {
594+ ColumnarValue :: Array ( array) => array,
595+ _ => panic ! ( "Expected array" ) ,
596+ } ;
597+ let result = as_dictionary_array ( & result) . unwrap ( ) ;
598+
599+ let keys = Int32Array :: from ( vec ! [ Some ( 0 ) , None , Some ( 1 ) ] ) ;
600+ let vals = StringArray :: from ( vec ! [ Some ( "6869" ) , None ] ) ;
601+ let expected = DictionaryArray :: new ( keys, Arc :: new ( vals) ) ;
602+
603+ assert_eq ! ( & expected, result) ;
604+ }
543605}
0 commit comments