Skip to content

Commit 3f23ef3

Browse files
committed
Support casting date literal to timestamp (opensearch-project#3831)
* Support following datetime casts in v2: date str -> timestamp timestamp str -> date timestamp str -> time Signed-off-by: Yuanchun Shen <yuanchu@amazon.com> * Invoke datetime UDF to cast date/time/timestamp with Calcite Signed-off-by: Yuanchun Shen <yuanchu@amazon.com> * Test cast to date/time/timestamp Signed-off-by: Yuanchun Shen <yuanchu@amazon.com> * Use stricter date and time formatters Signed-off-by: Yuanchun Shen <yuanchu@amazon.com> * Add a timestamp formatter for instantiating ExprTimestampValue Signed-off-by: Yuanchun Shen <yuanchu@amazon.com> * Simplify date/time expr constructors & parsers Signed-off-by: Yuanchun Shen <yuanchu@amazon.com> * Change to ExpressionEvaluationException when fail parsing malformat date/time strings Signed-off-by: Yuanchun Shen <yuanchu@amazon.com> --------- Signed-off-by: Yuanchun Shen <yuanchu@amazon.com> (cherry picked from commit 30aba65)
1 parent 99dea03 commit 3f23ef3

22 files changed

Lines changed: 489 additions & 385 deletions

File tree

core/src/main/java/org/opensearch/sql/calcite/ExtendedRexBuilder.java

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88
import com.google.common.collect.ImmutableList;
99
import java.util.Arrays;
1010
import java.util.List;
11-
import java.util.Set;
1211
import java.util.stream.Collectors;
1312

1413
import java.util.Locale;
@@ -24,18 +23,12 @@
2423
import org.apache.calcite.sql.type.SqlTypeUtil;
2524
import org.opensearch.sql.ast.expression.SpanUnit;
2625
import org.opensearch.sql.calcite.type.AbstractExprRelDataType;
27-
import org.opensearch.sql.calcite.type.ExprSqlType;
2826
import org.opensearch.sql.calcite.utils.OpenSearchTypeFactory;
2927
import org.opensearch.sql.data.type.ExprCoreType;
3028
import org.opensearch.sql.exception.ExpressionEvaluationException;
3129
import org.opensearch.sql.exception.SemanticCheckException;
3230
import org.opensearch.sql.expression.function.PPLBuiltinOperators;
3331

34-
import static org.opensearch.sql.calcite.utils.OpenSearchTypeFactory.ExprUDT.EXPR_DATE;
35-
import static org.opensearch.sql.calcite.utils.OpenSearchTypeFactory.ExprUDT.EXPR_IP;
36-
import static org.opensearch.sql.calcite.utils.OpenSearchTypeFactory.ExprUDT.EXPR_TIME;
37-
import static org.opensearch.sql.calcite.utils.OpenSearchTypeFactory.ExprUDT.EXPR_TIMESTAMP;
38-
3932
public class ExtendedRexBuilder extends RexBuilder {
4033

4134
public ExtendedRexBuilder(RexBuilder rexBuilder) {

core/src/main/java/org/opensearch/sql/calcite/utils/datetime/DateTimeConversionUtils.java

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
import org.apache.calcite.avatica.util.TimeUnit;
1313
import org.opensearch.sql.data.model.*;
1414
import org.opensearch.sql.data.type.ExprCoreType;
15-
import org.opensearch.sql.exception.SemanticCheckException;
15+
import org.opensearch.sql.exception.ExpressionEvaluationException;
1616
import org.opensearch.sql.expression.function.FunctionProperties;
1717

1818
public final class DateTimeConversionUtils {
@@ -43,7 +43,7 @@ public static ExprTimestampValue forceConvertToTimestampValue(
4343
ExprStringValue stringValue = (ExprStringValue) value;
4444
return new ExprTimestampValue(DateTimeParser.parse(stringValue.stringValue()).toInstant(ZoneOffset.UTC));
4545
} else {
46-
throw new SemanticCheckException(
46+
throw new ExpressionEvaluationException(
4747
String.format(
4848
"Cannot convert %s to timestamp, only STRING, DATE, TIME and TIMESTAMP are supported",
4949
value.type()));
@@ -71,8 +71,8 @@ public static ExprTimestampValue convertToTimestampValue(
7171
} else {
7272
try {
7373
return new ExprTimestampValue(value.timestampValue());
74-
} catch (SemanticCheckException e) {
75-
throw new SemanticCheckException(
74+
} catch (ExpressionEvaluationException e) {
75+
throw new ExpressionEvaluationException(
7676
String.format(
7777
"Cannot convert %s to timestamp, only STRING, DATE, TIME and TIMESTAMP are"
7878
+ " supported",
@@ -103,7 +103,7 @@ public static ExprDateValue convertToDateValue(ExprValue value, FunctionProperti
103103
} else if (value instanceof ExprStringValue) {
104104
return new ExprDateValue(value.stringValue());
105105
} else {
106-
throw new SemanticCheckException(
106+
throw new ExpressionEvaluationException(
107107
String.format(
108108
"Cannot convert %s to date, only STRING, DATE, TIME and TIMESTAMP are supported",
109109
value.type()));

core/src/main/java/org/opensearch/sql/calcite/utils/datetime/DateTimeParser.java

Lines changed: 13 additions & 102 deletions
Original file line numberDiff line numberDiff line change
@@ -5,127 +5,38 @@
55

66
package org.opensearch.sql.calcite.utils.datetime;
77

8-
import com.google.common.collect.ImmutableList;
98
import java.time.LocalDate;
109
import java.time.LocalDateTime;
1110
import java.time.LocalTime;
1211
import java.time.ZoneId;
13-
import java.time.format.DateTimeFormatter;
14-
import java.util.List;
15-
import org.opensearch.sql.exception.SemanticCheckException;
12+
import java.time.format.DateTimeParseException;
13+
import java.util.Locale;
1614
import org.opensearch.sql.utils.DateTimeFormatters;
1715

1816
public interface DateTimeParser {
1917
/**
20-
* Parse a string into a LocalDateTime If only date is found, time is set to 00:00:00. If only
21-
* time is found, date is set to today.
18+
* Parse a string into a LocalDateTime. If only date is found, time is set to 00:00:00. If only
19+
* time is found, date is set to today at UTC.
2220
*
2321
* @param input A date/time/timestamp string
2422
* @return A LocalDateTime
2523
* @throws IllegalArgumentException if parsing fails
2624
*/
2725
static LocalDateTime parse(String input) {
28-
29-
if (input == null || input.trim().isEmpty()) {
30-
throw new SemanticCheckException("Cannot parse a null/empty date-time string.");
31-
}
32-
33-
if (input.contains(":")) {
34-
try {
35-
return parseTimestamp(input);
36-
} catch (Exception ignored) {
37-
}
38-
39-
try {
40-
LocalTime t = parseTime(input);
41-
return LocalDateTime.of(LocalDate.now(ZoneId.of("UTC")), t);
42-
} catch (Exception ignored) {
43-
}
44-
} else {
45-
try {
46-
LocalDate d = parseDate(input);
47-
return d.atStartOfDay();
48-
} catch (Exception ignored) {
49-
}
50-
}
51-
throw new SemanticCheckException(String.format("Unable to parse %s as datetime", input));
52-
}
53-
54-
static LocalDateTime parseTimeOrTimestamp(String input) {
55-
if (input == null || input.trim().isEmpty()) {
56-
throw new SemanticCheckException("Cannot parse a null/empty date-time string.");
57-
}
58-
5926
try {
60-
return parseTime(input).atDate(LocalDate.now(ZoneId.of("UTC")));
61-
} catch (Exception ignored) {
27+
return LocalDateTime.parse(input, DateTimeFormatters.DATE_TIMESTAMP_FORMATTER);
28+
} catch (DateTimeParseException ignored) {
6229
}
6330

6431
try {
65-
return parseTimestamp(input);
32+
LocalTime t = LocalTime.parse(input, DateTimeFormatters.TIME_TIMESTAMP_FORMATTER);
33+
return LocalDateTime.of(LocalDate.now(ZoneId.of("UTC")), t);
6634
} catch (Exception ignored) {
35+
throw new IllegalArgumentException(
36+
String.format(
37+
Locale.ROOT,
38+
"timestamp:%s in unsupported format, please use 'yyyy-MM-dd HH:mm:ss[.SSSSSSSSS]'",
39+
input));
6740
}
68-
69-
throw new SemanticCheckException(
70-
String.format("time:%s in unsupported format, please use 'HH:mm:ss[.SSSSSSSSS]'", input));
71-
}
72-
73-
static LocalDateTime parseDateOrTimestamp(String input) {
74-
if (input == null || input.trim().isEmpty()) {
75-
throw new SemanticCheckException("Cannot parse a null/empty date-time string.");
76-
}
77-
78-
try {
79-
return parseDate(input).atStartOfDay();
80-
} catch (Exception ignored) {
81-
}
82-
83-
try {
84-
return parseTimestamp(input);
85-
} catch (Exception ignored) {
86-
}
87-
88-
throw new SemanticCheckException(
89-
String.format("date:%s in unsupported format, please use 'yyyy-MM-dd'", input));
90-
}
91-
92-
static LocalDateTime parseTimestamp(String input) {
93-
List<DateTimeFormatter> dateTimeFormatters =
94-
ImmutableList.of(DateTimeFormatters.DATE_TIME_FORMATTER_VARIABLE_NANOS_OPTIONAL);
95-
96-
for (DateTimeFormatter fmt : dateTimeFormatters) {
97-
try {
98-
return LocalDateTime.parse(input, fmt);
99-
} catch (Exception ignored) {
100-
}
101-
}
102-
throw new SemanticCheckException(
103-
String.format(
104-
"timestamp:%s in unsupported format, please use 'yyyy-MM-dd HH:mm:ss[.SSSSSSSSS]'",
105-
input));
106-
}
107-
108-
static LocalTime parseTime(String input) {
109-
List<DateTimeFormatter> timeFormatters = ImmutableList.of(DateTimeFormatter.ISO_TIME);
110-
for (DateTimeFormatter fmt : timeFormatters) {
111-
try {
112-
return LocalTime.parse(input, fmt);
113-
} catch (Exception ignored) {
114-
}
115-
}
116-
throw new SemanticCheckException(
117-
String.format("time:%s in unsupported format, please use 'HH:mm:ss[.SSSSSSSSS]'", input));
118-
}
119-
120-
static LocalDate parseDate(String input) {
121-
List<DateTimeFormatter> dateFormatters = ImmutableList.of(DateTimeFormatter.ISO_DATE);
122-
for (DateTimeFormatter fmt : dateFormatters) {
123-
try {
124-
return LocalDate.parse(input, fmt);
125-
} catch (Exception ignored) {
126-
}
127-
}
128-
throw new SemanticCheckException(
129-
String.format("date:%s in unsupported format, please use 'yyyy-MM-dd'", input));
13041
}
13142
}

core/src/main/java/org/opensearch/sql/data/model/ExprDateValue.java

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,35 +5,38 @@
55

66
package org.opensearch.sql.data.model;
77

8-
import static org.opensearch.sql.utils.DateTimeFormatters.DATE_TIME_FORMATTER_VARIABLE_NANOS_OPTIONAL;
98
import static org.opensearch.sql.utils.DateTimeUtils.UTC_ZONE_ID;
109

1110
import com.google.common.base.Objects;
1211
import java.time.Instant;
1312
import java.time.LocalDate;
1413
import java.time.LocalDateTime;
1514
import java.time.LocalTime;
16-
import java.time.ZoneOffset;
1715
import java.time.ZonedDateTime;
1816
import java.time.format.DateTimeFormatter;
1917
import java.time.format.DateTimeParseException;
2018
import lombok.RequiredArgsConstructor;
2119
import org.opensearch.sql.data.type.ExprCoreType;
2220
import org.opensearch.sql.data.type.ExprType;
23-
import org.opensearch.sql.exception.SemanticCheckException;
21+
import org.opensearch.sql.exception.ExpressionEvaluationException;
22+
import org.opensearch.sql.utils.DateTimeFormatters;
2423

2524
/** Expression Date Value. */
2625
@RequiredArgsConstructor
2726
public class ExprDateValue extends AbstractExprValue {
2827

2928
private final LocalDate date;
3029

31-
/** Constructor of ExprDateValue. */
30+
/**
31+
* Constructor with date string.
32+
*
33+
* @param date a date or timestamp string (does not accept time string)
34+
*/
3235
public ExprDateValue(String date) {
3336
try {
34-
this.date = LocalDate.parse(date, DATE_TIME_FORMATTER_VARIABLE_NANOS_OPTIONAL);
37+
this.date = LocalDate.parse(date, DateTimeFormatters.DATE_TIMESTAMP_FORMATTER);
3538
} catch (DateTimeParseException e) {
36-
throw new SemanticCheckException(
39+
throw new ExpressionEvaluationException(
3740
String.format("date:%s in unsupported format, please use 'yyyy-MM-dd'", date));
3841
}
3942
}

core/src/main/java/org/opensearch/sql/data/model/ExprDatetimeValue.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55

66
package org.opensearch.sql.data.model;
77

8-
import static org.opensearch.sql.utils.DateTimeFormatters.DATE_TIME_FORMATTER_WITH_TZ;
98
import static org.opensearch.sql.utils.DateTimeUtils.UTC_ZONE_ID;
109

1110
import com.google.common.base.Objects;
@@ -20,7 +19,8 @@
2019
import lombok.RequiredArgsConstructor;
2120
import org.opensearch.sql.data.type.ExprCoreType;
2221
import org.opensearch.sql.data.type.ExprType;
23-
import org.opensearch.sql.exception.SemanticCheckException;
22+
import org.opensearch.sql.exception.ExpressionEvaluationException;
23+
import org.opensearch.sql.utils.DateTimeFormatters;
2424

2525
@RequiredArgsConstructor
2626
public class ExprDatetimeValue extends AbstractExprValue {
@@ -29,9 +29,9 @@ public class ExprDatetimeValue extends AbstractExprValue {
2929
/** Constructor with datetime string as input. */
3030
public ExprDatetimeValue(String datetime) {
3131
try {
32-
this.datetime = LocalDateTime.parse(datetime, DATE_TIME_FORMATTER_WITH_TZ);
32+
this.datetime = LocalDateTime.parse(datetime, DateTimeFormatters.DATE_TIMESTAMP_FORMATTER);
3333
} catch (DateTimeParseException e) {
34-
throw new SemanticCheckException(
34+
throw new ExpressionEvaluationException(
3535
String.format(
3636
"datetime:%s in unsupported format, please use 'yyyy-MM-dd HH:mm:ss[.SSSSSSSSS]'",
3737
datetime));

core/src/main/java/org/opensearch/sql/data/model/ExprStringValue.java

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
import lombok.RequiredArgsConstructor;
1515
import org.opensearch.sql.data.type.ExprCoreType;
1616
import org.opensearch.sql.data.type.ExprType;
17+
import org.opensearch.sql.exception.ExpressionEvaluationException;
1718
import org.opensearch.sql.exception.SemanticCheckException;
1819

1920
/** Expression String Value. */
@@ -58,7 +59,7 @@ public LocalDateTime datetimeValue() {
5859
public Instant timestampValue() {
5960
try {
6061
return new ExprTimestampValue(value).timestampValue();
61-
} catch (SemanticCheckException e) {
62+
} catch (ExpressionEvaluationException e) {
6263
return new ExprTimestampValue(
6364
LocalDateTime.of(new ExprDateValue(value).dateValue(), LocalTime.of(0, 0, 0)).toInstant(ZoneOffset.UTC))
6465
.timestampValue();
@@ -69,7 +70,7 @@ public Instant timestampValue() {
6970
public LocalDate dateValue() {
7071
try {
7172
return new ExprDatetimeValue(value).dateValue();
72-
} catch (SemanticCheckException e) {
73+
} catch (ExpressionEvaluationException e) {
7374
return new ExprDateValue(value).dateValue();
7475
}
7576
}
@@ -78,7 +79,7 @@ public LocalDate dateValue() {
7879
public LocalTime timeValue() {
7980
try {
8081
return new ExprDatetimeValue(value).timeValue();
81-
} catch (SemanticCheckException e) {
82+
} catch (ExpressionEvaluationException e) {
8283
return new ExprTimeValue(value).timeValue();
8384
}
8485
}

core/src/main/java/org/opensearch/sql/data/model/ExprTimeValue.java

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
package org.opensearch.sql.data.model;
77

88
import static java.time.format.DateTimeFormatter.ISO_LOCAL_TIME;
9-
import static org.opensearch.sql.utils.DateTimeFormatters.DATE_TIME_FORMATTER_VARIABLE_NANOS_OPTIONAL;
109
import static org.opensearch.sql.utils.DateTimeUtils.UTC_ZONE_ID;
1110

1211
import java.time.Instant;
@@ -19,21 +18,26 @@
1918
import lombok.RequiredArgsConstructor;
2019
import org.opensearch.sql.data.type.ExprCoreType;
2120
import org.opensearch.sql.data.type.ExprType;
22-
import org.opensearch.sql.exception.SemanticCheckException;
21+
import org.opensearch.sql.exception.ExpressionEvaluationException;
2322
import org.opensearch.sql.expression.function.FunctionProperties;
23+
import org.opensearch.sql.utils.DateTimeFormatters;
2424

2525
/** Expression Time Value. */
2626
@RequiredArgsConstructor
2727
public class ExprTimeValue extends AbstractExprValue {
2828

2929
private final LocalTime time;
3030

31-
/** Constructor of ExprTimeValue. */
31+
/**
32+
* Constructor with time string.
33+
*
34+
* @param time a time or timestamp string (does not accept date string)
35+
*/
3236
public ExprTimeValue(String time) {
3337
try {
34-
this.time = LocalTime.parse(time, DATE_TIME_FORMATTER_VARIABLE_NANOS_OPTIONAL);
38+
this.time = LocalTime.parse(time, DateTimeFormatters.TIME_TIMESTAMP_FORMATTER);
3539
} catch (DateTimeParseException e) {
36-
throw new SemanticCheckException(
40+
throw new ExpressionEvaluationException(
3741
String.format("time:%s in unsupported format, please use 'HH:mm:ss[.SSSSSSSSS]'", time));
3842
}
3943
}

core/src/main/java/org/opensearch/sql/data/model/ExprTimestampValue.java

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,29 +13,34 @@
1313
import java.time.LocalDate;
1414
import java.time.LocalDateTime;
1515
import java.time.LocalTime;
16+
import java.time.ZoneOffset;
1617
import java.time.format.DateTimeParseException;
1718
import java.time.temporal.ChronoUnit;
1819
import java.util.Objects;
1920
import lombok.RequiredArgsConstructor;
2021
import org.opensearch.sql.data.type.ExprCoreType;
2122
import org.opensearch.sql.data.type.ExprType;
22-
import org.opensearch.sql.exception.SemanticCheckException;
23+
import org.opensearch.sql.exception.ExpressionEvaluationException;
24+
import org.opensearch.sql.utils.DateTimeFormatters;
2325

2426
/** Expression Timestamp Value. */
2527
@RequiredArgsConstructor
2628
public class ExprTimestampValue extends AbstractExprValue {
2729

2830
private final Instant timestamp;
2931

30-
/** Constructor. */
32+
/**
33+
* Constructor with timestamp string.
34+
*
35+
* @param timestamp a date or timestamp string (does not accept time string)
36+
*/
3137
public ExprTimestampValue(String timestamp) {
3238
try {
3339
this.timestamp =
34-
LocalDateTime.parse(timestamp, DATE_TIME_FORMATTER_VARIABLE_NANOS)
35-
.atZone(UTC_ZONE_ID)
36-
.toInstant();
40+
LocalDateTime.parse(timestamp, DateTimeFormatters.DATE_TIMESTAMP_FORMATTER)
41+
.toInstant(ZoneOffset.UTC);
3742
} catch (DateTimeParseException e) {
38-
throw new SemanticCheckException(
43+
throw new ExpressionEvaluationException(
3944
String.format(
4045
"timestamp:%s in unsupported format, please use 'yyyy-MM-dd HH:mm:ss[.SSSSSSSSS]'",
4146
timestamp));

0 commit comments

Comments
 (0)