diff --git a/core/src/main/java/org/opensearch/sql/ast/expression/Cast.java b/core/src/main/java/org/opensearch/sql/ast/expression/Cast.java index bd57d0a8a6..099bd23a58 100644 --- a/core/src/main/java/org/opensearch/sql/ast/expression/Cast.java +++ b/core/src/main/java/org/opensearch/sql/ast/expression/Cast.java @@ -31,6 +31,7 @@ import static org.opensearch.sql.expression.function.BuiltinFunctionName.CAST_TO_BOOLEAN; import static org.opensearch.sql.expression.function.BuiltinFunctionName.CAST_TO_BYTE; import static org.opensearch.sql.expression.function.BuiltinFunctionName.CAST_TO_DATE; +import static org.opensearch.sql.expression.function.BuiltinFunctionName.CAST_TO_DATETIME; import static org.opensearch.sql.expression.function.BuiltinFunctionName.CAST_TO_DOUBLE; import static org.opensearch.sql.expression.function.BuiltinFunctionName.CAST_TO_FLOAT; import static org.opensearch.sql.expression.function.BuiltinFunctionName.CAST_TO_INT; @@ -77,6 +78,7 @@ public class Cast extends UnresolvedExpression { .put("date", CAST_TO_DATE.getName()) .put("time", CAST_TO_TIME.getName()) .put("timestamp", CAST_TO_TIMESTAMP.getName()) + .put("datetime", CAST_TO_DATETIME.getName()) .build(); /** diff --git a/core/src/main/java/org/opensearch/sql/data/type/ExprCoreType.java b/core/src/main/java/org/opensearch/sql/data/type/ExprCoreType.java index 92da09490c..4fa023bd06 100644 --- a/core/src/main/java/org/opensearch/sql/data/type/ExprCoreType.java +++ b/core/src/main/java/org/opensearch/sql/data/type/ExprCoreType.java @@ -77,10 +77,10 @@ public enum ExprCoreType implements ExprType { * Date. * Todo. compatible relationship. */ - TIMESTAMP(UNDEFINED), - DATE(UNDEFINED), - TIME(UNDEFINED), - DATETIME(UNDEFINED), + TIMESTAMP(STRING), + DATE(STRING), + TIME(STRING), + DATETIME(STRING), INTERVAL(UNDEFINED), /** diff --git a/core/src/main/java/org/opensearch/sql/expression/DSL.java b/core/src/main/java/org/opensearch/sql/expression/DSL.java index f3e6957596..af51d0898a 100644 --- a/core/src/main/java/org/opensearch/sql/expression/DSL.java +++ b/core/src/main/java/org/opensearch/sql/expression/DSL.java @@ -645,4 +645,9 @@ public FunctionExpression castTimestamp(Expression value) { return (FunctionExpression) repository .compile(BuiltinFunctionName.CAST_TO_TIMESTAMP.getName(), Arrays.asList(value)); } + + public FunctionExpression castDatetime(Expression value) { + return (FunctionExpression) repository + .compile(BuiltinFunctionName.CAST_TO_DATETIME.getName(), Arrays.asList(value)); + } } diff --git a/core/src/main/java/org/opensearch/sql/expression/function/BuiltinFunctionName.java b/core/src/main/java/org/opensearch/sql/expression/function/BuiltinFunctionName.java index 0f6feeb94a..cd66825567 100644 --- a/core/src/main/java/org/opensearch/sql/expression/function/BuiltinFunctionName.java +++ b/core/src/main/java/org/opensearch/sql/expression/function/BuiltinFunctionName.java @@ -186,7 +186,8 @@ public enum BuiltinFunctionName { CAST_TO_BOOLEAN(FunctionName.of("cast_to_boolean")), CAST_TO_DATE(FunctionName.of("cast_to_date")), CAST_TO_TIME(FunctionName.of("cast_to_time")), - CAST_TO_TIMESTAMP(FunctionName.of("cast_to_timestamp")); + CAST_TO_TIMESTAMP(FunctionName.of("cast_to_timestamp")), + CAST_TO_DATETIME(FunctionName.of("cast_to_datetime")); private final FunctionName name; diff --git a/core/src/main/java/org/opensearch/sql/expression/operator/convert/TypeCastOperator.java b/core/src/main/java/org/opensearch/sql/expression/operator/convert/TypeCastOperator.java index 5f94eb63ee..c6a84985a0 100644 --- a/core/src/main/java/org/opensearch/sql/expression/operator/convert/TypeCastOperator.java +++ b/core/src/main/java/org/opensearch/sql/expression/operator/convert/TypeCastOperator.java @@ -50,6 +50,7 @@ import org.opensearch.sql.data.model.ExprBooleanValue; import org.opensearch.sql.data.model.ExprByteValue; import org.opensearch.sql.data.model.ExprDateValue; +import org.opensearch.sql.data.model.ExprDatetimeValue; import org.opensearch.sql.data.model.ExprDoubleValue; import org.opensearch.sql.data.model.ExprFloatValue; import org.opensearch.sql.data.model.ExprIntegerValue; @@ -80,6 +81,7 @@ public static void register(BuiltinFunctionRepository repository) { repository.register(castToDate()); repository.register(castToTime()); repository.register(castToTimestamp()); + repository.register(castToDatetime()); } @@ -205,4 +207,15 @@ private static FunctionResolver castToTimestamp() { impl(nullMissingHandling((v) -> v), TIMESTAMP, TIMESTAMP) ); } + + private static FunctionResolver castToDatetime() { + return FunctionDSL.define(BuiltinFunctionName.CAST_TO_DATETIME.getName(), + impl(nullMissingHandling( + (v) -> new ExprDatetimeValue(v.stringValue())), DATETIME, STRING), + impl(nullMissingHandling( + (v) -> new ExprDatetimeValue(v.datetimeValue())), DATETIME, TIMESTAMP), + impl(nullMissingHandling( + (v) -> new ExprDatetimeValue(v.datetimeValue())), DATETIME, DATE) + ); + } } diff --git a/core/src/test/java/org/opensearch/sql/analysis/ExpressionAnalyzerTest.java b/core/src/test/java/org/opensearch/sql/analysis/ExpressionAnalyzerTest.java index 99a316fd85..b0b1e7e773 100644 --- a/core/src/test/java/org/opensearch/sql/analysis/ExpressionAnalyzerTest.java +++ b/core/src/test/java/org/opensearch/sql/analysis/ExpressionAnalyzerTest.java @@ -160,7 +160,7 @@ public void castAnalyzer() { ); assertThrows(IllegalStateException.class, () -> analyze(AstDSL.cast(AstDSL.unresolvedAttr( - "boolean_value"), AstDSL.stringLiteral("DATETIME")))); + "boolean_value"), AstDSL.stringLiteral("INTERVAL")))); } @Test diff --git a/core/src/test/java/org/opensearch/sql/data/type/ExprTypeTest.java b/core/src/test/java/org/opensearch/sql/data/type/ExprTypeTest.java index 9beb11eb07..dc63c7d224 100644 --- a/core/src/test/java/org/opensearch/sql/data/type/ExprTypeTest.java +++ b/core/src/test/java/org/opensearch/sql/data/type/ExprTypeTest.java @@ -34,6 +34,8 @@ import static org.junit.jupiter.api.Assertions.assertTrue; import static org.opensearch.sql.data.type.ExprCoreType.ARRAY; import static org.opensearch.sql.data.type.ExprCoreType.BOOLEAN; +import static org.opensearch.sql.data.type.ExprCoreType.DATE; +import static org.opensearch.sql.data.type.ExprCoreType.DATETIME; import static org.opensearch.sql.data.type.ExprCoreType.DOUBLE; import static org.opensearch.sql.data.type.ExprCoreType.FLOAT; import static org.opensearch.sql.data.type.ExprCoreType.INTEGER; @@ -41,6 +43,8 @@ import static org.opensearch.sql.data.type.ExprCoreType.SHORT; import static org.opensearch.sql.data.type.ExprCoreType.STRING; import static org.opensearch.sql.data.type.ExprCoreType.STRUCT; +import static org.opensearch.sql.data.type.ExprCoreType.TIME; +import static org.opensearch.sql.data.type.ExprCoreType.TIMESTAMP; import static org.opensearch.sql.data.type.ExprCoreType.UNDEFINED; import static org.opensearch.sql.data.type.ExprCoreType.UNKNOWN; @@ -59,7 +63,12 @@ public void isCompatible() { assertTrue(FLOAT.isCompatible(LONG)); assertTrue(FLOAT.isCompatible(INTEGER)); assertTrue(FLOAT.isCompatible(SHORT)); + assertTrue(BOOLEAN.isCompatible(STRING)); + assertTrue(TIMESTAMP.isCompatible(STRING)); + assertTrue(DATE.isCompatible(STRING)); + assertTrue(TIME.isCompatible(STRING)); + assertTrue(DATETIME.isCompatible(STRING)); } @Test diff --git a/core/src/test/java/org/opensearch/sql/expression/function/WideningTypeRuleTest.java b/core/src/test/java/org/opensearch/sql/expression/function/WideningTypeRuleTest.java index 9e678c8091..745aa9cc3b 100644 --- a/core/src/test/java/org/opensearch/sql/expression/function/WideningTypeRuleTest.java +++ b/core/src/test/java/org/opensearch/sql/expression/function/WideningTypeRuleTest.java @@ -30,12 +30,16 @@ import static org.junit.jupiter.api.Assertions.assertThrows; import static org.opensearch.sql.data.type.ExprCoreType.BOOLEAN; import static org.opensearch.sql.data.type.ExprCoreType.BYTE; +import static org.opensearch.sql.data.type.ExprCoreType.DATE; +import static org.opensearch.sql.data.type.ExprCoreType.DATETIME; import static org.opensearch.sql.data.type.ExprCoreType.DOUBLE; import static org.opensearch.sql.data.type.ExprCoreType.FLOAT; import static org.opensearch.sql.data.type.ExprCoreType.INTEGER; import static org.opensearch.sql.data.type.ExprCoreType.LONG; import static org.opensearch.sql.data.type.ExprCoreType.SHORT; import static org.opensearch.sql.data.type.ExprCoreType.STRING; +import static org.opensearch.sql.data.type.ExprCoreType.TIME; +import static org.opensearch.sql.data.type.ExprCoreType.TIMESTAMP; import static org.opensearch.sql.data.type.ExprCoreType.UNDEFINED; import static org.opensearch.sql.data.type.WideningTypeRule.IMPOSSIBLE_WIDENING; import static org.opensearch.sql.data.type.WideningTypeRule.TYPE_EQUAL; @@ -73,6 +77,10 @@ class WideningTypeRuleTest { .put(LONG, DOUBLE, 2) .put(FLOAT, DOUBLE, 1) .put(STRING, BOOLEAN, 1) + .put(STRING, TIMESTAMP, 1) + .put(STRING, DATE, 1) + .put(STRING, TIME, 1) + .put(STRING, DATETIME, 1) .put(UNDEFINED, BYTE, 1) .put(UNDEFINED, SHORT, 2) .put(UNDEFINED, INTEGER, 3) diff --git a/core/src/test/java/org/opensearch/sql/expression/operator/convert/TypeCastOperatorTest.java b/core/src/test/java/org/opensearch/sql/expression/operator/convert/TypeCastOperatorTest.java index cc2acf5710..31bdca1426 100644 --- a/core/src/test/java/org/opensearch/sql/expression/operator/convert/TypeCastOperatorTest.java +++ b/core/src/test/java/org/opensearch/sql/expression/operator/convert/TypeCastOperatorTest.java @@ -33,6 +33,7 @@ import static org.opensearch.sql.data.type.ExprCoreType.BOOLEAN; import static org.opensearch.sql.data.type.ExprCoreType.BYTE; import static org.opensearch.sql.data.type.ExprCoreType.DATE; +import static org.opensearch.sql.data.type.ExprCoreType.DATETIME; import static org.opensearch.sql.data.type.ExprCoreType.DOUBLE; import static org.opensearch.sql.data.type.ExprCoreType.FLOAT; import static org.opensearch.sql.data.type.ExprCoreType.INTEGER; @@ -366,4 +367,20 @@ void castToTimestamp() { assertEquals(TIMESTAMP, expression.type()); assertEquals(new ExprTimestampValue("2012-08-07 01:01:01"), expression.valueOf(null)); } + + @Test + void castToDatetime() { + FunctionExpression expression = dsl.castDatetime(DSL.literal("2012-08-07 01:01:01")); + assertEquals(DATETIME, expression.type()); + assertEquals(new ExprDatetimeValue("2012-08-07 01:01:01"), expression.valueOf(null)); + + expression = dsl.castDatetime(DSL.literal(new ExprTimestampValue("2012-08-07 01:01:01"))); + assertEquals(DATETIME, expression.type()); + assertEquals(new ExprDatetimeValue("2012-08-07 01:01:01"), expression.valueOf(null)); + + expression = dsl.castDatetime(DSL.literal(new ExprDateValue("2012-08-07"))); + assertEquals(DATETIME, expression.type()); + assertEquals(new ExprDatetimeValue("2012-08-07 00:00:00"), expression.valueOf(null)); + } + } diff --git a/docs/user/general/datatypes.rst b/docs/user/general/datatypes.rst index fe44072831..c0a3bf62ac 100644 --- a/docs/user/general/datatypes.rst +++ b/docs/user/general/datatypes.rst @@ -147,7 +147,7 @@ The following matrix illustrates the conversions allowed by our query engine for +--------------+------+-------+---------+------+-------+--------+---------+--------------+------+--------+-----------+------+------+----------+----------+-----------+-----+--------+-----------+---------+ | TEXT | | | | | | | | | N/A | IE | | | | X | X | X | X | X | X | X | +--------------+------+-------+---------+------+-------+--------+---------+--------------+------+--------+-----------+------+------+----------+----------+-----------+-----+--------+-----------+---------+ -| STRING | E | E | E | E | E | E | IE | X | X | N/A | E | E | E | X | X | X | X | X | X | X | +| STRING | E | E | E | E | E | E | IE | X | X | N/A | IE | IE | IE | IE | X | X | X | X | X | X | +--------------+------+-------+---------+------+-------+--------+---------+--------------+------+--------+-----------+------+------+----------+----------+-----------+-----+--------+-----------+---------+ | TIMESTAMP | X | X | X | X | X | X | X | X | X | E | N/A | | | X | X | X | X | X | X | X | +--------------+------+-------+---------+------+-------+--------+---------+--------------+------+--------+-----------+------+------+----------+----------+-----------+-----+--------+-----------+---------+ @@ -183,13 +183,14 @@ Here are a few examples for implicit type conversion:: os> SELECT ... 1 = 1.0, - ... 'True' = true; + ... 'True' = true, + ... DATE('2021-06-10') < '2021-06-11'; fetched rows / total rows = 1/1 - +-----------+-----------------+ - | 1 = 1.0 | 'True' = true | - |-----------+-----------------| - | True | True | - +-----------+-----------------+ + +-----------+-----------------+-------------------------------------+ + | 1 = 1.0 | 'True' = true | DATE('2021-06-10') < '2021-06-11' | + |-----------+-----------------+-------------------------------------| + | True | True | True | + +-----------+-----------------+-------------------------------------+ Here are a few examples for explicit type conversion:: @@ -331,6 +332,21 @@ Conversion from TIMESTAMP - Conversion from timestamp is much more straightforward. To convert it to date is to extract the date value, and conversion to time is to extract the time value. Conversion to datetime, it will extracts the datetime value and leave the timezone information over. For example, the result to convert datetime '2020-08-17 14:09:00' UTC to date is date '2020-08-17', to time is '14:09:00' and to datetime is datetime '2020-08-17 14:09:00'. +Conversion from string to date and time types +--------------------------------------------- + +A string can also represent and be converted to date and time types (except to interval type). As long as the string value is of valid format required by the target date and time types, the conversion can happen implicitly or explicitly as follows:: + + os> SELECT + ... TIMESTAMP('2021-06-17 00:00:00') = '2021-06-17 00:00:00', + ... '2021-06-18' < DATE('2021-06-17'), + ... '10:20:00' <= TIME('11:00:00'); + fetched rows / total rows = 1/1 + +------------------------------------------------------------+-------------------------------------+----------------------------------+ + | TIMESTAMP('2021-06-17 00:00:00') = '2021-06-17 00:00:00' | '2021-06-18' < DATE('2021-06-17') | '10:20:00' <= TIME('11:00:00') | + |------------------------------------------------------------+-------------------------------------+----------------------------------| + | True | False | True | + +------------------------------------------------------------+-------------------------------------+----------------------------------+ String Data Types ================= diff --git a/integ-test/src/test/resources/correctness/expressions/cast.txt b/integ-test/src/test/resources/correctness/expressions/cast.txt index 3556e0b795..2d313203b4 100644 --- a/integ-test/src/test/resources/correctness/expressions/cast.txt +++ b/integ-test/src/test/resources/correctness/expressions/cast.txt @@ -21,3 +21,6 @@ false = 'False' as implicitCast false = 'true' as implicitCast 'TRUE' = true as implicitCast 'false' = true as implicitCast +CAST('2021-06-17 00:00:00' AS TIMESTAMP) = '2021-06-17 00:00:00' as implicitCast +'2021-06-18' < CAST('2021-06-17' AS DATE) as implicitCast +'10:20:00' <= CAST('11:00:00' AS TIME) as implicitCast