You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ignite.apache.org by al...@apache.org on 2021/09/21 14:13:37 UTC

[ignite] branch sql-calcite updated: IGNITE-15109 Fix TIMESTAMPDIFF for MICROSECOND time unit - Fixes #9368.

This is an automated email from the ASF dual-hosted git repository.

alexpl pushed a commit to branch sql-calcite
in repository https://gitbox.apache.org/repos/asf/ignite.git


The following commit(s) were added to refs/heads/sql-calcite by this push:
     new bcf055b  IGNITE-15109 Fix TIMESTAMPDIFF for MICROSECOND time unit - Fixes #9368.
bcf055b is described below

commit bcf055b76771e0b90164f5293cb20b2fe7abb85f
Author: Aleksey Plekhanov <pl...@gmail.com>
AuthorDate: Tue Sep 21 17:03:32 2021 +0300

    IGNITE-15109 Fix TIMESTAMPDIFF for MICROSECOND time unit - Fixes #9368.
    
    Signed-off-by: Aleksey Plekhanov <pl...@gmail.com>
---
 .../query/calcite/CalciteQueryProcessor.java       |   2 +
 .../calcite/prepare/IgniteConvertletTable.java     | 130 +++++++++++++++++++++
 .../processors/query/calcite/FunctionsTest.java    |  10 ++
 .../sql/function/timestamp/test_timestampdiff.test |  30 +++++
 .../timestamp/test_timestampdiff.test_ignore       |  12 +-
 5 files changed, 173 insertions(+), 11 deletions(-)

diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/CalciteQueryProcessor.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/CalciteQueryProcessor.java
index 7b4a909..00bdc66 100644
--- a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/CalciteQueryProcessor.java
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/CalciteQueryProcessor.java
@@ -59,6 +59,7 @@ import org.apache.ignite.internal.processors.query.calcite.metadata.AffinityServ
 import org.apache.ignite.internal.processors.query.calcite.metadata.MappingService;
 import org.apache.ignite.internal.processors.query.calcite.metadata.MappingServiceImpl;
 import org.apache.ignite.internal.processors.query.calcite.metadata.cost.IgniteCostFactory;
+import org.apache.ignite.internal.processors.query.calcite.prepare.IgniteConvertletTable;
 import org.apache.ignite.internal.processors.query.calcite.prepare.QueryPlanCache;
 import org.apache.ignite.internal.processors.query.calcite.prepare.QueryPlanCacheImpl;
 import org.apache.ignite.internal.processors.query.calcite.schema.SchemaHolder;
@@ -94,6 +95,7 @@ public class CalciteQueryProcessor extends GridProcessorAdapter implements Query
                     .build()
             )
         )
