This is an automated email from the ASF dual-hosted git repository.

yuxia pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/fluss-rust.git


The following commit(s) were added to refs/heads/main by this push:
     new 5589a68  chore: added unit test cases and throw an exception when any 
field doesn't exist on RowType (#310)
5589a68 is described below

commit 5589a68a7089682d8e66b9882f9f285f2ed56fea
Author: Nikhil Negi <[email protected]>
AuthorDate: Tue Feb 17 13:44:41 2026 +0530

    chore: added unit test cases and throw an exception when any field doesn't 
exist on RowType (#310)
---
 crates/fluss/src/metadata/datatype.rs | 211 +++++++++++++++++++++++++++++++++-
 1 file changed, 209 insertions(+), 2 deletions(-)

diff --git a/crates/fluss/src/metadata/datatype.rs 
b/crates/fluss/src/metadata/datatype.rs
index 66f68f4..6d888d9 100644
--- a/crates/fluss/src/metadata/datatype.rs
+++ b/crates/fluss/src/metadata/datatype.rs
@@ -952,8 +952,12 @@ impl RowType {
     pub fn project_with_field_names(&self, field_names: &[String]) -> 
Result<RowType> {
         let indices: Vec<usize> = field_names
             .iter()
-            .filter_map(|pk| self.get_field_index(pk))
-            .collect();
+            .map(|name| {
+                self.get_field_index(name).ok_or_else(|| IllegalArgument {
+                    message: format!("Field '{}' does not exist in the row 
type", name),
+                })
+            })
+            .collect::<Result<Vec<_>>>()?;
 
         self.project(indices.as_slice())
     }
@@ -1424,6 +1428,10 @@ fn test_deeply_nested_types() {
     assert_eq!(nested.to_string(), "ARRAY<MAP<STRING, ROW<x INT, y INT>>>");
 }
 
+// ============================================================================
+// DecimalType validation tests
+// ============================================================================
+
 #[test]
 fn test_decimal_invalid_precision() {
     // DecimalType::with_nullable should return an error for invalid precision
@@ -1450,6 +1458,76 @@ fn test_decimal_invalid_scale() {
     );
 }
 
+// ============================================================================
+// DecimalType validation tests - edge cases
+// ============================================================================
+
+#[test]
+fn test_decimal_valid_precision_and_scale() {
+    // Valid: precision=10, scale=2
+    let result = DecimalType::with_nullable(true, 10, 2);
+    assert!(result.is_ok());
+    let decimal = result.unwrap();
+    assert_eq!(decimal.precision(), 10);
+    assert_eq!(decimal.scale(), 2);
+    // Nullable: should NOT contain "NOT NULL"
+    assert!(!decimal.to_string().contains("NOT NULL"));
+
+    // Valid: precision=38, scale=0
+    let result = DecimalType::with_nullable(true, 38, 0);
+    assert!(result.is_ok());
+    let decimal = result.unwrap();
+    assert_eq!(decimal.precision(), 38);
+    assert_eq!(decimal.scale(), 0);
+
+    // Valid: precision=1, scale=0
+    let result = DecimalType::with_nullable(false, 1, 0);
+    assert!(result.is_ok());
+    let decimal = result.unwrap();
+    assert_eq!(decimal.precision(), 1);
+    assert_eq!(decimal.scale(), 0);
+    // Non-nullable: should contain "NOT NULL"
+    assert!(decimal.to_string().contains("NOT NULL"));
+}
+
+#[test]
+fn test_decimal_invalid_precision_zero() {
+    // Invalid: precision=0 (edge case not covered by existing tests)
+    let result = DecimalType::with_nullable(true, 0, 0);
+    assert!(result.is_err());
+    assert!(
+        result
+            .unwrap_err()
+            .to_string()
+            .contains("Decimal precision must be between 1 and 38")
+    );
+}
+
+#[test]
+fn test_decimal_scale_equals_precision_boundary() {
+    // Boundary: precision=10, scale=10 (scale == precision is valid)
+    let result = DecimalType::with_nullable(true, 10, 10);
+    assert!(result.is_ok());
+    let decimal = result.unwrap();
+    assert_eq!(decimal.precision(), 10);
+    assert_eq!(decimal.scale(), 10);
+}
+
+// ============================================================================
+// TimeType validation tests
+// ============================================================================
+
+#[test]
+fn test_time_valid_precision() {
+    // Test all valid precision values 0 through 9
+    for precision in 0..=9 {
+        let result = TimeType::with_nullable(true, precision);
+        assert!(result.is_ok(), "precision {} should be valid", precision);
+        let time = result.unwrap();
+        assert_eq!(time.precision(), precision);
+    }
+}
+
 #[test]
 fn test_time_invalid_precision() {
     // TimeType::with_nullable should return an error for invalid precision
@@ -1463,6 +1541,21 @@ fn test_time_invalid_precision() {
     );
 }
 
+// ============================================================================
+// TimestampType validation tests
+// ============================================================================
+
+#[test]
+fn test_timestamp_valid_precision() {
+    // Test all valid precision values 0 through 9
+    for precision in 0..=9 {
+        let result = TimestampType::with_nullable(true, precision);
+        assert!(result.is_ok(), "precision {} should be valid", precision);
+        let timestamp_type = result.unwrap();
+        assert_eq!(timestamp_type.precision(), precision);
+    }
+}
+
 #[test]
 fn test_timestamp_invalid_precision() {
     // TimestampType::with_nullable should return an error for invalid 
precision
@@ -1488,3 +1581,117 @@ fn test_timestamp_ltz_invalid_precision() {
             .contains("Timestamp with local time zone precision must be 
between 0 and 9")
     );
 }
+
+// ============================================================================
+// RowType projection tests
+// ============================================================================
+
+#[test]
+fn test_row_type_project_valid_indices() {
+    // Create a 3-column row type
+    let row_type = RowType::with_data_types_and_field_names(
+        vec![DataTypes::int(), DataTypes::string(), DataTypes::bigint()],
+        vec!["id", "name", "age"],
+    );
+
+    // Valid projection by indices: [0, 2]
+    let projected = row_type.project(&[0, 2]).unwrap();
+    assert_eq!(projected.fields().len(), 2);
+    assert_eq!(projected.fields()[0].name, "id");
+    assert_eq!(projected.fields()[1].name, "age");
+}
+
+#[test]
+fn test_row_type_project_empty_indices() {
+    // Create a 3-column row type
+    let row_type = RowType::with_data_types_and_field_names(
+        vec![DataTypes::int(), DataTypes::string(), DataTypes::bigint()],
+        vec!["id", "name", "age"],
+    );
+
+    // Projection with an empty indices array should yield an empty RowType
+    let projected = row_type.project(&[]).unwrap();
+    assert_eq!(projected.fields().len(), 0);
+}
+
+#[test]
+fn test_row_type_project_with_field_names_valid() {
+    // Create a 3-column row type
+    let row_type = RowType::with_data_types_and_field_names(
+        vec![DataTypes::int(), DataTypes::string(), DataTypes::bigint()],
+        vec!["id", "name", "age"],
+    );
+
+    // Valid projection by names: ["id", "name"]
+    let projected = row_type
+        .project_with_field_names(&["id".to_string(), "name".to_string()])
+        .unwrap();
+    assert_eq!(projected.fields().len(), 2);
+    assert_eq!(projected.fields()[0].name, "id");
+    assert_eq!(projected.fields()[1].name, "name");
+}
+
+#[test]
+fn test_row_type_project_index_out_of_bounds() {
+    // Create a 3-column row type
+    let row_type = RowType::with_data_types_and_field_names(
+        vec![DataTypes::int(), DataTypes::string(), DataTypes::bigint()],
+        vec!["id", "name", "age"],
+    );
+
+    // Error: index out of bounds
+    let result = row_type.project(&[0, 5]);
+    assert!(result.is_err());
+    assert!(
+        result
+            .unwrap_err()
+            .to_string()
+            .contains("invalid field position: 5")
+    );
+}
+
+#[test]
+fn test_row_type_project_with_field_names_nonexistent() {
+    // Create a 3-column row type
+    let row_type = RowType::with_data_types_and_field_names(
+        vec![DataTypes::int(), DataTypes::string(), DataTypes::bigint()],
+        vec!["id", "name", "age"],
+    );
+
+    // Error: non-existent field name should throw exception
+    let result = 
row_type.project_with_field_names(&["nonexistent".to_string()]);
+    assert!(result.is_err());
+    assert!(
+        result
+            .unwrap_err()
+            .to_string()
+            .contains("Field 'nonexistent' does not exist in the row type")
+    );
+
+    // Mixed existing and non-existing: should also error on the first 
non-existent field
+    let result = row_type.project_with_field_names(&["id".to_string(), 
"nonexistent".to_string()]);
+    assert!(result.is_err());
+    assert!(
+        result
+            .unwrap_err()
+            .to_string()
+            .contains("Field 'nonexistent' does not exist in the row type")
+    );
+}
+
+#[test]
+fn test_row_type_project_duplicate_indices() {
+    // Create a 3-column row type
+    let row_type = RowType::with_data_types_and_field_names(
+        vec![DataTypes::int(), DataTypes::string(), DataTypes::bigint()],
+        vec!["id", "name", "age"],
+    );
+
+    // Projection with duplicate indices: [0, 0, 1]
+    // This documents the expected behavior - duplicates are allowed
+    let projected = row_type.project(&[0, 0, 1]).unwrap();
+    assert_eq!(projected.fields().len(), 3);
+    assert_eq!(projected.fields()[0].name, "id");
+    assert_eq!(projected.fields()[1].name, "id");
+    assert_eq!(projected.fields()[2].name, "name");
+}

Reply via email to