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 d6a1d140dd..0450181c3c 100644 --- a/core/src/main/java/org/opensearch/sql/expression/DSL.java +++ b/core/src/main/java/org/opensearch/sql/expression/DSL.java @@ -361,6 +361,10 @@ public static FunctionExpression hour(Expression... expressions) { return compile(FunctionProperties.None, BuiltinFunctionName.HOUR, expressions); } + public static FunctionExpression hour_of_day(Expression... expressions) { + return compile(FunctionProperties.None, BuiltinFunctionName.HOUR_OF_DAY, expressions); + } + public static FunctionExpression microsecond(Expression... expressions) { return compile(FunctionProperties.None, BuiltinFunctionName.MICROSECOND, expressions); } diff --git a/core/src/main/java/org/opensearch/sql/expression/datetime/DateTimeFunction.java b/core/src/main/java/org/opensearch/sql/expression/datetime/DateTimeFunction.java index 5007d2fd49..89a870087e 100644 --- a/core/src/main/java/org/opensearch/sql/expression/datetime/DateTimeFunction.java +++ b/core/src/main/java/org/opensearch/sql/expression/datetime/DateTimeFunction.java @@ -6,7 +6,9 @@ package org.opensearch.sql.expression.datetime; + import static java.time.temporal.ChronoUnit.DAYS; +import static java.time.temporal.ChronoUnit.HOURS; import static java.time.temporal.ChronoUnit.MINUTES; import static java.time.temporal.ChronoUnit.MONTHS; import static java.time.temporal.ChronoUnit.SECONDS; @@ -120,7 +122,8 @@ public void register(BuiltinFunctionRepository repository) { repository.register(dayOfYear(BuiltinFunctionName.DAY_OF_YEAR)); repository.register(from_days()); repository.register(from_unixtime()); - repository.register(hour()); + repository.register(hour(BuiltinFunctionName.HOUR)); + repository.register(hour(BuiltinFunctionName.HOUR_OF_DAY)); repository.register(localtime()); repository.register(localtimestamp()); repository.register(makedate()); @@ -504,12 +507,13 @@ private FunctionResolver from_unixtime() { } /** - * HOUR(STRING/TIME/DATETIME/TIMESTAMP). return the hour value for time. + * HOUR(STRING/TIME/DATETIME/DATE/TIMESTAMP). return the hour value for time. */ - private DefaultFunctionResolver hour() { - return define(BuiltinFunctionName.HOUR.getName(), + private DefaultFunctionResolver hour(BuiltinFunctionName name) { + return define(name.getName(), impl(nullMissingHandling(DateTimeFunction::exprHour), INTEGER, STRING), impl(nullMissingHandling(DateTimeFunction::exprHour), INTEGER, TIME), + impl(nullMissingHandling(DateTimeFunction::exprHour), INTEGER, DATE), impl(nullMissingHandling(DateTimeFunction::exprHour), INTEGER, DATETIME), impl(nullMissingHandling(DateTimeFunction::exprHour), INTEGER, TIMESTAMP) ); @@ -1139,7 +1143,8 @@ private ExprValue exprFromUnixTimeFormat(ExprValue time, ExprValue format) { * @return ExprValue. */ private ExprValue exprHour(ExprValue time) { - return new ExprIntegerValue(time.timeValue().getHour()); + return new ExprIntegerValue( + HOURS.between(LocalTime.MIN, time.timeValue())); } /** 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 a0c12cc034..f9d38a0da3 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 @@ -77,6 +77,7 @@ public enum BuiltinFunctionName { FROM_DAYS(FunctionName.of("from_days")), FROM_UNIXTIME(FunctionName.of("from_unixtime")), HOUR(FunctionName.of("hour")), + HOUR_OF_DAY(FunctionName.of("hour_of_day")), MAKEDATE(FunctionName.of("makedate")), MAKETIME(FunctionName.of("maketime")), MICROSECOND(FunctionName.of("microsecond")), diff --git a/core/src/test/java/org/opensearch/sql/expression/datetime/DateTimeFunctionTest.java b/core/src/test/java/org/opensearch/sql/expression/datetime/DateTimeFunctionTest.java index deb1eb5fe6..ff9f154f75 100644 --- a/core/src/test/java/org/opensearch/sql/expression/datetime/DateTimeFunctionTest.java +++ b/core/src/test/java/org/opensearch/sql/expression/datetime/DateTimeFunctionTest.java @@ -925,6 +925,69 @@ public void invalidMinuteOfDay() { } + private void hourOfDayQuery(FunctionExpression dateExpression, int hour) { + assertEquals(INTEGER, dateExpression.type()); + assertEquals(integerValue(hour), eval(dateExpression)); + } + + @Test + public void hourOfDay() { + lenient().when(nullRef.valueOf(env)).thenReturn(nullValue()); + lenient().when(missingRef.valueOf(env)).thenReturn(missingValue()); + + FunctionExpression expression1 = DSL.hour_of_day(DSL.literal(new ExprTimeValue("01:02:03"))); + FunctionExpression expression2 = DSL.hour_of_day(DSL.literal("01:02:03")); + FunctionExpression expression3 = DSL.hour_of_day( + DSL.literal(new ExprTimestampValue("2020-08-17 01:02:03"))); + FunctionExpression expression4 = DSL.hour_of_day( + DSL.literal(new ExprDatetimeValue("2020-08-17 01:02:03"))); + FunctionExpression expression5 = DSL.hour_of_day(DSL.literal("2020-08-17 01:02:03")); + + assertAll( + () -> hourOfDayQuery(expression1, 1), + () -> assertEquals("hour_of_day(TIME '01:02:03')", expression1.toString()), + + () -> hourOfDayQuery(expression2, 1), + () -> assertEquals("hour_of_day(\"01:02:03\")", expression2.toString()), + + () -> hourOfDayQuery(expression3, 1), + () -> assertEquals("hour_of_day(TIMESTAMP '2020-08-17 01:02:03')", expression3.toString()), + + () -> hourOfDayQuery(expression4, 1), + () -> assertEquals("hour_of_day(DATETIME '2020-08-17 01:02:03')", expression4.toString()), + + () -> hourOfDayQuery(expression5, 1), + () -> assertEquals("hour_of_day(\"2020-08-17 01:02:03\")", expression5.toString()) + ); + } + + private void invalidHourOfDayQuery(String time) { + FunctionExpression expression = DSL.hour_of_day(DSL.literal(new ExprTimeValue(time))); + eval(expression); + } + + @Test + public void hourOfDayInvalidArguments() { + when(nullRef.type()).thenReturn(TIME); + when(missingRef.type()).thenReturn(TIME); + + assertAll( + () -> assertEquals(nullValue(), eval(DSL.hour(nullRef))), + () -> assertEquals(missingValue(), eval(DSL.hour(missingRef))), + //Invalid Seconds + () -> assertThrows(SemanticCheckException.class, () -> invalidHourOfDayQuery("12:23:61")), + //Invalid Minutes + () -> assertThrows(SemanticCheckException.class, () -> invalidHourOfDayQuery("12:61:34")), + + //Invalid Hours + () -> assertThrows(SemanticCheckException.class, () -> invalidHourOfDayQuery("25:23:34")), + + //incorrect format + () -> assertThrows(SemanticCheckException.class, () -> invalidHourOfDayQuery("asdfasdf")) + ); + + } + @Test public void microsecond() { when(nullRef.type()).thenReturn(TIME); diff --git a/docs/user/dql/functions.rst b/docs/user/dql/functions.rst index 648cbed585..f433845bb3 100644 --- a/docs/user/dql/functions.rst +++ b/docs/user/dql/functions.rst @@ -1763,6 +1763,7 @@ Description >>>>>>>>>>> Usage: hour(time) extracts the hour value for time. Different from the time of day value, the time value has a large range and can be greater than 23, so the return value of hour(time) can be also greater than 23. +The function `hour_of_day` is also provided as an alias. Argument type: STRING/TIME/DATETIME/TIMESTAMP @@ -1770,14 +1771,13 @@ Return type: INTEGER Example:: - os> SELECT HOUR((TIME '01:02:03')) + os> SELECT HOUR('01:02:03'), HOUR_OF_DAY('01:02:03') fetched rows / total rows = 1/1 - +---------------------------+ - | HOUR((TIME '01:02:03')) | - |---------------------------| - | 1 | - +---------------------------+ - + +--------------------+---------------------------+ + | HOUR('01:02:03') | HOUR_OF_DAY('01:02:03') | + |--------------------+---------------------------| + | 1 | 1 | + +--------------------+---------------------------+ LOCALTIMESTAMP -------------- diff --git a/integ-test/src/test/java/org/opensearch/sql/sql/DateTimeFunctionIT.java b/integ-test/src/test/java/org/opensearch/sql/sql/DateTimeFunctionIT.java index 2f261b7cfc..af8748deab 100644 --- a/integ-test/src/test/java/org/opensearch/sql/sql/DateTimeFunctionIT.java +++ b/integ-test/src/test/java/org/opensearch/sql/sql/DateTimeFunctionIT.java @@ -391,6 +391,57 @@ public void testHour() throws IOException { verifyDataRows(result, rows(17)); } + @Test + public void testHourOfDayWithUnderscores() throws IOException { + JSONObject result = executeQuery("select hour_of_day(timestamp('2020-09-16 17:30:00'))"); + verifySchema(result, schema( + "hour_of_day(timestamp('2020-09-16 17:30:00'))", null, "integer")); + verifyDataRows(result, rows(17)); + + result = executeQuery("select hour_of_day(datetime('2020-09-16 17:30:00'))"); + verifySchema(result, schema( + "hour_of_day(datetime('2020-09-16 17:30:00'))", null, "integer")); + verifyDataRows(result, rows(17)); + + result = executeQuery("select hour_of_day(time('17:30:00'))"); + verifySchema(result, schema("hour_of_day(time('17:30:00'))", null, "integer")); + verifyDataRows(result, rows(17)); + + result = executeQuery("select hour_of_day('2020-09-16 17:30:00')"); + verifySchema(result, schema("hour_of_day('2020-09-16 17:30:00')", null, "integer")); + verifyDataRows(result, rows(17)); + + result = executeQuery("select hour_of_day('17:30:00')"); + verifySchema(result, schema("hour_of_day('17:30:00')", null, "integer")); + verifyDataRows(result, rows(17)); + } + + @Test + public void testHourFunctionAliasesReturnTheSameResults() throws IOException { + JSONObject result1 = executeQuery("SELECT hour('11:30:00')"); + JSONObject result2 = executeQuery("SELECT hour_of_day('11:30:00')"); + verifyDataRows(result1, rows(11)); + result1.getJSONArray("datarows").similar(result2.getJSONArray("datarows")); + + result1 = executeQuery(String.format( + "SELECT hour(datetime(CAST(time0 AS STRING))) FROM %s", TEST_INDEX_CALCS)); + result2 = executeQuery(String.format( + "SELECT hour_of_day(datetime(CAST(time0 AS STRING))) FROM %s", TEST_INDEX_CALCS)); + result1.getJSONArray("datarows").similar(result2.getJSONArray("datarows")); + + result1 = executeQuery(String.format( + "SELECT hour(CAST(time0 AS STRING)) FROM %s", TEST_INDEX_CALCS)); + result2 = executeQuery(String.format( + "SELECT hour_of_day(CAST(time0 AS STRING)) FROM %s", TEST_INDEX_CALCS)); + result1.getJSONArray("datarows").similar(result2.getJSONArray("datarows")); + + result1 = executeQuery(String.format( + "SELECT hour(CAST(datetime0 AS timestamp)) FROM %s", TEST_INDEX_CALCS)); + result2 = executeQuery(String.format( + "SELECT hour_of_day(CAST(datetime0 AS timestamp)) FROM %s", TEST_INDEX_CALCS)); + result1.getJSONArray("datarows").similar(result2.getJSONArray("datarows")); + } + @Test public void testMicrosecond() throws IOException { JSONObject result = executeQuery("select microsecond(timestamp('2020-09-16 17:30:00.123456'))"); diff --git a/sql/src/main/antlr/OpenSearchSQLParser.g4 b/sql/src/main/antlr/OpenSearchSQLParser.g4 index ac9fafb0e4..eac179ef0c 100644 --- a/sql/src/main/antlr/OpenSearchSQLParser.g4 +++ b/sql/src/main/antlr/OpenSearchSQLParser.g4 @@ -444,6 +444,7 @@ dateTimeFunctionName | FROM_DAYS | FROM_UNIXTIME | HOUR + | HOUR_OF_DAY | MAKEDATE | MAKETIME | MICROSECOND diff --git a/sql/src/test/java/org/opensearch/sql/sql/antlr/SQLSyntaxParserTest.java b/sql/src/test/java/org/opensearch/sql/sql/antlr/SQLSyntaxParserTest.java index fff7f61872..379ec11db1 100644 --- a/sql/src/test/java/org/opensearch/sql/sql/antlr/SQLSyntaxParserTest.java +++ b/sql/src/test/java/org/opensearch/sql/sql/antlr/SQLSyntaxParserTest.java @@ -193,6 +193,11 @@ public void can_parse_now_like_functions(String name, Boolean hasFsp, Boolean ha assertNotNull(parser.parse("SELECT id FROM test WHERE " + String.join(" AND ", calls))); } + @Test + public void can_parse_hour_functions() { + assertNotNull(parser.parse("SELECT hour('2022-11-18 12:23:34')")); + assertNotNull(parser.parse("SELECT hour_of_day('12:23:34')")); + } @Test public void can_parse_week_of_year_functions() {