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 47bf25dedd IGNITE-19877 Sql. Fix not allowed CAST operations - Fixes 
#2368.
47bf25dedd is described below

commit 47bf25dedd581a279b5fa4c8566270bcc81156b2
Author: zstan <[email protected]>
AuthorDate: Wed Aug 9 14:54:58 2023 +0300

    IGNITE-19877 Sql. Fix not allowed CAST operations - Fixes #2368.
    
    Signed-off-by: zstan <[email protected]>
---
 .../ignite/jdbc/ItJdbcResultSetSelfTest.java       |  24 +-
 .../internal/sql/engine/ItDataTypesTest.java       |  33 ++-
 .../internal/sql/engine/ItFunctionsTest.java       |  29 ++-
 .../ignite/internal/sql/engine/ItIntervalTest.java |  42 ++--
 .../internal/sql/engine/ItSqlOperatorsTest.java    |   1 -
 .../sql/engine/datatypes/uuid/ItUuidQueryTest.java |   2 +-
 .../datatypes/varbinary/ItVarBinaryQueryTest.java  |   2 +-
 .../sql/cast/test_boolean_cast.test                |  73 ++----
 .../sql/engine/prepare/IgniteSqlValidator.java     |  71 +++++-
 .../ignite/internal/sql/engine/util/Commons.java   |   6 +
 .../engine/util/IgniteCustomAssigmentsRules.java   | 255 +++++++++++++++++++++
 .../sql/engine/planner/AbstractPlannerTest.java    |  12 +-
 .../sql/engine/planner/CastResolutionTest.java     | 217 ++++++++++++++++++
 .../sql/engine/prepare/TypeCoercionTest.java       |  30 +--
 .../internal/sql/engine/util/StatementChecker.java |  27 ++-
 15 files changed, 668 insertions(+), 156 deletions(-)

diff --git 
a/modules/jdbc/src/integrationTest/java/org/apache/ignite/jdbc/ItJdbcResultSetSelfTest.java
 
b/modules/jdbc/src/integrationTest/java/org/apache/ignite/jdbc/ItJdbcResultSetSelfTest.java
index 78d50e6db0..ac3da2922a 100644
--- 
a/modules/jdbc/src/integrationTest/java/org/apache/ignite/jdbc/ItJdbcResultSetSelfTest.java
+++ 
b/modules/jdbc/src/integrationTest/java/org/apache/ignite/jdbc/ItJdbcResultSetSelfTest.java
@@ -39,9 +39,6 @@ import java.sql.Time;
 import java.sql.Timestamp;
 import java.time.Instant;
 import java.time.LocalDate;
-import java.time.LocalTime;
-import java.time.ZoneId;
-import java.time.ZonedDateTime;
 import java.util.GregorianCalendar;
 import java.util.UUID;
 import org.apache.ignite.internal.tostring.S;
