From bac9c37f3020ea04b581c12c7a492d17ffe9636b Mon Sep 17 00:00:00 2001 From: GabeFernandez310 Date: Fri, 6 Jan 2023 11:06:20 -0800 Subject: [PATCH] Add Day_Of_Week Function As An Alias Of DayOfWeek (#190) (#1228) Added Implementation And Testing For Day_Of_Week Function Signed-off-by: GabeFernandez310 Signed-off-by: GabeFernandez310 --- .../org/opensearch/sql/expression/DSL.java | 10 +- .../expression/datetime/DateTimeFunction.java | 24 ++- .../function/BuiltinFunctionName.java | 1 + .../datetime/DateTimeFunctionTest.java | 162 +++++++++++++++--- docs/user/dql/functions.rst | 15 +- .../sql/sql/DateTimeFunctionIT.java | 43 +++++ sql/src/main/antlr/OpenSearchSQLParser.g4 | 1 + .../sql/sql/antlr/SQLSyntaxParserTest.java | 6 + 8 files changed, 226 insertions(+), 36 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 b3972af86d..bb343cd5f9 100644 --- a/core/src/main/java/org/opensearch/sql/expression/DSL.java +++ b/core/src/main/java/org/opensearch/sql/expression/DSL.java @@ -326,8 +326,9 @@ public static FunctionExpression dayofmonth(Expression... expressions) { return compile(FunctionProperties.None, BuiltinFunctionName.DAYOFMONTH, expressions); } - public static FunctionExpression dayofweek(Expression... expressions) { - return compile(FunctionProperties.None, BuiltinFunctionName.DAYOFWEEK, expressions); + public static FunctionExpression dayofweek( + FunctionProperties functionProperties, Expression... expressions) { + return compile(functionProperties, BuiltinFunctionName.DAYOFWEEK, expressions); } public static FunctionExpression dayofyear(Expression... expressions) { @@ -338,6 +339,11 @@ public static FunctionExpression day_of_year(Expression... expressions) { return compile(FunctionProperties.None, BuiltinFunctionName.DAY_OF_YEAR, expressions); } + public static FunctionExpression day_of_week( + FunctionProperties functionProperties, Expression... expressions) { + return compile(functionProperties, BuiltinFunctionName.DAY_OF_WEEK, expressions); + } + public static FunctionExpression from_days(Expression... expressions) { return compile(FunctionProperties.None, BuiltinFunctionName.FROM_DAYS, 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 4ad395537f..37f5babe89 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 @@ -109,7 +109,8 @@ public void register(BuiltinFunctionRepository repository) { repository.register(day()); repository.register(dayName()); repository.register(dayOfMonth()); - repository.register(dayOfWeek()); + repository.register(dayOfWeek(BuiltinFunctionName.DAYOFWEEK.getName())); + repository.register(dayOfWeek(BuiltinFunctionName.DAY_OF_WEEK.getName())); repository.register(dayOfYear(BuiltinFunctionName.DAYOFYEAR)); repository.register(dayOfYear(BuiltinFunctionName.DAY_OF_YEAR)); repository.register(from_days()); @@ -401,11 +402,14 @@ private DefaultFunctionResolver dayOfMonth() { } /** - * DAYOFWEEK(STRING/DATE/DATETIME/TIMESTAMP). + * DAYOFWEEK(STRING/DATE/DATETIME/TIME/TIMESTAMP). * return the weekday index for date (1 = Sunday, 2 = Monday, …, 7 = Saturday). */ - private DefaultFunctionResolver dayOfWeek() { - return define(BuiltinFunctionName.DAYOFWEEK.getName(), + private DefaultFunctionResolver dayOfWeek(FunctionName name) { + return define(name, + implWithProperties(nullMissingHandlingWithProperties( + (functionProperties, arg) -> DateTimeFunction.dayOfWeekToday( + functionProperties.getQueryStartClock())), INTEGER, TIME), impl(nullMissingHandling(DateTimeFunction::exprDayOfWeek), INTEGER, DATE), impl(nullMissingHandling(DateTimeFunction::exprDayOfWeek), INTEGER, DATETIME), impl(nullMissingHandling(DateTimeFunction::exprDayOfWeek), INTEGER, TIMESTAMP), @@ -728,6 +732,16 @@ private DefaultFunctionResolver date_format() { ); } + /** + * Day of Week implementation for ExprValue when passing in an arguemt of type TIME. + * + * @param clock Current clock taken from function properties + * @return ExprValue. + */ + private ExprValue dayOfWeekToday(Clock clock) { + return new ExprIntegerValue((formatNow(clock).getDayOfWeek().getValue() % 7) + 1); + } + /** * ADDDATE function implementation for ExprValue. * @@ -900,7 +914,7 @@ private ExprValue exprDayOfMonth(ExprValue date) { /** * Day of Week implementation for ExprValue. * - * @param date ExprValue of Date/String type. + * @param date ExprValue of Date/Datetime/String/Timstamp type. * @return ExprValue. */ private ExprValue exprDayOfWeek(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 eff36c9f64..3fca0a6fc8 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 @@ -70,6 +70,7 @@ public enum BuiltinFunctionName { DAYOFMONTH(FunctionName.of("dayofmonth")), DAYOFWEEK(FunctionName.of("dayofweek")), DAYOFYEAR(FunctionName.of("dayofyear")), + DAY_OF_WEEK(FunctionName.of("day_of_week")), DAY_OF_YEAR(FunctionName.of("day_of_year")), FROM_DAYS(FunctionName.of("from_days")), FROM_UNIXTIME(FunctionName.of("from_unixtime")), 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 617f3f7d3e..7761c1e94c 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 @@ -435,32 +435,150 @@ public void dayOfMonth() { assertEquals(integerValue(8), eval(expression)); } + private void dayOfWeekQuery( + FunctionExpression dateExpression, + int dayOfWeek, + String testExpr) { + assertEquals(INTEGER, dateExpression.type()); + assertEquals(integerValue(dayOfWeek), eval(dateExpression)); + assertEquals(testExpr, dateExpression.toString()); + } + @Test public void dayOfWeek() { + lenient().when(nullRef.valueOf(env)).thenReturn(nullValue()); + lenient().when(missingRef.valueOf(env)).thenReturn(missingValue()); + + FunctionExpression expression1 = DSL.dayofweek( + functionProperties, + DSL.literal(new ExprDateValue("2020-08-07"))); + FunctionExpression expression2 = DSL.dayofweek( + functionProperties, + DSL.literal(new ExprDateValue("2020-08-09"))); + FunctionExpression expression3 = DSL.dayofweek( + functionProperties, + DSL.literal("2020-08-09")); + FunctionExpression expression4 = DSL.dayofweek( + functionProperties, + DSL.literal("2020-08-09 01:02:03")); + + assertAll( + () -> dayOfWeekQuery(expression1, 6, "dayofweek(DATE '2020-08-07')"), + + () -> dayOfWeekQuery(expression2, 1, "dayofweek(DATE '2020-08-09')"), + + () -> dayOfWeekQuery(expression3, 1, "dayofweek(\"2020-08-09\")"), + + () -> dayOfWeekQuery(expression4, 1, "dayofweek(\"2020-08-09 01:02:03\")") + ); + } + + private void dayOfWeekWithUnderscoresQuery( + FunctionExpression dateExpression, + int dayOfWeek, + String testExpr) { + assertEquals(INTEGER, dateExpression.type()); + assertEquals(integerValue(dayOfWeek), eval(dateExpression)); + assertEquals(testExpr, dateExpression.toString()); + } + + @Test + public void dayOfWeekWithUnderscores() { + lenient().when(nullRef.valueOf(env)).thenReturn(nullValue()); + lenient().when(missingRef.valueOf(env)).thenReturn(missingValue()); + + FunctionExpression expression1 = DSL.day_of_week( + functionProperties, + DSL.literal(new ExprDateValue("2020-08-07"))); + FunctionExpression expression2 = DSL.day_of_week( + functionProperties, + DSL.literal(new ExprDateValue("2020-08-09"))); + FunctionExpression expression3 = DSL.day_of_week( + functionProperties, + DSL.literal("2020-08-09")); + FunctionExpression expression4 = DSL.day_of_week( + functionProperties, + DSL.literal("2020-08-09 01:02:03")); + + assertAll( + () -> dayOfWeekWithUnderscoresQuery(expression1, 6, "day_of_week(DATE '2020-08-07')"), + + () -> dayOfWeekWithUnderscoresQuery(expression2, 1, "day_of_week(DATE '2020-08-09')"), + + () -> dayOfWeekWithUnderscoresQuery(expression3, 1, "day_of_week(\"2020-08-09\")"), + + () -> dayOfWeekWithUnderscoresQuery( + expression4, 1, "day_of_week(\"2020-08-09 01:02:03\")") + ); + } + + @Test + public void testDayOfWeekWithTimeType() { + lenient().when(nullRef.valueOf(env)).thenReturn(nullValue()); + lenient().when(missingRef.valueOf(env)).thenReturn(missingValue()); + FunctionExpression expression = DSL.day_of_week( + functionProperties, DSL.literal(new ExprTimeValue("12:23:34"))); + + assertAll( + () -> assertEquals(INTEGER, eval(expression).type()), + () -> assertEquals(( + LocalDate.now( + functionProperties.getQueryStartClock()).getDayOfWeek().getValue() % 7) + 1, + eval(expression).integerValue()), + () -> assertEquals("day_of_week(TIME '12:23:34')", expression.toString()) + ); + } + + private void testInvalidDayOfWeek(String date) { + FunctionExpression expression = DSL.day_of_week( + functionProperties, DSL.literal(new ExprDateValue(date))); + eval(expression); + } + + @Test + public void dayOfWeekWithUnderscoresLeapYear() { + lenient().when(nullRef.valueOf(env)).thenReturn(nullValue()); + lenient().when(missingRef.valueOf(env)).thenReturn(missingValue()); + + assertAll( + //Feb. 29 of a leap year + () -> dayOfWeekWithUnderscoresQuery(DSL.day_of_week( + functionProperties, + DSL.literal("2020-02-29")), 7, "day_of_week(\"2020-02-29\")"), + //day after Feb. 29 of a leap year + () -> dayOfWeekWithUnderscoresQuery(DSL.day_of_week( + functionProperties, + DSL.literal("2020-03-01")), 1, "day_of_week(\"2020-03-01\")"), + //Feb. 28 of a non-leap year + () -> dayOfWeekWithUnderscoresQuery(DSL.day_of_week( + functionProperties, + DSL.literal("2021-02-28")), 1, "day_of_week(\"2021-02-28\")"), + //Feb. 29 of a non-leap year + () -> assertThrows( + SemanticCheckException.class, () -> testInvalidDayOfWeek("2021-02-29")) + ); + } + + @Test + public void dayOfWeekWithUnderscoresInvalidArgument() { when(nullRef.type()).thenReturn(DATE); when(missingRef.type()).thenReturn(DATE); - assertEquals(nullValue(), eval(DSL.dayofweek(nullRef))); - assertEquals(missingValue(), eval(DSL.dayofweek(missingRef))); - - FunctionExpression expression = DSL.dayofweek(DSL.literal(new ExprDateValue("2020-08-07"))); - assertEquals(INTEGER, expression.type()); - assertEquals("dayofweek(DATE '2020-08-07')", expression.toString()); - assertEquals(integerValue(6), eval(expression)); + assertEquals(nullValue(), eval(DSL.day_of_week(functionProperties, nullRef))); + assertEquals(missingValue(), eval(DSL.day_of_week(functionProperties, missingRef))); - expression = DSL.dayofweek(DSL.literal(new ExprDateValue("2020-08-09"))); - assertEquals(INTEGER, expression.type()); - assertEquals("dayofweek(DATE '2020-08-09')", expression.toString()); - assertEquals(integerValue(1), eval(expression)); + assertAll( + //40th day of the month + () -> assertThrows(SemanticCheckException.class, + () -> testInvalidDayOfWeek("2021-02-40")), - expression = DSL.dayofweek(DSL.literal("2020-08-09")); - assertEquals(INTEGER, expression.type()); - assertEquals("dayofweek(\"2020-08-09\")", expression.toString()); - assertEquals(integerValue(1), eval(expression)); + //13th month of the year + () -> assertThrows(SemanticCheckException.class, + () -> testInvalidDayOfWeek("2021-13-29")), - expression = DSL.dayofweek(DSL.literal("2020-08-09 01:02:03")); - assertEquals(INTEGER, expression.type()); - assertEquals("dayofweek(\"2020-08-09 01:02:03\")", expression.toString()); - assertEquals(integerValue(1), eval(expression)); + //incorrect format + () -> assertThrows(SemanticCheckException.class, + () -> testInvalidDayOfWeek("asdfasdf")) + ); } @Test @@ -486,7 +604,7 @@ public void dayOfYear() { assertEquals(integerValue(220), eval(expression)); } - public void testDayOfYearWithUnderscores(String date, int dayOfYear) { + private void testDayOfYearWithUnderscores(String date, int dayOfYear) { FunctionExpression expression = DSL.day_of_year(DSL.literal(new ExprDateValue(date))); assertEquals(INTEGER, expression.type()); assertEquals(integerValue(dayOfYear), eval(expression)); @@ -553,7 +671,7 @@ public void dayOfYearWithUnderscoresLeapYear() { ); } - public void testInvalidDayOfYear(String date) { + private void testInvalidDayOfYear(String date) { FunctionExpression expression = DSL.day_of_year(DSL.literal(new ExprDateValue(date))); eval(expression); } @@ -871,7 +989,7 @@ public void month() { assertEquals(integerValue(8), eval(expression)); } - public void testInvalidDates(String date) throws SemanticCheckException { + private void testInvalidDates(String date) throws SemanticCheckException { FunctionExpression expression = DSL.month_of_year(DSL.literal(new ExprDateValue(date))); eval(expression); } diff --git a/docs/user/dql/functions.rst b/docs/user/dql/functions.rst index 0ca2a6bad4..be5e788ca6 100644 --- a/docs/user/dql/functions.rst +++ b/docs/user/dql/functions.rst @@ -1454,20 +1454,21 @@ Description Usage: dayofweek(date) returns the weekday index for date (1 = Sunday, 2 = Monday, …, 7 = Saturday). +The `day_of_week` function is also provided as an alias. + Argument type: STRING/DATE/DATETIME/TIMESTAMP Return type: INTEGER Example:: - os> SELECT DAYOFWEEK(DATE('2020-08-26')) + os> SELECT DAYOFWEEK('2020-08-26'), DAY_OF_WEEK('2020-08-26') fetched rows / total rows = 1/1 - +---------------------------------+ - | DAYOFWEEK(DATE('2020-08-26')) | - |---------------------------------| - | 4 | - +---------------------------------+ - + +---------------------------+-----------------------------+ + | DAYOFWEEK('2020-08-26') | DAY_OF_WEEK('2020-08-26') | + |---------------------------+-----------------------------| + | 4 | 4 | + +---------------------------+-----------------------------+ DAYOFYEAR 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 6255ccf010..95e232ab21 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 @@ -219,6 +219,49 @@ public void testDayOfWeek() throws IOException { verifyDataRows(result, rows(4)); } + @Test + public void testDayOfWeekWithUnderscores() throws IOException { + JSONObject result = executeQuery("select day_of_week(date('2020-09-16'))"); + verifySchema(result, schema("day_of_week(date('2020-09-16'))", null, "integer")); + verifyDataRows(result, rows(4)); + + result = executeQuery("select day_of_week('2020-09-16')"); + verifySchema(result, schema("day_of_week('2020-09-16')", null, "integer")); + verifyDataRows(result, rows(4)); + } + + @Test + public void testDayOfWeekAliasesReturnTheSameResults() throws IOException { + JSONObject result1 = executeQuery("SELECT dayofweek(date('2022-11-22'))"); + JSONObject result2 = executeQuery("SELECT day_of_week(date('2022-11-22'))"); + verifyDataRows(result1, rows(3)); + result1.getJSONArray("datarows").similar(result2.getJSONArray("datarows")); + + result1 = executeQuery(String.format( + "SELECT dayofweek(CAST(date0 AS date)) FROM %s", TEST_INDEX_CALCS)); + result2 = executeQuery(String.format( + "SELECT day_of_week(CAST(date0 AS date)) FROM %s", TEST_INDEX_CALCS)); + result1.getJSONArray("datarows").similar(result2.getJSONArray("datarows")); + + result1 = executeQuery(String.format( + "SELECT dayofweek(datetime(CAST(time0 AS STRING))) FROM %s", TEST_INDEX_CALCS)); + result2 = executeQuery(String.format( + "SELECT day_of_week(datetime(CAST(time0 AS STRING))) FROM %s", TEST_INDEX_CALCS)); + result1.getJSONArray("datarows").similar(result2.getJSONArray("datarows")); + + result1 = executeQuery(String.format( + "SELECT dayofweek(CAST(time0 AS STRING)) FROM %s", TEST_INDEX_CALCS)); + result2 = executeQuery(String.format( + "SELECT day_of_week(CAST(time0 AS STRING)) FROM %s", TEST_INDEX_CALCS)); + result1.getJSONArray("datarows").similar(result2.getJSONArray("datarows")); + + result1 = executeQuery(String.format( + "SELECT dayofweek(CAST(datetime0 AS timestamp)) FROM %s", TEST_INDEX_CALCS)); + result2 = executeQuery(String.format( + "SELECT day_of_week(CAST(datetime0 AS timestamp)) FROM %s", TEST_INDEX_CALCS)); + result1.getJSONArray("datarows").similar(result2.getJSONArray("datarows")); + } + @Test public void testDayOfYear() throws IOException { JSONObject result = executeQuery("select dayofyear(date('2020-09-16'))"); diff --git a/sql/src/main/antlr/OpenSearchSQLParser.g4 b/sql/src/main/antlr/OpenSearchSQLParser.g4 index 0ef7e21b42..e8db0de53b 100644 --- a/sql/src/main/antlr/OpenSearchSQLParser.g4 +++ b/sql/src/main/antlr/OpenSearchSQLParser.g4 @@ -435,6 +435,7 @@ dateTimeFunctionName | DAYOFMONTH | DAYOFWEEK | DAYOFYEAR + | DAY_OF_WEEK | FROM_DAYS | FROM_UNIXTIME | HOUR 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 eee476a56a..32715a64e1 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_day_of_week_functions() { + assertNotNull(parser.parse("SELECT dayofweek('2022-11-18')")); + assertNotNull(parser.parse("SELECT day_of_week('2022-11-18')")); + } + @Test public void can_parse_dayofyear_functions() { assertNotNull(parser.parse("SELECT dayofyear('2022-11-18')"));