@@ -952,8 +952,12 @@ impl RowType {
952952 pub fn project_with_field_names ( & self , field_names : & [ String ] ) -> Result < RowType > {
953953 let indices: Vec < usize > = field_names
954954 . iter ( )
955- . filter_map ( |pk| self . get_field_index ( pk) )
956- . collect ( ) ;
955+ . map ( |name| {
956+ self . get_field_index ( name) . ok_or_else ( || IllegalArgument {
957+ message : format ! ( "Field '{}' does not exist in the row type" , name) ,
958+ } )
959+ } )
960+ . collect :: < Result < Vec < _ > > > ( ) ?;
957961
958962 self . project ( indices. as_slice ( ) )
959963 }
@@ -1424,6 +1428,10 @@ fn test_deeply_nested_types() {
14241428 assert_eq ! ( nested. to_string( ) , "ARRAY<MAP<STRING, ROW<x INT, y INT>>>" ) ;
14251429}
14261430
1431+ // ============================================================================
1432+ // DecimalType validation tests
1433+ // ============================================================================
1434+
14271435#[ test]
14281436fn test_decimal_invalid_precision ( ) {
14291437 // DecimalType::with_nullable should return an error for invalid precision
@@ -1450,6 +1458,76 @@ fn test_decimal_invalid_scale() {
14501458 ) ;
14511459}
14521460
1461+ // ============================================================================
1462+ // DecimalType validation tests - edge cases
1463+ // ============================================================================
1464+
1465+ #[ test]
1466+ fn test_decimal_valid_precision_and_scale ( ) {
1467+ // Valid: precision=10, scale=2
1468+ let result = DecimalType :: with_nullable ( true , 10 , 2 ) ;
1469+ assert ! ( result. is_ok( ) ) ;
1470+ let decimal = result. unwrap ( ) ;
1471+ assert_eq ! ( decimal. precision( ) , 10 ) ;
1472+ assert_eq ! ( decimal. scale( ) , 2 ) ;
1473+ // Nullable: should NOT contain "NOT NULL"
1474+ assert ! ( !decimal. to_string( ) . contains( "NOT NULL" ) ) ;
1475+
1476+ // Valid: precision=38, scale=0
1477+ let result = DecimalType :: with_nullable ( true , 38 , 0 ) ;
1478+ assert ! ( result. is_ok( ) ) ;
1479+ let decimal = result. unwrap ( ) ;
1480+ assert_eq ! ( decimal. precision( ) , 38 ) ;
1481+ assert_eq ! ( decimal. scale( ) , 0 ) ;
1482+
1483+ // Valid: precision=1, scale=0
1484+ let result = DecimalType :: with_nullable ( false , 1 , 0 ) ;
1485+ assert ! ( result. is_ok( ) ) ;
1486+ let decimal = result. unwrap ( ) ;
1487+ assert_eq ! ( decimal. precision( ) , 1 ) ;
1488+ assert_eq ! ( decimal. scale( ) , 0 ) ;
1489+ // Non-nullable: should contain "NOT NULL"
1490+ assert ! ( decimal. to_string( ) . contains( "NOT NULL" ) ) ;
1491+ }
1492+
1493+ #[ test]
1494+ fn test_decimal_invalid_precision_zero ( ) {
1495+ // Invalid: precision=0 (edge case not covered by existing tests)
1496+ let result = DecimalType :: with_nullable ( true , 0 , 0 ) ;
1497+ assert ! ( result. is_err( ) ) ;
1498+ assert ! (
1499+ result
1500+ . unwrap_err( )
1501+ . to_string( )
1502+ . contains( "Decimal precision must be between 1 and 38" )
1503+ ) ;
1504+ }
1505+
1506+ #[ test]
1507+ fn test_decimal_scale_equals_precision_boundary ( ) {
1508+ // Boundary: precision=10, scale=10 (scale == precision is valid)
1509+ let result = DecimalType :: with_nullable ( true , 10 , 10 ) ;
1510+ assert ! ( result. is_ok( ) ) ;
1511+ let decimal = result. unwrap ( ) ;
1512+ assert_eq ! ( decimal. precision( ) , 10 ) ;
1513+ assert_eq ! ( decimal. scale( ) , 10 ) ;
1514+ }
1515+
1516+ // ============================================================================
1517+ // TimeType validation tests
1518+ // ============================================================================
1519+
1520+ #[ test]
1521+ fn test_time_valid_precision ( ) {
1522+ // Test all valid precision values 0 through 9
1523+ for precision in 0 ..=9 {
1524+ let result = TimeType :: with_nullable ( true , precision) ;
1525+ assert ! ( result. is_ok( ) , "precision {} should be valid" , precision) ;
1526+ let time = result. unwrap ( ) ;
1527+ assert_eq ! ( time. precision( ) , precision) ;
1528+ }
1529+ }
1530+
14531531#[ test]
14541532fn test_time_invalid_precision ( ) {
14551533 // TimeType::with_nullable should return an error for invalid precision
@@ -1463,6 +1541,21 @@ fn test_time_invalid_precision() {
14631541 ) ;
14641542}
14651543
1544+ // ============================================================================
1545+ // TimestampType validation tests
1546+ // ============================================================================
1547+
1548+ #[ test]
1549+ fn test_timestamp_valid_precision ( ) {
1550+ // Test all valid precision values 0 through 9
1551+ for precision in 0 ..=9 {
1552+ let result = TimestampType :: with_nullable ( true , precision) ;
1553+ assert ! ( result. is_ok( ) , "precision {} should be valid" , precision) ;
1554+ let timestamp_type = result. unwrap ( ) ;
1555+ assert_eq ! ( timestamp_type. precision( ) , precision) ;
1556+ }
1557+ }
1558+
14661559#[ test]
14671560fn test_timestamp_invalid_precision ( ) {
14681561 // TimestampType::with_nullable should return an error for invalid precision
@@ -1488,3 +1581,117 @@ fn test_timestamp_ltz_invalid_precision() {
14881581 . contains( "Timestamp with local time zone precision must be between 0 and 9" )
14891582 ) ;
14901583}
1584+
1585+ // ============================================================================
1586+ // RowType projection tests
1587+ // ============================================================================
1588+
1589+ #[ test]
1590+ fn test_row_type_project_valid_indices ( ) {
1591+ // Create a 3-column row type
1592+ let row_type = RowType :: with_data_types_and_field_names (
1593+ vec ! [ DataTypes :: int( ) , DataTypes :: string( ) , DataTypes :: bigint( ) ] ,
1594+ vec ! [ "id" , "name" , "age" ] ,
1595+ ) ;
1596+
1597+ // Valid projection by indices: [0, 2]
1598+ let projected = row_type. project ( & [ 0 , 2 ] ) . unwrap ( ) ;
1599+ assert_eq ! ( projected. fields( ) . len( ) , 2 ) ;
1600+ assert_eq ! ( projected. fields( ) [ 0 ] . name, "id" ) ;
1601+ assert_eq ! ( projected. fields( ) [ 1 ] . name, "age" ) ;
1602+ }
1603+
1604+ #[ test]
1605+ fn test_row_type_project_empty_indices ( ) {
1606+ // Create a 3-column row type
1607+ let row_type = RowType :: with_data_types_and_field_names (
1608+ vec ! [ DataTypes :: int( ) , DataTypes :: string( ) , DataTypes :: bigint( ) ] ,
1609+ vec ! [ "id" , "name" , "age" ] ,
1610+ ) ;
1611+
1612+ // Projection with an empty indices array should yield an empty RowType
1613+ let projected = row_type. project ( & [ ] ) . unwrap ( ) ;
1614+ assert_eq ! ( projected. fields( ) . len( ) , 0 ) ;
1615+ }
1616+
1617+ #[ test]
1618+ fn test_row_type_project_with_field_names_valid ( ) {
1619+ // Create a 3-column row type
1620+ let row_type = RowType :: with_data_types_and_field_names (
1621+ vec ! [ DataTypes :: int( ) , DataTypes :: string( ) , DataTypes :: bigint( ) ] ,
1622+ vec ! [ "id" , "name" , "age" ] ,
1623+ ) ;
1624+
1625+ // Valid projection by names: ["id", "name"]
1626+ let projected = row_type
1627+ . project_with_field_names ( & [ "id" . to_string ( ) , "name" . to_string ( ) ] )
1628+ . unwrap ( ) ;
1629+ assert_eq ! ( projected. fields( ) . len( ) , 2 ) ;
1630+ assert_eq ! ( projected. fields( ) [ 0 ] . name, "id" ) ;
1631+ assert_eq ! ( projected. fields( ) [ 1 ] . name, "name" ) ;
1632+ }
1633+
1634+ #[ test]
1635+ fn test_row_type_project_index_out_of_bounds ( ) {
1636+ // Create a 3-column row type
1637+ let row_type = RowType :: with_data_types_and_field_names (
1638+ vec ! [ DataTypes :: int( ) , DataTypes :: string( ) , DataTypes :: bigint( ) ] ,
1639+ vec ! [ "id" , "name" , "age" ] ,
1640+ ) ;
1641+
1642+ // Error: index out of bounds
1643+ let result = row_type. project ( & [ 0 , 5 ] ) ;
1644+ assert ! ( result. is_err( ) ) ;
1645+ assert ! (
1646+ result
1647+ . unwrap_err( )
1648+ . to_string( )
1649+ . contains( "invalid field position: 5" )
1650+ ) ;
1651+ }
1652+
1653+ #[ test]
1654+ fn test_row_type_project_with_field_names_nonexistent ( ) {
1655+ // Create a 3-column row type
1656+ let row_type = RowType :: with_data_types_and_field_names (
1657+ vec ! [ DataTypes :: int( ) , DataTypes :: string( ) , DataTypes :: bigint( ) ] ,
1658+ vec ! [ "id" , "name" , "age" ] ,
1659+ ) ;
1660+
1661+ // Error: non-existent field name should throw exception
1662+ let result = row_type. project_with_field_names ( & [ "nonexistent" . to_string ( ) ] ) ;
1663+ assert ! ( result. is_err( ) ) ;
1664+ assert ! (
1665+ result
1666+ . unwrap_err( )
1667+ . to_string( )
1668+ . contains( "Field 'nonexistent' does not exist in the row type" )
1669+ ) ;
1670+
1671+ // Mixed existing and non-existing: should also error on the first non-existent field
1672+ let result = row_type. project_with_field_names ( & [ "id" . to_string ( ) , "nonexistent" . to_string ( ) ] ) ;
1673+ assert ! ( result. is_err( ) ) ;
1674+ assert ! (
1675+ result
1676+ . unwrap_err( )
1677+ . to_string( )
1678+ . contains( "Field 'nonexistent' does not exist in the row type" )
1679+ ) ;
1680+ }
1681+
1682+ #[ test]
1683+ fn test_row_type_project_duplicate_indices ( ) {
1684+ // Create a 3-column row type
1685+ let row_type = RowType :: with_data_types_and_field_names (
1686+ vec ! [ DataTypes :: int( ) , DataTypes :: string( ) , DataTypes :: bigint( ) ] ,
1687+ vec ! [ "id" , "name" , "age" ] ,
1688+ ) ;
1689+
1690+ // Projection with duplicate indices: [0, 0, 1]
1691+ // This documents the expected behavior - duplicates are allowed
1692+ let projected = row_type. project ( & [ 0 , 0 , 1 ] ) . unwrap ( ) ;
1693+ assert_eq ! ( projected. fields( ) . len( ) , 3 ) ;
1694+ assert_eq ! ( projected. fields( ) [ 0 ] . name, "id" ) ;
1695+ assert_eq ! ( projected. fields( ) [ 1 ] . name, "id" ) ;
1696+ assert_eq ! ( projected. fields( ) [ 2 ] . name, "name" ) ;
1697+ }
0 commit comments