Skip to main content

parquet_variant_compute/
shred_variant.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//! Module for shredding VariantArray with a given schema.
19
20use crate::type_conversion::VariantCastMode;
21use crate::variant_array::ShreddedVariantFieldArray;
22use crate::variant_to_arrow::{
23    ArrayVariantToArrowRowBuilder, PrimitiveVariantToArrowRowBuilder,
24    make_primitive_variant_to_arrow_row_builder,
25};
26use crate::{VariantArray, VariantValueArrayBuilder};
27use arrow::array::{ArrayRef, BinaryViewArray, NullBufferBuilder, StructArrayAssembler};
28use arrow::buffer::NullBuffer;
29use arrow::compute::CastOptions;
30use arrow::datatypes::{DataType, Field, FieldRef, Fields, TimeUnit};
31use arrow::error::{ArrowError, Result};
32use indexmap::IndexMap;
33use parquet_variant::{Variant, VariantBuilderExt, VariantPath, VariantPathElement};
34use std::collections::BTreeMap;
35use std::sync::Arc;
36
37/// Shreds the input binary variant using a target shredding schema derived from the requested data type.
38///
39/// For example, requesting `DataType::Int64` would produce an output variant array with the schema:
40///
41/// ```text
42/// {
43///    metadata: BINARY,
44///    value: BINARY,
45///    typed_value: LONG,
46/// }
47/// ```
48///
49/// Similarly, requesting `DataType::Struct` with two integer fields `a` and `b` would produce an
50/// output variant array with the schema:
51///
52/// ```text
53/// {
54///   metadata: BINARY,
55///   value: BINARY,
56///   typed_value: {
57///     a: {
58///       value: BINARY,
59///       typed_value: INT,
60///     },
61///     b: {
62///       value: BINARY,
63///       typed_value: INT,
64///     },
65///   }
66/// }
67/// ```
68///
69/// See [`ShreddedSchemaBuilder`] for a convenient way to build the `as_type`
70/// value passed to this function.
71pub fn shred_variant(array: &VariantArray, as_type: &DataType) -> Result<VariantArray> {
72    shred_variant_with_options(array, as_type, &CastOptions::default())
73}
74
75pub(crate) fn shred_variant_with_options(
76    array: &VariantArray,
77    as_type: &DataType,
78    cast_options: &CastOptions,
79) -> Result<VariantArray> {
80    if array.typed_value_column().is_some() {
81        return Err(ArrowError::InvalidArgumentError(
82            "Input is already shredded".to_string(),
83        ));
84    }
85
86    let mut builder = make_variant_to_shredded_variant_arrow_row_builder(
87        as_type,
88        cast_options,
89        array.len(),
90        NullValue::TopLevelVariant,
91        VariantCastMode::Shred,
92    )?;
93    for i in 0..array.len() {
94        if array.is_null(i) {
95            builder.append_null()?;
96        } else {
97            builder.append_value(array.value(i))?;
98        }
99    }
100    let (value, typed_value, nulls) = builder.finish()?;
101    Ok(VariantArray::from_parts(
102        array.metadata_column().clone(),
103        Arc::new(value),
104        Some(typed_value),
105        nulls,
106    ))
107}
108
109/// Controls how `append_null` is encoded for a shredded `(value, typed_value)` pair.
110///
111/// | Mode | Struct validity bit | `value` | `typed_value` | Meaning |
112/// | --- | --- | --- | --- | --- |
113/// | `TopLevelVariant` | null | NULL | NULL | SQL NULL at the top-level variant row |
114/// | `ObjectField` | non-null | NULL | NULL | Missing object field |
115/// | `ArrayElement` | non-null | `Variant::Null` | NULL | Explicit null array element |
116#[derive(Debug, Clone, Copy, PartialEq, Eq)]
117pub(crate) enum NullValue {
118    TopLevelVariant,
119    ObjectField,
120    ArrayElement,
121}
122
123impl NullValue {
124    fn append_to(
125        self,
126        nulls: &mut NullBufferBuilder,
127        value_builder: &mut VariantValueArrayBuilder,
128    ) {
129        match self {
130            Self::TopLevelVariant => nulls.append_null(),
131            Self::ObjectField | Self::ArrayElement => nulls.append_non_null(),
132        }
133        match self {
134            Self::TopLevelVariant | Self::ObjectField => value_builder.append_null(),
135            Self::ArrayElement => value_builder.append_value(Variant::Null),
136        }
137    }
138}
139
140pub(crate) fn make_variant_to_shredded_variant_arrow_row_builder<'a>(
141    data_type: &'a DataType,
142    cast_options: &'a CastOptions,
143    capacity: usize,
144    null_value: NullValue,
145    cast_mode: VariantCastMode,
146) -> Result<VariantToShreddedVariantRowBuilder<'a>> {
147    let builder = match data_type {
148        DataType::Struct(fields) => {
149            let typed_value_builder = VariantToShreddedObjectVariantRowBuilder::try_new(
150                fields,
151                cast_options,
152                capacity,
153                null_value,
154                cast_mode,
155            )?;
156            VariantToShreddedVariantRowBuilder::Object(typed_value_builder)
157        }
158        DataType::List(_)
159        | DataType::LargeList(_)
160        | DataType::ListView(_)
161        | DataType::LargeListView(_) => {
162            let typed_value_builder = VariantToShreddedArrayVariantRowBuilder::try_new(
163                data_type,
164                cast_options,
165                capacity,
166                null_value,
167            )?;
168            VariantToShreddedVariantRowBuilder::Array(typed_value_builder)
169        }
170        // Supported shredded primitive types, see Variant shredding spec:
171        // https://github.com/apache/parquet-format/blob/master/VariantShredding.md#shredded-value-types
172        DataType::Boolean
173        | DataType::Int8
174        | DataType::Int16
175        | DataType::Int32
176        | DataType::Int64
177        | DataType::Float32
178        | DataType::Float64
179        | DataType::Decimal32(..)
180        | DataType::Decimal64(..)
181        | DataType::Decimal128(..)
182        | DataType::Date32
183        | DataType::Time64(TimeUnit::Microsecond)
184        | DataType::Timestamp(TimeUnit::Microsecond | TimeUnit::Nanosecond, _)
185        | DataType::Binary
186        | DataType::BinaryView
187        | DataType::LargeBinary
188        | DataType::Utf8
189        | DataType::Utf8View
190        | DataType::LargeUtf8
191        | DataType::FixedSizeBinary(16) // UUID
192        => {
193            let builder =
194                make_primitive_variant_to_arrow_row_builder(data_type, cast_options, capacity, cast_mode)?;
195            let typed_value_builder =
196                VariantToShreddedPrimitiveVariantRowBuilder::new(builder, capacity, null_value);
197            VariantToShreddedVariantRowBuilder::Primitive(typed_value_builder)
198        }
199        DataType::FixedSizeBinary(_) => {
200            return Err(ArrowError::InvalidArgumentError(format!("{data_type} is not a valid variant shredding type. Only FixedSizeBinary(16) for UUID is supported.")))
201        }
202        _ => {
203            return Err(ArrowError::InvalidArgumentError(format!("{data_type} is not a valid variant shredding type")))
204        }
205    };
206    Ok(builder)
207}
208
209pub(crate) enum VariantToShreddedVariantRowBuilder<'a> {
210    Primitive(VariantToShreddedPrimitiveVariantRowBuilder<'a>),
211    Array(VariantToShreddedArrayVariantRowBuilder<'a>),
212    Object(VariantToShreddedObjectVariantRowBuilder<'a>),
213}
214
215impl VariantToShreddedVariantRowBuilder<'_> {
216    pub fn append_null(&mut self) -> Result<()> {
217        use VariantToShreddedVariantRowBuilder::*;
218        match self {
219            Primitive(b) => b.append_null(),
220            Array(b) => b.append_null(),
221            Object(b) => b.append_null(),
222        }
223    }
224
225    pub fn append_value(&mut self, value: Variant<'_, '_>) -> Result<bool> {
226        use VariantToShreddedVariantRowBuilder::*;
227        match self {
228            Primitive(b) => b.append_value(value),
229            Array(b) => b.append_value(value),
230            Object(b) => b.append_value(value),
231        }
232    }
233
234    pub fn finish(self) -> Result<(BinaryViewArray, ArrayRef, Option<NullBuffer>)> {
235        use VariantToShreddedVariantRowBuilder::*;
236        match self {
237            Primitive(b) => b.finish(),
238            Array(b) => b.finish(),
239            Object(b) => b.finish(),
240        }
241    }
242}
243
244/// A shredded primitive field builder.
245pub(crate) struct VariantToShreddedPrimitiveVariantRowBuilder<'a> {
246    value_builder: VariantValueArrayBuilder,
247    typed_value_builder: PrimitiveVariantToArrowRowBuilder<'a>,
248    nulls: NullBufferBuilder,
249    null_value: NullValue,
250}
251
252impl<'a> VariantToShreddedPrimitiveVariantRowBuilder<'a> {
253    pub(crate) fn new(
254        typed_value_builder: PrimitiveVariantToArrowRowBuilder<'a>,
255        capacity: usize,
256        null_value: NullValue,
257    ) -> Self {
258        Self {
259            value_builder: VariantValueArrayBuilder::new(capacity),
260            typed_value_builder,
261            nulls: NullBufferBuilder::new(capacity),
262            null_value,
263        }
264    }
265
266    fn append_null(&mut self) -> Result<()> {
267        self.null_value
268            .append_to(&mut self.nulls, &mut self.value_builder);
269        self.typed_value_builder.append_null()
270    }
271
272    fn append_value(&mut self, value: Variant<'_, '_>) -> Result<bool> {
273        self.nulls.append_non_null();
274        if self.typed_value_builder.append_value(&value)? {
275            self.value_builder.append_null();
276        } else {
277            self.value_builder.append_value(value);
278        }
279        Ok(true)
280    }
281
282    fn finish(mut self) -> Result<(BinaryViewArray, ArrayRef, Option<NullBuffer>)> {
283        Ok((
284            self.value_builder.build()?,
285            self.typed_value_builder.finish()?,
286            self.nulls.finish(),
287        ))
288    }
289}
290
291pub(crate) struct VariantToShreddedArrayVariantRowBuilder<'a> {
292    value_builder: VariantValueArrayBuilder,
293    typed_value_builder: ArrayVariantToArrowRowBuilder<'a>,
294    nulls: NullBufferBuilder,
295    null_value: NullValue,
296}
297
298impl<'a> VariantToShreddedArrayVariantRowBuilder<'a> {
299    fn try_new(
300        data_type: &'a DataType,
301        cast_options: &'a CastOptions,
302        capacity: usize,
303        null_value: NullValue,
304    ) -> Result<Self> {
305        Ok(Self {
306            value_builder: VariantValueArrayBuilder::new(capacity),
307            typed_value_builder: ArrayVariantToArrowRowBuilder::try_new(
308                data_type,
309                cast_options,
310                capacity,
311                true,
312            )?,
313            nulls: NullBufferBuilder::new(capacity),
314            null_value,
315        })
316    }
317
318    fn append_null(&mut self) -> Result<()> {
319        self.null_value
320            .append_to(&mut self.nulls, &mut self.value_builder);
321        self.typed_value_builder.append_null()?;
322        Ok(())
323    }
324
325    fn append_value(&mut self, variant: Variant<'_, '_>) -> Result<bool> {
326        // If the variant is not an array, typed_value must be null.
327        // If the variant is an array, value must be null.
328        match variant {
329            Variant::List(list) => {
330                self.nulls.append_non_null();
331                self.value_builder.append_null();
332
333                self.typed_value_builder
334                    .append_value(&Variant::List(list))?;
335                Ok(true)
336            }
337            other => {
338                self.nulls.append_non_null();
339                self.value_builder.append_value(other);
340                self.typed_value_builder.append_null()?;
341                Ok(false)
342            }
343        }
344    }
345
346    fn finish(mut self) -> Result<(BinaryViewArray, ArrayRef, Option<NullBuffer>)> {
347        Ok((
348            self.value_builder.build()?,
349            self.typed_value_builder.finish()?,
350            self.nulls.finish(),
351        ))
352    }
353}
354
355pub(crate) struct VariantToShreddedObjectVariantRowBuilder<'a> {
356    value_builder: VariantValueArrayBuilder,
357    typed_value_builders: IndexMap<&'a str, VariantToShreddedVariantRowBuilder<'a>>,
358    typed_value_nulls: NullBufferBuilder,
359    nulls: NullBufferBuilder,
360    null_value: NullValue,
361    /// Scratch space marking which of `typed_value_builders` the current row supplied a value for,
362    /// indexed the same way as `typed_value_builders`. Reused across rows.
363    seen: Vec<bool>,
364}
365
366impl<'a> VariantToShreddedObjectVariantRowBuilder<'a> {
367    fn try_new(
368        fields: &'a Fields,
369        cast_options: &'a CastOptions,
370        capacity: usize,
371        null_value: NullValue,
372        cast_mode: VariantCastMode,
373    ) -> Result<Self> {
374        let typed_value_builders = fields.iter().map(|field| {
375            let builder = make_variant_to_shredded_variant_arrow_row_builder(
376                field.data_type(),
377                cast_options,
378                capacity,
379                NullValue::ObjectField,
380                cast_mode,
381            )?;
382            Ok((field.name().as_str(), builder))
383        });
384        let typed_value_builders: IndexMap<_, _> = typed_value_builders.collect::<Result<_>>()?;
385        Ok(Self {
386            value_builder: VariantValueArrayBuilder::new(capacity),
387            seen: vec![false; typed_value_builders.len()],
388            typed_value_builders,
389            typed_value_nulls: NullBufferBuilder::new(capacity),
390            nulls: NullBufferBuilder::new(capacity),
391            null_value,
392        })
393    }
394
395    fn append_null(&mut self) -> Result<()> {
396        self.null_value
397            .append_to(&mut self.nulls, &mut self.value_builder);
398        self.typed_value_nulls.append_null();
399        for (_, typed_value_builder) in &mut self.typed_value_builders {
400            typed_value_builder.append_null()?;
401        }
402        Ok(())
403    }
404
405    fn append_value(&mut self, value: Variant<'_, '_>) -> Result<bool> {
406        let Variant::Object(ref obj) = value else {
407            // Not an object => fall back
408            self.nulls.append_non_null();
409            self.value_builder.append_value(value);
410            self.typed_value_nulls.append_null();
411            for (_, typed_value_builder) in &mut self.typed_value_builders {
412                typed_value_builder.append_null()?;
413            }
414            return Ok(false);
415        };
416
417        // Route the object's fields by name as either shredded or unshredded
418        let Self {
419            value_builder,
420            typed_value_builders,
421            seen,
422            ..
423        } = self;
424        seen.fill(false);
425        let mut builder = value_builder.builder_ext(value.metadata());
426        // Error on a repeated unshredded field instead of overwriting it
427        let mut object_builder = builder.try_new_object()?.with_validate_unique_fields(true);
428        let mut partially_shredded = false;
429        for (field_name, value) in obj.iter() {
430            match typed_value_builders.get_full_mut(field_name) {
431                Some((index, _, typed_value_builder)) => {
432                    // A repeated field would append a second row to this builder
433                    if seen[index] {
434                        return Err(ArrowError::InvalidArgumentError(format!(
435                            "Duplicate field name: {field_name}"
436                        )));
437                    }
438                    typed_value_builder.append_value(value)?;
439                    seen[index] = true;
440                }
441                None => {
442                    object_builder.try_insert_bytes(field_name, value)?;
443                    partially_shredded = true;
444                }
445            }
446        }
447
448        // Handle missing fields
449        for (index, (_, typed_value_builder)) in typed_value_builders.iter_mut().enumerate() {
450            if !seen[index] {
451                typed_value_builder.append_null()?;
452            }
453        }
454
455        // Only emit the value if it captured any unshredded object fields
456        if partially_shredded {
457            object_builder.finish();
458        } else {
459            drop(object_builder);
460            drop(builder);
461            value_builder.append_null();
462        }
463
464        self.typed_value_nulls.append_non_null();
465        self.nulls.append_non_null();
466        Ok(true)
467    }
468
469    fn finish(mut self) -> Result<(BinaryViewArray, ArrayRef, Option<NullBuffer>)> {
470        let mut builder = StructArrayAssembler::new();
471        for (field_name, typed_value_builder) in self.typed_value_builders {
472            let (value, typed_value, nulls) = typed_value_builder.finish()?;
473            let array =
474                ShreddedVariantFieldArray::from_parts(Arc::new(value), Some(typed_value), nulls);
475            builder = builder.with_field(field_name, ArrayRef::from(array), false);
476        }
477        if let Some(nulls) = self.typed_value_nulls.finish() {
478            builder = builder.with_nulls(nulls);
479        }
480        Ok((
481            self.value_builder.build()?,
482            Arc::new(builder.build()?),
483            self.nulls.finish(),
484        ))
485    }
486}
487
488/// Field configuration captured by the builder (data type + nullability).
489#[derive(Clone)]
490pub struct ShreddingField {
491    data_type: DataType,
492    nullable: bool,
493}
494
495impl ShreddingField {
496    fn new(data_type: DataType, nullable: bool) -> Self {
497        Self {
498            data_type,
499            nullable,
500        }
501    }
502
503    fn null() -> Self {
504        Self::new(DataType::Null, true)
505    }
506}
507
508/// Convenience conversion to allow passing either `FieldRef`, `DataType`, or `(DataType, bool)`.
509pub trait IntoShreddingField {
510    fn into_shredding_field(self) -> ShreddingField;
511}
512
513impl IntoShreddingField for FieldRef {
514    fn into_shredding_field(self) -> ShreddingField {
515        ShreddingField::new(self.data_type().clone(), self.is_nullable())
516    }
517}
518
519impl IntoShreddingField for &DataType {
520    fn into_shredding_field(self) -> ShreddingField {
521        ShreddingField::new(self.clone(), true)
522    }
523}
524
525impl IntoShreddingField for DataType {
526    fn into_shredding_field(self) -> ShreddingField {
527        ShreddingField::new(self, true)
528    }
529}
530
531impl IntoShreddingField for (&DataType, bool) {
532    fn into_shredding_field(self) -> ShreddingField {
533        ShreddingField::new(self.0.clone(), self.1)
534    }
535}
536
537impl IntoShreddingField for (DataType, bool) {
538    fn into_shredding_field(self) -> ShreddingField {
539        ShreddingField::new(self.0, self.1)
540    }
541}
542
543/// Builder for constructing a variant shredding schema.
544///
545/// The builder pattern makes it easy to incrementally define which fields
546/// should be shredded and with what types. Fields are nullable by default; pass
547/// a `(data_type, nullable)` pair or a `FieldRef` to control nullability.
548///
549/// `[*]` represents the shared element schema of a list, so `items[*].id` and
550/// `items[*].name` describe fields on the same list element struct. Numeric
551/// indexes refer to concrete list elements and are rejected by this builder.
552///
553/// # Example
554///
555/// ```
556/// use std::sync::Arc;
557/// use arrow::datatypes::{DataType, Field, TimeUnit};
558/// use parquet_variant::{VariantPath, VariantPathElement};
559/// use parquet_variant_compute::ShreddedSchemaBuilder;
560///
561/// fn main() -> Result<(), arrow::error::ArrowError> {
562///     // Define the shredding schema using the builder
563///     let shredding_type = ShreddedSchemaBuilder::default()
564///     // store the "time" field as a separate UTC timestamp
565///     .with_path("time", (&DataType::Timestamp(TimeUnit::Nanosecond, Some("UTC".into())), true))?
566///     // store hostname as non-nullable Utf8
567///     .with_path("hostname", (&DataType::Utf8, false))?
568///     // pass a FieldRef directly
569///     .with_path(
570///         "metadata.trace_id",
571///         Arc::new(Field::new("trace_id", DataType::FixedSizeBinary(16), false)),
572///     )?
573///     // field name with a dot: use VariantPath to avoid splitting
574///     .with_path(
575///         VariantPath::from_iter([VariantPathElement::from("metrics.cpu")]),
576///         &DataType::Float64,
577///     )?
578///     // [*] describes the shared schema for every element of a list
579///     .with_path("items[*].id", &DataType::Int64)?
580///     .build();
581///    Ok(())
582/// }
583/// // The shredding_type can now be passed to shred_variant:
584/// // let shredded = shred_variant(&input, &shredding_type)?;
585/// ```
586#[derive(Default, Clone)]
587pub struct ShreddedSchemaBuilder {
588    root: VariantSchemaNode,
589}
590
591impl ShreddedSchemaBuilder {
592    /// Create a new empty schema builder.
593    pub fn new() -> Self {
594        Self::default()
595    }
596
597    /// Insert a typed path into the schema using dot notation (or any
598    /// [`VariantPath`] convertible).
599    ///
600    /// The path uses dot notation to specify nested fields.
601    /// For example, "a.b.c" will create a nested structure.
602    ///
603    /// # Arguments
604    ///
605    /// * `path` - Anything convertible to [`VariantPath`] (e.g., a `&str`)
606    /// * `field` - Anything convertible via [`IntoShreddingField`] (e.g. `FieldRef`,
607    ///   `&DataType`, or `(&DataType, bool)` to control nullability)
608    ///
609    /// List schema paths must use `[*]`; numeric indexes return an error.
610    pub fn with_path<'a, P, F>(mut self, path: P, field: F) -> Result<Self>
611    where
612        P: TryInto<VariantPath<'a>>,
613        P::Error: std::fmt::Debug,
614        F: IntoShreddingField,
615    {
616        let path: VariantPath<'a> = path
617            .try_into()
618            .map_err(|e| ArrowError::InvalidArgumentError(format!("{e:?}")))?;
619        self.root.insert_path(&path, field.into_shredding_field())?;
620        Ok(self)
621    }
622
623    /// Build the final [`DataType`].
624    pub fn build(self) -> DataType {
625        let shredding_type = self.root.to_shredding_type();
626        match shredding_type {
627            Some(shredding_type) => shredding_type,
628            None => DataType::Null,
629        }
630    }
631}
632
633/// Internal tree node structure for building variant schemas.
634#[derive(Clone)]
635enum VariantSchemaNode {
636    /// A leaf node with a primitive/scalar type (and nullability)
637    Leaf(ShreddingField),
638    /// An inner struct node with nested fields
639    Struct(BTreeMap<String, VariantSchemaNode>),
640    /// An inner list node with a shared element schema
641    List(Box<VariantSchemaNode>),
642}
643
644impl Default for VariantSchemaNode {
645    fn default() -> Self {
646        Self::Leaf(ShreddingField::null())
647    }
648}
649
650impl VariantSchemaNode {
651    /// Insert a path into this node with the given data type.
652    fn insert_path(&mut self, path: &VariantPath<'_>, field: ShreddingField) -> Result<()> {
653        self.insert_path_elements(path, field)
654    }
655
656    fn insert_path_elements(
657        &mut self,
658        segments: &[VariantPathElement<'_>],
659        field: ShreddingField,
660    ) -> Result<()> {
661        let Some((head, tail)) = segments.split_first() else {
662            *self = Self::Leaf(field);
663            return Ok(());
664        };
665
666        match head {
667            VariantPathElement::Field { name } => {
668                // Ensure this node is a Struct node
669                let children = match self {
670                    Self::Struct(children) => children,
671                    Self::Leaf(_) | Self::List(_) => {
672                        *self = Self::Struct(BTreeMap::new());
673                        match self {
674                            Self::Struct(children) => children,
675                            Self::Leaf(_) | Self::List(_) => unreachable!(),
676                        }
677                    }
678                };
679
680                children
681                    .entry(name.to_string())
682                    .or_default()
683                    .insert_path_elements(tail, field)
684            }
685            VariantPathElement::ListElement => {
686                let element = match self {
687                    Self::List(element) => element,
688                    _ => {
689                        *self = Self::List(Box::default());
690                        match self {
691                            Self::List(element) => element,
692                            _ => unreachable!(),
693                        }
694                    }
695                };
696
697                element.insert_path_elements(tail, field)
698            }
699            VariantPathElement::Index { index } => Err(ArrowError::InvalidArgumentError(format!(
700                "List indexes are not supported in schema paths; use [*], got [{index}]"
701            ))),
702        }
703    }
704
705    /// Convert this node to a shredding type.
706    ///
707    /// Returns the [`DataType`] for passing to [`shred_variant`].
708    fn to_shredding_type(&self) -> Option<DataType> {
709        match self {
710            Self::Leaf(field) => Some(field.data_type.clone()),
711            Self::Struct(children) => {
712                let child_fields: Vec<_> = children
713                    .iter()
714                    .filter_map(|(name, child)| child.to_shredding_field(name))
715                    .collect();
716                if child_fields.is_empty() {
717                    None
718                } else {
719                    Some(DataType::Struct(Fields::from(child_fields)))
720                }
721            }
722            Self::List(element) => element.to_shredding_field("item").map(DataType::List),
723        }
724    }
725
726    fn to_shredding_field(&self, name: &str) -> Option<FieldRef> {
727        match self {
728            Self::Leaf(field) => Some(Arc::new(Field::new(
729                name,
730                field.data_type.clone(),
731                field.nullable,
732            ))),
733            Self::Struct(_) | Self::List(_) => self
734                .to_shredding_type()
735                .map(|data_type| Arc::new(Field::new(name, data_type, true))),
736        }
737    }
738}
739
740#[cfg(test)]
741mod tests {
742    use super::*;
743    use crate::VariantArrayBuilder;
744    use crate::variant_array::{all_null_value_column, binary_array_value, variant_from_arrays_at};
745    use arrow::array::{
746        Array, BinaryViewArray, Decimal32Array, Decimal64Array, Decimal128Array,
747        FixedSizeBinaryArray, Float64Array, GenericListArray, GenericListViewArray, Int64Array,
748        LargeBinaryArray, LargeStringArray, ListArray, ListLikeArray, OffsetSizeTrait,
749        PrimitiveArray, StringArray, StructArray,
750    };
751    use arrow::datatypes::{
752        ArrowPrimitiveType, DataType, Field, Fields, Int64Type, TimeUnit, UnionFields, UnionMode,
753    };
754    use arrow_schema::IntervalUnit;
755    use chrono::{DateTime, NaiveDate, NaiveTime};
756    use parquet_variant::{
757        BuilderSpecificState, EMPTY_VARIANT_METADATA_BYTES, ObjectBuilder, ReadOnlyMetadataBuilder,
758        ShortString, Variant, VariantBuilder, VariantDecimal4, VariantDecimal8, VariantDecimal16,
759        VariantPath, VariantPathElement,
760    };
761    use std::sync::Arc;
762    use uuid::Uuid;
763
764    const NULL_VALUES: [NullValue; 3] = [
765        NullValue::TopLevelVariant,
766        NullValue::ObjectField,
767        NullValue::ArrayElement,
768    ];
769
770    #[derive(Clone)]
771    enum VariantValue<'a> {
772        Value(Variant<'a, 'a>),
773        List(Vec<VariantValue<'a>>),
774        Object(Vec<(&'a str, VariantValue<'a>)>),
775        Null,
776    }
777
778    impl<'a, T> From<T> for VariantValue<'a>
779    where
780        T: Into<Variant<'a, 'a>>,
781    {
782        fn from(value: T) -> Self {
783            Self::Value(value.into())
784        }
785    }
786
787    #[derive(Clone)]
788    enum VariantRow<'a> {
789        Value(VariantValue<'a>),
790        List(Vec<VariantValue<'a>>),
791        Object(Vec<(&'a str, VariantValue<'a>)>),
792        Null,
793    }
794
795    fn build_variant_array(rows: Vec<VariantRow<'static>>) -> VariantArray {
796        let mut builder = VariantArrayBuilder::new(rows.len());
797
798        fn append_variant_value<B: VariantBuilderExt>(builder: &mut B, value: VariantValue) {
799            match value {
800                VariantValue::Value(v) => builder.append_value(v),
801                VariantValue::List(values) => {
802                    let mut list = builder.new_list();
803                    for v in values {
804                        append_variant_value(&mut list, v);
805                    }
806                    list.finish();
807                }
808                VariantValue::Object(fields) => {
809                    let mut object = builder.new_object();
810                    for (name, value) in fields {
811                        append_variant_field(&mut object, name, value);
812                    }
813                    object.finish();
814                }
815                VariantValue::Null => builder.append_null(),
816            }
817        }
818
819        fn append_variant_field<'a, S: BuilderSpecificState>(
820            object: &mut ObjectBuilder<'_, S>,
821            name: &'a str,
822            value: VariantValue<'a>,
823        ) {
824            match value {
825                VariantValue::Value(v) => {
826                    object.insert(name, v);
827                }
828                VariantValue::List(values) => {
829                    let mut list = object.new_list(name);
830                    for v in values {
831                        append_variant_value(&mut list, v);
832                    }
833                    list.finish();
834                }
835                VariantValue::Object(fields) => {
836                    let mut nested = object.new_object(name);
837                    for (field_name, v) in fields {
838                        append_variant_field(&mut nested, field_name, v);
839                    }
840                    nested.finish();
841                }
842                VariantValue::Null => {
843                    object.insert(name, Variant::Null);
844                }
845            }
846        }
847
848        rows.into_iter().for_each(|row| match row {
849            VariantRow::Value(value) => append_variant_value(&mut builder, value),
850            VariantRow::List(values) => {
851                let mut list = builder.new_list();
852                for value in values {
853                    append_variant_value(&mut list, value);
854                }
855                list.finish();
856            }
857            VariantRow::Object(fields) => {
858                let mut object = builder.new_object();
859                for (name, value) in fields {
860                    append_variant_field(&mut object, name, value);
861                }
862                object.finish();
863            }
864            VariantRow::Null => builder.append_null(),
865        });
866        builder.build()
867    }
868
869    trait TestListLikeArray: ListLikeArray {
870        type OffsetSize: OffsetSizeTrait;
871        fn value_offsets(&self) -> Option<&[Self::OffsetSize]>;
872        fn value_size(&self, index: usize) -> Self::OffsetSize;
873    }
874
875    impl<O: OffsetSizeTrait> TestListLikeArray for GenericListArray<O> {
876        type OffsetSize = O;
877
878        fn value_offsets(&self) -> Option<&[Self::OffsetSize]> {
879            Some(GenericListArray::value_offsets(self))
880        }
881
882        fn value_size(&self, index: usize) -> Self::OffsetSize {
883            GenericListArray::value_length(self, index)
884        }
885    }
886
887    impl<O: OffsetSizeTrait> TestListLikeArray for GenericListViewArray<O> {
888        type OffsetSize = O;
889
890        fn value_offsets(&self) -> Option<&[Self::OffsetSize]> {
891            Some(GenericListViewArray::value_offsets(self))
892        }
893
894        fn value_size(&self, index: usize) -> Self::OffsetSize {
895            GenericListViewArray::value_size(self, index)
896        }
897    }
898
899    fn downcast_list_like_array<O: OffsetSizeTrait>(
900        array: &VariantArray,
901    ) -> &dyn TestListLikeArray<OffsetSize = O> {
902        let typed_value = array.typed_value_column().unwrap();
903        if let Some(list) = typed_value.as_any().downcast_ref::<GenericListArray<O>>() {
904            list
905        } else if let Some(list_view) = typed_value
906            .as_any()
907            .downcast_ref::<GenericListViewArray<O>>()
908        {
909            list_view
910        } else {
911            panic!(
912                "Expected list-like typed_value with matching offset type, got {}",
913                typed_value.data_type()
914            );
915        }
916    }
917
918    fn assert_list_structure<O: OffsetSizeTrait>(
919        array: &VariantArray,
920        expected_len: usize,
921        expected_offsets: &[O],
922        expected_sizes: &[Option<O>],
923        expected_fallbacks: &[Option<Variant<'static, 'static>>],
924    ) {
925        assert_eq!(array.len(), expected_len);
926
927        let fallback_value = array.value_column();
928        let fallback_metadata = array.metadata_column();
929        let array = downcast_list_like_array::<O>(array);
930
931        assert_eq!(
932            array.value_offsets().unwrap(),
933            expected_offsets,
934            "list offsets mismatch"
935        );
936        assert_eq!(
937            array.len(),
938            expected_sizes.len(),
939            "expected_sizes should match array length"
940        );
941        assert_eq!(
942            array.len(),
943            expected_fallbacks.len(),
944            "expected_fallbacks should match array length"
945        );
946        assert_eq!(
947            array.len(),
948            fallback_value.len(),
949            "fallbacks value field should match array length"
950        );
951
952        // Validate per-row shredding outcomes for the list array
953        for (idx, (expected_size, expected_fallback)) in expected_sizes
954            .iter()
955            .zip(expected_fallbacks.iter())
956            .enumerate()
957        {
958            match expected_size {
959                Some(len) => {
960                    // Successfully shredded: typed list value present, no fallback value
961                    assert!(array.is_valid(idx));
962                    assert_eq!(array.value_size(idx), *len);
963                    assert!(fallback_value.is_null(idx));
964                }
965                None => {
966                    // Unable to shred: typed list value absent, fallback should carry the variant
967                    assert!(array.is_null(idx));
968                    assert_eq!(array.value_size(idx), O::zero());
969                    match expected_fallback {
970                        Some(expected_variant) => {
971                            assert!(fallback_value.is_valid(idx));
972                            let metadata_bytes =
973                                binary_array_value(fallback_metadata.as_ref(), idx).unwrap();
974                            let metadata_bytes =
975                                if fallback_metadata.is_valid(idx) && !metadata_bytes.is_empty() {
976                                    metadata_bytes
977                                } else {
978                                    EMPTY_VARIANT_METADATA_BYTES
979                                };
980                            assert_eq!(
981                                Variant::new(
982                                    metadata_bytes,
983                                    binary_array_value(fallback_value.as_ref(), idx).unwrap()
984                                ),
985                                expected_variant.clone()
986                            );
987                        }
988                        None => {
989                            assert!(fallback_value.is_null(idx));
990                        }
991                    }
992                }
993            }
994        }
995    }
996
997    fn assert_list_structure_and_elements<T: ArrowPrimitiveType, O: OffsetSizeTrait>(
998        array: &VariantArray,
999        expected_len: usize,
1000        expected_offsets: &[O],
1001        expected_sizes: &[Option<O>],
1002        expected_fallbacks: &[Option<Variant<'static, 'static>>],
1003        expected_shredded_elements: (&[Option<T::Native>], &[Option<Variant<'static, 'static>>]),
1004    ) {
1005        assert_list_structure(
1006            array,
1007            expected_len,
1008            expected_offsets,
1009            expected_sizes,
1010            expected_fallbacks,
1011        );
1012        let array = downcast_list_like_array::<O>(array);
1013
1014        // Validate the shredded state of list elements (typed values and fallbacks)
1015        let (expected_values, expected_fallbacks) = expected_shredded_elements;
1016        assert_eq!(
1017            expected_values.len(),
1018            expected_fallbacks.len(),
1019            "expected_values and expected_fallbacks should be aligned"
1020        );
1021
1022        // Validate the shredded primitive values for list elements
1023        let element_array = ShreddedVariantFieldArray::try_new(array.values().as_ref()).unwrap();
1024        let element_values = element_array
1025            .typed_value_column()
1026            .unwrap()
1027            .as_any()
1028            .downcast_ref::<PrimitiveArray<T>>()
1029            .unwrap();
1030        assert_eq!(element_values.len(), expected_values.len());
1031        for (idx, expected_value) in expected_values.iter().enumerate() {
1032            match expected_value {
1033                Some(value) => {
1034                    assert!(element_values.is_valid(idx));
1035                    assert_eq!(element_values.value(idx), *value);
1036                }
1037                None => assert!(element_values.is_null(idx)),
1038            }
1039        }
1040
1041        // Validate fallback variants for list elements that could not be shredded
1042        let element_fallbacks = element_array.value_column();
1043        assert_eq!(element_fallbacks.len(), expected_fallbacks.len());
1044        for (idx, expected_fallback) in expected_fallbacks.iter().enumerate() {
1045            match expected_fallback {
1046                Some(expected_variant) => {
1047                    assert!(element_fallbacks.is_valid(idx));
1048                    assert_eq!(
1049                        Variant::new(
1050                            EMPTY_VARIANT_METADATA_BYTES,
1051                            binary_array_value(element_fallbacks.as_ref(), idx).unwrap()
1052                        ),
1053                        expected_variant.clone()
1054                    );
1055                }
1056                None => assert!(element_fallbacks.is_null(idx)),
1057            }
1058        }
1059    }
1060
1061    fn assert_append_null_mode_value_and_struct_nulls(
1062        mode: NullValue,
1063        value: &BinaryViewArray,
1064        nulls: Option<&arrow::buffer::NullBuffer>,
1065    ) {
1066        if mode == NullValue::TopLevelVariant {
1067            assert!(nulls.is_some_and(|n| n.is_null(0)));
1068        } else {
1069            assert!(nulls.is_none());
1070        }
1071
1072        if mode == NullValue::ArrayElement {
1073            assert!(value.is_valid(0));
1074            assert_eq!(
1075                Variant::new(EMPTY_VARIANT_METADATA_BYTES, value.value(0)),
1076                Variant::Null
1077            );
1078        } else {
1079            assert!(value.is_null(0));
1080        }
1081    }
1082
1083    #[test]
1084    fn test_append_null_mode_semantics_primitive_builder() {
1085        let cast_options = arrow::compute::CastOptions::default();
1086
1087        for mode in NULL_VALUES {
1088            let mut primitive_builder = make_variant_to_shredded_variant_arrow_row_builder(
1089                &DataType::Int64,
1090                &cast_options,
1091                1,
1092                mode,
1093                VariantCastMode::Shred,
1094            )
1095            .unwrap();
1096            primitive_builder.append_null().unwrap();
1097            let (primitive_value, primitive_typed_value, primitive_nulls) =
1098                primitive_builder.finish().unwrap();
1099            let primitive_typed_value = primitive_typed_value
1100                .as_any()
1101                .downcast_ref::<Int64Array>()
1102                .unwrap();
1103
1104            assert!(primitive_typed_value.is_null(0));
1105            assert_append_null_mode_value_and_struct_nulls(
1106                mode,
1107                &primitive_value,
1108                primitive_nulls.as_ref(),
1109            );
1110        }
1111    }
1112
1113    #[test]
1114    fn test_append_null_mode_semantics_array_builder() {
1115        let cast_options = arrow::compute::CastOptions::default();
1116        let list_type = DataType::List(Arc::new(Field::new("item", DataType::Int64, true)));
1117
1118        for mode in NULL_VALUES {
1119            let mut array_builder = make_variant_to_shredded_variant_arrow_row_builder(
1120                &list_type,
1121                &cast_options,
1122                1,
1123                mode,
1124                VariantCastMode::Shred,
1125            )
1126            .unwrap();
1127            array_builder.append_null().unwrap();
1128            let (value, typed_value, nulls) = array_builder.finish().unwrap();
1129
1130            assert_append_null_mode_value_and_struct_nulls(mode, &value, nulls.as_ref());
1131
1132            let typed_value = typed_value.as_any().downcast_ref::<ListArray>().unwrap();
1133            assert_eq!(typed_value.len(), 1);
1134            assert!(typed_value.is_null(0));
1135            assert_eq!(typed_value.values().len(), 0);
1136        }
1137    }
1138
1139    #[test]
1140    fn test_append_null_mode_semantics_object_builder() {
1141        let cast_options = arrow::compute::CastOptions::default();
1142        let object_type = DataType::Struct(Fields::from(vec![
1143            Field::new("id", DataType::Int64, true),
1144            Field::new("name", DataType::Utf8, true),
1145        ]));
1146
1147        for mode in NULL_VALUES {
1148            let mut object_builder = make_variant_to_shredded_variant_arrow_row_builder(
1149                &object_type,
1150                &cast_options,
1151                1,
1152                mode,
1153                VariantCastMode::Shred,
1154            )
1155            .unwrap();
1156            object_builder.append_null().unwrap();
1157            let (value, typed_value, nulls) = object_builder.finish().unwrap();
1158
1159            assert_append_null_mode_value_and_struct_nulls(mode, &value, nulls.as_ref());
1160
1161            let typed_struct = typed_value
1162                .as_any()
1163                .downcast_ref::<arrow::array::StructArray>()
1164                .unwrap();
1165            assert_eq!(typed_struct.len(), 1);
1166            assert!(typed_struct.is_null(0));
1167
1168            for field_name in ["id", "name"] {
1169                let field = ShreddedVariantFieldArray::try_new(
1170                    typed_struct.column_by_name(field_name).unwrap(),
1171                )
1172                .unwrap();
1173                assert!(field.value_column().is_null(0));
1174                assert!(field.typed_value_column().unwrap().is_null(0));
1175            }
1176        }
1177    }
1178
1179    #[test]
1180    fn test_already_shredded_input_error() {
1181        // Create a VariantArray that already has typed_value_field
1182        // First create a valid VariantArray, then extract its parts to construct a shredded one
1183        let temp_array = VariantArray::from_iter([Some("test")]);
1184        let metadata = temp_array.metadata_column().clone();
1185        let value = temp_array.value_column().clone();
1186        let typed_value = Arc::new(Int64Array::from(vec![42])) as ArrayRef;
1187
1188        let shredded_array = VariantArray::from_parts(metadata, value, Some(typed_value), None);
1189
1190        let result = shred_variant(&shredded_array, &DataType::Int64);
1191        assert!(matches!(
1192            result.unwrap_err(),
1193            ArrowError::InvalidArgumentError(_)
1194        ));
1195    }
1196
1197    #[test]
1198    fn test_all_null_input() {
1199        // Create VariantArray whose value column is entirely null
1200        let metadata = Arc::new(BinaryViewArray::from_iter_values([
1201            EMPTY_VARIANT_METADATA_BYTES,
1202        ]));
1203        let all_null_array =
1204            VariantArray::from_parts(metadata, all_null_value_column(1), None, None);
1205        let result = shred_variant(&all_null_array, &DataType::Int64).unwrap();
1206
1207        // The row is valid but has no value, so it shreds to an explicit Variant::Null
1208        // stored in the value column, with a null typed_value
1209        assert!(result.typed_value_column().unwrap().is_null(0));
1210        assert_eq!(result.value(0), Variant::Null);
1211    }
1212
1213    #[test]
1214    fn test_invalid_fixed_size_binary_shredding() {
1215        let mock_uuid_1 = Uuid::new_v4();
1216
1217        let input = VariantArray::from_iter([Some(mock_uuid_1), None]);
1218
1219        // shred_variant only supports FixedSizeBinary(16). Any other length will err.
1220        let err = shred_variant(&input, &DataType::FixedSizeBinary(17)).unwrap_err();
1221
1222        assert_eq!(
1223            err.to_string(),
1224            "Invalid argument error: FixedSizeBinary(17) is not a valid variant shredding type. Only FixedSizeBinary(16) for UUID is supported."
1225        );
1226    }
1227
1228    #[test]
1229    fn test_uuid_shredding() {
1230        let mock_uuid_1 = Uuid::new_v4();
1231        let mock_uuid_2 = Uuid::new_v4();
1232
1233        let input = VariantArray::from_iter([
1234            Some(Variant::from(mock_uuid_1)),
1235            None,
1236            Some(Variant::from(false)),
1237            Some(Variant::from(mock_uuid_2)),
1238        ]);
1239
1240        let variant_array = shred_variant(&input, &DataType::FixedSizeBinary(16)).unwrap();
1241
1242        let typed_value_field = variant_array.inner().field_by_name("typed_value").unwrap();
1243
1244        assert!(typed_value_field.has_valid_extension_type::<arrow_schema::extension::Uuid>());
1245
1246        // probe the downcasted typed_value array to make sure uuids are shredded correctly
1247        let uuids = variant_array
1248            .typed_value_column()
1249            .unwrap()
1250            .as_any()
1251            .downcast_ref::<FixedSizeBinaryArray>()
1252            .unwrap();
1253
1254        assert_eq!(uuids.len(), 4);
1255
1256        assert!(!uuids.is_null(0));
1257
1258        let got_uuid_1: &[u8] = uuids.value(0);
1259        assert_eq!(got_uuid_1, mock_uuid_1.as_bytes());
1260
1261        assert!(uuids.is_null(1));
1262        assert!(uuids.is_null(2));
1263
1264        assert!(!uuids.is_null(3));
1265
1266        let got_uuid_2: &[u8] = uuids.value(3);
1267        assert_eq!(got_uuid_2, mock_uuid_2.as_bytes());
1268    }
1269
1270    #[test]
1271    fn test_uuid_nested_shredding() {
1272        let mock_uuid = Uuid::new_v4();
1273        let input = build_variant_array(vec![VariantRow::Object(vec![(
1274            "id",
1275            VariantValue::from(mock_uuid),
1276        )])]);
1277        let target = ShreddedSchemaBuilder::default()
1278            .with_path("id", DataType::FixedSizeBinary(16))
1279            .unwrap()
1280            .build();
1281
1282        let result = shred_variant(&input, &target).unwrap();
1283
1284        let typed_value = result.typed_value_column().unwrap();
1285        let typed_struct = typed_value.as_any().downcast_ref::<StructArray>().unwrap();
1286        let id =
1287            ShreddedVariantFieldArray::try_new(typed_struct.column_by_name("id").unwrap()).unwrap();
1288
1289        // The extension type lives on the field, not the array, so assert it on the inner struct.
1290        let leaf = id.inner().field_by_name("typed_value").unwrap();
1291
1292        assert_eq!(leaf.data_type(), &DataType::FixedSizeBinary(16));
1293        assert!(leaf.has_valid_extension_type::<arrow_schema::extension::Uuid>());
1294    }
1295
1296    #[test]
1297    fn test_primitive_shredding_comprehensive() {
1298        // Test mixed scenarios in a single array
1299        let input = VariantArray::from_iter(vec![
1300            Some(Variant::from(42i64)),   // successful shred
1301            Some(Variant::from("hello")), // failed shred (string)
1302            Some(Variant::from(100i64)),  // successful shred
1303            None,                         // array-level null
1304            Some(Variant::Null),          // variant null
1305            Some(Variant::from(3i8)),     // successful shred (int8->int64 conversion)
1306        ]);
1307
1308        let result = shred_variant(&input, &DataType::Int64).unwrap();
1309
1310        // Verify structure
1311        let metadata_field = result.metadata_column();
1312        let value_field = result.value_column();
1313        let typed_value_field = result
1314            .typed_value_column()
1315            .unwrap()
1316            .as_any()
1317            .downcast_ref::<Int64Array>()
1318            .unwrap();
1319
1320        // Check specific outcomes for each row
1321        assert_eq!(result.len(), 6);
1322
1323        // Row 0: 42 -> should shred successfully
1324        assert!(!result.is_null(0));
1325        assert!(value_field.is_null(0)); // value should be null when shredded
1326        assert!(!typed_value_field.is_null(0));
1327        assert_eq!(typed_value_field.value(0), 42);
1328
1329        // Row 1: "hello" -> should fail to shred
1330        assert!(!result.is_null(1));
1331        assert!(!value_field.is_null(1)); // value should contain original
1332        assert!(typed_value_field.is_null(1)); // typed_value should be null
1333        assert_eq!(
1334            variant_from_arrays_at(metadata_field, value_field, 1).unwrap(),
1335            Variant::from("hello")
1336        );
1337
1338        // Row 2: 100 -> should shred successfully
1339        assert!(!result.is_null(2));
1340        assert!(value_field.is_null(2));
1341        assert_eq!(typed_value_field.value(2), 100);
1342
1343        // Row 3: array null -> should be null in result
1344        assert!(result.is_null(3));
1345
1346        // Row 4: Variant::Null -> should not shred (it's a null variant, not an integer)
1347        assert!(!result.is_null(4));
1348        assert!(!value_field.is_null(4)); // should contain Variant::Null
1349        assert_eq!(
1350            variant_from_arrays_at(metadata_field, value_field, 4).unwrap(),
1351            Variant::Null
1352        );
1353        assert!(typed_value_field.is_null(4));
1354
1355        // Row 5: 3i8 -> should shred successfully (int8->int64 conversion)
1356        assert!(!result.is_null(5));
1357        assert!(value_field.is_null(5)); // value should be null when shredded
1358        assert!(!typed_value_field.is_null(5));
1359        assert_eq!(typed_value_field.value(5), 3);
1360    }
1361
1362    #[test]
1363    fn test_primitive_different_target_types() {
1364        let input = VariantArray::from_iter(vec![
1365            Variant::from(42i32),
1366            Variant::from(3.15f64),
1367            Variant::from("not_a_number"),
1368        ]);
1369
1370        // Test Int32 target
1371        let result_int32 = shred_variant(&input, &DataType::Int32).unwrap();
1372        let typed_value_int32 = result_int32
1373            .typed_value_column()
1374            .unwrap()
1375            .as_any()
1376            .downcast_ref::<arrow::array::Int32Array>()
1377            .unwrap();
1378        assert_eq!(typed_value_int32.value(0), 42);
1379        assert!(typed_value_int32.is_null(1)); // float doesn't shred to int32
1380        assert!(typed_value_int32.is_null(2)); // string doesn't convert to int32
1381
1382        // Test Float64 target
1383        let result_float64 = shred_variant(&input, &DataType::Float64).unwrap();
1384        let typed_value_float64 = result_float64
1385            .typed_value_column()
1386            .unwrap()
1387            .as_any()
1388            .downcast_ref::<Float64Array>()
1389            .unwrap();
1390        assert!(typed_value_float64.is_null(0)); // int doesn't shred to float
1391        assert_eq!(typed_value_float64.value(1), 3.15);
1392        assert!(typed_value_float64.is_null(2)); // string doesn't convert
1393    }
1394
1395    #[test]
1396    fn test_largeutf8_shredding() {
1397        let input = VariantArray::from_iter(vec![
1398            Some(Variant::from("hello")),
1399            Some(Variant::from(42i64)),
1400            None,
1401            Some(Variant::Null),
1402            Some(Variant::from("world")),
1403        ]);
1404
1405        let result = shred_variant(&input, &DataType::LargeUtf8).unwrap();
1406        let metadata = result.metadata_column();
1407        let value = result.value_column();
1408        let typed_value = result
1409            .typed_value_column()
1410            .unwrap()
1411            .as_any()
1412            .downcast_ref::<LargeStringArray>()
1413            .unwrap();
1414
1415        assert_eq!(result.len(), 5);
1416
1417        // Row 0: string shreds to typed_value
1418        assert!(result.is_valid(0));
1419        assert!(value.is_null(0));
1420        assert_eq!(typed_value.value(0), "hello");
1421
1422        // Row 1: integer falls back to value
1423        assert!(result.is_valid(1));
1424        assert!(value.is_valid(1));
1425        assert!(typed_value.is_null(1));
1426        assert_eq!(
1427            variant_from_arrays_at(metadata, value, 1).unwrap(),
1428            Variant::from(42i64)
1429        );
1430
1431        // Row 2: top-level null
1432        assert!(result.is_null(2));
1433        assert!(value.is_null(2));
1434        assert!(typed_value.is_null(2));
1435
1436        // Row 3: variant null falls back to value
1437        assert!(result.is_valid(3));
1438        assert!(value.is_valid(3));
1439        assert!(typed_value.is_null(3));
1440        assert_eq!(
1441            variant_from_arrays_at(metadata, value, 3).unwrap(),
1442            Variant::Null
1443        );
1444
1445        // Row 4: string shreds to typed_value
1446        assert!(result.is_valid(4));
1447        assert!(value.is_null(4));
1448        assert_eq!(typed_value.value(4), "world");
1449    }
1450
1451    #[test]
1452    fn test_largebinary_shredding() {
1453        let input = VariantArray::from_iter(vec![
1454            Some(Variant::from(&b"\x00\x01\x02"[..])),
1455            Some(Variant::from("not_binary")),
1456            None,
1457            Some(Variant::Null),
1458            Some(Variant::from(&b"\xff\xaa"[..])),
1459        ]);
1460
1461        let result = shred_variant(&input, &DataType::LargeBinary).unwrap();
1462        let metadata = result.metadata_column();
1463        let value = result.value_column();
1464        let typed_value = result
1465            .typed_value_column()
1466            .unwrap()
1467            .as_any()
1468            .downcast_ref::<LargeBinaryArray>()
1469            .unwrap();
1470
1471        assert_eq!(result.len(), 5);
1472
1473        // Row 0: binary shreds to typed_value
1474        assert!(result.is_valid(0));
1475        assert!(value.is_null(0));
1476        assert_eq!(typed_value.value(0), &[0x00, 0x01, 0x02]);
1477
1478        // Row 1: string falls back to value
1479        assert!(result.is_valid(1));
1480        assert!(value.is_valid(1));
1481        assert!(typed_value.is_null(1));
1482        assert_eq!(
1483            variant_from_arrays_at(metadata, value, 1).unwrap(),
1484            Variant::from("not_binary")
1485        );
1486
1487        // Row 2: top-level null
1488        assert!(result.is_null(2));
1489        assert!(value.is_null(2));
1490        assert!(typed_value.is_null(2));
1491
1492        // Row 3: variant null falls back to value
1493        assert!(result.is_valid(3));
1494        assert!(value.is_valid(3));
1495        assert!(typed_value.is_null(3));
1496        assert_eq!(
1497            variant_from_arrays_at(metadata, value, 3).unwrap(),
1498            Variant::Null
1499        );
1500
1501        // Row 4: binary shreds to typed_value
1502        assert!(result.is_valid(4));
1503        assert!(value.is_null(4));
1504        assert_eq!(typed_value.value(4), &[0xff, 0xaa]);
1505    }
1506
1507    #[test]
1508    fn test_invalid_shredded_types_rejected() {
1509        let input = VariantArray::from_iter([Some(42)]);
1510
1511        let invalid_types = vec![
1512            DataType::UInt8,
1513            DataType::Float16,
1514            DataType::Decimal256(38, 10),
1515            DataType::Date64,
1516            DataType::Time32(TimeUnit::Second),
1517            DataType::Time64(TimeUnit::Nanosecond),
1518            DataType::Timestamp(TimeUnit::Millisecond, None),
1519            DataType::FixedSizeList(Arc::new(Field::new("item", DataType::Int64, true)), 2),
1520            DataType::FixedSizeBinary(17),
1521            DataType::Union(
1522                UnionFields::from_fields(vec![
1523                    Field::new("int_field", DataType::Int32, false),
1524                    Field::new("str_field", DataType::Utf8, true),
1525                ]),
1526                UnionMode::Dense,
1527            ),
1528            DataType::Map(
1529                Arc::new(Field::new(
1530                    Field::MAP_ENTRIES_FIELD_DEFAULT_NAME,
1531                    DataType::Struct(Fields::from(vec![
1532                        Field::new(Field::MAP_KEY_FIELD_DEFAULT_NAME, DataType::Utf8, false),
1533                        Field::new(Field::MAP_VALUE_FIELD_DEFAULT_NAME, DataType::Int32, true),
1534                    ])),
1535                    false,
1536                )),
1537                false,
1538            ),
1539            DataType::Dictionary(Box::new(DataType::Int32), Box::new(DataType::Utf8)),
1540            DataType::RunEndEncoded(
1541                Arc::new(Field::new(
1542                    Field::REE_RUN_ENDS_FIELD_DEFAULT_NAME,
1543                    DataType::Int32,
1544                    false,
1545                )),
1546                Arc::new(Field::new(
1547                    Field::REE_VALUES_FIELD_DEFAULT_NAME,
1548                    DataType::Utf8,
1549                    true,
1550                )),
1551            ),
1552        ];
1553
1554        for data_type in invalid_types {
1555            let err = shred_variant(&input, &data_type).unwrap_err();
1556            assert!(
1557                matches!(err, ArrowError::InvalidArgumentError(_)),
1558                "expected InvalidArgumentError for {data_type:?}, got {err:?}"
1559            );
1560        }
1561    }
1562
1563    #[test]
1564    fn test_array_shredding_as_list() {
1565        let input = build_variant_array(vec![
1566            // Row 0: List of ints should shred entirely into typed_value
1567            VariantRow::List(vec![
1568                VariantValue::from(1i64),
1569                VariantValue::from(2i64),
1570                VariantValue::from(3i64),
1571            ]),
1572            // Row 1: Contains incompatible types so values fall back
1573            VariantRow::List(vec![
1574                VariantValue::from(1i64),
1575                VariantValue::from("two"),
1576                VariantValue::from(Variant::Null),
1577            ]),
1578            // Row 2: Not a list -> entire row falls back
1579            VariantRow::Value(VariantValue::from("not a list")),
1580            // Row 3: Array-level null propagates
1581            VariantRow::Null,
1582            // Row 4: Empty list exercises zero-length offsets
1583            VariantRow::List(vec![]),
1584        ]);
1585        let list_schema = DataType::List(Arc::new(Field::new("item", DataType::Int64, true)));
1586        let result = shred_variant(&input, &list_schema).unwrap();
1587        assert_eq!(result.len(), 5);
1588
1589        assert_list_structure_and_elements::<Int64Type, i32>(
1590            &result,
1591            5,
1592            &[0, 3, 6, 6, 6, 6],
1593            &[Some(3), Some(3), None, None, Some(0)],
1594            &[None, None, Some(Variant::from("not a list")), None, None],
1595            (
1596                &[Some(1), Some(2), Some(3), Some(1), None, None],
1597                &[
1598                    None,
1599                    None,
1600                    None,
1601                    None,
1602                    Some(Variant::from("two")),
1603                    Some(Variant::Null),
1604                ],
1605            ),
1606        );
1607    }
1608
1609    #[test]
1610    fn test_array_shredding_as_large_list() {
1611        let input = build_variant_array(vec![
1612            // Row 0: List of ints shreds to typed_value
1613            VariantRow::List(vec![VariantValue::from(1i64), VariantValue::from(2i64)]),
1614            // Row 1: Not a list -> entire row falls back
1615            VariantRow::Value(VariantValue::from("not a list")),
1616            // Row 2: Empty list
1617            VariantRow::List(vec![]),
1618        ]);
1619        let list_schema = DataType::LargeList(Arc::new(Field::new("item", DataType::Int64, true)));
1620        let result = shred_variant(&input, &list_schema).unwrap();
1621        assert_eq!(result.len(), 3);
1622
1623        assert_list_structure_and_elements::<Int64Type, i64>(
1624            &result,
1625            3,
1626            &[0, 2, 2, 2],
1627            &[Some(2), None, Some(0)],
1628            &[None, Some(Variant::from("not a list")), None],
1629            (&[Some(1), Some(2)], &[None, None]),
1630        );
1631    }
1632
1633    #[test]
1634    fn test_array_shredding_as_list_view() {
1635        let input = build_variant_array(vec![
1636            // Row 0: Standard list
1637            VariantRow::List(vec![
1638                VariantValue::from(1i64),
1639                VariantValue::from(2i64),
1640                VariantValue::from(3i64),
1641            ]),
1642            // Row 1: List with incompatible types -> element fallback
1643            VariantRow::List(vec![
1644                VariantValue::from(1i64),
1645                VariantValue::from("two"),
1646                VariantValue::from(Variant::Null),
1647            ]),
1648            // Row 2: Not a list -> top-level fallback
1649            VariantRow::Value(VariantValue::from("not a list")),
1650            // Row 3: Top-level Null
1651            VariantRow::Null,
1652            // Row 4: Empty list
1653            VariantRow::List(vec![]),
1654        ]);
1655        let list_schema = DataType::ListView(Arc::new(Field::new("item", DataType::Int64, true)));
1656        let result = shred_variant(&input, &list_schema).unwrap();
1657        assert_eq!(result.len(), 5);
1658
1659        assert_list_structure_and_elements::<Int64Type, i32>(
1660            &result,
1661            5,
1662            &[0, 3, 6, 6, 6],
1663            &[Some(3), Some(3), None, None, Some(0)],
1664            &[None, None, Some(Variant::from("not a list")), None, None],
1665            (
1666                &[Some(1), Some(2), Some(3), Some(1), None, None],
1667                &[
1668                    None,
1669                    None,
1670                    None,
1671                    None,
1672                    Some(Variant::from("two")),
1673                    Some(Variant::Null),
1674                ],
1675            ),
1676        );
1677    }
1678
1679    #[test]
1680    fn test_array_shredding_as_large_list_view() {
1681        let input = build_variant_array(vec![
1682            // Row 0: List of ints shreds to typed_value
1683            VariantRow::List(vec![VariantValue::from(1i64), VariantValue::from(2i64)]),
1684            // Row 1: Not a list -> entire row falls back
1685            VariantRow::Value(VariantValue::from("fallback")),
1686            // Row 2: Empty list
1687            VariantRow::List(vec![]),
1688        ]);
1689        let list_schema =
1690            DataType::LargeListView(Arc::new(Field::new("item", DataType::Int64, true)));
1691        let result = shred_variant(&input, &list_schema).unwrap();
1692        assert_eq!(result.len(), 3);
1693
1694        assert_list_structure_and_elements::<Int64Type, i64>(
1695            &result,
1696            3,
1697            &[0, 2, 2],
1698            &[Some(2), None, Some(0)],
1699            &[None, Some(Variant::from("fallback")), None],
1700            (&[Some(1), Some(2)], &[None, None]),
1701        );
1702    }
1703
1704    #[test]
1705    fn test_array_shredding_with_array_elements() {
1706        let input = build_variant_array(vec![
1707            // Row 0: [[1, 2], [3, 4], []] - clean nested lists
1708            VariantRow::List(vec![
1709                VariantValue::List(vec![VariantValue::from(1i64), VariantValue::from(2i64)]),
1710                VariantValue::List(vec![VariantValue::from(3i64), VariantValue::from(4i64)]),
1711                VariantValue::List(vec![]),
1712            ]),
1713            // Row 1: [[5, "bad", null], "not a list inner", null] - inner fallbacks
1714            VariantRow::List(vec![
1715                VariantValue::List(vec![
1716                    VariantValue::from(5i64),
1717                    VariantValue::from("bad"),
1718                    VariantValue::from(Variant::Null),
1719                ]),
1720                VariantValue::from("not a list inner"),
1721                VariantValue::Null,
1722            ]),
1723            // Row 2: "not a list" - top-level fallback
1724            VariantRow::Value(VariantValue::from("not a list")),
1725            // Row 3: null row
1726            VariantRow::Null,
1727        ]);
1728        let inner_field = Arc::new(Field::new("item", DataType::Int64, true));
1729        let inner_list_schema = DataType::List(inner_field);
1730        let list_schema = DataType::List(Arc::new(Field::new(
1731            "item",
1732            inner_list_schema.clone(),
1733            true,
1734        )));
1735        let result = shred_variant(&input, &list_schema).unwrap();
1736        assert_eq!(result.len(), 4);
1737
1738        let typed_value = result
1739            .typed_value_column()
1740            .unwrap()
1741            .as_any()
1742            .downcast_ref::<ListArray>()
1743            .unwrap();
1744
1745        assert_list_structure::<i32>(
1746            &result,
1747            4,
1748            &[0, 3, 6, 6, 6],
1749            &[Some(3), Some(3), None, None],
1750            &[None, None, Some(Variant::from("not a list")), None],
1751        );
1752
1753        let outer_elements =
1754            ShreddedVariantFieldArray::try_new(typed_value.values().as_ref()).unwrap();
1755        assert_eq!(outer_elements.len(), 6);
1756        let outer_values = outer_elements
1757            .typed_value_column()
1758            .unwrap()
1759            .as_any()
1760            .downcast_ref::<ListArray>()
1761            .unwrap();
1762        let outer_fallbacks = outer_elements.value_column();
1763
1764        let outer_metadata = Arc::new(BinaryViewArray::from_iter_values(std::iter::repeat_n(
1765            EMPTY_VARIANT_METADATA_BYTES,
1766            outer_elements.len(),
1767        )));
1768        let outer_variant = VariantArray::from_parts(
1769            outer_metadata,
1770            outer_fallbacks.clone(),
1771            Some(Arc::new(outer_values.clone())),
1772            None,
1773        );
1774
1775        assert_list_structure_and_elements::<Int64Type, i32>(
1776            &outer_variant,
1777            outer_elements.len(),
1778            &[0, 2, 4, 4, 7, 7, 7],
1779            &[Some(2), Some(2), Some(0), Some(3), None, None],
1780            &[
1781                None,
1782                None,
1783                None,
1784                None,
1785                Some(Variant::from("not a list inner")),
1786                Some(Variant::Null),
1787            ],
1788            (
1789                &[Some(1), Some(2), Some(3), Some(4), Some(5), None, None],
1790                &[
1791                    None,
1792                    None,
1793                    None,
1794                    None,
1795                    None,
1796                    Some(Variant::from("bad")),
1797                    Some(Variant::Null),
1798                ],
1799            ),
1800        );
1801    }
1802
1803    #[test]
1804    fn test_array_shredding_with_object_elements() {
1805        let input = build_variant_array(vec![
1806            // Row 0: [{"id": 1, "name": "Alice"}, {"id": null}] fully shards
1807            VariantRow::List(vec![
1808                VariantValue::Object(vec![
1809                    ("id", VariantValue::from(1i64)),
1810                    ("name", VariantValue::from("Alice")),
1811                ]),
1812                VariantValue::Object(vec![("id", VariantValue::from(Variant::Null))]),
1813            ]),
1814            // Row 1: "not a list" -> fallback
1815            VariantRow::Value(VariantValue::from("not a list")),
1816            // Row 2: Null row
1817            VariantRow::Null,
1818        ]);
1819
1820        // Target schema is List<Struct<id:int64,name:utf8>>
1821        let list_schema = ShreddedSchemaBuilder::default()
1822            .with_path("[*].id", &DataType::Int64)
1823            .unwrap()
1824            .with_path("[*].name", &DataType::Utf8)
1825            .unwrap()
1826            .build();
1827        let result = shred_variant(&input, &list_schema).unwrap();
1828        assert_eq!(result.len(), 3);
1829
1830        assert_list_structure::<i32>(
1831            &result,
1832            3,
1833            &[0, 2, 2, 2],
1834            &[Some(2), None, None],
1835            &[None, Some(Variant::from("not a list")), None],
1836        );
1837
1838        // Validate nested struct fields for each element
1839        let typed_value = result
1840            .typed_value_column()
1841            .unwrap()
1842            .as_any()
1843            .downcast_ref::<ListArray>()
1844            .unwrap();
1845        let element_array =
1846            ShreddedVariantFieldArray::try_new(typed_value.values().as_ref()).unwrap();
1847        assert_eq!(element_array.len(), 2);
1848        let element_objects = element_array
1849            .typed_value_column()
1850            .unwrap()
1851            .as_any()
1852            .downcast_ref::<arrow::array::StructArray>()
1853            .unwrap();
1854
1855        // Id field [1, Variant::Null]
1856        let id_field =
1857            ShreddedVariantFieldArray::try_new(element_objects.column_by_name("id").unwrap())
1858                .unwrap();
1859        let id_values = id_field.value_column();
1860        let id_typed_values = id_field
1861            .typed_value_column()
1862            .unwrap()
1863            .as_any()
1864            .downcast_ref::<Int64Array>()
1865            .unwrap();
1866        assert!(id_values.is_null(0));
1867        assert_eq!(id_typed_values.value(0), 1);
1868        // null is stored as Variant::Null in values
1869        assert!(id_values.is_valid(1));
1870        assert_eq!(
1871            Variant::new(
1872                EMPTY_VARIANT_METADATA_BYTES,
1873                binary_array_value(id_values.as_ref(), 1).unwrap()
1874            ),
1875            Variant::Null
1876        );
1877        assert!(id_typed_values.is_null(1));
1878
1879        // Name field ["Alice", null]
1880        let name_field =
1881            ShreddedVariantFieldArray::try_new(element_objects.column_by_name("name").unwrap())
1882                .unwrap();
1883        let name_values = name_field.value_column();
1884        let name_typed_values = name_field
1885            .typed_value_column()
1886            .unwrap()
1887            .as_any()
1888            .downcast_ref::<StringArray>()
1889            .unwrap();
1890        assert!(name_values.is_null(0));
1891        assert_eq!(name_typed_values.value(0), "Alice");
1892        // No value provided, both value and typed_value are null
1893        assert!(name_values.is_null(1));
1894        assert!(name_typed_values.is_null(1));
1895    }
1896
1897    #[test]
1898    fn test_object_shredding_comprehensive() -> Result<()> {
1899        let input = build_variant_array(vec![
1900            // Row 0: Fully shredded object
1901            VariantRow::Object(vec![
1902                ("score", VariantValue::from(95.5f64)),
1903                ("age", VariantValue::from(30i64)),
1904            ]),
1905            // Row 1: Partially shredded object (extra email field)
1906            VariantRow::Object(vec![
1907                ("score", VariantValue::from(87.2f64)),
1908                ("age", VariantValue::from(25i64)),
1909                ("email", VariantValue::from("bob@example.com")),
1910            ]),
1911            // Row 2: Missing field (no score)
1912            VariantRow::Object(vec![("age", VariantValue::from(35i64))]),
1913            // Row 3: Type mismatch (score is string, age is string)
1914            VariantRow::Object(vec![
1915                ("score", VariantValue::from("ninety-five")),
1916                ("age", VariantValue::from("thirty")),
1917            ]),
1918            // Row 4: Non-object
1919            VariantRow::Value(VariantValue::from("not an object")),
1920            // Row 5: Empty object
1921            VariantRow::Object(vec![]),
1922            // Row 6: Null
1923            VariantRow::Null,
1924            // Row 7: Object with only "wrong" fields
1925            VariantRow::Object(vec![("foo", VariantValue::from(10))]),
1926            // Row 8: Object with one "right" and one "wrong" field
1927            VariantRow::Object(vec![
1928                ("score", VariantValue::from(66.67f64)),
1929                ("foo", VariantValue::from(10)),
1930            ]),
1931        ]);
1932
1933        // Create target schema: struct<score: float64, age: int64>
1934        // Both types are supported for shredding
1935        let target_schema = ShreddedSchemaBuilder::default()
1936            .with_path("score", &DataType::Float64)?
1937            .with_path("age", &DataType::Int64)?
1938            .build();
1939
1940        let result = shred_variant(&input, &target_schema).unwrap();
1941
1942        // Verify structure
1943        assert!(result.typed_value_column().is_some());
1944        assert_eq!(result.len(), 9);
1945
1946        let metadata = result.metadata_column();
1947        let value = result.value_column();
1948        let typed_value = result
1949            .typed_value_column()
1950            .unwrap()
1951            .as_any()
1952            .downcast_ref::<arrow::array::StructArray>()
1953            .unwrap();
1954
1955        // Extract score and age fields from typed_value struct
1956        let score_field =
1957            ShreddedVariantFieldArray::try_new(typed_value.column_by_name("score").unwrap())
1958                .unwrap();
1959        let age_field =
1960            ShreddedVariantFieldArray::try_new(typed_value.column_by_name("age").unwrap()).unwrap();
1961
1962        let score_value = score_field.value_column();
1963        let score_typed_value = score_field
1964            .typed_value_column()
1965            .unwrap()
1966            .as_any()
1967            .downcast_ref::<Float64Array>()
1968            .unwrap();
1969        let age_value = age_field.value_column();
1970        let age_typed_value = age_field
1971            .typed_value_column()
1972            .unwrap()
1973            .as_any()
1974            .downcast_ref::<Int64Array>()
1975            .unwrap();
1976
1977        // Set up exhaustive checking of all shredded columns and their nulls/values
1978        struct ShreddedValue<'m, 'v, T> {
1979            value: Option<Variant<'m, 'v>>,
1980            typed_value: Option<T>,
1981        }
1982        struct ShreddedStruct<'m, 'v> {
1983            score: ShreddedValue<'m, 'v, f64>,
1984            age: ShreddedValue<'m, 'v, i64>,
1985        }
1986        fn get_value<'m, 'v>(
1987            i: usize,
1988            metadata: &'m dyn Array,
1989            value: &'v dyn Array,
1990        ) -> Variant<'m, 'v> {
1991            variant_from_arrays_at(metadata, value, i).unwrap()
1992        }
1993        let expect = |i, expected_result: Option<ShreddedValue<ShreddedStruct>>| {
1994            match expected_result {
1995                Some(ShreddedValue {
1996                    value: expected_value,
1997                    typed_value: expected_typed_value,
1998                }) => {
1999                    assert!(result.is_valid(i));
2000                    match expected_value {
2001                        Some(expected_value) => {
2002                            assert!(value.is_valid(i));
2003                            assert_eq!(
2004                                expected_value,
2005                                get_value(i, metadata.as_ref(), value.as_ref())
2006                            );
2007                        }
2008                        None => {
2009                            assert!(value.is_null(i));
2010                        }
2011                    }
2012                    match expected_typed_value {
2013                        Some(ShreddedStruct {
2014                            score: expected_score,
2015                            age: expected_age,
2016                        }) => {
2017                            assert!(typed_value.is_valid(i));
2018                            assert!(score_field.is_valid(i)); // non-nullable
2019                            assert!(age_field.is_valid(i)); // non-nullable
2020                            match expected_score.value {
2021                                Some(expected_score_value) => {
2022                                    assert!(score_value.is_valid(i));
2023                                    assert_eq!(
2024                                        expected_score_value,
2025                                        get_value(i, metadata.as_ref(), score_value.as_ref())
2026                                    );
2027                                }
2028                                None => {
2029                                    assert!(score_value.is_null(i));
2030                                }
2031                            }
2032                            match expected_score.typed_value {
2033                                Some(expected_score) => {
2034                                    assert!(score_typed_value.is_valid(i));
2035                                    assert_eq!(expected_score, score_typed_value.value(i));
2036                                }
2037                                None => {
2038                                    assert!(score_typed_value.is_null(i));
2039                                }
2040                            }
2041                            match expected_age.value {
2042                                Some(expected_age_value) => {
2043                                    assert!(age_value.is_valid(i));
2044                                    assert_eq!(
2045                                        expected_age_value,
2046                                        get_value(i, metadata.as_ref(), age_value.as_ref())
2047                                    );
2048                                }
2049                                None => {
2050                                    assert!(age_value.is_null(i));
2051                                }
2052                            }
2053                            match expected_age.typed_value {
2054                                Some(expected_age) => {
2055                                    assert!(age_typed_value.is_valid(i));
2056                                    assert_eq!(expected_age, age_typed_value.value(i));
2057                                }
2058                                None => {
2059                                    assert!(age_typed_value.is_null(i));
2060                                }
2061                            }
2062                        }
2063                        None => {
2064                            assert!(typed_value.is_null(i));
2065                        }
2066                    }
2067                }
2068                None => {
2069                    assert!(result.is_null(i));
2070                }
2071            }
2072        };
2073
2074        // Row 0: Fully shredded - both fields shred successfully
2075        expect(
2076            0,
2077            Some(ShreddedValue {
2078                value: None,
2079                typed_value: Some(ShreddedStruct {
2080                    score: ShreddedValue {
2081                        value: None,
2082                        typed_value: Some(95.5),
2083                    },
2084                    age: ShreddedValue {
2085                        value: None,
2086                        typed_value: Some(30),
2087                    },
2088                }),
2089            }),
2090        );
2091
2092        // Row 1: Partially shredded - value contains extra email field
2093        let mut builder = VariantBuilder::new();
2094        builder
2095            .new_object()
2096            .with_field("email", "bob@example.com")
2097            .finish();
2098        let (m, v) = builder.finish();
2099        let expected_value = Variant::new(&m, &v);
2100
2101        expect(
2102            1,
2103            Some(ShreddedValue {
2104                value: Some(expected_value),
2105                typed_value: Some(ShreddedStruct {
2106                    score: ShreddedValue {
2107                        value: None,
2108                        typed_value: Some(87.2),
2109                    },
2110                    age: ShreddedValue {
2111                        value: None,
2112                        typed_value: Some(25),
2113                    },
2114                }),
2115            }),
2116        );
2117
2118        // Row 2: Fully shredded -- missing score field
2119        expect(
2120            2,
2121            Some(ShreddedValue {
2122                value: None,
2123                typed_value: Some(ShreddedStruct {
2124                    score: ShreddedValue {
2125                        value: None,
2126                        typed_value: None,
2127                    },
2128                    age: ShreddedValue {
2129                        value: None,
2130                        typed_value: Some(35),
2131                    },
2132                }),
2133            }),
2134        );
2135
2136        // Row 3: Type mismatches - both score and age are strings
2137        expect(
2138            3,
2139            Some(ShreddedValue {
2140                value: None,
2141                typed_value: Some(ShreddedStruct {
2142                    score: ShreddedValue {
2143                        value: Some(Variant::from("ninety-five")),
2144                        typed_value: None,
2145                    },
2146                    age: ShreddedValue {
2147                        value: Some(Variant::from("thirty")),
2148                        typed_value: None,
2149                    },
2150                }),
2151            }),
2152        );
2153
2154        // Row 4: Non-object - falls back to value field
2155        expect(
2156            4,
2157            Some(ShreddedValue {
2158                value: Some(Variant::from("not an object")),
2159                typed_value: None,
2160            }),
2161        );
2162
2163        // Row 5: Empty object
2164        expect(
2165            5,
2166            Some(ShreddedValue {
2167                value: None,
2168                typed_value: Some(ShreddedStruct {
2169                    score: ShreddedValue {
2170                        value: None,
2171                        typed_value: None,
2172                    },
2173                    age: ShreddedValue {
2174                        value: None,
2175                        typed_value: None,
2176                    },
2177                }),
2178            }),
2179        );
2180
2181        // Row 6: Null
2182        expect(6, None);
2183
2184        // Helper to correctly create a variant object using a row's existing metadata
2185        let object_with_foo_field = |i| {
2186            use parquet_variant::{ParentState, ValueBuilder, VariantMetadata};
2187            let metadata = VariantMetadata::new(binary_array_value(metadata.as_ref(), i).unwrap());
2188            let mut metadata_builder = ReadOnlyMetadataBuilder::new(&metadata);
2189            let mut value_builder = ValueBuilder::new();
2190            let state = ParentState::variant(&mut value_builder, &mut metadata_builder);
2191            ObjectBuilder::new(state, false)
2192                .with_field("foo", 10)
2193                .finish();
2194            (metadata, value_builder.into_inner())
2195        };
2196
2197        // Row 7: Object with only a "wrong" field
2198        let (m, v) = object_with_foo_field(7);
2199        expect(
2200            7,
2201            Some(ShreddedValue {
2202                value: Some(Variant::new_with_metadata(m, &v)),
2203                typed_value: Some(ShreddedStruct {
2204                    score: ShreddedValue {
2205                        value: None,
2206                        typed_value: None,
2207                    },
2208                    age: ShreddedValue {
2209                        value: None,
2210                        typed_value: None,
2211                    },
2212                }),
2213            }),
2214        );
2215
2216        // Row 8: Object with one "wrong" and one "right" field
2217        let (m, v) = object_with_foo_field(8);
2218        expect(
2219            8,
2220            Some(ShreddedValue {
2221                value: Some(Variant::new_with_metadata(m, &v)),
2222                typed_value: Some(ShreddedStruct {
2223                    score: ShreddedValue {
2224                        value: None,
2225                        typed_value: Some(66.67),
2226                    },
2227                    age: ShreddedValue {
2228                        value: None,
2229                        typed_value: None,
2230                    },
2231                }),
2232            }),
2233        );
2234        Ok(())
2235    }
2236
2237    #[test]
2238    fn test_object_shredding_with_array_field() {
2239        let input = build_variant_array(vec![
2240            // Row 0: Object with well-typed scores list
2241            VariantRow::Object(vec![(
2242                "scores",
2243                VariantValue::List(vec![VariantValue::from(10i64), VariantValue::from(20i64)]),
2244            )]),
2245            // Row 1: Object whose scores list contains incompatible type
2246            VariantRow::Object(vec![(
2247                "scores",
2248                VariantValue::List(vec![
2249                    VariantValue::from("oops"),
2250                    VariantValue::from(Variant::Null),
2251                ]),
2252            )]),
2253            // Row 2: Object missing the scores field entirely
2254            VariantRow::Object(vec![]),
2255            // Row 3: Non-object fallback
2256            VariantRow::Value(VariantValue::from("not an object")),
2257            // Row 4: Top-level Null
2258            VariantRow::Null,
2259        ]);
2260        let list_field = Arc::new(Field::new("item", DataType::Int64, true));
2261        let inner_list_schema = DataType::List(list_field);
2262        let schema = DataType::Struct(Fields::from(vec![Field::new(
2263            "scores",
2264            inner_list_schema.clone(),
2265            true,
2266        )]));
2267
2268        let result = shred_variant(&input, &schema).unwrap();
2269        assert_eq!(result.len(), 5);
2270
2271        // Access base value/typed_value columns
2272        let value_field = result.value_column();
2273        let typed_struct = result
2274            .typed_value_column()
2275            .unwrap()
2276            .as_any()
2277            .downcast_ref::<arrow::array::StructArray>()
2278            .unwrap();
2279
2280        // Validate base value fallbacks for non-object rows
2281        assert!(value_field.is_null(0));
2282        assert!(value_field.is_null(1));
2283        assert!(value_field.is_null(2));
2284        assert!(value_field.is_valid(3));
2285        assert_eq!(
2286            variant_from_arrays_at(result.metadata_column(), value_field, 3).unwrap(),
2287            Variant::from("not an object")
2288        );
2289        assert!(value_field.is_null(4));
2290
2291        // Typed struct should only be null for the fallback row
2292        assert!(typed_struct.is_valid(0));
2293        assert!(typed_struct.is_valid(1));
2294        assert!(typed_struct.is_valid(2));
2295        assert!(typed_struct.is_null(3));
2296        assert!(typed_struct.is_null(4));
2297
2298        // Drill into the scores field on the typed struct
2299        let scores_field =
2300            ShreddedVariantFieldArray::try_new(typed_struct.column_by_name("scores").unwrap())
2301                .unwrap();
2302        assert_list_structure_and_elements::<Int64Type, i32>(
2303            &VariantArray::from_parts(
2304                Arc::new(BinaryViewArray::from_iter_values(std::iter::repeat_n(
2305                    EMPTY_VARIANT_METADATA_BYTES,
2306                    scores_field.len(),
2307                ))),
2308                scores_field.value_column().clone(),
2309                Some(scores_field.typed_value_column().unwrap().clone()),
2310                None,
2311            ),
2312            scores_field.len(),
2313            &[0i32, 2, 4, 4, 4, 4],
2314            &[Some(2), Some(2), None, None, None],
2315            &[None, None, None, None, None],
2316            (
2317                &[Some(10), Some(20), None, None],
2318                &[None, None, Some(Variant::from("oops")), Some(Variant::Null)],
2319            ),
2320        );
2321    }
2322
2323    #[test]
2324    fn test_object_different_schemas() -> Result<()> {
2325        // Create object with multiple fields
2326        let input = build_variant_array(vec![VariantRow::Object(vec![
2327            ("id", VariantValue::from(123i32)),
2328            ("age", VariantValue::from(25i64)),
2329            ("score", VariantValue::from(95.5f64)),
2330        ])]);
2331
2332        // Test with schema containing only id field
2333        let schema1 = ShreddedSchemaBuilder::default()
2334            .with_path("id", &DataType::Int32)?
2335            .build();
2336        let result1 = shred_variant(&input, &schema1).unwrap();
2337        let value_field1 = result1.value_column();
2338        assert!(!value_field1.is_null(0)); // should contain {"age": 25, "score": 95.5}
2339
2340        // Test with schema containing id and age fields
2341        let schema2 = ShreddedSchemaBuilder::default()
2342            .with_path("id", &DataType::Int32)?
2343            .with_path("age", &DataType::Int64)?
2344            .build();
2345        let result2 = shred_variant(&input, &schema2).unwrap();
2346        let value_field2 = result2.value_column();
2347        assert!(!value_field2.is_null(0)); // should contain {"score": 95.5}
2348
2349        // Test with schema containing all fields
2350        let schema3 = ShreddedSchemaBuilder::default()
2351            .with_path("id", &DataType::Int32)?
2352            .with_path("age", &DataType::Int64)?
2353            .with_path("score", &DataType::Float64)?
2354            .build();
2355        let result3 = shred_variant(&input, &schema3).unwrap();
2356        let value_field3 = result3.value_column();
2357        assert!(value_field3.is_null(0)); // fully shredded, no remaining fields
2358
2359        Ok(())
2360    }
2361
2362    #[test]
2363    fn test_object_with_duplicate_field_names_errors() -> Result<()> {
2364        // Unsorted dictionaries ["a"] and ["a", "a"]; only an unsorted dictionary may repeat a name
2365        let metadata_a: &[u8] = &[0x01, 1, 0, 1, b'a'];
2366        let metadata_aa: &[u8] = &[0x01, 2, 0, 1, 2, b'a', b'a'];
2367        // {"a": 0i8, "a": 1i8}, via field ids [0, 0] and [0, 1]
2368        let same_ids: &[u8] = &[0x02, 2, 0, 0, 0, 2, 4, 0x0C, 0, 0x0C, 1];
2369        let distinct_ids: &[u8] = &[0x02, 2, 0, 1, 0, 2, 4, 0x0C, 0, 0x0C, 1];
2370
2371        for (metadata, value) in [(metadata_a, same_ids), (metadata_aa, distinct_ids)] {
2372            // Full validation rejects the duplicate, but shredding only validates shallowly
2373            assert!(Variant::try_new(metadata, value).is_err());
2374            let input = VariantArray::from_parts(
2375                Arc::new(BinaryViewArray::from_iter_values([metadata])),
2376                Arc::new(BinaryViewArray::from_iter_values([value])),
2377                None,
2378                None,
2379            );
2380            // The duplicate is either shredded ("a") or left unshredded ("b")
2381            for path in ["a", "b"] {
2382                let as_type = ShreddedSchemaBuilder::default()
2383                    .with_path(path, &DataType::Int8)?
2384                    .build();
2385                let err = shred_variant(&input, &as_type).unwrap_err();
2386                assert_eq!(
2387                    err.to_string(),
2388                    "Invalid argument error: Duplicate field name: a"
2389                );
2390            }
2391        }
2392        Ok(())
2393    }
2394
2395    #[test]
2396    fn test_uuid_shredding_in_objects() -> Result<()> {
2397        let mock_uuid_1 = Uuid::new_v4();
2398        let mock_uuid_2 = Uuid::new_v4();
2399        let mock_uuid_3 = Uuid::new_v4();
2400
2401        let input = build_variant_array(vec![
2402            // Row 0: Fully shredded object with both UUID fields
2403            VariantRow::Object(vec![
2404                ("id", VariantValue::from(mock_uuid_1)),
2405                ("session_id", VariantValue::from(mock_uuid_2)),
2406            ]),
2407            // Row 1: Partially shredded object - UUID fields plus extra field
2408            VariantRow::Object(vec![
2409                ("id", VariantValue::from(mock_uuid_2)),
2410                ("session_id", VariantValue::from(mock_uuid_3)),
2411                ("name", VariantValue::from("test_user")),
2412            ]),
2413            // Row 2: Missing UUID field (no session_id)
2414            VariantRow::Object(vec![("id", VariantValue::from(mock_uuid_1))]),
2415            // Row 3: Type mismatch - id is UUID but session_id is a string
2416            VariantRow::Object(vec![
2417                ("id", VariantValue::from(mock_uuid_3)),
2418                ("session_id", VariantValue::from("not-a-uuid")),
2419            ]),
2420            // Row 4: Object with non-UUID value in id field
2421            VariantRow::Object(vec![
2422                ("id", VariantValue::from(12345i64)),
2423                ("session_id", VariantValue::from(mock_uuid_1)),
2424            ]),
2425            // Row 5: Null
2426            VariantRow::Null,
2427        ]);
2428
2429        let target_schema = ShreddedSchemaBuilder::default()
2430            .with_path("id", DataType::FixedSizeBinary(16))?
2431            .with_path("session_id", DataType::FixedSizeBinary(16))?
2432            .build();
2433
2434        let result = shred_variant(&input, &target_schema).unwrap();
2435
2436        assert!(result.typed_value_column().is_some());
2437        assert_eq!(result.len(), 6);
2438
2439        let metadata = result.metadata_column();
2440        let value = result.value_column();
2441        let typed_value = result
2442            .typed_value_column()
2443            .unwrap()
2444            .as_any()
2445            .downcast_ref::<arrow::array::StructArray>()
2446            .unwrap();
2447
2448        // Extract id and session_id fields from typed_value struct
2449        let id_field =
2450            ShreddedVariantFieldArray::try_new(typed_value.column_by_name("id").unwrap()).unwrap();
2451        let session_id_field =
2452            ShreddedVariantFieldArray::try_new(typed_value.column_by_name("session_id").unwrap())
2453                .unwrap();
2454
2455        let id_value = id_field.value_column();
2456        let id_typed_value = id_field
2457            .typed_value_column()
2458            .unwrap()
2459            .as_any()
2460            .downcast_ref::<FixedSizeBinaryArray>()
2461            .unwrap();
2462        let session_id_value = session_id_field.value_column();
2463        let session_id_typed_value = session_id_field
2464            .typed_value_column()
2465            .unwrap()
2466            .as_any()
2467            .downcast_ref::<FixedSizeBinaryArray>()
2468            .unwrap();
2469
2470        // Row 0: Fully shredded - both UUID fields shred successfully
2471        assert!(result.is_valid(0));
2472
2473        assert!(value.is_null(0)); // fully shredded, no remaining fields
2474        assert!(id_value.is_null(0));
2475        assert!(session_id_value.is_null(0));
2476
2477        assert!(typed_value.is_valid(0));
2478        assert!(id_typed_value.is_valid(0));
2479        assert!(session_id_typed_value.is_valid(0));
2480
2481        assert_eq!(id_typed_value.value(0), mock_uuid_1.as_bytes());
2482        assert_eq!(session_id_typed_value.value(0), mock_uuid_2.as_bytes());
2483
2484        // Row 1: Partially shredded - value contains extra name field
2485        assert!(result.is_valid(1));
2486
2487        assert!(value.is_valid(1)); // contains unshredded "name" field
2488        assert!(typed_value.is_valid(1));
2489
2490        assert!(id_value.is_null(1));
2491        assert!(id_typed_value.is_valid(1));
2492        assert_eq!(id_typed_value.value(1), mock_uuid_2.as_bytes());
2493
2494        assert!(session_id_value.is_null(1));
2495        assert!(session_id_typed_value.is_valid(1));
2496        assert_eq!(session_id_typed_value.value(1), mock_uuid_3.as_bytes());
2497
2498        // Verify the value field contains the name field
2499        let row_1_variant = variant_from_arrays_at(metadata, value, 1).unwrap();
2500        let Variant::Object(obj) = row_1_variant else {
2501            panic!("Expected object");
2502        };
2503
2504        assert_eq!(obj.get("name"), Some(Variant::from("test_user")));
2505
2506        // Row 2: Missing session_id field
2507        assert!(result.is_valid(2));
2508
2509        assert!(value.is_null(2)); // fully shredded, no extra fields
2510        assert!(typed_value.is_valid(2));
2511
2512        assert!(id_value.is_null(2));
2513        assert!(id_typed_value.is_valid(2));
2514        assert_eq!(id_typed_value.value(2), mock_uuid_1.as_bytes());
2515
2516        assert!(session_id_value.is_null(2));
2517        assert!(session_id_typed_value.is_null(2)); // missing field
2518
2519        // Row 3: Type mismatch - session_id is a string, not UUID
2520        assert!(result.is_valid(3));
2521
2522        assert!(value.is_null(3)); // no extra fields
2523        assert!(typed_value.is_valid(3));
2524
2525        assert!(id_value.is_null(3));
2526        assert!(id_typed_value.is_valid(3));
2527        assert_eq!(id_typed_value.value(3), mock_uuid_3.as_bytes());
2528
2529        assert!(session_id_value.is_valid(3)); // type mismatch, stored in value
2530        assert!(session_id_typed_value.is_null(3));
2531        let session_id_variant = variant_from_arrays_at(metadata, session_id_value, 3).unwrap();
2532        assert_eq!(session_id_variant, Variant::from("not-a-uuid"));
2533
2534        // Row 4: Type mismatch - id is int64, not UUID
2535        assert!(result.is_valid(4));
2536
2537        assert!(value.is_null(4)); // no extra fields
2538        assert!(typed_value.is_valid(4));
2539
2540        assert!(id_value.is_valid(4)); // type mismatch, stored in value
2541        assert!(id_typed_value.is_null(4));
2542        let id_variant = variant_from_arrays_at(metadata, id_value, 4).unwrap();
2543        assert_eq!(id_variant, Variant::from(12345i64));
2544
2545        assert!(session_id_value.is_null(4));
2546        assert!(session_id_typed_value.is_valid(4));
2547        assert_eq!(session_id_typed_value.value(4), mock_uuid_1.as_bytes());
2548
2549        // Row 5: Null
2550        assert!(result.is_null(5));
2551
2552        Ok(())
2553    }
2554
2555    macro_rules! validate_decimal_shredding {
2556        ($shred_type: expr, $array_type: ty, $expected_typed_value: ident $(, $expected_precision: literal, $expected_scale:literal)? $(,)?) => {{
2557            let input = VariantArray::from_iter(vec![
2558                Variant::from(12i8),
2559                Variant::from(234i16),
2560                Variant::from(456i32),
2561                Variant::from(456i64),
2562                Variant::from(VariantDecimal4::try_new(1200, 2).unwrap()),
2563                Variant::from(VariantDecimal8::try_new(1230, 2).unwrap()),
2564                Variant::from(VariantDecimal16::try_new(1234, 2).unwrap()),
2565            ]);
2566
2567            let result = shred_variant(&input, &$shred_type).unwrap();
2568
2569            assert!(result.typed_value_column().is_some());
2570            assert_eq!(result.len(), input.len());
2571
2572            let value = result.value_column();
2573            let typed_value = result
2574                .typed_value_column()
2575                .unwrap()
2576                .as_any()
2577                .downcast_ref::<$array_type>()
2578                .unwrap();
2579
2580            $(assert_eq!(typed_value.precision(), $expected_precision);)?
2581            $(assert_eq!(typed_value.scale(), $expected_scale);)?
2582
2583            for i in 0..$expected_typed_value.len() {
2584                assert_eq!(value.is_valid(i), $expected_typed_value.is_null(i));
2585                assert_eq!(typed_value.is_valid(i), $expected_typed_value.is_valid(i));
2586                assert_eq!(typed_value.value(i), $expected_typed_value.value(i));
2587            }
2588        }};
2589    }
2590
2591    #[test]
2592    fn test_shredding_decimal32_with_same_scale() {
2593        let expected_array = Decimal32Array::from(vec![
2594            Some(1200),
2595            None, // 234 can't convert decimal32(4, 2)
2596            None, // 456 can't convert to decimal32(4, 2)
2597            None, // 456 can't convert to decimal32(4, 2)
2598            Some(1200),
2599            Some(1230),
2600            Some(1234),
2601        ])
2602        .with_precision_and_scale(4, 2)
2603        .unwrap();
2604        validate_decimal_shredding!(
2605            DataType::Decimal32(4, 2),
2606            arrow::array::Decimal32Array,
2607            expected_array,
2608            4,
2609            2,
2610        );
2611    }
2612
2613    #[test]
2614    fn test_shredding_decimal32_with_bigger_scale() {
2615        let expected_array = Decimal32Array::from(vec![
2616            Some(12000),
2617            Some(234000),
2618            Some(456000),
2619            Some(456000),
2620            Some(12000),
2621            Some(12300),
2622            Some(12340),
2623        ])
2624        .with_precision_and_scale(6, 3)
2625        .unwrap();
2626
2627        validate_decimal_shredding!(
2628            DataType::Decimal32(6, 3),
2629            arrow::array::Decimal32Array,
2630            expected_array,
2631            6,
2632            3,
2633        );
2634    }
2635
2636    #[test]
2637    fn test_shredding_decimal32_with_smaller_scale() {
2638        let expected_array = Decimal32Array::from(vec![
2639            Some(12),
2640            Some(234),
2641            Some(456),
2642            Some(456),
2643            Some(12),
2644            None, // VariantDecimal8(1230, 2) can't convert to decimal32(6, 0),
2645            None, // VariantDecimal16(1234, 2) can't convert to decimal32(6, 0),
2646        ])
2647        .with_precision_and_scale(6, 0)
2648        .unwrap();
2649        validate_decimal_shredding!(
2650            DataType::Decimal32(6, 0),
2651            arrow::array::Decimal32Array,
2652            expected_array,
2653            6,
2654            0
2655        );
2656    }
2657
2658    #[test]
2659    fn test_shredding_decimal64_with_same_scale() {
2660        let expected_array_decimal64_same_scale = Decimal64Array::from(vec![
2661            Some(1200),
2662            None, // 234 can't convert decimal64(4, 2)
2663            None, // 456 can't convert to decimal64(4, 2)
2664            None, // 456 can't convert to decimal64(4, 2)
2665            Some(1200),
2666            Some(1230),
2667            Some(1234),
2668        ])
2669        .with_precision_and_scale(4, 2)
2670        .unwrap();
2671        validate_decimal_shredding!(
2672            DataType::Decimal64(4, 2),
2673            arrow::array::Decimal64Array,
2674            expected_array_decimal64_same_scale,
2675            4,
2676            2
2677        );
2678    }
2679
2680    #[test]
2681    fn test_shredding_decimal64_with_big_scale() {
2682        let expected_array = Decimal64Array::from(vec![
2683            Some(12000),
2684            Some(234000),
2685            Some(456000),
2686            Some(456000),
2687            Some(12000),
2688            Some(12300),
2689            Some(12340),
2690        ])
2691        .with_precision_and_scale(6, 3)
2692        .unwrap();
2693        validate_decimal_shredding!(
2694            DataType::Decimal64(6, 3),
2695            arrow::array::Decimal64Array,
2696            expected_array,
2697            6,
2698            3,
2699        );
2700    }
2701
2702    #[test]
2703    fn test_shredding_decimal64_with_smaller_scale() {
2704        let expected_array = Decimal64Array::from(vec![
2705            Some(12),
2706            Some(234),
2707            Some(456),
2708            Some(456),
2709            Some(12),
2710            None, // VariantDecimal8(1234, 2) can't convert to decimal32(6, 0),
2711            None, // VariantDecimal16(1234, 2) can't convert to decimal32(6, 0),
2712        ])
2713        .with_precision_and_scale(6, 0)
2714        .unwrap();
2715        validate_decimal_shredding!(
2716            DataType::Decimal64(6, 0),
2717            arrow::array::Decimal64Array,
2718            expected_array,
2719            6,
2720            0
2721        );
2722    }
2723
2724    #[test]
2725    fn test_shredding_decimal128_with_same_scale() {
2726        let expected_array = Decimal128Array::from(vec![
2727            Some(1200),
2728            None, // 234 can't convert decimal128(4, 2)
2729            None, // 456 can't convert to decimal128(4, 2)
2730            None, // 456 can't convert to decimal128(4, 2)
2731            Some(1200),
2732            Some(1230),
2733            Some(1234),
2734        ])
2735        .with_precision_and_scale(4, 2)
2736        .unwrap();
2737
2738        validate_decimal_shredding!(
2739            DataType::Decimal128(4, 2),
2740            arrow::array::Decimal128Array,
2741            expected_array,
2742            4,
2743            2,
2744        );
2745    }
2746
2747    #[test]
2748    fn test_shredding_decimal128_with_big_scale() {
2749        let expected_array = Decimal128Array::from(vec![
2750            Some(12000),
2751            Some(234000),
2752            Some(456000),
2753            Some(456000),
2754            Some(12000),
2755            Some(12300),
2756            Some(12340),
2757        ])
2758        .with_precision_and_scale(6, 3)
2759        .unwrap();
2760        validate_decimal_shredding!(
2761            DataType::Decimal128(6, 3),
2762            arrow::array::Decimal128Array,
2763            expected_array,
2764            6,
2765            3
2766        );
2767    }
2768
2769    #[test]
2770    fn test_shredding_decimal128_with_smaller_scale() {
2771        let expected_array = Decimal128Array::from(vec![
2772            Some(12),
2773            Some(234),
2774            Some(456),
2775            Some(456),
2776            Some(12),
2777            None, // VariantDecimal8(1234, 2) can't convert to decimal32(6, 0),
2778            None, // VariantDecimal16(1234, 2) can't convert to decimal32(6, 0),
2779        ])
2780        .with_precision_and_scale(6, 0)
2781        .unwrap();
2782        validate_decimal_shredding!(
2783            DataType::Decimal128(6, 0),
2784            arrow::array::Decimal128Array,
2785            expected_array,
2786            6,
2787            0
2788        );
2789    }
2790
2791    #[test]
2792    fn test_shredding_decimal128_to_integer() {
2793        let expected_array = Int64Array::from(vec![
2794            Some(12),
2795            Some(234),
2796            Some(456),
2797            Some(456),
2798            Some(12),
2799            None, // VariantDecimal8(1230, 2) can't convert to integer
2800            None, // VariantDecimal8(1234, 2) can't convert to integer
2801        ]);
2802
2803        validate_decimal_shredding!(DataType::Int64, arrow::array::Int64Array, expected_array);
2804    }
2805
2806    #[test]
2807    fn test_spec_compliance() {
2808        let input = VariantArray::from_iter(vec![Variant::from(42i64), Variant::from("hello")]);
2809
2810        let result = shred_variant(&input, &DataType::Int64).unwrap();
2811
2812        // Test field access by name (not position)
2813        let inner_struct = result.inner();
2814        assert!(inner_struct.column_by_name("metadata").is_some());
2815        assert!(inner_struct.column_by_name("value").is_some());
2816        assert!(inner_struct.column_by_name("typed_value").is_some());
2817
2818        // Test metadata preservation
2819        assert_eq!(
2820            result.metadata_column().len(),
2821            input.metadata_column().len()
2822        );
2823        // The metadata should be the same reference (cheap clone)
2824        // Note: BinaryViewArray doesn't have a .values() method, so we compare the arrays directly
2825        assert_eq!(
2826            result.metadata_column().len(),
2827            input.metadata_column().len()
2828        );
2829
2830        // Test output structure correctness
2831        assert_eq!(result.len(), input.len());
2832        assert!(result.typed_value_column().is_some());
2833
2834        // For primitive shredding, verify that value and typed_value are never both non-null
2835        // (This rule applies to primitives; for objects, both can be non-null for partial shredding)
2836        let value_field = result.value_column();
2837        let typed_value_field = result
2838            .typed_value_column()
2839            .unwrap()
2840            .as_any()
2841            .downcast_ref::<Int64Array>()
2842            .unwrap();
2843
2844        for i in 0..result.len() {
2845            if !result.is_null(i) {
2846                let value_is_null = value_field.is_null(i);
2847                let typed_value_is_null = typed_value_field.is_null(i);
2848                // For primitive shredding, at least one should be null
2849                assert!(
2850                    value_is_null || typed_value_is_null,
2851                    "Row {i}: both value and typed_value are non-null for primitive shredding"
2852                );
2853            }
2854        }
2855    }
2856
2857    #[test]
2858    fn test_variant_schema_builder_simple() -> Result<()> {
2859        let shredding_type = ShreddedSchemaBuilder::default()
2860            .with_path("a", &DataType::Int64)?
2861            .with_path("b", &DataType::Float64)?
2862            .build();
2863
2864        assert_eq!(
2865            shredding_type,
2866            DataType::Struct(Fields::from(vec![
2867                Field::new("a", DataType::Int64, true),
2868                Field::new("b", DataType::Float64, true),
2869            ]))
2870        );
2871
2872        Ok(())
2873    }
2874
2875    #[test]
2876    fn test_variant_schema_builder_nested() -> Result<()> {
2877        let shredding_type = ShreddedSchemaBuilder::default()
2878            .with_path("a", &DataType::Int64)?
2879            .with_path("b.c", &DataType::Utf8)?
2880            .with_path("b.d", &DataType::Float64)?
2881            .build();
2882
2883        assert_eq!(
2884            shredding_type,
2885            DataType::Struct(Fields::from(vec![
2886                Field::new("a", DataType::Int64, true),
2887                Field::new(
2888                    "b",
2889                    DataType::Struct(Fields::from(vec![
2890                        Field::new("c", DataType::Utf8, true),
2891                        Field::new("d", DataType::Float64, true),
2892                    ])),
2893                    true
2894                ),
2895            ]))
2896        );
2897
2898        Ok(())
2899    }
2900
2901    #[test]
2902    fn test_variant_schema_builder_list() -> Result<()> {
2903        let shredding_type = ShreddedSchemaBuilder::default()
2904            .with_path("items[*].id", &DataType::Int64)?
2905            .with_path("items[*].name", &DataType::Utf8)?
2906            .build();
2907
2908        assert_eq!(
2909            shredding_type,
2910            DataType::Struct(Fields::from(vec![Field::new(
2911                "items",
2912                DataType::new_list(
2913                    DataType::Struct(Fields::from(vec![
2914                        Field::new("id", DataType::Int64, true),
2915                        Field::new("name", DataType::Utf8, true),
2916                    ])),
2917                    true,
2918                ),
2919                true,
2920            )]))
2921        );
2922
2923        Ok(())
2924    }
2925
2926    #[test]
2927    fn test_variant_schema_builder_nested_lists() -> Result<()> {
2928        let shredding_type = ShreddedSchemaBuilder::default()
2929            .with_path("matrix[*][*]", (&DataType::Float64, false))?
2930            .build();
2931
2932        assert_eq!(
2933            shredding_type,
2934            DataType::Struct(Fields::from(vec![Field::new(
2935                "matrix",
2936                DataType::new_list(DataType::new_list(DataType::Float64, false), true),
2937                true,
2938            )]))
2939        );
2940
2941        Ok(())
2942    }
2943
2944    #[test]
2945    fn test_variant_schema_builder_rejects_list_indexes() {
2946        for (path, index) in [("items[0].id", 0), ("items[42].name", 42)] {
2947            let error = ShreddedSchemaBuilder::default()
2948                .with_path(path, &DataType::Int64)
2949                .err()
2950                .unwrap();
2951
2952            let ArrowError::InvalidArgumentError(message) = error else {
2953                panic!("expected InvalidArgumentError, got {error:?}");
2954            };
2955            assert_eq!(
2956                message,
2957                format!("List indexes are not supported in schema paths; use [*], got [{index}]")
2958            );
2959        }
2960    }
2961
2962    #[test]
2963    fn test_variant_schema_builder_with_path_variant_path_arg() -> Result<()> {
2964        let path = VariantPath::from_iter([VariantPathElement::from("a.b")]);
2965        let shredding_type = ShreddedSchemaBuilder::default()
2966            .with_path(path, &DataType::Int64)?
2967            .build();
2968
2969        match shredding_type {
2970            DataType::Struct(fields) => {
2971                assert_eq!(fields.len(), 1);
2972                assert_eq!(fields[0].name(), "a.b");
2973                assert_eq!(fields[0].data_type(), &DataType::Int64);
2974            }
2975            _ => panic!("expected struct data type"),
2976        }
2977
2978        Ok(())
2979    }
2980
2981    #[test]
2982    fn test_variant_schema_builder_custom_nullability() -> Result<()> {
2983        let shredding_type = ShreddedSchemaBuilder::default()
2984            .with_path(
2985                "foo",
2986                Arc::new(Field::new("should_be_renamed", DataType::Utf8, false)),
2987            )?
2988            .with_path("bar", (&DataType::Int64, false))?
2989            .build();
2990
2991        let DataType::Struct(fields) = shredding_type else {
2992            panic!("expected struct data type");
2993        };
2994
2995        let foo = fields.iter().find(|f| f.name() == "foo").unwrap();
2996        assert_eq!(foo.data_type(), &DataType::Utf8);
2997        assert!(!foo.is_nullable());
2998
2999        let bar = fields.iter().find(|f| f.name() == "bar").unwrap();
3000        assert_eq!(bar.data_type(), &DataType::Int64);
3001        assert!(!bar.is_nullable());
3002
3003        Ok(())
3004    }
3005
3006    #[test]
3007    fn test_variant_schema_builder_with_shred_variant() -> Result<()> {
3008        let input = build_variant_array(vec![
3009            VariantRow::Object(vec![
3010                ("time", VariantValue::from(1234567890i64)),
3011                ("hostname", VariantValue::from("server1")),
3012                ("extra", VariantValue::from(42)),
3013            ]),
3014            VariantRow::Object(vec![
3015                ("time", VariantValue::from(9876543210i64)),
3016                ("hostname", VariantValue::from("server2")),
3017            ]),
3018            VariantRow::Null,
3019        ]);
3020
3021        let shredding_type = ShreddedSchemaBuilder::default()
3022            .with_path("time", &DataType::Int64)?
3023            .with_path("hostname", &DataType::Utf8)?
3024            .build();
3025
3026        let result = shred_variant(&input, &shredding_type).unwrap();
3027
3028        assert_eq!(
3029            result.data_type(),
3030            &DataType::Struct(Fields::from(vec![
3031                Field::new("metadata", DataType::BinaryView, false),
3032                Field::new("value", DataType::BinaryView, true),
3033                Field::new(
3034                    "typed_value",
3035                    DataType::Struct(Fields::from(vec![
3036                        Field::new(
3037                            "hostname",
3038                            DataType::Struct(Fields::from(vec![
3039                                Field::new("value", DataType::BinaryView, true),
3040                                Field::new("typed_value", DataType::Utf8, true),
3041                            ])),
3042                            false,
3043                        ),
3044                        Field::new(
3045                            "time",
3046                            DataType::Struct(Fields::from(vec![
3047                                Field::new("value", DataType::BinaryView, true),
3048                                Field::new("typed_value", DataType::Int64, true),
3049                            ])),
3050                            false,
3051                        ),
3052                    ])),
3053                    true,
3054                ),
3055            ]))
3056        );
3057
3058        assert_eq!(result.len(), 3);
3059        assert!(result.typed_value_column().is_some());
3060
3061        let typed_value = result
3062            .typed_value_column()
3063            .unwrap()
3064            .as_any()
3065            .downcast_ref::<arrow::array::StructArray>()
3066            .unwrap();
3067
3068        let time_field =
3069            ShreddedVariantFieldArray::try_new(typed_value.column_by_name("time").unwrap())
3070                .unwrap();
3071        let hostname_field =
3072            ShreddedVariantFieldArray::try_new(typed_value.column_by_name("hostname").unwrap())
3073                .unwrap();
3074
3075        let time_typed = time_field
3076            .typed_value_column()
3077            .unwrap()
3078            .as_any()
3079            .downcast_ref::<Int64Array>()
3080            .unwrap();
3081        let hostname_typed = hostname_field
3082            .typed_value_column()
3083            .unwrap()
3084            .as_any()
3085            .downcast_ref::<arrow::array::StringArray>()
3086            .unwrap();
3087
3088        // Row 0
3089        assert!(!result.is_null(0));
3090        assert_eq!(time_typed.value(0), 1234567890);
3091        assert_eq!(hostname_typed.value(0), "server1");
3092
3093        // Row 1
3094        assert!(!result.is_null(1));
3095        assert_eq!(time_typed.value(1), 9876543210);
3096        assert_eq!(hostname_typed.value(1), "server2");
3097
3098        // Row 2
3099        assert!(result.is_null(2));
3100
3101        Ok(())
3102    }
3103
3104    #[test]
3105    fn test_variant_schema_builder_conflicting_path() -> Result<()> {
3106        let shredding_type = ShreddedSchemaBuilder::default()
3107            .with_path("a", &DataType::Int64)?
3108            .with_path("a", &DataType::Float64)?
3109            .build();
3110
3111        assert_eq!(
3112            shredding_type,
3113            DataType::Struct(Fields::from(
3114                vec![Field::new("a", DataType::Float64, true),]
3115            ))
3116        );
3117
3118        Ok(())
3119    }
3120
3121    #[test]
3122    fn test_variant_schema_builder_root_path() -> Result<()> {
3123        let path = VariantPath::new(vec![]);
3124        let shredding_type = ShreddedSchemaBuilder::default()
3125            .with_path(path, &DataType::Int64)?
3126            .build();
3127
3128        assert_eq!(shredding_type, DataType::Int64);
3129
3130        Ok(())
3131    }
3132
3133    #[test]
3134    fn test_variant_schema_builder_empty_path() -> Result<()> {
3135        let shredding_type = ShreddedSchemaBuilder::default()
3136            .with_path("", &DataType::Int64)?
3137            .build();
3138
3139        assert_eq!(shredding_type, DataType::Int64);
3140        Ok(())
3141    }
3142
3143    #[test]
3144    fn test_variant_schema_builder_default() {
3145        let shredding_type = ShreddedSchemaBuilder::default().build();
3146        assert_eq!(shredding_type, DataType::Null);
3147    }
3148
3149    // This test wants to cover that the variant can/can't be shredded to the given data type.
3150    #[test]
3151    fn test_variant_type_shredded_correctly() {
3152        // array contains all variant types
3153        let mut array_builder = VariantArrayBuilder::new(30);
3154        array_builder.append_value(Variant::Null);
3155        array_builder.append_value(Variant::Int8(1));
3156        array_builder.append_value(Variant::Int16(2));
3157        array_builder.append_value(Variant::Int32(3));
3158        array_builder.append_value(Variant::Int64(4));
3159        array_builder.append_value(Variant::Date(NaiveDate::from_epoch_days(12345).unwrap()));
3160        array_builder.append_value(Variant::TimestampMicros(
3161            DateTime::from_timestamp_micros(123456789).unwrap(),
3162        ));
3163        array_builder.append_value(Variant::TimestampNtzMicros(
3164            DateTime::from_timestamp_micros(123456789)
3165                .unwrap()
3166                .naive_utc(),
3167        ));
3168        array_builder.append_value(Variant::TimestampNanos(DateTime::from_timestamp_nanos(
3169            1234567890000,
3170        )));
3171        array_builder.append_value(Variant::TimestampNtzNanos(
3172            DateTime::from_timestamp_nanos(1234567890000).naive_utc(),
3173        ));
3174        array_builder.append_value(VariantDecimal4::try_new(123, 0).unwrap());
3175        array_builder.append_value(VariantDecimal8::try_new(123, 0).unwrap());
3176        array_builder.append_value(VariantDecimal16::try_new(123, 0).unwrap());
3177        array_builder.append_value(Variant::Float(5.0));
3178        array_builder.append_value(Variant::Double(6f64));
3179        array_builder.append_value(Variant::BooleanTrue);
3180        array_builder.append_value(Variant::BooleanFalse);
3181        array_builder.append_value(Variant::Binary(b"helow"));
3182        array_builder.append_value(Variant::String("hello"));
3183        array_builder.append_value(Variant::ShortString(
3184            ShortString::try_from("world").unwrap(),
3185        ));
3186        array_builder.append_value(Variant::Time(
3187            NaiveTime::from_num_seconds_from_midnight_opt(12345, 123).unwrap(),
3188        ));
3189
3190        let array = array_builder.build();
3191
3192        fn can_shred_to(v: &Variant, dt: &DataType) -> bool {
3193            matches!(
3194                (v, dt),
3195                (
3196                    Variant::Int8(_)
3197                        | Variant::Int16(_)
3198                        | Variant::Int32(_)
3199                        | Variant::Int64(_)
3200                        | Variant::Decimal4(_)
3201                        | Variant::Decimal8(_)
3202                        | Variant::Decimal16(_),
3203                    DataType::Int8
3204                        | DataType::Int16
3205                        | DataType::Int32
3206                        | DataType::Int64
3207                        | DataType::Decimal32(_, _)
3208                        | DataType::Decimal64(_, _)
3209                        | DataType::Decimal128(_, _)
3210                ) | (Variant::Date(_), DataType::Date32)
3211                    | (
3212                        Variant::TimestampMicros(_) | Variant::TimestampNanos(_),
3213                        DataType::Timestamp(TimeUnit::Microsecond | TimeUnit::Nanosecond, Some(_))
3214                    )
3215                    | (
3216                        Variant::TimestampNtzMicros(_) | Variant::TimestampNtzNanos(_),
3217                        DataType::Timestamp(TimeUnit::Microsecond | TimeUnit::Nanosecond, None)
3218                    )
3219                    | (Variant::Float(_), DataType::Float32)
3220                    | (Variant::Double(_), DataType::Float64)
3221                    | (
3222                        Variant::BooleanFalse | Variant::BooleanTrue,
3223                        DataType::Boolean
3224                    )
3225                    | (
3226                        Variant::Binary(_),
3227                        DataType::Binary | DataType::BinaryView | DataType::LargeBinary
3228                    )
3229                    | (
3230                        Variant::ShortString(_) | Variant::String(_),
3231                        DataType::Utf8 | DataType::Utf8View | DataType::LargeUtf8
3232                    )
3233                    | (Variant::Time(_), DataType::Time64(_))
3234            )
3235        }
3236
3237        macro_rules! assert_shred_type {
3238            ($shred_type:expr, $expected_value_valid_bits:expr) => {
3239                let shredded_array_result = shred_variant(&array, &$shred_type);
3240                match shredded_array_result {
3241                    Ok(shredded_array) => {
3242                        let value_column = shredded_array.inner().column_by_name("value").unwrap();
3243                        for (idx, valid) in $expected_value_valid_bits.iter().enumerate() {
3244                            match valid {
3245                                true => assert!(
3246                                    value_column.is_null(idx),
3247                                    "{:?} should be shredded to {}",
3248                                    array.value(idx),
3249                                    $shred_type
3250                                ),
3251                                false => assert!(
3252                                    value_column.is_valid(idx),
3253                                    "{:?} should not be shredded to {}",
3254                                    array.value(idx),
3255                                    $shred_type
3256                                ),
3257                            }
3258                        }
3259                    }
3260                    Err(e) => {
3261                        let error_msg = format!("is not a valid variant shredding type");
3262                        assert!(
3263                            e.to_string().contains(error_msg.as_str()),
3264                            "{} => {}",
3265                            $shred_type,
3266                            e.to_string()
3267                        );
3268                    }
3269                }
3270            };
3271        }
3272
3273        let types = [
3274            DataType::Null,
3275            DataType::Boolean,
3276            DataType::Int8,
3277            DataType::Int16,
3278            DataType::Int32,
3279            DataType::Int64,
3280            DataType::UInt8,
3281            DataType::UInt16,
3282            DataType::UInt32,
3283            DataType::UInt64,
3284            DataType::Float32,
3285            DataType::Float64,
3286            DataType::Timestamp(TimeUnit::Second, Some("+00:00".into())),
3287            DataType::Timestamp(TimeUnit::Second, None),
3288            DataType::Timestamp(TimeUnit::Millisecond, Some("-00:00".into())),
3289            DataType::Timestamp(TimeUnit::Millisecond, None),
3290            DataType::Timestamp(TimeUnit::Microsecond, Some("-00:00".into())),
3291            DataType::Timestamp(TimeUnit::Microsecond, None),
3292            DataType::Timestamp(TimeUnit::Nanosecond, Some("+00:00".into())),
3293            DataType::Timestamp(TimeUnit::Nanosecond, None),
3294            DataType::Date32,
3295            DataType::Date64,
3296            DataType::Time32(TimeUnit::Second),
3297            DataType::Time32(TimeUnit::Millisecond),
3298            DataType::Time64(TimeUnit::Microsecond),
3299            DataType::Time64(TimeUnit::Nanosecond),
3300            DataType::Duration(TimeUnit::Nanosecond),
3301            DataType::Interval(IntervalUnit::DayTime),
3302            DataType::Binary,
3303            DataType::FixedSizeBinary(16), // uuid
3304            DataType::FixedSizeBinary(32),
3305            DataType::LargeBinary,
3306            DataType::BinaryView,
3307            DataType::Utf8,
3308            DataType::LargeUtf8,
3309            DataType::Utf8View,
3310            DataType::Decimal32(7, 4),
3311            DataType::Decimal64(7, 4),
3312            DataType::Decimal128(7, 4),
3313            DataType::Decimal256(7, 4),
3314        ];
3315
3316        for data_type in types {
3317            let expected_bits = array
3318                .iter()
3319                .map(|v| can_shred_to(&v.unwrap(), &data_type))
3320                .collect::<Vec<bool>>();
3321            assert_shred_type!(data_type, expected_bits);
3322        }
3323    }
3324}