Skip to content

Commit 27265f0

Browse files
committed
SQL: Enhance timestamp escaped literal parsing (#52097)
Allow also whitespace ` ` (together with `T`) as a separator between date and time parts of the timestamp string. E.g.: ``` {ts '2020-02-08 12.10.45'} ``` or ``` {ts '2020-02-08T12.10.45'} ``` Fixes: #46069 (cherry picked from commit 07c9770)
1 parent 4e48153 commit 27265f0

File tree

2 files changed

+62
-11
lines changed

2 files changed

+62
-11
lines changed

x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/util/DateUtils.java

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -32,11 +32,16 @@ public final class DateUtils {
3232
public static final LocalDate EPOCH = LocalDate.of(1970, 1, 1);
3333
public static final long DAY_IN_MILLIS = 60 * 60 * 24 * 1000L;
3434

35-
private static final DateTimeFormatter DATE_TIME_ESCAPED_LITERAL_FORMATTER = new DateTimeFormatterBuilder()
36-
.append(ISO_LOCAL_DATE)
37-
.appendLiteral(" ")
38-
.append(ISO_LOCAL_TIME)
39-
.toFormatter().withZone(UTC);
35+
private static final DateTimeFormatter DATE_TIME_ESCAPED_LITERAL_FORMATTER_WHITESPACE = new DateTimeFormatterBuilder()
36+
.append(ISO_LOCAL_DATE)
37+
.appendLiteral(' ')
38+
.append(ISO_LOCAL_TIME)
39+
.toFormatter().withZone(UTC);
40+
private static final DateTimeFormatter DATE_TIME_ESCAPED_LITERAL_FORMATTER_T_LITERAL = new DateTimeFormatterBuilder()
41+
.append(ISO_LOCAL_DATE)
42+
.appendLiteral('T')
43+
.append(ISO_LOCAL_TIME)
44+
.toFormatter().withZone(UTC);
4045

4146
private static final DateFormatter UTC_DATE_TIME_FORMATTER = DateFormatter.forPattern("date_optional_time").withZone(UTC);
4247
private static final int DEFAULT_PRECISION_FOR_CURRENT_FUNCTIONS = 3;
@@ -105,7 +110,13 @@ public static ZonedDateTime asDateTime(String dateFormat) {
105110
}
106111

107112
public static ZonedDateTime ofEscapedLiteral(String dateFormat) {
108-
return ZonedDateTime.parse(dateFormat, DATE_TIME_ESCAPED_LITERAL_FORMATTER.withZone(UTC));
113+
int separatorIdx = dateFormat.lastIndexOf('-') + 3;
114+
// Avoid index out of bounds - it will lead to DateTimeParseException anyways
115+
if (separatorIdx >= dateFormat.length() || dateFormat.charAt(separatorIdx) == 'T') {
116+
return ZonedDateTime.parse(dateFormat, DATE_TIME_ESCAPED_LITERAL_FORMATTER_T_LITERAL.withZone(UTC));
117+
} else {
118+
return ZonedDateTime.parse(dateFormat, DATE_TIME_ESCAPED_LITERAL_FORMATTER_WHITESPACE.withZone(UTC));
119+
}
109120
}
110121

111122
public static String toString(ZonedDateTime dateTime) {

x-pack/plugin/sql/src/test/java/org/elasticsearch/xpack/sql/parser/EscapedFunctionsTests.java

Lines changed: 45 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,33 @@ private Literal timestampLiteral(String date) {
6262
return (Literal) exp;
6363
}
6464

65+
private String buildDate() {
66+
StringBuilder sb = new StringBuilder();
67+
int length = randomIntBetween(4, 9);
68+
69+
if (randomBoolean()) {
70+
sb.append('-');
71+
} else {
72+
if (length > 4) {
73+
sb.append('-');
74+
}
75+
}
76+
77+
for (int i = 1; i <= length; i++) {
78+
sb.append(i);
79+
}
80+
sb.append("-05-10");
81+
return sb.toString();
82+
}
83+
84+
private String buildSecsAndFractional() {
85+
if (randomBoolean()) {
86+
return ":55" + randomFrom("", ".1", ".12", ".123", ".1234", ".12345", ".123456",
87+
".1234567", ".12345678", ".123456789");
88+
}
89+
return "";
90+
}
91+
6592
private Literal guidLiteral(String guid) {
6693
Expression exp = parser.createExpression(buildExpression("guid", "'%s'", guid));
6794
assertThat(exp, instanceOf(Expression.class));
@@ -185,7 +212,7 @@ public void testFunctionWithFunctionWithArgAndParams() {
185212
}
186213

187214
public void testDateLiteral() {
188-
Literal l = dateLiteral("2012-01-01");
215+
Literal l = dateLiteral(buildDate());
189216
assertThat(l.dataType(), is(DATE));
190217
}
191218

@@ -197,7 +224,7 @@ public void testDateLiteralValidation() {
197224
}
198225

199226
public void testTimeLiteral() {
200-
Literal l = timeLiteral("12:23:56");
227+
Literal l = timeLiteral("12:23" + buildSecsAndFractional());
201228
assertThat(l.dataType(), is(TIME));
202229
}
203230

@@ -209,14 +236,27 @@ public void testTimeLiteralValidation() {
209236
}
210237

211238
public void testTimestampLiteral() {
212-
Literal l = timestampLiteral("2012-01-01 10:01:02.3456");
239+
Literal l = timestampLiteral(buildDate() + " 10:20" + buildSecsAndFractional());
240+
assertThat(l.dataType(), is(DATETIME));
241+
l = timestampLiteral(buildDate() + "T11:22" + buildSecsAndFractional());
213242
assertThat(l.dataType(), is(DATETIME));
214243
}
215244

216245
public void testTimestampLiteralValidation() {
217-
ParsingException ex = expectThrows(ParsingException.class, () -> timestampLiteral("2012-01-01T10:01:02.3456"));
246+
String date = buildDate();
247+
ParsingException ex = expectThrows(ParsingException.class, () -> timestampLiteral(date+ "_AB 10:01:02.3456"));
248+
assertEquals(
249+
"line 1:2: Invalid timestamp received; Text '" + date + "_AB 10:01:02.3456' could not be parsed at index " +
250+
date.length(),
251+
ex.getMessage());
252+
ex = expectThrows(ParsingException.class, () -> timestampLiteral("20120101_AB 10:01:02.3456"));
253+
assertEquals(
254+
"line 1:2: Invalid timestamp received; Text '20120101_AB 10:01:02.3456' could not be parsed at index 0",
255+
ex.getMessage());
256+
257+
ex = expectThrows(ParsingException.class, () -> timestampLiteral(date));
218258
assertEquals(
219-
"line 1:2: Invalid timestamp received; Text '2012-01-01T10:01:02.3456' could not be parsed at index 10",
259+
"line 1:2: Invalid timestamp received; Text '" + date + "' could not be parsed at index " + date.length(),
220260
ex.getMessage());
221261
}
222262

0 commit comments

Comments
 (0)