From 61e2374eace01945bb8b083c8b19859e5042e228 Mon Sep 17 00:00:00 2001 From: GabeFernandez310 Date: Fri, 6 Jan 2023 11:00:13 -0800 Subject: [PATCH] Add Minute_Of_Hour Function As An Alias Of Minute Function (#196) (#1230) Added Testing And Implementation For Minute_Of_Hour Function Signed-off-by: GabeFernandez310 Signed-off-by: GabeFernandez310 --- .../org/opensearch/sql/expression/DSL.java | 4 + .../expression/datetime/DateTimeFunction.java | 11 ++- .../function/BuiltinFunctionName.java | 1 + .../datetime/DateTimeFunctionTest.java | 81 ++++++++++++++++++- docs/user/dql/functions.rst | 13 +-- .../sql/sql/DateTimeFunctionIT.java | 46 +++++++++++ sql/src/main/antlr/OpenSearchSQLParser.g4 | 1 + .../sql/sql/antlr/SQLSyntaxParserTest.java | 9 +++ 8 files changed, 155 insertions(+), 11 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 7899e47a71..b3972af86d 100644 --- a/core/src/main/java/org/opensearch/sql/expression/DSL.java +++ b/core/src/main/java/org/opensearch/sql/expression/DSL.java @@ -358,6 +358,10 @@ public static FunctionExpression minute_of_day(Expression... expressions) { return compile(FunctionProperties.None, BuiltinFunctionName.MINUTE_OF_DAY, expressions); } + public static FunctionExpression minute_of_hour(Expression... expressions) { + return compile(FunctionProperties.None, BuiltinFunctionName.MINUTE_OF_HOUR, expressions); + } + public static FunctionExpression month(Expression... expressions) { return compile(FunctionProperties.None, BuiltinFunctionName.MONTH, 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 a29429fc04..4ad395537f 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 @@ -120,8 +120,9 @@ public void register(BuiltinFunctionRepository repository) { repository.register(makedate()); repository.register(maketime()); repository.register(microsecond()); - repository.register(minute()); + repository.register(minute(BuiltinFunctionName.MINUTE)); repository.register(minute_of_day()); + repository.register(minute(BuiltinFunctionName.MINUTE_OF_HOUR)); repository.register(month(BuiltinFunctionName.MONTH)); repository.register(month(BuiltinFunctionName.MONTH_OF_YEAR)); repository.register(monthName()); @@ -477,11 +478,12 @@ private DefaultFunctionResolver microsecond() { /** * MINUTE(STRING/TIME/DATETIME/TIMESTAMP). return the minute value for time. */ - private DefaultFunctionResolver minute() { - return define(BuiltinFunctionName.MINUTE.getName(), + private DefaultFunctionResolver minute(BuiltinFunctionName name) { + return define(name.getName(), impl(nullMissingHandling(DateTimeFunction::exprMinute), INTEGER, STRING), impl(nullMissingHandling(DateTimeFunction::exprMinute), INTEGER, TIME), impl(nullMissingHandling(DateTimeFunction::exprMinute), INTEGER, DATETIME), + impl(nullMissingHandling(DateTimeFunction::exprMinute), INTEGER, DATE), impl(nullMissingHandling(DateTimeFunction::exprMinute), INTEGER, TIMESTAMP) ); } @@ -1026,7 +1028,8 @@ private ExprValue exprMicrosecond(ExprValue time) { * @return ExprValue. */ private ExprValue exprMinute(ExprValue time) { - return new ExprIntegerValue(time.timeValue().getMinute()); + return new ExprIntegerValue( + (MINUTES.between(LocalTime.MIN, time.timeValue()) % 60)); } /** 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 06b46b6888..eff36c9f64 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 @@ -79,6 +79,7 @@ public enum BuiltinFunctionName { MICROSECOND(FunctionName.of("microsecond")), MINUTE(FunctionName.of("minute")), MINUTE_OF_DAY(FunctionName.of("minute_of_day")), + MINUTE_OF_HOUR(FunctionName.of("minute_of_hour")), MONTH(FunctionName.of("month")), MONTH_OF_YEAR(FunctionName.of("month_of_year")), MONTHNAME(FunctionName.of("monthname")), 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 7a486fc8d4..617f3f7d3e 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 @@ -732,7 +732,7 @@ private void testMinuteOfDay(String date, int value) { assertEquals(INTEGER, expression.type()); assertEquals(integerValue(value), eval(expression)); } - + @Test public void minuteOfDay() { when(nullRef.type()).thenReturn(TIME); @@ -769,6 +769,85 @@ public void minuteOfDay() { testMinuteOfDay("2020-08-17 00:00:01", 0); } + private void minuteOfHourQuery(FunctionExpression dateExpression, int minute, String testExpr) { + assertAll( + () -> assertEquals(INTEGER, dateExpression.type()), + () -> assertEquals(integerValue(minute), eval(dateExpression)), + () -> assertEquals(testExpr, dateExpression.toString()) + ); + } + + private static Stream getTestDataForMinuteOfHour() { + return Stream.of( + Arguments.of( + DSL.literal(new ExprTimeValue("01:02:03")), + 2, + "minute_of_hour(TIME '01:02:03')"), + Arguments.of( + DSL.literal("01:02:03"), + 2, + "minute_of_hour(\"01:02:03\")"), + Arguments.of( + DSL.literal(new ExprTimestampValue("2020-08-17 01:02:03")), + 2, + "minute_of_hour(TIMESTAMP '2020-08-17 01:02:03')"), + Arguments.of( + DSL.literal(new ExprDatetimeValue("2020-08-17 01:02:03")), + 2, + "minute_of_hour(DATETIME '2020-08-17 01:02:03')"), + Arguments.of( + DSL.literal("2020-08-17 01:02:03"), + 2, + "minute_of_hour(\"2020-08-17 01:02:03\")") + ); + } + + @ParameterizedTest(name = "{2}") + @MethodSource("getTestDataForMinuteOfHour") + public void minuteOfHour(LiteralExpression arg, int expectedResult, String expectedString) { + lenient().when(nullRef.valueOf(env)).thenReturn(nullValue()); + lenient().when(missingRef.valueOf(env)).thenReturn(missingValue()); + + minuteOfHourQuery(DSL.minute_of_hour(arg), expectedResult, expectedString); + } + + private void invalidMinuteOfHourQuery(String time) { + FunctionExpression expression = DSL.minute_of_hour(DSL.literal(new ExprTimeValue(time))); + eval(expression); + } + + @Test + public void minuteOfHourInvalidArguments() { + when(nullRef.type()).thenReturn(TIME); + when(missingRef.type()).thenReturn(TIME); + + assertAll( + () -> assertEquals(nullValue(), eval(DSL.minute_of_hour(nullRef))), + () -> assertEquals(missingValue(), eval(DSL.minute_of_hour(missingRef))), + + //Invalid Seconds + () -> assertThrows( + SemanticCheckException.class, + () -> invalidMinuteOfHourQuery("12:23:61")), + + //Invalid Minutes + () -> assertThrows( + SemanticCheckException.class, + () -> invalidMinuteOfHourQuery("12:61:34")), + + //Invalid Hours + () -> assertThrows( + SemanticCheckException.class, + () -> invalidMinuteOfHourQuery("25:23:34")), + + //incorrect format + () -> assertThrows( + SemanticCheckException.class, + () -> invalidMinuteOfHourQuery("asdfasdf")) + ); + } + + @Test public void month() { when(nullRef.type()).thenReturn(DATE); diff --git a/docs/user/dql/functions.rst b/docs/user/dql/functions.rst index f95947a7b5..0ca2a6bad4 100644 --- a/docs/user/dql/functions.rst +++ b/docs/user/dql/functions.rst @@ -1767,6 +1767,7 @@ Description >>>>>>>>>>> Usage: minute(time) returns the minute for time, in the range 0 to 59. +The `minute_of_hour` function is provided as an alias. Argument type: STRING/TIME/DATETIME/TIMESTAMP @@ -1774,13 +1775,13 @@ Return type: INTEGER Example:: - os> SELECT MINUTE((TIME '01:02:03')) + os> SELECT MINUTE(time('01:02:03')), MINUTE_OF_HOUR(time('01:02:03')) fetched rows / total rows = 1/1 - +-----------------------------+ - | MINUTE((TIME '01:02:03')) | - |-----------------------------| - | 2 | - +-----------------------------+ + +----------------------------+------------------------------------+ + | MINUTE(time('01:02:03')) | MINUTE_OF_HOUR(time('01:02:03')) | + |----------------------------+------------------------------------| + | 2 | 2 | + +----------------------------+------------------------------------+ MINUTE_OF_DAY ------ 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 62bb033af3..6255ccf010 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 @@ -390,6 +390,52 @@ public void testMinuteOfDay() throws IOException { verifyDataRows(result, rows(1050)); } + @Test + public void testMinuteOfHour() throws IOException { + JSONObject result = executeQuery("select minute_of_hour(timestamp('2020-09-16 17:30:00'))"); + verifySchema(result, schema( + "minute_of_hour(timestamp('2020-09-16 17:30:00'))", null, "integer")); + verifyDataRows(result, rows(30)); + + result = executeQuery("select minute_of_hour(time('17:30:00'))"); + verifySchema(result, schema("minute_of_hour(time('17:30:00'))", null, "integer")); + verifyDataRows(result, rows(30)); + + result = executeQuery("select minute_of_hour('2020-09-16 17:30:00')"); + verifySchema(result, schema("minute_of_hour('2020-09-16 17:30:00')", null, "integer")); + verifyDataRows(result, rows(30)); + + result = executeQuery("select minute_of_hour('17:30:00')"); + verifySchema(result, schema("minute_of_hour('17:30:00')", null, "integer")); + verifyDataRows(result, rows(30)); + } + + @Test + public void testMinuteFunctionAliasesReturnTheSameResults() throws IOException { + JSONObject result1 = executeQuery("SELECT minute('11:30:00')"); + JSONObject result2 = executeQuery("SELECT minute_of_hour('11:30:00')"); + verifyDataRows(result1, rows(30)); + result1.getJSONArray("datarows").similar(result2.getJSONArray("datarows")); + + result1 = executeQuery(String.format( + "SELECT minute(datetime(CAST(time0 AS STRING))) FROM %s", TEST_INDEX_CALCS)); + result2 = executeQuery(String.format( + "SELECT minute_of_hour(datetime(CAST(time0 AS STRING))) FROM %s", TEST_INDEX_CALCS)); + result1.getJSONArray("datarows").similar(result2.getJSONArray("datarows")); + + result1 = executeQuery(String.format( + "SELECT minute(CAST(time0 AS STRING)) FROM %s", TEST_INDEX_CALCS)); + result2 = executeQuery(String.format( + "SELECT minute_of_hour(CAST(time0 AS STRING)) FROM %s", TEST_INDEX_CALCS)); + result1.getJSONArray("datarows").similar(result2.getJSONArray("datarows")); + + result1 = executeQuery(String.format( + "SELECT minute(CAST(datetime0 AS timestamp)) FROM %s", TEST_INDEX_CALCS)); + result2 = executeQuery(String.format( + "SELECT minute_of_hour(CAST(datetime0 AS timestamp)) FROM %s", TEST_INDEX_CALCS)); + result1.getJSONArray("datarows").similar(result2.getJSONArray("datarows")); + } + @Test public void testMonth() throws IOException { JSONObject result = executeQuery("select month(date('2020-09-16'))"); diff --git a/sql/src/main/antlr/OpenSearchSQLParser.g4 b/sql/src/main/antlr/OpenSearchSQLParser.g4 index 9e827d03f8..0ef7e21b42 100644 --- a/sql/src/main/antlr/OpenSearchSQLParser.g4 +++ b/sql/src/main/antlr/OpenSearchSQLParser.g4 @@ -443,6 +443,7 @@ dateTimeFunctionName | MICROSECOND | MINUTE | MINUTE_OF_DAY + | MINUTE_OF_HOUR | MONTH | MONTHNAME | NOW 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 130534af10..eee476a56a 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 @@ -207,6 +207,15 @@ public void can_parse_dayofyear_functions() { assertNotNull(parser.parse("SELECT day_of_year('2022-11-18')")); } + @Test + public void can_parse_minute_functions() { + assertNotNull(parser.parse("SELECT minute('12:23:34')")); + assertNotNull(parser.parse("SELECT minute_of_hour('12:23:34')")); + + assertNotNull(parser.parse("SELECT minute('2022-12-20 12:23:34')")); + assertNotNull(parser.parse("SELECT minute_of_hour('2022-12-20 12:23:34')")); + } + @Test public void can_parse_month_of_year_function() { assertNotNull(parser.parse("SELECT month('2022-11-18')"));