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 <stanilov...@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();

Reply via email to