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));
+ }
}