Skip to content

Commit

Permalink
Add sec_to_time function to OpenSearch (#1378)
Browse files Browse the repository at this point in the history
* Added Tests

* Added Implementation And Documentation

* Added Integration Test And Fixed Checkstyle

* Addressed PR Comments

* Fixed Checkstyle

* Added Tests And Modified Docs

* Fixed formatNanos Helper Function

* Temporarily Removed Failing Tests

* Fixed Nanoseconds Float/Double Imprecision Issues

* Fixed Checkstyle

* Added Tests

* Altered Implementation To Use BigDecimal

---------

Signed-off-by: GabeFernandez310 <[email protected]>
(cherry picked from commit aa7f90e)
  • Loading branch information
GabeFernandez310 authored and github-actions[bot] committed Mar 15, 2023
1 parent 0b6e581 commit 3cc51e0
Show file tree
Hide file tree
Showing 9 changed files with 174 additions and 0 deletions.
4 changes: 4 additions & 0 deletions core/src/main/java/org/opensearch/sql/expression/DSL.java
Original file line number Diff line number Diff line change
Expand Up @@ -461,6 +461,10 @@ public static FunctionExpression module(Expression... expressions) {
return compile(FunctionProperties.None, BuiltinFunctionName.MODULES, expressions);
}

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

public static FunctionExpression substr(Expression... expressions) {
return compile(FunctionProperties.None, BuiltinFunctionName.SUBSTR, expressions);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
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.INTERVAL;
import static org.opensearch.sql.data.type.ExprCoreType.LONG;
Expand Down Expand Up @@ -59,6 +60,7 @@
import java.util.concurrent.TimeUnit;
import java.util.stream.Stream;
import lombok.experimental.UtilityClass;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.Pair;
import org.opensearch.sql.data.model.ExprDateValue;
import org.opensearch.sql.data.model.ExprDatetimeValue;
Expand Down Expand Up @@ -176,6 +178,7 @@ public void register(BuiltinFunctionRepository repository) {
repository.register(period_add());
repository.register(period_diff());
repository.register(quarter());
repository.register(sec_to_time());
repository.register(second(BuiltinFunctionName.SECOND));
repository.register(second(BuiltinFunctionName.SECOND_OF_MINUTE));
repository.register(subdate());
Expand Down Expand Up @@ -688,6 +691,15 @@ private DefaultFunctionResolver quarter() {
);
}

private DefaultFunctionResolver sec_to_time() {
return define(BuiltinFunctionName.SEC_TO_TIME.getName(),
impl((nullMissingHandling(DateTimeFunction::exprSecToTime)), TIME, INTEGER),
impl((nullMissingHandling(DateTimeFunction::exprSecToTime)), TIME, LONG),
impl((nullMissingHandling(DateTimeFunction::exprSecToTimeWithNanos)), TIME, DOUBLE),
impl((nullMissingHandling(DateTimeFunction::exprSecToTimeWithNanos)), TIME, FLOAT)
);
}

/**
* SECOND(STRING/TIME/DATETIME/TIMESTAMP). return the second value for time.
*/
Expand Down Expand Up @@ -1498,6 +1510,44 @@ private ExprValue exprQuarter(ExprValue date) {
return new ExprIntegerValue((month / 3) + ((month % 3) == 0 ? 0 : 1));
}

/**
* Returns TIME value of sec_to_time function for an INTEGER or LONG arguments.
* @param totalSeconds The total number of seconds
* @return A TIME value
*/
private ExprValue exprSecToTime(ExprValue totalSeconds) {
return new ExprTimeValue(LocalTime.MIN.plus(Duration.ofSeconds(totalSeconds.longValue())));
}

/**
* Helper function which obtains the decimal portion of the seconds value passed in.
* Uses BigDecimal to prevent issues with math on floating point numbers.
* Return is formatted to be used with Duration.ofSeconds();
*
* @param seconds and ExprDoubleValue or ExprFloatValue for the seconds
* @return A LONG representing the nanoseconds portion
*/
private long formatNanos(ExprValue seconds) {
//Convert ExprValue to BigDecimal
BigDecimal formattedNanos = BigDecimal.valueOf(seconds.doubleValue());
//Extract only the nanosecond part
formattedNanos = formattedNanos.subtract(BigDecimal.valueOf(formattedNanos.intValue()));

return formattedNanos.scaleByPowerOfTen(9).longValue();
}

/**
* Returns TIME value of sec_to_time function for FLOAT or DOUBLE arguments.
* @param totalSeconds The total number of seconds
* @return A TIME value
*/
private ExprValue exprSecToTimeWithNanos(ExprValue totalSeconds) {
long nanos = formatNanos(totalSeconds);

return new ExprTimeValue(
LocalTime.MIN.plus(Duration.ofSeconds(totalSeconds.longValue(), nanos)));
}

/**
* Second implementation for ExprValue.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ public enum BuiltinFunctionName {
PERIOD_ADD(FunctionName.of("period_add")),
PERIOD_DIFF(FunctionName.of("period_diff")),
QUARTER(FunctionName.of("quarter")),
SEC_TO_TIME(FunctionName.of("sec_to_time")),
SECOND(FunctionName.of("second")),
SECOND_OF_MINUTE(FunctionName.of("second_of_minute")),
SUBDATE(FunctionName.of("subdate")),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@
import org.mockito.junit.jupiter.MockitoExtension;
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.ExprIntegerValue;
import org.opensearch.sql.data.model.ExprLongValue;
import org.opensearch.sql.data.model.ExprNullValue;
import org.opensearch.sql.data.model.ExprStringValue;
Expand All @@ -56,6 +58,7 @@
import org.opensearch.sql.expression.FunctionExpression;
import org.opensearch.sql.expression.LiteralExpression;
import org.opensearch.sql.expression.env.Environment;
import org.opensearch.sql.expression.function.FunctionProperties;

@ExtendWith(MockitoExtension.class)
class DateTimeFunctionTest extends ExpressionTestBase {
Expand Down Expand Up @@ -1308,6 +1311,62 @@ public void quarter() {
assertEquals(integerValue(4), eval(expression));
}

private static Stream<Arguments> getTestDataForSecToTime() {
return Stream.of(
Arguments.of(1, "00:00:01"),
Arguments.of(2378, "00:39:38"),
Arguments.of(6897, "01:54:57"),
Arguments.of(-82800, "01:00:00"),
Arguments.of(-169200, "01:00:00"),
Arguments.of(3600, "01:00:00"),
Arguments.of(90000, "01:00:00"),
Arguments.of(176400, "01:00:00")
);
}

@ParameterizedTest(name = "{0}")
@MethodSource("getTestDataForSecToTime")
public void testSecToTime(int seconds, String expected) {
lenient().when(nullRef.valueOf(env)).thenReturn(nullValue());
lenient().when(missingRef.valueOf(env)).thenReturn(missingValue());
FunctionExpression expr = DSL.sec_to_time(
DSL.literal(new ExprIntegerValue(seconds)));

assertEquals(TIME, expr.type());
assertEquals(new ExprTimeValue(expected), eval(expr));
}

private static Stream<Arguments> getTestDataForSecToTimeWithDecimal() {
return Stream.of(
Arguments.of(1.123, "00:00:01.123"),
Arguments.of(1.00123, "00:00:01.00123"),
Arguments.of(1.001023, "00:00:01.001023"),
Arguments.of(1.000000042, "00:00:01.000000042"),
Arguments.of(3.14, "00:00:03.14")
);
}

@ParameterizedTest(name = "{0}")
@MethodSource("getTestDataForSecToTimeWithDecimal")
public void testSecToTimeWithDecimal(double arg, String expected) {
lenient().when(nullRef.valueOf(env)).thenReturn(nullValue());
lenient().when(missingRef.valueOf(env)).thenReturn(missingValue());
FunctionExpression expr = DSL.sec_to_time(DSL.literal(new ExprDoubleValue(arg)));

assertEquals(TIME, expr.type());
assertEquals(new ExprTimeValue(expected), eval(expr));
}

@Test
public void testSecToTimeWithNullValue() {
lenient().when(nullRef.valueOf(env)).thenReturn(nullValue());
lenient().when(missingRef.valueOf(env)).thenReturn(missingValue());
FunctionExpression expr = DSL.sec_to_time(
DSL.literal(nullValue()));

assertEquals(nullValue(), eval(expr));
}

@Test
public void second() {
when(nullRef.type()).thenReturn(TIME);
Expand Down
41 changes: 41 additions & 0 deletions docs/user/dql/functions.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2131,6 +2131,47 @@ Example::
| 3 |
+-------------------------------+

SEC_TO_TIME
-----------

Description
>>>>>>>>>>>

Usage: sec_to_time(number) returns the time in HH:mm:ssss[.nnnnnn] format.
Note that the function returns a time between 00:00:00 and 23:59:59.
If an input value is too large (greater than 86399), the function will wrap around and begin returning outputs starting from 00:00:00.
If an input value is too small (less than 0), the function will wrap around and begin returning outputs counting down from 23:59:59.

Argument type: INTEGER, LONG, DOUBLE, FLOAT

Return type: TIME

Example::

os> SELECT SEC_TO_TIME(3601)
fetched rows / total rows = 1/1
+---------------------+
| SEC_TO_TIME(3601) |
|---------------------|
| 01:00:01 |
+---------------------+

os> SELECT sec_to_time(1234.123);
fetched rows / total rows = 1/1
+-------------------------+
| sec_to_time(1234.123) |
|-------------------------|
| 00:20:34.123 |
+-------------------------+

os> SELECT sec_to_time(NULL);
fetched rows / total rows = 1/1
+---------------------+
| sec_to_time(NULL) |
|---------------------|
| null |
+---------------------+


SECOND
------
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -754,6 +754,16 @@ public void testQuarter() throws IOException {
verifyDataRows(result, rows(3));
}

@Test
public void testSecToTime() throws IOException {
JSONObject result = executeQuery(
String.format("SELECT sec_to_time(balance) FROM %s LIMIT 3", TEST_INDEX_BANK));
verifyDataRows(result,
rows("10:53:45"),
rows("01:34:46"),
rows("09:07:18"));
}

@Test
public void testSecond() throws IOException {
JSONObject result = executeQuery("select second(timestamp('2020-09-16 17:30:00'))");
Expand Down
1 change: 1 addition & 0 deletions sql/src/main/antlr/OpenSearchSQLLexer.g4
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,7 @@ RINT: 'RINT';
ROUND: 'ROUND';
RTRIM: 'RTRIM';
REVERSE: 'REVERSE';
SEC_TO_TIME: 'SEC_TO_TIME';
SIGN: 'SIGN';
SIGNUM: 'SIGNUM';
SIN: 'SIN';
Expand Down
1 change: 1 addition & 0 deletions sql/src/main/antlr/OpenSearchSQLParser.g4
Original file line number Diff line number Diff line change
Expand Up @@ -471,6 +471,7 @@ dateTimeFunctionName
| PERIOD_ADD
| PERIOD_DIFF
| QUARTER
| SEC_TO_TIME
| SECOND
| SECOND_OF_MINUTE
| SUBDATE
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -520,6 +520,13 @@ public void can_parse_minute_of_day_function() {
assertNotNull(parser.parse("SELECT minute_of_day('2022-12-14 12:23:34');"));;
}

@Test
public void can_parse_sec_to_time_function() {
assertNotNull(parser.parse("SELECT sec_to_time(-6897)"));
assertNotNull(parser.parse("SELECT sec_to_time(6897)"));
assertNotNull(parser.parse("SELECT sec_to_time(6897.123)"));
}

@Test
public void can_parse_last_day_function() {
assertNotNull(parser.parse("SELECT last_day(\"2017-06-20\")"));
Expand Down

0 comments on commit 3cc51e0

Please sign in to comment.