diff --git a/x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/util/DateUtils.java b/x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/util/DateUtils.java index 448b6bc537602..31c79f7a1bc6d 100644 --- a/x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/util/DateUtils.java +++ b/x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/util/DateUtils.java @@ -32,11 +32,16 @@ public final class DateUtils { public static final LocalDate EPOCH = LocalDate.of(1970, 1, 1); public static final long DAY_IN_MILLIS = 60 * 60 * 24 * 1000L; - private static final DateTimeFormatter DATE_TIME_ESCAPED_LITERAL_FORMATTER = new DateTimeFormatterBuilder() - .append(ISO_LOCAL_DATE) - .appendLiteral(" ") - .append(ISO_LOCAL_TIME) - .toFormatter().withZone(UTC); + private static final DateTimeFormatter DATE_TIME_ESCAPED_LITERAL_FORMATTER_WHITESPACE = new DateTimeFormatterBuilder() + .append(ISO_LOCAL_DATE) + .appendLiteral(' ') + .append(ISO_LOCAL_TIME) + .toFormatter().withZone(UTC); + private static final DateTimeFormatter DATE_TIME_ESCAPED_LITERAL_FORMATTER_T_LITERAL = new DateTimeFormatterBuilder() + .append(ISO_LOCAL_DATE) + .appendLiteral('T') + .append(ISO_LOCAL_TIME) + .toFormatter().withZone(UTC); private static final DateFormatter UTC_DATE_TIME_FORMATTER = DateFormatter.forPattern("date_optional_time").withZone(UTC); private static final int DEFAULT_PRECISION_FOR_CURRENT_FUNCTIONS = 3; @@ -105,7 +110,13 @@ public static ZonedDateTime asDateTime(String dateFormat) { } public static ZonedDateTime ofEscapedLiteral(String dateFormat) { - return ZonedDateTime.parse(dateFormat, DATE_TIME_ESCAPED_LITERAL_FORMATTER.withZone(UTC)); + int separatorIdx = dateFormat.lastIndexOf('-') + 3; + // Avoid index out of bounds - it will lead to DateTimeParseException anyways + if (separatorIdx >= dateFormat.length() || dateFormat.charAt(separatorIdx) == 'T') { + return ZonedDateTime.parse(dateFormat, DATE_TIME_ESCAPED_LITERAL_FORMATTER_T_LITERAL.withZone(UTC)); + } else { + return ZonedDateTime.parse(dateFormat, DATE_TIME_ESCAPED_LITERAL_FORMATTER_WHITESPACE.withZone(UTC)); + } } public static String toString(ZonedDateTime dateTime) { diff --git a/x-pack/plugin/sql/src/test/java/org/elasticsearch/xpack/sql/parser/EscapedFunctionsTests.java b/x-pack/plugin/sql/src/test/java/org/elasticsearch/xpack/sql/parser/EscapedFunctionsTests.java index 2852619edb004..93395f9dbd338 100644 --- a/x-pack/plugin/sql/src/test/java/org/elasticsearch/xpack/sql/parser/EscapedFunctionsTests.java +++ b/x-pack/plugin/sql/src/test/java/org/elasticsearch/xpack/sql/parser/EscapedFunctionsTests.java @@ -62,6 +62,33 @@ private Literal timestampLiteral(String date) { return (Literal) exp; } + private String buildDate() { + StringBuilder sb = new StringBuilder(); + int length = randomIntBetween(4, 9); + + if (randomBoolean()) { + sb.append('-'); + } else { + if (length > 4) { + sb.append('-'); + } + } + + for (int i = 1; i <= length; i++) { + sb.append(i); + } + sb.append("-05-10"); + return sb.toString(); + } + + private String buildSecsAndFractional() { + if (randomBoolean()) { + return ":55" + randomFrom("", ".1", ".12", ".123", ".1234", ".12345", ".123456", + ".1234567", ".12345678", ".123456789"); + } + return ""; + } + private Literal guidLiteral(String guid) { Expression exp = parser.createExpression(buildExpression("guid", "'%s'", guid)); assertThat(exp, instanceOf(Expression.class)); @@ -185,7 +212,7 @@ public void testFunctionWithFunctionWithArgAndParams() { } public void testDateLiteral() { - Literal l = dateLiteral("2012-01-01"); + Literal l = dateLiteral(buildDate()); assertThat(l.dataType(), is(DATE)); } @@ -197,7 +224,7 @@ public void testDateLiteralValidation() { } public void testTimeLiteral() { - Literal l = timeLiteral("12:23:56"); + Literal l = timeLiteral("12:23" + buildSecsAndFractional()); assertThat(l.dataType(), is(TIME)); } @@ -209,14 +236,27 @@ public void testTimeLiteralValidation() { } public void testTimestampLiteral() { - Literal l = timestampLiteral("2012-01-01 10:01:02.3456"); + Literal l = timestampLiteral(buildDate() + " 10:20" + buildSecsAndFractional()); + assertThat(l.dataType(), is(DATETIME)); + l = timestampLiteral(buildDate() + "T11:22" + buildSecsAndFractional()); assertThat(l.dataType(), is(DATETIME)); } public void testTimestampLiteralValidation() { - ParsingException ex = expectThrows(ParsingException.class, () -> timestampLiteral("2012-01-01T10:01:02.3456")); + String date = buildDate(); + ParsingException ex = expectThrows(ParsingException.class, () -> timestampLiteral(date+ "_AB 10:01:02.3456")); + assertEquals( + "line 1:2: Invalid timestamp received; Text '" + date + "_AB 10:01:02.3456' could not be parsed at index " + + date.length(), + ex.getMessage()); + ex = expectThrows(ParsingException.class, () -> timestampLiteral("20120101_AB 10:01:02.3456")); + assertEquals( + "line 1:2: Invalid timestamp received; Text '20120101_AB 10:01:02.3456' could not be parsed at index 0", + ex.getMessage()); + + ex = expectThrows(ParsingException.class, () -> timestampLiteral(date)); assertEquals( - "line 1:2: Invalid timestamp received; Text '2012-01-01T10:01:02.3456' could not be parsed at index 10", + "line 1:2: Invalid timestamp received; Text '" + date + "' could not be parsed at index " + date.length(), ex.getMessage()); }