@@ -56,8 +53,8 @@ public class ItJdbcResultSetSelfTest extends 
AbstractJdbcSelfTest {
     private static final String STATIC_SQL =
             "SELECT 1::INTEGER as id, true as boolVal, 1::TINYINT as byteVal, 
1::SMALLINT as shortVal, 1::INTEGER as intVal, 1::BIGINT "
                     + "as longVal, 1.0::FLOAT as floatVal, 1.0::DOUBLE as 
doubleVal, 1.0::DECIMAL as bigVal, "
-                    + "'1' as strVal, '1', '1901-02-01'::DATE as dateVal, 
'01:01:01'::TIME as timeVal, 0::TIMESTAMP as tsVal,"
-                    + "'fd10556e-fc27-4a99-b5e4-89b8344cb3ce'::UUID as 
uuidVal";
+                    + "'1' as strVal, '1', '1901-02-01'::DATE as dateVal, 
'01:01:01'::TIME as timeVal, "
+                    + "TIMESTAMP '1970-01-01 00:00:00' as tsVal, 
'fd10556e-fc27-4a99-b5e4-89b8344cb3ce'::UUID as uuidVal";
 
     /** SQL query. */
     private static final String SQL_SINGLE_RES = "select id, boolVal, byteVal, 
shortVal, intVal, longVal, floatVal, "
@@ -559,16 +556,17 @@ public class ItJdbcResultSetSelfTest extends 
AbstractJdbcSelfTest {
 
         assertTrue(rs.next());
 
-        Instant localEpoch = ZonedDateTime.of(LocalDate.EPOCH, 
LocalTime.MIDNIGHT, ZoneId.systemDefault()).toInstant();
+        Timestamp localEpoch = Timestamp.valueOf("1970-01-01 00:00:00");
+        Instant localEpochInst = localEpoch.toInstant();
 
-        assertEquals(Timestamp.from(localEpoch), rs.getTimestamp("tsVal"));
-        assertEquals(Date.from(localEpoch), rs.getDate(14));
-        assertEquals(Time.from(localEpoch), rs.getTime(14));
-        assertEquals(Timestamp.from(localEpoch), rs.getTimestamp(14));
+        assertEquals(localEpoch, rs.getTimestamp("tsVal"));
+        assertEquals(Date.from(localEpochInst), rs.getDate(14));
+        assertEquals(Time.from(localEpochInst), rs.getTime(14));
+        assertEquals(Timestamp.from(localEpochInst), rs.getTimestamp(14));
 
-        assertEquals(Date.from(localEpoch), rs.getObject(14, Date.class));
-        assertEquals(Time.from(localEpoch), rs.getObject(14, Time.class));
-        assertEquals(Timestamp.from(localEpoch), rs.getObject(14, 
Timestamp.class));
+        assertEquals(Date.from(localEpochInst), rs.getObject(14, Date.class));
+        assertEquals(Time.from(localEpochInst), rs.getObject(14, Time.class));
+        assertEquals(Timestamp.from(localEpochInst), rs.getObject(14, 
Timestamp.class));
 
         assertFalse(rs.next());
     }
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 90920a4192..751cad3bd9 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
@@ -442,59 +442,58 @@ public class ItDataTypesTest extends 
ClusterPerClassIntegrationTest {
                 arguments(CaseStatus.RUN, varcharType, "100", decimalType(3), 
bigDecimalVal("100")),
                 arguments(CaseStatus.RUN, varcharType, "100", decimalType(3), 
bigDecimalVal("100")),
                 arguments(CaseStatus.RUN, varcharType, "100", decimalType(3, 
0), bigDecimalVal("100")),
-                // TODO Uncomment these test cases after 
https://issues.apache.org/jira/browse/IGNITE-19822 is fixed.
-                arguments(CaseStatus.SKIP, varcharType, "100", decimalType(4, 
1), bigDecimalVal("100.0")),
-                arguments(CaseStatus.SKIP, varcharType, "100", decimalType(2, 
0), error(NUMERIC_OVERFLOW_ERROR)),
+                arguments(CaseStatus.RUN, varcharType, "100", decimalType(4, 
1), bigDecimalVal("100.0")),
+                arguments(CaseStatus.RUN, varcharType, "100", decimalType(2, 
0), error(NUMERIC_OVERFLOW_ERROR)),
 
                 // Tinyint
-                arguments(CaseStatus.SKIP, tinyIntType, (byte) 100, 
decimalType(3), bigDecimalVal("100")),
+                arguments(CaseStatus.RUN, tinyIntType, (byte) 100, 
decimalType(3), bigDecimalVal("100")),
                 arguments(CaseStatus.RUN, tinyIntType, (byte) 100, 
decimalType(3, 0), bigDecimalVal("100")),
                 // TODO Uncomment these test cases after 
https://issues.apache.org/jira/browse/IGNITE-19822 is fixed.
                 arguments(CaseStatus.SKIP, tinyIntType, (byte) 100, 
decimalType(4, 1), bigDecimalVal("100.0")),
-                arguments(CaseStatus.SKIP, tinyIntType, (byte) 100, 
decimalType(2, 0), error(NUMERIC_OVERFLOW_ERROR)),
+                arguments(CaseStatus.RUN, tinyIntType, (byte) 100, 
decimalType(2, 0), error(NUMERIC_OVERFLOW_ERROR)),
 
                 // Smallint
                 arguments(CaseStatus.RUN, smallIntType, (short) 100, 
decimalType(3), bigDecimalVal("100")),
                 arguments(CaseStatus.RUN, smallIntType, (short) 100, 
decimalType(3, 0), bigDecimalVal("100")),
                 // TODO Uncomment these test cases after 
https://issues.apache.org/jira/browse/IGNITE-19822 is fixed.
                 arguments(CaseStatus.SKIP, smallIntType, (short) 100, 
decimalType(4, 1), bigDecimalVal("100.0")),
-                arguments(CaseStatus.SKIP, smallIntType, (short) 100, 
decimalType(2, 0), error(NUMERIC_OVERFLOW_ERROR)),
+                arguments(CaseStatus.RUN, smallIntType, (short) 100, 
decimalType(2, 0), error(NUMERIC_OVERFLOW_ERROR)),
 
                 // Integer
                 arguments(CaseStatus.RUN, integerType, 100, decimalType(3), 
bigDecimalVal("100")),
                 arguments(CaseStatus.RUN, integerType, 100, decimalType(3, 0), 
bigDecimalVal("100")),
                 // TODO Uncomment these test cases after 
https://issues.apache.org/jira/browse/IGNITE-19822 is fixed.
                 arguments(CaseStatus.SKIP, integerType, 100, decimalType(4, 
1), bigDecimalVal("100.0")),
-                arguments(CaseStatus.SKIP, integerType, 100, decimalType(2, 
0), error(NUMERIC_OVERFLOW_ERROR)),
+                arguments(CaseStatus.RUN, integerType, 100, decimalType(2, 0), 
error(NUMERIC_OVERFLOW_ERROR)),
 
                 // Bigint
                 arguments(CaseStatus.RUN, bigintType, 100L, decimalType(3), 
bigDecimalVal("100")),
                 arguments(CaseStatus.RUN, bigintType, 100L, decimalType(3, 0), 
bigDecimalVal("100")),
                 // TODO Uncomment these test cases after 
https://issues.apache.org/jira/browse/IGNITE-19822 is fixed.
                 arguments(CaseStatus.SKIP, bigintType, 100L, decimalType(4, 
1), bigDecimalVal("100.0")),
-                arguments(CaseStatus.SKIP, bigintType, 100L, decimalType(2, 
0), error(NUMERIC_OVERFLOW_ERROR)),
+                arguments(CaseStatus.RUN, bigintType, 100L, decimalType(2, 0), 
error(NUMERIC_OVERFLOW_ERROR)),
 
                 // Real
                 // TODO Uncomment these test cases after 
https://issues.apache.org/jira/browse/IGNITE-19822 is fixed.
                 arguments(CaseStatus.SKIP, realType, 100.0f, decimalType(3), 
bigDecimalVal("100")),
                 arguments(CaseStatus.SKIP, realType, 100.0f, decimalType(3, 
0), bigDecimalVal("100")),
                 arguments(CaseStatus.SKIP, realType, 100.0f, decimalType(4, 
1), bigDecimalVal("100.0")),
-                arguments(CaseStatus.SKIP, realType, 100.0f, decimalType(2, 
0), error(NUMERIC_OVERFLOW_ERROR)),
+                arguments(CaseStatus.RUN, realType, 100.0f, decimalType(2, 0), 
error(NUMERIC_OVERFLOW_ERROR)),
                 arguments(CaseStatus.SKIP, realType, 0.1f, decimalType(1, 1), 
bigDecimalVal("0.1")),
                 arguments(CaseStatus.SKIP, realType, 0.1f, decimalType(2, 2), 
bigDecimalVal("0.10")),
-                arguments(CaseStatus.SKIP, realType, 10.12f, decimalType(2, 
1), error(NUMERIC_OVERFLOW_ERROR)),
-                arguments(CaseStatus.SKIP, realType, 0.12f, decimalType(1, 2), 
error(NUMERIC_OVERFLOW_ERROR)),
+                arguments(CaseStatus.RUN, realType, 10.12f, decimalType(2, 1), 
error(NUMERIC_OVERFLOW_ERROR)),
+                arguments(CaseStatus.RUN, realType, 0.12f, decimalType(1, 2), 
error(NUMERIC_OVERFLOW_ERROR)),
 
                 // Double
                 // TODO Uncomment these test cases after 
https://issues.apache.org/jira/browse/IGNITE-19822 is fixed.
                 arguments(CaseStatus.SKIP, doubleType, 100.0d, decimalType(3), 
bigDecimalVal("100")),
                 arguments(CaseStatus.SKIP, doubleType, 100.0d, decimalType(3, 
0), bigDecimalVal("100")),
                 arguments(CaseStatus.SKIP, doubleType, 100.0d, decimalType(4, 
1), bigDecimalVal("100.0")),
-                arguments(CaseStatus.SKIP, doubleType, 100.0d, decimalType(2, 
0), error(NUMERIC_OVERFLOW_ERROR)),
+                arguments(CaseStatus.RUN, doubleType, 100.0d, decimalType(2, 
0), error(NUMERIC_OVERFLOW_ERROR)),
                 arguments(CaseStatus.SKIP, doubleType, 0.1d, decimalType(1, 
1), bigDecimalVal("0.1")),
                 arguments(CaseStatus.SKIP, doubleType, 0.1d, decimalType(2, 
2), bigDecimalVal("0.10")),
-                arguments(CaseStatus.SKIP, doubleType, 10.12d, decimalType(2, 
1), error(NUMERIC_OVERFLOW_ERROR)),
-                arguments(CaseStatus.SKIP, doubleType, 0.12d, decimalType(1, 
2), error(NUMERIC_OVERFLOW_ERROR)),
+                arguments(CaseStatus.RUN, doubleType, 10.12d, decimalType(2, 
1), error(NUMERIC_OVERFLOW_ERROR)),
+                arguments(CaseStatus.RUN, doubleType, 0.12d, decimalType(1, 
2), error(NUMERIC_OVERFLOW_ERROR)),
 
                 // Decimal
                 arguments(CaseStatus.RUN, decimalType(1, 1), new 
BigDecimal("0.1"), decimalType(1, 1), bigDecimalVal("0.1")),
@@ -502,10 +501,10 @@ public class ItDataTypesTest extends 
ClusterPerClassIntegrationTest {
                 arguments(CaseStatus.RUN, decimalType(3), new 
BigDecimal("100"), decimalType(3, 0), bigDecimalVal("100")),
                 // TODO Uncomment these test cases after 
https://issues.apache.org/jira/browse/IGNITE-19822 is fixed.
                 arguments(CaseStatus.SKIP, decimalType(3), new 
BigDecimal("100"), decimalType(4, 1), bigDecimalVal("100.0")),
-                arguments(CaseStatus.SKIP, decimalType(3), new 
BigDecimal("100"), decimalType(2, 0), error(NUMERIC_OVERFLOW_ERROR)),
+                arguments(CaseStatus.RUN, decimalType(3), new 
BigDecimal("100"), decimalType(2, 0), error(NUMERIC_OVERFLOW_ERROR)),
                 arguments(CaseStatus.SKIP, decimalType(1, 1), new 
BigDecimal("0.1"), decimalType(2, 2), bigDecimalVal("0.10")),
-                arguments(CaseStatus.SKIP, decimalType(4, 2), new 
BigDecimal("10.12"), decimalType(2, 1), error(NUMERIC_OVERFLOW_ERROR)),
-                arguments(CaseStatus.SKIP, decimalType(2, 2), new 
BigDecimal("0.12"), decimalType(1, 2), error(NUMERIC_OVERFLOW_ERROR)),
+                arguments(CaseStatus.RUN, decimalType(4, 2), new 
BigDecimal("10.12"), decimalType(2, 1), error(NUMERIC_OVERFLOW_ERROR)),
+                arguments(CaseStatus.RUN, decimalType(2, 2), new 
BigDecimal("0.12"), decimalType(1, 2), error(NUMERIC_OVERFLOW_ERROR)),
                 arguments(CaseStatus.SKIP, decimalType(1, 1), new 
BigDecimal("0.1"), decimalType(1, 1), bigDecimalVal("0.1"))
         );
     }
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 e57be49b74..852aa09c30 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
@@ -17,6 +17,7 @@
 
 package org.apache.ignite.internal.sql.engine;
 
+import static org.apache.calcite.util.Static.RESOURCE;
 import static 
org.apache.ignite.internal.testframework.IgniteTestUtils.assertThrowsWithCause;
 import static org.apache.ignite.lang.IgniteStringFormatter.format;
 import static org.hamcrest.MatcherAssert.assertThat;
@@ -31,7 +32,10 @@ import java.time.LocalDate;
 import java.time.LocalDateTime;
 import java.time.LocalTime;
 import java.time.temporal.Temporal;
+import org.apache.calcite.runtime.Resources.ExInst;
+import org.apache.calcite.sql.SqlKind;
 import org.apache.calcite.sql.validate.SqlValidatorException;
+import org.apache.ignite.internal.sql.engine.util.IgniteResource;
 import org.apache.ignite.lang.IgniteException;
 import org.junit.jupiter.api.Test;
 
@@ -266,16 +270,21 @@ public class ItFunctionsTest extends 
ClusterPerClassIntegrationTest {
         assertQuery("SELECT 'TruE'::BOOLEAN").returns(true).check();
         assertQuery("SELECT 'false'::BOOLEAN").returns(false).check();
         assertQuery("SELECT 'FalsE'::BOOLEAN").returns(false).check();
-        assertQuery("SELECT 
NULL::DOUBLE::BOOLEAN").returns(NULL_RESULT).check();
-        assertQuery("SELECT 
?::DOUBLE::BOOLEAN").withParams(NULL_RESULT).returns(NULL_RESULT).check();
-
-        // TODO IGNITE-19877 Cast to boolean from other types (except 
true/false literals) is not permitted.
-        // assertThrows(SqlException.class, () -> sql("SELECT 1::BOOLEAN"));
-        // assertThrows(SqlException.class, () -> sql("SELECT ?::BOOLEAN", 1));
-        // assertThrows(SqlException.class, () -> sql("SELECT 1.0::BOOLEAN"));
-        // assertThrows(SqlException.class, () -> sql("SELECT ?::BOOLEAN", 
1.0));
-        // assertThrows(SqlException.class, () -> sql("SELECT '1'::BOOLEAN"));
-        // assertThrows(SqlException.class, () -> sql("SELECT ?::BOOLEAN", 
"1"));
+        assertQuery("SELECT NULL::CHAR::BOOLEAN").returns(NULL_RESULT).check();
+        assertQuery("SELECT 
?::CHAR::BOOLEAN").withParams(NULL_RESULT).returns(NULL_RESULT).check();
+
+        ExInst<SqlValidatorException> errDynParams = 
IgniteResource.INSTANCE.operationRequiresExplicitCast(SqlKind.CAST.name());
+        String errStrDynParams = errDynParams.ex().getMessage();
+
+        ExInst<SqlValidatorException> errWithoutDynParams = 
RESOURCE.incompatibleValueType(SqlKind.CAST.name());
+        String errStrWithoutDynParams = errWithoutDynParams.ex().getMessage();
+
+        assertThrows(IgniteException.class, () -> sql("SELECT 1::BOOLEAN"), 
errStrWithoutDynParams);
+        assertThrows(IgniteException.class, () -> sql("SELECT ?::BOOLEAN", 1), 
errStrDynParams);
+        assertThrows(IgniteException.class, () -> sql("SELECT 1.0::BOOLEAN"), 
errStrWithoutDynParams);
+        assertThrows(IgniteException.class, () -> sql("SELECT ?::BOOLEAN", 
1.0), errStrDynParams);
+        assertThrows(IgniteException.class, () -> sql("SELECT '1'::BOOLEAN"), 
errStrWithoutDynParams);
+        assertThrows(IgniteException.class, () -> sql("SELECT ?::BOOLEAN", 
"1"), errStrDynParams);
     }
 
     @Test
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 b5b51f2aba..d01c3ce46f 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
@@ -70,25 +70,29 @@ public class ItIntervalTest extends 
ClusterPerClassIntegrationTest {
      */
     @Test
     public void testIntervalIntCast() {
-        assertNull(eval("CAST(NULL::INTERVAL SECONDS AS INT)"));
-        assertNull(eval("CAST(NULL::INTERVAL MONTHS AS INT)"));
-        assertEquals(1, eval("CAST(INTERVAL 1 SECONDS AS INT)"));
-        assertEquals(2, eval("CAST(INTERVAL 2 MINUTES AS INT)"));
-        assertEquals(3, eval("CAST(INTERVAL 3 HOURS AS INT)"));
-        assertEquals(4, eval("CAST(INTERVAL 4 DAYS AS INT)"));
-        assertEquals(-4, eval("CAST(INTERVAL -4 DAYS AS INT)"));
-        assertEquals(5, eval("CAST(INTERVAL 5 MONTHS AS INT)"));
-        assertEquals(6, eval("CAST(INTERVAL 6 YEARS AS INT)"));
-        assertEquals(-6, eval("CAST(INTERVAL -6 YEARS AS INT)"));
-
-        assertNull(eval("CAST(NULL::INT AS INTERVAL SECONDS)"));
-        assertNull(eval("CAST(NULL::INT AS INTERVAL MONTHS)"));
-        assertEquals(Duration.ofSeconds(1), eval("CAST(1 AS INTERVAL 
SECONDS)"));
-        assertEquals(Duration.ofMinutes(2), eval("CAST(2 AS INTERVAL 
MINUTES)"));
-        assertEquals(Duration.ofHours(3), eval("CAST(3 AS INTERVAL HOURS)"));
-        assertEquals(Duration.ofDays(4), eval("CAST(4 AS INTERVAL DAYS)"));
-        assertEquals(Period.ofMonths(5), eval("CAST(5 AS INTERVAL MONTHS)"));
-        assertEquals(Period.ofYears(6), eval("CAST(6 AS INTERVAL YEARS)"));
+        assertThrows(IgniteException.class, () -> eval("CAST(NULL::INTERVAL 
SECONDS AS INT)"));
+        assertThrows(IgniteException.class, () -> eval("CAST(NULL::INTERVAL 
MONTHS AS INT)"));
+        assertThrows(IgniteException.class, () -> eval("CAST(INTERVAL 1 
SECONDS AS INT)"));
+        assertThrows(IgniteException.class, () -> eval("CAST(INTERVAL 2 
MINUTES AS INT)"));
+        assertThrows(IgniteException.class, () -> eval("CAST(INTERVAL 3 HOURS 
AS INT)"));
+        assertThrows(IgniteException.class, () -> eval("CAST(INTERVAL 4 DAYS 
AS INT)"));
+        assertThrows(IgniteException.class, () -> eval("CAST(INTERVAL -4 DAYS 
AS INT)"));
+        assertThrows(IgniteException.class, () -> eval("CAST(INTERVAL 5 MONTHS 
AS INT)"));
+        assertThrows(IgniteException.class, () -> eval("CAST(INTERVAL 6 YEARS 
AS INT)"));
+        assertThrows(IgniteException.class, () -> eval("CAST(INTERVAL -6 YEARS 
AS INT)"));
+
+        assertEquals("+6", eval("CAST(INTERVAL 6 YEARS AS VARCHAR)"));
+        assertEquals("+1", eval("CAST(INTERVAL 1 HOUR AS VARCHAR)"));
+        assertEquals("+7.000000", eval("CAST(INTERVAL 7 SECONDS AS VARCHAR)"));
+
+        assertThrows(IgniteException.class, () -> eval("CAST(NULL::INT AS 
INTERVAL SECONDS)"));
+        assertThrows(IgniteException.class, () -> eval("CAST(NULL::INT AS 
INTERVAL MONTHS)"));
+        assertThrows(IgniteException.class, () -> eval("CAST(1 AS INTERVAL 
SECONDS)"));
+        assertThrows(IgniteException.class, () -> eval("CAST(2 AS INTERVAL 
MINUTES)"));
+        assertThrows(IgniteException.class, () -> eval("CAST(3 AS INTERVAL 
HOURS)"));
+        assertThrows(IgniteException.class, () -> eval("CAST(4 AS INTERVAL 
DAYS)"));
+        assertThrows(IgniteException.class, () -> eval("CAST(5 AS INTERVAL 
MONTHS)"));
+        assertThrows(IgniteException.class, () -> eval("CAST(6 AS INTERVAL 
YEARS)"));
 
         // Compound interval types cannot be cast.
         assertThrowsEx("SELECT CAST(INTERVAL '1-2' YEAR TO MONTH AS INT)", 
IgniteException.class, "cannot convert");
diff --git 
a/modules/runner/src/integrationTest/java/org/apache/ignite/internal/sql/engine/ItSqlOperatorsTest.java
 
b/modules/runner/src/integrationTest/java/org/apache/ignite/internal/sql/engine/ItSqlOperatorsTest.java
index 7d169630fe..568d53b316 100644
--- 
a/modules/runner/src/integrationTest/java/org/apache/ignite/internal/sql/engine/ItSqlOperatorsTest.java
+++ 
b/modules/runner/src/integrationTest/java/org/apache/ignite/internal/sql/engine/ItSqlOperatorsTest.java
@@ -276,7 +276,6 @@ public class ItSqlOperatorsTest extends 
ClusterPerClassIntegrationTest {
         assertExpression("GREATEST('a', 'b')").returns("b").check();
         assertExpression("COMPRESS('')::VARCHAR").returns("").check();
         assertExpression("OCTET_LENGTH(x'01')").returns(1).check();
-        assertExpression("CAST(INTERVAL 1 SECONDS AS 
INT)").returns(1).check(); // Converted to REINTERPRED.
     }
 
     @Test
diff --git 
a/modules/runner/src/integrationTest/java/org/apache/ignite/internal/sql/engine/datatypes/uuid/ItUuidQueryTest.java
 
b/modules/runner/src/integrationTest/java/org/apache/ignite/internal/sql/engine/datatypes/uuid/ItUuidQueryTest.java
index 6ed097f2f2..2345e36fa5 100644
--- 
a/modules/runner/src/integrationTest/java/org/apache/ignite/internal/sql/engine/datatypes/uuid/ItUuidQueryTest.java
+++ 
b/modules/runner/src/integrationTest/java/org/apache/ignite/internal/sql/engine/datatypes/uuid/ItUuidQueryTest.java
@@ -45,7 +45,7 @@ public class ItUuidQueryTest extends 
BaseQueryDataTypeTest<UUID> {
         String query = format("SELECT * FROM t WHERE test_key {} 1", opSql);
 
         IgniteException t = assertThrows(IgniteException.class, () -> 
checkQuery(query).check());
-        String error = format("Cannot apply '{}'", opSql);
+        String error = format("Values passed to {} operator must have 
compatible types", opSql);
         assertThat(t.getMessage(), containsString(error));
     }
 
diff --git 
a/modules/runner/src/integrationTest/java/org/apache/ignite/internal/sql/engine/datatypes/varbinary/ItVarBinaryQueryTest.java
 
b/modules/runner/src/integrationTest/java/org/apache/ignite/internal/sql/engine/datatypes/varbinary/ItVarBinaryQueryTest.java
index 34e012b6a1..20c155dc88 100644
--- 
a/modules/runner/src/integrationTest/java/org/apache/ignite/internal/sql/engine/datatypes/varbinary/ItVarBinaryQueryTest.java
+++ 
b/modules/runner/src/integrationTest/java/org/apache/ignite/internal/sql/engine/datatypes/varbinary/ItVarBinaryQueryTest.java
@@ -46,7 +46,7 @@ public class ItVarBinaryQueryTest extends 
BaseQueryDataTypeTest<VarBinary> {
         String query = format("SELECT * FROM t WHERE test_key {} 1", opSql);
 
         IgniteException t = assertThrows(IgniteException.class, () -> 
checkQuery(query).check());
-        String error = format("Cannot apply '{}'", opSql);
+        String error = format("Values passed to {} operator must have 
compatible types", opSql);
         assertThat(t.getMessage(), containsString(error));
     }
 
diff --git a/modules/runner/src/integrationTest/sql/cast/test_boolean_cast.test 
b/modules/runner/src/integrationTest/sql/cast/test_boolean_cast.test
index a93a6f0abf..05f24475c4 100644
--- a/modules/runner/src/integrationTest/sql/cast/test_boolean_cast.test
+++ b/modules/runner/src/integrationTest/sql/cast/test_boolean_cast.test
@@ -39,92 +39,57 @@ false
 statement error
 SELECT CAST('12345' AS BOOLEAN)
 
-query T
+statement error
 SELECT CAST(CAST('12345' AS INTEGER) AS BOOLEAN)
-----
-true
 
-query T
+statement error
 SELECT CAST(CAST('0' AS INTEGER) AS BOOLEAN)
-----
-false
 
-query T
+statement error
 SELECT CAST(CAST('1' AS tinyint) AS BOOLEAN)
-----
-true
 
-query T
+statement error
 SELECT CAST(CAST('0' AS tinyint) AS BOOLEAN)
-----
-false
 
-query T
+statement error
 SELECT CAST(CAST('1' AS smallint) AS BOOLEAN)
-----
-true
 
-query T
+statement error
 SELECT CAST(CAST('0' AS smallint) AS BOOLEAN)
-----
-false
 
-query T
+statement error
 SELECT CAST(CAST('1' AS integer) AS BOOLEAN)
-----
-true
 
-query T
+statement error
 SELECT CAST(CAST('0' AS integer) AS BOOLEAN)
-----
-false
 
-query T
+statement error
 SELECT CAST(CAST('1' AS bigint) AS BOOLEAN)
-----
-true
 
-query T
+statement error
 SELECT CAST(CAST('0' AS bigint) AS BOOLEAN)
-----
-false
 
-query T
+statement error
 SELECT CAST(CAST('1' AS decimal) AS BOOLEAN)
-----
-true
 
-query T
+statement error
 SELECT CAST(CAST('0' AS decimal) AS BOOLEAN)
-----
-false
 
-query T
+statement error
 SELECT CAST(CAST('1' AS decimal(1,0)) AS BOOLEAN)
-----
-true
 
-query T
+statement error
 SELECT CAST(CAST('0' AS decimal(1,0)) AS BOOLEAN)
-----
-false
 
-query T
+statement error
 SELECT CAST(CAST('1' AS decimal(9,0)) AS BOOLEAN)
-----
-true
 
-query T
+statement error
 SELECT CAST(CAST('0' AS decimal(9,0)) AS BOOLEAN)
-----
-false
 
-query T
+statement error
 SELECT CAST(CAST('1' AS decimal(38,0)) AS BOOLEAN)
-----
-true
 
-query T
+statement error
 SELECT CAST(CAST('0' AS decimal(38,0)) AS BOOLEAN)
-----
-false
+
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 0708443984..2c132b256b 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,6 +17,7 @@
 
 package org.apache.ignite.internal.sql.engine.prepare;
 
+import static org.apache.calcite.sql.type.SqlTypeUtil.isNull;
 import static org.apache.calcite.util.Static.RESOURCE;
 import static org.apache.ignite.lang.IgniteStringFormatter.format;
 
@@ -34,6 +35,7 @@ import org.apache.calcite.rel.type.RelDataType;
 import org.apache.calcite.rel.type.RelDataTypeField;
 import org.apache.calcite.sql.JoinConditionType;
 import org.apache.calcite.sql.SqlAggFunction;
+import org.apache.calcite.sql.SqlBasicCall;
 import org.apache.calcite.sql.SqlCall;
 import org.apache.calcite.sql.SqlCallBinding;
 import org.apache.calcite.sql.SqlDelete;
@@ -47,6 +49,7 @@ import org.apache.calcite.sql.SqlLiteral;
 import org.apache.calcite.sql.SqlMerge;
 import org.apache.calcite.sql.SqlNode;
 import org.apache.calcite.sql.SqlNodeList;
+import org.apache.calcite.sql.SqlOperator;
 import org.apache.calcite.sql.SqlOperatorTable;
 import org.apache.calcite.sql.SqlSelect;
 import org.apache.calcite.sql.SqlUpdate;
@@ -65,6 +68,7 @@ import org.apache.calcite.sql.validate.SqlValidatorUtil;
 import org.apache.ignite.internal.sql.engine.schema.IgniteTable;
 import org.apache.ignite.internal.sql.engine.schema.TableDescriptor;
 import org.apache.ignite.internal.sql.engine.type.IgniteCustomType;
+import 
org.apache.ignite.internal.sql.engine.type.IgniteCustomTypeCoercionRules;
 import org.apache.ignite.internal.sql.engine.type.IgniteTypeFactory;
 import org.apache.ignite.internal.sql.engine.type.UuidType;
 import org.apache.ignite.internal.sql.engine.util.Commons;
@@ -354,13 +358,15 @@ public class IgniteSqlValidator extends SqlValidatorImpl {
     /** {@inheritDoc} */
     @Override
     public RelDataType deriveType(SqlValidatorScope scope, SqlNode expr) {
+        checkTypesInteroperability(scope, expr);
+
         RelDataType dataType = super.deriveType(scope, expr);
 
-        // Dynamic params
-        if (dataType.equals(unknownType) && expr instanceof SqlDynamicParam) {
-            // If type of dynamic parameter has not been inferred, use a type 
of its value.
-            RelDataType paramType = getDynamicParamType((SqlDynamicParam) 
expr);
+        // If type of dynamic parameter has not been inferred, use a type of 
its value.
+        RelDataType paramType = expr instanceof SqlDynamicParam
+                ? getDynamicParamType((SqlDynamicParam) expr) : null;
 
+        if (dataType.equals(unknownType) && expr instanceof SqlDynamicParam) {
             // If paramType is unknown setValidatedNodeType is a no-op.
             setValidatedNodeType(expr, paramType);
             return paramType;
@@ -397,6 +403,63 @@ public class IgniteSqlValidator extends SqlValidatorImpl {
         return dataType;
     }
 
+    /** Check appropriate type cast availability. */
+    private void checkTypesInteroperability(SqlValidatorScope scope, SqlNode 
expr) {
+        boolean castOp = expr.getKind() == SqlKind.CAST;
+
+        if (castOp || SqlKind.BINARY_COMPARISON.contains(expr.getKind())) {
+            SqlBasicCall expr0 = (SqlBasicCall) expr;
+            SqlNode first = expr0.getOperandList().get(0);
+            SqlNode ret = expr0.getOperandList().get(1);
+
+            RelDataType firstType;
+            RelDataType returnType = super.deriveType(scope, ret);
+
+            if (first instanceof SqlDynamicParam) {
+                firstType = getDynamicParamType((SqlDynamicParam) first);
+            } else {
+                firstType = super.deriveType(scope, first);
+            }
+
+            boolean nullType = isNull(returnType) || isNull(firstType);
+
+            // propagate null type validation
+            if (nullType) {
+                return;
+            }
+
+            RelDataType returnCustomType = returnType instanceof 
IgniteCustomType ? returnType : null;
+            RelDataType fromCustomType = firstType instanceof IgniteCustomType 
? firstType : null;
+
+            IgniteCustomTypeCoercionRules coercionRules = 
typeFactory().getCustomTypeCoercionRules();
+            boolean check;
+
+            if (fromCustomType != null && returnCustomType != null) {
+                // it`s not allowed to convert between different custom types 
for now.
+                check = SqlTypeUtil.equalSansNullability(typeFactory, 
firstType, returnType);
+            } else if (fromCustomType != null) {
+                check = coercionRules.needToCast(returnType, 
(IgniteCustomType) fromCustomType);
+            } else if (returnCustomType != null) {
+                check = coercionRules.needToCast(firstType, (IgniteCustomType) 
returnCustomType);
+            } else {
+                check = SqlTypeUtil.canCastFrom(returnType, firstType, true);
+            }
+
+            if (!check) {
+                if (castOp) {
+                    throw newValidationError(expr,
+                            RESOURCE.cannotCastValue(firstType.toString(), 
returnType.toString()));
+                } else {
+                    SqlBasicCall call = (SqlBasicCall) expr;
+                    SqlOperator operator = call.getOperator();
+
+                    var ex = 
RESOURCE.incompatibleValueType(operator.getName());
+                    throw 
SqlUtil.newContextException(expr.getParserPosition(), ex);
+                }
+            }
+        }
+    }
+
     /** {@inheritDoc} */
     @Override
     protected SqlNode performUnconditionalRewrites(SqlNode node, boolean 
underFrom) {
diff --git 
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/util/Commons.java
 
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/util/Commons.java
index f7fff2521b..4ed89cd976 100644
--- 
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/util/Commons.java
+++ 
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/util/Commons.java
@@ -64,6 +64,7 @@ import org.apache.calcite.rel.hint.HintStrategyTable;
 import org.apache.calcite.rex.RexBuilder;
 import org.apache.calcite.sql.SqlKind;
 import org.apache.calcite.sql.SqlNode;
+import org.apache.calcite.sql.type.SqlTypeCoercionRule;
 import org.apache.calcite.sql.validate.SqlValidator;
 import org.apache.calcite.sql2rel.SqlToRelConverter;
 import org.apache.calcite.tools.FrameworkConfig;
@@ -155,6 +156,7 @@ public final class Commons {
                     .withIdentifierExpansion(true)
                     .withDefaultNullCollation(NullCollation.HIGH)
                     .withSqlConformance(IgniteSqlConformance.INSTANCE)
+                    .withTypeCoercionRules(standardCompatibleCoercionRules())
                     .withTypeCoercionFactory(IgniteTypeCoercion::new))
             // Dialects support.
             .operatorTable(IgniteSqlOperatorTable.INSTANCE)
@@ -169,6 +171,10 @@ public final class Commons {
     private Commons() {
     }
 
+    private static SqlTypeCoercionRule standardCompatibleCoercionRules() {
+        return 
SqlTypeCoercionRule.instance(IgniteCustomAssigmentsRules.instance().getTypeMapping());
+    }
+
     /**
      * Gets appropriate field from two rows by offset.
      *
diff --git 
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/util/IgniteCustomAssigmentsRules.java
 
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/util/IgniteCustomAssigmentsRules.java
new file mode 100644
index 0000000000..5ba42535c5
--- /dev/null
+++ 
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/util/IgniteCustomAssigmentsRules.java
@@ -0,0 +1,255 @@
+/*
+ * 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.APPROX_TYPES;
+import static org.apache.calcite.sql.type.SqlTypeName.BINARY_TYPES;
+import static org.apache.calcite.sql.type.SqlTypeName.CHAR_TYPES;
+import static org.apache.calcite.sql.type.SqlTypeName.DAY_INTERVAL_TYPES;
+import static org.apache.calcite.sql.type.SqlTypeName.EXACT_TYPES;
+import static org.apache.calcite.sql.type.SqlTypeName.FRACTIONAL_TYPES;
+import static org.apache.calcite.sql.type.SqlTypeName.YEAR_INTERVAL_TYPES;
+
+import com.google.common.cache.CacheBuilder;
+import com.google.common.cache.CacheLoader;
+import com.google.common.cache.LoadingCache;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Sets;
+import com.google.common.util.concurrent.UncheckedExecutionException;
+import java.util.EnumSet;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ExecutionException;
+import org.apache.calcite.sql.type.SqlTypeAssignmentRule;
+import org.apache.calcite.sql.type.SqlTypeCoercionRule;
+import org.apache.calcite.sql.type.SqlTypeMappingRule;
+import org.apache.calcite.sql.type.SqlTypeMappingRules;
+import org.apache.calcite.sql.type.SqlTypeName;
+import org.apache.calcite.util.Util;
+
+/**
+ * Rules that determine whether a type is assignable from another type.
+ * These rules specify the conversion matrix with explicit CAST.
+ * Calcite native rules {@link SqlTypeCoercionRule} and {@link 
SqlTypeAssignmentRule} are not satisfy SQL standard rules,
+ * thus custom implementation is implemented.
+ */
+public class IgniteCustomAssigmentsRules implements SqlTypeMappingRule {
+    private final Map<SqlTypeName, ImmutableSet<SqlTypeName>> map;
+
+    private static final IgniteCustomAssigmentsRules INSTANCE;
+
+    private IgniteCustomAssigmentsRules(
+            Map<SqlTypeName, ImmutableSet<SqlTypeName>> map) {
+        this.map = ImmutableMap.copyOf(map);
+    }
+
+    static {
+        IgniteCustomAssigmentsRules.Builder rules = builder();
+
+        Set<SqlTypeName> rule = EnumSet.noneOf(SqlTypeName.class);
+
+        // IntervalYearMonth is assignable from...
+        for (SqlTypeName interval : YEAR_INTERVAL_TYPES) {
+            rules.add(interval, YEAR_INTERVAL_TYPES);
+        }
+        for (SqlTypeName interval : DAY_INTERVAL_TYPES) {
+            rules.add(interval, DAY_INTERVAL_TYPES);
+        }
+
+        // MULTISET is assignable from...
+        rules.add(SqlTypeName.MULTISET, EnumSet.of(SqlTypeName.MULTISET));
+
+        rule.clear();
+        rule.addAll(EXACT_TYPES);
+        rule.addAll(APPROX_TYPES);
+        rule.addAll(CHAR_TYPES);
+
+        // TINYINT is assignable from...
+        // SMALLINT is assignable from...
+        // INTEGER is assignable from...
+        // BIGINT is assignable from...
+        for (SqlTypeName type : EXACT_TYPES) {
+            rules.add(type, rule);
+        }
+
+        // FLOAT (up to 64 bit floating point) is assignable from...
+        // REAL (32 bit floating point) is assignable from...
+        // DOUBLE is assignable from...
+        // DECIMAL is assignable from...
+        for (SqlTypeName type : FRACTIONAL_TYPES) {
+            rules.add(type, rule);
+        }
+
+        // VARBINARY is assignable from...
+        rule.clear();
+        rule.addAll(BINARY_TYPES);
+        rule.addAll(CHAR_TYPES);
+        rules.add(SqlTypeName.VARBINARY, rule);
+
+        // CHAR is assignable from...
+        rule.clear();
+        rule.addAll(CHAR_TYPES);
+        rule.addAll(BINARY_TYPES);
+        rule.addAll(EXACT_TYPES);
+        rule.addAll(APPROX_TYPES);
+        rule.addAll(DAY_INTERVAL_TYPES);
+        rule.addAll(YEAR_INTERVAL_TYPES);
+        rule.add(SqlTypeName.BOOLEAN);
+        rule.add(SqlTypeName.DATE);
+        rule.add(SqlTypeName.TIME);
+        rule.add(SqlTypeName.TIMESTAMP);
+        rules.add(SqlTypeName.CHAR, rule);
+
+        // VARCHAR is assignable from...
+        rule.clear();
+        rule.addAll(CHAR_TYPES);
+        rule.addAll(BINARY_TYPES);
+        rule.addAll(EXACT_TYPES);
+        rule.addAll(APPROX_TYPES);
+        rule.addAll(DAY_INTERVAL_TYPES);
+        rule.addAll(YEAR_INTERVAL_TYPES);
+        rule.add(SqlTypeName.BOOLEAN);
+        rule.add(SqlTypeName.DATE);
+        rule.add(SqlTypeName.TIME);
+        rule.add(SqlTypeName.TIMESTAMP);
+        rules.add(SqlTypeName.VARCHAR, rule);
+
+        // BOOLEAN is assignable from...
+        rules.add(SqlTypeName.BOOLEAN, EnumSet.of(SqlTypeName.BOOLEAN, 
SqlTypeName.CHAR, SqlTypeName.VARCHAR));
+
+        // BINARY is assignable from...
+        rule.clear();
+        rule.addAll(BINARY_TYPES);
+        rule.addAll(CHAR_TYPES);
+        rules.add(SqlTypeName.BINARY, rule);
+
+        // DATE is assignable from...
+        rule.clear();
+        rule.add(SqlTypeName.DATE);
+        rule.addAll(CHAR_TYPES);
+        rule.add(SqlTypeName.TIMESTAMP);
+        rules.add(SqlTypeName.DATE, rule);
+
+        // TIME is assignable from...
+        rule.clear();
+        rule.add(SqlTypeName.TIME);
+        rule.addAll(CHAR_TYPES);
+        rule.add(SqlTypeName.TIMESTAMP);
+        rules.add(SqlTypeName.TIME, rule);
+
+        // TIME WITH LOCAL TIME ZONE is assignable from...
+        rules.add(SqlTypeName.TIME_WITH_LOCAL_TIME_ZONE,
+                EnumSet.of(SqlTypeName.TIME_WITH_LOCAL_TIME_ZONE));
+
+        // TIMESTAMP is assignable from ...
+        rule.clear();
+        rule.add(SqlTypeName.TIMESTAMP);
+        rule.addAll(CHAR_TYPES);
+        rule.add(SqlTypeName.TIME);
+        rule.add(SqlTypeName.DATE);
+        rules.add(SqlTypeName.TIMESTAMP, rule);
+
+        // TIMESTAMP WITH LOCAL TIME ZONE is assignable from...
+        rules.add(SqlTypeName.TIMESTAMP_WITH_LOCAL_TIME_ZONE,
+                EnumSet.of(SqlTypeName.TIMESTAMP_WITH_LOCAL_TIME_ZONE));
+
+        // GEOMETRY is assignable from ...
+        rule.clear();
+        rule.add(SqlTypeName.GEOMETRY);
+        rule.addAll(CHAR_TYPES);
+        rules.add(SqlTypeName.GEOMETRY, rule);
+
+        rule.clear();
+        rule.addAll(CHAR_TYPES);
+        rule.addAll(YEAR_INTERVAL_TYPES);
+
+        for (SqlTypeName type : YEAR_INTERVAL_TYPES) {
+            rules.add(type, rule);
+        }
+
+        rule.clear();
+        rule.addAll(CHAR_TYPES);
+        rule.addAll(DAY_INTERVAL_TYPES);
+
+        for (SqlTypeName type : DAY_INTERVAL_TYPES) {
+            rules.add(type, rule);
+        }
+
+        // ARRAY is assignable from ...
+        rules.add(SqlTypeName.ARRAY, EnumSet.of(SqlTypeName.ARRAY));
+
+        // MAP is assignable from ...
+        rules.add(SqlTypeName.MAP, EnumSet.of(SqlTypeName.MAP));
+
+        // SYMBOL is assignable from ...
+        rules.add(SqlTypeName.SYMBOL, EnumSet.of(SqlTypeName.SYMBOL));
+
+        // ANY is assignable from ...
+        rule.clear();
+        rule.add(SqlTypeName.TINYINT);
+        rule.add(SqlTypeName.SMALLINT);
+        rule.add(SqlTypeName.INTEGER);
+        rule.add(SqlTypeName.BIGINT);
+        rule.add(SqlTypeName.DECIMAL);
+        rule.add(SqlTypeName.FLOAT);
+        rule.add(SqlTypeName.REAL);
+        rule.add(SqlTypeName.TIME);
+        rule.add(SqlTypeName.DATE);
+        rule.add(SqlTypeName.TIMESTAMP);
+        rules.add(SqlTypeName.ANY, rule);
+
+        INSTANCE = new IgniteCustomAssigmentsRules(rules.map);
+    }
+
+    @Override public Map<SqlTypeName, ImmutableSet<SqlTypeName>> 
getTypeMapping() {
+        return this.map;
+    }
+
+    public static IgniteCustomAssigmentsRules instance() {
+        return INSTANCE;
+    }
+
+    public static IgniteCustomAssigmentsRules.Builder builder() {
+        return new IgniteCustomAssigmentsRules.Builder();
+    }
+
+    /** Keeps state while building the type mappings. */
+    public static class Builder {
+        final Map<SqlTypeName, ImmutableSet<SqlTypeName>> map;
+        final LoadingCache<Set<SqlTypeName>, ImmutableSet<SqlTypeName>> sets;
+
+        /** Creates an empty {@link SqlTypeMappingRules.Builder}. */
+        Builder() {
+            this.map = new HashMap<>();
+            this.sets =
+                    CacheBuilder.newBuilder()
+                            .build(CacheLoader.from(set -> 
Sets.immutableEnumSet(set)));
+        }
+
+        /** Add a map entry to the existing {@link 
SqlTypeMappingRules.Builder} mapping. */
+        void add(SqlTypeName fromType, Set<SqlTypeName> toTypes) {
+            try {
+                map.put(fromType, sets.get(toTypes));
+            } catch (UncheckedExecutionException | ExecutionException e) {
+                throw Util.throwAsRuntime("populating SqlTypeAssignmentRules", 
Util.causeOrSelf(e));
+            }
+        }
+    }
+}
diff --git 
a/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/planner/AbstractPlannerTest.java
 
b/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/planner/AbstractPlannerTest.java
index a8c6aee0b9..7df6627236 100644
--- 
a/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/planner/AbstractPlannerTest.java
+++ 
b/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/planner/AbstractPlannerTest.java
@@ -26,7 +26,6 @@ import static 
org.apache.ignite.internal.util.CollectionUtils.nullOrEmpty;
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertFalse;
 import static org.junit.jupiter.api.Assertions.assertNotNull;
-import static org.junit.jupiter.api.Assertions.assertTrue;
 import static org.junit.jupiter.api.Assertions.fail;
 
 import com.google.common.collect.ImmutableList;
@@ -116,6 +115,7 @@ import 
org.apache.ignite.internal.sql.engine.util.StatementChecker;
 import org.apache.ignite.internal.testframework.IgniteAbstractTest;
 import org.apache.ignite.internal.testframework.IgniteTestUtils;
 import org.apache.ignite.internal.utils.PrimaryReplica;
+import org.apache.ignite.lang.IgniteStringBuilder;
 import org.apache.ignite.network.ClusterNode;
 import org.jetbrains.annotations.Nullable;
 
@@ -676,11 +676,11 @@ public abstract class AbstractPlannerTest extends 
IgniteAbstractTest {
             clearHints(expected);
 
             if (!expected.deepEquals(deserialized)) {
-                assertTrue(
-                        expected.deepEquals(deserialized),
-                        "Invalid serialization / deserialization.\n"
-                                + "Expected:\n" + RelOptUtil.toString(expected)
-                                + "Deserialized:\n" + 
RelOptUtil.toString(deserialized)
+                IgniteStringBuilder sb = new IgniteStringBuilder();
+                fail(
+                        sb.app("Invalid serialization / deserialization.").nl()
+                                .app("Expected:").nl().app(expected).nl()
+                                
.app("Deserialized:").nl().app(deserialized).toString()
                 );
             }
         }
diff --git 
a/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/planner/CastResolutionTest.java
 
b/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/planner/CastResolutionTest.java
new file mode 100644
index 0000000000..f9ad59e10c
--- /dev/null
+++ 
b/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/planner/CastResolutionTest.java
@@ -0,0 +1,217 @@
+/*
+ * 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.planner;
+
+import static org.apache.calcite.sql.type.SqlTypeName.BINARY_TYPES;
+import static org.apache.calcite.sql.type.SqlTypeName.CHAR_TYPES;
+import static org.apache.calcite.sql.type.SqlTypeName.DATETIME_TYPES;
+import static org.apache.calcite.sql.type.SqlTypeName.INTERVAL_HOUR;
+import static org.apache.calcite.sql.type.SqlTypeName.INTERVAL_MINUTE;
+import static org.apache.calcite.sql.type.SqlTypeName.INTERVAL_MONTH;
+import static org.apache.calcite.sql.type.SqlTypeName.INTERVAL_YEAR;
+import static org.apache.calcite.sql.type.SqlTypeName.NUMERIC_TYPES;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+import org.apache.calcite.sql.type.SqlTypeName;
+import org.apache.ignite.internal.sql.engine.type.UuidType;
+import org.junit.jupiter.api.DynamicTest;
+import org.junit.jupiter.api.TestFactory;
+
+/** Test CAST type to type possibilities. */
+public class CastResolutionTest extends AbstractPlannerTest {
+    private static final String castErrorMessage = "Cast function cannot 
convert value of type";
+
+    private static final Set<String> numericNames = 
NUMERIC_TYPES.stream().map(SqlTypeName::getName).collect(Collectors.toSet());
+
+    private static final Set<String> charNames = 
CHAR_TYPES.stream().map(SqlTypeName::getName).collect(Collectors.toSet());
+
+    private static final Set<String> binaryNames = 
BINARY_TYPES.stream().map(SqlTypeName::getName).collect(Collectors.toSet());
+
+    private static final Set<String> dtNames = 
DATETIME_TYPES.stream().map(SqlTypeName::getName).collect(Collectors.toSet());
+
+    private static final Set<String> charAndNumericNames = new HashSet<>();
+
+    private static final Set<String> charAndBinaryNames = new HashSet<>();
+
+    private static final Set<String> charAndTs = new HashSet<>();
+
+    private static final Set<String> charAndDt = new HashSet<>();
+
+    private static final Set<String> charAndYInterval = new HashSet<>();
+
+    private static final Set<String> charAndDInterval = new HashSet<>();
+
+    private static final String commonTemplate = "SELECT CAST('1'::%s AS %s)";
+
+    private static final String intervalTemplate = "SELECT CAST(INTERVAL 1 %s 
AS %s)";
+
+    static {
+        numericNames.add("NUMERIC");
+
+        charAndNumericNames.addAll(numericNames);
+        charAndNumericNames.addAll(charNames);
+
+        charAndBinaryNames.addAll(charNames);
+        charAndBinaryNames.addAll(binaryNames);
+
+        charAndTs.addAll(charNames);
+        charAndTs.add(SqlTypeName.TIMESTAMP.getName());
+
+        charAndDt.addAll(dtNames);
+        charAndDt.addAll(charNames);
+
+        charAndYInterval.addAll(List.of(INTERVAL_YEAR.getName(), 
INTERVAL_MONTH.getName()));
+        charAndYInterval.addAll(charNames);
+
+        charAndDInterval.addAll(List.of(INTERVAL_HOUR.getName(), 
INTERVAL_MINUTE.getName()));
+        charAndDInterval.addAll(charNames);
+    }
+
+    /** Test CAST possibility for different supported types. */
+    @TestFactory
+    public Stream<DynamicTest> allowedCasts() {
+        List<DynamicTest> testItems = new ArrayList<>();
+
+        Set<String> allTypes = Arrays.stream(CastMatrix.values()).map(v -> 
v.from).collect(Collectors.toSet());
+
+        for (CastMatrix types : CastMatrix.values()) {
+            String from = types.from;
+            Set<String> toTypes = types.toTypes;
+            boolean allCastsPossible = false;
+
+            boolean interval = isInterval(from);
+            String template = interval ? intervalTemplate : commonTemplate;
+            from = interval ? from.substring("interval_".length()) : from;
+
+            for (String toType : toTypes) {
+                toType = isInterval(toType) ? makeUsableIntervalType(toType) : 
toType;
+
+                if (toType.equals("ALL")) {
+                    allCastsPossible = true;
+
+                    for (String type : allTypes) {
+                        type = isInterval(type) ? makeUsableIntervalType(type) 
: type;
+
+                        
testItems.add(checkStatement().sql(String.format(template, from, type)).ok());
+                    }
+
+                    break;
+                }
+
+                // TODO: https://issues.apache.org/jira/browse/IGNITE-19274
+                if (toType.contains("LOCAL_TIME")) {
+                    continue;
+                }
+
+                testItems.add(checkStatement().sql(String.format(template, 
from, toType)).ok(false));
+            }
+
+            if (!interval) {
+                testItems.add(checkStatement().sql(String.format("SELECT 
'1'::%s", from)).ok(false));
+            }
+
+            // all types are allowed.
+            if (allCastsPossible) {
+                continue;
+            }
+
+            if (!interval) {
+                testItems.add(checkStatement().sql(String.format(template, 
from, from)).ok());
+            }
+
+            String finalFrom = from;
+            Set<String> deprecatedCastTypes = allTypes.stream().filter(t -> 
!toTypes.contains(t) && !t.equals(finalFrom))
+                    .collect(Collectors.toSet());
+
+            for (String toType : deprecatedCastTypes) {
+                toType = isInterval(toType) ? makeUsableIntervalType(toType) : 
toType;
+
+                testItems.add(checkStatement().sql(String.format(template, 
from, toType)).fails(castErrorMessage));
+            }
+        }
+
+        return testItems.stream();
+    }
+
+    private static boolean isInterval(String typeName) {
+        return typeName.toLowerCase().contains("interval");
+    }
+
+    private static String makeUsableIntervalType(String typeName) {
+        return typeName.replace("_", " ");
+    }
+
+    private enum CastMatrix {
+        BOOLEAN(SqlTypeName.BOOLEAN.getName(), charNames),
+
+        INT8(SqlTypeName.TINYINT.getName(), charAndNumericNames),
+
+        INT16(SqlTypeName.SMALLINT.getName(), charAndNumericNames),
+
+        INT32(SqlTypeName.INTEGER.getName(), charAndNumericNames),
+
+        INT64(SqlTypeName.BIGINT.getName(), charAndNumericNames),
+
+        DECIMAL(SqlTypeName.DECIMAL.getName(), charAndNumericNames),
+
+        REAL(SqlTypeName.REAL.getName(), charAndNumericNames),
+
+        DOUBLE(SqlTypeName.DOUBLE.getName(), charAndNumericNames),
+
+        FLOAT(SqlTypeName.FLOAT.getName(), charAndNumericNames),
+
+        NUMERIC("NUMERIC", charAndNumericNames),
+
+        UUID(UuidType.NAME, new HashSet<>(charNames)),
+
+        VARCHAR(SqlTypeName.VARCHAR.getName(), Set.of("ALL")),
+
+        CHAR(SqlTypeName.CHAR.getName(), Set.of("ALL")),
+
+        VARBINARY(SqlTypeName.VARBINARY.getName(), charAndBinaryNames),
+
+        BINARY(SqlTypeName.BINARY.getName(), charAndBinaryNames),
+
+        DATE(SqlTypeName.DATE.getName(), charAndTs),
+
+        TIME(SqlTypeName.TIME.getName(), charAndTs),
+
+        TIMESTAMP(SqlTypeName.TIMESTAMP.getName(), charAndDt),
+
+        INTERVAL_YEAR(SqlTypeName.INTERVAL_YEAR.getName(), charAndYInterval),
+
+        INTERVAL_HOUR(SqlTypeName.INTERVAL_HOUR.getName(), charAndDInterval);
+
+        // TODO: https://issues.apache.org/jira/browse/IGNITE-19274
+        //TIMESTAMP_TS(SqlTypeName.TIMESTAMP_WITH_LOCAL_TIME_ZONE.getName(), 
charAndDt);
+
+        private String from;
+        private Set<String> toTypes;
+
+        CastMatrix(String from, Set<String> toTypes) {
+            this.from = from;
+            this.toTypes = toTypes;
+        }
+    }
+}
diff --git 
a/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/prepare/TypeCoercionTest.java
 
b/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/prepare/TypeCoercionTest.java
index 1d69733523..161dee6bd0 100644
--- 
a/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/prepare/TypeCoercionTest.java
+++ 
b/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/prepare/TypeCoercionTest.java
@@ -71,7 +71,6 @@ import 
org.apache.ignite.internal.sql.engine.type.IgniteCustomTypeCoercionRules;
 import org.apache.ignite.internal.sql.engine.type.UuidType;
 import org.apache.ignite.internal.tostring.S;
 import org.jetbrains.annotations.Nullable;
-import org.junit.jupiter.api.Assumptions;
 import org.junit.jupiter.api.Disabled;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.params.ParameterizedTest;
@@ -108,8 +107,8 @@ public class TypeCoercionTest extends AbstractPlannerTest {
         RelDataType booleanType = 
TYPE_FACTORY.createSqlType(SqlTypeName.BOOLEAN);
 
         for (RelDataType type : NUMERIC_TYPES) {
-            numericRules.add(typeCoercionRule(type, booleanType, new 
ToSpecificType(type)));
-            numericRules.add(typeCoercionRule(booleanType, type, new 
ToSpecificType(type)));
+            numericRules.add(typeCoercionIsNotSupported(type, booleanType));
+            numericRules.add(typeCoercionIsNotSupported(booleanType, type));
         }
 
         return numericRules.stream();
@@ -189,26 +188,6 @@ public class TypeCoercionTest extends AbstractPlannerTest {
     @ParameterizedTest
     @MethodSource("numericToDate")
     public void testNumericToDate(TypeCoercionRule rule) {
-        RelDataType lhs = rule.lhs;
-        RelDataType rhs = rule.rhs;
-        List<SqlTypeName> types = Arrays.asList(lhs.getSqlTypeName(), 
rhs.getSqlTypeName());
-
-        boolean tinyIntDate = types.contains(SqlTypeName.TINYINT);
-        boolean smallIntDate = types.contains(SqlTypeName.SMALLINT);
-        boolean intDate = types.contains(SqlTypeName.INTEGER);
-        boolean bigIntDate = types.contains(SqlTypeName.BIGINT);
-        boolean decimalDateLhsIsDecimal = types.contains(SqlTypeName.DECIMAL);
-
-        //TODO: https://issues.apache.org/jira/browse/IGNITE-18557
-        // Use assumptions otherwise we got an AssertionError:
-        /*
-        at 
org.apache.calcite.sql.validate.implicit.AbstractTypeCoercion.needToCast(AbstractTypeCoercion.java:274)
-        // Should keep sync with rules in SqlTypeCoercionRule.
-        assert SqlTypeUtil.canCastFrom(toType, fromType, true);
-         */
-        Assumptions.assumeFalse(tinyIntDate || smallIntDate || bigIntDate || 
intDate);
-        Assumptions.assumeFalse(decimalDateLhsIsDecimal);
-
         var tester = new BinaryOpTypeCoercionTester(rule);
         tester.execute();
     }
@@ -373,9 +352,8 @@ public class TypeCoercionTest extends AbstractPlannerTest {
             }
 
             runBinaryOpTypeCoercionTest(rule, (planner, node) -> {
-                String error = String.format("Cannot apply '%s' to arguments 
of type '<%s> %s <%s>",
-                        rule.operator.getName(), rule.lhs, 
rule.operator.getName(), rule.rhs
-                );
+                String error = String.format("Values passed to %s operator 
must have compatible types", rule.operator.getName());
+
                 CalciteContextException e = 
assertThrows(CalciteContextException.class, () -> planner.validate(node));
                 assertThat(e.getMessage(), containsString(error));
             });
diff --git 
a/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/util/StatementChecker.java
 
b/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/util/StatementChecker.java
index fb066e329f..15b15816a5 100644
--- 
a/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/util/StatementChecker.java
+++ 
b/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/util/StatementChecker.java
@@ -234,7 +234,13 @@ public class StatementChecker {
 
     /** Expect that validation succeeds. */
     public DynamicTest ok() {
-        return ok((node) -> {});
+        return ok((node) -> {}, true);
+    }
+
+    /** Expect that validation succeeds. */
+    // TODO: remote relCheck param after 
https://issues.apache.org/jira/browse/IGNITE-20170
+    public DynamicTest ok(boolean relCheck) {
+        return ok((node) -> {}, relCheck);
     }
 
     /**
@@ -245,7 +251,18 @@ public class StatementChecker {
         // Capture current stacktrace to show error location.
         AssertionError exception = new AssertionError("Statement check 
failed");
 
-        return shouldPass(name, exception, check);
+        return shouldPass(name, exception, check, true);
+    }
+
+    /**
+     * Expects that the provided validation function won't throw an exception.
+     */
+    public DynamicTest ok(Consumer<IgniteRel> check, boolean relCheck) {
+        String name = testName(true);
+        // Capture current stacktrace to show error location.
+        AssertionError exception = new AssertionError("Statement check 
failed");
+
+        return shouldPass(name, exception, check, relCheck);
     }
 
     /** Validation is expected to fail with an error that contains a the given 
message. */
@@ -371,14 +388,16 @@ public class StatementChecker {
         return schema;
     }
 
-    private DynamicTest shouldPass(String name, Throwable exception, 
Consumer<IgniteRel> check) {
+    private DynamicTest shouldPass(String name, Throwable exception, 
Consumer<IgniteRel> check, boolean relCheck) {
         return DynamicTest.dynamicTest(name, () -> {
             IgniteSchema schema = initSchema(exception);
             IgniteRel root;
 
             try {
                 root = (IgniteRel) sqlPrepare.prepare(schema, sqlStatement, 
dynamicParams);
-                checkRel(root, schema);
+                if (relCheck) {
+                    checkRel(root, schema);
+                }
             } catch (Throwable e) {
                 String message = format("Failed to validate:\n{}\n", 
formatSqlStatementForErrorMessage());
                 RuntimeException error = new RuntimeException(message, e);

Reply via email to