You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ignite.apache.org by zs...@apache.org on 2023/08/10 05:57:37 UTC
[ignite-3] branch main updated: IGNITE-19947 Sql. Fix CAST(n AS DECIMAL(precision, scale)) behaviour for scale > precision issues (#2396)
This is an automated email from the ASF dual-hosted git repository.
zstan pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/ignite-3.git
The following commit(s) were added to refs/heads/main by this push:
new ae8c6c5011 IGNITE-19947 Sql. Fix CAST(n AS DECIMAL(precision, scale)) behaviour for scale > precision issues (#2396)
ae8c6c5011 is described below
commit ae8c6c5011b96dd5017a154dc38208b78b6debaa
Author: Evgeniy Stanilovskiy <st...@gmail.com>
AuthorDate: Thu Aug 10 08:57:32 2023 +0300
IGNITE-19947 Sql. Fix CAST(n AS DECIMAL(precision, scale)) behaviour for scale > precision issues (#2396)
---
.../internal/sql/engine/ItDataTypesTest.java | 3 +
.../sql/engine/exec/exp/IgniteSqlFunctions.java | 111 +++++++++++++--------
.../engine/exec/exp/IgniteSqlFunctionsTest.java | 34 ++++++-
3 files changed, 104 insertions(+), 44 deletions(-)
diff --git a/modules/runner/src/integrationTest/java/org/apache/ignite/internal/sql/engine/ItDataTypesTest.java b/modules/runner/src/integrationTest/java/org/apache/ignite/internal/sql/engine/ItDataTypesTest.java
index 751cad3bd9..322a12bdd7 100644
--- a/modules/runner/src/integrationTest/java/org/apache/ignite/internal/sql/engine/ItDataTypesTest.java
+++ b/modules/runner/src/integrationTest/java/org/apache/ignite/internal/sql/engine/ItDataTypesTest.java
@@ -356,6 +356,7 @@ public class ItDataTypesTest extends ClusterPerClassIntegrationTest {
// ignored
RelDataType numeric = decimalType(4);
+ // TODO Align test datasets https://issues.apache.org/jira/browse/IGNITE-20130
return Stream.of(
// String
arguments(CaseStatus.RUN, varcharType, "100", decimalType(3), bigDecimalVal("100")),
@@ -498,6 +499,8 @@ public class ItDataTypesTest extends ClusterPerClassIntegrationTest {
// Decimal
arguments(CaseStatus.RUN, decimalType(1, 1), new BigDecimal("0.1"), decimalType(1, 1), bigDecimalVal("0.1")),
arguments(CaseStatus.RUN, decimalType(3), new BigDecimal("100"), decimalType(3), bigDecimalVal("100")),
+ // passed with runtime call and failed with parsing substitution
+ arguments(CaseStatus.SKIP, decimalType(5, 2), new BigDecimal("100.16"), decimalType(4, 1), bigDecimalVal("100.2")),
arguments(CaseStatus.RUN, decimalType(3), new BigDecimal("100"), decimalType(3, 0), bigDecimalVal("100")),
// TODO Uncomment these test cases after https://issues.apache.org/jira/browse/IGNITE-19822 is fixed.
arguments(CaseStatus.SKIP, decimalType(3), new BigDecimal("100"), decimalType(4, 1), bigDecimalVal("100.0")),
diff --git a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/exec/exp/IgniteSqlFunctions.java b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/exec/exp/IgniteSqlFunctions.java
index f69a2bfa29..e1d4f7886f 100644
--- a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/exec/exp/IgniteSqlFunctions.java
+++ b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/exec/exp/IgniteSqlFunctions.java
@@ -62,6 +62,7 @@ import org.jetbrains.annotations.Nullable;
public class IgniteSqlFunctions {
private static final DateTimeFormatter ISO_LOCAL_DATE_TIME_EX;
private static final String NUMERIC_FIELD_OVERFLOW_ERROR = "Numeric field overflow";
+ private static final RoundingMode roundingMode = RoundingMode.HALF_UP;
static {
ISO_LOCAL_DATE_TIME_EX = new DateTimeFormatterBuilder()
@@ -145,25 +146,25 @@ public class IgniteSqlFunctions {
/** CAST(java long AS DECIMAL). */
public static BigDecimal toBigDecimal(long val, int precision, int scale) {
BigDecimal decimal = BigDecimal.valueOf(val);
- return convertDecimal(decimal, precision, scale);
+ return toBigDecimal(decimal, precision, scale);
}
/** CAST(INT AS DECIMAL). */
public static BigDecimal toBigDecimal(int val, int precision, int scale) {
BigDecimal decimal = new BigDecimal(val);
- return convertDecimal(decimal, precision, scale);
+ return toBigDecimal(decimal, precision, scale);
}
/** CAST(java short AS DECIMAL). */
public static BigDecimal toBigDecimal(short val, int precision, int scale) {
BigDecimal decimal = new BigDecimal(val);
- return convertDecimal(decimal, precision, scale);
+ return toBigDecimal(decimal, precision, scale);
}
/** CAST(java byte AS DECIMAL). */
public static BigDecimal toBigDecimal(byte val, int precision, int scale) {
BigDecimal decimal = new BigDecimal(val);
- return convertDecimal(decimal, precision, scale);
+ return toBigDecimal(decimal, precision, scale);
}
/** CAST(BOOL AS DECIMAL). */
@@ -177,29 +178,7 @@ public class IgniteSqlFunctions {
return null;
}
BigDecimal decimal = new BigDecimal(s.trim());
- return convertDecimal(decimal, precision, scale);
- }
-
- /** CAST(REAL AS DECIMAL). */
- public static BigDecimal toBigDecimal(Number num, int precision, int scale) {
- if (num == null) {
- return null;
- }
-
- BigDecimal dec;
- if (num instanceof Float) {
- dec = new BigDecimal(num.floatValue());
- } else if (num instanceof Double) {
- dec = new BigDecimal(num.doubleValue());
- } else if (num instanceof BigDecimal) {
- dec = (BigDecimal) num;
- } else if (num instanceof BigInteger) {
- dec = new BigDecimal((BigInteger) num);
- } else {
- dec = new BigDecimal(num.longValue());
- }
-
- return convertDecimal(dec, precision, scale);
+ return toBigDecimal(decimal, precision, scale);
}
/** Cast object depending on type to DECIMAL. */
@@ -213,40 +192,88 @@ public class IgniteSqlFunctions {
}
return o instanceof Number ? toBigDecimal((Number) o, precision, scale)
- : toBigDecimal(o.toString(), precision, scale);
+ : toBigDecimal(o.toString(), precision, scale);
}
/**
- * Converts the given {@code BigDecimal} to a decimal with the given {@code precision} and {@code scale}
+ * Converts the given {@code Number} to a decimal with the given {@code precision} and {@code scale}
* according to SQL spec for CAST specification: General Rules, 8.
*/
- public static BigDecimal convertDecimal(BigDecimal value, int precision, int scale) {
+ public static BigDecimal toBigDecimal(Number value, int precision, int scale) {
assert precision > 0 : "Invalid precision: " + precision;
+ if (value == null) {
+ return null;
+ }
+
int defaultPrecision = IgniteTypeSystem.INSTANCE.getDefaultPrecision(SqlTypeName.DECIMAL);
+
if (precision == defaultPrecision) {
+ BigDecimal dec = convertToBigDecimal(value);
// This branch covers at least one known case: access to dynamic parameter from context.
// In this scenario precision = DefaultTypePrecision, because types for dynamic params
// are created by toSql(createType(param.class)).
- return value;
+ return dec;
}
- boolean nonZero = !value.unscaledValue().equals(BigInteger.ZERO);
+ if (value.longValue() == 0) {
+ return processFractionData(value, precision, scale);
+ } else {
+ return processValueWithIntegralPart(value, precision, scale);
+ }
+ }
- if (nonZero) {
- if (scale > precision) {
- throw new SqlException(RUNTIME_ERR, NUMERIC_FIELD_OVERFLOW_ERROR);
- } else {
- int currentSignificantDigits = value.precision() - value.scale();
- int expectedSignificantDigits = precision - scale;
+ private static BigDecimal processValueWithIntegralPart(Number value, int precision, int scale) {
+ BigDecimal dec = convertToBigDecimal(value);
- if (currentSignificantDigits > expectedSignificantDigits) {
- throw new SqlException(RUNTIME_ERR, NUMERIC_FIELD_OVERFLOW_ERROR);
- }
+ if (scale > precision) {
+ throw new SqlException(RUNTIME_ERR, NUMERIC_FIELD_OVERFLOW_ERROR);
+ } else {
+ int currentSignificantDigits = dec.precision() - dec.scale();
+ int expectedSignificantDigits = precision - scale;
+
+ if (currentSignificantDigits > expectedSignificantDigits) {
+ throw new SqlException(RUNTIME_ERR, NUMERIC_FIELD_OVERFLOW_ERROR);
}
}
- return value.setScale(scale, RoundingMode.HALF_UP);
+ return dec.setScale(scale, roundingMode);
+ }
+
+ private static BigDecimal processFractionData(Number value, int precision, int scale) {
+ BigDecimal num = convertToBigDecimal(value);
+
+ if (num.unscaledValue().equals(BigInteger.ZERO)) {
+ return num.setScale(scale, RoundingMode.UNNECESSARY);
+ }
+
+ // skip all fractional part after scale
+ BigDecimal num0 = num.movePointRight(scale).setScale(0, RoundingMode.DOWN);
+
+ int numPrecision = Math.min(num0.precision(), scale);
+
+ if (numPrecision > precision) {
+ throw new SqlException(RUNTIME_ERR, NUMERIC_FIELD_OVERFLOW_ERROR);
+ }
+
+ return num.setScale(scale, roundingMode);
+ }
+
+ private static BigDecimal convertToBigDecimal(Number value) {
+ BigDecimal dec;
+ if (value instanceof Float) {
+ dec = new BigDecimal(value.floatValue());
+ } else if (value instanceof Double) {
+ dec = new BigDecimal(value.doubleValue());
+ } else if (value instanceof BigDecimal) {
+ dec = (BigDecimal) value;
+ } else if (value instanceof BigInteger) {
+ dec = new BigDecimal((BigInteger) value);
+ } else {
+ dec = new BigDecimal(value.longValue());
+ }
+
+ return dec;
}
/** CAST(VARCHAR AS VARBINARY). */
diff --git a/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/exec/exp/IgniteSqlFunctionsTest.java b/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/exec/exp/IgniteSqlFunctionsTest.java
index be033ab911..962dc1b6f3 100644
--- a/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/exec/exp/IgniteSqlFunctionsTest.java
+++ b/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/exec/exp/IgniteSqlFunctionsTest.java
@@ -146,6 +146,19 @@ public class IgniteSqlFunctionsTest {
);
}
+ @Test
+ public void testFractionsToDecimal() {
+ assertEquals(
+ new BigDecimal("0.0101"),
+ IgniteSqlFunctions.toBigDecimal(Float.valueOf(0.0101f), 3, 4)
+ );
+
+ assertEquals(
+ new BigDecimal("0.0101"),
+ IgniteSqlFunctions.toBigDecimal(Double.valueOf(0.0101d), 3, 4)
+ );
+ }
+
/** Access of dynamic parameter value - parameter is not transformed. */
@Test
public void testToBigDecimalFromObject() {
@@ -170,13 +183,30 @@ public class IgniteSqlFunctionsTest {
"0.1, 1, 1, 0.1",
+ "0.0101, 3, 4, 0.0101",
+ "0.1234, 2, 1, 0.1",
+ "0.1234, 5, 4, 0.1234",
+ "0.123, 5, 4, 0.1230",
+
"0.12, 2, 1, 0.1",
"0.12, 2, 2, 0.12",
+ "0.12, 2, 3, overflow",
+ "0.12, 3, 3, 0.120",
+ "0.12, 3, 2, 0.12",
+
"0.123, 2, 2, 0.12",
"0.123, 2, 1, 0.1",
"0.123, 5, 5, 0.12300",
+ "1.123, 4, 3, 1.123",
+ "1.123, 4, 4, overflow",
- "1.23, 2, 1, 1.2",
+ "0.0011, 1, 3, 0.001",
+ "0.0016, 1, 3, 0.002",
+ "-0.0011, 1, 3, -0.001",
+ "-0.0011, 1, 4, overflow",
+ "-0.0011, 2, 4, -0.0011",
+ "-0.0011, 3, 4, -0.0011",
+ "-0.0011, 2, 5, overflow",
"10, 2, 0, 10",
"10.0, 2, 0, 10",
@@ -202,7 +232,7 @@ public class IgniteSqlFunctionsTest {
"-10.1, 2, 1, overflow",
})
public void testConvertDecimal(String input, int precision, int scale, String result) {
- Supplier<BigDecimal> convert = () -> IgniteSqlFunctions.convertDecimal(new BigDecimal(input), precision, scale);
+ Supplier<BigDecimal> convert = () -> IgniteSqlFunctions.toBigDecimal(new BigDecimal(input), precision, scale);
if (!"overflow".equalsIgnoreCase(result)) {
BigDecimal expected = convert.get();