@@ -1386,6 +1386,174 @@ mod tests {
13861386 assert ! ( diff. has_breaking_changes( ) ) ; // Type change is breaking
13871387 }
13881388
1389+ #[ test]
1390+ fn test_nested_array_nullability_loosened ( ) {
1391+ // Test: array<array<int> not null> -> array<array<int>>
1392+ // Outer array element nullability loosened (safe change)
1393+ let before = StructType :: new_unchecked ( [ create_field_with_id (
1394+ "matrix" ,
1395+ DataType :: Array ( Box :: new ( ArrayType :: new (
1396+ DataType :: Array ( Box :: new ( ArrayType :: new ( DataType :: INTEGER , false ) ) ) ,
1397+ false , // Outer array elements are non-nullable
1398+ ) ) ) ,
1399+ false ,
1400+ 1 ,
1401+ ) ] ) ;
1402+
1403+ let after = StructType :: new_unchecked ( [ create_field_with_id (
1404+ "matrix" ,
1405+ DataType :: Array ( Box :: new ( ArrayType :: new (
1406+ DataType :: Array ( Box :: new ( ArrayType :: new ( DataType :: INTEGER , false ) ) ) ,
1407+ true , // Outer array elements now nullable
1408+ ) ) ) ,
1409+ false ,
1410+ 1 ,
1411+ ) ] ) ;
1412+
1413+ let diff = SchemaDiffArgs {
1414+ before : & before,
1415+ after : & after,
1416+ }
1417+ . compute_diff ( )
1418+ . unwrap ( ) ;
1419+
1420+ assert_eq ! ( diff. added_fields. len( ) , 0 ) ;
1421+ assert_eq ! ( diff. removed_fields. len( ) , 0 ) ;
1422+ assert_eq ! ( diff. updated_fields. len( ) , 1 ) ;
1423+ assert_eq ! ( diff. updated_fields[ 0 ] . path, ColumnName :: new( [ "matrix" ] ) ) ;
1424+ assert_eq ! (
1425+ diff. updated_fields[ 0 ] . change_type,
1426+ FieldChangeType :: ContainerNullabilityLoosened
1427+ ) ;
1428+ assert ! ( !diff. has_breaking_changes( ) ) ; // Loosening is safe
1429+ }
1430+
1431+ #[ test]
1432+ fn test_nested_array_nullability_tightened ( ) {
1433+ // Test: array<array<int>> -> array<array<int> not null>
1434+ // Outer array element nullability tightened (breaking change)
1435+ let before = StructType :: new_unchecked ( [ create_field_with_id (
1436+ "matrix" ,
1437+ DataType :: Array ( Box :: new ( ArrayType :: new (
1438+ DataType :: Array ( Box :: new ( ArrayType :: new ( DataType :: INTEGER , false ) ) ) ,
1439+ true , // Outer array elements are nullable
1440+ ) ) ) ,
1441+ false ,
1442+ 1 ,
1443+ ) ] ) ;
1444+
1445+ let after = StructType :: new_unchecked ( [ create_field_with_id (
1446+ "matrix" ,
1447+ DataType :: Array ( Box :: new ( ArrayType :: new (
1448+ DataType :: Array ( Box :: new ( ArrayType :: new ( DataType :: INTEGER , false ) ) ) ,
1449+ false , // Outer array elements now non-nullable
1450+ ) ) ) ,
1451+ false ,
1452+ 1 ,
1453+ ) ] ) ;
1454+
1455+ let diff = SchemaDiffArgs {
1456+ before : & before,
1457+ after : & after,
1458+ }
1459+ . compute_diff ( )
1460+ . unwrap ( ) ;
1461+
1462+ assert_eq ! ( diff. added_fields. len( ) , 0 ) ;
1463+ assert_eq ! ( diff. removed_fields. len( ) , 0 ) ;
1464+ assert_eq ! ( diff. updated_fields. len( ) , 1 ) ;
1465+ assert_eq ! ( diff. updated_fields[ 0 ] . path, ColumnName :: new( [ "matrix" ] ) ) ;
1466+ assert_eq ! (
1467+ diff. updated_fields[ 0 ] . change_type,
1468+ FieldChangeType :: ContainerNullabilityTightened
1469+ ) ;
1470+ assert ! ( diff. has_breaking_changes( ) ) ; // Tightening is breaking
1471+ }
1472+
1473+ #[ test]
1474+ fn test_nested_array_inner_nullability_loosened ( ) {
1475+ // Test: array<array<int not null>> -> array<array<int>>
1476+ // Inner array element nullability loosened (safe change)
1477+ let before = StructType :: new_unchecked ( [ create_field_with_id (
1478+ "matrix" ,
1479+ DataType :: Array ( Box :: new ( ArrayType :: new (
1480+ DataType :: Array ( Box :: new ( ArrayType :: new ( DataType :: INTEGER , false ) ) ) , // Inner elements non-nullable
1481+ false ,
1482+ ) ) ) ,
1483+ false ,
1484+ 1 ,
1485+ ) ] ) ;
1486+
1487+ let after = StructType :: new_unchecked ( [ create_field_with_id (
1488+ "matrix" ,
1489+ DataType :: Array ( Box :: new ( ArrayType :: new (
1490+ DataType :: Array ( Box :: new ( ArrayType :: new ( DataType :: INTEGER , true ) ) ) , // Inner elements now nullable
1491+ false ,
1492+ ) ) ) ,
1493+ false ,
1494+ 1 ,
1495+ ) ] ) ;
1496+
1497+ let diff = SchemaDiffArgs {
1498+ before : & before,
1499+ after : & after,
1500+ }
1501+ . compute_diff ( )
1502+ . unwrap ( ) ;
1503+
1504+ assert_eq ! ( diff. added_fields. len( ) , 0 ) ;
1505+ assert_eq ! ( diff. removed_fields. len( ) , 0 ) ;
1506+ assert_eq ! ( diff. updated_fields. len( ) , 1 ) ;
1507+ assert_eq ! ( diff. updated_fields[ 0 ] . path, ColumnName :: new( [ "matrix" ] ) ) ;
1508+ assert_eq ! (
1509+ diff. updated_fields[ 0 ] . change_type,
1510+ FieldChangeType :: ContainerNullabilityLoosened
1511+ ) ;
1512+ assert ! ( !diff. has_breaking_changes( ) ) ; // Loosening is safe
1513+ }
1514+
1515+ #[ test]
1516+ fn test_nested_array_inner_nullability_tightened ( ) {
1517+ // Test: array<array<int>> -> array<array<int not null>>
1518+ // Inner array element nullability tightened (breaking change)
1519+ let before = StructType :: new_unchecked ( [ create_field_with_id (
1520+ "matrix" ,
1521+ DataType :: Array ( Box :: new ( ArrayType :: new (
1522+ DataType :: Array ( Box :: new ( ArrayType :: new ( DataType :: INTEGER , true ) ) ) , // Inner elements nullable
1523+ false ,
1524+ ) ) ) ,
1525+ false ,
1526+ 1 ,
1527+ ) ] ) ;
1528+
1529+ let after = StructType :: new_unchecked ( [ create_field_with_id (
1530+ "matrix" ,
1531+ DataType :: Array ( Box :: new ( ArrayType :: new (
1532+ DataType :: Array ( Box :: new ( ArrayType :: new ( DataType :: INTEGER , false ) ) ) , // Inner elements now non-nullable
1533+ false ,
1534+ ) ) ) ,
1535+ false ,
1536+ 1 ,
1537+ ) ] ) ;
1538+
1539+ let diff = SchemaDiffArgs {
1540+ before : & before,
1541+ after : & after,
1542+ }
1543+ . compute_diff ( )
1544+ . unwrap ( ) ;
1545+
1546+ assert_eq ! ( diff. added_fields. len( ) , 0 ) ;
1547+ assert_eq ! ( diff. removed_fields. len( ) , 0 ) ;
1548+ assert_eq ! ( diff. updated_fields. len( ) , 1 ) ;
1549+ assert_eq ! ( diff. updated_fields[ 0 ] . path, ColumnName :: new( [ "matrix" ] ) ) ;
1550+ assert_eq ! (
1551+ diff. updated_fields[ 0 ] . change_type,
1552+ FieldChangeType :: ContainerNullabilityTightened
1553+ ) ;
1554+ assert ! ( diff. has_breaking_changes( ) ) ; // Tightening is breaking
1555+ }
1556+
13891557 #[ test]
13901558 fn test_map_value_struct_field_changes ( ) {
13911559 let before = StructType :: new_unchecked ( [ create_field_with_id (
0 commit comments