Skip to content

Commit

Permalink
Rework on DATE_ADD/ADDDATE and DATE_SUB/SUBDATE functions.
Browse files Browse the repository at this point in the history
Signed-off-by: Yury-Fridlyand <[email protected]>
  • Loading branch information
Yury-Fridlyand committed Oct 7, 2022
1 parent 057fa44 commit 2c1e83d
Show file tree
Hide file tree
Showing 13 changed files with 926 additions and 534 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@

package org.opensearch.sql.data.model;

import java.time.Instant;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.temporal.TemporalAmount;
import java.util.ArrayList;
import java.util.LinkedHashMap;
Expand Down Expand Up @@ -61,6 +65,22 @@ public static ExprValue intervalValue(TemporalAmount value) {
return new ExprIntervalValue(value);
}

public static ExprValue dateValue(LocalDate value) {
return new ExprDateValue(value);
}

public static ExprValue datetimeValue(LocalDateTime value) {
return new ExprDatetimeValue(value);
}

public static ExprValue timeValue(LocalTime value) {
return new ExprTimeValue(value);
}

public static ExprValue timestampValue(Instant value) {
return new ExprTimestampValue(value);
}

/**
* {@link ExprTupleValue} constructor.
*/
Expand Down Expand Up @@ -115,6 +135,16 @@ public static ExprValue fromObjectValue(Object o) {
return stringValue((String) o);
} else if (o instanceof Float) {
return floatValue((Float) o);
} else if (o instanceof LocalDate) {
return dateValue((LocalDate) o);
} else if (o instanceof LocalDateTime) {
return datetimeValue((LocalDateTime) o);
} else if (o instanceof LocalTime) {
return timeValue((LocalTime) o);
} else if (o instanceof Instant) {
return timestampValue((Instant) o);
} else if (o instanceof TemporalAmount) {
return intervalValue((TemporalAmount) o);
} else {
throw new ExpressionEvaluationException("unsupported object " + o.getClass());
}
Expand Down
16 changes: 0 additions & 16 deletions core/src/main/java/org/opensearch/sql/expression/DSL.java
Original file line number Diff line number Diff line change
Expand Up @@ -281,10 +281,6 @@ public FunctionExpression multiply(Expression... expressions) {
return function(BuiltinFunctionName.MULTIPLY, expressions);
}

public FunctionExpression adddate(Expression... expressions) {
return function(BuiltinFunctionName.ADDDATE, expressions);
}

public FunctionExpression convert_tz(Expression... expressions) {
return function(BuiltinFunctionName.CONVERT_TZ, expressions);
}
Expand All @@ -297,14 +293,6 @@ public FunctionExpression datetime(Expression... expressions) {
return function(BuiltinFunctionName.DATETIME, expressions);
}

public FunctionExpression date_add(Expression... expressions) {
return function(BuiltinFunctionName.DATE_ADD, expressions);
}

public FunctionExpression date_sub(Expression... expressions) {
return function(BuiltinFunctionName.DATE_SUB, expressions);
}

public FunctionExpression day(Expression... expressions) {
return function(BuiltinFunctionName.DAY, expressions);
}
Expand Down Expand Up @@ -357,10 +345,6 @@ public FunctionExpression second(Expression... expressions) {
return function(BuiltinFunctionName.SECOND, expressions);
}

public FunctionExpression subdate(Expression... expressions) {
return function(BuiltinFunctionName.SUBDATE, expressions);
}

