Skip to content

Commit

Permalink
Update TIMESTAMP function implementation and signatures. (#1254)
Browse files Browse the repository at this point in the history
* Update `TIMESTAMP` function implementation and signatures.

Signed-off-by: Yury-Fridlyand <yury.fridlyand@improving.com>

* Simplify `TIMESTAMP` function implementation by using automatic datetime types cast (PR #1196).

Signed-off-by: Yury-Fridlyand <yury.fridlyand@improving.com>

* Checkstyle fix.

Signed-off-by: Yury-Fridlyand <yury.fridlyand@improving.com>

---------

Signed-off-by: Yury-Fridlyand <yury.fridlyand@improving.com>
  • Loading branch information
Yury-Fridlyand authored Feb 15, 2023
1 parent e990201 commit ecb9dec
Show file tree
Hide file tree
Showing 7 changed files with 238 additions and 46 deletions.
7 changes: 6 additions & 1 deletion core/src/main/java/org/opensearch/sql/expression/DSL.java
Original file line number Diff line number Diff line change
Expand Up @@ -407,7 +407,12 @@ public static FunctionExpression time_to_sec(Expression... expressions) {
}

public static FunctionExpression timestamp(Expression... expressions) {
return compile(FunctionProperties.None, BuiltinFunctionName.TIMESTAMP, expressions);
return timestamp(FunctionProperties.None, expressions);
}

public static FunctionExpression timestamp(FunctionProperties functionProperties,
Expression... expressions) {
return compile(functionProperties, BuiltinFunctionName.TIMESTAMP, expressions);
}

public static FunctionExpression date_format(Expression... expressions) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -751,15 +751,19 @@ private DefaultFunctionResolver time_to_sec() {

/**
* Extracts the timestamp of a date and time value.
* Also to construct a date type. The supported signatures:
* STRING/DATE/DATETIME/TIMESTAMP -> DATE
* Input strings may contain a timestamp only in format 'yyyy-MM-dd HH:mm:ss[.SSSSSSSSS]'
* STRING/DATE/TIME/DATETIME/TIMESTAMP -> TIMESTAMP
* STRING/DATE/TIME/DATETIME/TIMESTAMP, STRING/DATE/TIME/DATETIME/TIMESTAMP -> TIMESTAMP
* All types are converted to TIMESTAMP actually before the function call - it is responsibility
* of the automatic cast mechanism defined in `ExprCoreType` and performed by `TypeCastOperator`.
*/
private DefaultFunctionResolver 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));
impl(nullMissingHandling(v -> v), TIMESTAMP, TIMESTAMP),
// We can use FunctionProperties.None, because it is not used. It is required to convert
// TIME to other datetime types, but arguments there are already converted.
impl(nullMissingHandling((v1, v2) -> exprAddTime(FunctionProperties.None, v1, v2)),
TIMESTAMP, TIMESTAMP, TIMESTAMP));
}

/**
Expand Down Expand Up @@ -1440,20 +1444,6 @@ private ExprValue exprTimeDiff(ExprValue first, ExprValue second) {
Duration.between(second.timeValue(), first.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());
}
}

/**
* Time To Sec implementation for ExprValue.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1347,7 +1347,7 @@ public void timestamp() {
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());
assertEquals("timestamp(cast_to_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());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
package org.opensearch.sql.expression.datetime;

import static org.opensearch.sql.data.model.ExprValueUtils.fromObjectValue;
import static org.opensearch.sql.data.type.ExprCoreType.DOUBLE;

import java.time.Instant;
import java.time.LocalDate;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
/*
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*/

package org.opensearch.sql.expression.datetime;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.opensearch.sql.data.type.ExprCoreType.TIMESTAMP;

import java.time.Instant;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.util.stream.Stream;
import org.junit.jupiter.api.DisplayNameGeneration;
import org.junit.jupiter.api.DisplayNameGenerator;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.CsvSource;
import org.junit.jupiter.params.provider.MethodSource;
import org.opensearch.sql.data.model.ExprTimestampValue;
import org.opensearch.sql.data.model.ExprValueUtils;
import org.opensearch.sql.exception.SemanticCheckException;
import org.opensearch.sql.expression.DSL;
import org.opensearch.sql.expression.ExpressionTestBase;

@DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class)
public class TimestampTest extends ExpressionTestBase {

@Test
public void timestamp_one_arg_string() {
var expr = DSL.timestamp(functionProperties, DSL.literal("1961-04-12 09:07:00"));
assertEquals(TIMESTAMP, expr.type());
assertEquals(new ExprTimestampValue("1961-04-12 09:07:00"), expr.valueOf());

expr = DSL.timestamp(functionProperties, DSL.literal("1961-04-12 09:07:00.123456"));
assertEquals(TIMESTAMP, expr.type());
assertEquals(LocalDateTime.of(1961, 4, 12, 9, 7, 0, 123456000),
expr.valueOf().datetimeValue());
}

/**
* Check that `TIMESTAMP` function throws an exception on incorrect string input.
* @param value A value.
* @param testName A test name.
*/
@ParameterizedTest(name = "{1}")
@CsvSource({
"1984-02-30 12:20:42, Feb 30th",
"1984-02-10 24:00:00, 24:00:00",
"84-02-10 12:20:42, 2 digit year"
})
public void timestamp_one_arg_string_invalid_format(String value, String testName) {
// exception thrown from ExprTimestampValue(String) CTOR
var exception = assertThrows(SemanticCheckException.class,
() -> DSL.timestamp(functionProperties, DSL.literal(value)).valueOf());
assertEquals(String.format("timestamp:%s in unsupported format, please "
+ "use yyyy-MM-dd HH:mm:ss[.SSSSSSSSS]", value), exception.getMessage());
}

@Test
public void timestamp_one_arg_time() {
var expr = DSL.timestamp(functionProperties, DSL.time(DSL.literal("22:33:44")));
assertEquals(TIMESTAMP, expr.type());
var refValue = LocalDate.now().atTime(LocalTime.of(22, 33, 44))
.atZone(ExprTimestampValue.ZONE).toInstant();
assertEquals(new ExprTimestampValue(refValue), expr.valueOf());
}

@Test
public void timestamp_one_arg_date() {
var expr = DSL.timestamp(functionProperties, DSL.date(DSL.literal("2077-12-15")));
assertEquals(TIMESTAMP, expr.type());
var refValue = LocalDate.of(2077, 12, 15).atStartOfDay()
.atZone(ExprTimestampValue.ZONE).toInstant();
assertEquals(new ExprTimestampValue(refValue), expr.valueOf());
}

@Test
public void timestamp_one_arg_datetime() {
var expr = DSL.timestamp(functionProperties, DSL.datetime(DSL.literal("1961-04-12 09:07:00")));
assertEquals(TIMESTAMP, expr.type());
assertEquals(LocalDateTime.of(1961, 4, 12, 9, 7, 0), expr.valueOf().datetimeValue());
}

@Test
public void timestamp_one_arg_timestamp() {
var refValue = new ExprTimestampValue(Instant.ofEpochSecond(10050042));
var expr = DSL.timestamp(functionProperties,
DSL.timestamp(functionProperties, DSL.literal(refValue)));
assertEquals(TIMESTAMP, expr.type());
assertEquals(refValue, expr.valueOf());
}

private static Instant dateTime2Instant(LocalDateTime dt) {
return dt.atZone(ExprTimestampValue.ZONE).toInstant();
}

private static ExprTimestampValue dateTime2ExprTs(LocalDateTime dt) {
return new ExprTimestampValue(dateTime2Instant(dt));
}

private static Stream<Arguments> getTestData() {
var today = LocalDate.now();
// First argument of `TIMESTAMP` function, second argument and expected result value
return Stream.of(
// STRING and STRING/DATE/TIME/DATETIME/TIMESTAMP
Arguments.of("1961-04-12 09:07:00", "2077-12-15 01:48:00",
dateTime2ExprTs(LocalDateTime.of(1961, 4, 12, 10, 55, 0))),
Arguments.of("1984-02-10 12:20:42", LocalDate.of(2077, 12, 21),
dateTime2ExprTs(LocalDateTime.of(1984, 2, 10, 12, 20, 42))),
Arguments.of("1961-04-12 09:07:00", LocalTime.of(1, 48),
dateTime2ExprTs(LocalDateTime.of(1961, 4, 12, 10, 55, 0))),
Arguments.of("2020-12-31 17:30:00", LocalDateTime.of(2077, 12, 21, 12, 20, 42),
dateTime2ExprTs(LocalDateTime.of(2021, 1, 1, 5, 50, 42))),
Arguments.of("2020-12-31 17:30:00", Instant.ofEpochSecond(42),
dateTime2ExprTs(LocalDateTime.of(2020, 12, 31, 17, 30, 42))),
// DATE and STRING/DATE/TIME/DATETIME/TIMESTAMP
Arguments.of(LocalDate.of(2077, 12, 21), "2077-12-15 01:48:00",
dateTime2ExprTs(LocalDateTime.of(2077, 12, 21, 1, 48, 0))),
Arguments.of(LocalDate.of(2077, 12, 21), LocalDate.of(1984, 2, 3),
dateTime2ExprTs(LocalDateTime.of(2077, 12, 21, 0, 0, 0))),
Arguments.of(LocalDate.of(2077, 12, 21), LocalTime.of(22, 33, 44),
dateTime2ExprTs(LocalDateTime.of(2077, 12, 21, 22, 33, 44))),
Arguments.of(LocalDate.of(2077, 12, 21), LocalDateTime.of(1999, 9, 9, 22, 33, 44),
dateTime2ExprTs(LocalDateTime.of(2077, 12, 21, 22, 33, 44))),
Arguments.of(LocalDate.of(2077, 12, 21), Instant.ofEpochSecond(42),
dateTime2ExprTs(LocalDateTime.of(2077, 12, 21, 0, 0, 42))),
// TIME and STRING/DATE/TIME/DATETIME/TIMESTAMP
Arguments.of(LocalTime.of(9, 7, 0), "2077-12-15 01:48:00",
dateTime2ExprTs(today.atTime(LocalTime.of(10, 55, 0)))),
Arguments.of(LocalTime.of(12, 20, 42), LocalDate.of(2077, 12, 21),
dateTime2ExprTs(today.atTime(LocalTime.of(12, 20, 42)))),
Arguments.of(LocalTime.of(9, 7, 0), LocalTime.of(1, 48),
dateTime2ExprTs(today.atTime(LocalTime.of(10, 55, 0)))),
Arguments.of(LocalTime.of(17, 30, 0), LocalDateTime.of(2077, 12, 21, 12, 20, 42),
dateTime2ExprTs(today.plusDays(1).atTime(LocalTime.of(5, 50, 42)))),
Arguments.of(LocalTime.of(17, 30, 0), Instant.ofEpochSecond(42),
dateTime2ExprTs(today.atTime(LocalTime.of(17, 30, 42)))),
// DATETIME and STRING/DATE/TIME/DATETIME/TIMESTAMP
Arguments.of(LocalDateTime.of(1961, 4, 12, 9, 7, 0), "2077-12-15 01:48:00",
dateTime2ExprTs(LocalDateTime.of(1961, 4, 12, 10, 55, 0))),
Arguments.of(LocalDateTime.of(1984, 2, 10, 12, 20, 42), LocalDate.of(2077, 12, 21),
dateTime2ExprTs(LocalDateTime.of(1984, 2, 10, 12, 20, 42))),
Arguments.of(LocalDateTime.of(1961, 4, 12, 9, 7, 0), LocalTime.of(1, 48),
dateTime2ExprTs(LocalDateTime.of(1961, 4, 12, 10, 55, 0))),
Arguments.of(LocalDateTime.of(2020, 12, 31, 17, 30, 0),
LocalDateTime.of(2077, 12, 21, 12, 20, 42),
dateTime2ExprTs(LocalDateTime.of(2021, 1, 1, 5, 50, 42))),
Arguments.of(LocalDateTime.of(2020, 12, 31, 17, 30, 0), Instant.ofEpochSecond(42),
dateTime2ExprTs(LocalDateTime.of(2020, 12, 31, 17, 30, 42))),
// TIMESTAMP and STRING/DATE/TIME/DATETIME/TIMESTAMP
Arguments.of(dateTime2Instant(LocalDateTime.of(1961, 4, 12, 9, 7, 0)),
"2077-12-15 01:48:00",
dateTime2ExprTs(LocalDateTime.of(1961, 4, 12, 10, 55, 0))),
Arguments.of(dateTime2Instant(LocalDateTime.of(1984, 2, 10, 12, 20, 42)),
LocalDate.of(2077, 12, 21),
dateTime2ExprTs(LocalDateTime.of(1984, 2, 10, 12, 20, 42))),
Arguments.of(dateTime2Instant(LocalDateTime.of(1961, 4, 12, 9, 7, 0)),
LocalTime.of(1, 48),
dateTime2ExprTs(LocalDateTime.of(1961, 4, 12, 10, 55, 0))),
Arguments.of(dateTime2Instant(LocalDateTime.of(2020, 12, 31, 17, 30, 0)),
LocalDateTime.of(2077, 12, 21, 12, 20, 42),
dateTime2ExprTs(LocalDateTime.of(2021, 1, 1, 5, 50, 42))),
Arguments.of(dateTime2Instant(LocalDateTime.of(2020, 12, 31, 17, 30, 0)),
Instant.ofEpochSecond(42),
dateTime2ExprTs(LocalDateTime.of(2020, 12, 31, 17, 30, 42)))
);
}

/**
* Test `TIMESTAMP` function which takes 2 arguments with input of different types.
* @param arg1 First argument to be passed to `TIMESTAMP` function.
* @param arg2 Second argument to be passed to `TIMESTAMP` function.
* @param expected The expected result.
*/
@ParameterizedTest
@MethodSource("getTestData")
public void timestamp_with_two_args(Object arg1, Object arg2, ExprTimestampValue expected) {
var expr = DSL.timestamp(functionProperties,
DSL.literal(ExprValueUtils.fromObjectValue(arg1)),
DSL.literal(ExprValueUtils.fromObjectValue(arg2)));
assertEquals(TIMESTAMP, expr.type());
assertEquals(expected, expr.valueOf());
}
}
27 changes: 16 additions & 11 deletions docs/user/dql/functions.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1245,9 +1245,9 @@ Argument type: DATETIME/STRING

