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, StructArray,
244 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 rstest::rstest;
253
254 use crate::{
255 testing::{TestCases, check_basic_random, check_round_trip_encoding_of_data},
256 version::LanceFileVersion,
257 };
258
259 fn make_list_type(inner_type: DataType) -> DataType {
260 DataType::List(Arc::new(Field::new("item", inner_type, true)))
261 }
262
263 fn make_large_list_type(inner_type: DataType) -> DataType {
264 DataType::LargeList(Arc::new(Field::new("item", inner_type, true)))
265 }
266
267 async fn try_encode_v22_pages(
268 array: ArrayRef,
269 ) -> lance_core::Result<Vec<crate::encoder::EncodedPage>> {
270 try_encode_v22_pages_with_metadata(array, HashMap::new()).await
271 }
272
273 async fn try_encode_v22_pages_with_metadata(
274 array: ArrayRef,
275 field_metadata: HashMap<String, String>,
276 ) -> lance_core::Result<Vec<crate::encoder::EncodedPage>> {
277 let arrow_field =
278 Field::new("", array.data_type().clone(), true).with_metadata(field_metadata);
279 let lance_field = lance_core::datatypes::Field::try_from(&arrow_field).unwrap();
280 let encoding_strategy = crate::encoder::default_encoding_strategy(LanceFileVersion::V2_2);
281 let mut column_index_seq = crate::encoder::ColumnIndexSequence::default();
282 let encoding_options = crate::encoder::EncodingOptions {
283 version: LanceFileVersion::V2_2,
284 ..Default::default()
285 };
286 let mut encoder = encoding_strategy
287 .create_field_encoder(
288 encoding_strategy.as_ref(),
289 &lance_field,
290 &mut column_index_seq,
291 &encoding_options,
292 )
293 .unwrap();
294 let mut external_buffers =
295 crate::encoder::OutOfLineBuffers::new(0, crate::encoder::MIN_PAGE_BUFFER_ALIGNMENT);
296 let num_rows = array.len() as u64;
297 let mut pages = Vec::new();
298 for task in encoder
299 .maybe_encode(
300 array,
301 &mut external_buffers,
302 crate::repdef::RepDefBuilder::default(),
303 0,
304 num_rows,
305 )
306 .unwrap()
307 {
308 pages.push(task.await?);
309 }
310 for task in encoder.flush(&mut external_buffers).unwrap() {
311 pages.push(task.await?);
312 }
313 Ok(pages)
314 }
315
316 async fn encode_v22_pages(array: ArrayRef) -> Vec<crate::encoder::EncodedPage> {
317 try_encode_v22_pages(array).await.unwrap()
318 }
319
320 fn assert_split_miniblock_layout(
321 pages: &[crate::encoder::EncodedPage],
322 expect_structural_only_page: bool,
323 ) {
324 let mut miniblock_pages = 0;
325 let mut fullzip_pages = 0;
326 let mut structural_only_pages = 0;
327
328 for page in pages {
329 let crate::decoder::PageEncoding::Structural(layout) = &page.description else {
330 continue;
331 };
332 match layout.layout.as_ref().unwrap() {
333 crate::format::pb21::page_layout::Layout::MiniBlockLayout(_) => {
334 miniblock_pages += 1;
335 }
336 crate::format::pb21::page_layout::Layout::FullZipLayout(_) => {
337 fullzip_pages += 1;
338 }
339 crate::format::pb21::page_layout::Layout::ConstantLayout(layout) => {
340 if layout.inline_value.is_none()
341 && (layout.num_rep_values > 0 || layout.num_def_values > 0)
342 {
343 structural_only_pages += 1;
344 }
345 }
346 crate::format::pb21::page_layout::Layout::BlobLayout(_) => {}
347 }
348 }
349
350 assert!(
351 miniblock_pages > 0,
352 "expected leaf values to remain on mini-block pages"
353 );
354 assert_eq!(
355 fullzip_pages, 0,
356 "split list pages should not fall back to full-zip"
357 );
358 if expect_structural_only_page {
359 assert!(
360 structural_only_pages > 0,
361 "expected at least one structural-only page"
362 );
363 }
364 }
365
366 fn assert_has_fullzip_layout(pages: &[crate::encoder::EncodedPage]) {
367 let has_fullzip = pages.iter().any(|page| {
368 let crate::decoder::PageEncoding::Structural(layout) = &page.description else {
369 return false;
370 };
371 matches!(
372 layout.layout.as_ref().unwrap(),
373 crate::format::pb21::page_layout::Layout::FullZipLayout(_)
374 )
375 });
376 assert!(has_fullzip, "expected at least one full-zip page");
377 }
378
379 #[rstest]
380 #[test_log::test(tokio::test)]
381 async fn test_list(
382 #[values(STRUCTURAL_ENCODING_MINIBLOCK, STRUCTURAL_ENCODING_FULLZIP)]
383 structural_encoding: &str,
384 ) {
385 let mut field_metadata = HashMap::new();
386 field_metadata.insert(
387 STRUCTURAL_ENCODING_META_KEY.to_string(),
388 structural_encoding.into(),
389 );
390 let field =
391 Field::new("", make_list_type(DataType::Int32), true).with_metadata(field_metadata);
392 check_basic_random(field).await;
393 }
394
395 #[rstest]
396 #[test_log::test(tokio::test)]
397 async fn test_deeply_nested_lists(
398 #[values(STRUCTURAL_ENCODING_MINIBLOCK, STRUCTURAL_ENCODING_FULLZIP)]
399 structural_encoding: &str,
400 ) {
401 let mut field_metadata = HashMap::new();
402 field_metadata.insert(
403 STRUCTURAL_ENCODING_META_KEY.to_string(),
404 structural_encoding.into(),
405 );
406 let field = Field::new("item", DataType::Int32, true).with_metadata(field_metadata);
407 for _ in 0..5 {
408 let field = Field::new("", make_list_type(field.data_type().clone()), true);
409 check_basic_random(field).await;
410 }
411 }
412
413 #[test_log::test(tokio::test)]
414 async fn test_large_list() {
415 let field = Field::new("", make_large_list_type(DataType::Int32), true);
416 check_basic_random(field).await;
417 }
418
419 #[test_log::test(tokio::test)]
420 async fn test_nested_strings() {
421 let field = Field::new("", make_list_type(DataType::Utf8), true);
422 check_basic_random(field).await;
423 }
424
425 #[test_log::test(tokio::test)]
426 async fn test_nested_list() {
427 let field = Field::new("", make_list_type(make_list_type(DataType::Int32)), true);
428 check_basic_random(field).await;
429 }
430
431 #[rstest]
450 #[test_log::test(tokio::test)]
451 async fn test_multipage_nested_float_list(
452 #[values(STRUCTURAL_ENCODING_MINIBLOCK, STRUCTURAL_ENCODING_FULLZIP)]
453 structural_encoding: &str,
454 ) {
455 use arrow_array::Float32Array;
456
457 let inner_per_row: usize = 3;
459 let inner_len: usize = 768;
460 let chunk_rows: &[usize] = &[1366, 1365];
464
465 let make_chunk = |start_row: usize, num_rows: usize| -> Arc<dyn Array> {
466 let total_inner = num_rows * inner_per_row;
467 let total_values = total_inner * inner_len;
468 let values = Float32Array::from(
469 (0..total_values)
470 .map(|i| (start_row + i) as f32)
471 .collect::<Vec<_>>(),
472 );
473 let inner_offsets = ScalarBuffer::<i32>::from(
474 (0..=total_inner)
475 .map(|i| (i * inner_len) as i32)
476 .collect::<Vec<_>>(),
477 );
478 let inner_list = ListArray::new(
479 Arc::new(Field::new("item", DataType::Float32, true)),
480 OffsetBuffer::new(inner_offsets),
481 Arc::new(values),
482 None,
483 );
484 let outer_offsets = ScalarBuffer::<i32>::from(
485 (0..=num_rows)
486 .map(|i| (i * inner_per_row) as i32)
487 .collect::<Vec<_>>(),
488 );
489 Arc::new(ListArray::new(
490 Arc::new(Field::new(
491 "item",
492 DataType::List(Arc::new(Field::new("item", DataType::Float32, true))),
493 true,
494 )),
495 OffsetBuffer::new(outer_offsets),
496 Arc::new(inner_list),
497 None,
498 ))
499 };
500
501 let mut start = 0;
502 let chunks: Vec<Arc<dyn Array>> = chunk_rows
503 .iter()
504 .map(|&n| {
505 let c = make_chunk(start, n);
506 start += n;
507 c
508 })
509 .collect();
510
511 let mut field_metadata = HashMap::new();
512 field_metadata.insert(
513 STRUCTURAL_ENCODING_META_KEY.to_string(),
514 structural_encoding.into(),
515 );
516
517 let test_cases = TestCases::default().with_min_file_version(LanceFileVersion::V2_1);
518 check_round_trip_encoding_of_data(chunks, &test_cases, field_metadata).await;
519 }
520
521 #[test_log::test(tokio::test)]
522 async fn test_list_struct_list() {
523 let struct_type = DataType::Struct(Fields::from(vec![Field::new(
524 "inner_str",
525 DataType::Utf8,
526 false,
527 )]));
528
529 let field = Field::new("", make_list_type(struct_type), true);
530 check_basic_random(field).await;
531 }
532
533 #[test_log::test(tokio::test)]
534 async fn test_list_struct_empty() {
535 let fields = Fields::from(vec![Field::new("inner", DataType::UInt64, true)]);
536 let items = UInt64Array::from(Vec::<u64>::new());
537 let structs = StructArray::new(fields, vec![Arc::new(items)], None);
538 let offsets = OffsetBuffer::new(ScalarBuffer::<i32>::from(vec![0; 2 * 1024 * 1024 + 1]));
539 let lists = ListArray::new(
540 Arc::new(Field::new("item", structs.data_type().clone(), true)),
541 offsets,
542 Arc::new(structs),
543 None,
544 );
545
546 check_round_trip_encoding_of_data(
547 vec![Arc::new(lists)],
548 &TestCases::default(),
549 HashMap::new(),
550 )
551 .await;
552 }
553
554 #[rstest]
555 #[test_log::test(tokio::test)]
556 async fn test_simple_list(
557 #[values(STRUCTURAL_ENCODING_MINIBLOCK, STRUCTURAL_ENCODING_FULLZIP)]
558 structural_encoding: &str,
559 ) {
560 let items_builder = Int32Builder::new();
561 let mut list_builder = ListBuilder::new(items_builder);
562 list_builder.append_value([Some(1), Some(2), Some(3)]);
563 list_builder.append_value([Some(4), Some(5)]);
564 list_builder.append_null();
565 list_builder.append_value([Some(6), Some(7), Some(8)]);
566 let list_array = list_builder.finish();
567
568 let mut field_metadata = HashMap::new();
569 field_metadata.insert(
570 STRUCTURAL_ENCODING_META_KEY.to_string(),
571 structural_encoding.into(),
572 );
573
574 let test_cases = TestCases::default()
575 .with_range(0..2)
576 .with_range(0..3)
577 .with_range(1..3)
578 .with_indices(vec![1, 3])
579 .with_indices(vec![2]);
580 check_round_trip_encoding_of_data(vec![Arc::new(list_array)], &test_cases, field_metadata)
581 .await;
582 }
583
584 #[rstest]
585 #[test_log::test(tokio::test)]
586 async fn test_simple_nested_list_ends_with_null(
587 #[values(STRUCTURAL_ENCODING_MINIBLOCK, STRUCTURAL_ENCODING_FULLZIP)]
588 structural_encoding: &str,
589 ) {
590 use arrow_array::Int32Array;
591
592 let values = Int32Array::from(vec![1, 2, 3, 4, 5]);
593 let inner_offsets = ScalarBuffer::<i32>::from(vec![0, 1, 2, 3, 4, 5, 5]);
594 let inner_validity = BooleanBuffer::from(vec![true, true, true, true, true, false]);
595 let outer_offsets = ScalarBuffer::<i32>::from(vec![0, 1, 2, 3, 4, 5, 6, 6]);
596 let outer_validity = BooleanBuffer::from(vec![true, true, true, true, true, true, false]);
597
598 let inner_list = ListArray::new(
599 Arc::new(Field::new("item", DataType::Int32, true)),
600 OffsetBuffer::new(inner_offsets),
601 Arc::new(values),
602 Some(NullBuffer::new(inner_validity)),
603 );
604 let outer_list = ListArray::new(
605 Arc::new(Field::new(
606 "item",
607 DataType::List(Arc::new(Field::new("item", DataType::Int32, true))),
608 true,
609 )),
610 OffsetBuffer::new(outer_offsets),
611 Arc::new(inner_list),
612 Some(NullBuffer::new(outer_validity)),
613 );
614
615 let mut field_metadata = HashMap::new();
616 field_metadata.insert(
617 STRUCTURAL_ENCODING_META_KEY.to_string(),
618 structural_encoding.into(),
619 );
620
621 let test_cases = TestCases::default()
622 .with_range(0..2)
623 .with_range(0..3)
624 .with_range(5..7)
625 .with_indices(vec![1, 6])
626 .with_indices(vec![6])
627 .with_min_file_version(LanceFileVersion::V2_1);
628 check_round_trip_encoding_of_data(vec![Arc::new(outer_list)], &test_cases, field_metadata)
629 .await;
630 }
631
632 #[rstest]
633 #[test_log::test(tokio::test)]
634 async fn test_simple_string_list(
635 #[values(STRUCTURAL_ENCODING_MINIBLOCK, STRUCTURAL_ENCODING_FULLZIP)]
636 structural_encoding: &str,
637 ) {
638 let items_builder = StringBuilder::new();
639 let mut list_builder = ListBuilder::new(items_builder);
640 list_builder.append_value([Some("a"), Some("bc"), Some("def")]);
641 list_builder.append_value([Some("gh"), None]);
642 list_builder.append_null();
643 list_builder.append_value([Some("ijk"), Some("lmnop"), Some("qrs")]);
644 let list_array = list_builder.finish();
645
646 let mut field_metadata = HashMap::new();
647 field_metadata.insert(
648 STRUCTURAL_ENCODING_META_KEY.to_string(),
649 structural_encoding.into(),
650 );
651
652 let test_cases = TestCases::default()
653 .with_range(0..2)
654 .with_range(0..3)
655 .with_range(1..3)
656 .with_indices(vec![1, 3])
657 .with_indices(vec![2])
658 .with_min_file_version(LanceFileVersion::V2_1);
659 check_round_trip_encoding_of_data(vec![Arc::new(list_array)], &test_cases, field_metadata)
660 .await;
661 }
662
663 #[rstest]
664 #[test_log::test(tokio::test)]
665 async fn test_simple_string_list_no_null(
666 #[values(STRUCTURAL_ENCODING_MINIBLOCK, STRUCTURAL_ENCODING_FULLZIP)]
667 structural_encoding: &str,
668 ) {
669 let items_builder = StringBuilder::new();
670 let mut list_builder = ListBuilder::new(items_builder);
671 list_builder.append_value([Some("a"), Some("bc"), Some("def")]);
672 list_builder.append_value([Some("gh"), Some("zxy")]);
673 list_builder.append_value([Some("gh"), Some("z")]);
674 list_builder.append_value([Some("ijk"), Some("lmnop"), Some("qrs")]);
675 let list_array = list_builder.finish();
676
677 let mut field_metadata = HashMap::new();
678 field_metadata.insert(
679 STRUCTURAL_ENCODING_META_KEY.to_string(),
680 structural_encoding.into(),
681 );
682
683 let test_cases = TestCases::default()
684 .with_range(0..2)
685 .with_range(0..3)
686 .with_range(1..3)
687 .with_indices(vec![1, 3])
688 .with_indices(vec![2])
689 .with_min_file_version(LanceFileVersion::V2_1);
690 check_round_trip_encoding_of_data(vec![Arc::new(list_array)], &test_cases, field_metadata)
691 .await;
692 }
693
694 #[rstest]
695 #[test_log::test(tokio::test)]
696 async fn test_simple_sliced_list(
697 #[values(STRUCTURAL_ENCODING_MINIBLOCK, STRUCTURAL_ENCODING_FULLZIP)]
698 structural_encoding: &str,
699 ) {
700 let items_builder = Int32Builder::new();
701 let mut list_builder = ListBuilder::new(items_builder);
702 list_builder.append_value([Some(1), Some(2), Some(3)]);
703 list_builder.append_value([Some(4), Some(5)]);
704 list_builder.append_null();
705 list_builder.append_value([Some(6), Some(7), Some(8)]);
706 let list_array = list_builder.finish();
707
708 let list_array = list_array.slice(1, 2);
709
710 let mut field_metadata = HashMap::new();
711 field_metadata.insert(
712 STRUCTURAL_ENCODING_META_KEY.to_string(),
713 structural_encoding.into(),
714 );
715
716 let test_cases = TestCases::default()
717 .with_range(0..2)
718 .with_range(1..2)
719 .with_indices(vec![0])
720 .with_indices(vec![1])
721 .with_min_file_version(LanceFileVersion::V2_1);
722 check_round_trip_encoding_of_data(vec![Arc::new(list_array)], &test_cases, field_metadata)
723 .await;
724 }
725
726 #[test_log::test(tokio::test)]
727 async fn test_simple_list_dict() {
728 let values = LargeStringArray::from_iter_values(["a", "bb", "ccc"]);
729 let indices = UInt8Array::from(vec![0, 1, 2, 0, 1, 2, 0, 1, 2]);
730 let dict_array = DictionaryArray::new(indices, Arc::new(values));
731 let offsets = OffsetBuffer::new(ScalarBuffer::<i32>::from(vec![0, 3, 5, 6, 9]));
732 let list_array = ListArray::new(
733 Arc::new(Field::new("item", dict_array.data_type().clone(), true)),
734 offsets,
735 Arc::new(dict_array),
736 None,
737 );
738
739 let test_cases = TestCases::default()
740 .with_range(0..2)
741 .with_range(1..3)
742 .with_range(2..4)
743 .with_indices(vec![1])
744 .with_indices(vec![2]);
745 check_round_trip_encoding_of_data(
746 vec![Arc::new(list_array)],
747 &test_cases,
748 HashMap::default(),
749 )
750 .await;
751 }
752
753 #[test_log::test(tokio::test)]
754 async fn test_simple_list_all_null() {
755 let items = UInt64Array::from(vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]);
756 let offsets = ScalarBuffer::<i32>::from(vec![0, 5, 8, 10]);
757 let offsets = OffsetBuffer::new(offsets);
758 let list_validity = NullBuffer::new(BooleanBuffer::from(vec![false, false, false]));
759
760 let list_arr = ListArray::new(
762 Arc::new(Field::new("item", DataType::UInt64, false)),
763 offsets,
764 Arc::new(items),
765 Some(list_validity),
766 );
767
768 let test_cases = TestCases::default()
769 .with_range(0..3)
770 .with_range(1..2)
771 .with_indices(vec![1])
772 .with_indices(vec![2])
773 .with_min_file_version(LanceFileVersion::V2_1);
774 check_round_trip_encoding_of_data(
775 vec![Arc::new(list_arr)],
776 &test_cases,
777 HashMap::default(),
778 )
779 .await;
780 }
781
782 #[rstest]
783 #[test_log::test(tokio::test)]
784 async fn test_list_with_garbage_nulls(
785 #[values(STRUCTURAL_ENCODING_MINIBLOCK, STRUCTURAL_ENCODING_FULLZIP)]
786 structural_encoding: &str,
787 ) {
788 let items = UInt64Array::from(vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]);
791 let offsets = ScalarBuffer::<i32>::from(vec![0, 5, 8, 10]);
792 let offsets = OffsetBuffer::new(offsets);
793 let list_validity = NullBuffer::new(BooleanBuffer::from(vec![true, false, true]));
794 let list_arr = ListArray::new(
795 Arc::new(Field::new("item", DataType::UInt64, true)),
796 offsets,
797 Arc::new(items),
798 Some(list_validity),
799 );
800
801 let mut field_metadata = HashMap::new();
802 field_metadata.insert(
803 STRUCTURAL_ENCODING_META_KEY.to_string(),
804 structural_encoding.into(),
805 );
806
807 let test_cases = TestCases::default()
808 .with_range(0..3)
809 .with_range(1..2)
810 .with_indices(vec![1])
811 .with_indices(vec![2])
812 .with_min_file_version(LanceFileVersion::V2_1);
813 check_round_trip_encoding_of_data(vec![Arc::new(list_arr)], &test_cases, field_metadata)
814 .await;
815 }
816
817 #[rstest]
818 #[test_log::test(tokio::test)]
819 async fn test_simple_two_page_list(
820 #[values(STRUCTURAL_ENCODING_MINIBLOCK, STRUCTURAL_ENCODING_FULLZIP)]
821 structural_encoding: &str,
822 ) {
823 let items_builder = Int64Builder::new();
827 let mut list_builder = ListBuilder::new(items_builder);
828 for i in 0..512 {
829 list_builder.append_value([Some(i), Some(i * 2)]);
830 }
831 let list_array_1 = list_builder.finish();
832
833 let items_builder = Int64Builder::new();
834 let mut list_builder = ListBuilder::new(items_builder);
835 for i in 0..512 {
836 let i = i + 512;
837 list_builder.append_value([Some(i), Some(i * 2)]);
838 }
839 let list_array_2 = list_builder.finish();
840
841 let mut metadata = HashMap::new();
842 metadata.insert(
843 STRUCTURAL_ENCODING_META_KEY.to_string(),
844 structural_encoding.into(),
845 );
846
847 let test_cases = TestCases::default()
848 .with_min_file_version(LanceFileVersion::V2_1)
849 .with_page_sizes(vec![100])
850 .with_range(800..900);
851 check_round_trip_encoding_of_data(
852 vec![Arc::new(list_array_1), Arc::new(list_array_2)],
853 &test_cases,
854 metadata,
855 )
856 .await;
857 }
858
859 #[test_log::test(tokio::test)]
860 async fn test_simple_large_list() {
861 let items_builder = Int32Builder::new();
862 let mut list_builder = LargeListBuilder::new(items_builder);
863 list_builder.append_value([Some(1), Some(2), Some(3)]);
864 list_builder.append_value([Some(4), Some(5)]);
865 list_builder.append_null();
866 list_builder.append_value([Some(6), Some(7), Some(8)]);
867 let list_array = list_builder.finish();
868
869 let test_cases = TestCases::default()
870 .with_range(0..2)
871 .with_range(0..3)
872 .with_range(1..3)
873 .with_indices(vec![1, 3]);
874 check_round_trip_encoding_of_data(vec![Arc::new(list_array)], &test_cases, HashMap::new())
875 .await;
876 }
877
878 #[rstest]
879 #[test_log::test(tokio::test)]
880 async fn test_empty_lists(
881 #[values(STRUCTURAL_ENCODING_MINIBLOCK, STRUCTURAL_ENCODING_FULLZIP)]
882 structural_encoding: &str,
883 ) {
884 let mut field_metadata = HashMap::new();
885 field_metadata.insert(
886 STRUCTURAL_ENCODING_META_KEY.to_string(),
887 structural_encoding.into(),
888 );
889
890 let values = [vec![Some(1), Some(2), Some(3)], vec![], vec![None]];
893 for order in [[0, 1, 2], [1, 0, 2], [2, 0, 1]] {
895 let items_builder = Int32Builder::new();
896 let mut list_builder = ListBuilder::new(items_builder);
897 for idx in order {
898 list_builder.append_value(values[idx].clone());
899 }
900 let list_array = Arc::new(list_builder.finish());
901 let test_cases = TestCases::default()
902 .with_indices(vec![1])
903 .with_indices(vec![0])
904 .with_indices(vec![2])
905 .with_indices(vec![0, 1]);
906 check_round_trip_encoding_of_data(
907 vec![list_array.clone()],
908 &test_cases,
909 field_metadata.clone(),
910 )
911 .await;
912 let test_cases = test_cases.with_batch_size(1);
913 check_round_trip_encoding_of_data(
914 vec![list_array],
915 &test_cases,
916 field_metadata.clone(),
917 )
918 .await;
919 }
920
921 let items_builder = Int32Builder::new();
926 let mut list_builder = ListBuilder::new(items_builder);
927 list_builder.append(true);
928 list_builder.append_null();
929 list_builder.append(true);
930 let list_array = Arc::new(list_builder.finish());
931
932 let test_cases = TestCases::default().with_range(0..2).with_indices(vec![1]);
933 check_round_trip_encoding_of_data(
934 vec![list_array.clone()],
935 &test_cases,
936 field_metadata.clone(),
937 )
938 .await;
939 let test_cases = test_cases.with_batch_size(1);
940 check_round_trip_encoding_of_data(vec![list_array], &test_cases, field_metadata.clone())
941 .await;
942
943 let items_builder = StringBuilder::new();
948 let mut list_builder = ListBuilder::new(items_builder);
949 list_builder.append(true);
950 list_builder.append_null();
951 list_builder.append(true);
952 let list_array = Arc::new(list_builder.finish());
953
954 let test_cases = TestCases::default().with_range(0..2).with_indices(vec![1]);
955 check_round_trip_encoding_of_data(
956 vec![list_array.clone()],
957 &test_cases,
958 field_metadata.clone(),
959 )
960 .await;
961 let test_cases = test_cases.with_batch_size(1);
962 check_round_trip_encoding_of_data(vec![list_array], &test_cases, field_metadata.clone())
963 .await;
964
965 let items_builder = Int32Builder::new();
968 let mut list_builder = ListBuilder::new(items_builder);
969 list_builder.append_null();
970 list_builder.append_null();
971 list_builder.append_null();
972 let list_array = Arc::new(list_builder.finish());
973
974 let test_cases = TestCases::default().with_range(0..2).with_indices(vec![1]);
975 check_round_trip_encoding_of_data(
976 vec![list_array.clone()],
977 &test_cases,
978 field_metadata.clone(),
979 )
980 .await;
981 let test_cases = test_cases.with_batch_size(1);
982 check_round_trip_encoding_of_data(vec![list_array], &test_cases, field_metadata.clone())
983 .await;
984
985 let items_builder = Int32Builder::new();
988 let mut list_builder = ListBuilder::new(items_builder);
989 list_builder.append_null();
990 list_builder.append_null();
991 list_builder.append_null();
992 let list_array = Arc::new(list_builder.finish());
993
994 let struct_validity = NullBuffer::new(BooleanBuffer::from(vec![true, false, true]));
995 let struct_array = Arc::new(StructArray::new(
996 Fields::from(vec![Field::new(
997 "lists",
998 list_array.data_type().clone(),
999 true,
1000 )]),
1001 vec![list_array],
1002 Some(struct_validity),
1003 ));
1004
1005 let test_cases = TestCases::default()
1006 .with_range(0..2)
1007 .with_indices(vec![1])
1008 .with_min_file_version(LanceFileVersion::V2_1);
1009 check_round_trip_encoding_of_data(
1010 vec![struct_array.clone()],
1011 &test_cases,
1012 field_metadata.clone(),
1013 )
1014 .await;
1015 let test_cases = test_cases.with_batch_size(1);
1016 check_round_trip_encoding_of_data(vec![struct_array], &test_cases, field_metadata.clone())
1017 .await;
1018 }
1019
1020 #[test_log::test(tokio::test)]
1021 async fn test_empty_list_list() {
1022 let items_builder = Int32Builder::new();
1023 let list_builder = ListBuilder::new(items_builder);
1024 let mut outer_list_builder = ListBuilder::new(list_builder);
1025 outer_list_builder.append_null();
1026 outer_list_builder.append_null();
1027 outer_list_builder.append_null();
1028 let list_array = Arc::new(outer_list_builder.finish());
1029
1030 let test_cases = TestCases::default().with_min_file_version(LanceFileVersion::V2_1);
1031 check_round_trip_encoding_of_data(vec![list_array], &test_cases, HashMap::new()).await;
1032 }
1033
1034 #[test_log::test(tokio::test)]
1035 #[ignore] async fn test_jumbo_list() {
1037 let items = BooleanArray::new_null(1024 * 1024);
1041 let offsets = OffsetBuffer::new(ScalarBuffer::from(vec![0, 1024 * 1024]));
1042 let list_arr = Arc::new(ListArray::new(
1043 Arc::new(Field::new("item", DataType::Boolean, true)),
1044 offsets,
1045 Arc::new(items),
1046 None,
1047 )) as ArrayRef;
1048 let arrs = vec![list_arr; 5000];
1049
1050 let test_cases = TestCases::default().without_validation();
1052 check_round_trip_encoding_of_data(arrs, &test_cases, HashMap::new()).await;
1053 }
1054
1055 #[tokio::test]
1059 async fn test_fuzz_issue_4466() {
1060 let list_sizes = vec![
1063 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, ];
1074
1075 let mut list_builder = ListBuilder::new(Int32Builder::new());
1077 let mut total_values = 0;
1078
1079 for size in &list_sizes {
1080 for i in 0..*size {
1081 list_builder.values().append_value(i);
1082 }
1083 list_builder.append(true);
1084 total_values += size;
1085 }
1086
1087 let list_array = Arc::new(list_builder.finish());
1088
1089 assert_eq!(list_array.len(), 95);
1091 assert_eq!(total_values, 1025);
1092
1093 let test_cases = TestCases::default().with_min_file_version(LanceFileVersion::V2_1);
1096
1097 check_round_trip_encoding_of_data(vec![list_array], &test_cases, HashMap::new()).await;
1101 }
1102
1103 #[rstest]
1104 #[test_log::test(tokio::test)]
1105 async fn test_sparse_large_string_list(
1106 #[values(STRUCTURAL_ENCODING_MINIBLOCK, STRUCTURAL_ENCODING_FULLZIP)]
1107 structural_encoding: &str,
1108 ) {
1109 let num_rows = 2_500_000u32;
1111 let num_non_empty = 100u32;
1112 let strings_per_list = 10;
1113
1114 let items_builder = StringBuilder::new();
1115 let mut list_builder = ListBuilder::new(items_builder);
1116
1117 let step = num_rows / num_non_empty;
1119 let mut next_non_empty = step / 2;
1120
1121 for i in 0..num_rows {
1122 if i == next_non_empty {
1123 let vals: Vec<Option<&str>> = (0..strings_per_list)
1124 .map(|j| match j % 4 {
1125 0 => Some("a"),
1126 1 => Some("bb"),
1127 2 => Some("ccc"),
1128 _ => Some("d"),
1129 })
1130 .collect();
1131 list_builder.append_value(vals);
1132 next_non_empty = next_non_empty.saturating_add(step);
1133 } else {
1134 list_builder.append_value([] as [Option<&str>; 0]);
1135 }
1136 }
1137 let list_array = list_builder.finish();
1138
1139 let mut field_metadata = HashMap::new();
1140 field_metadata.insert(
1141 STRUCTURAL_ENCODING_META_KEY.to_string(),
1142 structural_encoding.into(),
1143 );
1144
1145 let test_cases = TestCases::default()
1146 .with_range(0..1000)
1147 .with_range(0..num_rows as u64)
1148 .with_indices(vec![0, (step / 2) as u64, num_rows as u64 - 1])
1149 .with_max_file_version(LanceFileVersion::V2_2);
1150 check_round_trip_encoding_of_data(vec![Arc::new(list_array)], &test_cases, field_metadata)
1151 .await;
1152 }
1153
1154 #[test_log::test(tokio::test)]
1155 async fn test_sparse_boolean_list_uses_miniblock() {
1156 let num_rows = 200_000usize;
1160 let num_non_empty = 10usize;
1161 let booleans_per_list = 8usize;
1162 let step = num_rows / num_non_empty;
1163
1164 let mut offsets = Vec::with_capacity(num_rows + 1);
1165 let mut values = Vec::with_capacity(num_non_empty * booleans_per_list);
1166 offsets.push(0i32);
1167
1168 let mut next_non_empty = step / 2;
1169 for row in 0..num_rows {
1170 if row == next_non_empty {
1171 values.extend((0..booleans_per_list).map(|idx| idx % 2 == 0));
1172 next_non_empty += step;
1173 }
1174 offsets.push(values.len() as i32);
1175 }
1176
1177 let items = BooleanArray::from(values);
1178 let list_array = ListArray::new(
1179 Arc::new(Field::new("item", DataType::Boolean, true)),
1180 OffsetBuffer::new(ScalarBuffer::from(offsets)),
1181 Arc::new(items),
1182 None,
1183 );
1184
1185 let test_cases = TestCases::default()
1186 .with_range(0..1000)
1187 .with_range(0..num_rows as u64)
1188 .with_indices(vec![0, (step / 2) as u64, num_rows as u64 - 1])
1189 .with_max_file_version(LanceFileVersion::V2_2);
1190 let list_array = Arc::new(list_array) as ArrayRef;
1191 let pages = encode_v22_pages(list_array.clone()).await;
1192 assert_split_miniblock_layout(&pages, false);
1193 check_round_trip_encoding_of_data(vec![list_array], &test_cases, HashMap::new()).await;
1194 }
1195
1196 #[test_log::test(tokio::test)]
1197 async fn test_sparse_boolean_list_with_long_empty_prefix() {
1198 let empty_prefix_rows = 70_000usize;
1199 let trailing_empty_rows = 9usize;
1200 let booleans_per_list = 8usize;
1201 let num_rows = empty_prefix_rows + 1 + trailing_empty_rows;
1202
1203 let mut offsets = Vec::with_capacity(num_rows + 1);
1204 offsets.extend(std::iter::repeat_n(0i32, empty_prefix_rows + 1));
1205 let values = (0..booleans_per_list)
1206 .map(|idx| idx % 2 == 0)
1207 .collect::<Vec<_>>();
1208 offsets.push(values.len() as i32);
1209 offsets.extend(std::iter::repeat_n(
1210 values.len() as i32,
1211 trailing_empty_rows,
1212 ));
1213
1214 let items = BooleanArray::from(values);
1215 let list_array = ListArray::new(
1216 Arc::new(Field::new("item", DataType::Boolean, true)),
1217 OffsetBuffer::new(ScalarBuffer::from(offsets)),
1218 Arc::new(items),
1219 None,
1220 );
1221
1222 let test_cases = TestCases::default()
1223 .with_range(0..num_rows as u64)
1224 .with_indices(vec![0, empty_prefix_rows as u64, num_rows as u64 - 1])
1225 .with_max_file_version(LanceFileVersion::V2_2);
1226 let list_array = Arc::new(list_array) as ArrayRef;
1227 let pages = encode_v22_pages(list_array.clone()).await;
1228 assert_split_miniblock_layout(&pages, true);
1229 check_round_trip_encoding_of_data(vec![list_array], &test_cases, HashMap::new()).await;
1230 }
1231
1232 #[test_log::test(tokio::test)]
1233 async fn test_sparse_boolean_list_with_long_null_prefix() {
1234 let null_prefix_rows = 70_000usize;
1235 let trailing_empty_rows = 9usize;
1236 let booleans_per_list = 8usize;
1237 let num_rows = null_prefix_rows + 1 + trailing_empty_rows;
1238
1239 let mut offsets = Vec::with_capacity(num_rows + 1);
1240 offsets.extend(std::iter::repeat_n(0i32, null_prefix_rows + 1));
1241 let values = (0..booleans_per_list)
1242 .map(|idx| idx % 2 == 0)
1243 .collect::<Vec<_>>();
1244 offsets.push(values.len() as i32);
1245 offsets.extend(std::iter::repeat_n(
1246 values.len() as i32,
1247 trailing_empty_rows,
1248 ));
1249 let validity = BooleanBuffer::from_iter((0..num_rows).map(|row| row >= null_prefix_rows));
1250
1251 let items = BooleanArray::from(values);
1252 let list_array = ListArray::new(
1253 Arc::new(Field::new("item", DataType::Boolean, true)),
1254 OffsetBuffer::new(ScalarBuffer::from(offsets)),
1255 Arc::new(items),
1256 Some(NullBuffer::new(validity)),
1257 );
1258
1259 let test_cases = TestCases::default()
1260 .with_range(0..num_rows as u64)
1261 .with_indices(vec![0, null_prefix_rows as u64, num_rows as u64 - 1])
1262 .with_max_file_version(LanceFileVersion::V2_2);
1263 let list_array = Arc::new(list_array) as ArrayRef;
1264 let pages = encode_v22_pages(list_array.clone()).await;
1265 assert_split_miniblock_layout(&pages, true);
1266 check_round_trip_encoding_of_data(vec![list_array], &test_cases, HashMap::new()).await;
1267 }
1268
1269 #[test_log::test(tokio::test)]
1270 async fn test_sparse_boolean_list_with_amortized_long_empty_prefix() {
1271 let empty_prefix_rows = 62_000usize;
1272 let booleans_per_list = 8_192usize;
1273 let num_rows = empty_prefix_rows + 1;
1274
1275 let mut offsets = Vec::with_capacity(num_rows + 1);
1276 offsets.extend(std::iter::repeat_n(0i32, empty_prefix_rows + 1));
1277 let values = (0..booleans_per_list)
1278 .map(|idx| idx % 2 == 0)
1279 .collect::<Vec<_>>();
1280 offsets.push(values.len() as i32);
1281
1282 let items = BooleanArray::from(values);
1283 let list_array = ListArray::new(
1284 Arc::new(Field::new("item", DataType::Boolean, true)),
1285 OffsetBuffer::new(ScalarBuffer::from(offsets)),
1286 Arc::new(items),
1287 None,
1288 );
1289
1290 let test_cases = TestCases::default()
1291 .with_range(0..num_rows as u64)
1292 .with_indices(vec![0, empty_prefix_rows as u64])
1293 .with_max_file_version(LanceFileVersion::V2_2);
1294 let list_array = Arc::new(list_array) as ArrayRef;
1295 let pages = encode_v22_pages(list_array.clone()).await;
1296 assert_split_miniblock_layout(&pages, true);
1297 check_round_trip_encoding_of_data(vec![list_array], &test_cases, HashMap::new()).await;
1298 }
1299
1300 #[test_log::test(tokio::test)]
1301 async fn test_nested_sparse_boolean_list_fails_without_panic() {
1302 let empty_inner_lists = 70_000usize;
1303 let booleans_per_list = 8usize;
1304
1305 let mut inner_offsets = vec![0i32; empty_inner_lists + 1];
1306 let values = (0..booleans_per_list)
1307 .map(|idx| idx % 2 == 0)
1308 .collect::<Vec<_>>();
1309 inner_offsets.push(values.len() as i32);
1310
1311 let inner_items = BooleanArray::from(values);
1312 let inner_list = ListArray::new(
1313 Arc::new(Field::new("item", DataType::Boolean, true)),
1314 OffsetBuffer::new(ScalarBuffer::from(inner_offsets)),
1315 Arc::new(inner_items),
1316 None,
1317 );
1318 let outer_list = ListArray::new(
1319 Arc::new(Field::new("item", inner_list.data_type().clone(), true)),
1320 OffsetBuffer::new(ScalarBuffer::from(vec![0i32, empty_inner_lists as i32 + 1])),
1321 Arc::new(inner_list),
1322 None,
1323 );
1324
1325 let err = try_encode_v22_pages(Arc::new(outer_list))
1326 .await
1327 .unwrap_err();
1328 assert!(
1329 err.to_string().contains("Mini-block cannot encode"),
1330 "unexpected error: {err}"
1331 );
1332 }
1333
1334 #[test_log::test(tokio::test)]
1335 async fn test_nested_sparse_string_single_row_falls_back_to_fullzip() {
1336 let empty_inner_lists = 70_000usize;
1337
1338 let mut inner_offsets = vec![0i32; empty_inner_lists + 1];
1339 inner_offsets.push(1);
1340 inner_offsets.push(2);
1341
1342 let mut strings = StringBuilder::new();
1343 strings.append_value("value");
1344 strings.append_value("other");
1345 let inner_items = strings.finish();
1346 let inner_list = ListArray::new(
1347 Arc::new(Field::new("item", DataType::Utf8, true)),
1348 OffsetBuffer::new(ScalarBuffer::from(inner_offsets)),
1349 Arc::new(inner_items),
1350 None,
1351 );
1352 let outer_list = ListArray::new(
1353 Arc::new(Field::new("item", inner_list.data_type().clone(), true)),
1354 OffsetBuffer::new(ScalarBuffer::from(vec![0i32, empty_inner_lists as i32 + 2])),
1355 Arc::new(inner_list),
1356 None,
1357 );
1358
1359 let outer_list = Arc::new(outer_list) as ArrayRef;
1360 let pages = encode_v22_pages(outer_list.clone()).await;
1361 assert_has_fullzip_layout(&pages);
1362
1363 let test_cases = TestCases::default()
1364 .with_range(0..1)
1365 .with_indices(vec![0])
1366 .with_max_file_version(LanceFileVersion::V2_2);
1367 check_round_trip_encoding_of_data(vec![outer_list], &test_cases, HashMap::new()).await;
1368 }
1369
1370 fn make_hnsw_shaped_list_u32() -> ListArray {
1375 const DENSE_ROWS: u32 = 40_000;
1376 const NEIGHBORS_PER_ROW: u32 = 32;
1377 const EMPTY_TAIL_ROWS: u32 = 240_000;
1378
1379 let mut list_builder = ListBuilder::new(UInt32Builder::new());
1380 let mut next_val: u32 = 0;
1381 for _ in 0..DENSE_ROWS {
1382 for _ in 0..NEIGHBORS_PER_ROW {
1383 list_builder.values().append_value(next_val);
1384 next_val = next_val.wrapping_add(1);
1385 }
1386 list_builder.append(true);
1387 }
1388 for _ in 0..EMPTY_TAIL_ROWS {
1389 list_builder.append(true);
1390 }
1391 list_builder.finish()
1392 }
1393
1394 #[test_log::test(tokio::test)]
1404 async fn test_list_hnsw_shape_splits_to_miniblock_v2_2() {
1405 let list_array = make_hnsw_shaped_list_u32();
1406 let dense_rows: u64 = 40_000;
1407 let total_rows = list_array.len() as u64;
1408
1409 let test_cases = TestCases::default()
1410 .with_range(0..1000)
1411 .with_range(dense_rows.saturating_sub(8)..(dense_rows + 8))
1412 .with_range(0..total_rows)
1413 .with_indices(vec![0, dense_rows - 1, dense_rows, total_rows - 1])
1414 .with_min_file_version(LanceFileVersion::V2_2)
1415 .with_max_file_version(LanceFileVersion::V2_2);
1416 let list_array = Arc::new(list_array) as ArrayRef;
1417 let pages = encode_v22_pages(list_array.clone()).await;
1418 assert_split_miniblock_layout(&pages, true);
1419 check_round_trip_encoding_of_data(vec![list_array], &test_cases, HashMap::new()).await;
1420 }
1421
1422 #[test_log::test(tokio::test)]
1428 async fn test_forced_miniblock_hnsw_shape_splits_to_miniblock_v2_2() {
1429 let list_array = make_hnsw_shaped_list_u32();
1430 let total_rows = list_array.len() as u64;
1431
1432 let mut field_metadata = HashMap::new();
1433 field_metadata.insert(
1434 STRUCTURAL_ENCODING_META_KEY.to_string(),
1435 STRUCTURAL_ENCODING_MINIBLOCK.into(),
1436 );
1437
1438 let test_cases = TestCases::default()
1439 .with_range(0..total_rows)
1440 .with_min_file_version(LanceFileVersion::V2_2)
1441 .with_max_file_version(LanceFileVersion::V2_2);
1442 let list_array = Arc::new(list_array) as ArrayRef;
1443 let pages = try_encode_v22_pages_with_metadata(list_array.clone(), field_metadata.clone())
1444 .await
1445 .unwrap();
1446 assert_split_miniblock_layout(&pages, true);
1447 check_round_trip_encoding_of_data(vec![list_array], &test_cases, field_metadata).await;
1448 }
1449}