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)

Reply via email to