mirror of
https://github.com/honeymoose/OpenSearch.git
synced 2025-02-22 21:05:23 +00:00
Improve the usability of the MS-SQL server/ODBC escaped date/time/timestamp literals, by allowing timezone/offset ids in the parsed string, e.g.: ``` {ts '2000-01-01T11:11:11Z'} ``` Closes: #58262 (cherry picked from commit 0af1f2fef805324e802d97d2fd9b4660abb403f0)
This commit is contained in:
parent
642b05a511
commit
e7c40d973e
@ -133,8 +133,8 @@ import static java.util.Collections.emptyList;
|
||||
import static java.util.Collections.singletonList;
|
||||
import static org.elasticsearch.xpack.sql.type.SqlDataTypeConverter.canConvert;
|
||||
import static org.elasticsearch.xpack.sql.type.SqlDataTypeConverter.converterFor;
|
||||
import static org.elasticsearch.xpack.sql.util.DateUtils.asDateOnly;
|
||||
import static org.elasticsearch.xpack.sql.util.DateUtils.asTimeOnly;
|
||||
import static org.elasticsearch.xpack.sql.util.DateUtils.dateOfEscapedLiteral;
|
||||
import static org.elasticsearch.xpack.sql.util.DateUtils.dateTimeOfEscapedLiteral;
|
||||
|
||||
abstract class ExpressionBuilder extends IdentifierBuilder {
|
||||
@ -778,7 +778,7 @@ abstract class ExpressionBuilder extends IdentifierBuilder {
|
||||
Source source = source(ctx);
|
||||
// parse yyyy-MM-dd (time optional but is set to 00:00:00.000 because of the conversion to DATE
|
||||
try {
|
||||
return new Literal(source, dateOfEscapedLiteral(string), SqlDataTypes.DATE);
|
||||
return new Literal(source, asDateOnly(string), SqlDataTypes.DATE);
|
||||
} catch(DateTimeParseException ex) {
|
||||
throw new ParsingException(source, "Invalid date received; {}", ex.getMessage());
|
||||
}
|
||||
|
@ -28,7 +28,6 @@ import java.time.temporal.TemporalAccessor;
|
||||
|
||||
import static java.time.format.DateTimeFormatter.ISO_LOCAL_DATE;
|
||||
import static java.time.format.DateTimeFormatter.ISO_LOCAL_TIME;
|
||||
import static java.time.format.DateTimeFormatter.ISO_TIME;
|
||||
|
||||
public final class DateUtils {
|
||||
|
||||
@ -37,38 +36,23 @@ 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_FORMATTER_WHITESPACE = new DateTimeFormatterBuilder()
|
||||
.append(ISO_LOCAL_DATE)
|
||||
.appendLiteral(' ')
|
||||
private static final DateTimeFormatter ISO_LOCAL_TIME_OPTIONAL_TZ = new DateTimeFormatterBuilder()
|
||||
.append(ISO_LOCAL_TIME)
|
||||
.toFormatter().withZone(UTC);
|
||||
private static final DateTimeFormatter DATE_TIME_FORMATTER_T_LITERAL = new DateTimeFormatterBuilder()
|
||||
.append(ISO_LOCAL_DATE)
|
||||
.appendLiteral('T')
|
||||
.append(ISO_LOCAL_TIME)
|
||||
.toFormatter().withZone(UTC);
|
||||
private static final DateTimeFormatter DATE_OPTIONAL_TIME_FORMATTER_WHITESPACE = new DateTimeFormatterBuilder()
|
||||
.append(ISO_LOCAL_DATE)
|
||||
.optionalStart()
|
||||
.appendLiteral(' ')
|
||||
.append(ISO_LOCAL_TIME)
|
||||
.toFormatter().withZone(UTC);
|
||||
private static final DateTimeFormatter DATE_OPTIONAL_TIME_FORMATTER_T_LITERAL = new DateTimeFormatterBuilder()
|
||||
.append(ISO_LOCAL_DATE)
|
||||
.optionalStart()
|
||||
.appendLiteral('T')
|
||||
.append(ISO_LOCAL_TIME)
|
||||
.toFormatter().withZone(UTC);
|
||||
private static final DateTimeFormatter ISO_LOCAL_DATE_OPTIONAL_TIME_FORMATTER_WHITESPACE = new DateTimeFormatterBuilder()
|
||||
.append(DATE_OPTIONAL_TIME_FORMATTER_WHITESPACE)
|
||||
.optionalStart()
|
||||
.appendZoneOrOffsetId()
|
||||
.toFormatter().withZone(UTC);
|
||||
private static final DateTimeFormatter ISO_LOCAL_DATE_OPTIONAL_TIME_FORMATTER_WHITESPACE = new DateTimeFormatterBuilder()
|
||||
.append(ISO_LOCAL_DATE)
|
||||
.optionalStart()
|
||||
.appendLiteral(' ')
|
||||
.append(ISO_LOCAL_TIME_OPTIONAL_TZ)
|
||||
.optionalEnd()
|
||||
.toFormatter().withZone(UTC);
|
||||
private static final DateTimeFormatter ISO_LOCAL_DATE_OPTIONAL_TIME_FORMATTER_T_LITERAL = new DateTimeFormatterBuilder()
|
||||
.append(DATE_OPTIONAL_TIME_FORMATTER_T_LITERAL)
|
||||
.append(ISO_LOCAL_DATE)
|
||||
.optionalStart()
|
||||
.appendZoneOrOffsetId()
|
||||
.appendLiteral('T')
|
||||
.append(ISO_LOCAL_TIME_OPTIONAL_TZ)
|
||||
.optionalEnd()
|
||||
.toFormatter().withZone(UTC);
|
||||
|
||||
@ -120,15 +104,7 @@ public final class DateUtils {
|
||||
* Parses the given string into a Date (SQL DATE type) using UTC as a default timezone.
|
||||
*/
|
||||
public static ZonedDateTime asDateOnly(String dateFormat) {
|
||||
int separatorIdx = dateFormat.indexOf('-'); // Find the first `-` date separator
|
||||
if (separatorIdx == 0) { // first char = `-` denotes a negative year
|
||||
separatorIdx = dateFormat.indexOf('-', 1); // Find the first `-` date separator past the negative year
|
||||
}
|
||||
// Find the second `-` date separator and move 3 places past the dayOfYear to find the time separator
|
||||
// e.g. 2020-06-01T10:20:30....
|
||||
// ^
|
||||
// +3 = ^
|
||||
separatorIdx = dateFormat.indexOf('-', separatorIdx + 1) + 3;
|
||||
int separatorIdx = timeSeparatorIdx(dateFormat);
|
||||
// Avoid index out of bounds - it will lead to DateTimeParseException anyways
|
||||
if (separatorIdx >= dateFormat.length() || dateFormat.charAt(separatorIdx) == 'T') {
|
||||
return LocalDate.parse(dateFormat, ISO_LOCAL_DATE_OPTIONAL_TIME_FORMATTER_T_LITERAL).atStartOfDay(UTC);
|
||||
@ -142,7 +118,7 @@ public final class DateUtils {
|
||||
}
|
||||
|
||||
public static OffsetTime asTimeOnly(String timeFormat) {
|
||||
return DateFormatters.from(ISO_TIME.parse(timeFormat)).toOffsetDateTime().toOffsetTime();
|
||||
return DateFormatters.from(ISO_LOCAL_TIME_OPTIONAL_TZ.parse(timeFormat)).toOffsetDateTime().toOffsetTime();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -152,23 +128,13 @@ public final class DateUtils {
|
||||
return DateFormatters.from(UTC_DATE_TIME_FORMATTER.parse(dateFormat)).withZoneSameInstant(UTC);
|
||||
}
|
||||
|
||||
public static ZonedDateTime dateOfEscapedLiteral(String dateFormat) {
|
||||
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 LocalDate.parse(dateFormat, DATE_OPTIONAL_TIME_FORMATTER_T_LITERAL).atStartOfDay(UTC);
|
||||
} else {
|
||||
return LocalDate.parse(dateFormat, DATE_TIME_FORMATTER_WHITESPACE).atStartOfDay(UTC);
|
||||
}
|
||||
}
|
||||
|
||||
public static ZonedDateTime dateTimeOfEscapedLiteral(String dateFormat) {
|
||||
int separatorIdx = dateFormat.lastIndexOf('-') + 3;
|
||||
int separatorIdx = timeSeparatorIdx(dateFormat);
|
||||
// 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_FORMATTER_T_LITERAL.withZone(UTC));
|
||||
return ZonedDateTime.parse(dateFormat, ISO_LOCAL_DATE_OPTIONAL_TIME_FORMATTER_T_LITERAL);
|
||||
} else {
|
||||
return ZonedDateTime.parse(dateFormat, DATE_TIME_FORMATTER_WHITESPACE.withZone(UTC));
|
||||
return ZonedDateTime.parse(dateFormat, ISO_LOCAL_DATE_OPTIONAL_TIME_FORMATTER_WHITESPACE);
|
||||
}
|
||||
}
|
||||
|
||||
@ -243,4 +209,16 @@ public final class DateUtils {
|
||||
return ta;
|
||||
}
|
||||
}
|
||||
|
||||
private static int timeSeparatorIdx(String timestampStr) {
|
||||
int separatorIdx = timestampStr.indexOf('-'); // Find the first `-` date separator
|
||||
if (separatorIdx == 0) { // first char = `-` denotes a negative year
|
||||
separatorIdx = timestampStr.indexOf('-', 1); // Find the first `-` date separator past the negative year
|
||||
}
|
||||
// Find the second `-` date separator and move 3 places past the dayOfYear to find the time separator
|
||||
// e.g. 2020-06-01T10:20:30....
|
||||
// ^
|
||||
// +3 = ^
|
||||
return timestampStr.indexOf('-', separatorIdx + 1) + 3;
|
||||
}
|
||||
}
|
||||
|
@ -34,6 +34,7 @@ import static org.hamcrest.Matchers.endsWith;
|
||||
import static org.hamcrest.Matchers.instanceOf;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
import static org.hamcrest.Matchers.matchesPattern;
|
||||
import static org.hamcrest.Matchers.startsWith;
|
||||
|
||||
public class EscapedFunctionsTests extends ESTestCase {
|
||||
|
||||
@ -83,17 +84,19 @@ public class EscapedFunctionsTests extends ESTestCase {
|
||||
|
||||
private String buildTime() {
|
||||
if (randomBoolean()) {
|
||||
return (randomBoolean() ? "T" : " ") + "11:22" + buildSecsAndFractional();
|
||||
return (randomBoolean() ? "T" : " ") + "11:22" + buildSecsFractionalAndTimezone();
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
private String buildSecsAndFractional() {
|
||||
private String buildSecsFractionalAndTimezone() {
|
||||
String str = "";
|
||||
if (randomBoolean()) {
|
||||
return ":55" + randomFrom("", ".1", ".12", ".123", ".1234", ".12345", ".123456",
|
||||
".1234567", ".12345678", ".123456789");
|
||||
str = ":55" + randomFrom("", ".1", ".12", ".123", ".1234", ".12345", ".123456",
|
||||
".1234567", ".12345678", ".123456789") +
|
||||
randomFrom("", "Z", "Etc/GMT-5", "-05:30", "+04:20");
|
||||
}
|
||||
return "";
|
||||
return str;
|
||||
}
|
||||
|
||||
private Literal guidLiteral(String guid) {
|
||||
@ -231,7 +234,7 @@ public class EscapedFunctionsTests extends ESTestCase {
|
||||
}
|
||||
|
||||
public void testTimeLiteral() {
|
||||
Literal l = timeLiteral("12:23" + buildSecsAndFractional());
|
||||
Literal l = timeLiteral("12:23" + buildSecsFractionalAndTimezone());
|
||||
assertThat(l.dataType(), is(TIME));
|
||||
}
|
||||
|
||||
@ -243,9 +246,9 @@ public class EscapedFunctionsTests extends ESTestCase {
|
||||
}
|
||||
|
||||
public void testTimestampLiteral() {
|
||||
Literal l = timestampLiteral(buildDate() + " 10:20" + buildSecsAndFractional());
|
||||
Literal l = timestampLiteral(buildDate() + " 10:20" + buildSecsFractionalAndTimezone());
|
||||
assertThat(l.dataType(), is(DATETIME));
|
||||
l = timestampLiteral(buildDate() + "T11:22" + buildSecsAndFractional());
|
||||
l = timestampLiteral(buildDate() + "T11:22" + buildSecsFractionalAndTimezone());
|
||||
assertThat(l.dataType(), is(DATETIME));
|
||||
}
|
||||
|
||||
@ -253,8 +256,8 @@ public class EscapedFunctionsTests extends ESTestCase {
|
||||
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(),
|
||||
"line 1:2: Invalid timestamp received; Text '" + date + "_AB 10:01:02.3456' could not be parsed, " +
|
||||
"unparsed text found at index " + date.length(),
|
||||
ex.getMessage());
|
||||
ex = expectThrows(ParsingException.class, () -> timestampLiteral("20120101_AB 10:01:02.3456"));
|
||||
assertEquals(
|
||||
@ -262,9 +265,9 @@ public class EscapedFunctionsTests extends ESTestCase {
|
||||
ex.getMessage());
|
||||
|
||||
ex = expectThrows(ParsingException.class, () -> timestampLiteral(date));
|
||||
assertEquals(
|
||||
"line 1:2: Invalid timestamp received; Text '" + date + "' could not be parsed at index " + date.length(),
|
||||
ex.getMessage());
|
||||
assertThat(ex.getMessage(), startsWith(
|
||||
"line 1:2: Invalid timestamp received; Text '" + date + "' could not be parsed: " +
|
||||
"Unable to obtain ZonedDateTime from TemporalAccessor"));
|
||||
}
|
||||
|
||||
public void testGUID() {
|
||||
|
Loading…
x
Reference in New Issue
Block a user