1use std::sync::Arc;
5
6use async_trait::async_trait;
7use futures::StreamExt;
8use futures::future::try_join;
9use futures::future::try_join_all;
10use vortex_array::ArrayContext;
11use vortex_array::ArrayRef;
12use vortex_array::ExecutionCtx;
13use vortex_array::IntoArray;
14use vortex_array::VortexSessionExecute;
15use vortex_array::arrays::ConstantArray;
16use vortex_array::arrays::List;
17use vortex_array::arrays::ListView;
18use vortex_array::arrays::PrimitiveArray;
19use vortex_array::arrays::list::ListDataParts;
20use vortex_array::arrays::listview::list_from_list_view;
21use vortex_array::builtins::ArrayBuiltins;
22use vortex_array::dtype::DType;
23use vortex_array::dtype::Nullability;
24use vortex_array::dtype::PType;
25use vortex_array::matcher::Matcher;
26use vortex_array::scalar_fn::fns::operators::Operator;
27use vortex_error::VortexExpect;
28use vortex_error::VortexResult;
29use vortex_error::vortex_bail;
30use vortex_io::kanal_ext::KanalExt;
31use vortex_io::session::RuntimeSessionExt;
32use vortex_session::VortexSession;
33
34use crate::IntoLayout;
35use crate::LayoutRef;
36use crate::LayoutStrategy;
37use crate::layouts::flat::writer::FlatLayoutStrategy;
38use crate::layouts::list::ListLayout;
39use crate::segments::SegmentSinkRef;
40use crate::sequence::SendableSequentialStream;
41use crate::sequence::SequenceId;
42use crate::sequence::SequencePointer;
43use crate::sequence::SequentialStream;
44use crate::sequence::SequentialStreamAdapter;
45use crate::sequence::SequentialStreamExt;
46
47type ChildChunk = VortexResult<(SequenceId, ArrayRef)>;
49
50#[derive(Clone)]
68pub struct ListLayoutStrategy {
69 elements: Arc<dyn LayoutStrategy>,
70 offsets: Arc<dyn LayoutStrategy>,
71 validity: Arc<dyn LayoutStrategy>,
72 fallback: Arc<dyn LayoutStrategy>,
73}
74
75impl Default for ListLayoutStrategy {
76 fn default() -> Self {
79 let flat: Arc<dyn LayoutStrategy> = Arc::new(FlatLayoutStrategy::default());
80 Self {
81 elements: Arc::clone(&flat),
82 offsets: Arc::clone(&flat),
83 validity: Arc::clone(&flat),
84 fallback: flat,
85 }
86 }
87}
88
89impl ListLayoutStrategy {
90 pub fn with_elements(mut self, elements: Arc<dyn LayoutStrategy>) -> Self {
92 self.elements = elements;
93 self
94 }
95
96 pub fn with_offsets(mut self, offsets: Arc<dyn LayoutStrategy>) -> Self {
98 self.offsets = offsets;
99 self
100 }
101
102 pub fn with_validity(mut self, validity: Arc<dyn LayoutStrategy>) -> Self {
104 self.validity = validity;
105 self
106 }
107
108 pub fn with_fallback(mut self, fallback: Arc<dyn LayoutStrategy>) -> Self {
110 self.fallback = fallback;
111 self
112 }
113}
114
115#[async_trait]
116impl LayoutStrategy for ListLayoutStrategy {
117 async fn write_stream(
118 &self,
119 ctx: ArrayContext,
120 segment_sink: SegmentSinkRef,
121 stream: SendableSequentialStream,
122 mut eof: SequencePointer,
123 session: &VortexSession,
124 ) -> VortexResult<LayoutRef> {
125 let dtype = stream.dtype().clone();
126 if !dtype.is_list() {
127 return self
128 .fallback
129 .write_stream(ctx, segment_sink, stream, eof, session)
130 .await;
131 }
132
133 let is_nullable = dtype.is_nullable();
134 let element_dtype = dtype
135 .as_list_element_opt()
136 .vortex_expect("DType is List")
137 .as_ref()
138 .clone();
139 let offsets_dtype = DType::Primitive(PType::U64, Nullability::NonNullable);
142
143 let (elements_tx, elements_rx) = kanal::bounded_async::<ChildChunk>(1);
145 let (offsets_tx, offsets_rx) = kanal::bounded_async::<ChildChunk>(1);
146 let (validity_tx, validity_rx) = if is_nullable {
147 let (tx, rx) = kanal::bounded_async::<ChildChunk>(1);
148 (Some(tx), Some(rx))
149 } else {
150 (None, None)
151 };
152
153 let fanout_fut = transpose_list_column(
157 stream,
158 session.clone(),
159 elements_tx,
160 offsets_tx,
161 validity_tx,
162 );
163
164 let handle = session.handle();
166 let mut child_specs: Vec<(
167 DType,
168 Arc<dyn LayoutStrategy>,
169 kanal::AsyncReceiver<ChildChunk>,
170 )> = vec![
171 (element_dtype, Arc::clone(&self.elements), elements_rx),
172 (offsets_dtype, Arc::clone(&self.offsets), offsets_rx),
173 ];
174 if let Some(validity_rx) = validity_rx {
175 child_specs.push((
176 DType::Bool(Nullability::NonNullable),
177 Arc::clone(&self.validity),
178 validity_rx,
179 ));
180 }
181
182 let layout_futures: Vec<_> = child_specs
183 .into_iter()
184 .map(|(child_dtype, strategy, rx)| {
185 let child_stream =
186 SequentialStreamAdapter::new(child_dtype, rx.into_stream().boxed()).sendable();
187 let child_eof = eof.split_off();
188 let ctx = ctx.clone();
189 let segment_sink = Arc::clone(&segment_sink);
190 let session = session.clone();
191 handle.spawn_nested(move |h| async move {
192 let session = session.with_handle(h);
193 strategy
194 .write_stream(ctx, segment_sink, child_stream, child_eof, &session)
195 .await
196 })
197 })
198 .collect();
199
200 let (_, layouts) = try_join(fanout_fut, try_join_all(layout_futures)).await?;
201 let mut layouts = layouts.into_iter();
202 let elements_layout = layouts.next().vortex_expect("elements layout present");
203 let offsets_layout = layouts.next().vortex_expect("offsets layout present");
204 let validity_layout =
205 is_nullable.then(|| layouts.next().vortex_expect("validity layout present"));
206
207 Ok(ListLayout::new(dtype, elements_layout, offsets_layout, validity_layout).into_layout())
208 }
209
210 fn buffered_bytes(&self) -> u64 {
211 let list_bytes = self.elements.buffered_bytes()
212 + self.offsets.buffered_bytes()
213 + self.validity.buffered_bytes();
214 list_bytes.max(self.fallback.buffered_bytes())
215 }
216}
217
218async fn transpose_list_column(
225 mut stream: SendableSequentialStream,
226 session: VortexSession,
227 elements_tx: kanal::AsyncSender<ChildChunk>,
228 offsets_tx: kanal::AsyncSender<ChildChunk>,
229 validity_tx: Option<kanal::AsyncSender<ChildChunk>>,
230) -> VortexResult<()> {
231 let mut exec_ctx = session.create_execution_ctx();
232 let mut element_base: u64 = 0;
233 let mut first = true;
234 let mut saw_chunk = false;
235 while let Some(chunk) = stream.next().await {
236 let (sequence_id, array) = chunk?;
237 saw_chunk = true;
238 let mut sp = sequence_id.descend();
239 let ListDataParts {
240 elements,
241 offsets,
242 validity,
243 ..
244 } = canonicalize_to_list_parts(array, &mut exec_ctx)?;
245 let n_elements = elements.len() as u64;
246 let row_count = offsets.len().saturating_sub(1);
247 let offsets = global_offsets(offsets, element_base, first, &mut exec_ctx)?;
248 element_base += n_elements;
249 first = false;
250
251 if elements_tx
252 .send(Ok((sp.advance(), elements)))
253 .await
254 .is_err()
255 || offsets_tx.send(Ok((sp.advance(), offsets))).await.is_err()
256 {
257 vortex_bail!("list child writer finished before all chunks were sent");
258 }
259 if let Some(validity_tx) = &validity_tx {
260 let validity = validity
261 .execute_mask(row_count, &mut exec_ctx)?
262 .into_array();
263 if validity_tx
264 .send(Ok((sp.advance(), validity)))
265 .await
266 .is_err()
267 {
268 vortex_bail!("list validity writer finished before all chunks were sent");
269 }
270 }
271 }
272 if !saw_chunk {
273 vortex_bail!("ListLayoutStrategy needs at least one chunk");
274 }
275 Ok(())
276}
277
278fn canonicalize_to_list_parts(
280 array: ArrayRef,
281 exec_ctx: &mut ExecutionCtx,
282) -> VortexResult<ListDataParts> {
283 let canonical = array.execute_until::<AnyList>(exec_ctx)?;
284 if let Some(list) = canonical.as_opt::<List>() {
285 Ok(list.into_owned().into_data_parts())
286 } else if let Some(view) = canonical.as_opt::<ListView>() {
287 Ok(list_from_list_view(view.into_owned(), exec_ctx)?.into_data_parts())
288 } else {
289 unreachable!("AnyList matcher guarantees List or ListView")
290 }
291}
292
293fn global_offsets(
299 offsets: ArrayRef,
300 element_base: u64,
301 first: bool,
302 exec_ctx: &mut ExecutionCtx,
303) -> VortexResult<ArrayRef> {
304 let widened = offsets.cast(DType::Primitive(PType::U64, Nullability::NonNullable))?;
305 let based = if element_base == 0 {
306 widened
307 } else {
308 let base = ConstantArray::new(element_base, widened.len()).into_array();
309 widened.binary(base, Operator::Add)?
310 };
311 let based = if first {
312 based
313 } else {
314 based.slice(1..based.len())?
315 };
316 Ok(based.execute::<PrimitiveArray>(exec_ctx)?.into_array())
318}
319
320struct AnyList;
322
323impl Matcher for AnyList {
324 type Match<'a> = ();
325
326 fn try_match(array: &ArrayRef) -> Option<Self::Match<'_>> {
327 (array.as_opt::<List>().is_some() || array.as_opt::<ListView>().is_some()).then_some(())
328 }
329}
330
331#[cfg(test)]
332mod tests {
333 use futures::stream;
334 use vortex_array::arrays::BoolArray;
335 use vortex_array::arrays::ChunkedArray;
336 use vortex_array::arrays::ListArray;
337 use vortex_array::arrays::StructArray;
338 use vortex_array::dtype::Nullability;
339 use vortex_array::dtype::PType;
340 use vortex_array::validity::Validity;
341 use vortex_buffer::buffer;
342 use vortex_io::session::RuntimeSession;
343
344 use super::*;
345 use crate::layouts::chunked::writer::ChunkedLayoutStrategy;
346 use crate::layouts::flat::writer::FlatLayoutStrategy;
347 use crate::layouts::table::TableStrategy;
348 use crate::segments::TestSegments;
349 use crate::sequence::SequentialArrayStreamExt;
350 use crate::session::LayoutSession;
351
352 fn layout_test_session() -> VortexSession {
353 vortex_array::array_session()
354 .with::<LayoutSession>()
355 .with::<RuntimeSession>()
356 .with_tokio()
357 }
358
359 fn flat_list_strategy() -> ListLayoutStrategy {
360 ListLayoutStrategy::default()
361 }
362
363 async fn write<S: LayoutStrategy>(strategy: &S, array: ArrayRef) -> VortexResult<LayoutRef> {
364 let session = layout_test_session();
365 let segments = Arc::new(TestSegments::default());
366 let (ptr, eof) = SequenceId::root().split();
367 let stream = array.to_array_stream().sequenced(ptr);
368 strategy
369 .write_stream(ArrayContext::empty(), segments, stream, eof, &session)
370 .await
371 }
372
373 fn i32_list_dtype(nullable: bool) -> DType {
374 DType::List(
375 Arc::new(DType::Primitive(PType::I32, Nullability::NonNullable)),
376 if nullable {
377 Nullability::Nullable
378 } else {
379 Nullability::NonNullable
380 },
381 )
382 }
383
384 fn create_basic_list(validity: Validity) -> ArrayRef {
385 ListArray::try_new(
386 buffer![1i32, 2, 3, 4, 5].into_array(),
387 buffer![0u32, 2, 5, 5].into_array(),
388 validity,
389 )
390 .unwrap()
391 .into_array()
392 }
393
394 #[tokio::test]
395 async fn basic_non_nullable_input() -> VortexResult<()> {
396 let list = create_basic_list(Validity::NonNullable);
397
398 let layout = write(&flat_list_strategy(), list).await?;
399 assert_eq!(layout.row_count(), 3);
400
401 insta::assert_snapshot!(layout.display_tree(), @"
402 vortex.list, dtype: list(i32), children: 2
403 ├── elements: vortex.flat, dtype: i32, segment: 0
404 └── offsets: vortex.flat, dtype: u64, segment: 1
405 ");
406 Ok(())
407 }
408
409 #[tokio::test]
410 async fn basic_nullable_input() -> VortexResult<()> {
411 let list = create_basic_list(Validity::Array(
412 BoolArray::from_iter([true, false, true]).into_array(),
413 ));
414
415 let layout = write(&flat_list_strategy(), list).await?;
416 assert_eq!(layout.row_count(), 3);
417
418 insta::assert_snapshot!(layout.display_tree(), @"
419 vortex.list, dtype: list(i32)?, children: 3
420 ├── elements: vortex.flat, dtype: i32, segment: 0
421 ├── offsets: vortex.flat, dtype: u64, segment: 1
422 └── validity: vortex.flat, dtype: bool, segment: 2
423 ");
424 Ok(())
425 }
426
427 #[tokio::test]
429 async fn non_list_input_routes_to_fallback() -> VortexResult<()> {
430 let primitive = buffer![1i32, 2, 3].into_array();
431 let layout = write(&flat_list_strategy(), primitive).await?;
432 insta::assert_snapshot!(layout.display_tree(), @"vortex.flat, dtype: i32, segment: 0");
433 Ok(())
434 }
435
436 #[tokio::test]
437 async fn empty_stream_errors() {
438 let segments = Arc::new(TestSegments::default());
439 let (_, eof) = SequenceId::root().split();
440 let empty = stream::empty::<VortexResult<(SequenceId, ArrayRef)>>().boxed();
441 let stream = SequentialStreamAdapter::new(i32_list_dtype(false), empty).sendable();
442 let session = layout_test_session();
443
444 let res = flat_list_strategy()
445 .write_stream(ArrayContext::empty(), segments, stream, eof, &session)
446 .await;
447 assert!(res.is_err())
448 }
449
450 #[tokio::test]
451 async fn list_of_struct_tree() -> VortexResult<()> {
452 let struct_array = StructArray::from_fields(
453 [
454 ("a", buffer![1i32, 2, 3, 4, 5].into_array()),
455 ("b", buffer![10i32, 20, 30, 40, 50].into_array()),
456 ]
457 .as_slice(),
458 )?
459 .into_array();
460 let list = ListArray::try_new(
461 struct_array,
462 buffer![0u32, 2, 5, 5].into_array(),
463 Validity::NonNullable,
464 )?
465 .into_array();
466
467 let flat: Arc<dyn LayoutStrategy> = Arc::new(FlatLayoutStrategy::default());
468 let table_strategy: Arc<dyn LayoutStrategy> =
469 Arc::new(TableStrategy::new(Arc::clone(&flat), Arc::clone(&flat)));
470 let writer = ListLayoutStrategy::default().with_elements(table_strategy);
471
472 let layout = write(&writer, list).await?;
473 insta::assert_snapshot!(layout.display_tree(), @"
474 vortex.list, dtype: list({a=i32, b=i32}), children: 2
475 ├── elements: vortex.struct, dtype: {a=i32, b=i32}, children: 2
476 │ ├── a: vortex.flat, dtype: i32, segment: 1
477 │ └── b: vortex.flat, dtype: i32, segment: 2
478 └── offsets: vortex.flat, dtype: u64, segment: 0
479 ");
480 Ok(())
481 }
482
483 #[tokio::test]
484 async fn list_of_list_tree() -> VortexResult<()> {
485 let inner_list = ListArray::try_new(
486 buffer![1i32, 2, 3, 4, 5, 6].into_array(),
487 buffer![0u32, 2, 5, 5, 6].into_array(),
488 Validity::NonNullable,
489 )?
490 .into_array();
491 let list = ListArray::try_new(
492 inner_list,
493 buffer![0u32, 2, 4].into_array(),
494 Validity::NonNullable,
495 )?
496 .into_array();
497
498 let writer =
499 ListLayoutStrategy::default().with_elements(Arc::new(ListLayoutStrategy::default()));
500 let layout = write(&writer, list).await?;
501 insta::assert_snapshot!(layout.display_tree(), @"
502 vortex.list, dtype: list(list(i32)), children: 2
503 ├── elements: vortex.list, dtype: list(i32), children: 2
504 │ ├── elements: vortex.flat, dtype: i32, segment: 1
505 │ └── offsets: vortex.flat, dtype: u64, segment: 2
506 └── offsets: vortex.flat, dtype: u64, segment: 0
507 ");
508 Ok(())
509 }
510
511 #[tokio::test]
512 async fn list_of_list_of_list_tree() -> VortexResult<()> {
513 let innermost = ListArray::try_new(
514 buffer![1i32, 2, 3, 4].into_array(),
515 buffer![0u32, 2, 4].into_array(),
516 Validity::NonNullable,
517 )?
518 .into_array();
519 let middle = ListArray::try_new(
520 innermost,
521 buffer![0u32, 2].into_array(),
522 Validity::NonNullable,
523 )?
524 .into_array();
525 let outer =
526 ListArray::try_new(middle, buffer![0u32, 1].into_array(), Validity::NonNullable)?
527 .into_array();
528
529 let writer = ListLayoutStrategy::default().with_elements(Arc::new(
530 ListLayoutStrategy::default().with_elements(Arc::new(ListLayoutStrategy::default())),
531 ));
532 let layout = write(&writer, outer).await?;
533 insta::assert_snapshot!(layout.display_tree(), @"
534 vortex.list, dtype: list(list(list(i32))), children: 2
535 ├── elements: vortex.list, dtype: list(list(i32)), children: 2
536 │ ├── elements: vortex.list, dtype: list(i32), children: 2
537 │ │ ├── elements: vortex.flat, dtype: i32, segment: 2
538 │ │ └── offsets: vortex.flat, dtype: u64, segment: 3
539 │ └── offsets: vortex.flat, dtype: u64, segment: 1
540 └── offsets: vortex.flat, dtype: u64, segment: 0
541 ");
542 Ok(())
543 }
544
545 #[tokio::test]
546 async fn chunked_list_input_with_chunked_strategy_succeeds() -> VortexResult<()> {
547 let chunk0 = ListArray::try_new(
548 buffer![1i32, 2, 3].into_array(),
549 buffer![0u32, 2, 3].into_array(),
550 Validity::NonNullable,
551 )
552 .unwrap()
553 .into_array();
554 let chunk1 = ListArray::try_new(
555 buffer![4i32, 5, 6, 7].into_array(),
556 buffer![0u32, 1, 4].into_array(),
557 Validity::NonNullable,
558 )
559 .unwrap()
560 .into_array();
561
562 let chunked =
563 ChunkedArray::try_new(vec![chunk0, chunk1], i32_list_dtype(false))?.into_array();
564
565 let layout = write(&ChunkedLayoutStrategy::new(flat_list_strategy()), chunked).await?;
566
567 insta::assert_snapshot!(layout.display_tree(), @"
568 vortex.chunked, dtype: list(i32), children: 2
569 ├── [0]: vortex.list, dtype: list(i32), children: 2
570 │ ├── elements: vortex.flat, dtype: i32, segment: 0
571 │ └── offsets: vortex.flat, dtype: u64, segment: 1
572 └── [1]: vortex.list, dtype: list(i32), children: 2
573 ├── elements: vortex.flat, dtype: i32, segment: 2
574 └── offsets: vortex.flat, dtype: u64, segment: 3
575 ");
576 Ok(())
577 }
578}