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 bb343cd5f9..611053f0bf 100644 --- a/core/src/main/java/org/opensearch/sql/expression/DSL.java +++ b/core/src/main/java/org/opensearch/sql/expression/DSL.java @@ -335,8 +335,9 @@ public static FunctionExpression dayofyear(Expression... expressions) { return compile(FunctionProperties.None, BuiltinFunctionName.DAYOFYEAR, expressions); } - public static FunctionExpression day_of_year(Expression... expressions) { - return compile(FunctionProperties.None, BuiltinFunctionName.DAY_OF_YEAR, expressions); + public static FunctionExpression day_of_year( + FunctionProperties functionProperties, Expression... expressions) { + return compile(functionProperties, BuiltinFunctionName.DAY_OF_YEAR, expressions); } public static FunctionExpression day_of_week( @@ -372,8 +373,9 @@ public static FunctionExpression month(Expression... expressions) { return compile(FunctionProperties.None, BuiltinFunctionName.MONTH, expressions); } - public static FunctionExpression month_of_year(Expression... expressions) { - return compile(FunctionProperties.None, BuiltinFunctionName.MONTH_OF_YEAR, expressions); + public static FunctionExpression month_of_year( + FunctionProperties functionProperties, Expression... expressions) { + return compile(functionProperties, BuiltinFunctionName.MONTH_OF_YEAR, expressions); } public static FunctionExpression monthname(Expression... expressions) { @@ -416,12 +418,14 @@ public static FunctionExpression to_days(Expression... expressions) { return compile(FunctionProperties.None, BuiltinFunctionName.TO_DAYS, expressions); } - public static FunctionExpression week(Expression... expressions) { - return compile(FunctionProperties.None, BuiltinFunctionName.WEEK, expressions); + public static FunctionExpression week( + FunctionProperties functionProperties, Expression... expressions) { + return compile(functionProperties, BuiltinFunctionName.WEEK, expressions); } - public static FunctionExpression week_of_year(Expression... expressions) { - return compile(FunctionProperties.None, BuiltinFunctionName.WEEK_OF_YEAR, expressions); + public static FunctionExpression week_of_year( + FunctionProperties functionProperties, Expression... expressions) { + return compile(functionProperties, BuiltinFunctionName.WEEK_OF_YEAR, expressions); } public static FunctionExpression year(Expression... 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 064d6ea857..68227d1ba4 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 @@ -89,6 +89,9 @@ public class DateTimeFunction { // 32536771199.999999, or equivalent '3001-01-18 23:59:59.999999' UTC private static final Double MYSQL_MAX_TIMESTAMP = 32536771200d; + // Mode used for week/week_of_year function by default when no argument is provided + private static final ExprIntegerValue DEFAULT_WEEK_OF_YEAR_MODE = new ExprIntegerValue(0); + /** * Register Date and Time Functions. * @@ -472,6 +475,9 @@ private DefaultFunctionResolver dayOfWeek(FunctionName name) { */ private DefaultFunctionResolver dayOfYear(BuiltinFunctionName dayOfYear) { return define(dayOfYear.getName(), + implWithProperties(nullMissingHandlingWithProperties((functionProperties, arg) + -> DateTimeFunction.dayOfYearToday( + functionProperties.getQueryStartClock())), INTEGER, TIME), impl(nullMissingHandling(DateTimeFunction::exprDayOfYear), INTEGER, DATE), impl(nullMissingHandling(DateTimeFunction::exprDayOfYear), INTEGER, DATETIME), impl(nullMissingHandling(DateTimeFunction::exprDayOfYear), INTEGER, TIMESTAMP), @@ -559,6 +565,9 @@ private DefaultFunctionResolver minute_of_day() { */ private DefaultFunctionResolver month(BuiltinFunctionName month) { return define(month.getName(), + implWithProperties(nullMissingHandlingWithProperties((functionProperties, arg) + -> DateTimeFunction.monthOfYearToday( + functionProperties.getQueryStartClock())), INTEGER, TIME), impl(nullMissingHandling(DateTimeFunction::exprMonth), INTEGER, DATE), impl(nullMissingHandling(DateTimeFunction::exprMonth), INTEGER, DATETIME), impl(nullMissingHandling(DateTimeFunction::exprMonth), INTEGER, TIMESTAMP), @@ -783,10 +792,18 @@ private DefaultFunctionResolver utc_timestamp() { */ private DefaultFunctionResolver week(BuiltinFunctionName week) { return define(week.getName(), + implWithProperties(nullMissingHandlingWithProperties((functionProperties, arg) + -> DateTimeFunction.weekOfYearToday( + DEFAULT_WEEK_OF_YEAR_MODE, + functionProperties.getQueryStartClock())), INTEGER, TIME), impl(nullMissingHandling(DateTimeFunction::exprWeekWithoutMode), INTEGER, DATE), impl(nullMissingHandling(DateTimeFunction::exprWeekWithoutMode), INTEGER, DATETIME), impl(nullMissingHandling(DateTimeFunction::exprWeekWithoutMode), INTEGER, TIMESTAMP), impl(nullMissingHandling(DateTimeFunction::exprWeekWithoutMode), INTEGER, STRING), + implWithProperties(nullMissingHandlingWithProperties((functionProperties, time, modeArg) + -> DateTimeFunction.weekOfYearToday( + modeArg, + functionProperties.getQueryStartClock())), INTEGER, TIME, INTEGER), impl(nullMissingHandling(DateTimeFunction::exprWeek), INTEGER, DATE, INTEGER), impl(nullMissingHandling(DateTimeFunction::exprWeek), INTEGER, DATETIME, INTEGER), impl(nullMissingHandling(DateTimeFunction::exprWeek), INTEGER, TIMESTAMP, INTEGER), @@ -827,6 +844,15 @@ private DefaultFunctionResolver date_format() { ); } + private ExprValue dayOfYearToday(Clock clock) { + return new ExprIntegerValue(LocalDateTime.now(clock).getDayOfYear()); + } + + private ExprValue weekOfYearToday(ExprValue mode, Clock clock) { + return new ExprIntegerValue( + CalendarLookup.getWeekNumber(mode.integerValue(), LocalDateTime.now(clock).toLocalDate())); + } + /** * Day of Week implementation for ExprValue when passing in an arguemt of type TIME. * @@ -1519,6 +1545,10 @@ private ExprValue exprYear(ExprValue date) { return new ExprIntegerValue(date.dateValue().getYear()); } + private ExprValue monthOfYearToday(Clock clock) { + return new ExprIntegerValue(LocalDateTime.now(clock).getMonthValue()); + } + private LocalDateTime formatNow(Clock clock) { return formatNow(clock, 0); } diff --git a/core/src/main/java/org/opensearch/sql/expression/function/FunctionDSL.java b/core/src/main/java/org/opensearch/sql/expression/function/FunctionDSL.java index 1bf38f7722..d94d7cdf60 100644 --- a/core/src/main/java/org/opensearch/sql/expression/function/FunctionDSL.java +++ b/core/src/main/java/org/opensearch/sql/expression/function/FunctionDSL.java @@ -141,11 +141,11 @@ public String toString() { * Implementation of a function that takes two arguments, returns a value, and * requires FunctionProperties to complete. * - * @param function {@link ExprValue} based unary function. + * @param function {@link ExprValue} based Binary function. * @param returnType return type. * @param args1Type first argument type. * @param args2Type second argument type. - * @return Unary Function Implementation. + * @return Binary Function Implementation. */ public static SerializableFunction> implWithProperties( 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 7761c1e94c..76f35dc68c 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 @@ -6,6 +6,7 @@ package org.opensearch.sql.expression.datetime; +import static java.time.temporal.ChronoField.ALIGNED_WEEK_OF_YEAR; import static org.junit.jupiter.api.Assertions.assertAll; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; @@ -604,10 +605,66 @@ public void dayOfYear() { assertEquals(integerValue(220), eval(expression)); } - 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)); + private static Stream getTestDataForDayOfYear() { + return Stream.of( + Arguments.of(DSL.literal( + new ExprDateValue("2020-08-07")), + "day_of_year(DATE '2020-08-07')", + 220), + Arguments.of(DSL.literal( + new ExprDatetimeValue("2020-08-07 12:23:34")), + "day_of_year(DATETIME '2020-08-07 12:23:34')", + 220), + Arguments.of(DSL.literal( + new ExprTimestampValue("2020-08-07 12:23:34")), + "day_of_year(TIMESTAMP '2020-08-07 12:23:34')", + 220), + Arguments.of(DSL.literal( + "2020-08-07"), + "day_of_year(\"2020-08-07\")", + 220), + Arguments.of(DSL.literal( + "2020-08-07 01:02:03"), + "day_of_year(\"2020-08-07 01:02:03\")", + 220) + ); + } + + @ParameterizedTest(name = "{0}") + @MethodSource("getTestDataForDayOfYear") + public void dayOfYearWithUnderscores( + LiteralExpression arg, + String expectedString, + int expectedResult) { + lenient().when(nullRef.valueOf(env)).thenReturn(nullValue()); + lenient().when(missingRef.valueOf(env)).thenReturn(missingValue()); + + validateStringFormat( + DSL.day_of_year(functionProperties, arg), + expectedString, + expectedResult + ); + } + + @Test + public void testDayOfYearWithTimeType() { + lenient().when(nullRef.valueOf(env)).thenReturn(nullValue()); + lenient().when(missingRef.valueOf(env)).thenReturn(missingValue()); + + validateStringFormat( + DSL.day_of_year(functionProperties, DSL.literal(new ExprTimeValue("12:23:34"))), + "day_of_year(TIME '12:23:34')", + LocalDate.now(functionProperties.getQueryStartClock()).getDayOfYear()); + } + + public void dayOfYearWithUnderscoresQuery(String date, int dayOfYear) { + FunctionExpression expression = DSL.day_of_year( + functionProperties, + DSL.literal(new ExprDateValue(date))); + assertAll( + () -> assertEquals(INTEGER, expression.type()), + () -> assertEquals(integerValue(dayOfYear), eval(expression)) + ); } @Test @@ -615,18 +672,24 @@ public void dayOfYearWithUnderscoresDifferentArgumentFormats() { lenient().when(nullRef.valueOf(env)).thenReturn(nullValue()); lenient().when(missingRef.valueOf(env)).thenReturn(missingValue()); - FunctionExpression expression1 = DSL.day_of_year(DSL.literal(new ExprDateValue("2020-08-07"))); - FunctionExpression expression2 = DSL.day_of_year(DSL.literal("2020-08-07")); - FunctionExpression expression3 = DSL.day_of_year(DSL.literal("2020-08-07 01:02:03")); + FunctionExpression expression1 = DSL.day_of_year( + functionProperties, + DSL.literal(new ExprDateValue("2020-08-07"))); + FunctionExpression expression2 = DSL.day_of_year( + functionProperties, + DSL.literal("2020-08-07")); + FunctionExpression expression3 = DSL.day_of_year( + functionProperties, + DSL.literal("2020-08-07 01:02:03")); assertAll( - () -> testDayOfYearWithUnderscores("2020-08-07", 220), + () -> dayOfYearWithUnderscoresQuery("2020-08-07", 220), () -> assertEquals("day_of_year(DATE '2020-08-07')", expression1.toString()), - () -> testDayOfYearWithUnderscores("2020-08-07", 220), + () -> dayOfYearWithUnderscoresQuery("2020-08-07", 220), () -> assertEquals("day_of_year(\"2020-08-07\")", expression2.toString()), - () -> testDayOfYearWithUnderscores("2020-08-07 01:02:03", 220), + () -> dayOfYearWithUnderscoresQuery("2020-08-07 01:02:03", 220), () -> assertEquals("day_of_year(\"2020-08-07 01:02:03\")", expression3.toString()) ); } @@ -638,11 +701,11 @@ public void dayOfYearWithUnderscoresCornerCaseDates() { assertAll( //31st of December during non leap year (should be 365) - () -> testDayOfYearWithUnderscores("2019-12-31", 365), + () -> dayOfYearWithUnderscoresQuery("2019-12-31", 365), //Year 1200 - () -> testDayOfYearWithUnderscores("1200-02-28", 59), + () -> dayOfYearWithUnderscoresQuery("1200-02-28", 59), //Year 4000 - () -> testDayOfYearWithUnderscores("4000-02-28", 59) + () -> dayOfYearWithUnderscoresQuery("4000-02-28", 59) ); } @@ -653,26 +716,28 @@ public void dayOfYearWithUnderscoresLeapYear() { assertAll( //28th of Feb - () -> testDayOfYearWithUnderscores("2020-02-28", 59), + () -> dayOfYearWithUnderscoresQuery("2020-02-28", 59), //29th of Feb during leap year - () -> testDayOfYearWithUnderscores("2020-02-29 23:59:59", 60), - () -> testDayOfYearWithUnderscores("2020-02-29", 60), + () -> dayOfYearWithUnderscoresQuery("2020-02-29 23:59:59", 60), + () -> dayOfYearWithUnderscoresQuery("2020-02-29", 60), //1st of March during leap year - () -> testDayOfYearWithUnderscores("2020-03-01 00:00:00", 61), - () -> testDayOfYearWithUnderscores("2020-03-01", 61), + () -> dayOfYearWithUnderscoresQuery("2020-03-01 00:00:00", 61), + () -> dayOfYearWithUnderscoresQuery("2020-03-01", 61), //1st of March during non leap year - () -> testDayOfYearWithUnderscores("2019-03-01", 60), + () -> dayOfYearWithUnderscoresQuery("2019-03-01", 60), //31st of December during leap year (should be 366) - () -> testDayOfYearWithUnderscores("2020-12-31", 366) + () -> dayOfYearWithUnderscoresQuery("2020-12-31", 366) ); } - private void testInvalidDayOfYear(String date) { - FunctionExpression expression = DSL.day_of_year(DSL.literal(new ExprDateValue(date))); + private void invalidDayOfYearQuery(String date) { + FunctionExpression expression = DSL.day_of_year( + functionProperties, + DSL.literal(new ExprDateValue(date))); eval(expression); } @@ -680,15 +745,26 @@ private void testInvalidDayOfYear(String date) { public void invalidDayOfYearArgument() { when(nullRef.type()).thenReturn(DATE); when(missingRef.type()).thenReturn(DATE); - assertEquals(nullValue(), eval(DSL.day_of_year(nullRef))); - assertEquals(missingValue(), eval(DSL.day_of_year(missingRef))); - //29th of Feb non-leapyear - assertThrows(SemanticCheckException.class, () -> testInvalidDayOfYear("2019-02-29")); - //13th month - assertThrows(SemanticCheckException.class, () -> testInvalidDayOfYear("2019-13-15")); - //incorrect format for type - assertThrows(SemanticCheckException.class, () -> testInvalidDayOfYear("asdfasdfasdf")); + assertAll( + () -> assertEquals(nullValue(), eval(DSL.day_of_year(functionProperties, nullRef))), + () -> assertEquals(missingValue(), eval(DSL.day_of_year(functionProperties, missingRef))), + + //29th of Feb non-leapyear + () -> assertThrows( + SemanticCheckException.class, + () -> invalidDayOfYearQuery("2019-02-29")), + + //13th month + () -> assertThrows( + SemanticCheckException.class, + () -> invalidDayOfYearQuery("2019-13-15")), + + //incorrect format for type + () -> assertThrows( + SemanticCheckException.class, + () -> invalidDayOfYearQuery("asdfasdfasdf")) + ); } @Test @@ -989,21 +1065,82 @@ public void month() { assertEquals(integerValue(8), eval(expression)); } - private void testInvalidDates(String date) throws SemanticCheckException { - FunctionExpression expression = DSL.month_of_year(DSL.literal(new ExprDateValue(date))); + private static Stream getTestDataForMonthOfYear() { + return Stream.of( + Arguments.of( + DSL.literal(new ExprDateValue("2020-08-07")), + "month_of_year(DATE '2020-08-07')", + 8), + Arguments.of( + DSL.literal(new ExprDatetimeValue("2020-08-07 12:23:34")), + "month_of_year(DATETIME '2020-08-07 12:23:34')", + 8), + Arguments.of( + DSL.literal(new ExprTimestampValue("2020-08-07 12:23:34")), + "month_of_year(TIMESTAMP '2020-08-07 12:23:34')", + 8), + Arguments.of( + DSL.literal("2020-08-07"), + "month_of_year(\"2020-08-07\")", + 8), + Arguments.of( + DSL.literal("2020-08-07 01:02:03"), + "month_of_year(\"2020-08-07 01:02:03\")", + 8) + ); + } + + @ParameterizedTest(name = "{0}") + @MethodSource("getTestDataForMonthOfYear") + public void monthOfYear(LiteralExpression arg, String expectedString, int expectedResult) { + lenient().when(nullRef.valueOf(env)).thenReturn(nullValue()); + lenient().when(missingRef.valueOf(env)).thenReturn(missingValue()); + + validateStringFormat( + DSL.month_of_year(functionProperties, arg), + expectedString, + expectedResult + ); + } + + @Test + public void testMonthOfYearWithTimeType() { + lenient().when(nullRef.valueOf(env)).thenReturn(nullValue()); + lenient().when(missingRef.valueOf(env)).thenReturn(missingValue()); + + validateStringFormat( + DSL.month_of_year(functionProperties, DSL.literal(new ExprTimeValue("12:23:34"))), + "month_of_year(TIME '12:23:34')", + LocalDate.now(functionProperties.getQueryStartClock()).getMonthValue()); + } + + private void invalidDatesQuery(String date) throws SemanticCheckException { + FunctionExpression expression = DSL.month_of_year( + functionProperties, + DSL.literal(new ExprDateValue(date))); eval(expression); } - @Test void monthOfYearInvalidDates() { + @Test + public void monthOfYearInvalidDates() { when(nullRef.type()).thenReturn(DATE); when(missingRef.type()).thenReturn(DATE); - assertEquals(nullValue(), eval(DSL.month_of_year(nullRef))); - assertEquals(missingValue(), eval(DSL.month_of_year(missingRef))); - assertThrows(SemanticCheckException.class, () -> testInvalidDates("2019-01-50")); - assertThrows(SemanticCheckException.class, () -> testInvalidDates("2019-02-29")); - assertThrows(SemanticCheckException.class, () -> testInvalidDates("2019-02-31")); - assertThrows(SemanticCheckException.class, () -> testInvalidDates("2019-13-05")); + assertAll( + () -> assertEquals(nullValue(), eval(DSL.month_of_year( + functionProperties, + nullRef))), + () -> assertEquals(missingValue(), eval(DSL.month_of_year( + functionProperties, + missingRef))), + () -> assertThrows(SemanticCheckException.class, () -> invalidDatesQuery("2019-01-50")), + () -> assertThrows(SemanticCheckException.class, () -> invalidDatesQuery("2019-02-29")), + () -> assertThrows(SemanticCheckException.class, () -> invalidDatesQuery("2019-02-31")), + () -> assertThrows(SemanticCheckException.class, () -> invalidDatesQuery("2019-13-05")) + ); + + + } @Test @@ -1011,17 +1148,23 @@ public void monthOfYearAlternateArgumentSyntaxes() { lenient().when(nullRef.valueOf(env)).thenReturn(nullValue()); lenient().when(missingRef.valueOf(env)).thenReturn(missingValue()); - FunctionExpression expression = DSL.month_of_year(DSL.literal(new ExprDateValue("2020-08-07"))); + FunctionExpression expression = DSL.month_of_year( + functionProperties, + DSL.literal(new ExprDateValue("2020-08-07"))); assertEquals(INTEGER, expression.type()); assertEquals("month_of_year(DATE '2020-08-07')", expression.toString()); assertEquals(integerValue(8), eval(expression)); - expression = DSL.month_of_year(DSL.literal("2020-08-07")); + expression = DSL.month_of_year( + functionProperties, + DSL.literal("2020-08-07")); assertEquals(INTEGER, expression.type()); assertEquals("month_of_year(\"2020-08-07\")", expression.toString()); assertEquals(integerValue(8), eval(expression)); - expression = DSL.month_of_year(DSL.literal("2020-08-07 01:02:03")); + expression = DSL.month_of_year( + functionProperties, + DSL.literal("2020-08-07 01:02:03")); assertEquals(INTEGER, expression.type()); assertEquals("month_of_year(\"2020-08-07 01:02:03\")", expression.toString()); assertEquals(integerValue(8), eval(expression)); @@ -1325,216 +1468,253 @@ public void timestamp() { assertEquals("timestamp(TIMESTAMP '2020-08-17 01:01:01')", expr.toString()); } - private void testWeek(String date, int mode, int expectedResult) { + private void weekQuery(String date, int mode, int expectedResult) { FunctionExpression expression = DSL - .week(DSL.literal(new ExprDateValue(date)), DSL.literal(mode)); + .week(functionProperties, DSL.literal(new ExprDateValue(date)), DSL.literal(mode)); assertEquals(INTEGER, expression.type()); assertEquals(String.format("week(DATE '%s', %d)", date, mode), expression.toString()); assertEquals(integerValue(expectedResult), eval(expression)); } - private void testNullMissingWeek(ExprCoreType date) { + private void weekOfYearQuery(String date, int mode, int expectedResult) { + FunctionExpression expression = DSL + .week_of_year( + functionProperties, + DSL.literal(new ExprDateValue(date)), DSL.literal(mode)); + assertEquals(INTEGER, expression.type()); + assertEquals(String.format("week_of_year(DATE '%s', %d)", date, mode), expression.toString()); + assertEquals(integerValue(expectedResult), eval(expression)); + } + + private void nullMissingWeekQuery(ExprCoreType date) { when(nullRef.type()).thenReturn(date); when(missingRef.type()).thenReturn(date); - assertEquals(nullValue(), eval(DSL.week(nullRef))); - assertEquals(missingValue(), eval(DSL.week(missingRef))); + assertEquals(nullValue(), eval(DSL.week(functionProperties, nullRef))); + assertEquals(missingValue(), eval(DSL.week(functionProperties, missingRef))); } @Test - public void week() { - testNullMissingWeek(DATE); - testNullMissingWeek(DATETIME); - testNullMissingWeek(TIMESTAMP); - testNullMissingWeek(STRING); + public void testNullMissingWeek() { + nullMissingWeekQuery(DATE); + nullMissingWeekQuery(DATETIME); + nullMissingWeekQuery(TIMESTAMP); + nullMissingWeekQuery(STRING); when(nullRef.type()).thenReturn(INTEGER); when(missingRef.type()).thenReturn(INTEGER); - assertEquals(nullValue(), eval(DSL.week(DSL.literal("2019-01-05"), nullRef))); - assertEquals(missingValue(), eval(DSL.week(DSL.literal("2019-01-05"), missingRef))); + assertEquals(nullValue(), eval(DSL.week( + functionProperties, + DSL.literal("2019-01-05"), nullRef))); + assertEquals(missingValue(), eval(DSL.week( + functionProperties, + DSL.literal("2019-01-05"), missingRef))); when(nullRef.type()).thenReturn(DATE); when(missingRef.type()).thenReturn(INTEGER); - assertEquals(missingValue(), eval(DSL.week(nullRef, missingRef))); + assertEquals(missingValue(), eval(DSL.week( + functionProperties, + nullRef, missingRef))); + } - FunctionExpression expression = DSL - .week(DSL.literal(new ExprTimestampValue("2019-01-05 01:02:03"))); - assertEquals(INTEGER, expression.type()); - assertEquals("week(TIMESTAMP '2019-01-05 01:02:03')", expression.toString()); - assertEquals(integerValue(0), eval(expression)); + private static Stream getTestDataForWeek() { + //Test the behavior of different modes passed into the 'week_of_year' function + return Stream.of( + Arguments.of("2019-01-05", 0, 0), + Arguments.of("2019-01-05", 1, 1), + Arguments.of("2019-01-05", 2, 52), + Arguments.of("2019-01-05", 3, 1), + Arguments.of("2019-01-05", 4, 1), + Arguments.of("2019-01-05", 5, 0), + Arguments.of("2019-01-05", 6, 1), + Arguments.of("2019-01-05", 7, 53), + + Arguments.of("2019-01-06", 0, 1), + Arguments.of("2019-01-06", 1, 1), + Arguments.of("2019-01-06", 2, 1), + Arguments.of("2019-01-06", 3, 1), + Arguments.of("2019-01-06", 4, 2), + Arguments.of("2019-01-06", 5, 0), + Arguments.of("2019-01-06", 6, 2), + Arguments.of("2019-01-06", 7, 53), + + Arguments.of("2019-01-07", 0, 1), + Arguments.of("2019-01-07", 1, 2), + Arguments.of("2019-01-07", 2, 1), + Arguments.of("2019-01-07", 3, 2), + Arguments.of("2019-01-07", 4, 2), + Arguments.of("2019-01-07", 5, 1), + Arguments.of("2019-01-07", 6, 2), + Arguments.of("2019-01-07", 7, 1), + + Arguments.of("2000-01-01", 0, 0), + Arguments.of("2000-01-01", 2, 52), + Arguments.of("1999-12-31", 0, 52) + ); + } - expression = DSL.week(DSL.literal("2019-01-05")); - assertEquals(INTEGER, expression.type()); - assertEquals("week(\"2019-01-05\")", expression.toString()); - assertEquals(integerValue(0), eval(expression)); + @ParameterizedTest(name = "{1}{2}") + @MethodSource("getTestDataForWeek") + public void testWeek(String date, int mode, int expected) { + lenient().when(nullRef.valueOf(env)).thenReturn(nullValue()); + lenient().when(missingRef.valueOf(env)).thenReturn(missingValue()); + weekQuery(date, mode, expected); + weekOfYearQuery(date, mode, expected); + } - expression = DSL.week(DSL.literal("2019-01-05 00:01:00")); - assertEquals(INTEGER, expression.type()); - assertEquals("week(\"2019-01-05 00:01:00\")", expression.toString()); - assertEquals(integerValue(0), eval(expression)); + private void validateStringFormat( + FunctionExpression expr, + String expectedString, + int expectedResult) { + assertAll( + () -> assertEquals(INTEGER, expr.type()), + () -> assertEquals(expectedString, expr.toString()), + () -> assertEquals(integerValue(expectedResult), eval(expr)) + ); + } + + private static Stream getTestDataForWeekFormats() { + return Stream.of( + Arguments.of(DSL.literal(new ExprDateValue("2019-01-05")), + "DATE '2019-01-05'", + 0), + Arguments.of(DSL.literal(new ExprDatetimeValue("2019-01-05 01:02:03")), + "DATETIME '2019-01-05 01:02:03'", + 0), + Arguments.of(DSL.literal(new ExprTimestampValue("2019-01-05 01:02:03")), + "TIMESTAMP '2019-01-05 01:02:03'", + 0), + Arguments.of( + DSL.literal("2019-01-05"), + "\"2019-01-05\"", + 0), + Arguments.of( + DSL.literal("2019-01-05 00:01:00"), + "\"2019-01-05 00:01:00\"", + 0) + ); + } + + @ParameterizedTest(name = "{0}") + @MethodSource("getTestDataForWeekFormats") + public void testWeekFormats( + LiteralExpression arg, + String expectedString, + Integer expectedInteger) { + lenient().when(nullRef.valueOf(env)).thenReturn(nullValue()); + lenient().when(missingRef.valueOf(env)).thenReturn(missingValue()); + validateStringFormat( + DSL.week(functionProperties, arg), + String.format("week(%s)", expectedString), expectedInteger); + validateStringFormat( + DSL.week_of_year(functionProperties, arg), + String.format("week_of_year(%s)", expectedString), expectedInteger); + } + + @Test + public void testWeekOfYearWithTimeType() { + lenient().when(nullRef.valueOf(env)).thenReturn(nullValue()); + lenient().when(missingRef.valueOf(env)).thenReturn(missingValue()); - testWeek("2019-01-05", 0, 0); - testWeek("2019-01-05", 1, 1); - testWeek("2019-01-05", 2, 52); - testWeek("2019-01-05", 3, 1); - testWeek("2019-01-05", 4, 1); - testWeek("2019-01-05", 5, 0); - testWeek("2019-01-05", 6, 1); - testWeek("2019-01-05", 7, 53); - - testWeek("2019-01-06", 0, 1); - testWeek("2019-01-06", 1, 1); - testWeek("2019-01-06", 2, 1); - testWeek("2019-01-06", 3, 1); - testWeek("2019-01-06", 4, 2); - testWeek("2019-01-06", 5, 0); - testWeek("2019-01-06", 6, 2); - testWeek("2019-01-06", 7, 53); - - testWeek("2019-01-07", 0, 1); - testWeek("2019-01-07", 1, 2); - testWeek("2019-01-07", 2, 1); - testWeek("2019-01-07", 3, 2); - testWeek("2019-01-07", 4, 2); - testWeek("2019-01-07", 5, 1); - testWeek("2019-01-07", 6, 2); - testWeek("2019-01-07", 7, 1); - - testWeek("2000-01-01", 0, 0); - testWeek("2000-01-01", 2, 52); - testWeek("1999-12-31", 0, 52); + assertAll( + () -> validateStringFormat( + DSL.week( + functionProperties, + DSL.literal(new ExprTimeValue("12:23:34")), + DSL.literal(0)), + "week(TIME '12:23:34', 0)", + LocalDate.now(functionProperties.getQueryStartClock()).get(ALIGNED_WEEK_OF_YEAR)), + + () -> validateStringFormat( + DSL.week_of_year(functionProperties, DSL.literal(new ExprTimeValue("12:23:34"))), + "week_of_year(TIME '12:23:34')", + LocalDate.now(functionProperties.getQueryStartClock()).get(ALIGNED_WEEK_OF_YEAR)) + ); } @Test public void modeInUnsupportedFormat() { - testNullMissingWeek(DATE); + nullMissingWeekQuery(DATE); FunctionExpression expression1 = DSL - .week(DSL.literal(new ExprDateValue("2019-01-05")), DSL.literal(8)); + .week(functionProperties, DSL.literal(new ExprDateValue("2019-01-05")), DSL.literal(8)); SemanticCheckException exception = assertThrows(SemanticCheckException.class, () -> eval(expression1)); assertEquals("mode:8 is invalid, please use mode value between 0-7", exception.getMessage()); FunctionExpression expression2 = DSL - .week(DSL.literal(new ExprDateValue("2019-01-05")), DSL.literal(-1)); + .week(functionProperties, DSL.literal(new ExprDateValue("2019-01-05")), DSL.literal(-1)); exception = assertThrows(SemanticCheckException.class, () -> eval(expression2)); assertEquals("mode:-1 is invalid, please use mode value between 0-7", exception.getMessage()); } - private void testWeekOfYear(String date, int mode, int expectedResult) { - FunctionExpression expression = DSL - .week_of_year(DSL.literal(new ExprDateValue(date)), DSL.literal(mode)); - assertEquals(INTEGER, expression.type()); - assertEquals(String.format("week_of_year(DATE '%s', %d)", date, mode), expression.toString()); - assertEquals(integerValue(expectedResult), eval(expression)); - } - - private void testNullMissingWeekOfYear(ExprCoreType date) { + private void nullMissingWeekOfYearQuery(ExprCoreType date) { when(nullRef.type()).thenReturn(date); when(missingRef.type()).thenReturn(date); - assertEquals(nullValue(), eval(DSL.week_of_year(nullRef))); - assertEquals(missingValue(), eval(DSL.week_of_year(missingRef))); + assertEquals(nullValue(), eval(DSL.week_of_year( + functionProperties, + nullRef))); + assertEquals(missingValue(), eval(DSL.week_of_year( + functionProperties, + missingRef))); } @Test public void testInvalidWeekOfYear() { - testNullMissingWeekOfYear(DATE); - testNullMissingWeekOfYear(DATETIME); - testNullMissingWeekOfYear(TIMESTAMP); - testNullMissingWeekOfYear(STRING); + nullMissingWeekOfYearQuery(DATE); + nullMissingWeekOfYearQuery(DATETIME); + nullMissingWeekOfYearQuery(TIMESTAMP); + nullMissingWeekOfYearQuery(STRING); when(nullRef.type()).thenReturn(INTEGER); when(missingRef.type()).thenReturn(INTEGER); - assertEquals(nullValue(), eval(DSL.week_of_year(DSL.literal("2019-01-05"), nullRef))); - assertEquals(missingValue(), eval(DSL.week_of_year(DSL.literal("2019-01-05"), missingRef))); + assertEquals(nullValue(), eval(DSL.week_of_year( + functionProperties, + DSL.literal("2019-01-05"), nullRef))); + assertEquals(missingValue(), eval(DSL.week_of_year( + functionProperties, + DSL.literal("2019-01-05"), missingRef))); when(nullRef.type()).thenReturn(DATE); when(missingRef.type()).thenReturn(INTEGER); - assertEquals(missingValue(), eval(DSL.week_of_year(nullRef, missingRef))); - - //test invalid month - assertThrows(SemanticCheckException.class, () -> testWeekOfYear("2019-13-05 01:02:03", 0, 0)); - //test invalid day - assertThrows(SemanticCheckException.class, () -> testWeekOfYear("2019-01-50 01:02:03", 0, 0)); - //test invalid leap year - assertThrows(SemanticCheckException.class, () -> testWeekOfYear("2019-02-29 01:02:03", 0, 0)); - } - - @Test - public void testWeekOfYearAlternateArgumentFormats() { - lenient().when(nullRef.valueOf(env)).thenReturn(nullValue()); - lenient().when(missingRef.valueOf(env)).thenReturn(missingValue()); - - FunctionExpression expression = DSL - .week_of_year(DSL.literal(new ExprTimestampValue("2019-01-05 01:02:03"))); - assertEquals(INTEGER, expression.type()); - assertEquals("week_of_year(TIMESTAMP '2019-01-05 01:02:03')", expression.toString()); - assertEquals(integerValue(0), eval(expression)); - - expression = DSL.week_of_year(DSL.literal("2019-01-05")); - assertEquals(INTEGER, expression.type()); - assertEquals("week_of_year(\"2019-01-05\")", expression.toString()); - assertEquals(integerValue(0), eval(expression)); - - expression = DSL.week_of_year(DSL.literal("2019-01-05 00:01:00")); - assertEquals(INTEGER, expression.type()); - assertEquals("week_of_year(\"2019-01-05 00:01:00\")", expression.toString()); - assertEquals(integerValue(0), eval(expression)); - } - - @Test - public void testWeekOfYearDifferentModes() { - lenient().when(nullRef.valueOf(env)).thenReturn(nullValue()); - lenient().when(missingRef.valueOf(env)).thenReturn(missingValue()); - - //Test the behavior of different modes passed into the 'week_of_year' function - testWeekOfYear("2019-01-05", 0, 0); - testWeekOfYear("2019-01-05", 1, 1); - testWeekOfYear("2019-01-05", 2, 52); - testWeekOfYear("2019-01-05", 3, 1); - testWeekOfYear("2019-01-05", 4, 1); - testWeekOfYear("2019-01-05", 5, 0); - testWeekOfYear("2019-01-05", 6, 1); - testWeekOfYear("2019-01-05", 7, 53); - - testWeekOfYear("2019-01-06", 0, 1); - testWeekOfYear("2019-01-06", 1, 1); - testWeekOfYear("2019-01-06", 2, 1); - testWeekOfYear("2019-01-06", 3, 1); - testWeekOfYear("2019-01-06", 4, 2); - testWeekOfYear("2019-01-06", 5, 0); - testWeekOfYear("2019-01-06", 6, 2); - testWeekOfYear("2019-01-06", 7, 53); - - testWeekOfYear("2019-01-07", 0, 1); - testWeekOfYear("2019-01-07", 1, 2); - testWeekOfYear("2019-01-07", 2, 1); - testWeekOfYear("2019-01-07", 3, 2); - testWeekOfYear("2019-01-07", 4, 2); - testWeekOfYear("2019-01-07", 5, 1); - testWeekOfYear("2019-01-07", 6, 2); - testWeekOfYear("2019-01-07", 7, 1); - - testWeekOfYear("2000-01-01", 0, 0); - testWeekOfYear("2000-01-01", 2, 52); - testWeekOfYear("1999-12-31", 0, 52); + assertEquals(missingValue(), eval(DSL.week_of_year( + functionProperties, + nullRef, missingRef))); + assertAll( + //test invalid month + () -> assertThrows( + SemanticCheckException.class, + () -> weekOfYearQuery("2019-13-05 01:02:03", 0, 0)), + //test invalid day + () -> assertThrows( + SemanticCheckException.class, + () -> weekOfYearQuery("2019-01-50 01:02:03", 0, 0)), + //test invalid leap year + () -> assertThrows( + SemanticCheckException.class, + () -> weekOfYearQuery("2019-02-29 01:02:03", 0, 0)) + ); } @Test public void weekOfYearModeInUnsupportedFormat() { - testNullMissingWeekOfYear(DATE); + nullMissingWeekOfYearQuery(DATE); FunctionExpression expression1 = DSL - .week_of_year(DSL.literal(new ExprDateValue("2019-01-05")), DSL.literal(8)); + .week_of_year( + functionProperties, + DSL.literal(new ExprDateValue("2019-01-05")), DSL.literal(8)); SemanticCheckException exception = assertThrows(SemanticCheckException.class, () -> eval(expression1)); assertEquals("mode:8 is invalid, please use mode value between 0-7", exception.getMessage()); FunctionExpression expression2 = DSL - .week_of_year(DSL.literal(new ExprDateValue("2019-01-05")), DSL.literal(-1)); + .week_of_year( + functionProperties, + DSL.literal(new ExprDateValue("2019-01-05")), DSL.literal(-1)); exception = assertThrows(SemanticCheckException.class, () -> eval(expression2)); assertEquals("mode:-1 is invalid, please use mode value between 0-7", exception.getMessage()); diff --git a/core/src/test/java/org/opensearch/sql/expression/function/FunctionDSLimplWithPropertiesTwoArgsTest.java b/core/src/test/java/org/opensearch/sql/expression/function/FunctionDSLimplWithPropertiesTwoArgsTest.java new file mode 100644 index 0000000000..f690485801 --- /dev/null +++ b/core/src/test/java/org/opensearch/sql/expression/function/FunctionDSLimplWithPropertiesTwoArgsTest.java @@ -0,0 +1,34 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.sql.expression.function; + + +import java.util.List; +import org.apache.commons.lang3.tuple.Pair; +import org.opensearch.sql.data.model.ExprValue; +import org.opensearch.sql.expression.DSL; +import org.opensearch.sql.expression.Expression; + +class FunctionDSLimplWithPropertiesTwoArgsTest extends FunctionDSLimplTestBase { + + @Override + SerializableFunction> + getImplementationGenerator() { + SerializableTriFunction functionBody + = (fp, arg1, arg2) -> ANY; + return FunctionDSL.implWithProperties(functionBody, ANY_TYPE, ANY_TYPE, ANY_TYPE); + } + + @Override + List getSampleArguments() { + return List.of(DSL.literal(ANY), DSL.literal(ANY)); + } + + @Override + String getExpected_toString() { + return "sample(ANY, ANY)"; + } +} diff --git a/docs/user/dql/functions.rst b/docs/user/dql/functions.rst index 3dbfedd143..4d0ab13879 100644 --- a/docs/user/dql/functions.rst +++ b/docs/user/dql/functions.rst @@ -1598,9 +1598,10 @@ Description >>>>>>>>>>> Usage: dayofyear(date) returns the day of the year for date, in the range 1 to 366. +If an argument of type `TIME` is given, the function will use the current date. The function `day_of_year`_ is also provided as an alias. -Argument type: STRING/DATE/DATETIME/TIMESTAMP +Argument type: STRING/DATE/DATETIME/TIME/TIMESTAMP Return type: INTEGER @@ -1637,9 +1638,10 @@ DAY_OF_YEAR Description >>>>>>>>>>> +If an argument of type `TIME` is given, the function will use the current date. This function is an alias to the `dayofyear`_ function -Argument type: STRING/DATE/DATETIME/TIMESTAMP +Argument type: STRING/DATE/DATETIME/TIME/TIMESTAMP Return type: INTEGER @@ -1934,9 +1936,10 @@ Description >>>>>>>>>>> Usage: month(date) returns the month for date, in the range 1 to 12 for January to December. The dates with value 0 such as '0000-00-00' or '2008-00-00' are invalid. -The function month_of_year is also provided as an alias +If an argument of type `TIME` is given, the function will use the current date. +The function `month_of_year`_ is also provided as an alias. -Argument type: STRING/DATE/DATETIME/TIMESTAMP +Argument type: STRING/DATE/DATETIME/TIME/TIMESTAMP Return type: INTEGER @@ -2451,6 +2454,7 @@ Description >>>>>>>>>>> Usage: week(date[, mode]) returns the week number for date. If the mode argument is omitted, the default mode 0 is used. +If an argument of type `TIME` is given, the function will use the current date. The function `week_of_year` is also provided as an alias. .. list-table:: The following table describes how the mode argument works. @@ -2494,7 +2498,7 @@ The function `week_of_year` is also provided as an alias. - 1-53 - with a Monday in this year -Argument type: DATE/DATETIME/TIMESTAMP/STRING +Argument type: DATE/DATETIME/TIME/TIMESTAMP/STRING Return type: INTEGER @@ -2515,8 +2519,9 @@ Description >>>>>>>>>>> The week_of_year function is a synonym for the `week`_ function. +If an argument of type `TIME` is given, the function will use the current date. -Argument type: DATE/DATETIME/TIMESTAMP/STRING +Argument type: DATE/DATETIME/TIME/TIMESTAMP/STRING Return type: INTEGER diff --git a/sql/src/main/antlr/OpenSearchSQLParser.g4 b/sql/src/main/antlr/OpenSearchSQLParser.g4 index 26e7885288..ae72521dec 100644 --- a/sql/src/main/antlr/OpenSearchSQLParser.g4 +++ b/sql/src/main/antlr/OpenSearchSQLParser.g4 @@ -237,14 +237,11 @@ datetimeConstantLiteral : CURRENT_DATE | CURRENT_TIME | CURRENT_TIMESTAMP - | DAY_OF_YEAR | LOCALTIME | LOCALTIMESTAMP - | MONTH_OF_YEAR | UTC_TIMESTAMP | UTC_DATE | UTC_TIME - | WEEK_OF_YEAR ; intervalLiteral @@ -436,6 +433,7 @@ dateTimeFunctionName | DAYOFMONTH | DAYOFWEEK | DAYOFYEAR + | DAY_OF_YEAR | DAY_OF_WEEK | FROM_DAYS | FROM_UNIXTIME @@ -448,6 +446,7 @@ dateTimeFunctionName | MINUTE_OF_HOUR | MONTH | MONTHNAME + | MONTH_OF_YEAR | NOW | PERIOD_ADD | PERIOD_DIFF @@ -464,6 +463,7 @@ dateTimeFunctionName | TO_DAYS | UNIX_TIMESTAMP | WEEK + | WEEK_OF_YEAR | YEAR ;