Return type map:

DATETIME, STRING -> DATETIME
(DATETIME, STRING) -> DATETIME

DATETIME -> DATETIME
(DATETIME) -> DATETIME

Example::

Expand Down Expand Up @@ -2337,21 +2337,26 @@ TIMESTAMP
Description
>>>>>>>>>>>

Usage: timestamp(expr) construct a timestamp type with the input string expr as an timestamp. If the argument is of date/datetime/timestamp type, cast expr to timestamp type with default timezone UTC.
Usage: timestamp(expr) constructs a timestamp type with the input string `expr` as an timestamp. If the argument is not a string, it casts `expr` to timestamp type with default timezone UTC. If argument is a time, it applies today's date before cast.
With two arguments `timestamp(expr1, expr2)` adds the time expression `expr2` to the date or datetime expression `expr1` and returns the result as a timestamp value.

Argument type: STRING/DATE/DATETIME/TIMESTAMP
Argument type: STRING/DATE/TIME/DATETIME/TIMESTAMP

Return type map:

Return type: TIMESTAMP
(STRING/DATE/TIME/DATETIME/TIMESTAMP) -> TIMESTAMP

(STRING/DATE/TIME/DATETIME/TIMESTAMP, STRING/DATE/TIME/DATETIME/TIMESTAMP) -> TIMESTAMP

