@@ -31,7 +31,7 @@ use std::{collections::HashSet, sync::Arc};
3131///
3232/// ## Field Matching Strategy
3333/// - **By Name**: Source struct fields are matched to target fields by name (case-sensitive)
34- /// - **By Position **: When there is no name overlap and the field counts match, fields are cast by index
34+ /// - **No Positional Mapping **: Structs with no overlapping field names are rejected
3535/// - **Type Adaptation**: When a matching field is found, it is recursively cast to the target field's type
3636/// - **Missing Fields**: Target fields not present in the source are filled with null values
3737/// - **Extra Fields**: Source fields not present in the target are ignored
@@ -67,24 +67,16 @@ fn cast_struct_column(
6767 if let Some ( source_struct) = source_col. as_any ( ) . downcast_ref :: < StructArray > ( ) {
6868 let source_fields = source_struct. fields ( ) ;
6969 validate_struct_compatibility ( source_fields, target_fields) ?;
70- let has_overlap = has_one_of_more_common_fields ( source_fields, target_fields) ;
71-
7270 let mut fields: Vec < Arc < Field > > = Vec :: with_capacity ( target_fields. len ( ) ) ;
7371 let mut arrays: Vec < ArrayRef > = Vec :: with_capacity ( target_fields. len ( ) ) ;
7472 let num_rows = source_col. len ( ) ;
7573
76- // Iterate target fields and pick source child either by name (when fields overlap)
77- // or by position (when there is no name overlap).
78- for ( index, target_child_field) in target_fields. iter ( ) . enumerate ( ) {
74+ // Iterate target fields and pick source child by name when present.
75+ for target_child_field in target_fields. iter ( ) {
7976 fields. push ( Arc :: clone ( target_child_field) ) ;
8077
81- // Determine the source child column: by name when overlapping names exist,
82- // otherwise by position.
83- let source_child_opt: Option < & ArrayRef > = if has_overlap {
84- source_struct. column_by_name ( target_child_field. name ( ) )
85- } else {
86- Some ( source_struct. column ( index) )
87- } ;
78+ let source_child_opt =
79+ source_struct. column_by_name ( target_child_field. name ( ) ) ;
8880
8981 match source_child_opt {
9082 Some ( source_child_col) => {
@@ -230,20 +222,11 @@ pub fn validate_struct_compatibility(
230222) -> Result < ( ) > {
231223 let has_overlap = has_one_of_more_common_fields ( source_fields, target_fields) ;
232224 if !has_overlap {
233- if source_fields. len ( ) != target_fields. len ( ) {
234- return _plan_err ! (
235- "Cannot cast struct with {} fields to {} fields without name overlap; positional mapping is ambiguous" ,
236- source_fields. len( ) ,
237- target_fields. len( )
238- ) ;
239- }
240-
241- for ( source_field, target_field) in source_fields. iter ( ) . zip ( target_fields. iter ( ) )
242- {
243- validate_field_compatibility ( source_field, target_field) ?;
244- }
245-
246- return Ok ( ( ) ) ;
225+ return _plan_err ! (
226+ "Cannot cast struct with {} fields to {} fields because there is no field name overlap" ,
227+ source_fields. len( ) ,
228+ target_fields. len( )
229+ ) ;
247230 }
248231
249232 // Check compatibility for each target field
@@ -323,7 +306,11 @@ fn validate_field_compatibility(
323306 Ok ( ( ) )
324307}
325308
326- fn has_one_of_more_common_fields (
309+ /// Check if two field lists have at least one common field by name.
310+ ///
311+ /// This is useful for validating struct compatibility when casting between structs,
312+ /// ensuring that source and target fields have overlapping names.
313+ pub fn has_one_of_more_common_fields (
327314 source_fields : & [ FieldRef ] ,
328315 target_fields : & [ FieldRef ] ,
329316) -> bool {
@@ -546,7 +533,7 @@ mod tests {
546533 }
547534
548535 #[ test]
549- fn test_validate_struct_compatibility_positional_no_overlap_mismatch_len ( ) {
536+ fn test_validate_struct_compatibility_no_overlap_mismatch_len ( ) {
550537 let source_fields = vec ! [
551538 arc_field( "left" , DataType :: Int32 ) ,
552539 arc_field( "right" , DataType :: Int32 ) ,
@@ -556,7 +543,7 @@ mod tests {
556543 let result = validate_struct_compatibility ( & source_fields, & target_fields) ;
557544 assert ! ( result. is_err( ) ) ;
558545 let error_msg = result. unwrap_err ( ) . to_string ( ) ;
559- assert ! ( error_msg. contains ( "positional mapping is ambiguous" ) ) ;
546+ assert_contains ! ( error_msg, "no field name overlap" ) ;
560547 }
561548
562549 #[ test]
@@ -665,21 +652,21 @@ mod tests {
665652 }
666653
667654 #[ test]
668- fn test_validate_struct_compatibility_positional_with_type_mismatch ( ) {
669- // Source struct: {left: Struct} - nested struct
670- let source_fields =
671- vec ! [ arc_struct_field( "left" , vec![ field( "x" , DataType :: Int32 ) ] ) ] ;
655+ fn test_validate_struct_compatibility_no_overlap_equal_len ( ) {
656+ let source_fields = vec ! [
657+ arc_field( "left" , DataType :: Int32 ) ,
658+ arc_field( "right" , DataType :: Utf8 ) ,
659+ ] ;
672660
673- // Target struct: {alpha: Int32} (no name overlap, incompatible type at position 0)
674- let target_fields = vec ! [ arc_field( "alpha" , DataType :: Int32 ) ] ;
661+ let target_fields = vec ! [
662+ arc_field( "alpha" , DataType :: Int32 ) ,
663+ arc_field( "beta" , DataType :: Utf8 ) ,
664+ ] ;
675665
676666 let result = validate_struct_compatibility ( & source_fields, & target_fields) ;
677667 assert ! ( result. is_err( ) ) ;
678668 let error_msg = result. unwrap_err ( ) . to_string ( ) ;
679- assert_contains ! (
680- error_msg,
681- "Cannot cast struct field 'alpha' from type Struct(\" x\" : Int32) to type Int32"
682- ) ;
669+ assert_contains ! ( error_msg, "no field name overlap" ) ;
683670 }
684671
685672 #[ test]
@@ -948,7 +935,7 @@ mod tests {
948935 }
949936
950937 #[ test]
951- fn test_cast_struct_positional_when_no_overlap ( ) {
938+ fn test_cast_struct_no_overlap_rejected ( ) {
952939 let first = Arc :: new ( Int32Array :: from ( vec ! [ Some ( 10 ) , Some ( 20 ) ] ) ) as ArrayRef ;
953940 let second =
954941 Arc :: new ( StringArray :: from ( vec ! [ Some ( "alpha" ) , Some ( "beta" ) ] ) ) as ArrayRef ;
@@ -964,17 +951,10 @@ mod tests {
964951 vec ! [ field( "a" , DataType :: Int64 ) , field( "b" , DataType :: Utf8 ) ] ,
965952 ) ;
966953
967- let result =
968- cast_column ( & source_col, & target_field, & DEFAULT_CAST_OPTIONS ) . unwrap ( ) ;
969- let struct_array = result. as_any ( ) . downcast_ref :: < StructArray > ( ) . unwrap ( ) ;
970-
971- let a_col = get_column_as ! ( & struct_array, "a" , Int64Array ) ;
972- assert_eq ! ( a_col. value( 0 ) , 10 ) ;
973- assert_eq ! ( a_col. value( 1 ) , 20 ) ;
974-
975- let b_col = get_column_as ! ( & struct_array, "b" , StringArray ) ;
976- assert_eq ! ( b_col. value( 0 ) , "alpha" ) ;
977- assert_eq ! ( b_col. value( 1 ) , "beta" ) ;
954+ let result = cast_column ( & source_col, & target_field, & DEFAULT_CAST_OPTIONS ) ;
955+ assert ! ( result. is_err( ) ) ;
956+ let error_msg = result. unwrap_err ( ) . to_string ( ) ;
957+ assert_contains ! ( error_msg, "no field name overlap" ) ;
978958 }
979959
980960 #[ test]
0 commit comments