From 021867e021a4e6c7dd0775799afe06408e372eb7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Wa=C5=9B?= Date: Sun, 29 Dec 2024 14:18:18 +0100 Subject: [PATCH] Support negative steps in Faker sequences --- .../trino/plugin/faker/FakerColumnHandle.java | 7 +- .../trino/plugin/faker/FakerPageSource.java | 204 +++++++++++++----- .../trino/plugin/faker/TestFakerQueries.java | 116 +++++++--- 3 files changed, 240 insertions(+), 87 deletions(-) diff --git a/plugin/trino-faker/src/main/java/io/trino/plugin/faker/FakerColumnHandle.java b/plugin/trino-faker/src/main/java/io/trino/plugin/faker/FakerColumnHandle.java index c7af196596c..38a47f72192 100644 --- a/plugin/trino-faker/src/main/java/io/trino/plugin/faker/FakerColumnHandle.java +++ b/plugin/trino-faker/src/main/java/io/trino/plugin/faker/FakerColumnHandle.java @@ -139,7 +139,12 @@ private static ValueSet stepValue(ColumnMetadata column) } if (DATE.equals(column.getType()) || type instanceof TimestampType || type instanceof TimestampWithTimeZoneType || type instanceof TimeType || type instanceof TimeWithTimeZoneType) { try { - return ValueSet.of(BIGINT, Duration.valueOf(rawStep).roundTo(TimeUnit.NANOSECONDS)); + byte sign = 1; + if (rawStep.charAt(0) == '-') { + rawStep = rawStep.substring(1); + sign = -1; + } + return ValueSet.of(BIGINT, sign * Duration.valueOf(rawStep).roundTo(TimeUnit.NANOSECONDS)); } catch (IllegalArgumentException e) { throw new TrinoException(INVALID_COLUMN_PROPERTY, "The `%s` property for a %s column must be a valid duration literal".formatted(STEP_PROPERTY, column.getType().getDisplayName()), e); diff --git a/plugin/trino-faker/src/main/java/io/trino/plugin/faker/FakerPageSource.java b/plugin/trino-faker/src/main/java/io/trino/plugin/faker/FakerPageSource.java index 9a3338eeb36..db6bba24a87 100644 --- a/plugin/trino-faker/src/main/java/io/trino/plugin/faker/FakerPageSource.java +++ b/plugin/trino-faker/src/main/java/io/trino/plugin/faker/FakerPageSource.java @@ -652,20 +652,24 @@ static LongRange of(Range range, long factor, long defaultMin, long defaultMax) static LongRange of(Range range, long factor, long defaultMin, long defaultMax, long step) { - return new LongRange( - roundDiv((long) range.getLowValue().orElse(defaultMin), factor) + (!range.isLowUnbounded() && !range.isLowInclusive() ? 1 : 0), - roundDiv((long) range.getHighValue().orElse(defaultMax), factor) + (!range.isHighUnbounded() && range.isHighInclusive() ? 1 : 0), - step); + if (step > 0) { + long low = roundDiv((long) range.getLowValue().orElse(defaultMin), factor) + (!range.isLowUnbounded() && !range.isLowInclusive() ? 1 : 0); + long high = roundDiv((long) range.getHighValue().orElse(defaultMax), factor) + (!range.isHighUnbounded() && range.isHighInclusive() ? 1 : 0); + return new LongRange(low, high, step); + } + long low = roundDiv((long) range.getHighValue().orElse(defaultMax), factor) + (!range.isHighUnbounded() && !range.isHighInclusive() ? 1 : 0); + long high = roundDiv((long) range.getLowValue().orElse(defaultMin), factor) + (!range.isLowUnbounded() && range.isLowInclusive() ? 1 : 0); + return new LongRange(low, high, step); } long at(long index) { - return Math.min(low + index * step, high - 1); + return step > 0 ? Math.min(low + index * step, high - 1) : Math.max(low + index * step, high - 1); } long at(long index, long factor) { - return Math.min(low + roundDiv(index * step, factor), high - 1); + return step > 0 ? Math.min(low + roundDiv(index * step, factor), high - 1) : Math.max(low + roundDiv(index * step, factor), high - 1); } } @@ -688,20 +692,24 @@ static IntRange of(Range range, long defaultMin, long defaultMax) static IntRange of(Range range, long defaultMin, long defaultMax, long step) { - return new IntRange( - toIntExact((long) range.getLowValue().orElse(defaultMin)) + (!range.isLowUnbounded() && !range.isLowInclusive() ? 1 : 0), - toIntExact((long) range.getHighValue().orElse(defaultMax)) + (!range.isHighUnbounded() && range.isHighInclusive() ? 1 : 0), - step); + if (step > 0) { + int low = toIntExact((long) range.getLowValue().orElse(defaultMin)) + (!range.isLowUnbounded() && !range.isLowInclusive() ? 1 : 0); + int high = toIntExact((long) range.getHighValue().orElse(defaultMax)) + (!range.isHighUnbounded() && range.isHighInclusive() ? 1 : 0); + return new IntRange(low, high, step); + } + int low = toIntExact((long) range.getHighValue().orElse(defaultMax)) + (!range.isHighUnbounded() && !range.isHighInclusive() ? 1 : 0); + int high = toIntExact((long) range.getLowValue().orElse(defaultMin)) + (!range.isLowUnbounded() && range.isLowInclusive() ? 1 : 0); + return new IntRange(low, high, step); } long at(long index) { - return Math.min(low + index * step, high - 1); + return step > 0 ? Math.min(low + index * step, high - 1) : Math.max(low + index * step, high - 1); } long at(long index, long factor) { - return Math.min(low + roundDiv(index * step, factor), high - 1); + return step > 0 ? Math.min(low + roundDiv(index * step, factor), high - 1) : Math.max(low + roundDiv(index * step, factor), high - 1); } } @@ -714,12 +722,23 @@ static FloatRange of(Range range) static FloatRange of(Range range, float step) { - float low = range.getLowValue().map(v -> intBitsToFloat(toIntExact((long) v))).orElse(Float.MIN_VALUE); - if (!range.isLowUnbounded() && !range.isLowInclusive()) { + if (step > 0) { + float low = range.getLowValue().map(v -> intBitsToFloat(toIntExact((long) v))).orElse(Float.MIN_VALUE); + if (!range.isLowUnbounded() && !range.isLowInclusive()) { + low = Math.nextUp(low); + } + float high = range.getHighValue().map(v -> intBitsToFloat(toIntExact((long) v))).orElse(Float.MAX_VALUE); + if (!range.isHighUnbounded() && range.isHighInclusive()) { + high = Math.nextUp(high); + } + return new FloatRange(low, high, step); + } + float low = range.getHighValue().map(v -> intBitsToFloat(toIntExact((long) v))).orElse(Float.MAX_VALUE); + if (!range.isHighUnbounded() && !range.isHighInclusive()) { low = Math.nextUp(low); } - float high = range.getHighValue().map(v -> intBitsToFloat(toIntExact((long) v))).orElse(Float.MAX_VALUE); - if (!range.isHighUnbounded() && range.isHighInclusive()) { + float high = range.getLowValue().map(v -> intBitsToFloat(toIntExact((long) v))).orElse(Float.MIN_VALUE); + if (!range.isLowUnbounded() && range.isLowInclusive()) { high = Math.nextUp(high); } return new FloatRange(low, high, step); @@ -727,7 +746,7 @@ static FloatRange of(Range range, float step) float at(long index) { - return Math.min(low + index * step, Math.nextDown(high)); + return step > 0 ? Math.min(low + index * step, Math.nextDown(high)) : Math.max(low + index * step, Math.nextDown(high)); } } @@ -740,12 +759,23 @@ static DoubleRange of(Range range) static DoubleRange of(Range range, double step) { - double low = (double) range.getLowValue().orElse(Double.MIN_VALUE); - if (!range.isLowUnbounded() && !range.isLowInclusive()) { + if (step > 0) { + double low = (double) range.getLowValue().orElse(Double.MIN_VALUE); + if (!range.isLowUnbounded() && !range.isLowInclusive()) { + low = Math.nextUp(low); + } + double high = (double) range.getHighValue().orElse(Double.MAX_VALUE); + if (!range.isHighUnbounded() && range.isHighInclusive()) { + high = Math.nextUp(high); + } + return new DoubleRange(low, high, step); + } + double low = (double) range.getHighValue().orElse(Double.MAX_VALUE); + if (!range.isHighUnbounded() && !range.isHighInclusive()) { low = Math.nextUp(low); } - double high = (double) range.getHighValue().orElse(Double.MAX_VALUE); - if (!range.isHighUnbounded() && range.isHighInclusive()) { + double high = (double) range.getLowValue().orElse(Double.MIN_VALUE); + if (!range.isLowUnbounded() && range.isLowInclusive()) { high = Math.nextUp(high); } return new DoubleRange(low, high, step); @@ -753,7 +783,7 @@ static DoubleRange of(Range range, double step) double at(long index) { - return Math.min(low + index * step, Math.nextDown(high)); + return step > 0 ? Math.min(low + index * step, Math.nextDown(high)) : Math.max(low + index * step, Math.nextDown(high)); } } @@ -768,14 +798,19 @@ static ShortDecimalRange of(Range range, int precision, long step) { long defaultMin = -999999999999999999L / POWERS_OF_TEN[18 - precision]; long defaultMax = 999999999999999999L / POWERS_OF_TEN[18 - precision]; - long low = (long) range.getLowValue().orElse(defaultMin) + (!range.isLowUnbounded() && !range.isLowInclusive() ? 1 : 0); - long high = (long) range.getHighValue().orElse(defaultMax) + (!range.isHighUnbounded() && range.isHighInclusive() ? 1 : 0); + if (step > 0) { + long low = (long) range.getLowValue().orElse(defaultMin) + (!range.isLowUnbounded() && !range.isLowInclusive() ? 1 : 0); + long high = (long) range.getHighValue().orElse(defaultMax) + (!range.isHighUnbounded() && range.isHighInclusive() ? 1 : 0); + return new ShortDecimalRange(low, high, step); + } + long low = (long) range.getHighValue().orElse(defaultMax) + (!range.isHighUnbounded() && !range.isHighInclusive() ? 1 : 0); + long high = (long) range.getLowValue().orElse(defaultMin) + (!range.isLowUnbounded() && range.isLowInclusive() ? 1 : 0); return new ShortDecimalRange(low, high, step); } long at(long index) { - return Math.min(low + index * step, high - 1); + return step > 0 ? Math.min(low + index * step, high - 1) : Math.max(low + index * step, high - 1); } } @@ -788,12 +823,23 @@ static Int128Range of(Range range) static Int128Range of(Range range, Int128 step) { - Int128 low = (Int128) range.getLowValue().orElse(Decimals.MIN_UNSCALED_DECIMAL); - Int128 high = (Int128) range.getHighValue().orElse(Decimals.MAX_UNSCALED_DECIMAL); - if (!range.isLowUnbounded() && !range.isLowInclusive()) { + if (!step.isNegative()) { + Int128 low = (Int128) range.getLowValue().orElse(Decimals.MIN_UNSCALED_DECIMAL); + Int128 high = (Int128) range.getHighValue().orElse(Decimals.MAX_UNSCALED_DECIMAL); + if (!range.isLowUnbounded() && !range.isLowInclusive()) { + low = Int128Math.add(low, Int128.ONE); + } + if (!range.isHighUnbounded() && range.isHighInclusive()) { + high = Int128Math.add(high, Int128.ONE); + } + return new Int128Range(low, high, step); + } + Int128 low = (Int128) range.getHighValue().orElse(Decimals.MAX_UNSCALED_DECIMAL); + Int128 high = (Int128) range.getLowValue().orElse(Decimals.MIN_UNSCALED_DECIMAL); + if (!range.isHighUnbounded() && !range.isHighInclusive()) { low = Int128Math.add(low, Int128.ONE); } - if (!range.isHighUnbounded() && range.isHighInclusive()) { + if (!range.isLowUnbounded() && range.isLowInclusive()) { high = Int128Math.add(high, Int128.ONE); } return new Int128Range(low, high, step); @@ -803,6 +849,9 @@ Int128 at(long index) { Int128 nextValue = Int128Math.add(low, Int128Math.multiply(Int128.valueOf(index), step)); Int128 highInclusive = Int128Math.subtract(high, Int128.ONE); + if (step.isNegative()) { + return highInclusive.compareTo(nextValue) >= 0 ? highInclusive : nextValue; + } return highInclusive.compareTo(nextValue) < 0 ? highInclusive : nextValue; } } @@ -818,22 +867,40 @@ static LongTimestampRange of(Range range, int precision, long step) { LongTimestamp low = (LongTimestamp) range.getLowValue().orElse(new LongTimestamp(Long.MIN_VALUE, 0)); LongTimestamp high = (LongTimestamp) range.getHighValue().orElse(new LongTimestamp(Long.MAX_VALUE, PICOSECONDS_PER_MICROSECOND - 1)); - int factor; + if (step > 0) { + if (precision <= 6) { + int factor = (int) POWERS_OF_TEN[6 - precision]; + low = new LongTimestamp(roundDiv(low.getEpochMicros(), factor) + (!range.isLowUnbounded() && !range.isLowInclusive() ? 1 : 0), 0); + high = new LongTimestamp(roundDiv(high.getEpochMicros(), factor) + (!range.isHighUnbounded() && range.isHighInclusive() ? 1 : 0), 0); + return new LongTimestampRange(low, high, factor, step); + } + int factor = (int) POWERS_OF_TEN[12 - precision]; + int lowPicosOfMicro = roundDiv(low.getPicosOfMicro(), factor) + (!range.isLowUnbounded() && !range.isLowInclusive() ? 1 : 0); + low = new LongTimestamp( + low.getEpochMicros() - (lowPicosOfMicro < 0 ? 1 : 0), + (lowPicosOfMicro + factor) % factor); + int highPicosOfMicro = roundDiv(high.getPicosOfMicro(), factor) + (!range.isHighUnbounded() && range.isHighInclusive() ? 1 : 0); + high = new LongTimestamp( + high.getEpochMicros() + (highPicosOfMicro > factor ? 1 : 0), + highPicosOfMicro % factor); + return new LongTimestampRange(low, high, factor, step); + } if (precision <= 6) { - factor = (int) POWERS_OF_TEN[6 - precision]; - low = new LongTimestamp(roundDiv(low.getEpochMicros(), factor) + (!range.isLowUnbounded() && !range.isLowInclusive() ? 1 : 0), 0); - high = new LongTimestamp(roundDiv(high.getEpochMicros(), factor) + (!range.isHighUnbounded() && range.isHighInclusive() ? 1 : 0), 0); + int factor = (int) POWERS_OF_TEN[6 - precision]; + low = new LongTimestamp(roundDiv(high.getEpochMicros(), factor) + (!range.isHighUnbounded() && !range.isHighInclusive() ? 1 : 0), 0); + high = new LongTimestamp(roundDiv(low.getEpochMicros(), factor) + (!range.isLowUnbounded() && range.isLowInclusive() ? 1 : 0), 0); return new LongTimestampRange(low, high, factor, step); } - factor = (int) POWERS_OF_TEN[12 - precision]; - int lowPicosOfMicro = roundDiv(low.getPicosOfMicro(), factor) + (!range.isLowUnbounded() && !range.isLowInclusive() ? 1 : 0); + int factor = (int) POWERS_OF_TEN[12 - precision]; + LongTimestamp oldLow = low; + int lowPicosOfMicro = roundDiv(high.getPicosOfMicro(), factor) + (!range.isHighUnbounded() && !range.isHighInclusive() ? 1 : 0); low = new LongTimestamp( - low.getEpochMicros() - (lowPicosOfMicro < 0 ? 1 : 0), - (lowPicosOfMicro + factor) % factor); - int highPicosOfMicro = roundDiv(high.getPicosOfMicro(), factor) + (!range.isHighUnbounded() && range.isHighInclusive() ? 1 : 0); + high.getEpochMicros() + (lowPicosOfMicro > factor ? 1 : 0), + lowPicosOfMicro % factor); + int highPicosOfMicro = roundDiv(oldLow.getPicosOfMicro(), factor) + (!range.isLowUnbounded() && range.isLowInclusive() ? 1 : 0); high = new LongTimestamp( - high.getEpochMicros() + (highPicosOfMicro > factor ? 1 : 0), - highPicosOfMicro % factor); + oldLow.getEpochMicros() - (highPicosOfMicro < 0 ? 1 : 0), + (highPicosOfMicro + factor) % factor); return new LongTimestampRange(low, high, factor, step); } @@ -861,8 +928,13 @@ static ShortTimestampWithTimeZoneRange of(Range range, int precision, long step) .map(v -> unpackZoneKey((long) v)) .orElse(TimeZoneKey.UTC_KEY)); long factor = POWERS_OF_TEN[3 - precision]; - long low = roundDiv(unpackMillisUtc((long) range.getLowValue().orElse(Long.MIN_VALUE)), factor) + (!range.isLowUnbounded() && !range.isLowInclusive() ? 1 : 0); - long high = roundDiv(unpackMillisUtc((long) range.getHighValue().orElse(Long.MAX_VALUE)), factor) + (!range.isHighUnbounded() && range.isHighInclusive() ? 1 : 0); + if (step > 0) { + long low = roundDiv(unpackMillisUtc((long) range.getLowValue().orElse(Long.MIN_VALUE)), factor) + (!range.isLowUnbounded() && !range.isLowInclusive() ? 1 : 0); + long high = roundDiv(unpackMillisUtc((long) range.getHighValue().orElse(Long.MAX_VALUE)), factor) + (!range.isHighUnbounded() && range.isHighInclusive() ? 1 : 0); + return new ShortTimestampWithTimeZoneRange(low, high, factor, defaultTZ, step); + } + long low = roundDiv(unpackMillisUtc((long) range.getHighValue().orElse(Long.MAX_VALUE)), factor) + (!range.isHighUnbounded() && !range.isHighInclusive() ? 1 : 0); + long high = roundDiv(unpackMillisUtc((long) range.getLowValue().orElse(Long.MIN_VALUE)), factor) + (!range.isLowUnbounded() && range.isLowInclusive() ? 1 : 0); return new ShortTimestampWithTimeZoneRange(low, high, factor, defaultTZ, step); } @@ -894,16 +966,30 @@ static LongTimestampWithTimeZoneRange of(Range range, int precision, long step) throw new TrinoException(INVALID_ROW_FILTER, "Range boundaries for timestamp with time zone columns must have the same time zone"); } int factor = (int) POWERS_OF_TEN[12 - precision]; - int lowPicosOfMilli = roundDiv(low.getPicosOfMilli(), factor) + (!range.isLowUnbounded() && !range.isLowInclusive() ? 1 : 0); + if (step > 0) { + int lowPicosOfMilli = roundDiv(low.getPicosOfMilli(), factor) + (!range.isLowUnbounded() && !range.isLowInclusive() ? 1 : 0); + low = fromEpochMillisAndFraction( + low.getEpochMillis() - (lowPicosOfMilli < 0 ? 1 : 0), + (lowPicosOfMilli + factor) % factor, + defaultTZ); + int highPicosOfMilli = roundDiv(high.getPicosOfMilli(), factor) + (!range.isHighUnbounded() && range.isHighInclusive() ? 1 : 0); + high = fromEpochMillisAndFraction( + high.getEpochMillis() + (highPicosOfMilli > factor ? 1 : 0), + highPicosOfMilli % factor, + defaultTZ); + return new LongTimestampWithTimeZoneRange(low, high, factor, defaultTZ, step); + } + LongTimestampWithTimeZone oldLow = low; + int lowPicosOfMilli = roundDiv(high.getPicosOfMilli(), factor) + (!range.isHighUnbounded() && !range.isHighInclusive() ? 1 : 0); low = fromEpochMillisAndFraction( - low.getEpochMillis() - (lowPicosOfMilli < 0 ? 1 : 0), - (lowPicosOfMilli + factor) % factor, - low.getTimeZoneKey()); - int highPicosOfMilli = roundDiv(high.getPicosOfMilli(), factor) + (!range.isHighUnbounded() && range.isHighInclusive() ? 1 : 0); + high.getEpochMillis() + (lowPicosOfMilli > factor ? 1 : 0), + lowPicosOfMilli % factor, + defaultTZ); + int highPicosOfMilli = roundDiv(oldLow.getPicosOfMilli(), factor) + (!range.isLowUnbounded() && range.isLowInclusive() ? 1 : 0); high = fromEpochMillisAndFraction( - high.getEpochMillis() + (highPicosOfMilli > factor ? 1 : 0), - highPicosOfMilli % factor, - high.getTimeZoneKey()); + oldLow.getEpochMillis() - (highPicosOfMilli < 0 ? 1 : 0), + (highPicosOfMilli + factor) % factor, + defaultTZ); return new LongTimestampWithTimeZoneRange(low, high, factor, defaultTZ, step); } @@ -931,8 +1017,13 @@ static ShortTimeWithTimeZoneRange of(Range range, int precision, long step) .map(v -> unpackOffsetMinutes((long) v)) .orElse(0)); long factor = POWERS_OF_TEN[9 - precision]; - long low = roundDiv(range.getLowValue().map(v -> unpackTimeNanos((long) v)).orElse(0L), factor) + (!range.isLowUnbounded() && !range.isLowInclusive() ? 1 : 0); - long high = roundDiv(range.getHighValue().map(v -> unpackTimeNanos((long) v)).orElse(NANOSECONDS_PER_DAY), factor) + (!range.isHighUnbounded() && range.isHighInclusive() ? 1 : 0); + if (step > 0) { + long low = roundDiv(range.getLowValue().map(v -> unpackTimeNanos((long) v)).orElse(0L), factor) + (!range.isLowUnbounded() && !range.isLowInclusive() ? 1 : 0); + long high = roundDiv(range.getHighValue().map(v -> unpackTimeNanos((long) v)).orElse(NANOSECONDS_PER_DAY), factor) + (!range.isHighUnbounded() && range.isHighInclusive() ? 1 : 0); + return new ShortTimeWithTimeZoneRange(low, high, factor, offsetMinutes, step); + } + long low = roundDiv(range.getHighValue().map(v -> unpackTimeNanos((long) v)).orElse(NANOSECONDS_PER_DAY), factor) + (!range.isHighUnbounded() && !range.isHighInclusive() ? 1 : 0); + long high = roundDiv(range.getLowValue().map(v -> unpackTimeNanos((long) v)).orElse(0L), factor) + (!range.isLowUnbounded() && range.isLowInclusive() ? 1 : 0); return new ShortTimeWithTimeZoneRange(low, high, factor, offsetMinutes, step); } @@ -963,8 +1054,13 @@ static LongTimeWithTimeZoneRange of(Range range, int precision, long step) throw new TrinoException(INVALID_ROW_FILTER, "Range boundaries for time with time zone columns must have the same time zone"); } int factor = (int) POWERS_OF_TEN[12 - precision]; - long longLow = roundDiv(low.getPicoseconds(), factor) + (!range.isLowUnbounded() && !range.isLowInclusive() ? 1 : 0); - long longHigh = roundDiv(high.getPicoseconds(), factor) + (!range.isHighUnbounded() && range.isHighInclusive() ? 1 : 0); + if (step > 0) { + long longLow = roundDiv(low.getPicoseconds(), factor) + (!range.isLowUnbounded() && !range.isLowInclusive() ? 1 : 0); + long longHigh = roundDiv(high.getPicoseconds(), factor) + (!range.isHighUnbounded() && range.isHighInclusive() ? 1 : 0); + return new LongTimeWithTimeZoneRange(longLow, longHigh, factor, offsetMinutes, step); + } + long longLow = roundDiv(high.getPicoseconds(), factor) + (!range.isHighUnbounded() && !range.isHighInclusive() ? 1 : 0); + long longHigh = roundDiv(low.getPicoseconds(), factor) + (!range.isLowUnbounded() && range.isLowInclusive() ? 1 : 0); return new LongTimeWithTimeZoneRange(longLow, longHigh, factor, offsetMinutes, step); } diff --git a/plugin/trino-faker/src/test/java/io/trino/plugin/faker/TestFakerQueries.java b/plugin/trino-faker/src/test/java/io/trino/plugin/faker/TestFakerQueries.java index 526cf2ce070..779f7328546 100644 --- a/plugin/trino-faker/src/test/java/io/trino/plugin/faker/TestFakerQueries.java +++ b/plugin/trino-faker/src/test/java/io/trino/plugin/faker/TestFakerQueries.java @@ -388,39 +388,39 @@ void testSelectValuesProperty() @Test void testSelectStepProperties() { - // small step in small ranges that produce only 10 unique values for 1000 rows + // small positive step in small ranges that produce only 4 unique values for 1000 rows List testCases = ImmutableList.builder() - .add(new TestDataType("rnd_bigint", "bigint", Map.of("min", "0", "max", "9", "step", "1"), "count(distinct rnd_bigint)", "10")) - .add(new TestDataType("rnd_integer", "integer", Map.of("min", "0", "max", "9", "step", "1"), "count(distinct rnd_integer)", "10")) - .add(new TestDataType("rnd_smallint", "smallint", Map.of("min", "0", "max", "9", "step", "1"), "count(distinct rnd_smallint)", "10")) - .add(new TestDataType("rnd_tinyint", "tinyint", Map.of("min", "0", "max", "9", "step", "1"), "count(distinct rnd_tinyint)", "10")) - .add(new TestDataType("rnd_date", "date", Map.of("min", "2022-03-01", "max", "2022-03-10", "step", "1d"), "count(distinct rnd_date)", "10")) - .add(new TestDataType("rnd_decimal1", "decimal", Map.of("min", "0", "max", "9", "step", "1"), "count(distinct rnd_decimal1)", "10")) - .add(new TestDataType("rnd_decimal2", "decimal(18,5)", Map.of("min", "0.00000", "max", "0.00009", "step", "0.00001"), "count(distinct rnd_decimal2)", "10")) - .add(new TestDataType("rnd_decimal3", "decimal(38,0)", Map.of("min", "0", "max", "9", "step", "1"), "count(distinct rnd_decimal3)", "10")) - .add(new TestDataType("rnd_decimal4", "decimal(38,38)", Map.of("min", "0.00000000000000000000000000000000000000", "max", "0.00000000000000000000000000000000000009", "step", "0.00000000000000000000000000000000000001"), "count(distinct rnd_decimal4)", "10")) - .add(new TestDataType("rnd_decimal5", "decimal(5,2)", Map.of("min", "0.00", "max", "1.09", "step", "0.01"), "count(distinct rnd_decimal5)", "110")) - .add(new TestDataType("rnd_real", "real", Map.of("min", "0.0", "max", "1.3E-44", "step", "1.4E-45"), "count(distinct rnd_real)", "10")) - .add(new TestDataType("rnd_double", "double", Map.of("min", "0.0", "max", "4.4E-323", "step", "4.9E-324"), "count(distinct rnd_double)", "10")) - .add(new TestDataType("rnd_interval1", "interval day to second", Map.of("min", "0.000", "max", "0.009", "step", "0.001"), "count(distinct rnd_interval1)", "10")) - .add(new TestDataType("rnd_interval2", "interval year to month", Map.of("min", "0", "max", "9", "step", "1"), "count(distinct rnd_interval2)", "10")) - .add(new TestDataType("rnd_timestamp", "timestamp", Map.of("min", "2022-03-21 00:00:00.000", "max", "2022-03-21 00:00:00.009", "step", "1ms"), "count(distinct rnd_timestamp)", "10")) - .add(new TestDataType("rnd_timestamp0", "timestamp(0)", Map.of("min", "2022-03-21 00:00:00", "max", "2022-03-21 00:00:09", "step", "1s"), "count(distinct rnd_timestamp0)", "10")) - .add(new TestDataType("rnd_timestamp6", "timestamp(6)", Map.of("min", "2022-03-21 00:00:00.000000", "max", "2022-03-21 00:00:00.000009", "step", "1us"), "count(distinct rnd_timestamp6)", "10")) - .add(new TestDataType("rnd_timestamp9", "timestamp(9)", Map.of("min", "2022-03-21 00:00:00.000000000", "max", "2022-03-21 00:00:00.000009000", "step", "1us"), "count(distinct rnd_timestamp9)", "10")) - .add(new TestDataType("rnd_timestamptz", "timestamp with time zone", Map.of("min", "2022-03-21 00:00:00.000 +01:00", "max", "2022-03-21 00:00:00.009 +01:00", "step", "1ms"), "count(distinct rnd_timestamptz)", "10")) - .add(new TestDataType("rnd_timestamptz0", "timestamp(0) with time zone", Map.of("min", "2022-03-21 00:00:00 +01:00", "max", "2022-03-21 00:00:09 +01:00", "step", "1s"), "count(distinct rnd_timestamptz0)", "10")) - .add(new TestDataType("rnd_timestamptz6", "timestamp(6) with time zone", Map.of("min", "2022-03-21 00:00:00.000000 +01:00", "max", "2022-03-21 00:00:00.009000 +01:00", "step", "1ms"), "count(distinct rnd_timestamptz6)", "10")) - .add(new TestDataType("rnd_timestamptz9", "timestamp(9) with time zone", Map.of("min", "2022-03-21 00:00:00.000000000 +01:00", "max", "2022-03-21 00:00:00.009000000 +01:00", "step", "1ms"), "count(distinct rnd_timestamptz9)", "10")) - .add(new TestDataType("rnd_time", "time", Map.of("min", "01:02:03.456", "max", "01:02:03.465", "step", "1ms"), "count(distinct rnd_time)", "10")) - .add(new TestDataType("rnd_time0", "time(0)", Map.of("min", "01:02:03", "max", "01:02:12", "step", "1s"), "count(distinct rnd_time0)", "10")) - .add(new TestDataType("rnd_time6", "time(6)", Map.of("min", "01:02:03.000456", "max", "01:02:03.000465", "step", "1us"), "count(distinct rnd_time6)", "10")) - .add(new TestDataType("rnd_time9", "time(9)", Map.of("min", "01:02:03.000000456", "max", "01:02:03.000000465", "step", "1ns"), "count(distinct rnd_time9)", "10")) - .add(new TestDataType("rnd_timetz", "time with time zone", Map.of("min", "01:02:03.456 +01:00", "max", "01:02:03.465 +01:00", "step", "1ms"), "count(distinct rnd_timetz)", "10")) - .add(new TestDataType("rnd_timetz0", "time(0) with time zone", Map.of("min", "01:02:03 +01:00", "max", "01:02:12 +01:00", "step", "1s"), "count(distinct rnd_timetz0)", "10")) - .add(new TestDataType("rnd_timetz6", "time(6) with time zone", Map.of("min", "01:02:03.000456 +01:00", "max", "01:02:03.000465 +01:00", "step", "1us"), "count(distinct rnd_timetz6)", "10")) - .add(new TestDataType("rnd_timetz9", "time(9) with time zone", Map.of("min", "01:02:03.000000456 +01:00", "max", "01:02:03.000000465 +01:00", "step", "1ns"), "count(distinct rnd_timetz9)", "10")) - .add(new TestDataType("rnd_timetz12", "time(12) with time zone", Map.of("min", "01:02:03.000000000456 +01:00", "max", "01:02:03.000000009456 +01:00", "step", "1ns"), "count(distinct rnd_timetz12)", "10")) + .add(new TestDataType("pos_bigint", "bigint", Map.of("min", "0", "max", "9", "step", "4"), "count(distinct pos_bigint)", "4")) + .add(new TestDataType("pos_integer", "integer", Map.of("min", "0", "max", "9", "step", "4"), "count(distinct pos_integer)", "4")) + .add(new TestDataType("pos_smallint", "smallint", Map.of("min", "0", "max", "9", "step", "4"), "count(distinct pos_smallint)", "4")) + .add(new TestDataType("pos_tinyint", "tinyint", Map.of("min", "0", "max", "9", "step", "4"), "count(distinct pos_tinyint)", "4")) + .add(new TestDataType("pos_date", "date", Map.of("min", "2022-03-01", "max", "2022-03-10", "step", "4d"), "count(distinct pos_date)", "4")) + .add(new TestDataType("pos_decimal1", "decimal", Map.of("min", "0", "max", "9", "step", "4"), "count(distinct pos_decimal1)", "4")) + .add(new TestDataType("pos_decimal2", "decimal(18,5)", Map.of("min", "0.00000", "max", "0.00009", "step", "0.00004"), "count(distinct pos_decimal2)", "4")) + .add(new TestDataType("pos_decimal3", "decimal(38,0)", Map.of("min", "0", "max", "9", "step", "4"), "count(distinct pos_decimal3)", "4")) + .add(new TestDataType("pos_decimal4", "decimal(38,38)", Map.of("min", "0.00000000000000000000000000000000000000", "max", "0.00000000000000000000000000000000000009", "step", "0.00000000000000000000000000000000000004"), "count(distinct pos_decimal4)", "4")) + .add(new TestDataType("pos_decimal5", "decimal(5,2)", Map.of("min", "0.00", "max", "0.09", "step", "0.04"), "count(distinct pos_decimal5)", "4")) + .add(new TestDataType("pos_real", "real", Map.of("min", "0.0", "max", "1.3E-44", "step", "5.6E-45"), "count(distinct pos_real)", "4")) + .add(new TestDataType("pos_double", "double", Map.of("min", "0.0", "max", "4.4E-323", "step", "2.0E-323"), "count(distinct pos_double)", "4")) + .add(new TestDataType("pos_interval1", "interval day to second", Map.of("min", "0.000", "max", "0.009", "step", "0.004"), "count(distinct pos_interval1)", "4")) + .add(new TestDataType("pos_interval2", "interval year to month", Map.of("min", "0", "max", "9", "step", "4"), "count(distinct pos_interval2)", "4")) + .add(new TestDataType("pos_timestamp", "timestamp", Map.of("min", "2022-03-21 00:00:00.000", "max", "2022-03-21 00:00:00.009", "step", "4ms"), "count(distinct pos_timestamp)", "4")) + .add(new TestDataType("pos_timestamp0", "timestamp(0)", Map.of("min", "2022-03-21 00:00:00", "max", "2022-03-21 00:00:09", "step", "4s"), "count(distinct pos_timestamp0)", "4")) + .add(new TestDataType("pos_timestamp6", "timestamp(6)", Map.of("min", "2022-03-21 00:00:00.000000", "max", "2022-03-21 00:00:00.000009", "step", "4us"), "count(distinct pos_timestamp6)", "4")) + .add(new TestDataType("pos_timestamp9", "timestamp(9)", Map.of("min", "2022-03-21 00:00:00.000000000", "max", "2022-03-21 00:00:00.000009000", "step", "4us"), "count(distinct pos_timestamp9)", "4")) + .add(new TestDataType("pos_timestamptz", "timestamp with time zone", Map.of("min", "2022-03-21 00:00:00.000 +01:00", "max", "2022-03-21 00:00:00.009 +01:00", "step", "4ms"), "count(distinct pos_timestamptz)", "4")) + .add(new TestDataType("pos_timestamptz0", "timestamp(0) with time zone", Map.of("min", "2022-03-21 00:00:00 +01:00", "max", "2022-03-21 00:00:09 +01:00", "step", "4s"), "count(distinct pos_timestamptz0)", "4")) + .add(new TestDataType("pos_timestamptz6", "timestamp(6) with time zone", Map.of("min", "2022-03-21 00:00:00.000000 +01:00", "max", "2022-03-21 00:00:00.009000 +01:00", "step", "4ms"), "count(distinct pos_timestamptz6)", "4")) + .add(new TestDataType("pos_timestamptz9", "timestamp(9) with time zone", Map.of("min", "2022-03-21 00:00:00.000000000 +01:00", "max", "2022-03-21 00:00:00.009000000 +01:00", "step", "4ms"), "count(distinct pos_timestamptz9)", "4")) + .add(new TestDataType("pos_time", "time", Map.of("min", "01:02:03.456", "max", "01:02:03.465", "step", "4ms"), "count(distinct pos_time)", "4")) + .add(new TestDataType("pos_time0", "time(0)", Map.of("min", "01:02:03", "max", "01:02:12", "step", "4s"), "count(distinct pos_time0)", "4")) + .add(new TestDataType("pos_time6", "time(6)", Map.of("min", "01:02:03.000456", "max", "01:02:03.000465", "step", "4us"), "count(distinct pos_time6)", "4")) + .add(new TestDataType("pos_time9", "time(9)", Map.of("min", "01:02:03.000000456", "max", "01:02:03.000000465", "step", "4ns"), "count(distinct pos_time9)", "4")) + .add(new TestDataType("pos_timetz", "time with time zone", Map.of("min", "01:02:03.456 +01:00", "max", "01:02:03.465 +01:00", "step", "4ms"), "count(distinct pos_timetz)", "4")) + .add(new TestDataType("pos_timetz0", "time(0) with time zone", Map.of("min", "01:02:03 +01:00", "max", "01:02:12 +01:00", "step", "4s"), "count(distinct pos_timetz0)", "4")) + .add(new TestDataType("pos_timetz6", "time(6) with time zone", Map.of("min", "01:02:03.000456 +01:00", "max", "01:02:03.000465 +01:00", "step", "4us"), "count(distinct pos_timetz6)", "4")) + .add(new TestDataType("pos_timetz9", "time(9) with time zone", Map.of("min", "01:02:03.000000456 +01:00", "max", "01:02:03.000000465 +01:00", "step", "4ns"), "count(distinct pos_timetz9)", "4")) + .add(new TestDataType("pos_timetz12", "time(12) with time zone", Map.of("min", "01:02:03.000000000456 +01:00", "max", "01:02:03.000000009456 +01:00", "step", "4ns"), "count(distinct pos_timetz12)", "4")) .build(); for (TestDataType testCase : testCases) { @@ -428,6 +428,58 @@ void testSelectStepProperties() assertQuery("SELECT %s FROM %s".formatted(testCase.queryExpression(), table.getName()), "VALUES (%s)".formatted(testCase.expectedValue())); } } + + // small negative step in small ranges that produce only 4 unique values for 1000 rows + testCases = ImmutableList.builder() + .add(new TestDataType("neg_bigint", "bigint", Map.of("min", "0", "max", "9", "step", "-4"), "count(distinct neg_bigint)", "4")) + .add(new TestDataType("neg_integer", "integer", Map.of("min", "0", "max", "9", "step", "-4"), "count(distinct neg_integer)", "4")) + .add(new TestDataType("neg_smallint", "smallint", Map.of("min", "0", "max", "9", "step", "-4"), "count(distinct neg_smallint)", "4")) + .add(new TestDataType("neg_tinyint", "tinyint", Map.of("min", "0", "max", "9", "step", "-4"), "count(distinct neg_tinyint)", "4")) + .add(new TestDataType("neg_date", "date", Map.of("min", "2022-03-01", "max", "2022-03-10", "step", "-4d"), "count(distinct neg_date)", "4")) + .add(new TestDataType("neg_decimal1", "decimal", Map.of("min", "0", "max", "9", "step", "-4"), "count(distinct neg_decimal1)", "4")) + .add(new TestDataType("neg_decimal2", "decimal(18,5)", Map.of("min", "0.00000", "max", "0.00009", "step", "-0.00004"), "count(distinct neg_decimal2)", "4")) + .add(new TestDataType("neg_decimal3", "decimal(38,0)", Map.of("min", "0", "max", "9", "step", "-4"), "count(distinct neg_decimal3)", "4")) + .add(new TestDataType("neg_decimal4", "decimal(38,38)", Map.of("min", "0.00000000000000000000000000000000000000", "max", "0.00000000000000000000000000000000000009", "step", "-0.00000000000000000000000000000000000004"), "count(distinct neg_decimal4)", "4")) + .add(new TestDataType("neg_decimal5", "decimal(5,2)", Map.of("min", "0.00", "max", "0.09", "step", "-0.04"), "count(distinct neg_decimal5)", "4")) + .add(new TestDataType("neg_real", "real", Map.of("min", "0.0", "max", "1.3E-44", "step", "-5.6E-45"), "count(distinct neg_real)", "4")) + .add(new TestDataType("neg_double", "double", Map.of("min", "0.0", "max", "4.4E-323", "step", "-2.0E-323"), "count(distinct neg_double)", "4")) + .add(new TestDataType("neg_interval1", "interval day to second", Map.of("min", "0.000", "max", "0.009", "step", "-0.004"), "count(distinct neg_interval1)", "4")) + .add(new TestDataType("neg_interval2", "interval year to month", Map.of("min", "0", "max", "9", "step", "-4"), "count(distinct neg_interval2)", "4")) + .add(new TestDataType("neg_timestamp", "timestamp", Map.of("min", "2022-03-21 00:00:00.000", "max", "2022-03-21 00:00:00.009", "step", "-4ms"), "count(distinct neg_timestamp)", "4")) + .add(new TestDataType("neg_timestamp0", "timestamp(0)", Map.of("min", "2022-03-21 00:00:00", "max", "2022-03-21 00:00:09", "step", "-4s"), "count(distinct neg_timestamp0)", "4")) + .add(new TestDataType("neg_timestamp6", "timestamp(6)", Map.of("min", "2022-03-21 00:00:00.000000", "max", "2022-03-21 00:00:00.000009", "step", "-4us"), "count(distinct neg_timestamp6)", "4")) + .add(new TestDataType("neg_timestamp9", "timestamp(9)", Map.of("min", "2022-03-21 00:00:00.000000000", "max", "2022-03-21 00:00:00.000009000", "step", "-4us"), "count(distinct neg_timestamp9)", "4")) + .add(new TestDataType("neg_timestamptz", "timestamp with time zone", Map.of("min", "2022-03-21 00:00:00.000 +01:00", "max", "2022-03-21 00:00:00.009 +01:00", "step", "-4ms"), "count(distinct neg_timestamptz)", "4")) + .add(new TestDataType("neg_timestamptz0", "timestamp(0) with time zone", Map.of("min", "2022-03-21 00:00:00 +01:00", "max", "2022-03-21 00:00:09 +01:00", "step", "-4s"), "count(distinct neg_timestamptz0)", "4")) + .add(new TestDataType("neg_timestamptz6", "timestamp(6) with time zone", Map.of("min", "2022-03-21 00:00:00.000000 +01:00", "max", "2022-03-21 00:00:00.009000 +01:00", "step", "-4ms"), "count(distinct neg_timestamptz6)", "4")) + .add(new TestDataType("neg_timestamptz9", "timestamp(9) with time zone", Map.of("min", "2022-03-21 00:00:00.000000000 +01:00", "max", "2022-03-21 00:00:00.009000000 +01:00", "step", "-4ms"), "count(distinct neg_timestamptz9)", "4")) + .add(new TestDataType("neg_time", "time", Map.of("min", "01:02:03.456", "max", "01:02:03.465", "step", "-4ms"), "count(distinct neg_time)", "4")) + .add(new TestDataType("neg_time0", "time(0)", Map.of("min", "01:02:03", "max", "01:02:12", "step", "-4s"), "count(distinct neg_time0)", "4")) + .add(new TestDataType("neg_time6", "time(6)", Map.of("min", "01:02:03.000456", "max", "01:02:03.000465", "step", "-4us"), "count(distinct neg_time6)", "4")) + .add(new TestDataType("neg_time9", "time(9)", Map.of("min", "01:02:03.000000456", "max", "01:02:03.000000465", "step", "-4ns"), "count(distinct neg_time9)", "4")) + .add(new TestDataType("neg_timetz", "time with time zone", Map.of("min", "01:02:03.456 +01:00", "max", "01:02:03.465 +01:00", "step", "-4ms"), "count(distinct neg_timetz)", "4")) + .add(new TestDataType("neg_timetz0", "time(0) with time zone", Map.of("min", "01:02:03 +01:00", "max", "01:02:12 +01:00", "step", "-4s"), "count(distinct neg_timetz0)", "4")) + .add(new TestDataType("neg_timetz6", "time(6) with time zone", Map.of("min", "01:02:03.000456 +01:00", "max", "01:02:03.000465 +01:00", "step", "-4us"), "count(distinct neg_timetz6)", "4")) + .add(new TestDataType("neg_timetz9", "time(9) with time zone", Map.of("min", "01:02:03.000000456 +01:00", "max", "01:02:03.000000465 +01:00", "step", "-4ns"), "count(distinct neg_timetz9)", "4")) + .add(new TestDataType("neg_timetz12", "time(12) with time zone", Map.of("min", "01:02:03.000000000456 +01:00", "max", "01:02:03.000000009456 +01:00", "step", "-4ns"), "count(distinct neg_timetz12)", "4")) + .build(); + + for (TestDataType testCase : testCases) { + try (TestTable table = new TestTable(getQueryRunner()::execute, "step_small_" + testCase.name(), "(%s)".formatted(testCase.columnSchema()))) { + assertQuery("SELECT %s FROM %s".formatted(testCase.queryExpression(), table.getName()), "VALUES (%s)".formatted(testCase.expectedValue())); + } + } + + // step smaller than the precision + testCases = ImmutableList.builder() + .add(new TestDataType("seq_date", "date", Map.of("min", "2022-03-01", "max", "2022-03-13", "step", "10m"), "count(distinct seq_date)", "8")) + .build(); + + for (TestDataType testCase : testCases) { + try (TestTable table = new TestTable(getQueryRunner()::execute, "step_large_" + testCase.name(), "(%s)".formatted(testCase.columnSchema()))) { + assertQuery("SELECT %s FROM %s".formatted(testCase.queryExpression(), table.getName()), "VALUES (%s)".formatted(testCase.expectedValue())); + } + } } private record TestDataType(String name, String type, Map properties, String queryExpression, String expectedValue)