@@ -20,23 +20,9 @@ use crate::execution::operators::ExecutionError;
2020use arrow:: {
2121 array:: ArrayData ,
2222 datatypes:: Field ,
23- error:: ArrowError ,
2423 ffi:: { FFI_ArrowArray , FFI_ArrowSchema } ,
2524} ;
2625
27- fn ffi_schema_for_field ( field : & Field ) -> Result < FFI_ArrowSchema , ArrowError > {
28- if field. name ( ) . contains ( '\0' ) {
29- // ArrowSchema names are NUL-terminated C strings. Spark owns the logical output name, so
30- // substitute only the exported name while retaining the Field's type and metadata.
31- let field = field
32- . clone ( )
33- . with_name ( field. name ( ) . replace ( '\0' , "\u{fffd} " ) ) ;
34- FFI_ArrowSchema :: try_from ( & field)
35- } else {
36- FFI_ArrowSchema :: try_from ( field)
37- }
38- }
39-
4026pub trait SparkArrowConvert {
4127 /// Move Arrow Arrays to C data interface.
4228 fn move_to_spark ( & self , field : & Field , array : i64 , schema : i64 ) -> Result < ( ) , ExecutionError > ;
@@ -51,7 +37,10 @@ impl SparkArrowConvert for ArrayData {
5137 let array_align = std:: mem:: align_of :: < FFI_ArrowArray > ( ) ;
5238 let schema_align = std:: mem:: align_of :: < FFI_ArrowSchema > ( ) ;
5339 let ffi_array = FFI_ArrowArray :: new ( self ) ;
54- let ffi_schema = ffi_schema_for_field ( field) ?;
40+ // Spark owns the top-level name and nullability. Preserve the existing anonymous schema
41+ // shape while carrying logical extension metadata from the RecordBatch field.
42+ let ffi_schema =
43+ FFI_ArrowSchema :: try_from ( self . data_type ( ) ) ?. with_metadata ( field. metadata ( ) ) ?;
5544
5645 // Check if the pointer alignment is correct.
5746 if array_ptr. align_offset ( array_align) != 0 || schema_ptr. align_offset ( schema_align) != 0 {
@@ -86,22 +75,39 @@ pub use datafusion_comet_common::bytes_to_i128;
8675#[ cfg( test) ]
8776mod tests {
8877 use super :: * ;
89- use arrow:: datatypes:: DataType ;
90- use std:: collections:: HashMap ;
78+ use arrow:: {
79+ array:: { Array , Int32Array } ,
80+ datatypes:: DataType ,
81+ } ;
82+ use std:: { collections:: HashMap , mem:: MaybeUninit } ;
9183
9284 #[ test]
93- fn test_ffi_schema_preserves_field_and_sanitizes_nul_name ( ) {
94- let field = Field :: new ( "v\0 tail " , DataType :: Int32 , true ) . with_metadata ( HashMap :: from ( [ (
85+ fn test_move_to_spark_preserves_field_metadata ( ) {
86+ let field = Field :: new ( "v" , DataType :: Int32 , true ) . with_metadata ( HashMap :: from ( [ (
9587 "ARROW:extension:name" . to_string ( ) ,
9688 "example.logical-type" . to_string ( ) ,
9789 ) ] ) ) ;
90+ let data = Int32Array :: from ( vec ! [ Some ( 1 ) , None ] ) . into_data ( ) ;
91+ let mut ffi_array = MaybeUninit :: < FFI_ArrowArray > :: uninit ( ) ;
92+ let mut ffi_schema = MaybeUninit :: < FFI_ArrowSchema > :: uninit ( ) ;
9893
99- let ffi_schema = ffi_schema_for_field ( & field) . unwrap ( ) ;
94+ data. move_to_spark (
95+ & field,
96+ ffi_array. as_mut_ptr ( ) as i64 ,
97+ ffi_schema. as_mut_ptr ( ) as i64 ,
98+ )
99+ . unwrap ( ) ;
100+
101+ let ffi_array = unsafe { ffi_array. assume_init ( ) } ;
102+ let ffi_schema = unsafe { ffi_schema. assume_init ( ) } ;
100103 let exported = Field :: try_from ( & ffi_schema) . unwrap ( ) ;
101104
102- assert_eq ! ( exported. name( ) , "v \u{fffd} tail " ) ;
105+ assert_eq ! ( exported. name( ) , "" ) ;
103106 assert_eq ! ( exported. data_type( ) , field. data_type( ) ) ;
104- assert_eq ! ( exported. is_nullable ( ) , field . is_nullable( ) ) ;
107+ assert ! ( ! exported. is_nullable( ) ) ;
105108 assert_eq ! ( exported. metadata( ) , field. metadata( ) ) ;
109+
110+ drop ( ffi_array) ;
111+ drop ( ffi_schema) ;
106112 }
107113}
0 commit comments