diff --git a/core/src/main/java/com/amazon/opendistroforelasticsearch/sql/data/model/ExprDateValue.java b/core/src/main/java/com/amazon/opendistroforelasticsearch/sql/data/model/ExprDateValue.java index 52394dcd72..bebc8259af 100644 --- a/core/src/main/java/com/amazon/opendistroforelasticsearch/sql/data/model/ExprDateValue.java +++ b/core/src/main/java/com/amazon/opendistroforelasticsearch/sql/data/model/ExprDateValue.java @@ -23,6 +23,8 @@ import com.google.common.base.Objects; import java.time.Instant; import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; import java.time.ZoneId; import java.time.ZonedDateTime; import java.time.format.DateTimeFormatter; @@ -34,19 +36,15 @@ */ @RequiredArgsConstructor public class ExprDateValue extends AbstractExprValue { - /** - * todo. only support UTC now. - */ - private static final ZoneId ZONE = ZoneId.of("UTC"); - private final Instant date; + + private final LocalDate date; /** * Constructor of ExprDateValue. */ public ExprDateValue(String date) { try { - LocalDate localDate = LocalDate.parse(date); - this.date = localDate.atStartOfDay(ZONE).toInstant(); + this.date = LocalDate.parse(date); } catch (DateTimeParseException e) { throw new SemanticCheckException(String.format("date:%s in unsupported format, please use " + "yyyy-MM-dd", date)); @@ -55,7 +53,7 @@ public ExprDateValue(String date) { @Override public String value() { - return DateTimeFormatter.ISO_LOCAL_DATE.withZone(ZONE).format(date); + return DateTimeFormatter.ISO_LOCAL_DATE.format(date); } @Override @@ -64,8 +62,23 @@ public ExprType type() { } @Override - public ZonedDateTime dateValue() { - return date.atZone(ZONE); + public LocalDate dateValue() { + return date; + } + + @Override + public LocalTime timeValue() { + return LocalTime.of(0, 0, 0); + } + + @Override + public LocalDateTime datetimeValue() { + return LocalDateTime.of(date, timeValue()); + } + + @Override + public Instant timestampValue() { + return ZonedDateTime.of(date, timeValue(), ZoneId.systemDefault()).toInstant(); } @Override @@ -75,12 +88,12 @@ public String toString() { @Override public int compare(ExprValue other) { - return date.compareTo(other.dateValue().toInstant()); + return date.compareTo(other.dateValue()); } @Override public boolean equal(ExprValue other) { - return date.atZone(ZONE).equals(other.dateValue()); + return date.equals(other.dateValue()); } @Override diff --git a/core/src/main/java/com/amazon/opendistroforelasticsearch/sql/data/model/ExprDatetimeValue.java b/core/src/main/java/com/amazon/opendistroforelasticsearch/sql/data/model/ExprDatetimeValue.java new file mode 100644 index 0000000000..e932e6ca5d --- /dev/null +++ b/core/src/main/java/com/amazon/opendistroforelasticsearch/sql/data/model/ExprDatetimeValue.java @@ -0,0 +1,100 @@ +/* + * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +package com.amazon.opendistroforelasticsearch.sql.data.model; + +import com.amazon.opendistroforelasticsearch.sql.data.type.ExprCoreType; +import com.amazon.opendistroforelasticsearch.sql.data.type.ExprType; +import com.amazon.opendistroforelasticsearch.sql.exception.SemanticCheckException; +import com.google.common.base.Objects; +import java.time.Instant; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.ZoneId; +import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; +import java.time.format.DateTimeParseException; +import lombok.RequiredArgsConstructor; + +@RequiredArgsConstructor +public class ExprDatetimeValue extends AbstractExprValue { + private static final DateTimeFormatter formatter = DateTimeFormatter + .ofPattern("yyyy-MM-dd HH:mm:ss"); + private final LocalDateTime datetime; + + /** + * Constructor with datetime string as input. + */ + public ExprDatetimeValue(String datetime) { + try { + this.datetime = LocalDateTime.parse(datetime, formatter); + } catch (DateTimeParseException e) { + throw new SemanticCheckException(String.format("datetime:%s in unsupported format, please " + + "use yyyy-MM-dd HH:mm:ss", datetime)); + } + } + + @Override + public LocalDateTime datetimeValue() { + return datetime; + } + + @Override + public LocalDate dateValue() { + return datetime.toLocalDate(); + } + + @Override + public LocalTime timeValue() { + return datetime.toLocalTime(); + } + + @Override + public Instant timestampValue() { + return ZonedDateTime.of(datetime, ZoneId.of("UTC")).toInstant(); + } + + @Override + public int compare(ExprValue other) { + return datetime.compareTo(other.datetimeValue()); + } + + @Override + public boolean equal(ExprValue other) { + return datetime.equals(other.datetimeValue()); + } + + @Override + public String value() { + return String.format("%s %s", DateTimeFormatter.ISO_DATE.format(datetime), + DateTimeFormatter.ISO_TIME.format(datetime)); + } + + @Override + public ExprType type() { + return ExprCoreType.DATETIME; + } + + @Override + public String toString() { + return String.format("DATETIME '%s'", value()); + } + + @Override + public int hashCode() { + return Objects.hashCode(datetime); + } +} diff --git a/core/src/main/java/com/amazon/opendistroforelasticsearch/sql/data/model/ExprTimeValue.java b/core/src/main/java/com/amazon/opendistroforelasticsearch/sql/data/model/ExprTimeValue.java index 22917a80eb..762d48b883 100644 --- a/core/src/main/java/com/amazon/opendistroforelasticsearch/sql/data/model/ExprTimeValue.java +++ b/core/src/main/java/com/amazon/opendistroforelasticsearch/sql/data/model/ExprTimeValue.java @@ -33,10 +33,6 @@ */ @RequiredArgsConstructor public class ExprTimeValue extends AbstractExprValue { - /** - * todo. only support UTC now. - */ - private static final ZoneId ZONE = ZoneId.of("UTC"); private final LocalTime time; /** @@ -53,7 +49,7 @@ public ExprTimeValue(String time) { @Override public String value() { - return DateTimeFormatter.ISO_LOCAL_TIME.withZone(ZONE).format(time); + return DateTimeFormatter.ISO_LOCAL_TIME.format(time); } @Override diff --git a/core/src/main/java/com/amazon/opendistroforelasticsearch/sql/data/model/ExprTimestampValue.java b/core/src/main/java/com/amazon/opendistroforelasticsearch/sql/data/model/ExprTimestampValue.java index 2904457598..dbc40dd133 100644 --- a/core/src/main/java/com/amazon/opendistroforelasticsearch/sql/data/model/ExprTimestampValue.java +++ b/core/src/main/java/com/amazon/opendistroforelasticsearch/sql/data/model/ExprTimestampValue.java @@ -21,13 +21,14 @@ import com.amazon.opendistroforelasticsearch.sql.data.type.ExprType; import com.amazon.opendistroforelasticsearch.sql.exception.SemanticCheckException; import java.time.Instant; +import java.time.LocalDate; import java.time.LocalDateTime; +import java.time.LocalTime; import java.time.ZoneId; import java.time.format.DateTimeFormatter; import java.time.format.DateTimeParseException; import java.time.temporal.ChronoUnit; import java.util.Objects; -import lombok.EqualsAndHashCode; import lombok.RequiredArgsConstructor; /** @@ -74,6 +75,21 @@ public Instant timestampValue() { return timestamp; } + @Override + public LocalDate dateValue() { + return timestamp.atZone(ZONE).toLocalDate(); + } + + @Override + public LocalTime timeValue() { + return timestamp.atZone(ZONE).toLocalTime(); + } + + @Override + public LocalDateTime datetimeValue() { + return timestamp.atZone(ZONE).toLocalDateTime(); + } + @Override public String toString() { return String.format("TIMESTAMP '%s'", value()); diff --git a/core/src/main/java/com/amazon/opendistroforelasticsearch/sql/data/model/ExprValue.java b/core/src/main/java/com/amazon/opendistroforelasticsearch/sql/data/model/ExprValue.java index bc319a08c5..266de7628a 100644 --- a/core/src/main/java/com/amazon/opendistroforelasticsearch/sql/data/model/ExprValue.java +++ b/core/src/main/java/com/amazon/opendistroforelasticsearch/sql/data/model/ExprValue.java @@ -21,6 +21,8 @@ import com.amazon.opendistroforelasticsearch.sql.storage.bindingtuple.BindingTuple; import java.io.Serializable; import java.time.Instant; +import java.time.LocalDate; +import java.time.LocalDateTime; import java.time.LocalTime; import java.time.ZonedDateTime; import java.time.temporal.TemporalAmount; @@ -142,11 +144,19 @@ default LocalTime timeValue() { /** * Get date value. */ - default ZonedDateTime dateValue() { + default LocalDate dateValue() { throw new ExpressionEvaluationException( "invalid to get dateValue from value of type " + type()); } + /** + * Get datetime value. + */ + default LocalDateTime datetimeValue() { + throw new ExpressionEvaluationException( + "invalid to get datetimeValue from value of type " + type()); + } + /** * Get interval value. */ diff --git a/core/src/main/java/com/amazon/opendistroforelasticsearch/sql/data/model/ExprValueUtils.java b/core/src/main/java/com/amazon/opendistroforelasticsearch/sql/data/model/ExprValueUtils.java index fa3f7fffe7..3fddbdde7f 100644 --- a/core/src/main/java/com/amazon/opendistroforelasticsearch/sql/data/model/ExprValueUtils.java +++ b/core/src/main/java/com/amazon/opendistroforelasticsearch/sql/data/model/ExprValueUtils.java @@ -17,6 +17,11 @@ import com.amazon.opendistroforelasticsearch.sql.data.type.ExprCoreType; import com.amazon.opendistroforelasticsearch.sql.exception.ExpressionEvaluationException; +import com.google.common.annotations.VisibleForTesting; +import java.time.Instant; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; import java.time.ZonedDateTime; import java.time.temporal.TemporalAmount; import java.util.ArrayList; @@ -128,6 +133,8 @@ public static ExprValue fromObjectValue(Object o, ExprCoreType type) { return new ExprDateValue((String)o); case TIME: return new ExprTimeValue((String)o); + case DATETIME: + return new ExprDatetimeValue((String)o); default: return fromObjectValue(o); } @@ -164,15 +171,4 @@ public static Map getTupleValue(ExprValue exprValue) { public static Boolean getBooleanValue(ExprValue exprValue) { return exprValue.booleanValue(); } - - /** - * Get {@link ZonedDateTime} from ExprValue of Date type. - */ - public static ZonedDateTime getDateValue(ExprValue exprValue) { - return exprValue.dateValue(); - } - - public static TemporalAmount getIntervalValue(ExprValue exprValue) { - return exprValue.intervalValue(); - } } diff --git a/core/src/main/java/com/amazon/opendistroforelasticsearch/sql/data/type/ExprCoreType.java b/core/src/main/java/com/amazon/opendistroforelasticsearch/sql/data/type/ExprCoreType.java index 9090673813..3c59ce6e18 100644 --- a/core/src/main/java/com/amazon/opendistroforelasticsearch/sql/data/type/ExprCoreType.java +++ b/core/src/main/java/com/amazon/opendistroforelasticsearch/sql/data/type/ExprCoreType.java @@ -57,6 +57,7 @@ public enum ExprCoreType implements ExprType { TIMESTAMP, DATE, TIME, + DATETIME, INTERVAL, /** diff --git a/core/src/main/java/com/amazon/opendistroforelasticsearch/sql/expression/DSL.java b/core/src/main/java/com/amazon/opendistroforelasticsearch/sql/expression/DSL.java index 86f7978427..ff53ddd629 100644 --- a/core/src/main/java/com/amazon/opendistroforelasticsearch/sql/expression/DSL.java +++ b/core/src/main/java/com/amazon/opendistroforelasticsearch/sql/expression/DSL.java @@ -236,8 +236,19 @@ public FunctionExpression multiply(Expression... expressions) { } public FunctionExpression dayofmonth(Expression... expressions) { - return (FunctionExpression) - repository.compile(BuiltinFunctionName.DAYOFMONTH.getName(), Arrays.asList(expressions)); + return function(BuiltinFunctionName.DAYOFMONTH, expressions); + } + + public FunctionExpression date(Expression... expressions) { + return function(BuiltinFunctionName.DATE, expressions); + } + + public FunctionExpression time(Expression... expressions) { + return function(BuiltinFunctionName.TIME, expressions); + } + + public FunctionExpression timestamp(Expression... expressions) { + return function(BuiltinFunctionName.TIMESTAMP, expressions); } public FunctionExpression divide(Expression... expressions) { diff --git a/core/src/main/java/com/amazon/opendistroforelasticsearch/sql/expression/datetime/DateTimeFunction.java b/core/src/main/java/com/amazon/opendistroforelasticsearch/sql/expression/datetime/DateTimeFunction.java index 4b96eb80a6..ea4716e9a2 100644 --- a/core/src/main/java/com/amazon/opendistroforelasticsearch/sql/expression/datetime/DateTimeFunction.java +++ b/core/src/main/java/com/amazon/opendistroforelasticsearch/sql/expression/datetime/DateTimeFunction.java @@ -17,15 +17,26 @@ package com.amazon.opendistroforelasticsearch.sql.expression.datetime; -import static com.amazon.opendistroforelasticsearch.sql.data.model.ExprValueUtils.getDateValue; +import static com.amazon.opendistroforelasticsearch.sql.data.model.ExprValueUtils.getStringValue; import static com.amazon.opendistroforelasticsearch.sql.data.type.ExprCoreType.DATE; +import static com.amazon.opendistroforelasticsearch.sql.data.type.ExprCoreType.DATETIME; import static com.amazon.opendistroforelasticsearch.sql.data.type.ExprCoreType.INTEGER; +import static com.amazon.opendistroforelasticsearch.sql.data.type.ExprCoreType.STRING; +import static com.amazon.opendistroforelasticsearch.sql.data.type.ExprCoreType.TIME; +import static com.amazon.opendistroforelasticsearch.sql.data.type.ExprCoreType.TIMESTAMP; import static com.amazon.opendistroforelasticsearch.sql.expression.function.BuiltinFunctionName.DAYOFMONTH; +import static com.amazon.opendistroforelasticsearch.sql.expression.function.FunctionDSL.define; +import static com.amazon.opendistroforelasticsearch.sql.expression.function.FunctionDSL.impl; +import static com.amazon.opendistroforelasticsearch.sql.expression.function.FunctionDSL.nullMissingHandling; +import com.amazon.opendistroforelasticsearch.sql.data.model.ExprDateValue; import com.amazon.opendistroforelasticsearch.sql.data.model.ExprIntegerValue; +import com.amazon.opendistroforelasticsearch.sql.data.model.ExprStringValue; +import com.amazon.opendistroforelasticsearch.sql.data.model.ExprTimeValue; +import com.amazon.opendistroforelasticsearch.sql.data.model.ExprTimestampValue; import com.amazon.opendistroforelasticsearch.sql.data.model.ExprValue; +import com.amazon.opendistroforelasticsearch.sql.expression.function.BuiltinFunctionName; import com.amazon.opendistroforelasticsearch.sql.expression.function.BuiltinFunctionRepository; -import com.amazon.opendistroforelasticsearch.sql.expression.function.FunctionDSL; import com.amazon.opendistroforelasticsearch.sql.expression.function.FunctionResolver; import lombok.experimental.UtilityClass; @@ -36,26 +47,113 @@ */ @UtilityClass public class DateTimeFunction { + /** + * Register Date and Time Functions. + * + * @param repository {@link BuiltinFunctionRepository}. + */ public void register(BuiltinFunctionRepository repository) { + repository.register(date()); repository.register(dayOfMonth()); + repository.register(time()); + repository.register(timestamp()); + } + + /** + * Extracts the date part of a date and time value. + * Also to construct a date type. The supported signatures: + * STRING/DATE/DATETIME/TIMESTAMP -> DATE + */ + private FunctionResolver date() { + return define(BuiltinFunctionName.DATE.getName(), + impl(nullMissingHandling(DateTimeFunction::exprDate), DATE, STRING), + impl(nullMissingHandling(DateTimeFunction::exprDate), DATE, DATE), + impl(nullMissingHandling(DateTimeFunction::exprDate), DATE, DATETIME), + impl(nullMissingHandling(DateTimeFunction::exprDate), DATE, TIMESTAMP)); } /** * DAYOFMONTH(DATE). return the day of the month (1-31). */ private FunctionResolver dayOfMonth() { - return FunctionDSL.define(DAYOFMONTH.getName(), - FunctionDSL.impl(FunctionDSL.nullMissingHandling(DateTimeFunction::exprDayOfMonth), + return define(DAYOFMONTH.getName(), + impl(nullMissingHandling(DateTimeFunction::exprDayOfMonth), INTEGER, DATE) ); } + /** + * Extracts the time part of a date and time value. + * Also to construct a time type. The supported signatures: + * STRING/DATE/DATETIME/TIME/TIMESTAMP -> TIME + */ + private FunctionResolver time() { + return define(BuiltinFunctionName.TIME.getName(), + impl(nullMissingHandling(DateTimeFunction::exprTime), TIME, STRING), + impl(nullMissingHandling(DateTimeFunction::exprTime), TIME, DATE), + impl(nullMissingHandling(DateTimeFunction::exprTime), TIME, DATETIME), + impl(nullMissingHandling(DateTimeFunction::exprTime), TIME, TIME), + impl(nullMissingHandling(DateTimeFunction::exprTime), TIME, TIMESTAMP)); + } + + /** + * Extracts the timestamp of a date and time value. + * Also to construct a date type. The supported signatures: + * STRING/DATE/DATETIME/TIMESTAMP -> DATE + */ + private FunctionResolver timestamp() { + return define(BuiltinFunctionName.TIMESTAMP.getName(), + impl(nullMissingHandling(DateTimeFunction::exprTimestamp), TIMESTAMP, STRING), + impl(nullMissingHandling(DateTimeFunction::exprTimestamp), TIMESTAMP, DATE), + impl(nullMissingHandling(DateTimeFunction::exprTimestamp), TIMESTAMP, DATETIME), + impl(nullMissingHandling(DateTimeFunction::exprTimestamp), TIMESTAMP, TIMESTAMP)); + } + + /** + * Date implementation for ExprValue. + * @param exprValue ExprValue of Date type or String type. + * @return ExprValue. + */ + private ExprValue exprDate(ExprValue exprValue) { + if (exprValue instanceof ExprStringValue) { + return new ExprDateValue(exprValue.stringValue()); + } else { + return new ExprDateValue(exprValue.dateValue()); + } + } + /** * Day of Month implementation for ExprValue. * @param date ExprValue of Date type. * @return ExprValue. */ private ExprValue exprDayOfMonth(ExprValue date) { - return new ExprIntegerValue(getDateValue(date).getMonthValue()); + return new ExprIntegerValue(date.dateValue().getMonthValue()); + } + + /** + * Time implementation for ExprValue. + * @param exprValue ExprValue of Time type or String. + * @return ExprValue. + */ + private ExprValue exprTime(ExprValue exprValue) { + if (exprValue instanceof ExprStringValue) { + return new ExprTimeValue(exprValue.stringValue()); + } else { + return new ExprTimeValue(exprValue.timeValue()); + } + } + + /** + * Timestamp implementation for ExprValue. + * @param exprValue ExprValue of Timestamp type or String type. + * @return ExprValue. + */ + private ExprValue exprTimestamp(ExprValue exprValue) { + if (exprValue instanceof ExprStringValue) { + return new ExprTimestampValue(exprValue.stringValue()); + } else { + return new ExprTimestampValue(exprValue.timestampValue()); + } } } diff --git a/core/src/main/java/com/amazon/opendistroforelasticsearch/sql/expression/function/BuiltinFunctionName.java b/core/src/main/java/com/amazon/opendistroforelasticsearch/sql/expression/function/BuiltinFunctionName.java index 3103999f88..2edbe11d11 100644 --- a/core/src/main/java/com/amazon/opendistroforelasticsearch/sql/expression/function/BuiltinFunctionName.java +++ b/core/src/main/java/com/amazon/opendistroforelasticsearch/sql/expression/function/BuiltinFunctionName.java @@ -48,6 +48,14 @@ public enum BuiltinFunctionName { SIN(FunctionName.of("sin")), TAN(FunctionName.of("tan")), + /** + * Date and Time Functions. + */ + DATE(FunctionName.of("date")), + DAYOFMONTH(FunctionName.of("dayofmonth")), + TIME(FunctionName.of("time")), + TIMESTAMP(FunctionName.of("timestamp")), + /** * Text Functions. */ @@ -78,11 +86,6 @@ public enum BuiltinFunctionName { LIKE(FunctionName.of("like")), NOT_LIKE(FunctionName.of("not like")), - /** - * Date and Time Functions. - */ - DAYOFMONTH(FunctionName.of("dayofmonth")), - /** * Aggregation Function. */ diff --git a/core/src/test/java/com/amazon/opendistroforelasticsearch/sql/data/model/DateTimeValueTest.java b/core/src/test/java/com/amazon/opendistroforelasticsearch/sql/data/model/DateTimeValueTest.java index be4ad7f7fc..e77936dc25 100644 --- a/core/src/test/java/com/amazon/opendistroforelasticsearch/sql/data/model/DateTimeValueTest.java +++ b/core/src/test/java/com/amazon/opendistroforelasticsearch/sql/data/model/DateTimeValueTest.java @@ -25,10 +25,11 @@ import com.amazon.opendistroforelasticsearch.sql.exception.ExpressionEvaluationException; import com.amazon.opendistroforelasticsearch.sql.exception.SemanticCheckException; -import java.time.Instant; import java.time.LocalDate; +import java.time.LocalDateTime; import java.time.LocalTime; import java.time.ZoneId; +import java.time.ZonedDateTime; import org.junit.jupiter.api.Test; public class DateTimeValueTest { @@ -41,6 +42,8 @@ public void timeValueInterfaceTest() { assertEquals(LocalTime.parse("01:01:01"), timeValue.timeValue()); assertEquals("01:01:01", timeValue.value()); assertEquals("TIME '01:01:01'", timeValue.toString()); + assertThrows(ExpressionEvaluationException.class, () -> integerValue(1).timeValue(), + "invalid to get timeValue from value of type INTEGER"); } @Test @@ -48,24 +51,46 @@ public void timestampValueInterfaceTest() { ExprValue timestampValue = new ExprTimestampValue("2020-07-07 01:01:01"); assertEquals(TIMESTAMP, timestampValue.type()); - assertEquals(Instant.ofEpochSecond(1594083661), timestampValue.timestampValue()); + assertEquals(ZonedDateTime.of(LocalDateTime.parse("2020-07-07T01:01:01"), + ZoneId.of("UTC")).toInstant(), timestampValue.timestampValue()); assertEquals("2020-07-07 01:01:01", timestampValue.value()); assertEquals("TIMESTAMP '2020-07-07 01:01:01'", timestampValue.toString()); + assertEquals(LocalDate.parse("2020-07-07"), timestampValue.dateValue()); + assertEquals(LocalTime.parse("01:01:01"), timestampValue.timeValue()); + assertEquals(LocalDateTime.parse("2020-07-07T01:01:01"), timestampValue.datetimeValue()); + assertThrows(ExpressionEvaluationException.class, () -> integerValue(1).timestampValue(), + "invalid to get timestampValue from value of type INTEGER"); } @Test public void dateValueInterfaceTest() { ExprValue dateValue = new ExprDateValue("2012-07-07"); - assertEquals(LocalDate.parse("2012-07-07").atStartOfDay(ZoneId.of("UTC")), - dateValue.dateValue()); + assertEquals(LocalDate.parse("2012-07-07"), dateValue.dateValue()); + assertEquals(LocalTime.parse("00:00:00"), dateValue.timeValue()); + assertEquals(LocalDateTime.parse("2012-07-07T00:00:00"), dateValue.datetimeValue()); + assertEquals(ZonedDateTime.of(LocalDateTime.parse("2012-07-07T00:00:00"), + ZoneId.systemDefault()).toInstant(), dateValue.timestampValue()); ExpressionEvaluationException exception = - assertThrows(ExpressionEvaluationException.class, - () -> ExprValueUtils.getDateValue(integerValue(1))); + assertThrows(ExpressionEvaluationException.class, () -> integerValue(1).dateValue()); assertEquals("invalid to get dateValue from value of type INTEGER", exception.getMessage()); } + @Test + public void datetimeValueInterfaceTest() { + ExprValue datetimeValue = new ExprDatetimeValue("2020-08-17 19:44:00"); + + assertEquals(LocalDateTime.parse("2020-08-17T19:44:00"), datetimeValue.datetimeValue()); + assertEquals(LocalDate.parse("2020-08-17"), datetimeValue.dateValue()); + assertEquals(LocalTime.parse("19:44:00"), datetimeValue.timeValue()); + assertEquals(ZonedDateTime.of(LocalDateTime.parse("2020-08-17T19:44:00"), + ZoneId.of("UTC")).toInstant(), datetimeValue.timestampValue()); + assertEquals("DATETIME '2020-08-17 19:44:00'", datetimeValue.toString()); + assertThrows(ExpressionEvaluationException.class, () -> integerValue(1).datetimeValue(), + "invalid to get datetimeValue from value of type INTEGER"); + } + @Test public void dateInUnsupportedFormat() { SemanticCheckException exception = @@ -91,4 +116,14 @@ public void timestampInUnsupportedFormat() { "timestamp:2020-07-07T01:01:01Z in unsupported format, please use yyyy-MM-dd HH:mm:ss", exception.getMessage()); } + + @Test + public void datetimeInUnsupportedFormat() { + SemanticCheckException exception = + assertThrows(SemanticCheckException.class, + () -> new ExprDatetimeValue("2020-07-07T01:01:01Z")); + assertEquals( + "datetime:2020-07-07T01:01:01Z in unsupported format, please use yyyy-MM-dd HH:mm:ss", + exception.getMessage()); + } } diff --git a/core/src/test/java/com/amazon/opendistroforelasticsearch/sql/data/model/ExprIntervalValueTest.java b/core/src/test/java/com/amazon/opendistroforelasticsearch/sql/data/model/ExprIntervalValueTest.java index e691fddada..b376513337 100644 --- a/core/src/test/java/com/amazon/opendistroforelasticsearch/sql/data/model/ExprIntervalValueTest.java +++ b/core/src/test/java/com/amazon/opendistroforelasticsearch/sql/data/model/ExprIntervalValueTest.java @@ -29,7 +29,7 @@ public class ExprIntervalValueTest { @Test public void equals_to_self() { ExprValue interval = ExprValueUtils.intervalValue(Duration.ofNanos(1000)); - assertEquals(ExprValueUtils.getIntervalValue(interval), Duration.ofNanos(1000)); + assertEquals(interval.intervalValue(), Duration.ofNanos(1000)); } @Test @@ -54,6 +54,13 @@ public void invalid_compare() { String.format("invalid to compare intervals with units %s and %s", v1.unit(), v2.unit())); } + @Test + public void invalid_get_value() { + ExprDateValue value = new ExprDateValue("2020-08-20"); + assertThrows(ExpressionEvaluationException.class, value::intervalValue, + String.format("invalid to get intervalValue from value of type %s", value.type())); + } + @Test public void value() { ExprValue value = new ExprIntervalValue(Period.ofWeeks(1)); diff --git a/core/src/test/java/com/amazon/opendistroforelasticsearch/sql/data/model/ExprValueCompareTest.java b/core/src/test/java/com/amazon/opendistroforelasticsearch/sql/data/model/ExprValueCompareTest.java index 1ef39a79bd..c37b404e29 100644 --- a/core/src/test/java/com/amazon/opendistroforelasticsearch/sql/data/model/ExprValueCompareTest.java +++ b/core/src/test/java/com/amazon/opendistroforelasticsearch/sql/data/model/ExprValueCompareTest.java @@ -41,6 +41,19 @@ public void dateValueCompare() { assertEquals(-1, new ExprDateValue("2012-08-07").compareTo(new ExprDateValue("2012-08-08"))); } + @Test + public void datetimeValueCompare() { + assertEquals(0, + new ExprDatetimeValue("2012-08-07 18:00:00") + .compareTo(new ExprDatetimeValue("2012-08-07 18:00:00"))); + assertEquals(1, + new ExprDatetimeValue("2012-08-07 19:00:00") + .compareTo(new ExprDatetimeValue("2012-08-07 18:00:00"))); + assertEquals(-1, + new ExprDatetimeValue("2012-08-07 18:00:00") + .compareTo(new ExprDatetimeValue("2012-08-07 19:00:00"))); + } + @Test public void timestampValueCompare() { assertEquals(0, diff --git a/core/src/test/java/com/amazon/opendistroforelasticsearch/sql/data/model/ExprValueUtilsTest.java b/core/src/test/java/com/amazon/opendistroforelasticsearch/sql/data/model/ExprValueUtilsTest.java index dacc7f85ff..6400e80153 100644 --- a/core/src/test/java/com/amazon/opendistroforelasticsearch/sql/data/model/ExprValueUtilsTest.java +++ b/core/src/test/java/com/amazon/opendistroforelasticsearch/sql/data/model/ExprValueUtilsTest.java @@ -16,15 +16,19 @@ package com.amazon.opendistroforelasticsearch.sql.data.model; import static com.amazon.opendistroforelasticsearch.sql.data.model.ExprValueUtils.integerValue; +import static com.amazon.opendistroforelasticsearch.sql.data.type.ExprCoreType.ARRAY; +import static com.amazon.opendistroforelasticsearch.sql.data.type.ExprCoreType.BOOLEAN; import static com.amazon.opendistroforelasticsearch.sql.data.type.ExprCoreType.DATE; +import static com.amazon.opendistroforelasticsearch.sql.data.type.ExprCoreType.DATETIME; import static com.amazon.opendistroforelasticsearch.sql.data.type.ExprCoreType.INTERVAL; +import static com.amazon.opendistroforelasticsearch.sql.data.type.ExprCoreType.STRING; +import static com.amazon.opendistroforelasticsearch.sql.data.type.ExprCoreType.STRUCT; import static com.amazon.opendistroforelasticsearch.sql.data.type.ExprCoreType.TIME; import static com.amazon.opendistroforelasticsearch.sql.data.type.ExprCoreType.TIMESTAMP; import static org.hamcrest.MatcherAssert.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotEquals; import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assertions.assertTrue; import com.amazon.opendistroforelasticsearch.sql.data.type.ExprCoreType; import com.amazon.opendistroforelasticsearch.sql.exception.ExpressionEvaluationException; @@ -36,8 +40,10 @@ import java.time.Duration; import java.time.Instant; import java.time.LocalDate; +import java.time.LocalDateTime; import java.time.LocalTime; import java.time.ZoneId; +import java.time.ZonedDateTime; import java.util.AbstractMap; import java.util.ArrayList; import java.util.Arrays; @@ -65,16 +71,16 @@ public class ExprValueUtilsTest { private static List numberValues = Stream.of(1, 1L, 1f, 1D) .map(ExprValueUtils::fromObjectValue).collect(Collectors.toList()); - private static List nonNumberValues = - Arrays.asList( - new ExprStringValue("1"), - ExprBooleanValue.of(true), - new ExprCollectionValue(ImmutableList.of(new ExprIntegerValue(1))), - new ExprTupleValue(testTuple), - new ExprDateValue("2012-08-07"), - new ExprTimeValue("18:00:00"), - new ExprTimestampValue("2012-08-07 18:00:00"), - new ExprIntervalValue(Duration.ofSeconds(100))); + private static List nonNumberValues = Arrays.asList( + new ExprStringValue("1"), + ExprBooleanValue.of(true), + new ExprCollectionValue(ImmutableList.of(new ExprIntegerValue(1))), + new ExprTupleValue(testTuple), + new ExprDateValue("2012-08-07"), + new ExprTimeValue("18:00:00"), + new ExprDatetimeValue("2012-08-07 18:00:00"), + new ExprTimestampValue("2012-08-07 18:00:00"), + new ExprIntervalValue(Duration.ofSeconds(100))); private static List allValues = Lists.newArrayList(Iterables.concat(numberValues, nonNumberValues)); @@ -88,30 +94,35 @@ public class ExprValueUtilsTest { ExprValueUtils::getStringValue, ExprValueUtils::getBooleanValue, ExprValueUtils::getCollectionValue, - ExprValueUtils::getTupleValue, + ExprValueUtils::getTupleValue + ); + private static List> dateAndTimeValueExtractor = Arrays.asList( ExprValue::dateValue, ExprValue::timeValue, + ExprValue::datetimeValue, ExprValue::timestampValue, ExprValue::intervalValue); private static List> allValueExtractor = Lists.newArrayList( - Iterables.concat(numberValueExtractor, nonNumberValueExtractor)); + Iterables.concat(numberValueExtractor, nonNumberValueExtractor, dateAndTimeValueExtractor)); private static List numberTypes = Arrays.asList(ExprCoreType.INTEGER, ExprCoreType.LONG, ExprCoreType.FLOAT, ExprCoreType.DOUBLE); private static List nonNumberTypes = - Arrays.asList(ExprCoreType.STRING, ExprCoreType.BOOLEAN, ExprCoreType.ARRAY, - ExprCoreType.STRUCT, DATE, TIME, TIMESTAMP, INTERVAL); + Arrays.asList(STRING, BOOLEAN, ARRAY, STRUCT); + private static List dateAndTimeTypes = + Arrays.asList(DATE, TIME, DATETIME, TIMESTAMP, INTERVAL); private static List allTypes = - Lists.newArrayList(Iterables.concat(numberTypes, nonNumberTypes)); + Lists.newArrayList(Iterables.concat(numberTypes, nonNumberTypes, dateAndTimeTypes)); private static Stream getValueTestArgumentStream() { List expectedValues = Arrays.asList(1, 1L, 1f, 1D, "1", true, Arrays.asList(integerValue(1)), ImmutableMap.of("1", integerValue(1)), - LocalDate.parse("2012-08-07").atStartOfDay(ZoneId.of("UTC")), + LocalDate.parse("2012-08-07"), LocalTime.parse("18:00:00"), - Instant.ofEpochSecond(1344362400), + LocalDateTime.parse("2012-08-07T18:00:00"), + ZonedDateTime.of(LocalDateTime.parse("2012-08-07T18:00:00"), ZoneId.of("UTC")).toInstant(), Duration.ofSeconds(100) ); Stream.Builder builder = Stream.builder(); @@ -215,7 +226,7 @@ public void unSupportedObject() { @Test public void bindingTuples() { for (ExprValue value : allValues) { - if (ExprCoreType.STRUCT == value.type()) { + if (STRUCT == value.type()) { assertNotEquals(BindingTuple.EMPTY, value.bindingTuples()); } else { assertEquals(BindingTuple.EMPTY, value.bindingTuples()); @@ -229,6 +240,8 @@ public void constructDateAndTimeValue() { ExprValueUtils.fromObjectValue("2012-07-07", DATE)); assertEquals(new ExprTimeValue("01:01:01"), ExprValueUtils.fromObjectValue("01:01:01", TIME)); + assertEquals(new ExprDatetimeValue("2012-07-07 01:01:01"), + ExprValueUtils.fromObjectValue("2012-07-07 01:01:01", DATETIME)); assertEquals(new ExprTimestampValue("2012-07-07 01:01:01"), ExprValueUtils.fromObjectValue("2012-07-07 01:01:01", TIMESTAMP)); } @@ -245,6 +258,8 @@ public void hashCodeTest() { new ExprDateValue("2012-08-07").hashCode()); assertEquals(new ExprTimeValue("18:00:00").hashCode(), new ExprTimeValue("18:00:00").hashCode()); + assertEquals(new ExprDatetimeValue("2012-08-07 18:00:00").hashCode(), + new ExprDatetimeValue("2012-08-07 18:00:00").hashCode()); assertEquals(new ExprTimestampValue("2012-08-07 18:00:00").hashCode(), new ExprTimestampValue("2012-08-07 18:00:00").hashCode()); } diff --git a/core/src/test/java/com/amazon/opendistroforelasticsearch/sql/expression/datetime/DateTimeFunctionTest.java b/core/src/test/java/com/amazon/opendistroforelasticsearch/sql/expression/datetime/DateTimeFunctionTest.java index 32e4f9b257..1f6e35ba16 100644 --- a/core/src/test/java/com/amazon/opendistroforelasticsearch/sql/expression/datetime/DateTimeFunctionTest.java +++ b/core/src/test/java/com/amazon/opendistroforelasticsearch/sql/expression/datetime/DateTimeFunctionTest.java @@ -21,11 +21,17 @@ import static com.amazon.opendistroforelasticsearch.sql.data.model.ExprValueUtils.missingValue; import static com.amazon.opendistroforelasticsearch.sql.data.model.ExprValueUtils.nullValue; import static com.amazon.opendistroforelasticsearch.sql.data.type.ExprCoreType.DATE; +import static com.amazon.opendistroforelasticsearch.sql.data.type.ExprCoreType.DATETIME; import static com.amazon.opendistroforelasticsearch.sql.data.type.ExprCoreType.INTEGER; +import static com.amazon.opendistroforelasticsearch.sql.data.type.ExprCoreType.TIME; +import static com.amazon.opendistroforelasticsearch.sql.data.type.ExprCoreType.TIMESTAMP; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.mockito.Mockito.when; import com.amazon.opendistroforelasticsearch.sql.data.model.ExprDateValue; +import com.amazon.opendistroforelasticsearch.sql.data.model.ExprDatetimeValue; +import com.amazon.opendistroforelasticsearch.sql.data.model.ExprTimeValue; +import com.amazon.opendistroforelasticsearch.sql.data.model.ExprTimestampValue; import com.amazon.opendistroforelasticsearch.sql.data.model.ExprValue; import com.amazon.opendistroforelasticsearch.sql.expression.DSL; import com.amazon.opendistroforelasticsearch.sql.expression.Expression; @@ -68,6 +74,60 @@ public void dayOfMonth() { assertEquals(missingValue(), eval(dsl.dayofmonth(missingRef))); } + @Test + public void date() { + when(nullRef.type()).thenReturn(DATE); + when(missingRef.type()).thenReturn(DATE); + assertEquals(nullValue(), eval(dsl.date(nullRef))); + assertEquals(missingValue(), eval(dsl.date(missingRef))); + + FunctionExpression expr = dsl.date(DSL.literal("2020-08-17")); + assertEquals(DATE, expr.type()); + assertEquals(new ExprDateValue("2020-08-17"), eval(expr)); + assertEquals("date(\"2020-08-17\")", expr.toString()); + + expr = dsl.date(DSL.literal(new ExprDateValue("2020-08-17"))); + assertEquals(DATE, expr.type()); + assertEquals(new ExprDateValue("2020-08-17"), eval(expr)); + assertEquals("date(DATE '2020-08-17')", expr.toString()); + } + + @Test + public void time() { + when(nullRef.type()).thenReturn(TIME); + when(missingRef.type()).thenReturn(TIME); + assertEquals(nullValue(), eval(dsl.time(nullRef))); + assertEquals(missingValue(), eval(dsl.time(missingRef))); + + FunctionExpression expr = dsl.time(DSL.literal("01:01:01")); + assertEquals(TIME, expr.type()); + assertEquals(new ExprTimeValue("01:01:01"), eval(expr)); + assertEquals("time(\"01:01:01\")", expr.toString()); + + expr = dsl.time(DSL.literal(new ExprTimeValue("01:01:01"))); + assertEquals(TIME, expr.type()); + assertEquals(new ExprTimeValue("01:01:01"), eval(expr)); + assertEquals("time(TIME '01:01:01')", expr.toString()); + } + + @Test + public void timestamp() { + when(nullRef.type()).thenReturn(TIMESTAMP); + when(missingRef.type()).thenReturn(TIMESTAMP); + assertEquals(nullValue(), eval(dsl.timestamp(nullRef))); + assertEquals(missingValue(), eval(dsl.timestamp(missingRef))); + + FunctionExpression expr = dsl.timestamp(DSL.literal("2020-08-17 01:01:01")); + assertEquals(TIMESTAMP, expr.type()); + assertEquals(new ExprTimestampValue("2020-08-17 01:01:01"), expr.valueOf(env)); + assertEquals("timestamp(\"2020-08-17 01:01:01\")", expr.toString()); + + expr = dsl.timestamp(DSL.literal(new ExprTimestampValue("2020-08-17 01:01:01"))); + assertEquals(TIMESTAMP, expr.type()); + assertEquals(new ExprTimestampValue("2020-08-17 01:01:01"), expr.valueOf(env)); + assertEquals("timestamp(TIMESTAMP '2020-08-17 01:01:01')", expr.toString()); + } + private ExprValue eval(Expression expression) { return expression.valueOf(env); } diff --git a/ppl/src/main/antlr/OpenDistroPPLLexer.g4 b/ppl/src/main/antlr/OpenDistroPPLLexer.g4 index 07a7699ab5..663210e019 100644 --- a/ppl/src/main/antlr/OpenDistroPPLLexer.g4 +++ b/ppl/src/main/antlr/OpenDistroPPLLexer.g4 @@ -67,7 +67,8 @@ TRUE: 'TRUE'; FALSE: 'FALSE'; LIKE: 'LIKE'; -// INTERVAL AND UNIT KEYWORDS +// DATETIME, INTERVAL AND UNIT KEYWORDS +DATETIME: 'DATETIME'; INTERVAL: 'INTERVAL'; MICROSECOND: 'MICROSECOND'; SECOND: 'SECOND'; @@ -191,6 +192,11 @@ RADIANS: 'RADIANS'; SIN: 'SIN'; TAN: 'TAN'; +// DATE AND TIME FUNCTIONS +DATE: 'DATE'; +TIME: 'TIME'; +TIMESTAMP: 'TIMESTAMP'; + // LITERALS AND VALUES //STRING_LITERAL: DQUOTA_STRING | SQUOTA_STRING | BQUOTA_STRING; ID: ID_LITERAL; diff --git a/ppl/src/main/antlr/OpenDistroPPLParser.g4 b/ppl/src/main/antlr/OpenDistroPPLParser.g4 index 3e57f3ab20..9031d1aedd 100644 --- a/ppl/src/main/antlr/OpenDistroPPLParser.g4 +++ b/ppl/src/main/antlr/OpenDistroPPLParser.g4 @@ -213,7 +213,7 @@ trigonometricFunctionName ; dateAndTimeFunctionBase - : + : DATE | TIME | TIMESTAMP ; textFunctionBase diff --git a/sql/src/main/antlr/OpenDistroSQLParser.g4 b/sql/src/main/antlr/OpenDistroSQLParser.g4 index e8cbef1908..95672097a1 100644 --- a/sql/src/main/antlr/OpenDistroSQLParser.g4 +++ b/sql/src/main/antlr/OpenDistroSQLParser.g4 @@ -211,7 +211,7 @@ trigonometricFunctionName ; dateTimeFunctionName - : DAYOFMONTH + : DAYOFMONTH | DATE | TIME | TIMESTAMP ; functionArgs