@@ -49,13 +49,13 @@ impl ExportDeviceArray for CanonicalDeviceArrayExport {
4949 ) -> VortexResult < ArrowDeviceArray > {
5050 let cuda_array = array. execute_cuda ( ctx) . await ?;
5151
52- let ( arrow_array, _ ) = export_canonical ( cuda_array, ctx) . await ?;
52+ let ( arrow_array, sync_event ) = export_canonical ( cuda_array, ctx) . await ?;
5353
5454 Ok ( ArrowDeviceArray {
5555 array : arrow_array,
5656 device_id : ctx. stream ( ) . context ( ) . ordinal ( ) as i64 ,
5757 device_type : ARROW_DEVICE_CUDA ,
58- sync_event : ptr :: null_mut ( ) ,
58+ sync_event,
5959 reserved : Default :: default ( ) ,
6060 } )
6161 }
@@ -232,8 +232,8 @@ fn export_fixed_size(
232232 "buffer must already be copied to device before calling"
233233 ) ;
234234
235- // TODO(aduffy): currently the null buffer is always None, in the future we will need
236- // to pass it .
235+ // Non-trivial validity is rejected before fixed-size export, so the Arrow null bitmap slot is
236+ // always null for now. Future nullable export support should pass the validity bitmap here .
237237 let mut private_data = PrivateData :: new ( vec ! [ None , Some ( buffer) ] , vec ! [ ] , ctx) ?;
238238 let sync_event: SyncEvent = private_data. sync_event ( ) ;
239239
@@ -271,7 +271,9 @@ unsafe extern "C" fn release_array(array: *mut ArrowArray) {
271271 let children = mem:: take ( & mut private_data. children ) ;
272272 for child in children {
273273 if !child. is_null ( ) {
274- release_array ( child) ;
274+ if let Some ( release) = ( * child) . release {
275+ release ( child) ;
276+ }
275277 // Children are allocated with Box::into_raw in PrivateData::new, so the
276278 // release callback must also reclaim the ArrowArray allocation itself.
277279 drop ( Box :: from_raw ( child) ) ;
@@ -286,6 +288,9 @@ unsafe extern "C" fn release_array(array: *mut ArrowArray) {
286288
287289#[ cfg( test) ]
288290mod tests {
291+ use arrow_schema:: DataType ;
292+ use arrow_schema:: Field ;
293+ use arrow_schema:: Schema ;
289294 use rstest:: rstest;
290295 use vortex:: array:: ArrayRef ;
291296 use vortex:: array:: IntoArray ;
@@ -479,4 +484,79 @@ mod tests {
479484 unsafe { release_array ( & raw mut device_array. array ) } ;
480485 Ok ( ( ) )
481486 }
487+
488+ #[ crate :: test]
489+ async fn test_export_struct_with_schema ( ) -> VortexResult < ( ) > {
490+ let mut ctx = CudaSession :: create_execution_ctx ( & VortexSession :: empty ( ) )
491+ . vortex_expect ( "failed to create execution context" ) ;
492+
493+ let array = StructArray :: new (
494+ FieldNames :: from_iter ( [ "a" , "b" , "c" ] ) ,
495+ vec ! [
496+ PrimitiveArray :: from_iter( 0u32 ..5 ) . into_array( ) ,
497+ PrimitiveArray :: from_iter( 0i64 ..5 ) . into_array( ) ,
498+ VarBinViewArray :: from_iter_str( [ "one" , "two" , "three" , "four" , "five" ] )
499+ . into_array( ) ,
500+ ] ,
501+ 5 ,
502+ Validity :: NonNullable ,
503+ )
504+ . into_array ( ) ;
505+ let mut exported = array. export_device_array_with_schema ( & mut ctx) . await ?;
506+
507+ let schema = Schema :: try_from ( & exported. schema ) ?;
508+ assert_eq ! (
509+ schema,
510+ Schema :: new( vec![
511+ Field :: new( "a" , DataType :: UInt32 , false ) ,
512+ Field :: new( "b" , DataType :: Int64 , false ) ,
513+ Field :: new( "c" , DataType :: Utf8 , false ) ,
514+ ] )
515+ ) ;
516+ assert_eq ! ( exported. array. array. length, 5 ) ;
517+ assert_eq ! ( exported. array. array. n_buffers, 1 ) ;
518+ assert_eq ! ( exported. array. array. n_children, 3 ) ;
519+ assert_eq ! ( exported. array. device_type, ARROW_DEVICE_CUDA ) ;
520+
521+ unsafe { release_array ( & raw mut exported. array . array ) } ;
522+ Ok ( ( ) )
523+ }
524+
525+ #[ crate :: test]
526+ async fn test_export_primitive_with_schema_is_column_shaped ( ) -> VortexResult < ( ) > {
527+ let mut ctx = CudaSession :: create_execution_ctx ( & VortexSession :: empty ( ) )
528+ . vortex_expect ( "failed to create execution context" ) ;
529+
530+ let array = PrimitiveArray :: from_iter ( 0u32 ..5 ) . into_array ( ) ;
531+ let mut exported = array. export_device_array_with_schema ( & mut ctx) . await ?;
532+
533+ let field = Field :: try_from ( & exported. schema ) ?;
534+ assert_eq ! ( field, Field :: new( "" , DataType :: UInt32 , false ) ) ;
535+ assert_eq ! ( exported. array. array. length, 5 ) ;
536+ assert_eq ! ( exported. array. array. n_buffers, 2 ) ;
537+ assert_eq ! ( exported. array. array. n_children, 0 ) ;
538+ assert_eq ! ( exported. array. device_type, ARROW_DEVICE_CUDA ) ;
539+
540+ unsafe { release_array ( & raw mut exported. array . array ) } ;
541+ Ok ( ( ) )
542+ }
543+
544+ #[ crate :: test]
545+ async fn test_export_varbinview_with_schema_uses_utf8_layout ( ) -> VortexResult < ( ) > {
546+ let mut ctx = CudaSession :: create_execution_ctx ( & VortexSession :: empty ( ) )
547+ . vortex_expect ( "failed to create execution context" ) ;
548+
549+ let array = VarBinViewArray :: from_iter_str ( [ "one" , "two" , "three" ] ) . into_array ( ) ;
550+ let mut exported = array. export_device_array_with_schema ( & mut ctx) . await ?;
551+
552+ let field = Field :: try_from ( & exported. schema ) ?;
553+ assert_eq ! ( field, Field :: new( "" , DataType :: Utf8 , false ) ) ;
554+ assert_eq ! ( exported. array. array. length, 3 ) ;
555+ assert_eq ! ( exported. array. array. n_buffers, 3 ) ;
556+ assert_eq ! ( exported. array. array. n_children, 0 ) ;
557+ assert_eq ! ( exported. array. device_type, ARROW_DEVICE_CUDA ) ;
558+
559+ unsafe { release_array ( & raw mut exported. array . array ) } ;
560+ Ok ( ( ) )
561+ }
482562}
0 commit comments