From 11083797bf69acf8348f79bca438e8c2cb8187f4 Mon Sep 17 00:00:00 2001 From: GabeFernandez310 Date: Mon, 9 Jan 2023 19:13:47 -0800 Subject: [PATCH] Add Day_Of_Month Function As An Alias Of DayOfMonth (#1227) * Add Day_Of_Month Function As An Alias Of DayOfMonth (#194) Signed-off-by: GabeFernandez310 --- .../org/opensearch/sql/expression/DSL.java | 12 ++- .../expression/datetime/DateTimeFunction.java | 22 +++-- .../function/BuiltinFunctionName.java | 1 + .../datetime/DateTimeFunctionTest.java | 89 ++++++++++++++++++- docs/user/dql/functions.rst | 35 ++++++-- .../sql/sql/DateTimeFunctionIT.java | 42 +++++++++ sql/src/main/antlr/OpenSearchSQLParser.g4 | 1 + .../sql/sql/antlr/SQLSyntaxParserTest.java | 6 ++ 8 files changed, 189 insertions(+), 19 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 611053f0bf..d6a1d140dd 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( @@ -335,6 +337,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( FunctionProperties functionProperties, Expression... expressions) { return compile(functionProperties, 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 68227d1ba4..5007d2fd49 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 @@ -64,7 +64,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; @@ -113,7 +112,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(BuiltinFunctionName.DAYOFWEEK.getName())); repository.register(dayOfWeek(BuiltinFunctionName.DAY_OF_WEEK.getName())); repository.register(dayOfYear(BuiltinFunctionName.DAYOFYEAR)); @@ -444,12 +444,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) ); } @@ -844,6 +847,11 @@ private DefaultFunctionResolver date_format() { ); } + + private ExprValue dayOfMonthToday(Clock clock) { + return new ExprIntegerValue(LocalDateTime.now(clock).getDayOfMonth()); + } + private ExprValue dayOfYearToday(Clock clock) { return new ExprIntegerValue(LocalDateTime.now(clock).getDayOfYear()); } @@ -1058,7 +1066,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 5572f41a9f..a0c12cc034 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 @@ -69,6 +69,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_WEEK(FunctionName.of("day_of_week")), 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 76f35dc68c..deb1eb5fe6 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 @@ -422,20 +422,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")) + ); + } + private void dayOfWeekQuery( FunctionExpression dateExpression, int dayOfWeek, diff --git a/docs/user/dql/functions.rst b/docs/user/dql/functions.rst index 4d0ab13879..648cbed585 100644 --- a/docs/user/dql/functions.rst +++ b/docs/user/dql/functions.rst @@ -1499,13 +1499,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:: @@ -1547,13 +1547,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:: @@ -1565,6 +1565,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 85661bfa97..2f261b7cfc 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 @@ -208,6 +208,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 ae72521dec..c9424eb09e 100644 --- a/sql/src/main/antlr/OpenSearchSQLParser.g4 +++ b/sql/src/main/antlr/OpenSearchSQLParser.g4 @@ -431,6 +431,7 @@ dateTimeFunctionName | DAY | DAYNAME | DAYOFMONTH + | DAY_OF_MONTH | DAYOFWEEK | DAYOFYEAR | DAY_OF_YEAR 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 32715a64e1..97cf84b9ee 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 @@ -201,6 +201,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_day_of_week_functions() { assertNotNull(parser.parse("SELECT dayofweek('2022-11-18')"));