Skip to main content

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