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..359e28168863 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 @@ -24,6 +24,7 @@ import io.trino.spi.type.CharType; 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 +40,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; @@ -69,6 +71,7 @@ import static io.trino.spi.type.DoubleType.DOUBLE; import static io.trino.spi.type.IntegerType.INTEGER; 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 +84,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 +176,13 @@ 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(ComparisonExpression expression) { // Canonicalization is handled by CanonicalizeExpressionRewriter @@ -387,6 +399,208 @@ private Optional unwrapTimestampToDateCast(Session session, Timestam }; } + private Expression unwrapCast(BetweenPredicate expression) + { + // Canonicalization is handled by CanonicalizeExpressionRewriter + if (!(expression.getValue() instanceof Cast cast)) { + return expression; + } + + Object rangeMin = new ExpressionInterpreter(expression.getMin(), plannerContext, session, typeAnalyzer.getTypes(session, types, expression.getMin())) + .optimize(NoOpSymbolResolver.INSTANCE); + Object rangeMax = new ExpressionInterpreter(expression.getMax(), plannerContext, session, typeAnalyzer.getTypes(session, types, expression.getMax())) + .optimize(NoOpSymbolResolver.INSTANCE); + + if (rangeMin == null || rangeMin instanceof NullLiteral) { + return new Cast(new NullLiteral(), toSqlType(BOOLEAN)); + } + + if (rangeMin instanceof Expression || rangeMax instanceof Expression) { + return expression; + } + + Type sourceType = typeAnalyzer.getType(session, types, cast.getExpression()); + Type rangeMinType = typeAnalyzer.getType(session, types, expression.getMin()); + Type rangeMaxType = typeAnalyzer.getType(session, types, expression.getMax()); + if (rangeMinType != rangeMaxType) { + return expression; + } + Type targetType = rangeMinType; + + if (sourceType instanceof TimestampType && targetType == DATE) { + return unwrapTimestampToDateCastForRange(session, (TimestampType) sourceType, cast.getExpression(), (long) rangeMin, (long) rangeMax); + } + + if (targetType instanceof TimestampWithTimeZoneType) { + // Note: two TIMESTAMP WITH TIME ZONE values differing in zone only (same instant) are considered equal. + rangeMin = withTimeZone(((TimestampWithTimeZoneType) targetType), rangeMin, session.getTimeZoneKey()); + rangeMax = withTimeZone(((TimestampWithTimeZoneType) targetType), rangeMax, session.getTimeZoneKey()); + } + + if (!hasInjectiveImplicitCoercion(sourceType, targetType, rangeMin)) { + return expression; + } + + // Handle comparison against NaN. + // It must be done before source type range bounds are compared to target value. + if (isFloatingPointNaN(targetType, rangeMin) || isFloatingPointNaN(targetType, rangeMax)) { + return falseIfNotNull(cast.getExpression()); + } + + if (compare(targetType, rangeMin, rangeMax) > 0) { + // range min gte range max + return falseIfNotNull(cast.getExpression()); + } + + ResolvedFunction sourceToTarget = plannerContext.getMetadata().getCoercion(session, sourceType, targetType); + + Optional sourceRange = sourceType.getRange(); + if (sourceRange.isPresent()) { + Object maxInSourceType = sourceRange.get().getMax(); + Object maxInTargetType = coerce(maxInSourceType, sourceToTarget); + + // NaN values of `rangeMin` and `rangeMax` are excluded at this point. Otherwise, NaN would be recognized as + // greater than source type upper bound, and incorrect expression might be derived. + int upperBoundRangeMinComparison = compare(targetType, rangeMin, maxInTargetType); + if (upperBoundRangeMinComparison > 0) { + // range min is larger than maximum representable value + return falseIfNotNull(cast.getExpression()); + } + if (upperBoundRangeMinComparison == 0) { + // range min equal to max representable value + return new ComparisonExpression(EQUAL, cast.getExpression(), literalEncoder.toExpression(session, maxInSourceType, sourceType)); + } + int upperBoundRangeMaxComparison = compare(targetType, rangeMax, maxInTargetType); + if (upperBoundRangeMaxComparison >= 0) { + // range max larger or equal to the maximum representable value + return unwrapCast(new ComparisonExpression(GREATER_THAN_OR_EQUAL, cast, expression.getMin())); + } + + Object minInSourceType = sourceRange.get().getMin(); + Object minInTargetType = coerce(minInSourceType, sourceToTarget); + + int lowerBoundRageMaxComparison = compare(targetType, rangeMax, minInTargetType); + if (lowerBoundRageMaxComparison < 0) { + // range max smaller than minimum representable value + return falseIfNotNull(cast.getExpression()); + } + if (lowerBoundRageMaxComparison == 0) { + // range max equal to min representable value + return new ComparisonExpression(EQUAL, cast.getExpression(), literalEncoder.toExpression(session, minInSourceType, sourceType)); + } + int lowerBoundRageMinComparison = compare(targetType, rangeMin, minInTargetType); + if (lowerBoundRageMinComparison <= 0) { + // range min smaller or equal to the minimum representable value + return unwrapCast(new ComparisonExpression(LESS_THAN_OR_EQUAL, cast, expression.getMax())); + } + } + + ResolvedFunction targetToSource; + try { + targetToSource = plannerContext.getMetadata().getCoercion(session, targetType, sourceType); + } + catch (OperatorNotFoundException e) { + // Without a cast between target -> source, there's nothing more we can do + return expression; + } + + Object rangeMinLiteralInSourceType; + Object rangeMaxLiteralInSourceType; + try { + rangeMinLiteralInSourceType = coerce(rangeMin, targetToSource); + rangeMaxLiteralInSourceType = coerce(rangeMax, 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; + } + + Object rangeMinRoundtripLiteral = coerce(rangeMinLiteralInSourceType, sourceToTarget); + int rangeMinLiteralVsRoundtripped = compare(targetType, rangeMin, rangeMinRoundtripLiteral); + Object rangeMaxRoundtripLiteral = coerce(rangeMaxLiteralInSourceType, sourceToTarget); + int rangeMaxLiteralVsRoundtripped = compare(targetType, rangeMax, rangeMaxRoundtripLiteral); + + Expression rangeMinLiteralExpression = literalEncoder.toExpression(session, rangeMinLiteralInSourceType, sourceType); + Expression rangeMaxLiteralExpression = literalEncoder.toExpression(session, rangeMaxLiteralInSourceType, sourceType); + if (rangeMinLiteralVsRoundtripped > 0) { + // We expect implicit coercions to be order-preserving, so the result of converting back from target -> source + // cannot produce a value larger than the next value in the source type + ComparisonExpression rangeMinComparisonExpression = new ComparisonExpression(GREATER_THAN, cast.getExpression(), rangeMinLiteralExpression); + if (rangeMaxLiteralVsRoundtripped >= 0) { + return and( + rangeMinComparisonExpression, + new ComparisonExpression(LESS_THAN_OR_EQUAL, cast.getExpression(), rangeMaxLiteralExpression)); + } + else { + return and( + rangeMinComparisonExpression, + // We expect implicit coercions to be order-preserving, so the result of converting back from target -> source cannot + // produce a value smaller than the next value in the source type + new ComparisonExpression(LESS_THAN, cast.getExpression(), rangeMaxLiteralExpression)); + } + } + else { + ComparisonExpression rangeMinComparisonExpression = new ComparisonExpression(GREATER_THAN_OR_EQUAL, cast.getExpression(), rangeMinLiteralExpression); + if (rangeMaxLiteralVsRoundtripped >= 0) { + return new BetweenPredicate(cast.getExpression(), rangeMinLiteralExpression, rangeMaxLiteralExpression); + } + else { + return and( + rangeMinComparisonExpression, + // We expect implicit coercions to be order-preserving, so the result of converting back from target -> source cannot + // produce a value smaller than the next value in the source type + new ComparisonExpression(LESS_THAN, cast.getExpression(), rangeMaxLiteralExpression)); + } + } + } + + 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 - 1; + 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 boolean hasInjectiveImplicitCoercion(Type source, Type target, Object value) { if ((source.equals(BIGINT) && target.equals(DOUBLE)) || diff --git a/core/trino-main/src/test/java/io/trino/sql/planner/TestUnwrapCastInComparison.java b/core/trino-main/src/test/java/io/trino/sql/planner/TestUnwrapCastInComparison.java index b13f6e9ddfd0..9e7c9b80bb74 100644 --- a/core/trino-main/src/test/java/io/trino/sql/planner/TestUnwrapCastInComparison.java +++ b/core/trino-main/src/test/java/io/trino/sql/planner/TestUnwrapCastInComparison.java @@ -297,6 +297,80 @@ public void testGreaterThanOrEqual() testUnwrap("bigint", "a >= DOUBLE '-18446744073709551616'", "NOT (a IS NULL) OR NULL"); } + @Test + public void testBetween() + { + // representable + testUnwrap("smallint", "a BETWEEN DOUBLE '1' AND DOUBLE '2'", "a BETWEEN SMALLINT '1' AND SMALLINT '2'"); + testUnwrap("bigint", "a BETWEEN DOUBLE '1' AND DOUBLE '2'", "a BETWEEN BIGINT '1' AND BIGINT '2'"); + testUnwrap("decimal(7, 2)", "a BETWEEN DOUBLE '1.23' AND DOUBLE '4.56'", "a BETWEEN CAST(DECIMAL '1.23' AS decimal(7, 2)) AND CAST(DECIMAL '4.56' AS decimal(7, 2))"); + // cast down is possible + testUnwrap("decimal(7, 2)", "CAST(a AS DECIMAL(12,2)) BETWEEN CAST(DECIMAL '111.00' AS decimal(12,2)) AND CAST(DECIMAL '222.0' AS decimal(12,2))", "a BETWEEN CAST(DECIMAL '111.00' AS decimal(7, 2)) AND CAST(DECIMAL '222.00' AS decimal(7, 2))"); + + // non-representable, min cast round down, max cast round down + testUnwrap("bigint", "a BETWEEN DOUBLE '1.1' AND DOUBLE '2.2'", "a > BIGINT '1' AND a <= BIGINT '2'"); + // non-representable, min cast round down, max cast round up + testUnwrap("bigint", "a BETWEEN DOUBLE '1.1' AND DOUBLE '1.9'", "a > BIGINT '1' AND a < BIGINT '2'"); + // non-representable, min cast round down, max cast no rounding + testUnwrap("bigint", "a BETWEEN DOUBLE '1.1' AND DOUBLE '2'", "a > BIGINT '1' AND a <= BIGINT '2'"); + // non-representable, min cast round up, max cast round down + testUnwrap("bigint", "a BETWEEN DOUBLE '1.9' AND DOUBLE '2.2'", "a BETWEEN BIGINT '2' AND BIGINT '2'"); + // non-representable, min cast round up, max cast round up + testUnwrap("bigint", "a BETWEEN DOUBLE '1.9' AND DOUBLE '2.9'", "a >= BIGINT '2' AND a < BIGINT '3'"); + // non-representable, min cast round up, max cast no rounding + testUnwrap("bigint", "a BETWEEN DOUBLE '1.9' AND DOUBLE '3'", "a BETWEEN BIGINT '2' AND BIGINT '3'"); + // non-representable, min cast no rounding, max cast round down + testUnwrap("bigint", "a BETWEEN DOUBLE '2' AND DOUBLE '3.2'", "a BETWEEN BIGINT '2' AND BIGINT '3'"); + // non-representable, min cast no rounding, max cast round up + testUnwrap("bigint", "a BETWEEN DOUBLE '2' AND DOUBLE '2.9'", "a >= BIGINT '2' AND a < BIGINT '3'"); + // non-representable, min cast no rounding, max cast no rounding + testUnwrap("bigint", "a BETWEEN DOUBLE '2' AND DOUBLE '3'", "a BETWEEN BIGINT '2' AND BIGINT '3'"); + + // cast down not possible + testUnwrap("decimal(7, 2)", "CAST(a AS DECIMAL(12,2)) BETWEEN CAST(DECIMAL '1111111111.00' AS decimal(12,2)) AND CAST(DECIMAL '2222222222.0' AS decimal(12,2))", "CAST(a AS decimal(12,2)) BETWEEN CAST(DECIMAL '1111111111.00' AS decimal(12, 2)) AND CAST(DECIMAL '2222222222.00' AS decimal(12, 2))"); + + // illegal range + testUnwrap("smallint", "a BETWEEN DOUBLE '5' AND DOUBLE '4'", "a IS NULL AND NULL"); + + // nan + testUnwrap("smallint", "a BETWEEN nan() AND DOUBLE '2'", "a IS NULL AND NULL"); + testUnwrap("smallint", "a BETWEEN DOUBLE '2' AND nan()", "a IS NULL AND NULL"); + + // min and max below bottom of range + testUnwrap("smallint", "a BETWEEN DOUBLE '-50000' AND DOUBLE '-40000'", "a IS NULL AND NULL"); + // min below bottom of range, max at the bottom of range + testUnwrap("smallint", "a BETWEEN DOUBLE '-32768.1' AND DOUBLE '-32768'", "a = SMALLINT '-32768'"); + testUnwrap("smallint", "a BETWEEN DOUBLE '-32768.1' AND DOUBLE '-32767.9'", "a = SMALLINT '-32768'"); + // min below bottom of range, max within range + testUnwrap("smallint", "a BETWEEN DOUBLE '-32768.1' AND DOUBLE '0'", "a <= SMALLINT '0'"); + // min at the bottom of range, max within range + testUnwrap("smallint", "a BETWEEN DOUBLE '-32768' AND DOUBLE '0'", "a <= SMALLINT '0'"); + // min round to bottom of range, max within range + testUnwrap("smallint", "a BETWEEN DOUBLE '-32767.9' AND DOUBLE '0'", "a > SMALLINT '-32768' AND a <= SMALLINT '0'"); + // min above bottom of range, max within range + testUnwrap("smallint", "a BETWEEN DOUBLE '-32767' AND DOUBLE '0'", "a BETWEEN SMALLINT '-32767' AND SMALLINT '0'"); + // min & max below within of range + testUnwrap("smallint", "a BETWEEN DOUBLE '32765' AND DOUBLE '32766'", "a BETWEEN SMALLINT '32765' AND SMALLINT '32766'"); + // min within and max round to top of range + testUnwrap("smallint", "a BETWEEN DOUBLE '32765.9' AND DOUBLE '32766.9'", "a >= SMALLINT '32766' AND a < SMALLINT '32767'"); + // min below and max at the top of range + testUnwrap("smallint", "a BETWEEN DOUBLE '32760' AND DOUBLE '32767'", "a >= SMALLINT '32760'"); + // min below and max above top of range + testUnwrap("smallint", "a BETWEEN DOUBLE '32760.1' AND DOUBLE '32768.1'", "a > SMALLINT '32760'"); + // min at the top of range and max above top of range + testUnwrap("smallint", "a BETWEEN DOUBLE '32767' AND DOUBLE '32768.1'", "a = SMALLINT '32767'"); + testUnwrap("smallint", "a BETWEEN DOUBLE '32766.9' AND DOUBLE '32768.1'", "a = SMALLINT '32767'"); + // min and max above top of range + testUnwrap("smallint", "a BETWEEN DOUBLE '40000' AND DOUBLE '50000'", "a IS NULL AND NULL"); + // min below range and max at the top of range + testUnwrap("smallint", "a BETWEEN DOUBLE '-40000' AND DOUBLE '32767'", "NOT (a IS NULL) OR NULL"); + // min below range and max above range + testUnwrap("smallint", "a BETWEEN DOUBLE '-40000' AND DOUBLE '40000'", "NOT (a IS NULL) OR NULL"); + + // -2^64 constant + testUnwrap("bigint", "a BETWEEN DOUBLE '-18446744073709551616' AND DOUBLE '0'", "a <= BIGINT '0'"); + } + @Test public void testDistinctFrom() { @@ -446,6 +520,7 @@ public void testCastTimestampToTimestampWithTimeZone() // long timestamp, long timestamp with time zone testUnwrap(warsawSession, "timestamp(9)", "a > TIMESTAMP '2020-10-26 11:02:18.123456 UTC'", "a > TIMESTAMP '2020-10-26 12:02:18.123456000'"); + testUnwrap(warsawSession, "timestamp(9)", "a BETWEEN TIMESTAMP '2020-10-26 11:02:18.123456 UTC' AND TIMESTAMP '2020-10-26 12:03:20.345678 UTC'", "a BETWEEN TIMESTAMP '2020-10-26 12:02:18.123456000' AND TIMESTAMP '2020-10-26 13:03:20.345678000'"); testUnwrap(losAngelesSession, "timestamp(9)", "a > TIMESTAMP '2020-10-26 11:02:18.123456 UTC'", "a > TIMESTAMP '2020-10-26 04:02:18.123456000'"); // maximum precision @@ -543,6 +618,7 @@ public void testNoEffect() // DECIMAL(p)->DOUBLE not injective for p > 15 testUnwrap("decimal(16)", "a = DOUBLE '1'", "CAST(a AS DOUBLE) = 1E0"); + testUnwrap("decimal(16)", "a BETWEEN DOUBLE '1' AND DOUBLE '2'", "CAST(a AS DOUBLE) BETWEEN 1E0 AND 2E0"); // DECIMAL(p)->REAL not injective for p > 7 testUnwrap("decimal(8)", "a = REAL '1'", "CAST(a AS REAL) = REAL '1.0'"); @@ -593,6 +669,12 @@ public void testUnwrapCastTimestampAsDate() testUnwrap("timestamp(9)", "CAST(a AS DATE) >= DATE '1981-06-22'", "a >= TIMESTAMP '1981-06-22 00:00:00.000000000'"); testUnwrap("timestamp(12)", "CAST(a AS DATE) >= DATE '1981-06-22'", "a >= TIMESTAMP '1981-06-22 00:00:00.000000000000'"); + // between + testUnwrap("timestamp(3)", "CAST(a AS DATE) BETWEEN DATE '1981-06-22' AND DATE '1981-07-23'", "a BETWEEN TIMESTAMP '1981-06-22 00:00:00.000' AND TIMESTAMP '1981-07-23 23:59:59.999'"); + testUnwrap("timestamp(6)", "CAST(a AS DATE) BETWEEN DATE '1981-06-22' AND DATE '1981-07-23'", "a BETWEEN TIMESTAMP '1981-06-22 00:00:00.000000' AND TIMESTAMP '1981-07-23 23:59:59.999999'"); + testUnwrap("timestamp(9)", "CAST(a AS DATE) BETWEEN DATE '1981-06-22' AND DATE '1981-07-23'", "a BETWEEN TIMESTAMP '1981-06-22 00:00:00.000000000' AND TIMESTAMP '1981-07-23 23:59:59.999999999'"); + testUnwrap("timestamp(12)", "CAST(a AS DATE) BETWEEN DATE '1981-06-22' AND DATE '1981-07-23'", "a BETWEEN TIMESTAMP '1981-06-22 00:00:00.000000000000' AND TIMESTAMP '1981-07-23 23:59:59.999999999999'"); + // is distinct testUnwrap("timestamp(3)", "CAST(a AS DATE) IS DISTINCT FROM DATE '1981-06-22'", "a IS NULL OR a < TIMESTAMP '1981-06-22 00:00:00.000' OR a >= TIMESTAMP '1981-06-23 00:00:00.000'"); testUnwrap("timestamp(6)", "CAST(a AS DATE) IS DISTINCT FROM DATE '1981-06-22'", "a IS NULL OR a < TIMESTAMP '1981-06-22 00:00:00.000000' OR a >= TIMESTAMP '1981-06-23 00:00:00.000000'"); @@ -659,6 +741,12 @@ public void testUnwrapConvertTimestatmpToDate() testUnwrap("timestamp(9)", "date(a) >= DATE '1981-06-22'", "a >= TIMESTAMP '1981-06-22 00:00:00.000000000'"); testUnwrap("timestamp(12)", "date(a) >= DATE '1981-06-22'", "a >= TIMESTAMP '1981-06-22 00:00:00.000000000000'"); + // between + testUnwrap("timestamp(3)", "date(a) BETWEEN DATE '1981-06-22' AND DATE '1981-07-23'", "a BETWEEN TIMESTAMP '1981-06-22 00:00:00.000' AND TIMESTAMP '1981-07-23 23:59:59.999'"); + testUnwrap("timestamp(6)", "date(a) BETWEEN DATE '1981-06-22' AND DATE '1981-07-23'", "a BETWEEN TIMESTAMP '1981-06-22 00:00:00.000000' AND TIMESTAMP '1981-07-23 23:59:59.999999'"); + testUnwrap("timestamp(9)", "date(a) BETWEEN DATE '1981-06-22' AND DATE '1981-07-23'", "a BETWEEN TIMESTAMP '1981-06-22 00:00:00.000000000' AND TIMESTAMP '1981-07-23 23:59:59.999999999'"); + testUnwrap("timestamp(12)", "date(a) BETWEEN DATE '1981-06-22' AND DATE '1981-07-23'", "a BETWEEN TIMESTAMP '1981-06-22 00:00:00.000000000000' AND TIMESTAMP '1981-07-23 23:59:59.999999999999'"); + // is distinct testUnwrap("timestamp(3)", "date(a) IS DISTINCT FROM DATE '1981-06-22'", "a IS NULL OR a < TIMESTAMP '1981-06-22 00:00:00.000' OR a >= TIMESTAMP '1981-06-23 00:00:00.000'"); testUnwrap("timestamp(6)", "date(a) IS DISTINCT FROM DATE '1981-06-22'", "a IS NULL OR a < TIMESTAMP '1981-06-22 00:00:00.000000' OR a >= TIMESTAMP '1981-06-23 00:00:00.000000'"); diff --git a/core/trino-main/src/test/java/io/trino/sql/query/TestUnwrapCastInComparison.java b/core/trino-main/src/test/java/io/trino/sql/query/TestUnwrapCastInComparison.java index 84bc3382f35e..79e80579db3a 100644 --- a/core/trino-main/src/test/java/io/trino/sql/query/TestUnwrapCastInComparison.java +++ b/core/trino-main/src/test/java/io/trino/sql/query/TestUnwrapCastInComparison.java @@ -77,6 +77,14 @@ public void testTinyint() validate(operator, fromType, from, "DOUBLE", to); } } + + for (Number to : asList(null, Byte.MIN_VALUE - 1, Byte.MIN_VALUE, 0, 1, Byte.MAX_VALUE, Byte.MAX_VALUE + 1)) { + validateBetween(fromType, from, "SMALLINT", to, to); + validateBetween(fromType, from, "INTEGER", to, to); + validateBetween(fromType, from, "BIGINT", to, to); + validateBetween(fromType, from, "REAL", to, to); + validateBetween(fromType, from, "DOUBLE", to, to); + } } } @@ -102,6 +110,13 @@ public void testSmallint() validate(operator, fromType, from, "DOUBLE", to); } } + + for (Number to : asList(null, Short.MIN_VALUE - 1, Short.MIN_VALUE, 0, 1, Short.MAX_VALUE, Short.MAX_VALUE + 1)) { + validateBetween(fromType, from, "INTEGER", to, to); + validateBetween(fromType, from, "BIGINT", to, to); + validateBetween(fromType, from, "REAL", to, to); + validateBetween(fromType, from, "DOUBLE", to, to); + } } } @@ -123,6 +138,16 @@ public void testInteger() validate(operator, fromType, from, "REAL", to); } } + + for (Number to : asList(null, Integer.MIN_VALUE - 1L, Integer.MIN_VALUE, 0, 1, Integer.MAX_VALUE, Integer.MAX_VALUE + 1L)) { + validateBetween(fromType, from, "BIGINT", to, to); + } + for (Number to : asList(null, Integer.MIN_VALUE - 1L, Integer.MIN_VALUE, 0, 0.1, 0.9, 1, Integer.MAX_VALUE, Integer.MAX_VALUE + 1L)) { + validateBetween(fromType, from, "DOUBLE", to, to); + } + for (Number to : asList(null, Integer.MIN_VALUE - 1L, Integer.MIN_VALUE, -1L << 23 + 1, 0, 0.1, 0.9, 1, 1L << 23 - 1, Integer.MAX_VALUE, Integer.MAX_VALUE + 1L)) { + validateBetween(fromType, from, "REAL", to, to); + } } } @@ -140,6 +165,13 @@ public void testBigint() validate(operator, fromType, from, "REAL", to); } } + + for (Number to : asList(null, Long.MIN_VALUE, Long.MIN_VALUE + 1, -1L << 53 + 1, 0, 0.1, 0.9, 1, 1L << 53 - 1, Long.MAX_VALUE - 1, Long.MAX_VALUE)) { + validateBetween(fromType, from, "DOUBLE", to, to); + } + for (Number to : asList(null, Long.MIN_VALUE, Long.MIN_VALUE + 1, -1L << 23 + 1, 0, 0.1, 0.9, 1, 1L << 23 - 1, Long.MAX_VALUE - 1, Long.MAX_VALUE)) { + validateBetween(fromType, from, "REAL", to, to); + } } } @@ -155,6 +187,9 @@ public void testReal() validate(operator, fromType, from, toType, to); } } + for (String to : toLiteral(toType, asList(null, Double.NEGATIVE_INFINITY, Math.nextDown((double) -Float.MIN_VALUE), (double) -Float.MIN_VALUE, 0, 0.1, 0.9, 1, (double) Float.MAX_VALUE, Math.nextUp((double) Float.MAX_VALUE), Double.POSITIVE_INFINITY, Double.NaN))) { + validateBetween(fromType, from, toType, to, to); + } } } @@ -169,6 +204,9 @@ public void testDecimal() validate(operator, "DECIMAL(15, 0)", from, "DOUBLE", Double.valueOf(to)); } } + for (String to : values) { + validateBetween("DECIMAL(15, 0)", from, "DOUBLE", Double.valueOf(to), Double.valueOf(to)); + } } // decimal(16) -> double @@ -179,6 +217,9 @@ public void testDecimal() validate(operator, "DECIMAL(16, 0)", from, "DOUBLE", Double.valueOf(to)); } } + for (String to : values) { + validateBetween("DECIMAL(16, 0)", from, "DOUBLE", Double.valueOf(to), Double.valueOf(to)); + } } // decimal(7) -> real @@ -189,6 +230,9 @@ public void testDecimal() validate(operator, "DECIMAL(7, 0)", from, "REAL", Double.valueOf(to)); } } + for (String to : values) { + validateBetween("DECIMAL(7, 0)", from, "REAL", Double.valueOf(to), Double.valueOf(to)); + } } // decimal(8) -> real @@ -199,6 +243,9 @@ public void testDecimal() validate(operator, "DECIMAL(8, 0)", from, "REAL", Double.valueOf(to)); } } + for (String to : values) { + validateBetween("DECIMAL(8, 0)", from, "REAL", Double.valueOf(to), Double.valueOf(to)); + } } } @@ -211,6 +258,9 @@ public void testVarchar() validate(operator, "VARCHAR(1)", from, "VARCHAR(2)", to); } } + for (String to : asList(null, "''", "'a'", "'aa'", "'b'", "'bb'")) { + validateBetween("VARCHAR(1)", from, "VARCHAR(2)", to, to); + } } // type with no range @@ -219,6 +269,9 @@ public void testVarchar() validate(operator, "VARCHAR(200)", "'" + "a".repeat(200) + "'", "VARCHAR(300)", to); } } + for (String to : asList("'" + "a".repeat(200) + "'", "'" + "b".repeat(200) + "'")) { + validateBetween("VARCHAR(200)", "'" + "a".repeat(200) + "'", "VARCHAR(300)", to, to); + } } @Test @@ -240,6 +293,15 @@ public void testCastTimestampToTimestampWithTimeZone() validate(session, operator, "timestamp(12)", "TIMESTAMP '2020-07-03 01:23:45.123456789123'", "timestamp(12) with time zone", "TIMESTAMP '2020-07-03 01:23:45 UTC'"); } + validateBetween(session, "timestamp(3)", "TIMESTAMP '2020-07-03 01:23:45.123'", "timestamp(3) with time zone", "TIMESTAMP '2020-07-03 01:23:45 Europe/Warsaw'", "TIMESTAMP '2020-07-03 01:23:45 Europe/Warsaw'"); + validateBetween(session, "timestamp(3)", "TIMESTAMP '2020-07-03 01:23:45.123'", "timestamp(3) with time zone", "TIMESTAMP '2020-07-03 01:23:45 UTC'", "TIMESTAMP '2020-07-03 01:23:45 UTC'"); + validateBetween(session, "timestamp(6)", "TIMESTAMP '2020-07-03 01:23:45.123456'", "timestamp(6) with time zone", "TIMESTAMP '2020-07-03 01:23:45 Europe/Warsaw'", "TIMESTAMP '2020-07-03 01:23:45 Europe/Warsaw'"); + validateBetween(session, "timestamp(6)", "TIMESTAMP '2020-07-03 01:23:45.123456'", "timestamp(6) with time zone", "TIMESTAMP '2020-07-03 01:23:45 UTC'", "TIMESTAMP '2020-07-03 01:23:45 UTC'"); + validateBetween(session, "timestamp(9)", "TIMESTAMP '2020-07-03 01:23:45.123456789'", "timestamp(9) with time zone", "TIMESTAMP '2020-07-03 01:23:45 Europe/Warsaw'", "TIMESTAMP '2020-07-03 01:23:45 Europe/Warsaw'"); + validateBetween(session, "timestamp(9)", "TIMESTAMP '2020-07-03 01:23:45.123456789'", "timestamp(9) with time zone", "TIMESTAMP '2020-07-03 01:23:45 UTC'", "TIMESTAMP '2020-07-03 01:23:45 UTC'"); + validateBetween(session, "timestamp(12)", "TIMESTAMP '2020-07-03 01:23:45.123456789123'", "timestamp(12) with time zone", "TIMESTAMP '2020-07-03 01:23:45 Europe/Warsaw'", "TIMESTAMP '2020-07-03 01:23:45 Europe/Warsaw'"); + validateBetween(session, "timestamp(12)", "TIMESTAMP '2020-07-03 01:23:45.123456789123'", "timestamp(12) with time zone", "TIMESTAMP '2020-07-03 01:23:45 UTC'", "TIMESTAMP '2020-07-03 01:23:45 UTC'"); + // DST forward change (2017-09-24 03:00 -> 2017-09-24 04:00) List fromLocalTimes = asList( LocalTime.parse("02:59:59.999999999"), @@ -350,6 +412,30 @@ private void validate(Session session, String operator, String fromType, Object assertTrue(result, "Query evaluated to false: " + query); } + private void validateBetween(String fromType, Object fromValue, String toType, Object minValue, Object maxValue) + { + validateBetween(assertions.getDefaultSession(), fromType, fromValue, toType, minValue, maxValue); + } + + private void validateBetween(Session session, String fromType, Object fromValue, String toType, Object minValue, Object maxValue) + { + String query = format( + "SELECT (CAST(v AS %s) BETWEEN CAST(%s AS %s) AND CAST(%s AS %s)) " + + "IS NOT DISTINCT FROM " + + "(CAST(%s AS %s) BETWEEN CAST(%s AS %s) AND CAST(%s AS %s)) " + + "FROM (VALUES CAST(%s AS %s)) t(v)", + toType, minValue, toType, maxValue, toType, + fromValue, toType, minValue, toType, maxValue, toType, + fromValue, fromType); + + boolean result = (boolean) assertions.execute(session, query) + .getMaterializedRows() + .get(0) + .getField(0); + + assertTrue(result, "Query evaluated to false: " + query); + } + @Test public void testUnwrapTimestampToDate() { 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 9b7678fc4785..b6267a7471e7 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 @@ -1399,19 +1399,33 @@ 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(); // 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'")) @@ -1505,6 +1519,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(); @@ -1514,10 +1530,14 @@ 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'")) @@ -1645,6 +1665,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'")) @@ -1659,6 +1681,8 @@ 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'")) @@ -1758,19 +1782,29 @@ 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'")) @@ -1885,6 +1919,8 @@ 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'")) @@ -1981,16 +2017,22 @@ 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'")) @@ -2112,9 +2154,16 @@ 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'")) @@ -2224,6 +2273,8 @@ 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'")) @@ -2316,6 +2367,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'")) @@ -2326,6 +2381,8 @@ 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'")) @@ -2436,6 +2493,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(); @@ -2445,6 +2504,8 @@ 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'")) @@ -2531,6 +2592,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(); @@ -2540,6 +2605,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(); @@ -2549,6 +2616,8 @@ 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'")) diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/partitioned/q13.plan.txt b/testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/partitioned/q13.plan.txt index 515bc8719e42..5f3c2793600a 100644 --- a/testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/partitioned/q13.plan.txt +++ b/testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/partitioned/q13.plan.txt @@ -3,24 +3,24 @@ final aggregation over () remote exchange (GATHER, SINGLE, []) partial aggregation over () join (INNER, REPLICATED): - join (INNER, PARTITIONED): - remote exchange (REPARTITION, HASH, ["cd_demo_sk"]) - scan customer_demographics - local exchange (GATHER, SINGLE, []) - remote exchange (REPARTITION, HASH, ["ss_cdemo_sk"]) - join (INNER, REPLICATED): + scan customer_demographics + local exchange (GATHER, SINGLE, []) + remote exchange (REPLICATE, BROADCAST, []) + join (INNER, PARTITIONED): + remote exchange (REPARTITION, HASH, ["ss_hdemo_sk"]) join (INNER, REPLICATED): join (INNER, REPLICATED): - scan store_sales + join (INNER, REPLICATED): + scan store_sales + local exchange (GATHER, SINGLE, []) + remote exchange (REPLICATE, BROADCAST, []) + scan customer_address local exchange (GATHER, SINGLE, []) remote exchange (REPLICATE, BROADCAST, []) scan date_dim local exchange (GATHER, SINGLE, []) remote exchange (REPLICATE, BROADCAST, []) - scan customer_address - local exchange (GATHER, SINGLE, []) - remote exchange (REPLICATE, BROADCAST, []) - scan household_demographics - local exchange (GATHER, SINGLE, []) - remote exchange (REPLICATE, BROADCAST, []) - scan store + scan store + local exchange (GATHER, SINGLE, []) + remote exchange (REPARTITION, HASH, ["hd_demo_sk"]) + scan household_demographics diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/partitioned/q85.plan.txt b/testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/partitioned/q85.plan.txt index 2ae4cae3b89b..3411867b65ab 100644 --- a/testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/partitioned/q85.plan.txt +++ b/testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/partitioned/q85.plan.txt @@ -4,35 +4,35 @@ local exchange (GATHER, SINGLE, []) local exchange (GATHER, SINGLE, []) remote exchange (REPARTITION, HASH, ["r_reason_desc"]) partial aggregation over (r_reason_desc) - join (INNER, REPLICATED): - scan customer_demographics + join (INNER, PARTITIONED): + remote exchange (REPARTITION, HASH, ["wp_web_page_sk"]) + scan web_page local exchange (GATHER, SINGLE, []) - remote exchange (REPLICATE, BROADCAST, []) + remote exchange (REPARTITION, HASH, ["ws_web_page_sk"]) join (INNER, REPLICATED): - join (INNER, REPLICATED): - join (INNER, PARTITIONED): - remote exchange (REPARTITION, HASH, ["ca_address_sk"]) - scan customer_address - local exchange (GATHER, SINGLE, []) - remote exchange (REPARTITION, HASH, ["wr_refunded_addr_sk"]) - join (INNER, PARTITIONED): - remote exchange (REPARTITION, HASH, ["wr_refunded_cdemo_sk"]) - join (INNER, PARTITIONED): - remote exchange (REPARTITION, HASH, ["wr_item_sk", "wr_order_number"]) - scan web_returns - local exchange (GATHER, SINGLE, []) - remote exchange (REPARTITION, HASH, ["ws_item_sk", "ws_order_number"]) - join (INNER, REPLICATED): - scan web_sales - local exchange (GATHER, SINGLE, []) - remote exchange (REPLICATE, BROADCAST, []) - scan date_dim - local exchange (GATHER, SINGLE, []) - remote exchange (REPARTITION, HASH, ["cd_demo_sk"]) - scan customer_demographics - local exchange (GATHER, SINGLE, []) - remote exchange (REPLICATE, BROADCAST, []) - scan web_page + scan customer_demographics local exchange (GATHER, SINGLE, []) remote exchange (REPLICATE, BROADCAST, []) - scan reason + join (INNER, REPLICATED): + scan customer_demographics + local exchange (GATHER, SINGLE, []) + remote exchange (REPLICATE, BROADCAST, []) + join (INNER, REPLICATED): + join (INNER, PARTITIONED): + remote exchange (REPARTITION, HASH, ["wr_refunded_addr_sk"]) + join (INNER, PARTITIONED): + remote exchange (REPARTITION, HASH, ["wr_item_sk", "wr_order_number"]) + scan web_returns + local exchange (GATHER, SINGLE, []) + remote exchange (REPARTITION, HASH, ["ws_item_sk", "ws_order_number"]) + join (INNER, REPLICATED): + scan web_sales + local exchange (GATHER, SINGLE, []) + remote exchange (REPLICATE, BROADCAST, []) + scan date_dim + local exchange (GATHER, SINGLE, []) + remote exchange (REPARTITION, HASH, ["ca_address_sk"]) + scan customer_address + local exchange (GATHER, SINGLE, []) + remote exchange (REPLICATE, BROADCAST, []) + scan reason diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/unpartitioned/q13.plan.txt b/testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/unpartitioned/q13.plan.txt index 011d5b20fbba..b5c9b53bb2a1 100644 --- a/testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/unpartitioned/q13.plan.txt +++ b/testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/unpartitioned/q13.plan.txt @@ -3,23 +3,24 @@ final aggregation over () remote exchange (GATHER, SINGLE, []) partial aggregation over () join (INNER, REPLICATED): - join (INNER, REPLICATED): - join (INNER, REPLICATED): - join (INNER, REPLICATED): + join (INNER, PARTITIONED): + remote exchange (REPARTITION, HASH, ["cd_demo_sk"]) + scan customer_demographics + local exchange (GATHER, SINGLE, []) + remote exchange (REPARTITION, HASH, ["ss_cdemo_sk"]) join (INNER, REPLICATED): - scan store_sales + join (INNER, REPLICATED): + join (INNER, REPLICATED): + scan store_sales + local exchange (GATHER, SINGLE, []) + remote exchange (REPLICATE, BROADCAST, []) + scan customer_address + local exchange (GATHER, SINGLE, []) + remote exchange (REPLICATE, BROADCAST, []) + scan date_dim local exchange (GATHER, SINGLE, []) remote exchange (REPLICATE, BROADCAST, []) - scan customer_address - local exchange (GATHER, SINGLE, []) - remote exchange (REPLICATE, BROADCAST, []) - scan date_dim - local exchange (GATHER, SINGLE, []) - remote exchange (REPLICATE, BROADCAST, []) - scan household_demographics - local exchange (GATHER, SINGLE, []) - remote exchange (REPLICATE, BROADCAST, []) - scan customer_demographics + scan household_demographics local exchange (GATHER, SINGLE, []) remote exchange (REPLICATE, BROADCAST, []) scan store diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/unpartitioned/q85.plan.txt b/testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/unpartitioned/q85.plan.txt index ab3b2c159101..1df6d7678d78 100644 --- a/testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/unpartitioned/q85.plan.txt +++ b/testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/unpartitioned/q85.plan.txt @@ -4,35 +4,35 @@ local exchange (GATHER, SINGLE, []) local exchange (GATHER, SINGLE, []) remote exchange (REPARTITION, HASH, ["r_reason_desc"]) partial aggregation over (r_reason_desc) - join (INNER, REPLICATED): - join (INNER, REPLICATED): - scan customer_demographics - local exchange (GATHER, SINGLE, []) - remote exchange (REPLICATE, BROADCAST, []) - join (INNER, REPLICATED): - join (INNER, PARTITIONED): - remote exchange (REPARTITION, HASH, ["ca_address_sk"]) - scan customer_address + join (INNER, PARTITIONED): + remote exchange (REPARTITION, HASH, ["wr_reason_sk"]) + join (INNER, REPLICATED): + scan customer_demographics + local exchange (GATHER, SINGLE, []) + remote exchange (REPLICATE, BROADCAST, []) + join (INNER, REPLICATED): + scan customer_demographics local exchange (GATHER, SINGLE, []) - remote exchange (REPARTITION, HASH, ["wr_refunded_addr_sk"]) - join (INNER, PARTITIONED): - remote exchange (REPARTITION, HASH, ["wr_refunded_cdemo_sk"]) - join (INNER, PARTITIONED): - remote exchange (REPARTITION, HASH, ["ws_item_sk", "ws_order_number"]) - join (INNER, REPLICATED): - scan web_sales - local exchange (GATHER, SINGLE, []) - remote exchange (REPLICATE, BROADCAST, []) - scan date_dim - local exchange (GATHER, SINGLE, []) - remote exchange (REPARTITION, HASH, ["wr_item_sk", "wr_order_number"]) - scan web_returns + remote exchange (REPLICATE, BROADCAST, []) + join (INNER, REPLICATED): + join (INNER, PARTITIONED): + remote exchange (REPARTITION, HASH, ["wr_refunded_addr_sk"]) + join (INNER, PARTITIONED): + remote exchange (REPARTITION, HASH, ["ws_item_sk", "ws_order_number"]) + join (INNER, REPLICATED): + scan web_sales + local exchange (GATHER, SINGLE, []) + remote exchange (REPLICATE, BROADCAST, []) + scan date_dim + local exchange (GATHER, SINGLE, []) + remote exchange (REPARTITION, HASH, ["wr_item_sk", "wr_order_number"]) + scan web_returns + local exchange (GATHER, SINGLE, []) + remote exchange (REPARTITION, HASH, ["ca_address_sk"]) + scan customer_address local exchange (GATHER, SINGLE, []) - remote exchange (REPARTITION, HASH, ["cd_demo_sk"]) - scan customer_demographics - local exchange (GATHER, SINGLE, []) - remote exchange (REPLICATE, BROADCAST, []) - scan web_page + remote exchange (REPLICATE, BROADCAST, []) + scan web_page local exchange (GATHER, SINGLE, []) - remote exchange (REPLICATE, BROADCAST, []) + remote exchange (REPARTITION, HASH, ["r_reason_sk"]) scan reason diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/partitioned/q13.plan.txt b/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/partitioned/q13.plan.txt index a4d7d7d75438..a40ad57b57f8 100644 --- a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/partitioned/q13.plan.txt +++ b/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/partitioned/q13.plan.txt @@ -4,24 +4,24 @@ final aggregation over () partial aggregation over () join (INNER, REPLICATED): join (INNER, PARTITIONED): - remote exchange (REPARTITION, HASH, ["ss_cdemo_sk"]) - join (INNER, REPLICATED): - join (INNER, PARTITIONED): - remote exchange (REPARTITION, HASH, ["ss_addr_sk"]) - join (INNER, REPLICATED): - scan store_sales - local exchange (GATHER, SINGLE, []) - remote exchange (REPLICATE, BROADCAST, []) - scan date_dim - local exchange (GATHER, SINGLE, []) - remote exchange (REPARTITION, HASH, ["ca_address_sk"]) - scan customer_address - local exchange (GATHER, SINGLE, []) - remote exchange (REPLICATE, BROADCAST, []) - scan household_demographics + remote exchange (REPARTITION, HASH, ["cd_demo_sk"]) + scan customer_demographics local exchange (GATHER, SINGLE, []) - remote exchange (REPARTITION, HASH, ["cd_demo_sk"]) - scan customer_demographics + remote exchange (REPARTITION, HASH, ["ss_cdemo_sk"]) + join (INNER, REPLICATED): + join (INNER, PARTITIONED): + remote exchange (REPARTITION, HASH, ["ss_addr_sk"]) + join (INNER, REPLICATED): + scan store_sales + local exchange (GATHER, SINGLE, []) + remote exchange (REPLICATE, BROADCAST, []) + scan date_dim + local exchange (GATHER, SINGLE, []) + remote exchange (REPARTITION, HASH, ["ca_address_sk"]) + scan customer_address + local exchange (GATHER, SINGLE, []) + remote exchange (REPLICATE, BROADCAST, []) + scan household_demographics local exchange (GATHER, SINGLE, []) remote exchange (REPLICATE, BROADCAST, []) scan store diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/partitioned/q85.plan.txt b/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/partitioned/q85.plan.txt index 2a72b1dbbed5..1df6d7678d78 100644 --- a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/partitioned/q85.plan.txt +++ b/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/partitioned/q85.plan.txt @@ -4,35 +4,35 @@ local exchange (GATHER, SINGLE, []) local exchange (GATHER, SINGLE, []) remote exchange (REPARTITION, HASH, ["r_reason_desc"]) partial aggregation over (r_reason_desc) - join (INNER, REPLICATED): - join (INNER, REPLICATED): - scan customer_address - local exchange (GATHER, SINGLE, []) - remote exchange (REPLICATE, BROADCAST, []) - join (INNER, REPLICATED): - join (INNER, PARTITIONED): - remote exchange (REPARTITION, HASH, ["cd_demo_sk_6", "cd_education_status_9", "cd_marital_status_8"]) - scan customer_demographics + join (INNER, PARTITIONED): + remote exchange (REPARTITION, HASH, ["wr_reason_sk"]) + join (INNER, REPLICATED): + scan customer_demographics + local exchange (GATHER, SINGLE, []) + remote exchange (REPLICATE, BROADCAST, []) + join (INNER, REPLICATED): + scan customer_demographics local exchange (GATHER, SINGLE, []) - remote exchange (REPARTITION, HASH, ["cd_education_status", "cd_marital_status", "wr_returning_cdemo_sk"]) - join (INNER, PARTITIONED): - remote exchange (REPARTITION, HASH, ["wr_refunded_cdemo_sk"]) - join (INNER, PARTITIONED): - remote exchange (REPARTITION, HASH, ["ws_item_sk", "ws_order_number"]) - join (INNER, REPLICATED): - scan web_sales - local exchange (GATHER, SINGLE, []) - remote exchange (REPLICATE, BROADCAST, []) - scan date_dim - local exchange (GATHER, SINGLE, []) - remote exchange (REPARTITION, HASH, ["wr_item_sk", "wr_order_number"]) - scan web_returns + remote exchange (REPLICATE, BROADCAST, []) + join (INNER, REPLICATED): + join (INNER, PARTITIONED): + remote exchange (REPARTITION, HASH, ["wr_refunded_addr_sk"]) + join (INNER, PARTITIONED): + remote exchange (REPARTITION, HASH, ["ws_item_sk", "ws_order_number"]) + join (INNER, REPLICATED): + scan web_sales + local exchange (GATHER, SINGLE, []) + remote exchange (REPLICATE, BROADCAST, []) + scan date_dim + local exchange (GATHER, SINGLE, []) + remote exchange (REPARTITION, HASH, ["wr_item_sk", "wr_order_number"]) + scan web_returns + local exchange (GATHER, SINGLE, []) + remote exchange (REPARTITION, HASH, ["ca_address_sk"]) + scan customer_address local exchange (GATHER, SINGLE, []) - remote exchange (REPARTITION, HASH, ["cd_demo_sk"]) - scan customer_demographics - local exchange (GATHER, SINGLE, []) - remote exchange (REPLICATE, BROADCAST, []) - scan web_page + remote exchange (REPLICATE, BROADCAST, []) + scan web_page local exchange (GATHER, SINGLE, []) - remote exchange (REPLICATE, BROADCAST, []) + remote exchange (REPARTITION, HASH, ["r_reason_sk"]) scan reason diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/unpartitioned/q13.plan.txt b/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/unpartitioned/q13.plan.txt index a4d7d7d75438..a40ad57b57f8 100644 --- a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/unpartitioned/q13.plan.txt +++ b/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/unpartitioned/q13.plan.txt @@ -4,24 +4,24 @@ final aggregation over () partial aggregation over () join (INNER, REPLICATED): join (INNER, PARTITIONED): - remote exchange (REPARTITION, HASH, ["ss_cdemo_sk"]) - join (INNER, REPLICATED): - join (INNER, PARTITIONED): - remote exchange (REPARTITION, HASH, ["ss_addr_sk"]) - join (INNER, REPLICATED): - scan store_sales - local exchange (GATHER, SINGLE, []) - remote exchange (REPLICATE, BROADCAST, []) - scan date_dim - local exchange (GATHER, SINGLE, []) - remote exchange (REPARTITION, HASH, ["ca_address_sk"]) - scan customer_address - local exchange (GATHER, SINGLE, []) - remote exchange (REPLICATE, BROADCAST, []) - scan household_demographics + remote exchange (REPARTITION, HASH, ["cd_demo_sk"]) + scan customer_demographics local exchange (GATHER, SINGLE, []) - remote exchange (REPARTITION, HASH, ["cd_demo_sk"]) - scan customer_demographics + remote exchange (REPARTITION, HASH, ["ss_cdemo_sk"]) + join (INNER, REPLICATED): + join (INNER, PARTITIONED): + remote exchange (REPARTITION, HASH, ["ss_addr_sk"]) + join (INNER, REPLICATED): + scan store_sales + local exchange (GATHER, SINGLE, []) + remote exchange (REPLICATE, BROADCAST, []) + scan date_dim + local exchange (GATHER, SINGLE, []) + remote exchange (REPARTITION, HASH, ["ca_address_sk"]) + scan customer_address + local exchange (GATHER, SINGLE, []) + remote exchange (REPLICATE, BROADCAST, []) + scan household_demographics local exchange (GATHER, SINGLE, []) remote exchange (REPLICATE, BROADCAST, []) scan store diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/unpartitioned/q85.plan.txt b/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/unpartitioned/q85.plan.txt index 2a72b1dbbed5..1df6d7678d78 100644 --- a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/unpartitioned/q85.plan.txt +++ b/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/unpartitioned/q85.plan.txt @@ -4,35 +4,35 @@ local exchange (GATHER, SINGLE, []) local exchange (GATHER, SINGLE, []) remote exchange (REPARTITION, HASH, ["r_reason_desc"]) partial aggregation over (r_reason_desc) - join (INNER, REPLICATED): - join (INNER, REPLICATED): - scan customer_address - local exchange (GATHER, SINGLE, []) - remote exchange (REPLICATE, BROADCAST, []) - join (INNER, REPLICATED): - join (INNER, PARTITIONED): - remote exchange (REPARTITION, HASH, ["cd_demo_sk_6", "cd_education_status_9", "cd_marital_status_8"]) - scan customer_demographics + join (INNER, PARTITIONED): + remote exchange (REPARTITION, HASH, ["wr_reason_sk"]) + join (INNER, REPLICATED): + scan customer_demographics + local exchange (GATHER, SINGLE, []) + remote exchange (REPLICATE, BROADCAST, []) + join (INNER, REPLICATED): + scan customer_demographics local exchange (GATHER, SINGLE, []) - remote exchange (REPARTITION, HASH, ["cd_education_status", "cd_marital_status", "wr_returning_cdemo_sk"]) - join (INNER, PARTITIONED): - remote exchange (REPARTITION, HASH, ["wr_refunded_cdemo_sk"]) - join (INNER, PARTITIONED): - remote exchange (REPARTITION, HASH, ["ws_item_sk", "ws_order_number"]) - join (INNER, REPLICATED): - scan web_sales - local exchange (GATHER, SINGLE, []) - remote exchange (REPLICATE, BROADCAST, []) - scan date_dim - local exchange (GATHER, SINGLE, []) - remote exchange (REPARTITION, HASH, ["wr_item_sk", "wr_order_number"]) - scan web_returns + remote exchange (REPLICATE, BROADCAST, []) + join (INNER, REPLICATED): + join (INNER, PARTITIONED): + remote exchange (REPARTITION, HASH, ["wr_refunded_addr_sk"]) + join (INNER, PARTITIONED): + remote exchange (REPARTITION, HASH, ["ws_item_sk", "ws_order_number"]) + join (INNER, REPLICATED): + scan web_sales + local exchange (GATHER, SINGLE, []) + remote exchange (REPLICATE, BROADCAST, []) + scan date_dim + local exchange (GATHER, SINGLE, []) + remote exchange (REPARTITION, HASH, ["wr_item_sk", "wr_order_number"]) + scan web_returns + local exchange (GATHER, SINGLE, []) + remote exchange (REPARTITION, HASH, ["ca_address_sk"]) + scan customer_address local exchange (GATHER, SINGLE, []) - remote exchange (REPARTITION, HASH, ["cd_demo_sk"]) - scan customer_demographics - local exchange (GATHER, SINGLE, []) - remote exchange (REPLICATE, BROADCAST, []) - scan web_page + remote exchange (REPLICATE, BROADCAST, []) + scan web_page local exchange (GATHER, SINGLE, []) - remote exchange (REPLICATE, BROADCAST, []) + remote exchange (REPARTITION, HASH, ["r_reason_sk"]) scan reason