@@ -5,12 +5,10 @@ use std::collections::BTreeSet;
55use std:: ops:: Range ;
66use std:: sync:: Arc ;
77
8- use futures:: { FutureExt , try_join} ;
8+ use futures:: try_join;
99use itertools:: Itertools ;
10- use vortex_array:: arrays:: StructArray ;
1110use vortex_array:: stats:: Precision ;
12- use vortex_array:: validity:: Validity ;
13- use vortex_array:: { Array , IntoArray , MaskFuture , ToCanonical } ;
11+ use vortex_array:: { MaskFuture , ToCanonical } ;
1412use vortex_dtype:: { DType , FieldMask , FieldName , Nullability , StructFields } ;
1513use vortex_error:: { VortexExpect , VortexResult , vortex_err} ;
1614use vortex_expr:: transform:: immediate_access:: annotate_scope_access;
@@ -275,25 +273,10 @@ impl LayoutReader for StructReader {
275273
276274 // Partition the expression into expressions that can be evaluated over individual fields
277275 let array_future = match & self . partition_expr ( expr. clone ( ) ) {
278- Partitioned :: Single ( name, partition) => {
279- // We should yield a new StructArray that has the given partition info.
280- let name = name. clone ( ) ;
281- self . field_reader ( & name) ?
282- . projection_evaluation ( row_range, partition, mask) ?
283- . map ( |result| {
284- result. and_then ( move |array| {
285- let len = array. len ( ) ;
286- StructArray :: try_new (
287- vec ! [ name] . into ( ) ,
288- vec ! [ array] ,
289- len,
290- Validity :: NonNullable ,
291- )
292- . map ( |a| a. into_array ( ) )
293- } )
294- } )
295- . boxed ( )
296- }
276+ Partitioned :: Single ( name, partition) => self
277+ . field_reader ( name) ?
278+ . projection_evaluation ( row_range, partition, mask) ?,
279+
297280 Partitioned :: Multi ( partitioned) => {
298281 // Apply the validity to each internal field instead.
299282 partitioned
@@ -329,8 +312,8 @@ mod tests {
329312 use vortex_array:: validity:: Validity ;
330313 use vortex_array:: { Array , ArrayContext , IntoArray , MaskFuture , ToCanonical } ;
331314 use vortex_buffer:: buffer;
332- use vortex_dtype:: { DType , FieldDType , FieldName , Nullability , PType , StructFields } ;
333- use vortex_expr:: { col, eq, get_item, gt, lit, or, pack, root} ;
315+ use vortex_dtype:: { DType , FieldName , Nullability , PType } ;
316+ use vortex_expr:: { col, eq, get_item, gt, lit, or, pack, root, select } ;
334317 use vortex_io:: runtime:: single:: block_on;
335318 use vortex_mask:: Mask ;
336319 use vortex_scalar:: Scalar ;
@@ -407,6 +390,57 @@ mod tests {
407390 ( segments, layout)
408391 }
409392
393+ /// Writes a nested struct layout with the following values:
394+ ///
395+ /// | a |
396+ /// |------------------|
397+ /// |`{"b": {"c": 4 }}`|
398+ /// | `NULL` |
399+ /// |`{"b": {"c": 6 }}`|
400+ #[ fixture]
401+ fn nested_struct_layout ( ) -> ( Arc < dyn SegmentSource > , LayoutRef ) {
402+ let ctx = ArrayContext :: empty ( ) ;
403+ let segments = Arc :: new ( TestSegments :: default ( ) ) ;
404+ let ( ptr, eof) = SequenceId :: root ( ) . split ( ) ;
405+ let strategy =
406+ StructStrategy :: new ( FlatLayoutStrategy :: default ( ) , FlatLayoutStrategy :: default ( ) ) ;
407+ let layout = block_on ( |handle| {
408+ strategy. write_stream (
409+ ctx,
410+ segments. clone ( ) ,
411+ StructArray :: try_from_iter_with_validity (
412+ [ (
413+ "a" ,
414+ StructArray :: try_from_iter_with_validity (
415+ [ (
416+ "b" ,
417+ StructArray :: try_from_iter_with_validity (
418+ [ ( "c" , buffer ! [ 4 , 5 , 6 ] . into_array ( ) ) ] ,
419+ Validity :: NonNullable ,
420+ )
421+ . unwrap ( )
422+ . into_array ( ) ,
423+ ) ] ,
424+ Validity :: Array ( BoolArray :: from_iter ( [ true , false , true ] ) . into_array ( ) ) ,
425+ )
426+ . unwrap ( )
427+ . into_array ( ) ,
428+ ) ] ,
429+ Validity :: NonNullable ,
430+ )
431+ . unwrap ( )
432+ . into_array ( )
433+ . to_array_stream ( )
434+ . sequenced ( ptr) ,
435+ eof,
436+ handle,
437+ )
438+ } )
439+ . unwrap ( ) ;
440+
441+ ( segments, layout)
442+ }
443+
410444 #[ rstest]
411445 fn test_struct_layout_or (
412446 #[ from( struct_layout) ] ( segments, layout) : ( Arc < dyn SegmentSource > , LayoutRef ) ,
@@ -527,24 +561,37 @@ mod tests {
527561 . unwrap ( ) ;
528562
529563 let result = block_on ( move |_| project) . unwrap ( ) ;
530- // Result should be a struct with single field
564+ // Result should be the primitive array with a single field.
531565 assert_eq ! (
532566 result. dtype( ) ,
533- & DType :: Struct (
534- StructFields :: from_fields(
535- vec![ FieldName :: from( "a" ) ] . into( ) ,
536- vec![ FieldDType :: from( DType :: Primitive (
537- PType :: I32 ,
538- Nullability :: NonNullable ,
539- ) ) ]
540- ) ,
541- Nullability :: Nullable ,
542- )
567+ & DType :: Primitive ( PType :: I32 , Nullability :: Nullable )
543568 ) ;
544569
570+ // ...and the result is masked with the validity of the parent StructArray
545571 assert_eq ! ( result. scalar_at( 0 ) , Scalar :: null( result. dtype( ) . clone( ) ) , ) ;
572+ assert_eq ! ( result. scalar_at( 1 ) , 2 . into( ) ) ;
573+ assert_eq ! ( result. scalar_at( 2 ) , 3 . into( ) ) ;
574+ }
575+
576+ #[ rstest]
577+ fn test_struct_layout_nested (
578+ #[ from( nested_struct_layout) ] ( segments, layout) : ( Arc < dyn SegmentSource > , LayoutRef ) ,
579+ ) {
580+ // Project out the nested struct field.
581+ // The projection should preserve the nulls of the `a` column when we select out the
582+ // child column `c`.
583+ let reader = layout. new_reader ( "" . into ( ) , segments) . unwrap ( ) ;
584+ let expr = select (
585+ vec ! [ FieldName :: from( "c" ) ] ,
586+ get_item ( "b" , get_item ( "a" , root ( ) ) ) ,
587+ ) ;
588+
589+ // Also make sure that nulls are handled appropriately.
590+ // Also make sure the mask is pushed down and applied to the nested types.
591+ let project = reader
592+ . projection_evaluation ( & ( 0 ..3 ) , & expr, MaskFuture :: new_true ( 3 ) )
593+ . unwrap ( ) ;
546594
547- assert ! ( result. scalar_at( 1 ) . is_valid( ) ) ;
548- assert ! ( result. scalar_at( 2 ) . is_valid( ) ) ;
595+ // evaluate the projection, yielding some buff
549596 }
550597}
0 commit comments