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 951d199062 IGNITE-18662 Sql. Numeric to/from decimal cast with
overflow does not produce an error (#2786)
951d199062 is described below
commit 951d19906262f772fc3ae7695ed0b53d48719c91
Author: Evgeniy Stanilovskiy <[email protected]>
AuthorDate: Mon Nov 20 09:14:23 2023 +0300
IGNITE-18662 Sql. Numeric to/from decimal cast with overflow does not
produce an error (#2786)
---
.../Apache.Ignite.Tests/Linq/LinqTests.Cast.cs | 6 +-
.../internal/sql/engine/ItDataTypesTest.java | 102 +++++++
.../ignite/internal/sql/engine/ItDmlTest.java | 48 ++++
.../sql/engine/ItDynamicParameterTest.java | 1 +
.../internal/sql/engine/ItFunctionsTest.java | 6 +-
.../ignite/internal/sql/engine/ItIntervalTest.java | 7 +
.../sql/types/decimal/cast_from_decimal.test | 52 ++--
.../sql/types/decimal/cast_to_decimal.test | 96 -------
.../sql/engine/exec/exp/ConverterUtils.java | 31 +++
.../sql/engine/exec/exp/IgniteExpressions.java | 179 ++++++++++++
.../sql/engine/exec/exp/IgniteSqlFunctions.java | 2 +-
.../internal/sql/engine/exec/exp/RexImpTable.java | 16 +-
.../engine/prepare/IgniteSqlToRelConvertor.java | 28 ++
.../sql/engine/prepare/IgniteSqlValidator.java | 131 ++++++++-
.../sql/engine/prepare/IgniteTypeCoercion.java | 8 +
.../internal/sql/engine/util/IgniteMath.java | 306 +++++++++++++++++++++
.../engine/exec/exp/IgniteSqlFunctionsTest.java | 3 +-
.../sql/engine/planner/ImplicitCastsTest.java | 26 +-
18 files changed, 891 insertions(+), 157 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 f7001c4683..ac5cb6601f 100644
--- a/modules/platforms/dotnet/Apache.Ignite.Tests/Linq/LinqTests.Cast.cs
+++ b/modules/platforms/dotnet/Apache.Ignite.Tests/Linq/LinqTests.Cast.cs
@@ -32,7 +32,7 @@ public partial class LinqTests
var query = PocoIntView.AsQueryable()
.Select(x => new
{
- Byte = (sbyte)x.Val,
+ Byte = (sbyte)(x.Val / 10),
Short = (short)x.Val,
Long = (long)x.Val,
Float = (float)x.Val / 1000,
@@ -42,14 +42,14 @@ public partial class LinqTests
var res = query.ToList();
- Assert.AreEqual(-124, res[0].Byte);
+ Assert.AreEqual(90, res[0].Byte);
Assert.AreEqual(900, res[0].Short);
Assert.AreEqual(900, res[0].Long);
Assert.AreEqual(900f / 1000, res[0].Float);
Assert.AreEqual(900d / 2000, res[0].Double);
StringAssert.Contains(
- "select cast(_T0.VAL as tinyint) as BYTE, cast(_T0.VAL as
smallint) as SHORT, cast(_T0.VAL as bigint) as LONG, " +
+ "select cast((_T0.VAL / ?) as tinyint) as BYTE, cast(_T0.VAL as
smallint) as SHORT, cast(_T0.VAL as bigint) as LONG, " +
"(cast(_T0.VAL as real) / ?) as FLOAT, (cast(_T0.VAL as double) /
?) as DOUBLE " +
"from PUBLIC.TBL_INT32 as _T0 " +
"order by cast(_T0.VAL as bigint) desc",
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 c2aef2ac70..85a23500a7 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
@@ -56,6 +56,8 @@ public class ItDataTypesTest extends BaseSqlIntegrationTest {
private static final String NUMERIC_FORMAT_ERROR = "neither a decimal
digit number";
+ private static final Object EMPTY_PARAM = new Object();
+
/**
* Drops all created tables.
*/
@@ -408,6 +410,106 @@ public class ItDataTypesTest extends
BaseSqlIntegrationTest {
expectResult(checker, result);
}
+ @ParameterizedTest(name = "{1}")
+ @MethodSource("decimalOverflows")
+ public void testCalcOpOverflow(SqlTypeName type, String expr, Object
param) {
+ if (param == EMPTY_PARAM) {
+ assertThrowsSqlException(Sql.RUNTIME_ERR, type.getName() + " out
of range", () -> sql(expr));
+ } else {
+ assertThrowsSqlException(Sql.RUNTIME_ERR, type.getName() + " out
of range", () -> sql(expr, param));
+ }
+ }
+
+ private static Stream<Arguments> decimalOverflows() {
+ return Stream.of(
+ //BIGINT
+ arguments(SqlTypeName.BIGINT, "SELECT 9223372036854775807 +
1", EMPTY_PARAM),
+ arguments(SqlTypeName.BIGINT, "SELECT 9223372036854775807 *
2", EMPTY_PARAM),
+ arguments(SqlTypeName.BIGINT, "SELECT -9223372036854775808 -
1", EMPTY_PARAM),
+ arguments(SqlTypeName.BIGINT, "SELECT -(-9223372036854775807 -
1)", EMPTY_PARAM),
+ arguments(SqlTypeName.BIGINT, "SELECT
-CAST(-9223372036854775808 AS BIGINT)", EMPTY_PARAM),
+ arguments(SqlTypeName.BIGINT, "SELECT -(?)",
-9223372036854775808L),
+ arguments(SqlTypeName.BIGINT, "SELECT
-9223372036854775808/-1", EMPTY_PARAM),
+
+ // INTEGER
+ arguments(SqlTypeName.INTEGER, "SELECT 2147483647 + 1",
EMPTY_PARAM),
+ arguments(SqlTypeName.INTEGER, "SELECT 2147483647 * 2",
EMPTY_PARAM),
+ arguments(SqlTypeName.INTEGER, "SELECT -2147483648 - 1",
EMPTY_PARAM),
+ arguments(SqlTypeName.INTEGER, "SELECT -(-2147483647 - 1)",
EMPTY_PARAM),
+ arguments(SqlTypeName.INTEGER, "SELECT -CAST(-2147483648 AS
INTEGER)", EMPTY_PARAM),
+ arguments(SqlTypeName.INTEGER, "SELECT -(?)", -2147483648),
+ arguments(SqlTypeName.INTEGER, "SELECT -2147483648/-1",
EMPTY_PARAM),
+
+ //SMALLINT
+ arguments(SqlTypeName.SMALLINT, "SELECT 32000::SMALLINT +
1000::SMALLINT", EMPTY_PARAM),
+ arguments(SqlTypeName.SMALLINT, "SELECT 17000::SMALLINT *
2::SMALLINT", EMPTY_PARAM),
+ arguments(SqlTypeName.SMALLINT, "SELECT -32000::SMALLINT -
1000::SMALLINT", EMPTY_PARAM),
+ arguments(SqlTypeName.SMALLINT, "SELECT -(-32767::SMALLINT -
1::SMALLINT)", EMPTY_PARAM),
+ arguments(SqlTypeName.SMALLINT, "SELECT -CAST(-32768 AS
SMALLINT)", EMPTY_PARAM),
+ arguments(SqlTypeName.SMALLINT, "SELECT -CAST(? AS SMALLINT)",
-32768),
+ arguments(SqlTypeName.SMALLINT, "SELECT CAST (-32768 AS
SMALLINT)/-1::SMALLINT", EMPTY_PARAM),
+
+ //TINYINT
+ arguments(SqlTypeName.TINYINT, "SELECT 2::TINYINT +
127::TINYINT", EMPTY_PARAM),
+ arguments(SqlTypeName.TINYINT, "SELECT 2::TINYINT *
127::TINYINT", EMPTY_PARAM),
+ arguments(SqlTypeName.TINYINT, "SELECT -2::TINYINT -
127::TINYINT", EMPTY_PARAM),
+ arguments(SqlTypeName.TINYINT, "SELECT -(-127::TINYINT -
1::TINYINT)", EMPTY_PARAM),
+ arguments(SqlTypeName.TINYINT, "SELECT -CAST(-128 AS
TINYINT)", EMPTY_PARAM),
+ arguments(SqlTypeName.TINYINT, "SELECT -CAST(? AS TINYINT)",
-128),
+ arguments(SqlTypeName.TINYINT, "SELECT CAST(-128 AS
TINYINT)/-1::TINYINT", EMPTY_PARAM)
+ );
+ }
+
+ @ParameterizedTest(name = "{1}")
+ @MethodSource("decimalOverflowsValidation")
+ public void testCalcOpOverflowValidationCheck(SqlTypeName type, String
expr, Boolean withException) {
+ if (withException) {
+ assertThrowsSqlException(Sql.STMT_PARSE_ERR, "out of range", () ->
sql(expr));
+ } else {
+ sql(expr);
+ }
+ }
+
+ private static Stream<Arguments> decimalOverflowsValidation() {
+ return Stream.of(
+ //BIGINT
+ arguments(SqlTypeName.BIGINT, "SELECT
CAST(9223372036854775807.1 AS BIGINT)", false),
+ arguments(SqlTypeName.BIGINT, "SELECT
CAST(9223372036854775807.5 AS BIGINT)", true),
+ arguments(SqlTypeName.BIGINT, "SELECT
CAST(9223372036854775807.5 - 1 AS BIGINT)", false),
+ arguments(SqlTypeName.BIGINT, "SELECT
CAST(9223372036854775808.1 AS BIGINT)", true),
+ arguments(SqlTypeName.BIGINT, "SELECT CAST(9223372036854775808
AS BIGINT)", true),
+ arguments(SqlTypeName.BIGINT, "SELECT
CAST(-9223372036854775809 AS BIGINT)", true),
+ arguments(SqlTypeName.BIGINT, "SELECT
CAST(-9223372036854775808.1 AS BIGINT)", false),
+
+ // INTEGER
+ arguments(SqlTypeName.INTEGER, "SELECT CAST(2147483647.1 AS
INTEGER)", false),
+ arguments(SqlTypeName.INTEGER, "SELECT CAST(2147483647.5 AS
INTEGER)", true),
+ arguments(SqlTypeName.INTEGER, "SELECT CAST(2147483647.5 - 1
AS INTEGER)", false),
+ arguments(SqlTypeName.INTEGER, "SELECT CAST(2147483648.1 AS
INTEGER)", true),
+ arguments(SqlTypeName.INTEGER, "SELECT CAST(2147483648 AS
INTEGER)", true),
+ arguments(SqlTypeName.INTEGER, "SELECT CAST(-2147483649 AS
INTEGER)", true),
+ arguments(SqlTypeName.INTEGER, "SELECT CAST(-2147483648.1 AS
INTEGER)", false),
+
+ //SMALLINT
+ arguments(SqlTypeName.SMALLINT, "SELECT CAST(32767.1 AS
SMALLINT)", false),
+ arguments(SqlTypeName.SMALLINT, "SELECT CAST(32767.5 AS
SMALLINT)", true),
+ arguments(SqlTypeName.SMALLINT, "SELECT CAST(32767.5 - 1 AS
SMALLINT)", false),
+ arguments(SqlTypeName.SMALLINT, "SELECT CAST(32768.1 AS
SMALLINT)", true),
+ arguments(SqlTypeName.SMALLINT, "SELECT CAST(32768 AS
SMALLINT)", true),
+ arguments(SqlTypeName.SMALLINT, "SELECT CAST(-32769 AS
SMALLINT)", true),
+ arguments(SqlTypeName.SMALLINT, "SELECT CAST(-32768.1 AS
SMALLINT)", false),
+
+ //TINYINT
+ arguments(SqlTypeName.TINYINT, "SELECT CAST(127.1 AS
TINYINT)", false),
+ arguments(SqlTypeName.TINYINT, "SELECT CAST(127.5 AS
TINYINT)", true),
+ arguments(SqlTypeName.TINYINT, "SELECT CAST(127.5 - 1 AS
TINYINT)", false),
+ arguments(SqlTypeName.TINYINT, "SELECT CAST(128.1 AS
TINYINT)", true),
+ arguments(SqlTypeName.TINYINT, "SELECT CAST(128 AS TINYINT)",
true),
+ arguments(SqlTypeName.TINYINT, "SELECT CAST(-129 AS TINYINT)",
true),
+ arguments(SqlTypeName.TINYINT, "SELECT CAST(-128.1 AS
TINYINT)", false)
+ );
+ }
+
static String asLiteral(Object value, RelDataType type) {
if (SqlTypeUtil.isCharacter(type)) {
String str = (String) value;
diff --git
a/modules/runner/src/integrationTest/java/org/apache/ignite/internal/sql/engine/ItDmlTest.java
b/modules/runner/src/integrationTest/java/org/apache/ignite/internal/sql/engine/ItDmlTest.java
index 67cc93dbb3..4ab9b5a5db 100644
---
a/modules/runner/src/integrationTest/java/org/apache/ignite/internal/sql/engine/ItDmlTest.java
+++
b/modules/runner/src/integrationTest/java/org/apache/ignite/internal/sql/engine/ItDmlTest.java
@@ -20,6 +20,7 @@ package org.apache.ignite.internal.sql.engine;
import static
org.apache.ignite.internal.sql.engine.util.SqlTestUtils.assertThrowsSqlException;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.params.provider.Arguments.arguments;
import java.math.BigDecimal;
import java.time.LocalDate;
@@ -29,6 +30,7 @@ import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;
+import org.apache.calcite.sql.type.SqlTypeName;
import org.apache.ignite.internal.sql.BaseSqlIntegrationTest;
import org.apache.ignite.internal.sql.engine.exec.rel.AbstractNode;
import org.apache.ignite.internal.testframework.WithSystemProperty;
@@ -39,6 +41,9 @@ import org.jetbrains.annotations.Nullable;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
/**
* Various DML tests.
@@ -666,4 +671,47 @@ public class ItDmlTest extends BaseSqlIntegrationTest {
sql("DELETE FROM test WHERE a = 0");
assertQuery("SELECT d FROM test").returnNothing().check();
}
+
+ @ParameterizedTest(name = "{0}")
+ @MethodSource("decimalLimits")
+ public void testInsertValueOverflow(String type, long max, long min) {
+ try {
+ sql(String.format("CREATE TABLE %s (ID INT PRIMARY KEY, VAL %s);",
type, type));
+
+ sql(String.format("CREATE TABLE T_HELPER (ID INT PRIMARY KEY, VAL
%s);", type));
+ sql("INSERT INTO T_HELPER VALUES (1, 1);");
+ sql(String.format("INSERT INTO T_HELPER VALUES (2, %d);", max));
+ sql("INSERT INTO T_HELPER VALUES (3, -1);");
+ sql(String.format("INSERT INTO T_HELPER VALUES (4, %d);", min));
+
+ BigDecimal moreThanMax = new BigDecimal(max).add(BigDecimal.ONE);
+
+ assertThrowsSqlException(Sql.RUNTIME_ERR, String.format("%s out of
range", type),
+ () -> sql(String.format("INSERT INTO %s (ID, VAL) VALUES
(1, %s);", type, moreThanMax.toString())));
+ assertThrowsSqlException(Sql.RUNTIME_ERR, String.format("%s out of
range", type),
+ () -> sql(String.format("INSERT INTO %s (ID, VAL) VALUES
(1, %d + 1);", type, max)));
+ assertThrowsSqlException(Sql.RUNTIME_ERR, String.format("%s out of
range", type),
+ () -> sql(String.format("INSERT INTO %s (ID, VAL) VALUES
(1, %d - 1);", type, min)));
+ assertThrowsSqlException(Sql.RUNTIME_ERR, String.format("%s out of
range", type),
+ () -> sql(String.format("INSERT INTO %s (ID, VAL) VALUES
(1, %d + (SELECT 1));", type, max)));
+ assertThrowsSqlException(Sql.RUNTIME_ERR, String.format("%s out of
range", type),
+ () -> sql(String.format("INSERT INTO %s (ID, VAL) VALUES
(1, %d + (SELECT -1));", type, min)));
+ assertThrowsSqlException(Sql.RUNTIME_ERR, String.format("%s out of
range", type),
+ () -> sql(String.format("INSERT INTO %s (ID, VAL) VALUES
(1, (SELECT SUM(VAL) FROM T_HELPER WHERE VAL > 0));", type)));
+ assertThrowsSqlException(Sql.RUNTIME_ERR, String.format("%s out of
range", type),
+ () -> sql(String.format("INSERT INTO %s (ID, VAL) VALUES
(1, (SELECT SUM(VAL) FROM T_HELPER WHERE VAL < 0));", type)));
+ } finally {
+ sql("DROP TABLE " + type);
+ sql("DROP TABLE T_HELPER");
+ }
+ }
+
+ private static Stream<Arguments> decimalLimits() {
+ return Stream.of(
+ arguments(SqlTypeName.BIGINT.getName(), Long.MAX_VALUE,
Long.MIN_VALUE),
+ arguments(SqlTypeName.INTEGER.getName(), Integer.MAX_VALUE,
Integer.MIN_VALUE),
+ arguments(SqlTypeName.SMALLINT.getName(), Short.MAX_VALUE,
Short.MIN_VALUE),
+ arguments(SqlTypeName.TINYINT.getName(), Byte.MAX_VALUE,
Byte.MIN_VALUE)
+ );
+ }
}
diff --git
a/modules/runner/src/integrationTest/java/org/apache/ignite/internal/sql/engine/ItDynamicParameterTest.java
b/modules/runner/src/integrationTest/java/org/apache/ignite/internal/sql/engine/ItDynamicParameterTest.java
index 00d301b6e9..517b100b52 100644
---
a/modules/runner/src/integrationTest/java/org/apache/ignite/internal/sql/engine/ItDynamicParameterTest.java
+++
b/modules/runner/src/integrationTest/java/org/apache/ignite/internal/sql/engine/ItDynamicParameterTest.java
@@ -82,6 +82,7 @@ public class ItDynamicParameterTest extends
BaseSqlIntegrationTest {
assertQuery("SELECT ? % ?").withParams(11, 10).returns(1).check();
assertQuery("SELECT ? + ?, LOWER(?) ").withParams(2, 2,
"TeSt").returns(4, "test").check();
assertQuery("SELECT LOWER(?), ? + ? ").withParams("TeSt", 2,
2).returns("test", 4).check();
+ assertQuery("SELECT (? +
1)::INTEGER").withParams(1).returns(2).check();
createAndPopulateTable();
assertQuery("SELECT name LIKE '%' || ? || '%' FROM person where name
is not null").withParams("go").returns(true).returns(false)
diff --git
a/modules/runner/src/integrationTest/java/org/apache/ignite/internal/sql/engine/ItFunctionsTest.java
b/modules/runner/src/integrationTest/java/org/apache/ignite/internal/sql/engine/ItFunctionsTest.java
index d60805517b..ee103610b8 100644
---
a/modules/runner/src/integrationTest/java/org/apache/ignite/internal/sql/engine/ItFunctionsTest.java
+++
b/modules/runner/src/integrationTest/java/org/apache/ignite/internal/sql/engine/ItFunctionsTest.java
@@ -22,10 +22,8 @@ import static
org.apache.ignite.internal.lang.IgniteStringFormatter.format;
import static
org.apache.ignite.internal.sql.engine.util.SqlTestUtils.assertThrowsSqlException;
import static
org.apache.ignite.internal.testframework.IgniteTestUtils.assertThrowsWithCause;
import static org.hamcrest.MatcherAssert.assertThat;
-import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.instanceOf;
import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertSame;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
@@ -314,9 +312,7 @@ public class ItFunctionsTest extends BaseSqlIntegrationTest
{
assertQuery("SELECT TYPEOF('abcd' || COALESCE('efg',
?))").withParams("2").returns("VARCHAR").check();
// An expression that produces an error
- IgniteException failed = assertThrows(IgniteException.class, () ->
assertQuery("SELECT typeof(CAST('NONE' as INTEGER))").check());
- assertSame(NumberFormatException.class, failed.getCause().getClass(),
"cause");
- assertThat(failed.getCause().getMessage(), containsString("For input
string: \"NONE\""));
+ assertThrowsSqlException(Sql.STMT_PARSE_ERR, "", () -> sql("SELECT
typeof(CAST('NONE' as INTEGER))"));
assertThrowsWithCause(() -> sql("SELECT TYPEOF()"),
SqlValidatorException.class, "Invalid number of arguments");
diff --git
a/modules/runner/src/integrationTest/java/org/apache/ignite/internal/sql/engine/ItIntervalTest.java
b/modules/runner/src/integrationTest/java/org/apache/ignite/internal/sql/engine/ItIntervalTest.java
index 02b35a83ae..36706a6552 100644
---
a/modules/runner/src/integrationTest/java/org/apache/ignite/internal/sql/engine/ItIntervalTest.java
+++
b/modules/runner/src/integrationTest/java/org/apache/ignite/internal/sql/engine/ItIntervalTest.java
@@ -17,6 +17,7 @@
package org.apache.ignite.internal.sql.engine;
+import static
org.apache.ignite.internal.sql.engine.util.SqlTestUtils.assertThrowsSqlException;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
@@ -29,6 +30,7 @@ import java.time.LocalTime;
import java.time.Period;
import org.apache.ignite.internal.lang.IgniteInternalException;
import org.apache.ignite.internal.sql.BaseSqlIntegrationTest;
+import org.apache.ignite.lang.ErrorGroups.Sql;
import org.apache.ignite.lang.IgniteException;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
@@ -64,6 +66,11 @@ public class ItIntervalTest extends BaseSqlIntegrationTest {
assertEquals(Duration.ofMillis(3723456), eval("INTERVAL '0 1:2:3.456'
DAY TO SECOND"));
assertThrowsEx("SELECT INTERVAL '123' SECONDS", IgniteException.class,
"exceeds precision");
+
+ // Interval range overflow
+ assertThrowsSqlException(Sql.RUNTIME_ERR, "INTEGER out of range", ()
-> sql("SELECT INTERVAL 5000000 MONTHS * 1000"));
+ assertThrowsSqlException(Sql.RUNTIME_ERR, "BIGINT out of range", () ->
sql("SELECT DATE '2021-01-01' + INTERVAL 999999999999 DAY"));
+ assertThrowsSqlException(Sql.RUNTIME_ERR, "INTEGER out of range", ()
-> sql("SELECT DATE '2021-01-01' + INTERVAL -999999999 YEAR"));
}
/**
diff --git
a/modules/runner/src/integrationTest/sql/types/decimal/cast_from_decimal.test
b/modules/runner/src/integrationTest/sql/types/decimal/cast_from_decimal.test
index f522e245ed..766520cb94 100644
---
a/modules/runner/src/integrationTest/sql/types/decimal/cast_from_decimal.test
+++
b/modules/runner/src/integrationTest/sql/types/decimal/cast_from_decimal.test
@@ -11,23 +11,47 @@ SELECT 127::DECIMAL(3,0)::TINYINT,
-127::DECIMAL(3,0)::TINYINT, -7::DECIMAL(9,1)
----
127 -127 -7 27 33
-skipif ignite3
-# https://issues.apache.org/jira/browse/IGNITE-18662
statement error
SELECT 128::DECIMAL(3,0)::TINYINT
-skipif ignite3
-# https://issues.apache.org/jira/browse/IGNITE-18662
statement error
SELECT -128::DECIMAL(9,0)::TINYINT
-skipif ignite3
-# https://issues.apache.org/jira/browse/IGNITE-18662
statement error
SELECT 128::DECIMAL(18,0)::TINYINT
-skipif ignite3
-# https://issues.apache.org/jira/browse/IGNITE-18662
+statement error
+SELECT (SELECT 128::DECIMAL(18,0)::TINYINT)
+
+statement error
+SELECT (SELECT (127 + 1)::DECIMAL(18,0)::TINYINT)
+
+statement error
+SELECT (SELECT (32768 + 1)::DECIMAL(18,0)::SMALLINT)
+
+statement error
+SELECT 2147483647 + 1
+
+query I
+SELECT 2147483648
+----
+2147483648
+
+query I
+SELECT (null)::INTEGER
+----
+null
+
+query I
+SELECT (1 + null)::INTEGER
+----
+null
+
+query I
+SELECT (SELECT 127::DECIMAL(18,0)::TINYINT)
+----
+127
+
statement error
SELECT 14751947891758972421513::DECIMAL(38,0)::TINYINT
@@ -37,18 +61,12 @@ SELECT 127::DECIMAL(3,0)::SMALLINT,
-32767::DECIMAL(5,0)::SMALLINT, -7::DECIMAL(
----
127 -32767 -7 27 33
-skipif ignite3
-# https://issues.apache.org/jira/browse/IGNITE-18662
statement error
SELECT -32768::DECIMAL(9,0)::SMALLINT
-skipif ignite3
-# https://issues.apache.org/jira/browse/IGNITE-18662
statement error
SELECT 32768::DECIMAL(18,0)::SMALLINT
-skipif ignite3
-# https://issues.apache.org/jira/browse/IGNITE-18662
statement error
SELECT 14751947891758972421513::DECIMAL(38,0)::SMALLINT
@@ -58,13 +76,9 @@ SELECT 127::DECIMAL(3,0)::INTEGER,
-2147483647::DECIMAL(10,0)::INTEGER, -7::DECI
----
127 -2147483647 -7 27 33
-skipif ignite3
-# https://issues.apache.org/jira/browse/IGNITE-18662
statement error
SELECT 2147483648::DECIMAL(18,0)::INTEGER
-skipif ignite3
-# https://issues.apache.org/jira/browse/IGNITE-18662
statement error
SELECT 14751947891758972421513::DECIMAL(38,0)::INTEGER
@@ -74,8 +88,6 @@ SELECT 127::DECIMAL(3,0)::BIGINT,
-9223372036854775807::DECIMAL(19,0)::BIGINT, -
----
127 -9223372036854775807 -7 27 33
-skipif ignite3
-# https://issues.apache.org/jira/browse/IGNITE-18662
statement error
SELECT 14751947891758972421513::DECIMAL(38,0)::BIGINT
diff --git
a/modules/runner/src/integrationTest/sql/types/decimal/cast_to_decimal.test
b/modules/runner/src/integrationTest/sql/types/decimal/cast_to_decimal.test
index 5be5e76eb4..4758fb3353 100644
--- a/modules/runner/src/integrationTest/sql/types/decimal/cast_to_decimal.test
+++ b/modules/runner/src/integrationTest/sql/types/decimal/cast_to_decimal.test
@@ -17,28 +17,18 @@ SELECT 100::TINYINT::DECIMAL(38,35),
100::TINYINT::DECIMAL(9,6)
100 100
# overflow
-skipif ignite3
-# https://issues.apache.org/jira/browse/IGNITE-18662
statement error
SELECT 100::TINYINT::DECIMAL(3,1)
-skipif ignite3
-# https://issues.apache.org/jira/browse/IGNITE-18662
statement error
SELECT 1::TINYINT::DECIMAL(3,3)
-skipif ignite3
-# https://issues.apache.org/jira/browse/IGNITE-18662
statement error
SELECT 100::TINYINT::DECIMAL(18,17)
-skipif ignite3
-# https://issues.apache.org/jira/browse/IGNITE-18662
statement error
SELECT 100::TINYINT::DECIMAL(9,7)
-skipif ignite3
-# https://issues.apache.org/jira/browse/IGNITE-18662
statement error
SELECT 100::TINYINT::DECIMAL(38,37)
@@ -54,28 +44,18 @@ SELECT 100::SMALLINT::DECIMAL(38,35),
100::SMALLINT::DECIMAL(9,6)
100 100
# overflow
-skipif ignite3
-# https://issues.apache.org/jira/browse/IGNITE-18662
statement error
SELECT 100::SMALLINT::DECIMAL(3,1)
-skipif ignite3
-# https://issues.apache.org/jira/browse/IGNITE-18662
statement error
SELECT 1::SMALLINT::DECIMAL(3,3)
-skipif ignite3
-# https://issues.apache.org/jira/browse/IGNITE-18662
statement error
SELECT 100::SMALLINT::DECIMAL(18,17)
-skipif ignite3
-# https://issues.apache.org/jira/browse/IGNITE-18662
statement error
SELECT 100::SMALLINT::DECIMAL(9,7)
-skipif ignite3
-# https://issues.apache.org/jira/browse/IGNITE-18662
statement error
SELECT 100::SMALLINT::DECIMAL(38,37)
@@ -91,38 +71,24 @@ SELECT 100::INTEGER::DECIMAL(38,35),
100::INTEGER::DECIMAL(9,6), 2147483647::INT
100 100 2147483647 -2147483647
# overflow
-skipif ignite3
-# https://issues.apache.org/jira/browse/IGNITE-18662
statement error
SELECT 100::INTEGER::DECIMAL(3,1)
-skipif ignite3
-# https://issues.apache.org/jira/browse/IGNITE-18662
statement error
SELECT 10000000::INTEGER::DECIMAL(3,1)
-skipif ignite3
-# https://issues.apache.org/jira/browse/IGNITE-18662
statement error
SELECT -10000000::INTEGER::DECIMAL(3,1)
-skipif ignite3
-# https://issues.apache.org/jira/browse/IGNITE-18662
statement error
SELECT 1::INTEGER::DECIMAL(3,3)
-skipif ignite3
-# https://issues.apache.org/jira/browse/IGNITE-18662
statement error
SELECT 100::INTEGER::DECIMAL(18,17)
-skipif ignite3
-# https://issues.apache.org/jira/browse/IGNITE-18662
statement error
SELECT 100::INTEGER::DECIMAL(9,7)
-skipif ignite3
-# https://issues.apache.org/jira/browse/IGNITE-18662
statement error
SELECT 100::INTEGER::DECIMAL(38,37)
@@ -143,38 +109,24 @@ SELECT 922337203685477580::BIGINT::DECIMAL(18,0),
(-922337203685477580)::BIGINT:
922337203685477580 -922337203685477580
# overflow
-skipif ignite3
-# https://issues.apache.org/jira/browse/IGNITE-18662
statement error
SELECT 100::BIGINT::DECIMAL(3,1)
-skipif ignite3
-# https://issues.apache.org/jira/browse/IGNITE-18662
statement error
SELECT 10000000::BIGINT::DECIMAL(3,1)
-skipif ignite3
-# https://issues.apache.org/jira/browse/IGNITE-18662
statement error
SELECT -10000000::BIGINT::DECIMAL(3,1)
-skipif ignite3
-# https://issues.apache.org/jira/browse/IGNITE-18662
statement error
SELECT 1::BIGINT::DECIMAL(3,3)
-skipif ignite3
-# https://issues.apache.org/jira/browse/IGNITE-18662
statement error
SELECT 100::BIGINT::DECIMAL(18,17)
-skipif ignite3
-# https://issues.apache.org/jira/browse/IGNITE-18662
statement error
SELECT 100::BIGINT::DECIMAL(9,7)
-skipif ignite3
-# https://issues.apache.org/jira/browse/IGNITE-18662
statement error
SELECT 100::BIGINT::DECIMAL(38,37)
@@ -195,63 +147,39 @@ SELECT 1.25::FLOAT::DECIMAL(3,2)
1.25
# overflow
-skipif ignite3
-# https://issues.apache.org/jira/browse/IGNITE-18662
statement error
SELECT 100::FLOAT::DECIMAL(3,1)
-skipif ignite3
-# https://issues.apache.org/jira/browse/IGNITE-18662
statement error
SELECT 10000000::FLOAT::DECIMAL(3,1)
-skipif ignite3
-# https://issues.apache.org/jira/browse/IGNITE-18662
statement error
SELECT -10000000::FLOAT::DECIMAL(3,1)
-skipif ignite3
-# https://issues.apache.org/jira/browse/IGNITE-18662
statement error
SELECT 1::FLOAT::DECIMAL(3,3)
-skipif ignite3
-# https://issues.apache.org/jira/browse/IGNITE-18662
statement error
SELECT 100::FLOAT::DECIMAL(18,17)
-skipif ignite3
-# https://issues.apache.org/jira/browse/IGNITE-18662
statement error
SELECT 100::FLOAT::DECIMAL(9,7)
-skipif ignite3
-# https://issues.apache.org/jira/browse/IGNITE-18662
statement error
SELECT 100::FLOAT::DECIMAL(38,37)
-skipif ignite3
-# https://issues.apache.org/jira/browse/IGNITE-18662
statement error
SELECT 17014118346046923173168730371588410572::FLOAT::DECIMAL(38,1)
-skipif ignite3
-# https://issues.apache.org/jira/browse/IGNITE-18662
statement error
SELECT 17014118346046923173168730371588410572::FLOAT::DECIMAL(37,0)
-skipif ignite3
-# https://issues.apache.org/jira/browse/IGNITE-18662
statement error
SELECT 17014118346046923173168730371588410572::FLOAT::DECIMAL(18,0)
-skipif ignite3
-# https://issues.apache.org/jira/browse/IGNITE-18662
statement error
SELECT 17014118346046923173168730371588410572::FLOAT::DECIMAL(9,0)
-skipif ignite3
-# https://issues.apache.org/jira/browse/IGNITE-18662
statement error
SELECT 17014118346046923173168730371588410572::FLOAT::DECIMAL(4,0)
@@ -272,63 +200,39 @@ SELECT 1.25::DOUBLE::DECIMAL(3,2)
1.25
# overflow
-skipif ignite3
-# https://issues.apache.org/jira/browse/IGNITE-18662
statement error
SELECT 100::DOUBLE::DECIMAL(3,1)
-skipif ignite3
-# https://issues.apache.org/jira/browse/IGNITE-18662
statement error
SELECT 10000000::DOUBLE::DECIMAL(3,1)
-skipif ignite3
-# https://issues.apache.org/jira/browse/IGNITE-18662
statement error
SELECT -10000000::DOUBLE::DECIMAL(3,1)
-skipif ignite3
-# https://issues.apache.org/jira/browse/IGNITE-18662
statement error
SELECT 1::DOUBLE::DECIMAL(3,3)
-skipif ignite3
-# https://issues.apache.org/jira/browse/IGNITE-18662
statement error
SELECT 100::DOUBLE::DECIMAL(18,17)
-skipif ignite3
-# https://issues.apache.org/jira/browse/IGNITE-18662
statement error
SELECT 100::DOUBLE::DECIMAL(9,7)
-skipif ignite3
-# https://issues.apache.org/jira/browse/IGNITE-18662
statement error
SELECT 100::DOUBLE::DECIMAL(38,37)
-skipif ignite3
-# https://issues.apache.org/jira/browse/IGNITE-18662
statement error
SELECT 17014118346046923173168730371588410572::DOUBLE::DECIMAL(38,1)
-skipif ignite3
-# https://issues.apache.org/jira/browse/IGNITE-18662
statement error
SELECT 17014118346046923173168730371588410572::DOUBLE::DECIMAL(37,0)
-skipif ignite3
-# https://issues.apache.org/jira/browse/IGNITE-18662
statement error
SELECT 17014118346046923173168730371588410572::DOUBLE::DECIMAL(18,0)
-skipif ignite3
-# https://issues.apache.org/jira/browse/IGNITE-18662
statement error
SELECT 17014118346046923173168730371588410572::DOUBLE::DECIMAL(9,0)
-skipif ignite3
-# https://issues.apache.org/jira/browse/IGNITE-18662
statement error
SELECT 17014118346046923173168730371588410572::DOUBLE::DECIMAL(4,0)
diff --git
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/exec/exp/ConverterUtils.java
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/exec/exp/ConverterUtils.java
index 6dba51075a..601176a96e 100644
---
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/exec/exp/ConverterUtils.java
+++
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/exec/exp/ConverterUtils.java
@@ -219,6 +219,37 @@ public class ConverterUtils {
throw new AssertionError("For conversion to decimal,
ConverterUtils#convertToDecimal method should be used instead.");
}
+ Primitive toPrimitive = Primitive.of(toType);
+ Primitive fromPrimitive = Primitive.of(fromType);
+
+ // check overflow for 'integer' subtypes
+ if (fromPrimitive == Primitive.LONG && toPrimitive == Primitive.INT) {
+ return IgniteExpressions.convertToIntExact(operand);
+ }
+
+ if ((fromPrimitive == Primitive.LONG || fromPrimitive ==
Primitive.INT) && toPrimitive == Primitive.SHORT) {
+ return IgniteExpressions.convertToShortExact(operand);
+ }
+
+ if ((fromPrimitive == Primitive.LONG || fromPrimitive == Primitive.INT
|| fromPrimitive == Primitive.SHORT)
+ && toPrimitive == Primitive.BYTE) {
+ return IgniteExpressions.convertToByteExact(operand);
+ }
+
+ if (!Primitive.isBox(fromType)) {
+ if ((fromType == BigDecimal.class || fromType == String.class) &&
toPrimitive == Primitive.LONG) {
+ return IgniteExpressions.convertToLongExact(operand);
+ }
+
+ if (fromType == BigDecimal.class && toPrimitive == Primitive.BYTE)
{
+ return IgniteExpressions.convertToByteExact(operand);
+ }
+
+ if (fromType == BigDecimal.class && toPrimitive ==
Primitive.SHORT) {
+ return IgniteExpressions.convertToShortExact(operand);
+ }
+ }
+
// SELECT '0.1'::DECIMAL::VARCHAR case, looks like a stub
if (toType == String.class) {
if (fromType == BigDecimal.class) {
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
new file mode 100644
index 0000000000..09081d6da0
--- /dev/null
+++
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/exec/exp/IgniteExpressions.java
@@ -0,0 +1,179 @@
+/*
+ * 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.exec.exp;
+
+import java.lang.reflect.Type;
+import java.math.BigDecimal;
+import org.apache.calcite.linq4j.tree.Expression;
+import org.apache.calcite.linq4j.tree.ExpressionType;
+import org.apache.calcite.linq4j.tree.Expressions;
+import org.apache.ignite.internal.sql.engine.util.IgniteMath;
+
+/** Calcite liq4j expressions customized for Ignite. */
+public class IgniteExpressions {
+ /** Make binary expression with arithmetic operations override. */
+ public static Expression makeBinary(ExpressionType binaryType, Expression
left, Expression right) {
+ switch (binaryType) {
+ case Add:
+ return addExact(left, right);
+ case Subtract:
+ return subtractExact(left, right);
+ case Multiply:
+ return multiplyExact(left, right);
+ case Divide:
+ return divideExact(left, right);
+ default:
+ return Expressions.makeBinary(binaryType, left, right);
+ }
+ }
+
+ /** Make unary expression with arithmetic operations override. */
+ public static Expression makeUnary(ExpressionType unaryType, Expression
operand) {
+ switch (unaryType) {
+ case Negate:
+ case NegateChecked:
+ return negateExact(unaryType, operand);
+ default:
+ return Expressions.makeUnary(unaryType, operand);
+ }
+ }
+
+ /** Generate expression for method IgniteMath.addExact() for integer
subtypes. */
+ public static Expression addExact(Expression left, Expression right) {
+ Type largerType = larger(left.getType(), right.getType());
+
+ if (largerType == Integer.TYPE || largerType == Long.TYPE ||
largerType == Short.TYPE || largerType == Byte.TYPE) {
+ return Expressions.call(IgniteMath.class, "addExact", left, right);
+ }
+
+ return Expressions.makeBinary(ExpressionType.Add, left, right);
+ }
+
+ /** Generate expression for method IgniteMath.subtractExact() for integer
subtypes. */
+ public static Expression subtractExact(Expression left, Expression right) {
+ Type largerType = larger(left.getType(), right.getType());
+
+ if (largerType == Integer.TYPE || largerType == Long.TYPE ||
largerType == Short.TYPE || largerType == Byte.TYPE) {
+ return Expressions.call(IgniteMath.class, "subtractExact", left,
right);
+ }
+
+ return Expressions.makeBinary(ExpressionType.Subtract, left, right);
+ }
+
+ /** Generate expression for method IgniteMath.multiplyExact() for integer
subtypes. */
+ public static Expression multiplyExact(Expression left, Expression right) {
+ Type largerType = larger(left.getType(), right.getType());
+
+ if (largerType == Integer.TYPE || largerType == Long.TYPE ||
largerType == Short.TYPE || largerType == Byte.TYPE) {
+ return Expressions.call(IgniteMath.class, "multiplyExact", left,
right);
+ }
+
+ return Expressions.makeBinary(ExpressionType.Multiply, left, right);
+ }
+
+ /** Generate expression for method IgniteMath.divideExact() for integer
subtypes. */
+ public static Expression divideExact(Expression left, Expression right) {
+ Type largerType = larger(left.getType(), right.getType());
+
+ if (largerType == Integer.TYPE || largerType == Long.TYPE ||
largerType == Short.TYPE || largerType == Byte.TYPE) {
+ return Expressions.call(IgniteMath.class, "divideExact", left,
right);
+ }
+
+ return Expressions.makeBinary(ExpressionType.Divide, left, right);
+ }
+
+ /** Generate expression for method IgniteMath.convertToIntExact(). */
+ public static Expression convertToIntExact(Expression exp) {
+ Type type = exp.getType();
+
+ if (type == Long.TYPE || type == Long.class) {
+ return Expressions.call(IgniteMath.class, "convertToIntExact",
exp);
+ }
+
+ return exp;
+ }
+
+ /** Generate expression for method IgniteMath.convertToIntExact(). */
+ public static Expression convertToLongExact(Expression exp) {
+ Type type = exp.getType();
+
+ if (type == BigDecimal.class || type == String.class) {
+ return Expressions.call(IgniteMath.class, "convertToLongExact",
exp);
+ }
+
+ return exp;
+ }
+
+ /** Generate expression for method IgniteMath.convertToShortExact(). */
+ public static Expression convertToShortExact(Expression exp) {
+ Type type = exp.getType();
+
+ if (type == Long.TYPE || type == Long.class || type == Integer.TYPE ||
type == Integer.class || type == BigDecimal.class) {
+ return Expressions.call(IgniteMath.class, "convertToShortExact",
exp);
+ }
+
+ return exp;
+ }
+
+ /** Generate expression for method IgniteMath.convertToByteExact(). */
+ public static Expression convertToByteExact(Expression exp) {
+ Type type = exp.getType();
+
+ if (type == Long.TYPE || type == Long.class || type == Integer.TYPE ||
type == Integer.class
+ || type == Short.TYPE || type == Short.class || type ==
BigDecimal.class) {
+ return Expressions.call(IgniteMath.class, "convertToByteExact",
exp);
+ }
+
+ return exp;
+ }
+
+ /** Generate expression for method IgniteMath.negateExact() for integer
subtypes. */
+ private static Expression negateExact(ExpressionType unaryType, Expression
operand) {
+ assert unaryType == ExpressionType.Negate || unaryType ==
ExpressionType.NegateChecked;
+
+ Type opType = operand.getType();
+
+ if (opType == Integer.TYPE || opType == Long.TYPE || opType ==
Short.TYPE || opType == Byte.TYPE) {
+ return Expressions.call(IgniteMath.class, "negateExact", operand);
+ }
+
+ return Expressions.makeUnary(unaryType, operand);
+ }
+
+ /** Find larger in type hierarchy. */
+ private static Type larger(Type type0, Type type1) {
+ if (type0 != Double.TYPE && type0 != Double.class && type1 !=
Double.TYPE && type1 != Double.class) {
+ if (type0 != Float.TYPE && type0 != Float.class && type1 !=
Float.TYPE && type1 != Float.class) {
+ if (type0 != Long.TYPE && type0 != Long.class && type1 !=
Long.TYPE && type1 != Long.class) {
+ if (type0 != Integer.TYPE && type0 != Integer.class &&
type1 != Integer.TYPE && type1 != Integer.class) {
+ return type0 != Short.TYPE && type0 != Short.class &&
type1 != Short.TYPE && type1 != Short.class
+ ? Byte.TYPE : Short.TYPE;
+ } else {
+ return Integer.TYPE;
+ }
+ } else {
+ return Long.TYPE;
+ }
+ } else {
+ return Float.TYPE;
+ }
+ } else {
+ return Double.TYPE;
+ }
+ }
+}
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 e89fb0af20..da90432e0f 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
@@ -20,6 +20,7 @@ package org.apache.ignite.internal.sql.engine.exec.exp;
import static java.time.format.DateTimeFormatter.ISO_LOCAL_DATE;
import static java.time.format.DateTimeFormatter.ISO_LOCAL_TIME;
import static org.apache.calcite.runtime.SqlFunctions.charLength;
+import static
org.apache.ignite.internal.sql.engine.prepare.IgniteSqlValidator.NUMERIC_FIELD_OVERFLOW_ERROR;
import static org.apache.ignite.lang.ErrorGroups.Sql.RUNTIME_ERR;
import java.math.BigDecimal;
@@ -61,7 +62,6 @@ 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 {
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 4bb6dc7943..4a6e3011c3 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
@@ -2911,7 +2911,7 @@ public class RexImpTable {
argValueList);
}
- return Expressions.makeBinary(expressionType,
+ return IgniteExpressions.makeBinary(expressionType,
argValueList.get(0), argValueList.get(1));
}
@@ -2969,7 +2969,7 @@ public class RexImpTable {
&& null != backupMethodName) {
e = Expressions.call(argValue, backupMethodName);
} else {
- e = Expressions.makeUnary(expressionType, argValue);
+ e = IgniteExpressions.makeUnary(expressionType, argValue);
}
if (e.type.equals(argValue.type)) {
@@ -3517,7 +3517,7 @@ public class RexImpTable {
case TIMESTAMP:
trop0 =
Expressions.convert_(
- Expressions.multiply(trop0,
+ IgniteExpressions.multiplyExact(trop0,
Expressions.constant(DateTimeUtils.MILLIS_PER_DAY)),
long.class);
break;
@@ -3535,7 +3535,7 @@ public class RexImpTable {
case INTERVAL_SECOND:
trop1 =
Expressions.convert_(
- Expressions.divide(trop1,
+ IgniteExpressions.divideExact(trop1,
Expressions.constant(DateTimeUtils.MILLIS_PER_DAY)),
int.class);
break;
@@ -3556,7 +3556,7 @@ public class RexImpTable {
case INTERVAL_MONTH:
switch (call.getKind()) {
case MINUS:
- trop1 = Expressions.negate(trop1);
+ trop1 = IgniteExpressions.makeUnary(Negate, trop1);
break;
default:
break;
@@ -3584,9 +3584,9 @@ public class RexImpTable {
case INTERVAL_SECOND:
switch (call.getKind()) {
case MINUS:
- return normalize(typeName, Expressions.subtract(trop0, trop1));
+ return normalize(typeName, IgniteExpressions.subtractExact(trop0,
trop1));
default:
- return normalize(typeName, Expressions.add(trop0, trop1));
+ return normalize(typeName, IgniteExpressions.addExact(trop0, trop1));
}
default:
@@ -3605,7 +3605,7 @@ public class RexImpTable {
typeName1 == SqlTypeName.DATE ? TimeUnit.DAY :
TimeUnit.MILLISECOND;
TimeUnit toUnit = TimeUnit.MILLISECOND;
return multiplyDivide(
- Expressions.convert_(Expressions.subtract(trop0, trop1),
+ Expressions.convert_(IgniteExpressions.subtractExact(trop0,
trop1),
long.class),
fromUnit.multiplier, toUnit.multiplier);
default:
diff --git
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/prepare/IgniteSqlToRelConvertor.java
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/prepare/IgniteSqlToRelConvertor.java
index c7649bd149..74c6f49bc1 100644
---
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/prepare/IgniteSqlToRelConvertor.java
+++
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/prepare/IgniteSqlToRelConvertor.java
@@ -36,14 +36,18 @@ import org.apache.calcite.rel.logical.LogicalValues;
import org.apache.calcite.rel.type.RelDataType;
import org.apache.calcite.rel.type.RelDataTypeField;
import org.apache.calcite.rex.RexInputRef;
+import org.apache.calcite.rex.RexLiteral;
import org.apache.calcite.rex.RexNode;
import org.apache.calcite.sql.SqlCall;
+import org.apache.calcite.sql.SqlDataTypeSpec;
import org.apache.calcite.sql.SqlIdentifier;
import org.apache.calcite.sql.SqlInsert;
import org.apache.calcite.sql.SqlKind;
import org.apache.calcite.sql.SqlMerge;
import org.apache.calcite.sql.SqlNode;
+import org.apache.calcite.sql.SqlNumericLiteral;
import org.apache.calcite.sql.SqlUpdate;
+import org.apache.calcite.sql.type.SqlTypeName;
import org.apache.calcite.sql.util.SqlShuttle;
import org.apache.calcite.sql.validate.SqlValidator;
import org.apache.calcite.sql.validate.SqlValidatorScope;
@@ -83,6 +87,30 @@ public class IgniteSqlToRelConvertor extends
SqlToRelConverter {
}
}
+ @Override
+ protected RexNode convertExtendedExpression(
+ SqlNode expr,
+ Blackboard bb) {
+ SqlKind kind = expr.getKind();
+ if (kind == SqlKind.CAST) {
+ SqlCall call = (SqlCall) expr;
+ SqlNode op0 = call.operand(0);
+ SqlNode type = call.operand(1);
+ if (!(op0 instanceof SqlNumericLiteral) || !(type instanceof
SqlDataTypeSpec)) {
+ return null;
+ }
+ SqlNumericLiteral literal = (SqlNumericLiteral) op0;
+ RelDataType derived = ((SqlDataTypeSpec)
type).deriveType(validator);
+ // if BIGINT is present we need to preserve CAST from BIGINT to
BIGINT for further overflow check possibility
+ // TODO: need to be removed after
https://issues.apache.org/jira/browse/IGNITE-20889
+ if (derived.getSqlTypeName() == SqlTypeName.BIGINT) {
+ RexLiteral lit = rexBuilder.makeLiteral(literal.toValue());
+ return rexBuilder.makeCast(derived, lit, false, false);
+ }
+ }
+ return null;
+ }
+
@Override protected RelNode convertInsert(SqlInsert call) {
datasetStack.push(call);
diff --git
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/prepare/IgniteSqlValidator.java
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/prepare/IgniteSqlValidator.java
index 98b5210bda..a3a4a40a1d 100644
---
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/prepare/IgniteSqlValidator.java
+++
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/prepare/IgniteSqlValidator.java
@@ -17,17 +17,24 @@
package org.apache.ignite.internal.sql.engine.prepare;
+import static java.util.Objects.requireNonNull;
+import static org.apache.calcite.sql.type.SqlTypeName.INT_TYPES;
+import static org.apache.calcite.sql.type.SqlTypeUtil.equalSansNullability;
import static org.apache.calcite.sql.type.SqlTypeUtil.isNull;
import static org.apache.calcite.util.Static.RESOURCE;
import static org.apache.ignite.internal.lang.IgniteStringFormatter.format;
+import static org.apache.ignite.lang.ErrorGroups.Sql.STMT_PARSE_ERR;
import java.math.BigDecimal;
+import java.math.RoundingMode;
import java.util.Collections;
import java.util.EnumSet;
import java.util.List;
import java.util.Map;
+import java.util.Objects;
import java.util.Set;
import java.util.UUID;
+import java.util.regex.Pattern;
import org.apache.calcite.plan.RelOptTable;
import org.apache.calcite.prepare.CalciteCatalogReader;
import org.apache.calcite.prepare.Prepare;
@@ -57,7 +64,9 @@ import org.apache.calcite.sql.SqlUtil;
import org.apache.calcite.sql.dialect.CalciteSqlDialect;
import org.apache.calcite.sql.parser.SqlParserPos;
import org.apache.calcite.sql.type.SqlTypeName;
+import org.apache.calcite.sql.type.SqlTypeName.Limit;
import org.apache.calcite.sql.type.SqlTypeUtil;
+import org.apache.calcite.sql.util.SqlBasicVisitor;
import org.apache.calcite.sql.validate.SelectScope;
import org.apache.calcite.sql.validate.SqlValidator;
import org.apache.calcite.sql.validate.SqlValidatorImpl;
@@ -65,6 +74,7 @@ import org.apache.calcite.sql.validate.SqlValidatorNamespace;
import org.apache.calcite.sql.validate.SqlValidatorScope;
import org.apache.calcite.sql.validate.SqlValidatorTable;
import org.apache.calcite.sql.validate.SqlValidatorUtil;
+import org.apache.calcite.util.Util;
import org.apache.ignite.internal.sql.engine.schema.IgniteDataSource;
import org.apache.ignite.internal.sql.engine.schema.IgniteSystemView;
import org.apache.ignite.internal.sql.engine.schema.IgniteTable;
@@ -76,6 +86,7 @@ import org.apache.ignite.internal.sql.engine.type.UuidType;
import org.apache.ignite.internal.sql.engine.util.Commons;
import org.apache.ignite.internal.sql.engine.util.IgniteResource;
import org.apache.ignite.internal.sql.engine.util.TypeUtils;
+import org.apache.ignite.sql.SqlException;
import org.jetbrains.annotations.Nullable;
/** Validator. */
@@ -87,6 +98,11 @@ public class IgniteSqlValidator extends SqlValidatorImpl {
private static final Set<SqlKind> HUMAN_READABLE_ALIASES_FOR;
+ public static final String NUMERIC_FIELD_OVERFLOW_ERROR = "Numeric field
overflow";
+
+ //approximate and exact numeric types
+ private static final Pattern NUMERIC =
Pattern.compile("^\\s*\\d+(\\.{1}\\d*)\\s*$");
+
static {
EnumSet<SqlKind> kinds = EnumSet.noneOf(SqlKind.class);
@@ -109,6 +125,9 @@ public class IgniteSqlValidator extends SqlValidatorImpl {
/** Dynamic parameters SQL AST nodes for invariant checks - see {@link
#validateInferredDynamicParameters()}. */
private final SqlDynamicParam[] dynamicParamNodes;
+ /** Literal processing. */
+ private LiteralExtractor litExtractor;
+
/**
* Creates a validator.
*
@@ -178,6 +197,47 @@ public class IgniteSqlValidator extends SqlValidatorImpl {
syncSelectList(select, call);
}
+ /** {@inheritDoc} */
+ @Override
+ protected void checkTypeAssignment(
+ SqlValidatorScope sourceScope,
+ SqlValidatorTable table,
+ RelDataType sourceRowType,
+ RelDataType targetRowType,
+ SqlNode query
+ ) {
+ boolean coerced = false;
+
+ if (query instanceof SqlUpdate) {
+ SqlNodeList targetColumnList =
+ requireNonNull(((SqlUpdate) query).getTargetColumnList());
+ int targetColumnCount = targetColumnList.size();
+ targetRowType =
+ SqlTypeUtil.extractLastNFields(typeFactory, targetRowType,
+ targetColumnCount);
+ sourceRowType =
+ SqlTypeUtil.extractLastNFields(typeFactory, sourceRowType,
+ targetColumnCount);
+ }
+
+ // if BIGINT is present we need to preserve CAST from BIGINT to BIGINT
for further overflow check possibility
+ // TODO: need to be removed after
https://issues.apache.org/jira/browse/IGNITE-20889
+ if (config().typeCoercionEnabled()) {
+ if (SqlTypeUtil.equalAsStructSansNullability(typeFactory,
+ sourceRowType, targetRowType, null)) {
+ if ((query.getKind() == SqlKind.INSERT || query.getKind() ==
SqlKind.UPDATE)
+ && targetRowType.getFieldList().stream().anyMatch(fld
-> fld.getType().getSqlTypeName() == SqlTypeName.BIGINT)
+ && sourceRowType.getFieldList().stream().anyMatch(fld
-> fld.getType().getSqlTypeName() == SqlTypeName.BIGINT)) {
+ coerced =
getTypeCoercion().querySourceCoercion(sourceScope, sourceRowType,
targetRowType, query);
+ }
+ }
+ }
+
+ if (!coerced) {
+ super.checkTypeAssignment(sourceScope, table, sourceRowType,
targetRowType, query);
+ }
+ }
+
/** {@inheritDoc} */
@Override
public void validateMerge(SqlMerge call) {
@@ -458,7 +518,7 @@ public class IgniteSqlValidator extends SqlValidatorImpl {
if (fromCustomType != null && returnCustomType != null) {
// it`s not allowed to convert between different custom types
for now.
- check = SqlTypeUtil.equalSansNullability(typeFactory,
firstType, returnType);
+ check = equalSansNullability(typeFactory, firstType,
returnType);
} else if (fromCustomType != null) {
check = coercionRules.needToCast(returnType,
(IgniteCustomType) fromCustomType);
} else if (returnCustomType != null) {
@@ -479,6 +539,71 @@ public class IgniteSqlValidator extends SqlValidatorImpl {
throw
SqlUtil.newContextException(expr.getParserPosition(), ex);
}
}
+
+ if (castOp) {
+ literalCanFitType(expr, returnType);
+ }
+ }
+ }
+
+ /** Check literal can fit to declared exact numeric type, work only for
single literal. */
+ private void literalCanFitType(SqlNode expr, RelDataType toType) {
+ if (INT_TYPES.contains(toType.getSqlTypeName())) {
+ SqlLiteral literal = Objects.requireNonNullElseGet(litExtractor,
LiteralExtractor::new).getLiteral(expr);
+
+ if (literal == null || literal.toValue() == null) {
+ return;
+ }
+
+ int precision = toType.getSqlTypeName().allowsPrec() ?
toType.getPrecision() : -1;
+ int scale = toType.getSqlTypeName().allowsScale() ?
toType.getScale() : -1;
+
+ BigDecimal max = (BigDecimal)
toType.getSqlTypeName().getLimit(true, Limit.OVERFLOW, false, precision, scale);
+ BigDecimal min = (BigDecimal)
toType.getSqlTypeName().getLimit(false, Limit.OVERFLOW, false, precision,
scale);
+
+ String litValue = Objects.requireNonNull(literal.toValue());
+
+ BigDecimal litValueToDecimal = null;
+
+ try {
+ litValueToDecimal = new BigDecimal(litValue).setScale(0,
RoundingMode.HALF_UP);
+ } catch (NumberFormatException e) {
+ if (!NUMERIC.matcher(litValue).matches()) {
+ throw new SqlException(STMT_PARSE_ERR, e);
+ }
+ }
+
+ if (max.compareTo(litValueToDecimal) < 0 ||
min.compareTo(litValueToDecimal) > 0) {
+ throw new SqlException(STMT_PARSE_ERR, "Value '" + litValue +
"'"
+ + " out of range for type " + toType.getSqlTypeName());
+ }
+ }
+ }
+
+ private static class LiteralExtractor extends SqlBasicVisitor<SqlNode> {
+ private @Nullable SqlLiteral extracted = null;
+
+ private @Nullable SqlLiteral getLiteral(SqlNode expr) {
+ try {
+ expr.accept(this);
+ } catch (Util.FoundOne e) {
+ Util.swallow(e, null);
+ }
+ return extracted;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public SqlNode visit(SqlLiteral literal) {
+ extracted = extracted != null ? null : literal;
+ return literal;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public SqlNode visit(SqlDynamicParam param) {
+ extracted = null;
+ throw Util.FoundOne.NULL;
}
}
@@ -675,7 +800,7 @@ public class IgniteSqlValidator extends SqlValidatorImpl {
* operator's SqlOperandTypeInference and SqlOperandTypeCheckers.
*/
- if (inferredType.equals(unknownType) ||
(!SqlTypeUtil.equalSansNullability(type, inferredType))) {
+ if (inferredType.equals(unknownType) || (!equalSansNullability(type,
inferredType))) {
paramTypeToUse = type;
} else {
paramTypeToUse = inferredType;
@@ -724,7 +849,7 @@ public class IgniteSqlValidator extends SqlValidatorImpl {
RelDataType derivedType = getValidatedNodeType(param);
// We can check for nullability, but it was set to true.
- if (!SqlTypeUtil.equalSansNullability(derivedType, paramType)) {
+ if (!equalSansNullability(derivedType, paramType)) {
String message = format(
"Type of dynamic parameter#{} does not match.
Expected: {} derived: {}", i, paramType.getFullTypeString(),
derivedType.getFullTypeString()
diff --git
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/prepare/IgniteTypeCoercion.java
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/prepare/IgniteTypeCoercion.java
index 237c72767b..5ddb4efce7 100644
---
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/prepare/IgniteTypeCoercion.java
+++
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/prepare/IgniteTypeCoercion.java
@@ -271,6 +271,14 @@ public class IgniteTypeCoercion extends TypeCoercionImpl {
if (fromType == null) {
return false;
}
+
+ // we need this check for further possibility to validate BIGINT
overflow
+ // TODO: need to be removed after
https://issues.apache.org/jira/browse/IGNITE-20889
+ if (fromType.getSqlTypeName() == SqlTypeName.BIGINT &&
toType.getSqlTypeName() == SqlTypeName.BIGINT) {
+ if (node.getKind() == SqlKind.LITERAL) {
+ return true;
+ }
+ }
// The following checks ensure that there no ClassCastException
when casting from one
// integer type to another (e.g. int to smallint, int to bigint)
if (SqlTypeUtil.isIntType(fromType) && fromType.getSqlTypeName()
!= toType.getSqlTypeName()) {
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
new file mode 100644
index 0000000000..4b49020c4e
--- /dev/null
+++
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/util/IgniteMath.java
@@ -0,0 +1,306 @@
+/*
+ * 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.util;
+
+import static org.apache.calcite.sql.type.SqlTypeName.BIGINT;
+import static org.apache.calcite.sql.type.SqlTypeName.INTEGER;
+import static org.apache.calcite.sql.type.SqlTypeName.SMALLINT;
+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.ignite.sql.SqlException;
+
+/** Math operations with overflow checking. */
+public class IgniteMath {
+ private static final BigDecimal UPPER_LONG =
BigDecimal.valueOf(Long.MAX_VALUE);
+ private static final BigDecimal LOWER_LONG =
BigDecimal.valueOf(Long.MIN_VALUE);
+
+ /** 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;
+
+ if (((x ^ r) & (y ^ r)) < 0) {
+ throw new SqlException(RUNTIME_ERR, BIGINT.getName() + " out of
range");
+ }
+
+ return r;
+ }
+
+ /** Returns the sum of its arguments, throwing an exception if the result
overflows an {@code int}. */
+ public static int addExact(int x, int y) {
+ int r = x + y;
+
+ if (((x ^ r) & (y ^ r)) < 0) {
+ throw new SqlException(RUNTIME_ERR, INTEGER.getName() + " out of
range");
+ }
+
+ return r;
+ }
+
+ /** Returns the sum of its arguments, throwing an exception if the result
overflows an {@code short}. */
+ public static short addExact(short x, short y) {
+ int r = x + y;
+
+ if (r != (short) r) {
+ throw new SqlException(RUNTIME_ERR, SMALLINT.getName() + " out of
range");
+ }
+
+ return (short) r;
+ }
+
+ /** Returns the sum of its arguments, throwing an exception if the result
overflows an {@code byte}. */
+ public static byte addExact(byte x, byte y) {
+ int r = x + y;
+
+ if (r != (byte) r) {
+ throw new SqlException(RUNTIME_ERR, TINYINT.getName() + " out of
range");
+ }
+
+ return (byte) r;
+ }
+
+ /** Returns the negation of the argument, throwing an exception if the
result overflows an {@code long}. */
+ public static long negateExact(long x) {
+ long res = -x;
+
+ if (x != 0 && x == res) {
+ throw new SqlException(RUNTIME_ERR, BIGINT.getName() + " out of
range");
+ }
+
+ return res;
+ }
+
+ /** Returns the negation of the argument, throwing an exception if the
result overflows an {@code int}. */
+ public static int negateExact(int x) {
+ int res = -x;
+
+ if (x != 0 && x == res) {
+ throw new SqlException(RUNTIME_ERR, INTEGER.getName() + " out of
range");
+ }
+
+ return res;
+ }
+
+ /** Returns the negation of the argument, throwing an exception if the
result overflows an {@code short}. */
+ public static short negateExact(short x) {
+ int res = -x;
+
+ if (res > Short.MAX_VALUE) {
+ throw new SqlException(RUNTIME_ERR, SMALLINT.getName() + " out of
range");
+ }
+
+ return (short) res;
+ }
+
+ /** Returns the negation of the argument, throwing an exception if the
result overflows an {@code byte}. */
+ public static byte negateExact(byte x) {
+ int res = -x;
+
+ if (res > Byte.MAX_VALUE) {
+ throw new SqlException(RUNTIME_ERR, TINYINT.getName() + " out of
range");
+ }
+
+ return (byte) res;
+ }
+
+ /** Returns the difference of the arguments, throwing an exception if the
result overflows an {@code long}.*/
+ public static long subtractExact(long x, long y) {
+ long r = x - y;
+
+ if (((x ^ y) & (x ^ r)) < 0) {
+ throw new SqlException(RUNTIME_ERR, BIGINT.getName() + " out of
range");
+ }
+
+ return r;
+ }
+
+ /** Returns the difference of the arguments, throwing an exception if the
result overflows an {@code int}.*/
+ public static int subtractExact(int x, int y) {
+ int r = x - y;
+
+ if (((x ^ y) & (x ^ r)) < 0) {
+ throw new SqlException(RUNTIME_ERR, INTEGER.getName() + " out of
range");
+ }
+
+ return r;
+ }
+
+ /** Returns the difference of the arguments, throwing an exception if the
result overflows an {@code short}.*/
+ public static short subtractExact(short x, short y) {
+ int r = x - y;
+
+ if (r != (short) r) {
+ throw new SqlException(RUNTIME_ERR, SMALLINT.getName() + " out of
range");
+ }
+
+ return (short) r;
+ }
+
+ /** Returns the difference of the arguments, throwing an exception if the
result overflows an {@code byte}.*/
+ public static byte subtractExact(byte x, byte y) {
+ int r = x - y;
+
+ if (r != (byte) r) {
+ throw new SqlException(RUNTIME_ERR, TINYINT.getName() + " out of
range");
+ }
+
+ return (byte) r;
+ }
+
+ /** Returns the product of the arguments, throwing an exception if the
result overflows an {@code long}. */
+ public static long multiplyExact(long x, long y) {
+ long r = x * y;
+ long ax = Math.abs(x);
+ long ay = Math.abs(y);
+
+ if ((ax | ay) >>> 31 != 0 && ((y != 0 && r / y != x) || (x ==
Long.MIN_VALUE && y == -1))) {
+ throw new SqlException(RUNTIME_ERR, BIGINT.getName() + " out of
range");
+ }
+
+ return r;
+ }
+
+ /** Returns the product of the arguments, throwing an exception if the
result overflows an {@code int}. */
+ public static int multiplyExact(int x, int y) {
+ long r = (long) x * (long) y;
+
+ if ((int) r != r) {
+ throw new SqlException(RUNTIME_ERR, INTEGER.getName() + " out of
range");
+ }
+
+ return (int) r;
+ }
+
+ /** Returns the product of the arguments, throwing an exception if the
result overflows an {@code short}. */
+ public static short multiplyExact(short x, short y) {
+ int r = x * y;
+
+ if (r != (short) r) {
+ throw new SqlException(RUNTIME_ERR, SMALLINT.getName() + " out of
range");
+ }
+
+ return (short) r;
+ }
+
+ /** Returns the product of the arguments, throwing an exception if the
result overflows an {@code byte}. */
+ public static byte multiplyExact(byte x, byte y) {
+ int r = x * y;
+
+ if (r != (byte) r) {
+ throw new SqlException(RUNTIME_ERR, TINYINT.getName() + " out of
range");
+ }
+
+ return (byte) r;
+ }
+
+ /** Returns the quotient of the arguments, throwing an exception if the
result overflows an {@code long}. */
+ public static long divideExact(long x, long y) {
+ if (y == -1) {
+ return negateExact(x);
+ }
+
+ return x / y;
+ }
+
+ /** Returns the quotient of the arguments, throwing an exception if the
result overflows an {@code int}. */
+ public static int divideExact(int x, int y) {
+ if (y == -1) {
+ return negateExact(x);
+ }
+
+ return x / y;
+ }
+
+ /** Returns the quotient of the arguments, throwing an exception if the
result overflows an {@code short}. */
+ public static short divideExact(short x, short y) {
+ if (y == -1) {
+ return negateExact(x);
+ }
+
+ return (short) (x / y);
+ }
+
+ /** Returns the quotient of the arguments, throwing an exception if the
result overflows an {@code byte}. */
+ public static byte divideExact(byte x, byte y) {
+ if (y == -1) {
+ return negateExact(x);
+ }
+
+ return (byte) (x / y);
+ }
+
+ /** Cast value to {@code int}, throwing an exception if the result
overflows an {@code int}. */
+ public static int convertToIntExact(long x) {
+ if ((int) x != x) {
+ throw new SqlException(RUNTIME_ERR, INTEGER.getName() + " out of
range");
+ }
+
+ return (int) x;
+ }
+
+ /** Cast value to {@code long}, throwing an exception if the result
overflows an {@code long}. */
+ public static long convertToLongExact(BigDecimal x) {
+ if (x.compareTo(UPPER_LONG) > 0 || x.compareTo(LOWER_LONG) < 0) {
+ throw new SqlException(RUNTIME_ERR, BIGINT.getName() + " out of
range");
+ }
+ return x.longValue();
+ }
+
+ /** Cast value to {@code long}, throwing an exception if the result
overflows an {@code long}. */
+ public static long convertToLongExact(String x) {
+ BigDecimal decimal = new BigDecimal(x);
+ if (UPPER_LONG.compareTo(decimal.setScale(0, RoundingMode.HALF_UP)) < 0
+ || LOWER_LONG.compareTo(decimal.setScale(0,
RoundingMode.HALF_UP)) > 0) {
+ throw new SqlException(RUNTIME_ERR, BIGINT.getName() + " out of
range");
+ }
+
+ return decimal.longValue();
+ }
+
+ /** Cast value to {@code short}, throwing an exception if the result
overflows an {@code short}. */
+ public static short convertToShortExact(long x) {
+ if ((short) x != x) {
+ throw new SqlException(RUNTIME_ERR, SMALLINT.getName() + " out of
range");
+ }
+
+ return (short) x;
+ }
+
+ /** Cast value to {@code byte}, throwing an exception if the result
overflows an {@code byte}. */
+ public static short convertToShortExact(BigDecimal x) {
+ long num = x.longValue();
+ return convertToShortExact(num);
+ }
+
+ /** Cast value to {@code byte}, throwing an exception if the result
overflows an {@code byte}. */
+ public static byte convertToByteExact(long x) {
+ if ((byte) x != x) {
+ throw new SqlException(RUNTIME_ERR, TINYINT.getName() + " out of
range");
+ }
+
+ return (byte) x;
+ }
+
+ /** Cast value to {@code byte}, throwing an exception if the result
overflows an {@code byte}. */
+ public static byte convertToByteExact(BigDecimal x) {
+ long num = x.longValue();
+ return convertToByteExact(num);
+ }
+}
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 85435c0883..024a10dc18 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
@@ -17,6 +17,7 @@
package org.apache.ignite.internal.sql.engine.exec.exp;
+import static
org.apache.ignite.internal.sql.engine.prepare.IgniteSqlValidator.NUMERIC_FIELD_OVERFLOW_ERROR;
import static
org.apache.ignite.internal.sql.engine.util.SqlTestUtils.assertThrowsSqlException;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNull;
@@ -239,7 +240,7 @@ public class IgniteSqlFunctionsTest {
BigDecimal expected = convert.get();
assertEquals(new BigDecimal(result), expected);
} else {
- assertThrowsSqlException(Sql.RUNTIME_ERR, "Numeric field
overflow", convert::get);
+ assertThrowsSqlException(Sql.RUNTIME_ERR,
NUMERIC_FIELD_OVERFLOW_ERROR, convert::get);
}
}
diff --git
a/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/planner/ImplicitCastsTest.java
b/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/planner/ImplicitCastsTest.java
index 8a1efd7eca..d876e3a46a 100644
---
a/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/planner/ImplicitCastsTest.java
+++
b/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/planner/ImplicitCastsTest.java
@@ -296,11 +296,6 @@ public class ImplicitCastsTest extends AbstractPlannerTest
{
.sql("UPDATE t1 SET c1 = '10'")
.project("$t0", "10:BIGINT"),
- checkStatement()
- .table("t1", "c1", NativeTypes.INT32)
- .sql("UPDATE t1 SET c1 = 'abc'")
- .project("$t0", "CAST(_UTF-8'abc'):INTEGER NOT NULL"),
-
checkStatement()
.table("t1", "c1", NativeTypes.INT64, "c2",
NativeTypes.INT64)
.table("t2", "c1", NativeTypes.INT32, "c2",
NativeTypes.INT32)
@@ -341,23 +336,14 @@ public class ImplicitCastsTest extends
AbstractPlannerTest {
return Stream.of(
checkStatement()
.table("t1", "c1", NativeTypes.INT32)
- .sql("UPDATE t1 SET c1 = '1'")
- .project("$t0", "1"),
-
- checkStatement()
- .table("t1", "c1", NativeTypes.INT32)
- .sql("UPDATE t1 SET c1 = 'abc'")
- .project("$t0", "CAST(_UTF-8'abc'):INTEGER NOT NULL"),
+ .sql("UPDATE t1 SET c1 = '1'::INTEGER + 1")
+ .project("$t0", "+(1, 1)"),
checkStatement()
.table("t1", "c1", NativeTypes.stringOf(4))
.sql("UPDATE t1 SET c1 = 1")
.project("$t0", "_UTF-8'1':VARCHAR(4) CHARACTER SET
\"UTF-8\""),
- // If int_col is accessed too early, we get:
- // java.lang.UnsupportedOperationException:
- // at
org.apache.calcite.util.Util.needToImplement(Util.java:1111)
- // at
org.apache.calcite.sql.validate.SqlValidatorImpl.getValidatedNodeType(SqlValidatorImpl.java:1795)
checkStatement()
.table("t1", "id", NativeTypes.INT32, "int_col",
NativeTypes.INT32, "str_col", NativeTypes.STRING)
.sql("UPDATE t1 SET str_col = 1, int_col = id + 1")
@@ -407,15 +393,15 @@ public class ImplicitCastsTest extends
AbstractPlannerTest {
public Stream<DynamicTest> testInExpression() {
return Stream.of(
// literals
- sql("SELECT '1'::int IN ('a')").project("=(1,
CAST(_UTF-8'a'):INTEGER NOT NULL)"),
+ sql("SELECT '1'::int IN ('1'::INTEGER)").project("true"),
sql("SELECT 1 IN ('1', 2)").project("true"),
sql("SELECT '1' IN (1, 2)").project("true"),
- sql("SELECT 2 IN ('c', 1)").project("=(2,
CAST(_UTF-8'c'):INTEGER NOT NULL)"),
+ sql("SELECT 2 IN ('2'::REAL, 1)").project("true"),
checkStatement()
.table("t", "int_col", NativeTypes.INT32, "str_col",
NativeTypes.stringOf(4), "bigint_col", NativeTypes.INT64)
- .sql("SELECT int_col IN ('c', 1) FROM t")
- .project("OR(=($t0, CAST(_UTF-8'c'):INTEGER NOT NULL),
=($t0, 1))"),
+ .sql("SELECT int_col IN ('c'::REAL, 1) FROM t")
+ .project("OR(=(CAST($t0):REAL, CAST(_UTF-8'c'):REAL
NOT NULL), =(CAST($t0):REAL, 1))"),
checkStatement()
.table("t", "int_col", NativeTypes.INT32, "str_col",
NativeTypes.stringOf(4), "bigint_col", NativeTypes.INT64)