+        .convertletTable(IgniteConvertletTable.INSTANCE)
         .parserConfig(
             SqlParser.config()
                 .withParserFactory(IgniteSqlParserImpl.FACTORY)
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/prepare/IgniteConvertletTable.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/prepare/IgniteConvertletTable.java
new file mode 100644
index 0000000..39ff956
--- /dev/null
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/prepare/IgniteConvertletTable.java
@@ -0,0 +1,130 @@
+/*
+ * 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.processors.query.calcite.prepare;
+
+import java.math.BigDecimal;
+import com.google.common.collect.ImmutableList;
+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.StandardConvertletTable;
+import org.checkerframework.checker.nullness.qual.Nullable;
+
+/**
+ * Ignite convertlet table.
+ */
+public class IgniteConvertletTable extends ReflectiveConvertletTable {
+    /** Instance. */
+    public static final IgniteConvertletTable INSTANCE = new IgniteConvertletTable();
+
+    /** */
+    private IgniteConvertletTable() {
+        // Replace Calcite's convertlet with our own.
+        registerOp(SqlStdOperatorTable.TIMESTAMP_DIFF, new TimestampDiffConvertlet());
+    }
+
+    /** {@inheritDoc} */
+    @Nullable
+    @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,
+                ImmutableList.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/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/FunctionsTest.java b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/FunctionsTest.java
index 5bd22df..7a331bd 100644
--- a/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/FunctionsTest.java
+++ b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/FunctionsTest.java
@@ -54,6 +54,16 @@ public class FunctionsTest extends GridCommonAbstractTest {
 
     /** */
     @Test
+    public void testTimestampDiffWithFractionsOfSecond() {
+        checkQuery("SELECT TIMESTAMPDIFF(MICROSECOND, TIMESTAMP '2022-02-01 10:30:28.000', " +
+            "TIMESTAMP '2022-02-01 10:30:28.128')").returns(128000).check();
+
+        checkQuery("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() {
         checkQuery("SELECT LENGTH('TEST')").returns(4).check();
         checkQuery("SELECT LENGTH(NULL)").returns(NULL_RESULT).check();
diff --git a/modules/calcite/src/test/sql/function/timestamp/test_timestampdiff.test b/modules/calcite/src/test/sql/function/timestamp/test_timestampdiff.test
index 3d71ce6..6d3ef34 100644
--- a/modules/calcite/src/test/sql/function/timestamp/test_timestampdiff.test
+++ b/modules/calcite/src/test/sql/function/timestamp/test_timestampdiff.test
@@ -106,3 +106,33 @@ query T
 SELECT TIMESTAMPDIFF(YEAR, TIMESTAMP '2022-02-02 11:32:26', TIMESTAMP '2099-01-01 10:30:25')
 ----
 76
+
+query T
+SELECT TIMESTAMPDIFF(MICROSECOND, TIMESTAMP '2022-02-01 10:30:28.000', TIMESTAMP '2022-02-01 10:30:28.128')
+----
+128000
+
+query T
+SELECT TIMESTAMPDIFF(MICROSECOND, TIMESTAMP '2022-02-01 10:30:26.000', TIMESTAMP '2022-02-01 10:30:28.122')
+----
+2122000
+
+query T
+SELECT TIMESTAMPDIFF(NANOSECOND, TIMESTAMP '2022-02-01 10:30:28.000', TIMESTAMP '2022-02-01 10:30:28.128')
+----
+128000000
+
+query T
+SELECT TIMESTAMPDIFF(NANOSECOND, TIMESTAMP '2022-02-01 10:30:26.000', TIMESTAMP '2022-02-01 10:30:28.122')
+----
+2122000000
+
+query T
+SELECT TIMESTAMPDIFF(NANOSECOND, TIMESTAMP '2022-02-01 10:30:26.000', TIMESTAMP '2022-05-01 10:30:28.122')
+----
+7689602122000000
+
+query T
+SELECT TIMESTAMPDIFF(NANOSECOND, TIMESTAMP '2022-05-01 10:30:28.122', TIMESTAMP '2022-02-01 10:30:26.398')
+----
+-7689601724000000
diff --git a/modules/calcite/src/test/sql/function/timestamp/test_timestampdiff.test_ignore b/modules/calcite/src/test/sql/function/timestamp/test_timestampdiff.test_ignore
index 5bd500b..cd792fb 100644
--- a/modules/calcite/src/test/sql/function/timestamp/test_timestampdiff.test_ignore
+++ b/modules/calcite/src/test/sql/function/timestamp/test_timestampdiff.test_ignore
@@ -1,17 +1,7 @@
 # name: test/sql/function/test/test_timestampdiff.test
 # description: Just simple test to check support of timestampdiff functions
 # group: [timestamp]
-# Ignored: https://issues.apache.org/jira/browse/IGNITE-15109
-
-query T
-SELECT TIMESTAMPDIFF(MICROSECOND, TIMESTAMP '2022-02-01 10:30:28.000', TIMESTAMP '2022-02-01 10:30:28.128')
-----
-128
-
-query T
-SELECT TIMESTAMPDIFF(MICROSECOND, TIMESTAMP '2022-02-01 10:30:26.000', TIMESTAMP '2022-02-01 10:30:28.122')
-----
-2122000
+# Ignored: https://issues.apache.org/jira/browse/IGNITE-15418
 
 query T
 SELECT TIMESTAMPDIFF(MICROSECOND, TIMESTAMP '2022-02-01 10:30:26.000', TIMESTAMP '2022-05-01 10:30:28.122')