Skip to content

Commit 5589a68

Browse files
authored
chore: added unit test cases and throw an exception when any field doesn't exist on RowType (#310)
1 parent c325929 commit 5589a68

1 file changed

Lines changed: 209 additions & 2 deletions

File tree

crates/fluss/src/metadata/datatype.rs

Lines changed: 209 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -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]
14281436
fn 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]
14541532
fn 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]
14671560
fn 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

Comments
 (0)