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);