Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update TIMESTAMP function #139

Merged
merged 14 commits into from
Dec 20, 2022
Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@ -387,7 +387,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 @@ -631,15 +631,72 @@ 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
*/
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));
implWithProperties(nullMissingHandlingWithProperties(DateTimeFunction::exprTimestamp),
TIMESTAMP, STRING),
implWithProperties(nullMissingHandlingWithProperties(DateTimeFunction::exprTimestamp),
TIMESTAMP, DATE),
implWithProperties(nullMissingHandlingWithProperties(DateTimeFunction::exprTimestamp),
TIMESTAMP, TIME),
implWithProperties(nullMissingHandlingWithProperties(DateTimeFunction::exprTimestamp),
TIMESTAMP, DATETIME),
implWithProperties(nullMissingHandlingWithProperties(DateTimeFunction::exprTimestamp),
TIMESTAMP, TIMESTAMP),
implWithProperties(nullMissingHandlingWithProperties(DateTimeFunction::exprTimestampEx),
TIMESTAMP, STRING, STRING),
implWithProperties(nullMissingHandlingWithProperties(DateTimeFunction::exprTimestampEx),
TIMESTAMP, STRING, DATE),
implWithProperties(nullMissingHandlingWithProperties(DateTimeFunction::exprTimestampEx),
TIMESTAMP, STRING, TIME),
implWithProperties(nullMissingHandlingWithProperties(DateTimeFunction::exprTimestampEx),
TIMESTAMP, STRING, DATETIME),
implWithProperties(nullMissingHandlingWithProperties(DateTimeFunction::exprTimestampEx),
TIMESTAMP, STRING, TIMESTAMP),
implWithProperties(nullMissingHandlingWithProperties(DateTimeFunction::exprTimestampEx),
TIMESTAMP, DATE, STRING),
implWithProperties(nullMissingHandlingWithProperties(DateTimeFunction::exprTimestampEx),
TIMESTAMP, DATE, DATE),
implWithProperties(nullMissingHandlingWithProperties(DateTimeFunction::exprTimestampEx),
TIMESTAMP, DATE, TIME),
implWithProperties(nullMissingHandlingWithProperties(DateTimeFunction::exprTimestampEx),
TIMESTAMP, DATE, DATETIME),
implWithProperties(nullMissingHandlingWithProperties(DateTimeFunction::exprTimestampEx),
TIMESTAMP, DATE, TIMESTAMP),
implWithProperties(nullMissingHandlingWithProperties(DateTimeFunction::exprTimestampEx),
TIMESTAMP, TIME, STRING),
implWithProperties(nullMissingHandlingWithProperties(DateTimeFunction::exprTimestampEx),
TIMESTAMP, TIME, DATE),
implWithProperties(nullMissingHandlingWithProperties(DateTimeFunction::exprTimestampEx),
TIMESTAMP, TIME, TIME),
implWithProperties(nullMissingHandlingWithProperties(DateTimeFunction::exprTimestampEx),
TIMESTAMP, TIME, DATETIME),
implWithProperties(nullMissingHandlingWithProperties(DateTimeFunction::exprTimestampEx),
TIMESTAMP, TIME, TIMESTAMP),
implWithProperties(nullMissingHandlingWithProperties(DateTimeFunction::exprTimestampEx),
TIMESTAMP, DATETIME, STRING),
implWithProperties(nullMissingHandlingWithProperties(DateTimeFunction::exprTimestampEx),
TIMESTAMP, DATETIME, DATE),
implWithProperties(nullMissingHandlingWithProperties(DateTimeFunction::exprTimestampEx),
TIMESTAMP, DATETIME, TIME),
implWithProperties(nullMissingHandlingWithProperties(DateTimeFunction::exprTimestampEx),
TIMESTAMP, DATETIME, DATETIME),
implWithProperties(nullMissingHandlingWithProperties(DateTimeFunction::exprTimestampEx),
TIMESTAMP, DATETIME, TIMESTAMP),
implWithProperties(nullMissingHandlingWithProperties(DateTimeFunction::exprTimestampEx),
TIMESTAMP, TIMESTAMP, STRING),
implWithProperties(nullMissingHandlingWithProperties(DateTimeFunction::exprTimestampEx),
TIMESTAMP, TIMESTAMP, DATE),
implWithProperties(nullMissingHandlingWithProperties(DateTimeFunction::exprTimestampEx),
TIMESTAMP, TIMESTAMP, TIME),
implWithProperties(nullMissingHandlingWithProperties(DateTimeFunction::exprTimestampEx),
TIMESTAMP, TIMESTAMP, DATETIME),
implWithProperties(nullMissingHandlingWithProperties(DateTimeFunction::exprTimestampEx),
TIMESTAMP, TIMESTAMP, TIMESTAMP));
}

/**
Expand Down Expand Up @@ -1185,15 +1242,29 @@ private ExprValue exprTime(ExprValue exprValue) {
/**
* Timestamp implementation for ExprValue.
*
* @param exprValue ExprValue of Timestamp type or String type.
* @param exprValue ExprValue of Timestamp.
* @return ExprValue.
*/
private ExprValue exprTimestamp(ExprValue exprValue) {
private ExprValue exprTimestamp(FunctionProperties functionProperties, ExprValue exprValue) {
if (exprValue instanceof ExprStringValue) {
return new ExprTimestampValue(exprValue.stringValue());
} else {
return new ExprTimestampValue(exprValue.timestampValue());
}
return new ExprTimestampValue(DateTimeUtils.extractTimestamp(exprValue, functionProperties));
}

/**
* Timestamp implementation for two arguments. It extracts time expression from exprValue2
* and adds it to the expression exprValue1 and returns the result as a timestamp value.
*
* @param exprValue1 ExprValue of Timestamp type or String type.
* @param exprValue2 ExprValue of Timestamp type or String type.
* @return ExprValue.
*/
private ExprValue exprTimestampEx(FunctionProperties functionProperties,
ExprValue exprValue1, ExprValue exprValue2) {
return exprAddTime(functionProperties,
exprTimestamp(functionProperties, exprValue1),
exprTimestamp(functionProperties, exprValue2));
}

/**
Expand Down
11 changes: 11 additions & 0 deletions core/src/main/java/org/opensearch/sql/utils/DateTimeUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -139,4 +139,15 @@ public static LocalDateTime extractDateTime(ExprValue value,
? ((ExprTimeValue) value).datetimeValue(functionProperties)
: value.datetimeValue();
}

/**
* Extracts Instant from a datetime ExprValue.
* Uses `FunctionProperties` for `ExprTimeValue`.
*/
public static Instant extractTimestamp(ExprValue value,
FunctionProperties functionProperties) {
return value instanceof ExprTimeValue
? ((ExprTimeValue) value).timestampValue(functionProperties)
: value.timestampValue();

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oooh. I do not like this logic is required here.
It would be better if the logic was in Expr*Value classes.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you give an example, please?

}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
/*
* 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.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;

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 @@ -1172,9 +1172,9 @@ Argument type: DATETIME/STRING

Return type map:

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

DATETIME -> DATETIME
(DATETIME) -> DATETIME

Example::

Expand Down Expand Up @@ -2167,21 +2167,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 applied to today's date before cast.
Yury-Fridlyand marked this conversation as resolved.
Show resolved Hide resolved
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:

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

Return type: 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
Loading