public FunctionExpression time(Expression... expressions) {
return function(BuiltinFunctionName.TIME, expressions);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@

package org.opensearch.sql.expression.datetime;

import static java.time.temporal.ChronoUnit.DAYS;
import static java.time.temporal.ChronoUnit.MONTHS;
import static org.opensearch.sql.data.type.ExprCoreType.DATE;
import static org.opensearch.sql.data.type.ExprCoreType.DATETIME;
import static org.opensearch.sql.data.type.ExprCoreType.DOUBLE;
Expand Down Expand Up @@ -38,11 +40,15 @@
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException;
import java.time.format.TextStyle;
import java.time.temporal.TemporalAmount;
import java.util.Arrays;
import java.util.Locale;
import java.util.TimeZone;
import java.util.concurrent.TimeUnit;
import java.util.stream.Stream;
import javax.annotation.Nullable;
import lombok.experimental.UtilityClass;
import org.apache.commons.lang3.tuple.Pair;
import org.opensearch.sql.data.model.ExprDateValue;
import org.opensearch.sql.data.model.ExprDatetimeValue;
import org.opensearch.sql.data.model.ExprDoubleValue;
Expand All @@ -58,8 +64,11 @@
import org.opensearch.sql.expression.function.BuiltinFunctionName;
import org.opensearch.sql.expression.function.BuiltinFunctionRepository;
import org.opensearch.sql.expression.function.DefaultFunctionResolver;
import org.opensearch.sql.expression.function.FunctionBuilder;
import org.opensearch.sql.expression.function.FunctionName;
import org.opensearch.sql.expression.function.FunctionResolver;
import org.opensearch.sql.expression.function.FunctionSignature;
import org.opensearch.sql.expression.function.SerializableFunction;
import org.opensearch.sql.utils.DateTimeUtils;

/**
Expand All @@ -68,6 +77,7 @@
* 2) the implementation should rely on ExprValue.
*/
@UtilityClass
@SuppressWarnings("unchecked")
public class DateTimeFunction {
// The number of days from year zero to year 1970.
private static final Long DAYS_0000_TO_1970 = (146097 * 5L) - (30L * 365L + 7L);
Expand Down Expand Up @@ -196,29 +206,46 @@ private FunctionResolver current_date() {
/**
* Specify a start date and add a temporal amount to the date.
* The return type depends on the date type and the interval unit. Detailed supported signatures:
* (STRING/DATE/DATETIME/TIMESTAMP, INTERVAL) -> DATETIME
* (DATE, LONG) -> DATE
* (STRING/DATETIME/TIMESTAMP, LONG) -> DATETIME
* (DATE/DATETIME/TIMESTAMP/TIME, INTERVAL) -> DATETIME
* TODO: MySQL has these signatures too
* (STRING, INTERVAL) -> STRING
* (DATE, INTERVAL) -> DATE // when interval has no time part
* (TIME, INTERVAL) -> TIME // when interval has no date part
* (STRING, INTERVAL) -> STRING // when argument has date or datetime string,
* // result has date or datetime depending on interval type
*/

private DefaultFunctionResolver add_date(FunctionName functionName) {
return define(functionName,
impl(nullMissingHandling(DateTimeFunction::exprAddDateInterval),
DATETIME, STRING, INTERVAL),
private SerializableFunction<?, ?>[] get_date_add_signatures() {
return new SerializableFunction[]{
impl(nullMissingHandling(DateTimeFunction::exprAddDateInterval), DATETIME, DATE, INTERVAL),
impl(nullMissingHandling(DateTimeFunction::exprAddDateInterval),
DATETIME, DATETIME, INTERVAL),
DATETIME, DATETIME, INTERVAL),
impl(nullMissingHandling(DateTimeFunction::exprAddDateInterval),
DATETIME, TIMESTAMP, INTERVAL),
impl(nullMissingHandling(DateTimeFunction::exprAddDateInterval),
DATETIME, TIMESTAMP, INTERVAL),
DATETIME, TIME, INTERVAL),
};
}

/**
* Adds an integer number of days to the first argument.
* (DATE, LONG) -> DATE
* (TIME/DATETIME/TIMESTAMP, LONG) -> DATETIME
*/
private SerializableFunction<?, ?>[] get_adddate_signatures() {
return new SerializableFunction[]{
impl(nullMissingHandling(DateTimeFunction::exprAddDateDays), DATE, DATE, LONG),
impl(nullMissingHandling(DateTimeFunction::exprAddDateDays), DATETIME, DATETIME, LONG),
impl(nullMissingHandling(DateTimeFunction::exprAddDateDays), DATETIME, TIMESTAMP, LONG),
impl(nullMissingHandling(DateTimeFunction::exprAddDateDays), DATETIME, STRING, LONG)
);
impl(nullMissingHandling(DateTimeFunction::exprAddDateDays), DATETIME, TIME, LONG)
};
}

private DefaultFunctionResolver adddate() {
return add_date(BuiltinFunctionName.ADDDATE.getName());
return define(BuiltinFunctionName.ADDDATE.getName(),
(SerializableFunction<FunctionName, Pair<FunctionSignature, FunctionBuilder>>[])
(Stream.concat(Arrays.stream(get_date_add_signatures()),
Arrays.stream(get_adddate_signatures()))
.toArray(SerializableFunction<?, ?>[]::new)));
}

/**
Expand Down Expand Up @@ -265,34 +292,51 @@ private FunctionResolver datetime() {
}

private DefaultFunctionResolver date_add() {
return add_date(BuiltinFunctionName.DATE_ADD.getName());
return define(BuiltinFunctionName.DATE_ADD.getName(),
(SerializableFunction<FunctionName, Pair<FunctionSignature, FunctionBuilder>>[])
get_date_add_signatures());
}

/**
* Specify a start date and subtract a temporal amount to the date.
* The return type depends on the date type and the interval unit. Detailed supported signatures:
* (STRING/DATE/DATETIME/TIMESTAMP, INTERVAL) -> DATETIME
* (DATE, LONG) -> DATE
* (STRING/DATETIME/TIMESTAMP, LONG) -> DATETIME
* (DATE/DATETIME/TIMESTAMP/TIME, INTERVAL) -> DATETIME
* TODO: MySQL has these signatures too
* (DATE, INTERVAL) -> DATE // when interval has no time part
* (TIME, INTERVAL) -> TIME // when interval has no date part
* (STRING, INTERVAL) -> STRING // when argument has date or datetime string,
* // result has date or datetime depending on interval type
*/
private DefaultFunctionResolver sub_date(FunctionName functionName) {
return define(functionName,
impl(nullMissingHandling(DateTimeFunction::exprSubDateInterval),
DATETIME, STRING, INTERVAL),
private SerializableFunction<?, ?>[] get_date_sub_signatures() {
return new SerializableFunction[]{
impl(nullMissingHandling(DateTimeFunction::exprSubDateInterval), DATETIME, DATE, INTERVAL),
impl(nullMissingHandling(DateTimeFunction::exprSubDateInterval),
DATETIME, DATETIME, INTERVAL),
DATETIME, DATETIME, INTERVAL),
impl(nullMissingHandling(DateTimeFunction::exprSubDateInterval),
DATETIME, TIMESTAMP, INTERVAL),
impl(nullMissingHandling(DateTimeFunction::exprSubDateInterval),
DATETIME, TIMESTAMP, INTERVAL),
DATETIME, TIME, INTERVAL)
};
}

/**
* Subtracts an integer number of days to the first argument.
* (DATE, LONG) -> DATE
* (TIME/DATETIME/TIMESTAMP, LONG) -> DATETIME
*/
private SerializableFunction<?, ?>[] get_subdate_signatures() {
return new SerializableFunction[]{
impl(nullMissingHandling(DateTimeFunction::exprSubDateDays), DATE, DATE, LONG),
impl(nullMissingHandling(DateTimeFunction::exprSubDateDays), DATETIME, DATETIME, LONG),
impl(nullMissingHandling(DateTimeFunction::exprSubDateDays), DATETIME, TIMESTAMP, LONG),
impl(nullMissingHandling(DateTimeFunction::exprSubDateDays), DATETIME, STRING, LONG)
);
impl(nullMissingHandling(DateTimeFunction::exprSubDateDays), DATETIME, TIME, LONG)
};
}

private DefaultFunctionResolver date_sub() {
return sub_date(BuiltinFunctionName.DATE_SUB.getName());
return define(BuiltinFunctionName.DATE_SUB.getName(),
(SerializableFunction<FunctionName, Pair<FunctionSignature, FunctionBuilder>>[])
get_date_sub_signatures());
}

/**
Expand Down Expand Up @@ -469,7 +513,11 @@ private DefaultFunctionResolver second() {
}

private DefaultFunctionResolver subdate() {
return sub_date(BuiltinFunctionName.SUBDATE.getName());
return define(BuiltinFunctionName.SUBDATE.getName(),
(SerializableFunction<FunctionName, Pair<FunctionSignature, FunctionBuilder>>[])
(Stream.concat(Arrays.stream(get_date_sub_signatures()),
Arrays.stream(get_subdate_signatures()))
.toArray(SerializableFunction<?, ?>[]::new)));
}

/**
Expand Down Expand Up @@ -582,16 +630,33 @@ private DefaultFunctionResolver date_format() {
}

/**
* ADDDATE function implementation for ExprValue.
* DATE_ADD function implementation for ExprValue.
*
* @param date ExprValue of String/Date/Datetime/Timestamp type.
* @param expr ExprValue of Interval type, the temporal amount to add.
* @param date ExprValue of Date/Time/Datetime/Timestamp type.
* @param interval ExprValue of Interval type, the temporal amount to add.
* @return Datetime resulted from expr added to date.
*/
private ExprValue exprAddDateInterval(ExprValue date, ExprValue expr) {
ExprValue exprValue = new ExprDatetimeValue(date.datetimeValue().plus(expr.intervalValue()));
return (exprValue.timeValue().toSecondOfDay() == 0 ? new ExprDateValue(exprValue.dateValue())
: exprValue);
private ExprValue exprAddDateInterval(ExprValue date, ExprValue interval) {
return exprDateApplyInterval(date, interval.intervalValue(), true);
}

/**
* Adds or subtracts `interval` to/from `date`.
*
* @param date A Date/Time/Datetime/Timestamp value to change.
* @param interval An Interval to add or subtract.
* @param add A flag: true to add, false to subtract.
* @return Datetime calculated.
*/
private ExprValue exprDateApplyInterval(ExprValue date, TemporalAmount interval, Boolean add) {
var dt = LocalDateTime.MIN;
if (date.type() == TIME) {
dt = LocalDateTime.of(LocalDate.now(), date.timeValue());
} else {
dt = date.datetimeValue();
}
dt = add ? dt.plus(interval) : dt.minus(interval);
return new ExprDatetimeValue(dt);
}

/**
Expand All @@ -602,9 +667,29 @@ private ExprValue exprAddDateInterval(ExprValue date, ExprValue expr) {
* @return Date/Datetime resulted from days added to date.
*/
private ExprValue exprAddDateDays(ExprValue date, ExprValue days) {
ExprValue exprValue = new ExprDatetimeValue(date.datetimeValue().plusDays(days.longValue()));
return (exprValue.timeValue().toSecondOfDay() == 0 ? new ExprDateValue(exprValue.dateValue())
: exprValue);
return exprDateApplyDays(date, days.longValue(), true);
}

/**
* Adds or subtracts `days` to/from `date`.
*
* @param date A Date/Time/Datetime/Timestamp value to change.
* @param days A days amount to add or subtract.
* @param add A flag: true to add, false to subtract.
* @return Datetime calculated.
*/
private ExprValue exprDateApplyDays(ExprValue date, Long days, Boolean add) {
if (date.type() == DATE) {
return new ExprDateValue(add ? date.dateValue().plusDays(days)
: date.dateValue().minusDays(days));
}
var dt = LocalDateTime.MIN;
if (date.type() == TIME) {
dt = LocalDateTime.of(LocalDate.now(), date.timeValue());
} else {
dt = date.datetimeValue();
}
return new ExprDatetimeValue(add ? dt.plusDays(days) : dt.minusDays(days));
}

/**
Expand Down Expand Up @@ -918,22 +1003,18 @@ private ExprValue exprSecond(ExprValue time) {
* @return Date/Datetime resulted from days subtracted to date.
*/
private ExprValue exprSubDateDays(ExprValue date, ExprValue days) {
ExprValue exprValue = new ExprDatetimeValue(date.datetimeValue().minusDays(days.longValue()));
return (exprValue.timeValue().toSecondOfDay() == 0 ? new ExprDateValue(exprValue.dateValue())
: exprValue);
return exprDateApplyDays(date, days.longValue(), false);
}

/**
* SUBDATE function implementation for ExprValue.
* DATE_SUB function implementation for ExprValue.
*
* @param date ExprValue of String/Date/Datetime/Timestamp type.
* @param expr ExprValue of Interval type, the temporal amount to subtract.
* @return Datetime resulted from expr subtracted to date.
*/
private ExprValue exprSubDateInterval(ExprValue date, ExprValue expr) {
ExprValue exprValue = new ExprDatetimeValue(date.datetimeValue().minus(expr.intervalValue()));
return (exprValue.timeValue().toSecondOfDay() == 0 ? new ExprDateValue(exprValue.dateValue())
: exprValue);
return exprDateApplyInterval(date, expr.intervalValue(), false);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ private ExprValue hour(ExprValue value) {
}

private ExprValue day(ExprValue value) {
return new ExprIntervalValue(Duration.ofDays(getIntegerValue(value)));
return new ExprIntervalValue(Period.ofDays(getIntegerValue(value)));
}

private ExprValue week(ExprValue value) {
Expand Down
Loading

0 comments on commit 2c1e83d

Please sign in to comment.