@@ -11,12 +11,10 @@ use arrow_schema::FieldRef;
1111use vortex_array:: ArrayRef ;
1212use vortex_array:: Canonical ;
1313use vortex_array:: ExecutionCtx ;
14- use vortex_array:: arrays:: Chunked ;
1514use vortex_array:: arrays:: List ;
1615use vortex_array:: arrays:: ListArray ;
1716use vortex_array:: arrays:: ListView ;
1817use vortex_array:: arrays:: ListViewArray ;
19- use vortex_array:: arrays:: chunked:: ChunkedArrayExt ;
2018use vortex_array:: arrays:: list:: ListArraySlotsExt ;
2119use vortex_array:: arrays:: listview:: ListViewArraySlotsExt ;
2220use vortex_array:: arrays:: listview:: ListViewDataParts ;
@@ -41,7 +39,7 @@ impl Matcher for ArrowListExportable {
4139 type Match < ' a > = & ' a ArrayRef ;
4240
4341 fn try_match ( array : & ArrayRef ) -> Option < Self :: Match < ' _ > > {
44- ( array. is :: < List > ( ) || array. is :: < Chunked > ( ) || array . is :: < ListView > ( ) ) . then_some ( array)
42+ ( array. is :: < List > ( ) || array. is :: < ListView > ( ) ) . then_some ( array)
4543 }
4644}
4745
@@ -66,23 +64,9 @@ pub(super) fn to_arrow_list<O: OffsetSizeTrait + NativePType>(
6664 return list_to_list :: < O > ( & list. into_owned ( ) , elements_field, exporter, ctx) ;
6765 }
6866
69- // Converting each chunk individually, then using the fast concat logic from arrow
70- if let Some ( chunked) = array. as_opt :: < Chunked > ( ) {
71- let mut arrow_chunks: Vec < ArrowArrayRef > = Vec :: with_capacity ( chunked. nchunks ( ) ) ;
72- for chunk in chunked. chunks ( ) {
73- arrow_chunks. push ( to_arrow_list :: < O > (
74- chunk. clone ( ) ,
75- elements_field,
76- exporter,
77- ctx,
78- ) ?) ;
79- }
80-
81- let refs = arrow_chunks. iter ( ) . map ( |a| a. as_ref ( ) ) . collect :: < Vec < _ > > ( ) ;
82- return Ok ( arrow_select:: concat:: concat ( & refs) ?) ;
83- }
84-
8567 // Otherwise the array is canonical: a ListViewArray, which we rebuild to ZCTL if needed.
68+ // Chunked lists canonicalize to a zero-copy ListView over chunked elements, so they take
69+ // this path without an intermediate Arrow concatenation.
8670 // Note: arrow_cast::cast supports ListView → List (apache/arrow-rs#8735), but it
8771 // unconditionally uses take. Our rebuild uses a heuristic that picks list-by-list
8872 // for large lists, which avoids materializing a large index buffer.
@@ -226,12 +210,14 @@ mod tests {
226210 use arrow_array:: GenericListArray ;
227211 use arrow_array:: Int32Array ;
228212 use arrow_array:: Int64Array ;
213+ use arrow_buffer:: OffsetBuffer ;
229214 use arrow_schema:: DataType ;
230215 use arrow_schema:: Field ;
231216 use vortex_array:: Canonical ;
232217 use vortex_array:: IntoArray ;
233218 use vortex_array:: VortexSessionExecute ;
234219 use vortex_array:: arrays:: BoolArray ;
220+ use vortex_array:: arrays:: ChunkedArray ;
235221 use vortex_array:: arrays:: PrimitiveArray ;
236222 use vortex_array:: arrays:: SliceArray ;
237223 use vortex_array:: dtype:: DType ;
@@ -249,6 +235,39 @@ mod tests {
249235 /// A shared session for these list-executor tests, used to create execution contexts.
250236 static SESSION : LazyLock < VortexSession > = LazyLock :: new ( VortexSession :: empty) ;
251237
238+ #[ test]
239+ fn chunked_list_exports ( ) -> VortexResult < ( ) > {
240+ let mut ctx = SESSION . create_execution_ctx ( ) ;
241+ let first = ListArray :: try_new (
242+ buffer ! [ 1i32 , 2 , 3 ] . into_array ( ) ,
243+ buffer ! [ 0i32 , 2 , 3 ] . into_array ( ) ,
244+ Validity :: NonNullable ,
245+ & mut ctx,
246+ ) ?;
247+ let second = ListArray :: try_new (
248+ buffer ! [ 4i32 , 5 ] . into_array ( ) ,
249+ buffer ! [ 0i32 , 0 , 2 ] . into_array ( ) ,
250+ Validity :: NonNullable ,
251+ & mut ctx,
252+ ) ?;
253+ let dtype = first. dtype ( ) . clone ( ) ;
254+ let chunked = ChunkedArray :: try_new ( vec ! [ first. into_array( ) , second. into_array( ) ] , dtype) ?;
255+
256+ let field = Field :: new ( "item" , DataType :: Int32 , false ) ;
257+ let actual = chunked
258+ . into_array ( )
259+ . execute_arrow ( Some ( & DataType :: List ( field. clone ( ) . into ( ) ) ) , & mut ctx) ?;
260+
261+ let expected = GenericListArray :: < i32 > :: try_new (
262+ field. into ( ) ,
263+ OffsetBuffer :: new ( vec ! [ 0 , 2 , 3 , 3 , 5 ] . into ( ) ) ,
264+ Arc :: new ( Int32Array :: from ( vec ! [ 1 , 2 , 3 , 4 , 5 ] ) ) ,
265+ None ,
266+ ) ?;
267+ assert_eq ! ( & expected as & dyn Array , actual. as_ref( ) ) ;
268+ Ok ( ( ) )
269+ }
270+
252271 #[ test]
253272 fn test_to_arrow_list_i32 ( ) -> VortexResult < ( ) > {
254273 let mut ctx = SESSION . create_execution_ctx ( ) ;
0 commit comments