Skip to main content

parquet_variant_compute/
variant_array.rs

1// SPDX-License-Identifier: Apache-2.0
2// SPDX-FileCopyrightText: See the NOTICE file distributed with this work for additional information regarding copyright ownership
3// SPDX-FileContributor: Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements
4
5//! [`VariantArray`] implementation
6
7use crate::VariantArrayBuilder;
8use crate::type_conversion::{
9    generic_conversion_single_value, generic_conversion_single_value_with_result,
10    primitive_conversion_single_value,
11};
12use arrow::array::{
13    Array, ArrayRef, AsArray, StructArray, StructArrayAssembler, downcast_dictionary_array,
14    downcast_run_array, new_null_array,
15};
16use arrow::buffer::NullBuffer;
17use arrow::compute::cast;
18use arrow::datatypes::{
19    Date32Type, Decimal32Type, Decimal64Type, Decimal128Type, Float16Type, Float32Type,
20    Float64Type, Int8Type, Int16Type, Int32Type, Int64Type, Time64MicrosecondType,
21    TimestampMicrosecondType, TimestampNanosecondType,
22};
23use arrow::error::Result;
24use arrow_schema::extension::Uuid as UuidExtension;
25use arrow_schema::{ArrowError, DataType, Field, FieldRef, TimeUnit};
26use chrono::{DateTime, NaiveTime};
27use parquet_variant::{
28    Uuid, Variant, VariantDecimal4, VariantDecimal8, VariantDecimal16, VariantDecimalType as _,
29    VariantType,
30};
31
32use std::borrow::Cow;
33use std::sync::Arc;
34
35/// Returns the logical bytes at the given index from a binary-like array, resolving dictionary
36/// and run-end encodings. Returns `None` for nulls or if the logical values aren't binary-like.
37pub(crate) fn binary_array_value(array: &dyn Array, index: usize) -> Option<&[u8]> {
38    if array.is_null(index) {
39        return None;
40    }
41    match array.data_type() {
42        DataType::Binary => Some(array.as_binary::<i32>().value(index)),
43        DataType::LargeBinary => Some(array.as_binary::<i64>().value(index)),
44        DataType::BinaryView => Some(array.as_binary_view().value(index)),
45        DataType::Dictionary(..) => downcast_dictionary_array! {
46            array => {
47                let index = array.key(index)?;
48                binary_array_value(array.values().as_ref(), index)
49            },
50            _ => unreachable!(),
51        },
52        DataType::RunEndEncoded(..) => downcast_run_array! {
53            array => {
54                let index = array.get_physical_index(index);
55                binary_array_value(array.values().as_ref(), index)
56            },
57            _ => unreachable!(),
58        },
59        _ => None,
60    }
61}
62
63/// Returns a [`Variant`] from a `metadata` and `value` byte arrays, returns `None`
64/// if one of them is of invalid type.
65pub(crate) fn variant_from_arrays_at<'m, 'v>(
66    metadata: &'m dyn Array,
67    value: &'v dyn Array,
68    index: usize,
69) -> Option<Variant<'m, 'v>> {
70    let metadata = binary_array_value(metadata, index)?;
71    let value = binary_array_value(value, index)?;
72    Some(Variant::new(metadata, value))
73}
74
75/// Returns an all-null binary `value` column of the given length.
76///
77/// The shredding spec requires the `value` column to always be present in the
78/// schema, so producers that have no unshredded values to store must still
79/// emit an all-null column. See <https://github.com/apache/arrow-rs/issues/10306>.
80pub(crate) fn all_null_value_column(len: usize) -> ArrayRef {
81    new_null_array(&DataType::BinaryView, len)
82}
83
84/// Validates that an array has a binary-like data type.
85pub(crate) fn validate_binary_array(array: &dyn Array, field_name: &str) -> Result<()> {
86    match array.data_type() {
87        DataType::Binary | DataType::LargeBinary | DataType::BinaryView => Ok(()),
88        _ => Err(ArrowError::InvalidArgumentError(format!(
89            "VariantArray '{field_name}' field must be Binary, LargeBinary, or BinaryView, got {}",
90            array.data_type()
91        ))),
92    }
93}
94
95/// Validates that a metadata array has binary-like logical values.
96fn validate_metadata_array(array: &dyn Array) -> Result<()> {
97    let is_binary = |data_type: &DataType| {
98        matches!(
99            data_type,
100            DataType::Binary | DataType::LargeBinary | DataType::BinaryView
101        )
102    };
103    match array.data_type() {
104        data_type if is_binary(data_type) => Ok(()),
105        DataType::Dictionary(_, values) if is_binary(values) => Ok(()),
106        DataType::RunEndEncoded(_, values) if is_binary(values.data_type()) => Ok(()),
107        _ => Err(ArrowError::InvalidArgumentError(format!(
108            "VariantArray 'metadata' field must be Binary, LargeBinary, BinaryView, or a Dictionary or RunEndEncoded array of one of those types, got {}",
109            array.data_type()
110        ))),
111    }
112}
113
114/// An array of Parquet [`Variant`] values
115///
116/// A [`VariantArray`] wraps an Arrow [`StructArray`] that stores the underlying
117/// `metadata` and `value` fields, and adds convenience methods to access
118/// the [`Variant`]s.
119///
120/// See [`VariantArrayBuilder`] for constructing `VariantArray` row by row.
121///
122/// See the examples below from converting between `VariantArray` and
123/// `StructArray`.
124///
125/// # Example: collecting optional values
126///
127/// `VariantArray` can collect optional values that convert into [`Variant`].
128/// `None` creates a null row, while `Some(Variant::Null)` creates a valid row
129/// whose value is `Variant::Null`.
130///
131/// ```
132/// # use parquet_variant::Variant;
133/// # use parquet_variant_compute::VariantArray;
134/// let values = [Some(42_i64), None, Some(-1_i64)];
135/// let array: VariantArray = values.into_iter().collect();
136///
137/// assert_eq!(array.value(0), Variant::Int64(42));
138/// assert!(array.is_null(1));
139/// ```
140///
141/// For an all-null array, specify the type when no `Some` value can infer it:
142///
143/// ```
144/// # use parquet_variant::Variant;
145/// # use parquet_variant_compute::VariantArray;
146/// let null_rows = VariantArray::from_iter(vec![None::<Variant>; 3]);
147/// assert!(null_rows.is_null(0));
148/// ```
149///
150/// [`VariantArrayBuilder`]: crate::VariantArrayBuilder
151///
152/// # Documentation
153///
154/// Variant is documented as a canonical Arrow extension type in the
155/// [Parquet Variant] section of the [official list of extension types] on
156/// the Apache Arrow website.
157///
158/// [Parquet Variant]: https://arrow.apache.org/docs/format/CanonicalExtensions.html#parquet-variant
159/// [official list of extension types]: https://arrow.apache.org/docs/format/CanonicalExtensions.html
160///
161/// # Example: Check if a [`StructArray`] has the [`VariantType`] extension
162///
163/// Arrow Arrays only provide [`DataType`], but the extension type information
164/// is stored on a [`Field`]. Thus, you must have access to the [`Schema`] or
165/// [`Field`] to check for the extension type.
166///
167/// [`Schema`]: arrow_schema::Schema
168/// ```
169/// # use arrow::array::StructArray;
170/// # use arrow_schema::{Schema, Field, DataType};
171/// # use parquet_variant_compute::{VariantArray, VariantType};
172/// # fn get_variant_array() -> VariantArray {
173/// #   VariantArray::from_iter([Some("such wow")])
174/// # }
175/// # fn get_schema() -> Schema {
176/// #   Schema::new(vec![
177/// #     Field::new("id", DataType::Int32, false),
178/// #     get_variant_array().field("var"),
179/// #   ])
180/// # }
181/// let schema = get_schema();
182/// assert_eq!(schema.fields().len(), 2);
183/// // first field is not a Variant
184/// assert!(!schema.field(0).has_valid_extension_type::<VariantType>());
185/// // second field is a Variant
186/// assert!(schema.field(1).has_valid_extension_type::<VariantType>());
187/// ```
188///
189/// # Example: Constructing the correct [`Field`] for a [`VariantArray`]
190///
191/// You can construct the correct [`Field`] for a [`VariantArray`] using the
192/// [`VariantArray::field`] method.
193///
194/// ```
195/// # use arrow_schema::{Schema, Field, DataType};
196/// # use parquet_variant_compute::{VariantArray, VariantType};
197/// # fn get_variant_array() -> VariantArray {
198/// #   VariantArray::from_iter([Some("such wow")])
199/// # }
200/// let variant_array = get_variant_array();
201/// // First field is an integer id, second field is a variant
202/// let schema = Schema::new(vec![
203///   Field::new("id", DataType::Int32, false),
204///   // call VariantArray::field to get the correct Field
205///   variant_array.field("var"),
206/// ]);
207/// ```
208///
209/// You can also construct the [`Field`] using [`VariantType`] directly
210///
211/// ```
212/// # use arrow_schema::{Schema, Field, DataType};
213/// # use parquet_variant_compute::{VariantArray, VariantType};
214/// # fn get_variant_array() -> VariantArray {
215/// #   VariantArray::from_iter([Some("such wow")])
216/// # }
217/// # let variant_array = get_variant_array();
218/// // The DataType of a VariantArray varies depending on how it is shredded
219/// let data_type = variant_array.data_type().clone();
220/// // First field is an integer id, second field is a variant
221/// let schema = Schema::new(vec![
222///   Field::new("id", DataType::Int32, false),
223///   Field::new("var", data_type, false)
224///     // Add extension metadata to the field using `VariantType`
225///     .with_extension_type(VariantType),
226/// ]);
227/// ```
228///
229/// # Example: Converting a [`VariantArray`] to a [`StructArray`]
230///
231/// ```
232/// # use arrow::array::StructArray;
233/// # use parquet_variant_compute::VariantArray;
234/// // Create Variant Array
235/// let variant_array = VariantArray::from_iter([Some("such wow")]);
236/// // convert to StructArray
237/// let struct_array: StructArray = variant_array.into();
238/// ```
239///
240/// # Example: Converting a [`StructArray`] to a [`VariantArray`]
241///
242/// ```
243/// # use arrow::array::StructArray;
244/// # use parquet_variant::Variant;
245/// # use parquet_variant_compute::VariantArray;
246/// # fn get_struct_array() -> StructArray {
247/// #   VariantArray::from_iter([Some("such wow")]).into()
248/// # }
249/// let struct_array: StructArray = get_struct_array();
250/// // try and create a VariantArray from it
251/// let variant_array = VariantArray::try_new(&struct_array).unwrap();
252/// assert_eq!(variant_array.value(0), Variant::from("such wow"));
253/// ```
254///
255#[derive(Debug, Clone)]
256pub struct VariantArray {
257    /// Reference to the underlying StructArray
258    inner: StructArray,
259
260    /// The metadata column of this variant (Binary, LargeBinary, or BinaryView), possibly encoded
261    metadata: ArrayRef,
262
263    /// how is this variant array shredded?
264    shredding_state: ShreddingState,
265}
266
267impl VariantArray {
268    /// Creates a new `VariantArray` from a [`StructArray`].
269    ///
270    /// # Arguments
271    /// - `inner` - The underlying [`StructArray`] that contains the variant data.
272    ///
273    /// # Returns
274    /// - A new instance of `VariantArray`.
275    ///
276    /// # Errors:
277    /// - If the `StructArray` does not contain the required fields
278    ///
279    /// # Requirements of the `StructArray`
280    ///
281    /// 1. A required field named `metadata` which is binary, large_binary, or
282    ///    binary_view, optionally dictionary or run-end-encoded
283    ///
284    /// 2. A required field named `value` that is binary, large_binary, or
285    ///    binary_view
286    ///
287    /// 3. An optional field named `typed_value` which can be any primitive type
288    ///    or be a list, large_list, fixed_size_list, list_view or struct. Fixed-size lists are
289    ///    normalized to variable-length lists on read.
290    ///
291    pub fn try_new(inner: &dyn Array) -> Result<Self> {
292        // Canonicalize shredded typed_value fields (e.g. decimal narrowing)
293        let inner = canonicalize_shredded_types(inner)?;
294
295        let Some(inner) = inner.as_struct_opt() else {
296            return Err(ArrowError::InvalidArgumentError(
297                "Invalid VariantArray: requires StructArray as input".to_string(),
298            ));
299        };
300
301        // Note the specification allows for any order so we must search by name
302
303        // Ensure the StructArray has a metadata field with binary-like logical values
304        let Some(metadata_col) = inner.column_by_name("metadata") else {
305            return Err(ArrowError::InvalidArgumentError(
306                "Invalid VariantArray: StructArray must contain a 'metadata' field".to_string(),
307            ));
308        };
309        validate_metadata_array(metadata_col.as_ref())?;
310
311        let shredding_state = ShreddingState::try_from(inner)?;
312
313        // `try_from` synthesizes an all-null `value` when the input omits it. Rebuild the inner
314        // struct so `inner()` and write-back carry the column too.
315        if inner.column_by_name("value").is_none() {
316            return Ok(Self::from_parts(
317                metadata_col.clone(),
318                shredding_state.value_column().clone(),
319                shredding_state.typed_value_column().cloned(),
320                inner.nulls().cloned(),
321            ));
322        }
323
324        // Note these clones are cheap, they just bump the ref count
325        Ok(Self {
326            inner: inner.clone(),
327            metadata: metadata_col.clone(),
328            shredding_state,
329        })
330    }
331
332    /// Note: annotates `value` as nullable, which the spec only permits for shredded
333    /// variants. It is also needed by `variant_get`'s unshredded intermediates, whose
334    /// `value` column can contain unmasked nulls. Unshredded producers should use
335    /// [`Self::from_parts_unshredded`] instead.
336    pub(crate) fn from_parts(
337        metadata: ArrayRef,
338        value: ArrayRef,
339        typed_value: Option<ArrayRef>,
340        nulls: Option<NullBuffer>,
341    ) -> Self {
342        Self::from_parts_with_nullable_value(metadata, value, typed_value, nulls, true)
343    }
344
345    /// Construct an unshredded `VariantArray`, annotating `value` as non-nullable as the
346    /// spec requires when there is no `typed_value` column.
347    ///
348    /// # Panics
349    /// If `value` contains nulls not masked by `nulls`.
350    pub(crate) fn from_parts_unshredded(
351        metadata: ArrayRef,
352        value: ArrayRef,
353        nulls: Option<NullBuffer>,
354    ) -> Self {
355        Self::from_parts_with_nullable_value(metadata, value, None, nulls, false)
356    }
357
358    fn from_parts_with_nullable_value(
359        metadata: ArrayRef,
360        value: ArrayRef,
361        typed_value: Option<ArrayRef>,
362        nulls: Option<NullBuffer>,
363        value_nullable: bool,
364    ) -> Self {
365        let mut builder = StructArrayAssembler::new()
366            .with_field("metadata", metadata.clone(), false)
367            .with_field("value", value.clone(), value_nullable);
368        if let Some(typed_value) = typed_value.clone() {
369            builder = builder.with_field_ref(typed_value_field(&typed_value), typed_value);
370        }
371        if let Some(nulls) = nulls {
372            builder = builder.with_nulls(nulls);
373        }
374
375        Self {
376            inner: builder.build().unwrap(),
377            metadata,
378            shredding_state: ShreddingState::new(value, typed_value),
379        }
380    }
381
382    /// Returns a reference to the underlying [`StructArray`].
383    pub fn inner(&self) -> &StructArray {
384        &self.inner
385    }
386
387    /// Returns the inner [`StructArray`], consuming self
388    pub fn into_inner(self) -> StructArray {
389        self.inner
390    }
391
392    /// Return the shredding state of this `VariantArray`
393    pub fn shredding_state(&self) -> &ShreddingState {
394        &self.shredding_state
395    }
396
397    /// Return the [`Variant`] instance stored at the given row
398    ///
399    /// This is a convenience wrapper that calls [`VariantArray::try_value`] and unwraps the `Result`.
400    /// Use `try_value` if you need to handle conversion errors gracefully.
401    ///
402    /// # Panics
403    /// Panics if
404    /// * the index is out of bounds,
405    /// * the `metadata`/`value` bytes of the row are invalid, which includes reading a null row, or
406    /// * both `value` and `typed_value` are non-null for a non-struct `typed_value`.
407    pub fn value(&self, index: usize) -> Variant<'_, '_> {
408        self.try_value(index)
409            .unwrap_or_else(|err| panic!("VariantArray::value({index}) failed: {err}"))
410    }
411
412    /// Return the [`Variant`] instance stored at the given row
413    ///
414    /// Note: This method does not check for nulls and the value is arbitrary
415    /// (but still well-defined) if [`is_null`](Self::is_null) returns true for the index.
416    ///
417    /// # Errors
418    ///
419    /// Errors if
420    /// - the index is out of bounds
421    /// - the data in `typed_value` cannot be interpreted as a valid `Variant`
422    /// - both `value` and `typed_value` are non-null for a non-struct `typed_value`
423    ///
424    /// # Panics
425    ///
426    /// Panics if the unshredded `metadata`/`value` bytes fail basic validation, since those are
427    /// read with [`Variant::new`]. This includes reading a row that is null.
428    ///
429    /// If this is a shredded variant but has no value at the shredded location, it
430    /// will return [`Variant::Null`].
431    ///
432    ///
433    /// # Performance Note
434    ///
435    /// This is certainly not the most efficient way to access values in a
436    /// `VariantArray`, but it is useful for testing and debugging.
437    ///
438    /// Note: Does not do deep validation of the [`Variant`], so it is up to the
439    /// caller to ensure that the metadata and value were constructed correctly.
440    pub fn try_value(&self, index: usize) -> Result<Variant<'_, '_>> {
441        if self.len() <= index {
442            return Err(ArrowError::InvalidArgumentError(format!(
443                "Index {index} out of bounds for VariantArray of length {}",
444                self.len()
445            )));
446        }
447
448        let value = self.value_column();
449        match self.typed_value_column() {
450            // Always prefer typed_value, if available
451            Some(typed_value) if typed_value.is_valid(index) => {
452                if !matches!(typed_value.data_type(), DataType::Struct(_)) && value.is_valid(index) {
453                    // Only a partially shredded struct is allowed to have values for both columns
454                    return Err(ArrowError::InvalidArgumentError(
455                        "Invalid variant, conflicting value and typed_value".to_owned(),
456                    ));
457                }
458                typed_value_to_variant(typed_value, index)
459            }
460            // Otherwise fall back to value, if available
461            _ if value.is_valid(index) => variant_from_arrays_at(&self.metadata, value, index)
462                .ok_or_else(|| {
463                    ArrowError::InvalidArgumentError(format!(
464                        "metadata and value fields must be binary-like arrays, instead got {} and {}",
465                        self.metadata.data_type(),
466                        value.data_type()
467                    ))
468                }),
469            // It is technically invalid for both value and typed_value to be null,
470            // but the spec specifically requires readers to return Variant::Null in this case.
471            _ => Ok(Variant::Null),
472        }
473    }
474
475    /// Return a reference to the `metadata` column of the [`StructArray`]
476    pub fn metadata_column(&self) -> &ArrayRef {
477        &self.metadata
478    }
479
480    /// Return a reference to the `value` column of the [`StructArray`]
481    pub fn value_column(&self) -> &ArrayRef {
482        self.shredding_state.value_column()
483    }
484
485    /// Return a reference to the `typed_value` column of the [`StructArray`], if present
486    pub fn typed_value_column(&self) -> Option<&ArrayRef> {
487        self.shredding_state.typed_value_column()
488    }
489
490    /// Return a field to represent this VariantArray in a `Schema` with
491    /// a particular name
492    pub fn field(&self, name: impl Into<String>) -> Field {
493        Field::new(
494            name.into(),
495            self.data_type().clone(),
496            self.inner.is_nullable(),
497        )
498        .with_extension_type(VariantType)
499    }
500
501    /// Returns a new DataType representing this VariantArray's inner type
502    pub fn data_type(&self) -> &DataType {
503        self.inner.data_type()
504    }
505
506    pub fn slice(&self, offset: usize, length: usize) -> Self {
507        let inner = self.inner.slice(offset, length);
508        let metadata = self.metadata.slice(offset, length);
509        let shredding_state = self.shredding_state.slice(offset, length);
510        Self {
511            inner,
512            metadata,
513            shredding_state,
514        }
515    }
516
517    pub fn len(&self) -> usize {
518        self.inner.len()
519    }
520
521    pub fn is_empty(&self) -> bool {
522        self.inner.is_empty()
523    }
524
525    pub fn nulls(&self) -> Option<&NullBuffer> {
526        self.inner.nulls()
527    }
528
529    /// Is the element at index null?
530    pub fn is_null(&self, index: usize) -> bool {
531        self.nulls().is_some_and(|n| n.is_null(index))
532    }
533
534    /// Is the element at index valid (not null)?
535    pub fn is_valid(&self, index: usize) -> bool {
536        !self.is_null(index)
537    }
538
539    /// Returns an iterator over the values in this array
540    pub fn iter(&self) -> VariantArrayIter<'_> {
541        VariantArrayIter::new(self)
542    }
543}
544
545impl<'a> IntoIterator for &'a VariantArray {
546    type Item = Option<Variant<'a, 'a>>;
547    type IntoIter = VariantArrayIter<'a>;
548
549    fn into_iter(self) -> Self::IntoIter {
550        VariantArrayIter::new(self)
551    }
552}
553
554impl PartialEq for VariantArray {
555    fn eq(&self, other: &Self) -> bool {
556        self.inner == other.inner
557    }
558}
559
560impl From<VariantArray> for StructArray {
561    fn from(variant_array: VariantArray) -> Self {
562        variant_array.into_inner()
563    }
564}
565
566impl From<VariantArray> for ArrayRef {
567    fn from(variant_array: VariantArray) -> Self {
568        Arc::new(variant_array.into_inner())
569    }
570}
571
572impl<'m, 'v, V: Into<Variant<'m, 'v>>> FromIterator<Option<V>> for VariantArray {
573    fn from_iter<T: IntoIterator<Item = Option<V>>>(iter: T) -> Self {
574        let iter = iter.into_iter().map(|value| value.map(Into::into));
575
576        let mut b = VariantArrayBuilder::new(iter.size_hint().0);
577        b.extend(iter);
578        b.build()
579    }
580}
581
582impl<'m, 'v> FromIterator<Variant<'m, 'v>> for VariantArray {
583    fn from_iter<T: IntoIterator<Item = Variant<'m, 'v>>>(iter: T) -> Self {
584        Self::from_iter(iter.into_iter().map(Some))
585    }
586}
587
588/// An iterator over [`VariantArray`]
589///
590/// This iterator returns `Option<Option<Variant<'a, 'a>>>` where:
591/// - `None` indicates the end of iteration
592/// - `Some(None)` indicates a null value at this position
593/// - `Some(Some(variant))` indicates a valid variant value
594///
595/// # Example
596///
597/// ```
598/// # use parquet_variant::Variant;
599/// # use parquet_variant_compute::VariantArrayBuilder;
600/// let mut builder = VariantArrayBuilder::new(10);
601/// builder.append_variant(Variant::from(42));
602/// builder.append_null();
603/// builder.append_variant(Variant::from("hello"));
604/// let array = builder.build();
605///
606/// let values = array.iter().collect::<Vec<_>>();
607/// assert_eq!(values.len(), 3);
608/// assert_eq!(values[0], Some(Variant::from(42)));
609/// assert_eq!(values[1], None);
610/// assert_eq!(values[2], Some(Variant::from("hello")));
611/// ```
612#[derive(Debug)]
613pub struct VariantArrayIter<'a> {
614    array: &'a VariantArray,
615    head_i: usize,
616    tail_i: usize,
617}
618
619impl<'a> VariantArrayIter<'a> {
620    /// Creates a new iterator over the given [`VariantArray`]
621    pub fn new(array: &'a VariantArray) -> Self {
622        Self {
623            array,
624            head_i: 0,
625            tail_i: array.len(),
626        }
627    }
628
629    fn value_opt(&self, i: usize) -> Option<Variant<'a, 'a>> {
630        self.array.is_valid(i).then(|| self.array.value(i))
631    }
632}
633
634impl<'a> Iterator for VariantArrayIter<'a> {
635    type Item = Option<Variant<'a, 'a>>;
636
637    #[inline]
638    fn next(&mut self) -> Option<Self::Item> {
639        if self.head_i == self.tail_i {
640            return None;
641        }
642
643        let out = self.value_opt(self.head_i);
644
645        self.head_i += 1;
646
647        Some(out)
648    }
649
650    fn size_hint(&self) -> (usize, Option<usize>) {
651        let remainder = self.tail_i - self.head_i;
652
653        (remainder, Some(remainder))
654    }
655}
656
657impl DoubleEndedIterator for VariantArrayIter<'_> {
658    fn next_back(&mut self) -> Option<Self::Item> {
659        if self.head_i == self.tail_i {
660            return None;
661        }
662
663        self.tail_i -= 1;
664
665        Some(self.value_opt(self.tail_i))
666    }
667}
668
669impl ExactSizeIterator for VariantArrayIter<'_> {}
670
671/// One shredded field of a partially or perfectly shredded variant. For example, suppose the
672/// shredding schema for variant `v` treats it as an object with a single field `a`, where `a` is
673/// itself a struct with the single field `b` of type INT. Then the physical layout of the column
674/// is:
675///
676/// ```text
677/// v: VARIANT {
678///     metadata: BINARY,
679///     value: BINARY,
680///     typed_value: STRUCT {
681///         a: SHREDDED_VARIANT_FIELD {
682///             value: BINARY,
683///             typed_value: STRUCT {
684///                 a: SHREDDED_VARIANT_FIELD {
685///                     value: BINARY,
686///                     typed_value: INT,
687///                 },
688///             },
689///         },
690///     },
691/// }
692/// ```
693///
694/// In the above, each row of `v.value` is either a variant value (shredding failed, `v` was not an
695/// object at all) or a variant object (partial shredding, `v` was an object but included unexpected
696/// fields other than `a`), or is NULL (perfect shredding, `v` was an object containing only the
697/// single expected field `a`).
698///
699/// A similar story unfolds for each `v.typed_value.a.value` -- a variant value if shredding failed
700/// (`v:a` was not an object at all), or a variant object (`v:a` was an object with unexpected
701/// additional fields), or NULL (`v:a` was an object containing only the single expected field `b`).
702///
703/// Finally, `v.typed_value.a.typed_value.b.value` is either NULL (`v:a.b` was an integer) or else a
704/// variant value (which could be `Variant::Null`).
705#[derive(Debug)]
706pub struct ShreddedVariantFieldArray {
707    /// Reference to the underlying StructArray
708    inner: StructArray,
709    shredding_state: ShreddingState,
710}
711
712impl ShreddedVariantFieldArray {
713    /// Creates a new `ShreddedVariantFieldArray` from a [`StructArray`].
714    ///
715    /// # Arguments
716    /// - `inner` - The underlying [`StructArray`] that contains the variant data.
717    ///
718    /// # Returns
719    /// - A new instance of `ShreddedVariantFieldArray`.
720    ///
721    /// # Errors:
722    /// - If the `StructArray` does not contain the required fields
723    ///
724    /// # Requirements of the `StructArray`
725    ///
726    /// 1. A required field named `value` that is binary, large_binary, or
727    ///    binary_view
728    ///
729    /// 2. An optional field named `typed_value` which can be any primitive type
730    ///    or be a list, large_list, list_view or struct
731    ///
732    pub fn try_new(inner: &dyn Array) -> Result<Self> {
733        let Some(inner_struct) = inner.as_struct_opt() else {
734            return Err(ArrowError::InvalidArgumentError(
735                "Invalid ShreddedVariantFieldArray: requires StructArray as input".to_string(),
736            ));
737        };
738
739        let shredding_state = ShreddingState::try_from(inner_struct)?;
740
741        // `try_from` synthesizes an all-null `value` when the input omits it. Rebuild the inner
742        // struct so `inner()` and write-back carry the column too (see `VariantArray::try_new`).
743        if inner_struct.column_by_name("value").is_none() {
744            return Ok(Self::from_parts(
745                shredding_state.value_column().clone(),
746                shredding_state.typed_value_column().cloned(),
747                inner_struct.nulls().cloned(),
748            ));
749        }
750
751        // Note this clone is cheap, it just bumps the ref count
752        Ok(Self {
753            inner: inner_struct.clone(),
754            shredding_state,
755        })
756    }
757
758    /// Return the shredding state of this `VariantArray`
759    pub fn shredding_state(&self) -> &ShreddingState {
760        &self.shredding_state
761    }
762
763    /// Return a reference to the `value` column of the [`StructArray`]
764    pub fn value_column(&self) -> &ArrayRef {
765        self.shredding_state.value_column()
766    }
767
768    /// Return a reference to the `typed_value` column of the [`StructArray`], if present
769    pub fn typed_value_column(&self) -> Option<&ArrayRef> {
770        self.shredding_state.typed_value_column()
771    }
772
773    /// Returns a reference to the underlying [`StructArray`].
774    pub fn inner(&self) -> &StructArray {
775        &self.inner
776    }
777
778    pub(crate) fn from_parts(
779        value: ArrayRef,
780        typed_value: Option<ArrayRef>,
781        nulls: Option<NullBuffer>,
782    ) -> Self {
783        let mut builder = StructArrayAssembler::new().with_field("value", value.clone(), true);
784        if let Some(typed_value) = typed_value.clone() {
785            builder = builder.with_field_ref(typed_value_field(&typed_value), typed_value);
786        }
787        if let Some(nulls) = nulls {
788            builder = builder.with_nulls(nulls);
789        }
790
791        Self {
792            inner: builder.build().unwrap(),
793            shredding_state: ShreddingState::new(value, typed_value),
794        }
795    }
796
797    /// Returns the inner [`StructArray`], consuming self
798    pub fn into_inner(self) -> StructArray {
799        self.inner
800    }
801
802    pub fn data_type(&self) -> &DataType {
803        self.inner.data_type()
804    }
805
806    pub fn len(&self) -> usize {
807        self.inner.len()
808    }
809
810    pub fn is_empty(&self) -> bool {
811        self.inner.is_empty()
812    }
813
814    pub fn offset(&self) -> usize {
815        self.inner.offset()
816    }
817
818    pub fn nulls(&self) -> Option<&NullBuffer> {
819        // According to the shredding spec, ShreddedVariantFieldArray should be
820        // physically non-nullable - SQL NULL is inferred by both value and
821        // typed_value being physically NULL
822        None
823    }
824    /// Is the element at index null?
825    pub fn is_null(&self, index: usize) -> bool {
826        self.nulls().is_some_and(|n| n.is_null(index))
827    }
828
829    /// Is the element at index valid (not null)?
830    pub fn is_valid(&self, index: usize) -> bool {
831        !self.is_null(index)
832    }
833}
834
835impl From<ShreddedVariantFieldArray> for ArrayRef {
836    fn from(array: ShreddedVariantFieldArray) -> Self {
837        Arc::new(array.into_inner())
838    }
839}
840
841impl From<ShreddedVariantFieldArray> for StructArray {
842    fn from(array: ShreddedVariantFieldArray) -> Self {
843        array.into_inner()
844    }
845}
846
847/// Represents the shredding state of a [`VariantArray`]
848///
849/// [`VariantArray`]s can be shredded according to the [Parquet Variant
850/// Shredding Spec]. Shredding means that the actual value is stored in a typed
851/// `typed_field` instead of the generic `value` field.
852///
853/// The `value` column is always present (the spec requires writers to emit
854/// it); `typed_value` is optional. Values in the two columns must be
855/// interpreted according to the following table (see [Parquet Variant
856/// Shredding Spec] for more details):
857///
858/// | value    | typed_value  | Meaning |
859/// |----------|--------------|---------|
860/// | NULL     | NULL         | The value is missing; only valid for shredded object fields |
861/// | non-NULL | NULL         | The value is present and may be any type, including [`Variant::Null`] |
862/// | NULL     | non-NULL     | The value is present and is the shredded type |
863/// | non-NULL | non-NULL     | The value is present and is a partially shredded object |
864///
865///
866/// Applying the above rules to entire columns, we obtain the following:
867///
868/// | value  | typed_value  | Meaning |
869/// |--------|-------------|---------|
870/// | exists | --          | **Unshredded**: If present, the value may be any type, including [`Variant::Null`]
871/// | exists | exists      | **Shredded**: perfectly if `value` is all-null, otherwise imperfectly |
872///
873/// Note the spec requires the `value` column to always be present in the
874/// schema; structs without one are rejected
875/// (see <https://github.com/apache/arrow-rs/issues/10306>).
876///
877/// NOTE: Partial shredding is a row-wise situation that can arise under imperfect shredding (a
878/// column-wise situation): When both columns exist (imperfect shredding) and the typed_value column
879/// is a struct, then both columns can be non-NULL for the same row if value is a variant object
880/// (partial shredding).
881///
882/// [Parquet Variant Shredding Spec]: https://github.com/apache/parquet-format/blob/master/VariantShredding.md#value-shredding
883#[derive(Debug, Clone)]
884pub struct ShreddingState {
885    value: ArrayRef,
886    typed_value: Option<ArrayRef>,
887}
888
889impl ShreddingState {
890    /// Create a new `ShreddingState` from the given `value` and `typed_value` fields
891    ///
892    /// Note you can create a `ShreddingState` from a &[`StructArray`] using
893    /// `ShreddingState::try_from(&struct_array)`, for example:
894    ///
895    /// ```no_run
896    /// # use arrow::array::StructArray;
897    /// # use parquet_variant_compute::ShreddingState;
898    /// # fn get_struct_array() -> StructArray {
899    /// #   unimplemented!()
900    /// # }
901    /// let struct_array: StructArray = get_struct_array();
902    /// let shredding_state = ShreddingState::try_from(&struct_array).unwrap();
903    /// ```
904    pub fn new(value: ArrayRef, typed_value: Option<ArrayRef>) -> Self {
905        Self { value, typed_value }
906    }
907
908    /// Return a reference to the `value` column
909    pub fn value_column(&self) -> &ArrayRef {
910        &self.value
911    }
912
913    /// Return a reference to the `typed_value` column, if present
914    pub fn typed_value_column(&self) -> Option<&ArrayRef> {
915        self.typed_value.as_ref()
916    }
917
918    /// Slice all the underlying arrays
919    pub fn slice(&self, offset: usize, length: usize) -> Self {
920        Self {
921            value: self.value.slice(offset, length),
922            typed_value: self.typed_value.as_ref().map(|tv| tv.slice(offset, length)),
923        }
924    }
925}
926
927impl TryFrom<&StructArray> for ShreddingState {
928    type Error = ArrowError;
929
930    fn try_from(inner_struct: &StructArray) -> Result<Self> {
931        let typed_value = inner_struct.column_by_name("typed_value").cloned();
932        let value = match inner_struct.column_by_name("value") {
933            Some(value) => {
934                validate_binary_array(value.as_ref(), "value")?;
935                value.clone()
936            }
937            // Lenient read: a shredded group may omit the spec-required `value`. Synthesize an
938            // all-null one so the model always has a `value`.
939            None if typed_value.is_some() => all_null_value_column(inner_struct.len()),
940            None => {
941                return Err(ArrowError::InvalidArgumentError(
942                    "Invalid VariantArray: StructArray must contain a 'value' field".to_string(),
943                ));
944            }
945        };
946        Ok(ShreddingState::new(value, typed_value))
947    }
948}
949
950/// Build the `typed_value` [`FieldRef`] for a shredded column.
951///
952/// The Variant spec maps `FixedSizeBinary(16)` exclusively to UUID, so any
953/// shredded column of that type must carry the canonical [`UuidExtension`]
954/// extension metadata on its field.
955fn typed_value_field(array: &ArrayRef) -> FieldRef {
956    let mut field = Field::new("typed_value", array.data_type().clone(), true);
957    if matches!(array.data_type(), DataType::FixedSizeBinary(16)) {
958        field = field.with_extension_type(UuidExtension);
959    }
960    Arc::new(field)
961}
962
963/// returns the non-null element at index as a Variant
964fn typed_value_to_variant(typed_value: &ArrayRef, index: usize) -> Result<Variant<'_, '_>> {
965    let data_type = typed_value.data_type();
966    match data_type {
967        DataType::Null => Ok(Variant::Null),
968        DataType::Boolean => {
969            let boolean_array = typed_value.as_boolean();
970            let value = boolean_array.value(index);
971            Ok(Variant::from(value))
972        }
973        // 16-byte FixedSizeBinary always corresponds to a UUID; all other sizes are illegal.
974        DataType::FixedSizeBinary(16) => {
975            let array = typed_value.as_fixed_size_binary();
976            let value = array.value(index);
977            Ok(Uuid::from_slice(value).unwrap().into()) // unwrap is safe: slice is always 16 bytes
978        }
979        DataType::Binary => {
980            let array = typed_value.as_binary::<i32>();
981            let value = array.value(index);
982            Ok(Variant::from(value))
983        }
984        DataType::LargeBinary => {
985            let array = typed_value.as_binary::<i64>();
986            let value = array.value(index);
987            Ok(Variant::from(value))
988        }
989        DataType::BinaryView => {
990            let array = typed_value.as_binary_view();
991            let value = array.value(index);
992            Ok(Variant::from(value))
993        }
994        DataType::Utf8 => {
995            let array = typed_value.as_string::<i32>();
996            let value = array.value(index);
997            Ok(Variant::from(value))
998        }
999        DataType::LargeUtf8 => {
1000            let array = typed_value.as_string::<i64>();
1001            let value = array.value(index);
1002            Ok(Variant::from(value))
1003        }
1004        DataType::Utf8View => {
1005            let array = typed_value.as_string_view();
1006            let value = array.value(index);
1007            Ok(Variant::from(value))
1008        }
1009        DataType::Int8 => {
1010            primitive_conversion_single_value!(Int8Type, typed_value, index)
1011        }
1012        DataType::Int16 => {
1013            primitive_conversion_single_value!(Int16Type, typed_value, index)
1014        }
1015        DataType::Int32 => {
1016            primitive_conversion_single_value!(Int32Type, typed_value, index)
1017        }
1018        DataType::Int64 => {
1019            primitive_conversion_single_value!(Int64Type, typed_value, index)
1020        }
1021        DataType::Float16 => {
1022            primitive_conversion_single_value!(Float16Type, typed_value, index)
1023        }
1024        DataType::Float32 => {
1025            primitive_conversion_single_value!(Float32Type, typed_value, index)
1026        }
1027        DataType::Float64 => {
1028            primitive_conversion_single_value!(Float64Type, typed_value, index)
1029        }
1030        DataType::Decimal32(_, s) => {
1031            generic_conversion_single_value_with_result!(
1032                Decimal32Type,
1033                as_primitive,
1034                |v| VariantDecimal4::try_new(v, *s as u8),
1035                typed_value,
1036                index
1037            )
1038        }
1039        DataType::Decimal64(_, s) => {
1040            generic_conversion_single_value_with_result!(
1041                Decimal64Type,
1042                as_primitive,
1043                |v| VariantDecimal8::try_new(v, *s as u8),
1044                typed_value,
1045                index
1046            )
1047        }
1048        DataType::Decimal128(_, s) => {
1049            generic_conversion_single_value_with_result!(
1050                Decimal128Type,
1051                as_primitive,
1052                |v| VariantDecimal16::try_new(v, *s as u8),
1053                typed_value,
1054                index
1055            )
1056        }
1057        DataType::Date32 => {
1058            generic_conversion_single_value!(
1059                Date32Type,
1060                as_primitive,
1061                |v| Date32Type::to_naive_date_opt(v).unwrap(),
1062                typed_value,
1063                index
1064            )
1065        }
1066        DataType::Time64(TimeUnit::Microsecond) => {
1067            generic_conversion_single_value_with_result!(
1068                Time64MicrosecondType,
1069                as_primitive,
1070                |v| NaiveTime::from_num_seconds_from_midnight_opt(
1071                    (v / 1_000_000) as u32,
1072                    (v % 1_000_000) as u32 * 1000
1073                )
1074                .ok_or_else(|| format!("Invalid microsecond from midnight: {v}")),
1075                typed_value,
1076                index
1077            )
1078        }
1079        DataType::Timestamp(TimeUnit::Microsecond, Some(_)) => {
1080            generic_conversion_single_value!(
1081                TimestampMicrosecondType,
1082                as_primitive,
1083                |v| DateTime::from_timestamp_micros(v).unwrap(),
1084                typed_value,
1085                index
1086            )
1087        }
1088        DataType::Timestamp(TimeUnit::Microsecond, None) => {
1089            generic_conversion_single_value!(
1090                TimestampMicrosecondType,
1091                as_primitive,
1092                |v| DateTime::from_timestamp_micros(v).unwrap().naive_utc(),
1093                typed_value,
1094                index
1095            )
1096        }
1097        DataType::Timestamp(TimeUnit::Nanosecond, Some(_)) => {
1098            generic_conversion_single_value!(
1099                TimestampNanosecondType,
1100                as_primitive,
1101                DateTime::from_timestamp_nanos,
1102                typed_value,
1103                index
1104            )
1105        }
1106        DataType::Timestamp(TimeUnit::Nanosecond, None) => {
1107            generic_conversion_single_value!(
1108                TimestampNanosecondType,
1109                as_primitive,
1110                |v| DateTime::from_timestamp_nanos(v).naive_utc(),
1111                typed_value,
1112                index
1113            )
1114        }
1115        // todo other types here (note this is very similar to cast_to_variant.rs)
1116        // so it would be great to figure out how to share this code
1117        //
1118        // Composite shredded values may require combining `value` and
1119        // `typed_value` and allocating new encoded bytes. `try_value` returns
1120        // borrowed Variant, so callers must unshred the array first.
1121        _ => Err(ArrowError::NotYetImplemented(format!(
1122            "VariantArray::try_value cannot materialize typed_value of type {} \
1123             as a borrowed Variant; call unshred_variant first",
1124            typed_value.data_type()
1125        ))),
1126    }
1127}
1128
1129/// Canonicalize shredded typed_value fields (e.g. decimal narrowing) and
1130/// verify that all data types in the struct are legal for a variant array.
1131fn canonicalize_shredded_types(array: &dyn Array) -> Result<ArrayRef> {
1132    let new_type = canonicalize_and_verify_data_type_impl(array.data_type(), true)?;
1133    if let Cow::Borrowed(_) = new_type
1134        && let Some(array) = array.as_struct_opt()
1135    {
1136        return Ok(Arc::new(array.clone())); // bypass the unnecessary cast
1137    }
1138    cast(array, new_type.as_ref())
1139}
1140
1141/// Recursively visits a data type, ensuring that it only contains data types that can legally
1142/// appear in a (possibly shredded) variant array. It also narrows decimal types to the smallest
1143/// valid precision (e.g. Decimal128 -> Decimal32 when the precision fits).
1144fn canonicalize_and_verify_data_type(data_type: &DataType) -> Result<Cow<'_, DataType>> {
1145    canonicalize_and_verify_data_type_impl(data_type, false)
1146}
1147
1148fn canonicalize_and_verify_data_type_impl(
1149    data_type: &DataType,
1150    skip_top_level_metadata: bool,
1151) -> Result<Cow<'_, DataType>> {
1152    use DataType::*;
1153
1154    // helper macros
1155    macro_rules! fail {
1156        () => {
1157            return Err(ArrowError::InvalidArgumentError(format!(
1158                "Illegal shredded value type: {data_type}"
1159            )))
1160        };
1161    }
1162    macro_rules! borrow {
1163        () => {
1164            Cow::Borrowed(data_type)
1165        };
1166    }
1167
1168    let new_data_type = match data_type {
1169        // Primitive arrow types that have a direct variant counterpart are allowed
1170        Null | Boolean => borrow!(),
1171        Int8 | Int16 | Int32 | Int64 | Float32 | Float64 => borrow!(),
1172
1173        // Unsigned integers and half-float are not allowed
1174        UInt8 | UInt16 | UInt32 | UInt64 | Float16 => fail!(),
1175
1176        // Most decimal types are allowed, with restrictions on precision and scale
1177        //
1178        // NOTE: arrow-parquet reads widens 32- and 64-bit decimals to 128-bit, but the variant spec
1179        // requires using the narrowest decimal type for a given precision. Fix those up first.
1180        Decimal64(p, s) | Decimal128(p, s)
1181            if VariantDecimal4::is_valid_precision_and_scale(p, s) =>
1182        {
1183            Cow::Owned(Decimal32(*p, *s))
1184        }
1185        Decimal128(p, s) if VariantDecimal8::is_valid_precision_and_scale(p, s) => {
1186            Cow::Owned(Decimal64(*p, *s))
1187        }
1188        Decimal32(p, s) if VariantDecimal4::is_valid_precision_and_scale(p, s) => borrow!(),
1189        Decimal64(p, s) if VariantDecimal8::is_valid_precision_and_scale(p, s) => borrow!(),
1190        Decimal128(p, s) if VariantDecimal16::is_valid_precision_and_scale(p, s) => borrow!(),
1191        Decimal32(..) | Decimal64(..) | Decimal128(..) | Decimal256(..) => fail!(),
1192
1193        // Only micro and nano timestamps are allowed
1194        Timestamp(TimeUnit::Microsecond | TimeUnit::Nanosecond, _) => borrow!(),
1195        Timestamp(TimeUnit::Millisecond | TimeUnit::Second, _) => fail!(),
1196
1197        // Only 32-bit dates and 64-bit microsecond time are allowed.
1198        Date32 | Time64(TimeUnit::Microsecond) => borrow!(),
1199        Date64 | Time32(_) | Time64(_) | Duration(_) | Interval(_) => fail!(),
1200
1201        // Binary, string, and their view counterparts are allowed.
1202        Binary | LargeBinary | BinaryView | Utf8 | LargeUtf8 | Utf8View => borrow!(),
1203
1204        // UUID maps to 16-byte fixed-size binary; no other width is allowed
1205        FixedSizeBinary(16) => borrow!(),
1206        FixedSizeBinary(_) => fail!(),
1207
1208        // FixedSizeList is an Arrow-specific distinction. Normalize it to List on read so
1209        // Variant data written by older arrow-rs versions remains readable without treating
1210        // FixedSizeList as a supported shredding target.
1211        FixedSizeList(field, _) => match canonicalize_and_verify_field(field)? {
1212            Cow::Borrowed(_) => Cow::Owned(DataType::List(field.clone())),
1213            Cow::Owned(new_field) => Cow::Owned(DataType::List(new_field)),
1214        },
1215
1216        // List-like containers and struct are allowed, maps and unions are not
1217        List(field) => match canonicalize_and_verify_field(field)? {
1218            Cow::Borrowed(_) => borrow!(),
1219            Cow::Owned(new_field) => Cow::Owned(DataType::List(new_field)),
1220        },
1221        LargeList(field) => match canonicalize_and_verify_field(field)? {
1222            Cow::Borrowed(_) => borrow!(),
1223            Cow::Owned(new_field) => Cow::Owned(DataType::LargeList(new_field)),
1224        },
1225        ListView(field) => match canonicalize_and_verify_field(field)? {
1226            Cow::Borrowed(_) => borrow!(),
1227            Cow::Owned(new_field) => Cow::Owned(DataType::ListView(new_field)),
1228        },
1229        LargeListView(field) => match canonicalize_and_verify_field(field)? {
1230            Cow::Borrowed(_) => borrow!(),
1231            Cow::Owned(new_field) => Cow::Owned(DataType::LargeListView(new_field)),
1232        },
1233        // Struct is used by the internal layout, and can also represent a shredded variant object.
1234        Struct(fields) => {
1235            // Avoid allocation unless at least one field changes, to avoid unnecessary deep cloning
1236            // of the data type. Even if some fields change, the others are shallow arc clones.
1237            let mut new_fields = std::collections::HashMap::new();
1238            for (i, field) in fields.iter().enumerate() {
1239                if skip_top_level_metadata && field.name() == "metadata" {
1240                    continue;
1241                }
1242                if let Cow::Owned(new_field) = canonicalize_and_verify_field(field)? {
1243                    new_fields.insert(i, new_field);
1244                }
1245            }
1246
1247            if new_fields.is_empty() {
1248                borrow!()
1249            } else {
1250                let new_fields = fields
1251                    .iter()
1252                    .enumerate()
1253                    .map(|(i, field)| new_fields.remove(&i).unwrap_or_else(|| field.clone()));
1254                Cow::Owned(DataType::Struct(new_fields.collect()))
1255            }
1256        }
1257        Map(..) | Union(..) => fail!(),
1258
1259        // We can _possibly_ support (some of) these some day?
1260        Dictionary(..) | RunEndEncoded(..) => fail!(),
1261    };
1262    Ok(new_data_type)
1263}
1264
1265fn canonicalize_and_verify_field(field: &Arc<Field>) -> Result<Cow<'_, Arc<Field>>> {
1266    let new_data_type = canonicalize_and_verify_data_type(field.data_type())?;
1267
1268    // A shredded FixedSizeBinary(16) column is always a UUID. Tag it with the UUID extension type
1269    // on read, as a safety net against writers that emit the column without the extension metadata.
1270    // Canonicalization never rewrites FixedSizeBinary(16), so the type is already correct here.
1271    if matches!(new_data_type.as_ref(), DataType::FixedSizeBinary(16))
1272        && !field.has_valid_extension_type::<UuidExtension>()
1273    {
1274        let new_field = field.as_ref().clone().with_extension_type(UuidExtension);
1275        return Ok(Cow::Owned(Arc::new(new_field)));
1276    }
1277
1278    let Cow::Owned(new_data_type) = new_data_type else {
1279        return Ok(Cow::Borrowed(field));
1280    };
1281    let new_field = field.as_ref().clone().with_data_type(new_data_type);
1282    Ok(Cow::Owned(Arc::new(new_field)))
1283}
1284
1285/// Test-only constructors for perfectly shredded arrays, where every value lives in
1286/// `typed_value` and the required `value` column is all-null.
1287#[cfg(test)]
1288impl VariantArray {
1289    pub(crate) fn perfectly_shredded(
1290        metadata: ArrayRef,
1291        typed_value: ArrayRef,
1292        nulls: Option<NullBuffer>,
1293    ) -> Self {
1294        let value = all_null_value_column(typed_value.len());
1295        Self::from_parts(metadata, value, Some(typed_value), nulls)
1296    }
1297}
1298
1299#[cfg(test)]
1300impl ShreddedVariantFieldArray {
1301    pub(crate) fn perfectly_shredded(typed_value: ArrayRef) -> Self {
1302        let value = all_null_value_column(typed_value.len());
1303        Self::from_parts(value, Some(typed_value), None)
1304    }
1305}
1306
1307#[cfg(test)]
1308mod test {
1309    use crate::{GetOptions, VariantArrayBuilder, json_to_variant, variant_get, variant_to_json};
1310    use std::str::FromStr;
1311
1312    use super::*;
1313    use arrow::array::{
1314        BinaryArray, BinaryDictionaryBuilder, BinaryRunBuilder, BinaryViewArray, Decimal32Array,
1315        Decimal64Array, Decimal128Array, FixedSizeBinaryArray, FixedSizeListArray, Int8Array,
1316        Int32Array, Int64Array, LargeBinaryArray, LargeListArray, LargeListViewArray, ListArray,
1317        ListViewArray, StringArray, Time64MicrosecondArray,
1318    };
1319    use arrow::buffer::{OffsetBuffer, ScalarBuffer};
1320    use arrow_schema::{Field, Fields};
1321    use parquet_variant::{EMPTY_VARIANT_METADATA_BYTES, ShortString};
1322
1323    #[test]
1324    fn invalid_not_a_struct_array() {
1325        let array = make_binary_view_array();
1326        // Should fail because the input is not a StructArray
1327        let err = VariantArray::try_new(&array);
1328        assert_eq!(
1329            err.unwrap_err().to_string(),
1330            "Invalid argument error: Invalid VariantArray: requires StructArray as input"
1331        );
1332    }
1333
1334    #[test]
1335    fn invalid_missing_metadata() {
1336        let fields = Fields::from(vec![Field::new("value", DataType::BinaryView, true)]);
1337        let array = StructArray::new(fields, vec![make_binary_view_array()], None);
1338        // Should fail because the StructArray does not contain a 'metadata' field
1339        let err = VariantArray::try_new(&array);
1340        assert_eq!(
1341            err.unwrap_err().to_string(),
1342            "Invalid argument error: Invalid VariantArray: StructArray must contain a 'metadata' field"
1343        );
1344    }
1345
1346    #[test]
1347    fn read_missing_value_column() {
1348        // With `typed_value` present, a missing `value` is read leniently: an all-null `value` is
1349        // synthesized and normalized into the inner struct (so a write-back emits it), and values
1350        // still decode from `typed_value`.
1351        let typed_value = Arc::new(Int64Array::from(vec![Some(1), None, Some(3)])) as ArrayRef;
1352        let metadata =
1353            BinaryViewArray::from_iter_values(std::iter::repeat_n(EMPTY_VARIANT_METADATA_BYTES, 3));
1354        let struct_array = StructArrayAssembler::new()
1355            .with_field("metadata", Arc::new(metadata), false)
1356            .with_field("typed_value", typed_value, true)
1357            .build()
1358            .unwrap();
1359        assert!(struct_array.column_by_name("value").is_none());
1360
1361        let variant_array = VariantArray::try_new(&struct_array).unwrap();
1362        assert_eq!(variant_array.value_column().len(), 3);
1363        assert_eq!(variant_array.value_column().null_count(), 3);
1364        assert!(variant_array.inner().column_by_name("value").is_some());
1365        assert!(variant_array.typed_value_column().is_some());
1366        assert_eq!(variant_array.value(0), Variant::from(1i64));
1367        assert_eq!(variant_array.value(1), Variant::Null);
1368        assert_eq!(variant_array.value(2), Variant::from(3i64));
1369
1370        // With no `typed_value` either, there is nothing to read, so `value` stays required.
1371        let fields = Fields::from(vec![Field::new("metadata", DataType::BinaryView, false)]);
1372        let metadata_only = StructArray::new(fields, vec![make_binary_view_array()], None);
1373        let err = VariantArray::try_new(&metadata_only);
1374        assert_eq!(
1375            err.unwrap_err().to_string(),
1376            "Invalid argument error: Invalid VariantArray: StructArray must contain a 'value' field"
1377        );
1378    }
1379
1380    #[test]
1381    fn invalid_metadata_field_type() {
1382        let fields = Fields::from(vec![
1383            Field::new("metadata", DataType::Int32, true), // not supported
1384            Field::new("value", DataType::BinaryView, true),
1385        ]);
1386        let array = StructArray::new(
1387            fields,
1388            vec![make_int32_array(), make_binary_view_array()],
1389            None,
1390        );
1391        let err = VariantArray::try_new(&array);
1392        assert_eq!(
1393            err.unwrap_err().to_string(),
1394            "Invalid argument error: VariantArray 'metadata' field must be Binary, LargeBinary, BinaryView, or a Dictionary or RunEndEncoded array of one of those types, got Int32"
1395        );
1396    }
1397
1398    #[test]
1399    fn encoded_metadata_supports_nulls_slices_and_variant_get() {
1400        let json: ArrayRef = Arc::new(StringArray::from(vec![
1401            Some(r#"{"a":0}"#),
1402            Some(r#"{"a":1}"#),
1403            None,
1404            Some(r#"{"b":3}"#),
1405            Some(r#"{"b":4}"#),
1406        ]));
1407        let baseline = json_to_variant(&json).unwrap();
1408        let metadata = baseline.metadata_column().as_binary_view();
1409        let metadata_a = metadata.value(0);
1410        let metadata_b = metadata.value(3);
1411
1412        let logical_metadata = [
1413            Some(metadata_a),
1414            Some(metadata_a),
1415            None,
1416            Some(metadata_b),
1417            Some(metadata_b),
1418        ];
1419
1420        let mut dictionary = BinaryDictionaryBuilder::<Int8Type>::new();
1421        dictionary.extend(logical_metadata);
1422        let dictionary: ArrayRef = Arc::new(dictionary.finish());
1423
1424        let mut ree = BinaryRunBuilder::<Int16Type>::new();
1425        ree.extend(logical_metadata);
1426        let run_end_encoded: ArrayRef = Arc::new(ree.finish());
1427
1428        for metadata in [dictionary, run_end_encoded] {
1429            assert_eq!(binary_array_value(metadata.as_ref(), 2), None);
1430            let fields = Fields::from(vec![
1431                Field::new("metadata", metadata.data_type().clone(), false),
1432                Field::new("value", baseline.value_column().data_type().clone(), false),
1433            ]);
1434            let input = StructArray::try_new(
1435                fields,
1436                vec![metadata, baseline.value_column().clone()],
1437                baseline.nulls().cloned(),
1438            )
1439            .unwrap()
1440            .slice(1, 3);
1441
1442            let variant = VariantArray::try_new(&input).unwrap();
1443            assert_eq!(variant.value(0), baseline.value(1));
1444            assert!(variant.is_null(1));
1445            assert_eq!(variant.value(2), baseline.value(3));
1446
1447            let input: ArrayRef = Arc::new(input);
1448            let options = GetOptions::new_with_path("b".try_into().unwrap())
1449                .with_as_type(Some(Arc::new(Field::new("b", DataType::Int8, true))));
1450            let result = variant_get(&input, options).unwrap();
1451            assert_eq!(
1452                result.as_primitive::<Int8Type>(),
1453                &Int8Array::from(vec![None, None, Some(3)])
1454            );
1455            assert_eq!(
1456                variant_to_json(&input).unwrap(),
1457                StringArray::from(vec![Some(r#"{"a":1}"#), None, Some(r#"{"b":3}"#)])
1458            );
1459        }
1460    }
1461
1462    #[test]
1463    fn invalid_value_field_type() {
1464        let fields = Fields::from(vec![
1465            Field::new("metadata", DataType::BinaryView, true),
1466            Field::new("value", DataType::Int32, true),
1467        ]);
1468        let array = StructArray::new(
1469            fields,
1470            vec![make_binary_view_array(), make_int32_array()],
1471            None,
1472        );
1473        let err = VariantArray::try_new(&array);
1474        assert_eq!(
1475            err.unwrap_err().to_string(),
1476            "Invalid argument error: VariantArray 'value' field must be Binary, LargeBinary, or BinaryView, got Int32"
1477        );
1478    }
1479
1480    fn make_binary_view_array() -> ArrayRef {
1481        Arc::new(BinaryViewArray::from(vec![b"test" as &[u8]]))
1482    }
1483
1484    fn make_int32_array() -> ArrayRef {
1485        Arc::new(Int32Array::from(vec![1]))
1486    }
1487
1488    fn make_variant_struct_with_typed_value(typed_value: ArrayRef) -> StructArray {
1489        let metadata = BinaryViewArray::from_iter_values(std::iter::repeat_n(
1490            EMPTY_VARIANT_METADATA_BYTES,
1491            typed_value.len(),
1492        ));
1493        let value = new_null_array(&DataType::BinaryView, typed_value.len());
1494        StructArrayAssembler::new()
1495            .with_field("metadata", Arc::new(metadata), false)
1496            .with_field("value", value, true)
1497            .with_field("typed_value", typed_value, true)
1498            .build()
1499            .unwrap()
1500    }
1501
1502    #[test]
1503    fn try_new_tags_untagged_uuid_on_read() {
1504        // Simulate a foreign writer that shredded a UUID column as bare FixedSizeBinary(16),
1505        // omitting the UUID extension type.
1506        let typed_value = FixedSizeBinaryArray::try_from_iter(std::iter::repeat_n([0u8; 16], 2));
1507        let input = make_variant_struct_with_typed_value(Arc::new(typed_value.unwrap()));
1508
1509        // try_new canonicalizes on the read path and attaches the extension.
1510        let variant_array = VariantArray::try_new(&input).unwrap();
1511        let typed_value = variant_array.inner().field_by_name("typed_value").unwrap();
1512        assert_eq!(typed_value.data_type(), &DataType::FixedSizeBinary(16));
1513        assert!(typed_value.has_valid_extension_type::<UuidExtension>());
1514    }
1515
1516    #[test]
1517    fn try_new_tags_untagged_nested_uuid_on_read() {
1518        // A shredded object { id: { typed_value: FixedSizeBinary(16) } } whose inner UUID leaf
1519        // carries no extension type; canonicalization must reach it recursively.
1520        let leaf = FixedSizeBinaryArray::try_from_iter(std::iter::repeat_n([0u8; 16], 1)).unwrap();
1521        let inner = StructArrayAssembler::new()
1522            .with_field("typed_value", Arc::new(leaf), true)
1523            .build()
1524            .unwrap();
1525        let object = StructArrayAssembler::new()
1526            .with_field("id", Arc::new(inner), false)
1527            .build()
1528            .unwrap();
1529        let input = make_variant_struct_with_typed_value(Arc::new(object));
1530
1531        // typed_value (struct) -> id (struct) -> typed_value (the FixedSizeBinary(16) UUID leaf).
1532        let variant_array = VariantArray::try_new(&input).unwrap();
1533        let object = variant_array.typed_value_column().unwrap().as_struct();
1534        let id = object.column_by_name("id").unwrap().as_struct();
1535        let uuid_leaf = id.field_by_name("typed_value").unwrap();
1536        assert!(uuid_leaf.has_valid_extension_type::<UuidExtension>());
1537    }
1538
1539    #[test]
1540    fn all_null_value_column_is_valid_and_unshredded() {
1541        // An all-null `value` column is accepted (only a *missing* column is
1542        // rejected) and the array is unshredded (no typed_value column)
1543        let metadata = BinaryViewArray::from(vec![b"test" as &[u8]; 3]);
1544        let value = new_null_array(&DataType::BinaryView, 3);
1545
1546        let fields = Fields::from(vec![
1547            Field::new("metadata", DataType::BinaryView, false),
1548            Field::new("value", DataType::BinaryView, true),
1549        ]);
1550        let struct_array = StructArray::new(fields, vec![Arc::new(metadata), value], None);
1551
1552        let variant_array = VariantArray::try_new(&struct_array).unwrap();
1553        assert!(variant_array.typed_value_column().is_none());
1554
1555        // The rows are valid but have neither value nor typed_value; the spec
1556        // requires readers to return Variant::Null in this case
1557        for i in 0..variant_array.len() {
1558            assert!(variant_array.is_valid(i));
1559            assert_eq!(variant_array.value(i), Variant::Null);
1560        }
1561    }
1562
1563    #[test]
1564    fn canonicalize_and_verify_list_like_data_types() {
1565        // `parquet/tests/variant_integration.rs` validates Parquet shredded-variant fixtures that
1566        // use Parquet LIST encoding, but those fixtures do not cover Arrow-specific list container
1567        // variants (`LargeList`, `ListView`, `LargeListView`) accepted by `VariantArray::try_new`.
1568        let make_item_binary = || Arc::new(Field::new("item", DataType::Binary, true));
1569        let make_large_binary = || Arc::new(Field::new("item", DataType::LargeBinary, true));
1570        let make_item_binary_view = || Arc::new(Field::new("item", DataType::BinaryView, true));
1571
1572        let cases = vec![
1573            // Binary item
1574            DataType::LargeList(make_item_binary()),
1575            DataType::ListView(make_item_binary()),
1576            DataType::LargeListView(make_item_binary()),
1577            // Large binary item
1578            DataType::LargeList(make_large_binary()),
1579            DataType::ListView(make_large_binary()),
1580            DataType::LargeListView(make_large_binary()),
1581            // Binary view item
1582            DataType::LargeList(make_item_binary_view()),
1583            DataType::ListView(make_item_binary_view()),
1584            DataType::LargeListView(make_item_binary_view()),
1585        ];
1586
1587        for input in cases {
1588            assert_eq!(
1589                canonicalize_and_verify_data_type(&input).unwrap().as_ref(),
1590                &input
1591            );
1592        }
1593    }
1594
1595    #[test]
1596    fn variant_array_try_new_supports_list_like_typed_value() {
1597        let item_field = Arc::new(Field::new("item", DataType::Int64, true));
1598        let values: ArrayRef = Arc::new(Int64Array::from(vec![Some(1), None, Some(3)]));
1599
1600        let typed_values = vec![
1601            Arc::new(ListArray::new(
1602                item_field.clone(),
1603                OffsetBuffer::new(ScalarBuffer::from(vec![0, 2, 3])),
1604                values.clone(),
1605                None,
1606            )) as ArrayRef,
1607            Arc::new(LargeListArray::new(
1608                item_field.clone(),
1609                OffsetBuffer::new(ScalarBuffer::from(vec![0_i64, 2, 3])),
1610                values.clone(),
1611                None,
1612            )) as ArrayRef,
1613            Arc::new(ListViewArray::new(
1614                item_field.clone(),
1615                ScalarBuffer::from(vec![0, 2]),
1616                ScalarBuffer::from(vec![2, 1]),
1617                values.clone(),
1618                None,
1619            )) as ArrayRef,
1620            Arc::new(LargeListViewArray::new(
1621                item_field,
1622                ScalarBuffer::from(vec![0_i64, 2]),
1623                ScalarBuffer::from(vec![2_i64, 1]),
1624                values,
1625                None,
1626            )) as ArrayRef,
1627        ];
1628
1629        for typed_value in typed_values {
1630            let input = make_variant_struct_with_typed_value(typed_value.clone());
1631            let variant_array = VariantArray::try_new(&input).unwrap();
1632            assert_eq!(
1633                variant_array.typed_value_column().unwrap().data_type(),
1634                typed_value.data_type(),
1635            );
1636        }
1637    }
1638
1639    #[test]
1640    fn variant_array_try_new_normalizes_fixed_size_list_typed_value() {
1641        let element_values: ArrayRef =
1642            ShreddedVariantFieldArray::perfectly_shredded(Arc::new(Int64Array::from(vec![
1643                1, 2, 3, 4,
1644            ])))
1645            .into();
1646        let item_field = Arc::new(Field::new("item", element_values.data_type().clone(), true));
1647        let typed_value: ArrayRef = Arc::new(FixedSizeListArray::new(
1648            item_field.clone(),
1649            2,
1650            element_values,
1651            None,
1652        ));
1653        let input = make_variant_struct_with_typed_value(typed_value);
1654
1655        let variant_array = VariantArray::try_new(&input).unwrap();
1656        assert_eq!(
1657            variant_array.typed_value_column().unwrap().data_type(),
1658            &DataType::List(item_field),
1659        );
1660
1661        let unshredded = crate::unshred_variant(&variant_array).unwrap();
1662        assert!(unshredded.typed_value_column().is_none());
1663        assert_eq!(unshredded.len(), 2);
1664    }
1665
1666    #[test]
1667    fn test_try_value_out_of_bounds() {
1668        let mut b = VariantArrayBuilder::new(2);
1669        b.append_variant(Variant::from(1_i8));
1670        b.append_variant(Variant::Null);
1671        let v = b.build();
1672
1673        assert_eq!(v.try_value(0).unwrap(), Variant::Int8(1));
1674        assert_eq!(v.try_value(1).unwrap(), Variant::Null);
1675
1676        let err = v.try_value(2).unwrap_err();
1677        assert_eq!(
1678            err.to_string(),
1679            "Invalid argument error: Index 2 out of bounds for VariantArray of length 2"
1680        );
1681    }
1682
1683    #[test]
1684    fn test_variant_array_iterable() {
1685        let mut b = VariantArrayBuilder::new(6);
1686
1687        b.append_null();
1688        b.append_variant(Variant::from(1_i8));
1689        b.append_variant(Variant::Null);
1690        b.append_variant(Variant::from(2_i32));
1691        b.append_variant(Variant::from(3_i64));
1692        b.append_null();
1693
1694        let v = b.build();
1695
1696        let variants = v.iter().collect::<Vec<_>>();
1697
1698        assert_eq!(
1699            variants,
1700            vec![
1701                None,
1702                Some(Variant::Int8(1)),
1703                Some(Variant::Null),
1704                Some(Variant::Int32(2)),
1705                Some(Variant::Int64(3)),
1706                None,
1707            ]
1708        );
1709    }
1710
1711    #[test]
1712    fn test_variant_array_iter_double_ended() {
1713        let array = VariantArray::from_iter([Some(0_i32), None, Some(2_i32), None, Some(4_i32)]);
1714        let mut iter = array.iter();
1715
1716        assert_eq!(iter.next(), Some(Some(Variant::from(0_i32))));
1717        assert_eq!(iter.next(), Some(None));
1718
1719        assert_eq!(iter.next_back(), Some(Some(Variant::from(4_i32))));
1720        assert_eq!(iter.next_back(), Some(None));
1721        assert_eq!(iter.next_back(), Some(Some(Variant::from(2_i32))));
1722
1723        assert_eq!(iter.next_back(), None);
1724        assert_eq!(iter.next(), None);
1725    }
1726
1727    #[test]
1728    fn test_variant_array_iter_reverse() {
1729        let array = VariantArray::from_iter([Some("a"), None, Some("aaa"), None, Some("aaaaa")]);
1730
1731        let result: Vec<_> = array.iter().rev().collect();
1732        assert_eq!(
1733            result,
1734            vec![
1735                Some(Variant::from("aaaaa")),
1736                None,
1737                Some(Variant::from("aaa")),
1738                None,
1739                Some(Variant::from("a")),
1740            ]
1741        );
1742    }
1743
1744    #[test]
1745    fn test_variant_array_iter_empty() {
1746        let v = VariantArrayBuilder::new(0).build();
1747        let mut i = v.iter();
1748        assert!(i.next().is_none());
1749        assert!(i.next_back().is_none());
1750    }
1751
1752    #[test]
1753    fn test_from_variant_opts_into_variant_array() {
1754        let v = vec![None, Some(Variant::Null), Some(Variant::BooleanFalse), None];
1755
1756        let variant_array = VariantArray::from_iter(v);
1757
1758        assert_eq!(variant_array.len(), 4);
1759
1760        assert!(variant_array.is_null(0));
1761
1762        assert!(!variant_array.is_null(1));
1763        assert_eq!(variant_array.value(1), Variant::Null);
1764
1765        assert!(!variant_array.is_null(2));
1766        assert_eq!(variant_array.value(2), Variant::BooleanFalse);
1767
1768        assert!(variant_array.is_null(3));
1769    }
1770
1771    #[test]
1772    fn test_from_option_into_variants_into_variant_array() {
1773        // Items that convert cleanly to `Variant` can be collected directly,
1774        // without wrapping them in `Some(Variant::from(..))` first
1775        let v = vec![Some(42_i64), None, Some(-1_i64)];
1776
1777        let variant_array = VariantArray::from_iter(v);
1778
1779        assert_eq!(variant_array.len(), 3);
1780
1781        assert!(!variant_array.is_null(0));
1782        assert_eq!(variant_array.value(0), Variant::Int64(42));
1783
1784        assert!(variant_array.is_null(1));
1785
1786        assert!(!variant_array.is_null(2));
1787        assert_eq!(variant_array.value(2), Variant::Int64(-1));
1788    }
1789
1790    #[test]
1791    fn test_from_option_str_into_variant_array() {
1792        let v = vec![
1793            Some("hello"),
1794            None,
1795            Some(
1796                "hello world this is much longer than the maximum short string length of sixty three bytes",
1797            ),
1798        ];
1799
1800        let variant_array: VariantArray = v.into_iter().collect();
1801
1802        assert_eq!(variant_array.len(), 3);
1803
1804        assert!(!variant_array.is_null(0));
1805        assert_eq!(
1806            variant_array.value(0),
1807            Variant::ShortString(ShortString::try_new("hello").unwrap())
1808        );
1809
1810        assert!(variant_array.is_null(1));
1811
1812        assert!(!variant_array.is_null(2));
1813        assert_eq!(
1814            variant_array.value(2),
1815            Variant::String(
1816                "hello world this is much longer than the maximum short string length of sixty three bytes"
1817            )
1818        );
1819    }
1820
1821    #[test]
1822    fn test_from_variants_into_variant_array() {
1823        let v = vec![
1824            Variant::Null,
1825            Variant::BooleanFalse,
1826            Variant::ShortString(ShortString::try_new("norm").unwrap()),
1827        ];
1828
1829        let variant_array = VariantArray::from_iter(v);
1830
1831        assert_eq!(variant_array.len(), 3);
1832
1833        assert!(!variant_array.is_null(0));
1834        assert_eq!(variant_array.value(0), Variant::Null);
1835
1836        assert!(!variant_array.is_null(1));
1837        assert_eq!(variant_array.value(1), Variant::BooleanFalse);
1838
1839        assert!(!variant_array.is_null(2));
1840        assert_eq!(
1841            variant_array.value(2),
1842            Variant::ShortString(ShortString::try_new("norm").unwrap())
1843        );
1844    }
1845
1846    #[test]
1847    fn test_variant_equality() {
1848        let v_iter = [None, Some(Variant::BooleanFalse), Some(Variant::Null), None];
1849        let v = VariantArray::from_iter(v_iter.clone());
1850
1851        {
1852            let v_copy = v.clone();
1853            assert_eq!(v, v_copy);
1854        }
1855
1856        {
1857            let v_iter_reversed = v_iter.iter().cloned().rev();
1858            let v_reversed = VariantArray::from_iter(v_iter_reversed);
1859
1860            assert_ne!(v, v_reversed);
1861        }
1862
1863        {
1864            let v_sliced = v.slice(0, 1);
1865            assert_ne!(v, v_sliced);
1866        }
1867    }
1868
1869    #[test]
1870    fn binary_typed_value_roundtrips() {
1871        // Verify that a shredded variant with Binary typed_value can be read back
1872        let typed_value: ArrayRef = Arc::new(BinaryArray::from(vec![b"hello" as &[u8]]));
1873        let struct_array = make_variant_struct_with_typed_value(typed_value);
1874
1875        let variant_array = VariantArray::try_new(&struct_array).unwrap();
1876        assert_eq!(variant_array.value(0), Variant::from(b"hello" as &[u8]));
1877    }
1878
1879    #[test]
1880    fn large_binary_typed_value_roundtrips() {
1881        // Verify that a shredded variant with LargeBinary typed_value can be read back
1882        let typed_value: ArrayRef = Arc::new(LargeBinaryArray::from(vec![b"world" as &[u8]]));
1883        let struct_array = make_variant_struct_with_typed_value(typed_value);
1884
1885        let variant_array = VariantArray::try_new(&struct_array).unwrap();
1886        assert_eq!(variant_array.value(0), Variant::from(b"world" as &[u8]));
1887    }
1888
1889    macro_rules! invalid_variant_array_test {
1890        ($fn_name: ident, $invalid_typed_value: expr, $error_msg: literal) => {
1891            #[test]
1892            fn $fn_name() {
1893                let invalid_typed_value = $invalid_typed_value;
1894
1895                let struct_array =
1896                    make_variant_struct_with_typed_value(Arc::new(invalid_typed_value));
1897
1898                let array: VariantArray = VariantArray::try_new(&struct_array)
1899                    .expect("should create variant array")
1900                    .into();
1901
1902                let result = array.try_value(0);
1903                assert!(result.is_err());
1904                let error = result.unwrap_err();
1905                assert!(matches!(error, ArrowError::CastError(_)));
1906
1907                let expected: &str = $error_msg;
1908                assert!(
1909                    error.to_string().contains($error_msg),
1910                    "error `{}` did not contain `{}`",
1911                    error,
1912                    expected
1913                )
1914            }
1915        };
1916    }
1917
1918    invalid_variant_array_test!(
1919        test_variant_array_invalid_time,
1920        Time64MicrosecondArray::from(vec![Some(86401000000)]),
1921        "Cast error: Cast failed at index 0 (array type: Time64(µs)): Invalid microsecond from midnight: 86401000000"
1922    );
1923
1924    invalid_variant_array_test!(
1925        test_variant_array_invalid_decimal32,
1926        Decimal32Array::from(vec![Some(1234567890)]),
1927        "Cast error: Cast failed at index 0 (array type: Decimal32(9, 2)): Invalid argument error: 1234567890 is wider than max precision 9"
1928    );
1929
1930    invalid_variant_array_test!(
1931        test_variant_array_invalid_decimal64,
1932        Decimal64Array::from(vec![Some(1234567890123456789)]),
1933        "Cast error: Cast failed at index 0 (array type: Decimal64(18, 6)): Invalid argument error: 1234567890123456789 is wider than max precision 18"
1934    );
1935
1936    invalid_variant_array_test!(
1937        test_variant_array_invalid_decimal128,
1938        Decimal128Array::from(vec![Some(
1939            i128::from_str("123456789012345678901234567890123456789").unwrap()
1940        ),]),
1941        "Cast error: Cast failed at index 0 (array type: Decimal128(38, 10)): Invalid argument error: 123456789012345678901234567890123456789 is wider than max precision 38"
1942    );
1943    #[test]
1944    fn try_value_errors_on_unimplemented_typed_value_type() {
1945        use crate::{json_to_variant, shred_variant};
1946        use arrow::array::StringArray;
1947
1948        let json: ArrayRef = Arc::new(StringArray::from(vec![r#"{"qty": 3}"#]));
1949        let variant = json_to_variant(&json).unwrap();
1950        let shred_type = DataType::Struct(vec![Field::new("qty", DataType::Int64, true)].into());
1951        let shredded = shred_variant(&variant, &shred_type).unwrap();
1952        // Object-shredded typed_value is not yet implemented: reading it must
1953        // error, never silently return Variant::Null
1954        // TODO: https://github.com/apache/arrow-rs/issues/10620
1955        let err = shredded.try_value(0).unwrap_err();
1956        assert!(
1957            err.to_string().starts_with(
1958                "Not yet implemented: VariantArray::try_value cannot materialize typed_value"
1959            ),
1960            "unexpected error: {err}"
1961        );
1962    }
1963}