From f931b9a6597f6aa91e01f06f1b2e04a0df2f42f8 Mon Sep 17 00:00:00 2001 From: GabeFernandez310 Date: Thu, 5 Jan 2023 10:55:28 -0800 Subject: [PATCH 1/2] Add Hour_Of_Day Function As An Alias Of Hour Added Tests And ImplementationFor Hour_Of_Day Function Signed-off-by: GabeFernandez310 --- .../org/opensearch/sql/expression/DSL.java | 4 ++ .../expression/datetime/DateTimeFunction.java | 14 +++-- .../function/BuiltinFunctionName.java | 1 + .../datetime/DateTimeFunctionTest.java | 63 +++++++++++++++++++ docs/user/dql/functions.rst | 14 ++--- .../sql/sql/DateTimeFunctionIT.java | 51 +++++++++++++++ sql/src/main/antlr/OpenSearchSQLParser.g4 | 1 + .../sql/sql/antlr/SQLSyntaxParserTest.java | 5 ++ 8 files changed, 141 insertions(+), 12 deletions(-) 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 dfe380b507..ee6ddc1723 100644 --- a/core/src/main/java/org/opensearch/sql/expression/DSL.java +++ b/core/src/main/java/org/opensearch/sql/expression/DSL.java @@ -346,6 +346,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 be3966188f..857a576b23 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,6 +6,7 @@ package org.opensearch.sql.expression.datetime; +import static java.time.temporal.ChronoUnit.HOURS; import static java.time.temporal.ChronoUnit.MINUTES; import static java.time.temporal.ChronoUnit.MONTHS; import static org.opensearch.sql.data.type.ExprCoreType.DATE; @@ -108,7 +109,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()); @@ -393,12 +395,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) ); @@ -880,7 +883,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 e461419f04..8bf3769125 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 @@ -73,6 +73,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 29a0843287..96eddc0bc8 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 @@ -645,6 +645,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 6c6e77a24b..77fd7b3707 100644 --- a/docs/user/dql/functions.rst +++ b/docs/user/dql/functions.rst @@ -1597,6 +1597,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 @@ -1604,14 +1605,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 957275852f..83df24857e 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 @@ -306,6 +306,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 91ccb4d16d..43b77eff82 100644 --- a/sql/src/main/antlr/OpenSearchSQLParser.g4 +++ b/sql/src/main/antlr/OpenSearchSQLParser.g4 @@ -424,6 +424,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 6f1f8d7955..f81efbf23f 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 @@ -194,6 +194,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() { From 0993de13472a3bcd684d83f45c780aab939021f2 Mon Sep 17 00:00:00 2001 From: GabeFernandez310 Date: Mon, 9 Jan 2023 15:35:00 -0800 Subject: [PATCH 2/2] Fixed Checkstyle Signed-off-by: GabeFernandez310 --- .../opensearch/sql/expression/datetime/DateTimeFunction.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 788be1b17a..1a8a68e170 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 @@ -7,8 +7,8 @@ package org.opensearch.sql.expression.datetime; -import static java.time.temporal.ChronoUnit.HOURS; 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;