Repository: calcite
Updated Branches:
  refs/heads/master 6f07293a3 -> a377f8f28


[CALCITE-1765] Druid adapter: Gracefully handle granularity that cannot be 
pushed to extraction function (Slim Bouguerra)

Close apache/calcite#437


Project: http://git-wip-us.apache.org/repos/asf/calcite/repo
Commit: http://git-wip-us.apache.org/repos/asf/calcite/commit/a377f8f2
Tree: http://git-wip-us.apache.org/repos/asf/calcite/tree/a377f8f2
Diff: http://git-wip-us.apache.org/repos/asf/calcite/diff/a377f8f2

Branch: refs/heads/master
Commit: a377f8f2826f51c77b639ee711e2bd578f4de822
Parents: 6f07293
Author: Slim Bouguerra <[email protected]>
Authored: Thu Apr 27 11:54:14 2017 -0700
Committer: Julian Hyde <[email protected]>
Committed: Fri Apr 28 09:22:26 2017 -0700

----------------------------------------------------------------------
 .../adapter/druid/DruidDateTimeUtils.java       | 25 +++----
 .../calcite/adapter/druid/DruidQuery.java       | 53 +++++++--------
 .../calcite/adapter/druid/DruidRules.java       | 22 +++----
 .../adapter/druid/ExtractionDimensionSpec.java  |  8 ++-
 .../adapter/druid/ExtractionFunctionUtil.java   | 68 --------------------
 .../druid/TimeExtractionDimensionSpec.java      | 24 ++-----
 .../adapter/druid/TimeExtractionFunction.java   | 37 ++++++++++-
 .../org/apache/calcite/test/DruidAdapterIT.java | 41 ++++++++++++
 8 files changed, 136 insertions(+), 142 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/calcite/blob/a377f8f2/druid/src/main/java/org/apache/calcite/adapter/druid/DruidDateTimeUtils.java
