Skip to content
This repository has been archived by the owner on Aug 2, 2022. It is now read-only.

Support date and time function: week #757

Merged
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@

import com.amazon.opendistroforelasticsearch.sql.data.type.ExprCoreType;
import com.amazon.opendistroforelasticsearch.sql.data.type.ExprType;
import com.amazon.opendistroforelasticsearch.sql.exception.SemanticCheckException;
import java.time.LocalDate;
import java.util.Objects;
import lombok.EqualsAndHashCode;
import lombok.RequiredArgsConstructor;
Expand Down Expand Up @@ -48,6 +50,17 @@ public String toString() {
return String.format("\"%s\"", value);
}

@Override
public LocalDate dateValue() {
ExprValue date;
try {
date = new ExprDatetimeValue(value);
} catch (SemanticCheckException e) {
date = new ExprDateValue(value);
}
return date.dateValue();
}

@Override
public int compare(ExprValue other) {
return value.compareTo(other.stringValue());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -261,6 +261,10 @@ public FunctionExpression timestamp(Expression... expressions) {
return function(BuiltinFunctionName.TIMESTAMP, expressions);
}

public FunctionExpression week(Expression... expressions) {
return function(BuiltinFunctionName.WEEK, expressions);
}

public FunctionExpression adddate(Expression... expressions) {
return function(BuiltinFunctionName.ADDDATE, expressions);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
/*
* Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License").
* You may not use this file except in compliance with the License.
* A copy of the License is located at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* or in the "license" file accompanying this file. This file is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
* express or implied. See the License for the specific language governing
* permissions and limitations under the License.
*/

package com.amazon.opendistroforelasticsearch.sql.expression.datetime;

import com.amazon.opendistroforelasticsearch.sql.data.model.ExprValue;
import com.amazon.opendistroforelasticsearch.sql.exception.SemanticCheckException;
import java.util.Arrays;
import java.util.Calendar;
import java.util.HashMap;
import java.util.Map;

public class CalenderLookup {
rupal-bq marked this conversation as resolved.
Show resolved Hide resolved

private Map<Integer, Calendar> map = new HashMap<>();

/**
* Set Calender in map for all modes.
* @param date ExprValue of Date/Datetime/Timestamp/String type.
*/
public CalenderLookup(ExprValue date) {
map.put(0, getCalender(Calendar.SUNDAY, 7, date));
map.put(1, getCalender(Calendar.MONDAY, 5, date));
map.put(2, getCalender(Calendar.SUNDAY, 7, date));
map.put(3, getCalender(Calendar.MONDAY, 5, date));
map.put(4, getCalender(Calendar.SUNDAY, 4, date));
map.put(5, getCalender(Calendar.MONDAY, 7, date));
map.put(6, getCalender(Calendar.SUNDAY, 4, date));
map.put(7, getCalender(Calendar.MONDAY, 7, date));
}

/**
* Set first day of week, minimal days in first week and date in calendar.
* @param firstDayOfWeek the given first day of the week.
* @param minimalDaysInWeek the given minimal days required in the first week of the year.
* @param date the ExprValue of Date/Datetime/Timestamp/String type.
*/
private Calendar getCalender(int firstDayOfWeek, int minimalDaysInWeek, ExprValue date) {
Calendar calendar = Calendar.getInstance();
calendar.setFirstDayOfWeek(firstDayOfWeek);
calendar.setMinimalDaysInFirstWeek(minimalDaysInWeek);
calendar.set(date.dateValue().getYear(), date.dateValue().getMonthValue() - 1,
date.dateValue().getDayOfMonth());
return calendar;
}

/**
* Returns week number for date according to mode.
* @param mode Integer for mode. Valid mode values are 0 to 7.
*/
public int getWeekNumber(int mode) {
if (map.containsKey(mode)) {
int weekNumber = map.get(mode).get(Calendar.WEEK_OF_YEAR);
if ((weekNumber > 51)
&& (map.get(mode).get(Calendar.DAY_OF_MONTH) < 7)
&& Arrays.asList(0, 1, 4, 5).contains(mode)) {
weekNumber = 0;
}
return weekNumber;
}
throw new SemanticCheckException(
String.format("mode:%s is invalid, please use mode value between 0-7", mode));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ public void register(BuiltinFunctionRepository repository) {
repository.register(dayOfMonth());
repository.register(time());
repository.register(timestamp());
repository.register(week());
repository.register(adddate());
}

Expand Down Expand Up @@ -134,8 +135,25 @@ private FunctionResolver adddate() {
);
}

/**
* WEEK(DATE[,mode]). return the week number for date.
*/
private FunctionResolver week() {
return define(BuiltinFunctionName.WEEK.getName(),
impl(nullMissingHandling(DateTimeFunction::exprWeekWithoutMode), INTEGER, DATE),
impl(nullMissingHandling(DateTimeFunction::exprWeekWithoutMode), INTEGER, DATETIME),
impl(nullMissingHandling(DateTimeFunction::exprWeekWithoutMode), INTEGER, TIMESTAMP),
impl(nullMissingHandling(DateTimeFunction::exprWeekWithoutMode), INTEGER, STRING),
impl(nullMissingHandling(DateTimeFunction::exprWeek), INTEGER, DATE, INTEGER),
impl(nullMissingHandling(DateTimeFunction::exprWeek), INTEGER, DATETIME, INTEGER),
impl(nullMissingHandling(DateTimeFunction::exprWeek), INTEGER, TIMESTAMP, INTEGER),
impl(nullMissingHandling(DateTimeFunction::exprWeek), INTEGER, STRING, INTEGER)
);
}

/**
* Date implementation for ExprValue.
*
* @param exprValue ExprValue of Date type or String type.
* @return ExprValue.
*/
Expand Down Expand Up @@ -186,10 +204,30 @@ private ExprValue exprTimestamp(ExprValue exprValue) {
}
}

/**
* Week for date implementation for ExprValue.
* When mode is not specified default value mode 0 is used for default_week_format.
* @param date ExprValue of Date/Datetime/Timestamp type.
* @return ExprValue.
*/
private ExprValue exprWeekWithoutMode(ExprValue date) {
return exprWeek(date, new ExprIntegerValue(0));
}

/**
* Week for date implementation for ExprValue.
* @param date ExprValue of Date/Datetime/Timestamp/String type.
* @param mode ExprValue of Integer type.
*/
private ExprValue exprWeek(ExprValue date, ExprValue mode) {
CalenderLookup calenderLookup = new CalenderLookup(date);
return new ExprIntegerValue(calenderLookup.getWeekNumber(mode.integerValue()));
}

/**
* ADDDATE function implementation for ExprValue.
*
* @param date ExprValue of Date/Datetime/Timestamp type.
* @param date ExprValue of Date/Datetime/Timestamp/String type.
* @param expr ExprValue of Interval type, the temporal amount to add.
* @return Date/Datetime resulted from expr added to date.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ public enum BuiltinFunctionName {
DAYOFMONTH(FunctionName.of("dayofmonth")),
TIME(FunctionName.of("time")),
TIMESTAMP(FunctionName.of("timestamp")),
WEEK(FunctionName.of("week")),
ADDDATE(FunctionName.of("adddate")),

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,15 @@
import static com.amazon.opendistroforelasticsearch.sql.data.type.ExprCoreType.TIME;
import static com.amazon.opendistroforelasticsearch.sql.data.type.ExprCoreType.TIMESTAMP;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.Mockito.when;

import com.amazon.opendistroforelasticsearch.sql.data.model.ExprDateValue;
import com.amazon.opendistroforelasticsearch.sql.data.model.ExprDatetimeValue;
import com.amazon.opendistroforelasticsearch.sql.data.model.ExprTimeValue;
import com.amazon.opendistroforelasticsearch.sql.data.model.ExprTimestampValue;
import com.amazon.opendistroforelasticsearch.sql.data.model.ExprValue;
import com.amazon.opendistroforelasticsearch.sql.exception.SemanticCheckException;
import com.amazon.opendistroforelasticsearch.sql.expression.DSL;
import com.amazon.opendistroforelasticsearch.sql.expression.Expression;
import com.amazon.opendistroforelasticsearch.sql.expression.ExpressionTestBase;
Expand Down Expand Up @@ -135,6 +137,83 @@ public void timestamp() {
assertEquals("timestamp(TIMESTAMP '2020-08-17 01:01:01')", expr.toString());
}

private void testWeek(String date, int mode, int expectedResult) {
FunctionExpression expression = dsl
.week(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));
}

@Test
public void week() {
when(nullRef.type()).thenReturn(DATE);
when(missingRef.type()).thenReturn(DATE);
assertEquals(nullValue(), eval(dsl.week(nullRef)));
assertEquals(missingValue(), eval(dsl.week(missingRef)));

chloe-zh marked this conversation as resolved.
Show resolved Hide resolved
FunctionExpression expression = dsl.week(DSL.literal(new ExprDateValue("2019-01-05")));
assertEquals(INTEGER, expression.type());
assertEquals("week(DATE '2019-01-05')", expression.toString());
assertEquals(integerValue(0), eval(expression));

expression = dsl.week(DSL.literal("2019-01-05"));
assertEquals(INTEGER, expression.type());
assertEquals("week(\"2019-01-05\")", expression.toString());
assertEquals(integerValue(0), eval(expression));

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

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

@Test
public void modeInUnsupportedFormat() {
when(nullRef.type()).thenReturn(DATE);
when(missingRef.type()).thenReturn(DATE);
assertEquals(nullValue(), eval(dsl.week(nullRef)));
assertEquals(missingValue(), eval(dsl.week(missingRef)));

FunctionExpression expression = dsl
.week(DSL.literal(new ExprDateValue("2019-01-05")), DSL.literal(8));
SemanticCheckException exception =
assertThrows(SemanticCheckException.class, () -> eval(expression));
assertEquals("mode:8 is invalid, please use mode value between 0-7",
exception.getMessage());
}

@Test
public void adddate() {
FunctionExpression expr = dsl.adddate(dsl.date(DSL.literal("2020-08-26")), DSL.literal(7));
Expand Down
64 changes: 64 additions & 0 deletions docs/user/dql/functions.rst
Original file line number Diff line number Diff line change
Expand Up @@ -953,6 +953,70 @@ Example::
+------------------------------------+


WEEK
---------

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

Usage: week(date[, mode]) returns the week number for date. If the mode argument is omitted, the default mode 0 is used.

.. list-table:: The following table describes how the mode argument works.
:widths: 25 50 25 75
:header-rows: 1

* - Mode
- First day of week
- Range
- Week 1 is the first week …
* - 0
- Sunday
- 0-53
- with a Sunday in this year
* - 1
- Monday
- 0-53
- with 4 or more days this year
* - 2
- Sunday
- 1-53
- with a Sunday in this year
* - 3
- Monday
- 1-53
- with 4 or more days this year
* - 4
- Sunday
- 0-53
- with 4 or more days this year
* - 5
- Monday
- 0-53
- with a Monday in this year
* - 6
- Sunday
- 1-53
- with 4 or more days this year
* - 7
- Monday
- 1-53
- with a Monday in this year

Argument type: DATE/DATETIME/TIMESTAMP/STRING

Return type: INTEGER

Example::

>od 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 |
+----------------------------+-------------------------------+


YEAR
----

Expand Down
Loading