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..51b6636493 100644 --- a/core/src/main/java/org/opensearch/sql/expression/DSL.java +++ b/core/src/main/java/org/opensearch/sql/expression/DSL.java @@ -322,8 +322,10 @@ public static FunctionExpression dayname(Expression... expressions) { return compile(FunctionProperties.None, BuiltinFunctionName.DAYNAME, expressions); } - public static FunctionExpression dayofmonth(Expression... expressions) { - return compile(FunctionProperties.None, BuiltinFunctionName.DAYOFMONTH, expressions); + public static FunctionExpression dayofmonth( + FunctionProperties functionProperties, + Expression... expressions) { + return compile(functionProperties, BuiltinFunctionName.DAYOFMONTH, expressions); } public static FunctionExpression dayofweek(Expression... expressions) { @@ -334,6 +336,12 @@ public static FunctionExpression dayofyear(Expression... expressions) { return compile(FunctionProperties.None, BuiltinFunctionName.DAYOFYEAR, expressions); } + public static FunctionExpression day_of_month( + FunctionProperties functionProperties, + Expression... expressions) { + return compile(functionProperties, BuiltinFunctionName.DAY_OF_MONTH, expressions); + } + public static FunctionExpression day_of_year(Expression... expressions) { return compile(FunctionProperties.None, BuiltinFunctionName.DAY_OF_YEAR, 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..651a916b28 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.DAYS; 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; @@ -20,6 +21,7 @@ import static org.opensearch.sql.expression.function.FunctionDSL.impl; import static org.opensearch.sql.expression.function.FunctionDSL.implWithProperties; import static org.opensearch.sql.expression.function.FunctionDSL.nullMissingHandling; +import static org.opensearch.sql.expression.function.FunctionDSL.nullMissingHandlingWithProperties; import static org.opensearch.sql.utils.DateTimeFormatters.DATE_FORMATTER_LONG_YEAR; import static org.opensearch.sql.utils.DateTimeFormatters.DATE_FORMATTER_SHORT_YEAR; import static org.opensearch.sql.utils.DateTimeFormatters.DATE_TIME_FORMATTER_LONG_YEAR; @@ -57,7 +59,6 @@ import org.opensearch.sql.data.model.ExprValue; import org.opensearch.sql.data.type.ExprCoreType; import org.opensearch.sql.exception.ExpressionEvaluationException; -import org.opensearch.sql.exception.SemanticCheckException; import org.opensearch.sql.expression.function.BuiltinFunctionName; import org.opensearch.sql.expression.function.BuiltinFunctionRepository; import org.opensearch.sql.expression.function.DefaultFunctionResolver; @@ -100,7 +101,8 @@ public void register(BuiltinFunctionRepository repository) { repository.register(date_sub()); repository.register(day()); repository.register(dayName()); - repository.register(dayOfMonth()); + repository.register(dayOfMonth(BuiltinFunctionName.DAYOFMONTH)); + repository.register(dayOfMonth(BuiltinFunctionName.DAY_OF_MONTH)); repository.register(dayOfWeek()); repository.register(dayOfYear(BuiltinFunctionName.DAYOFYEAR)); repository.register(dayOfYear(BuiltinFunctionName.DAY_OF_YEAR)); @@ -336,12 +338,15 @@ private DefaultFunctionResolver dayName() { /** * DAYOFMONTH(STRING/DATE/DATETIME/TIMESTAMP). return the day of the month (1-31). */ - private DefaultFunctionResolver dayOfMonth() { - return define(BuiltinFunctionName.DAYOFMONTH.getName(), + private DefaultFunctionResolver dayOfMonth(BuiltinFunctionName name) { + return define(name.getName(), + implWithProperties(nullMissingHandlingWithProperties( + (functionProperties, arg) -> DateTimeFunction.dayOfMonthToday( + functionProperties.getQueryStartClock())), INTEGER, TIME), impl(nullMissingHandling(DateTimeFunction::exprDayOfMonth), INTEGER, DATE), impl(nullMissingHandling(DateTimeFunction::exprDayOfMonth), INTEGER, DATETIME), - impl(nullMissingHandling(DateTimeFunction::exprDayOfMonth), INTEGER, TIMESTAMP), - impl(nullMissingHandling(DateTimeFunction::exprDayOfMonth), INTEGER, STRING) + impl(nullMissingHandling(DateTimeFunction::exprDayOfMonth), INTEGER, STRING), + impl(nullMissingHandling(DateTimeFunction::exprDayOfMonth), INTEGER, TIMESTAMP) ); } @@ -615,6 +620,10 @@ private DefaultFunctionResolver date_format() { ); } + private ExprValue dayOfMonthToday(Clock clock) { + return new ExprIntegerValue(LocalDateTime.now(clock).getDayOfMonth()); + } + /** * ADDDATE function implementation for ExprValue. * @@ -761,7 +770,7 @@ private ExprValue exprDayName(ExprValue date) { /** * Day of Month implementation for ExprValue. * - * @param date ExprValue of Date/String type. + * @param date ExprValue of Date/Datetime/String/Time/Timestamp type. * @return ExprValue. */ private ExprValue exprDayOfMonth(ExprValue date) { 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..1435e8a556 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 @@ -67,6 +67,7 @@ public enum BuiltinFunctionName { DAY(FunctionName.of("day")), DAYNAME(FunctionName.of("dayname")), DAYOFMONTH(FunctionName.of("dayofmonth")), + DAY_OF_MONTH(FunctionName.of("day_of_month")), DAYOFWEEK(FunctionName.of("dayofweek")), DAYOFYEAR(FunctionName.of("dayofyear")), DAY_OF_YEAR(FunctionName.of("day_of_year")), 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..3be327db91 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 @@ -416,20 +416,101 @@ public void dayName() { public void dayOfMonth() { when(nullRef.type()).thenReturn(DATE); when(missingRef.type()).thenReturn(DATE); - assertEquals(nullValue(), eval(DSL.dayofmonth(nullRef))); - assertEquals(missingValue(), eval(DSL.dayofmonth(missingRef))); + assertEquals(nullValue(), eval(DSL.dayofmonth(functionProperties, nullRef))); + assertEquals(missingValue(), eval(DSL.dayofmonth(functionProperties, missingRef))); - FunctionExpression expression = DSL.dayofmonth(DSL.literal(new ExprDateValue("2020-08-07"))); + FunctionExpression expression = DSL.dayofmonth( + functionProperties, DSL.literal(new ExprDateValue("2020-08-07"))); assertEquals(INTEGER, expression.type()); assertEquals("dayofmonth(DATE '2020-08-07')", expression.toString()); assertEquals(integerValue(7), eval(expression)); - expression = DSL.dayofmonth(DSL.literal("2020-07-08")); + expression = DSL.dayofmonth(functionProperties, DSL.literal("2020-07-08")); assertEquals(INTEGER, expression.type()); assertEquals("dayofmonth(\"2020-07-08\")", expression.toString()); assertEquals(integerValue(8), eval(expression)); } + private void testDayOfMonthWithUnderscores(FunctionExpression dateExpression, int dayOfMonth) { + assertEquals(INTEGER, dateExpression.type()); + assertEquals(integerValue(dayOfMonth), eval(dateExpression)); + } + + @Test + public void dayOfMonthWithUnderscores() { + lenient().when(nullRef.valueOf(env)).thenReturn(nullValue()); + lenient().when(missingRef.valueOf(env)).thenReturn(missingValue()); + + + FunctionExpression expression1 = DSL.dayofmonth( + functionProperties, DSL.literal(new ExprDateValue("2020-08-07"))); + FunctionExpression expression2 = DSL.dayofmonth(functionProperties, DSL.literal("2020-07-08")); + + assertAll( + () -> testDayOfMonthWithUnderscores(expression1, 7), + () -> assertEquals("dayofmonth(DATE '2020-08-07')", expression1.toString()), + + () -> testDayOfMonthWithUnderscores(expression2, 8), + () -> assertEquals("dayofmonth(\"2020-07-08\")", expression2.toString()) + + ); + } + + @Test + public void testDayOfMonthWithTimeType() { + lenient().when(nullRef.valueOf(env)).thenReturn(nullValue()); + lenient().when(missingRef.valueOf(env)).thenReturn(missingValue()); + FunctionExpression expression = DSL.day_of_month( + functionProperties, DSL.literal(new ExprTimeValue("12:23:34"))); + + assertEquals(INTEGER, eval(expression).type()); + assertEquals( + LocalDate.now(functionProperties.getQueryStartClock()).getDayOfMonth(), + eval(expression).integerValue()); + assertEquals("day_of_month(TIME '12:23:34')", expression.toString()); + } + + private void testInvalidDayOfMonth(String date) { + FunctionExpression expression = DSL.day_of_month( + functionProperties, DSL.literal(new ExprDateValue(date))); + eval(expression); + } + + @Test + public void dayOfMonthWithUnderscoresLeapYear() { + lenient().when(nullRef.valueOf(env)).thenReturn(nullValue()); + lenient().when(missingRef.valueOf(env)).thenReturn(missingValue()); + + //Feb. 29 of a leap year + testDayOfMonthWithUnderscores(DSL.day_of_month( + functionProperties, DSL.literal("2020-02-29")), 29); + + //Feb. 29 of a non-leap year + assertThrows(SemanticCheckException.class, () -> testInvalidDayOfMonth("2021-02-29")); + } + + @Test + public void dayOfMonthWithUnderscoresInvalidArguments() { + lenient().when(nullRef.type()).thenReturn(DATE); + lenient().when(missingRef.type()).thenReturn(DATE); + + assertAll( + () -> assertEquals(nullValue(), eval(DSL.day_of_month(functionProperties, nullRef))), + () -> assertEquals( + missingValue(), eval(DSL.day_of_month(functionProperties, missingRef))), + + //40th day of the month + () -> assertThrows( + SemanticCheckException.class, () -> testInvalidDayOfMonth("2021-02-40")), + //13th month of the year + () -> assertThrows( + SemanticCheckException.class, () -> testInvalidDayOfMonth("2021-13-40")), + //incorrect format + () -> assertThrows( + SemanticCheckException.class, () -> testInvalidDayOfMonth("asdfasdfasdf")) + ); + } + @Test public void dayOfWeek() { when(nullRef.type()).thenReturn(DATE); diff --git a/docs/user/dql/functions.rst b/docs/user/dql/functions.rst index 843d6c7e45..8819b05d25 100644 --- a/docs/user/dql/functions.rst +++ b/docs/user/dql/functions.rst @@ -1359,13 +1359,13 @@ DAY Description >>>>>>>>>>> -Usage: day(date) extracts the day of the month for date, in the range 1 to 31. The dates with value 0 such as '0000-00-00' or '2008-00-00' are invalid. +Usage: day(date) extracts the day of the month for date, in the range 1 to 31. -Argument type: STRING/DATE/DATETIME/TIMESTAMP +Argument type: STRING/DATE/DATETIME/TIME/TIMESTAMP Return type: INTEGER -Synonyms: DAYOFMONTH +Synonyms: `DAYOFMONTH`_, `DAY_OF_MONTH`_ Example:: @@ -1407,13 +1407,13 @@ DAYOFMONTH Description >>>>>>>>>>> -Usage: dayofmonth(date) extracts the day of the month for date, in the range 1 to 31. The dates with value 0 such as '0000-00-00' or '2008-00-00' are invalid. +Usage: dayofmonth(date) extracts the day of the month for date, in the range 1 to 31. -Argument type: STRING/DATE/DATETIME/TIMESTAMP +Argument type: STRING/DATE/DATETIME/TIME/TIMESTAMP Return type: INTEGER -Synonyms: DAY +Synonyms: `DAY`_, `DAY_OF_MONTH`_ Example:: @@ -1425,6 +1425,29 @@ Example:: | 26 | +----------------------------------+ +DAY_OF_MONTH +------------ + +Description +>>>>>>>>>>> + +Usage: day_of_month(date) extracts the day of the month for date, in the range 1 to 31. + +Argument type: STRING/DATE/TIME/DATETIME/TIMESTAMP + +Return type: INTEGER + +Synonyms: `DAY`_, `DAYOFMONTH`_ + +Example:: + + os> SELECT DAY_OF_MONTH('2020-08-26') + fetched rows / total rows = 1/1 + +------------------------------+ + | DAY_OF_MONTH('2020-08-26') | + |------------------------------| + | 26 | + +------------------------------+ DAYOFWEEK --------- 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..fd638911ee 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 @@ -206,6 +206,48 @@ public void testDayOfMonth() throws IOException { verifyDataRows(result, rows(16)); } + @Test + public void testDayOfMonthWithUnderscores() throws IOException { + JSONObject result = executeQuery("select day_of_month(date('2020-09-16'))"); + verifySchema(result, schema("day_of_month(date('2020-09-16'))", null, "integer")); + verifyDataRows(result, rows(16)); + + result = executeQuery("select day_of_month('2020-09-16')"); + verifySchema(result, schema("day_of_month('2020-09-16')", null, "integer")); + verifyDataRows(result, rows(16)); + } + + @Test + public void testDayOfMonthAliasesReturnTheSameResults() throws IOException { + JSONObject result1 = executeQuery("SELECT dayofmonth(date('2022-11-22'))"); + JSONObject result2 = executeQuery("SELECT day_of_month(date('2022-11-22'))"); + verifyDataRows(result1, rows(22)); + result1.getJSONArray("datarows").similar(result2.getJSONArray("datarows")); + + result1 = executeQuery(String.format( + "SELECT dayofmonth(CAST(date0 AS date)) FROM %s", TEST_INDEX_CALCS)); + result2 = executeQuery(String.format( + "SELECT day_of_month(CAST(date0 AS date)) FROM %s", TEST_INDEX_CALCS)); + result1.getJSONArray("datarows").similar(result2.getJSONArray("datarows")); + + result1 = executeQuery(String.format( + "SELECT dayofmonth(datetime(CAST(time0 AS STRING))) FROM %s", TEST_INDEX_CALCS)); + result2 = executeQuery(String.format( + "SELECT day_of_month(datetime(CAST(time0 AS STRING))) FROM %s", TEST_INDEX_CALCS)); + result1.getJSONArray("datarows").similar(result2.getJSONArray("datarows")); + + result1 = executeQuery(String.format( + "SELECT dayofmonth(CAST(time0 AS STRING)) FROM %s", TEST_INDEX_CALCS)); + result2 = executeQuery(String.format( + "SELECT day_of_month(CAST(time0 AS STRING)) FROM %s", TEST_INDEX_CALCS)); + result1.getJSONArray("datarows").similar(result2.getJSONArray("datarows")); + + result1 = executeQuery(String.format( + "SELECT dayofmonth(CAST(datetime0 AS timestamp)) FROM %s", TEST_INDEX_CALCS)); + result2 = executeQuery(String.format( + "SELECT day_of_month(CAST(datetime0 AS timestamp)) FROM %s", TEST_INDEX_CALCS)); + result1.getJSONArray("datarows").similar(result2.getJSONArray("datarows")); + } @Test public void testDayOfWeek() throws IOException { JSONObject result = executeQuery("select dayofweek(date('2020-09-16'))"); diff --git a/sql/src/main/antlr/OpenSearchSQLParser.g4 b/sql/src/main/antlr/OpenSearchSQLParser.g4 index 47a43362ea..f9c9f79465 100644 --- a/sql/src/main/antlr/OpenSearchSQLParser.g4 +++ b/sql/src/main/antlr/OpenSearchSQLParser.g4 @@ -426,6 +426,7 @@ dateTimeFunctionName | DAY | DAYNAME | DAYOFMONTH + | DAY_OF_MONTH | DAYOFWEEK | DAYOFYEAR | FROM_DAYS 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..67a13cf6e5 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 @@ -197,6 +197,12 @@ public void can_parse_week_of_year_functions() { assertNotNull(parser.parse("SELECT week_of_year('2022-11-18')")); } + @Test + public void can_parse_dayofmonth_functions() { + assertNotNull(parser.parse("SELECT dayofmonth('2022-11-18')")); + assertNotNull(parser.parse("SELECT day_of_month('2022-11-18')")); + } + @Test public void can_parse_dayofyear_functions() { assertNotNull(parser.parse("SELECT dayofyear('2022-11-18')"));