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

Add sec_to_time Function To OpenSearch SQL #1378

Merged
Merged
Show file tree
Hide file tree
Changes from 2 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
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 @@ -466,6 +466,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 @@ -521,6 +521,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