@@ -1953,3 +1953,233 @@ async fn test_segment_ordering_zonemaps_after_data() -> VortexResult<()> {
19531953
19541954 Ok ( ( ) )
19551955}
1956+
1957+ #[ tokio:: test]
1958+ #[ cfg_attr( miri, ignore) ]
1959+ async fn test_segment_ordering_array_trees_consolidated_and_after_data ( ) -> VortexResult < ( ) > {
1960+ // Multi-column struct large enough to produce chunked data, so each column will have
1961+ // many ArrayTreeFlat leaves. The collector should consolidate their compact trees into a
1962+ // single segment per ArrayTreeLayout.
1963+ let n = 100_000 ;
1964+ let values: Vec < & str > = ( 0 ..n) . map ( |i| [ "alpha" , "beta" , "gamma" ] [ i % 3 ] ) . collect ( ) ;
1965+ let strings = VarBinArray :: from ( values) . into_array ( ) ;
1966+ let numbers = PrimitiveArray :: from_iter ( 0 ..n as i32 ) . into_array ( ) ;
1967+ let floats = PrimitiveArray :: from_iter ( ( 0 ..n) . map ( |i| i as f64 * 0.1 ) ) . into_array ( ) ;
1968+
1969+ let st = StructArray :: from_fields ( & [
1970+ ( "strings" , strings) ,
1971+ ( "numbers" , numbers) ,
1972+ ( "floats" , floats) ,
1973+ ] )
1974+ . unwrap ( ) ;
1975+
1976+ let mut buf = ByteBufferMut :: empty ( ) ;
1977+ let strategy = crate :: WriteStrategyBuilder :: default ( )
1978+ . with_array_tree ( true )
1979+ . build ( ) ;
1980+ let summary = SESSION
1981+ . write_options ( )
1982+ . with_strategy ( strategy)
1983+ . write ( & mut buf, st. into_array ( ) . to_array_stream ( ) )
1984+ . await ?;
1985+
1986+ let footer = summary. footer ( ) ;
1987+ let segment_specs = footer. segment_map ( ) ;
1988+ let root = footer. layout ( ) ;
1989+
1990+ // For each ArrayTreeLayout in the tree, assert:
1991+ // 1. **Consolidation:** the auxiliary `array_trees` child writes exactly one segment.
1992+ // 2. **Per-column ordering:** every data segment under child 0 appears before the
1993+ // array_trees segment under child 1.
1994+ fn check_array_tree_layouts (
1995+ layout : & dyn Layout ,
1996+ segment_specs : & [ SegmentSpec ] ,
1997+ found_any : & mut bool ,
1998+ ) {
1999+ if layout. encoding_id ( ) . as_ref ( ) == "vortex.array_tree" {
2000+ * found_any = true ;
2001+
2002+ let data_child = layout. child ( 0 ) . unwrap ( ) ;
2003+ let array_trees_child = layout. child ( 1 ) . unwrap ( ) ;
2004+
2005+ let data_offsets = collect_segment_offsets ( data_child. as_ref ( ) , segment_specs) ;
2006+ let array_trees_offsets =
2007+ collect_segment_offsets ( array_trees_child. as_ref ( ) , segment_specs) ;
2008+
2009+ assert_eq ! (
2010+ array_trees_offsets. len( ) ,
2011+ 1 ,
2012+ "array_tree: auxiliary child must consolidate to exactly 1 segment, got {} segments at offsets {:?}" ,
2013+ array_trees_offsets. len( ) ,
2014+ array_trees_offsets,
2015+ ) ;
2016+
2017+ assert ! (
2018+ !data_offsets. is_empty( ) ,
2019+ "array_tree: data child must have at least one segment"
2020+ ) ;
2021+
2022+ assert_offsets_ordered (
2023+ & data_offsets,
2024+ & array_trees_offsets,
2025+ "array_tree: all data segments should come before the array_trees segment" ,
2026+ ) ;
2027+ }
2028+
2029+ for child in layout. children ( ) . unwrap ( ) {
2030+ check_array_tree_layouts ( child. as_ref ( ) , segment_specs, found_any) ;
2031+ }
2032+ }
2033+
2034+ let mut found_any = false ;
2035+ check_array_tree_layouts ( root. as_ref ( ) , segment_specs, & mut found_any) ;
2036+ assert ! (
2037+ found_any,
2038+ "test setup expected the default write strategy to produce at least one ArrayTreeLayout"
2039+ ) ;
2040+
2041+ Ok ( ( ) )
2042+ }
2043+
2044+ #[ tokio:: test]
2045+ #[ cfg_attr( miri, ignore) ]
2046+ async fn test_segment_ordering_array_trees_before_zones ( ) -> VortexResult < ( ) > {
2047+ // The write strategy wraps every column in `ZonedStrategy { data: ArrayTree, zones }`.
2048+ // Assert per-Zoned-layout that the array_trees segment (inside the data child) appears
2049+ // before every zone-map segment in the same column.
2050+ let n = 100_000 ;
2051+ let values: Vec < & str > = ( 0 ..n) . map ( |i| [ "alpha" , "beta" , "gamma" ] [ i % 3 ] ) . collect ( ) ;
2052+ let strings = VarBinArray :: from ( values) . into_array ( ) ;
2053+ let numbers = PrimitiveArray :: from_iter ( 0 ..n as i32 ) . into_array ( ) ;
2054+ let floats = PrimitiveArray :: from_iter ( ( 0 ..n) . map ( |i| i as f64 * 0.1 ) ) . into_array ( ) ;
2055+
2056+ let st = StructArray :: from_fields ( & [
2057+ ( "strings" , strings) ,
2058+ ( "numbers" , numbers) ,
2059+ ( "floats" , floats) ,
2060+ ] )
2061+ . unwrap ( ) ;
2062+
2063+ let mut buf = ByteBufferMut :: empty ( ) ;
2064+ let strategy = crate :: WriteStrategyBuilder :: default ( )
2065+ . with_array_tree ( true )
2066+ . build ( ) ;
2067+ let summary = SESSION
2068+ . write_options ( )
2069+ . with_strategy ( strategy)
2070+ . write ( & mut buf, st. into_array ( ) . to_array_stream ( ) )
2071+ . await ?;
2072+
2073+ let footer = summary. footer ( ) ;
2074+ let segment_specs = footer. segment_map ( ) ;
2075+ let root = footer. layout ( ) ;
2076+
2077+ fn check_zoned_with_array_tree (
2078+ layout : & dyn Layout ,
2079+ segment_specs : & [ SegmentSpec ] ,
2080+ found_any : & mut bool ,
2081+ ) {
2082+ if layout. encoding_id ( ) . as_ref ( ) == "vortex.stats" {
2083+ let data_child = layout. child ( 0 ) . unwrap ( ) ;
2084+ let zones_child = layout. child ( 1 ) . unwrap ( ) ;
2085+
2086+ if data_child. encoding_id ( ) . as_ref ( ) == "vortex.array_tree" {
2087+ * found_any = true ;
2088+ let array_trees_offsets =
2089+ collect_segment_offsets ( data_child. child ( 1 ) . unwrap ( ) . as_ref ( ) , segment_specs) ;
2090+ let zones_offsets = collect_segment_offsets ( zones_child. as_ref ( ) , segment_specs) ;
2091+
2092+ assert_offsets_ordered (
2093+ & array_trees_offsets,
2094+ & zones_offsets,
2095+ "zoned wrapping array_tree: the array_trees segment should come before zone-map segments" ,
2096+ ) ;
2097+ }
2098+ }
2099+
2100+ for child in layout. children ( ) . unwrap ( ) {
2101+ check_zoned_with_array_tree ( child. as_ref ( ) , segment_specs, found_any) ;
2102+ }
2103+ }
2104+
2105+ let mut found_any = false ;
2106+ check_zoned_with_array_tree ( root. as_ref ( ) , segment_specs, & mut found_any) ;
2107+ assert ! (
2108+ found_any,
2109+ "test setup expected the default write strategy to produce at least one Zoned wrapping an ArrayTree"
2110+ ) ;
2111+
2112+ Ok ( ( ) )
2113+ }
2114+
2115+ #[ tokio:: test]
2116+ #[ cfg_attr( miri, ignore) ]
2117+ async fn test_roundtrip_array_tree_layout ( ) -> VortexResult < ( ) > {
2118+ // End-to-end coverage: write with `with_array_tree(true)`, then read back through the
2119+ // source-publishing reader-ctx path and assert the data matches. Exercises:
2120+ // - ArrayTreeCollectorStrategy collecting compact trees from leaf transient state
2121+ // - ArrayTreeLayout::new_reader publishing the ArrayTreesSource into the reader ctx
2122+ // - ArrayTreeFlatReader pulling the source and resolving its tree by segment_id
2123+ // - ColumnarSerializedArray::from_segment_and_tree decoding the data segment
2124+ let mut ctx = SESSION . create_execution_ctx ( ) ;
2125+
2126+ let n = 10_000 ;
2127+ let strings_in: Vec < & str > = ( 0 ..n) . map ( |i| [ "alpha" , "beta" , "gamma" ] [ i % 3 ] ) . collect ( ) ;
2128+ let strings = VarBinArray :: from ( strings_in. clone ( ) ) . into_array ( ) ;
2129+ let numbers_in: Vec < i32 > = ( 0 ..n as i32 ) . collect ( ) ;
2130+ let numbers = PrimitiveArray :: from_iter ( numbers_in. iter ( ) . copied ( ) ) . into_array ( ) ;
2131+
2132+ let st = StructArray :: from_fields ( & [ ( "strings" , strings) , ( "numbers" , numbers) ] ) ?. into_array ( ) ;
2133+ let dtype = st. dtype ( ) . clone ( ) ;
2134+
2135+ let mut buf = ByteBufferMut :: empty ( ) ;
2136+ let strategy = crate :: WriteStrategyBuilder :: default ( )
2137+ . with_array_tree ( true )
2138+ . build ( ) ;
2139+ SESSION
2140+ . write_options ( )
2141+ . with_strategy ( strategy)
2142+ . write ( & mut buf, st. to_array_stream ( ) )
2143+ . await ?;
2144+
2145+ // Sanity-check we actually wrote ArrayTreeLayout nodes — otherwise the test would
2146+ // silently pass on the default code path.
2147+ let file = SESSION . open_options ( ) . open_buffer ( buf) ?;
2148+ fn has_array_tree ( layout : & dyn Layout ) -> bool {
2149+ if layout. encoding_id ( ) . as_ref ( ) == "vortex.array_tree" {
2150+ return true ;
2151+ }
2152+ layout
2153+ . children ( )
2154+ . map ( |cs| cs. iter ( ) . any ( |c| has_array_tree ( c. as_ref ( ) ) ) )
2155+ . unwrap_or ( false )
2156+ }
2157+ assert ! (
2158+ has_array_tree( file. footer( ) . layout( ) . as_ref( ) ) ,
2159+ "test expected ArrayTreeLayout in the written file"
2160+ ) ;
2161+
2162+ let result = file. scan ( ) ?. into_array_stream ( ) ?. read_all ( ) . await ?;
2163+ assert_eq ! ( result. len( ) , n) ;
2164+ assert_eq ! ( result. dtype( ) , & dtype) ;
2165+
2166+ let struct_array = result. execute :: < StructArray > ( & mut ctx) ?;
2167+
2168+ let read_numbers = struct_array. unmasked_field_by_name ( "numbers" ) . cloned ( ) ?;
2169+ let expected_numbers = PrimitiveArray :: from_iter ( numbers_in. iter ( ) . copied ( ) ) . into_array ( ) ;
2170+ assert_arrays_eq ! ( read_numbers, expected_numbers) ;
2171+
2172+ let read_strings = struct_array
2173+ . unmasked_field_by_name ( "strings" )
2174+ . cloned ( ) ?
2175+ . execute :: < VarBinViewArray > ( & mut ctx) ?
2176+ . with_iterator ( |iter| {
2177+ iter. map ( |s| s. map ( |st| unsafe { String :: from_utf8_unchecked ( st. to_vec ( ) ) } ) )
2178+ . collect :: < Vec < _ > > ( )
2179+ } ) ;
2180+ let expected_strings: Vec < Option < String > > =
2181+ strings_in. iter ( ) . map ( |s| Some ( ( * s) . to_string ( ) ) ) . collect ( ) ;
2182+ assert_eq ! ( read_strings, expected_strings) ;
2183+
2184+ Ok ( ( ) )
2185+ }
0 commit comments