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 2f0c07a8970..04679904e74 100644 --- a/core/src/main/java/org/opensearch/sql/expression/DSL.java +++ b/core/src/main/java/org/opensearch/sql/expression/DSL.java @@ -443,6 +443,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 fc8cdc93ef6..05cd35e25e6 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 b64207bb099..fe2df23c780 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 @@ -110,6 +110,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 00000000000..4b97639996b --- /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 127f8044bff..5c951551f0d 100644 --- a/docs/user/dql/functions.rst +++ b/docs/user/dql/functions.rst @@ -2639,6 +2639,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 4254641524e..950678a2ca2 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 25f23a7bd6d..a09bb887783 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 44ca9953317..0f5109b11b7 100644 --- a/sql/src/main/antlr/OpenSearchSQLParser.g4 +++ b/sql/src/main/antlr/OpenSearchSQLParser.g4 @@ -484,6 +484,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 1fe8b728852..a59d822a12a 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 @@ -174,6 +174,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) {