----------------------------------------------------------------------
diff --git 
a/druid/src/main/java/org/apache/calcite/adapter/druid/DruidDateTimeUtils.java 
b/druid/src/main/java/org/apache/calcite/adapter/druid/DruidDateTimeUtils.java
index e3e86c3..0e2b3d3 100644
--- 
a/druid/src/main/java/org/apache/calcite/adapter/druid/DruidDateTimeUtils.java
+++ 
b/druid/src/main/java/org/apache/calcite/adapter/druid/DruidDateTimeUtils.java
@@ -278,21 +278,24 @@ public class DruidDateTimeUtils {
    * It support {@code FLOOR(<time> TO <timeunit>)} and {@code 
EXTRACT(<timeunit> FROM <time>)}.
    * It returns null if it cannot be inferred.
    *
-   * @param call the function call
+   * @param node the Rex node
    * @return the granularity, or null if it cannot be inferred
    */
-  public static Granularity extractGranularity(RexCall call) {
-    if ((call.getKind() != SqlKind.FLOOR && call.getKind() != SqlKind.EXTRACT)
-            || call.getOperands().size() != 2) {
-      return null;
-    }
-    int flagIndex;
-    if (call.getKind() == SqlKind.EXTRACT) {
-      // EXTRACT
+  public static Granularity extractGranularity(RexNode node) {
+    final int flagIndex;
+    switch (node.getKind()) {
+    case EXTRACT:
       flagIndex = 0;
-    } else {
-      // FLOOR
+      break;
+    case FLOOR:
       flagIndex = 1;
+      break;
+    default:
+      return null;
+    }
+    final RexCall call = (RexCall) node;
+    if (call.operands.size() != 2) {
+      return null;
     }
     final RexLiteral flag = (RexLiteral) call.operands.get(flagIndex);
     final TimeUnitRange timeUnit = (TimeUnitRange) flag.getValue();

http://git-wip-us.apache.org/repos/asf/calcite/blob/a377f8f2/druid/src/main/java/org/apache/calcite/adapter/druid/DruidQuery.java
----------------------------------------------------------------------
diff --git 
a/druid/src/main/java/org/apache/calcite/adapter/druid/DruidQuery.java 
b/druid/src/main/java/org/apache/calcite/adapter/druid/DruidQuery.java
index b287f45..504e06d 100644
--- a/druid/src/main/java/org/apache/calcite/adapter/druid/DruidQuery.java
+++ b/druid/src/main/java/org/apache/calcite/adapter/druid/DruidQuery.java
@@ -18,7 +18,6 @@ package org.apache.calcite.adapter.druid;
 
 import org.apache.calcite.DataContext;
 import org.apache.calcite.avatica.ColumnMetaData;
-import org.apache.calcite.avatica.util.TimeUnitRange;
 import org.apache.calcite.config.CalciteConnectionConfig;
 import org.apache.calcite.config.CalciteConnectionProperty;
 import org.apache.calcite.interpreter.BindableRel;
@@ -90,10 +89,6 @@ import static org.apache.calcite.sql.SqlKind.INPUT_REF;
  */
 public class DruidQuery extends AbstractRelNode implements BindableRel {
 
-  private static final List<TimeUnitRange> LIST_OF_VALID_TIME_EXTRACT = 
ImmutableList.of(
-      TimeUnitRange.YEAR,
-      TimeUnitRange.MONTH,
-      TimeUnitRange.DAY);
   protected QuerySpec querySpec;
 
   final RelOptTable table;
@@ -230,22 +225,12 @@ public class DruidQuery extends AbstractRelNode 
implements BindableRel {
     case CAST:
       return isValidCast((RexCall) e, boundedComparator);
     case EXTRACT:
-      return isValidExtract((RexCall) e);
+      return TimeExtractionFunction.isValidTimeExtract((RexCall) e);
     default:
       return false;
     }
   }
 
-  private boolean isValidExtract(RexCall call) {
-    assert call.isA(SqlKind.EXTRACT);
-    final RexLiteral flag = (RexLiteral) call.operands.get(0);
-    final TimeUnitRange timeUnit = (TimeUnitRange) flag.getValue();
-    if (timeUnit != null && LIST_OF_VALID_TIME_EXTRACT.contains(timeUnit)) {
-      return true;
-    }
-    return false;
-  }
-
   private boolean areValidFilters(List<RexNode> es, boolean boundedComparator) 
{
     for (RexNode e : es) {
       if (!isValidFilter(e, boundedComparator)) {
@@ -567,24 +552,29 @@ public class DruidQuery extends AbstractRelNode 
implements BindableRel {
             final RexCall call = (RexCall) project;
             final Granularity funcGranularity = 
DruidDateTimeUtils.extractGranularity(call);
             if (funcGranularity != null) {
-              if (call.getKind().equals(SqlKind.EXTRACT)) {
+              final String extractColumnName;
+              switch (call.getKind()) {
+              case EXTRACT:
                 // case extract field from time column
                 finalGranularity = Granularity.ALL;
-                String extractColumnName = 
SqlValidatorUtil.uniquify(EXTRACT_COLUMN_NAME_PREFIX
-                    + "_" + funcGranularity.value, usedFieldNames, 
SqlValidatorUtil.EXPR_SUGGESTER);
-                timeExtractionDimensionSpec = 
TimeExtractionDimensionSpec.makeExtract(
+                extractColumnName = 
SqlValidatorUtil.uniquify(EXTRACT_COLUMN_NAME_PREFIX
+                        + "_" + funcGranularity.value, usedFieldNames,
+                    SqlValidatorUtil.EXPR_SUGGESTER);
+                timeExtractionDimensionSpec = 
TimeExtractionDimensionSpec.makeTimeExtract(
                     funcGranularity, extractColumnName);
                 dimensions.add(timeExtractionDimensionSpec);
                 builder.add(extractColumnName);
-              } else {
+                break;
+              case FLOOR:
                 // case floor time column
                 if (groupSet.cardinality() > 1) {
                   // case we have more than 1 group by key -> then will have 
druid group by
-                  String extractColumnName = 
SqlValidatorUtil.uniquify(FLOOR_COLUMN_NAME_PREFIX
-                      + "_" + funcGranularity.value, usedFieldNames, 
SqlValidatorUtil
-                      .EXPR_SUGGESTER);
+                  extractColumnName = 
SqlValidatorUtil.uniquify(FLOOR_COLUMN_NAME_PREFIX
+                          + "_" + funcGranularity.value, usedFieldNames,
+                      SqlValidatorUtil.EXPR_SUGGESTER);
                   dimensions.add(
-                      TimeExtractionDimensionSpec.makeFloor(funcGranularity, 
extractColumnName));
+                      
TimeExtractionDimensionSpec.makeTimeFloor(funcGranularity,
+                          extractColumnName));
                   finalGranularity = Granularity.ALL;
                   builder.add(extractColumnName);
                 } else {
@@ -594,6 +584,9 @@ public class DruidQuery extends AbstractRelNode implements 
BindableRel {
                 }
                 assert timePositionIdx == -1;
                 timePositionIdx = groupKey;
+                break;
+              default:
+                throw new AssertionError();
               }
 
             } else {
@@ -1014,9 +1007,13 @@ public class DruidQuery extends AbstractRelNode 
implements BindableRel {
         final boolean numeric =
             call.getOperands().get(posRef).getType().getFamily()
                 == SqlTypeFamily.NUMERIC;
-
-        final ExtractionFunction extractionFunction = 
ExtractionFunctionUtil.buildExtraction(call
-            .getOperands().get(posRef));
+        final Granularity granularity = 
DruidDateTimeUtils.extractGranularity(call.getOperands()
+            .get(posRef));
+        // in case no extraction the field will be omitted from the 
serialization
+        ExtractionFunction extractionFunction = null;
+        if (granularity != null) {
+          extractionFunction = 
TimeExtractionFunction.createExtractFromGranularity(granularity);
+        }
         String dimName = tr(e, posRef);
         if 
(dimName.equals(DruidConnectionImpl.DEFAULT_RESPONSE_TIMESTAMP_COLUMN)) {
           // We need to use Druid default column name to refer to the time 
dimension in a filter

http://git-wip-us.apache.org/repos/asf/calcite/blob/a377f8f2/druid/src/main/java/org/apache/calcite/adapter/druid/DruidRules.java
----------------------------------------------------------------------
diff --git 
a/druid/src/main/java/org/apache/calcite/adapter/druid/DruidRules.java 
b/druid/src/main/java/org/apache/calcite/adapter/druid/DruidRules.java
index 8a02fd9..bbc8b4c 100644
--- a/druid/src/main/java/org/apache/calcite/adapter/druid/DruidRules.java
+++ b/druid/src/main/java/org/apache/calcite/adapter/druid/DruidRules.java
@@ -467,27 +467,25 @@ public class DruidRules {
             // Already one usage of timestamp column
             return -1;
           }
-          if (call.getKind() == SqlKind.FLOOR) {
+          switch (call.getKind()) {
+          case FLOOR:
             hasFloor = true;
             if (!(call.getOperands().get(0) instanceof RexInputRef)) {
               return -1;
             }
             final RexInputRef ref = (RexInputRef) call.getOperands().get(0);
             if (!(checkTimestampRefOnQuery(ImmutableBitSet.of(ref.getIndex()),
-                query.getTopNode(), query))) {
+                query.getTopNode(),
+                query))) {
               return -1;
             }
             idxTimestamp = i;
-          } else {
-            RexInputRef ref;
-            // Case extract from Calcite EXTRACT_DATE(FLAG(DAY), 
/INT(Reinterpret($0),86400000))
-            if (call.getOperands().get(1) instanceof RexCall) {
-              RexCall refCall = (RexCall) call.getOperands().get(1);
-              ref = (RexInputRef) ((RexCall) 
refCall.getOperands().get(0)).getOperands().get(0);
-            } else {
-              ref = (RexInputRef) call.getOperands().get(1);
-            }
-            idxTimestamp = ref.getIndex();
+            break;
+          case EXTRACT:
+            idxTimestamp = RelOptUtil.InputFinder.bits(call).asList().get(0);
+            break;
+          default:
+            throw new AssertionError();
           }
           continue;
         }

http://git-wip-us.apache.org/repos/asf/calcite/blob/a377f8f2/druid/src/main/java/org/apache/calcite/adapter/druid/ExtractionDimensionSpec.java
----------------------------------------------------------------------
diff --git 
a/druid/src/main/java/org/apache/calcite/adapter/druid/ExtractionDimensionSpec.java
 
b/druid/src/main/java/org/apache/calcite/adapter/druid/ExtractionDimensionSpec.java
index 25057c9..601fc89 100644
--- 
a/druid/src/main/java/org/apache/calcite/adapter/druid/ExtractionDimensionSpec.java
+++ 
b/druid/src/main/java/org/apache/calcite/adapter/druid/ExtractionDimensionSpec.java
@@ -17,9 +17,11 @@
 package org.apache.calcite.adapter.druid;
 
 import com.fasterxml.jackson.core.JsonGenerator;
+import com.google.common.base.Preconditions;
 
 import java.io.IOException;
 
+import static org.apache.calcite.adapter.druid.DruidQuery.writeField;
 import static org.apache.calcite.adapter.druid.DruidQuery.writeFieldIf;
 
 /**
@@ -35,8 +37,8 @@ public class ExtractionDimensionSpec implements DimensionSpec 
{
 
   public ExtractionDimensionSpec(String dimension, ExtractionFunction 
extractionFunction,
       String outputName) {
-    this.dimension = dimension;
-    this.extractionFunction = extractionFunction;
+    this.dimension = Preconditions.checkNotNull(dimension);
+    this.extractionFunction = Preconditions.checkNotNull(extractionFunction);
     this.outputName = outputName;
   }
 
@@ -49,7 +51,7 @@ public class ExtractionDimensionSpec implements DimensionSpec 
{
     generator.writeStringField("type", "extraction");
     generator.writeStringField("dimension", dimension);
     writeFieldIf(generator, "outputName", outputName);
-    writeFieldIf(generator, "extractionFn", extractionFunction);
+    writeField(generator, "extractionFn", extractionFunction);
     generator.writeEndObject();
   }
 

http://git-wip-us.apache.org/repos/asf/calcite/blob/a377f8f2/druid/src/main/java/org/apache/calcite/adapter/druid/ExtractionFunctionUtil.java
----------------------------------------------------------------------
diff --git 
a/druid/src/main/java/org/apache/calcite/adapter/druid/ExtractionFunctionUtil.java
 
b/druid/src/main/java/org/apache/calcite/adapter/druid/ExtractionFunctionUtil.java
deleted file mode 100644
index b7cf372..0000000
--- 
a/druid/src/main/java/org/apache/calcite/adapter/druid/ExtractionFunctionUtil.java
+++ /dev/null
@@ -1,68 +0,0 @@
-/*
- * 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.calcite.adapter.druid;
-
-
-import org.apache.calcite.avatica.util.TimeUnitRange;
-import org.apache.calcite.rex.RexCall;
-import org.apache.calcite.rex.RexLiteral;
-import org.apache.calcite.rex.RexNode;
-import org.apache.calcite.sql.SqlKind;
-
-
-/**
- * Utility class for extraction function mapping between SQL and Druid.
- */
-public final class ExtractionFunctionUtil {
-
-  private ExtractionFunctionUtil() {
-  }
-
-  //~ Methods ----------------------------------------------------------------
-
-  /**
-   * This method will be used to build a Druid extraction function out of a 
SQL EXTRACT rexNode.
-   *
-   * @param rexNode node that might contain an extraction function on time
-   * @return the correspondent Druid extraction function or null if it is not 
recognisable
-   */
-  public static ExtractionFunction buildExtraction(RexNode rexNode) {
-    if (rexNode instanceof RexCall) {
-      RexCall call = (RexCall) rexNode;
-      if (call.getKind().equals(SqlKind.EXTRACT)) {
-        final RexLiteral flag = (RexLiteral) call.operands.get(0);
-        final TimeUnitRange timeUnit = (TimeUnitRange) flag.getValue();
-        if (timeUnit == null) {
-          return null;
-        }
-        switch (timeUnit) {
-        case YEAR:
-          return 
TimeExtractionFunction.createExtractFromGranularity(Granularity.YEAR);
-        case MONTH:
-          return 
TimeExtractionFunction.createExtractFromGranularity(Granularity.MONTH);
-        case DAY:
-          return 
TimeExtractionFunction.createExtractFromGranularity(Granularity.DAY);
-        default:
-          return null;
-        }
-      }
-    }
-    return null;
-  }
-}
-
-// End ExtractionFunctionUtil.java

http://git-wip-us.apache.org/repos/asf/calcite/blob/a377f8f2/druid/src/main/java/org/apache/calcite/adapter/druid/TimeExtractionDimensionSpec.java
----------------------------------------------------------------------
diff --git 
a/druid/src/main/java/org/apache/calcite/adapter/druid/TimeExtractionDimensionSpec.java
 
b/druid/src/main/java/org/apache/calcite/adapter/druid/TimeExtractionDimensionSpec.java
index 8f38720..656ee77 100644
--- 
a/druid/src/main/java/org/apache/calcite/adapter/druid/TimeExtractionDimensionSpec.java
+++ 
b/druid/src/main/java/org/apache/calcite/adapter/druid/TimeExtractionDimensionSpec.java
@@ -42,7 +42,7 @@ public class TimeExtractionDimensionSpec extends 
ExtractionDimensionSpec {
   /**
    * Creates a time extraction DimensionSpec that formats the '__time' column
    * according to the given granularity and outputs the column with the given
-   * name. Only YEAR, MONTH, and DAY granularity are supported.
+   * name. See {@link TimeExtractionFunction#VALID_TIME_EXTRACT} for set of 
valid extract
    *
    * @param granularity granularity to apply to the column
    * @param outputName  name of the output column
@@ -50,25 +50,12 @@ public class TimeExtractionDimensionSpec extends 
ExtractionDimensionSpec {
    * @return time field extraction DimensionSpec instance or null if 
granularity
    * is not supported
    */
-  public static TimeExtractionDimensionSpec makeExtract(
+  public static TimeExtractionDimensionSpec makeTimeExtract(
       Granularity granularity, String outputName) {
-    switch (granularity) {
-    case YEAR:
-      return new TimeExtractionDimensionSpec(
-          TimeExtractionFunction.createExtractFromGranularity(granularity), 
outputName);
-    case MONTH:
-      return new TimeExtractionDimensionSpec(
-          TimeExtractionFunction.createExtractFromGranularity(granularity), 
outputName);
-    case DAY:
-      return new TimeExtractionDimensionSpec(
-          TimeExtractionFunction.createExtractFromGranularity(granularity), 
outputName);
-    // TODO: Support other granularities
-    default:
-      return null;
-    }
+    return new TimeExtractionDimensionSpec(
+        TimeExtractionFunction.createExtractFromGranularity(granularity), 
outputName);
   }
 
-
   /**
    * Creates floor time extraction dimension spec from Granularity with a 
given output name
    * @param granularity granularity to apply to the time column
@@ -76,7 +63,8 @@ public class TimeExtractionDimensionSpec extends 
ExtractionDimensionSpec {
    *
    * @return floor time extraction DimensionSpec instance.
    */
-  public static TimeExtractionDimensionSpec makeFloor(Granularity granularity, 
String outputName) {
+  public static TimeExtractionDimensionSpec makeTimeFloor(Granularity 
granularity,
+      String outputName) {
     ExtractionFunction fn = 
TimeExtractionFunction.createFloorFromGranularity(granularity);
     return new TimeExtractionDimensionSpec(fn, outputName);
   }

http://git-wip-us.apache.org/repos/asf/calcite/blob/a377f8f2/druid/src/main/java/org/apache/calcite/adapter/druid/TimeExtractionFunction.java
----------------------------------------------------------------------
diff --git 
a/druid/src/main/java/org/apache/calcite/adapter/druid/TimeExtractionFunction.java
 
b/druid/src/main/java/org/apache/calcite/adapter/druid/TimeExtractionFunction.java
index ff1f1cb..22733be 100644
--- 
a/druid/src/main/java/org/apache/calcite/adapter/druid/TimeExtractionFunction.java
+++ 
b/druid/src/main/java/org/apache/calcite/adapter/druid/TimeExtractionFunction.java
@@ -16,7 +16,14 @@
  */
 package org.apache.calcite.adapter.druid;
 
+import org.apache.calcite.avatica.util.TimeUnitRange;
+import org.apache.calcite.rex.RexCall;
+import org.apache.calcite.rex.RexLiteral;
+import org.apache.calcite.sql.SqlKind;
+
 import com.fasterxml.jackson.core.JsonGenerator;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Sets;
 
 import java.io.IOException;
 import java.util.Locale;
@@ -34,6 +41,12 @@ import static 
org.apache.calcite.adapter.druid.DruidQuery.writeFieldIf;
  */
 public class TimeExtractionFunction implements ExtractionFunction {
 
+  private static final ImmutableSet<TimeUnitRange> VALID_TIME_EXTRACT = 
Sets.immutableEnumSet(
+      TimeUnitRange.YEAR,
+      TimeUnitRange.MONTH,
+      TimeUnitRange.DAY,
+      TimeUnitRange.WEEK);
+
   private static final String ISO_TIME_FORMAT = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'";
   private final String format;
   private final String granularity;
@@ -71,7 +84,8 @@ public class TimeExtractionFunction implements 
ExtractionFunction {
    * Only YEAR, MONTH, and DAY granularity are supported.
    *
    * @param granularity granularity to apply to the column
-   * @return the time extraction function or null if granularity is not 
supported
+   * @return the time extraction function corresponding to the granularity 
input unit
+   * {@link TimeExtractionFunction#VALID_TIME_EXTRACT} for supported 
granularity
    */
   public static TimeExtractionFunction 
createExtractFromGranularity(Granularity granularity) {
     switch (granularity) {
@@ -81,8 +95,10 @@ public class TimeExtractionFunction implements 
ExtractionFunction {
       return new TimeExtractionFunction("M", null, "UTC", 
Locale.getDefault().toLanguageTag());
     case YEAR:
       return new TimeExtractionFunction("yyyy", null, "UTC", 
Locale.getDefault().toLanguageTag());
+    case WEEK:
+      return new TimeExtractionFunction("w", null, "UTC", 
Locale.getDefault().toLanguageTag());
     default:
-      throw new AssertionError("Extraction " + granularity.value + " is not 
valid");
+      throw new IllegalArgumentException("Granularity [" + granularity + "] is 
not supported");
     }
   }
 
@@ -96,6 +112,23 @@ public class TimeExtractionFunction implements 
ExtractionFunction {
     return new TimeExtractionFunction(ISO_TIME_FORMAT, granularity.value, 
"UTC", Locale
         .getDefault().toLanguageTag());
   }
+
+  /**
+   * Returns whether the RexCall contains a valid extract unit that we can
+   * serialize to Druid.
+   *
+   * @param call Extract expression
+   *
+   * @return true if the extract unit is valid
+   */
+  public static boolean isValidTimeExtract(RexCall call) {
+    if (call.getKind() != SqlKind.EXTRACT) {
+      return false;
+    }
+    final RexLiteral flag = (RexLiteral) call.operands.get(0);
+    final TimeUnitRange timeUnit = (TimeUnitRange) flag.getValue();
+    return timeUnit != null && VALID_TIME_EXTRACT.contains(timeUnit);
+  }
 }
 
 // End TimeExtractionFunction.java

http://git-wip-us.apache.org/repos/asf/calcite/blob/a377f8f2/druid/src/test/java/org/apache/calcite/test/DruidAdapterIT.java
----------------------------------------------------------------------
diff --git a/druid/src/test/java/org/apache/calcite/test/DruidAdapterIT.java 
b/druid/src/test/java/org/apache/calcite/test/DruidAdapterIT.java
index ced8fa8..b903fbb 100644
--- a/druid/src/test/java/org/apache/calcite/test/DruidAdapterIT.java
+++ b/druid/src/test/java/org/apache/calcite/test/DruidAdapterIT.java
@@ -2047,6 +2047,47 @@ public class DruidAdapterIT {
         + "S=7270\nbrand_name=Toucan; C=123; 
S=380").queryContains(druidChecker(druidSubQuery));
 
   }
+
+  @Test public void testGroupByWeekExtract() {
+    final String sql = "SELECT extract(week from \"timestamp\") from 
\"foodmart\" where "
+        + "\"product_id\" = 1558 and extract(week from \"timestamp\") IN (10, 
11)group by extract"
+        + "(week from \"timestamp\")";
+
+    final String druidQuery = "{'queryType':'groupBy','dataSource':'foodmart',"
+        + "'granularity':'all','dimensions':[{'type':'extraction',"
+        + "'dimension':'__time','outputName':'extract_week',"
+        + "'extractionFn':{'type':'timeFormat','format':'w','timeZone':'UTC',"
+        + "'locale':'en-US'}}],'limitSpec':{'type':'default'},"
+        + "'filter':{'type':'and','fields':[{'type':'selector',"
+        + "'dimension':'product_id','value':'1558'},{'type':'or',"
+        + "'fields':[{'type':'selector','dimension':'__time','value':'10',"
+        + "'extractionFn':{'type':'timeFormat','format':'w','timeZone':'UTC',"
+        + "'locale':'en-US'}},{'type':'selector','dimension':'__time',"
+        + "'value':'11','extractionFn':{'type':'timeFormat','format':'w',"
+        + "'timeZone':'UTC','locale':'en-US'}}]}]},"
+        + "'aggregations':[{'type':'longSum','name':'dummy_agg',"
+        + "'fieldName':'dummy_agg'}],"
+        + "'intervals':['1900-01-09T00:00:00.000/2992-01-10T00:00:00.000']}";
+    
sql(sql).returnsOrdered("EXPR$0=10\nEXPR$0=11").queryContains(druidChecker(druidQuery));
+  }
+
+  /** Test case for
+   * <a 
href="https://issues.apache.org/jira/browse/CALCITE-1765";>[CALCITE-1765]
+   * Druid adapter: Gracefully handle granularity that cannot be pushed to
+   * extraction function</a>. */
+  @Test public void testTimeExtractThatCannotBePushed() {
+    final String sql = "SELECT extract(CENTURY from \"timestamp\") from 
\"foodmart\" where "
+        + "\"product_id\" = 1558 group by extract(CENTURY from \"timestamp\")";
+    final String plan = "PLAN=EnumerableInterpreter\n"
+        + "  BindableAggregate(group=[{0}])\n"
+        + "    BindableProject(EXPR$0=[/INT(EXTRACT_DATE(FLAG(YEAR), 
/INT(Reinterpret($0), "
+        + "86400000)), 100)])\n"
+        + "      DruidQuery(table=[[foodmart, foodmart]], "
+        + "intervals=[[1900-01-09T00:00:00.000/2992-01-10T00:00:00.000]], 
filter=[=($1, 1558)], "
+        + "projects=[[$0]])";
+    
sql(sql).explainContains(plan).queryContains(druidChecker("'queryType':'select'"))
+        .returnsUnordered("EXPR$0=19");
+  }
 }
 
 // End DruidAdapterIT.java

Reply via email to