Skip to main content

datafusion_common/
nested_struct.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
18use crate::error::{_plan_err, Result};
19use arrow::{
20    array::{
21        Array, ArrayRef, AsArray, DictionaryArray, FixedSizeListArray, GenericListArray,
22        GenericListViewArray, StructArray, downcast_integer, make_array, new_null_array,
23    },
24    buffer::NullBuffer,
25    compute::{CastOptions, can_cast_types, cast_with_options},
26    datatypes::{DataType, DataType::Struct, Field, FieldRef},
27};
28use std::{collections::HashSet, sync::Arc};
29
30/// Cast a struct column to match target struct fields, handling nested structs recursively.
31///
32/// This function implements struct-to-struct casting with the assumption that **structs should
33/// always be allowed to cast to other structs**. However, the source column must already be
34/// a struct type - non-struct sources will result in an error.
35///
36/// ## Field Matching Strategy
37/// - **By Name**: Source struct fields are matched to target fields by name (case-sensitive)
38/// - **No Positional Mapping**: Structs with no overlapping field names are rejected
39/// - **Type Adaptation**: When a matching field is found, it is recursively cast to the target field's type
40/// - **Missing Fields**: Target fields not present in the source are filled with null values
41/// - **Extra Fields**: Source fields not present in the target are ignored
42///
43/// ## Nested Struct Handling
44/// - Nested structs are handled recursively using the same casting rules
45/// - Each level of nesting follows the same field matching and null-filling strategy
46/// - This allows for complex struct transformations while maintaining data integrity
47///
48/// # Arguments
49/// * `source_col` - The source array to cast (must be a struct array)
50/// * `target_fields` - The target struct field definitions to cast to
51///
52/// # Returns
53/// A `Result<ArrayRef>` containing the cast struct array
54///
55/// # Errors
56/// Returns a `DataFusionError::Plan` if the source column is not a struct type
57fn cast_struct_column(
58    source_col: &ArrayRef,
59    target_fields: &[Arc<Field>],
60    cast_options: &CastOptions,
61) -> Result<ArrayRef> {
62    if source_col.data_type() == &DataType::Null {
63        return Ok(new_null_array(
64            &Struct(target_fields.to_vec().into()),
65            source_col.len(),
66        ));
67    }
68
69    if let Some(source_struct) = source_col.as_any().downcast_ref::<StructArray>() {
70        let source_fields = source_struct.fields();
71        validate_struct_compatibility(source_fields, target_fields)?;
72
73        if !source_col.is_empty() && source_col.null_count() == source_col.len() {
74            return Ok(new_null_array(
75                &Struct(target_fields.to_vec().into()),
76                source_col.len(),
77            ));
78        }
79
80        let mut fields: Vec<Arc<Field>> = Vec::with_capacity(target_fields.len());
81        let mut arrays: Vec<ArrayRef> = Vec::with_capacity(target_fields.len());
82        let num_rows = source_col.len();
83
84        // Iterate target fields and pick source child by name when present.
85        for target_child_field in target_fields.iter() {
86            fields.push(Arc::clone(target_child_field));
87
88            let source_child_opt =
89                source_struct.column_by_name(target_child_field.name());
90
91            match source_child_opt {
92                Some(source_child_col) => {
93                    let adapted_child = cast_column(
94                        source_child_col,
95                        target_child_field.data_type(),
96                        cast_options,
97                    )
98                    .map_err(|e| {
99                        e.context(format!(
100                            "While casting struct field '{}'",
101                            target_child_field.name()
102                        ))
103                    })?;
104                    arrays.push(adapted_child);
105                }
106                None => {
107                    arrays.push(new_null_array(target_child_field.data_type(), num_rows));
108                }
109            }
110        }
111
112        let struct_array =
113            StructArray::new(fields.into(), arrays, source_struct.nulls().cloned());
114        Ok(Arc::new(struct_array))
115    } else {
116        // Return error if source is not a struct type
117        _plan_err!(
118            "Cannot cast column of type {} to struct type. Source must be a struct to cast to struct.",
119            source_col.data_type()
120        )
121    }
122}
123
124/// Cast a column to match the target field type, with special handling for nested structs.
125///
126/// This function serves as the main entry point for column casting operations. For struct
127/// types, it enforces that **only struct columns can be cast to struct types**.
128///
129/// ## Casting Behavior
130/// - **Struct Types**: Delegates to `cast_struct_column` for struct-to-struct casting only
131/// - **Non-Struct Types**: Uses Arrow's standard `cast` function for primitive type conversions
132///
133/// ## Cast Options
134/// The `cast_options` argument controls how Arrow handles values that cannot be represented
135/// in the target type. When `safe` is `false` (DataFusion's default) the cast will return an
136/// error if such a value is encountered. Setting `safe` to `true` instead produces `NULL`
137/// for out-of-range or otherwise invalid values. The options also allow customizing how
138/// temporal values are formatted when cast to strings.
139///
140/// ```
141/// use arrow::array::{ArrayRef, Int64Array};
142/// use arrow::compute::CastOptions;
143/// use arrow::datatypes::DataType;
144/// use datafusion_common::nested_struct::cast_column;
145/// use std::sync::Arc;
146///
147/// let source: ArrayRef = Arc::new(Int64Array::from(vec![1, i64::MAX]));
148/// // Permit lossy conversions by producing NULL on overflow instead of erroring
149/// let options = CastOptions {
150///     safe: true,
151///     ..Default::default()
152/// };
153/// let result = cast_column(&source, &DataType::Int32, &options).unwrap();
154/// assert!(result.is_null(1));
155/// ```
156///
157/// ## Struct Casting Requirements
158/// The struct casting logic requires that the source column must already be a struct type.
159/// This makes the function useful for:
160/// - Schema evolution scenarios where struct layouts change over time
161/// - Data migration between different struct schemas
162/// - Type-safe data processing pipelines that maintain struct type integrity
163///
164/// # Arguments
165/// * `source_col` - The source array to cast
166/// * `target_type` - The target data type to cast to
167/// * `cast_options` - Options that govern strictness and formatting of the cast
168///
169/// # Returns
170/// A `Result<ArrayRef>` containing the cast array
171///
172/// # Errors
173/// Returns an error if:
174/// - Attempting to cast a non-struct column to a struct type
175/// - Arrow's cast function fails for non-struct types
176/// - Memory allocation fails during struct construction
177/// - Invalid data type combinations are encountered
178pub fn cast_column(
179    source_col: &ArrayRef,
180    target_type: &DataType,
181    cast_options: &CastOptions,
182) -> Result<ArrayRef> {
183    match (source_col.data_type(), target_type) {
184        (_, Struct(target_fields)) => {
185            cast_struct_column(source_col, target_fields, cast_options)
186        }
187        (DataType::List(_), DataType::List(target_inner)) => {
188            cast_list_column::<i32>(source_col, target_inner, cast_options)
189        }
190        (DataType::LargeList(_), DataType::LargeList(target_inner)) => {
191            cast_list_column::<i64>(source_col, target_inner, cast_options)
192        }
193        (
194            DataType::FixedSizeList(_, source_list_size),
195            DataType::FixedSizeList(target_inner, target_list_size),
196        ) if source_list_size == target_list_size => cast_fixed_size_list_column(
197            source_col,
198            target_inner,
199            *target_list_size,
200            cast_options,
201        ),
202        (DataType::ListView(_), DataType::ListView(target_inner)) => {
203            cast_list_view_column::<i32>(source_col, target_inner, cast_options)
204        }
205        (DataType::LargeListView(_), DataType::LargeListView(target_inner)) => {
206            cast_list_view_column::<i64>(source_col, target_inner, cast_options)
207        }
208        (
209            DataType::Dictionary(source_key_type, _),
210            DataType::Dictionary(target_key_type, target_value_type),
211        ) => cast_dictionary_column(
212            source_col,
213            source_key_type,
214            target_key_type,
215            target_value_type,
216            cast_options,
217        ),
218        _ => Ok(cast_with_options(source_col, target_type, cast_options)?),
219    }
220}
221
222fn cast_list_column<O: arrow::array::OffsetSizeTrait>(
223    source_col: &ArrayRef,
224    target_inner_field: &FieldRef,
225    cast_options: &CastOptions,
226) -> Result<ArrayRef> {
227    let source_list = source_col.as_list::<O>();
228
229    let cast_values = cast_column(
230        source_list.values(),
231        target_inner_field.data_type(),
232        cast_options,
233    )?;
234
235    let result = GenericListArray::<O>::new(
236        Arc::clone(target_inner_field),
237        source_list.offsets().clone(),
238        cast_values,
239        source_list.nulls().cloned(),
240    );
241    Ok(Arc::new(result))
242}
243
244fn cast_list_view_column<O: arrow::array::OffsetSizeTrait>(
245    source_col: &ArrayRef,
246    target_inner_field: &FieldRef,
247    cast_options: &CastOptions,
248) -> Result<ArrayRef> {
249    let source_list = source_col.as_list_view::<O>();
250
251    let cast_values = cast_column(
252        source_list.values(),
253        target_inner_field.data_type(),
254        cast_options,
255    )?;
256
257    let result = GenericListViewArray::<O>::try_new(
258        Arc::clone(target_inner_field),
259        source_list.offsets().clone(),
260        source_list.sizes().clone(),
261        cast_values,
262        source_list.nulls().cloned(),
263    )?;
264    Ok(Arc::new(result))
265}
266
267fn cast_fixed_size_list_column(
268    source_col: &ArrayRef,
269    target_inner_field: &FieldRef,
270    target_list_size: i32,
271    cast_options: &CastOptions,
272) -> Result<ArrayRef> {
273    let source_list = source_col.as_fixed_size_list();
274
275    let source_values = source_list.values();
276    let target_type = target_inner_field.data_type();
277
278    let cast_values = match cast_column(source_values, target_type, cast_options) {
279        Ok(cast_values) => cast_values,
280        Err(error) => match cast_fixed_size_list_values_with_parent_nulls(
281            source_values,
282            target_type,
283            cast_options,
284            source_list.nulls(),
285            target_list_size,
286        ) {
287            Some(masked_cast) => masked_cast?,
288            None => return Err(error),
289        },
290    };
291
292    Ok(Arc::new(FixedSizeListArray::try_new(
293        Arc::clone(target_inner_field),
294        target_list_size,
295        cast_values,
296        source_list.nulls().cloned(),
297    )?))
298}
299
300fn cast_fixed_size_list_values_with_parent_nulls(
301    source_values: &ArrayRef,
302    target_type: &DataType,
303    cast_options: &CastOptions,
304    parent_nulls: Option<&NullBuffer>,
305    list_size: i32,
306) -> Option<Result<ArrayRef>> {
307    let parent_nulls = parent_nulls.filter(|nulls| nulls.null_count() > 0)?;
308
309    // FixedSizeList stores child slots for null parent lists. Those child
310    // values are semantically hidden, but recursive casts still inspect them.
311    let hidden_child_nulls = parent_nulls.expand(list_size as usize);
312    let masked_values = mask_array_values(source_values, &hidden_child_nulls);
313    Some(masked_values.and_then(|values| cast_column(&values, target_type, cast_options)))
314}
315
316fn mask_array_values(
317    values: &ArrayRef,
318    additional_nulls: &NullBuffer,
319) -> Result<ArrayRef> {
320    let nulls = NullBuffer::union(values.nulls(), Some(additional_nulls));
321
322    if let Some(struct_array) = values.as_any().downcast_ref::<StructArray>() {
323        let struct_nulls = nulls
324            .as_ref()
325            .expect("additional nulls always produce nulls");
326        let arrays = struct_array
327            .columns()
328            .iter()
329            .map(|child| mask_array_values(child, struct_nulls))
330            .collect::<Result<Vec<_>>>()?;
331        return Ok(Arc::new(StructArray::new(
332            struct_array.fields().clone(),
333            arrays,
334            nulls,
335        )));
336    }
337
338    Ok(make_array(
339        values.to_data().into_builder().nulls(nulls).build()?,
340    ))
341}
342
343fn cast_dictionary_column(
344    source_col: &ArrayRef,
345    source_key_type: &DataType,
346    target_key_type: &DataType,
347    target_value_type: &DataType,
348    cast_options: &CastOptions,
349) -> Result<ArrayRef> {
350    // Dispatch on source key type to access keys/values, then recursively
351    // cast values. Rebuild with the source key type first.
352    macro_rules! cast_dict_values {
353        ($t:ty) => {{
354            let source_dict = source_col
355                .as_any()
356                .downcast_ref::<DictionaryArray<$t>>()
357                .expect("downcast must succeed");
358            let cast_values =
359                cast_column(source_dict.values(), target_value_type, cast_options)?;
360            Ok(Arc::new(DictionaryArray::<$t>::new(
361                source_dict.keys().clone(),
362                cast_values,
363            )) as ArrayRef)
364        }};
365    }
366
367    let result: Result<ArrayRef> = downcast_integer! {
368        source_key_type => (cast_dict_values),
369        k => _plan_err!("Unsupported dictionary key type: {k}")
370    };
371    let result = result?;
372
373    // If key types differ, delegate key casting to Arrow.
374    if source_key_type != target_key_type {
375        let target_dict_type = DataType::Dictionary(
376            Box::new(target_key_type.clone()),
377            Box::new(target_value_type.clone()),
378        );
379        Ok(cast_with_options(&result, &target_dict_type, cast_options)?)
380    } else {
381        Ok(result)
382    }
383}
384
385/// Validates compatibility between source and target struct fields for casting operations.
386///
387/// This function implements comprehensive struct compatibility checking by examining:
388/// - Field name matching between source and target structs
389/// - Type castability for each matching field (including recursive struct validation)
390/// - Proper handling of missing fields (target fields not in source are allowed - filled with nulls)
391/// - Proper handling of extra fields (source fields not in target are allowed - ignored)
392///
393/// # Compatibility Rules
394/// - **Field Matching**: Fields are matched by name (case-sensitive)
395/// - **Missing Target Fields**: Allowed - will be filled with null values during casting
396/// - **Extra Source Fields**: Allowed - will be ignored during casting
397/// - **Type Compatibility**: Each matching field must be castable using Arrow's type system
398/// - **Nested Structs**: Recursively validates nested struct compatibility
399///
400/// # Arguments
401/// * `source_fields` - Fields from the source struct type
402/// * `target_fields` - Fields from the target struct type
403///
404/// # Returns
405/// * `Ok(())` if the structs are compatible for casting
406/// * `Err(DataFusionError)` with detailed error message if incompatible
407///
408/// # Examples
409/// ```text
410/// // Compatible: source has extra field, target has missing field
411/// // Source: {a: i32, b: string, c: f64}
412/// // Target: {a: i64, d: bool}
413/// // Result: Ok(()) - 'a' can cast i32->i64, 'b','c' ignored, 'd' filled with nulls
414///
415/// // Incompatible: matching field has incompatible types
416/// // Source: {a: string}
417/// // Target: {a: binary}
418/// // Result: Err(...) - string cannot cast to binary
419/// ```
420///
421pub fn validate_struct_compatibility(
422    source_fields: &[FieldRef],
423    target_fields: &[FieldRef],
424) -> Result<()> {
425    let has_overlap = has_one_of_more_common_fields(source_fields, target_fields);
426    if !has_overlap {
427        return _plan_err!(
428            "Cannot cast struct with {} fields to {} fields because there is no field name overlap",
429            source_fields.len(),
430            target_fields.len()
431        );
432    }
433
434    // Check compatibility for each target field
435    for target_field in target_fields {
436        // Look for matching field in source by name
437        if let Some(source_field) = source_fields
438            .iter()
439            .find(|f| f.name() == target_field.name())
440        {
441            validate_field_compatibility(source_field, target_field)?;
442        } else {
443            // Target field is missing from source
444            // If it's non-nullable, we cannot fill it with NULL
445            if !target_field.is_nullable() {
446                return _plan_err!(
447                    "Cannot cast struct: target field '{}' is non-nullable but missing from source. \
448                     Cannot fill with NULL.",
449                    target_field.name()
450                );
451            }
452        }
453    }
454
455    // Extra fields in source are OK - they'll be ignored
456    Ok(())
457}
458
459fn validate_field_compatibility(
460    source_field: &Field,
461    target_field: &Field,
462) -> Result<()> {
463    if source_field.data_type() == &DataType::Null {
464        // Validate that target allows nulls before returning early.
465        // It is invalid to cast a NULL source field to a non-nullable target field.
466        if !target_field.is_nullable() {
467            return _plan_err!(
468                "Cannot cast NULL struct field '{}' to non-nullable field '{}'",
469                source_field.name(),
470                target_field.name()
471            );
472        }
473        return Ok(());
474    }
475
476    // Ensure nullability is compatible. It is invalid to cast a nullable
477    // source field to a non-nullable target field as this may discard
478    // null values.
479    if source_field.is_nullable() && !target_field.is_nullable() {
480        return _plan_err!(
481            "Cannot cast nullable struct field '{}' to non-nullable field",
482            target_field.name()
483        );
484    }
485
486    validate_data_type_compatibility(
487        target_field.name(),
488        source_field.data_type(),
489        target_field.data_type(),
490    )
491}
492
493/// Validates that `source_type` can be cast to `target_type`, recursively
494/// handling container types that wrap structs.
495pub fn validate_data_type_compatibility(
496    field_name: &str,
497    source_type: &DataType,
498    target_type: &DataType,
499) -> Result<()> {
500    match (source_type, target_type) {
501        (Struct(source_nested), Struct(target_nested)) => {
502            validate_struct_compatibility(source_nested, target_nested)?;
503        }
504        (
505            DataType::FixedSizeList(s, source_list_size),
506            DataType::FixedSizeList(t, target_list_size),
507        ) if source_list_size == target_list_size => {
508            validate_field_compatibility(s, t)?;
509        }
510        (DataType::List(s), DataType::List(t))
511        | (DataType::LargeList(s), DataType::LargeList(t))
512        | (DataType::ListView(s), DataType::ListView(t))
513        | (DataType::LargeListView(s), DataType::LargeListView(t)) => {
514            validate_field_compatibility(s, t)?;
515        }
516        (DataType::Dictionary(s_key, s_val), DataType::Dictionary(t_key, t_val)) => {
517            if !can_cast_types(s_key, t_key) {
518                return _plan_err!(
519                    "Cannot cast dictionary key type {} to {} for field '{}'",
520                    s_key,
521                    t_key,
522                    field_name
523                );
524            }
525            validate_data_type_compatibility(field_name, s_val, t_val)?;
526        }
527        _ => {
528            if !can_cast_types(source_type, target_type) {
529                return _plan_err!(
530                    "Cannot cast struct field '{}' from type {} to type {}",
531                    field_name,
532                    source_type,
533                    target_type
534                );
535            }
536        }
537    }
538    Ok(())
539}
540
541/// Returns true if casting from `source_type` to `target_type` requires
542/// name-based nested struct casting logic, rather than Arrow's standard cast.
543///
544/// This is the case when both types are struct types, or both are the same
545/// container type (List, LargeList, equal-width FixedSizeList, ListView,
546/// LargeListView, Dictionary) wrapping types that recursively contain structs.
547///
548/// Use this predicate at both planning time (to decide whether to apply struct
549/// compatibility validation) and execution time (to decide whether to route
550/// through [`cast_column`] instead of Arrow's generic cast).
551pub fn requires_nested_struct_cast(
552    source_type: &DataType,
553    target_type: &DataType,
554) -> bool {
555    match (source_type, target_type) {
556        (Struct(_), Struct(_)) => true,
557        (
558            DataType::FixedSizeList(s, source_list_size),
559            DataType::FixedSizeList(t, target_list_size),
560        ) if source_list_size == target_list_size => {
561            requires_nested_struct_cast(s.data_type(), t.data_type())
562        }
563        (DataType::List(s), DataType::List(t))
564        | (DataType::LargeList(s), DataType::LargeList(t))
565        | (DataType::ListView(s), DataType::ListView(t))
566        | (DataType::LargeListView(s), DataType::LargeListView(t)) => {
567            requires_nested_struct_cast(s.data_type(), t.data_type())
568        }
569        (DataType::Dictionary(_, s_val), DataType::Dictionary(_, t_val)) => {
570            requires_nested_struct_cast(s_val, t_val)
571        }
572        _ => false,
573    }
574}
575
576/// Check if two field lists have at least one common field by name.
577///
578/// This is useful for validating struct compatibility when casting between structs,
579/// ensuring that source and target fields have overlapping names.
580pub fn has_one_of_more_common_fields(
581    source_fields: &[FieldRef],
582    target_fields: &[FieldRef],
583) -> bool {
584    let source_names: HashSet<&str> = source_fields
585        .iter()
586        .map(|field| field.name().as_str())
587        .collect();
588    target_fields
589        .iter()
590        .any(|field| source_names.contains(field.name().as_str()))
591}
592
593#[cfg(test)]
594mod tests {
595    use super::*;
596    use crate::{assert_contains, format::DEFAULT_CAST_OPTIONS};
597    use arrow::{
598        array::{
599            BinaryArray, FixedSizeListArray, Int32Array, Int32Builder, Int64Array,
600            ListArray, ListViewArray, MapArray, MapBuilder, NullArray, StringArray,
601            StringBuilder,
602        },
603        buffer::{NullBuffer, ScalarBuffer},
604        datatypes::{DataType, Field, FieldRef, Int32Type},
605    };
606    /// Macro to extract and downcast a column from a StructArray
607    macro_rules! get_column_as {
608        ($struct_array:expr, $column_name:expr, $array_type:ty) => {
609            $struct_array
610                .column_by_name($column_name)
611                .unwrap()
612                .as_any()
613                .downcast_ref::<$array_type>()
614                .unwrap()
615        };
616    }
617
618    fn field(name: &str, data_type: DataType) -> Field {
619        Field::new(name, data_type, true)
620    }
621
622    fn non_null_field(name: &str, data_type: DataType) -> Field {
623        Field::new(name, data_type, false)
624    }
625
626    fn arc_field(name: &str, data_type: DataType) -> FieldRef {
627        Arc::new(field(name, data_type))
628    }
629
630    fn struct_type(fields: Vec<Field>) -> DataType {
631        Struct(fields.into())
632    }
633
634    fn struct_field(name: &str, fields: Vec<Field>) -> Field {
635        field(name, struct_type(fields))
636    }
637
638    fn arc_struct_field(name: &str, fields: Vec<Field>) -> FieldRef {
639        Arc::new(struct_field(name, fields))
640    }
641
642    #[test]
643    fn test_cast_simple_column() {
644        let source = Arc::new(Int32Array::from(vec![1, 2, 3])) as ArrayRef;
645        let target_field = field("ints", DataType::Int64);
646        let result =
647            cast_column(&source, target_field.data_type(), &DEFAULT_CAST_OPTIONS)
648                .unwrap();
649        let result = result.as_any().downcast_ref::<Int64Array>().unwrap();
650        assert_eq!(result.len(), 3);
651        assert_eq!(result.value(0), 1);
652        assert_eq!(result.value(1), 2);
653        assert_eq!(result.value(2), 3);
654    }
655
656    #[test]
657    fn test_cast_column_with_options() {
658        let source = Arc::new(Int64Array::from(vec![1, i64::MAX])) as ArrayRef;
659        let target_field = field("ints", DataType::Int32);
660
661        let safe_opts = CastOptions {
662            // safe: false - return Err for failure
663            safe: false,
664            ..DEFAULT_CAST_OPTIONS
665        };
666        assert!(cast_column(&source, target_field.data_type(), &safe_opts).is_err());
667
668        let unsafe_opts = CastOptions {
669            // safe: true - return Null for failure
670            safe: true,
671            ..DEFAULT_CAST_OPTIONS
672        };
673        let result =
674            cast_column(&source, target_field.data_type(), &unsafe_opts).unwrap();
675        let result = result.as_any().downcast_ref::<Int32Array>().unwrap();
676        assert_eq!(result.value(0), 1);
677        assert!(result.is_null(1));
678    }
679
680    #[test]
681    fn test_cast_struct_with_missing_field() {
682        let a_array = Arc::new(Int32Array::from(vec![1, 2])) as ArrayRef;
683        let source_struct = StructArray::from(vec![(
684            arc_field("a", DataType::Int32),
685            Arc::clone(&a_array),
686        )]);
687        let source_col = Arc::new(source_struct) as ArrayRef;
688
689        let target_field = struct_field(
690            "s",
691            vec![field("a", DataType::Int32), field("b", DataType::Utf8)],
692        );
693
694        let result =
695            cast_column(&source_col, target_field.data_type(), &DEFAULT_CAST_OPTIONS)
696                .unwrap();
697        let struct_array = result.as_any().downcast_ref::<StructArray>().unwrap();
698        assert_eq!(struct_array.fields().len(), 2);
699        let a_result = get_column_as!(&struct_array, "a", Int32Array);
700        assert_eq!(a_result.value(0), 1);
701        assert_eq!(a_result.value(1), 2);
702
703        let b_result = get_column_as!(&struct_array, "b", StringArray);
704        assert_eq!(b_result.len(), 2);
705        assert!(b_result.is_null(0));
706        assert!(b_result.is_null(1));
707    }
708
709    #[test]
710    fn test_cast_struct_source_not_struct() {
711        let source = Arc::new(Int32Array::from(vec![10, 20])) as ArrayRef;
712        let target_field = struct_field("s", vec![field("a", DataType::Int32)]);
713
714        let result =
715            cast_column(&source, target_field.data_type(), &DEFAULT_CAST_OPTIONS);
716        assert!(result.is_err());
717        let error_msg = result.unwrap_err().to_string();
718        assert!(error_msg.contains("Cannot cast column of type"));
719        assert!(error_msg.contains("to struct type"));
720        assert!(error_msg.contains("Source must be a struct"));
721    }
722
723    #[test]
724    fn test_cast_struct_incompatible_child_type() {
725        let a_array = Arc::new(BinaryArray::from(vec![
726            Some(b"a".as_ref()),
727            Some(b"b".as_ref()),
728        ])) as ArrayRef;
729        let source_struct =
730            StructArray::from(vec![(arc_field("a", DataType::Binary), a_array)]);
731        let source_col = Arc::new(source_struct) as ArrayRef;
732
733        let target_field = struct_field("s", vec![field("a", DataType::Int32)]);
734
735        let result =
736            cast_column(&source_col, target_field.data_type(), &DEFAULT_CAST_OPTIONS);
737        assert!(result.is_err());
738        let error_msg = result.unwrap_err().to_string();
739        assert!(error_msg.contains("Cannot cast struct field 'a'"));
740    }
741
742    #[test]
743    fn test_validate_struct_compatibility_incompatible_types() {
744        // Source struct: {field1: Binary, field2: String}
745        let source_fields = vec![
746            arc_field("field1", DataType::Binary),
747            arc_field("field2", DataType::Utf8),
748        ];
749
750        // Target struct: {field1: Int32}
751        let target_fields = vec![arc_field("field1", DataType::Int32)];
752
753        let result = validate_struct_compatibility(&source_fields, &target_fields);
754        assert!(result.is_err());
755        let error_msg = result.unwrap_err().to_string();
756        assert!(error_msg.contains("Cannot cast struct field 'field1'"));
757        assert!(error_msg.contains("Binary"));
758        assert!(error_msg.contains("Int32"));
759    }
760
761    #[test]
762    fn test_validate_struct_compatibility_compatible_types() {
763        // Source struct: {field1: Int32, field2: String}
764        let source_fields = vec![
765            arc_field("field1", DataType::Int32),
766            arc_field("field2", DataType::Utf8),
767        ];
768
769        // Target struct: {field1: Int64} (Int32 can cast to Int64)
770        let target_fields = vec![arc_field("field1", DataType::Int64)];
771
772        let result = validate_struct_compatibility(&source_fields, &target_fields);
773        assert!(result.is_ok());
774    }
775
776    #[test]
777    fn test_validate_struct_compatibility_missing_field_in_source() {
778        // Source struct: {field1: Int32} (missing field2)
779        let source_fields = vec![arc_field("field1", DataType::Int32)];
780
781        // Target struct: {field1: Int32, field2: Utf8}
782        let target_fields = vec![
783            arc_field("field1", DataType::Int32),
784            arc_field("field2", DataType::Utf8),
785        ];
786
787        // Should be OK - missing fields will be filled with nulls
788        let result = validate_struct_compatibility(&source_fields, &target_fields);
789        assert!(result.is_ok());
790    }
791
792    #[test]
793    fn test_validate_struct_compatibility_additional_field_in_source() {
794        // Source struct: {field1: Int32, field2: String} (extra field2)
795        let source_fields = vec![
796            arc_field("field1", DataType::Int32),
797            arc_field("field2", DataType::Utf8),
798        ];
799
800        // Target struct: {field1: Int32}
801        let target_fields = vec![arc_field("field1", DataType::Int32)];
802
803        // Should be OK - extra fields in source are ignored
804        let result = validate_struct_compatibility(&source_fields, &target_fields);
805        assert!(result.is_ok());
806    }
807
808    #[test]
809    fn test_validate_struct_compatibility_no_overlap_mismatch_len() {
810        let source_fields = vec![
811            arc_field("left", DataType::Int32),
812            arc_field("right", DataType::Int32),
813        ];
814        let target_fields = vec![arc_field("alpha", DataType::Int32)];
815
816        let result = validate_struct_compatibility(&source_fields, &target_fields);
817        assert!(result.is_err());
818        let error_msg = result.unwrap_err().to_string();
819        assert_contains!(error_msg, "no field name overlap");
820    }
821
822    #[test]
823    fn test_cast_struct_parent_nulls_retained() {
824        let a_array = Arc::new(Int32Array::from(vec![Some(1), Some(2)])) as ArrayRef;
825        let fields = vec![arc_field("a", DataType::Int32)];
826        let nulls = Some(NullBuffer::from(vec![true, false]));
827        let source_struct = StructArray::new(fields.clone().into(), vec![a_array], nulls);
828        let source_col = Arc::new(source_struct) as ArrayRef;
829
830        let target_field = struct_field("s", vec![field("a", DataType::Int64)]);
831
832        let result =
833            cast_column(&source_col, target_field.data_type(), &DEFAULT_CAST_OPTIONS)
834                .unwrap();
835        let struct_array = result.as_any().downcast_ref::<StructArray>().unwrap();
836        assert_eq!(struct_array.null_count(), 1);
837        assert!(struct_array.is_valid(0));
838        assert!(struct_array.is_null(1));
839
840        let a_result = get_column_as!(&struct_array, "a", Int64Array);
841        assert_eq!(a_result.value(0), 1);
842        assert_eq!(a_result.value(1), 2);
843    }
844
845    #[test]
846    fn test_validate_struct_compatibility_nullable_to_non_nullable() {
847        // Source struct: {field1: Int32 nullable}
848        let source_fields = vec![arc_field("field1", DataType::Int32)];
849
850        // Target struct: {field1: Int32 non-nullable}
851        let target_fields = vec![Arc::new(non_null_field("field1", DataType::Int32))];
852
853        let result = validate_struct_compatibility(&source_fields, &target_fields);
854        assert!(result.is_err());
855        let error_msg = result.unwrap_err().to_string();
856        assert!(error_msg.contains("field1"));
857        assert!(error_msg.contains("non-nullable"));
858    }
859
860    #[test]
861    fn test_validate_struct_compatibility_non_nullable_to_nullable() {
862        // Source struct: {field1: Int32 non-nullable}
863        let source_fields = vec![Arc::new(non_null_field("field1", DataType::Int32))];
864
865        // Target struct: {field1: Int32 nullable}
866        let target_fields = vec![arc_field("field1", DataType::Int32)];
867
868        let result = validate_struct_compatibility(&source_fields, &target_fields);
869        assert!(result.is_ok());
870    }
871
872    #[test]
873    fn test_validate_struct_compatibility_nested_nullable_to_non_nullable() {
874        // Source struct: {field1: {nested: Int32 nullable}}
875        let source_fields = vec![Arc::new(non_null_field(
876            "field1",
877            struct_type(vec![field("nested", DataType::Int32)]),
878        ))];
879
880        // Target struct: {field1: {nested: Int32 non-nullable}}
881        let target_fields = vec![Arc::new(non_null_field(
882            "field1",
883            struct_type(vec![non_null_field("nested", DataType::Int32)]),
884        ))];
885
886        let result = validate_struct_compatibility(&source_fields, &target_fields);
887        assert!(result.is_err());
888        let error_msg = result.unwrap_err().to_string();
889        assert!(error_msg.contains("nested"));
890        assert!(error_msg.contains("non-nullable"));
891    }
892
893    #[test]
894    fn test_validate_struct_compatibility_by_name() {
895        // Source struct: {field1: Int32, field2: String}
896        let source_fields = vec![
897            arc_field("field1", DataType::Int32),
898            arc_field("field2", DataType::Utf8),
899        ];
900
901        // Target struct: {field2: String, field1: Int64}
902        let target_fields = vec![
903            arc_field("field2", DataType::Utf8),
904            arc_field("field1", DataType::Int64),
905        ];
906
907        let result = validate_struct_compatibility(&source_fields, &target_fields);
908        assert!(result.is_ok());
909    }
910
911    #[test]
912    fn test_validate_struct_compatibility_by_name_with_type_mismatch() {
913        // Source struct: {field1: Binary}
914        let source_fields = vec![arc_field("field1", DataType::Binary)];
915
916        // Target struct: {field1: Int32} (incompatible type)
917        let target_fields = vec![arc_field("field1", DataType::Int32)];
918
919        let result = validate_struct_compatibility(&source_fields, &target_fields);
920        assert!(result.is_err());
921        let error_msg = result.unwrap_err().to_string();
922        assert_contains!(
923            error_msg,
924            "Cannot cast struct field 'field1' from type Binary to type Int32"
925        );
926    }
927
928    #[test]
929    fn test_validate_struct_compatibility_no_overlap_equal_len() {
930        let source_fields = vec![
931            arc_field("left", DataType::Int32),
932            arc_field("right", DataType::Utf8),
933        ];
934
935        let target_fields = vec![
936            arc_field("alpha", DataType::Int32),
937            arc_field("beta", DataType::Utf8),
938        ];
939
940        let result = validate_struct_compatibility(&source_fields, &target_fields);
941        assert!(result.is_err());
942        let error_msg = result.unwrap_err().to_string();
943        assert_contains!(error_msg, "no field name overlap");
944    }
945
946    #[test]
947    fn test_validate_struct_compatibility_mixed_name_overlap() {
948        // Source struct: {a: Int32, b: String, extra: Boolean}
949        let source_fields = vec![
950            arc_field("a", DataType::Int32),
951            arc_field("b", DataType::Utf8),
952            arc_field("extra", DataType::Boolean),
953        ];
954
955        // Target struct: {b: String, a: Int64, c: Float32}
956        // Name overlap with a and b, missing c (nullable)
957        let target_fields = vec![
958            arc_field("b", DataType::Utf8),
959            arc_field("a", DataType::Int64),
960            arc_field("c", DataType::Float32),
961        ];
962
963        let result = validate_struct_compatibility(&source_fields, &target_fields);
964        assert!(result.is_ok());
965    }
966
967    #[test]
968    fn test_validate_struct_compatibility_by_name_missing_required_field() {
969        // Source struct: {field1: Int32} (missing field2)
970        let source_fields = vec![arc_field("field1", DataType::Int32)];
971
972        // Target struct: {field1: Int32, field2: Int32 non-nullable}
973        let target_fields = vec![
974            arc_field("field1", DataType::Int32),
975            Arc::new(non_null_field("field2", DataType::Int32)),
976        ];
977
978        let result = validate_struct_compatibility(&source_fields, &target_fields);
979        assert!(result.is_err());
980        let error_msg = result.unwrap_err().to_string();
981        assert_contains!(
982            error_msg,
983            "Cannot cast struct: target field 'field2' is non-nullable but missing from source. Cannot fill with NULL."
984        );
985    }
986
987    #[test]
988    fn test_validate_struct_compatibility_partial_name_overlap_with_count_mismatch() {
989        // Source struct: {a: Int32} (only one field)
990        let source_fields = vec![arc_field("a", DataType::Int32)];
991
992        // Target struct: {a: Int32, b: String} (two fields, but 'a' overlaps)
993        let target_fields = vec![
994            arc_field("a", DataType::Int32),
995            arc_field("b", DataType::Utf8),
996        ];
997
998        // This should succeed - partial overlap means by-name mapping
999        // and missing field 'b' is nullable
1000        let result = validate_struct_compatibility(&source_fields, &target_fields);
1001        assert!(result.is_ok());
1002    }
1003
1004    #[test]
1005    fn test_cast_nested_struct_with_extra_and_missing_fields() {
1006        // Source inner struct has fields a, b, extra
1007        let a = Arc::new(Int32Array::from(vec![Some(1), None])) as ArrayRef;
1008        let b = Arc::new(Int32Array::from(vec![Some(2), Some(3)])) as ArrayRef;
1009        let extra = Arc::new(Int32Array::from(vec![Some(9), Some(10)])) as ArrayRef;
1010
1011        let inner = StructArray::from(vec![
1012            (arc_field("a", DataType::Int32), a),
1013            (arc_field("b", DataType::Int32), b),
1014            (arc_field("extra", DataType::Int32), extra),
1015        ]);
1016
1017        let source_struct = StructArray::from(vec![(
1018            arc_struct_field(
1019                "inner",
1020                vec![
1021                    field("a", DataType::Int32),
1022                    field("b", DataType::Int32),
1023                    field("extra", DataType::Int32),
1024                ],
1025            ),
1026            Arc::new(inner) as ArrayRef,
1027        )]);
1028        let source_col = Arc::new(source_struct) as ArrayRef;
1029
1030        // Target inner struct reorders fields, adds "missing", and drops "extra"
1031        let target_field = struct_field(
1032            "outer",
1033            vec![struct_field(
1034                "inner",
1035                vec![
1036                    field("b", DataType::Int64),
1037                    field("a", DataType::Int32),
1038                    field("missing", DataType::Int32),
1039                ],
1040            )],
1041        );
1042
1043        let result =
1044            cast_column(&source_col, target_field.data_type(), &DEFAULT_CAST_OPTIONS)
1045                .unwrap();
1046        let outer = result.as_any().downcast_ref::<StructArray>().unwrap();
1047        let inner = get_column_as!(&outer, "inner", StructArray);
1048        assert_eq!(inner.fields().len(), 3);
1049
1050        let b = get_column_as!(inner, "b", Int64Array);
1051        assert_eq!(b.value(0), 2);
1052        assert_eq!(b.value(1), 3);
1053        assert!(!b.is_null(0));
1054        assert!(!b.is_null(1));
1055
1056        let a = get_column_as!(inner, "a", Int32Array);
1057        assert_eq!(a.value(0), 1);
1058        assert!(a.is_null(1));
1059
1060        let missing = get_column_as!(inner, "missing", Int32Array);
1061        assert!(missing.is_null(0));
1062        assert!(missing.is_null(1));
1063    }
1064
1065    #[test]
1066    fn test_cast_null_struct_field_to_nested_struct() {
1067        let null_inner = Arc::new(NullArray::new(2)) as ArrayRef;
1068        let source_struct = StructArray::from(vec![(
1069            arc_field("inner", DataType::Null),
1070            Arc::clone(&null_inner),
1071        )]);
1072        let source_col = Arc::new(source_struct) as ArrayRef;
1073
1074        let target_field = struct_field(
1075            "outer",
1076            vec![struct_field("inner", vec![field("a", DataType::Int32)])],
1077        );
1078
1079        let result =
1080            cast_column(&source_col, target_field.data_type(), &DEFAULT_CAST_OPTIONS)
1081                .unwrap();
1082        let outer = result.as_any().downcast_ref::<StructArray>().unwrap();
1083        let inner = get_column_as!(&outer, "inner", StructArray);
1084        assert_eq!(inner.len(), 2);
1085        assert!(inner.is_null(0));
1086        assert!(inner.is_null(1));
1087
1088        let inner_a = get_column_as!(inner, "a", Int32Array);
1089        assert!(inner_a.is_null(0));
1090        assert!(inner_a.is_null(1));
1091    }
1092
1093    #[test]
1094    fn test_cast_struct_with_array_and_map_fields() {
1095        // Array field with second row null
1096        let arr_array = Arc::new(ListArray::from_iter_primitive::<Int32Type, _, _>(vec![
1097            Some(vec![Some(1), Some(2)]),
1098            None,
1099        ])) as ArrayRef;
1100
1101        // Map field with second row null
1102        let string_builder = StringBuilder::new();
1103        let int_builder = Int32Builder::new();
1104        let mut map_builder = MapBuilder::new(None, string_builder, int_builder);
1105        map_builder.keys().append_value("a");
1106        map_builder.values().append_value(1);
1107        map_builder.append(true).unwrap();
1108        map_builder.append(false).unwrap();
1109        let map_array = Arc::new(map_builder.finish()) as ArrayRef;
1110
1111        let source_struct = StructArray::from(vec![
1112            (
1113                arc_field(
1114                    "arr",
1115                    DataType::List(Arc::new(field("item", DataType::Int32))),
1116                ),
1117                arr_array,
1118            ),
1119            (
1120                arc_field(
1121                    "map",
1122                    DataType::Map(
1123                        Arc::new(non_null_field(
1124                            "entries",
1125                            struct_type(vec![
1126                                non_null_field("keys", DataType::Utf8),
1127                                field("values", DataType::Int32),
1128                            ]),
1129                        )),
1130                        false,
1131                    ),
1132                ),
1133                map_array,
1134            ),
1135        ]);
1136        let source_col = Arc::new(source_struct) as ArrayRef;
1137
1138        let target_field = struct_field(
1139            "s",
1140            vec![
1141                field(
1142                    "arr",
1143                    DataType::List(Arc::new(field("item", DataType::Int32))),
1144                ),
1145                field(
1146                    "map",
1147                    DataType::Map(
1148                        Arc::new(non_null_field(
1149                            "entries",
1150                            struct_type(vec![
1151                                non_null_field("keys", DataType::Utf8),
1152                                field("values", DataType::Int32),
1153                            ]),
1154                        )),
1155                        false,
1156                    ),
1157                ),
1158            ],
1159        );
1160
1161        let result =
1162            cast_column(&source_col, target_field.data_type(), &DEFAULT_CAST_OPTIONS)
1163                .unwrap();
1164        let struct_array = result.as_any().downcast_ref::<StructArray>().unwrap();
1165
1166        let arr = get_column_as!(&struct_array, "arr", ListArray);
1167        assert!(!arr.is_null(0));
1168        assert!(arr.is_null(1));
1169        let arr0 = arr.value(0);
1170        let values = arr0.as_any().downcast_ref::<Int32Array>().unwrap();
1171        assert_eq!(values.value(0), 1);
1172        assert_eq!(values.value(1), 2);
1173
1174        let map = get_column_as!(&struct_array, "map", MapArray);
1175        assert!(!map.is_null(0));
1176        assert!(map.is_null(1));
1177        let map0 = map.value(0);
1178        let entries = map0.as_any().downcast_ref::<StructArray>().unwrap();
1179        let keys = get_column_as!(entries, "keys", StringArray);
1180        let vals = get_column_as!(entries, "values", Int32Array);
1181        assert_eq!(keys.value(0), "a");
1182        assert_eq!(vals.value(0), 1);
1183    }
1184
1185    #[test]
1186    fn test_cast_struct_field_order_differs() {
1187        let a = Arc::new(Int32Array::from(vec![Some(1), Some(2)])) as ArrayRef;
1188        let b = Arc::new(Int32Array::from(vec![Some(3), None])) as ArrayRef;
1189
1190        let source_struct = StructArray::from(vec![
1191            (arc_field("a", DataType::Int32), a),
1192            (arc_field("b", DataType::Int32), b),
1193        ]);
1194        let source_col = Arc::new(source_struct) as ArrayRef;
1195
1196        let target_field = struct_field(
1197            "s",
1198            vec![field("b", DataType::Int64), field("a", DataType::Int32)],
1199        );
1200
1201        let result =
1202            cast_column(&source_col, target_field.data_type(), &DEFAULT_CAST_OPTIONS)
1203                .unwrap();
1204        let struct_array = result.as_any().downcast_ref::<StructArray>().unwrap();
1205
1206        let b_col = get_column_as!(&struct_array, "b", Int64Array);
1207        assert_eq!(b_col.value(0), 3);
1208        assert!(b_col.is_null(1));
1209
1210        let a_col = get_column_as!(&struct_array, "a", Int32Array);
1211        assert_eq!(a_col.value(0), 1);
1212        assert_eq!(a_col.value(1), 2);
1213    }
1214
1215    #[test]
1216    fn test_cast_struct_no_overlap_rejected() {
1217        let first = Arc::new(Int32Array::from(vec![Some(10), Some(20)])) as ArrayRef;
1218        let second =
1219            Arc::new(StringArray::from(vec![Some("alpha"), Some("beta")])) as ArrayRef;
1220
1221        let source_struct = StructArray::from(vec![
1222            (arc_field("left", DataType::Int32), first),
1223            (arc_field("right", DataType::Utf8), second),
1224        ]);
1225        let source_col = Arc::new(source_struct) as ArrayRef;
1226
1227        let target_field = struct_field(
1228            "s",
1229            vec![field("a", DataType::Int64), field("b", DataType::Utf8)],
1230        );
1231
1232        let result =
1233            cast_column(&source_col, target_field.data_type(), &DEFAULT_CAST_OPTIONS);
1234        assert!(result.is_err());
1235        let error_msg = result.unwrap_err().to_string();
1236        assert_contains!(error_msg, "no field name overlap");
1237    }
1238
1239    #[test]
1240    fn test_cast_struct_missing_non_nullable_field_fails() {
1241        // Source has only field 'a'
1242        let a = Arc::new(Int32Array::from(vec![Some(1), Some(2)])) as ArrayRef;
1243        let source_struct = StructArray::from(vec![(arc_field("a", DataType::Int32), a)]);
1244        let source_col = Arc::new(source_struct) as ArrayRef;
1245
1246        // Target has fields 'a' (nullable) and 'b' (non-nullable)
1247        let target_field = struct_field(
1248            "s",
1249            vec![
1250                field("a", DataType::Int32),
1251                non_null_field("b", DataType::Int32),
1252            ],
1253        );
1254
1255        // Should fail because 'b' is non-nullable but missing from source
1256        let result =
1257            cast_column(&source_col, target_field.data_type(), &DEFAULT_CAST_OPTIONS);
1258        assert!(result.is_err());
1259        let err = result.unwrap_err();
1260        assert!(
1261            err.to_string()
1262                .contains("target field 'b' is non-nullable but missing from source"),
1263            "Unexpected error: {err}"
1264        );
1265    }
1266
1267    #[test]
1268    fn test_cast_struct_missing_nullable_field_succeeds() {
1269        // Source has only field 'a'
1270        let a = Arc::new(Int32Array::from(vec![Some(1), Some(2)])) as ArrayRef;
1271        let source_struct = StructArray::from(vec![(arc_field("a", DataType::Int32), a)]);
1272        let source_col = Arc::new(source_struct) as ArrayRef;
1273
1274        // Target has fields 'a' and 'b' (both nullable)
1275        let target_field = struct_field(
1276            "s",
1277            vec![field("a", DataType::Int32), field("b", DataType::Int32)],
1278        );
1279
1280        // Should succeed - 'b' is nullable so can be filled with NULL
1281        let result =
1282            cast_column(&source_col, target_field.data_type(), &DEFAULT_CAST_OPTIONS)
1283                .unwrap();
1284        let struct_array = result.as_any().downcast_ref::<StructArray>().unwrap();
1285
1286        let a_col = get_column_as!(&struct_array, "a", Int32Array);
1287        assert_eq!(a_col.value(0), 1);
1288        assert_eq!(a_col.value(1), 2);
1289
1290        let b_col = get_column_as!(&struct_array, "b", Int32Array);
1291        assert!(b_col.is_null(0));
1292        assert!(b_col.is_null(1));
1293    }
1294
1295    #[test]
1296    fn test_validate_dictionary_value_evolution() {
1297        let source_inner = struct_type(vec![field("a", DataType::Int32)]);
1298        let target_inner = struct_type(vec![
1299            field("a", DataType::Int32),
1300            field("b", DataType::Utf8),
1301        ]);
1302        let source =
1303            DataType::Dictionary(Box::new(DataType::Int32), Box::new(source_inner));
1304        let target =
1305            DataType::Dictionary(Box::new(DataType::Int32), Box::new(target_inner));
1306        assert!(validate_data_type_compatibility("col", &source, &target).is_ok());
1307    }
1308
1309    #[test]
1310    fn test_cast_dictionary_struct_value() {
1311        // Build a Dictionary<Int32, Struct{a: Int32}> and cast to
1312        // Dictionary<Int32, Struct{a: Int64, b: Utf8}> (field added, type widened).
1313        let struct_arr = StructArray::from(vec![(
1314            arc_field("a", DataType::Int32),
1315            Arc::new(Int32Array::from(vec![10, 20])) as ArrayRef,
1316        )]);
1317        // keys: [0, null, 1] mapping into the 2-element struct values array.
1318        let keys = Int32Array::from(vec![Some(0), None, Some(1)]);
1319        let source_dict = DictionaryArray::<Int32Type>::new(keys, Arc::new(struct_arr));
1320        let source_col: ArrayRef = Arc::new(source_dict);
1321
1322        let target_type = DataType::Dictionary(
1323            Box::new(DataType::Int32),
1324            Box::new(struct_type(vec![
1325                field("a", DataType::Int64),
1326                field("b", DataType::Utf8),
1327            ])),
1328        );
1329
1330        let result =
1331            cast_column(&source_col, &target_type, &DEFAULT_CAST_OPTIONS).unwrap();
1332        let result_dict = result
1333            .as_any()
1334            .downcast_ref::<DictionaryArray<Int32Type>>()
1335            .unwrap();
1336
1337        assert!(result_dict.is_valid(0));
1338        assert!(result_dict.is_null(1));
1339        assert!(result_dict.is_valid(2));
1340
1341        let struct_values = result_dict
1342            .values()
1343            .as_any()
1344            .downcast_ref::<StructArray>()
1345            .unwrap();
1346        let a_col = get_column_as!(&struct_values, "a", Int64Array);
1347        assert_eq!(a_col.values(), &[10, 20]);
1348        let b_col = get_column_as!(&struct_values, "b", StringArray);
1349        assert!(b_col.iter().all(|v| v.is_none()));
1350    }
1351
1352    #[test]
1353    fn test_cast_list_view_struct() {
1354        // Build a ListView<Struct{a: Int32}> and cast to
1355        // ListView<Struct{a: Int64, b: Utf8}>.
1356        let struct_arr = StructArray::from(vec![(
1357            arc_field("a", DataType::Int32),
1358            Arc::new(Int32Array::from(vec![1, 2, 3])) as ArrayRef,
1359        )]);
1360
1361        let source_field =
1362            arc_field("item", struct_type(vec![field("a", DataType::Int32)]));
1363        let target_field = arc_field(
1364            "item",
1365            struct_type(vec![
1366                field("a", DataType::Int64),
1367                field("b", DataType::Utf8),
1368            ]),
1369        );
1370
1371        // Two list-view entries: [0..2] and [2..3]
1372        let list_view = ListViewArray::new(
1373            source_field,
1374            ScalarBuffer::from(vec![0i32, 2]),
1375            ScalarBuffer::from(vec![2i32, 1]),
1376            Arc::new(struct_arr),
1377            None,
1378        );
1379        let source_col: ArrayRef = Arc::new(list_view);
1380
1381        let target_type = DataType::ListView(target_field);
1382
1383        let result =
1384            cast_column(&source_col, &target_type, &DEFAULT_CAST_OPTIONS).unwrap();
1385        let result_lv = result.as_any().downcast_ref::<ListViewArray>().unwrap();
1386        assert_eq!(result_lv.len(), 2);
1387
1388        let struct_values = result_lv
1389            .values()
1390            .as_any()
1391            .downcast_ref::<StructArray>()
1392            .unwrap();
1393        let a_col = get_column_as!(&struct_values, "a", Int64Array);
1394        assert_eq!(a_col.values(), &[1, 2, 3]);
1395        let b_col = get_column_as!(&struct_values, "b", StringArray);
1396        assert!(b_col.iter().all(|v| v.is_none()));
1397    }
1398
1399    fn fixed_size_list_struct_field(fields: Vec<(&str, DataType)>) -> FieldRef {
1400        arc_field(
1401            "item",
1402            struct_type(
1403                fields
1404                    .into_iter()
1405                    .map(|(name, data_type)| field(name, data_type))
1406                    .collect(),
1407            ),
1408        )
1409    }
1410
1411    fn create_fixed_size_list_test_fields(
1412        source_struct_fields: Vec<(&str, DataType)>,
1413        target_struct_fields: Vec<(&str, DataType)>,
1414    ) -> (FieldRef, FieldRef) {
1415        (
1416            fixed_size_list_struct_field(source_struct_fields),
1417            fixed_size_list_struct_field(target_struct_fields),
1418        )
1419    }
1420
1421    fn fixed_size_list_struct_values(
1422        array: &ArrayRef,
1423    ) -> (&FixedSizeListArray, &StructArray) {
1424        let list = array.as_any().downcast_ref::<FixedSizeListArray>().unwrap();
1425        let values = list
1426            .values()
1427            .as_any()
1428            .downcast_ref::<StructArray>()
1429            .unwrap();
1430        (list, values)
1431    }
1432
1433    #[test]
1434    fn test_cast_fixed_size_list_struct() {
1435        let struct_arr = StructArray::from(vec![(
1436            arc_field("a", DataType::Int32),
1437            Arc::new(Int32Array::from(vec![1, 2, 3, 4])) as ArrayRef,
1438        )]);
1439
1440        let (source_field, target_field) = create_fixed_size_list_test_fields(
1441            vec![("a", DataType::Int32)],
1442            vec![("a", DataType::Int64), ("b", DataType::Utf8)],
1443        );
1444        let source_col: ArrayRef = Arc::new(FixedSizeListArray::new(
1445            source_field,
1446            2,
1447            Arc::new(struct_arr),
1448            Some(NullBuffer::from(vec![true, false])),
1449        ));
1450        let target_type = DataType::FixedSizeList(target_field, 2);
1451
1452        let result =
1453            cast_column(&source_col, &target_type, &DEFAULT_CAST_OPTIONS).unwrap();
1454        let (result_list, struct_values) = fixed_size_list_struct_values(&result);
1455        assert_eq!(result_list.len(), 2);
1456        assert!(result_list.is_valid(0));
1457        assert!(result_list.is_null(1));
1458        let a_col = get_column_as!(&struct_values, "a", Int64Array);
1459        assert_eq!(a_col.values(), &[1, 2, 3, 4]);
1460        let b_col = get_column_as!(&struct_values, "b", StringArray);
1461        assert!(b_col.iter().all(|v| v.is_none()));
1462    }
1463
1464    #[test]
1465    fn test_validate_fixed_size_list_struct_compatibility() {
1466        let (source_field, target_field) = create_fixed_size_list_test_fields(
1467            vec![("a", DataType::Int32)],
1468            vec![("a", DataType::Int64), ("b", DataType::Utf8)],
1469        );
1470        let source = DataType::FixedSizeList(source_field, 2);
1471        let target = DataType::FixedSizeList(target_field, 2);
1472
1473        assert!(requires_nested_struct_cast(&source, &target));
1474        assert!(validate_data_type_compatibility("col", &source, &target).is_ok());
1475    }
1476
1477    #[test]
1478    fn test_validate_fixed_size_list_struct_missing_non_nullable_field_rejected() {
1479        let (source_field, _) = create_fixed_size_list_test_fields(
1480            vec![("a", DataType::Int32)],
1481            vec![("a", DataType::Int64), ("b", DataType::Utf8)],
1482        );
1483        let source = DataType::FixedSizeList(source_field, 2);
1484        let target = DataType::FixedSizeList(
1485            arc_field(
1486                "item",
1487                struct_type(vec![
1488                    field("a", DataType::Int32),
1489                    non_null_field("b", DataType::Utf8),
1490                ]),
1491            ),
1492            2,
1493        );
1494
1495        let error = validate_data_type_compatibility("col", &source, &target)
1496            .unwrap_err()
1497            .to_string();
1498        assert_contains!(
1499            error,
1500            "target field 'b' is non-nullable but missing from source"
1501        );
1502    }
1503
1504    #[test]
1505    fn test_fixed_size_list_struct_size_mismatch_rejected() {
1506        let source_field = fixed_size_list_struct_field(vec![("a", DataType::Int32)]);
1507        let target_field = Arc::clone(&source_field);
1508        let source_type = DataType::FixedSizeList(Arc::clone(&source_field), 2);
1509        let target_type = DataType::FixedSizeList(target_field, 3);
1510
1511        let validation_error =
1512            validate_data_type_compatibility("col", &source_type, &target_type)
1513                .unwrap_err()
1514                .to_string();
1515        assert_contains!(validation_error, "Cannot cast struct field 'col'");
1516
1517        let struct_arr = StructArray::from(vec![(
1518            arc_field("a", DataType::Int32),
1519            Arc::new(Int32Array::from(vec![1, 2])) as ArrayRef,
1520        )]);
1521        let source_col: ArrayRef = Arc::new(FixedSizeListArray::new(
1522            source_field,
1523            2,
1524            Arc::new(struct_arr),
1525            None,
1526        ));
1527
1528        let runtime_error = cast_column(&source_col, &target_type, &DEFAULT_CAST_OPTIONS)
1529            .unwrap_err()
1530            .to_string();
1531        assert_contains!(
1532            runtime_error,
1533            "cannot cast fixed-size-list to fixed-size-list with different size"
1534        );
1535    }
1536
1537    #[test]
1538    fn test_cast_fixed_size_list_struct_all_null() {
1539        let (source_field, target_field) = create_fixed_size_list_test_fields(
1540            vec![("a", DataType::Int32)],
1541            vec![("a", DataType::Int64), ("b", DataType::Utf8)],
1542        );
1543        let source_col: ArrayRef =
1544            Arc::new(FixedSizeListArray::new_null(source_field, 2, 2));
1545        let target_type = DataType::FixedSizeList(target_field, 2);
1546
1547        let result =
1548            cast_column(&source_col, &target_type, &DEFAULT_CAST_OPTIONS).unwrap();
1549        let (result_list, struct_values) = fixed_size_list_struct_values(&result);
1550        assert_eq!(result_list.null_count(), 2);
1551        let a_col = get_column_as!(&struct_values, "a", Int64Array);
1552        let b_col = get_column_as!(&struct_values, "b", StringArray);
1553        assert!(a_col.iter().all(|v| v.is_none()));
1554        assert!(b_col.iter().all(|v| v.is_none()));
1555    }
1556
1557    #[test]
1558    fn test_fixed_size_list_struct_planner_runtime_parity_on_incompatible_type() {
1559        let source_field =
1560            arc_field("item", struct_type(vec![field("a", DataType::Binary)]));
1561        let target_field =
1562            arc_field("item", struct_type(vec![field("a", DataType::Int32)]));
1563        let source_type = DataType::FixedSizeList(Arc::clone(&source_field), 2);
1564        let target_type = DataType::FixedSizeList(target_field, 2);
1565        let validation_error =
1566            validate_data_type_compatibility("col", &source_type, &target_type)
1567                .unwrap_err()
1568                .to_string();
1569        assert_contains!(validation_error, "Cannot cast struct field 'a'");
1570
1571        let struct_arr = StructArray::from(vec![(
1572            arc_field("a", DataType::Binary),
1573            Arc::new(BinaryArray::from(vec![
1574                Some(b"x".as_ref()),
1575                Some(b"y".as_ref()),
1576            ])) as ArrayRef,
1577        )]);
1578        let source_col: ArrayRef = Arc::new(FixedSizeListArray::new(
1579            source_field,
1580            2,
1581            Arc::new(struct_arr),
1582            None,
1583        ));
1584
1585        let runtime_error = cast_column(&source_col, &target_type, &DEFAULT_CAST_OPTIONS)
1586            .unwrap_err()
1587            .to_string();
1588        assert_contains!(runtime_error, "Cannot cast struct field 'a'");
1589    }
1590
1591    #[test]
1592    fn test_cast_fixed_size_list_struct_missing_non_nullable_field_runtime_rejected() {
1593        let source_field =
1594            arc_field("item", struct_type(vec![field("a", DataType::Int32)]));
1595        let target_field = arc_field(
1596            "item",
1597            struct_type(vec![
1598                field("a", DataType::Int32),
1599                non_null_field("b", DataType::Utf8),
1600            ]),
1601        );
1602        let source_col: ArrayRef =
1603            Arc::new(FixedSizeListArray::new_null(source_field, 2, 1));
1604        let target_type = DataType::FixedSizeList(target_field, 2);
1605
1606        let error = cast_column(&source_col, &target_type, &DEFAULT_CAST_OPTIONS)
1607            .unwrap_err()
1608            .to_string();
1609        assert_contains!(
1610            error,
1611            "target field 'b' is non-nullable but missing from source"
1612        );
1613    }
1614
1615    #[test]
1616    fn test_cast_fixed_size_list_returns_error_for_non_nullable_child() {
1617        let source_field = Arc::new(Field::new("item", DataType::Int32, true));
1618        let target_field = Arc::new(Field::new("item", DataType::Int32, false));
1619        let source_col: ArrayRef = Arc::new(FixedSizeListArray::new(
1620            source_field,
1621            2,
1622            Arc::new(Int32Array::from(vec![None, Some(1)])),
1623            None,
1624        ));
1625        let target_type = DataType::FixedSizeList(target_field, 2);
1626
1627        let error = cast_column(&source_col, &target_type, &DEFAULT_CAST_OPTIONS)
1628            .unwrap_err()
1629            .to_string();
1630        assert_contains!(error, "Found unmasked nulls for non-nullable");
1631    }
1632
1633    #[test]
1634    fn test_cast_sliced_fixed_size_list_struct_ignores_hidden_child_values() {
1635        let source_field =
1636            arc_field("item", struct_type(vec![field("a", DataType::Utf8)]));
1637        let target_field =
1638            arc_field("item", struct_type(vec![field("a", DataType::Int32)]));
1639        let struct_arr = StructArray::from(vec![(
1640            arc_field("a", DataType::Utf8),
1641            Arc::new(StringArray::from(vec![
1642                "0", "0", "not_int", "also_bad", "1", "2",
1643            ])) as ArrayRef,
1644        )]);
1645        let source_col: ArrayRef = Arc::new(
1646            FixedSizeListArray::new(
1647                source_field,
1648                2,
1649                Arc::new(struct_arr),
1650                Some(NullBuffer::from(vec![true, false, true])),
1651            )
1652            .slice(1, 2),
1653        );
1654        let target_type = DataType::FixedSizeList(target_field, 2);
1655
1656        let result =
1657            cast_column(&source_col, &target_type, &DEFAULT_CAST_OPTIONS).unwrap();
1658        let (result_list, struct_values) = fixed_size_list_struct_values(&result);
1659        assert!(result_list.is_null(0));
1660        assert!(result_list.is_valid(1));
1661        let a_col = get_column_as!(&struct_values, "a", Int32Array);
1662        assert!(a_col.is_null(0));
1663        assert!(a_col.is_null(1));
1664        assert_eq!(a_col.value(2), 1);
1665        assert_eq!(a_col.value(3), 2);
1666    }
1667
1668    #[test]
1669    fn test_requires_nested_struct_cast() {
1670        let s1 = struct_type(vec![field("a", DataType::Int32)]);
1671        let s2 = struct_type(vec![field("a", DataType::Int64)]);
1672
1673        assert!(requires_nested_struct_cast(&s1, &s2));
1674        assert!(requires_nested_struct_cast(
1675            &DataType::List(arc_field("item", s1.clone())),
1676            &DataType::List(arc_field("item", s2.clone())),
1677        ));
1678        assert!(requires_nested_struct_cast(
1679            &DataType::Dictionary(Box::new(DataType::Int32), Box::new(s1.clone())),
1680            &DataType::Dictionary(Box::new(DataType::Int32), Box::new(s2.clone())),
1681        ));
1682        assert!(requires_nested_struct_cast(
1683            &DataType::ListView(arc_field("item", s1.clone())),
1684            &DataType::ListView(arc_field("item", s2.clone())),
1685        ));
1686        assert!(requires_nested_struct_cast(
1687            &DataType::FixedSizeList(arc_field("item", s1), 2),
1688            &DataType::FixedSizeList(arc_field("item", s2), 2),
1689        ));
1690
1691        // Non-struct types should return false.
1692        assert!(!requires_nested_struct_cast(
1693            &DataType::Int32,
1694            &DataType::Int64
1695        ));
1696        assert!(!requires_nested_struct_cast(
1697            &DataType::List(arc_field("item", DataType::Int32)),
1698            &DataType::List(arc_field("item", DataType::Int64)),
1699        ));
1700        assert!(!requires_nested_struct_cast(
1701            &DataType::FixedSizeList(arc_field("item", DataType::Int32), 2),
1702            &DataType::FixedSizeList(arc_field("item", DataType::Int64), 2),
1703        ));
1704    }
1705}