Skip to content
Merged
4 changes: 4 additions & 0 deletions core/src/main/java/org/opensearch/sql/expression/DSL.java
Original file line number Diff line number Diff line change
Expand Up @@ -389,6 +389,10 @@ public static FunctionExpression week(Expression... expressions) {
return compile(BuiltinFunctionName.WEEK, expressions);
}

public static FunctionExpression week_of_year(Expression... expressions) {
return compile(BuiltinFunctionName.WEEK_OF_YEAR, expressions);
}

public static FunctionExpression year(Expression... expressions) {
return compile(BuiltinFunctionName.YEAR, expressions);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,8 @@ public void register(BuiltinFunctionRepository repository) {
repository.register(date_format());
repository.register(to_days());
repository.register(unix_timestamp());
repository.register(week());
repository.register(week(BuiltinFunctionName.WEEK));
repository.register(week(BuiltinFunctionName.WEEK_OF_YEAR));
repository.register(year());
}

Expand Down Expand Up @@ -548,8 +549,8 @@ private DefaultFunctionResolver unix_timestamp() {
/**
* WEEK(DATE[,mode]). return the week number for date.
*/
private DefaultFunctionResolver week() {
return define(BuiltinFunctionName.WEEK.getName(),
private DefaultFunctionResolver week(BuiltinFunctionName week) {
return define(week.getName(),
impl(nullMissingHandling(DateTimeFunction::exprWeekWithoutMode), INTEGER, DATE),
impl(nullMissingHandling(DateTimeFunction::exprWeekWithoutMode), INTEGER, DATETIME),
impl(nullMissingHandling(DateTimeFunction::exprWeekWithoutMode), INTEGER, TIMESTAMP),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ public enum BuiltinFunctionName {
TO_DAYS(FunctionName.of("to_days")),
UNIX_TIMESTAMP(FunctionName.of("unix_timestamp")),
WEEK(FunctionName.of("week")),
WEEK_OF_YEAR(FunctionName.of("week_of_year")),
YEAR(FunctionName.of("year")),
// `now`-like functions
NOW(FunctionName.of("now")),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.Mockito.lenient;
import static org.mockito.Mockito.when;
import static org.opensearch.sql.data.model.ExprValueUtils.integerValue;
import static org.opensearch.sql.data.model.ExprValueUtils.longValue;
Expand Down Expand Up @@ -949,6 +950,124 @@ public void modeInUnsupportedFormat() {
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) {
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)));
}

@Test
public void testInvalidWeekOfYear() {
testNullMissingWeekOfYear(DATE);
testNullMissingWeekOfYear(DATETIME);
testNullMissingWeekOfYear(TIMESTAMP);
testNullMissingWeekOfYear(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)));

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);

}

@Test
public void weekOfYearModeInUnsupportedFormat() {
testNullMissingWeekOfYear(DATE);

FunctionExpression expression1 = DSL
.week_of_year(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));
exception = assertThrows(SemanticCheckException.class, () -> eval(expression2));
assertEquals("mode:-1 is invalid, please use mode value between 0-7",
exception.getMessage());
}

