You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@calcite.apache.org by jh...@apache.org on 2018/01/12 03:55:42 UTC
calcite git commit: [CALCITE-2090] Convert FLOOR and CEIL into time
ranges (Nishant Bangarwa)
Repository: calcite
Updated Branches:
refs/heads/master 1c7f690d3 -> fa19580ec
[CALCITE-2090] Convert FLOOR and CEIL into time ranges (Nishant Bangarwa)
Add test for multiple operands.
Allow RexLiteral.getValueAs(Calendar) on TIMESTAMP_WITH_LOCAL_TIME_ZONE
literals.
Close apache/calcite#603
Project: http://git-wip-us.apache.org/repos/asf/calcite/repo
Commit: http://git-wip-us.apache.org/repos/asf/calcite/commit/fa19580e
Tree: http://git-wip-us.apache.org/repos/asf/calcite/tree/fa19580e
Diff: http://git-wip-us.apache.org/repos/asf/calcite/diff/fa19580e
Branch: refs/heads/master
Commit: fa19580ecfe404b75fcb6c9c174cfe9da73d2db8
Parents: 1c7f690
Author: Nishant <ni...@gmail.com>
Authored: Thu Jan 11 02:24:26 2018 +0530
Committer: Julian Hyde <jh...@apache.org>
Committed: Thu Jan 11 19:53:24 2018 -0800
----------------------------------------------------------------------
.../calcite/rel/rules/DateRangeRules.java | 269 ++++++++++--
.../java/org/apache/calcite/rex/RexLiteral.java | 3 +
.../calcite/rel/rules/DateRangeRulesTest.java | 407 ++++++++++++++++++-
.../org/apache/calcite/test/DruidAdapterIT.java | 3 +-
4 files changed, 636 insertions(+), 46 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/calcite/blob/fa19580e/core/src/main/java/org/apache/calcite/rel/rules/DateRangeRules.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/rel/rules/DateRangeRules.java b/core/src/main/java/org/apache/calcite/rel/rules/DateRangeRules.java
index d670305..c8d17bf 100644
--- a/core/src/main/java/org/apache/calcite/rel/rules/DateRangeRules.java
+++ b/core/src/main/java/org/apache/calcite/rel/rules/DateRangeRules.java
@@ -64,7 +64,9 @@ import java.util.Set;
/**
* Collection of planner rules that convert
- * {@code EXTRACT(timeUnit FROM dateTime) = constant} to
+ * {@code EXTRACT(timeUnit FROM dateTime) = constant},
+ * {@code FLOOR(dateTime to timeUnit} = constant} and
+ * {@code CEIL(dateTime to timeUnit} = constant} to
* {@code dateTime BETWEEN lower AND upper}.
*
* <p>The rules allow conversion of queries on time dimension tables, such as
@@ -87,13 +89,14 @@ public abstract class DateRangeRules {
private static final Predicate<Filter> FILTER_PREDICATE =
new PredicateImpl<Filter>() {
@Override public boolean test(Filter filter) {
- final ExtractFinder finder = ExtractFinder.THREAD_INSTANCES.get();
- assert finder.timeUnits.isEmpty() : "previous user did not clean up";
- try {
+ try (ExtractFinder finder = ExtractFinder.THREAD_INSTANCES.get()) {
+ assert finder.timeUnits.isEmpty() && finder.opKinds.isEmpty()
+ : "previous user did not clean up";
filter.getCondition().accept(finder);
- return !finder.timeUnits.isEmpty();
- } finally {
- finder.timeUnits.clear();
+ // bail out if there is no EXTRACT of YEAR, or call to FLOOR or CEIL
+ return finder.timeUnits.contains(TimeUnitRange.YEAR)
+ || finder.opKinds.contains(SqlKind.FLOOR)
+ || finder.opKinds.contains(SqlKind.CEIL);
}
}
};
@@ -132,23 +135,24 @@ public abstract class DateRangeRules {
* is no predicate on YEAR. Then when we apply the predicate on DAY it doesn't
* generate hundreds of ranges we'll later throw away. */
static ImmutableSortedSet<TimeUnitRange> extractTimeUnits(RexNode e) {
- final ExtractFinder finder = ExtractFinder.THREAD_INSTANCES.get();
- try {
- assert finder.timeUnits.isEmpty() : "previous user did not clean up";
+ try (ExtractFinder finder = ExtractFinder.THREAD_INSTANCES.get()) {
+ assert finder.timeUnits.isEmpty() && finder.opKinds.isEmpty()
+ : "previous user did not clean up";
e.accept(finder);
return ImmutableSortedSet.copyOf(finder.timeUnits);
- } finally {
- finder.timeUnits.clear();
}
}
- /** Replaces calls to EXTRACT in an expression. */
+ /** Replaces calls to EXTRACT, FLOOR and CEIL in an expression. */
@VisibleForTesting
public static RexNode replaceTimeUnits(RexBuilder rexBuilder, RexNode e) {
- final ImmutableSortedSet<TimeUnitRange> timeUnits = extractTimeUnits(e);
+ ImmutableSortedSet<TimeUnitRange> timeUnits = extractTimeUnits(e);
if (!timeUnits.contains(TimeUnitRange.YEAR)) {
- // bail out if there is no year extract.
- return e;
+ // Case when we have FLOOR or CEIL but no extract on YEAR.
+ // Add YEAR as TimeUnit so that FLOOR gets replaced in first iteration
+ // with timeUnit YEAR.
+ timeUnits = ImmutableSortedSet.<TimeUnitRange>naturalOrder()
+ .addAll(timeUnits).add(TimeUnitRange.YEAR).build();
}
final Map<String, RangeSet<Calendar>> operandRanges = new HashMap<>();
for (TimeUnitRange timeUnit : timeUnits) {
@@ -158,7 +162,8 @@ public abstract class DateRangeRules {
return e;
}
- /** Rule that converts EXTRACT in a Filter condition into a date range. */
+ /** Rule that converts EXTRACT, FLOOR and CEIL in a {@link Filter} into a date
+ * range. */
@SuppressWarnings("WeakerAccess")
public static class FilterDateRangeRule extends RelOptRule {
public FilterDateRangeRule(RelBuilderFactory relBuilderFactory) {
@@ -182,11 +187,13 @@ public abstract class DateRangeRules {
}
}
- /** Visitor that searches for calls to the {@code EXTRACT} function, building
- * a list of distinct time units. */
- private static class ExtractFinder extends RexVisitorImpl {
+ /** Visitor that searches for calls to {@code EXTRACT}, {@code FLOOR} or
+ * {@code CEIL}, building a list of distinct time units. */
+ private static class ExtractFinder extends RexVisitorImpl
+ implements AutoCloseable {
private final Set<TimeUnitRange> timeUnits =
EnumSet.noneOf(TimeUnitRange.class);
+ private final Set<SqlKind> opKinds = EnumSet.noneOf(SqlKind.class);
private static final ThreadLocal<ExtractFinder> THREAD_INSTANCES =
new ThreadLocal<ExtractFinder>() {
@@ -204,12 +211,26 @@ public abstract class DateRangeRules {
case EXTRACT:
final RexLiteral operand = (RexLiteral) call.getOperands().get(0);
timeUnits.add((TimeUnitRange) operand.getValue());
+ break;
+ case FLOOR:
+ case CEIL:
+ // Check that the call to FLOOR/CEIL is on date-time
+ if (call.getOperands().size() == 2) {
+ opKinds.add(call.getKind());
+ }
+ break;
}
return super.visitCall(call);
}
+
+ public void close() {
+ timeUnits.clear();
+ opKinds.clear();
+ }
}
- /** Walks over an expression, replacing {@code EXTRACT} with date ranges. */
+ /** Walks over an expression, replacing calls to
+ * {@code EXTRACT}, {@code FLOOR} and {@code CEIL} with date ranges. */
@VisibleForTesting
static class ExtractShuttle extends RexShuttle {
private final RexBuilder rexBuilder;
@@ -241,16 +262,45 @@ public abstract class DateRangeRules {
final RexNode op1 = call.operands.get(1);
switch (op0.getKind()) {
case LITERAL:
- if (isExtractCall(op1) && canRewriteExtract()) {
- return compareExtract(call.getKind().reverse(),
- ((RexCall) op1).getOperands().get(1), (RexLiteral) op0);
+ assert op0 instanceof RexLiteral;
+ if (isExtractCall(op1)) {
+ assert op1 instanceof RexCall;
+ final RexCall subCall = (RexCall) op1;
+ RexNode operand = subCall.getOperands().get(1);
+ if (canRewriteExtract(operand)) {
+ return compareExtract(call.getKind().reverse(), operand,
+ (RexLiteral) op0);
+ }
+ }
+ if (isFloorCeilCall(op1)) {
+ assert op1 instanceof RexCall;
+ final RexCall subCall = (RexCall) op1;
+ final RexLiteral flag = (RexLiteral) subCall.operands.get(1);
+ final TimeUnitRange timeUnit = (TimeUnitRange) flag.getValue();
+ return compareFloorCeil(call.getKind().reverse(),
+ subCall.getOperands().get(0), (RexLiteral) op0,
+ timeUnit, op1.getKind() == SqlKind.FLOOR);
}
}
switch (op1.getKind()) {
case LITERAL:
- if (isExtractCall(op0) && canRewriteExtract()) {
- return compareExtract(call.getKind(),
- ((RexCall) op0).getOperands().get(1), (RexLiteral) op1);
+ assert op1 instanceof RexLiteral;
+ if (isExtractCall(op0)) {
+ assert op0 instanceof RexCall;
+ final RexCall subCall = (RexCall) op0;
+ RexNode operand = subCall.operands.get(1);
+ if (canRewriteExtract(operand)) {
+ return compareExtract(call.getKind(),
+ subCall.operands.get(1), (RexLiteral) op1);
+ }
+ }
+ if (isFloorCeilCall(op0)) {
+ final RexCall subCall = (RexCall) op0;
+ final RexLiteral flag = (RexLiteral) subCall.operands.get(1);
+ final TimeUnitRange timeUnit = (TimeUnitRange) flag.getValue();
+ return compareFloorCeil(call.getKind(),
+ subCall.getOperands().get(0), (RexLiteral) op1,
+ timeUnit, op0.getKind() == SqlKind.FLOOR);
}
}
// fall through
@@ -264,7 +314,7 @@ public abstract class DateRangeRules {
}
}
- private boolean canRewriteExtract() {
+ private boolean canRewriteExtract(RexNode operand) {
// We rely on timeUnits being sorted (so YEAR comes before MONTH
// before HOUR) and unique. If we have seen a predicate on YEAR,
// operandRanges will not be empty. This checks whether we can rewrite
@@ -274,7 +324,21 @@ public abstract class DateRangeRules {
// OR extract(YEAR from time) = someValue
//
// we cannot rewrite extract on MONTH.
- return timeUnit == TimeUnitRange.YEAR || !operandRanges.isEmpty();
+ if (timeUnit == TimeUnitRange.YEAR) {
+ return true;
+ }
+ final RangeSet<Calendar> calendarRangeSet =
+ operandRanges.get(operand.toString());
+ if (calendarRangeSet == null || calendarRangeSet.isEmpty()) {
+ return false;
+ }
+ for (Range<Calendar> range : calendarRangeSet.asRanges()) {
+ // Cannot reWrite if range does not have an upper or lower bound
+ if (!range.hasUpperBound() || !range.hasLowerBound()) {
+ return false;
+ }
+ }
+ return true;
}
@Override protected List<RexNode> visitList(List<? extends RexNode> exprs,
@@ -339,6 +403,12 @@ public abstract class DateRangeRules {
// Calendar.MONTH is 0-based
final int v = ((BigDecimal) literal.getValue()).intValue()
- (timeUnit == TimeUnitRange.MONTH ? 1 : 0);
+
+ if (!isValid(v, timeUnit)) {
+ // Comparison with an invalid value for timeUnit, always false.
+ return rexBuilder.makeLiteral(false);
+ }
+
for (Range<Calendar> r : rangeSet.asRanges()) {
final Calendar c;
switch (timeUnit) {
@@ -346,18 +416,18 @@ public abstract class DateRangeRules {
c = Util.calendar();
c.clear();
c.set(v, Calendar.JANUARY, 1);
- s2.add(range(timeUnit, comparison, c));
+ s2.add(extractRange(timeUnit, comparison, c));
break;
case MONTH:
case DAY:
case HOUR:
case MINUTE:
case SECOND:
- if (r.hasLowerBound()) {
+ if (r.hasLowerBound() && r.hasUpperBound()) {
c = (Calendar) r.lowerEndpoint().clone();
int i = 0;
while (next(c, timeUnit, v, r, i++ > 0)) {
- s2.add(range(timeUnit, comparison, c));
+ s2.add(extractRange(timeUnit, comparison, c));
}
}
}
@@ -372,6 +442,7 @@ public abstract class DateRangeRules {
return RexUtil.composeDisjunction(rexBuilder, nodes);
}
+ // Assumes v is a valid value for given timeunit
private boolean next(Calendar c, TimeUnitRange timeUnit, int v,
Range<Calendar> r, boolean strict) {
final Calendar original = (Calendar) c.clone();
@@ -395,6 +466,24 @@ public abstract class DateRangeRules {
}
}
+ private static boolean isValid(int v, TimeUnitRange timeUnit) {
+ switch (timeUnit) {
+ case YEAR:
+ return v > 0;
+ case MONTH:
+ return v >= Calendar.JANUARY && v <= Calendar.DECEMBER;
+ case DAY:
+ return v > 0 && v <= 31;
+ case HOUR:
+ return v >= 0 && v <= 24;
+ case MINUTE:
+ case SECOND:
+ return v >= 0 && v <= 60;
+ default:
+ return false;
+ }
+ }
+
private RexNode toRex(RexNode operand, Range<Calendar> r) {
final List<RexNode> nodes = new ArrayList<>();
if (r.hasLowerBound()) {
@@ -431,7 +520,7 @@ public abstract class DateRangeRules {
}
}
- private Range<Calendar> range(TimeUnitRange timeUnit, SqlKind comparison,
+ private Range<Calendar> extractRange(TimeUnitRange timeUnit, SqlKind comparison,
Calendar c) {
switch (comparison) {
case EQUALS:
@@ -461,6 +550,120 @@ public abstract class DateRangeRules {
}
return c;
}
+
+ private RexNode compareFloorCeil(SqlKind comparison, RexNode operand,
+ RexLiteral timeLiteral, TimeUnitRange timeUnit, boolean floor) {
+ RangeSet<Calendar> rangeSet = operandRanges.get(operand.toString());
+ if (rangeSet == null) {
+ rangeSet = ImmutableRangeSet.<Calendar>of().complement();
+ }
+ final RangeSet<Calendar> s2 = TreeRangeSet.create();
+ final Calendar c = timeLiteral.getValueAs(Calendar.class);
+ final Range<Calendar> range = floor
+ ? floorRange(timeUnit, comparison, c)
+ : ceilRange(timeUnit, comparison, c);
+ s2.add(range);
+ // Intersect old range set with new.
+ s2.removeAll(rangeSet.complement());
+ operandRanges.put(operand.toString(), ImmutableRangeSet.copyOf(s2));
+ if (range.isEmpty()) {
+ return rexBuilder.makeLiteral(false);
+ }
+ return toRex(operand, range);
+ }
+
+ private Range<Calendar> floorRange(TimeUnitRange timeUnit, SqlKind comparison,
+ Calendar c) {
+ Calendar floor = floor(c, timeUnit);
+ boolean boundary = floor.equals(c);
+ switch (comparison) {
+ case EQUALS:
+ return Range.closedOpen(floor, boundary ? increment(floor, timeUnit) : floor);
+ case LESS_THAN:
+ return boundary ? Range.lessThan(floor) : Range.lessThan(increment(floor, timeUnit));
+ case LESS_THAN_OR_EQUAL:
+ return Range.lessThan(increment(floor, timeUnit));
+ case GREATER_THAN:
+ return Range.atLeast(increment(floor, timeUnit));
+ case GREATER_THAN_OR_EQUAL:
+ return boundary ? Range.atLeast(floor) : Range.atLeast(increment(floor, timeUnit));
+ default:
+ throw Util.unexpected(comparison);
+ }
+ }
+
+ private Range<Calendar> ceilRange(TimeUnitRange timeUnit, SqlKind comparison,
+ Calendar c) {
+ final Calendar ceil = ceil(c, timeUnit);
+ boolean boundary = ceil.equals(c);
+ switch (comparison) {
+ case EQUALS:
+ return Range.openClosed(boundary ? decrement(ceil, timeUnit) : ceil, ceil);
+ case LESS_THAN:
+ return Range.atMost(decrement(ceil, timeUnit));
+ case LESS_THAN_OR_EQUAL:
+ return boundary ? Range.atMost(ceil) : Range.atMost(decrement(ceil, timeUnit));
+ case GREATER_THAN:
+ return boundary ? Range.greaterThan(ceil) : Range.greaterThan(decrement(ceil, timeUnit));
+ case GREATER_THAN_OR_EQUAL:
+ return Range.greaterThan(decrement(ceil, timeUnit));
+ default:
+ throw Util.unexpected(comparison);
+ }
+ }
+
+ boolean isFloorCeilCall(RexNode e) {
+ switch (e.getKind()) {
+ case FLOOR:
+ case CEIL:
+ final RexCall call = (RexCall) e;
+ return call.getOperands().size() == 2;
+ default:
+ return false;
+ }
+ }
+
+ private Calendar increment(Calendar c, TimeUnitRange timeUnit) {
+ c = (Calendar) c.clone();
+ c.add(TIME_UNIT_CODES.get(timeUnit), 1);
+ return c;
+ }
+
+ private Calendar decrement(Calendar c, TimeUnitRange timeUnit) {
+ c = (Calendar) c.clone();
+ c.add(TIME_UNIT_CODES.get(timeUnit), -1);
+ return c;
+ }
+
+ private Calendar ceil(Calendar c, TimeUnitRange timeUnit) {
+ Calendar floor = floor(c, timeUnit);
+ return floor.equals(c) ? floor : increment(floor, timeUnit);
+ }
+
+ /**
+ * Commputes floor of given calendar object to provided timeunit
+ * @return returns a copy of calendar, floored to the given timeunit
+ */
+ private Calendar floor(Calendar c, TimeUnitRange timeUnit) {
+ c = (Calendar) c.clone();
+ switch (timeUnit) {
+ case YEAR:
+ c.set(TIME_UNIT_CODES.get(TimeUnitRange.MONTH), Calendar.JANUARY);
+ // Ignore checkstyle failure for Fall through from previous branch of the switch statement.
+ // CHECKSTYLE: IGNORE 10
+ case MONTH:
+ c.set(TIME_UNIT_CODES.get(TimeUnitRange.DAY), 1);
+ case DAY:
+ c.set(TIME_UNIT_CODES.get(TimeUnitRange.HOUR), 0);
+ case HOUR:
+ c.set(TIME_UNIT_CODES.get(TimeUnitRange.MINUTE), 0);
+ case MINUTE:
+ c.set(TIME_UNIT_CODES.get(TimeUnitRange.SECOND), 0);
+ case SECOND:
+ c.set(TIME_UNIT_CODES.get(TimeUnitRange.MILLISECOND), 0);
+ }
+ return c;
+ }
}
}
http://git-wip-us.apache.org/repos/asf/calcite/blob/fa19580e/core/src/main/java/org/apache/calcite/rex/RexLiteral.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/rex/RexLiteral.java b/core/src/main/java/org/apache/calcite/rex/RexLiteral.java
index a5ed918..b5900bb 100644
--- a/core/src/main/java/org/apache/calcite/rex/RexLiteral.java
+++ b/core/src/main/java/org/apache/calcite/rex/RexLiteral.java
@@ -868,6 +868,9 @@ public class RexLiteral extends RexNode {
if (clazz == Long.class) {
// Milliseconds since 1970-01-01 00:00:00
return clazz.cast(((TimestampString) value).getMillisSinceEpoch());
+ } else if (clazz == Calendar.class) {
+ // Note: Nanos are ignored
+ return clazz.cast(((TimestampString) value).toCalendar());
}
break;
case INTERVAL_YEAR:
http://git-wip-us.apache.org/repos/asf/calcite/blob/fa19580e/core/src/test/java/org/apache/calcite/rel/rules/DateRangeRulesTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/apache/calcite/rel/rules/DateRangeRulesTest.java b/core/src/test/java/org/apache/calcite/rel/rules/DateRangeRulesTest.java
index f247fbb..f0439ab 100644
--- a/core/src/test/java/org/apache/calcite/rel/rules/DateRangeRulesTest.java
+++ b/core/src/test/java/org/apache/calcite/rel/rules/DateRangeRulesTest.java
@@ -20,19 +20,17 @@ import org.apache.calcite.avatica.util.TimeUnitRange;
import org.apache.calcite.rex.RexNode;
import org.apache.calcite.sql.fun.SqlStdOperatorTable;
import org.apache.calcite.test.RexImplicationCheckerTest.Fixture;
+import org.apache.calcite.util.TimestampString;
+import org.apache.calcite.util.Util;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
-import com.google.common.collect.ImmutableSortedSet;
-import com.google.common.collect.RangeSet;
import org.hamcrest.CoreMatchers;
import org.hamcrest.Matcher;
import org.junit.Test;
import java.util.Calendar;
-import java.util.HashMap;
-import java.util.Map;
import java.util.Set;
import static org.hamcrest.core.Is.is;
@@ -301,6 +299,364 @@ public class DateRangeRulesTest {
+ " AND(>=($8, 2001-01-01), <($8, 2001-02-01)))))"));
}
+ @Test public void testExtractRewriteForInvalidMonthComparison() {
+ // "EXTRACT(MONTH FROM ts) = 14" will never be TRUE
+ final Fixture2 f = new Fixture2();
+ checkDateRange(f,
+ f.and(f.eq(f.exYearTs, f.literal(2010)),
+ f.eq(f.exMonthTs, f.literal(14))),
+ is("AND(AND(>=($9, 2010-01-01 00:00:00), <($9, 2011-01-01 00:00:00)),"
+ + " false)"));
+
+ // "EXTRACT(MONTH FROM ts) = 0" will never be TRUE
+ checkDateRange(f,
+ f.and(f.eq(f.exYearTs, f.literal(2010)),
+ f.eq(f.exMonthTs, f.literal(0))),
+ is("AND(AND(>=($9, 2010-01-01 00:00:00), <($9, 2011-01-01 00:00:00)),"
+ + " false)"));
+
+ // "EXTRACT(MONTH FROM ts) = 13" will never be TRUE
+ checkDateRange(f,
+ f.and(f.eq(f.exYearTs, f.literal(2010)),
+ f.eq(f.exMonthTs, f.literal(13))),
+ is("AND(AND(>=($9, 2010-01-01 00:00:00), <($9, 2011-01-01 00:00:00)),"
+ + " false)"));
+
+ // "EXTRACT(MONTH FROM ts) = 12" might be TRUE
+ // Careful with boundaries, because Calendar.DECEMBER = 11
+ checkDateRange(f,
+ f.and(f.eq(f.exYearTs, f.literal(2010)),
+ f.eq(f.exMonthTs, f.literal(12))),
+ is("AND(AND(>=($9, 2010-01-01 00:00:00), <($9, 2011-01-01 00:00:00)),"
+ + " AND(>=($9, 2010-12-01 00:00:00), <($9, 2011-01-01 00:00:00)))"));
+
+ // "EXTRACT(MONTH FROM ts) = 1" can happen
+ // Careful with boundaries, because Calendar.JANUARY = 0
+ checkDateRange(f,
+ f.and(f.eq(f.exYearTs, f.literal(2010)),
+ f.eq(f.exMonthTs, f.literal(1))),
+ is("AND(AND(>=($9, 2010-01-01 00:00:00), <($9, 2011-01-01 00:00:00)),"
+ + " AND(>=($9, 2010-01-01 00:00:00), <($9, 2010-02-01 00:00:00)))"));
+ }
+
+ @Test public void testExtractRewriteForInvalidDayComparison() {
+ final Fixture2 f = new Fixture2();
+ checkDateRange(f,
+ f.and(f.eq(f.exYearTs, f.literal(2010)),
+ f.eq(f.exMonthTs, f.literal(11)),
+ f.eq(f.exDayTs, f.literal(32))),
+ is("AND(AND(>=($9, 2010-01-01 00:00:00), <($9, 2011-01-01 00:00:00)),"
+ + " AND(>=($9, 2010-11-01 00:00:00), <($9, 2010-12-01 00:00:00)), false)"));
+ // Feb 31 is an invalid date
+ checkDateRange(f,
+ f.and(f.eq(f.exYearTs, f.literal(2010)),
+ f.eq(f.exMonthTs, f.literal(2)),
+ f.eq(f.exDayTs, f.literal(31))),
+ is("AND(AND(>=($9, 2010-01-01 00:00:00), <($9, 2011-01-01 00:00:00)),"
+ + " AND(>=($9, 2010-02-01 00:00:00), <($9, 2010-03-01 00:00:00)), false)"));
+ }
+
+ @Test public void testUnboundYearExtractRewrite() {
+ final Fixture2 f = new Fixture2();
+ // No lower bound on YEAR
+ checkDateRange(f,
+ f.and(f.le(f.exYearTs, f.literal(2010)),
+ f.eq(f.exMonthTs, f.literal(11)),
+ f.eq(f.exDayTs, f.literal(2))),
+ is("AND(<($9, 2011-01-01 00:00:00), =(EXTRACT(FLAG(MONTH), $9), 11),"
+ + " =(EXTRACT(FLAG(DAY), $9), 2))"));
+
+ // No upper bound on YEAR
+ checkDateRange(f,
+ f.and(f.ge(f.exYearTs, f.literal(2010)),
+ f.eq(f.exMonthTs, f.literal(11)),
+ f.eq(f.exDayTs, f.literal(2))),
+ // Since the year does not have a upper bound, MONTH and DAY cannot be replaced
+ is("AND(>=($9, 2010-01-01 00:00:00), =(EXTRACT(FLAG(MONTH), $9), 11),"
+ + " =(EXTRACT(FLAG(DAY), $9), 2))"));
+
+ // No lower/upper bound on YEAR for individual rexNodes.
+ checkDateRange(f,
+ f.and(f.le(f.exYearTs, f.literal(2010)),
+ f.ge(f.exYearTs, f.literal(2010)),
+ f.eq(f.exMonthTs, f.literal(5))),
+ is("AND(<($9, 2011-01-01 00:00:00), AND(>=($9, 2010-01-01 00:00:00),"
+ + " <($9, 2011-01-01 00:00:00)), AND(>=($9, 2010-05-01 00:00:00),"
+ + " <($9, 2010-06-01 00:00:00)))"));
+ }
+
+ // Test reWrite with multiple operands
+ @Test public void testExtractRewriteMultipleOperands() {
+ final Fixture2 f = new Fixture2();
+ checkDateRange(f,
+ f.and(f.eq(f.exYearTs, f.literal(2010)),
+ f.eq(f.exMonthTs, f.literal(10)),
+ f.eq(f.exMonthD, f.literal(5))),
+ is("AND(AND(>=($9, 2010-01-01 00:00:00), <($9, 2011-01-01 00:00:00)),"
+ + " AND(>=($9, 2010-10-01 00:00:00), <($9, 2010-11-01 00:00:00)),"
+ + " =(EXTRACT(FLAG(MONTH), $8), 5))"));
+
+ checkDateRange(f,
+ f.and(f.eq(f.exYearTs, f.literal(2010)),
+ f.eq(f.exMonthTs, f.literal(10)),
+ f.eq(f.exYearD, f.literal(2011)),
+ f.eq(f.exMonthD, f.literal(5))),
+ is("AND(AND(>=($9, 2010-01-01 00:00:00), <($9, 2011-01-01 00:00:00)),"
+ + " AND(>=($9, 2010-10-01 00:00:00), <($9, 2010-11-01 00:00:00)),"
+ + " AND(>=($8, 2011-01-01), <($8, 2012-01-01)), AND(>=($8, 2011-05-01),"
+ + " <($8, 2011-06-01)))"));
+ }
+
+ @Test public void testFloorEqRewrite() {
+ final Calendar c = Util.calendar();
+ c.clear();
+ c.set(2010, Calendar.FEBRUARY, 10, 11, 12, 05);
+ final Fixture2 f = new Fixture2();
+ // Always False
+ checkDateRange(f, f.eq(f.floorYear, f.timestampLiteral(TimestampString.fromCalendarFields(c))),
+ is("false"));
+ checkDateRange(f, f.eq(f.timestampLiteral(TimestampString.fromCalendarFields(c)), f.floorYear),
+ is("false"));
+
+ c.clear();
+ c.set(2010, Calendar.JANUARY, 1, 0, 0, 0);
+ checkDateRange(f, f.eq(f.floorYear, f.timestampLiteral(TimestampString.fromCalendarFields(c))),
+ is("AND(>=($9, 2010-01-01 00:00:00), <($9, 2011-01-01 00:00:00))"));
+
+ c.set(2010, Calendar.FEBRUARY, 1, 0, 0, 0);
+ checkDateRange(f, f.eq(f.floorMonth, f.timestampLiteral(TimestampString.fromCalendarFields(c))),
+ is("AND(>=($9, 2010-02-01 00:00:00), <($9, 2010-03-01 00:00:00))"));
+
+ c.set(2010, Calendar.DECEMBER, 1, 0, 0, 0);
+ checkDateRange(f, f.eq(f.floorMonth, f.timestampLiteral(TimestampString.fromCalendarFields(c))),
+ is("AND(>=($9, 2010-12-01 00:00:00), <($9, 2011-01-01 00:00:00))"));
+
+ c.set(2010, Calendar.FEBRUARY, 4, 0, 0, 0);
+ checkDateRange(f, f.eq(f.floorDay, f.timestampLiteral(TimestampString.fromCalendarFields(c))),
+ is("AND(>=($9, 2010-02-04 00:00:00), <($9, 2010-02-05 00:00:00))"));
+
+ c.set(2010, Calendar.DECEMBER, 31, 0, 0, 0);
+ checkDateRange(f, f.eq(f.floorDay, f.timestampLiteral(TimestampString.fromCalendarFields(c))),
+ is("AND(>=($9, 2010-12-31 00:00:00), <($9, 2011-01-01 00:00:00))"));
+
+ c.set(2010, Calendar.FEBRUARY, 4, 4, 0, 0);
+ checkDateRange(f, f.eq(f.floorHour, f.timestampLiteral(TimestampString.fromCalendarFields(c))),
+ is("AND(>=($9, 2010-02-04 04:00:00), <($9, 2010-02-04 05:00:00))"));
+
+ c.set(2010, Calendar.DECEMBER, 31, 23, 0, 0);
+ checkDateRange(f, f.eq(f.floorHour, f.timestampLiteral(TimestampString.fromCalendarFields(c))),
+ is("AND(>=($9, 2010-12-31 23:00:00), <($9, 2011-01-01 00:00:00))"));
+
+ c.set(2010, Calendar.FEBRUARY, 4, 2, 32, 0);
+ checkDateRange(f,
+ f.eq(f.floorMinute, f.timestampLiteral(TimestampString.fromCalendarFields(c))),
+ is("AND(>=($9, 2010-02-04 02:32:00), <($9, 2010-02-04 02:33:00))"));
+
+ c.set(2010, Calendar.FEBRUARY, 4, 2, 59, 0);
+ checkDateRange(f,
+ f.eq(f.floorMinute, f.timestampLiteral(TimestampString.fromCalendarFields(c))),
+ is("AND(>=($9, 2010-02-04 02:59:00), <($9, 2010-02-04 03:00:00))"));
+ }
+
+ @Test public void testFloorLtRewrite() {
+ final Calendar c = Util.calendar();
+
+ c.clear();
+ c.set(2010, Calendar.FEBRUARY, 10, 11, 12, 05);
+ final Fixture2 f = new Fixture2();
+ checkDateRange(f, f.lt(f.floorYear, f.timestampLiteral(TimestampString.fromCalendarFields(c))),
+ is("<($9, 2011-01-01 00:00:00)"));
+
+ c.clear();
+ c.set(2010, Calendar.JANUARY, 1, 0, 0, 0);
+ checkDateRange(f, f.lt(f.floorYear, f.timestampLiteral(TimestampString.fromCalendarFields(c))),
+ is("<($9, 2010-01-01 00:00:00)"));
+ }
+
+ @Test public void testFloorLeRewrite() {
+ final Calendar c = Util.calendar();
+ c.clear();
+ c.set(2010, Calendar.FEBRUARY, 10, 11, 12, 05);
+ final Fixture2 f = new Fixture2();
+ checkDateRange(f, f.le(f.floorYear, f.timestampLiteral(TimestampString.fromCalendarFields(c))),
+ is("<($9, 2011-01-01 00:00:00)"));
+
+ c.clear();
+ c.set(2010, Calendar.JANUARY, 1, 0, 0, 0);
+ checkDateRange(f, f.le(f.floorYear, f.timestampLiteral(TimestampString.fromCalendarFields(c))),
+ is("<($9, 2011-01-01 00:00:00)"));
+ }
+
+ @Test public void testFloorGtRewrite() {
+ final Calendar c = Util.calendar();
+ c.clear();
+ c.set(2010, Calendar.FEBRUARY, 10, 11, 12, 05);
+ final Fixture2 f = new Fixture2();
+ checkDateRange(f, f.gt(f.floorYear, f.timestampLiteral(TimestampString.fromCalendarFields(c))),
+ is(">=($9, 2011-01-01 00:00:00)"));
+
+ c.clear();
+ c.set(2010, Calendar.JANUARY, 1, 0, 0, 0);
+ checkDateRange(f, f.gt(f.floorYear, f.timestampLiteral(TimestampString.fromCalendarFields(c))),
+ is(">=($9, 2011-01-01 00:00:00)"));
+ }
+
+ @Test public void testFloorGeRewrite() {
+ final Calendar c = Util.calendar();
+ c.clear();
+ c.set(2010, Calendar.FEBRUARY, 10, 11, 12, 05);
+ final Fixture2 f = new Fixture2();
+ checkDateRange(f, f.ge(f.floorYear, f.timestampLiteral(TimestampString.fromCalendarFields(c))),
+ is(">=($9, 2011-01-01 00:00:00)"));
+
+ c.clear();
+ c.set(2010, Calendar.JANUARY, 1, 0, 0, 0);
+ checkDateRange(f, f.ge(f.floorYear, f.timestampLiteral(TimestampString.fromCalendarFields(c))),
+ is(">=($9, 2010-01-01 00:00:00)"));
+ }
+
+ @Test public void testFloorExtractBothRewrite() {
+ final Calendar c = Util.calendar();
+ c.clear();
+ Fixture2 f = new Fixture2();
+ c.clear();
+ c.set(2010, Calendar.JANUARY, 1, 0, 0, 0);
+ checkDateRange(f,
+ f.and(f.eq(f.floorYear, f.timestampLiteral(TimestampString.fromCalendarFields(c))),
+ f.eq(f.exMonthTs, f.literal(5))),
+ is("AND(AND(>=($9, 2010-01-01 00:00:00), <($9, 2011-01-01 00:00:00)),"
+ + " AND(>=($9, 2010-05-01 00:00:00), <($9, 2010-06-01 00:00:00)))"));
+
+ // No lower range for floor
+ checkDateRange(f,
+ f.and(f.le(f.floorYear, f.timestampLiteral(TimestampString.fromCalendarFields(c))),
+ f.eq(f.exMonthTs, f.literal(5))),
+ is("AND(<($9, 2011-01-01 00:00:00), =(EXTRACT(FLAG(MONTH), $9), 5))"));
+
+ // No lower range for floor
+ checkDateRange(f,
+ f.and(f.gt(f.floorYear, f.timestampLiteral(TimestampString.fromCalendarFields(c))),
+ f.eq(f.exMonthTs, f.literal(5))),
+ is("AND(>=($9, 2011-01-01 00:00:00), =(EXTRACT(FLAG(MONTH), $9), 5))"));
+
+ // No upper range for individual floor rexNodes, but combined results in bounded interval
+ checkDateRange(f,
+ f.and(f.le(f.floorYear, f.timestampLiteral(TimestampString.fromCalendarFields(c))),
+ f.eq(f.exMonthTs, f.literal(5)),
+ f.ge(f.floorYear, f.timestampLiteral(TimestampString.fromCalendarFields(c)))),
+ is("AND(<($9, 2011-01-01 00:00:00), AND(>=($9, 2010-05-01 00:00:00),"
+ + " <($9, 2010-06-01 00:00:00)), >=($9, 2010-01-01 00:00:00))"));
+
+ }
+
+ @Test public void testCeilEqRewrite() {
+ final Calendar c = Util.calendar();
+ c.clear();
+ c.set(2010, Calendar.FEBRUARY, 10, 11, 12, 05);
+ final Fixture2 f = new Fixture2();
+ // Always False
+ checkDateRange(f, f.eq(f.ceilYear, f.timestampLiteral(TimestampString.fromCalendarFields(c))),
+ is("false"));
+ checkDateRange(f, f.eq(f.timestampLiteral(TimestampString.fromCalendarFields(c)), f.ceilYear),
+ is("false"));
+
+ c.clear();
+ c.set(2010, Calendar.JANUARY, 1, 0, 0, 0);
+ checkDateRange(f, f.eq(f.ceilYear, f.timestampLiteral(TimestampString.fromCalendarFields(c))),
+ is("AND(>($9, 2009-01-01 00:00:00), <=($9, 2010-01-01 00:00:00))"));
+
+ c.set(2010, Calendar.FEBRUARY, 1, 0, 0, 0);
+ checkDateRange(f, f.eq(f.ceilMonth, f.timestampLiteral(TimestampString.fromCalendarFields(c))),
+ is("AND(>($9, 2010-01-01 00:00:00), <=($9, 2010-02-01 00:00:00))"));
+
+ c.set(2010, Calendar.DECEMBER, 1, 0, 0, 0);
+ checkDateRange(f, f.eq(f.ceilMonth, f.timestampLiteral(TimestampString.fromCalendarFields(c))),
+ is("AND(>($9, 2010-11-01 00:00:00), <=($9, 2010-12-01 00:00:00))"));
+
+ c.set(2010, Calendar.FEBRUARY, 4, 0, 0, 0);
+ checkDateRange(f, f.eq(f.ceilDay, f.timestampLiteral(TimestampString.fromCalendarFields(c))),
+ is("AND(>($9, 2010-02-03 00:00:00), <=($9, 2010-02-04 00:00:00))"));
+
+ c.set(2010, Calendar.DECEMBER, 31, 0, 0, 0);
+ checkDateRange(f, f.eq(f.ceilDay, f.timestampLiteral(TimestampString.fromCalendarFields(c))),
+ is("AND(>($9, 2010-12-30 00:00:00), <=($9, 2010-12-31 00:00:00))"));
+
+ c.set(2010, Calendar.FEBRUARY, 4, 4, 0, 0);
+ checkDateRange(f, f.eq(f.ceilHour, f.timestampLiteral(TimestampString.fromCalendarFields(c))),
+ is("AND(>($9, 2010-02-04 03:00:00), <=($9, 2010-02-04 04:00:00))"));
+
+ c.set(2010, Calendar.DECEMBER, 31, 23, 0, 0);
+ checkDateRange(f, f.eq(f.ceilHour, f.timestampLiteral(TimestampString.fromCalendarFields(c))),
+ is("AND(>($9, 2010-12-31 22:00:00), <=($9, 2010-12-31 23:00:00))"));
+
+ c.set(2010, Calendar.FEBRUARY, 4, 2, 32, 0);
+ checkDateRange(f,
+ f.eq(f.ceilMinute, f.timestampLiteral(TimestampString.fromCalendarFields(c))),
+ is("AND(>($9, 2010-02-04 02:31:00), <=($9, 2010-02-04 02:32:00))"));
+
+ c.set(2010, Calendar.FEBRUARY, 4, 2, 59, 0);
+ checkDateRange(f,
+ f.eq(f.ceilMinute, f.timestampLiteral(TimestampString.fromCalendarFields(c))),
+ is("AND(>($9, 2010-02-04 02:58:00), <=($9, 2010-02-04 02:59:00))"));
+ }
+
+ @Test public void testCeilLtRewrite() {
+ final Calendar c = Util.calendar();
+
+ c.clear();
+ c.set(2010, Calendar.FEBRUARY, 10, 11, 12, 05);
+ final Fixture2 f = new Fixture2();
+ checkDateRange(f, f.lt(f.ceilYear, f.timestampLiteral(TimestampString.fromCalendarFields(c))),
+ is("<=($9, 2010-01-01 00:00:00)"));
+
+ c.clear();
+ c.set(2010, Calendar.JANUARY, 1, 0, 0, 0);
+ checkDateRange(f, f.lt(f.ceilYear, f.timestampLiteral(TimestampString.fromCalendarFields(c))),
+ is("<=($9, 2009-01-01 00:00:00)"));
+ }
+
+ @Test public void testCeilLeRewrite() {
+ final Calendar c = Util.calendar();
+ c.clear();
+ c.set(2010, Calendar.FEBRUARY, 10, 11, 12, 05);
+ final Fixture2 f = new Fixture2();
+ checkDateRange(f, f.le(f.ceilYear, f.timestampLiteral(TimestampString.fromCalendarFields(c))),
+ is("<=($9, 2010-01-01 00:00:00)"));
+
+ c.clear();
+ c.set(2010, Calendar.JANUARY, 1, 0, 0, 0);
+ checkDateRange(f, f.le(f.ceilYear, f.timestampLiteral(TimestampString.fromCalendarFields(c))),
+ is("<=($9, 2010-01-01 00:00:00)"));
+ }
+
+ @Test public void testCeilGtRewrite() {
+ final Calendar c = Util.calendar();
+ c.clear();
+ c.set(2010, Calendar.FEBRUARY, 10, 11, 12, 05);
+ final Fixture2 f = new Fixture2();
+ checkDateRange(f, f.gt(f.ceilYear, f.timestampLiteral(TimestampString.fromCalendarFields(c))),
+ is(">($9, 2010-01-01 00:00:00)"));
+
+ c.clear();
+ c.set(2010, Calendar.JANUARY, 1, 0, 0, 0);
+ checkDateRange(f, f.gt(f.ceilYear, f.timestampLiteral(TimestampString.fromCalendarFields(c))),
+ is(">($9, 2010-01-01 00:00:00)"));
+ }
+
+ @Test public void testCeilGeRewrite() {
+ final Calendar c = Util.calendar();
+ c.clear();
+ c.set(2010, Calendar.FEBRUARY, 10, 11, 12, 05);
+ final Fixture2 f = new Fixture2();
+ checkDateRange(f, f.ge(f.ceilYear, f.timestampLiteral(TimestampString.fromCalendarFields(c))),
+ is(">($9, 2010-01-01 00:00:00)"));
+
+ c.clear();
+ c.set(2010, Calendar.JANUARY, 1, 0, 0, 0);
+ checkDateRange(f, f.ge(f.ceilYear, f.timestampLiteral(TimestampString.fromCalendarFields(c))),
+ is(">($9, 2009-01-01 00:00:00)"));
+ }
+
private static Set<TimeUnitRange> set(TimeUnitRange... es) {
return ImmutableSet.copyOf(es);
}
@@ -311,14 +667,7 @@ public class DateRangeRulesTest {
private void checkDateRange(Fixture f, RexNode e, Matcher<String> matcher,
Matcher<String> simplifyMatcher) {
- final Map<String, RangeSet<Calendar>> operandRanges = new HashMap<>();
- final ImmutableSortedSet<TimeUnitRange> timeUnits =
- DateRangeRules.extractTimeUnits(e);
- for (TimeUnitRange timeUnit : timeUnits) {
- e = e.accept(
- new DateRangeRules.ExtractShuttle(f.rexBuilder, timeUnit,
- operandRanges, timeUnits));
- }
+ e = DateRangeRules.replaceTimeUnits(f.rexBuilder, e);
assertThat(e.toString(), matcher);
final RexNode e2 = f.simplify.simplify(e);
assertThat(e2.toString(), simplifyMatcher);
@@ -333,6 +682,18 @@ public class DateRangeRulesTest {
private final RexNode exMonthD; // EXTRACT MONTH from DATE field
private final RexNode exDayD; // EXTRACT DAY from DATE field
+ private final RexNode floorYear;
+ private final RexNode floorMonth;
+ private final RexNode floorDay;
+ private final RexNode floorHour;
+ private final RexNode floorMinute;
+
+ private final RexNode ceilYear;
+ private final RexNode ceilMonth;
+ private final RexNode ceilDay;
+ private final RexNode ceilHour;
+ private final RexNode ceilMinute;
+
Fixture2() {
exYearTs = rexBuilder.makeCall(SqlStdOperatorTable.EXTRACT,
ImmutableList.of(rexBuilder.makeFlag(TimeUnitRange.YEAR), ts));
@@ -350,6 +711,28 @@ public class DateRangeRulesTest {
exDayD = rexBuilder.makeCall(intRelDataType,
SqlStdOperatorTable.EXTRACT,
ImmutableList.of(rexBuilder.makeFlag(TimeUnitRange.DAY), d));
+
+ floorYear = rexBuilder.makeCall(intRelDataType, SqlStdOperatorTable.FLOOR,
+ ImmutableList.<RexNode>of(ts, rexBuilder.makeFlag(TimeUnitRange.YEAR)));
+ floorMonth = rexBuilder.makeCall(intRelDataType, SqlStdOperatorTable.FLOOR,
+ ImmutableList.<RexNode>of(ts, rexBuilder.makeFlag(TimeUnitRange.MONTH)));
+ floorDay = rexBuilder.makeCall(intRelDataType, SqlStdOperatorTable.FLOOR,
+ ImmutableList.<RexNode>of(ts, rexBuilder.makeFlag(TimeUnitRange.DAY)));
+ floorHour = rexBuilder.makeCall(intRelDataType, SqlStdOperatorTable.FLOOR,
+ ImmutableList.<RexNode>of(ts, rexBuilder.makeFlag(TimeUnitRange.HOUR)));
+ floorMinute = rexBuilder.makeCall(intRelDataType, SqlStdOperatorTable.FLOOR,
+ ImmutableList.<RexNode>of(ts, rexBuilder.makeFlag(TimeUnitRange.MINUTE)));
+
+ ceilYear = rexBuilder.makeCall(intRelDataType, SqlStdOperatorTable.CEIL,
+ ImmutableList.<RexNode>of(ts, rexBuilder.makeFlag(TimeUnitRange.YEAR)));
+ ceilMonth = rexBuilder.makeCall(intRelDataType, SqlStdOperatorTable.CEIL,
+ ImmutableList.<RexNode>of(ts, rexBuilder.makeFlag(TimeUnitRange.MONTH)));
+ ceilDay = rexBuilder.makeCall(intRelDataType, SqlStdOperatorTable.CEIL,
+ ImmutableList.<RexNode>of(ts, rexBuilder.makeFlag(TimeUnitRange.DAY)));
+ ceilHour = rexBuilder.makeCall(intRelDataType, SqlStdOperatorTable.CEIL,
+ ImmutableList.<RexNode>of(ts, rexBuilder.makeFlag(TimeUnitRange.HOUR)));
+ ceilMinute = rexBuilder.makeCall(intRelDataType, SqlStdOperatorTable.CEIL,
+ ImmutableList.<RexNode>of(ts, rexBuilder.makeFlag(TimeUnitRange.MINUTE)));
}
}
}
http://git-wip-us.apache.org/repos/asf/calcite/blob/fa19580e/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 e5931b1..3e7f7bd 100644
--- a/druid/src/test/java/org/apache/calcite/test/DruidAdapterIT.java
+++ b/druid/src/test/java/org/apache/calcite/test/DruidAdapterIT.java
@@ -1434,13 +1434,14 @@ public class DruidAdapterIT {
+ "from \"wiki\"\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"
+ + "and \"time\" + INTERVAL '1' DAY > '1997-01-01'\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=[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"
+ + " BindableFilter(condition=[AND(>($0, 1996-12-31 00:00:00), <=($0, 1997-08-31 00:00:00), >(+($0, 86400000), CAST('1997-01-01'):TIMESTAMP_WITH_LOCAL_TIME_ZONE(0) NOT NULL))])\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.