diff --git a/core/trino-main/src/main/java/io/trino/sql/planner/iterative/rule/UnwrapCastInComparison.java b/core/trino-main/src/main/java/io/trino/sql/planner/iterative/rule/UnwrapCastInComparison.java index bd72b003c6b6..7d47c48b97ca 100644 --- a/core/trino-main/src/main/java/io/trino/sql/planner/iterative/rule/UnwrapCastInComparison.java +++ b/core/trino-main/src/main/java/io/trino/sql/planner/iterative/rule/UnwrapCastInComparison.java @@ -22,8 +22,10 @@ import io.trino.spi.TrinoException; import io.trino.spi.function.InvocationConvention; import io.trino.spi.type.CharType; +import io.trino.spi.type.DateType; import io.trino.spi.type.DecimalType; import io.trino.spi.type.DoubleType; +import io.trino.spi.type.LongTimestamp; import io.trino.spi.type.LongTimestampWithTimeZone; import io.trino.spi.type.RealType; import io.trino.spi.type.TimeWithTimeZoneType; @@ -39,6 +41,7 @@ import io.trino.sql.planner.NoOpSymbolResolver; import io.trino.sql.planner.TypeAnalyzer; import io.trino.sql.planner.TypeProvider; +import io.trino.sql.tree.BetweenPredicate; import io.trino.sql.tree.Cast; import io.trino.sql.tree.ComparisonExpression; import io.trino.sql.tree.Expression; @@ -68,7 +71,9 @@ import static io.trino.spi.type.DateType.DATE; import static io.trino.spi.type.DoubleType.DOUBLE; import static io.trino.spi.type.IntegerType.INTEGER; +import static io.trino.spi.type.LongTimestampWithTimeZone.fromEpochMillisAndFraction; import static io.trino.spi.type.RealType.REAL; +import static io.trino.spi.type.TimestampType.createTimestampType; import static io.trino.spi.type.Timestamps.PICOSECONDS_PER_NANOSECOND; import static io.trino.spi.type.TypeUtils.isFloatingPointNaN; import static io.trino.sql.ExpressionUtils.and; @@ -81,6 +86,8 @@ import static io.trino.sql.tree.ComparisonExpression.Operator.LESS_THAN; import static io.trino.sql.tree.ComparisonExpression.Operator.LESS_THAN_OR_EQUAL; import static io.trino.sql.tree.ComparisonExpression.Operator.NOT_EQUAL; +import static io.trino.type.DateTimes.PICOSECONDS_PER_MICROSECOND; +import static io.trino.type.DateTimes.scaleFactor; import static java.lang.Float.intBitsToFloat; import static java.lang.Math.toIntExact; import static java.util.Objects.requireNonNull; @@ -171,6 +178,51 @@ public Expression rewriteComparisonExpression(ComparisonExpression node, Void co return unwrapCast(expression); } + @Override + public Expression rewriteBetweenPredicate(BetweenPredicate node, Void context, ExpressionTreeRewriter treeRewriter) + { + BetweenPredicate expression = (BetweenPredicate) treeRewriter.defaultRewrite((Expression) node, null); + return unwrapCast(expression); + } + + private Expression unwrapCast(BetweenPredicate expression) + { + // Canonicalization is handled by CanonicalizeExpressionRewriter + if (!(expression.getValue() instanceof Cast cast)) { + return expression; + } + + Object min = new ExpressionInterpreter(expression.getMin(), plannerContext, session, typeAnalyzer.getTypes(session, types, expression.getMin())) + .optimize(NoOpSymbolResolver.INSTANCE); + Object max = new ExpressionInterpreter(expression.getMax(), plannerContext, session, typeAnalyzer.getTypes(session, types, expression.getMax())) + .optimize(NoOpSymbolResolver.INSTANCE); + + if (min == null || min instanceof NullLiteral || max == null || max instanceof NullLiteral) { + return new Cast(new NullLiteral(), toSqlType(BOOLEAN)); + } + + if (min instanceof Expression || max instanceof Expression) { + return expression; + } + + Type sourceType = typeAnalyzer.getType(session, types, cast.getExpression()); + Type minType = typeAnalyzer.getType(session, types, expression.getMin()); + Type maxType = typeAnalyzer.getType(session, types, expression.getMax()); + verify(minType.equals(maxType), "Mismatched types: %s and %s", minType, maxType); + + if (sourceType instanceof TimestampType && minType == DATE) { + return unwrapTimestampToDateCastForRange(session, (TimestampType) sourceType, cast.getExpression(), (long) min, (long) max); + } + if (!hasInjectiveImplicitCoercion(sourceType, minType, min) || !hasInjectiveImplicitCoercion(sourceType, maxType, max)) { + return expression; + } + if (sourceType instanceof DateType && minType instanceof TimestampType) { + return unwrapDateToTimestampCastForRange(expression, cast, min, max, sourceType, minType); + } + + return expression; + } + private Expression unwrapCast(ComparisonExpression expression) { // Canonicalization is handled by CanonicalizeExpressionRewriter @@ -387,6 +439,81 @@ private Optional unwrapTimestampToDateCast(Session session, Timestam }; } + private Expression unwrapTimestampToDateCastForRange( + Session session, + TimestampType sourceType, + Expression timestampExpression, + long minDateInclusive, + long maxDateInclusive) + { + ResolvedFunction targetToSource; + try { + targetToSource = plannerContext.getMetadata().getCoercion(session, DATE, sourceType); + } + catch (OperatorNotFoundException e) { + throw new TrinoException(GENERIC_INTERNAL_ERROR, e); + } + + Expression minDateInclusiveTimestamp = literalEncoder.toExpression(session, coerce(minDateInclusive, targetToSource), sourceType); + Expression maxDateInclusiveTimestamp; + if (sourceType.isShort()) { + long maxDateExclusive = (long) coerce(maxDateInclusive + 1, targetToSource); + long maxTimestampInclusiveMicros = maxDateExclusive - scaleFactor(sourceType.getPrecision(), TimestampType.MAX_SHORT_PRECISION); + maxDateInclusiveTimestamp = literalEncoder.toExpression(session, maxTimestampInclusiveMicros, sourceType); + } + else { + ResolvedFunction targetToSourceShortTimestamp; + try { + targetToSourceShortTimestamp = plannerContext.getMetadata().getCoercion(session, DATE, createTimestampType(TimestampType.MAX_SHORT_PRECISION)); + } + catch (OperatorNotFoundException e) { + throw new TrinoException(GENERIC_INTERNAL_ERROR, e); + } + long maxDateExclusive = (long) coerce(maxDateInclusive + 1, targetToSourceShortTimestamp); + long maxTimestampInclusiveMicros = maxDateExclusive - scaleFactor(sourceType.getPrecision(), TimestampType.MAX_SHORT_PRECISION); + int picosOfMicro = toIntExact(PICOSECONDS_PER_MICROSECOND - scaleFactor(sourceType.getPrecision(), TimestampType.MAX_PRECISION)); + maxDateInclusiveTimestamp = literalEncoder.toExpression( + session, + new LongTimestamp(maxTimestampInclusiveMicros, picosOfMicro), + sourceType); + } + + return new BetweenPredicate(timestampExpression, minDateInclusiveTimestamp, maxDateInclusiveTimestamp); + } + + private BetweenPredicate unwrapDateToTimestampCastForRange(BetweenPredicate expression, Cast cast, Object min, Object max, Type sourceType, Type minType) + { + ResolvedFunction targetToSource; + try { + targetToSource = plannerContext.getMetadata().getCoercion(session, minType, sourceType); + } + catch (OperatorNotFoundException e) { + // Without a cast between target -> source, there's nothing more we can do + return expression; + } + + Object minLiteralInSourceType; + Object maxLiteralInSourceType; + try { + minLiteralInSourceType = coerce(min, targetToSource); + maxLiteralInSourceType = coerce(max, targetToSource); + } + catch (TrinoException e) { + // A failure to cast from target -> source type could be because: + // 1. missing cast + // 2. bad implementation + // 3. out of range or otherwise unrepresentable value + // Since we can't distinguish between those cases, take the conservative option + // and bail out. + return expression; + } + + return new BetweenPredicate( + cast.getExpression(), + literalEncoder.toExpression(session, minLiteralInSourceType, sourceType), + literalEncoder.toExpression(session, maxLiteralInSourceType, sourceType)); + } + private boolean hasInjectiveImplicitCoercion(Type source, Type target, Object value) { if ((source.equals(BIGINT) && target.equals(DOUBLE)) || @@ -506,7 +633,7 @@ private static Object withTimeZone(TimestampWithTimeZoneType type, Object value, return packDateTimeWithZone(unpackMillisUtc((long) value), newZone); } LongTimestampWithTimeZone longTimestampWithTimeZone = (LongTimestampWithTimeZone) value; - return LongTimestampWithTimeZone.fromEpochMillisAndFraction(longTimestampWithTimeZone.getEpochMillis(), longTimestampWithTimeZone.getPicosOfMilli(), newZone); + return fromEpochMillisAndFraction(longTimestampWithTimeZone.getEpochMillis(), longTimestampWithTimeZone.getPicosOfMilli(), newZone); } private static TimeZoneKey getTimeZone(TimestampWithTimeZoneType type, Object value) diff --git a/core/trino-main/src/main/java/io/trino/sql/planner/iterative/rule/UnwrapDateTruncInComparison.java b/core/trino-main/src/main/java/io/trino/sql/planner/iterative/rule/UnwrapDateTruncInComparison.java index 8671c0bbf737..12a20f4c7a57 100644 --- a/core/trino-main/src/main/java/io/trino/sql/planner/iterative/rule/UnwrapDateTruncInComparison.java +++ b/core/trino-main/src/main/java/io/trino/sql/planner/iterative/rule/UnwrapDateTruncInComparison.java @@ -152,6 +152,93 @@ public Expression rewriteComparisonExpression(ComparisonExpression node, Void co return unwrapDateTrunc(expression); } + @Override + public Expression rewriteBetweenPredicate(BetweenPredicate node, Void context, ExpressionTreeRewriter treeRewriter) + { + BetweenPredicate expression = (BetweenPredicate) treeRewriter.defaultRewrite((Expression) node, null); + return unwrapDateTrunc(expression); + } + + /** + * Given constant temporal unit U, the constant expressions: + *
    + *
  • tmin
  • + *
  • tmax
  • + *
+ * and epsilon as the minimum unit of precision for the temporal + * type of the expression dt, rewrite expression of the form + *
date_trunc(U, dt) BETWEEN tmin AND tmax
+ *

+ * into + *

dt BETWEEN tmin AND floor(tmax, U) + U - epsilon
+ *

+ */ + private Expression unwrapDateTrunc(BetweenPredicate expression) + { + if (!(expression.getValue() instanceof FunctionCall call) || + !extractFunctionName(call.getName()).equals("date_trunc") || + call.getArguments().size() != 2) { + return expression; + } + + Map, Type> expressionTypes = typeAnalyzer.getTypes(session, types, expression); + Expression unitExpression = call.getArguments().get(0); + if (!(expressionTypes.get(NodeRef.of(unitExpression)) instanceof VarcharType) || !isEffectivelyLiteral(plannerContext, session, unitExpression)) { + return expression; + } + Slice unitName = (Slice) new ExpressionInterpreter(unitExpression, plannerContext, session, expressionTypes) + .optimize(NoOpSymbolResolver.INSTANCE); + if (unitName == null) { + return expression; + } + + Expression argument = call.getArguments().get(1); + Type argumentType = expressionTypes.get(NodeRef.of(argument)); + + Type minType = expressionTypes.get(NodeRef.of(expression.getMin())); + Type maxType = expressionTypes.get(NodeRef.of(expression.getMax())); + verify(argumentType.equals(minType), "Mismatched types: %s and %s", argumentType, minType); + verify(argumentType.equals(maxType), "Mismatched types: %s and %s", argumentType, maxType); + + Object min = new ExpressionInterpreter(expression.getMin(), plannerContext, session, expressionTypes) + .optimize(NoOpSymbolResolver.INSTANCE); + Object max = new ExpressionInterpreter(expression.getMax(), plannerContext, session, expressionTypes) + .optimize(NoOpSymbolResolver.INSTANCE); + + if (min == null || min instanceof NullLiteral || max == null || max instanceof NullLiteral) { + return new Cast(new NullLiteral(), toSqlType(BOOLEAN)); + } + + if (min instanceof Expression || max instanceof Expression) { + return expression; + } + if (minType instanceof TimestampWithTimeZoneType || maxType instanceof TimestampWithTimeZoneType) { + // Cannot replace with a range due to how date_trunc operates on value's local date/time. + // I.e. unwrapping is possible only when values are all of some fixed zone and the zone is known. + return expression; + } + + Optional unitIfSupported = Enums.getIfPresent(SupportedUnit.class, unitName.toStringUtf8().toUpperCase(Locale.ENGLISH)).toJavaUtil(); + if (unitIfSupported.isEmpty()) { + return expression; + } + SupportedUnit unit = unitIfSupported.get(); + if (minType == DATE && (unit == SupportedUnit.DAY || unit == SupportedUnit.HOUR)) { + // DAY case handled by CanonicalizeExpressionRewriter, other is illegal, will fail + return expression; + } + + ResolvedFunction resolvedFunction = plannerContext.getMetadata().decodeFunction(call.getName()); + Object rangeHigh = functionInvoker.invoke(resolvedFunction, session.toConnectorSession(), ImmutableList.of(unitName, max)); + int compareMax = compare(maxType, rangeHigh, max); + verify(compareMax <= 0, "Truncation of %s value %s resulted in a bigger value %s", maxType, max, rangeHigh); + + return new BetweenPredicate( + argument, + toExpression(min, minType), + toExpression(calculateRangeEndInclusive(rangeHigh, maxType, unit), maxType)); + } + // Simplify `date_trunc(unit, d) ? value` private Expression unwrapDateTrunc(ComparisonExpression expression) { @@ -281,12 +368,12 @@ private Object calculateRangeEndInclusive(Object rangeStart, Type type, Supporte }; long endExclusiveMicros = endExclusive.toEpochSecond(ZoneOffset.UTC) * MICROSECONDS_PER_SECOND + LongMath.divide(endExclusive.getNano(), NANOSECONDS_PER_MICROSECOND, UNNECESSARY); - return endExclusiveMicros - scaleFactor(timestampType.getPrecision(), 6); + return endExclusiveMicros - scaleFactor(timestampType.getPrecision(), TimestampType.MAX_SHORT_PRECISION); } LongTimestamp longTimestamp = (LongTimestamp) rangeStart; verify(longTimestamp.getPicosOfMicro() == 0, "Unexpected picos in %s, value not rounded to %s", rangeStart, rangeUnit); - long endInclusiveMicros = (long) calculateRangeEndInclusive(longTimestamp.getEpochMicros(), createTimestampType(6), rangeUnit); - return new LongTimestamp(endInclusiveMicros, toIntExact(PICOSECONDS_PER_MICROSECOND - scaleFactor(timestampType.getPrecision(), 12))); + long endInclusiveMicros = (long) calculateRangeEndInclusive(longTimestamp.getEpochMicros(), createTimestampType(TimestampType.MAX_SHORT_PRECISION), rangeUnit); + return new LongTimestamp(endInclusiveMicros, toIntExact(PICOSECONDS_PER_MICROSECOND - scaleFactor(timestampType.getPrecision(), TimestampType.MAX_PRECISION))); } throw new UnsupportedOperationException("Unsupported type: " + type); } diff --git a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/BaseIcebergConnectorTest.java b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/BaseIcebergConnectorTest.java index 4a94b7f2873f..581fce922567 100644 --- a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/BaseIcebergConnectorTest.java +++ b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/BaseIcebergConnectorTest.java @@ -1397,29 +1397,57 @@ else if (format == AVRO) { .isFullyPushedDown(); assertThat(query("SELECT * FROM test_hour_transform_timestamp WHERE CAST(d AS date) >= DATE '2015-05-15'")) .isFullyPushedDown(); + assertThat(query("SELECT * FROM test_hour_transform_timestamp WHERE CAST(d AS DATE) BETWEEN DATE '2015-05-15' AND DATE '2015-06-15'")) + .isFullyPushedDown(); assertThat(query("SELECT * FROM test_hour_transform_timestamp WHERE d >= TIMESTAMP '2015-05-15 12:00:00'")) .isFullyPushedDown(); + assertThat(query("SELECT * FROM test_hour_transform_timestamp WHERE d BETWEEN TIMESTAMP '2015-05-15 12:00:00' AND TIMESTAMP '2015-06-15 11:59:59.999999'")) + .isFullyPushedDown(); assertThat(query("SELECT * FROM test_hour_transform_timestamp WHERE d >= TIMESTAMP '2015-05-15 12:00:00.000001'")) .isNotFullyPushedDown(FilterNode.class); + assertThat(query("SELECT * FROM test_hour_transform_timestamp WHERE d BETWEEN TIMESTAMP '2015-05-15 12:00:00.000001' AND TIMESTAMP '2015-06-15 11:59:59.999999'")) + .isNotFullyPushedDown(FilterNode.class); + assertThat(query("SELECT * FROM test_hour_transform_timestamp WHERE d BETWEEN TIMESTAMP '2015-05-15 12:00:00' AND TIMESTAMP '2015-06-15 12:00:00.00000'")) + .isNotFullyPushedDown(FilterNode.class); // date() assertThat(query("SELECT * FROM test_hour_transform_timestamp WHERE date(d) = DATE '2015-05-15'")) .isFullyPushedDown(); + assertThat(query("SELECT * FROM test_hour_transform_timestamp WHERE date(d) BETWEEN DATE '2015-05-15' AND DATE '2015-06-15'")) + .isFullyPushedDown(); + assertThat(query("SELECT * FROM test_hour_transform_timestamp WHERE date(d) BETWEEN TIMESTAMP '2015-05-15 12:00:00' AND TIMESTAMP '2015-06-15 12:00:00.00000'")) + .isFullyPushedDown(); // the range TIMESTAMP bound values are coerced to DATE // year() assertThat(query("SELECT * FROM test_hour_transform_timestamp WHERE year(d) = 2015")) .isNotFullyPushedDown(FilterNode.class); // TODO convert into range + assertThat(query("SELECT * FROM test_hour_transform_timestamp WHERE year(d) BETWEEN 2015 AND 2016")) + .isNotFullyPushedDown(FilterNode.class); // TODO convert into range // date_trunc assertThat(query("SELECT * FROM test_hour_transform_timestamp WHERE date_trunc('hour', d) = TIMESTAMP '2015-05-15 12:00:00'")) .isFullyPushedDown(); + assertThat(query("SELECT * FROM test_hour_transform_timestamp WHERE date_trunc('hour', d) BETWEEN TIMESTAMP '2015-05-15 12:00:00' AND TIMESTAMP '2015-05-15 13:59:59.999999'")) + .isFullyPushedDown(); + assertThat(query("SELECT * FROM test_hour_transform_timestamp WHERE date_trunc('hour', d) BETWEEN DATE '2015-05-15' AND DATE '2015-05-16'")) + .isFullyPushedDown(); + assertThat(query("SELECT * FROM test_hour_transform_timestamp WHERE date_trunc('hour', d) BETWEEN TIMESTAMP '2015-05-15 12:00:00.000001' AND TIMESTAMP '2015-05-15 13:59:59.999999'")) + .isNotFullyPushedDown(FilterNode.class); assertThat(query("SELECT * FROM test_hour_transform_timestamp WHERE date_trunc('day', d) = DATE '2015-05-15'")) .isFullyPushedDown(); + assertThat(query("SELECT * FROM test_hour_transform_timestamp WHERE date_trunc('day', d) BETWEEN DATE '2015-05-15' AND DATE '2015-05-16'")) + .isFullyPushedDown(); + assertThat(query("SELECT * FROM test_hour_transform_timestamp WHERE date_trunc('day', d) BETWEEN TIMESTAMP '2015-05-15 00:00:00' AND TIMESTAMP '2015-05-16 00:00:00'")) + .isFullyPushedDown(); assertThat(query("SELECT * FROM test_hour_transform_timestamp WHERE date_trunc('month', d) = DATE '2015-05-01'")) .isFullyPushedDown(); + assertThat(query("SELECT * FROM test_hour_transform_timestamp WHERE date_trunc('month', d) BETWEEN DATE '2015-05-01' AND DATE '2015-06-01'")) + .isFullyPushedDown(); assertThat(query("SELECT * FROM test_hour_transform_timestamp WHERE date_trunc('year', d) = DATE '2015-01-01'")) .isFullyPushedDown(); + assertThat(query("SELECT * FROM test_hour_transform_timestamp WHERE date_trunc('year', d) BETWEEN DATE '2015-01-01' AND DATE '2016-01-01'")) + .isFullyPushedDown(); assertUpdate("DROP TABLE test_hour_transform_timestamp"); } @@ -1503,6 +1531,8 @@ else if (format == AVRO) { .isFullyPushedDown(); assertThat(query("SELECT * FROM test_hour_transform_timestamptz WHERE CAST(d AS date) >= DATE '2015-05-15'")) .isFullyPushedDown(); + assertThat(query("SELECT * FROM test_hour_transform_timestamptz WHERE CAST(d AS date) BETWEEN DATE '2015-05-15' AND DATE '2015-06-15'")) + .isFullyPushedDown(); assertThat(query("SELECT * FROM test_hour_transform_timestamptz WHERE d >= TIMESTAMP '2015-05-15 12:00:00 UTC'")) .isFullyPushedDown(); @@ -1512,20 +1542,36 @@ else if (format == AVRO) { // date() assertThat(query("SELECT * FROM test_hour_transform_timestamptz WHERE date(d) = DATE '2015-05-15'")) .isFullyPushedDown(); + assertThat(query("SELECT * FROM test_hour_transform_timestamptz WHERE date(d) BETWEEN DATE '2015-05-15' AND DATE '2015-06-15'")) + .isFullyPushedDown(); // year() assertThat(query("SELECT * FROM test_hour_transform_timestamptz WHERE year(d) = 2015")) .isNotFullyPushedDown(FilterNode.class); // TODO convert into range + assertThat(query("SELECT * FROM test_hour_transform_timestamptz WHERE year(d) BETWEEN 2015 AND 2016")) + .isNotFullyPushedDown(FilterNode.class); // TODO convert into range // date_trunc assertThat(query("SELECT * FROM test_hour_transform_timestamptz WHERE date_trunc('hour', d) = TIMESTAMP '2015-05-15 12:00:00.000000 UTC'")) .isFullyPushedDown(); + assertThat(query("SELECT * FROM test_hour_transform_timestamptz WHERE date_trunc('hour', d) BETWEEN TIMESTAMP '2015-05-15 12:00:00.000000 UTC' AND TIMESTAMP '2015-05-15 13:59:59.999999 UTC'")) + .isFullyPushedDown(); assertThat(query("SELECT * FROM test_hour_transform_timestamptz WHERE date_trunc('day', d) = TIMESTAMP '2015-05-15 00:00:00.000000 UTC'")) .isFullyPushedDown(); + assertThat(query("SELECT * FROM test_hour_transform_timestamptz WHERE date_trunc('day', d) BETWEEN TIMESTAMP '2015-05-15 00:00:00' AND TIMESTAMP '2015-05-16 00:00:00'")) + .isFullyPushedDown(); + assertThat(query("SELECT * FROM test_hour_transform_timestamptz WHERE date_trunc('day', d) BETWEEN DATE '2015-05-15' AND DATE '2015-05-16'")) + .isFullyPushedDown(); assertThat(query("SELECT * FROM test_hour_transform_timestamptz WHERE date_trunc('month', d) = TIMESTAMP '2015-05-01 00:00:00.000000 UTC'")) .isFullyPushedDown(); + assertThat(query("SELECT * FROM test_hour_transform_timestamptz WHERE date_trunc('month', d) BETWEEN DATE '2015-05-01' AND DATE '2015-06-08'")) + .isFullyPushedDown(); assertThat(query("SELECT * FROM test_hour_transform_timestamptz WHERE date_trunc('year', d) = TIMESTAMP '2015-01-01 00:00:00.000000 UTC'")) .isFullyPushedDown(); + assertThat(query("SELECT * FROM test_hour_transform_timestamptz WHERE date_trunc('year', d) BETWEEN TIMESTAMP '2015-01-01 00:00:00.000000 UTC' AND TIMESTAMP '2016-01-01 00:00:00.000000 UTC'")) + .isFullyPushedDown(); + assertThat(query("SELECT * FROM test_hour_transform_timestamptz WHERE date_trunc('year', d) BETWEEN DATE '2015-01-01' AND DATE '2016-01-01'")) + .isFullyPushedDown(); assertUpdate("DROP TABLE test_hour_transform_timestamptz"); } @@ -1643,6 +1689,8 @@ public void testDayTransformDate() .isFullyPushedDown(); assertThat(query("SELECT * FROM test_day_transform_date WHERE CAST(d AS date) >= DATE '2015-01-13'")) .isFullyPushedDown(); + assertThat(query("SELECT * FROM test_day_transform_date WHERE d BETWEEN DATE '2015-01-13' AND DATE '2015-01-14'")) + .isFullyPushedDown(); // d comparison with TIMESTAMP can be unwrapped assertThat(query("SELECT * FROM test_day_transform_date WHERE d >= TIMESTAMP '2015-01-13 00:00:00'")) @@ -1657,14 +1705,22 @@ public void testDayTransformDate() // year() assertThat(query("SELECT * FROM test_day_transform_date WHERE year(d) = 2015")) .isNotFullyPushedDown(FilterNode.class); // TODO convert into range + assertThat(query("SELECT * FROM test_day_transform_date WHERE year(d) BETWEEN 2015 AND 2016")) + .isNotFullyPushedDown(FilterNode.class); // TODO convert into range // date_trunc assertThat(query("SELECT * FROM test_day_transform_date WHERE date_trunc('day', d) = DATE '2015-01-13'")) .isFullyPushedDown(); + assertThat(query("SELECT * FROM test_day_transform_date WHERE date_trunc('day', d) BETWEEN DATE '2015-01-13' AND DATE '2015-01-14'")) + .isFullyPushedDown(); assertThat(query("SELECT * FROM test_day_transform_date WHERE date_trunc('month', d) = DATE '2015-01-01'")) .isFullyPushedDown(); + assertThat(query("SELECT * FROM test_day_transform_date WHERE date_trunc('month', d) BETWEEN DATE '2015-01-01' AND DATE '2015-02-01'")) + .isFullyPushedDown(); assertThat(query("SELECT * FROM test_day_transform_date WHERE date_trunc('year', d) = DATE '2015-01-01'")) .isFullyPushedDown(); + assertThat(query("SELECT * FROM test_day_transform_date WHERE date_trunc('year', d) BETWEEN DATE '2015-01-01' AND DATE '2016-01-01'")) + .isFullyPushedDown(); dropTable("test_day_transform_date"); } @@ -1756,27 +1812,43 @@ else if (format == AVRO) { .isFullyPushedDown(); assertThat(query("SELECT * FROM test_day_transform_timestamp WHERE CAST(d AS date) >= DATE '2015-05-15'")) .isFullyPushedDown(); + assertThat(query("SELECT * FROM test_day_transform_timestamp WHERE d BETWEEN DATE '2015-05-15' AND DATE '2015-05-16'")) + .isNotFullyPushedDown(FilterNode.class); assertThat(query("SELECT * FROM test_day_transform_timestamp WHERE d >= TIMESTAMP '2015-05-15 00:00:00'")) .isFullyPushedDown(); + assertThat(query("SELECT * FROM test_day_transform_timestamp WHERE d BETWEEN TIMESTAMP '2015-05-15 00:00:00' AND TIMESTAMP '2015-05-16 23:59:59.999999'")) + .isFullyPushedDown(); assertThat(query("SELECT * FROM test_day_transform_timestamp WHERE d >= TIMESTAMP '2015-05-15 00:00:00.000001'")) .isNotFullyPushedDown(FilterNode.class); + assertThat(query("SELECT * FROM test_day_transform_timestamp WHERE d BETWEEN TIMESTAMP '2015-05-15 00:00:00' AND TIMESTAMP '2015-05-16 00:00:00'")) + .isNotFullyPushedDown(FilterNode.class); // date() assertThat(query("SELECT * FROM test_day_transform_timestamp WHERE date(d) = DATE '2015-05-15'")) .isFullyPushedDown(); + assertThat(query("SELECT * FROM test_day_transform_timestamp WHERE date(d) BETWEEN DATE '2015-05-15' AND DATE '2015-06-15'")) + .isFullyPushedDown(); // year() assertThat(query("SELECT * FROM test_day_transform_timestamp WHERE year(d) = 2015")) .isNotFullyPushedDown(FilterNode.class); // TODO convert into range + assertThat(query("SELECT * FROM test_day_transform_timestamp WHERE year(d) BETWEEN 2015 AND 2016")) + .isNotFullyPushedDown(FilterNode.class); // TODO convert into range // date_trunc assertThat(query("SELECT * FROM test_day_transform_timestamp WHERE date_trunc('day', d) = DATE '2015-05-15'")) .isFullyPushedDown(); + assertThat(query("SELECT * FROM test_day_transform_timestamp WHERE date_trunc('day', d) BETWEEN DATE '2015-05-15' AND DATE '2015-05-16'")) + .isFullyPushedDown(); assertThat(query("SELECT * FROM test_day_transform_timestamp WHERE date_trunc('month', d) = DATE '2015-05-01'")) .isFullyPushedDown(); + assertThat(query("SELECT * FROM test_day_transform_timestamp WHERE date_trunc('month', d) BETWEEN DATE '2015-05-01' AND DATE '2015-06-01'")) + .isFullyPushedDown(); assertThat(query("SELECT * FROM test_day_transform_timestamp WHERE date_trunc('year', d) = DATE '2015-01-01'")) .isFullyPushedDown(); + assertThat(query("SELECT * FROM test_day_transform_timestamp WHERE date_trunc('year', d) BETWEEN DATE '2015-01-01' AND DATE '2016-01-01'")) + .isFullyPushedDown(); dropTable("test_day_transform_timestamp"); } @@ -1883,14 +1955,22 @@ else if (format == AVRO) { // year() assertThat(query("SELECT * FROM test_day_transform_timestamptz WHERE year(d) = 2015")) .isNotFullyPushedDown(FilterNode.class); // TODO convert into range + assertThat(query("SELECT * FROM test_day_transform_timestamptz WHERE year(d) BETWEEN 2015 AND 2016")) + .isNotFullyPushedDown(FilterNode.class); // TODO convert into range // date_trunc assertThat(query("SELECT * FROM test_day_transform_timestamptz WHERE date_trunc('day', d) = TIMESTAMP '2015-05-15 00:00:00.000000 UTC'")) .isFullyPushedDown(); + assertThat(query("SELECT * FROM test_day_transform_timestamptz WHERE date_trunc('day', d) BETWEEN DATE '2015-05-15' AND DATE '2015-05-16'")) + .isFullyPushedDown(); assertThat(query("SELECT * FROM test_day_transform_timestamptz WHERE date_trunc('month', d) = TIMESTAMP '2015-05-01 00:00:00.000000 UTC'")) .isFullyPushedDown(); + assertThat(query("SELECT * FROM test_day_transform_timestamptz WHERE date_trunc('month', d) BETWEEN DATE '2015-05-01' AND DATE '2015-06-01'")) + .isFullyPushedDown(); assertThat(query("SELECT * FROM test_day_transform_timestamptz WHERE date_trunc('year', d) = TIMESTAMP '2015-01-01 00:00:00.000000 UTC'")) .isFullyPushedDown(); + assertThat(query("SELECT * FROM test_day_transform_timestamptz WHERE date_trunc('year', d) BETWEEN DATE '2015-01-01' AND DATE '2016-01-01'")) + .isFullyPushedDown(); assertUpdate("DROP TABLE test_day_transform_timestamptz"); } @@ -1979,22 +2059,32 @@ public void testMonthTransformDate() .isFullyPushedDown(); assertThat(query("SELECT * FROM test_month_transform_date WHERE CAST(d AS date) >= DATE '2020-06-02'")) .isNotFullyPushedDown(FilterNode.class); + assertThat(query("SELECT * FROM test_month_transform_date WHERE d BETWEEN DATE '2020-06-01' AND DATE '2020-07-31'")) + .isFullyPushedDown(); // d comparison with TIMESTAMP can be unwrapped assertThat(query("SELECT * FROM test_month_transform_date WHERE d >= TIMESTAMP '2015-06-01 00:00:00'")) .isFullyPushedDown(); assertThat(query("SELECT * FROM test_month_transform_date WHERE d >= TIMESTAMP '2015-05-01 00:00:00.000001'")) .isNotFullyPushedDown(FilterNode.class); + assertThat(query("SELECT * FROM test_month_transform_date WHERE d BETWEEN TIMESTAMP '2015-05-01 00:00:00' AND TIMESTAMP '2015-06-30 00:00:00'")) + .isFullyPushedDown(); // year() assertThat(query("SELECT * FROM test_month_transform_date WHERE year(d) = 2015")) .isNotFullyPushedDown(FilterNode.class); // TODO convert into range + assertThat(query("SELECT * FROM test_month_transform_date WHERE year(d) BETWEEN 2015 AND 2016")) + .isNotFullyPushedDown(FilterNode.class); // TODO convert into range // date_trunc assertThat(query("SELECT * FROM test_month_transform_date WHERE date_trunc('month', d) = DATE '2015-01-01'")) .isFullyPushedDown(); + assertThat(query("SELECT * FROM test_month_transform_date WHERE date_trunc('month', d) BETWEEN DATE '2015-01-01' AND DATE '2015-02-01'")) + .isFullyPushedDown(); assertThat(query("SELECT * FROM test_month_transform_date WHERE date_trunc('year', d) = DATE '2015-01-01'")) .isFullyPushedDown(); + assertThat(query("SELECT * FROM test_month_transform_date WHERE date_trunc('year', d) BETWEEN DATE '2015-01-01' AND DATE '2016-01-01'")) + .isFullyPushedDown(); if (format != AVRO) { assertThat(query("SHOW STATS FOR test_month_transform_date")) @@ -2110,15 +2200,26 @@ else if (format == AVRO) { assertThat(query("SELECT * FROM test_month_transform_timestamp WHERE d >= TIMESTAMP '2015-05-01 00:00:00.000001'")) .isNotFullyPushedDown(FilterNode.class); + assertThat(query("SELECT * FROM test_month_transform_timestamp WHERE d BETWEEN DATE '2015-05-01' AND DATE '2015-06-01'")) + .isNotFullyPushedDown(FilterNode.class); + assertThat(query("SELECT * FROM test_month_transform_timestamp WHERE d BETWEEN DATE '2015-05-01' AND TIMESTAMP '2015-05-31 23:59:59.999999'")) + .isFullyPushedDown(); + // year() assertThat(query("SELECT * FROM test_month_transform_timestamp WHERE year(d) = 2015")) .isNotFullyPushedDown(FilterNode.class); // TODO convert into range + assertThat(query("SELECT * FROM test_month_transform_timestamp WHERE year(d) BETWEEN 2015 AND 2016")) + .isNotFullyPushedDown(FilterNode.class); // TODO convert into range // date_trunc assertThat(query("SELECT * FROM test_month_transform_timestamp WHERE date_trunc('month', d) = DATE '2015-05-01'")) .isFullyPushedDown(); + assertThat(query("SELECT * FROM test_month_transform_timestamp WHERE date_trunc('month', d) BETWEEN DATE '2015-05-01' AND DATE '2015-06-01'")) + .isFullyPushedDown(); assertThat(query("SELECT * FROM test_month_transform_timestamp WHERE date_trunc('year', d) = DATE '2015-01-01'")) .isFullyPushedDown(); + assertThat(query("SELECT * FROM test_month_transform_timestamp WHERE date_trunc('year', d) BETWEEN DATE '2015-01-01' AND DATE '2016-01-01'")) + .isFullyPushedDown(); dropTable("test_month_transform_timestamp"); } @@ -2222,12 +2323,18 @@ else if (format == AVRO) { // year() assertThat(query("SELECT * FROM test_month_transform_timestamptz WHERE year(d) = 2015")) .isNotFullyPushedDown(FilterNode.class); // TODO convert into range + assertThat(query("SELECT * FROM test_month_transform_timestamptz WHERE year(d) BETWEEN 2015 AND 2016")) + .isNotFullyPushedDown(FilterNode.class); // TODO convert into range // date_trunc assertThat(query("SELECT * FROM test_month_transform_timestamptz WHERE date_trunc('month', d) = TIMESTAMP '2015-05-01 00:00:00.000000 UTC'")) .isFullyPushedDown(); + assertThat(query("SELECT * FROM test_month_transform_timestamptz WHERE date_trunc('month', d) BETWEEN DATE '2015-05-01' AND DATE '2015-06-01'")) + .isFullyPushedDown(); assertThat(query("SELECT * FROM test_month_transform_timestamptz WHERE date_trunc('year', d) = TIMESTAMP '2015-01-01 00:00:00.000000 UTC'")) .isFullyPushedDown(); + assertThat(query("SELECT * FROM test_month_transform_timestamptz WHERE date_trunc('year', d) BETWEEN DATE '2015-01-01' AND DATE '2016-01-01'")) + .isFullyPushedDown(); assertUpdate("DROP TABLE test_month_transform_timestamptz"); } @@ -2314,6 +2421,10 @@ public void testYearTransformDate() .isFullyPushedDown(); assertThat(query("SELECT * FROM test_year_transform_date WHERE CAST(d AS date) >= DATE '2015-01-02'")) .isNotFullyPushedDown(FilterNode.class); + assertThat(query("SELECT * FROM test_year_transform_date WHERE d BETWEEN DATE '2015-01-01' AND DATE '2016-01-01'")) + .isNotFullyPushedDown(FilterNode.class); + assertThat(query("SELECT * FROM test_year_transform_date WHERE d BETWEEN DATE '2015-01-01' AND TIMESTAMP '2015-12-31 23:59:59.999999'")) + .isFullyPushedDown(); // d comparison with TIMESTAMP can be unwrapped assertThat(query("SELECT * FROM test_year_transform_date WHERE d >= TIMESTAMP '2015-01-01 00:00:00'")) @@ -2324,10 +2435,14 @@ public void testYearTransformDate() // year() assertThat(query("SELECT * FROM test_year_transform_date WHERE year(d) = 2015")) .isNotFullyPushedDown(FilterNode.class); // TODO convert into range + assertThat(query("SELECT * FROM test_year_transform_date WHERE year(d) BETWEEN 2015 AND 2016")) + .isNotFullyPushedDown(FilterNode.class); // TODO convert into range // date_trunc assertThat(query("SELECT * FROM test_year_transform_date WHERE date_trunc('year', d) = DATE '2015-01-01'")) .isFullyPushedDown(); + assertThat(query("SELECT * FROM test_year_transform_date WHERE date_trunc('year', d) BETWEEN DATE '2015-01-01' AND DATE '2016-01-01'")) + .isFullyPushedDown(); if (format != AVRO) { assertThat(query("SHOW STATS FOR test_year_transform_date")) @@ -2434,6 +2549,8 @@ else if (format == AVRO) { .isFullyPushedDown(); assertThat(query("SELECT * FROM test_year_transform_timestamp WHERE CAST(d AS date) >= DATE '2015-01-02'")) .isNotFullyPushedDown(FilterNode.class); + assertThat(query("SELECT * FROM test_year_transform_timestamp WHERE CAST(d AS date) BETWEEN DATE '2015-01-01' AND DATE '2016-12-31'")) + .isFullyPushedDown(); assertThat(query("SELECT * FROM test_year_transform_timestamp WHERE d >= TIMESTAMP '2015-01-01 00:00:00'")) .isFullyPushedDown(); @@ -2443,10 +2560,14 @@ else if (format == AVRO) { // year() assertThat(query("SELECT * FROM test_year_transform_timestamp WHERE year(d) = 2015")) .isNotFullyPushedDown(FilterNode.class); // TODO convert into range + assertThat(query("SELECT * FROM test_year_transform_timestamp WHERE year(d) BETWEEN 2015 AND 2016")) + .isNotFullyPushedDown(FilterNode.class); // TODO convert into range // date_trunc assertThat(query("SELECT * FROM test_year_transform_timestamp WHERE date_trunc('year', d) = DATE '2015-01-01'")) .isFullyPushedDown(); + assertThat(query("SELECT * FROM test_year_transform_timestamp WHERE date_trunc('year', d) BETWEEN DATE '2015-01-01' AND DATE '2016-01-01'")) + .isFullyPushedDown(); dropTable("test_year_transform_timestamp"); } @@ -2529,6 +2650,10 @@ else if (format == AVRO) { .isFullyPushedDown(); assertThat(query("SELECT * FROM test_year_transform_timestamptz WHERE d >= with_timezone(DATE '2015-01-02', 'UTC')")) .isNotFullyPushedDown(FilterNode.class); + assertThat(query("SELECT * FROM test_year_transform_timestamptz WHERE d BETWEEN DATE '2015-01-01' AND DATE '2016-01-01'")) + .isNotFullyPushedDown(FilterNode.class); + assertThat(query("SELECT * FROM test_year_transform_timestamptz WHERE d BETWEEN with_timezone(DATE '2015-01-01', 'UTC') AND with_timezone(TIMESTAMP '2016-12-31 23:59:59.999999', 'UTC')")) + .isFullyPushedDown(); assertThat(query("SELECT * FROM test_year_transform_timestamptz WHERE CAST(d AS date) >= DATE '2015-01-01'")) .isFullyPushedDown(); @@ -2538,6 +2663,8 @@ else if (format == AVRO) { // Engine can eliminate the table scan after connector accepts the filter pushdown .hasPlan(node(OutputNode.class, node(ValuesNode.class))) .returnsEmptyResult(); + assertThat(query("SELECT * FROM test_year_transform_timestamptz WHERE CAST(d AS date) BETWEEN DATE '2015-01-01' AND DATE '2016-12-31'")) + .isFullyPushedDown(); assertThat(query("SELECT * FROM test_year_transform_timestamptz WHERE d >= TIMESTAMP '2015-01-01 00:00:00 UTC'")) .isFullyPushedDown(); @@ -2547,10 +2674,14 @@ else if (format == AVRO) { // year() assertThat(query("SELECT * FROM test_year_transform_timestamptz WHERE year(d) = 2015")) .isNotFullyPushedDown(FilterNode.class); // TODO convert into range + assertThat(query("SELECT * FROM test_year_transform_timestamptz WHERE year(d) BETWEEN 2015 AND 2016")) + .isNotFullyPushedDown(FilterNode.class); // TODO convert into range // date_trunc assertThat(query("SELECT * FROM test_year_transform_timestamptz WHERE date_trunc('year', d) = TIMESTAMP '2015-01-01 00:00:00.000000 UTC'")) .isFullyPushedDown(); + assertThat(query("SELECT * FROM test_year_transform_timestamptz WHERE date_trunc('year', d) BETWEEN DATE '2015-01-01' AND DATE '2016-01-01'")) + .isFullyPushedDown(); assertUpdate("DROP TABLE test_year_transform_timestamptz"); }