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 fc425c6c20..3e9828cea3 100644 --- a/core/src/main/java/org/opensearch/sql/expression/DSL.java +++ b/core/src/main/java/org/opensearch/sql/expression/DSL.java @@ -354,6 +354,10 @@ public static FunctionExpression minute(Expression... expressions) { return compile(FunctionProperties.None, BuiltinFunctionName.MINUTE, expressions); } + public static FunctionExpression minute_of_day(Expression... expressions) { + return compile(FunctionProperties.None, BuiltinFunctionName.MINUTE_OF_DAY, 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 a111f672af..5519a3fc88 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.MINUTES; import static java.time.temporal.ChronoUnit.MONTHS; import static org.opensearch.sql.data.type.ExprCoreType.DATE; import static org.opensearch.sql.data.type.ExprCoreType.DATETIME; @@ -113,6 +114,7 @@ public void register(BuiltinFunctionRepository repository) { repository.register(maketime()); repository.register(microsecond()); repository.register(minute()); + repository.register(minute_of_day()); repository.register(month(BuiltinFunctionName.MONTH)); repository.register(month(BuiltinFunctionName.MONTH_OF_YEAR)); repository.register(monthName()); @@ -432,6 +434,19 @@ private DefaultFunctionResolver minute() { ); } + /** + * MINUTE(STRING/TIME/DATETIME/TIMESTAMP). return the minute value for time. + */ + private DefaultFunctionResolver minute_of_day() { + return define(BuiltinFunctionName.MINUTE_OF_DAY.getName(), + impl(nullMissingHandling(DateTimeFunction::exprMinuteOfDay), INTEGER, STRING), + impl(nullMissingHandling(DateTimeFunction::exprMinuteOfDay), INTEGER, TIME), + impl(nullMissingHandling(DateTimeFunction::exprMinuteOfDay), INTEGER, DATE), + impl(nullMissingHandling(DateTimeFunction::exprMinuteOfDay), INTEGER, DATETIME), + impl(nullMissingHandling(DateTimeFunction::exprMinuteOfDay), INTEGER, TIMESTAMP) + ); + } + /** * MONTH(STRING/DATE/DATETIME/TIMESTAMP). return the month for date (1-12). */ @@ -902,6 +917,17 @@ private ExprValue exprMinute(ExprValue time) { return new ExprIntegerValue(time.timeValue().getMinute()); } + /** + * Minute_of_day implementation for ExprValue. + * + * @param time ExprValue of Time/String type. + * @return ExprValue. + */ + private ExprValue exprMinuteOfDay(ExprValue time) { + return new ExprIntegerValue( + MINUTES.between(LocalTime.MIN, time.timeValue())); + } + /** * Month for date implementation for ExprValue. * 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 b23c7613d6..dfa48991a5 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 { MAKETIME(FunctionName.of("maketime")), MICROSECOND(FunctionName.of("microsecond")), MINUTE(FunctionName.of("minute")), + MINUTE_OF_DAY(FunctionName.of("minute_of_day")), 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 092b64d5d7..29a0843287 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 @@ -614,6 +614,37 @@ public void hour() { assertEquals("hour(\"2020-08-17 01:02:03\")", expression.toString()); } + private void testInvalidMinuteOfDay(String date) { + FunctionExpression expression = DSL.minute_of_day(DSL.literal(new ExprDateValue(date))); + eval(expression); + } + + @Test + public void invalidMinuteOfDay() { + lenient().when(nullRef.valueOf(env)).thenReturn(nullValue()); + lenient().when(missingRef.valueOf(env)).thenReturn(missingValue()); + + assertThrows(SemanticCheckException.class, + () -> testInvalidMinuteOfDay("2022-12-14 12:23:3400")); + assertThrows(SemanticCheckException.class, + () -> testInvalidMinuteOfDay("2022-12-14 12:2300:34")); + assertThrows(SemanticCheckException.class, + () -> testInvalidMinuteOfDay("2022-12-14 1200:23:34")); + assertThrows(SemanticCheckException.class, + () -> testInvalidMinuteOfDay("2022-12-1400 12:23:34")); + assertThrows(SemanticCheckException.class, + () -> testInvalidMinuteOfDay("2022-1200-14 12:23:34")); + assertThrows(SemanticCheckException.class, + () -> testInvalidMinuteOfDay("12:23:3400")); + assertThrows(SemanticCheckException.class, + () -> testInvalidMinuteOfDay("12:2300:34")); + assertThrows(SemanticCheckException.class, + () -> testInvalidMinuteOfDay("1200:23:34")); + assertThrows(SemanticCheckException.class, + () -> testInvalidMinuteOfDay("asdfasdfasdf")); + + } + @Test public void microsecond() { when(nullRef.type()).thenReturn(TIME); @@ -691,6 +722,48 @@ public void minute() { assertEquals("minute(\"2020-08-17 01:02:03\")", expression.toString()); } + private void testMinuteOfDay(String date, int value) { + FunctionExpression expression = DSL.minute_of_day(DSL.literal(new ExprTimeValue(date))); + assertEquals(INTEGER, expression.type()); + assertEquals(integerValue(value), eval(expression)); + } + + @Test + public void minuteOfDay() { + when(nullRef.type()).thenReturn(TIME); + when(missingRef.type()).thenReturn(TIME); + assertEquals(nullValue(), eval(DSL.minute_of_day(nullRef))); + assertEquals(missingValue(), eval(DSL.minute_of_day(missingRef))); + + FunctionExpression expression = DSL.minute_of_day(DSL.literal(new ExprTimeValue("01:02:03"))); + assertEquals(INTEGER, expression.type()); + assertEquals(integerValue(62), eval(expression)); + assertEquals("minute_of_day(TIME '01:02:03')", expression.toString()); + + expression = DSL.minute_of_day(DSL.literal("01:02:03")); + assertEquals(INTEGER, expression.type()); + assertEquals(integerValue(62), eval(expression)); + assertEquals("minute_of_day(\"01:02:03\")", expression.toString()); + + expression = DSL.minute_of_day(DSL.literal(new ExprTimestampValue("2020-08-17 01:02:03"))); + assertEquals(INTEGER, expression.type()); + assertEquals(integerValue(62), expression.valueOf(env)); + assertEquals("minute_of_day(TIMESTAMP '2020-08-17 01:02:03')", expression.toString()); + + expression = DSL.minute_of_day(DSL.literal(new ExprDatetimeValue("2020-08-17 01:02:03"))); + assertEquals(INTEGER, expression.type()); + assertEquals(integerValue(62), expression.valueOf(env)); + assertEquals("minute_of_day(DATETIME '2020-08-17 01:02:03')", expression.toString()); + + expression = DSL.minute_of_day(DSL.literal("2020-08-17 01:02:03")); + assertEquals(INTEGER, expression.type()); + assertEquals(integerValue(62), expression.valueOf(env)); + assertEquals("minute_of_day(\"2020-08-17 01:02:03\")", expression.toString()); + + testMinuteOfDay("2020-08-17 23:59:59", 1439); + testMinuteOfDay("2020-08-17 00:00:01", 0); + } + @Test public void month() { when(nullRef.type()).thenReturn(DATE); diff --git a/docs/user/dql/functions.rst b/docs/user/dql/functions.rst index 843d6c7e45..d113e9bf9e 100644 --- a/docs/user/dql/functions.rst +++ b/docs/user/dql/functions.rst @@ -1762,6 +1762,28 @@ Example:: | 2 | +-----------------------------+ +MINUTE_OF_DAY +------ + +Description +>>>>>>>>>>> + +Usage: minute_of_day(time) returns the minute value for time within a 24 hour day, in the range 0 to 1439. + +Argument type: STRING/TIME/DATETIME/TIMESTAMP + +Return type: INTEGER + +Example:: + + os> SELECT MINUTE_OF_DAY((TIME '01:02:03')) + fetched rows / total rows = 1/1 + +------------------------------------+ + | MINUTE_OF_DAY((TIME '01:02:03')) | + |------------------------------------| + | 62 | + +------------------------------------+ + MONTH ----- 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 3503877d64..f8ce2a71a1 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 @@ -364,6 +364,30 @@ public void testMinute() throws IOException { verifyDataRows(result, rows(30)); } + + @Test + public void testMinuteOfDay() throws IOException { + JSONObject result = executeQuery("select minute_of_day(timestamp('2020-09-16 17:30:00'))"); + verifySchema(result, schema("minute_of_day(timestamp('2020-09-16 17:30:00'))", null, "integer")); + verifyDataRows(result, rows(1050)); + + result = executeQuery("select minute_of_day(datetime('2020-09-16 17:30:00'))"); + verifySchema(result, schema("minute_of_day(datetime('2020-09-16 17:30:00'))", null, "integer")); + verifyDataRows(result, rows(1050)); + + result = executeQuery("select minute_of_day(time('17:30:00'))"); + verifySchema(result, schema("minute_of_day(time('17:30:00'))", null, "integer")); + verifyDataRows(result, rows(1050)); + + result = executeQuery("select minute_of_day('2020-09-16 17:30:00')"); + verifySchema(result, schema("minute_of_day('2020-09-16 17:30:00')", null, "integer")); + verifyDataRows(result, rows(1050)); + + result = executeQuery("select minute_of_day('17:30:00')"); + verifySchema(result, schema("minute_of_day('17:30:00')", null, "integer")); + verifyDataRows(result, rows(1050)); + } + @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 58d4be1813..ab1f673de8 100644 --- a/sql/src/main/antlr/OpenSearchSQLParser.g4 +++ b/sql/src/main/antlr/OpenSearchSQLParser.g4 @@ -433,6 +433,7 @@ dateTimeFunctionName | MAKETIME | MICROSECOND | MINUTE + | MINUTE_OF_DAY | 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 bfd0f93ec9..05680f994d 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 @@ -448,6 +448,14 @@ public void can_parse_match_phrase_relevance_function() { assertNotNull(parser.parse("SELECT * FROM test WHERE match_phrase(column, 100500)")); } + @Test + public void can_parse_minute_of_day_function() { + assertNotNull(parser.parse("SELECT minute_of_day(\"12:23:34\");")); + assertNotNull(parser.parse("SELECT minute_of_day('12:23:34');"));; + assertNotNull(parser.parse("SELECT minute_of_day(\"2022-12-14 12:23:34\");"));; + assertNotNull(parser.parse("SELECT minute_of_day('2022-12-14 12:23:34');"));; + } + @Test public void can_parse_wildcard_query_relevance_function() { assertNotNull(