Skip to main content

lance_encoding/encodings/logical/
struct.rs

1// SPDX-License-Identifier: Apache-2.0
2// SPDX-FileCopyrightText: Copyright The Lance Authors
3
4use std::{
5    collections::{BinaryHeap, VecDeque},
6    ops::Range,
7    sync::Arc,
8};
9
10use super::{
11    fixed_size_list::StructuralFixedSizeListDecoder, list::StructuralListDecoder,
12    map::StructuralMapDecoder, primitive::StructuralPrimitiveFieldDecoder,
13};
14use crate::{
15    decoder::{
16        DecodedArray, FilterExpression, LoadedPageShard, NextDecodeTask, PageEncoding,
17        ScheduledScanLine, SchedulerContext, StructuralDecodeArrayTask, StructuralFieldDecoder,
18        StructuralFieldScheduler, StructuralSchedulingJob,
19    },
20    encoder::{EncodeTask, EncodedColumn, EncodedPage, FieldEncoder, OutOfLineBuffers},
21    format::pb,
22    repdef::{CompositeRepDefUnraveler, RepDefBuilder},
23};
24use arrow_array::{Array, ArrayRef, StructArray, cast::AsArray};
25use arrow_schema::{DataType, Fields};
26use futures::{
27    FutureExt, StreamExt, TryStreamExt,
28    future::BoxFuture,
29    stream::{FuturesOrdered, FuturesUnordered},
30};
31use itertools::Itertools;
32use lance_arrow::FieldExt;
33use lance_arrow::{deepcopy::deep_copy_nulls, r#struct::StructArrayExt};
34use lance_core::{Error, Result};
35use log::trace;
36
37#[derive(Debug)]
38struct StructuralSchedulingJobWithStatus<'a> {
39    col_idx: u32,
40    col_name: &'a str,
41    job: Box<dyn StructuralSchedulingJob + 'a>,
42    rows_scheduled: u64,
43    rows_remaining: u64,
44    ready_scan_lines: VecDeque<ScheduledScanLine>,
45}
46
47impl PartialEq for StructuralSchedulingJobWithStatus<'_> {
48    fn eq(&self, other: &Self) -> bool {
49        self.col_idx == other.col_idx
50    }
51}
52
53impl Eq for StructuralSchedulingJobWithStatus<'_> {}
54
55impl PartialOrd for StructuralSchedulingJobWithStatus<'_> {
56    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
57        Some(self.cmp(other))
58    }
59}
60
61impl Ord for StructuralSchedulingJobWithStatus<'_> {
62    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
63        // Note this is reversed to make it min-heap
64        other.rows_scheduled.cmp(&self.rows_scheduled)
65    }
66}
67
68/// Scheduling job for struct data
69///
70/// The order in which we schedule the children is important.  We want to schedule the child
71/// with the least amount of data first.
72///
73/// This allows us to decode entire rows as quickly as possible
74#[derive(Debug)]
75struct RepDefStructSchedulingJob<'a> {
76    /// A min-heap whose key is the # of rows currently scheduled
77    children: BinaryHeap<StructuralSchedulingJobWithStatus<'a>>,
78    rows_scheduled: u64,
79    num_rows: u64,
80}
81
82impl<'a> RepDefStructSchedulingJob<'a> {
83    fn new(
84        scheduler: &'a StructuralStructScheduler,
85        children: Vec<Box<dyn StructuralSchedulingJob + 'a>>,
86        num_rows: u64,
87    ) -> Self {
88        let children = children
89            .into_iter()
90            .enumerate()
91            .map(|(idx, job)| StructuralSchedulingJobWithStatus {
92                col_idx: idx as u32,
93                col_name: scheduler.child_fields[idx].name(),
94                job,
95                rows_scheduled: 0,
96                rows_remaining: num_rows,
97                ready_scan_lines: VecDeque::new(),
98            })
99            .collect::<BinaryHeap<_>>();
100        Self {
101            children,
102            rows_scheduled: 0,
103            num_rows,
104        }
105    }
106}
107
108impl StructuralSchedulingJob for RepDefStructSchedulingJob<'_> {
109    fn schedule_next(
110        &mut self,
111        mut context: &mut SchedulerContext,
112    ) -> Result<Vec<ScheduledScanLine>> {
113        if self.children.is_empty() {
114            // Special path for empty structs
115            if self.rows_scheduled == self.num_rows {
116                return Ok(Vec::new());
117            }
118            self.rows_scheduled = self.num_rows;
119            return Ok(vec![ScheduledScanLine {
120                decoders: Vec::new(),
121                rows_scheduled: self.num_rows,
122            }]);
123        }
124
125        let mut decoders = Vec::new();
126        let old_rows_scheduled = self.rows_scheduled;
127        // Schedule as many children as we need to until we have scheduled at least one
128        // complete row
129        while old_rows_scheduled == self.rows_scheduled {
130            if self.children.is_empty() {
131                // Early exit when schedulers are exhausted prematurely (TODO: does this still happen?)
132                return Ok(Vec::new());
133            }
134            let mut next_child = self.children.pop().unwrap();
135            if next_child.ready_scan_lines.is_empty() {
136                let scoped = context.push(next_child.col_name, next_child.col_idx);
137                let child_scans = next_child.job.schedule_next(scoped.context)?;
138                context = scoped.pop();
139                if child_scans.is_empty() {
140                    // Continue without pushing next_child back onto the heap (it is done)
141                    continue;
142                }
143                next_child.ready_scan_lines.extend(child_scans);
144            }
145            let child_scan = next_child.ready_scan_lines.pop_front().unwrap();
146            trace!(
147                "Scheduled {} rows for child {}",
148                child_scan.rows_scheduled, next_child.col_idx
149            );
150            next_child.rows_scheduled += child_scan.rows_scheduled;
151            next_child.rows_remaining -= child_scan.rows_scheduled;
152            decoders.extend(child_scan.decoders);
153            self.children.push(next_child);
154            self.rows_scheduled = self.children.peek().unwrap().rows_scheduled;
155        }
156        let struct_rows_scheduled = self.rows_scheduled - old_rows_scheduled;
157        Ok(vec![ScheduledScanLine {
158            decoders,
159            rows_scheduled: struct_rows_scheduled,
160        }])
161    }
162}
163
164/// A scheduler for structs
165///
166/// The implementation is actually a bit more tricky than one might initially think.  We can't just
167/// go through and schedule each column one after the other.  This would mean our decode can't start
168/// until nearly all the data has arrived (since we need data from each column to yield a batch)
169///
170/// Instead, we schedule in row-major fashion
171///
172/// Note: this scheduler is the starting point for all decoding.  This is because we treat the top-level
173/// record batch as a non-nullable struct.
174#[derive(Debug)]
175pub struct StructuralStructScheduler {
176    children: Vec<Box<dyn StructuralFieldScheduler>>,
177    child_fields: Fields,
178}
179
180impl StructuralStructScheduler {
181    pub fn new(children: Vec<Box<dyn StructuralFieldScheduler>>, child_fields: Fields) -> Self {
182        Self {
183            children,
184            child_fields,
185        }
186    }
187}
188
189impl StructuralFieldScheduler for StructuralStructScheduler {
190    fn schedule_ranges<'a>(
191        &'a self,
192        ranges: &[Range<u64>],
193        filter: &FilterExpression,
194    ) -> Result<Box<dyn StructuralSchedulingJob + 'a>> {
195        let num_rows = ranges.iter().map(|r| r.end - r.start).sum();
196
197        let child_schedulers = self
198            .children
199            .iter()
200            .map(|child| child.schedule_ranges(ranges, filter))
201            .collect::<Result<Vec<_>>>()?;
202
203        Ok(Box::new(RepDefStructSchedulingJob::new(
204            self,
205            child_schedulers,
206            num_rows,
207        )))
208    }
209
210    fn initialize<'a>(
211        &'a mut self,
212        filter: &'a FilterExpression,
213        context: &'a SchedulerContext,
214    ) -> BoxFuture<'a, Result<()>> {
215        let children_initialization = self
216            .children
217            .iter_mut()
218            .map(|child| child.initialize(filter, context))
219            .collect::<FuturesUnordered<_>>();
220        async move {
221            children_initialization
222                .map(|res| res.map(|_| ()))
223                .try_collect::<Vec<_>>()
224                .await?;
225            Ok(())
226        }
227        .boxed()
228    }
229}
230
231#[derive(Debug)]
232pub struct StructuralStructDecoder {
233    children: Vec<Box<dyn StructuralFieldDecoder>>,
234    data_type: DataType,
235    child_fields: Fields,
236    // The root decoder is slightly different because it cannot have nulls
237    is_root: bool,
238}
239
240impl StructuralStructDecoder {
241    pub fn new(fields: Fields, should_validate: bool, is_root: bool) -> Result<Self> {
242        let children = fields
243            .iter()
244            .map(|field| Self::field_to_decoder(field, should_validate))
245            .collect::<Result<Vec<_>>>()?;
246        let data_type = DataType::Struct(fields.clone());
247        Ok(Self {
248            data_type,
249            children,
250            child_fields: fields,
251            is_root,
252        })
253    }
254
255    fn field_to_decoder(
256        field: &Arc<arrow_schema::Field>,
257        should_validate: bool,
258    ) -> Result<Box<dyn StructuralFieldDecoder>> {
259        match field.data_type() {
260            DataType::Struct(fields) => {
261                if field.is_packed_struct() || field.is_blob() {
262                    let decoder =
263                        StructuralPrimitiveFieldDecoder::new(&field.clone(), should_validate);
264                    Ok(Box::new(decoder))
265                } else {
266                    Ok(Box::new(Self::new(fields.clone(), should_validate, false)?))
267                }
268            }
269            DataType::List(child_field) | DataType::LargeList(child_field) => {
270                let child_decoder = Self::field_to_decoder(child_field, should_validate)?;
271                Ok(Box::new(StructuralListDecoder::new(
272                    child_decoder,
273                    field.data_type().clone(),
274                )))
275            }
276            DataType::FixedSizeList(child_field, _)
277                if matches!(child_field.data_type(), DataType::Struct(_)) =>
278            {
279                // FixedSizeList containing Struct needs structural decoding
280                let child_decoder = Self::field_to_decoder(child_field, should_validate)?;
281                Ok(Box::new(StructuralFixedSizeListDecoder::new(
282                    child_decoder,
283                    field.data_type().clone(),
284                )))
285            }
286            DataType::Map(entries_field, keys_sorted) => {
287                if *keys_sorted {
288                    return Err(Error::not_supported_source(
289                        "Map data type with keys_sorted=true is not supported yet"
290                            .to_string()
291                            .into(),
292                    ));
293                }
294                let child_decoder = Self::field_to_decoder(entries_field, should_validate)?;
295                Ok(Box::new(StructuralMapDecoder::new(
296                    child_decoder,
297                    field.data_type().clone(),
298                )))
299            }
300            DataType::RunEndEncoded(_, _) => todo!(),
301            DataType::ListView(_) | DataType::LargeListView(_) => todo!(),
302            DataType::Union(_, _) => todo!(),
303            _ => Ok(Box::new(StructuralPrimitiveFieldDecoder::new(
304                field,
305                should_validate,
306            ))),
307        }
308    }
309
310    pub fn drain_batch_task(&mut self, num_rows: u64) -> Result<NextDecodeTask> {
311        let array_drain = self.drain(num_rows)?;
312        Ok(NextDecodeTask {
313            num_rows,
314            task: Box::new(array_drain),
315        })
316    }
317}
318
319impl StructuralFieldDecoder for StructuralStructDecoder {
320    fn accept_page(&mut self, mut child: LoadedPageShard) -> Result<()> {
321        // children with empty path should not be delivered to this method
322        let child_idx = child.path.pop_front().unwrap();
323        // This decoder is intended for one of our children
324        self.children[child_idx as usize].accept_page(child)?;
325        Ok(())
326    }
327
328    fn drain(&mut self, num_rows: u64) -> Result<Box<dyn StructuralDecodeArrayTask>> {
329        let child_tasks = self
330            .children
331            .iter_mut()
332            .map(|child| child.drain(num_rows))
333            .collect::<Result<Vec<_>>>()?;
334        Ok(Box::new(RepDefStructDecodeTask {
335            children: child_tasks,
336            child_fields: self.child_fields.clone(),
337            is_root: self.is_root,
338            num_rows,
339        }))
340    }
341
342    fn data_type(&self) -> &DataType {
343        &self.data_type
344    }
345}
346
347#[derive(Debug)]
348struct RepDefStructDecodeTask {
349    children: Vec<Box<dyn StructuralDecodeArrayTask>>,
350    child_fields: Fields,
351    is_root: bool,
352    num_rows: u64,
353}
354
355impl StructuralDecodeArrayTask for RepDefStructDecodeTask {
356    fn decode(self: Box<Self>) -> Result<DecodedArray> {
357        if self.children.is_empty() {
358            return Ok(DecodedArray {
359                array: Arc::new(StructArray::new_empty_fields(self.num_rows as usize, None)),
360                repdef: CompositeRepDefUnraveler::new(vec![]),
361                data_size: 0,
362            });
363        }
364
365        let arrays = self
366            .children
367            .into_iter()
368            .map(|task| task.decode())
369            .collect::<Result<Vec<_>>>()?;
370        let mut children = Vec::with_capacity(arrays.len());
371        let mut repdefs = Vec::with_capacity(arrays.len());
372        let mut data_size = 0u64;
373        let mut arrays_iter = arrays.into_iter();
374        let first_array = arrays_iter.next().ok_or_else(|| {
375            Error::internal("Struct decoder unexpectedly has no child arrays".to_string())
376        })?;
377        let length = first_array.array.len();
378
379        // The repdef should be identical across all children at this point
380        repdefs.push(first_array.repdef);
381        data_size += first_array.data_size;
382        children.push(first_array.array);
383
384        for array in arrays_iter {
385            if length != array.array.len() {
386                return Err(Error::invalid_input_source(
387                    format!(
388                        "Struct child array length {} does not match sibling length {}",
389                        array.array.len(),
390                        length
391                    )
392                    .into(),
393                ));
394            }
395            data_size += array.data_size;
396            children.push(array.array);
397            repdefs.push(array.repdef);
398        }
399
400        // Dense rep/def state can retain child-specific repetition information after a child
401        // decoder finishes, so comparing dense siblings is not meaningful. If any child carries
402        // sparse state, keep a sparse child as the canonical structural plan and compare it with
403        // every other sparse sibling so sparse metadata is never silently discarded.
404        let primary_repdef = repdefs
405            .iter()
406            .position(CompositeRepDefUnraveler::has_sparse)
407            .unwrap_or(0);
408        let mut repdef = repdefs.swap_remove(primary_repdef);
409        if repdef.has_sparse() {
410            for sibling in repdefs {
411                if sibling.has_sparse() {
412                    repdef.add_compatibility_check(sibling);
413                }
414            }
415        }
416
417        let validity = if self.is_root {
418            repdef.ensure_exhausted()?;
419            None
420        } else {
421            repdef.unravel_validity(length)?
422        };
423
424        let array = StructArray::try_new(self.child_fields, children, validity)
425            .map_err(|e| Error::invalid_input_source(e.to_string().into()))?;
426        Ok(DecodedArray {
427            array: Arc::new(array),
428            repdef,
429            data_size,
430        })
431    }
432}
433
434/// A structural encoder for struct fields
435///
436/// The struct's validity is added to the rep/def builder
437/// and the builder is cloned to all children.
438pub struct StructStructuralEncoder {
439    keep_original_array: bool,
440    children: Vec<Box<dyn FieldEncoder>>,
441}
442
443impl StructStructuralEncoder {
444    pub fn new(keep_original_array: bool, children: Vec<Box<dyn FieldEncoder>>) -> Self {
445        Self {
446            keep_original_array,
447            children,
448        }
449    }
450}
451
452impl FieldEncoder for StructStructuralEncoder {
453    fn maybe_encode(
454        &mut self,
455        array: ArrayRef,
456        external_buffers: &mut OutOfLineBuffers,
457        mut repdef: RepDefBuilder,
458        row_number: u64,
459        num_rows: u64,
460    ) -> Result<Vec<EncodeTask>> {
461        let struct_array = array.as_struct();
462        let mut struct_array = struct_array.normalize_slicing()?;
463        if let Some(validity) = struct_array.nulls() {
464            if self.keep_original_array {
465                repdef.add_validity_bitmap(validity.clone())
466            } else {
467                repdef.add_validity_bitmap(deep_copy_nulls(Some(validity)).unwrap())
468            }
469            struct_array = struct_array.pushdown_nulls()?;
470        } else {
471            repdef.add_no_null(struct_array.len());
472        }
473        let child_tasks = self
474            .children
475            .iter_mut()
476            .zip(struct_array.columns().iter())
477            .map(|(encoder, arr)| {
478                encoder.maybe_encode(
479                    arr.clone(),
480                    external_buffers,
481                    repdef.clone(),
482                    row_number,
483                    num_rows,
484                )
485            })
486            .collect::<Result<Vec<_>>>()?;
487        Ok(child_tasks.into_iter().flatten().collect::<Vec<_>>())
488    }
489
490    fn flush(&mut self, external_buffers: &mut OutOfLineBuffers) -> Result<Vec<EncodeTask>> {
491        self.children
492            .iter_mut()
493            .map(|encoder| encoder.flush(external_buffers))
494            .flatten_ok()
495            .collect::<Result<Vec<_>>>()
496    }
497
498    fn num_columns(&self) -> u32 {
499        self.children
500            .iter()
501            .map(|child| child.num_columns())
502            .sum::<u32>()
503    }
504
505    fn finish(
506        &mut self,
507        external_buffers: &mut OutOfLineBuffers,
508    ) -> BoxFuture<'_, Result<Vec<crate::encoder::EncodedColumn>>> {
509        let mut child_columns = self
510            .children
511            .iter_mut()
512            .map(|child| child.finish(external_buffers))
513            .collect::<FuturesOrdered<_>>();
514        async move {
515            let mut encoded_columns = Vec::with_capacity(child_columns.len());
516            while let Some(child_cols) = child_columns.next().await {
517                encoded_columns.extend(child_cols?);
518            }
519            Ok(encoded_columns)
520        }
521        .boxed()
522    }
523}
524
525pub struct StructFieldEncoder {
526    children: Vec<Box<dyn FieldEncoder>>,
527    column_index: u32,
528    num_rows_seen: u64,
529}
530
531impl StructFieldEncoder {
532    pub fn new(children: Vec<Box<dyn FieldEncoder>>, column_index: u32) -> Self {
533        Self {
534            children,
535            column_index,
536            num_rows_seen: 0,
537        }
538    }
539}
540
541impl FieldEncoder for StructFieldEncoder {
542    fn maybe_encode(
543        &mut self,
544        array: ArrayRef,
545        external_buffers: &mut OutOfLineBuffers,
546        repdef: RepDefBuilder,
547        row_number: u64,
548        num_rows: u64,
549    ) -> Result<Vec<EncodeTask>> {
550        self.num_rows_seen += array.len() as u64;
551        let struct_array = array.as_struct();
552        let child_tasks = self
553            .children
554            .iter_mut()
555            .zip(struct_array.columns().iter())
556            .map(|(encoder, arr)| {
557                encoder.maybe_encode(
558                    arr.clone(),
559                    external_buffers,
560                    repdef.clone(),
561                    row_number,
562                    num_rows,
563                )
564            })
565            .collect::<Result<Vec<_>>>()?;
566        Ok(child_tasks.into_iter().flatten().collect::<Vec<_>>())
567    }
568
569    fn flush(&mut self, external_buffers: &mut OutOfLineBuffers) -> Result<Vec<EncodeTask>> {
570        let child_tasks = self
571            .children
572            .iter_mut()
573            .map(|encoder| encoder.flush(external_buffers))
574            .collect::<Result<Vec<_>>>()?;
575        Ok(child_tasks.into_iter().flatten().collect::<Vec<_>>())
576    }
577
578    fn num_columns(&self) -> u32 {
579        self.children
580            .iter()
581            .map(|child| child.num_columns())
582            .sum::<u32>()
583            + 1
584    }
585
586    fn finish(
587        &mut self,
588        external_buffers: &mut OutOfLineBuffers,
589    ) -> BoxFuture<'_, Result<Vec<crate::encoder::EncodedColumn>>> {
590        let mut child_columns = self
591            .children
592            .iter_mut()
593            .map(|child| child.finish(external_buffers))
594            .collect::<FuturesOrdered<_>>();
595        let num_rows_seen = self.num_rows_seen;
596        let column_index = self.column_index;
597        async move {
598            let mut columns = Vec::new();
599            // Add a column for the struct header
600            let mut header = EncodedColumn::default();
601            header.final_pages.push(EncodedPage {
602                data: Vec::new(),
603                description: PageEncoding::Legacy(pb::ArrayEncoding {
604                    array_encoding: Some(pb::array_encoding::ArrayEncoding::Struct(
605                        pb::SimpleStruct {},
606                    )),
607                }),
608                num_rows: num_rows_seen,
609                column_idx: column_index,
610                row_number: 0, // Not used by legacy encoding
611            });
612            columns.push(header);
613            // Now run finish on the children
614            while let Some(child_cols) = child_columns.next().await {
615                columns.extend(child_cols?);
616            }
617            Ok(columns)
618        }
619        .boxed()
620    }
621}
622
623#[cfg(test)]
624mod tests {
625
626    use std::{collections::HashMap, sync::Arc};
627
628    use arrow_array::{
629        Array, ArrayRef, Float64Array, Int32Array, Int64Array, ListArray, StructArray,
630        builder::{Int32Builder, ListBuilder},
631    };
632    use arrow_buffer::{BooleanBuffer, NullBuffer, OffsetBuffer, ScalarBuffer};
633    use arrow_schema::{DataType, Field, Fields};
634
635    use crate::testing::{TestCases, check_basic_random, check_round_trip_encoding_of_data};
636
637    #[test_log::test(tokio::test)]
638    async fn test_simple_struct() {
639        let data_type = DataType::Struct(Fields::from(vec![
640            Field::new("a", DataType::Int32, false),
641            Field::new("b", DataType::Int32, false),
642        ]));
643        let field = Field::new("", data_type, false);
644        check_basic_random(field).await;
645    }
646
647    #[test_log::test(tokio::test)]
648    async fn test_nullable_struct() {
649        // Test data struct<score: int32, location: struct<x: int32, y: int32>>
650        // - score: null
651        //   location:
652        //     x: 1
653        //     y: 6
654        // - score: 12
655        //   location:
656        //     x: 2
657        //     y: null
658        // - score: 13
659        //   location:
660        //     x: 3
661        //     y: 8
662        // - score: 14
663        //   location: null
664        // - null
665        //
666        let inner_fields = Fields::from(vec![
667            Field::new("x", DataType::Int32, false),
668            Field::new("y", DataType::Int32, true),
669        ]);
670        let inner_struct = DataType::Struct(inner_fields.clone());
671        let outer_fields = Fields::from(vec![
672            Field::new("score", DataType::Int32, true),
673            Field::new("location", inner_struct, true),
674        ]);
675
676        let x_vals = Int32Array::from(vec![Some(1), Some(2), Some(3), Some(4), Some(5)]);
677        let y_vals = Int32Array::from(vec![Some(6), None, Some(8), Some(9), Some(10)]);
678        let scores = Int32Array::from(vec![None, Some(12), Some(13), Some(14), Some(15)]);
679
680        let location_validity = NullBuffer::from(vec![true, true, true, false, true]);
681        let locations = StructArray::new(
682            inner_fields,
683            vec![Arc::new(x_vals), Arc::new(y_vals)],
684            Some(location_validity),
685        );
686
687        let rows_validity = NullBuffer::from(vec![true, true, true, true, false]);
688        let rows = StructArray::new(
689            outer_fields,
690            vec![Arc::new(scores), Arc::new(locations)],
691            Some(rows_validity),
692        );
693
694        let test_cases = TestCases::default().with_structural_encodings();
695
696        check_round_trip_encoding_of_data(vec![Arc::new(rows)], &test_cases, HashMap::new()).await;
697    }
698
699    #[test_log::test(tokio::test)]
700    async fn test_simple_masked_nonempty_list() {
701        // [[1, 2], [NULL], [4], [], NULL, NULL-STRUCT]
702        //
703        let items = Int32Array::from(vec![Some(1), Some(2), None, Some(4), Some(5), Some(6)]);
704        let offsets = OffsetBuffer::new(ScalarBuffer::<i32>::from(vec![0, 2, 3, 4, 4, 4, 5]));
705        let list_validity = BooleanBuffer::from(vec![true, true, true, true, false, true]);
706        let list_array = ListArray::new(
707            Arc::new(Field::new("item", DataType::Int32, true)),
708            offsets,
709            Arc::new(items),
710            Some(NullBuffer::new(list_validity)),
711        );
712        let struct_validity = BooleanBuffer::from(vec![true, true, true, true, true, false]);
713        let struct_array = StructArray::new(
714            Fields::from(vec![Field::new(
715                "inner_list",
716                list_array.data_type().clone(),
717                true,
718            )]),
719            vec![Arc::new(list_array)],
720            Some(NullBuffer::new(struct_validity)),
721        );
722        check_round_trip_encoding_of_data(
723            vec![Arc::new(struct_array)],
724            &TestCases::default().with_structural_encodings(),
725            HashMap::new(),
726        )
727        .await;
728    }
729
730    #[test_log::test(tokio::test)]
731    async fn test_simple_struct_list() {
732        // [[1, 2], [NULL], [4], [], NULL, NULL-STRUCT]
733        //
734        let items = Int32Array::from(vec![Some(1), Some(2), None, Some(4)]);
735        let offsets = OffsetBuffer::new(ScalarBuffer::<i32>::from(vec![0, 2, 3, 4, 4, 4, 4]));
736        let list_validity = BooleanBuffer::from(vec![true, true, true, true, false, true]);
737        let list_array = ListArray::new(
738            Arc::new(Field::new("item", DataType::Int32, true)),
739            offsets,
740            Arc::new(items),
741            Some(NullBuffer::new(list_validity)),
742        );
743        let struct_validity = BooleanBuffer::from(vec![true, true, true, true, true, false]);
744        let struct_array = StructArray::new(
745            Fields::from(vec![Field::new(
746                "inner_list",
747                list_array.data_type().clone(),
748                true,
749            )]),
750            vec![Arc::new(list_array)],
751            Some(NullBuffer::new(struct_validity)),
752        );
753        check_round_trip_encoding_of_data(
754            vec![Arc::new(struct_array)],
755            &TestCases::default().with_structural_encodings(),
756            HashMap::new(),
757        )
758        .await;
759    }
760
761    #[test_log::test(tokio::test)]
762    async fn test_struct_list() {
763        let data_type = DataType::Struct(Fields::from(vec![
764            Field::new(
765                "inner_list",
766                DataType::List(Arc::new(Field::new("item", DataType::Int32, true))),
767                true,
768            ),
769            Field::new("outer_int", DataType::Int32, true),
770        ]));
771        let field = Field::new("row", data_type, false);
772        check_basic_random(field).await;
773    }
774
775    #[test_log::test(tokio::test)]
776    async fn test_empty_struct() {
777        // It's technically legal for a struct to have 0 children, need to
778        // make sure we support that
779        let data_type = DataType::Struct(Fields::from(Vec::<Field>::default()));
780        let field = Field::new("row", data_type, false);
781        check_basic_random(field).await;
782    }
783
784    #[test_log::test(tokio::test)]
785    async fn test_complicated_struct() {
786        let data_type = DataType::Struct(Fields::from(vec![
787            Field::new("int", DataType::Int32, true),
788            Field::new(
789                "inner",
790                DataType::Struct(Fields::from(vec![
791                    Field::new("inner_int", DataType::Int32, true),
792                    Field::new(
793                        "inner_list",
794                        DataType::List(Arc::new(Field::new("item", DataType::Int32, true))),
795                        true,
796                    ),
797                ])),
798                true,
799            ),
800            Field::new("outer_binary", DataType::Binary, true),
801        ]));
802        let field = Field::new("row", data_type, false);
803        check_basic_random(field).await;
804    }
805
806    #[test_log::test(tokio::test)]
807    async fn test_list_of_struct_with_null_struct_element() {
808        // Regression: a list containing structs where most struct elements are null
809        // causes a length mismatch during decoding with V2_2 encoding.
810        use arrow_array::StringArray;
811
812        let tag_array = StringArray::from(vec![
813            Some("valid"),
814            Some("null_struct"),
815            Some("valid"),
816            Some("valid"),
817        ]);
818        let struct_fields = Fields::from(vec![Field::new("tag", DataType::Utf8, true)]);
819        // 3 out of 4 struct elements are null
820        let struct_validity = NullBuffer::from(vec![false, true, false, false]);
821        let struct_array = StructArray::new(
822            struct_fields.clone(),
823            vec![Arc::new(tag_array)],
824            Some(struct_validity),
825        );
826
827        let offsets = OffsetBuffer::new(ScalarBuffer::<i32>::from(vec![0, 4]));
828        let list_field = Field::new("item", DataType::Struct(struct_fields), true);
829        let list_array =
830            ListArray::new(Arc::new(list_field), offsets, Arc::new(struct_array), None);
831
832        check_round_trip_encoding_of_data(
833            vec![Arc::new(list_array)],
834            &TestCases::default().with_u32_structural_encodings(),
835            HashMap::new(),
836        )
837        .await;
838    }
839
840    #[test_log::test(tokio::test)]
841    async fn test_list_of_struct_with_all_null_child() {
842        use arrow_array::StringArray;
843
844        let a_array = StringArray::from(vec![Some("w"), Some("x"), Some("y"), Some("z")]);
845        let b_array = StringArray::from(vec![None::<&str>, None, None, None]);
846        let struct_fields = Fields::from(vec![
847            Field::new("a", DataType::Utf8, true),
848            Field::new("b", DataType::Utf8, true),
849        ]);
850        let struct_array = StructArray::new(
851            struct_fields.clone(),
852            vec![Arc::new(a_array), Arc::new(b_array)],
853            None,
854        );
855
856        let offsets = OffsetBuffer::new(ScalarBuffer::<i32>::from(vec![0, 2, 4]));
857        let list_field = Field::new("item", DataType::Struct(struct_fields), true);
858        let list_array =
859            ListArray::new(Arc::new(list_field), offsets, Arc::new(struct_array), None);
860
861        check_round_trip_encoding_of_data(
862            vec![Arc::new(list_array)],
863            &TestCases::default()
864                .with_range(0..1)
865                .with_range(1..2)
866                .with_indices(vec![0])
867                .with_indices(vec![1])
868                .with_structural_encodings(),
869            HashMap::new(),
870        )
871        .await;
872    }
873
874    #[test_log::test(tokio::test)]
875    async fn test_list_of_struct_with_constant_child_and_empty_lists() {
876        let item_fields = Fields::from(vec![
877            Field::new("maneuver", DataType::Int64, true),
878            Field::new("remaining_dist", DataType::Float64, true),
879        ]);
880        let item_array = StructArray::new(
881            item_fields.clone(),
882            vec![
883                Arc::new(Int64Array::from(vec![Some(3), Some(3), Some(3)])),
884                Arc::new(Float64Array::from(vec![Some(1.0), Some(2.0), Some(3.0)])),
885            ],
886            None,
887        );
888
889        let list_field = Field::new("item", DataType::Struct(item_fields), true);
890        let list_array = ListArray::new(
891            Arc::new(list_field),
892            OffsetBuffer::new(ScalarBuffer::<i32>::from(vec![0, 1, 2, 3, 3, 3, 3])),
893            Arc::new(item_array),
894            None,
895        );
896
897        let data_fields = Fields::from(vec![Field::new(
898            "maneuvers",
899            list_array.data_type().clone(),
900            true,
901        )]);
902        let data_array = StructArray::new(data_fields.clone(), vec![Arc::new(list_array)], None);
903        let row_validity = NullBuffer::from(vec![true, true, true, true, true, false]);
904        let row_array = StructArray::new(
905            Fields::from(vec![Field::new(
906                "data",
907                DataType::Struct(data_fields),
908                true,
909            )]),
910            vec![Arc::new(data_array)],
911            Some(row_validity),
912        );
913
914        check_round_trip_encoding_of_data(
915            vec![Arc::new(row_array)],
916            &TestCases::default().with_structural_encodings(),
917            HashMap::new(),
918        )
919        .await;
920    }
921
922    #[test_log::test(tokio::test)]
923    async fn test_ragged_scheduling() {
924        // This test covers scheduling when batches straddle page boundaries
925
926        // Create a list with 10k nulls
927        let items_builder = Int32Builder::new();
928        let mut list_builder = ListBuilder::new(items_builder);
929        for _ in 0..10000 {
930            list_builder.append_null();
931        }
932        let list_array = Arc::new(list_builder.finish());
933        let int_array = Arc::new(Int32Array::from_iter_values(0..10000));
934        let fields = vec![
935            Field::new("", list_array.data_type().clone(), true),
936            Field::new("", int_array.data_type().clone(), true),
937        ];
938        let struct_array = Arc::new(StructArray::new(
939            Fields::from(fields),
940            vec![list_array, int_array],
941            None,
942        )) as ArrayRef;
943        let struct_arrays = (0..10000)
944            // Intentionally skip in some randomish amount to create more ragged scheduling
945            .step_by(437)
946            .map(|offset| struct_array.slice(offset, 437.min(10000 - offset)))
947            .collect::<Vec<_>>();
948        check_round_trip_encoding_of_data(struct_arrays, &TestCases::default(), HashMap::new())
949            .await;
950    }
951}