Example::

>od SELECT TIMESTAMP('2020-08-26 13:49:00')
os> SELECT TIMESTAMP('2020-08-26 13:49:00'), TIMESTAMP('2020-08-26 13:49:00', TIME('12:15:42'))
fetched rows / total rows = 1/1
+------------------------------------+
| TIMESTAMP('2020-08-26 13:49:00') |
|------------------------------------|
| TIMESTAMP '2020-08-26 13:49:00 |
+------------------------------------+
+------------------------------------+------------------------------------------------------+
| TIMESTAMP('2020-08-26 13:49:00') | TIMESTAMP('2020-08-26 13:49:00', TIME('12:15:42')) |
|------------------------------------+------------------------------------------------------|
| 2020-08-26 13:49:00 | 2020-08-27 02:04:42 |
+------------------------------------+------------------------------------------------------+


TO_DAYS
Expand Down
28 changes: 16 additions & 12 deletions docs/user/ppl/functions/datetime.rst
Original file line number Diff line number Diff line change
Expand Up @@ -516,7 +516,6 @@ Example::
+-----------------------------------------------+----------------------------------------------------------------+



DATETIME
--------

Expand All @@ -529,9 +528,9 @@ Argument type: DATETIME/STRING

Return type map:

DATETIME, STRING -> DATETIME
(DATETIME, STRING) -> DATETIME

