@@ -15,6 +15,7 @@ use vortex::array::arrays::decimal::DecimalDataParts;
1515use vortex:: array:: arrays:: extension:: ExtensionArrayExt ;
1616use vortex:: array:: arrays:: primitive:: PrimitiveDataParts ;
1717use vortex:: array:: arrays:: struct_:: StructDataParts ;
18+ use vortex:: array:: arrays:: varbinview:: VarBinViewDataParts ;
1819use vortex:: array:: buffer:: BufferHandle ;
1920use vortex:: dtype:: DecimalType ;
2021use vortex:: error:: VortexResult ;
@@ -30,8 +31,6 @@ use crate::arrow::ExportDeviceArray;
3031use crate :: arrow:: PrivateData ;
3132use crate :: arrow:: SyncEvent ;
3233use crate :: arrow:: check_validity_empty;
33- use crate :: arrow:: varbinview:: BinaryParts ;
34- use crate :: arrow:: varbinview:: copy_varbinview_to_varbin;
3534use crate :: executor:: CudaArrayExt ;
3635
3736/// An implementation of `ExportDeviceArray` that exports Vortex arrays to `ArrowDeviceArray` by
@@ -49,13 +48,13 @@ impl ExportDeviceArray for CanonicalDeviceArrayExport {
4948 ) -> VortexResult < ArrowDeviceArray > {
5049 let cuda_array = array. execute_cuda ( ctx) . await ?;
5150
52- let ( arrow_array, _ ) = export_canonical ( cuda_array, ctx) . await ?;
51+ let ( arrow_array, sync_event ) = export_canonical ( cuda_array, ctx) . await ?;
5352
5453 Ok ( ArrowDeviceArray {
5554 array : arrow_array,
5655 device_id : ctx. stream ( ) . context ( ) . ordinal ( ) as i64 ,
5756 device_type : ARROW_DEVICE_CUDA ,
58- sync_event : ptr :: null_mut ( ) ,
57+ sync_event,
5958 reserved : Default :: default ( ) ,
6059 } )
6160 }
@@ -148,24 +147,32 @@ fn export_canonical(
148147 }
149148 Canonical :: VarBinView ( varbinview) => {
150149 let len = varbinview. len ( ) ;
151- check_validity_empty ( & varbinview. validity ( ) ?) ?;
150+ let VarBinViewDataParts {
151+ views,
152+ buffers : data_buffers,
153+ validity,
154+ ..
155+ } = varbinview. into_data_parts ( ) ;
152156
153- let BinaryParts { offsets, bytes } =
154- copy_varbinview_to_varbin ( varbinview, ctx) . await ?;
157+ check_validity_empty ( & validity) ?;
155158
156- let offsets = ctx. ensure_on_device ( offsets) . await ?;
157- let bytes = ctx. ensure_on_device ( bytes) . await ?;
159+ let views = ctx. ensure_on_device ( views) . await ?;
160+ let mut buffers = Vec :: with_capacity ( data_buffers. len ( ) + 2 ) ;
161+ buffers. push ( None ) ;
162+ buffers. push ( Some ( views) ) ;
163+ for buffer in data_buffers. iter ( ) {
164+ buffers. push ( Some ( ctx. ensure_on_device ( buffer. clone ( ) ) . await ?) ) ;
165+ }
158166
159- let buffers = vec ! [ None , Some ( offsets ) , Some ( bytes ) ] ;
167+ let n_buffers = i64 :: try_from ( buffers . len ( ) ) ? ;
160168 let mut private_data = PrivateData :: new ( buffers, vec ! [ ] , ctx) ?;
161169 let sync_event = private_data. sync_event ( ) ;
162- //
163170 let arrow_array = ArrowArray {
164171 length : len as i64 ,
165172 null_count : 0 ,
166173 offset : 0 ,
167- // Arrow Utf8/Binary layout: optional null bitmap, offsets , and data bytes .
168- n_buffers : 3 ,
174+ // Arrow Utf8View/BinaryView layout: optional null bitmap, views , and data buffers .
175+ n_buffers,
169176 buffers : private_data. buffer_ptrs . as_mut_ptr ( ) ,
170177 n_children : 0 ,
171178 children : ptr:: null_mut ( ) ,
@@ -232,8 +239,8 @@ fn export_fixed_size(
232239 "buffer must already be copied to device before calling"
233240 ) ;
234241
235- // TODO(aduffy): currently the null buffer is always None, in the future we will need
236- // to pass it .
242+ // Non-trivial validity is rejected before fixed-size export, so the Arrow null bitmap slot is
243+ // always null for now. Future nullable export support should pass the validity bitmap here .
237244 let mut private_data = PrivateData :: new ( vec ! [ None , Some ( buffer) ] , vec ! [ ] , ctx) ?;
238245 let sync_event: SyncEvent = private_data. sync_event ( ) ;
239246
@@ -271,7 +278,9 @@ unsafe extern "C" fn release_array(array: *mut ArrowArray) {
271278 let children = mem:: take ( & mut private_data. children ) ;
272279 for child in children {
273280 if !child. is_null ( ) {
274- release_array ( child) ;
281+ if let Some ( release) = ( * child) . release {
282+ release ( child) ;
283+ }
275284 // Children are allocated with Box::into_raw in PrivateData::new, so the
276285 // release callback must also reclaim the ArrowArray allocation itself.
277286 drop ( Box :: from_raw ( child) ) ;
@@ -286,6 +295,9 @@ unsafe extern "C" fn release_array(array: *mut ArrowArray) {
286295
287296#[ cfg( test) ]
288297mod tests {
298+ use arrow_schema:: DataType ;
299+ use arrow_schema:: Field ;
300+ use arrow_schema:: Schema ;
289301 use rstest:: rstest;
290302 use vortex:: array:: ArrayRef ;
291303 use vortex:: array:: IntoArray ;
@@ -432,14 +444,10 @@ mod tests {
432444
433445 assert_eq ! ( device_array. array. length, 3 ) ;
434446 assert_eq ! ( device_array. array. null_count, 0 ) ;
435- // VarBin export: null buffer + offsets + data
447+ // VarBinView export: null buffer + views + data buffers
436448 assert_eq ! ( device_array. array. n_buffers, 3 ) ;
437- let buffers = unsafe {
438- std:: slice:: from_raw_parts (
439- device_array. array . buffers ,
440- device_array. array . n_buffers as usize ,
441- )
442- } ;
449+ let n_buffers = usize:: try_from ( device_array. array . n_buffers ) ?;
450+ let buffers = unsafe { std:: slice:: from_raw_parts ( device_array. array . buffers , n_buffers) } ;
443451 assert ! ( buffers[ 0 ] . is_null( ) ) ;
444452 assert ! ( !buffers[ 1 ] . is_null( ) ) ;
445453 assert ! ( !buffers[ 2 ] . is_null( ) ) ;
@@ -479,4 +487,84 @@ mod tests {
479487 unsafe { release_array ( & raw mut device_array. array ) } ;
480488 Ok ( ( ) )
481489 }
490+
491+ #[ crate :: test]
492+ async fn test_export_struct_with_schema ( ) -> VortexResult < ( ) > {
493+ let mut ctx = CudaSession :: create_execution_ctx ( & VortexSession :: empty ( ) )
494+ . vortex_expect ( "failed to create execution context" ) ;
495+
496+ let array = StructArray :: new (
497+ FieldNames :: from_iter ( [ "a" , "b" , "c" ] ) ,
498+ vec ! [
499+ PrimitiveArray :: from_iter( 0u32 ..5 ) . into_array( ) ,
500+ PrimitiveArray :: from_iter( 0i64 ..5 ) . into_array( ) ,
501+ VarBinViewArray :: from_iter_str( [ "one" , "two" , "three" , "four" , "five" ] )
502+ . into_array( ) ,
503+ ] ,
504+ 5 ,
505+ Validity :: NonNullable ,
506+ )
507+ . into_array ( ) ;
508+ let mut exported = array. export_device_array_with_schema ( & mut ctx) . await ?;
509+
510+ let schema = Schema :: try_from ( & exported. schema ) ?;
511+ assert_eq ! (
512+ schema,
513+ Schema :: new( vec![
514+ Field :: new( "a" , DataType :: UInt32 , false ) ,
515+ Field :: new( "b" , DataType :: Int64 , false ) ,
516+ Field :: new( "c" , DataType :: Utf8View , false ) ,
517+ ] )
518+ ) ;
519+ assert_eq ! ( exported. array. array. length, 5 ) ;
520+ assert_eq ! ( exported. array. array. n_buffers, 1 ) ;
521+ assert_eq ! ( exported. array. array. n_children, 3 ) ;
522+ assert_eq ! ( exported. array. device_type, ARROW_DEVICE_CUDA ) ;
523+
524+ unsafe { release_array ( & raw mut exported. array . array ) } ;
525+ Ok ( ( ) )
526+ }
527+
528+ #[ crate :: test]
529+ async fn test_export_primitive_with_schema_is_column_shaped ( ) -> VortexResult < ( ) > {
530+ let mut ctx = CudaSession :: create_execution_ctx ( & VortexSession :: empty ( ) )
531+ . vortex_expect ( "failed to create execution context" ) ;
532+
533+ let array = PrimitiveArray :: from_iter ( 0u32 ..5 ) . into_array ( ) ;
534+ let mut exported = array. export_device_array_with_schema ( & mut ctx) . await ?;
535+
536+ let field = Field :: try_from ( & exported. schema ) ?;
537+ assert_eq ! ( field, Field :: new( "" , DataType :: UInt32 , false ) ) ;
538+ assert_eq ! ( exported. array. array. length, 5 ) ;
539+ assert_eq ! ( exported. array. array. n_buffers, 2 ) ;
540+ assert_eq ! ( exported. array. array. n_children, 0 ) ;
541+ assert_eq ! ( exported. array. device_type, ARROW_DEVICE_CUDA ) ;
542+
543+ unsafe { release_array ( & raw mut exported. array . array ) } ;
544+ Ok ( ( ) )
545+ }
546+
547+ #[ crate :: test]
548+ async fn test_export_varbinview_with_schema_uses_utf8_view_layout ( ) -> VortexResult < ( ) > {
549+ let mut ctx = CudaSession :: create_execution_ctx ( & VortexSession :: empty ( ) )
550+ . vortex_expect ( "failed to create execution context" ) ;
551+
552+ let array = VarBinViewArray :: from_iter_str ( [
553+ "one" ,
554+ "two" ,
555+ "this is a longer string for out-of-line storage" ,
556+ ] )
557+ . into_array ( ) ;
558+ let mut exported = array. export_device_array_with_schema ( & mut ctx) . await ?;
559+
560+ let field = Field :: try_from ( & exported. schema ) ?;
561+ assert_eq ! ( field, Field :: new( "" , DataType :: Utf8View , false ) ) ;
562+ assert_eq ! ( exported. array. array. length, 3 ) ;
563+ assert_eq ! ( exported. array. array. n_buffers, 3 ) ;
564+ assert_eq ! ( exported. array. array. n_children, 0 ) ;
565+ assert_eq ! ( exported. array. device_type, ARROW_DEVICE_CUDA ) ;
566+
567+ unsafe { release_array ( & raw mut exported. array . array ) } ;
568+ Ok ( ( ) )
569+ }
482570}
0 commit comments