@@ -497,6 +497,10 @@ impl DecimalType {
497497 self . scale
498498 }
499499
500+ pub fn is_nullable ( & self ) -> bool {
501+ self . nullable
502+ }
503+
500504 pub fn as_non_nullable ( & self ) -> Self {
501505 Self :: with_nullable ( false , self . precision , self . scale )
502506 . expect ( "Invalid decimal precision or scale" )
@@ -594,6 +598,10 @@ impl TimeType {
594598 self . precision
595599 }
596600
601+ pub fn is_nullable ( & self ) -> bool {
602+ self . nullable
603+ }
604+
597605 pub fn as_non_nullable ( & self ) -> Self {
598606 Self :: with_nullable ( false , self . precision ) . expect ( "Invalid time precision" )
599607 }
@@ -655,6 +663,10 @@ impl TimestampType {
655663 self . precision
656664 }
657665
666+ pub fn is_nullable ( & self ) -> bool {
667+ self . nullable
668+ }
669+
658670 pub fn as_non_nullable ( & self ) -> Self {
659671 Self :: with_nullable ( false , self . precision ) . expect ( "Invalid timestamp precision" )
660672 }
@@ -717,6 +729,10 @@ impl TimestampLTzType {
717729 self . precision
718730 }
719731
732+ pub fn is_nullable ( & self ) -> bool {
733+ self . nullable
734+ }
735+
720736 pub fn as_non_nullable ( & self ) -> Self {
721737 Self :: with_nullable ( false , self . precision )
722738 . expect ( "Invalid timestamp with local time zone precision" )
@@ -1405,6 +1421,11 @@ fn test_deeply_nested_types() {
14051421 assert_eq ! ( nested. to_string( ) , "ARRAY<MAP<STRING, ROW<x INT, y INT>>>" ) ;
14061422}
14071423
1424+
1425+ // ============================================================================
1426+ // DecimalType validation tests
1427+ // ============================================================================
1428+
14081429#[ test]
14091430fn test_decimal_invalid_precision ( ) {
14101431 // DecimalType::with_nullable should return an error for invalid precision
@@ -1431,6 +1452,65 @@ fn test_decimal_invalid_scale() {
14311452 ) ;
14321453}
14331454
1455+ // ============================================================================
1456+ // DecimalType validation tests - edge cases
1457+ // ============================================================================
1458+
1459+ #[ test]
1460+ fn test_decimal_valid_precision_and_scale ( ) {
1461+ // Valid: precision=10, scale=2
1462+ let result = DecimalType :: with_nullable ( true , 10 , 2 ) ;
1463+ assert ! ( result. is_ok( ) ) ;
1464+ let decimal = result. unwrap ( ) ;
1465+ assert_eq ! ( decimal. precision( ) , 10 ) ;
1466+ assert_eq ! ( decimal. scale( ) , 2 ) ;
1467+ assert ! ( decimal. is_nullable( ) ) ;
1468+
1469+ // Valid: precision=38, scale=0
1470+ let result = DecimalType :: with_nullable ( true , 38 , 0 ) ;
1471+ assert ! ( result. is_ok( ) ) ;
1472+ let decimal = result. unwrap ( ) ;
1473+ assert_eq ! ( decimal. precision( ) , 38 ) ;
1474+ assert_eq ! ( decimal. scale( ) , 0 ) ;
1475+
1476+ // Valid: precision=1, scale=0
1477+ let result = DecimalType :: with_nullable ( false , 1 , 0 ) ;
1478+ assert ! ( result. is_ok( ) ) ;
1479+ let decimal = result. unwrap ( ) ;
1480+ assert_eq ! ( decimal. precision( ) , 1 ) ;
1481+ assert_eq ! ( decimal. scale( ) , 0 ) ;
1482+ assert ! ( !decimal. is_nullable( ) ) ;
1483+ }
1484+
1485+ #[ test]
1486+ fn test_decimal_invalid_precision_zero ( ) {
1487+ // Invalid: precision=0 (edge case not covered by existing tests)
1488+ let result = DecimalType :: with_nullable ( true , 0 , 0 ) ;
1489+ assert ! ( result. is_err( ) ) ;
1490+ assert ! (
1491+ result
1492+ . unwrap_err( )
1493+ . to_string( )
1494+ . contains( "Decimal precision must be between 1 and 38" )
1495+ ) ;
1496+ }
1497+
1498+
1499+ // ============================================================================
1500+ // TimeType validation tests
1501+ // ============================================================================
1502+
1503+ #[ test]
1504+ fn test_time_valid_precision ( ) {
1505+ // Test all valid precision values 0 through 9
1506+ for precision in 0 ..=9 {
1507+ let result = TimeType :: with_nullable ( true , precision) ;
1508+ assert ! ( result. is_ok( ) , "precision {} should be valid" , precision) ;
1509+ let time = result. unwrap ( ) ;
1510+ assert_eq ! ( time. precision( ) , precision) ;
1511+ }
1512+ }
1513+
14341514#[ test]
14351515fn test_time_invalid_precision ( ) {
14361516 // TimeType::with_nullable should return an error for invalid precision
@@ -1444,6 +1524,25 @@ fn test_time_invalid_precision() {
14441524 ) ;
14451525}
14461526
1527+ // ============================================================================
1528+ // TimestampType validation tests
1529+ // ============================================================================
1530+
1531+ #[ test]
1532+ fn test_timestamp_valid_precision ( ) {
1533+ // Test all valid precision values 0 through 9
1534+ for precision in 0 ..=9 {
1535+ let result = TimestampType :: with_nullable ( true , precision) ;
1536+ assert ! (
1537+ result. is_ok( ) ,
1538+ "precision {} should be valid" ,
1539+ precision
1540+ ) ;
1541+ let timestamp_type = result. unwrap ( ) ;
1542+ assert_eq ! ( timestamp_type. precision( ) , precision) ;
1543+ }
1544+ }
1545+
14471546#[ test]
14481547fn test_timestamp_invalid_precision ( ) {
14491548 // TimestampType::with_nullable should return an error for invalid precision
@@ -1469,3 +1568,79 @@ fn test_timestamp_ltz_invalid_precision() {
14691568 . contains( "Timestamp with local time zone precision must be between 0 and 9" )
14701569 ) ;
14711570}
1571+
1572+ // ============================================================================
1573+ // RowType projection tests
1574+ // ============================================================================
1575+
1576+ #[ test]
1577+ fn test_row_type_project_valid_indices ( ) {
1578+ // Create a 3-column row type
1579+ let row_type = RowType :: with_data_types_and_field_names (
1580+ vec ! [ DataTypes :: int( ) , DataTypes :: string( ) , DataTypes :: bigint( ) ] ,
1581+ vec ! [ "id" , "name" , "age" ] ,
1582+ ) ;
1583+
1584+ // Valid projection by indices: [0, 2]
1585+ let projected = row_type. project ( & [ 0 , 2 ] ) . unwrap ( ) ;
1586+ assert_eq ! ( projected. fields( ) . len( ) , 2 ) ;
1587+ assert_eq ! ( projected. fields( ) [ 0 ] . name, "id" ) ;
1588+ assert_eq ! ( projected. fields( ) [ 1 ] . name, "age" ) ;
1589+ }
1590+
1591+ #[ test]
1592+ fn test_row_type_project_with_field_names_valid ( ) {
1593+ // Create a 3-column row type
1594+ let row_type = RowType :: with_data_types_and_field_names (
1595+ vec ! [ DataTypes :: int( ) , DataTypes :: string( ) , DataTypes :: bigint( ) ] ,
1596+ vec ! [ "id" , "name" , "age" ] ,
1597+ ) ;
1598+
1599+ // Valid projection by names: ["id", "name"]
1600+ let projected = row_type
1601+ . project_with_field_names ( & [ "id" . to_string ( ) , "name" . to_string ( ) ] )
1602+ . unwrap ( ) ;
1603+ assert_eq ! ( projected. fields( ) . len( ) , 2 ) ;
1604+ assert_eq ! ( projected. fields( ) [ 0 ] . name, "id" ) ;
1605+ assert_eq ! ( projected. fields( ) [ 1 ] . name, "name" ) ;
1606+ }
1607+
1608+ #[ test]
1609+ fn test_row_type_project_index_out_of_bounds ( ) {
1610+ // Create a 3-column row type
1611+ let row_type = RowType :: with_data_types_and_field_names (
1612+ vec ! [ DataTypes :: int( ) , DataTypes :: string( ) , DataTypes :: bigint( ) ] ,
1613+ vec ! [ "id" , "name" , "age" ] ,
1614+ ) ;
1615+
1616+ // Error: index out of bounds
1617+ let result = row_type. project ( & [ 0 , 5 ] ) ;
1618+ assert ! ( result. is_err( ) ) ;
1619+ assert ! (
1620+ result. unwrap_err( ) . to_string( ) . contains( "invalid field position: 5" )
1621+ ) ;
1622+ }
1623+
1624+ #[ test]
1625+ fn test_row_type_project_with_field_names_nonexistent ( ) {
1626+ // Create a 3-column row type
1627+ let row_type = RowType :: with_data_types_and_field_names (
1628+ vec ! [ DataTypes :: int( ) , DataTypes :: string( ) , DataTypes :: bigint( ) ] ,
1629+ vec ! [ "id" , "name" , "age" ] ,
1630+ ) ;
1631+
1632+ // Error: non-existent field name
1633+ // Note: project_with_field_names filters out non-existent field names using filter_map,
1634+ // so the result will be an empty row if all field names are non-existent
1635+ let projected = row_type
1636+ . project_with_field_names ( & [ "nonexistent" . to_string ( ) ] )
1637+ . unwrap ( ) ;
1638+ assert_eq ! ( projected. fields( ) . len( ) , 0 ) ;
1639+
1640+ // Mixed existing and non-existing: only existing fields are projected
1641+ let projected = row_type
1642+ . project_with_field_names ( & [ "id" . to_string ( ) , "nonexistent" . to_string ( ) ] )
1643+ . unwrap ( ) ;
1644+ assert_eq ! ( projected. fields( ) . len( ) , 1 ) ;
1645+ assert_eq ! ( projected. fields( ) [ 0 ] . name, "id" ) ;
1646+ }
0 commit comments