DATETIME -> DATETIME
(DATETIME) -> DATETIME


Converting datetime with timezone to the second argument timezone.
Expand Down Expand Up @@ -1350,21 +1349,26 @@ TIMESTAMP
Description
>>>>>>>>>>>

Usage: timestamp(expr) construct a timestamp type with the input string expr as an timestamp. If the argument is of date/datetime/timestamp type, cast expr to timestamp type with default timezone UTC.
Usage: timestamp(expr) constructs a timestamp type with the input string `expr` as an timestamp. If the argument is not a string, it casts `expr` to timestamp type with default timezone UTC. If argument is a time, it applies today's date before cast.
With two arguments `timestamp(expr1, expr2)` adds the time expression `expr2` to the date or datetime expression `expr1` and returns the result as a timestamp value.

Argument type: STRING/DATE/DATETIME/TIMESTAMP
Argument type: STRING/DATE/TIME/DATETIME/TIMESTAMP

Return type map:

Return type: TIMESTAMP
(STRING/DATE/TIME/DATETIME/TIMESTAMP) -> TIMESTAMP

(STRING/DATE/TIME/DATETIME/TIMESTAMP, STRING/DATE/TIME/DATETIME/TIMESTAMP) -> TIMESTAMP

Example::

>od source=people | eval `TIMESTAMP('2020-08-26 13:49:00')` = TIMESTAMP('2020-08-26 13:49:00') | fields `TIMESTAMP('2020-08-26 13:49:00')`
os> source=people | eval `TIMESTAMP('2020-08-26 13:49:00')` = TIMESTAMP('2020-08-26 13:49:00'), `TIMESTAMP('2020-08-26 13:49:00', TIME('12:15:42'))` = TIMESTAMP('2020-08-26 13:49:00', TIME('12:15:42')) | fields `TIMESTAMP('2020-08-26 13:49:00')`, `TIMESTAMP('2020-08-26 13:49:00', TIME('12:15:42'))`
fetched rows / total rows = 1/1
+------------------------------------+
| TIMESTAMP('2020-08-26 13:49:00') |
|------------------------------------|
| TIMESTAMP '2020-08-26 13:49:00 |
+------------------------------------+
+------------------------------------+------------------------------------------------------+
| TIMESTAMP('2020-08-26 13:49:00') | TIMESTAMP('2020-08-26 13:49:00', TIME('12:15:42')) |
|------------------------------------+------------------------------------------------------|
| 2020-08-26 13:49:00 | 2020-08-27 02:04:42 |
+------------------------------------+------------------------------------------------------+


TO_DAYS
Expand Down

0 comments on commit ecb9dec

Please sign in to comment.