Skip to main content

datafusion_physical_plan/
unnest.rs

1// Licensed to the Apache Software Foundation (ASF) under one
2// or more contributor license agreements.  See the NOTICE file
3// distributed with this work for additional information
4// regarding copyright ownership.  The ASF licenses this file
5// to you under the Apache License, Version 2.0 (the
6// "License"); you may not use this file except in compliance
7// with the License.  You may obtain a copy of the License at
8//
9//   http://www.apache.org/licenses/LICENSE-2.0
10//
11// Unless required by applicable law or agreed to in writing,
12// software distributed under the License is distributed on an
13// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14// KIND, either express or implied.  See the License for the
15// specific language governing permissions and limitations
16// under the License.
17
18//! Define a plan for unnesting values in columns that contain a list type.
19
20use std::cmp::{self, Ordering};
21use std::sync::Arc;
22use std::task::{Poll, ready};
23
24use super::metrics::{
25    self, BaselineMetrics, ExecutionPlanMetricsSet, MetricBuilder, MetricCategory,
26    MetricsSet, RecordOutput,
27};
28use super::{DisplayAs, ExecutionPlanProperties, PlanProperties};
29use crate::stream::EmptyRecordBatchStream;
30use crate::{
31    ChildrenPropertiesMode, DisplayFormatType, Distribution, ExecutionPlan,
32    RecordBatchStream, ReplaceChildrenOptions, SendableRecordBatchStream,
33    validate_child_count,
34};
35
36use arrow::array::{
37    Array, ArrayRef, AsArray, BooleanBufferBuilder, FixedSizeListArray, Int64Array,
38    LargeListArray, LargeListViewArray, ListArray, ListViewArray, PrimitiveArray, Scalar,
39    StructArray, new_null_array,
40};
41use arrow::compute::kernels::length::length;
42use arrow::compute::kernels::zip::zip;
43use arrow::compute::{cast, is_not_null, kernels, sum};
44use arrow::datatypes::{DataType, Int64Type, Schema, SchemaRef};
45use arrow::record_batch::RecordBatch;
46use arrow_ord::cmp::lt;
47use async_trait::async_trait;
48use datafusion_common::tree_node::TreeNodeRecursion;
49use datafusion_common::{
50    Constraints, HashMap, HashSet, Result, UnnestOptions, exec_datafusion_err, exec_err,
51    internal_err,
52};
53use datafusion_execution::TaskContext;
54use datafusion_physical_expr::PhysicalExpr;
55use datafusion_physical_expr::equivalence::ProjectionMapping;
56use datafusion_physical_expr::expressions::Column;
57use futures::{Stream, StreamExt};
58use log::trace;
59
60/// Unnest the given columns (either with type struct or list)
61/// For list unnesting, each row is vertically transformed into multiple rows
62/// For struct unnesting, each column is horizontally transformed into multiple columns,
63/// Thus the original RecordBatch with dimension (n x m) may have new dimension (n' x m')
64///
65/// See [`UnnestOptions`] for more details and an example.
66#[derive(Debug, Clone)]
67pub struct UnnestExec {
68    /// Input execution plan
69    input: Arc<dyn ExecutionPlan>,
70    /// The schema once the unnest is applied
71    schema: SchemaRef,
72    /// Indices of the list-typed columns in the input schema
73    list_column_indices: Vec<ListUnnest>,
74    /// Indices of the struct-typed columns in the input schema
75    struct_column_indices: Vec<usize>,
76    /// Options
77    options: UnnestOptions,
78    /// Execution metrics
79    metrics: ExecutionPlanMetricsSet,
80    /// Cache holding plan properties like equivalences, output partitioning etc.
81    cache: Arc<PlanProperties>,
82}
83
84impl UnnestExec {
85    /// Create a new [UnnestExec].
86    pub fn new(
87        input: Arc<dyn ExecutionPlan>,
88        list_column_indices: Vec<ListUnnest>,
89        struct_column_indices: Vec<usize>,
90        schema: SchemaRef,
91        options: UnnestOptions,
92    ) -> Result<Self> {
93        let cache = Self::compute_properties(
94            &input,
95            &list_column_indices,
96            &struct_column_indices,
97            &schema,
98        )?;
99
100        Ok(UnnestExec {
101            input,
102            schema,
103            list_column_indices,
104            struct_column_indices,
105            options,
106            metrics: Default::default(),
107            cache: Arc::new(cache),
108        })
109    }
110
111    /// This function creates the cache object that stores the plan properties such as schema, equivalence properties, ordering, partitioning, etc.
112    fn compute_properties(
113        input: &Arc<dyn ExecutionPlan>,
114        list_column_indices: &[ListUnnest],
115        struct_column_indices: &[usize],
116        schema: &SchemaRef,
117    ) -> Result<PlanProperties> {
118        // Find out which indices are not unnested, such that they can be copied over from the input plan
119        let input_schema = input.schema();
120        let mut unnested_indices = BooleanBufferBuilder::new(input_schema.fields().len());
121        unnested_indices.append_n(input_schema.fields().len(), false);
122        for list_unnest in list_column_indices {
123            unnested_indices.set_bit(list_unnest.index_in_input_schema, true);
124        }
125        for struct_unnest in struct_column_indices {
126            unnested_indices.set_bit(*struct_unnest, true)
127        }
128        let unnested_indices = unnested_indices.finish();
129        let non_unnested_indices: Vec<usize> = (0..input_schema.fields().len())
130            .filter(|idx| !unnested_indices.value(*idx))
131            .collect();
132
133        // Manually build projection mapping from non-unnested input columns to their positions in the output
134        let input_schema = input.schema();
135        let projection_mapping: ProjectionMapping = non_unnested_indices
136            .iter()
137            .map(|&input_idx| {
138                // Find what index the input column has in the output schema
139                let input_field = input_schema.field(input_idx);
140                let output_idx = schema
141                    .fields()
142                    .iter()
143                    .position(|output_field| output_field.name() == input_field.name())
144                    .ok_or_else(|| {
145                        exec_datafusion_err!(
146                            "Non-unnested column '{}' must exist in output schema",
147                            input_field.name()
148                        )
149                    })?;
150
151                let input_col = Arc::new(Column::new(input_field.name(), input_idx))
152                    as Arc<dyn PhysicalExpr>;
153                let target_col = Arc::new(Column::new(input_field.name(), output_idx))
154                    as Arc<dyn PhysicalExpr>;
155                // Use From<Vec<(Arc<dyn PhysicalExpr>, usize)>> for ProjectionTargets
156                let targets = vec![(target_col, output_idx)].into();
157                Ok((input_col, targets))
158            })
159            .collect::<Result<ProjectionMapping>>()?;
160
161        // Create the unnest's equivalence properties by copying the input plan's equivalence properties
162        // for the unaffected columns. Except for the constraints, which are removed entirely because
163        // the unnest operation invalidates any global uniqueness or primary-key constraints.
164        let input_eq_properties = input.equivalence_properties();
165        let eq_properties = input_eq_properties
166            .project(&projection_mapping, Arc::clone(schema))
167            .with_constraints(Constraints::default());
168
169        // Output partitioning must use the projection mapping
170        let output_partitioning = input
171            .output_partitioning()
172            .project(&projection_mapping, &eq_properties);
173
174        Ok(PlanProperties::new(
175            eq_properties,
176            output_partitioning,
177            input.pipeline_behavior(),
178            input.boundedness(),
179        ))
180    }
181
182    /// Input execution plan
183    pub fn input(&self) -> &Arc<dyn ExecutionPlan> {
184        &self.input
185    }
186
187    /// Indices of the list-typed columns in the input schema
188    pub fn list_column_indices(&self) -> &[ListUnnest] {
189        &self.list_column_indices
190    }
191
192    /// Indices of the struct-typed columns in the input schema
193    pub fn struct_column_indices(&self) -> &[usize] {
194        &self.struct_column_indices
195    }
196
197    pub fn options(&self) -> &UnnestOptions {
198        &self.options
199    }
200}
201
202impl DisplayAs for UnnestExec {
203    fn fmt_as(
204        &self,
205        t: DisplayFormatType,
206        f: &mut std::fmt::Formatter,
207    ) -> std::fmt::Result {
208        match t {
209            DisplayFormatType::Default | DisplayFormatType::Verbose => {
210                write!(f, "UnnestExec")
211            }
212            DisplayFormatType::TreeRender => {
213                write!(f, "")
214            }
215        }
216    }
217}
218
219impl ExecutionPlan for UnnestExec {
220    fn name(&self) -> &'static str {
221        "UnnestExec"
222    }
223
224    fn properties(&self) -> &Arc<PlanProperties> {
225        &self.cache
226    }
227
228    fn children(&self) -> Vec<&Arc<dyn ExecutionPlan>> {
229        vec![&self.input]
230    }
231
232    fn apply_expressions(
233        &self,
234        _f: &mut dyn FnMut(&Arc<dyn PhysicalExpr>) -> Result<TreeNodeRecursion>,
235    ) -> Result<TreeNodeRecursion> {
236        Ok(TreeNodeRecursion::Continue)
237    }
238
239    fn replace_children(
240        self: Arc<Self>,
241        mut children: Vec<Arc<dyn ExecutionPlan>>,
242        options: ReplaceChildrenOptions,
243    ) -> Result<Arc<dyn ExecutionPlan>> {
244        validate_child_count!(self, children);
245        match options.children_properties {
246            ChildrenPropertiesMode::Keep => Ok(Arc::new(Self {
247                input: children.swap_remove(0),
248                metrics: ExecutionPlanMetricsSet::new(),
249                ..Self::clone(&*self)
250            })),
251            ChildrenPropertiesMode::Recompute => Ok(Arc::new(UnnestExec::new(
252                children.swap_remove(0),
253                self.list_column_indices.clone(),
254                self.struct_column_indices.clone(),
255                Arc::clone(&self.schema),
256                self.options.clone(),
257            )?)),
258        }
259    }
260
261    fn with_new_children(
262        self: Arc<Self>,
263        children: Vec<Arc<dyn ExecutionPlan>>,
264    ) -> Result<Arc<dyn ExecutionPlan>> {
265        self.replace_children(
266            children,
267            ReplaceChildrenOptions::new(ChildrenPropertiesMode::Recompute),
268        )
269    }
270
271    fn with_new_children_and_same_properties(
272        self: Arc<Self>,
273        children: Vec<Arc<dyn ExecutionPlan>>,
274    ) -> Result<Arc<dyn ExecutionPlan>> {
275        self.replace_children(
276            children,
277            ReplaceChildrenOptions::new(ChildrenPropertiesMode::Keep),
278        )
279    }
280
281    fn required_input_distribution(&self) -> Vec<Distribution> {
282        self.input_distribution_requirements().into_per_child()
283    }
284
285    fn input_distribution_requirements(&self) -> crate::InputDistributionRequirements {
286        crate::InputDistributionRequirements::new(vec![
287            Distribution::UnspecifiedDistribution,
288        ])
289    }
290
291    fn execute(
292        &self,
293        partition: usize,
294        context: Arc<TaskContext>,
295    ) -> Result<SendableRecordBatchStream> {
296        let input = self.input.execute(partition, context)?;
297        let metrics = UnnestMetrics::new(partition, &self.metrics);
298
299        Ok(Box::pin(UnnestStream {
300            input,
301            schema: Arc::clone(&self.schema),
302            list_type_columns: self.list_column_indices.clone(),
303            struct_column_indices: self.struct_column_indices.iter().copied().collect(),
304            options: self.options.clone(),
305            metrics,
306        }))
307    }
308
309    fn metrics(&self) -> Option<MetricsSet> {
310        Some(self.metrics.clone_inner())
311    }
312
313    #[cfg(feature = "proto")]
314    fn try_to_proto(
315        &self,
316        ctx: &crate::proto::ExecutionPlanEncodeCtx<'_>,
317    ) -> Result<Option<datafusion_proto_models::protobuf::PhysicalPlanNode>> {
318        use datafusion_proto_models::protobuf;
319
320        // Exhaustive destructure: adding a field to `UnnestExec` without
321        // deciding how it is serialized is a compile error, not a silent
322        // round-trip gap.
323        let Self {
324            input,
325            schema,
326            list_column_indices,
327            struct_column_indices,
328            options,
329            // Runtime execution state, rebuilt empty on decode.
330            metrics: _,
331            // Derived at construction by `UnnestExec::compute_properties`.
332            cache: _,
333        } = self;
334
335        let input = ctx.encode_child(input)?;
336        let schema = schema.as_ref().try_into()?;
337        let list_type_columns = list_column_indices
338            .iter()
339            .map(|column| protobuf::ListUnnest {
340                index_in_input_schema: column.index_in_input_schema as _,
341                depth: column.depth as _,
342            })
343            .collect();
344        let struct_type_columns = struct_column_indices
345            .iter()
346            .map(|index| *index as _)
347            .collect();
348        let null_handling = {
349            use datafusion_common::NullHandling;
350            use protobuf::unnest_options::NullHandling as ProtoNullHandling;
351            match options.null_handling {
352                NullHandling::Preserve => ProtoNullHandling::Preserve,
353                NullHandling::Drop => ProtoNullHandling::Drop,
354                NullHandling::PreserveAndExpandEmpty => {
355                    ProtoNullHandling::PreserveAndExpandEmpty
356                }
357            }
358        } as i32;
359        let options = protobuf::UnnestOptions {
360            null_handling,
361            recursions: options
362                .recursions
363                .iter()
364                .map(|recursion| protobuf::RecursionUnnestOption {
365                    input_column: Some((&recursion.input_column).into()),
366                    output_column: Some((&recursion.output_column).into()),
367                    depth: recursion.depth as _,
368                })
369                .collect(),
370        };
371
372        Ok(Some(protobuf::PhysicalPlanNode {
373            physical_plan_type: Some(
374                protobuf::physical_plan_node::PhysicalPlanType::Unnest(Box::new(
375                    protobuf::UnnestExecNode {
376                        input: Some(Box::new(input)),
377                        schema: Some(schema),
378                        list_type_columns,
379                        struct_type_columns,
380                        options: Some(options),
381                    },
382                )),
383            ),
384        }))
385    }
386}
387
388#[cfg(feature = "proto")]
389impl UnnestExec {
390    /// Reconstruct an [`UnnestExec`] from its protobuf representation.
391    ///
392    /// The exact inverse of [`ExecutionPlan::try_to_proto`].
393    ///
394    /// [`ExecutionPlan::try_to_proto`]: crate::ExecutionPlan::try_to_proto
395    pub fn try_from_proto(
396        node: &datafusion_proto_models::protobuf::PhysicalPlanNode,
397        ctx: &crate::proto::ExecutionPlanDecodeCtx<'_>,
398    ) -> Result<Arc<dyn ExecutionPlan>> {
399        use datafusion_proto_models::protobuf;
400
401        let unnest = crate::expect_plan_variant!(
402            node,
403            protobuf::physical_plan_node::PhysicalPlanType::Unnest,
404            "UnnestExec",
405        );
406        // Exhaustive destructure: a new field on `UnnestExecNode` is a compile
407        // error here rather than a silently ignored wire field.
408        let protobuf::UnnestExecNode {
409            input,
410            schema,
411            list_type_columns,
412            struct_type_columns,
413            options,
414        } = unnest.as_ref();
415
416        let input = ctx.decode_required_child(input.as_deref(), "UnnestExec", "input")?;
417        let schema: Schema = schema
418            .as_ref()
419            .ok_or_else(|| {
420                datafusion_common::internal_datafusion_err!(
421                    "UnnestExec is missing required field 'schema'"
422                )
423            })?
424            .try_into()?;
425        let list_column_indices = list_type_columns
426            .iter()
427            .map(|column| ListUnnest {
428                index_in_input_schema: column.index_in_input_schema as _,
429                depth: column.depth as _,
430            })
431            .collect();
432        let struct_column_indices = struct_type_columns
433            .iter()
434            .map(|index| *index as _)
435            .collect();
436        let options = options.as_ref().ok_or_else(|| {
437            datafusion_common::internal_datafusion_err!(
438                "UnnestExec is missing required field 'options'"
439            )
440        })?;
441        let null_handling = {
442            use datafusion_common::NullHandling;
443            use protobuf::unnest_options::NullHandling as ProtoNullHandling;
444            match ProtoNullHandling::try_from(options.null_handling) {
445                Ok(ProtoNullHandling::Preserve) => NullHandling::Preserve,
446                Ok(ProtoNullHandling::Drop) => NullHandling::Drop,
447                Ok(ProtoNullHandling::PreserveAndExpandEmpty) => {
448                    NullHandling::PreserveAndExpandEmpty
449                }
450                // Unknown enum values fall back to the default (Preserve),
451                // matching DataFusion's historical behavior.
452                Err(_) => NullHandling::Preserve,
453            }
454        };
455        let options = UnnestOptions {
456            null_handling,
457            recursions: options
458                .recursions
459                .iter()
460                .map(|recursion| datafusion_common::RecursionUnnestOption {
461                    input_column: recursion.input_column.as_ref().unwrap().into(),
462                    output_column: recursion.output_column.as_ref().unwrap().into(),
463                    depth: recursion.depth as _,
464                })
465                .collect(),
466        };
467
468        Ok(Arc::new(UnnestExec::new(
469            input,
470            list_column_indices,
471            struct_column_indices,
472            Arc::new(schema),
473            options,
474        )?))
475    }
476}
477
478#[derive(Clone, Debug)]
479struct UnnestMetrics {
480    /// Execution metrics
481    baseline_metrics: BaselineMetrics,
482    /// Number of batches consumed
483    input_batches: metrics::Count,
484    /// Number of rows consumed
485    input_rows: metrics::Count,
486}
487
488impl UnnestMetrics {
489    fn new(partition: usize, metrics: &ExecutionPlanMetricsSet) -> Self {
490        let input_batches = MetricBuilder::new(metrics)
491            .with_category(MetricCategory::Rows)
492            .counter("input_batches", partition);
493
494        let input_rows = MetricBuilder::new(metrics)
495            .with_category(MetricCategory::Rows)
496            .counter("input_rows", partition);
497
498        Self {
499            baseline_metrics: BaselineMetrics::new(metrics, partition),
500            input_batches,
501            input_rows,
502        }
503    }
504}
505
506/// A stream that issues [RecordBatch]es with unnested column data.
507struct UnnestStream {
508    /// Input stream
509    input: SendableRecordBatchStream,
510    /// Unnested schema
511    schema: Arc<Schema>,
512    /// represents all unnest operations to be applied to the input (input index, depth)
513    /// e.g unnest(col1),unnest(unnest(col1)) where col1 has index 1 in original input schema
514    /// then list_type_columns = [ListUnnest{1,1},ListUnnest{1,2}]
515    list_type_columns: Vec<ListUnnest>,
516    struct_column_indices: HashSet<usize>,
517    /// Options
518    options: UnnestOptions,
519    /// Metrics
520    metrics: UnnestMetrics,
521}
522
523impl RecordBatchStream for UnnestStream {
524    fn schema(&self) -> SchemaRef {
525        Arc::clone(&self.schema)
526    }
527}
528
529#[async_trait]
530impl Stream for UnnestStream {
531    type Item = Result<RecordBatch>;
532
533    fn poll_next(
534        mut self: std::pin::Pin<&mut Self>,
535        cx: &mut std::task::Context<'_>,
536    ) -> Poll<Option<Self::Item>> {
537        self.poll_next_impl(cx)
538    }
539}
540
541impl UnnestStream {
542    /// Separate implementation function that unpins the [`UnnestStream`] so
543    /// that partial borrows work correctly
544    fn poll_next_impl(
545        &mut self,
546        cx: &mut std::task::Context<'_>,
547    ) -> Poll<Option<Result<RecordBatch>>> {
548        loop {
549            return Poll::Ready(match ready!(self.input.poll_next_unpin(cx)) {
550                Some(Ok(batch)) => {
551                    let elapsed_compute =
552                        self.metrics.baseline_metrics.elapsed_compute().clone();
553                    let timer = elapsed_compute.timer();
554                    self.metrics.input_batches.add(1);
555                    self.metrics.input_rows.add(batch.num_rows());
556                    let result = build_batch(
557                        &batch,
558                        &self.schema,
559                        &self.list_type_columns,
560                        &self.struct_column_indices,
561                        &self.options,
562                    )?;
563                    timer.done();
564                    let Some(result_batch) = result else {
565                        continue;
566                    };
567                    (&result_batch).record_output(&self.metrics.baseline_metrics);
568
569                    // Empty record batches should not be emitted.
570                    // They need to be treated as  [`Option<RecordBatch>`]es and handled separately
571                    debug_assert!(result_batch.num_rows() > 0);
572                    Some(Ok(result_batch))
573                }
574                // If the stream is depleted or returned an error, log the finish message:
575                other => {
576                    trace!(
577                        "Processed {} probe-side input batches containing {} rows and \
578                        produced {} output batches containing {} rows in {}",
579                        self.metrics.input_batches,
580                        self.metrics.input_rows,
581                        self.metrics.baseline_metrics.output_batches(),
582                        self.metrics.baseline_metrics.output_rows(),
583                        self.metrics.baseline_metrics.elapsed_compute(),
584                    );
585
586                    // In the non-error case, i.e., input is simply depleted:
587                    if other.is_none() {
588                        // Release the input pipeline's resources.
589                        let input_schema = self.input.schema();
590                        self.input = Box::pin(EmptyRecordBatchStream::new(input_schema));
591                    }
592
593                    other
594                }
595            });
596        }
597    }
598}
599
600/// Given a set of struct column indices to flatten
601/// try converting the column in input into multiple subfield columns
602/// For example
603/// struct_col: [a: struct(item: int, name: string), b: int]
604/// with a batch
605/// {a: {item: 1, name: "a"}, b: 2},
606/// {a: {item: 3, name: "b"}, b: 4]
607/// will be converted into
608/// {a.item: 1, a.name: "a", b: 2},
609/// {a.item: 3, a.name: "b", b: 4}
610fn flatten_struct_cols(
611    input_batch: &[Arc<dyn Array>],
612    schema: &SchemaRef,
613    struct_column_indices: &HashSet<usize>,
614) -> Result<RecordBatch> {
615    // horizontal expansion because of struct unnest
616    let columns_expanded = input_batch
617        .iter()
618        .enumerate()
619        .map(|(idx, column_data)| match struct_column_indices.get(&idx) {
620            Some(_) => match column_data.data_type() {
621                DataType::Struct(_) => {
622                    let struct_arr =
623                        column_data.as_any().downcast_ref::<StructArray>().unwrap();
624                    Ok(struct_arr.columns().to_vec())
625                }
626                data_type => internal_err!(
627                    "expecting column {idx} from input plan to be a struct, got {data_type}"
628                ),
629            },
630            None => Ok(vec![Arc::clone(column_data)]),
631        })
632        .collect::<Result<Vec<_>>>()?
633        .into_iter()
634        .flatten()
635        .collect();
636    Ok(RecordBatch::try_new(Arc::clone(schema), columns_expanded)?)
637}
638
639#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
640pub struct ListUnnest {
641    pub index_in_input_schema: usize,
642    pub depth: usize,
643}
644
645/// This function is used to execute the unnesting on multiple columns all at once, but
646/// one level at a time, and is called n times, where n is the highest recursion level among
647/// the unnest exprs in the query.
648///
649/// For example giving the following query:
650/// ```sql
651/// select unnest(colA, max_depth:=3) as P1, unnest(colA,max_depth:=2) as P2, unnest(colB, max_depth:=1) as P3 from temp;
652/// ```
653/// Then the total times this function being called is 3
654///
655/// It needs to be aware of which level the current unnesting is, because if there exists
656/// multiple unnesting on the same column, but with different recursion levels, say
657/// **unnest(colA, max_depth:=3)** and **unnest(colA, max_depth:=2)**, then the unnesting
658/// of expr **unnest(colA, max_depth:=3)** will start at level 3, while unnesting for expr
659/// **unnest(colA, max_depth:=2)** has to start at level 2
660///
661/// Set *colA* as a 3-dimension columns and *colB* as an array (1-dimension). As stated,
662/// this function is called with the descending order of recursion depth
663///
664/// Depth = 3
665/// - colA(3-dimension) unnest into temp column temp_P1(2_dimension) (unnesting of P1 starts
666///   from this level)
667/// - colA(3-dimension) having indices repeated by the unnesting operation above
668/// - colB(1-dimension) having indices repeated by the unnesting operation above
669///
670/// Depth = 2
671/// - temp_P1(2-dimension) unnest into temp column temp_P1(1-dimension)
672/// - colA(3-dimension) unnest into temp column temp_P2(2-dimension) (unnesting of P2 starts
673///   from this level)
674/// - colB(1-dimension) having indices repeated by the unnesting operation above
675///
676/// Depth = 1
677/// - temp_P1(1-dimension) unnest into P1
678/// - temp_P2(2-dimension) unnest into P2
679/// - colB(1-dimension) unnest into P3 (unnesting of P3 starts from this level)
680///
681/// The returned array will has the same size as the input batch
682/// and only contains original columns that are not being unnested.
683fn list_unnest_at_level(
684    batch: &[ArrayRef],
685    list_type_unnests: &[ListUnnest],
686    temp_unnested_arrs: &mut HashMap<ListUnnest, ArrayRef>,
687    level_to_unnest: usize,
688    options: &UnnestOptions,
689) -> Result<Option<Vec<ArrayRef>>> {
690    // Extract unnestable columns at this level
691    let (arrs_to_unnest, list_unnest_specs): (Vec<Arc<dyn Array>>, Vec<_>) =
692        list_type_unnests
693            .iter()
694            .filter_map(|unnesting| {
695                if level_to_unnest == unnesting.depth {
696                    return Some((
697                        Arc::clone(&batch[unnesting.index_in_input_schema]),
698                        *unnesting,
699                    ));
700                }
701                // This means the unnesting on this item has started at higher level
702                // and need to continue until depth reaches 1
703                if level_to_unnest < unnesting.depth {
704                    return Some((
705                        Arc::clone(temp_unnested_arrs.get(unnesting).unwrap()),
706                        *unnesting,
707                    ));
708                }
709                None
710            })
711            .unzip();
712
713    // Filter out so that list_arrays only contain column with the highest depth
714    // at the same time, during iteration remove this depth so next time we don't have to unnest them again
715    let longest_length = find_longest_length(&arrs_to_unnest, options)?;
716    let unnested_length = longest_length.as_primitive::<Int64Type>();
717    let total_length = if unnested_length.is_empty() {
718        0
719    } else {
720        sum(unnested_length).ok_or_else(|| {
721            exec_datafusion_err!("Failed to calculate the total unnested length")
722        })? as usize
723    };
724    if total_length == 0 {
725        return Ok(None);
726    }
727
728    // Unnest all the list arrays
729    let unnested_temp_arrays =
730        unnest_list_arrays(arrs_to_unnest.as_ref(), unnested_length, total_length)?;
731
732    // Create the take indices array for other columns
733    let take_indices = create_take_indices(unnested_length, total_length);
734    unnested_temp_arrays
735        .into_iter()
736        .zip(list_unnest_specs.iter())
737        .for_each(|(flatten_arr, unnesting)| {
738            temp_unnested_arrs.insert(*unnesting, flatten_arr);
739        });
740
741    let repeat_mask: Vec<bool> = batch
742        .iter()
743        .enumerate()
744        .map(|(i, _)| {
745            // Check if the column is needed in future levels (levels below the current one)
746            let needed_in_future_levels = list_type_unnests.iter().any(|unnesting| {
747                unnesting.index_in_input_schema == i && unnesting.depth < level_to_unnest
748            });
749
750            // Check if the column is involved in unnesting at any level
751            let is_involved_in_unnesting = list_type_unnests
752                .iter()
753                .any(|unnesting| unnesting.index_in_input_schema == i);
754
755            // Repeat columns needed in future levels or not unnested.
756            needed_in_future_levels || !is_involved_in_unnesting
757        })
758        .collect();
759
760    // Dimension of arrays in batch is untouched, but the values are repeated
761    // as the side effect of unnesting
762    let ret = repeat_arrs_from_indices(batch, &take_indices, &repeat_mask)?;
763
764    Ok(Some(ret))
765}
766struct UnnestingResult {
767    arr: ArrayRef,
768    depth: usize,
769}
770
771/// For each row in a `RecordBatch`, some list/struct columns need to be unnested.
772/// - For list columns: We will expand the values in each list into multiple rows,
773///   taking the longest length among these lists, and shorter lists are padded with NULLs.
774/// - For struct columns: We will expand the struct columns into multiple subfield columns.
775///
776/// For columns that don't need to be unnested, repeat their values until reaching the longest length.
777///
778/// Note: unnest has a big difference in behavior between Postgres and DuckDB
779///
780/// Take this example
781///
782/// 1. Postgres
783/// ```ignored
784/// create table temp (
785///     i integer[][][], j integer[]
786/// )
787/// insert into temp values ('{{{1,2},{3,4}},{{5,6},{7,8}}}', '{1,2}');
788/// select unnest(i), unnest(j) from temp;
789/// ```
790///
791/// Result
792/// ```text
793///     1   1
794///     2   2
795///     3
796///     4
797///     5
798///     6
799///     7
800///     8
801/// ```
802/// 2. DuckDB
803/// ```ignore
804///     create table temp (i integer[][][], j integer[]);
805///     insert into temp values ([[[1,2],[3,4]],[[5,6],[7,8]]], [1,2]);
806///     select unnest(i,recursive:=true), unnest(j,recursive:=true) from temp;
807/// ```
808/// Result:
809/// ```text
810///
811///     ┌────────────────────────────────────────────────┬────────────────────────────────────────────────┐
812///     │ unnest(i, "recursive" := CAST('t' AS BOOLEAN)) │ unnest(j, "recursive" := CAST('t' AS BOOLEAN)) │
813///     │                     int32                      │                     int32                      │
814///     ├────────────────────────────────────────────────┼────────────────────────────────────────────────┤
815///     │                                              1 │                                              1 │
816///     │                                              2 │                                              2 │
817///     │                                              3 │                                              1 │
818///     │                                              4 │                                              2 │
819///     │                                              5 │                                              1 │
820///     │                                              6 │                                              2 │
821///     │                                              7 │                                              1 │
822///     │                                              8 │                                              2 │
823///     └────────────────────────────────────────────────┴────────────────────────────────────────────────┘
824/// ```
825///
826/// The following implementation refer to DuckDB's implementation
827fn build_batch(
828    batch: &RecordBatch,
829    schema: &SchemaRef,
830    list_type_columns: &[ListUnnest],
831    struct_column_indices: &HashSet<usize>,
832    options: &UnnestOptions,
833) -> Result<Option<RecordBatch>> {
834    let transformed = match list_type_columns.len() {
835        0 => flatten_struct_cols(batch.columns(), schema, struct_column_indices),
836        _ => {
837            let mut temp_unnested_result = HashMap::new();
838            let max_recursion = list_type_columns
839                .iter()
840                .fold(0, |highest_depth, ListUnnest { depth, .. }| {
841                    cmp::max(highest_depth, *depth)
842                });
843
844            // This arr always has the same column count with the input batch
845            let mut flatten_arrs = vec![];
846
847            // Original batch has the same columns
848            // All unnesting results are written to temp_batch
849            for depth in (1..=max_recursion).rev() {
850                let input = match depth == max_recursion {
851                    true => batch.columns(),
852                    false => &flatten_arrs,
853                };
854                let Some(temp_result) = list_unnest_at_level(
855                    input,
856                    list_type_columns,
857                    &mut temp_unnested_result,
858                    depth,
859                    options,
860                )?
861                else {
862                    return Ok(None);
863                };
864                flatten_arrs = temp_result;
865            }
866            let unnested_array_map: HashMap<usize, Vec<UnnestingResult>> =
867                temp_unnested_result.into_iter().fold(
868                    HashMap::new(),
869                    |mut acc,
870                     (
871                        ListUnnest {
872                            index_in_input_schema,
873                            depth,
874                        },
875                        flattened_array,
876                    )| {
877                        acc.entry(index_in_input_schema).or_default().push(
878                            UnnestingResult {
879                                arr: flattened_array,
880                                depth,
881                            },
882                        );
883                        acc
884                    },
885                );
886            let output_order: HashMap<ListUnnest, usize> = list_type_columns
887                .iter()
888                .enumerate()
889                .map(|(order, unnest_def)| (*unnest_def, order))
890                .collect();
891
892            // One original column may be unnested multiple times into separate columns
893            let mut multi_unnested_per_original_index = unnested_array_map
894                .into_iter()
895                .map(
896                    // Each item in unnested_columns is the result of unnesting the same input column
897                    // we need to sort them to conform with the original expression order
898                    // e.g unnest(unnest(col)) must goes before unnest(col)
899                    |(original_index, mut unnested_columns)| {
900                        unnested_columns.sort_by(
901                            |UnnestingResult { depth: depth1, .. },
902                             UnnestingResult { depth: depth2, .. }|
903                             -> Ordering {
904                                output_order
905                                    .get(&ListUnnest {
906                                        depth: *depth1,
907                                        index_in_input_schema: original_index,
908                                    })
909                                    .unwrap()
910                                    .cmp(
911                                        output_order
912                                            .get(&ListUnnest {
913                                                depth: *depth2,
914                                                index_in_input_schema: original_index,
915                                            })
916                                            .unwrap(),
917                                    )
918                            },
919                        );
920                        (
921                            original_index,
922                            unnested_columns
923                                .into_iter()
924                                .map(|result| result.arr)
925                                .collect::<Vec<_>>(),
926                        )
927                    },
928                )
929                .collect::<HashMap<_, _>>();
930
931            let ret = flatten_arrs
932                .into_iter()
933                .enumerate()
934                .flat_map(|(col_idx, arr)| {
935                    // Convert original column into its unnested version(s)
936                    // Plural because one column can be unnested with different recursion level
937                    // and into separate output columns
938                    match multi_unnested_per_original_index.remove(&col_idx) {
939                        Some(unnested_arrays) => unnested_arrays,
940                        None => vec![arr],
941                    }
942                })
943                .collect::<Vec<_>>();
944
945            flatten_struct_cols(&ret, schema, struct_column_indices)
946        }
947    }?;
948    Ok(Some(transformed))
949}
950
951/// Find the longest list length among the given list arrays for each row.
952///
953/// For example if we have the following two list arrays:
954///
955/// ```ignore
956/// l1: [1, 2, 3], null, [], [3]
957/// l2: [4,5], [], null, [6, 7]
958/// ```
959///
960/// With [`datafusion_common::NullHandling::Drop`], the longest length array will be:
961///
962/// ```ignore
963/// longest_length: [3, 0, 0, 2]
964/// ```
965///
966/// With [`datafusion_common::NullHandling::Preserve`] (the default), the longest length array
967/// will be:
968///
969/// ```ignore
970/// longest_length: [3, 1, 1, 2]
971/// ```
972///
973/// With [`datafusion_common::NullHandling::PreserveAndExpandEmpty`], empty input lists are
974/// also bumped to length 1 so they produce a single `NULL` output row:
975///
976/// ```ignore
977/// longest_length: [3, 1, 1, 2]
978/// ```
979fn find_longest_length(
980    list_arrays: &[ArrayRef],
981    options: &UnnestOptions,
982) -> Result<ArrayRef> {
983    // The length to substitute for a NULL input list.
984    let null_length = if options.preserve_nulls() {
985        Scalar::new(Int64Array::from_value(1, 1))
986    } else {
987        Scalar::new(Int64Array::from_value(0, 1))
988    };
989    let expand_empty = options.expand_empty_as_null();
990    // Reused scalars for the empty-list rewrite when expand_empty is set.
991    let zero = Scalar::new(Int64Array::from_value(0, 1));
992    let one = Scalar::new(Int64Array::from_value(1, 1));
993    let list_lengths: Vec<ArrayRef> = list_arrays
994        .iter()
995        .map(|list_array| {
996            let mut length_array = length(list_array)?;
997            // Make sure length arrays have the same type. Int64 is the most general one.
998            length_array = cast(&length_array, &DataType::Int64)?;
999            length_array =
1000                zip(&is_not_null(&length_array)?, &length_array, &null_length)?;
1001            if expand_empty {
1002                // Bump empty lists (length 0) to length 1 so they
1003                // produce a single output row padded with NULL.
1004                let is_zero = arrow_ord::cmp::eq(&length_array, &zero)?;
1005                length_array = zip(&is_zero, &one, &length_array)?;
1006            }
1007            Ok(length_array)
1008        })
1009        .collect::<Result<_>>()?;
1010
1011    let longest_length = list_lengths.iter().skip(1).try_fold(
1012        Arc::clone(&list_lengths[0]),
1013        |longest, current| {
1014            let is_lt = lt(&longest, &current)?;
1015            zip(&is_lt, &current, &longest)
1016        },
1017    )?;
1018    Ok(longest_length)
1019}
1020
1021/// Trait defining common methods used for unnesting, implemented by list array types.
1022trait ListArrayType: Array {
1023    /// Returns a reference to the values of this list.
1024    fn values(&self) -> &ArrayRef;
1025
1026    /// Returns the start and end offset of the values for the given row.
1027    fn value_offsets(&self, row: usize) -> (i64, i64);
1028}
1029
1030impl ListArrayType for ListArray {
1031    fn values(&self) -> &ArrayRef {
1032        self.values()
1033    }
1034
1035    fn value_offsets(&self, row: usize) -> (i64, i64) {
1036        let offsets = self.value_offsets();
1037        (offsets[row].into(), offsets[row + 1].into())
1038    }
1039}
1040
1041impl ListArrayType for LargeListArray {
1042    fn values(&self) -> &ArrayRef {
1043        self.values()
1044    }
1045
1046    fn value_offsets(&self, row: usize) -> (i64, i64) {
1047        let offsets = self.value_offsets();
1048        (offsets[row], offsets[row + 1])
1049    }
1050}
1051
1052impl ListArrayType for FixedSizeListArray {
1053    fn values(&self) -> &ArrayRef {
1054        self.values()
1055    }
1056
1057    fn value_offsets(&self, row: usize) -> (i64, i64) {
1058        let start = self.value_offset(row) as i64;
1059        (start, start + self.value_length() as i64)
1060    }
1061}
1062
1063impl ListArrayType for ListViewArray {
1064    fn values(&self) -> &ArrayRef {
1065        self.values()
1066    }
1067
1068    fn value_offsets(&self, row: usize) -> (i64, i64) {
1069        let offset = self.value_offsets()[row] as i64;
1070        let size = self.value_sizes()[row] as i64;
1071        (offset, offset + size)
1072    }
1073}
1074
1075impl ListArrayType for LargeListViewArray {
1076    fn values(&self) -> &ArrayRef {
1077        self.values()
1078    }
1079
1080    fn value_offsets(&self, row: usize) -> (i64, i64) {
1081        let offset = self.value_offsets()[row];
1082        let size = self.value_sizes()[row];
1083        (offset, offset + size)
1084    }
1085}
1086
1087/// Unnest multiple list arrays according to the length array.
1088fn unnest_list_arrays(
1089    list_arrays: &[ArrayRef],
1090    length_array: &PrimitiveArray<Int64Type>,
1091    capacity: usize,
1092) -> Result<Vec<ArrayRef>> {
1093    let typed_arrays = list_arrays
1094        .iter()
1095        .map(|list_array| match list_array.data_type() {
1096            DataType::List(_) => Ok(list_array.as_list::<i32>() as &dyn ListArrayType),
1097            DataType::LargeList(_) => {
1098                Ok(list_array.as_list::<i64>() as &dyn ListArrayType)
1099            }
1100            DataType::FixedSizeList(_, _) => {
1101                Ok(list_array.as_fixed_size_list() as &dyn ListArrayType)
1102            }
1103            DataType::ListView(_) => {
1104                Ok(list_array.as_list_view::<i32>() as &dyn ListArrayType)
1105            }
1106            DataType::LargeListView(_) => {
1107                Ok(list_array.as_list_view::<i64>() as &dyn ListArrayType)
1108            }
1109            other => exec_err!("Invalid unnest datatype {other }"),
1110        })
1111        .collect::<Result<Vec<_>>>()?;
1112
1113    typed_arrays
1114        .iter()
1115        .map(|list_array| unnest_list_array(*list_array, length_array, capacity))
1116        .collect::<Result<_>>()
1117}
1118
1119/// Unnest a list array according the target length array.
1120///
1121/// Consider a list array like this:
1122///
1123/// ```ignore
1124/// [1], [2, 3, 4], null, [5], [],
1125/// ```
1126///
1127/// and the length array is:
1128///
1129/// ```ignore
1130/// [2, 3, 2, 1, 2]
1131/// ```
1132///
1133/// If the length of a certain list is less than the target length, pad with NULLs.
1134/// So the unnested array will look like this:
1135///
1136/// ```ignore
1137/// [1, null, 2, 3, 4, null, null, 5, null, null]
1138/// ```
1139fn unnest_list_array(
1140    list_array: &dyn ListArrayType,
1141    length_array: &PrimitiveArray<Int64Type>,
1142    capacity: usize,
1143) -> Result<ArrayRef> {
1144    let values = list_array.values();
1145    let mut take_indices_builder = PrimitiveArray::<Int64Type>::builder(capacity);
1146    for row in 0..list_array.len() {
1147        let mut value_length = 0;
1148        if !list_array.is_null(row) {
1149            let (start, end) = list_array.value_offsets(row);
1150            value_length = end - start;
1151            for i in start..end {
1152                take_indices_builder.append_value(i)
1153            }
1154        }
1155        let target_length = length_array.value(row);
1156        debug_assert!(
1157            value_length <= target_length,
1158            "value length is beyond the longest length"
1159        );
1160        // Pad with NULL values
1161        for _ in value_length..target_length {
1162            take_indices_builder.append_null();
1163        }
1164    }
1165    Ok(kernels::take::take(
1166        &values,
1167        &take_indices_builder.finish(),
1168        None,
1169    )?)
1170}
1171
1172/// Creates take indices that will be used to expand all columns except for the list type
1173/// [`columns`](UnnestExec::list_column_indices) that is being unnested.
1174/// Every column value needs to be repeated multiple times according to the length array.
1175///
1176/// If the length array looks like this:
1177///
1178/// ```ignore
1179/// [2, 3, 1]
1180/// ```
1181/// Then [`create_take_indices`] will return an array like this
1182///
1183/// ```ignore
1184/// [0, 0, 1, 1, 1, 2]
1185/// ```
1186fn create_take_indices(
1187    length_array: &PrimitiveArray<Int64Type>,
1188    capacity: usize,
1189) -> PrimitiveArray<Int64Type> {
1190    // `find_longest_length()` guarantees this.
1191    debug_assert!(
1192        length_array.null_count() == 0,
1193        "length array should not contain nulls"
1194    );
1195    let mut builder = PrimitiveArray::<Int64Type>::builder(capacity);
1196    for (index, repeat) in length_array.iter().enumerate() {
1197        // The length array should not contain nulls, so unwrap is safe
1198        let repeat = repeat.unwrap();
1199        (0..repeat).for_each(|_| builder.append_value(index as i64));
1200    }
1201    builder.finish()
1202}
1203
1204/// Create a batch of arrays based on an input `batch` and a `indices` array.
1205/// The `indices` array is used by the take kernel to repeat values in the arrays
1206/// that are marked with `true` in the `repeat_mask`. Arrays marked with `false`
1207/// in the `repeat_mask` will be replaced with arrays filled with nulls of the
1208/// appropriate length.
1209///
1210/// For example if we have the following batch:
1211///
1212/// ```ignore
1213/// c1: [1], null, [2, 3, 4], null, [5, 6]
1214/// c2: 'a', 'b',  'c', null, 'd'
1215/// ```
1216///
1217/// then the `unnested_list_arrays` contains the unnest column that will replace `c1` in
1218/// the final batch if `preserve_nulls` is true:
1219///
1220/// ```ignore
1221/// c1: 1, null, 2, 3, 4, null, 5, 6
1222/// ```
1223///
1224/// And the `indices` array contains the indices that are used by `take` kernel to
1225/// repeat the values in `c2`:
1226///
1227/// ```ignore
1228/// 0, 1, 2, 2, 2, 3, 4, 4
1229/// ```
1230///
1231/// so that the final batch will look like:
1232///
1233/// ```ignore
1234/// c1: 1, null, 2, 3, 4, null, 5, 6
1235/// c2: 'a', 'b', 'c', 'c', 'c', null, 'd', 'd'
1236/// ```
1237///
1238/// The `repeat_mask` determines whether an array's values are repeated or replaced with nulls.
1239/// For example, if the `repeat_mask` is:
1240///
1241/// ```ignore
1242/// [true, false]
1243/// ```
1244///
1245/// The final batch will look like:
1246///
1247/// ```ignore
1248/// c1: 1, null, 2, 3, 4, null, 5, 6  // Repeated using `indices`
1249/// c2: null, null, null, null, null, null, null, null  // Replaced with nulls
1250fn repeat_arrs_from_indices(
1251    batch: &[ArrayRef],
1252    indices: &PrimitiveArray<Int64Type>,
1253    repeat_mask: &[bool],
1254) -> Result<Vec<Arc<dyn Array>>> {
1255    batch
1256        .iter()
1257        .zip(repeat_mask.iter())
1258        .map(|(arr, &repeat)| {
1259            if repeat {
1260                Ok(kernels::take::take(arr, indices, None)?)
1261            } else {
1262                Ok(new_null_array(arr.data_type(), arr.len()))
1263            }
1264        })
1265        .collect()
1266}
1267
1268#[cfg(test)]
1269mod tests {
1270    use super::*;
1271    use arrow::array::{
1272        GenericListArray, NullBufferBuilder, OffsetSizeTrait, StringArray,
1273    };
1274    use arrow::buffer::{NullBuffer, OffsetBuffer};
1275    use arrow::datatypes::{Field, Int32Type};
1276    use datafusion_common::NullHandling;
1277    use datafusion_common::test_util::batches_to_string;
1278    use insta::assert_snapshot;
1279
1280    // Create a GenericListArray with the following list values:
1281    //  [A, B, C], [], NULL, [D], NULL, [NULL, F]
1282    fn make_generic_array<OffsetSize>() -> GenericListArray<OffsetSize>
1283    where
1284        OffsetSize: OffsetSizeTrait,
1285    {
1286        let mut values = vec![];
1287        let mut offsets: Vec<OffsetSize> = vec![OffsetSize::zero()];
1288        let mut valid = NullBufferBuilder::new(6);
1289
1290        // [A, B, C]
1291        values.extend_from_slice(&[Some("A"), Some("B"), Some("C")]);
1292        offsets.push(OffsetSize::from_usize(values.len()).unwrap());
1293        valid.append_non_null();
1294
1295        // []
1296        offsets.push(OffsetSize::from_usize(values.len()).unwrap());
1297        valid.append_non_null();
1298
1299        // NULL with non-zero value length
1300        // Issue https://github.com/apache/datafusion/issues/9932
1301        values.push(Some("?"));
1302        offsets.push(OffsetSize::from_usize(values.len()).unwrap());
1303        valid.append_null();
1304
1305        // [D]
1306        values.push(Some("D"));
1307        offsets.push(OffsetSize::from_usize(values.len()).unwrap());
1308        valid.append_non_null();
1309
1310        // Another NULL with zero value length
1311        offsets.push(OffsetSize::from_usize(values.len()).unwrap());
1312        valid.append_null();
1313
1314        // [NULL, F]
1315        values.extend_from_slice(&[None, Some("F")]);
1316        offsets.push(OffsetSize::from_usize(values.len()).unwrap());
1317        valid.append_non_null();
1318
1319        let field = Arc::new(Field::new_list_field(DataType::Utf8, true));
1320        GenericListArray::<OffsetSize>::new(
1321            field,
1322            OffsetBuffer::new(offsets.into()),
1323            Arc::new(StringArray::from(values)),
1324            valid.finish(),
1325        )
1326    }
1327
1328    // Create a FixedSizeListArray with the following list values:
1329    //  [A, B], NULL, [C, D], NULL, [NULL, F], [NULL, NULL]
1330    fn make_fixed_list() -> FixedSizeListArray {
1331        let values = Arc::new(StringArray::from_iter([
1332            Some("A"),
1333            Some("B"),
1334            None,
1335            None,
1336            Some("C"),
1337            Some("D"),
1338            None,
1339            None,
1340            None,
1341            Some("F"),
1342            None,
1343            None,
1344        ]));
1345        let field = Arc::new(Field::new_list_field(DataType::Utf8, true));
1346        let valid = NullBuffer::from(vec![true, false, true, false, true, true]);
1347        FixedSizeListArray::new(field, 2, values, Some(valid))
1348    }
1349
1350    fn verify_unnest_list_array(
1351        list_array: &dyn ListArrayType,
1352        lengths: Vec<i64>,
1353        expected: Vec<Option<&str>>,
1354    ) -> Result<()> {
1355        let length_array = Int64Array::from(lengths);
1356        let unnested_array = unnest_list_array(list_array, &length_array, 3 * 6)?;
1357        let strs = unnested_array.as_string::<i32>().iter().collect::<Vec<_>>();
1358        assert_eq!(strs, expected);
1359        Ok(())
1360    }
1361
1362    #[test]
1363    fn test_build_batch_list_arr_recursive() -> Result<()> {
1364        // col1                             | col2
1365        // [[1,2,3],null,[4,5]]             | ['a','b']
1366        // [[7,8,9,10], null, [11,12,13]]   | ['c','d']
1367        // null                             | ['e']
1368        let list_arr1 = ListArray::from_iter_primitive::<Int32Type, _, _>(vec![
1369            Some(vec![Some(1), Some(2), Some(3)]),
1370            None,
1371            Some(vec![Some(4), Some(5)]),
1372            Some(vec![Some(7), Some(8), Some(9), Some(10)]),
1373            None,
1374            Some(vec![Some(11), Some(12), Some(13)]),
1375        ]);
1376
1377        let list_arr1_ref = Arc::new(list_arr1) as ArrayRef;
1378        let offsets = OffsetBuffer::from_lengths([3, 3, 0]);
1379        let mut nulls = NullBufferBuilder::new(3);
1380        nulls.append_non_null();
1381        nulls.append_non_null();
1382        nulls.append_null();
1383        // list<list<int32>>
1384        let col1_field = Field::new_list_field(
1385            DataType::List(Arc::new(Field::new_list_field(
1386                list_arr1_ref.data_type().to_owned(),
1387                true,
1388            ))),
1389            true,
1390        );
1391        let col1 = ListArray::new(
1392            Arc::new(Field::new_list_field(
1393                list_arr1_ref.data_type().to_owned(),
1394                true,
1395            )),
1396            offsets,
1397            list_arr1_ref,
1398            nulls.finish(),
1399        );
1400
1401        let list_arr2 = StringArray::from(vec![
1402            Some("a"),
1403            Some("b"),
1404            Some("c"),
1405            Some("d"),
1406            Some("e"),
1407        ]);
1408
1409        let offsets = OffsetBuffer::from_lengths([2, 2, 1]);
1410        let mut nulls = NullBufferBuilder::new(3);
1411        nulls.append_n_non_nulls(3);
1412        let col2_field = Field::new(
1413            "col2",
1414            DataType::List(Arc::new(Field::new_list_field(DataType::Utf8, true))),
1415            true,
1416        );
1417        let col2 = GenericListArray::<i32>::new(
1418            Arc::new(Field::new_list_field(DataType::Utf8, true)),
1419            OffsetBuffer::new(offsets.into()),
1420            Arc::new(list_arr2),
1421            nulls.finish(),
1422        );
1423        // convert col1 and col2 to a record batch
1424        let schema = Arc::new(Schema::new(vec![col1_field, col2_field]));
1425        let out_schema = Arc::new(Schema::new(vec![
1426            Field::new(
1427                "col1_unnest_placeholder_depth_1",
1428                DataType::List(Arc::new(Field::new_list_field(DataType::Int32, true))),
1429                true,
1430            ),
1431            Field::new("col1_unnest_placeholder_depth_2", DataType::Int32, true),
1432            Field::new("col2_unnest_placeholder_depth_1", DataType::Utf8, true),
1433        ]));
1434        let batch = RecordBatch::try_new(
1435            Arc::clone(&schema),
1436            vec![Arc::new(col1) as ArrayRef, Arc::new(col2) as ArrayRef],
1437        )
1438        .unwrap();
1439        let list_type_columns = vec![
1440            ListUnnest {
1441                index_in_input_schema: 0,
1442                depth: 1,
1443            },
1444            ListUnnest {
1445                index_in_input_schema: 0,
1446                depth: 2,
1447            },
1448            ListUnnest {
1449                index_in_input_schema: 1,
1450                depth: 1,
1451            },
1452        ];
1453        let ret = build_batch(
1454            &batch,
1455            &out_schema,
1456            list_type_columns.as_ref(),
1457            &HashSet::default(),
1458            &UnnestOptions {
1459                null_handling: NullHandling::Preserve,
1460                recursions: vec![],
1461            },
1462        )?
1463        .unwrap();
1464
1465        assert_snapshot!(batches_to_string(&[ret]),
1466        @r"
1467        +---------------------------------+---------------------------------+---------------------------------+
1468        | col1_unnest_placeholder_depth_1 | col1_unnest_placeholder_depth_2 | col2_unnest_placeholder_depth_1 |
1469        +---------------------------------+---------------------------------+---------------------------------+
1470        | [1, 2, 3]                       | 1                               | a                               |
1471        |                                 | 2                               | b                               |
1472        | [4, 5]                          | 3                               |                                 |
1473        | [1, 2, 3]                       |                                 | a                               |
1474        |                                 |                                 | b                               |
1475        | [4, 5]                          |                                 |                                 |
1476        | [1, 2, 3]                       | 4                               | a                               |
1477        |                                 | 5                               | b                               |
1478        | [4, 5]                          |                                 |                                 |
1479        | [7, 8, 9, 10]                   | 7                               | c                               |
1480        |                                 | 8                               | d                               |
1481        | [11, 12, 13]                    | 9                               |                                 |
1482        |                                 | 10                              |                                 |
1483        | [7, 8, 9, 10]                   |                                 | c                               |
1484        |                                 |                                 | d                               |
1485        | [11, 12, 13]                    |                                 |                                 |
1486        | [7, 8, 9, 10]                   | 11                              | c                               |
1487        |                                 | 12                              | d                               |
1488        | [11, 12, 13]                    | 13                              |                                 |
1489        |                                 |                                 | e                               |
1490        +---------------------------------+---------------------------------+---------------------------------+
1491        ");
1492        Ok(())
1493    }
1494
1495    #[test]
1496    fn test_build_batch_preserve_and_expand_empty() -> Result<()> {
1497        // c1: [A, B, C], [], NULL, [D], NULL, [NULL, F]   c2: 1, 2, 3, 4, 5, 6
1498        // Expected for `NullHandling::PreserveAndExpandEmpty`:
1499        //   [A, B, C] -> three rows with c2 = 1, 1, 1
1500        //   []        -> one  row  with c2 = 2 and unnested value NULL
1501        //   NULL      -> one  row  with c2 = 3 and unnested value NULL
1502        //   [D]       -> one  row  with c2 = 4
1503        //   NULL      -> one  row  with c2 = 5 and unnested value NULL
1504        //   [NULL, F] -> two  rows with c2 = 6, 6
1505        let list_array = Arc::new(make_generic_array::<i32>()) as ArrayRef;
1506        let other =
1507            Arc::new(arrow::array::Int32Array::from(vec![1, 2, 3, 4, 5, 6])) as ArrayRef;
1508        let in_schema = Arc::new(Schema::new(vec![
1509            Field::new(
1510                "c1",
1511                DataType::List(Arc::new(Field::new_list_field(DataType::Utf8, true))),
1512                true,
1513            ),
1514            Field::new("c2", DataType::Int32, true),
1515        ]));
1516        let out_schema = Arc::new(Schema::new(vec![
1517            Field::new("c1_unnested", DataType::Utf8, true),
1518            Field::new("c2", DataType::Int32, true),
1519        ]));
1520        let batch = RecordBatch::try_new(
1521            Arc::clone(&in_schema),
1522            vec![Arc::clone(&list_array), Arc::clone(&other)],
1523        )?;
1524        let list_type_columns = vec![ListUnnest {
1525            index_in_input_schema: 0,
1526            depth: 1,
1527        }];
1528
1529        let ret = build_batch(
1530            &batch,
1531            &out_schema,
1532            &list_type_columns,
1533            &HashSet::default(),
1534            &UnnestOptions {
1535                null_handling: NullHandling::PreserveAndExpandEmpty,
1536                recursions: vec![],
1537            },
1538        )?
1539        .unwrap();
1540
1541        assert_snapshot!(batches_to_string(&[ret]),
1542        @r"
1543        +-------------+----+
1544        | c1_unnested | c2 |
1545        +-------------+----+
1546        | A           | 1  |
1547        | B           | 1  |
1548        | C           | 1  |
1549        |             | 2  |
1550        |             | 3  |
1551        | D           | 4  |
1552        |             | 5  |
1553        |             | 6  |
1554        | F           | 6  |
1555        +-------------+----+
1556        ");
1557        Ok(())
1558    }
1559
1560    // PreserveAndExpandEmpty must work for LargeListArray (i64 offsets) too,
1561    // not just the i32-offset ListArray exercised above.
1562    #[test]
1563    fn test_build_batch_preserve_and_expand_empty_largelist() -> Result<()> {
1564        let list_array = Arc::new(make_generic_array::<i64>()) as ArrayRef;
1565        let other =
1566            Arc::new(arrow::array::Int32Array::from(vec![1, 2, 3, 4, 5, 6])) as ArrayRef;
1567        let in_schema = Arc::new(Schema::new(vec![
1568            Field::new(
1569                "c1",
1570                DataType::LargeList(Arc::new(Field::new_list_field(
1571                    DataType::Utf8,
1572                    true,
1573                ))),
1574                true,
1575            ),
1576            Field::new("c2", DataType::Int32, true),
1577        ]));
1578        let out_schema = Arc::new(Schema::new(vec![
1579            Field::new("c1_unnested", DataType::Utf8, true),
1580            Field::new("c2", DataType::Int32, true),
1581        ]));
1582        let batch = RecordBatch::try_new(
1583            Arc::clone(&in_schema),
1584            vec![Arc::clone(&list_array), Arc::clone(&other)],
1585        )?;
1586        let list_type_columns = vec![ListUnnest {
1587            index_in_input_schema: 0,
1588            depth: 1,
1589        }];
1590
1591        let ret = build_batch(
1592            &batch,
1593            &out_schema,
1594            &list_type_columns,
1595            &HashSet::default(),
1596            &UnnestOptions {
1597                null_handling: NullHandling::PreserveAndExpandEmpty,
1598                recursions: vec![],
1599            },
1600        )?
1601        .unwrap();
1602
1603        // Same expected shape as the ListArray case — exercises the LargeList
1604        // code path in unnest_list_array.
1605        assert_snapshot!(batches_to_string(&[ret]),
1606        @r"
1607        +-------------+----+
1608        | c1_unnested | c2 |
1609        +-------------+----+
1610        | A           | 1  |
1611        | B           | 1  |
1612        | C           | 1  |
1613        |             | 2  |
1614        |             | 3  |
1615        | D           | 4  |
1616        |             | 5  |
1617        |             | 6  |
1618        | F           | 6  |
1619        +-------------+----+
1620        ");
1621        Ok(())
1622    }
1623
1624    // When two list columns are unnested together, `find_longest_length`
1625    // takes the per-row max. PreserveAndExpandEmpty must bump zeros to ones
1626    // in each input column independently, then the row-wise max picks up
1627    // the right value.
1628    #[test]
1629    fn test_build_batch_preserve_and_expand_empty_multi_column() -> Result<()> {
1630        // col_a: [1, 2], [],   NULL,  [3]
1631        // col_b: ['x'],  ['y'],['z'], NULL
1632        let col_a = ListArray::from_iter_primitive::<Int32Type, _, _>(vec![
1633            Some(vec![Some(1), Some(2)]),
1634            Some(vec![]),
1635            None,
1636            Some(vec![Some(3)]),
1637        ]);
1638        let col_b = {
1639            let mut b =
1640                arrow::array::ListBuilder::new(arrow::array::StringBuilder::new());
1641            b.values().append_value("x");
1642            b.append(true);
1643            b.values().append_value("y");
1644            b.append(true);
1645            b.values().append_value("z");
1646            b.append(true);
1647            b.append(false);
1648            b.finish()
1649        };
1650        let id =
1651            Arc::new(arrow::array::Int32Array::from(vec![10, 20, 30, 40])) as ArrayRef;
1652
1653        let in_schema = Arc::new(Schema::new(vec![
1654            Field::new(
1655                "a",
1656                DataType::List(Arc::new(Field::new_list_field(DataType::Int32, true))),
1657                true,
1658            ),
1659            Field::new(
1660                "b",
1661                DataType::List(Arc::new(Field::new_list_field(DataType::Utf8, true))),
1662                true,
1663            ),
1664            Field::new("id", DataType::Int32, true),
1665        ]));
1666        let out_schema = Arc::new(Schema::new(vec![
1667            Field::new("a_unnested", DataType::Int32, true),
1668            Field::new("b_unnested", DataType::Utf8, true),
1669            Field::new("id", DataType::Int32, true),
1670        ]));
1671        let batch = RecordBatch::try_new(
1672            Arc::clone(&in_schema),
1673            vec![
1674                Arc::new(col_a) as ArrayRef,
1675                Arc::new(col_b) as ArrayRef,
1676                Arc::clone(&id),
1677            ],
1678        )?;
1679        let list_type_columns = vec![
1680            ListUnnest {
1681                index_in_input_schema: 0,
1682                depth: 1,
1683            },
1684            ListUnnest {
1685                index_in_input_schema: 1,
1686                depth: 1,
1687            },
1688        ];
1689
1690        let ret = build_batch(
1691            &batch,
1692            &out_schema,
1693            &list_type_columns,
1694            &HashSet::default(),
1695            &UnnestOptions {
1696                null_handling: NullHandling::PreserveAndExpandEmpty,
1697                recursions: vec![],
1698            },
1699        )?
1700        .unwrap();
1701
1702        // Row 0: longest = max(len([1,2])=2, len(['x'])=1) = 2 → a=[1,2], b=['x',NULL]
1703        // Row 1: a=[] bumped to len 1, b=['y'] len 1 → a=[NULL], b=['y']
1704        // Row 2: a=NULL bumped to len 1, b=['z'] len 1 → a=[NULL], b=['z']
1705        // Row 3: a=[3] len 1, b=NULL bumped to len 1 → a=[3], b=[NULL]
1706        assert_snapshot!(batches_to_string(&[ret]),
1707        @r"
1708        +------------+------------+----+
1709        | a_unnested | b_unnested | id |
1710        +------------+------------+----+
1711        | 1          | x          | 10 |
1712        | 2          |            | 10 |
1713        |            | y          | 20 |
1714        |            | z          | 30 |
1715        | 3          |            | 40 |
1716        +------------+------------+----+
1717        ");
1718        Ok(())
1719    }
1720
1721    // PreserveAndExpandEmpty must propagate through recursive depth-2
1722    // unnesting: an outer NULL or empty produces one NULL output row at
1723    // each level. Adapted from `test_build_batch_list_arr_recursive`.
1724    #[test]
1725    fn test_build_batch_preserve_and_expand_empty_recursive() -> Result<()> {
1726        // col1                             | col2
1727        // [[1,2,3],null,[4,5]]             | ['a','b']
1728        // [[7,8,9,10], null, [11,12,13]]   | ['c','d']
1729        // null                             | ['e']
1730        let list_arr1 = ListArray::from_iter_primitive::<Int32Type, _, _>(vec![
1731            Some(vec![Some(1), Some(2), Some(3)]),
1732            None,
1733            Some(vec![Some(4), Some(5)]),
1734            Some(vec![Some(7), Some(8), Some(9), Some(10)]),
1735            None,
1736            Some(vec![Some(11), Some(12), Some(13)]),
1737        ]);
1738        let list_arr1_ref = Arc::new(list_arr1) as ArrayRef;
1739        let offsets = OffsetBuffer::from_lengths([3, 3, 0]);
1740        let mut nulls = NullBufferBuilder::new(3);
1741        nulls.append_non_null();
1742        nulls.append_non_null();
1743        nulls.append_null();
1744        let col1_field = Field::new_list_field(
1745            DataType::List(Arc::new(Field::new_list_field(
1746                list_arr1_ref.data_type().to_owned(),
1747                true,
1748            ))),
1749            true,
1750        );
1751        let col1 = ListArray::new(
1752            Arc::new(Field::new_list_field(
1753                list_arr1_ref.data_type().to_owned(),
1754                true,
1755            )),
1756            offsets,
1757            list_arr1_ref,
1758            nulls.finish(),
1759        );
1760
1761        let list_arr2 = StringArray::from(vec![
1762            Some("a"),
1763            Some("b"),
1764            Some("c"),
1765            Some("d"),
1766            Some("e"),
1767        ]);
1768        let offsets = OffsetBuffer::from_lengths([2, 2, 1]);
1769        let mut nulls = NullBufferBuilder::new(3);
1770        nulls.append_n_non_nulls(3);
1771        let col2_field = Field::new(
1772            "col2",
1773            DataType::List(Arc::new(Field::new_list_field(DataType::Utf8, true))),
1774            true,
1775        );
1776        let col2 = GenericListArray::<i32>::new(
1777            Arc::new(Field::new_list_field(DataType::Utf8, true)),
1778            OffsetBuffer::new(offsets.into()),
1779            Arc::new(list_arr2),
1780            nulls.finish(),
1781        );
1782        let schema = Arc::new(Schema::new(vec![col1_field, col2_field]));
1783        let out_schema = Arc::new(Schema::new(vec![
1784            Field::new(
1785                "col1_unnest_placeholder_depth_1",
1786                DataType::List(Arc::new(Field::new_list_field(DataType::Int32, true))),
1787                true,
1788            ),
1789            Field::new("col1_unnest_placeholder_depth_2", DataType::Int32, true),
1790            Field::new("col2_unnest_placeholder_depth_1", DataType::Utf8, true),
1791        ]));
1792        let batch = RecordBatch::try_new(
1793            Arc::clone(&schema),
1794            vec![Arc::new(col1) as ArrayRef, Arc::new(col2) as ArrayRef],
1795        )?;
1796        let list_type_columns = vec![
1797            ListUnnest {
1798                index_in_input_schema: 0,
1799                depth: 1,
1800            },
1801            ListUnnest {
1802                index_in_input_schema: 0,
1803                depth: 2,
1804            },
1805            ListUnnest {
1806                index_in_input_schema: 1,
1807                depth: 1,
1808            },
1809        ];
1810
1811        let ret = build_batch(
1812            &batch,
1813            &out_schema,
1814            &list_type_columns,
1815            &HashSet::default(),
1816            &UnnestOptions {
1817                null_handling: NullHandling::PreserveAndExpandEmpty,
1818                recursions: vec![],
1819            },
1820        )?
1821        .unwrap();
1822
1823        // The third input row (col1 = null, col2 = ['e']) now produces a
1824        // NULL row for the depth-1 col1 placeholder *and* the depth-2 one,
1825        // instead of being dropped at depth 1 and again at depth 2 the way
1826        // it would be under `Drop`. Inner NULLs inside [...null...] sub-
1827        // lists are still padded with NULL as before.
1828        assert_snapshot!(batches_to_string(&[ret]),
1829        @r"
1830        +---------------------------------+---------------------------------+---------------------------------+
1831        | col1_unnest_placeholder_depth_1 | col1_unnest_placeholder_depth_2 | col2_unnest_placeholder_depth_1 |
1832        +---------------------------------+---------------------------------+---------------------------------+
1833        | [1, 2, 3]                       | 1                               | a                               |
1834        |                                 | 2                               | b                               |
1835        | [4, 5]                          | 3                               |                                 |
1836        | [1, 2, 3]                       |                                 | a                               |
1837        |                                 |                                 | b                               |
1838        | [4, 5]                          |                                 |                                 |
1839        | [1, 2, 3]                       | 4                               | a                               |
1840        |                                 | 5                               | b                               |
1841        | [4, 5]                          |                                 |                                 |
1842        | [7, 8, 9, 10]                   | 7                               | c                               |
1843        |                                 | 8                               | d                               |
1844        | [11, 12, 13]                    | 9                               |                                 |
1845        |                                 | 10                              |                                 |
1846        | [7, 8, 9, 10]                   |                                 | c                               |
1847        |                                 |                                 | d                               |
1848        | [11, 12, 13]                    |                                 |                                 |
1849        | [7, 8, 9, 10]                   | 11                              | c                               |
1850        |                                 | 12                              | d                               |
1851        | [11, 12, 13]                    | 13                              |                                 |
1852        |                                 |                                 | e                               |
1853        +---------------------------------+---------------------------------+---------------------------------+
1854        ");
1855        Ok(())
1856    }
1857
1858    #[test]
1859    fn test_unnest_list_array() -> Result<()> {
1860        // [A, B, C], [], NULL, [D], NULL, [NULL, F]
1861        let list_array = make_generic_array::<i32>();
1862        verify_unnest_list_array(
1863            &list_array,
1864            vec![3, 2, 1, 2, 0, 3],
1865            vec![
1866                Some("A"),
1867                Some("B"),
1868                Some("C"),
1869                None,
1870                None,
1871                None,
1872                Some("D"),
1873                None,
1874                None,
1875                Some("F"),
1876                None,
1877            ],
1878        )?;
1879
1880        // [A, B], NULL, [C, D], NULL, [NULL, F], [NULL, NULL]
1881        let list_array = make_fixed_list();
1882        verify_unnest_list_array(
1883            &list_array,
1884            vec![3, 1, 2, 0, 2, 3],
1885            vec![
1886                Some("A"),
1887                Some("B"),
1888                None,
1889                None,
1890                Some("C"),
1891                Some("D"),
1892                None,
1893                Some("F"),
1894                None,
1895                None,
1896                None,
1897            ],
1898        )?;
1899
1900        Ok(())
1901    }
1902
1903    fn verify_longest_length(
1904        list_arrays: &[ArrayRef],
1905        null_handling: NullHandling,
1906        expected: Vec<i64>,
1907    ) -> Result<()> {
1908        let options = UnnestOptions {
1909            null_handling,
1910            recursions: vec![],
1911        };
1912        let longest_length = find_longest_length(list_arrays, &options)?;
1913        let expected_array = Int64Array::from(expected);
1914        assert_eq!(
1915            longest_length
1916                .as_any()
1917                .downcast_ref::<Int64Array>()
1918                .unwrap(),
1919            &expected_array
1920        );
1921        Ok(())
1922    }
1923
1924    #[test]
1925    fn test_longest_list_length() -> Result<()> {
1926        // Test with single ListArray
1927        //  [A, B, C], [], NULL, [D], NULL, [NULL, F]
1928        let list_array = Arc::new(make_generic_array::<i32>()) as ArrayRef;
1929        verify_longest_length(
1930            &[Arc::clone(&list_array)],
1931            NullHandling::Drop,
1932            vec![3, 0, 0, 1, 0, 2],
1933        )?;
1934        verify_longest_length(
1935            &[Arc::clone(&list_array)],
1936            NullHandling::Preserve,
1937            vec![3, 0, 1, 1, 1, 2],
1938        )?;
1939        // PreserveAndExpandEmpty also treats empty lists as a NULL row.
1940        verify_longest_length(
1941            &[Arc::clone(&list_array)],
1942            NullHandling::PreserveAndExpandEmpty,
1943            vec![3, 1, 1, 1, 1, 2],
1944        )?;
1945
1946        // Test with single LargeListArray
1947        //  [A, B, C], [], NULL, [D], NULL, [NULL, F]
1948        let list_array = Arc::new(make_generic_array::<i64>()) as ArrayRef;
1949        verify_longest_length(
1950            &[Arc::clone(&list_array)],
1951            NullHandling::Drop,
1952            vec![3, 0, 0, 1, 0, 2],
1953        )?;
1954        verify_longest_length(
1955            &[Arc::clone(&list_array)],
1956            NullHandling::Preserve,
1957            vec![3, 0, 1, 1, 1, 2],
1958        )?;
1959        verify_longest_length(
1960            &[Arc::clone(&list_array)],
1961            NullHandling::PreserveAndExpandEmpty,
1962            vec![3, 1, 1, 1, 1, 2],
1963        )?;
1964
1965        // Test with single FixedSizeListArray
1966        //  [A, B], NULL, [C, D], NULL, [NULL, F], [NULL, NULL]
1967        let list_array = Arc::new(make_fixed_list()) as ArrayRef;
1968        verify_longest_length(
1969            &[Arc::clone(&list_array)],
1970            NullHandling::Drop,
1971            vec![2, 0, 2, 0, 2, 2],
1972        )?;
1973        verify_longest_length(
1974            &[Arc::clone(&list_array)],
1975            NullHandling::Preserve,
1976            vec![2, 1, 2, 1, 2, 2],
1977        )?;
1978
1979        // Test with multiple list arrays
1980        //  [A, B, C], [], NULL, [D], NULL, [NULL, F]
1981        //  [A, B], NULL, [C, D], NULL, [NULL, F], [NULL, NULL]
1982        let list1 = Arc::new(make_generic_array::<i32>()) as ArrayRef;
1983        let list2 = Arc::new(make_fixed_list()) as ArrayRef;
1984        let list_arrays = vec![Arc::clone(&list1), Arc::clone(&list2)];
1985        verify_longest_length(&list_arrays, NullHandling::Drop, vec![3, 0, 2, 1, 2, 2])?;
1986        verify_longest_length(
1987            &list_arrays,
1988            NullHandling::Preserve,
1989            vec![3, 1, 2, 1, 2, 2],
1990        )?;
1991        verify_longest_length(
1992            &list_arrays,
1993            NullHandling::PreserveAndExpandEmpty,
1994            vec![3, 1, 2, 1, 2, 2],
1995        )?;
1996
1997        Ok(())
1998    }
1999
2000    #[test]
2001    fn test_create_take_indices() -> Result<()> {
2002        let length_array = Int64Array::from(vec![2, 3, 1]);
2003        let take_indices = create_take_indices(&length_array, 6);
2004        let expected = Int64Array::from(vec![0, 0, 1, 1, 1, 2]);
2005        assert_eq!(take_indices, expected);
2006        Ok(())
2007    }
2008}