You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ignite.apache.org by zs...@apache.org on 2023/08/23 12:40:40 UTC

[ignite-3] branch main updated: IGNITE-20185 Sql. Fix missed casting rules (#2431)

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 19f158ceff IGNITE-20185 Sql. Fix missed casting rules (#2431)
19f158ceff is described below

commit 19f158ceff6ac9bf71113edc3464b43771c55404
Author: Evgeniy Stanilovskiy <st...@gmail.com>
AuthorDate: Wed Aug 23 15:40:34 2023 +0300

    IGNITE-20185 Sql. Fix missed casting rules (#2431)
---
 .../ignite/internal/sql/engine/ItIntervalTest.java |  36 +--
 .../internal/sql/engine/ItSqlOperatorsTest.java    |   6 +-
 .../sql/engine/datatypes/DataTypeTestSpecs.java    |  10 +-
 .../datatypes/tests/BaseDmlDataTypeTest.java       |  21 ++
 .../tests/BaseExpressionDataTypeTest.java          |  11 +
 .../datatypes/tests/BaseQueryDataTypeTest.java     |   6 +
 .../engine/datatypes/tests/TestTypeArguments.java  |   2 +
 .../varbinary/ItVarBinaryExpressionTest.java       |  12 -
 .../integrationTest/sql/types/blob/test_blob.test  |  34 +--
 .../sql/types/blob/test_blob_cast.test             |  20 +-
 .../sql/types/blob/test_blob_function.test         |  27 +-
 .../sql/types/blob/test_blob_operator.test         |  45 ++-
 .../sql/types/blob/test_blob_string.test           |   2 +-
 .../internal/sql/engine/exec/row/BaseTypeSpec.java |   2 +-
 .../engine/util/IgniteCustomAssigmentsRules.java   | 104 ++++---
 .../ignite/internal/sql/engine/util/TypeUtils.java |   2 -
 .../sql/engine/planner/CastResolutionTest.java     | 311 +++++++++++++++------
 17 files changed, 391 insertions(+), 260 deletions(-)

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 d01c3ce46f..c6b9ca5ac1 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,29 +70,29 @@ public class ItIntervalTest extends ClusterPerClassIntegrationTest {
      */
     @Test
     public void testIntervalIntCast() {
-        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)"));
+        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)"));
 
         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)"));
+        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)"));
 
         // 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 cba6d99d9a..e3e8018f57 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
@@ -144,7 +144,7 @@ public class ItSqlOperatorsTest extends ClusterPerClassIntegrationTest {
         assertExpression("LOWER('aA')").returns("aa").check();
         assertExpression("INITCAP('aA')").returns("Aa").check();
         assertExpression("TO_BASE64('aA')").returns("YUE=").check();
-        assertExpression("FROM_BASE64('YUE=')::VARCHAR").returns("aA").check();
+        assertExpression("FROM_BASE64('YUE=')").returns(new byte[] {(byte) 97, (byte) 65}).check();
         assertExpression("MD5('aa')").returns("4124bc0a9335c27f086f24ba207a4912").check();
         assertExpression("SHA1('aa')").returns("e0c9035898dd52fc65c41454cec9c4d2611bfb37").check();
         assertExpression("SUBSTRING('aAaA', 2, 2)").returns("Aa").check();
@@ -275,8 +275,10 @@ public class ItSqlOperatorsTest extends ClusterPerClassIntegrationTest {
         assertExpression("DECODE(1, 1, 1, 2)").returns(1).check();
         assertExpression("LEAST('a', 'b')").returns("a").check();
         assertExpression("GREATEST('a', 'b')").returns("b").check();
-        assertExpression("COMPRESS('')::VARCHAR").returns("").check();
+        assertExpression("COMPRESS('')").returns(new byte[]{}).check();
         assertExpression("OCTET_LENGTH(x'01')").returns(1).check();
+        assertExpression("CAST(INTERVAL 1 SECONDS AS INT)").returns(1).check(); // Converted to REINTERPRED.
+        assertExpression("CAST(INTERVAL 1 DAY AS INT)").returns(1).check(); // Converted to REINTERPRED.
     }
 
     @Test
diff --git a/modules/runner/src/integrationTest/java/org/apache/ignite/internal/sql/engine/datatypes/DataTypeTestSpecs.java b/modules/runner/src/integrationTest/java/org/apache/ignite/internal/sql/engine/datatypes/DataTypeTestSpecs.java
index 0b1811fa65..3e654d4a55 100644
--- a/modules/runner/src/integrationTest/java/org/apache/ignite/internal/sql/engine/datatypes/DataTypeTestSpecs.java
+++ b/modules/runner/src/integrationTest/java/org/apache/ignite/internal/sql/engine/datatypes/DataTypeTestSpecs.java
@@ -17,7 +17,6 @@
 
 package org.apache.ignite.internal.sql.engine.datatypes;
 
-import static org.apache.ignite.internal.sql.engine.util.VarBinary.fromUtf8String;
 import static org.apache.ignite.internal.sql.engine.util.VarBinary.varBinary;
 import static org.apache.ignite.lang.IgniteStringFormatter.format;
 
@@ -101,7 +100,7 @@ public final class DataTypeTestSpecs {
 
         @Override
         public String toValueExpr(VarBinary value) {
-            return format("'{}'::VARBINARY", value.asString(StandardCharsets.UTF_8));
+            return toLiteral(value);
         }
 
         /** {@inheritDoc} */
@@ -120,12 +119,13 @@ public final class DataTypeTestSpecs {
         @Override
         public TestDataSamples<VarBinary> createSamples(IgniteTypeFactory typeFactory) {
             List<VarBinary> values = List.of(
-                    fromUtf8String("1"), fromUtf8String("2"), fromUtf8String("3"));
+                    VarBinary.fromBytes(new byte[] {(byte) 1}),
+                    VarBinary.fromBytes(new byte[] {(byte) 2}),
+                    VarBinary.fromBytes(new byte[] {(byte) 3}));
 
             TestDataSamples.Builder<VarBinary> samples = TestDataSamples.builder();
 
-            samples.add(values, SqlTypeName.VARCHAR, b -> b.asString(StandardCharsets.UTF_8));
-            samples.add(values, SqlTypeName.CHAR, b -> b.asString(StandardCharsets.UTF_8));
+            samples.add(values, SqlTypeName.BINARY, b -> b.asString(StandardCharsets.UTF_8).getBytes(StandardCharsets.UTF_8));
 
             return samples.build();
         }
diff --git a/modules/runner/src/integrationTest/java/org/apache/ignite/internal/sql/engine/datatypes/tests/BaseDmlDataTypeTest.java b/modules/runner/src/integrationTest/java/org/apache/ignite/internal/sql/engine/datatypes/tests/BaseDmlDataTypeTest.java
index 79a61a5f57..f5f2246304 100644
--- a/modules/runner/src/integrationTest/java/org/apache/ignite/internal/sql/engine/datatypes/tests/BaseDmlDataTypeTest.java
+++ b/modules/runner/src/integrationTest/java/org/apache/ignite/internal/sql/engine/datatypes/tests/BaseDmlDataTypeTest.java
@@ -17,6 +17,7 @@
 
 package org.apache.ignite.internal.sql.engine.datatypes.tests;
 
+import static org.apache.ignite.internal.sql.engine.datatypes.DataTypeTestSpecs.VARBINARY_TYPE;
 import static org.apache.ignite.lang.IgniteStringFormatter.format;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.hamcrest.Matchers.containsString;
@@ -48,6 +49,11 @@ public abstract class BaseDmlDataTypeTest<T extends Comparable<T>> extends BaseD
     @ParameterizedTest
     @MethodSource("convertedFrom")
     public void testInsertFromDynamicParameterFromConvertible(TestTypeArguments<T> arguments) {
+        // TODO: fix in scope of https://issues.apache.org/jira/browse/IGNITE-20226
+        if (testTypeSpec == VARBINARY_TYPE) {
+            return;
+        }
+
         var t = assertThrows(IgniteException.class, () -> {
             runSql("INSERT INTO t VALUES (1, ?)", arguments.argValue(0));
         });
@@ -104,6 +110,11 @@ public abstract class BaseDmlDataTypeTest<T extends Comparable<T>> extends BaseD
     @ParameterizedTest
     @MethodSource("convertedFrom")
     public void testUpdateFromDynamicParameterFromConvertible(TestTypeArguments<T> arguments) {
+        // TODO: fix in scope of https://issues.apache.org/jira/browse/IGNITE-20226
+        if (testTypeSpec == VARBINARY_TYPE) {
+            return;
+        }
+
         String insert = format("INSERT INTO t VALUES (1, {})", arguments.valueExpr(0));
         runSql(insert);
 
@@ -124,6 +135,11 @@ public abstract class BaseDmlDataTypeTest<T extends Comparable<T>> extends BaseD
     @ParameterizedTest
     @MethodSource("convertedFrom")
     public void testDisallowMismatchTypesOnInsert(TestTypeArguments<T> arguments) {
+        // TODO: fix in scope of https://issues.apache.org/jira/browse/IGNITE-20226
+        if (testTypeSpec == VARBINARY_TYPE) {
+            return;
+        }
+
         var query = format("INSERT INTO t (id, test_key) VALUES (10, null), (20, {})", arguments.valueExpr(0));
         var t = assertThrows(IgniteException.class, () -> runSql(query));
 
@@ -136,6 +152,11 @@ public abstract class BaseDmlDataTypeTest<T extends Comparable<T>> extends BaseD
     @ParameterizedTest
     @MethodSource("convertedFrom")
     public void testDisallowMismatchTypesOnInsertDynamicParam(TestTypeArguments<T> arguments) {
+        // TODO: fix in scope of https://issues.apache.org/jira/browse/IGNITE-20226
+        if (testTypeSpec == VARBINARY_TYPE) {
+            return;
+        }
+
         Object value1 = arguments.argValue(0);
 
         var query = "INSERT INTO t (id, test_key) VALUES (1, null), (2, ?)";
diff --git a/modules/runner/src/integrationTest/java/org/apache/ignite/internal/sql/engine/datatypes/tests/BaseExpressionDataTypeTest.java b/modules/runner/src/integrationTest/java/org/apache/ignite/internal/sql/engine/datatypes/tests/BaseExpressionDataTypeTest.java
index 60adb2705d..b7dc86924d 100644
--- a/modules/runner/src/integrationTest/java/org/apache/ignite/internal/sql/engine/datatypes/tests/BaseExpressionDataTypeTest.java
+++ b/modules/runner/src/integrationTest/java/org/apache/ignite/internal/sql/engine/datatypes/tests/BaseExpressionDataTypeTest.java
@@ -17,6 +17,7 @@
 
 package org.apache.ignite.internal.sql.engine.datatypes.tests;
 
+import static org.apache.ignite.internal.sql.engine.datatypes.DataTypeTestSpecs.VARBINARY_TYPE;
 import static org.apache.ignite.lang.IgniteStringFormatter.format;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.hamcrest.Matchers.containsString;
@@ -127,6 +128,11 @@ public abstract class BaseExpressionDataTypeTest<T extends Comparable<T>> extend
     @ParameterizedTest
     @MethodSource("convertedFrom")
     public void testCoalesceMissingTypesIsIllegal(TestTypeArguments arguments) {
+        // TODO: fix in scope of https://issues.apache.org/jira/browse/IGNITE-20226
+        if (testTypeSpec == VARBINARY_TYPE) {
+            return;
+        }
+
         IgniteException t = assertThrows(IgniteException.class, () -> {
             checkQuery(format("SELECT COALESCE($0, {})", arguments.valueExpr(0))).check();
         });
@@ -219,6 +225,11 @@ public abstract class BaseExpressionDataTypeTest<T extends Comparable<T>> extend
     /** Data type from string. **/
     @Test
     public void testCastFromString() {
+        // TODO: fix in scope of https://issues.apache.org/jira/browse/IGNITE-20226
+        if (testTypeSpec == VARBINARY_TYPE) {
+            return;
+        }
+
         T value = dataSamples.values().get(0);
         String stringValue = testTypeSpec.toStringValue(value);
 
diff --git a/modules/runner/src/integrationTest/java/org/apache/ignite/internal/sql/engine/datatypes/tests/BaseQueryDataTypeTest.java b/modules/runner/src/integrationTest/java/org/apache/ignite/internal/sql/engine/datatypes/tests/BaseQueryDataTypeTest.java
index 4e1a5162a7..b5e543f1c0 100644
--- a/modules/runner/src/integrationTest/java/org/apache/ignite/internal/sql/engine/datatypes/tests/BaseQueryDataTypeTest.java
+++ b/modules/runner/src/integrationTest/java/org/apache/ignite/internal/sql/engine/datatypes/tests/BaseQueryDataTypeTest.java
@@ -17,6 +17,7 @@
 
 package org.apache.ignite.internal.sql.engine.datatypes.tests;
 
+import static org.apache.ignite.internal.sql.engine.datatypes.DataTypeTestSpecs.VARBINARY_TYPE;
 import static org.apache.ignite.lang.IgniteStringFormatter.format;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.hamcrest.Matchers.containsString;
@@ -103,6 +104,11 @@ public abstract class BaseQueryDataTypeTest<T extends Comparable<T>> extends Bas
     @ParameterizedTest
     @MethodSource("convertedFrom")
     public void testEqConditionWithDynamicParameters(TestTypeArguments<T> arguments) {
+        // TODO: fix in scope of https://issues.apache.org/jira/browse/IGNITE-20226
+        if (testTypeSpec == VARBINARY_TYPE) {
+            return;
+        }
+
         T value1 = values.get(0);
 
         runSql("INSERT INTO t VALUES(1, ?)", value1);
diff --git a/modules/runner/src/integrationTest/java/org/apache/ignite/internal/sql/engine/datatypes/tests/TestTypeArguments.java b/modules/runner/src/integrationTest/java/org/apache/ignite/internal/sql/engine/datatypes/tests/TestTypeArguments.java
index 286132a6eb..4a17be11eb 100644
--- a/modules/runner/src/integrationTest/java/org/apache/ignite/internal/sql/engine/datatypes/tests/TestTypeArguments.java
+++ b/modules/runner/src/integrationTest/java/org/apache/ignite/internal/sql/engine/datatypes/tests/TestTypeArguments.java
@@ -408,6 +408,8 @@ public final class TestTypeArguments<T extends Comparable<T>> {
                 }
             } else if (argument instanceof String) {
                 sqlLiteral = SqlLiteral.createCharString(argument.toString(), "UTF-8", SqlParserPos.ZERO);
+            } else if (argument instanceof byte[]) {
+                sqlLiteral = SqlLiteral.createBinaryString((byte[]) argument, SqlParserPos.ZERO);
             } else if (argument instanceof Boolean) {
                 sqlLiteral = SqlLiteral.createBoolean((Boolean) argument, SqlParserPos.ZERO);
             } else {
diff --git a/modules/runner/src/integrationTest/java/org/apache/ignite/internal/sql/engine/datatypes/varbinary/ItVarBinaryExpressionTest.java b/modules/runner/src/integrationTest/java/org/apache/ignite/internal/sql/engine/datatypes/varbinary/ItVarBinaryExpressionTest.java
index 2e91db6bb3..d4ae9c70c7 100644
--- a/modules/runner/src/integrationTest/java/org/apache/ignite/internal/sql/engine/datatypes/varbinary/ItVarBinaryExpressionTest.java
+++ b/modules/runner/src/integrationTest/java/org/apache/ignite/internal/sql/engine/datatypes/varbinary/ItVarBinaryExpressionTest.java
@@ -117,18 +117,6 @@ public class ItVarBinaryExpressionTest extends BaseExpressionDataTypeTest<VarBin
      */
     @Test
     public void testCastToDifferentLengths() {
-        checkQuery("SELECT CAST('123' AS VARBINARY(2))")
-                .returns(VarBinary.fromUtf8String("12"))
-                .check();
-
-        checkQuery("SELECT CAST('123' AS VARBINARY(100))")
-                .returns((VarBinary.fromUtf8String("123")))
-                .check();
-
-        checkQuery("SELECT CAST('123' AS VARBINARY)")
-                .returns((VarBinary.fromUtf8String("123")))
-                .check();
-
         checkQuery("SELECT CAST(X'ffffff' AS VARBINARY(2))")
                 .returns((varBinary(new byte[]{(byte) 0xfff, (byte) 0xff})))
                 .check();
diff --git a/modules/runner/src/integrationTest/sql/types/blob/test_blob.test b/modules/runner/src/integrationTest/sql/types/blob/test_blob.test
index ea38858017..b2ed31e2d0 100644
--- a/modules/runner/src/integrationTest/sql/types/blob/test_blob.test
+++ b/modules/runner/src/integrationTest/sql/types/blob/test_blob.test
@@ -62,46 +62,16 @@ statement ok
 DELETE FROM blobs
 
 # Implicit cast
-statement ok
+statement error
 INSERT INTO blobs VALUES('blablabla')
 
-# BINARY with “non-printable” octets
-statement ok
-INSERT INTO blobs VALUES('abc �'::VARBINARY)
-
-query T
-SELECT b::varchar FROM blobs ORDER BY b
-----
-abc �
-blablabla
-
 # BINARY null and empty values
+statement error
 query T
 SELECT ''::VARBINARY
-----
-(empty)
 
 query T
 SELECT NULL::VARBINARY
 ----
 NULL
 
-statement ok
-CREATE TABLE blob_empty (b binary);
-
-statement ok
-INSERT INTO blob_empty VALUES('')
-
-statement ok
-INSERT INTO blob_empty VALUES(''::VARBINARY)
-
-statement ok
-INSERT INTO blob_empty VALUES(NULL), (NULL::VARBINARY)
-
-query T rowsort
-SELECT * FROM blob_empty
-----
-NULL
-NULL
-(empty)
-(empty)
diff --git a/modules/runner/src/integrationTest/sql/types/blob/test_blob_cast.test b/modules/runner/src/integrationTest/sql/types/blob/test_blob_cast.test
index 17fc15761d..fb74587428 100644
--- a/modules/runner/src/integrationTest/sql/types/blob/test_blob_cast.test
+++ b/modules/runner/src/integrationTest/sql/types/blob/test_blob_cast.test
@@ -5,17 +5,15 @@
 statement ok
 PRAGMA enable_verification
 
-# BLOB to VARCHAR -> CastFromBlob, it always results in a hex representation
+# BLOB to VARCHAR -> CastFromBlob, which is forbidden.
+statement error
 query T
 SELECT 'a'::binary::VARCHAR
-----
-a
 
 # VARCHAR to BLOB -> CastToBlob
+statement error
 query T
 SELECT 'a'::VARCHAR::binary
-----
-61
 
 # Hex string with BLOB
 query T
@@ -24,28 +22,24 @@ SELECT x'2000FF'::varbinary
 2000ff
 
 # CastFromBlob with hex string
+statement error
 query T
 SELECT x'612061'::VARBINARY::VARCHAR
-----
-a a
 
 # CastFromBlob and after CastToBlob with hex string
+statement error
 query T
 SELECT x'612061'::VARBINARY::VARCHAR::VARBINARY
-----
-612061
 
 # CastFromBlob -> CastToBlob -> CastFromBlob with hex string
+statement error
 query T
 SELECT x'612061'::VARBINARY::VARCHAR::VARBINARY::VARCHAR
-----
-a a
 
 # CastToBlob -> CastFromBlob -> CastToBlob with hex string
+statement error
 query T
 SELECT x'612061'::VARCHAR::VARBINARY::VARCHAR::VARBINARY
-----
-612061
 
 statement error
 SELECT 1::VARBINARY
diff --git a/modules/runner/src/integrationTest/sql/types/blob/test_blob_function.test b/modules/runner/src/integrationTest/sql/types/blob/test_blob_function.test
index 80381607d6..7649e9e11d 100644
--- a/modules/runner/src/integrationTest/sql/types/blob/test_blob_function.test
+++ b/modules/runner/src/integrationTest/sql/types/blob/test_blob_function.test
@@ -8,22 +8,11 @@ PRAGMA enable_verification
 statement ok
 CREATE TABLE blobs (b varbinary);
 
-statement ok
+statement error
 INSERT INTO blobs VALUES ('a'::binary)
 
-# conventional concat
-query T
-SELECT (b || 'ZZ'::varbinary)::varchar FROM blobs
-----
-aZZ
-
-query T
-SELECT 'abc '::varbinary || 'klm *'::varbinary || x'EFBFBD'::varbinary || 'T'::varbinary
-----
-616263206b6c6d202aefbfbd54
-
 statement ok
-INSERT INTO blobs VALUES ('abc '::varbinary || 'klm *'::varbinary || x'EFBFBD'::varbinary || 'T'::varbinary)
+INSERT INTO blobs VALUES (x'aa'::binary), (x'bbcc')
 
 query I
 SELECT COUNT(*) FROM blobs
@@ -35,7 +24,7 @@ query I
 SELECT OCTET_LENGTH(b) FROM blobs ORDER BY 1
 ----
 1
-13
+2
 
 # HEX strings
 statement ok
@@ -45,9 +34,9 @@ statement ok
 INSERT INTO blobs VALUES (x'FF'::binary)
 
 query T
-SELECT b || 'ZZ'::varbinary FROM blobs
+SELECT b || x'BB'::varbinary FROM blobs
 ----
-ff5a5a
+ffbb
 
 query T
 SELECT b || x'5A5A'::varbinary FROM blobs
@@ -65,19 +54,17 @@ DELETE FROM blobs
 statement ok
 INSERT INTO blobs VALUES (x'FF'::binary)
 
-statement ok
+statement error
 INSERT INTO blobs VALUES ('FF'::varbinary)
 
 statement ok
 INSERT INTO blobs VALUES (x'55AAFF55AAFF55AAFF01'::varbinary)
 
-statement ok
+statement error
 INSERT INTO blobs VALUES ('55AAFF55AAFF55AAFF01'::varbinary)
 
 query I
 SELECT OCTET_LENGTH(b) FROM blobs ORDER BY 1
 ----
 1
-2
 10
-20
diff --git a/modules/runner/src/integrationTest/sql/types/blob/test_blob_operator.test b/modules/runner/src/integrationTest/sql/types/blob/test_blob_operator.test
index 3572d8e54a..bd59b8bfa5 100644
--- a/modules/runner/src/integrationTest/sql/types/blob/test_blob_operator.test
+++ b/modules/runner/src/integrationTest/sql/types/blob/test_blob_operator.test
@@ -10,10 +10,10 @@ CREATE TABLE blobs (b varbinary, g INTEGER);
 
 # strings: hello -> \x68656C6C6F, r -> \x72
 statement ok
-INSERT INTO blobs VALUES ('hello', 0)
+INSERT INTO blobs VALUES (x'aabbccddee', 0)
 
 statement ok
-INSERT INTO blobs VALUES (x'00' || 'whatisgoingon'::varbinary, 1)
+INSERT INTO blobs VALUES (x'00' || x'aabb'::varbinary, 1)
 
 statement ok
 INSERT INTO blobs VALUES (NULL, 0)
@@ -25,36 +25,23 @@ INSERT INTO blobs VALUES (x'FFFEFB', 1)
 query IITT
 SELECT COUNT(*), COUNT(b), MIN(b), MAX(b) FROM blobs
 ----
-4	3	00776861746973676f696e676f6e	fffefb
+4	3	00aabb	fffefb
 
 # ORDER BY
 query TI
 SELECT * FROM blobs ORDER BY b
 ----
-00776861746973676f696e676f6e	1
-68656c6c6f	0
+00aabb	1
+aabbccddee	0
 fffefb	1
 NULL	0
 
-# GROUP BY
-statement ok
-INSERT INTO blobs VALUES ('hello', 3)
-
-statement ok
-INSERT INTO blobs VALUES (x'00' || 'whatisgoingon'::varbinary, 9)
-
-statement ok
-INSERT INTO blobs VALUES (NULL, 0)
-
-statement ok
-INSERT INTO blobs VALUES (x'FFFEFB', 19)
-
 query II
 SELECT b, SUM(g) FROM blobs GROUP BY b ORDER BY b
 ----
-00776861746973676f696e676f6e	10
-68656c6c6f	3
-fffefb	20
+00aabb	1
+aabbccddee	0
+fffefb	1
 NULL	0
 
 # JOIN
@@ -62,10 +49,10 @@ statement ok
 CREATE TABLE blobs2 (b VARBINARY, g INTEGER);
 
 statement ok
-INSERT INTO blobs2 VALUES ('hello', 0)
+INSERT INTO blobs2 VALUES (x'aabbccddee', 0)
 
 statement ok
-INSERT INTO blobs2 VALUES (x'00' || 'whatisgoingon'::varbinary, 100)
+INSERT INTO blobs2 VALUES (x'00' || x'aabb'::varbinary, 100)
 
 statement ok
 INSERT INTO blobs2 VALUES (NULL, 0)
@@ -77,14 +64,14 @@ INSERT INTO blobs2 VALUES (x'FFFEFB', 200)
 query TR
 SELECT L.b, SUM(L.g) FROM blobs as L JOIN blobs2 AS R ON L.b=R.b GROUP BY L.b ORDER BY L.b
 ----
-00776861746973676f696e676f6e	10.000000
-68656c6c6f	3.000000
-fffefb	20.000000
+00aabb	1
+aabbccddee	0
+fffefb	1
 
 # group by blobs2.b, implicit JOIN
 query TR
 SELECT R.b, SUM(R.g) FROM blobs as L, blobs2 AS R WHERE L.b=R.b GROUP BY R.b ORDER BY R.b
 ----
-00776861746973676f696e676f6e	200.000000
-68656c6c6f	0.000000
-fffefb	400.000000
+00aabb	100
+aabbccddee	0
+fffefb	200
diff --git a/modules/runner/src/integrationTest/sql/types/blob/test_blob_string.test b/modules/runner/src/integrationTest/sql/types/blob/test_blob_string.test
index a4866ab117..dc279feb34 100644
--- a/modules/runner/src/integrationTest/sql/types/blob/test_blob_string.test
+++ b/modules/runner/src/integrationTest/sql/types/blob/test_blob_string.test
@@ -10,7 +10,7 @@ CREATE TABLE blobs (b varbinary);
 
 # insert BINARY from string
 statement ok
-INSERT INTO blobs VALUES ('aaaaaaaaaa'::varbinary)
+INSERT INTO blobs VALUES (x'aaaaaaaaaaaaaaaaaaaa'::varbinary)
 
 # sizes: 10, 100, 1000, 10000 -> double plus two due to hexadecimal representation
 # The concat function casts BINARY to VARCHAR, resulting in a hex string
diff --git a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/exec/row/BaseTypeSpec.java b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/exec/row/BaseTypeSpec.java
index 97b11880cc..ad000e06f8 100644
--- a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/exec/row/BaseTypeSpec.java
+++ b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/exec/row/BaseTypeSpec.java
@@ -37,7 +37,7 @@ public class BaseTypeSpec extends TypeSpec {
     /** Creates a basic type with the given nullability. */
     public BaseTypeSpec(NativeType nativeType, boolean nullable) {
         super(nullable);
-        // TODO Uncomment his check after https://issues.apache.org/jira/browse/IGNITE-19096 is fixed
+        // TODO Uncomment his check after https://issues.apache.org/jira/browse/IGNITE-20163 is fixed
         //this.nativeType = Objects.requireNonNull(nativeType, "native type has not been specified.");
         this.nativeType = nativeType;
     }
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
index 5ba42535c5..adaced4036 100644
--- 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
@@ -23,6 +23,19 @@ 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.INTERVAL_DAY;
+import static org.apache.calcite.sql.type.SqlTypeName.INTERVAL_DAY_HOUR;
+import static org.apache.calcite.sql.type.SqlTypeName.INTERVAL_DAY_MINUTE;
+import static org.apache.calcite.sql.type.SqlTypeName.INTERVAL_DAY_SECOND;
+import static org.apache.calcite.sql.type.SqlTypeName.INTERVAL_HOUR;
+import static org.apache.calcite.sql.type.SqlTypeName.INTERVAL_HOUR_MINUTE;
+import static org.apache.calcite.sql.type.SqlTypeName.INTERVAL_HOUR_SECOND;
+import static org.apache.calcite.sql.type.SqlTypeName.INTERVAL_MINUTE;
+import static org.apache.calcite.sql.type.SqlTypeName.INTERVAL_MINUTE_SECOND;
+import static org.apache.calcite.sql.type.SqlTypeName.INTERVAL_MONTH;
+import static org.apache.calcite.sql.type.SqlTypeName.INTERVAL_SECOND;
+import static org.apache.calcite.sql.type.SqlTypeName.INTERVAL_YEAR;
+import static org.apache.calcite.sql.type.SqlTypeName.INTERVAL_YEAR_MONTH;
 import static org.apache.calcite.sql.type.SqlTypeName.YEAR_INTERVAL_TYPES;
 
 import com.google.common.cache.CacheBuilder;
@@ -34,6 +47,7 @@ import com.google.common.collect.Sets;
 import com.google.common.util.concurrent.UncheckedExecutionException;
 import java.util.EnumSet;
 import java.util.HashMap;
+import java.util.List;
 import java.util.Map;
 import java.util.Set;
 import java.util.concurrent.ExecutionException;
@@ -65,30 +79,14 @@ public class IgniteCustomAssigmentsRules implements SqlTypeMappingRule {
 
         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(FRACTIONAL_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...
@@ -97,30 +95,32 @@ public class IgniteCustomAssigmentsRules implements SqlTypeMappingRule {
             rules.add(type, rule);
         }
 
-        // VARBINARY is assignable from...
-        rule.clear();
-        rule.addAll(BINARY_TYPES);
-        rule.addAll(CHAR_TYPES);
-        rules.add(SqlTypeName.VARBINARY, rule);
+        rule.add(INTERVAL_YEAR);
+        rule.add(INTERVAL_MONTH);
+        rule.add(INTERVAL_DAY);
+        rule.add(INTERVAL_HOUR);
+        rule.add(INTERVAL_MINUTE);
+        rule.add(INTERVAL_SECOND);
 
-        // CHAR is assignable from...
+        // 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);
+        }
+
+        // BINARY, VARBINARY 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);
+        for (SqlTypeName type : BINARY_TYPES) {
+            rules.add(type, rule);
+        }
 
+        // CHAR is assignable from...
         // 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);
@@ -129,17 +129,13 @@ public class IgniteCustomAssigmentsRules implements SqlTypeMappingRule {
         rule.add(SqlTypeName.DATE);
         rule.add(SqlTypeName.TIME);
         rule.add(SqlTypeName.TIMESTAMP);
+
+        rules.add(SqlTypeName.CHAR, rule);
         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);
@@ -180,15 +176,39 @@ public class IgniteCustomAssigmentsRules implements SqlTypeMappingRule {
         rule.addAll(CHAR_TYPES);
         rule.addAll(YEAR_INTERVAL_TYPES);
 
-        for (SqlTypeName type : YEAR_INTERVAL_TYPES) {
+        // IntervalYearMonth is assignable from...
+        rules.add(INTERVAL_YEAR_MONTH, rule);
+
+        rule.clear();
+        rule.addAll(CHAR_TYPES);
+        rule.addAll(DAY_INTERVAL_TYPES);
+
+        List<SqlTypeName> multiIntervals = List.of(INTERVAL_DAY_HOUR, INTERVAL_DAY_MINUTE, INTERVAL_DAY_SECOND, INTERVAL_HOUR_MINUTE,
+                INTERVAL_HOUR_SECOND, INTERVAL_MINUTE_SECOND);
+
+        // IntervalDayHourMinuteSecond is assignable from...
+        for (SqlTypeName type : multiIntervals) {
             rules.add(type, rule);
         }
 
         rule.clear();
         rule.addAll(CHAR_TYPES);
+        rule.addAll(EXACT_TYPES);
+        rule.addAll(YEAR_INTERVAL_TYPES);
+
+        List<SqlTypeName> singleYearIntervals = List.of(INTERVAL_YEAR, INTERVAL_MONTH);
+
+        for (SqlTypeName type : singleYearIntervals) {
+            rules.add(type, rule);
+        }
+
+        rule.removeAll(YEAR_INTERVAL_TYPES);
         rule.addAll(DAY_INTERVAL_TYPES);
 
-        for (SqlTypeName type : DAY_INTERVAL_TYPES) {
+        List<SqlTypeName> singleDayIntervals = List.of(INTERVAL_DAY, INTERVAL_HOUR, INTERVAL_MINUTE,
+                INTERVAL_SECOND);
+
+        for (SqlTypeName type : singleDayIntervals) {
             rules.add(type, rule);
         }
 
diff --git a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/util/TypeUtils.java b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/util/TypeUtils.java
index f80a7343b0..d09f7a5e89 100644
--- a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/util/TypeUtils.java
+++ b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/util/TypeUtils.java
@@ -635,8 +635,6 @@ public class TypeUtils {
             NativeType nativeType = IgniteTypeFactory.relDataTypeToNative(type);
             return RowSchemaTypes.nativeTypeWithNullability(nativeType, nullable);
         } else if (SqlTypeName.ANY == type.getSqlTypeName()) {
-            // TODO: After https://issues.apache.org/jira/browse/IGNITE-19096 is fixed
-            //  it should be possible to remove branch.
             // TODO Some JSON functions that return ANY as well : https://issues.apache.org/jira/browse/IGNITE-20163
             return new BaseTypeSpec(null, nullable);
         } else if (SqlTypeUtil.isNull(type)) {
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
index f9ad59e10c..9e16b484d4 100644
--- 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
@@ -17,14 +17,28 @@
 
 package org.apache.ignite.internal.sql.engine.planner;
 
+import static java.lang.String.format;
+import static org.apache.calcite.sql.type.SqlTypeName.ALL_TYPES;
 import static org.apache.calcite.sql.type.SqlTypeName.BINARY_TYPES;
+import static org.apache.calcite.sql.type.SqlTypeName.BOOLEAN_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.EXACT_TYPES;
+import static org.apache.calcite.sql.type.SqlTypeName.FRACTIONAL_TYPES;
+import static org.apache.calcite.sql.type.SqlTypeName.INTERVAL_DAY;
+import static org.apache.calcite.sql.type.SqlTypeName.INTERVAL_DAY_MINUTE;
+import static org.apache.calcite.sql.type.SqlTypeName.INTERVAL_DAY_SECOND;
 import static org.apache.calcite.sql.type.SqlTypeName.INTERVAL_HOUR;
+import static org.apache.calcite.sql.type.SqlTypeName.INTERVAL_HOUR_MINUTE;
+import static org.apache.calcite.sql.type.SqlTypeName.INTERVAL_HOUR_SECOND;
 import static org.apache.calcite.sql.type.SqlTypeName.INTERVAL_MINUTE;
+import static org.apache.calcite.sql.type.SqlTypeName.INTERVAL_MINUTE_SECOND;
 import static org.apache.calcite.sql.type.SqlTypeName.INTERVAL_MONTH;
+import static org.apache.calcite.sql.type.SqlTypeName.INTERVAL_SECOND;
 import static org.apache.calcite.sql.type.SqlTypeName.INTERVAL_YEAR;
-import static org.apache.calcite.sql.type.SqlTypeName.NUMERIC_TYPES;
+import static org.apache.calcite.sql.type.SqlTypeName.INTERVAL_YEAR_MONTH;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
 
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -33,59 +47,92 @@ import java.util.List;
 import java.util.Set;
 import java.util.stream.Collectors;
 import java.util.stream.Stream;
+import org.apache.calcite.sql.type.SqlTypeCoercionRule;
+import org.apache.calcite.sql.type.SqlTypeMappingRule;
 import org.apache.calcite.sql.type.SqlTypeName;
 import org.apache.ignite.internal.sql.engine.type.UuidType;
+import org.apache.ignite.internal.sql.engine.util.IgniteCustomAssigmentsRules;
 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 String CAST_ERROR_MESSAGE = "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> NUMERIC_NAMES = new HashSet<>();
 
-    private static final Set<String> charNames = CHAR_TYPES.stream().map(SqlTypeName::getName).collect(Collectors.toSet());
+    private static final Set<String> FRACTIONAL_NAMES = FRACTIONAL_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> EXACT_NUMERIC = EXACT_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> CHAR_NAMES = CHAR_TYPES.stream().map(SqlTypeName::getName).collect(Collectors.toSet());
 
-    private static final Set<String> charAndNumericNames = new HashSet<>();
+    private static final Set<String> BINARY_NAMES = BINARY_TYPES.stream().map(SqlTypeName::getName).collect(Collectors.toSet());
 
-    private static final Set<String> charAndBinaryNames = new HashSet<>();
+    private static final Set<String> DT_NAMES = DATETIME_TYPES.stream().map(SqlTypeName::getName).collect(Collectors.toSet());
 
-    private static final Set<String> charAndTs = new HashSet<>();
+    private static final Set<String> YM_INTERVAL = Set.of(INTERVAL_YEAR.getName(), INTERVAL_MONTH.getName());
 
-    private static final Set<String> charAndDt = new HashSet<>();
+    private static final Set<String> DAY_INTERVAL = Set.of(INTERVAL_HOUR.getName(), INTERVAL_MINUTE.getName(), INTERVAL_SECOND.getName());
 
-    private static final Set<String> charAndYInterval = new HashSet<>();
+    private static final Set<String> ALL_BESIDES_BINARY_NAMES =
+            BOOLEAN_TYPES.stream().map(SqlTypeName::getName).collect(Collectors.toSet());
 
-    private static final Set<String> charAndDInterval = new HashSet<>();
+    private static final Set<String> CHAR_NUMERIC_AND_INTERVAL_NAMES = new HashSet<>();
 
-    private static final String commonTemplate = "SELECT CAST('1'::%s AS %s)";
+    private static final Set<String> CHAR_AND_NUMERIC_NAMES = new HashSet<>();
 
-    private static final String intervalTemplate = "SELECT CAST(INTERVAL 1 %s AS %s)";
+    private static final Set<String> CHAR_AND_TS = new HashSet<>();
 
-    static {
-        numericNames.add("NUMERIC");
+    private static final Set<String> CHAR_AND_DT = new HashSet<>();
 
-        charAndNumericNames.addAll(numericNames);
-        charAndNumericNames.addAll(charNames);
+    private static final Set<String> CHAR_EXACT_AND_YM_INTERVAL = new HashSet<>();
 
-        charAndBinaryNames.addAll(charNames);
-        charAndBinaryNames.addAll(binaryNames);
+    private static final Set<String> CHAR_EXACT_AND_DAY_INTERVAL = new HashSet<>();
 
-        charAndTs.addAll(charNames);
-        charAndTs.add(SqlTypeName.TIMESTAMP.getName());
+    private static final String COMMON_TEMPLATE = "SELECT CAST('1'::%s AS %s)";
 
-        charAndDt.addAll(dtNames);
-        charAndDt.addAll(charNames);
+    private static final String INTERVAL_TEMPLATE = "SELECT CAST(%s AS %s)";
 
-        charAndYInterval.addAll(List.of(INTERVAL_YEAR.getName(), INTERVAL_MONTH.getName()));
-        charAndYInterval.addAll(charNames);
+    private static final String BINARY_TEMPLATE = "SELECT CAST(X'01'::%s AS %s)";
 
-        charAndDInterval.addAll(List.of(INTERVAL_HOUR.getName(), INTERVAL_MINUTE.getName()));
-        charAndDInterval.addAll(charNames);
+    static {
+        NUMERIC_NAMES.add("NUMERIC");
+        NUMERIC_NAMES.addAll(EXACT_NUMERIC);
+        NUMERIC_NAMES.addAll(FRACTIONAL_NAMES);
+
+        ALL_BESIDES_BINARY_NAMES.addAll(NUMERIC_NAMES);
+        ALL_BESIDES_BINARY_NAMES.addAll(FRACTIONAL_NAMES);
+        ALL_BESIDES_BINARY_NAMES.addAll(CHAR_NAMES);
+        ALL_BESIDES_BINARY_NAMES.addAll(DT_NAMES);
+        ALL_BESIDES_BINARY_NAMES.addAll(YM_INTERVAL);
+        ALL_BESIDES_BINARY_NAMES.addAll(DAY_INTERVAL);
+        ALL_BESIDES_BINARY_NAMES.add("NUMERIC");
+        ALL_BESIDES_BINARY_NAMES.add(UuidType.NAME);
+
+        CHAR_NUMERIC_AND_INTERVAL_NAMES.addAll(NUMERIC_NAMES);
+        CHAR_NUMERIC_AND_INTERVAL_NAMES.addAll(CHAR_NAMES);
+        CHAR_NUMERIC_AND_INTERVAL_NAMES.addAll(YM_INTERVAL);
+        CHAR_NUMERIC_AND_INTERVAL_NAMES.addAll(DAY_INTERVAL);
+
+        CHAR_AND_NUMERIC_NAMES.addAll(NUMERIC_NAMES);
+        CHAR_AND_NUMERIC_NAMES.addAll(CHAR_NAMES);
+
+        CHAR_AND_TS.addAll(CHAR_NAMES);
+        CHAR_AND_TS.add(SqlTypeName.TIMESTAMP.getName());
+
+        CHAR_AND_DT.addAll(DT_NAMES);
+        CHAR_AND_DT.addAll(CHAR_NAMES);
+
+        CHAR_EXACT_AND_YM_INTERVAL.addAll(List.of(INTERVAL_YEAR.getName(), INTERVAL_MONTH.getName()));
+        CHAR_EXACT_AND_YM_INTERVAL.addAll(CHAR_NAMES);
+        CHAR_EXACT_AND_YM_INTERVAL.addAll(EXACT_NUMERIC);
+        CHAR_EXACT_AND_YM_INTERVAL.add("NUMERIC");
+
+        CHAR_EXACT_AND_DAY_INTERVAL.addAll(List.of(INTERVAL_HOUR.getName(), INTERVAL_MINUTE.getName()));
+        CHAR_EXACT_AND_DAY_INTERVAL.addAll(CHAR_NAMES);
+        CHAR_EXACT_AND_DAY_INTERVAL.addAll(EXACT_NUMERIC);
+        CHAR_EXACT_AND_DAY_INTERVAL.add("NUMERIC");
     }
 
     /** Test CAST possibility for different supported types. */
@@ -96,112 +143,210 @@ public class CastResolutionTest extends AbstractPlannerTest {
         Set<String> allTypes = Arrays.stream(CastMatrix.values()).map(v -> v.from).collect(Collectors.toSet());
 
         for (CastMatrix types : CastMatrix.values()) {
-            String from = types.from;
+            String fromInitial = 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;
+            String template = template(fromInitial);
+            String from = makeUsableIntervalFromType(fromInitial);
 
             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;
-                }
+                toType = makeUsableIntervalToType(toType);
 
                 // TODO: https://issues.apache.org/jira/browse/IGNITE-19274
-                if (toType.contains("LOCAL_TIME")) {
+                if (toType.contains("LOCAL TIME")) {
                     continue;
                 }
 
-                testItems.add(checkStatement().sql(String.format(template, from, toType)).ok(false));
+                testItems.add(checkStatement().sql(format(template, from, toType)).ok(false));
             }
 
-            if (!interval) {
-                testItems.add(checkStatement().sql(String.format("SELECT '1'::%s", from)).ok(false));
+            testItems.add(checkStatement().sql(format(template, from, makeUsableIntervalToType(fromInitial))).ok());
+
+            testItems.add(checkStatement().sql(format(INTERVAL_TEMPLATE, "NULL", makeUsableIntervalToType(fromInitial))).ok());
+
+            String finalFrom = from;
+            Set<String> deprecatedCastTypes = allTypes.stream().filter(t -> !toTypes.contains(t) && !t.equals(finalFrom))
+                    .collect(Collectors.toSet());
+
+            for (String toType : deprecatedCastTypes) {
+                boolean isInterval = toType.toLowerCase().contains("interval");
+
+                toType = isInterval ? makeUsableIntervalToType(toType) : toType;
+
+                testItems.add(checkStatement().sql(format(template, from, toType)).fails(CAST_ERROR_MESSAGE));
             }
+        }
+
+        return testItems.stream();
+    }
+
+    /* Check that casts between intervals and exact numerics can involve only intervals with a single datetime field. */
+    @TestFactory
+    public Stream<DynamicTest> testMultiIntervals() {
+        List<DynamicTest> testItems = new ArrayList<>();
+
+        testItems.add(checkStatement().sql("SELECT CAST(1 AS interval year to month)").fails(CAST_ERROR_MESSAGE));
+        testItems.add(checkStatement().sql("SELECT CAST(1 AS interval day to hour)").fails(CAST_ERROR_MESSAGE));
+        testItems.add(checkStatement().sql("SELECT CAST(1 AS interval day to minute)").fails(CAST_ERROR_MESSAGE));
+        testItems.add(checkStatement().sql("SELECT CAST(1 AS interval day to second)").fails(CAST_ERROR_MESSAGE));
+        testItems.add(checkStatement().sql("SELECT CAST(1 AS interval hour to minute)").fails(CAST_ERROR_MESSAGE));
+        testItems.add(checkStatement().sql("SELECT CAST(1 AS interval hour to second)").fails(CAST_ERROR_MESSAGE));
+
+        testItems.add(checkStatement().sql("SELECT CAST('1' AS interval hour to second)").ok());
+        testItems.add(checkStatement().sql("SELECT CAST('1' AS interval day to hour)").ok());
+        testItems.add(checkStatement().sql("SELECT CAST('1' AS interval day to minute)").ok());
+        testItems.add(checkStatement().sql("SELECT CAST('1' AS interval day to second)").ok());
+        testItems.add(checkStatement().sql("SELECT CAST('1' AS interval hour to minute)").ok());
+        testItems.add(checkStatement().sql("SELECT CAST('1' AS interval hour to second)").ok());
+
+        testItems.add(checkStatement().sql("SELECT CAST('1'::VARCHAR AS interval hour to second)").ok());
+        testItems.add(checkStatement().sql("SELECT CAST('1'::VARCHAR AS interval day to hour)").ok());
+        testItems.add(checkStatement().sql("SELECT CAST('1'::VARCHAR AS interval day to minute)").ok());
+        testItems.add(checkStatement().sql("SELECT CAST('1'::VARCHAR AS interval day to second)").ok());
+        testItems.add(checkStatement().sql("SELECT CAST('1'::VARCHAR AS interval hour to minute)").ok());
+        testItems.add(checkStatement().sql("SELECT CAST('1'::VARCHAR AS interval hour to second)").ok());
+
+        return testItems.stream();
+    }
+
+    @TestFactory
+    public Stream<DynamicTest> allowedCastsFromNull() {
+        List<DynamicTest> testItems = new ArrayList<>();
+
+        SqlTypeMappingRule rules = SqlTypeCoercionRule.instance(IgniteCustomAssigmentsRules.instance().getTypeMapping());
 
-            // all types are allowed.
-            if (allCastsPossible) {
+        for (SqlTypeName type : ALL_TYPES) {
+            if (type == SqlTypeName.NULL) {
                 continue;
             }
 
-            if (!interval) {
-                testItems.add(checkStatement().sql(String.format(template, from, from)).ok());
+            testItems.add(DynamicTest.dynamicTest(format("ALLOW: from: %s to: %s", SqlTypeName.NULL.getName(), type),
+                    () -> assertTrue(rules.canApplyFrom(type, SqlTypeName.NULL))));
+        }
+
+        return testItems.stream();
+    }
+
+    @TestFactory
+    public Stream<DynamicTest> testRulesContainsIntervalWithExactTypesRules() {
+        List<DynamicTest> testItems = new ArrayList<>();
+
+        List<SqlTypeName> singleIntervals = List.of(INTERVAL_YEAR, INTERVAL_MONTH, INTERVAL_DAY, INTERVAL_HOUR, INTERVAL_MINUTE,
+                INTERVAL_SECOND);
+
+        List<SqlTypeName> nonSingleIntervals = List.of(INTERVAL_YEAR_MONTH, INTERVAL_DAY_MINUTE, INTERVAL_DAY_SECOND,
+                INTERVAL_HOUR_MINUTE, INTERVAL_HOUR_SECOND, INTERVAL_MINUTE_SECOND);
+
+        List<SqlTypeName> singleYearIntervals = List.of(INTERVAL_YEAR, INTERVAL_MONTH);
+
+        List<SqlTypeName> singleDayIntervals = List.of(INTERVAL_DAY, INTERVAL_HOUR, INTERVAL_MINUTE,
+                INTERVAL_SECOND);
+
+        SqlTypeMappingRule rules = SqlTypeCoercionRule.instance(IgniteCustomAssigmentsRules.instance().getTypeMapping());
+
+        for (SqlTypeName toType : singleIntervals) {
+            for (SqlTypeName fromType : EXACT_TYPES) {
+                testItems.add(DynamicTest.dynamicTest(format("ALLOW: from: %s to: %s", fromType, toType),
+                        () -> assertTrue(rules.canApplyFrom(toType, fromType))));
             }
+        }
 
-            String finalFrom = from;
-            Set<String> deprecatedCastTypes = allTypes.stream().filter(t -> !toTypes.contains(t) && !t.equals(finalFrom))
-                    .collect(Collectors.toSet());
+        for (SqlTypeName toType : nonSingleIntervals) {
+            for (SqlTypeName fromType : EXACT_TYPES) {
+                testItems.add(DynamicTest.dynamicTest(format("ALLOW: from: %s to: %s", fromType, toType),
+                        () -> assertFalse(rules.canApplyFrom(toType, fromType))));
+            }
+        }
 
-            for (String toType : deprecatedCastTypes) {
-                toType = isInterval(toType) ? makeUsableIntervalType(toType) : toType;
+        for (SqlTypeName toType : singleYearIntervals) {
+            for (SqlTypeName fromType : singleYearIntervals) {
+                testItems.add(DynamicTest.dynamicTest(format("ALLOW: from: %s to: %s", fromType, toType),
+                        () -> assertTrue(rules.canApplyFrom(toType, fromType))));
+            }
+        }
 
-                testItems.add(checkStatement().sql(String.format(template, from, toType)).fails(castErrorMessage));
+        for (SqlTypeName toType : singleDayIntervals) {
+            for (SqlTypeName fromType : singleDayIntervals) {
+                testItems.add(DynamicTest.dynamicTest(format("ALLOW: from: %s to: %s", fromType, toType),
+                        () -> assertTrue(rules.canApplyFrom(toType, fromType))));
+            }
+        }
+
+        for (SqlTypeName toType : singleYearIntervals) {
+            for (SqlTypeName fromType : singleDayIntervals) {
+                testItems.add(DynamicTest.dynamicTest(format("FORBID: from: %s to: %s", fromType, toType),
+                        () -> assertFalse(rules.canApplyFrom(toType, fromType))));
+            }
+        }
+
+        for (SqlTypeName toType : singleDayIntervals) {
+            for (SqlTypeName fromType : singleYearIntervals) {
+                testItems.add(DynamicTest.dynamicTest(format("FORBID: from: %s to: %s", fromType, toType),
+                        () -> assertFalse(rules.canApplyFrom(toType, fromType))));
             }
         }
 
         return testItems.stream();
     }
 
-    private static boolean isInterval(String typeName) {
-        return typeName.toLowerCase().contains("interval");
+    private static String template(String typeName) {
+        if (typeName.toLowerCase().contains("interval")) {
+            return INTERVAL_TEMPLATE;
+        } else if (typeName.toLowerCase().contains("binary")) {
+            return BINARY_TEMPLATE;
+        } else {
+            return COMMON_TEMPLATE;
+        }
     }
 
-    private static String makeUsableIntervalType(String typeName) {
+    private static String makeUsableIntervalToType(String typeName) {
         return typeName.replace("_", " ");
     }
 
+    private static String makeUsableIntervalFromType(String typeName) {
+        return typeName.replace("_", " 1 ");
+    }
+
     private enum CastMatrix {
-        BOOLEAN(SqlTypeName.BOOLEAN.getName(), charNames),
+        BOOLEAN(SqlTypeName.BOOLEAN.getName(), CHAR_NAMES),
 
-        INT8(SqlTypeName.TINYINT.getName(), charAndNumericNames),
+        INT8(SqlTypeName.TINYINT.getName(), CHAR_NUMERIC_AND_INTERVAL_NAMES),
 
-        INT16(SqlTypeName.SMALLINT.getName(), charAndNumericNames),
+        INT16(SqlTypeName.SMALLINT.getName(), CHAR_NUMERIC_AND_INTERVAL_NAMES),
 
-        INT32(SqlTypeName.INTEGER.getName(), charAndNumericNames),
+        INT32(SqlTypeName.INTEGER.getName(), CHAR_NUMERIC_AND_INTERVAL_NAMES),
 
-        INT64(SqlTypeName.BIGINT.getName(), charAndNumericNames),
+        INT64(SqlTypeName.BIGINT.getName(), CHAR_NUMERIC_AND_INTERVAL_NAMES),
 
-        DECIMAL(SqlTypeName.DECIMAL.getName(), charAndNumericNames),
+        DECIMAL(SqlTypeName.DECIMAL.getName(), CHAR_NUMERIC_AND_INTERVAL_NAMES),
 
-        REAL(SqlTypeName.REAL.getName(), charAndNumericNames),
+        REAL(SqlTypeName.REAL.getName(), CHAR_AND_NUMERIC_NAMES),
 
-        DOUBLE(SqlTypeName.DOUBLE.getName(), charAndNumericNames),
+        DOUBLE(SqlTypeName.DOUBLE.getName(), CHAR_AND_NUMERIC_NAMES),
 
-        FLOAT(SqlTypeName.FLOAT.getName(), charAndNumericNames),
+        FLOAT(SqlTypeName.FLOAT.getName(), CHAR_AND_NUMERIC_NAMES),
 
-        NUMERIC("NUMERIC", charAndNumericNames),
+        NUMERIC("NUMERIC", CHAR_NUMERIC_AND_INTERVAL_NAMES),
 
-        UUID(UuidType.NAME, new HashSet<>(charNames)),
+        UUID(UuidType.NAME, new HashSet<>(CHAR_NAMES)),
 
-        VARCHAR(SqlTypeName.VARCHAR.getName(), Set.of("ALL")),
+        VARCHAR(SqlTypeName.VARCHAR.getName(), ALL_BESIDES_BINARY_NAMES),
 
-        CHAR(SqlTypeName.CHAR.getName(), Set.of("ALL")),
+        CHAR(SqlTypeName.CHAR.getName(), ALL_BESIDES_BINARY_NAMES),
 
-        VARBINARY(SqlTypeName.VARBINARY.getName(), charAndBinaryNames),
+        VARBINARY(SqlTypeName.VARBINARY.getName(), BINARY_NAMES),
 
-        BINARY(SqlTypeName.BINARY.getName(), charAndBinaryNames),
+        BINARY(SqlTypeName.BINARY.getName(), BINARY_NAMES),
 
-        DATE(SqlTypeName.DATE.getName(), charAndTs),
+        DATE(SqlTypeName.DATE.getName(), CHAR_AND_TS),
 
-        TIME(SqlTypeName.TIME.getName(), charAndTs),
+        TIME(SqlTypeName.TIME.getName(), CHAR_AND_TS),
 
-        TIMESTAMP(SqlTypeName.TIMESTAMP.getName(), charAndDt),
+        TIMESTAMP(SqlTypeName.TIMESTAMP.getName(), CHAR_AND_DT),
 
-        INTERVAL_YEAR(SqlTypeName.INTERVAL_YEAR.getName(), charAndYInterval),
+        INTERVAL_YEAR(SqlTypeName.INTERVAL_YEAR.getName(), CHAR_EXACT_AND_YM_INTERVAL),
 
-        INTERVAL_HOUR(SqlTypeName.INTERVAL_HOUR.getName(), charAndDInterval);
+        INTERVAL_HOUR(SqlTypeName.INTERVAL_HOUR.getName(), CHAR_EXACT_AND_DAY_INTERVAL);
 
         // TODO: https://issues.apache.org/jira/browse/IGNITE-19274
         //TIMESTAMP_TS(SqlTypeName.TIMESTAMP_WITH_LOCAL_TIME_ZONE.getName(), charAndDt);