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 2022/03/04 07:20:21 UTC
[ignite-3] branch main updated: IGNITE-16633 Adoption of a bunch of calcite related tickets from Ignite-2 - Fixes #698.
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 157a8f0 IGNITE-16633 Adoption of a bunch of calcite related tickets from Ignite-2 - Fixes #698.
157a8f0 is described below
commit 157a8f09bd98314f548f1672467f7dd682b32df2
Author: zstan <st...@gmail.com>
AuthorDate: Fri Mar 4 10:10:29 2022 +0300
IGNITE-16633 Adoption of a bunch of calcite related tickets from Ignite-2 - Fixes #698.
IGNITE-15109 TIMESTAMPDIFF for MICROSECOND unit doesn't work.
IGNITE-14975 Introduce suppot INTERVAL type.
IGNITE-15596 java.lang.Integer cannot be cast to java.lang.Long with JOIN and EXISTS.
IGNITE-16129 RelJsonReader can't deserialize enums.
Signed-off-by: zstan <st...@gmail.com>
---
.../internal/sql/engine/ItDataTypesTest.java | 64 +++-
.../internal/sql/engine/ItFunctionsTest.java | 9 +
.../ignite/internal/sql/engine/ItIntervalTest.java | 398 +++++++++++++++++++++
.../ignite/internal/sql/engine/ItMetadataTest.java | 9 +-
modules/sql-engine/src/main/codegen/config.fmpp | 1 +
.../src/main/codegen/includes/parserImpls.ftl | 32 +-
.../sql/engine/exec/exp/IgniteBuiltInMethod.java | 8 +-
.../internal/sql/engine/exec/exp/RexImpTable.java | 15 +-
.../sql/engine/exec/exp/RexToLixTranslator.java | 39 ++
.../internal/sql/engine/externalize/RelJson.java | 10 +-
.../sql/engine/prepare/IgniteConvertletTable.java | 128 +++++++
.../sql/engine/prepare/IgniteTypeCoercion.java | 58 +++
.../engine/sql/IgniteSqlIntervalTypeNameSpec.java | 62 ++++
.../sql/engine/type/IgniteTypeFactory.java | 59 ++-
.../ignite/internal/sql/engine/util/Commons.java | 6 +-
.../ignite/internal/sql/engine/util/TypeUtils.java | 36 +-
.../sql/engine/planner/TableSpoolPlannerTest.java | 6 +-
17 files changed, 897 insertions(+), 43 deletions(-)
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 57c512c..d9f8bfe 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
@@ -77,7 +77,7 @@ public class ItDataTypesTest extends AbstractBasicIntegrationTest {
@Disabled("https://issues.apache.org/jira/browse/IGNITE-16292")
@Test
public void testCheckDefaultsAndNullables() {
- sql("CREATE TABLE tbl(c1 int primary key, c2 int NOT NULL, c3 int NOT NULL DEFAULT 100)");
+ sql("CREATE TABLE tbl(c1 int PRIMARY KEY, c2 int NOT NULL, c3 int NOT NULL DEFAULT 100)");
sql("INSERT INTO tbl(c1, c2) VALUES (1, 2)");
@@ -94,4 +94,66 @@ public class ItDataTypesTest extends AbstractBasicIntegrationTest {
//todo: correct exception https://issues.apache.org/jira/browse/IGNITE-16095
assertThrows(IgniteException.class, () -> sql("INSERT INTO tbl(c1, c2) VALUES (2, NULL)"));
}
+
+ /**
+ * Tests numeric types mapping on Java types.
+ */
+ @Test
+ public void testNumericRanges() {
+ try {
+ sql("CREATE TABLE tbl(id int PRIMARY KEY, tiny TINYINT, small SMALLINT, i INTEGER, big BIGINT)");
+
+ sql("INSERT INTO tbl VALUES (1, " + Byte.MAX_VALUE + ", " + Short.MAX_VALUE + ", "
+ + Integer.MAX_VALUE + ", " + Long.MAX_VALUE + ')');
+
+ assertQuery("SELECT tiny FROM tbl").returns(Byte.MAX_VALUE).check();
+ assertQuery("SELECT small FROM tbl").returns(Short.MAX_VALUE).check();
+ assertQuery("SELECT i FROM tbl").returns(Integer.MAX_VALUE).check();
+ assertQuery("SELECT big FROM tbl").returns(Long.MAX_VALUE).check();
+
+ sql("DELETE from tbl");
+
+ sql("INSERT INTO tbl VALUES (1, " + Byte.MIN_VALUE + ", " + Short.MIN_VALUE + ", "
+ + Integer.MIN_VALUE + ", " + Long.MIN_VALUE + ')');
+
+ assertQuery("SELECT tiny FROM tbl").returns(Byte.MIN_VALUE).check();
+ assertQuery("SELECT small FROM tbl").returns(Short.MIN_VALUE).check();
+ assertQuery("SELECT i FROM tbl").returns(Integer.MIN_VALUE).check();
+ assertQuery("SELECT big FROM tbl").returns(Long.MIN_VALUE).check();
+ } finally {
+ sql("DROP TABLE if exists tbl");
+ }
+ }
+
+ /**
+ * Tests numeric type convertation on equals.
+ */
+ @Test
+ public void testNumericConvertingOnEquals() {
+ try {
+ sql("CREATE TABLE tbl(id int PRIMARY KEY, tiny TINYINT, small SMALLINT, i INTEGER, big BIGINT)");
+
+ sql("INSERT INTO tbl VALUES (-1, 1, 2, 3, 4), (0, 5, 5, 5, 5)");
+
+ assertQuery("SELECT t1.tiny FROM tbl t1 JOIN tbl t2 ON (t1.tiny=t2.small)").returns((byte) 5).check();
+ assertQuery("SELECT t1.small FROM tbl t1 JOIN tbl t2 ON (t1.small=t2.tiny)").returns((short) 5).check();
+
+ assertQuery("SELECT t1.tiny FROM tbl t1 JOIN tbl t2 ON (t1.tiny=t2.i)").returns((byte) 5).check();
+ assertQuery("SELECT t1.i FROM tbl t1 JOIN tbl t2 ON (t1.i=t2.tiny)").returns(5).check();
+
+ assertQuery("SELECT t1.tiny FROM tbl t1 JOIN tbl t2 ON (t1.tiny=t2.big)").returns((byte) 5).check();
+ assertQuery("SELECT t1.big FROM tbl t1 JOIN tbl t2 ON (t1.big=t2.tiny)").returns(5L).check();
+
+ assertQuery("SELECT t1.small FROM tbl t1 JOIN tbl t2 ON (t1.small=t2.i)").returns((short) 5).check();
+ assertQuery("SELECT t1.i FROM tbl t1 JOIN tbl t2 ON (t1.i=t2.small)").returns(5).check();
+
+ assertQuery("SELECT t1.small FROM tbl t1 JOIN tbl t2 ON (t1.small=t2.big)").returns((short) 5).check();
+ assertQuery("SELECT t1.big FROM tbl t1 JOIN tbl t2 ON (t1.big=t2.small)").returns(5L).check();
+
+ assertQuery("SELECT t1.i FROM tbl t1 JOIN tbl t2 ON (t1.i=t2.big)").returns(5).check();
+ assertQuery("SELECT t1.big FROM tbl t1 JOIN tbl t2 ON (t1.big=t2.i)").returns(5L).check();
+ } finally {
+ sql("DROP TABLE if exists tbl");
+ }
+ }
}
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 fa00aa3..67540b6 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
@@ -47,6 +47,15 @@ public class ItFunctionsTest extends AbstractBasicIntegrationTest {
private static final Object[] NULL_RESULT = new Object[] { null };
@Test
+ public void testTimestampDiffWithFractionsOfSecond() {
+ assertQuery("SELECT TIMESTAMPDIFF(MICROSECOND, TIMESTAMP '2022-02-01 10:30:28.000', "
+ + "TIMESTAMP '2022-02-01 10:30:28.128')").returns(128000).check();
+
+ assertQuery("SELECT TIMESTAMPDIFF(NANOSECOND, TIMESTAMP '2022-02-01 10:30:28.000', "
+ + "TIMESTAMP '2022-02-01 10:30:28.128')").returns(128000000L).check();
+ }
+
+ @Test
public void testLength() {
assertQuery("SELECT LENGTH('TEST')").returns(4).check();
assertQuery("SELECT LENGTH(NULL)").returns(NULL_RESULT).check();
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
new file mode 100644
index 0000000..47211c9
--- /dev/null
+++ b/modules/runner/src/integrationTest/java/org/apache/ignite/internal/sql/engine/ItIntervalTest.java
@@ -0,0 +1,398 @@
+/*
+ * 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;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.sql.Date;
+import java.sql.Time;
+import java.sql.Timestamp;
+import java.time.Duration;
+import java.time.Period;
+import org.apache.ignite.lang.IgniteInternalException;
+import org.junit.jupiter.api.Disabled;
+import org.junit.jupiter.api.Test;
+
+/** Interval coverage tests. */
+public class ItIntervalTest extends AbstractBasicIntegrationTest {
+ @Override
+ protected int nodes() {
+ return 1;
+ }
+
+ /**
+ * Test returned result for interval data types.
+ */
+ @Test
+ public void testIntervalResult() {
+ assertEquals(Duration.ofDays(4), eval("INTERVAL 4 DAYS"));
+ assertEquals(Duration.ofSeconds(1), eval("INTERVAL 1 SECONDS"));
+ assertEquals(Duration.ofSeconds(-1), eval("INTERVAL -1 SECONDS"));
+ assertEquals(Duration.ofSeconds(123), eval("INTERVAL 123 SECONDS"));
+ assertEquals(Duration.ofSeconds(123), eval("INTERVAL '123' SECONDS(3)"));
+ assertEquals(Duration.ofMinutes(2), eval("INTERVAL 2 MINUTES"));
+ assertEquals(Duration.ofHours(3), eval("INTERVAL 3 HOURS"));
+ assertEquals(Duration.ofDays(4), eval("INTERVAL 4 DAYS"));
+ assertEquals(Period.ofMonths(5), eval("INTERVAL 5 MONTHS"));
+ assertEquals(Period.ofMonths(-5), eval("INTERVAL -5 MONTHS"));
+ assertEquals(Period.ofYears(6), eval("INTERVAL 6 YEARS"));
+ assertEquals(Period.of(1, 2, 0), eval("INTERVAL '1-2' YEAR TO MONTH"));
+ assertEquals(Duration.ofHours(25), eval("INTERVAL '1 1' DAY TO HOUR"));
+ assertEquals(Duration.ofMinutes(62), eval("INTERVAL '1:2' HOUR TO MINUTE"));
+ assertEquals(Duration.ofSeconds(63), eval("INTERVAL '1:3' MINUTE TO SECOND"));
+ assertEquals(Duration.ofSeconds(3723), eval("INTERVAL '1:2:3' HOUR TO SECOND"));
+ assertEquals(Duration.ofMillis(3723456), eval("INTERVAL '0 1:2:3.456' DAY TO SECOND"));
+
+ assertThrowsEx("SELECT INTERVAL '123' SECONDS", IgniteInternalException.class, "exceeds precision");
+ }
+
+ /**
+ * Test cast interval types to integer and integer to interval.
+ */
+ @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)"));
+
+ // Compound interval types cannot be cast.
+ assertThrowsEx("SELECT CAST(INTERVAL '1-2' YEAR TO MONTH AS INT)", IgniteInternalException.class, "cannot convert");
+ assertThrowsEx("SELECT CAST(INTERVAL '1 2' DAY TO HOUR AS INT)", IgniteInternalException.class, "cannot convert");
+
+ assertThrowsEx("SELECT CAST(1 AS INTERVAL YEAR TO MONTH)", IgniteInternalException.class, "cannot convert");
+ assertThrowsEx("SELECT CAST(1 AS INTERVAL DAY TO HOUR)", IgniteInternalException.class, "cannot convert");
+ }
+
+ /**
+ * Test cast interval types to string and string to interval.
+ */
+ @Test
+ public void testIntervalStringCast() {
+ assertNull(eval("CAST(NULL::INTERVAL SECONDS AS VARCHAR)"));
+ assertNull(eval("CAST(NULL::INTERVAL MONTHS AS VARCHAR)"));
+ assertEquals("+1.234", eval("CAST(INTERVAL '1.234' SECONDS (1,3) AS VARCHAR)"));
+ assertEquals("+1.000000", eval("CAST(INTERVAL 1 SECONDS AS VARCHAR)"));
+ assertEquals("+2", eval("CAST(INTERVAL 2 MINUTES AS VARCHAR)"));
+ assertEquals("+3", eval("CAST(INTERVAL 3 HOURS AS VARCHAR)"));
+ assertEquals("+4", eval("CAST(INTERVAL 4 DAYS AS VARCHAR)"));
+ assertEquals("+5", eval("CAST(INTERVAL 5 MONTHS AS VARCHAR)"));
+ assertEquals("+6", eval("CAST(INTERVAL 6 YEARS AS VARCHAR)"));
+ assertEquals("+1-02", eval("CAST(INTERVAL '1-2' YEAR TO MONTH AS VARCHAR)"));
+ assertEquals("+1 02", eval("CAST(INTERVAL '1 2' DAY TO HOUR AS VARCHAR)"));
+ assertEquals("-1 02:03:04.000000", eval("CAST(INTERVAL '-1 2:3:4' DAY TO SECOND AS VARCHAR)"));
+
+ assertNull(eval("CAST(NULL::VARCHAR AS INTERVAL SECONDS)"));
+ assertNull(eval("CAST(NULL::VARCHAR 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(Duration.ofHours(26), eval("CAST('1 2' AS INTERVAL DAY TO HOUR)"));
+ assertEquals(Duration.ofMinutes(62), eval("CAST('1:2' AS INTERVAL HOUR TO MINUTE)"));
+ assertEquals(Duration.ofMillis(3723456), eval("CAST('0 1:2:3.456' AS INTERVAL DAY TO SECOND)"));
+ assertEquals(Duration.ofMillis(-3723456), eval("CAST('-0 1:2:3.456' AS INTERVAL DAY TO SECOND)"));
+ assertEquals(Period.ofMonths(5), eval("CAST('5' AS INTERVAL MONTHS)"));
+ assertEquals(Period.ofYears(6), eval("CAST('6' AS INTERVAL YEARS)"));
+ assertEquals(Period.of(1, 2, 0), eval("CAST('1-2' AS INTERVAL YEAR TO MONTH)"));
+ }
+
+ /**
+ * Test cast between interval types.
+ */
+ @Test
+ public void testIntervalToIntervalCast() {
+ assertNull(eval("CAST(NULL::INTERVAL MINUTE AS INTERVAL SECONDS)"));
+ assertNull(eval("CAST(NULL::INTERVAL YEAR AS INTERVAL MONTHS)"));
+ assertEquals(Duration.ofMinutes(1), eval("CAST(INTERVAL 60 SECONDS AS INTERVAL MINUTE)"));
+ assertEquals(Duration.ofHours(1), eval("CAST(INTERVAL 60 MINUTES AS INTERVAL HOUR)"));
+ assertEquals(Duration.ofDays(1), eval("CAST(INTERVAL 24 HOURS AS INTERVAL DAY)"));
+ assertEquals(Period.ofYears(1), eval("CAST(INTERVAL 1 YEAR AS INTERVAL MONTHS)"));
+ assertEquals(Period.ofYears(1), eval("CAST(INTERVAL 12 MONTHS AS INTERVAL YEARS)"));
+
+ // Cannot convert between month-year and day-time interval types.
+ assertThrowsEx("SELECT CAST(INTERVAL 1 MONTHS AS INTERVAL DAYS)", IgniteInternalException.class, "cannot convert");
+ assertThrowsEx("SELECT CAST(INTERVAL 1 DAYS AS INTERVAL MONTHS)", IgniteInternalException.class, "cannot convert");
+ }
+
+ /**
+ * Test DML statements with interval data type.
+ */
+ @Disabled("https://issues.apache.org/jira/browse/IGNITE-16637")
+ @Test
+ public void testDml() {
+ sql("CREATE TABLE test(id int PRIMARY KEY, ym INTERVAL YEAR, dt INTERVAL DAYS)");
+ sql("INSERT INTO test VALUES (1, INTERVAL 1 MONTH, INTERVAL 2 DAYS)");
+ sql("INSERT INTO test VALUES (2, INTERVAL 3 YEARS, INTERVAL 4 HOURS)");
+ sql("INSERT INTO test VALUES (3, INTERVAL '4-5' YEARS TO MONTHS, INTERVAL '6:7' HOURS TO MINUTES)");
+ sql("INSERT INTO test VALUES (4, NULL, NULL)");
+
+ assertThrowsEx("INSERT INTO test VALUES (5, INTERVAL 1 DAYS, INTERVAL 1 HOURS)", IgniteInternalException.class,
+ "cannot assign");
+
+ assertThrowsEx("INSERT INTO test VALUES (6, INTERVAL 1 YEARS, INTERVAL 1 MONTHS)", IgniteInternalException.class,
+ "cannot assign");
+
+ assertQuery("SELECT ym, dt FROM test")
+ .returns(Period.ofMonths(1), Duration.ofDays(2))
+ .returns(Period.ofYears(3), Duration.ofHours(4))
+ .returns(Period.of(4, 5, 0), Duration.ofMinutes(367))
+ .returns(null, null)
+ .check();
+
+ assertThrowsEx("SELECT * FROM test WHERE ym = INTERVAL 6 DAYS", IgniteInternalException.class, "Cannot apply");
+ assertThrowsEx("SELECT * FROM test WHERE dt = INTERVAL 6 YEARS", IgniteInternalException.class, "Cannot apply");
+
+ sql("UPDATE test SET dt = INTERVAL 3 DAYS WHERE ym = INTERVAL 1 MONTH");
+ sql("UPDATE test SET ym = INTERVAL 5 YEARS WHERE dt = INTERVAL 4 HOURS");
+ sql("UPDATE test SET ym = INTERVAL '6-7' YEARS TO MONTHS, dt = INTERVAL '8 9' DAYS TO HOURS "
+ + "WHERE ym = INTERVAL '4-5' YEARS TO MONTHS AND dt = INTERVAL '6:7' HOURS TO MINUTES");
+
+ assertThrowsEx("UPDATE test SET dt = INTERVAL 5 YEARS WHERE ym = INTERVAL 1 MONTH", IgniteInternalException.class,
+ "Cannot assign");
+
+ assertThrowsEx("UPDATE test SET ym = INTERVAL 8 YEARS WHERE dt = INTERVAL 1 MONTH", IgniteInternalException.class,
+ "Cannot apply");
+
+ assertQuery("SELECT * FROM test")
+ .returns(Period.ofMonths(1), Duration.ofDays(3))
+ .returns(Period.ofYears(5), Duration.ofHours(4))
+ .returns(Period.of(6, 7, 0), Duration.ofHours(201))
+ .returns(null, null)
+ .check();
+
+ assertThrowsEx("DELETE FROM test WHERE ym = INTERVAL 6 DAYS", IgniteInternalException.class, "cannot apply");
+ assertThrowsEx("DELETE FROM test WHERE dt = INTERVAL 6 YEARS", IgniteInternalException.class, "cannot apply");
+
+ sql("DELETE FROM test WHERE ym = INTERVAL 1 MONTH");
+ sql("DELETE FROM test WHERE dt = INTERVAL 4 HOURS");
+ sql("DELETE FROM test WHERE ym = INTERVAL '6-7' YEARS TO MONTHS AND dt = INTERVAL '8 9' DAYS TO HOURS");
+ sql("DELETE FROM test WHERE ym IS NULL AND dt IS NULL");
+
+ assertEquals(0, sql("SELECT * FROM test").size());
+
+ sql("ALTER TABLE test ADD (ym2 INTERVAL MONTH, dt2 INTERVAL HOURS)");
+
+ sql("INSERT INTO test(id, ym, ym2, dt, dt2) VALUES (7, INTERVAL 1 YEAR, INTERVAL 2 YEARS, "
+ + "INTERVAL 1 SECOND, INTERVAL 2 MINUTES)");
+
+ assertQuery("SELECT ym, ym2, dt, dt2 FROM test")
+ .returns(Period.ofYears(1), Period.ofYears(2), Duration.ofSeconds(1), Duration.ofMinutes(2))
+ .check();
+ }
+
+ /**
+ * Test interval arithmetic.
+ */
+ @Test
+ public void testIntervalArithmetic() {
+ // Date +/- interval.
+ assertEquals(Date.valueOf("2021-01-02"), eval("DATE '2021-01-01' + INTERVAL 1 DAY"));
+ assertEquals(Date.valueOf("2020-12-31"), eval("DATE '2021-01-01' - INTERVAL 1 DAY"));
+ assertEquals(Date.valueOf("2020-12-31"), eval("DATE '2021-01-01' + INTERVAL -1 DAY"));
+ assertEquals(Date.valueOf("2021-02-01"), eval("DATE '2021-01-01' + INTERVAL 1 MONTH"));
+ assertEquals(Date.valueOf("2022-01-01"), eval("DATE '2021-01-01' + INTERVAL 1 YEAR"));
+ assertEquals(Date.valueOf("2022-02-01"), eval("DATE '2021-01-01' + INTERVAL '1-1' YEAR TO MONTH"));
+
+ // Timestamp +/- interval.
+ assertEquals(Timestamp.valueOf("2021-01-01 00:00:01"),
+ eval("TIMESTAMP '2021-01-01 00:00:00' + INTERVAL 1 SECOND"));
+ assertEquals(Timestamp.valueOf("2021-01-01 00:00:01.123"),
+ eval("TIMESTAMP '2021-01-01 00:00:00.123' + INTERVAL 1 SECOND"));
+ assertEquals(Timestamp.valueOf("2021-01-01 00:00:01.123"),
+ eval("TIMESTAMP '2021-01-01 00:00:00' + INTERVAL '1.123' SECOND"));
+ assertEquals(Timestamp.valueOf("2021-01-01 00:00:01.246"),
+ eval("TIMESTAMP '2021-01-01 00:00:00.123' + INTERVAL '1.123' SECOND"));
+ assertEquals(Timestamp.valueOf("2020-12-31 23:59:59"),
+ eval("TIMESTAMP '2021-01-01 00:00:00' - INTERVAL 1 SECOND"));
+ assertEquals(Timestamp.valueOf("2020-12-31 23:59:59"),
+ eval("TIMESTAMP '2021-01-01 00:00:00' + INTERVAL -1 SECOND"));
+ assertEquals(Timestamp.valueOf("2021-01-01 00:01:00"),
+ eval("TIMESTAMP '2021-01-01 00:00:00' + INTERVAL 1 MINUTE"));
+ assertEquals(Timestamp.valueOf("2021-01-01 01:00:00"),
+ eval("TIMESTAMP '2021-01-01 00:00:00' + INTERVAL 1 HOUR"));
+ assertEquals(Timestamp.valueOf("2021-01-02 00:00:00"),
+ eval("TIMESTAMP '2021-01-01 00:00:00' + INTERVAL 1 DAY"));
+ assertEquals(Timestamp.valueOf("2021-02-01 00:00:00"),
+ eval("TIMESTAMP '2021-01-01 00:00:00' + INTERVAL 1 MONTH"));
+ assertEquals(Timestamp.valueOf("2022-01-01 00:00:00"),
+ eval("TIMESTAMP '2021-01-01 00:00:00' + INTERVAL 1 YEAR"));
+ assertEquals(Timestamp.valueOf("2021-01-02 01:01:01.123"),
+ eval("TIMESTAMP '2021-01-01 00:00:00' + INTERVAL '1 1:1:1.123' DAY TO SECOND"));
+ assertEquals(Timestamp.valueOf("2022-02-01 01:01:01.123"),
+ eval("TIMESTAMP '2021-01-01 01:01:01.123' + INTERVAL '1-1' YEAR TO MONTH"));
+
+ // Time +/- interval.
+ assertEquals(Time.valueOf("00:00:01"), eval("TIME '00:00:00' + INTERVAL 1 SECOND"));
+ assertEquals(Time.valueOf("00:01:00"), eval("TIME '00:00:00' + INTERVAL 1 MINUTE"));
+ assertEquals(Time.valueOf("01:00:00"), eval("TIME '00:00:00' + INTERVAL 1 HOUR"));
+
+ // Date - date as interval.
+ assertEquals(Duration.ofDays(1), eval("(DATE '2021-01-02' - DATE '2021-01-01') DAYS"));
+ assertEquals(Duration.ofDays(-1), eval("(DATE '2021-01-01' - DATE '2021-01-02') DAYS"));
+ assertEquals(Duration.ofDays(1), eval("(DATE '2021-01-02' - DATE '2021-01-01') HOURS"));
+ assertEquals(Period.ofYears(1), eval("(DATE '2022-01-01' - DATE '2021-01-01') YEARS"));
+ assertEquals(Period.ofMonths(1), eval("(DATE '2021-02-01' - DATE '2021-01-01') MONTHS"));
+ assertEquals(Period.ofMonths(-1), eval("(DATE '2021-01-01' - DATE '2021-02-01') MONTHS"));
+ assertEquals(Period.ofMonths(0), eval("(DATE '2021-01-20' - DATE '2021-01-01') MONTHS"));
+
+ // Timestamp - timestamp as interval.
+ assertEquals(Duration.ofDays(1),
+ eval("(TIMESTAMP '2021-01-02 00:00:00' - TIMESTAMP '2021-01-01 00:00:00') DAYS"));
+ assertEquals(Duration.ofDays(-1),
+ eval("(TIMESTAMP '2021-01-01 00:00:00' - TIMESTAMP '2021-01-02 00:00:00') DAYS"));
+ assertEquals(Duration.ofHours(1),
+ eval("(TIMESTAMP '2021-01-01 01:00:00' - TIMESTAMP '2021-01-01 00:00:00') HOURS"));
+ assertEquals(Duration.ofMinutes(1),
+ eval("(TIMESTAMP '2021-01-01 00:01:00' - TIMESTAMP '2021-01-01 00:00:00') MINUTES"));
+ assertEquals(Duration.ofSeconds(1),
+ eval("(TIMESTAMP '2021-01-01 00:00:01' - TIMESTAMP '2021-01-01 00:00:00') SECONDS"));
+ assertEquals(Duration.ofMillis(123),
+ eval("(TIMESTAMP '2021-01-01 00:00:00.123' - TIMESTAMP '2021-01-01 00:00:00') SECONDS"));
+ assertEquals(Period.ofYears(1),
+ eval("(TIMESTAMP '2022-01-01 00:00:00' - TIMESTAMP '2021-01-01 00:00:00') YEARS"));
+ assertEquals(Period.ofMonths(1),
+ eval("(TIMESTAMP '2021-02-01 00:00:00' - TIMESTAMP '2021-01-01 00:00:00') MONTHS"));
+ assertEquals(Period.ofMonths(-1),
+ eval("(TIMESTAMP '2021-01-01 00:00:00' - TIMESTAMP '2021-02-01 00:00:00') MONTHS"));
+ assertEquals(Period.ofMonths(0),
+ eval("(TIMESTAMP '2021-01-20 00:00:00' - TIMESTAMP '2021-01-01 00:00:00') MONTHS"));
+
+ // Time - time as interval.
+ assertEquals(Duration.ofHours(1), eval("(TIME '02:00:00' - TIME '01:00:00') HOURS"));
+ assertEquals(Duration.ofMinutes(1), eval("(TIME '00:02:00' - TIME '00:01:00') HOURS"));
+ assertEquals(Duration.ofMinutes(1), eval("(TIME '00:02:00' - TIME '00:01:00') MINUTES"));
+ assertEquals(Duration.ofSeconds(1), eval("(TIME '00:00:02' - TIME '00:00:01') SECONDS"));
+ assertEquals(Duration.ofMillis(123), eval("(TIME '00:00:01.123' - TIME '00:00:01') SECONDS"));
+
+ // Interval +/- interval.
+ assertEquals(Duration.ofSeconds(2), eval("INTERVAL 1 SECONDS + INTERVAL 1 SECONDS"));
+ assertEquals(Duration.ofSeconds(1), eval("INTERVAL 2 SECONDS - INTERVAL 1 SECONDS"));
+ assertEquals(Duration.ofSeconds(61), eval("INTERVAL 1 MINUTE + INTERVAL 1 SECONDS"));
+ assertEquals(Duration.ofSeconds(59), eval("INTERVAL 1 MINUTE - INTERVAL 1 SECONDS"));
+ assertEquals(Duration.ofSeconds(59), eval("INTERVAL 1 MINUTE + INTERVAL -1 SECONDS"));
+ assertEquals(Duration.ofSeconds(3723), eval("INTERVAL 1 HOUR + INTERVAL '2:3' MINUTE TO SECONDS"));
+ assertEquals(Duration.ofSeconds(3477), eval("INTERVAL 1 HOUR - INTERVAL '2:3' MINUTE TO SECONDS"));
+ assertEquals(Duration.ofHours(25), eval("INTERVAL 1 DAY + INTERVAL 1 HOUR"));
+ assertEquals(Period.ofMonths(2), eval("INTERVAL 1 MONTH + INTERVAL 1 MONTH"));
+ assertEquals(Period.ofYears(2), eval("INTERVAL 1 YEAR + INTERVAL 1 YEAR"));
+ assertEquals(Period.of(1, 1, 0), eval("INTERVAL 1 YEAR + INTERVAL 1 MONTH"));
+ assertEquals(Period.ofMonths(11), eval("INTERVAL 1 YEAR - INTERVAL 1 MONTH"));
+ assertEquals(Period.ofMonths(11), eval("INTERVAL 1 YEAR + INTERVAL -1 MONTH"));
+ assertThrowsEx("SELECT INTERVAL 1 DAY + INTERVAL 1 MONTH", IgniteInternalException.class, "Cannot apply");
+
+ // Interval * scalar.
+ assertEquals(Duration.ofSeconds(2), eval("INTERVAL 1 SECONDS * 2"));
+ assertEquals(Duration.ofSeconds(-2), eval("INTERVAL -1 SECONDS * 2"));
+ assertEquals(Duration.ofMinutes(4), eval("INTERVAL 2 MINUTES * 2"));
+ assertEquals(Duration.ofHours(6), eval("INTERVAL 3 HOURS * 2"));
+ assertEquals(Duration.ofDays(8), eval("INTERVAL 4 DAYS * 2"));
+ assertEquals(Period.ofMonths(10), eval("INTERVAL 5 MONTHS * 2"));
+ assertEquals(Period.ofMonths(-10), eval("INTERVAL -5 MONTHS * 2"));
+ assertEquals(Period.ofYears(12), eval("INTERVAL 6 YEARS * 2"));
+ assertEquals(Period.of(2, 4, 0), eval("INTERVAL '1-2' YEAR TO MONTH * 2"));
+ assertEquals(Duration.ofHours(50), eval("INTERVAL '1 1' DAY TO HOUR * 2"));
+ assertEquals(Duration.ofMinutes(124), eval("INTERVAL '1:2' HOUR TO MINUTE * 2"));
+ assertEquals(Duration.ofSeconds(126), eval("INTERVAL '1:3' MINUTE TO SECOND * 2"));
+ assertEquals(Duration.ofSeconds(7446), eval("INTERVAL '1:2:3' HOUR TO SECOND * 2"));
+ assertEquals(Duration.ofMillis(7446912), eval("INTERVAL '0 1:2:3.456' DAY TO SECOND * 2"));
+
+ // Interval / scalar
+ assertEquals(Duration.ofSeconds(1), eval("INTERVAL 2 SECONDS / 2"));
+ assertEquals(Duration.ofSeconds(-1), eval("INTERVAL -2 SECONDS / 2"));
+ assertEquals(Duration.ofSeconds(30), eval("INTERVAL 1 MINUTES / 2"));
+ assertEquals(Duration.ofMinutes(90), eval("INTERVAL 3 HOURS / 2"));
+ assertEquals(Duration.ofDays(2), eval("INTERVAL 4 DAYS / 2"));
+ assertEquals(Period.ofMonths(2), eval("INTERVAL 5 MONTHS / 2"));
+ assertEquals(Period.ofMonths(-2), eval("INTERVAL -5 MONTHS / 2"));
+ assertEquals(Period.of(3, 6, 0), eval("INTERVAL 7 YEARS / 2"));
+ assertEquals(Period.ofMonths(7), eval("INTERVAL '1-2' YEAR TO MONTH / 2"));
+ assertEquals(Duration.ofHours(13), eval("INTERVAL '1 2' DAY TO HOUR / 2"));
+ assertEquals(Duration.ofMinutes(31), eval("INTERVAL '1:2' HOUR TO MINUTE / 2"));
+ assertEquals(Duration.ofSeconds(31), eval("INTERVAL '1:2' MINUTE TO SECOND / 2"));
+ assertEquals(Duration.ofSeconds(1862), eval("INTERVAL '1:2:4' HOUR TO SECOND / 2"));
+ assertEquals(Duration.ofMillis(1862228), eval("INTERVAL '0 1:2:4.456' DAY TO SECOND / 2"));
+ }
+
+ /**
+ * Test EXTRACT function with interval data types.
+ */
+ @Test
+ public void testExtract() {
+ assertEquals(2L, eval("EXTRACT(MONTH FROM INTERVAL 14 MONTHS)"));
+ assertEquals(0L, eval("EXTRACT(MONTH FROM INTERVAL 1 YEAR)"));
+ assertEquals(2L, eval("EXTRACT(MONTH FROM INTERVAL '1-2' YEAR TO MONTH)"));
+ assertEquals(1L, eval("EXTRACT(YEAR FROM INTERVAL '1-2' YEAR TO MONTH)"));
+ assertEquals(-1L, eval("EXTRACT(MONTH FROM INTERVAL -1 MONTHS)"));
+ assertEquals(-1L, eval("EXTRACT(YEAR FROM INTERVAL -14 MONTHS)"));
+ assertEquals(-2L, eval("EXTRACT(MONTH FROM INTERVAL -14 MONTHS)"));
+ assertEquals(-20L, eval("EXTRACT(MINUTE FROM INTERVAL '-10:20' HOURS TO MINUTES)"));
+ assertEquals(1L, eval("EXTRACT(DAY FROM INTERVAL '1 2:3:4.567' DAY TO SECOND)"));
+ assertEquals(2L, eval("EXTRACT(HOUR FROM INTERVAL '1 2:3:4.567' DAY TO SECOND)"));
+ assertEquals(3L, eval("EXTRACT(MINUTE FROM INTERVAL '1 2:3:4.567' DAY TO SECOND)"));
+ assertEquals(4L, eval("EXTRACT(SECOND FROM INTERVAL '1 2:3:4.567' DAY TO SECOND)"));
+ assertEquals(4567L, eval("EXTRACT(MILLISECOND FROM INTERVAL '1 2:3:4.567' DAY TO SECOND)"));
+ assertEquals(-1L, eval("EXTRACT(DAY FROM INTERVAL '-1 2:3:4.567' DAY TO SECOND)"));
+ assertEquals(-2L, eval("EXTRACT(HOUR FROM INTERVAL '-1 2:3:4.567' DAY TO SECOND)"));
+ assertEquals(-3L, eval("EXTRACT(MINUTE FROM INTERVAL '-1 2:3:4.567' DAY TO SECOND)"));
+ assertEquals(-4L, eval("EXTRACT(SECOND FROM INTERVAL '-1 2:3:4.567' DAY TO SECOND)"));
+ assertEquals(-4567L, eval("EXTRACT(MILLISECOND FROM INTERVAL '-1 2:3:4.567' DAY TO SECOND)"));
+
+ assertThrowsEx("SELECT EXTRACT(DAY FROM INTERVAL 1 MONTH)", IgniteInternalException.class, "Cannot apply");
+ assertThrowsEx("SELECT EXTRACT(MONTH FROM INTERVAL 1 DAY)", IgniteInternalException.class, "Cannot apply");
+ }
+
+ /**
+ * Test caching of expressions by digest.
+ */
+ @Test
+ public void testScalarCache() {
+ // These expressions differs only in return data type, so digest should include also data type correctly
+ // compile scalar for second expression (should not get compiled scalar from the cache).
+ assertEquals(Duration.ofDays(1), eval("(DATE '2021-01-02' - DATE '2021-01-01') DAYS"));
+ assertEquals(Period.ofMonths(0), eval("(DATE '2021-01-02' - DATE '2021-01-01') MONTHS"));
+ }
+
+ public Object eval(String exp) {
+ return sql("SELECT " + exp).get(0).get(0);
+ }
+
+ private void assertThrowsEx(String sql, Class<? extends Exception> cls, String errMsg) {
+ Exception ex = assertThrows(cls, () -> sql(sql));
+
+ assertTrue(ex.getMessage().toLowerCase().contains(errMsg.toLowerCase()));
+ }
+}
diff --git a/modules/runner/src/integrationTest/java/org/apache/ignite/internal/sql/engine/ItMetadataTest.java b/modules/runner/src/integrationTest/java/org/apache/ignite/internal/sql/engine/ItMetadataTest.java
index 3060bca..ea0dd72 100644
--- a/modules/runner/src/integrationTest/java/org/apache/ignite/internal/sql/engine/ItMetadataTest.java
+++ b/modules/runner/src/integrationTest/java/org/apache/ignite/internal/sql/engine/ItMetadataTest.java
@@ -79,9 +79,12 @@ public class ItMetadataTest extends AbstractBasicIntegrationTest {
@Test
public void infixTypeCast() {
- assertQuery("select id, id::tinyint as tid, id::smallint as sid, id::varchar as vid from person")
- .columnNames("ID", "TID", "SID", "VID")
- .columnTypes(Integer.class, Byte.class, Short.class, String.class)
+ assertQuery("select id, id::tinyint as tid, id::smallint as sid, id::varchar as vid, id::interval hour, "
+ + "id::interval year from person")
+ .columnNames("ID", "TID", "SID", "VID", "ID :: INTERVAL INTERVAL_HOUR", "ID :: INTERVAL INTERVAL_YEAR")
+ // TODO: IGNITE-16635 replace byte arrays for correct types.
+ //.columnTypes(Integer.class, Byte.class, Short.class, String.class, Duration.class, Period.class)
+ .columnTypes(Integer.class, Byte.class, Short.class, String.class, byte[].class, byte[].class)
.check();
}
diff --git a/modules/sql-engine/src/main/codegen/config.fmpp b/modules/sql-engine/src/main/codegen/config.fmpp
index ada5e57..57d3f80 100644
--- a/modules/sql-engine/src/main/codegen/config.fmpp
+++ b/modules/sql-engine/src/main/codegen/config.fmpp
@@ -38,6 +38,7 @@ data: {
"org.apache.ignite.internal.sql.engine.sql.IgniteSqlCreateTableOption",
"org.apache.ignite.internal.sql.engine.sql.IgniteSqlCreateTableOptionEnum",
"org.apache.ignite.internal.sql.engine.sql.IgniteSqlDropIndex",
+ "org.apache.ignite.internal.sql.engine.sql.IgniteSqlIntervalTypeNameSpec",
"org.apache.calcite.sql.ddl.SqlDdlNodes",
]
diff --git a/modules/sql-engine/src/main/codegen/includes/parserImpls.ftl b/modules/sql-engine/src/main/codegen/includes/parserImpls.ftl
index 9a140e8..c95183b 100644
--- a/modules/sql-engine/src/main/codegen/includes/parserImpls.ftl
+++ b/modules/sql-engine/src/main/codegen/includes/parserImpls.ftl
@@ -66,6 +66,32 @@ void CreateTableOption(List<SqlNode> list) :
}
}
+SqlDataTypeSpec DataTypeEx() :
+{
+ final SqlDataTypeSpec dt;
+}
+{
+ (
+ dt = DataType()
+ |
+ dt = IntervalType()
+ )
+ {
+ return dt;
+ }
+}
+
+SqlDataTypeSpec IntervalType() :
+{
+ final Span s;
+ final SqlIntervalQualifier intervalQualifier;
+}
+{
+ <INTERVAL> { s = span(); } intervalQualifier = IntervalQualifier() {
+ return new SqlDataTypeSpec(new IgniteSqlIntervalTypeNameSpec(intervalQualifier, s.end(this)), s.pos());
+ }
+}
+
void TableElement(List<SqlNode> list) :
{
final SqlDataTypeSpec type;
@@ -77,7 +103,7 @@ void TableElement(List<SqlNode> list) :
SqlIdentifier id = null;
}
{
- id = SimpleIdentifier() type = DataType() nullable = NullableOptDefaultNull()
+ id = SimpleIdentifier() type = DataTypeEx() nullable = NullableOptDefaultNull()
(
<DEFAULT_> { s.add(this); } dflt = Literal() {
strategy = ColumnStrategy.DEFAULT;
@@ -257,7 +283,7 @@ void InfixCast(List<Object> list, ExprContext exprContext, Span s) :
<INFIX_CAST> {
checkNonQueryExpression(exprContext);
}
- dt = DataType() {
+ dt = DataTypeEx() {
list.add(
new SqlParserUtil.ToTreeListItem(SqlLibraryOperators.INFIX_CAST,
s.pos()));
@@ -293,7 +319,7 @@ SqlNode ColumnWithType() :
}
{
id = SimpleIdentifier()
- type = DataType()
+ type = DataTypeEx()
[
<NOT> <NULL> {
nullable = false;
diff --git a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/exec/exp/IgniteBuiltInMethod.java b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/exec/exp/IgniteBuiltInMethod.java
index db8c1c1..953b9d7 100644
--- a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/exec/exp/IgniteBuiltInMethod.java
+++ b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/exec/exp/IgniteBuiltInMethod.java
@@ -19,6 +19,8 @@ package org.apache.ignite.internal.sql.engine.exec.exp;
import java.lang.reflect.Method;
import org.apache.calcite.linq4j.tree.Types;
+import org.apache.calcite.sql.SqlIntervalQualifier;
+import org.apache.calcite.sql.parser.SqlParserUtil;
/**
* Built-in methods.
@@ -26,7 +28,11 @@ import org.apache.calcite.linq4j.tree.Types;
public enum IgniteBuiltInMethod {
SYSTEM_RANGE2(IgniteSqlFunctions.class, "systemRange", Object.class, Object.class),
- SYSTEM_RANGE3(IgniteSqlFunctions.class, "systemRange", Object.class, Object.class, Object.class);
+ SYSTEM_RANGE3(IgniteSqlFunctions.class, "systemRange", Object.class, Object.class, Object.class),
+
+ PARSE_INTERVAL_YEAR_MONTH(SqlParserUtil.class, "intervalToMonths", String.class, SqlIntervalQualifier.class),
+
+ PARSE_INTERVAL_DAY_TIME(SqlParserUtil.class, "intervalToMillis", String.class, SqlIntervalQualifier.class);
public final Method method;
diff --git a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/exec/exp/RexImpTable.java b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/exec/exp/RexImpTable.java
index dbd52f4..3c5ee07 100644
--- a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/exec/exp/RexImpTable.java
+++ b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/exec/exp/RexImpTable.java
@@ -1320,6 +1320,8 @@ public class RexImpTable {
Expression operand = argValueList.get(1);
final SqlTypeName sqlTypeName =
call.operands.get(1).getType().getSqlTypeName();
+ final boolean isIntervalType = SqlTypeUtil.isInterval(call.operands.get(1).getType());
+
switch (unit) {
case MILLENNIUM:
case CENTURY:
@@ -1372,7 +1374,7 @@ public class RexImpTable {
return Expressions.constant(0L);
}
- operand = mod(operand, TimeUnit.MINUTE.multiplier.longValue());
+ operand = mod(operand, TimeUnit.MINUTE.multiplier.longValue(), !isIntervalType);
return Expressions.multiply(
operand, Expressions.constant((long) (1 / unit.multiplier.doubleValue())));
case EPOCH:
@@ -1426,7 +1428,10 @@ public class RexImpTable {
// No-Op.
}
- operand = mod(operand, getFactor(unit));
+ // According to SQL standard result for interval data types should have the same sign as the source,
+ // but QUARTER is not covered by standard and negative values for QUARTER make no sense.
+ operand = mod(operand, getFactor(unit), unit == TimeUnit.QUARTER || !isIntervalType);
+
if (unit == TimeUnit.QUARTER) {
operand = Expressions.subtract(operand, Expressions.constant(1L));
}
@@ -1441,12 +1446,12 @@ public class RexImpTable {
}
}
- private static Expression mod(Expression operand, long factor) {
+ private static Expression mod(Expression operand, long factor, boolean floorMod) {
if (factor == 1L) {
return operand;
} else {
- return Expressions.call(BuiltInMethod.FLOOR_MOD.method,
- operand, Expressions.constant(factor));
+ return floorMod ? Expressions.call(BuiltInMethod.FLOOR_MOD.method, operand, Expressions.constant(factor)) :
+ Expressions.modulo(operand, Expressions.constant(factor));
}
}
diff --git a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/exec/exp/RexToLixTranslator.java b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/exec/exp/RexToLixTranslator.java
index 4017f24..bd073c8 100644
--- a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/exec/exp/RexToLixTranslator.java
+++ b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/exec/exp/RexToLixTranslator.java
@@ -20,6 +20,7 @@ package org.apache.ignite.internal.sql.engine.exec.exp;
import static org.apache.calcite.sql.fun.SqlStdOperatorTable.CASE;
import static org.apache.calcite.sql.fun.SqlStdOperatorTable.SEARCH;
+import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.Type;
import java.math.BigDecimal;
@@ -66,6 +67,7 @@ import org.apache.calcite.runtime.Geometries;
import org.apache.calcite.sql.SqlIntervalQualifier;
import org.apache.calcite.sql.SqlOperator;
import org.apache.calcite.sql.fun.SqlStdOperatorTable;
+import org.apache.calcite.sql.parser.SqlParserPos;
import org.apache.calcite.sql.type.SqlTypeName;
import org.apache.calcite.sql.type.SqlTypeUtil;
import org.apache.calcite.sql.validate.SqlConformance;
@@ -514,6 +516,43 @@ public class RexToLixTranslator implements RexVisitor<RexToLixTranslator.Result>
// No-Op.
}
break;
+ case INTERVAL_YEAR:
+ case INTERVAL_YEAR_MONTH:
+ case INTERVAL_MONTH:
+ case INTERVAL_DAY:
+ case INTERVAL_DAY_HOUR:
+ case INTERVAL_DAY_MINUTE:
+ case INTERVAL_DAY_SECOND:
+ case INTERVAL_HOUR:
+ case INTERVAL_HOUR_MINUTE:
+ case INTERVAL_HOUR_SECOND:
+ case INTERVAL_MINUTE:
+ case INTERVAL_MINUTE_SECOND:
+ case INTERVAL_SECOND:
+ switch (sourceType.getSqlTypeName().getFamily()) {
+ case CHARACTER:
+ SqlIntervalQualifier intervalQualifier = targetType.getIntervalQualifier();
+
+ Method method = intervalQualifier.isYearMonth()
+ ? IgniteBuiltInMethod.PARSE_INTERVAL_YEAR_MONTH.method
+ : IgniteBuiltInMethod.PARSE_INTERVAL_DAY_TIME.method;
+
+ convert = Expressions.call(
+ method,
+ operand,
+ Expressions.new_(SqlIntervalQualifier.class,
+ Expressions.constant(intervalQualifier.getStartUnit()),
+ Expressions.constant(intervalQualifier.getStartPrecisionPreservingDefault()),
+ Expressions.constant(intervalQualifier.getEndUnit()),
+ Expressions.constant(intervalQualifier.getFractionalSecondPrecisionPreservingDefault()),
+ Expressions.field(null, SqlParserPos.class, "ZERO")
+ )
+ );
+ break;
+ default:
+ // No-Op.
+ }
+ break;
default:
// No-Op.
}
diff --git a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/externalize/RelJson.java b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/externalize/RelJson.java
index 6f12e78..64646b9 100644
--- a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/externalize/RelJson.java
+++ b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/externalize/RelJson.java
@@ -626,16 +626,16 @@ class RelJson {
SqlTypeName sqlTypeName = toEnum(map.get("type"));
Integer precision = (Integer) map.get("precision");
Integer scale = (Integer) map.get("scale");
+ RelDataType type;
+
if (SqlTypeName.INTERVAL_TYPES.contains(sqlTypeName)) {
TimeUnit startUnit = sqlTypeName.getStartUnit();
TimeUnit endUnit = sqlTypeName.getEndUnit();
- return typeFactory.createSqlIntervalType(
+ type = typeFactory.createSqlIntervalType(
new SqlIntervalQualifier(startUnit, endUnit, SqlParserPos.ZERO));
} else if (sqlTypeName == SqlTypeName.ARRAY) {
- return typeFactory.createArrayType(toType(typeFactory, map.get("elementType")), -1);
- }
- RelDataType type;
- if (precision == null) {
+ type = typeFactory.createArrayType(toType(typeFactory, map.get("elementType")), -1);
+ } else if (precision == null) {
type = typeFactory.createSqlType(sqlTypeName);
} else if (scale == null) {
type = typeFactory.createSqlType(sqlTypeName, precision);
diff --git a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/prepare/IgniteConvertletTable.java b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/prepare/IgniteConvertletTable.java
new file mode 100644
index 0000000..26b30da
--- /dev/null
+++ b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/prepare/IgniteConvertletTable.java
@@ -0,0 +1,128 @@
+/*
+ * 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.prepare;
+
+import java.math.BigDecimal;
+import java.util.List;
+import org.apache.calcite.avatica.util.DateTimeUtils;
+import org.apache.calcite.avatica.util.TimeUnit;
+import org.apache.calcite.rel.type.RelDataType;
+import org.apache.calcite.rex.RexBuilder;
+import org.apache.calcite.rex.RexCall;
+import org.apache.calcite.rex.RexNode;
+import org.apache.calcite.sql.SqlCall;
+import org.apache.calcite.sql.SqlIntervalQualifier;
+import org.apache.calcite.sql.SqlLiteral;
+import org.apache.calcite.sql.fun.SqlStdOperatorTable;
+import org.apache.calcite.sql.parser.SqlParserPos;
+import org.apache.calcite.sql.type.SqlTypeName;
+import org.apache.calcite.sql.type.SqlTypeUtil;
+import org.apache.calcite.sql2rel.ReflectiveConvertletTable;
+import org.apache.calcite.sql2rel.SqlRexContext;
+import org.apache.calcite.sql2rel.SqlRexConvertlet;
+import org.apache.calcite.sql2rel.SqlRexConvertletTable;
+import org.apache.calcite.sql2rel.StandardConvertletTable;
+
+/**
+ * Implementation of {@link SqlRexConvertletTable}.
+ */
+public class IgniteConvertletTable extends ReflectiveConvertletTable {
+ public static final IgniteConvertletTable INSTANCE = new IgniteConvertletTable();
+
+ private IgniteConvertletTable() {
+ // Replace Calcite's convertlet with our own.
+ registerOp(SqlStdOperatorTable.TIMESTAMP_DIFF, new TimestampDiffConvertlet());
+ }
+
+ /** {@inheritDoc} */
+ @Override public SqlRexConvertlet get(SqlCall call) {
+ SqlRexConvertlet res = super.get(call);
+
+ return res == null ? StandardConvertletTable.INSTANCE.get(call) : res;
+ }
+
+ /** Convertlet that handles the {@code TIMESTAMPDIFF} function. */
+ private static class TimestampDiffConvertlet implements SqlRexConvertlet {
+ /** {@inheritDoc} */
+ @Override public RexNode convertCall(SqlRexContext cx, SqlCall call) {
+ // TIMESTAMPDIFF(unit, t1, t2)
+ // => (t2 - t1) UNIT
+ final RexBuilder rexBuilder = cx.getRexBuilder();
+ final SqlLiteral unitLiteral = call.operand(0);
+ TimeUnit unit = unitLiteral.getValueAs(TimeUnit.class);
+ BigDecimal multiplier = BigDecimal.ONE;
+ BigDecimal divider = BigDecimal.ONE;
+ SqlTypeName sqlTypeName = unit == TimeUnit.NANOSECOND
+ ? SqlTypeName.BIGINT
+ : SqlTypeName.INTEGER;
+ switch (unit) {
+ case MICROSECOND:
+ case MILLISECOND:
+ case NANOSECOND:
+ divider = unit.multiplier;
+ unit = TimeUnit.MILLISECOND;
+ break;
+ case WEEK:
+ multiplier = BigDecimal.valueOf(DateTimeUtils.MILLIS_PER_SECOND);
+ divider = unit.multiplier;
+ unit = TimeUnit.SECOND;
+ break;
+ case QUARTER:
+ divider = unit.multiplier;
+ unit = TimeUnit.MONTH;
+ break;
+ default:
+ break;
+ }
+ final SqlIntervalQualifier qualifier =
+ new SqlIntervalQualifier(unit, null, SqlParserPos.ZERO);
+ final RexNode op2 = cx.convertExpression(call.operand(2));
+ final RexNode op1 = cx.convertExpression(call.operand(1));
+ final RelDataType intervalType =
+ cx.getTypeFactory().createTypeWithNullability(
+ cx.getTypeFactory().createSqlIntervalType(qualifier),
+ op1.getType().isNullable() || op2.getType().isNullable());
+ final RexCall rexCall = (RexCall) rexBuilder.makeCall(
+ intervalType, SqlStdOperatorTable.MINUS_DATE,
+ List.of(op2, op1));
+ final RelDataType intType =
+ cx.getTypeFactory().createTypeWithNullability(
+ cx.getTypeFactory().createSqlType(sqlTypeName),
+ SqlTypeUtil.containsNullable(rexCall.getType()));
+
+ RexNode e;
+
+ // Since Calcite converts internal time representation to seconds during cast we need our own cast
+ // method to keep fraction of seconds.
+ if (unit == TimeUnit.MILLISECOND) {
+ e = makeCastMilliseconds(rexBuilder, intType, rexCall);
+ } else {
+ e = rexBuilder.makeCast(intType, rexCall);
+ }
+
+ return rexBuilder.multiplyDivide(e, multiplier, divider);
+ }
+
+ /**
+ * Creates a call to cast milliseconds interval.
+ */
+ static RexNode makeCastMilliseconds(RexBuilder builder, RelDataType type, RexNode exp) {
+ return builder.ensureType(type, builder.decodeIntervalOrDecimal(exp), false);
+ }
+ }
+}
diff --git a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/prepare/IgniteTypeCoercion.java b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/prepare/IgniteTypeCoercion.java
new file mode 100644
index 0000000..55a69a5
--- /dev/null
+++ b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/prepare/IgniteTypeCoercion.java
@@ -0,0 +1,58 @@
+/*
+ * 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.prepare;
+
+import org.apache.calcite.rel.type.RelDataType;
+import org.apache.calcite.rel.type.RelDataTypeFactory;
+import org.apache.calcite.sql.SqlNode;
+import org.apache.calcite.sql.type.SqlTypeUtil;
+import org.apache.calcite.sql.validate.SqlValidator;
+import org.apache.calcite.sql.validate.SqlValidatorScope;
+import org.apache.calcite.sql.validate.implicit.TypeCoercionImpl;
+
+/** Implicit type cast implementation. */
+public class IgniteTypeCoercion extends TypeCoercionImpl {
+ public IgniteTypeCoercion(RelDataTypeFactory typeFactory, SqlValidator validator) {
+ super(typeFactory, validator);
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ protected boolean needToCast(SqlValidatorScope scope, SqlNode node, RelDataType toType) {
+ if (SqlTypeUtil.isInterval(toType)) {
+ RelDataType fromType = validator.deriveType(scope, node);
+
+ if (SqlTypeUtil.isInterval(fromType)) {
+ // Two different families of intervals: INTERVAL_DAY_TIME and INTERVAL_YEAR_MONTH.
+ return fromType.getSqlTypeName().getFamily() != toType.getSqlTypeName().getFamily();
+ }
+ } else if (SqlTypeUtil.isIntType(toType)) {
+ RelDataType fromType = validator.deriveType(scope, node);
+
+ if (fromType == null) {
+ return false;
+ }
+
+ if (SqlTypeUtil.isIntType(fromType) && fromType.getSqlTypeName() != toType.getSqlTypeName()) {
+ return true;
+ }
+ }
+
+ return super.needToCast(scope, node, toType);
+ }
+}
diff --git a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/sql/IgniteSqlIntervalTypeNameSpec.java b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/sql/IgniteSqlIntervalTypeNameSpec.java
new file mode 100644
index 0000000..e063ca4
--- /dev/null
+++ b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/sql/IgniteSqlIntervalTypeNameSpec.java
@@ -0,0 +1,62 @@
+/*
+ * 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.sql;
+
+import org.apache.calcite.rel.type.RelDataType;
+import org.apache.calcite.sql.SqlIdentifier;
+import org.apache.calcite.sql.SqlIntervalQualifier;
+import org.apache.calcite.sql.SqlTypeNameSpec;
+import org.apache.calcite.sql.SqlWriter;
+import org.apache.calcite.sql.parser.SqlParserPos;
+import org.apache.calcite.sql.validate.SqlValidator;
+import org.apache.calcite.util.Litmus;
+
+/**
+ * A SQL type name specification of interval types.
+ */
+public class IgniteSqlIntervalTypeNameSpec extends SqlTypeNameSpec {
+ private final SqlIntervalQualifier sqlIntervalQualifier;
+
+ public IgniteSqlIntervalTypeNameSpec(SqlIntervalQualifier intervalQualifier, SqlParserPos pos) {
+ super(new SqlIdentifier(intervalQualifier.typeName().name(), pos), pos);
+ sqlIntervalQualifier = intervalQualifier;
+ }
+
+ /** {@inheritDoc} */
+ @Override public RelDataType deriveType(SqlValidator validator) {
+ return validator.getTypeFactory().createSqlIntervalType(sqlIntervalQualifier);
+ }
+
+ /** {@inheritDoc} */
+ @Override public void unparse(SqlWriter writer, int leftPrec, int rightPrec) {
+ writer.keyword("INTERVAL");
+
+ getTypeName().unparse(writer, leftPrec, rightPrec);
+ }
+
+ /** {@inheritDoc} */
+ @Override public boolean equalsDeep(SqlTypeNameSpec spec, Litmus litmus) {
+ if (!(spec instanceof IgniteSqlIntervalTypeNameSpec)) {
+ return false;
+ }
+
+ IgniteSqlIntervalTypeNameSpec that = (IgniteSqlIntervalTypeNameSpec) spec;
+
+ return getTypeName().equalsDeep(that.getTypeName(), litmus);
+ }
+}
diff --git a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/type/IgniteTypeFactory.java b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/type/IgniteTypeFactory.java
index a18746e..a6df9bc 100644
--- a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/type/IgniteTypeFactory.java
+++ b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/type/IgniteTypeFactory.java
@@ -26,18 +26,22 @@ import java.math.BigInteger;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.sql.Timestamp;
+import java.time.Duration;
import java.time.LocalDateTime;
import java.time.LocalTime;
+import java.time.Period;
import java.util.List;
-import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import org.apache.calcite.avatica.util.ByteString;
+import org.apache.calcite.avatica.util.TimeUnit;
import org.apache.calcite.jdbc.JavaTypeFactoryImpl;
import org.apache.calcite.rel.type.RelDataType;
import org.apache.calcite.rel.type.RelDataTypeSystem;
import org.apache.calcite.runtime.Geometries;
+import org.apache.calcite.sql.SqlIntervalQualifier;
import org.apache.calcite.sql.SqlUtil;
+import org.apache.calcite.sql.parser.SqlParserPos;
import org.apache.calcite.sql.type.BasicSqlType;
import org.apache.calcite.sql.type.IntervalSqlType;
import org.apache.ignite.internal.schema.configuration.SchemaConfigurationConverter;
@@ -47,13 +51,23 @@ import org.apache.ignite.schema.definition.ColumnType;
* Ignite type factory.
*/
public class IgniteTypeFactory extends JavaTypeFactoryImpl {
+ /** Interval qualifier to create year-month interval types. */
+ private static final SqlIntervalQualifier INTERVAL_QUALIFIER_YEAR_MONTH = new SqlIntervalQualifier(TimeUnit.YEAR,
+ TimeUnit.MONTH, SqlParserPos.ZERO);
+
+ /** Interval qualifier to create day-time interval types. */
+ private static final SqlIntervalQualifier INTERVAL_QUALIFIER_DAY_TIME = new SqlIntervalQualifier(TimeUnit.DAY,
+ TimeUnit.SECOND, SqlParserPos.ZERO);
+
+ /** Default charset. */
+ private final Charset charset;
/**
* Constructor.
* TODO Documentation https://issues.apache.org/jira/browse/IGNITE-15859
*/
public IgniteTypeFactory() {
- super(IgniteTypeSystem.INSTANCE);
+ this(IgniteTypeSystem.INSTANCE);
}
/**
@@ -63,6 +77,14 @@ public class IgniteTypeFactory extends JavaTypeFactoryImpl {
*/
public IgniteTypeFactory(RelDataTypeSystem typeSystem) {
super(typeSystem);
+
+ if (SqlUtil.translateCharacterSetName(Charset.defaultCharset().name()) != null) {
+ // Use JVM default charset rather then Calcite default charset (ISO-8859-1).
+ charset = Charset.defaultCharset();
+ } else {
+ // If JVM default charset is not supported by Calcite - use UTF-8.
+ charset = StandardCharsets.UTF_8;
+ }
}
/** {@inheritDoc} */
@@ -192,11 +214,13 @@ public class IgniteTypeFactory extends JavaTypeFactoryImpl {
case TIMESTAMP_WITH_LOCAL_TIME_ZONE:
return LocalDateTime.class;
case INTEGER:
+ return type.isNullable() ? Integer.class : int.class;
case INTERVAL_YEAR:
case INTERVAL_YEAR_MONTH:
case INTERVAL_MONTH:
- return type.isNullable() ? Integer.class : int.class;
+ return Period.class;
case BIGINT:
+ return type.isNullable() ? Long.class : long.class;
case INTERVAL_DAY:
case INTERVAL_DAY_HOUR:
case INTERVAL_DAY_MINUTE:
@@ -207,7 +231,7 @@ public class IgniteTypeFactory extends JavaTypeFactoryImpl {
case INTERVAL_MINUTE:
case INTERVAL_MINUTE_SECOND:
case INTERVAL_SECOND:
- return type.isNullable() ? Long.class : long.class;
+ return Duration.class;
case SMALLINT:
return type.isNullable() ? Short.class : short.class;
case TINYINT:
@@ -266,14 +290,31 @@ public class IgniteTypeFactory extends JavaTypeFactoryImpl {
/** {@inheritDoc} */
@Override
public Charset getDefaultCharset() {
- // Use JVM default charset rather then Calcite default charset (ISO-8859-1).
- Charset jvmDefault = Charset.defaultCharset();
+ return charset;
+ }
- if (SqlUtil.translateCharacterSetName(jvmDefault.name().toUpperCase(Locale.ROOT)) == null) {
- jvmDefault = StandardCharsets.UTF_8;
+ /** {@inheritDoc} */
+ @Override public RelDataType toSql(RelDataType type) {
+ if (type instanceof JavaType) {
+ Class<?> clazz = ((JavaType) type).getJavaClass();
+
+ if (clazz == Duration.class) {
+ return createTypeWithNullability(createSqlIntervalType(INTERVAL_QUALIFIER_DAY_TIME), true);
+ } else if (clazz == Period.class) {
+ return createTypeWithNullability(createSqlIntervalType(INTERVAL_QUALIFIER_YEAR_MONTH), true);
+ }
+ }
+
+ return super.toSql(type);
+ }
+
+ /** {@inheritDoc} */
+ @Override public RelDataType createType(Type type) {
+ if (type == Duration.class || type == Period.class) {
+ return createJavaType((Class<?>) type);
}
- return jvmDefault;
+ return super.createType(type);
}
private boolean allEquals(List<RelDataType> types) {
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 4b60ea9..7715808 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
@@ -95,6 +95,8 @@ import org.apache.ignite.internal.sql.engine.exec.exp.RexExecutorImpl;
import org.apache.ignite.internal.sql.engine.metadata.cost.IgniteCostFactory;
import org.apache.ignite.internal.sql.engine.prepare.AbstractMultiStepPlan;
import org.apache.ignite.internal.sql.engine.prepare.ExplainPlan;
+import org.apache.ignite.internal.sql.engine.prepare.IgniteConvertletTable;
+import org.apache.ignite.internal.sql.engine.prepare.IgniteTypeCoercion;
import org.apache.ignite.internal.sql.engine.prepare.MultiStepPlan;
import org.apache.ignite.internal.sql.engine.prepare.PlanningContext;
import org.apache.ignite.internal.sql.engine.prepare.QueryPlan;
@@ -138,6 +140,7 @@ public final class Commons {
.build()
)
)
+ .convertletTable(IgniteConvertletTable.INSTANCE)
.parserConfig(
SqlParser.config()
.withParserFactory(IgniteSqlParserImpl.FACTORY)
@@ -146,7 +149,8 @@ public final class Commons {
.sqlValidatorConfig(SqlValidator.Config.DEFAULT
.withIdentifierExpansion(true)
.withDefaultNullCollation(NullCollation.LOW)
- .withSqlConformance(IgniteSqlConformance.INSTANCE))
+ .withSqlConformance(IgniteSqlConformance.INSTANCE)
+ .withTypeCoercionFactory(IgniteTypeCoercion::new))
// Dialects support.
.operatorTable(SqlOperatorTables.chain(
SqlLibraryOperatorTableFactory.INSTANCE
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 fa82561..10b2217 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
@@ -24,12 +24,14 @@ import static org.apache.ignite.internal.util.CollectionUtils.nullOrEmpty;
import java.lang.reflect.Type;
import java.sql.Timestamp;
+import java.time.Duration;
+import java.time.Period;
import java.util.Arrays;
-import java.util.EnumSet;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.TimeZone;
+import java.util.concurrent.TimeUnit;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
@@ -61,17 +63,13 @@ import org.jetbrains.annotations.Nullable;
* TODO Documentation https://issues.apache.org/jira/browse/IGNITE-15859
*/
public class TypeUtils {
- private static final EnumSet<SqlTypeName> CONVERTABLE_SQL_TYPES = EnumSet.of(
- SqlTypeName.DATE,
- SqlTypeName.TIME,
- SqlTypeName.TIMESTAMP
- );
-
private static final Set<Type> CONVERTABLE_TYPES = Set.of(
java.util.Date.class,
java.sql.Date.class,
java.sql.Time.class,
- java.sql.Timestamp.class
+ java.sql.Timestamp.class,
+ Duration.class,
+ Period.class
);
/**
@@ -266,10 +264,12 @@ public class TypeUtils {
}
private static Function<Object, Object> fieldConverter(ExecutionContext<?> ectx, RelDataType fieldType) {
- if (CONVERTABLE_SQL_TYPES.contains(fieldType.getSqlTypeName())) {
- Type storageType = ectx.getTypeFactory().getJavaClass(fieldType);
+ Type storageType = ectx.getTypeFactory().getJavaClass(fieldType);
+
+ if (isConvertableType(storageType)) {
return v -> fromInternal(ectx, v, storageType);
}
+
return Function.identity();
}
@@ -286,12 +286,13 @@ public class TypeUtils {
* TODO Documentation https://issues.apache.org/jira/browse/IGNITE-15859
*/
public static boolean isConvertableType(RelDataType type) {
- return CONVERTABLE_SQL_TYPES.contains(type.getSqlTypeName());
+ return type instanceof RelDataTypeFactoryImpl.JavaType
+ && isConvertableType(((RelDataTypeFactoryImpl.JavaType) type).getJavaClass());
}
private static boolean hasConvertableFields(RelDataType resultType) {
return RelOptUtil.getFieldTypeList(resultType).stream()
- .anyMatch(t -> CONVERTABLE_SQL_TYPES.contains(t.getSqlTypeName()));
+ .anyMatch(TypeUtils::isConvertableType);
}
/**
@@ -319,6 +320,11 @@ public class TypeUtils {
return SqlFunctions.toLong((java.util.Date) val, DataContext.Variable.TIME_ZONE.get(ectx));
} else if (storageType == java.util.Date.class) {
return SqlFunctions.toLong((java.util.Date) val, DataContext.Variable.TIME_ZONE.get(ectx));
+ } else if (storageType == Duration.class) {
+ return TimeUnit.SECONDS.toMillis(((Duration) val).getSeconds())
+ + TimeUnit.NANOSECONDS.toMillis(((Duration) val).getNano());
+ } else if (storageType == Period.class) {
+ return (int) ((Period) val).toTotalMonths();
} else {
return val;
}
@@ -340,6 +346,10 @@ public class TypeUtils {
return new Timestamp((Long) val - DataContext.Variable.TIME_ZONE.<TimeZone>get(ectx).getOffset((Long) val));
} else if (storageType == java.util.Date.class && val instanceof Long) {
return new java.util.Date((Long) val - DataContext.Variable.TIME_ZONE.<TimeZone>get(ectx).getOffset((Long) val));
+ } else if (storageType == Duration.class && val instanceof Long) {
+ return Duration.ofMillis((Long) val);
+ } else if (storageType == Period.class && val instanceof Integer) {
+ return Period.of((Integer) val / 12, (Integer) val % 12, 0);
} else {
return val;
}
@@ -385,7 +395,7 @@ public class TypeUtils {
case OTHER:
return NativeTypes.blobOf(type.getPrecision());
default:
- assert false : "Unexpected type of result: " + type;
+ assert false : "Unexpected type of result: " + type.getSqlTypeName();
return null;
}
}
diff --git a/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/planner/TableSpoolPlannerTest.java b/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/planner/TableSpoolPlannerTest.java
index 75ab749..f1144ad 100644
--- a/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/planner/TableSpoolPlannerTest.java
+++ b/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/planner/TableSpoolPlannerTest.java
@@ -20,8 +20,8 @@ package org.apache.ignite.internal.sql.engine.planner;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import org.apache.calcite.plan.RelOptUtil;
-import org.apache.calcite.rel.RelNode;
import org.apache.calcite.rel.type.RelDataTypeFactory;
+import org.apache.ignite.internal.sql.engine.rel.IgniteRel;
import org.apache.ignite.internal.sql.engine.rel.IgniteTableSpool;
import org.apache.ignite.internal.sql.engine.schema.IgniteSchema;
import org.apache.ignite.internal.sql.engine.trait.IgniteDistribution;
@@ -79,7 +79,7 @@ public class TableSpoolPlannerTest extends AbstractPlannerTest {
+ "from t0 "
+ "join t1 on t0.jid > t1.jid";
- RelNode phys = physicalPlan(sql, publicSchema,
+ IgniteRel phys = physicalPlan(sql, publicSchema,
"MergeJoinConverter", "NestedLoopJoinConverter", "FilterSpoolMergeRule");
assertNotNull(phys);
@@ -87,5 +87,7 @@ public class TableSpoolPlannerTest extends AbstractPlannerTest {
IgniteTableSpool tblSpool = findFirstNode(phys, byClass(IgniteTableSpool.class));
assertNotNull(tblSpool, "Invalid plan:\n" + RelOptUtil.toString(phys));
+
+ checkSplitAndSerialization(phys, publicSchema);
}
}