diff --git a/x-pack/plugin/ql/src/main/java/org/elasticsearch/xpack/ql/util/DateUtils.java b/x-pack/plugin/ql/src/main/java/org/elasticsearch/xpack/ql/util/DateUtils.java index 934c6c8ca9208..583d8de43f32d 100644 --- a/x-pack/plugin/ql/src/main/java/org/elasticsearch/xpack/ql/util/DateUtils.java +++ b/x-pack/plugin/ql/src/main/java/org/elasticsearch/xpack/ql/util/DateUtils.java @@ -26,7 +26,7 @@ import static java.time.temporal.ChronoField.SECOND_OF_MINUTE; -//FIXME: Taken from sql-proto. +//FIXME: Taken from sql-proto (StringUtils) //Ideally it should be shared but the dependencies across projects and and SQL-client make it tricky. // Maybe a gradle task would fix that... public class DateUtils { @@ -136,8 +136,14 @@ public static String toString(Object value) { sb.append(":"); durationInSec = durationInSec % SECONDS_PER_MINUTE; sb.append(indent(durationInSec)); - sb.append("."); - sb.append(TimeUnit.NANOSECONDS.toMillis(d.getNano())); + long millis = TimeUnit.NANOSECONDS.toMillis(d.getNano()); + if (millis > 0) { + sb.append("."); + while (millis % 10 == 0) { + millis /= 10; + } + sb.append(millis); + } return sb.toString(); } diff --git a/x-pack/plugin/sql/qa/src/main/java/org/elasticsearch/xpack/sql/qa/SqlProtocolTestCase.java b/x-pack/plugin/sql/qa/src/main/java/org/elasticsearch/xpack/sql/qa/SqlProtocolTestCase.java index 871edad817799..9f4102532d8c7 100644 --- a/x-pack/plugin/sql/qa/src/main/java/org/elasticsearch/xpack/sql/qa/SqlProtocolTestCase.java +++ b/x-pack/plugin/sql/qa/src/main/java/org/elasticsearch/xpack/sql/qa/SqlProtocolTestCase.java @@ -95,22 +95,22 @@ public void testIPs() throws IOException { public void testDateTimeIntervals() throws IOException { assertQuery("SELECT INTERVAL '326' YEAR", "INTERVAL '326' YEAR", "interval_year", "P326Y", "+326-0", 7); assertQuery("SELECT INTERVAL '50' MONTH", "INTERVAL '50' MONTH", "interval_month", "P50M", "+0-50", 7); - assertQuery("SELECT INTERVAL '520' DAY", "INTERVAL '520' DAY", "interval_day", "PT12480H", "+520 00:00:00.0", 23); - assertQuery("SELECT INTERVAL '163' HOUR", "INTERVAL '163' HOUR", "interval_hour", "PT163H", "+6 19:00:00.0", 23); - assertQuery("SELECT INTERVAL '163' MINUTE", "INTERVAL '163' MINUTE", "interval_minute", "PT2H43M", "+0 02:43:00.0", 23); - assertQuery("SELECT INTERVAL '223.16' SECOND", "INTERVAL '223.16' SECOND", "interval_second", "PT3M43.016S", "+0 00:03:43.16", 23); + assertQuery("SELECT INTERVAL '520' DAY", "INTERVAL '520' DAY", "interval_day", "PT12480H", "+520 00:00:00", 23); + assertQuery("SELECT INTERVAL '163' HOUR", "INTERVAL '163' HOUR", "interval_hour", "PT163H", "+6 19:00:00", 23); + assertQuery("SELECT INTERVAL '163' MINUTE", "INTERVAL '163' MINUTE", "interval_minute", "PT2H43M", "+0 02:43:00", 23); + assertQuery("SELECT INTERVAL '223.16' SECOND", "INTERVAL '223.16' SECOND", "interval_second", "PT3M43.16S", "+0 00:03:43.16", 23); assertQuery("SELECT INTERVAL '163-11' YEAR TO MONTH", "INTERVAL '163-11' YEAR TO MONTH", "interval_year_to_month", "P163Y11M", "+163-11", 7); assertQuery("SELECT INTERVAL '163 12' DAY TO HOUR", "INTERVAL '163 12' DAY TO HOUR", "interval_day_to_hour", "PT3924H", - "+163 12:00:00.0", 23); + "+163 12:00:00", 23); assertQuery("SELECT INTERVAL '163 12:39' DAY TO MINUTE", "INTERVAL '163 12:39' DAY TO MINUTE", "interval_day_to_minute", - "PT3924H39M", "+163 12:39:00.0", 23); + "PT3924H39M", "+163 12:39:00", 23); assertQuery("SELECT INTERVAL '163 12:39:59.163' DAY TO SECOND", "INTERVAL '163 12:39:59.163' DAY TO SECOND", "interval_day_to_second", "PT3924H39M59.163S", "+163 12:39:59.163", 23); assertQuery("SELECT INTERVAL -'163 23:39:56.23' DAY TO SECOND", "INTERVAL -'163 23:39:56.23' DAY TO SECOND", - "interval_day_to_second", "PT-3935H-39M-56.023S", "-163 23:39:56.23", 23); + "interval_day_to_second", "PT-3935H-39M-56.23S", "-163 23:39:56.23", 23); assertQuery("SELECT INTERVAL '163:39' HOUR TO MINUTE", "INTERVAL '163:39' HOUR TO MINUTE", "interval_hour_to_minute", - "PT163H39M", "+6 19:39:00.0", 23); + "PT163H39M", "+6 19:39:00", 23); assertQuery("SELECT INTERVAL '163:39:59.163' HOUR TO SECOND", "INTERVAL '163:39:59.163' HOUR TO SECOND", "interval_hour_to_second", "PT163H39M59.163S", "+6 19:39:59.163", 23); assertQuery("SELECT INTERVAL '163:59.163' MINUTE TO SECOND", "INTERVAL '163:59.163' MINUTE TO SECOND", "interval_minute_to_second", diff --git a/x-pack/plugin/sql/qa/src/main/resources/datetime-interval.csv-spec b/x-pack/plugin/sql/qa/src/main/resources/datetime-interval.csv-spec index 785ea8f404dce..b60807d161cc7 100644 --- a/x-pack/plugin/sql/qa/src/main/resources/datetime-interval.csv-spec +++ b/x-pack/plugin/sql/qa/src/main/resources/datetime-interval.csv-spec @@ -8,17 +8,17 @@ exactIntervals SELECT INTERVAL 1 YEAR AS y, INTERVAL 2 MONTH AS m, INTERVAL 3 DAY AS d, INTERVAL 4 HOUR AS h, INTERVAL 5 MINUTE AS mm, INTERVAL 6 SECOND AS s; - y | m | d | h | mm | s ----------------+---------------+---------------+---------------+---------------+--------------- -+1-0 |+0-2 |+3 00:00:00.0 |+0 04:00:00.0 |+0 00:05:00.0 |+0 00:00:06.0 + y | m | d | h | mm | s +---------------+---------------+-------------+-------------+-------------+------------- ++1-0 |+0-2 |+3 00:00:00 |+0 04:00:00 |+0 00:05:00 |+0 00:00:06 ; testExactIntervalPlural SELECT INTERVAL 1 YEARS AS y, INTERVAL 2 MONTHS AS m, INTERVAL 3 DAYS AS d, INTERVAL 4 HOURS AS h, INTERVAL 5 MINUTES AS mm, INTERVAL 6 SECONDS AS s; - y | m | d | h | mm | s ----------------+---------------+---------------+---------------+---------------+--------------- -+1-0 |+0-2 |+3 00:00:00.0 |+0 04:00:00.0 |+0 00:05:00.0 |+0 00:00:06.0 + y | m | d | h | mm | s +---------------+---------------+-------------+-------------+-------------+------------- ++1-0 |+0-2 |+3 00:00:00 |+0 04:00:00 |+0 00:05:00 |+0 00:00:06 ; // take the examples from https://docs.microsoft.com/en-us/sql/odbc/reference/appendixes/interval-literals?view=sql-server-2017 @@ -43,7 +43,7 @@ SELECT INTERVAL '3261' DAY; INTERVAL '3261' DAY ------------------- -+3261 00:00:00.0 ++3261 00:00:00 ; hour @@ -51,7 +51,7 @@ SELECT INTERVAL '163' HOUR; INTERVAL '163' HOUR ------------------- -+6 19:00:00.0 ++6 19:00:00 ; minute @@ -59,7 +59,7 @@ SELECT INTERVAL '163' MINUTE; INTERVAL '163' MINUTE --------------------- -+0 02:43:00.0 ++0 02:43:00 ; second @@ -83,7 +83,7 @@ SELECT INTERVAL '163 12' DAY TO HOUR; INTERVAL '163 12' DAY TO HOUR ----------------------------- -+163 12:00:00.0 ++163 12:00:00 ; dayMinute @@ -91,7 +91,7 @@ SELECT INTERVAL '163 12:39' DAY TO MINUTE AS interval; interval --------------- -+163 12:39:00.0 ++163 12:39:00 ; daySecond @@ -115,7 +115,7 @@ SELECT INTERVAL '163:39' HOUR TO MINUTE AS interval; interval --------------- -+6 19:39:00.0 ++6 19:39:00 ; hourSecond @@ -139,7 +139,7 @@ SELECT INTERVAL 1 DAY + INTERVAL 53 MINUTES; INTERVAL 1 DAY + INTERVAL 53 MINUTES ------------------------------------ -+1 00:53:00.0 ++1 00:53:00 ; datePlusIntervalInline @@ -162,9 +162,9 @@ SELECT - INTERVAL '49-1' YEAR TO MONTH result; intervalMinusInterval SELECT INTERVAL '1' DAY - INTERVAL '2' HOURS AS result; - result ---------------- -+0 22:00:00.0 + result +------------- ++0 22:00:00 ; @@ -179,16 +179,16 @@ SELECT -2 * INTERVAL '3' YEARS AS result; intervalDayMultiply SELECT -2 * INTERVAL '1 23:45' DAY TO MINUTES AS result; - result ---------------- --3 23:30:00.0 + result +------------- +-3 23:30:00 ; intervalHoursMultiply SELECT 4 * -INTERVAL '2' HOURS AS result1, -5 * -INTERVAL '3' HOURS AS result2; - result1 | result2 ----------------+-------------- --0 08:00:00.0 | +0 15:00:00.0 + result1 | result2 +-------------+------------ +-0 08:00:00 | +0 15:00:00 ; intervalNullMath @@ -206,11 +206,11 @@ SELECT languages, CAST (languages * INTERVAL '1 10:30' DAY TO MINUTES AS string) languages | result ---------------+--------------------------------------------- -2 | +2 21:00:00.0 -5 | +7 04:30:00.0 -4 | +5 18:00:00.0 -5 | +7 04:30:00.0 -1 | +1 10:30:00.0 +2 | +2 21:00:00 +5 | +7 04:30:00 +4 | +5 18:00:00 +5 | +7 04:30:00 +1 | +1 10:30:00 ; dateMinusInterval diff --git a/x-pack/plugin/sql/qa/src/main/resources/docs/docs.csv-spec b/x-pack/plugin/sql/qa/src/main/resources/docs/docs.csv-spec index 9a59c6161bc72..d366f1b85617b 100644 --- a/x-pack/plugin/sql/qa/src/main/resources/docs/docs.csv-spec +++ b/x-pack/plugin/sql/qa/src/main/resources/docs/docs.csv-spec @@ -876,9 +876,9 @@ dtIntervalPlusInterval // tag::dtIntervalPlusInterval SELECT INTERVAL 1 DAY + INTERVAL 53 MINUTES AS result; - result + result --------------- -+1 00:53:00.0 ++1 00:53:00 // end::dtIntervalPlusInterval ; @@ -909,9 +909,9 @@ dtIntervalMinusInterval // tag::dtIntervalMinusInterval SELECT INTERVAL '1' DAY - INTERVAL '2' HOURS AS result; - result + result --------------- -+0 22:00:00.0 ++0 22:00:00 // end::dtIntervalMinusInterval ; diff --git a/x-pack/plugin/sql/sql-proto/src/main/java/org/elasticsearch/xpack/sql/proto/StringUtils.java b/x-pack/plugin/sql/sql-proto/src/main/java/org/elasticsearch/xpack/sql/proto/StringUtils.java index d0c4c0f0b35ee..26846ecfb4fee 100644 --- a/x-pack/plugin/sql/sql-proto/src/main/java/org/elasticsearch/xpack/sql/proto/StringUtils.java +++ b/x-pack/plugin/sql/sql-proto/src/main/java/org/elasticsearch/xpack/sql/proto/StringUtils.java @@ -129,8 +129,14 @@ public static String toString(Object value) { sb.append(":"); durationInSec = durationInSec % SECONDS_PER_MINUTE; sb.append(indent(durationInSec)); - sb.append("."); - sb.append(TimeUnit.NANOSECONDS.toMillis(d.getNano())); + long millis = TimeUnit.NANOSECONDS.toMillis(d.getNano()); + if (millis > 0) { + sb.append("."); + while (millis % 10 == 0) { + millis /= 10; + } + sb.append(millis); + } return sb.toString(); } diff --git a/x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/literal/interval/Intervals.java b/x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/literal/interval/Intervals.java index 5b9d5a48ec849..9d96fb9606a5b 100644 --- a/x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/literal/interval/Intervals.java +++ b/x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/literal/interval/Intervals.java @@ -341,6 +341,10 @@ TemporalAmount parse(Source source, String string) { + ": negative value [{}] not allowed (negate the entire interval instead)", v); } + if (units.get(unitIndex) == TimeUnit.MILLISECOND && number.length() < 3) { + // normalize the number past DOT to millis + v *= number.length() < 2 ? 100 : 10; + } values[unitIndex++] = v; } catch (QlIllegalArgumentException siae) { throw new ParsingException(source, invalidIntervalMessage(string), siae.getMessage()); diff --git a/x-pack/plugin/sql/src/test/java/org/elasticsearch/xpack/sql/expression/literal/interval/IntervalsTests.java b/x-pack/plugin/sql/src/test/java/org/elasticsearch/xpack/sql/expression/literal/interval/IntervalsTests.java index 3cb8143162ad1..0aa159c5dc9c3 100644 --- a/x-pack/plugin/sql/src/test/java/org/elasticsearch/xpack/sql/expression/literal/interval/IntervalsTests.java +++ b/x-pack/plugin/sql/src/test/java/org/elasticsearch/xpack/sql/expression/literal/interval/IntervalsTests.java @@ -84,7 +84,8 @@ public void testMinuteInterval() throws Exception { public void testSecondInterval() throws Exception { int randomSeconds = randomNonNegativeInt(); int randomMillis = randomBoolean() ? (randomBoolean() ? 0 : 999) : randomInt(999); - String value = format(Locale.ROOT, "%s%d.%d", sign, randomSeconds, randomMillis); + String value = format(Locale.ROOT, "%s%d", sign, randomSeconds); + value += randomMillis > 0 ? format(Locale.ROOT, ".%03d", randomMillis) : ""; TemporalAmount amount = parseInterval(EMPTY, value, INTERVAL_SECOND); assertEquals(maybeNegate(sign, Duration.ofSeconds(randomSeconds).plusMillis(randomMillis)), amount); } @@ -129,7 +130,7 @@ public void testDayToSecond() throws Exception { boolean withMillis = randomBoolean(); int randomMilli = withMillis ? randomInt(999) : 0; - String millisString = withMillis ? "." + randomMilli : ""; + String millisString = withMillis && randomMilli > 0 ? format(Locale.ROOT, ".%03d", randomMilli) : ""; String value = format(Locale.ROOT, "%s%d %d:%d:%d%s", sign, randomDay, randomHour, randomMinute, randomSecond, millisString); TemporalAmount amount = parseInterval(EMPTY, value, INTERVAL_DAY_TO_SECOND); @@ -152,7 +153,7 @@ public void testHourToSecond() throws Exception { boolean withMillis = randomBoolean(); int randomMilli = withMillis ? randomInt(999) : 0; - String millisString = withMillis ? "." + randomMilli : ""; + String millisString = withMillis && randomMilli > 0 ? format(Locale.ROOT, ".%03d", randomMilli) : ""; String value = format(Locale.ROOT, "%s%d:%d:%d%s", sign, randomHour, randomMinute, randomSecond, millisString); TemporalAmount amount = parseInterval(EMPTY, value, INTERVAL_HOUR_TO_SECOND); @@ -166,7 +167,7 @@ public void testMinuteToSecond() throws Exception { boolean withMillis = randomBoolean(); int randomMilli = withMillis ? randomInt(999) : 0; - String millisString = withMillis ? "." + randomMilli : ""; + String millisString = withMillis && randomMilli > 0 ? format(Locale.ROOT, ".%03d", randomMilli) : ""; String value = format(Locale.ROOT, "%s%d:%d%s", sign, randomMinute, randomSecond, millisString); TemporalAmount amount = parseInterval(EMPTY, value, INTERVAL_MINUTE_TO_SECOND); diff --git a/x-pack/plugin/sql/src/test/java/org/elasticsearch/xpack/sql/parser/ExpressionTests.java b/x-pack/plugin/sql/src/test/java/org/elasticsearch/xpack/sql/parser/ExpressionTests.java index ab037f7a58f6a..ad2d3066e121f 100644 --- a/x-pack/plugin/sql/src/test/java/org/elasticsearch/xpack/sql/parser/ExpressionTests.java +++ b/x-pack/plugin/sql/src/test/java/org/elasticsearch/xpack/sql/parser/ExpressionTests.java @@ -159,7 +159,7 @@ public void testStringInterval() { int randomSecond = randomInt(59); int randomMilli = randomInt(999); - String value = format(Locale.ROOT, "INTERVAL '%d %d:%d:%d.%d' DAY TO SECOND", randomDay, randomHour, randomMinute, randomSecond, + String value = format(Locale.ROOT, "INTERVAL '%d %d:%d:%d.%03d' DAY TO SECOND", randomDay, randomHour, randomMinute, randomSecond, randomMilli); assertEquals(Duration.ofDays(randomDay).plusHours(randomHour).plusMinutes(randomMinute).plusSeconds(randomSecond) .plusMillis(randomMilli), intervalOf(value)); @@ -172,7 +172,7 @@ public void testNegativeStringInterval() { int randomSecond = randomInt(59); int randomMilli = randomInt(999); - String value = format(Locale.ROOT, "INTERVAL -'%d %d:%d:%d.%d' DAY TO SECOND", randomDay, randomHour, randomMinute, randomSecond, + String value = format(Locale.ROOT, "INTERVAL -'%d %d:%d:%d.%03d' DAY TO SECOND", randomDay, randomHour, randomMinute, randomSecond, randomMilli); assertEquals(Duration.ofDays(randomDay).plusHours(randomHour).plusMinutes(randomMinute).plusSeconds(randomSecond) .plusMillis(randomMilli).negated(), intervalOf(value));