1use std::{ops::Range, sync::Arc};
5
6use arrow_array::{Array, ArrayRef, LargeListArray, ListArray, cast::AsArray, make_array};
7use arrow_schema::DataType;
8use futures::future::BoxFuture;
9use lance_arrow::deepcopy::deep_copy_nulls;
10use lance_arrow::list::ListArrayExt;
11use lance_core::Result;
12
13use crate::{
14 decoder::{
15 DecodedArray, FilterExpression, ScheduledScanLine, SchedulerContext,
16 StructuralDecodeArrayTask, StructuralFieldDecoder, StructuralFieldScheduler,
17 StructuralSchedulingJob,
18 },
19 encoder::{EncodeTask, FieldEncoder, OutOfLineBuffers},
20 repdef::RepDefBuilder,
21};
22
23pub struct ListStructuralEncoder {
31 keep_original_array: bool,
32 child: Box<dyn FieldEncoder>,
33}
34
35impl ListStructuralEncoder {
36 pub fn new(keep_original_array: bool, child: Box<dyn FieldEncoder>) -> Self {
37 Self {
38 keep_original_array,
39 child,
40 }
41 }
42}
43
44impl FieldEncoder for ListStructuralEncoder {
45 fn maybe_encode(
46 &mut self,
47 array: ArrayRef,
48 external_buffers: &mut OutOfLineBuffers,
49 mut repdef: RepDefBuilder,
50 row_number: u64,
51 num_rows: u64,
52 ) -> Result<Vec<EncodeTask>> {
53 let values = if let Some(list_arr) = array.as_list_opt::<i32>() {
54 let has_garbage_values = if self.keep_original_array {
55 repdef.add_offsets(list_arr.offsets().clone(), array.nulls().cloned())
56 } else {
57 repdef.add_offsets(list_arr.offsets().clone(), deep_copy_nulls(array.nulls()))
59 };
60 if has_garbage_values {
61 list_arr.filter_garbage_nulls().trimmed_values()
62 } else {
63 list_arr.trimmed_values()
64 }
65 } else if let Some(list_arr) = array.as_list_opt::<i64>() {
66 let has_garbage_values = if self.keep_original_array {
67 repdef.add_offsets(list_arr.offsets().clone(), array.nulls().cloned())
68 } else {
69 repdef.add_offsets(list_arr.offsets().clone(), deep_copy_nulls(array.nulls()))
70 };
71 if has_garbage_values {
72 list_arr.filter_garbage_nulls().trimmed_values()
73 } else {
74 list_arr.trimmed_values()
75 }
76 } else {
77 panic!("List encoder used for non-list data")
78 };
79 self.child
80 .maybe_encode(values, external_buffers, repdef, row_number, num_rows)
81 }
82
83 fn flush(&mut self, external_buffers: &mut OutOfLineBuffers) -> Result<Vec<EncodeTask>> {
84 self.child.flush(external_buffers)
85 }
86
87 fn num_columns(&self) -> u32 {
88 self.child.num_columns()
89 }
90
91 fn finish(
92 &mut self,
93 external_buffers: &mut OutOfLineBuffers,
94 ) -> BoxFuture<'_, Result<Vec<crate::encoder::EncodedColumn>>> {
95 self.child.finish(external_buffers)
96 }
97}
98
99#[derive(Debug)]
100pub struct StructuralListScheduler {
101 child: Box<dyn StructuralFieldScheduler>,
102}
103
104impl StructuralListScheduler {
105 pub fn new(child: Box<dyn StructuralFieldScheduler>) -> Self {
106 Self { child }
107 }
108}
109
110impl StructuralFieldScheduler for StructuralListScheduler {
111 fn schedule_ranges<'a>(
112 &'a self,
113 ranges: &[Range<u64>],
114 filter: &FilterExpression,
115 ) -> Result<Box<dyn StructuralSchedulingJob + 'a>> {
116 let child = self.child.schedule_ranges(ranges, filter)?;
117
118 Ok(Box::new(StructuralListSchedulingJob::new(child)))
119 }
120
121 fn initialize<'a>(
122 &'a mut self,
123 filter: &'a FilterExpression,
124 context: &'a SchedulerContext,
125 ) -> BoxFuture<'a, Result<()>> {
126 self.child.initialize(filter, context)
127 }
128}
129
130#[derive(Debug)]
135struct StructuralListSchedulingJob<'a> {
136 child: Box<dyn StructuralSchedulingJob + 'a>,
137}
138
139impl<'a> StructuralListSchedulingJob<'a> {
140 fn new(child: Box<dyn StructuralSchedulingJob + 'a>) -> Self {
141 Self { child }
142 }
143}
144
145impl StructuralSchedulingJob for StructuralListSchedulingJob<'_> {
146 fn schedule_next(&mut self, context: &mut SchedulerContext) -> Result<Vec<ScheduledScanLine>> {
147 self.child.schedule_next(context)
148 }
149}
150
151#[derive(Debug)]
152pub struct StructuralListDecoder {
153 child: Box<dyn StructuralFieldDecoder>,
154 data_type: DataType,
155}
156
157impl StructuralListDecoder {
158 pub fn new(child: Box<dyn StructuralFieldDecoder>, data_type: DataType) -> Self {
159 Self { child, data_type }
160 }
161}
162
163impl StructuralFieldDecoder for StructuralListDecoder {
164 fn accept_page(&mut self, child: crate::decoder::LoadedPageShard) -> Result<()> {
165 self.child.accept_page(child)
166 }
167
168 fn drain(&mut self, num_rows: u64) -> Result<Box<dyn StructuralDecodeArrayTask>> {
169 let child_task = self.child.drain(num_rows)?;
170 Ok(Box::new(StructuralListDecodeTask::new(
171 child_task,
172 self.data_type.clone(),
173 )))
174 }
175
176 fn data_type(&self) -> &DataType {
177 &self.data_type
178 }
179}
180
181#[derive(Debug)]
182struct StructuralListDecodeTask {
183 child_task: Box<dyn StructuralDecodeArrayTask>,
184 data_type: DataType,
185}
186
187impl StructuralListDecodeTask {
188 fn new(child_task: Box<dyn StructuralDecodeArrayTask>, data_type: DataType) -> Self {
189 Self {
190 child_task,
191 data_type,
192 }
193 }
194}
195
196impl StructuralDecodeArrayTask for StructuralListDecodeTask {
197 fn decode(self: Box<Self>) -> Result<DecodedArray> {
198 let DecodedArray {
199 array,
200 mut repdef,
201 data_size,
202 } = self.child_task.decode()?;
203 match &self.data_type {
204 DataType::List(child_field) => {
205 let (offsets, validity) = repdef.unravel_offsets::<i32>()?;
206 let array = if !child_field.is_nullable() && array.null_count() == array.len() {
207 make_array(array.into_data().into_builder().nulls(None).build()?)
208 } else {
209 array
210 };
211 let list_array = ListArray::try_new(child_field.clone(), offsets, array, validity)?;
212
213 Ok(DecodedArray {
214 array: Arc::new(list_array),
215 repdef,
216 data_size,
217 })
218 }
219 DataType::LargeList(child_field) => {
220 let (offsets, validity) = repdef.unravel_offsets::<i64>()?;
221 let list_array =
222 LargeListArray::try_new(child_field.clone(), offsets, array, validity)?;
223 Ok(DecodedArray {
224 array: Arc::new(list_array),
225 repdef,
226 data_size,
227 })
228 }
229 _ => panic!("List decoder did not have a list field"),
230 }
231 }
232}
233
234#[cfg(test)]
235mod tests {
236
237 use std::{collections::HashMap, sync::Arc};
238
239 use crate::constants::{
240 STRUCTURAL_ENCODING_FULLZIP, STRUCTURAL_ENCODING_META_KEY, STRUCTURAL_ENCODING_MINIBLOCK,
241 };
242 use arrow_array::{
243 Array, ArrayRef, BooleanArray, DictionaryArray, LargeStringArray, ListArray, StringArray,
244 StructArray, UInt8Array, UInt64Array,
245 builder::{
246 Int32Builder, Int64Builder, LargeListBuilder, ListBuilder, StringBuilder, UInt32Builder,
247 },
248 };
249
250 use arrow_buffer::{BooleanBuffer, NullBuffer, OffsetBuffer, ScalarBuffer};
251 use arrow_schema::{DataType, Field, Fields};
252 use lance_datagen::{RowCount, Seed, array, gen_batch};
253 use rstest::rstest;
254
255 use crate::testing::{
256 TestCases, TestEncoding, check_round_trip_encoding_of_data, create_test_field_encoder,
257 test_encoding_strategy,
258 };
259
260 fn make_list_type(inner_type: DataType) -> DataType {
261 DataType::List(Arc::new(Field::new("item", inner_type, true)))
262 }
263
264 fn make_large_list_type(inner_type: DataType) -> DataType {
265 DataType::LargeList(Arc::new(Field::new("item", inner_type, true)))
266 }
267
268 #[derive(Clone, Copy)]
269 enum NullPattern {
270 None,
271 Mixed,
272 All,
273 }
274
275 async fn check_nested_type(
276 data_type: DataType,
277 null_pattern: NullPattern,
278 encoding: TestEncoding,
279 ) {
280 check_nested_type_with_metadata(data_type, null_pattern, encoding, HashMap::new()).await;
281 }
282
283 async fn check_nested_type_with_metadata(
284 data_type: DataType,
285 null_pattern: NullPattern,
286 encoding: TestEncoding,
287 field_metadata: HashMap<String, String>,
288 ) {
289 let null_rate = match null_pattern {
290 NullPattern::None => None,
291 NullPattern::Mixed => Some(0.5),
292 NullPattern::All => Some(1.0),
293 };
294 let make_batch = |seed, rows| {
295 let mut generator = gen_batch()
296 .with_seed(Seed::from(seed))
297 .anon_col(array::rand_type(&data_type));
298 if let Some(null_rate) = null_rate {
299 generator.with_random_nulls(null_rate);
300 }
301 generator
302 .into_batch_rows(RowCount::from(rows))
303 .unwrap()
304 .column(0)
305 .clone()
306 };
307
308 let first = make_batch(0, 513).slice(1, 512);
312 let second = make_batch(1, 513);
313 let test_cases = TestCases::default()
314 .with_page_sizes(vec![4096])
315 .with_encoding(encoding)
316 .with_batch_size(257)
317 .with_range(510..515)
318 .with_indices(vec![0, 511, 512, 1024]);
319
320 check_round_trip_encoding_of_data(vec![first, second], &test_cases, field_metadata).await;
321 }
322
323 async fn try_encode_v22_pages(
324 array: ArrayRef,
325 ) -> lance_core::Result<Vec<crate::encoder::EncodedPage>> {
326 try_encode_v22_pages_with_metadata(array, HashMap::new()).await
327 }
328
329 async fn try_encode_v22_pages_with_metadata(
330 array: ArrayRef,
331 field_metadata: HashMap<String, String>,
332 ) -> lance_core::Result<Vec<crate::encoder::EncodedPage>> {
333 let arrow_field =
334 Field::new("", array.data_type().clone(), true).with_metadata(field_metadata);
335 let lance_field = lance_core::datatypes::Field::try_from(&arrow_field).unwrap();
336 let encoding_strategy = test_encoding_strategy(TestEncoding::StructuralU32);
337 let mut column_index_seq = crate::encoder::ColumnIndexSequence::default();
338 let encoding_options = crate::encoder::EncodingOptions::default();
339 let mut encoder = create_test_field_encoder(
340 encoding_strategy.as_ref(),
341 &lance_field,
342 &mut column_index_seq,
343 &encoding_options,
344 )
345 .unwrap();
346 let mut external_buffers =
347 crate::encoder::OutOfLineBuffers::new(0, crate::encoder::MIN_PAGE_BUFFER_ALIGNMENT);
348 let num_rows = array.len() as u64;
349 let mut pages = Vec::new();
350 for task in encoder
351 .maybe_encode(
352 array,
353 &mut external_buffers,
354 crate::repdef::RepDefBuilder::default(),
355 0,
356 num_rows,
357 )
358 .unwrap()
359 {
360 pages.push(task.await?);
361 }
362 for task in encoder.flush(&mut external_buffers).unwrap() {
363 pages.push(task.await?);
364 }
365 Ok(pages)
366 }
367
368 async fn encode_v22_pages(array: ArrayRef) -> Vec<crate::encoder::EncodedPage> {
369 try_encode_v22_pages(array).await.unwrap()
370 }
371
372 fn assert_split_miniblock_layout(
373 pages: &[crate::encoder::EncodedPage],
374 min_miniblock_pages: usize,
375 expect_structural_only_page: bool,
376 ) {
377 let mut miniblock_pages = 0;
378 let mut fullzip_pages = 0;
379 let mut structural_only_pages = 0;
380
381 for page in pages {
382 let crate::decoder::PageEncoding::Structural(layout) = &page.description else {
383 continue;
384 };
385 match layout.layout.as_ref().unwrap() {
386 crate::format::pb21::page_layout::Layout::MiniBlockLayout(_) => {
387 miniblock_pages += 1;
388 }
389 crate::format::pb21::page_layout::Layout::FullZipLayout(_) => {
390 fullzip_pages += 1;
391 }
392 crate::format::pb21::page_layout::Layout::ConstantLayout(layout) => {
393 if layout.inline_value.is_none()
394 && (layout.num_rep_values > 0 || layout.num_def_values > 0)
395 {
396 structural_only_pages += 1;
397 }
398 }
399 crate::format::pb21::page_layout::Layout::BlobLayout(_) => {}
400 crate::format::pb21::page_layout::Layout::SparseLayout(_) => {}
401 }
402 }
403
404 assert!(
405 miniblock_pages >= min_miniblock_pages,
406 "expected at least {min_miniblock_pages} mini-block pages, got {miniblock_pages}"
407 );
408 assert_eq!(
409 fullzip_pages, 0,
410 "split list pages should not fall back to full-zip"
411 );
412 assert_eq!(
413 structural_only_pages > 0,
414 expect_structural_only_page,
415 "structural-only page presence did not match expectation; got {structural_only_pages}"
416 );
417 }
418
419 fn assert_has_fullzip_layout(pages: &[crate::encoder::EncodedPage]) {
420 let has_fullzip = pages.iter().any(|page| {
421 let crate::decoder::PageEncoding::Structural(layout) = &page.description else {
422 return false;
423 };
424 matches!(
425 layout.layout.as_ref().unwrap(),
426 crate::format::pb21::page_layout::Layout::FullZipLayout(_)
427 )
428 });
429 assert!(has_fullzip, "expected at least one full-zip page");
430 }
431
432 #[rstest]
433 #[test_log::test(tokio::test)]
434 async fn test_list(
435 #[values(STRUCTURAL_ENCODING_MINIBLOCK, STRUCTURAL_ENCODING_FULLZIP)]
436 structural_encoding: &str,
437 #[values(NullPattern::None, NullPattern::Mixed, NullPattern::All)]
438 null_pattern: NullPattern,
439 #[values(
440 TestEncoding::Array,
441 TestEncoding::StructuralU16,
442 TestEncoding::StructuralU32,
443 TestEncoding::StructuralSparse
444 )]
445 encoding: TestEncoding,
446 ) {
447 let mut field_metadata = HashMap::new();
448 field_metadata.insert(
449 STRUCTURAL_ENCODING_META_KEY.to_string(),
450 structural_encoding.into(),
451 );
452 check_nested_type_with_metadata(
453 make_list_type(DataType::Int32),
454 null_pattern,
455 encoding,
456 field_metadata,
457 )
458 .await;
459 }
460
461 #[rstest]
462 #[test_log::test(tokio::test)]
463 async fn test_deeply_nested_lists(
464 #[values(STRUCTURAL_ENCODING_MINIBLOCK, STRUCTURAL_ENCODING_FULLZIP)]
465 structural_encoding: &str,
466 #[values(1, 2, 3, 4, 5)] depth: usize,
467 #[values(
468 TestEncoding::Array,
469 TestEncoding::StructuralU16,
470 TestEncoding::StructuralU32,
471 TestEncoding::StructuralSparse
472 )]
473 test_encoding: TestEncoding,
474 ) {
475 let mut data_type = DataType::Int32;
476 for _ in 0..depth {
477 data_type = make_list_type(data_type);
478 }
479
480 let mut generator = gen_batch()
481 .with_seed(Seed::from(depth as u64))
482 .anon_col(array::rand_type(&data_type));
483 generator.with_random_nulls(0.2);
484 let source = generator
485 .into_batch_rows(RowCount::from(1026))
486 .unwrap()
487 .column(0)
488 .clone();
489
490 let data = vec![source.slice(1, 512), source.slice(513, 513)];
493 let test_cases = TestCases::default()
494 .with_page_sizes(vec![4096])
495 .with_encoding(test_encoding)
496 .with_batch_size(257)
497 .with_range(510..515)
498 .with_indices(vec![0, 511, 512, 1024]);
499 let field_metadata = HashMap::from([(
500 STRUCTURAL_ENCODING_META_KEY.to_string(),
501 structural_encoding.into(),
502 )]);
503
504 check_round_trip_encoding_of_data(data, &test_cases, field_metadata).await;
505 }
506
507 #[rstest]
508 #[test_log::test(tokio::test)]
509 async fn test_large_list(
510 #[values(NullPattern::None, NullPattern::Mixed, NullPattern::All)]
511 null_pattern: NullPattern,
512 #[values(
513 TestEncoding::Array,
514 TestEncoding::StructuralU16,
515 TestEncoding::StructuralU32,
516 TestEncoding::StructuralSparse
517 )]
518 encoding: TestEncoding,
519 ) {
520 check_nested_type(
521 make_large_list_type(DataType::Int32),
522 null_pattern,
523 encoding,
524 )
525 .await;
526 }
527
528 #[rstest]
529 #[test_log::test(tokio::test)]
530 async fn test_nested_strings(
531 #[values(NullPattern::None, NullPattern::Mixed, NullPattern::All)]
532 null_pattern: NullPattern,
533 #[values(
534 TestEncoding::Array,
535 TestEncoding::StructuralU16,
536 TestEncoding::StructuralU32,
537 TestEncoding::StructuralSparse
538 )]
539 encoding: TestEncoding,
540 ) {
541 check_nested_type(make_list_type(DataType::Utf8), null_pattern, encoding).await;
542 }
543
544 #[rstest]
545 #[test_log::test(tokio::test)]
546 async fn test_nested_list(
547 #[values(NullPattern::None, NullPattern::Mixed, NullPattern::All)]
548 null_pattern: NullPattern,
549 #[values(
550 TestEncoding::Array,
551 TestEncoding::StructuralU16,
552 TestEncoding::StructuralU32,
553 TestEncoding::StructuralSparse
554 )]
555 encoding: TestEncoding,
556 ) {
557 check_nested_type(
558 make_list_type(make_list_type(DataType::Int32)),
559 null_pattern,
560 encoding,
561 )
562 .await;
563 }
564
565 #[rstest]
584 #[test_log::test(tokio::test)]
585 async fn test_multipage_nested_float_list(
586 #[values(STRUCTURAL_ENCODING_MINIBLOCK, STRUCTURAL_ENCODING_FULLZIP)]
587 structural_encoding: &str,
588 #[values(
589 TestEncoding::StructuralU16,
590 TestEncoding::StructuralU32,
591 TestEncoding::StructuralSparse
592 )]
593 test_encoding: TestEncoding,
594 ) {
595 use arrow_array::Float32Array;
596
597 let inner_per_row: usize = 3;
599 let inner_len: usize = 768;
600 let chunk_rows: &[usize] = &[150, 149];
604
605 let make_chunk = |start_row: usize, num_rows: usize| -> Arc<dyn Array> {
606 let total_inner = num_rows * inner_per_row;
607 let total_values = total_inner * inner_len;
608 let values = Float32Array::from(
609 (0..total_values)
610 .map(|i| (start_row + i) as f32)
611 .collect::<Vec<_>>(),
612 );
613 let inner_offsets = ScalarBuffer::<i32>::from(
614 (0..=total_inner)
615 .map(|i| (i * inner_len) as i32)
616 .collect::<Vec<_>>(),
617 );
618 let inner_list = ListArray::new(
619 Arc::new(Field::new("item", DataType::Float32, true)),
620 OffsetBuffer::new(inner_offsets),
621 Arc::new(values),
622 None,
623 );
624 let outer_offsets = ScalarBuffer::<i32>::from(
625 (0..=num_rows)
626 .map(|i| (i * inner_per_row) as i32)
627 .collect::<Vec<_>>(),
628 );
629 Arc::new(ListArray::new(
630 Arc::new(Field::new(
631 "item",
632 DataType::List(Arc::new(Field::new("item", DataType::Float32, true))),
633 true,
634 )),
635 OffsetBuffer::new(outer_offsets),
636 Arc::new(inner_list),
637 None,
638 ))
639 };
640
641 let mut start = 0;
642 let chunks: Vec<Arc<dyn Array>> = chunk_rows
643 .iter()
644 .map(|&n| {
645 let c = make_chunk(start, n);
646 start += n;
647 c
648 })
649 .collect();
650
651 let mut field_metadata = HashMap::new();
652 field_metadata.insert(
653 STRUCTURAL_ENCODING_META_KEY.to_string(),
654 structural_encoding.into(),
655 );
656
657 let test_cases = TestCases::default()
658 .with_page_sizes(vec![1024 * 1024])
659 .with_encoding(test_encoding)
660 .with_batch_size(151)
661 .with_range(148..152)
662 .with_indices(vec![0, 149, 150, 298]);
663 check_round_trip_encoding_of_data(chunks, &test_cases, field_metadata).await;
664 }
665
666 #[rstest]
667 #[test_log::test(tokio::test)]
668 async fn test_list_struct_list(
669 #[values(NullPattern::None, NullPattern::Mixed, NullPattern::All)]
670 null_pattern: NullPattern,
671 #[values(
672 TestEncoding::Array,
673 TestEncoding::StructuralU16,
674 TestEncoding::StructuralU32,
675 TestEncoding::StructuralSparse
676 )]
677 encoding: TestEncoding,
678 ) {
679 let struct_type = DataType::Struct(Fields::from(vec![Field::new(
680 "inner_str",
681 DataType::Utf8,
682 false,
683 )]));
684
685 check_nested_type(make_list_type(struct_type), null_pattern, encoding).await;
686 }
687
688 #[rstest]
689 #[test_log::test(tokio::test)]
690 async fn test_list_struct_empty(
691 #[values(
692 TestEncoding::Array,
693 TestEncoding::StructuralU16,
694 TestEncoding::StructuralU32,
695 TestEncoding::StructuralSparse
696 )]
697 encoding: TestEncoding,
698 ) {
699 let fields = Fields::from(vec![Field::new("inner", DataType::UInt64, true)]);
700 let items = UInt64Array::from(Vec::<u64>::new());
701 let structs = StructArray::new(fields, vec![Arc::new(items)], None);
702 let num_rows = 2 * 1024 * 1024 / size_of::<i32>() + 1;
705 let offsets = OffsetBuffer::new(ScalarBuffer::<i32>::from(vec![0; num_rows + 1]));
706 let lists = ListArray::new(
707 Arc::new(Field::new("item", structs.data_type().clone(), true)),
708 offsets,
709 Arc::new(structs),
710 None,
711 );
712
713 check_round_trip_encoding_of_data(
714 vec![Arc::new(lists)],
715 &TestCases::default()
716 .with_page_sizes(vec![1024 * 1024])
717 .with_encoding(encoding),
718 HashMap::new(),
719 )
720 .await;
721 }
722
723 #[rstest]
724 #[test_log::test(tokio::test)]
725 async fn test_simple_list(
726 #[values(STRUCTURAL_ENCODING_MINIBLOCK, STRUCTURAL_ENCODING_FULLZIP)]
727 structural_encoding: &str,
728 ) {
729 let items_builder = Int32Builder::new();
730 let mut list_builder = ListBuilder::new(items_builder);
731 list_builder.append_value([Some(1), Some(2), Some(3)]);
732 list_builder.append_value([Some(4), Some(5)]);
733 list_builder.append_null();
734 list_builder.append_value([Some(6), Some(7), Some(8)]);
735 let list_array = list_builder.finish();
736
737 let mut field_metadata = HashMap::new();
738 field_metadata.insert(
739 STRUCTURAL_ENCODING_META_KEY.to_string(),
740 structural_encoding.into(),
741 );
742
743 let test_cases = TestCases::default()
744 .with_range(0..2)
745 .with_range(0..3)
746 .with_range(1..3)
747 .with_indices(vec![1, 3])
748 .with_indices(vec![2]);
749 check_round_trip_encoding_of_data(vec![Arc::new(list_array)], &test_cases, field_metadata)
750 .await;
751 }
752
753 #[rstest]
754 #[test_log::test(tokio::test)]
755 async fn test_simple_nested_list_ends_with_null(
756 #[values(STRUCTURAL_ENCODING_MINIBLOCK, STRUCTURAL_ENCODING_FULLZIP)]
757 structural_encoding: &str,
758 ) {
759 use arrow_array::Int32Array;
760
761 let values = Int32Array::from(vec![1, 2, 3, 4, 5]);
762 let inner_offsets = ScalarBuffer::<i32>::from(vec![0, 1, 2, 3, 4, 5, 5]);
763 let inner_validity = BooleanBuffer::from(vec![true, true, true, true, true, false]);
764 let outer_offsets = ScalarBuffer::<i32>::from(vec![0, 1, 2, 3, 4, 5, 6, 6]);
765 let outer_validity = BooleanBuffer::from(vec![true, true, true, true, true, true, false]);
766
767 let inner_list = ListArray::new(
768 Arc::new(Field::new("item", DataType::Int32, true)),
769 OffsetBuffer::new(inner_offsets),
770 Arc::new(values),
771 Some(NullBuffer::new(inner_validity)),
772 );
773 let outer_list = ListArray::new(
774 Arc::new(Field::new(
775 "item",
776 DataType::List(Arc::new(Field::new("item", DataType::Int32, true))),
777 true,
778 )),
779 OffsetBuffer::new(outer_offsets),
780 Arc::new(inner_list),
781 Some(NullBuffer::new(outer_validity)),
782 );
783
784 let mut field_metadata = HashMap::new();
785 field_metadata.insert(
786 STRUCTURAL_ENCODING_META_KEY.to_string(),
787 structural_encoding.into(),
788 );
789
790 let test_cases = TestCases::default()
791 .with_range(0..2)
792 .with_range(0..3)
793 .with_range(5..7)
794 .with_indices(vec![1, 6])
795 .with_indices(vec![6])
796 .with_structural_encodings();
797 check_round_trip_encoding_of_data(vec![Arc::new(outer_list)], &test_cases, field_metadata)
798 .await;
799 }
800
801 #[rstest]
802 #[test_log::test(tokio::test)]
803 async fn test_simple_string_list(
804 #[values(STRUCTURAL_ENCODING_MINIBLOCK, STRUCTURAL_ENCODING_FULLZIP)]
805 structural_encoding: &str,
806 ) {
807 let items_builder = StringBuilder::new();
808 let mut list_builder = ListBuilder::new(items_builder);
809 list_builder.append_value([Some("a"), Some("bc"), Some("def")]);
810 list_builder.append_value([Some("gh"), None]);
811 list_builder.append_null();
812 list_builder.append_value([Some("ijk"), Some("lmnop"), Some("qrs")]);
813 let list_array = list_builder.finish();
814
815 let mut field_metadata = HashMap::new();
816 field_metadata.insert(
817 STRUCTURAL_ENCODING_META_KEY.to_string(),
818 structural_encoding.into(),
819 );
820
821 let test_cases = TestCases::default()
822 .with_range(0..2)
823 .with_range(0..3)
824 .with_range(1..3)
825 .with_indices(vec![1, 3])
826 .with_indices(vec![2])
827 .with_structural_encodings();
828 check_round_trip_encoding_of_data(vec![Arc::new(list_array)], &test_cases, field_metadata)
829 .await;
830 }
831
832 #[rstest]
833 #[test_log::test(tokio::test)]
834 async fn test_simple_string_list_no_null(
835 #[values(STRUCTURAL_ENCODING_MINIBLOCK, STRUCTURAL_ENCODING_FULLZIP)]
836 structural_encoding: &str,
837 ) {
838 let items_builder = StringBuilder::new();
839 let mut list_builder = ListBuilder::new(items_builder);
840 list_builder.append_value([Some("a"), Some("bc"), Some("def")]);
841 list_builder.append_value([Some("gh"), Some("zxy")]);
842 list_builder.append_value([Some("gh"), Some("z")]);
843 list_builder.append_value([Some("ijk"), Some("lmnop"), Some("qrs")]);
844 let list_array = list_builder.finish();
845
846 let mut field_metadata = HashMap::new();
847 field_metadata.insert(
848 STRUCTURAL_ENCODING_META_KEY.to_string(),
849 structural_encoding.into(),
850 );
851
852 let test_cases = TestCases::default()
853 .with_range(0..2)
854 .with_range(0..3)
855 .with_range(1..3)
856 .with_indices(vec![1, 3])
857 .with_indices(vec![2])
858 .with_structural_encodings();
859 check_round_trip_encoding_of_data(vec![Arc::new(list_array)], &test_cases, field_metadata)
860 .await;
861 }
862
863 #[rstest]
864 #[test_log::test(tokio::test)]
865 async fn test_simple_sliced_list(
866 #[values(STRUCTURAL_ENCODING_MINIBLOCK, STRUCTURAL_ENCODING_FULLZIP)]
867 structural_encoding: &str,
868 ) {
869 let items_builder = Int32Builder::new();
870 let mut list_builder = ListBuilder::new(items_builder);
871 list_builder.append_value([Some(1), Some(2), Some(3)]);
872 list_builder.append_value([Some(4), Some(5)]);
873 list_builder.append_null();
874 list_builder.append_value([Some(6), Some(7), Some(8)]);
875 let list_array = list_builder.finish();
876
877 let list_array = list_array.slice(1, 2);
878
879 let mut field_metadata = HashMap::new();
880 field_metadata.insert(
881 STRUCTURAL_ENCODING_META_KEY.to_string(),
882 structural_encoding.into(),
883 );
884
885 let test_cases = TestCases::default()
886 .with_range(0..2)
887 .with_range(1..2)
888 .with_indices(vec![0])
889 .with_indices(vec![1])
890 .with_structural_encodings();
891 check_round_trip_encoding_of_data(vec![Arc::new(list_array)], &test_cases, field_metadata)
892 .await;
893 }
894
895 #[test_log::test(tokio::test)]
896 async fn test_simple_list_dict() {
897 let values = LargeStringArray::from_iter_values(["a", "bb", "ccc"]);
898 let indices = UInt8Array::from(vec![0, 1, 2, 0, 1, 2, 0, 1, 2]);
899 let dict_array = DictionaryArray::new(indices, Arc::new(values));
900 let offsets = OffsetBuffer::new(ScalarBuffer::<i32>::from(vec![0, 3, 5, 6, 9]));
901 let list_array = ListArray::new(
902 Arc::new(Field::new("item", dict_array.data_type().clone(), true)),
903 offsets,
904 Arc::new(dict_array),
905 None,
906 );
907
908 let test_cases = TestCases::default()
909 .with_range(0..2)
910 .with_range(1..3)
911 .with_range(2..4)
912 .with_indices(vec![1])
913 .with_indices(vec![2]);
914 check_round_trip_encoding_of_data(
915 vec![Arc::new(list_array)],
916 &test_cases,
917 HashMap::default(),
918 )
919 .await;
920 }
921
922 #[test_log::test(tokio::test)]
923 async fn test_simple_list_all_null() {
924 let items = UInt64Array::from(vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]);
925 let offsets = ScalarBuffer::<i32>::from(vec![0, 5, 8, 10]);
926 let offsets = OffsetBuffer::new(offsets);
927 let list_validity = NullBuffer::new(BooleanBuffer::from(vec![false, false, false]));
928
929 let list_arr = ListArray::new(
931 Arc::new(Field::new("item", DataType::UInt64, false)),
932 offsets,
933 Arc::new(items),
934 Some(list_validity),
935 );
936
937 let test_cases = TestCases::default()
938 .with_range(0..3)
939 .with_range(1..2)
940 .with_indices(vec![1])
941 .with_indices(vec![2])
942 .with_structural_encodings();
943 check_round_trip_encoding_of_data(
944 vec![Arc::new(list_arr)],
945 &test_cases,
946 HashMap::default(),
947 )
948 .await;
949 }
950
951 #[rstest]
952 #[test_log::test(tokio::test)]
953 async fn test_list_with_garbage_nulls(
954 #[values(STRUCTURAL_ENCODING_MINIBLOCK, STRUCTURAL_ENCODING_FULLZIP)]
955 structural_encoding: &str,
956 ) {
957 let items = UInt64Array::from(vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]);
960 let offsets = ScalarBuffer::<i32>::from(vec![0, 5, 8, 10]);
961 let offsets = OffsetBuffer::new(offsets);
962 let list_validity = NullBuffer::new(BooleanBuffer::from(vec![true, false, true]));
963 let list_arr = ListArray::new(
964 Arc::new(Field::new("item", DataType::UInt64, true)),
965 offsets,
966 Arc::new(items),
967 Some(list_validity),
968 );
969
970 let mut field_metadata = HashMap::new();
971 field_metadata.insert(
972 STRUCTURAL_ENCODING_META_KEY.to_string(),
973 structural_encoding.into(),
974 );
975
976 let test_cases = TestCases::default()
977 .with_range(0..3)
978 .with_range(1..2)
979 .with_indices(vec![1])
980 .with_indices(vec![2])
981 .with_structural_encodings();
982 check_round_trip_encoding_of_data(vec![Arc::new(list_arr)], &test_cases, field_metadata)
983 .await;
984 }
985
986 #[rstest]
987 #[test_log::test(tokio::test)]
988 async fn test_simple_two_page_list(
989 #[values(STRUCTURAL_ENCODING_MINIBLOCK, STRUCTURAL_ENCODING_FULLZIP)]
990 structural_encoding: &str,
991 ) {
992 let items_builder = Int64Builder::new();
996 let mut list_builder = ListBuilder::new(items_builder);
997 for i in 0..512 {
998 list_builder.append_value([Some(i), Some(i * 2)]);
999 }
1000 let list_array_1 = list_builder.finish();
1001
1002 let items_builder = Int64Builder::new();
1003 let mut list_builder = ListBuilder::new(items_builder);
1004 for i in 0..512 {
1005 let i = i + 512;
1006 list_builder.append_value([Some(i), Some(i * 2)]);
1007 }
1008 let list_array_2 = list_builder.finish();
1009
1010 let mut metadata = HashMap::new();
1011 metadata.insert(
1012 STRUCTURAL_ENCODING_META_KEY.to_string(),
1013 structural_encoding.into(),
1014 );
1015
1016 let test_cases = TestCases::default()
1017 .with_structural_encodings()
1018 .with_page_sizes(vec![100])
1019 .with_range(800..900);
1020 check_round_trip_encoding_of_data(
1021 vec![Arc::new(list_array_1), Arc::new(list_array_2)],
1022 &test_cases,
1023 metadata,
1024 )
1025 .await;
1026 }
1027
1028 #[test_log::test(tokio::test)]
1029 async fn test_simple_large_list() {
1030 let items_builder = Int32Builder::new();
1031 let mut list_builder = LargeListBuilder::new(items_builder);
1032 list_builder.append_value([Some(1), Some(2), Some(3)]);
1033 list_builder.append_value([Some(4), Some(5)]);
1034 list_builder.append_null();
1035 list_builder.append_value([Some(6), Some(7), Some(8)]);
1036 let list_array = list_builder.finish();
1037
1038 let test_cases = TestCases::default()
1039 .with_range(0..2)
1040 .with_range(0..3)
1041 .with_range(1..3)
1042 .with_indices(vec![1, 3]);
1043 check_round_trip_encoding_of_data(vec![Arc::new(list_array)], &test_cases, HashMap::new())
1044 .await;
1045 }
1046
1047 #[rstest]
1048 #[test_log::test(tokio::test)]
1049 async fn test_empty_lists(
1050 #[values(STRUCTURAL_ENCODING_MINIBLOCK, STRUCTURAL_ENCODING_FULLZIP)]
1051 structural_encoding: &str,
1052 ) {
1053 let mut field_metadata = HashMap::new();
1054 field_metadata.insert(
1055 STRUCTURAL_ENCODING_META_KEY.to_string(),
1056 structural_encoding.into(),
1057 );
1058
1059 let values = [vec![Some(1), Some(2), Some(3)], vec![], vec![None]];
1062 for order in [[0, 1, 2], [1, 0, 2], [2, 0, 1]] {
1064 let items_builder = Int32Builder::new();
1065 let mut list_builder = ListBuilder::new(items_builder);
1066 for idx in order {
1067 list_builder.append_value(values[idx].clone());
1068 }
1069 let list_array = Arc::new(list_builder.finish());
1070 let test_cases = TestCases::default()
1071 .with_indices(vec![1])
1072 .with_indices(vec![0])
1073 .with_indices(vec![2])
1074 .with_indices(vec![0, 1]);
1075 check_round_trip_encoding_of_data(
1076 vec![list_array.clone()],
1077 &test_cases,
1078 field_metadata.clone(),
1079 )
1080 .await;
1081 let test_cases = test_cases.with_batch_size(1);
1082 check_round_trip_encoding_of_data(
1083 vec![list_array],
1084 &test_cases,
1085 field_metadata.clone(),
1086 )
1087 .await;
1088 }
1089
1090 let items_builder = Int32Builder::new();
1095 let mut list_builder = ListBuilder::new(items_builder);
1096 list_builder.append(true);
1097 list_builder.append_null();
1098 list_builder.append(true);
1099 let list_array = Arc::new(list_builder.finish());
1100
1101 let test_cases = TestCases::default().with_range(0..2).with_indices(vec![1]);
1102 check_round_trip_encoding_of_data(
1103 vec![list_array.clone()],
1104 &test_cases,
1105 field_metadata.clone(),
1106 )
1107 .await;
1108 let test_cases = test_cases.with_batch_size(1);
1109 check_round_trip_encoding_of_data(vec![list_array], &test_cases, field_metadata.clone())
1110 .await;
1111
1112 let items_builder = StringBuilder::new();
1117 let mut list_builder = ListBuilder::new(items_builder);
1118 list_builder.append(true);
1119 list_builder.append_null();
1120 list_builder.append(true);
1121 let list_array = Arc::new(list_builder.finish());
1122
1123 let test_cases = TestCases::default().with_range(0..2).with_indices(vec![1]);
1124 check_round_trip_encoding_of_data(
1125 vec![list_array.clone()],
1126 &test_cases,
1127 field_metadata.clone(),
1128 )
1129 .await;
1130 let test_cases = test_cases.with_batch_size(1);
1131 check_round_trip_encoding_of_data(vec![list_array], &test_cases, field_metadata.clone())
1132 .await;
1133
1134 let items_builder = Int32Builder::new();
1137 let mut list_builder = ListBuilder::new(items_builder);
1138 list_builder.append_null();
1139 list_builder.append_null();
1140 list_builder.append_null();
1141 let list_array = Arc::new(list_builder.finish());
1142
1143 let test_cases = TestCases::default().with_range(0..2).with_indices(vec![1]);
1144 check_round_trip_encoding_of_data(
1145 vec![list_array.clone()],
1146 &test_cases,
1147 field_metadata.clone(),
1148 )
1149 .await;
1150 let test_cases = test_cases.with_batch_size(1);
1151 check_round_trip_encoding_of_data(vec![list_array], &test_cases, field_metadata.clone())
1152 .await;
1153
1154 let items_builder = Int32Builder::new();
1157 let mut list_builder = ListBuilder::new(items_builder);
1158 list_builder.append_null();
1159 list_builder.append_null();
1160 list_builder.append_null();
1161 let list_array = Arc::new(list_builder.finish());
1162
1163 let struct_validity = NullBuffer::new(BooleanBuffer::from(vec![true, false, true]));
1164 let struct_array = Arc::new(StructArray::new(
1165 Fields::from(vec![Field::new(
1166 "lists",
1167 list_array.data_type().clone(),
1168 true,
1169 )]),
1170 vec![list_array],
1171 Some(struct_validity),
1172 ));
1173
1174 let test_cases = TestCases::default()
1175 .with_range(0..2)
1176 .with_indices(vec![1])
1177 .with_structural_encodings();
1178 check_round_trip_encoding_of_data(
1179 vec![struct_array.clone()],
1180 &test_cases,
1181 field_metadata.clone(),
1182 )
1183 .await;
1184 let test_cases = test_cases.with_batch_size(1);
1185 check_round_trip_encoding_of_data(vec![struct_array], &test_cases, field_metadata.clone())
1186 .await;
1187 }
1188
1189 #[test_log::test(tokio::test)]
1190 async fn test_empty_list_list() {
1191 let items_builder = Int32Builder::new();
1192 let list_builder = ListBuilder::new(items_builder);
1193 let mut outer_list_builder = ListBuilder::new(list_builder);
1194 outer_list_builder.append_null();
1195 outer_list_builder.append_null();
1196 outer_list_builder.append_null();
1197 let list_array = Arc::new(outer_list_builder.finish());
1198
1199 let test_cases = TestCases::default().with_structural_encodings();
1200 check_round_trip_encoding_of_data(vec![list_array], &test_cases, HashMap::new()).await;
1201 }
1202
1203 #[test_log::test(tokio::test)]
1204 #[ignore] async fn test_jumbo_list() {
1206 let items = BooleanArray::new_null(1024 * 1024);
1210 let offsets = OffsetBuffer::new(ScalarBuffer::from(vec![0, 1024 * 1024]));
1211 let list_arr = Arc::new(ListArray::new(
1212 Arc::new(Field::new("item", DataType::Boolean, true)),
1213 offsets,
1214 Arc::new(items),
1215 None,
1216 )) as ArrayRef;
1217 let arrs = vec![list_arr; 5000];
1218
1219 let test_cases = TestCases::default().without_validation();
1221 check_round_trip_encoding_of_data(arrs, &test_cases, HashMap::new()).await;
1222 }
1223
1224 #[tokio::test]
1228 async fn test_fuzz_issue_4466() {
1229 let list_sizes = vec![
1232 13, 18, 12, 7, 14, 12, 6, 13, 18, 8, 6, 11, 17, 12, 8, 19, 5, 6, 10, 13, 8, 6, 10, 4, 8, 16, 14, 12, 18, 9, 17, 8, 14, 18, 15, 3, 2, 4, 5, 1, 3, 13, 1, 2, 10, 4, 10, 18, 7, 14, 18, 13, 9, 17, 3, 13, 10, 14, 8, 19, 17, 10, 5, 11, 6, 15, 10, 18, 18, 20, 16, 11, 12, 15, 7, 9, 3, 10, 20, 5, 2, 3, 17, 4, 8, 12, 15, 6, 3, 20, 15, 20, 1, 19, 16, ];
1243
1244 let mut list_builder = ListBuilder::new(Int32Builder::new());
1246 let mut total_values = 0;
1247
1248 for size in &list_sizes {
1249 for i in 0..*size {
1250 list_builder.values().append_value(i);
1251 }
1252 list_builder.append(true);
1253 total_values += size;
1254 }
1255
1256 let list_array = Arc::new(list_builder.finish());
1257
1258 assert_eq!(list_array.len(), 95);
1260 assert_eq!(total_values, 1025);
1261
1262 let test_cases = TestCases::default().with_structural_encodings();
1265
1266 check_round_trip_encoding_of_data(vec![list_array], &test_cases, HashMap::new()).await;
1270 }
1271
1272 #[rstest]
1273 #[test_log::test(tokio::test)]
1274 async fn test_sparse_large_string_list(
1275 #[values(STRUCTURAL_ENCODING_MINIBLOCK, STRUCTURAL_ENCODING_FULLZIP)]
1276 structural_encoding: &str,
1277 ) {
1278 let levels_per_chunk =
1281 crate::encodings::logical::primitive::miniblock::max_repdef_levels_per_chunk(2);
1282 let num_rows = (levels_per_chunk * 3) as u32;
1283 let num_non_empty = 100u32;
1284 let strings_per_list = 10;
1285
1286 let items_builder = StringBuilder::new();
1287 let mut list_builder = ListBuilder::new(items_builder);
1288
1289 let step = num_rows / num_non_empty;
1291 let mut next_non_empty = step / 2;
1292
1293 for i in 0..num_rows {
1294 if i == next_non_empty {
1295 let vals: Vec<Option<&str>> = (0..strings_per_list)
1296 .map(|j| match j % 4 {
1297 0 => Some("a"),
1298 1 => Some("bb"),
1299 2 => Some("ccc"),
1300 _ => Some("d"),
1301 })
1302 .collect();
1303 list_builder.append_value(vals);
1304 next_non_empty = next_non_empty.saturating_add(step);
1305 } else {
1306 list_builder.append_value([] as [Option<&str>; 0]);
1307 }
1308 }
1309 let list_array = list_builder.finish();
1310
1311 let mut field_metadata = HashMap::new();
1312 field_metadata.insert(
1313 STRUCTURAL_ENCODING_META_KEY.to_string(),
1314 structural_encoding.into(),
1315 );
1316
1317 let list_array = Arc::new(list_array) as ArrayRef;
1318 let pages = try_encode_v22_pages_with_metadata(list_array.clone(), field_metadata.clone())
1319 .await
1320 .unwrap();
1321 if structural_encoding == STRUCTURAL_ENCODING_MINIBLOCK {
1322 assert_split_miniblock_layout(&pages, 2, true);
1323 }
1324
1325 let chunk_boundary = levels_per_chunk;
1326 let test_cases = TestCases::default()
1327 .with_range(chunk_boundary - 2..chunk_boundary + 2)
1328 .with_indices(vec![
1329 0,
1330 (step / 2) as u64,
1331 chunk_boundary - 1,
1332 chunk_boundary,
1333 num_rows as u64 - 1,
1334 ])
1335 .with_batch_size(64 * 1024)
1336 .with_page_sizes(vec![1024 * 1024])
1337 .with_encoding(TestEncoding::StructuralU32);
1338 check_round_trip_encoding_of_data(vec![list_array], &test_cases, field_metadata).await;
1339 }
1340
1341 #[test_log::test(tokio::test)]
1342 async fn test_sparse_boolean_list_uses_miniblock() {
1343 let levels_per_chunk =
1347 crate::encodings::logical::primitive::miniblock::max_repdef_levels_per_chunk(2);
1348 let num_rows = (levels_per_chunk + 1) as usize;
1351 let booleans_per_list = 8usize;
1352
1353 let mut offsets = Vec::with_capacity(num_rows + 1);
1354 let mut values = Vec::with_capacity(2 * booleans_per_list);
1355 offsets.push(0i32);
1356
1357 for row in 0..num_rows {
1358 if row == 0 || row == num_rows - 1 {
1359 values.extend((0..booleans_per_list).map(|idx| idx % 2 == 0));
1360 }
1361 offsets.push(values.len() as i32);
1362 }
1363
1364 let items = BooleanArray::from(values);
1365 let list_array = ListArray::new(
1366 Arc::new(Field::new("item", DataType::Boolean, true)),
1367 OffsetBuffer::new(ScalarBuffer::from(offsets)),
1368 Arc::new(items),
1369 None,
1370 );
1371
1372 let test_cases = TestCases::default()
1373 .with_range(0..1000)
1374 .with_indices(vec![0, levels_per_chunk / 2, num_rows as u64 - 1])
1375 .with_batch_size(64 * 1024)
1376 .with_page_sizes(vec![1024 * 1024])
1377 .with_encoding(TestEncoding::StructuralU32);
1378 let list_array = Arc::new(list_array) as ArrayRef;
1379 let pages = encode_v22_pages(list_array.clone()).await;
1380 assert_split_miniblock_layout(&pages, 2, false);
1381 check_round_trip_encoding_of_data(vec![list_array], &test_cases, HashMap::new()).await;
1382 }
1383
1384 #[test_log::test(tokio::test)]
1385 async fn test_sparse_boolean_list_with_long_empty_prefix() {
1386 let empty_prefix_rows = 70_000usize;
1387 let trailing_empty_rows = 9usize;
1388 let booleans_per_list = 8usize;
1389 let num_rows = empty_prefix_rows + 1 + trailing_empty_rows;
1390
1391 let mut offsets = Vec::with_capacity(num_rows + 1);
1392 offsets.extend(std::iter::repeat_n(0i32, empty_prefix_rows + 1));
1393 let values = (0..booleans_per_list)
1394 .map(|idx| idx % 2 == 0)
1395 .collect::<Vec<_>>();
1396 offsets.push(values.len() as i32);
1397 offsets.extend(std::iter::repeat_n(
1398 values.len() as i32,
1399 trailing_empty_rows,
1400 ));
1401
1402 let items = BooleanArray::from(values);
1403 let list_array = ListArray::new(
1404 Arc::new(Field::new("item", DataType::Boolean, true)),
1405 OffsetBuffer::new(ScalarBuffer::from(offsets)),
1406 Arc::new(items),
1407 None,
1408 );
1409
1410 let test_cases = TestCases::default()
1411 .with_range(0..num_rows as u64)
1412 .with_indices(vec![0, empty_prefix_rows as u64, num_rows as u64 - 1])
1413 .with_dense_encodings();
1414 let list_array = Arc::new(list_array) as ArrayRef;
1415 let pages = encode_v22_pages(list_array.clone()).await;
1416 assert_split_miniblock_layout(&pages, 1, true);
1417 check_round_trip_encoding_of_data(vec![list_array], &test_cases, HashMap::new()).await;
1418 }
1419
1420 #[rstest]
1421 #[test_log::test(tokio::test)]
1422 async fn test_sparse_boolean_list_with_long_null_prefix(
1423 #[values(
1424 TestEncoding::Array,
1425 TestEncoding::StructuralU16,
1426 TestEncoding::StructuralU32
1427 )]
1428 encoding: TestEncoding,
1429 ) {
1430 let null_prefix_rows = 70_000usize;
1431 let trailing_empty_rows = 9usize;
1432 let booleans_per_list = 8usize;
1433 let num_rows = null_prefix_rows + 1 + trailing_empty_rows;
1434
1435 let mut offsets = Vec::with_capacity(num_rows + 1);
1436 offsets.extend(std::iter::repeat_n(0i32, null_prefix_rows + 1));
1437 let values = (0..booleans_per_list)
1438 .map(|idx| idx % 2 == 0)
1439 .collect::<Vec<_>>();
1440 offsets.push(values.len() as i32);
1441 offsets.extend(std::iter::repeat_n(
1442 values.len() as i32,
1443 trailing_empty_rows,
1444 ));
1445 let validity = BooleanBuffer::from_iter((0..num_rows).map(|row| row >= null_prefix_rows));
1446
1447 let items = BooleanArray::from(values);
1448 let list_array = ListArray::new(
1449 Arc::new(Field::new("item", DataType::Boolean, true)),
1450 OffsetBuffer::new(ScalarBuffer::from(offsets)),
1451 Arc::new(items),
1452 Some(NullBuffer::new(validity)),
1453 );
1454
1455 let test_cases = TestCases::default()
1456 .with_range(0..num_rows as u64)
1457 .with_indices(vec![0, null_prefix_rows as u64, num_rows as u64 - 1])
1458 .with_encoding(encoding);
1459 let list_array = Arc::new(list_array) as ArrayRef;
1460 let pages = encode_v22_pages(list_array.clone()).await;
1461 assert_split_miniblock_layout(&pages, 1, true);
1462 check_round_trip_encoding_of_data(vec![list_array], &test_cases, HashMap::new()).await;
1463 }
1464
1465 #[test_log::test(tokio::test)]
1466 async fn test_sparse_boolean_list_with_amortized_long_empty_prefix() {
1467 let empty_prefix_rows = 62_000usize;
1468 let booleans_per_list = 8_192usize;
1469 let num_rows = empty_prefix_rows + 1;
1470
1471 let mut offsets = Vec::with_capacity(num_rows + 1);
1472 offsets.extend(std::iter::repeat_n(0i32, empty_prefix_rows + 1));
1473 let values = (0..booleans_per_list)
1474 .map(|idx| idx % 2 == 0)
1475 .collect::<Vec<_>>();
1476 offsets.push(values.len() as i32);
1477
1478 let items = BooleanArray::from(values);
1479 let list_array = ListArray::new(
1480 Arc::new(Field::new("item", DataType::Boolean, true)),
1481 OffsetBuffer::new(ScalarBuffer::from(offsets)),
1482 Arc::new(items),
1483 None,
1484 );
1485
1486 let test_cases = TestCases::default()
1487 .with_range(0..num_rows as u64)
1488 .with_indices(vec![0, empty_prefix_rows as u64])
1489 .with_dense_encodings();
1490 let list_array = Arc::new(list_array) as ArrayRef;
1491 let pages = encode_v22_pages(list_array.clone()).await;
1492 assert_split_miniblock_layout(&pages, 1, true);
1493 check_round_trip_encoding_of_data(vec![list_array], &test_cases, HashMap::new()).await;
1494 }
1495
1496 fn unsplittable_nested_list(items: ArrayRef, empty_inner_lists: usize) -> ArrayRef {
1497 let mut inner_offsets = vec![0i32; empty_inner_lists + 1];
1498 inner_offsets.push(items.len() as i32);
1499 let inner_list = ListArray::new(
1500 Arc::new(Field::new("item", items.data_type().clone(), true)),
1501 OffsetBuffer::new(ScalarBuffer::from(inner_offsets)),
1502 items,
1503 None,
1504 );
1505 Arc::new(ListArray::new(
1506 Arc::new(Field::new("item", inner_list.data_type().clone(), true)),
1507 OffsetBuffer::new(ScalarBuffer::from(vec![0i32, empty_inner_lists as i32 + 1])),
1508 Arc::new(inner_list),
1509 None,
1510 ))
1511 }
1512
1513 #[rstest]
1514 #[case::boolean(Arc::new(BooleanArray::from(vec![true, false, true, false, true, false, true, false])))]
1515 #[case::string(Arc::new(StringArray::from(vec!["value", "other"])))]
1516 #[test_log::test(tokio::test)]
1517 async fn test_nested_sparse_single_row_falls_back_to_fullzip(#[case] items: ArrayRef) {
1518 let list = unsplittable_nested_list(items, 70_000);
1519 let pages = encode_v22_pages(list.clone()).await;
1520 assert_has_fullzip_layout(&pages);
1521
1522 let test_cases = TestCases::default()
1523 .with_range(0..1)
1524 .with_indices(vec![0])
1525 .with_dense_encodings();
1526 check_round_trip_encoding_of_data(vec![list], &test_cases, HashMap::new()).await;
1527 }
1528
1529 fn make_hnsw_shaped_list_u32() -> ListArray {
1534 const DENSE_ROWS: u32 = 5_000;
1535 const NEIGHBORS_PER_ROW: u32 = 32;
1536 const EMPTY_TAIL_ROWS: u32 = 70_000;
1537
1538 let mut list_builder = ListBuilder::new(UInt32Builder::new());
1539 let mut next_val: u32 = 0;
1540 for _ in 0..DENSE_ROWS {
1541 for _ in 0..NEIGHBORS_PER_ROW {
1542 list_builder.values().append_value(next_val);
1543 next_val = next_val.wrapping_add(1);
1544 }
1545 list_builder.append(true);
1546 }
1547 for _ in 0..EMPTY_TAIL_ROWS {
1548 list_builder.append(true);
1549 }
1550 list_builder.finish()
1551 }
1552
1553 #[test_log::test(tokio::test)]
1563 async fn test_list_hnsw_shape_splits_to_miniblock_v2_2() {
1564 let list_array = make_hnsw_shaped_list_u32();
1565 let dense_rows: u64 = 5_000;
1566 let total_rows = list_array.len() as u64;
1567
1568 let test_cases = TestCases::default()
1569 .with_range(0..1000)
1570 .with_range(dense_rows.saturating_sub(8)..(dense_rows + 8))
1571 .with_range(0..total_rows)
1572 .with_indices(vec![0, dense_rows - 1, dense_rows, total_rows - 1])
1573 .with_encoding(TestEncoding::StructuralU32);
1574 let list_array = Arc::new(list_array) as ArrayRef;
1575 let pages = encode_v22_pages(list_array.clone()).await;
1576 assert_split_miniblock_layout(&pages, 1, true);
1577 check_round_trip_encoding_of_data(vec![list_array], &test_cases, HashMap::new()).await;
1578 }
1579
1580 #[test_log::test(tokio::test)]
1586 async fn test_forced_miniblock_hnsw_shape_splits_to_miniblock_v2_2() {
1587 let list_array = make_hnsw_shaped_list_u32();
1588 let total_rows = list_array.len() as u64;
1589
1590 let mut field_metadata = HashMap::new();
1591 field_metadata.insert(
1592 STRUCTURAL_ENCODING_META_KEY.to_string(),
1593 STRUCTURAL_ENCODING_MINIBLOCK.into(),
1594 );
1595
1596 let test_cases = TestCases::default()
1597 .with_range(0..total_rows)
1598 .with_encoding(TestEncoding::StructuralU32);
1599 let list_array = Arc::new(list_array) as ArrayRef;
1600 let pages = try_encode_v22_pages_with_metadata(list_array.clone(), field_metadata.clone())
1601 .await
1602 .unwrap();
1603 assert_split_miniblock_layout(&pages, 1, true);
1604 check_round_trip_encoding_of_data(vec![list_array], &test_cases, field_metadata).await;
1605 }
1606}