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 616f431283..7f369bc342 100644 --- a/core/src/main/java/org/opensearch/sql/expression/DSL.java +++ b/core/src/main/java/org/opensearch/sql/expression/DSL.java @@ -439,6 +439,11 @@ public static FunctionExpression week( return compile(functionProperties, BuiltinFunctionName.WEEK, expressions); } + public static FunctionExpression weekday(FunctionProperties functionProperties, + Expression... expressions) { + return compile(functionProperties, BuiltinFunctionName.WEEKDAY, expressions); + } + public static FunctionExpression weekofyear( FunctionProperties functionProperties, Expression... expressions) { return compile(functionProperties, BuiltinFunctionName.WEEKOFYEAR, 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 fc8cdc93ef..05cd35e25e 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 @@ -195,6 +195,7 @@ public void register(BuiltinFunctionRepository repository) { repository.register(week(BuiltinFunctionName.WEEK)); repository.register(week(BuiltinFunctionName.WEEKOFYEAR)); repository.register(week(BuiltinFunctionName.WEEK_OF_YEAR)); + repository.register(weekday()); repository.register(year()); } @@ -888,6 +889,19 @@ private DefaultFunctionResolver week(BuiltinFunctionName week) { ); } + private DefaultFunctionResolver weekday() { + return define(BuiltinFunctionName.WEEKDAY.getName(), + implWithProperties(nullMissingHandlingWithProperties( + (functionProperties, arg) -> new ExprIntegerValue( + formatNow(functionProperties.getQueryStartClock()).getDayOfWeek().getValue() - 1)), + INTEGER, TIME), + impl(nullMissingHandling(DateTimeFunction::exprWeekday), INTEGER, DATE), + impl(nullMissingHandling(DateTimeFunction::exprWeekday), INTEGER, DATETIME), + impl(nullMissingHandling(DateTimeFunction::exprWeekday), INTEGER, TIMESTAMP), + impl(nullMissingHandling(DateTimeFunction::exprWeekday), INTEGER, STRING) + ); + } + /** * YEAR(STRING/DATE/DATETIME/TIMESTAMP). return the year for date (1000-9999). */ @@ -1637,6 +1651,16 @@ private ExprValue exprWeek(ExprValue date, ExprValue mode) { CalendarLookup.getWeekNumber(mode.integerValue(), date.dateValue())); } + /** + * Weekday implementation for ExprValue. + * + * @param date ExprValue of Date/Datetime/String/Timstamp type. + * @return ExprValue. + */ + private ExprValue exprWeekday(ExprValue date) { + return new ExprIntegerValue(date.dateValue().getDayOfWeek().getValue() - 1); + } + private ExprValue unixTimeStamp(Clock clock) { return new ExprLongValue(Instant.now(clock).getEpochSecond()); } 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 ec4a7bc140..968950c09a 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 @@ -109,6 +109,7 @@ public enum BuiltinFunctionName { UTC_TIMESTAMP(FunctionName.of("utc_timestamp")), UNIX_TIMESTAMP(FunctionName.of("unix_timestamp")), WEEK(FunctionName.of("week")), + WEEKDAY(FunctionName.of("weekday")), WEEKOFYEAR(FunctionName.of("weekofyear")), WEEK_OF_YEAR(FunctionName.of("week_of_year")), YEAR(FunctionName.of("year")), diff --git a/core/src/test/java/org/opensearch/sql/expression/datetime/WeekdayTest.java b/core/src/test/java/org/opensearch/sql/expression/datetime/WeekdayTest.java new file mode 100644 index 0000000000..4b97639996 --- /dev/null +++ b/core/src/test/java/org/opensearch/sql/expression/datetime/WeekdayTest.java @@ -0,0 +1,139 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + + +package org.opensearch.sql.expression.datetime; + +import static org.junit.jupiter.api.Assertions.assertAll; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.opensearch.sql.data.model.ExprValueUtils.integerValue; +import static org.opensearch.sql.data.type.ExprCoreType.INTEGER; + +import java.time.LocalDate; +import java.util.stream.Stream; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import org.opensearch.sql.data.model.ExprDateValue; +import org.opensearch.sql.data.model.ExprTimeValue; +import org.opensearch.sql.data.model.ExprValue; +import org.opensearch.sql.exception.SemanticCheckException; +import org.opensearch.sql.expression.DSL; +import org.opensearch.sql.expression.Expression; +import org.opensearch.sql.expression.ExpressionTestBase; +import org.opensearch.sql.expression.FunctionExpression; +import org.opensearch.sql.expression.LiteralExpression; + + +class WeekdayTest extends ExpressionTestBase { + + private void weekdayQuery( + FunctionExpression dateExpression, + int dayOfWeek, + String testExpr) { + + assertAll( + () -> assertEquals(INTEGER, dateExpression.type()), + () -> assertEquals(integerValue(dayOfWeek), eval(dateExpression)), + () -> assertEquals(testExpr, dateExpression.toString()) + ); + } + + private static Stream getTestDataForWeekday() { + return Stream.of( + Arguments.of( + DSL.literal(new ExprDateValue("2020-08-07")), + 4, + "weekday(DATE '2020-08-07')"), + Arguments.of( + DSL.literal(new ExprDateValue("2020-08-09")), + 6, + "weekday(DATE '2020-08-09')"), + Arguments.of( + DSL.literal("2020-08-09"), + 6, + "weekday(\"2020-08-09\")"), + Arguments.of( + DSL.literal("2020-08-09 01:02:03"), + 6, + "weekday(\"2020-08-09 01:02:03\")") + ); + } + + @MethodSource("getTestDataForWeekday") + @ParameterizedTest + public void weekday(LiteralExpression arg, int expectedInt, String expectedString) { + FunctionExpression expression = DSL.weekday( + functionProperties, + arg); + + weekdayQuery(expression, expectedInt, expectedString); + } + + @Test + public void testWeekdayWithTimeType() { + FunctionExpression expression = DSL.weekday( + functionProperties, DSL.literal(new ExprTimeValue("12:23:34"))); + + assertAll( + () -> assertEquals(INTEGER, eval(expression).type()), + () -> assertEquals(( + LocalDate.now( + functionProperties.getQueryStartClock()).getDayOfWeek().getValue() - 1), + eval(expression).integerValue()), + () -> assertEquals("weekday(TIME '12:23:34')", expression.toString()) + ); + } + + private void testInvalidWeekday(String date) { + FunctionExpression expression = DSL.weekday( + functionProperties, DSL.literal(new ExprDateValue(date))); + eval(expression); + } + + @Test + public void weekdayLeapYear() { + assertAll( + //Feb. 29 of a leap year + () -> weekdayQuery(DSL.weekday( + functionProperties, + DSL.literal("2020-02-29")), 5, "weekday(\"2020-02-29\")"), + //day after Feb. 29 of a leap year + () -> weekdayQuery(DSL.weekday( + functionProperties, + DSL.literal("2020-03-01")), 6, "weekday(\"2020-03-01\")"), + //Feb. 28 of a non-leap year + () -> weekdayQuery(DSL.weekday( + functionProperties, + DSL.literal("2021-02-28")), 6, "weekday(\"2021-02-28\")"), + //Feb. 29 of a non-leap year + () -> assertThrows( + SemanticCheckException.class, () -> testInvalidWeekday("2021-02-29")) + ); + } + + @Test + public void weekdayInvalidArgument() { + assertAll( + //40th day of the month + () -> assertThrows(SemanticCheckException.class, + () -> testInvalidWeekday("2021-02-40")), + + //13th month of the year + () -> assertThrows(SemanticCheckException.class, + () -> testInvalidWeekday("2021-13-29")), + + //incorrect format + () -> assertThrows(SemanticCheckException.class, + () -> testInvalidWeekday("asdfasdf")) + ); + } + + private ExprValue eval(Expression expression) { + return expression.valueOf(); + } +} diff --git a/docs/user/dql/functions.rst b/docs/user/dql/functions.rst index 5d5a3e1f96..23c9a8e29c 100644 --- a/docs/user/dql/functions.rst +++ b/docs/user/dql/functions.rst @@ -2627,6 +2627,30 @@ Example:: | 7 | 8 | +----------------------------+-------------------------------+ +WEEKDAY +_______ + +Description +>>>>>>>>>>> + +Usage: weekday(date) returns the weekday index for date (0 = Monday, 1 = Tuesday, ..., 6 = Sunday). + +It is similar to the `dayofweek`_ function, but returns different indexes for each day. + +Argument type: STRING/DATE/DATETIME/TIME/TIMESTAMP + +Return type: INTEGER + +Example:: + + os> SELECT weekday('2020-08-26'), weekday('2020-08-27') + fetched rows / total rows = 1/1 + +-------------------------+-------------------------+ + | weekday('2020-08-26') | weekday('2020-08-27') | + |-------------------------+-------------------------| + | 2 | 3 | + +-------------------------+-------------------------+ + WEEK_OF_YEAR ------------ 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 4254641524..950678a2ca 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 @@ -932,6 +932,12 @@ public void testWeek() throws IOException { week("2000-01-01", 2, 52, "week"); } + @Test + public void testWeekday() throws IOException { + JSONObject result = executeQuery(String.format("SELECT weekday(date0) FROM %s LIMIT 3", TEST_INDEX_CALCS)); + verifyDataRows(result, rows(3), rows(1), rows(2)); + } + @Test public void testWeekOfYearUnderscores() throws IOException { JSONObject result = executeQuery("select week_of_year(date('2008-02-20'))"); diff --git a/sql/src/main/antlr/OpenSearchSQLLexer.g4 b/sql/src/main/antlr/OpenSearchSQLLexer.g4 index 25f23a7bd6..a09bb88778 100644 --- a/sql/src/main/antlr/OpenSearchSQLLexer.g4 +++ b/sql/src/main/antlr/OpenSearchSQLLexer.g4 @@ -331,6 +331,7 @@ TOPHITS: 'TOPHITS'; TYPEOF: 'TYPEOF'; WEEK_OF_YEAR: 'WEEK_OF_YEAR'; WEEKOFYEAR: 'WEEKOFYEAR'; +WEEKDAY: 'WEEKDAY'; WILDCARDQUERY: 'WILDCARDQUERY'; WILDCARD_QUERY: 'WILDCARD_QUERY'; diff --git a/sql/src/main/antlr/OpenSearchSQLParser.g4 b/sql/src/main/antlr/OpenSearchSQLParser.g4 index e5efeabba0..e801363724 100644 --- a/sql/src/main/antlr/OpenSearchSQLParser.g4 +++ b/sql/src/main/antlr/OpenSearchSQLParser.g4 @@ -479,6 +479,7 @@ dateTimeFunctionName | TO_DAYS | UNIX_TIMESTAMP | WEEK + | WEEKDAY | WEEK_OF_YEAR | WEEKOFYEAR | 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 0b8e64d0bb..a00231425f 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 @@ -175,6 +175,12 @@ private static Stream nowLikeFunctionsData() { ); } + @Test + public void can_parse_weekday_function() { + assertNotNull(parser.parse("SELECT weekday('2022-11-18')")); + assertNotNull(parser.parse("SELECT day_of_week('2022-11-18')")); + } + @ParameterizedTest(name = "{0}") @MethodSource("nowLikeFunctionsData") public void can_parse_now_like_functions(String name, Boolean hasFsp, Boolean hasShortcut) {