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

davidzollo pushed a commit to branch dev
in repository https://gitbox.apache.org/repos/asf/seatunnel.git


The following commit(s) were added to refs/heads/dev by this push:
     new 149cde8bd3 [Fix][Transform-V2][DataValidator] Normalize numeric bounds 
in RANGE rule to fix ClassCastException (#11920)
149cde8bd3 is described below

commit 149cde8bd3e390476c2d0df70419827bf6d7b987
Author: AOA <[email protected]>
AuthorDate: Sat Aug 22 13:10:49 2026 +0800

    [Fix][Transform-V2][DataValidator] Normalize numeric bounds in RANGE rule 
to fix ClassCastException (#11920)
    
    Co-authored-by: Claude <[email protected]>
---
 .../validator/rule/RangeValidationRule.java        |  39 ++++++-
 .../validator/DataValidatorTransformTest.java      | 125 +++++++++++++++++++++
 2 files changed, 161 insertions(+), 3 deletions(-)

diff --git 
a/seatunnel-transforms-v2/src/main/java/org/apache/seatunnel/transform/validator/rule/RangeValidationRule.java
 
b/seatunnel-transforms-v2/src/main/java/org/apache/seatunnel/transform/validator/rule/RangeValidationRule.java
index 5ad9b2f1d1..000aa3e1bd 100644
--- 
a/seatunnel-transforms-v2/src/main/java/org/apache/seatunnel/transform/validator/rule/RangeValidationRule.java
+++ 
b/seatunnel-transforms-v2/src/main/java/org/apache/seatunnel/transform/validator/rule/RangeValidationRule.java
@@ -88,10 +88,43 @@ public class RangeValidationRule implements ValidationRule {
     }
 
     private int compare(Comparable value, Comparable bound) {
-        if (value instanceof BigDecimal && bound instanceof Number) {
-            return ((BigDecimal) value).compareTo(new 
BigDecimal(bound.toString()));
+        if (value instanceof Number && bound instanceof Number) {
+            return compareNumbers((Number) value, (Number) bound);
+        }
+        return compareAsNumberOrString(value, bound);
+    }
+
+    /**
+     * Compares two numeric values. The field value type (e.g. BIGINT/DOUBLE) 
may differ from the
+     * parsed bound type (Integer/Long/Double), so finite values are compared 
as {@link BigDecimal}
+     * to avoid {@link ClassCastException}. Non-finite values (NaN, Infinity) 
cannot be represented
+     * as {@link BigDecimal}, so they are compared as double - {@link 
Double#compare} sorts them
+     * beyond all finite values, so they are treated as out of range instead 
of crashing.
+     */
+    private int compareNumbers(Number value, Number bound) {
+        double valueAsDouble = value.doubleValue();
+        double boundAsDouble = bound.doubleValue();
+        if (Double.isNaN(valueAsDouble)
+                || Double.isInfinite(valueAsDouble)
+                || Double.isNaN(boundAsDouble)
+                || Double.isInfinite(boundAsDouble)) {
+            return Double.compare(valueAsDouble, boundAsDouble);
+        }
+        return new BigDecimal(value.toString()).compareTo(new 
BigDecimal(bound.toString()));
+    }
+
+    /**
+     * Compares a value and a bound of different types (e.g. a STRING field 
against a numeric bound,
+     * or a numeric field against a string bound) without throwing {@link 
ClassCastException}.
+     * Values are compared numerically when both can be parsed as {@link 
BigDecimal}, otherwise by
+     * their string representation.
+     */
+    private int compareAsNumberOrString(Comparable value, Comparable bound) {
+        try {
+            return new BigDecimal(value.toString()).compareTo(new 
BigDecimal(bound.toString()));
+        } catch (NumberFormatException e) {
+            return value.toString().compareTo(bound.toString());
         }
-        return value.compareTo(bound);
     }
 
     @Override
diff --git 
a/seatunnel-transforms-v2/src/test/java/org/apache/seatunnel/transform/validator/DataValidatorTransformTest.java
 
b/seatunnel-transforms-v2/src/test/java/org/apache/seatunnel/transform/validator/DataValidatorTransformTest.java
index c7ef26b72c..3834717599 100644
--- 
a/seatunnel-transforms-v2/src/test/java/org/apache/seatunnel/transform/validator/DataValidatorTransformTest.java
+++ 
b/seatunnel-transforms-v2/src/test/java/org/apache/seatunnel/transform/validator/DataValidatorTransformTest.java
@@ -172,4 +172,129 @@ public class DataValidatorTransformTest {
 
         assertEquals(row, transform.map(row));
     }
+
+    @Test
+    void rangeRuleShouldValidateBigintFieldAgainstIntegerBounds() {
+        SeaTunnelRowType inputRowType =
+                new SeaTunnelRowType(
+                        new String[] {"quantity"}, new SeaTunnelDataType[] 
{BasicType.LONG_TYPE});
+        CatalogTable inputCatalogTable =
+                CatalogTableUtil.getCatalogTable("catalog", "db1", null, 
"source", inputRowType);
+        ReadonlyConfig config =
+                ReadonlyConfig.fromMap(
+                        ImmutableMap.of(
+                                "field_rules",
+                                Arrays.asList(
+                                        ImmutableMap.of(
+                                                "field_name",
+                                                "quantity",
+                                                "rules",
+                                                Arrays.asList(
+                                                        ImmutableMap.of(
+                                                                "rule_type",
+                                                                "RANGE",
+                                                                "min_value",
+                                                                0,
+                                                                "max_value",
+                                                                1000))))));
+        DataValidatorTransform transform = new DataValidatorTransform(config, 
inputCatalogTable);
+        SeaTunnelRow row = new SeaTunnelRow(new Object[] {500L});
+
+        assertEquals(row, transform.map(row));
+    }
+
+    @Test
+    void rangeRuleShouldValidateDoubleFieldAgainstIntegerBounds() {
+        SeaTunnelRowType inputRowType =
+                new SeaTunnelRowType(
+                        new String[] {"score"}, new SeaTunnelDataType[] 
{BasicType.DOUBLE_TYPE});
+        CatalogTable inputCatalogTable =
+                CatalogTableUtil.getCatalogTable("catalog", "db1", null, 
"source", inputRowType);
+        ReadonlyConfig config =
+                ReadonlyConfig.fromMap(
+                        ImmutableMap.of(
+                                "field_rules",
+                                Arrays.asList(
+                                        ImmutableMap.of(
+                                                "field_name",
+                                                "score",
+                                                "rules",
+                                                Arrays.asList(
+                                                        ImmutableMap.of(
+                                                                "rule_type",
+                                                                "RANGE",
+                                                                "min_value",
+                                                                0,
+                                                                "max_value",
+                                                                100))))));
+        DataValidatorTransform transform = new DataValidatorTransform(config, 
inputCatalogTable);
+        SeaTunnelRow row = new SeaTunnelRow(new Object[] {85.5d});
+
+        assertEquals(row, transform.map(row));
+    }
+
+    @Test
+    void rangeRuleShouldRouteNonFiniteDoubleToErrorTableWithIntegerBounds() {
+        SeaTunnelRowType inputRowType =
+                new SeaTunnelRowType(
+                        new String[] {"score"}, new SeaTunnelDataType[] 
{BasicType.DOUBLE_TYPE});
+        CatalogTable inputCatalogTable =
+                CatalogTableUtil.getCatalogTable("catalog", "db1", null, 
"source", inputRowType);
+        ReadonlyConfig config =
+                ReadonlyConfig.fromMap(
+                        ImmutableMap.of(
+                                "row_error_handle_way",
+                                "ROUTE_TO_TABLE",
+                                "row_error_handle_way.error_table",
+                                "ffp",
+                                "field_rules",
+                                Arrays.asList(
+                                        ImmutableMap.of(
+                                                "field_name",
+                                                "score",
+                                                "rules",
+                                                Arrays.asList(
+                                                        ImmutableMap.of(
+                                                                "rule_type",
+                                                                "RANGE",
+                                                                "min_value",
+                                                                0,
+                                                                "max_value",
+                                                                100))))));
+        DataValidatorTransform transform = new DataValidatorTransform(config, 
inputCatalogTable);
+        SeaTunnelRow row = new SeaTunnelRow(new Object[] {Double.NaN});
+
+        SeaTunnelRow routedRow = transform.map(row);
+        assertEquals("db1.ffp", routedRow.getTableId());
+    }
+
+    @Test
+    void rangeRuleShouldValidateNumericStringFieldAgainstIntegerBounds() {
+        SeaTunnelRowType inputRowType =
+                new SeaTunnelRowType(
+                        new String[] {"quantity"}, new SeaTunnelDataType[] 
{BasicType.STRING_TYPE});
+        CatalogTable inputCatalogTable =
+                CatalogTableUtil.getCatalogTable("catalog", "db1", null, 
"source", inputRowType);
+        ReadonlyConfig config =
+                ReadonlyConfig.fromMap(
+                        ImmutableMap.of(
+                                "field_rules",
+                                Arrays.asList(
+                                        ImmutableMap.of(
+                                                "field_name",
+                                                "quantity",
+                                                "rules",
+                                                Arrays.asList(
+                                                        ImmutableMap.of(
+                                                                "rule_type",
+                                                                "RANGE",
+                                                                "min_value",
+                                                                0,
+                                                                "max_value",
+                                                                1000))))));
+        DataValidatorTransform transform = new DataValidatorTransform(config, 
inputCatalogTable);
+        SeaTunnelRow row = new SeaTunnelRow(new Object[] {"500"});
+
+        assertEquals(row, transform.map(row));
+    }
 }

Reply via email to