You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@calcite.apache.org by jc...@apache.org on 2017/12/22 21:01:43 UTC
calcite git commit: [CALCITE-2089] Druid adapter: Push filter on
floor(time) to Druid (Nishant Bangarwa)
Repository: calcite
Updated Branches:
refs/heads/master 159bcab20 -> ac5cce707
[CALCITE-2089] Druid adapter: Push filter on floor(time) to Druid (Nishant Bangarwa)
Close apache/calcite#582
Project: http://git-wip-us.apache.org/repos/asf/calcite/repo
Commit: http://git-wip-us.apache.org/repos/asf/calcite/commit/ac5cce70
Tree: http://git-wip-us.apache.org/repos/asf/calcite/tree/ac5cce70
Diff: http://git-wip-us.apache.org/repos/asf/calcite/diff/ac5cce70
Branch: refs/heads/master
Commit: ac5cce707a743299107ba3fd1cfc9a24e745ef42
Parents: 159bcab
Author: Nishant Bangarwa <ni...@gmail.com>
Authored: Fri Dec 22 12:53:31 2017 -0800
Committer: Jesus Camacho Rodriguez <jc...@apache.org>
Committed: Fri Dec 22 12:53:34 2017 -0800
----------------------------------------------------------------------
.../org/apache/calcite/test/CalciteAssert.java | 10 +-
.../adapter/druid/DruidDateTimeUtils.java | 2 +-
.../calcite/adapter/druid/DruidQuery.java | 95 +++++++++++----
.../adapter/druid/TimeExtractionFunction.java | 21 +++-
.../org/apache/calcite/test/DruidAdapterIT.java | 117 ++++++++++++++++++-
5 files changed, 209 insertions(+), 36 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/calcite/blob/ac5cce70/core/src/test/java/org/apache/calcite/test/CalciteAssert.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/apache/calcite/test/CalciteAssert.java b/core/src/test/java/org/apache/calcite/test/CalciteAssert.java
index 70a415b..49a9c31 100644
--- a/core/src/test/java/org/apache/calcite/test/CalciteAssert.java
+++ b/core/src/test/java/org/apache/calcite/test/CalciteAssert.java
@@ -548,9 +548,13 @@ public class CalciteAssert {
calciteConnection.getProperties().setProperty(
CalciteConnectionProperty.CREATE_MATERIALIZATIONS.camelName(),
Boolean.toString(materializationsEnabled));
- calciteConnection.getProperties().setProperty(
- CalciteConnectionProperty.TIME_ZONE.camelName(),
- DateTimeUtils.UTC_ZONE.getID());
+ if (!calciteConnection.getProperties()
+ .containsKey(CalciteConnectionProperty.TIME_ZONE.camelName())) {
+ // Do not override id some test has already set this property.
+ calciteConnection.getProperties().setProperty(
+ CalciteConnectionProperty.TIME_ZONE.camelName(),
+ DateTimeUtils.UTC_ZONE.getID());
+ }
}
for (Pair<Hook, Function> hook : hooks) {
closer.add(hook.left.addThread(hook.right));
http://git-wip-us.apache.org/repos/asf/calcite/blob/ac5cce70/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 562eb4a..566e781 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
@@ -249,7 +249,7 @@ public class DruidDateTimeUtils {
}
}
- private static TimestampString literalValue(RexNode node, TimeZone timeZone) {
+ protected static TimestampString literalValue(RexNode node, TimeZone timeZone) {
switch (node.getKind()) {
case LITERAL:
switch (((RexLiteral) node).getTypeName()) {
http://git-wip-us.apache.org/repos/asf/calcite/blob/ac5cce70/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 db7d4ff..2469841 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
@@ -63,6 +63,7 @@ import org.apache.calcite.sql.validate.SqlValidatorUtil;
import org.apache.calcite.util.ImmutableBitSet;
import org.apache.calcite.util.Litmus;
import org.apache.calcite.util.Pair;
+import org.apache.calcite.util.TimestampString;
import org.apache.calcite.util.Util;
import com.fasterxml.jackson.core.JsonFactory;
@@ -80,12 +81,14 @@ import org.joda.time.Interval;
import java.io.IOException;
import java.io.StringWriter;
import java.math.BigDecimal;
+import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.Objects;
import java.util.Properties;
import java.util.Set;
+import java.util.TimeZone;
import java.util.regex.Pattern;
import static org.apache.calcite.sql.SqlKind.INPUT_REF;
@@ -234,6 +237,8 @@ public class DruidQuery extends AbstractRelNode implements BindableRel {
return isValidCast((RexCall) e, boundedComparator);
case EXTRACT:
return TimeExtractionFunction.isValidTimeExtract((RexCall) e);
+ case FLOOR:
+ return TimeExtractionFunction.isValidTimeFloor((RexCall) e);
case IS_TRUE:
return isValidFilter(((RexCall) e).getOperands().get(0), boundedComparator);
default:
@@ -264,7 +269,9 @@ public class DruidQuery extends AbstractRelNode implements BindableRel {
return true;
}
if (e.getOperands().get(0).isA(SqlKind.LITERAL)
- && e.getType().getFamily() == SqlTypeFamily.TIMESTAMP) {
+ && (e.getType().getSqlTypeName() == SqlTypeName.DATE
+ || e.getType().getSqlTypeName() == SqlTypeName.TIMESTAMP
+ || e.getType().getSqlTypeName() == SqlTypeName.TIMESTAMP_WITH_LOCAL_TIME_ZONE)) {
// CAST of literal to timestamp type
return true;
}
@@ -523,7 +530,7 @@ public class DruidQuery extends AbstractRelNode implements BindableRel {
translator.clearFieldNameLists();
final ImmutableList.Builder<String> builder = ImmutableList.builder();
for (RexNode project : projects) {
- builder.add(translator.translate(project, true));
+ builder.add(translator.translate(project, true, false));
}
fieldNames = builder.build();
}
@@ -1110,6 +1117,7 @@ public class DruidQuery extends AbstractRelNode implements BindableRel {
final DruidTable druidTable;
final RelDataType rowType;
final String timeZone;
+ final SimpleDateFormat dateFormatter;
Translator(DruidTable druidTable, RelDataType rowType, String timeZone) {
this.druidTable = druidTable;
@@ -1124,6 +1132,11 @@ public class DruidQuery extends AbstractRelNode implements BindableRel {
}
}
this.timeZone = timeZone;
+ this.dateFormatter = new SimpleDateFormat(TimeExtractionFunction.ISO_TIME_FORMAT,
+ Locale.ROOT);
+ if (timeZone != null) {
+ this.dateFormatter.setTimeZone(TimeZone.getTimeZone(timeZone));
+ }
}
protected void clearFieldNameLists() {
@@ -1131,7 +1144,13 @@ public class DruidQuery extends AbstractRelNode implements BindableRel {
metrics.clear();
}
- @SuppressWarnings("incomplete-switch") String translate(RexNode e, boolean set) {
+ @SuppressWarnings("incomplete-switch")
+ /**
+ * formatDateString is used to format timestamp values to druid format using
+ * {@link DruidQuery.Translator#dateFormatter}. This is needed when pushing timestamp
+ * comparisons to druid using TimeFormatExtractionFunction that returns a string value.
+ */
+ String translate(RexNode e, boolean set, boolean formatDateString) {
int index = -1;
switch (e.getKind()) {
case INPUT_REF:
@@ -1139,9 +1158,23 @@ public class DruidQuery extends AbstractRelNode implements BindableRel {
index = ref.getIndex();
break;
case CAST:
- return tr(e, 0, set);
+ return tr(e, 0, set, formatDateString);
case LITERAL:
- return ((RexLiteral) e).getValue3().toString();
+ final RexLiteral rexLiteral = (RexLiteral) e;
+ if (!formatDateString) {
+ return Objects.toString(rexLiteral.getValue3());
+ } else {
+ // Case when we are passing to druid as an extractionFunction
+ // Need to format the timestamp String in druid format.
+ TimestampString timestampString = DruidDateTimeUtils
+ .literalValue(e, TimeZone.getTimeZone(timeZone));
+ if (timestampString == null) {
+ throw new AssertionError(
+ "Cannot translate Literal" + e + " of type "
+ + rexLiteral.getTypeName() + " to TimestampString");
+ }
+ return dateFormatter.format(timestampString.getMillisSinceEpoch());
+ }
case FLOOR:
case EXTRACT:
final RexCall call = (RexCall) e;
@@ -1200,18 +1233,29 @@ public class DruidQuery extends AbstractRelNode implements BindableRel {
} else {
throw new AssertionError("it is not a valid comparison: " + e);
}
+ RexNode posRefNode = call.getOperands().get(posRef);
final boolean numeric =
call.getOperands().get(posRef).getType().getFamily()
== SqlTypeFamily.NUMERIC;
- final Granularity granularity = DruidDateTimeUtils.extractGranularity(call.getOperands()
- .get(posRef));
+ boolean formatDateString = false;
+ final Granularity granularity = DruidDateTimeUtils.extractGranularity(posRefNode);
// in case no extraction the field will be omitted from the serialization
ExtractionFunction extractionFunction = null;
if (granularity != null) {
- extractionFunction =
- TimeExtractionFunction.createExtractFromGranularity(granularity, timeZone);
+ switch (posRefNode.getKind()) {
+ case EXTRACT:
+ extractionFunction =
+ TimeExtractionFunction.createExtractFromGranularity(granularity, timeZone);
+ break;
+ case FLOOR:
+ extractionFunction =
+ TimeExtractionFunction.createFloorFromGranularity(granularity, timeZone);
+ formatDateString = true;
+ break;
+
+ }
}
- String dimName = tr(e, posRef);
+ String dimName = tr(e, posRef, formatDateString);
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
dimName = DruidTable.DEFAULT_TIMESTAMP_COLUMN;
@@ -1223,17 +1267,18 @@ public class DruidQuery extends AbstractRelNode implements BindableRel {
// we have guarantees about the format of the output and thus we can apply the
// normal selector
if (numeric && extractionFunction == null) {
- String constantValue = tr(e, posConstant);
+ String constantValue = tr(e, posConstant, formatDateString);
return new JsonBound(dimName, constantValue, false, constantValue, false,
numeric, extractionFunction);
}
- return new JsonSelector(dimName, tr(e, posConstant), extractionFunction);
+ return new JsonSelector(dimName, tr(e, posConstant, formatDateString),
+ extractionFunction);
case NOT_EQUALS:
// extractionFunction should be null because if we are using an extraction function
// we have guarantees about the format of the output and thus we can apply the
// normal selector
if (numeric && extractionFunction == null) {
- String constantValue = tr(e, posConstant);
+ String constantValue = tr(e, posConstant, formatDateString);
return new JsonCompositeFilter(JsonFilter.Type.OR,
new JsonBound(dimName, constantValue, true, null, false,
numeric, extractionFunction),
@@ -1241,30 +1286,30 @@ public class DruidQuery extends AbstractRelNode implements BindableRel {
numeric, extractionFunction));
}
return new JsonCompositeFilter(JsonFilter.Type.NOT,
- new JsonSelector(dimName, tr(e, posConstant), extractionFunction));
+ new JsonSelector(dimName, tr(e, posConstant, formatDateString), extractionFunction));
case GREATER_THAN:
- return new JsonBound(dimName, tr(e, posConstant),
+ return new JsonBound(dimName, tr(e, posConstant, formatDateString),
true, null, false, numeric, extractionFunction);
case GREATER_THAN_OR_EQUAL:
- return new JsonBound(dimName, tr(e, posConstant),
+ return new JsonBound(dimName, tr(e, posConstant, formatDateString),
false, null, false, numeric, extractionFunction);
case LESS_THAN:
return new JsonBound(dimName, null, false,
- tr(e, posConstant), true, numeric, extractionFunction);
+ tr(e, posConstant, formatDateString), true, numeric, extractionFunction);
case LESS_THAN_OR_EQUAL:
return new JsonBound(dimName, null, false,
- tr(e, posConstant), false, numeric, extractionFunction);
+ tr(e, posConstant, formatDateString), false, numeric, extractionFunction);
case IN:
ImmutableList.Builder<String> listBuilder = ImmutableList.builder();
for (RexNode rexNode: call.getOperands()) {
if (rexNode.getKind() == SqlKind.LITERAL) {
- listBuilder.add(((RexLiteral) rexNode).getValue3().toString());
+ listBuilder.add(Objects.toString(((RexLiteral) rexNode).getValue3()));
}
}
return new JsonInFilter(dimName, listBuilder.build(), extractionFunction);
case BETWEEN:
- return new JsonBound(dimName, tr(e, 2), false,
- tr(e, 3), false, numeric, extractionFunction);
+ return new JsonBound(dimName, tr(e, 2, formatDateString), false,
+ tr(e, 3, formatDateString), false, numeric, extractionFunction);
case IS_NULL:
return new JsonSelector(dimName, null, extractionFunction);
case IS_NOT_NULL:
@@ -1284,12 +1329,12 @@ public class DruidQuery extends AbstractRelNode implements BindableRel {
}
}
- private String tr(RexNode call, int index) {
- return tr(call, index, false);
+ private String tr(RexNode call, int index, boolean formatDateString) {
+ return tr(call, index, false, formatDateString);
}
- private String tr(RexNode call, int index, boolean set) {
- return translate(((RexCall) call).getOperands().get(index), set);
+ private String tr(RexNode call, int index, boolean set, boolean formatDateString) {
+ return translate(((RexCall) call).getOperands().get(index), set, formatDateString);
}
private List<JsonFilter> translateFilters(List<RexNode> operands) {
http://git-wip-us.apache.org/repos/asf/calcite/blob/ac5cce70/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 b1f8870..ba4d9d7 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
@@ -47,7 +47,8 @@ public class TimeExtractionFunction implements ExtractionFunction {
TimeUnitRange.DAY,
TimeUnitRange.WEEK);
- private static final String ISO_TIME_FORMAT = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'";
+ public static final String ISO_TIME_FORMAT = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'";
+
private final String format;
private final String granularity;
private final String timeZone;
@@ -132,6 +133,24 @@ public class TimeExtractionFunction implements ExtractionFunction {
final TimeUnitRange timeUnit = (TimeUnitRange) flag.getValue();
return timeUnit != null && VALID_TIME_EXTRACT.contains(timeUnit);
}
+
+ /**
+ * Returns whether the RexCall contains a valid FLOOR unit that we can
+ * serialize to Druid.
+ *
+ * @param call Extract expression
+ *
+ * @return true if the extract unit is valid
+ */
+ public static boolean isValidTimeFloor(RexCall call) {
+ if (call.getKind() != SqlKind.FLOOR) {
+ return false;
+ }
+ final RexLiteral flag = (RexLiteral) call.operands.get(1);
+ 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/ac5cce70/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 913e30c..0b9dc4a 100644
--- a/druid/src/test/java/org/apache/calcite/test/DruidAdapterIT.java
+++ b/druid/src/test/java/org/apache/calcite/test/DruidAdapterIT.java
@@ -1429,18 +1429,18 @@ public class DruidAdapterIT {
@Test public void testFieldBasedCostColumnPruning() {
// A query where filter cannot be pushed to Druid but
// the project can still be pushed in order to prune extra columns.
- String sql = "select \"countryName\", floor(CAST(\"time\" AS TIMESTAMP) to DAY),\n"
+ String sql = "select \"countryName\", ceil(CAST(\"time\" AS TIMESTAMP) to DAY),\n"
+ " cast(count(*) as integer) as c\n"
+ "from \"wiki\"\n"
- + "where floor(\"time\" to DAY) >= '1997-01-01 00:00:00 UTC'\n"
- + "and floor(\"time\" to DAY) < '1997-09-01 00:00:00 UTC'\n"
- + "group by \"countryName\", floor(CAST(\"time\" AS TIMESTAMP) TO DAY)\n"
+ + "where ceil(\"time\" to DAY) >= '1997-01-01 00:00:00 UTC'\n"
+ + "and ceil(\"time\" to DAY) < '1997-09-01 00:00:00 UTC'\n"
+ + "group by \"countryName\", ceil(CAST(\"time\" AS TIMESTAMP) TO DAY)\n"
+ "order by c limit 5";
String plan = "BindableProject(countryName=[$0], EXPR$1=[$1], C=[CAST($2):INTEGER NOT NULL])\n"
+ " BindableSort(sort0=[$2], dir0=[ASC], fetch=[5])\n"
+ " BindableAggregate(group=[{0, 1}], agg#0=[COUNT()])\n"
- + " BindableProject(countryName=[$1], EXPR$1=[FLOOR(CAST($0):TIMESTAMP(0) NOT NULL, FLAG(DAY))])\n"
- + " BindableFilter(condition=[AND(>=(FLOOR($0, FLAG(DAY)), 1997-01-01 00:00:00), <(FLOOR($0, FLAG(DAY)), 1997-09-01 00:00:00))])\n"
+ + " BindableProject(countryName=[$1], EXPR$1=[CEIL(CAST($0):TIMESTAMP(0) NOT NULL, FLAG(DAY))])\n"
+ + " BindableFilter(condition=[AND(>=(CEIL($0, FLAG(DAY)), 1997-01-01 00:00:00), <(CEIL($0, FLAG(DAY)), 1997-09-01 00:00:00))])\n"
+ " DruidQuery(table=[[wiki, wiki]], intervals=[[1900-01-09T00:00:00.000Z/2992-01-10T00:00:00.000Z]], projects=[[$0, $5]])";
// NOTE: Druid query only has countryName as the dimension
// being queried after project is pushed to druid query.
@@ -3380,6 +3380,111 @@ public class DruidAdapterIT {
.returnsUnordered("C=86829");
}
+ @Test
+ public void testFilterwithFloorOnTime() {
+ // Test filter on floor on time column is pushed to druid
+ final String sql =
+ "Select cast(floor(\"timestamp\" to MONTH) as timestamp) as t from \"foodmart\" where "
+ + "floor(\"timestamp\" to MONTH) between '1997-01-01 00:00:00 UTC'"
+ + "and '1997-03-01 00:00:00 UTC' order by t limit 2";
+
+ final String druidQueryPart1 = "\"filter\":{\"type\":\"and\",\"fields\":"
+ + "[{\"type\":\"bound\",\"dimension\":\"__time\",\"lower\":\"1997-01-01T00:00:00.000Z\","
+ + "\"lowerStrict\":false,\"ordering\":\"lexicographic\","
+ + "\"extractionFn\":{\"type\":\"timeFormat\",\"format\":\"yyyy-MM-dd";
+ final String druidQueryPart2 = "HH:mm:ss.SSS";
+ final String druidQueryPart3 = ",\"granularity\":\"month\",\"timeZone\":\"UTC\","
+ + "\"locale\":\"en-US\"}},{\"type\":\"bound\",\"dimension\":\"__time\""
+ + ",\"upper\":\"1997-03-01T00:00:00.000Z\",\"upperStrict\":false,"
+ + "\"ordering\":\"lexicographic\",\"extractionFn\":{\"type\":\"timeFormat\"";
+ final String druidQueryPart4 = "\"columns\":[\"__time\"],\"granularity\":\"all\"";
+
+ sql(sql, FOODMART)
+ .queryContains(
+ druidChecker(druidQueryPart1, druidQueryPart2, druidQueryPart3, druidQueryPart4))
+ .returnsOrdered("T=1997-01-01 00:00:00", "T=1997-01-01 00:00:00");
+ }
+
+ @Test
+ public void testSelectFloorOnTimeWithFilterOnFloorOnTime() {
+ final String sql = "Select cast(floor(\"timestamp\" to MONTH) as timestamp) as t from "
+ + "\"foodmart\" where floor(\"timestamp\" to MONTH) >= '1997-05-01 00:00:00 UTC' order by t"
+ + " limit 1";
+ final String druidQueryPart1 = "filter\":{\"type\":\"bound\",\"dimension\":\"__time\","
+ + "\"lower\":\"1997-05-01T00:00:00.000Z\",\"lowerStrict\":false,"
+ + "\"ordering\":\"lexicographic\",\"extractionFn\":{\"type\":\"timeFormat\","
+ + "\"format\":\"yyyy-MM-dd";
+ final String druidQueryPart2 = "\"granularity\":\"month\",\"timeZone\":\"UTC\","
+ + "\"locale\":\"en-US\"}},\"columns\":[\"__time\"],\"granularity\":\"all\"";
+
+ sql(sql, FOODMART).queryContains(druidChecker(druidQueryPart1, druidQueryPart2))
+ .returnsOrdered("T=1997-05-01 00:00:00");
+ }
+
+ @Test
+ public void testTmeWithFilterOnFloorOnTimeAndCastToTimestamp() {
+ final String sql = "Select cast(floor(\"timestamp\" to MONTH) as timestamp) as t from "
+ + "\"foodmart\" where floor(\"timestamp\" to MONTH) >= cast('1997-05-01 00:00:00' as TIMESTAMP) order by t"
+ + " limit 1";
+ final String druidQueryPart1 = "filter\":{\"type\":\"bound\",\"dimension\":\"__time\","
+ + "\"lower\":\"1997-05-01T00:00:00.000Z\",\"lowerStrict\":false,"
+ + "\"ordering\":\"lexicographic\",\"extractionFn\":{\"type\":\"timeFormat\","
+ + "\"format\":\"yyyy-MM-dd";
+ final String druidQueryPart2 = "\"granularity\":\"month\",\"timeZone\":\"UTC\","
+ + "\"locale\":\"en-US\"}},\"columns\":[\"__time\"],\"granularity\":\"all\"";
+
+ sql(sql, FOODMART).queryContains(druidChecker(druidQueryPart1, druidQueryPart2))
+ .returnsOrdered("T=1997-05-01 00:00:00");
+ }
+
+ @Test
+ public void testTmeWithFilterOnFloorOnTimeWithTimezone() {
+ final String sql = "Select cast(floor(\"timestamp\" to MONTH) as timestamp) as t from "
+ + "\"foodmart\" where floor(\"timestamp\" to MONTH) >= cast('1997-05-01 00:00:00'"
+ + " as TIMESTAMP) order by t limit 1";
+ final String druidQueryPart1 = "filter\":{\"type\":\"bound\",\"dimension\":\"__time\","
+ + "\"lower\":\"1997-05-01T00:00:00.000Z\",\"lowerStrict\":false,"
+ + "\"ordering\":\"lexicographic\",\"extractionFn\":{\"type\":\"timeFormat\","
+ + "\"format\":\"yyyy-MM-dd";
+ final String druidQueryPart2 = "\"granularity\":\"month\",\"timeZone\":\"IST\","
+ + "\"locale\":\"en-US\"}},\"columns\":[\"__time\"],\"granularity\":\"all\"";
+
+ CalciteAssert.that()
+ .enable(enabled())
+ .with(ImmutableMap.of("model", FOODMART.getPath()))
+ .with(CalciteConnectionProperty.TIME_ZONE.camelName(), "IST")
+ .query(sql)
+ .runs()
+ .queryContains(druidChecker(druidQueryPart1, druidQueryPart2))
+ // NOTE: this return value is not as expected
+ // see https://issues.apache.org/jira/browse/CALCITE-2107
+ .returnsOrdered("T=1997-05-01 05:30:00");
+ }
+
+ @Test
+ public void testTmeWithFilterOnFloorOnTimeWithTimezoneConversion() {
+ final String sql = "Select cast(floor(\"timestamp\" to MONTH) as timestamp) as t from "
+ + "\"foodmart\" where floor(\"timestamp\" to MONTH) >= '1997-04-30 18:30:00 UTC' order by t"
+ + " limit 1";
+ final String druidQueryPart1 = "filter\":{\"type\":\"bound\",\"dimension\":\"__time\","
+ + "\"lower\":\"1997-05-01T00:00:00.000Z\",\"lowerStrict\":false,"
+ + "\"ordering\":\"lexicographic\",\"extractionFn\":{\"type\":\"timeFormat\","
+ + "\"format\":\"yyyy-MM-dd";
+ final String druidQueryPart2 = "\"granularity\":\"month\",\"timeZone\":\"IST\","
+ + "\"locale\":\"en-US\"}},\"columns\":[\"__time\"],\"granularity\":\"all\"";
+
+ CalciteAssert.that()
+ .enable(enabled())
+ .with(ImmutableMap.of("model", FOODMART.getPath()))
+ .with(CalciteConnectionProperty.TIME_ZONE.camelName(), "IST")
+ .query(sql)
+ .runs()
+ .queryContains(druidChecker(druidQueryPart1, druidQueryPart2))
+ // NOTE: this return value is not as expected
+ // see https://issues.apache.org/jira/browse/CALCITE-2107
+ .returnsOrdered("T=1997-05-01 05:30:00");
+ }
+
}
// End DruidAdapterIT.java