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