@Test
public void to_days() {
when(nullRef.type()).thenReturn(DATE);
Expand Down
27 changes: 25 additions & 2 deletions docs/user/dql/functions.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2060,6 +2060,7 @@ Description
>>>>>>>>>>>

Usage: week(date[, mode]) returns the week number for date. If the mode argument is omitted, the default mode 0 is used.
The function `week_of_year` is also provided as an alias.

.. list-table:: The following table describes how the mode argument works.
:widths: 25 50 25 75
Expand Down Expand Up @@ -2108,14 +2109,36 @@ Return type: INTEGER

Example::

>od SELECT WEEK(DATE('2008-02-20')), WEEK(DATE('2008-02-20'), 1)
os> SELECT WEEK(DATE('2008-02-20')), WEEK(DATE('2008-02-20'), 1)
fetched rows / total rows = 1/1
+----------------------------+-------------------------------+
| WEEK(DATE('2008-02-20')) | WEEK(DATE('2008-02-20'), 1) |
|----------------------------|-------------------------------|
|----------------------------+-------------------------------|
| 7 | 8 |
+----------------------------+-------------------------------+

WEEK_OF_YEAR
----

Description
>>>>>>>>>>>

The week_of_year function is a synonym for the `week`_ function.

Argument type: DATE/DATETIME/TIMESTAMP/STRING

Return type: INTEGER

Example::

os> SELECT WEEK_OF_YEAR(DATE('2008-02-20')), WEEK_OF_YEAR(DATE('2008-02-20'), 1)
fetched rows / total rows = 1/1
+------------------------------------+---------------------------------------+
| WEEK_OF_YEAR(DATE('2008-02-20')) | WEEK_OF_YEAR(DATE('2008-02-20'), 1) |
|------------------------------------+---------------------------------------|
| 7 | 8 |
+------------------------------------+---------------------------------------+


YEAR
----
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
package org.opensearch.sql.sql;

import static org.opensearch.sql.legacy.TestsConstants.TEST_INDEX_BANK;
import static org.opensearch.sql.legacy.TestsConstants.TEST_INDEX_CALCS;
import static org.opensearch.sql.legacy.TestsConstants.TEST_INDEX_PEOPLE2;
import static org.opensearch.sql.legacy.plugin.RestSqlAction.QUERY_API_ENDPOINT;
import static org.opensearch.sql.util.MatcherUtils.rows;
Expand Down Expand Up @@ -48,6 +49,7 @@ public class DateTimeFunctionIT extends SQLIntegTestCase {
public void init() throws Exception {
super.init();
loadIndex(Index.BANK);
loadIndex(Index.CALCS);
loadIndex(Index.PEOPLE2);
}

Expand Down Expand Up @@ -423,11 +425,11 @@ public void testYear() throws IOException {
verifyDataRows(result, rows(2020));
}

private void week(String date, int mode, int expectedResult) throws IOException {
JSONObject result = executeQuery(StringUtils.format("select week(date('%s'), %d)", date,
private void week(String date, int mode, int expectedResult, String functionName) throws IOException {
JSONObject result = executeQuery(StringUtils.format("select %s(date('%s'), %d)", functionName, date,
mode));
verifySchema(result,
schema(StringUtils.format("week(date('%s'), %d)", date, mode), null, "integer"));
schema(StringUtils.format("%s(date('%s'), %d)", functionName, date, mode), null, "integer"));
verifyDataRows(result, rows(expectedResult));
}

Expand All @@ -437,11 +439,56 @@ public void testWeek() throws IOException {
verifySchema(result, schema("week(date('2008-02-20'))", null, "integer"));
verifyDataRows(result, rows(7));

week("2008-02-20", 0, 7);
week("2008-02-20", 1, 8);
week("2008-12-31", 1, 53);
week("2000-01-01", 0, 0);
week("2000-01-01", 2, 52);
week("2008-02-20", 0, 7, "week");
week("2008-02-20", 1, 8, "week");
week("2008-12-31", 1, 53, "week");
week("2000-01-01", 0, 0, "week");
week("2000-01-01", 2, 52, "week");
}

@Test
public void testWeekOfYear() throws IOException {
JSONObject result = executeQuery("select week_of_year(date('2008-02-20'))");
verifySchema(result, schema("week_of_year(date('2008-02-20'))", null, "integer"));
verifyDataRows(result, rows(7));

week("2008-02-20", 0, 7, "week_of_year");
week("2008-02-20", 1, 8, "week_of_year");
week("2008-12-31", 1, 53, "week_of_year");
week("2000-01-01", 0, 0, "week_of_year");
week("2000-01-01", 2, 52, "week_of_year");
}

@Test
public void testWeekAlternateSyntaxesReturnTheSameResults() throws IOException {
JSONObject result1 = executeQuery("SELECT week(date('2022-11-22'))");
JSONObject result2 = executeQuery("SELECT week_of_year(date('2022-11-22'))");
verifyDataRows(result1, rows(47));
result1.getJSONArray("datarows").similar(result2.getJSONArray("datarows"));

result1 = executeQuery(String.format(
"SELECT week(CAST(date0 AS date)) FROM %s", TEST_INDEX_CALCS));
result2 = executeQuery(String.format(
"SELECT week_of_year(CAST(date0 AS date)) FROM %s", TEST_INDEX_CALCS));
result1.getJSONArray("datarows").similar(result2.getJSONArray("datarows"));

result1 = executeQuery(String.format(
"SELECT week(datetime(CAST(time0 AS STRING))) FROM %s", TEST_INDEX_CALCS));
result2 = executeQuery(String.format(
"SELECT week_of_year(datetime(CAST(time0 AS STRING))) FROM %s", TEST_INDEX_CALCS));
result1.getJSONArray("datarows").similar(result2.getJSONArray("datarows"));

result1 = executeQuery(String.format(
"SELECT week(CAST(time0 AS STRING)) FROM %s", TEST_INDEX_CALCS));
result2 = executeQuery(String.format(
"SELECT week_of_year(CAST(time0 AS STRING)) FROM %s", TEST_INDEX_CALCS));
result1.getJSONArray("datarows").similar(result2.getJSONArray("datarows"));

result1 = executeQuery(String.format(
"SELECT week(CAST(datetime0 AS timestamp)) FROM %s", TEST_INDEX_CALCS));
result2 = executeQuery(String.format(
"SELECT week_of_year(CAST(datetime0 AS timestamp)) FROM %s", TEST_INDEX_CALCS));
result1.getJSONArray("datarows").similar(result2.getJSONArray("datarows"));
}

void verifyDateFormat(String date, String type, String format, String formatted) throws IOException {
Expand Down
2 changes: 1 addition & 1 deletion sql/src/main/antlr/OpenSearchSQLParser.g4
Original file line number Diff line number Diff line change
Expand Up @@ -397,7 +397,7 @@ dateTimeFunctionName
| DATETIME | DAY | DAYNAME | DAYOFMONTH | DAYOFWEEK | DAYOFYEAR | FROM_DAYS | FROM_UNIXTIME
| HOUR | MAKEDATE | MAKETIME | MICROSECOND | MINUTE | MONTH | MONTHNAME | PERIOD_ADD
| PERIOD_DIFF | QUARTER | SECOND | SUBDATE | SYSDATE | TIME | TIME_TO_SEC
| TIMESTAMP | TO_DAYS | UNIX_TIMESTAMP | WEEK | YEAR
| TIMESTAMP | TO_DAYS | UNIX_TIMESTAMP | WEEK | WEEK_OF_YEAR | YEAR
;

// Functions which value could be cached in scope of a single query
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,13 @@ public void can_parse_now_like_functions(String name, Boolean hasFsp, Boolean ha
assertNotNull(parser.parse("SELECT id FROM test WHERE " + String.join(" AND ", calls)));
}


@Test
public void can_parse_week_of_year_functions() {
assertNotNull(parser.parse("SELECT week('2022-11-18')"));
assertNotNull(parser.parse("SELECT week_of_year('2022-11-18')"));
}

@Test
public void can_parse_multi_match_relevance_function() {
assertNotNull(parser.parse(
Expand Down