This is an automated email from the ASF dual-hosted git repository.
jooger 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 6ff9a297e4 IGNITE-23171: Sql. Result of division doesn't match derived
type (#4478)
6ff9a297e4 is described below
commit 6ff9a297e4bd7dc941151ffbd73baa54e9bac61b
Author: Max Zhuravkov <[email protected]>
AuthorDate: Fri Oct 11 12:21:06 2024 +0300
IGNITE-23171: Sql. Result of division doesn't match derived type (#4478)
---
.../Apache.Ignite.Tests/Linq/LinqTests.Cast.cs | 5 +-
.../internal/sql/engine/ItDataTypesTest.java | 2 +-
.../engine/datatypes/ItDivisionDecimalTest.java | 238 +++++++++++++++++++++
.../sql/engine/exec/exp/IgniteExpressions.java | 6 +
.../sql/engine/exec/exp/IgniteSqlFunctions.java | 16 +-
.../internal/sql/engine/exec/exp/RexImpTable.java | 16 +-
.../sql/engine/exec/exp/agg/Accumulators.java | 4 +-
.../sql/engine/sql/fun/IgniteSqlOperatorTable.java | 3 +-
.../internal/sql/engine/util/IgniteMath.java | 17 ++
.../internal/sql/engine/util/IgniteMethod.java | 6 +-
.../NumericBinaryOperationsExecutionTest.java | 4 -
.../engine/exec/exp/IgniteSqlFunctionsTest.java | 5 +-
.../sql/engine/type/IgniteTypeSystemTest.java | 103 ++++++++-
13 files changed, 394 insertions(+), 31 deletions(-)
diff --git
a/modules/platforms/dotnet/Apache.Ignite.Tests/Linq/LinqTests.Cast.cs
b/modules/platforms/dotnet/Apache.Ignite.Tests/Linq/LinqTests.Cast.cs
index f46c74809b..f0f392a6bc 100644
--- a/modules/platforms/dotnet/Apache.Ignite.Tests/Linq/LinqTests.Cast.cs
+++ b/modules/platforms/dotnet/Apache.Ignite.Tests/Linq/LinqTests.Cast.cs
@@ -64,6 +64,7 @@ public partial class LinqTests
query.ToString());
}
+ [Ignore("IGNITE-23243 Value was either too large or too small for a
Decimal")]
[Test]
public void TestCastToDecimalPrecision()
{
@@ -75,8 +76,8 @@ public partial class LinqTests
var res = query.ToList();
- // TODO IGNITE-23171 Sql. Result of division doesn't match derived type
- // Assert.AreEqual(900m / 33m, res[0]);
+ // The result can not be presented by decimal type.
+ // The expected value should be replaced when
https://issues.apache.org/jira/browse/IGNITE-23243 is fixed.
Assert.AreEqual(27.27272727272727m, res[0]);
StringAssert.Contains("select cast((cast(_T0.VAL as decimal(60, 30)) /
?) as decimal(60, 30))", query.ToString());
diff --git
a/modules/sql-engine/src/integrationTest/java/org/apache/ignite/internal/sql/engine/ItDataTypesTest.java
b/modules/sql-engine/src/integrationTest/java/org/apache/ignite/internal/sql/engine/ItDataTypesTest.java
index ec6c6b8b1b..d96838db87 100644
---
a/modules/sql-engine/src/integrationTest/java/org/apache/ignite/internal/sql/engine/ItDataTypesTest.java
+++
b/modules/sql-engine/src/integrationTest/java/org/apache/ignite/internal/sql/engine/ItDataTypesTest.java
@@ -446,7 +446,7 @@ public class ItDataTypesTest extends BaseSqlIntegrationTest
{
assertQuery("SELECT DECIMAL '10.000' + DECIMAL '0.1'").returns(new
BigDecimal(("10.100"))).check();
assertQuery("SELECT DECIMAL '10.000' - DECIMAL '0.01'").returns(new
BigDecimal(("9.990"))).check();
assertQuery("SELECT DECIMAL '10.000' * DECIMAL '0.01'").returns(new
BigDecimal(("0.10000"))).check();
- assertQuery("SELECT DECIMAL '10.000' / DECIMAL '0.01'").returns(new
BigDecimal(("1000.0"))).check();
+ assertQuery("SELECT DECIMAL '10.000' / DECIMAL '0.01'").returns(new
BigDecimal(("1000.0000000"))).check();
assertQuery("SELECT CASE WHEN true THEN DECIMAL '1.00' ELSE DECIMAL
'0' END")
.returns(new BigDecimal("1.00")).check();
diff --git
a/modules/sql-engine/src/integrationTest/java/org/apache/ignite/internal/sql/engine/datatypes/ItDivisionDecimalTest.java
b/modules/sql-engine/src/integrationTest/java/org/apache/ignite/internal/sql/engine/datatypes/ItDivisionDecimalTest.java
new file mode 100644
index 0000000000..fea774bf8b
--- /dev/null
+++
b/modules/sql-engine/src/integrationTest/java/org/apache/ignite/internal/sql/engine/datatypes/ItDivisionDecimalTest.java
@@ -0,0 +1,238 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.sql.engine.datatypes;
+
+import static org.apache.ignite.internal.lang.IgniteStringFormatter.format;
+
+import java.math.BigDecimal;
+import java.math.RoundingMode;
+import java.util.Random;
+import org.apache.calcite.rel.type.RelDataType;
+import org.apache.calcite.sql.type.SqlTypeName;
+import org.apache.ignite.internal.sql.BaseSqlIntegrationTest;
+import org.apache.ignite.internal.sql.engine.type.IgniteTypeFactory;
+import org.apache.ignite.internal.sql.engine.type.IgniteTypeSystem;
+import org.apache.ignite.internal.sql.engine.util.Commons;
+import org.apache.ignite.internal.sql.engine.util.MetadataMatcher;
+import org.apache.ignite.internal.testframework.IgniteTestUtils;
+import org.apache.ignite.sql.ColumnType;
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.CsvSource;
+
+/** Test cases for the division operator involving decimal operand(s). */
+public class ItDivisionDecimalTest extends BaseSqlIntegrationTest {
+
+ @BeforeAll
+ public static void createTable() {
+ sql("CREATE TABLE t (id INT PRIMARY KEY, "
+ + " ti TINYINT, si SMALLINT, i INT, bi BIGINT, "
+ + " r REAL, d DOUBLE, "
+ + " d3_1 DECIMAL(3, 1), d5_2 DECIMAL(5, 2)"
+ + ")");
+ }
+
+ /**
+ * Drops all created tables.
+ */
+ @AfterAll
+ public void dropTables() {
+ var igniteTables = CLUSTER.aliveNode().tables();
+
+ for (var table : igniteTables.tables()) {
+ sql("DROP TABLE " + table.name());
+ }
+ }
+
+
+ @AfterEach
+ void clearTable() {
+ sql("DELETE FROM t");
+ }
+
+ @ParameterizedTest
+ @CsvSource({
+ "5, 1, 5, 1",
+ "5, 2, 5, 2",
+ "5, 3, 5, 3",
+ "5, 4, 5, 4",
+
+ "10, 5, 20, 5",
+ "10, 6, 20, 6",
+ "10, 7, 20, 7",
+ "10, 8, 20, 8",
+ "10, 9, 20, 9",
+ })
+ public void decimalDivide(int p1, int s1, int p2, int s2) {
+ IgniteTypeFactory tf = Commons.typeFactory();
+
+ RelDataType t1 = tf.createSqlType(SqlTypeName.DECIMAL, p1, s1);
+ RelDataType t2 = tf.createSqlType(SqlTypeName.DECIMAL, p2, s2);
+ RelDataType rt =
IgniteTypeSystem.INSTANCE.deriveDecimalDivideType(Commons.typeFactory(), t1,
t2);
+
+ long seed = System.nanoTime();
+ log.info("Seed: {}", seed);
+
+ Random rnd = new Random();
+ rnd.setSeed(seed);
+
+ BigDecimal a1 = IgniteTestUtils.randomBigDecimal(rnd,
t1.getPrecision(), t1.getScale());
+ BigDecimal a2 = IgniteTestUtils.randomBigDecimal(rnd,
t2.getPrecision(), t2.getScale());
+ BigDecimal rs = a1.divide(a2, rt.getScale(), RoundingMode.HALF_UP);
+
+ String type1 = format("DECIMAL({}, {})", p1, s1);
+ String type2 = format("DECIMAL({}, {})", p2, s2);
+
+ // Literals no casts
+ assertQuery("SELECT " + a1 + "/" + a2)
+ .returns(rs)
+ .columnMetadata(new MetadataMatcher()
+ .type(ColumnType.DECIMAL)
+ .precision(rt.getPrecision())
+ .scale(rt.getScale()))
+ .check();
+
+ // Literals w/ casts
+ assertQuery("SELECT " + a1 + "::" + type1 + "/" + a2 + "::" + type2)
+ .returns(rs)
+ .columnMetadata(new MetadataMatcher()
+ .type(ColumnType.DECIMAL)
+ .precision(rt.getPrecision())
+ .scale(rt.getScale()))
+ .check();
+
+ // Dynamic params
+ assertQuery("SELECT ?::" + type1 + "/?::" + type2)
+ .withParams(a1, a2)
+ .returns(rs)
+ .columnMetadata(new MetadataMatcher()
+ .type(ColumnType.DECIMAL)
+ .precision(rt.getPrecision())
+ .scale(rt.getScale()))
+ .check();
+ }
+
+
+ @Test
+ public void decimalTinyint() {
+ sql("INSERT INTO t (id, ti, d3_1) VALUES (1, 10, 3.1)");
+
+ assertQuery("SELECT ti/d3_1 FROM t")
+ .returns(new BigDecimal("3.225806"))
+ .columnMetadata(new
MetadataMatcher().type(ColumnType.DECIMAL).precision(10).scale(6))
+ .check();
+
+ assertQuery("SELECT d3_1/ti FROM t")
+ .returns(new BigDecimal("0.310000"))
+ .columnMetadata(new
MetadataMatcher().type(ColumnType.DECIMAL).precision(8).scale(6))
+ .check();
+ }
+
+ @Test
+ public void decimalSmallint() {
+ sql("INSERT INTO t (id, si, d3_1) VALUES (1, 10, 3.1)");
+
+ assertQuery("SELECT si/d3_1 FROM t")
+ .returns(new BigDecimal("3.225806"))
+ .columnMetadata(new
MetadataMatcher().type(ColumnType.DECIMAL).precision(12).scale(6))
+ .check();
+
+ assertQuery("SELECT d3_1/si FROM t")
+ .returns(new BigDecimal("0.3100000"))
+ .columnMetadata(new
MetadataMatcher().type(ColumnType.DECIMAL).precision(9).scale(7))
+ .check();
+ }
+
+ @Test
+ public void decimalInt() {
+ sql("INSERT INTO t (id, i, d3_1) VALUES (1, 10, 3.1)");
+
+ assertQuery("SELECT i/d3_1 FROM t")
+ .returns(new BigDecimal("3.225806"))
+ .columnMetadata(new
MetadataMatcher().type(ColumnType.DECIMAL).precision(17).scale(6))
+ .check();
+
+ assertQuery("SELECT d3_1/i FROM t")
+ .returns(new BigDecimal("0.310000000000"))
+ .columnMetadata(new
MetadataMatcher().type(ColumnType.DECIMAL).precision(14).scale(12))
+ .check();
+ }
+
+ @Test
+ public void decimalBigInt() {
+ sql("INSERT INTO t (id, bi, d3_1) VALUES (1, 10, 3.1)");
+
+ assertQuery("SELECT bi/d3_1 FROM t")
+ .returns(new BigDecimal("3.225806"))
+ .columnMetadata(new
MetadataMatcher().type(ColumnType.DECIMAL).precision(26).scale(6))
+ .check();
+
+ assertQuery("SELECT d3_1/bi FROM t")
+ .returns(new BigDecimal("0.310000000000000000000"))
+ .columnMetadata(new
MetadataMatcher().type(ColumnType.DECIMAL).precision(23).scale(21))
+ .check();
+ }
+
+ @Test
+ public void decimalReal() {
+ sql("INSERT INTO t (id, r, d3_1) VALUES (1, 10, 2.0)");
+
+ assertQuery("SELECT r/d3_1 FROM t")
+ .returns(5.0d)
+ .columnMetadata(new MetadataMatcher().type(ColumnType.DOUBLE))
+ .check();
+
+ assertQuery("SELECT d3_1/r FROM t")
+ .returns(0.2d)
+ .columnMetadata(new MetadataMatcher().type(ColumnType.DOUBLE))
+ .check();
+ }
+
+ @Test
+ public void decimalDouble() {
+ sql("INSERT INTO t (id, d, d3_1) VALUES (1, 10, 2.0)");
+
+ assertQuery("SELECT d/d3_1 FROM t")
+ .returns(5.0d)
+ .columnMetadata(new MetadataMatcher().type(ColumnType.DOUBLE))
+ .check();
+
+ assertQuery("SELECT d3_1/d FROM t")
+ .returns(0.2d)
+ .columnMetadata(new MetadataMatcher().type(ColumnType.DOUBLE))
+ .check();
+ }
+
+ @Test
+ public void decimalDecimal() {
+ sql("INSERT INTO t (id, d5_2, d3_1) VALUES (1, 10.00, 3.1)");
+
+ assertQuery("SELECT d5_2/d3_1 FROM t")
+ .returns(new BigDecimal("3.225806"))
+ .columnMetadata(new
MetadataMatcher().type(ColumnType.DECIMAL).precision(10).scale(6))
+ .check();
+
+ assertQuery("SELECT d3_1/d5_2 FROM t")
+ .returns(new BigDecimal("0.3100000"))
+ .columnMetadata(new
MetadataMatcher().type(ColumnType.DECIMAL).precision(11).scale(7))
+ .check();
+ }
+}
diff --git
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/exec/exp/IgniteExpressions.java
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/exec/exp/IgniteExpressions.java
index 99d81c1e36..1291c0fc1b 100644
---
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/exec/exp/IgniteExpressions.java
+++
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/exec/exp/IgniteExpressions.java
@@ -44,6 +44,12 @@ public class IgniteExpressions {
}
}
+ /** Make decimal division expression. */
+ public static Expression makeDecimalDivision(Expression left, Expression
right, int precision, int scale) {
+ return Expressions.call(IgniteMath.class, "decimalDivide", left, right,
+ Expressions.constant(precision, int.class),
Expressions.constant(scale, int.class));
+ }
+
/** Make unary expression with arithmetic operations override. */
public static Expression makeUnary(ExpressionType unaryType, Expression
operand) {
switch (unaryType) {
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 17e18e4d9a..6af137c138 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
@@ -35,9 +35,9 @@ import org.apache.calcite.linq4j.function.NonDeterministic;
import org.apache.calcite.runtime.SqlFunctions;
import org.apache.calcite.sql.type.SqlTypeName;
import org.apache.ignite.internal.lang.IgniteStringBuilder;
-import org.apache.ignite.internal.sql.engine.sql.fun.IgniteSqlOperatorTable;
import org.apache.ignite.internal.sql.engine.type.IgniteTypeSystem;
import org.apache.ignite.internal.sql.engine.util.Commons;
+import org.apache.ignite.internal.sql.engine.util.IgniteMath;
import org.apache.ignite.internal.sql.engine.util.TypeUtils;
import org.apache.ignite.sql.SqlException;
import org.jetbrains.annotations.Nullable;
@@ -46,8 +46,6 @@ import org.jetbrains.annotations.Nullable;
* Ignite SQL functions.
*/
public class IgniteSqlFunctions {
- private static final RoundingMode roundingMode = RoundingMode.HALF_UP;
-
/**
* Default constructor.
*/
@@ -356,14 +354,6 @@ public class IgniteSqlFunctions {
}
}
- /**
- * Division function for REDUCE phase of AVG aggregate. Precision and
scale is only used by type inference
- * (see {@link IgniteSqlOperatorTable#DECIMAL_DIVIDE}, their values are
ignored at runtime.
- */
- public static BigDecimal decimalDivide(BigDecimal sum, BigDecimal cnt, int
p, int s) {
- return sum.divide(cnt, s, roundingMode);
- }
-
private static BigDecimal processValueWithIntegralPart(Number value, int
precision, int scale) {
BigDecimal dec = convertToBigDecimal(value);
@@ -378,7 +368,7 @@ public class IgniteSqlFunctions {
}
}
- return dec.setScale(scale, roundingMode);
+ return dec.setScale(scale, IgniteMath.ROUNDING_MODE);
}
private static BigDecimal processFractionData(Number value, int precision,
int scale) {
@@ -397,7 +387,7 @@ public class IgniteSqlFunctions {
throw numericOverflowError(precision, scale);
}
- return num.setScale(scale, roundingMode);
+ return num.setScale(scale, IgniteMath.ROUNDING_MODE);
}
private static BigDecimal convertToBigDecimal(Number value) {
diff --git
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/exec/exp/RexImpTable.java
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/exec/exp/RexImpTable.java
index 44e42b301c..d4525063e7 100644
---
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/exec/exp/RexImpTable.java
+++
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/exec/exp/RexImpTable.java
@@ -476,9 +476,10 @@ import org.checkerframework.checker.nullness.qual.Nullable;
* 5. Amend calls of:
* EnumUtils.fromInternal -> ConverterUtils.fromInternal
* Expressions.makeUnary -> IgniteExpressions.makeUnary
- * Expressions.makeBinary -> IgniteExpressions.makeBinary
+ * Expressions.makeBinary -> IgniteExpressions.makeBinary (or
IgniteExpressions.decimalDivision in cause of decimal division).
* Expressions.multiply -> IgniteExpressions.multiplyExact
* Expressions.divide -> IgniteExpressions.divideExact
+ * Expressions.divide -> IgniteExpressions.makeDecimalDivision (if one
or both operands are decimals)
* Expressions.negate -> IgniteExpressions.makeUnary
* Expressions.subtract -> IgniteExpressions.subtractExact
* Expressions.add -> IgniteExpressions.addExact
@@ -3077,6 +3078,19 @@ public class RexImpTable {
argValueList = FlatLists.append(argValueList, fieldComparator);
}
+ if (type0 == BigDecimal.class && type1 == BigDecimal.class && op ==
IgniteSqlOperatorTable.DIVIDE) {
+ int precision = call.getType().getPrecision();
+ int scale = call.getType().getScale();
+
+ assert precision != RelDataType.PRECISION_NOT_SPECIFIED || scale !=
RelDataType.SCALE_NOT_SPECIFIED
+ : "No precision/scale for decimal division. Return type: "
+ + call.getType()
+ + " operands: "
+ +
call.getOperands().stream().map(RexNode::getType).collect(Collectors.toList());
+
+ return IgniteExpressions.makeDecimalDivision(argValueList.get(0),
argValueList.get(1), precision, scale);
+ }
+
final Primitive primitive = Primitive.ofBoxOr(type0);
if (primitive == null
|| type1 == BigDecimal.class
diff --git
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/exec/exp/agg/Accumulators.java
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/exec/exp/agg/Accumulators.java
index a0a8b641eb..54bec16a39 100644
---
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/exec/exp/agg/Accumulators.java
+++
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/exec/exp/agg/Accumulators.java
@@ -37,9 +37,9 @@ import org.apache.calcite.avatica.util.ByteString;
import org.apache.calcite.rel.core.AggregateCall;
import org.apache.calcite.rel.type.RelDataType;
import org.apache.calcite.rel.type.RelDataTypeFactory;
-import org.apache.ignite.internal.sql.engine.exec.exp.IgniteSqlFunctions;
import org.apache.ignite.internal.sql.engine.type.IgniteCustomType;
import org.apache.ignite.internal.sql.engine.type.IgniteTypeFactory;
+import org.apache.ignite.internal.sql.engine.util.IgniteMath;
import org.apache.ignite.internal.util.ArrayUtils;
import org.apache.ignite.lang.ErrorGroups.Sql;
import org.apache.ignite.sql.SqlException;
@@ -341,7 +341,7 @@ public class Accumulators {
/** {@inheritDoc} */
@Override
public Object end() {
- return cnt.compareTo(BigDecimal.ZERO) == 0 ? null :
IgniteSqlFunctions.decimalDivide(sum, cnt, precision, scale);
+ return cnt.compareTo(BigDecimal.ZERO) == 0 ? null :
IgniteMath.decimalDivide(sum, cnt, precision, scale);
}
/** {@inheritDoc} */
diff --git
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/sql/fun/IgniteSqlOperatorTable.java
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/sql/fun/IgniteSqlOperatorTable.java
index db7c07c767..7e14873ae6 100644
---
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/sql/fun/IgniteSqlOperatorTable.java
+++
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/sql/fun/IgniteSqlOperatorTable.java
@@ -183,8 +183,7 @@ public class IgniteSqlOperatorTable extends
ReflectiveSqlOperatorTable {
SqlFunctionCategory.NUMERIC);
/**
- * Division operator used by REDUCE phase of AVG aggregate.
- * Uses provided values of {@code scale} and {@code precision} to return
inferred type.
+ * Division operator for decimal type. Uses provided values of {@code
scale} and {@code precision} to return inferred type.
*/
public static final SqlFunction DECIMAL_DIVIDE =
SqlBasicFunction.create("DECIMAL_DIVIDE",
new SqlReturnTypeInference() {
diff --git
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/util/IgniteMath.java
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/util/IgniteMath.java
index 8f724fd8ab..b1edf426ab 100644
---
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/util/IgniteMath.java
+++
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/util/IgniteMath.java
@@ -24,8 +24,11 @@ import static
org.apache.calcite.sql.type.SqlTypeName.TINYINT;
import static org.apache.ignite.lang.ErrorGroups.Sql.RUNTIME_ERR;
import java.math.BigDecimal;
+import java.math.RoundingMode;
import org.apache.calcite.sql.type.SqlTypeName;
+import org.apache.ignite.internal.sql.engine.sql.fun.IgniteSqlOperatorTable;
import org.apache.ignite.sql.SqlException;
+import org.jetbrains.annotations.Nullable;
/** Math operations with overflow checking. */
public class IgniteMath {
@@ -44,6 +47,9 @@ public class IgniteMath {
private static final double UPPER_FLOAT_DOUBLE = Float.MAX_VALUE;
private static final double LOWER_FLOAT_DOUBLE = -Float.MAX_VALUE;
+ /** Decimal rounding mode. */
+ public static final RoundingMode ROUNDING_MODE = RoundingMode.HALF_UP;
+
/** Returns the sum of its arguments, throwing an exception if the result
overflows an {@code long}. */
public static long addExact(long x, long y) {
long r = x + y;
@@ -270,6 +276,17 @@ public class IgniteMath {
return (byte) (x / y);
}
+ /**
+ * Decimal division. Precision is only used by type inferenc, its value is
ignored at runtime.
+ * See {@link IgniteSqlOperatorTable#DECIMAL_DIVIDE}.
+ */
+ public static @Nullable BigDecimal decimalDivide(@Nullable BigDecimal x,
@Nullable BigDecimal y, int p, int s) {
+ if (x == null || y == null) {
+ return null;
+ }
+ return x.divide(y, s, ROUNDING_MODE);
+ }
+
private static void throwDivisionByZero() {
throw new SqlException(RUNTIME_ERR, "Division by zero");
}
diff --git
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/util/IgniteMethod.java
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/util/IgniteMethod.java
index 8605a54231..ab20fcc849 100644
---
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/util/IgniteMethod.java
+++
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/util/IgniteMethod.java
@@ -117,10 +117,10 @@ public enum IgniteMethod {
TRUNCATE(IgniteSqlFunctions.class, "struncate", true),
/**
- * Division operator used by REDUCE phase of AVG aggregate.
- * See {@link IgniteSqlFunctions#decimalDivide(BigDecimal, BigDecimal,
int, int)}.
+ * Decimal division as well as division operator used by REDUCE phase of
AVG aggregate.
+ * See {@link IgniteMath#decimalDivide(BigDecimal, BigDecimal, int, int)}.
*/
- DECIMAL_DIVIDE(IgniteSqlFunctions.class, "decimalDivide",
BigDecimal.class, BigDecimal.class, int.class, int.class),
+ DECIMAL_DIVIDE(IgniteMath.class, "decimalDivide", BigDecimal.class,
BigDecimal.class, int.class, int.class),
/**
* Conversion of timestamp to string (precision aware).
diff --git
a/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/exec/coercion/NumericBinaryOperationsExecutionTest.java
b/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/exec/coercion/NumericBinaryOperationsExecutionTest.java
index 13ac23f4e5..4ae7031cc0 100644
---
a/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/exec/coercion/NumericBinaryOperationsExecutionTest.java
+++
b/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/exec/coercion/NumericBinaryOperationsExecutionTest.java
@@ -19,10 +19,8 @@ package org.apache.ignite.internal.sql.engine.exec.coercion;
import java.util.Set;
import
org.apache.ignite.internal.sql.engine.planner.datatypes.utils.NumericPair;
-import org.apache.ignite.internal.type.DecimalNativeType;
import org.apache.ignite.internal.type.NativeType;
import org.apache.ignite.internal.type.NativeTypes;
-import org.junit.jupiter.api.Assumptions;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.EnumSource;
@@ -61,8 +59,6 @@ public class NumericBinaryOperationsExecutionTest extends
BaseTypeCheckExecution
@EnumSource
public void divOp(NumericPair typePair) throws Exception {
String sql = "SELECT c1 / c2 FROM t";
- Assumptions.assumeFalse(typePair.first() instanceof DecimalNativeType
|| typePair.second() instanceof DecimalNativeType,
- "need to be fixed after:
https://issues.apache.org/jira/browse/IGNITE-23171");
try (ClusterWrapper testCluster = testCluster(typePair,
dataProviderStrict(typePair))) {
testCluster.process(sql, checkReturnResult());
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 c4b90693cd..da55cf97c6 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
@@ -35,6 +35,7 @@ import java.util.TimeZone;
import java.util.function.Supplier;
import org.apache.calcite.sql.type.SqlTypeName;
import org.apache.ignite.internal.sql.engine.type.IgniteTypeSystem;
+import org.apache.ignite.internal.sql.engine.util.IgniteMath;
import org.apache.ignite.lang.ErrorGroups.Sql;
import org.jetbrains.annotations.Nullable;
import org.junit.jupiter.api.Test;
@@ -535,10 +536,10 @@ public class IgniteSqlFunctionsTest {
BigDecimal denum = new BigDecimal(b);
if (expected != null) {
- BigDecimal actual = IgniteSqlFunctions.decimalDivide(num, denum,
4, 2);
+ BigDecimal actual = IgniteMath.decimalDivide(num, denum, 4, 2);
assertEquals(new BigDecimal(expected), actual);
} else {
- assertThrows(ArithmeticException.class, () ->
IgniteSqlFunctions.decimalDivide(num, denum, 4, 2));
+ assertThrows(ArithmeticException.class, () ->
IgniteMath.decimalDivide(num, denum, 4, 2));
}
}
diff --git
a/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/type/IgniteTypeSystemTest.java
b/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/type/IgniteTypeSystemTest.java
index 8be9cced4c..47c469619f 100644
---
a/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/type/IgniteTypeSystemTest.java
+++
b/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/type/IgniteTypeSystemTest.java
@@ -168,5 +168,106 @@ public class IgniteTypeSystemTest extends
BaseIgniteAbstractTest {
native2relationalType(typeFactory, NativeTypes.DOUBLE)
)
);
- }
+ }
+
+
+ @ParameterizedTest
+ @MethodSource("deriveDivideDecimalArgs")
+ void deriveDivide(RelDataType a1, RelDataType a2, RelDataType rt) {
+ RelDataType actual =
typeSystem.deriveDecimalDivideType(Commons.typeFactory(), a1, a2);
+
+ assertThat(actual, Matchers.equalTo(rt));
+ }
+
+ private static Stream<Arguments> deriveDivideDecimalArgs() {
+ IgniteTypeFactory typeFactory = Commons.typeFactory();
+
+ return Stream.of(
+ Arguments.of(
+ native2relationalType(typeFactory,
NativeTypes.decimalOf(2, 0)),
+ native2relationalType(typeFactory,
NativeTypes.decimalOf(2, 0)),
+ native2relationalType(typeFactory,
NativeTypes.decimalOf(8, 6))
+ ),
+ Arguments.of(
+ native2relationalType(typeFactory,
NativeTypes.decimalOf(5, 0)),
+ native2relationalType(typeFactory,
NativeTypes.decimalOf(5, 0)),
+ native2relationalType(typeFactory,
NativeTypes.decimalOf(11, 6))
+ ),
+ Arguments.of(
+ native2relationalType(typeFactory,
NativeTypes.decimalOf(5, 1)),
+ native2relationalType(typeFactory,
NativeTypes.decimalOf(5, 0)),
+ native2relationalType(typeFactory,
NativeTypes.decimalOf(11, 7))
+ ),
+ Arguments.of(
+ native2relationalType(typeFactory,
NativeTypes.decimalOf(5, 1)),
+ native2relationalType(typeFactory,
NativeTypes.decimalOf(5, 1)),
+ native2relationalType(typeFactory,
NativeTypes.decimalOf(12, 7))
+ ),
+ Arguments.of(
+ native2relationalType(typeFactory,
NativeTypes.decimalOf(5, 2)),
+ native2relationalType(typeFactory,
NativeTypes.decimalOf(5, 2)),
+ native2relationalType(typeFactory,
NativeTypes.decimalOf(13, 8))
+ ),
+ Arguments.of(
+ native2relationalType(typeFactory,
NativeTypes.decimalOf(5, 3)),
+ native2relationalType(typeFactory,
NativeTypes.decimalOf(5, 3)),
+ native2relationalType(typeFactory,
NativeTypes.decimalOf(14, 9))
+ ),
+ Arguments.of(
+ native2relationalType(typeFactory,
NativeTypes.decimalOf(10, 5)),
+ native2relationalType(typeFactory,
NativeTypes.decimalOf(10, 5)),
+ native2relationalType(typeFactory,
NativeTypes.decimalOf(26, 16))
+ ),
+ Arguments.of(
+ native2relationalType(typeFactory,
NativeTypes.decimalOf(10, 9)),
+ native2relationalType(typeFactory,
NativeTypes.decimalOf(10, 9)),
+ native2relationalType(typeFactory,
NativeTypes.decimalOf(30, 20))
+ ),
+ Arguments.of(
+ native2relationalType(typeFactory,
NativeTypes.decimalOf(32000, 9)),
+ native2relationalType(typeFactory,
NativeTypes.decimalOf(32000, 0)),
+ native2relationalType(typeFactory,
NativeTypes.decimalOf(32767, 776))
+ ),
+ Arguments.of(
+ native2relationalType(typeFactory,
NativeTypes.decimalOf(32000, 0)),
+ native2relationalType(typeFactory,
NativeTypes.decimalOf(32000, 9)),
+ native2relationalType(typeFactory,
NativeTypes.decimalOf(32767, 758))
+ ),
+ Arguments.of(
+ native2relationalType(typeFactory,
NativeTypes.decimalOf(32000, 9)),
+ native2relationalType(typeFactory,
NativeTypes.decimalOf(32000, 9)),
+ native2relationalType(typeFactory,
NativeTypes.decimalOf(32767, 767))
+ ),
+ Arguments.of(
+ native2relationalType(typeFactory,
NativeTypes.decimalOf(32767, 9)),
+ native2relationalType(typeFactory,
NativeTypes.decimalOf(32767, 0)),
+ native2relationalType(typeFactory,
NativeTypes.decimalOf(32767, 9))
+ ),
+ Arguments.of(
+ native2relationalType(typeFactory,
NativeTypes.decimalOf(32767, 0)),
+ native2relationalType(typeFactory,
NativeTypes.decimalOf(32767, 150)),
+ native2relationalType(typeFactory,
NativeTypes.decimalOf(32767, 0))
+ ),
+ Arguments.of(
+ native2relationalType(typeFactory,
NativeTypes.decimalOf(32767, 32767)),
+ native2relationalType(typeFactory,
NativeTypes.decimalOf(32767, 0)),
+ native2relationalType(typeFactory,
NativeTypes.decimalOf(32767, 32767))
+ ),
+ Arguments.of(
+ native2relationalType(typeFactory,
NativeTypes.decimalOf(32767, 32767)),
+ native2relationalType(typeFactory,
NativeTypes.decimalOf(32767, 32765)),
+ native2relationalType(typeFactory,
NativeTypes.decimalOf(32767, 2))
+ ),
+ Arguments.of(
+ native2relationalType(typeFactory,
NativeTypes.decimalOf(32767, 32767)),
+ native2relationalType(typeFactory,
NativeTypes.decimalOf(32767, 32667)),
+ native2relationalType(typeFactory,
NativeTypes.decimalOf(32767, 100))
+ ),
+ Arguments.of(
+ native2relationalType(typeFactory,
NativeTypes.decimalOf(32767, 32767)),
+ native2relationalType(typeFactory,
NativeTypes.decimalOf(32767, 32767)),
+ native2relationalType(typeFactory,
NativeTypes.decimalOf(32767, 0))
+ )
+ );
+ }
}