@@ -1984,3 +1984,159 @@ async fn test_segment_ordering_zonemaps_after_data() -> VortexResult<()> {
19841984
19851985 Ok ( ( ) )
19861986}
1987+
1988+ #[ tokio:: test]
1989+ #[ cfg_attr( miri, ignore) ]
1990+ async fn test_segment_ordering_array_trees_consolidated_and_after_data ( ) -> VortexResult < ( ) > {
1991+ // Multi-column struct with enough rows to produce chunked data, so each column will have
1992+ // many ArrayTreeFlat leaves. The collector should consolidate their compact trees into a
1993+ // single segment per ArrayTreeLayout.
1994+ let n = 100_000 ;
1995+ let values: Vec < & str > = ( 0 ..n) . map ( |i| [ "alpha" , "beta" , "gamma" ] [ i % 3 ] ) . collect ( ) ;
1996+ let strings = VarBinArray :: from ( values) . into_array ( ) ;
1997+ let numbers = PrimitiveArray :: from_iter ( 0 ..n as i32 ) . into_array ( ) ;
1998+ let floats = PrimitiveArray :: from_iter ( ( 0 ..n) . map ( |i| i as f64 * 0.1 ) ) . into_array ( ) ;
1999+
2000+ let st = StructArray :: from_fields ( & [
2001+ ( "strings" , strings) ,
2002+ ( "numbers" , numbers) ,
2003+ ( "floats" , floats) ,
2004+ ] )
2005+ . unwrap ( ) ;
2006+
2007+ let mut buf = ByteBufferMut :: empty ( ) ;
2008+ let summary = SESSION
2009+ . write_options ( )
2010+ . write ( & mut buf, st. into_array ( ) . to_array_stream ( ) )
2011+ . await ?;
2012+
2013+ let footer = summary. footer ( ) ;
2014+ let segment_specs = footer. segment_map ( ) ;
2015+ let root = footer. layout ( ) ;
2016+
2017+ // Walk the layout tree and validate every ArrayTreeLayout we find.
2018+ //
2019+ // For each ArrayTreeLayout we assert two invariants:
2020+ // 1. **Consolidation:** the auxiliary `array_trees` child (child idx 1) writes exactly
2021+ // one segment — all compact flatbuffers from the leaves should land in a single
2022+ // contiguous payload, not be scattered across the file.
2023+ // 2. **Per-column ordering:** every data segment under child 0 appears before the
2024+ // array_trees segment under child 1.
2025+ fn check_array_tree_layouts (
2026+ layout : & dyn Layout ,
2027+ segment_specs : & [ SegmentSpec ] ,
2028+ found_any : & mut bool ,
2029+ ) {
2030+ if layout. encoding_id ( ) . as_ref ( ) == "vortex.array_tree" {
2031+ * found_any = true ;
2032+
2033+ let data_child = layout. child ( 0 ) . unwrap ( ) ;
2034+ let array_trees_child = layout. child ( 1 ) . unwrap ( ) ;
2035+
2036+ let data_offsets = collect_segment_offsets ( data_child. as_ref ( ) , segment_specs) ;
2037+ let array_trees_offsets =
2038+ collect_segment_offsets ( array_trees_child. as_ref ( ) , segment_specs) ;
2039+
2040+ assert_eq ! (
2041+ array_trees_offsets. len( ) ,
2042+ 1 ,
2043+ "array_tree: auxiliary child must consolidate to exactly 1 segment, got {} segments at offsets {:?}" ,
2044+ array_trees_offsets. len( ) ,
2045+ array_trees_offsets,
2046+ ) ;
2047+
2048+ assert ! (
2049+ !data_offsets. is_empty( ) ,
2050+ "array_tree: data child must have at least one segment"
2051+ ) ;
2052+
2053+ assert_offsets_ordered (
2054+ & data_offsets,
2055+ & array_trees_offsets,
2056+ "array_tree: all data segments should come before the array_trees segment" ,
2057+ ) ;
2058+ }
2059+
2060+ for child in layout. children ( ) . unwrap ( ) {
2061+ check_array_tree_layouts ( child. as_ref ( ) , segment_specs, found_any) ;
2062+ }
2063+ }
2064+
2065+ let mut found_any = false ;
2066+ check_array_tree_layouts ( root. as_ref ( ) , segment_specs, & mut found_any) ;
2067+ assert ! (
2068+ found_any,
2069+ "test setup expected the default write strategy to produce at least one ArrayTreeLayout"
2070+ ) ;
2071+
2072+ Ok ( ( ) )
2073+ }
2074+
2075+ #[ tokio:: test]
2076+ #[ cfg_attr( miri, ignore) ]
2077+ async fn test_segment_ordering_array_trees_before_zones ( ) -> VortexResult < ( ) > {
2078+ // The default write strategy wraps every column in `ZonedStrategy { data: ArrayTree, zones }`.
2079+ // We assert per-Zoned-layout that the array_trees segment (sitting inside the data child)
2080+ // appears before every zone-map segment in the same column.
2081+ let n = 100_000 ;
2082+ let values: Vec < & str > = ( 0 ..n) . map ( |i| [ "alpha" , "beta" , "gamma" ] [ i % 3 ] ) . collect ( ) ;
2083+ let strings = VarBinArray :: from ( values) . into_array ( ) ;
2084+ let numbers = PrimitiveArray :: from_iter ( 0 ..n as i32 ) . into_array ( ) ;
2085+ let floats = PrimitiveArray :: from_iter ( ( 0 ..n) . map ( |i| i as f64 * 0.1 ) ) . into_array ( ) ;
2086+
2087+ let st = StructArray :: from_fields ( & [
2088+ ( "strings" , strings) ,
2089+ ( "numbers" , numbers) ,
2090+ ( "floats" , floats) ,
2091+ ] )
2092+ . unwrap ( ) ;
2093+
2094+ let mut buf = ByteBufferMut :: empty ( ) ;
2095+ let summary = SESSION
2096+ . write_options ( )
2097+ . write ( & mut buf, st. into_array ( ) . to_array_stream ( ) )
2098+ . await ?;
2099+
2100+ let footer = summary. footer ( ) ;
2101+ let segment_specs = footer. segment_map ( ) ;
2102+ let root = footer. layout ( ) ;
2103+
2104+ fn check_zoned_with_array_tree (
2105+ layout : & dyn Layout ,
2106+ segment_specs : & [ SegmentSpec ] ,
2107+ found_any : & mut bool ,
2108+ ) {
2109+ if layout. encoding_id ( ) . as_ref ( ) == "vortex.stats" {
2110+ let data_child = layout. child ( 0 ) . unwrap ( ) ;
2111+ let zones_child = layout. child ( 1 ) . unwrap ( ) ;
2112+
2113+ if data_child. encoding_id ( ) . as_ref ( ) == "vortex.array_tree" {
2114+ * found_any = true ;
2115+ let array_trees_offsets = collect_segment_offsets (
2116+ data_child. child ( 1 ) . unwrap ( ) . as_ref ( ) ,
2117+ segment_specs,
2118+ ) ;
2119+ let zones_offsets = collect_segment_offsets ( zones_child. as_ref ( ) , segment_specs) ;
2120+
2121+ assert_offsets_ordered (
2122+ & array_trees_offsets,
2123+ & zones_offsets,
2124+ "zoned wrapping array_tree: the array_trees segment should come before zone-map segments" ,
2125+ ) ;
2126+ }
2127+ }
2128+
2129+ for child in layout. children ( ) . unwrap ( ) {
2130+ check_zoned_with_array_tree ( child. as_ref ( ) , segment_specs, found_any) ;
2131+ }
2132+ }
2133+
2134+ let mut found_any = false ;
2135+ check_zoned_with_array_tree ( root. as_ref ( ) , segment_specs, & mut found_any) ;
2136+ assert ! (
2137+ found_any,
2138+ "test setup expected the default write strategy to produce at least one Zoned wrapping an ArrayTree"
2139+ ) ;
2140+
2141+ Ok ( ( ) )
2142+ }
0 commit comments