[FE](fucntion) add date_floor/ceil in FE function (#23539)

This commit is contained in:
zhangstar333
2023-08-31 19:26:47 +08:00
committed by GitHub
parent e54cd6a35d
commit 3a34ec95af
20 changed files with 897 additions and 77 deletions

View File

@ -160,8 +160,10 @@ DATE: 'DATE';
DATEADD: 'DATEADD';
DATEDIFF: 'DATEDIFF';
DATE_ADD: 'DATE_ADD';
DATE_SUB: 'DATE_SUB';
DATE_CEIL: 'DATE_CEIL';
DATE_DIFF: 'DATE_DIFF';
DATE_FLOOR: 'DATE_FLOOR';
DATE_SUB: 'DATE_SUB';
DBPROPERTIES: 'DBPROPERTIES';
DEFAULT: 'DEFAULT';
DEFINED: 'DEFINED';

View File

@ -395,6 +395,18 @@ primaryExpression
(INTERVAL unitsAmount=valueExpression unit=datetimeUnit
| unitsAmount=valueExpression)
RIGHT_PAREN #date_sub
| name=DATE_FLOOR
LEFT_PAREN
timestamp=valueExpression COMMA
(INTERVAL unitsAmount=valueExpression unit=datetimeUnit
| unitsAmount=valueExpression)
RIGHT_PAREN #dateFloor
| name=DATE_CEIL
LEFT_PAREN
timestamp=valueExpression COMMA
(INTERVAL unitsAmount=valueExpression unit=datetimeUnit
| unitsAmount=valueExpression)
RIGHT_PAREN #dateCeil
| CASE whenClause+ (ELSE elseExpression=expression)? END #searchedCase
| CASE value=expression whenClause+ (ELSE elseExpression=expression)? END #simpleCase
| name=CAST LEFT_PAREN expression AS dataType RIGHT_PAREN #cast
@ -624,8 +636,10 @@ nonReserved
| DATE
| DATEV2
| DATE_ADD
| DATE_CEIL
| DATEDIFF
| DATE_DIFF
| DATE_FLOOR
| DAY
| DBPROPERTIES
| DEFINED

View File

@ -6873,8 +6873,14 @@ timestamp_arithmetic_expr ::=
// This function should not fully qualified
throw new Exception("interval should not be qualified by database name");
}
RESULT = new TimestampArithmeticExpr(functionName.getFunction(), l.get(0), v, u);
//eg: date_floor("0001-01-01 00:00:18",interval 5 second) convert to
//second_floor("0001-01-01 00:00:18", 5, "0001-01-01 00:00:00");
if ("date_floor".equalsIgnoreCase(functionName.getFunction()) ||
"date_ceil".equalsIgnoreCase(functionName.getFunction())) {
RESULT = FunctionCallExpr.functionWithIntervalConvert(functionName.getFunction().toLowerCase(), l.get(0), v, u);
} else {
RESULT = new TimestampArithmeticExpr(functionName.getFunction(), l.get(0), v, u);
}
:}
| function_name:functionName LPAREN time_unit:u COMMA expr:e1 COMMA expr:e2 RPAREN
{:

View File

@ -1046,19 +1046,19 @@ public class DateLiteral extends LiteralExpr {
return LocalDateTime.of(year, month, dayOfMonth, hour, minute, second, microSeconds * 1000);
}
public DateLiteral plusYears(int year) throws AnalysisException {
public DateLiteral plusYears(long year) throws AnalysisException {
return new DateLiteral(getTimeFormatter().plusYears(year), type);
}
public DateLiteral plusMonths(int month) throws AnalysisException {
public DateLiteral plusMonths(long month) throws AnalysisException {
return new DateLiteral(getTimeFormatter().plusMonths(month), type);
}
public DateLiteral plusDays(int day) throws AnalysisException {
public DateLiteral plusDays(long day) throws AnalysisException {
return new DateLiteral(getTimeFormatter().plusDays(day), type);
}
public DateLiteral plusHours(int hour) throws AnalysisException {
public DateLiteral plusHours(long hour) throws AnalysisException {
if (type.isDate()) {
return new DateLiteral(getTimeFormatter().plusHours(hour), Type.DATETIME);
}
@ -1068,7 +1068,7 @@ public class DateLiteral extends LiteralExpr {
return new DateLiteral(getTimeFormatter().plusHours(hour), type);
}
public DateLiteral plusMinutes(int minute) {
public DateLiteral plusMinutes(long minute) {
if (type.isDate()) {
return new DateLiteral(getTimeFormatter().plusMinutes(minute), Type.DATETIME);
}
@ -1078,7 +1078,7 @@ public class DateLiteral extends LiteralExpr {
return new DateLiteral(getTimeFormatter().plusMinutes(minute), type);
}
public DateLiteral plusSeconds(int second) {
public DateLiteral plusSeconds(long second) {
if (type.isDate()) {
return new DateLiteral(getTimeFormatter().plusSeconds(second), Type.DATETIME);
}
@ -1536,6 +1536,10 @@ public class DateLiteral extends LiteralExpr {
}
}
public long daynr() {
return calcDaynr(this.year, this.month, this.day);
}
// calculate the number of days from year 0000-00-00 to year-month-day
private long calcDaynr(long year, long month, long day) {
long delsum = 0;

View File

@ -2267,4 +2267,17 @@ public class FunctionCallExpr extends Expr {
}
return fn;
}
// eg: date_floor("0001-01-01 00:00:18",interval 5 second) convert to
// second_floor("0001-01-01 00:00:18", 5, "0001-01-01 00:00:00");
public static FunctionCallExpr functionWithIntervalConvert(String functionName, Expr str, Expr interval,
String timeUnitIdent) throws AnalysisException {
String newFunctionName = timeUnitIdent + "_" + functionName.split("_")[1];
List<Expr> params = new ArrayList<>();
Expr defaultDatetime = new DateLiteral(0001, 01, 01, 0, 0, 0, 0, Type.DATETIMEV2);
params.add(str);
params.add(interval);
params.add(defaultDatetime);
return new FunctionCallExpr(newFunctionName, params);
}
}

View File

@ -48,6 +48,8 @@ import org.apache.doris.nereids.DorisParser.ComplexDataTypeContext;
import org.apache.doris.nereids.DorisParser.ConstantContext;
import org.apache.doris.nereids.DorisParser.CreateRowPolicyContext;
import org.apache.doris.nereids.DorisParser.CteContext;
import org.apache.doris.nereids.DorisParser.DateCeilContext;
import org.apache.doris.nereids.DorisParser.DateFloorContext;
import org.apache.doris.nereids.DorisParser.Date_addContext;
import org.apache.doris.nereids.DorisParser.Date_subContext;
import org.apache.doris.nereids.DorisParser.DecimalLiteralContext;
@ -198,26 +200,40 @@ import org.apache.doris.nereids.trees.expressions.functions.scalar.Char;
import org.apache.doris.nereids.trees.expressions.functions.scalar.ConvertTo;
import org.apache.doris.nereids.trees.expressions.functions.scalar.CreateMap;
import org.apache.doris.nereids.trees.expressions.functions.scalar.CreateStruct;
import org.apache.doris.nereids.trees.expressions.functions.scalar.DayCeil;
import org.apache.doris.nereids.trees.expressions.functions.scalar.DayFloor;
import org.apache.doris.nereids.trees.expressions.functions.scalar.DaysAdd;
import org.apache.doris.nereids.trees.expressions.functions.scalar.DaysDiff;
import org.apache.doris.nereids.trees.expressions.functions.scalar.DaysSub;
import org.apache.doris.nereids.trees.expressions.functions.scalar.ElementAt;
import org.apache.doris.nereids.trees.expressions.functions.scalar.EncryptKeyRef;
import org.apache.doris.nereids.trees.expressions.functions.scalar.HourCeil;
import org.apache.doris.nereids.trees.expressions.functions.scalar.HourFloor;
import org.apache.doris.nereids.trees.expressions.functions.scalar.HoursAdd;
import org.apache.doris.nereids.trees.expressions.functions.scalar.HoursDiff;
import org.apache.doris.nereids.trees.expressions.functions.scalar.HoursSub;
import org.apache.doris.nereids.trees.expressions.functions.scalar.MinuteCeil;
import org.apache.doris.nereids.trees.expressions.functions.scalar.MinuteFloor;
import org.apache.doris.nereids.trees.expressions.functions.scalar.MinutesAdd;
import org.apache.doris.nereids.trees.expressions.functions.scalar.MinutesDiff;
import org.apache.doris.nereids.trees.expressions.functions.scalar.MinutesSub;
import org.apache.doris.nereids.trees.expressions.functions.scalar.MonthCeil;
import org.apache.doris.nereids.trees.expressions.functions.scalar.MonthFloor;
import org.apache.doris.nereids.trees.expressions.functions.scalar.MonthsAdd;
import org.apache.doris.nereids.trees.expressions.functions.scalar.MonthsDiff;
import org.apache.doris.nereids.trees.expressions.functions.scalar.MonthsSub;
import org.apache.doris.nereids.trees.expressions.functions.scalar.SecondCeil;
import org.apache.doris.nereids.trees.expressions.functions.scalar.SecondFloor;
import org.apache.doris.nereids.trees.expressions.functions.scalar.SecondsAdd;
import org.apache.doris.nereids.trees.expressions.functions.scalar.SecondsDiff;
import org.apache.doris.nereids.trees.expressions.functions.scalar.SecondsSub;
import org.apache.doris.nereids.trees.expressions.functions.scalar.WeekCeil;
import org.apache.doris.nereids.trees.expressions.functions.scalar.WeekFloor;
import org.apache.doris.nereids.trees.expressions.functions.scalar.WeeksAdd;
import org.apache.doris.nereids.trees.expressions.functions.scalar.WeeksDiff;
import org.apache.doris.nereids.trees.expressions.functions.scalar.WeeksSub;
import org.apache.doris.nereids.trees.expressions.functions.scalar.YearCeil;
import org.apache.doris.nereids.trees.expressions.functions.scalar.YearFloor;
import org.apache.doris.nereids.trees.expressions.functions.scalar.YearsAdd;
import org.apache.doris.nereids.trees.expressions.functions.scalar.YearsDiff;
import org.apache.doris.nereids.trees.expressions.functions.scalar.YearsSub;
@ -1095,6 +1111,64 @@ public class LogicalPlanBuilder extends DorisParserBaseVisitor<Object> {
+ ", supported time unit: YEAR/MONTH/DAY/HOUR/MINUTE/SECOND", ctx);
}
@Override
public Expression visitDateFloor(DateFloorContext ctx) {
Expression timeStamp = (Expression) visit(ctx.timestamp);
Expression amount = (Expression) visit(ctx.unitsAmount);
if (ctx.unit == null) {
// use "SECOND" as unit by default
return new SecondFloor(timeStamp, amount);
}
Expression e = new DateTimeV2Literal(0001L, 01L, 01L, 0L, 0L, 0L, 0L);
if ("Year".equalsIgnoreCase(ctx.unit.getText())) {
return new YearFloor(timeStamp, amount, e);
} else if ("MONTH".equalsIgnoreCase(ctx.unit.getText())) {
return new MonthFloor(timeStamp, amount, e);
} else if ("WEEK".equalsIgnoreCase(ctx.unit.getText())) {
return new WeekFloor(timeStamp, amount, e);
} else if ("DAY".equalsIgnoreCase(ctx.unit.getText())) {
return new DayFloor(timeStamp, amount, e);
} else if ("Hour".equalsIgnoreCase(ctx.unit.getText())) {
return new HourFloor(timeStamp, amount, e);
} else if ("Minute".equalsIgnoreCase(ctx.unit.getText())) {
return new MinuteFloor(timeStamp, amount, e);
} else if ("Second".equalsIgnoreCase(ctx.unit.getText())) {
return new SecondFloor(timeStamp, amount, e);
}
throw new ParseException("Unsupported time unit: " + ctx.unit
+ ", supported time unit: YEAR/MONTH/WEEK/DAY/HOUR/MINUTE/SECOND", ctx);
}
@Override
public Expression visitDateCeil(DateCeilContext ctx) {
Expression timeStamp = (Expression) visit(ctx.timestamp);
Expression amount = (Expression) visit(ctx.unitsAmount);
if (ctx.unit == null) {
// use "Second" as unit by default
return new SecondCeil(timeStamp, amount);
}
DateTimeV2Literal e = new DateTimeV2Literal(0001L, 01L, 01L, 0L, 0L, 0L, 0L);
if ("Year".equalsIgnoreCase(ctx.unit.getText())) {
return new YearCeil(timeStamp, amount, e);
} else if ("MONTH".equalsIgnoreCase(ctx.unit.getText())) {
return new MonthCeil(timeStamp, amount, e);
} else if ("WEEK".equalsIgnoreCase(ctx.unit.getText())) {
return new WeekCeil(timeStamp, amount, e);
} else if ("DAY".equalsIgnoreCase(ctx.unit.getText())) {
return new DayCeil(timeStamp, amount, e);
} else if ("Hour".equalsIgnoreCase(ctx.unit.getText())) {
return new HourCeil(timeStamp, amount, e);
} else if ("Minute".equalsIgnoreCase(ctx.unit.getText())) {
return new MinuteCeil(timeStamp, amount, e);
} else if ("Second".equalsIgnoreCase(ctx.unit.getText())) {
return new SecondCeil(timeStamp, amount, e);
}
throw new ParseException("Unsupported time unit: " + ctx.unit
+ ", supported time unit: YEAR/MONTH/WEEK/DAY/HOUR/MINUTE/SECOND", ctx);
}
@Override
public Expression visitDoublePipes(DorisParser.DoublePipesContext ctx) {
return ParserUtils.withOrigin(ctx, () -> {

View File

@ -27,6 +27,7 @@ import org.apache.doris.nereids.trees.expressions.functions.executable.DateTimeA
import org.apache.doris.nereids.trees.expressions.functions.executable.DateTimeExtractAndTransform;
import org.apache.doris.nereids.trees.expressions.functions.executable.ExecutableFunctions;
import org.apache.doris.nereids.trees.expressions.functions.executable.NumericArithmetic;
import org.apache.doris.nereids.trees.expressions.functions.executable.TimeRoundSeries;
import org.apache.doris.nereids.trees.expressions.literal.DateLiteral;
import org.apache.doris.nereids.trees.expressions.literal.Literal;
import org.apache.doris.nereids.trees.expressions.literal.NullLiteral;
@ -141,7 +142,8 @@ public enum ExpressionEvaluator {
ExecutableFunctions.class,
DateLiteral.class,
DateTimeArithmetic.class,
NumericArithmetic.class
NumericArithmetic.class,
TimeRoundSeries.class
);
for (Class<?> cls : classes) {
for (Method method : cls.getDeclaredMethods()) {

View File

@ -25,10 +25,7 @@ import org.apache.doris.nereids.trees.expressions.literal.DateTimeV2Literal;
import org.apache.doris.nereids.trees.expressions.literal.DateV2Literal;
import org.apache.doris.nereids.trees.expressions.literal.IntegerLiteral;
import java.time.Duration;
import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;
import java.time.temporal.TemporalUnit;
/**
* executable functions:
@ -48,49 +45,82 @@ public class TimeRoundSeries {
SECOND
}
private static ChronoUnit dateEnumToUnit(DATE tag) {
switch (tag) {
case YEAR:
return ChronoUnit.YEARS;
case MONTH:
return ChronoUnit.MONTHS;
case DAY:
return ChronoUnit.DAYS;
case HOUR:
return ChronoUnit.HOURS;
case MINUTE:
return ChronoUnit.MINUTES;
default:
return ChronoUnit.SECONDS;
}
}
// get it's from be/src/vec/functions/function_datetime_floor_ceil.cpp##time_round
private static LocalDateTime getDateCeilOrFloor(DATE tag, LocalDateTime date, int period, LocalDateTime origin,
boolean getCeil) {
// Algorithm:
// Firstly, get the unit distance of the two date.
// Secondly, if the origin date is bigger than the date, subtract it to a date before the date by unit.
// Thirdly, re-calculate the distance of the two date.
// Fourthly, get the ceil and floor date of the date by unit and select the corresponding date as the answer.
// handle origin > date
TemporalUnit unit = dateEnumToUnit(tag);
if (origin.isAfter(date)) {
Duration duration = Duration.between(date, origin);
long hour = Math.abs(duration.get(unit));
long ceil = ((hour - 1) / period + 1) * period;
origin = origin.minus(ceil, unit);
DateTimeV2Literal dt = (DateTimeV2Literal) DateTimeV2Literal.fromJavaDateType(date);
DateTimeV2Literal start = (DateTimeV2Literal) DateTimeV2Literal.fromJavaDateType(origin);
long diff = 0;
long trivialPart = 0;
switch (tag) {
case YEAR: {
diff = dt.getYear() - start.getYear();
trivialPart = (dt.getValue() % 10000000000L) - (start.getValue() % 10000000000L);
break;
}
case MONTH: {
diff = (dt.getYear() - start.getYear()) * 12 + (dt.getMonth() - start.getMonth());
trivialPart = (dt.getValue() % 100000000L) - (start.getValue() % 100000000L);
break;
}
case DAY: {
diff = dt.getTotalDays() - start.getTotalDays();
long part2 = dt.getHour() * 3600 + dt.getMinute() * 60 + dt.getSecond();
long part1 = start.getHour() * 3600 + start.getMinute() * 60 + start.getSecond();
trivialPart = part2 - part1;
break;
}
case HOUR: {
diff = (dt.getTotalDays() - start.getTotalDays()) * 24 + (dt.getHour() - start.getHour());
trivialPart = (dt.getMinute() * 60 + dt.getSecond())
- (start.getMinute() * 60 + start.getSecond());
break;
}
case MINUTE: {
diff = (dt.getTotalDays() - start.getTotalDays()) * 24 * 60 + (dt.getHour() - start.getHour()) * 60
+ (dt.getMinute() - start.getMinute());
trivialPart = dt.getSecond() - start.getSecond();
break;
}
case SECOND: {
diff = (dt.getTotalDays() - start.getTotalDays()) * 24 * 60 * 60
+ (dt.getHour() - start.getHour()) * 60 * 60
+ (dt.getMinute() - start.getMinute()) * 60
+ (dt.getSecond() - start.getSecond());
trivialPart = 0;
break;
}
default: {
return null;
}
}
// get distance
Duration duration = Duration.between(origin, date);
long hour = Math.abs(duration.get(unit));
long ceil = ((hour - 1) / period + 1) * period;
long floor = hour / period * period;
LocalDateTime floorDate = origin.plus(floor, unit);
LocalDateTime ceilDate = origin.plus(ceil, unit);
return getCeil ? ceilDate : floorDate;
if (getCeil) {
diff = diff + (trivialPart > 0 ? 1 : 0);
} else {
diff = diff - (trivialPart < 0 ? 1 : 0);
}
long deltaInsidePeriod = (diff % period + period) % period;
long step = diff - deltaInsidePeriod;
if (getCeil) {
step = step + (deltaInsidePeriod == 0 ? 0 : period);
}
switch (tag) {
case YEAR:
return ((DateTimeLiteral) start.plusYears(step)).toJavaDateType();
case MONTH:
return ((DateTimeLiteral) start.plusMonths(step)).toJavaDateType();
case DAY:
return ((DateTimeLiteral) start.plusDays(step)).toJavaDateType();
case HOUR:
return ((DateTimeLiteral) start.plusHours(step)).toJavaDateType();
case MINUTE:
return ((DateTimeLiteral) start.plusMinutes(step)).toJavaDateType();
case SECOND:
return ((DateTimeLiteral) start.plusSeconds(step)).toJavaDateType();
default:
break;
}
return null;
}
/**

View File

@ -206,15 +206,15 @@ public class DateLiteral extends Literal {
return day;
}
public Expression plusDays(int days) {
public Expression plusDays(long days) {
return fromJavaDateType(DateUtils.getTime(DATE_FORMATTER, getStringValue()).plusDays(days));
}
public Expression plusMonths(int months) {
public Expression plusMonths(long months) {
return fromJavaDateType(DateUtils.getTime(DATE_FORMATTER, getStringValue()).plusMonths(months));
}
public Expression plusYears(int years) {
public Expression plusYears(long years) {
return fromJavaDateType(DateUtils.getTime(DATE_FORMATTER, getStringValue()).plusYears(years));
}
@ -222,6 +222,34 @@ public class DateLiteral extends Literal {
return LocalDateTime.of(((int) getYear()), ((int) getMonth()), ((int) getDay()), 0, 0, 0);
}
public long getTotalDays() {
return calculateDays(this.year, this.month, this.day);
}
// calculate the number of days from year 0000-00-00 to year-month-day
private long calculateDays(long year, long month, long day) {
long totalDays = 0;
long y = year;
if (year == 0 && month == 0) {
return 0;
}
/* Cast to int to be able to handle month == 0 */
totalDays = 365 * y + 31 * (month - 1) + day;
if (month <= 2) {
// No leap year
y--;
} else {
// This is great!!!
// 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12
// 0, 0, 3, 3, 4, 4, 5, 5, 5, 6, 7, 8
totalDays -= (month * 4 + 23) / 10;
}
// Every 400 year has 97 leap year, 100, 200, 300 are not leap year.
return totalDays + y / 4 - y / 100 + y / 400;
}
public static Expression fromJavaDateType(LocalDateTime dateTime) {
return isDateOutOfRange(dateTime)
? new NullLiteral(DateType.INSTANCE)

View File

@ -316,23 +316,23 @@ public class DateTimeLiteral extends DateLiteral {
return new org.apache.doris.analysis.DateLiteral(year, month, day, hour, minute, second, Type.DATETIME);
}
public Expression plusYears(int years) {
public Expression plusYears(long years) {
return fromJavaDateType(DateUtils.getTime(DATE_TIME_FORMATTER, getStringValue()).plusYears(years));
}
public Expression plusMonths(int months) {
public Expression plusMonths(long months) {
return fromJavaDateType(DateUtils.getTime(DATE_TIME_FORMATTER, getStringValue()).plusMonths(months));
}
public Expression plusDays(int days) {
public Expression plusDays(long days) {
return fromJavaDateType(DateUtils.getTime(DATE_TIME_FORMATTER, getStringValue()).plusDays(days));
}
public Expression plusHours(int hours) {
public Expression plusHours(long hours) {
return fromJavaDateType(DateUtils.getTime(DATE_TIME_FORMATTER, getStringValue()).plusHours(hours));
}
public Expression plusMinutes(int minutes) {
public Expression plusMinutes(long minutes) {
return fromJavaDateType(DateUtils.getTime(DATE_TIME_FORMATTER, getStringValue()).plusMinutes(minutes));
}

View File

@ -82,31 +82,31 @@ public class DateTimeV2Literal extends DateTimeLiteral {
}
@Override
public Expression plusYears(int years) {
public Expression plusYears(long years) {
return fromJavaDateType(DateUtils.getTime(DATE_TIME_FORMATTER_TO_MICRO_SECOND, getStringValue())
.plusYears(years), getDataType().getScale());
}
@Override
public Expression plusMonths(int months) {
public Expression plusMonths(long months) {
return fromJavaDateType(DateUtils.getTime(DATE_TIME_FORMATTER_TO_MICRO_SECOND, getStringValue())
.plusMonths(months), getDataType().getScale());
}
@Override
public Expression plusDays(int days) {
public Expression plusDays(long days) {
return fromJavaDateType(DateUtils.getTime(DATE_TIME_FORMATTER_TO_MICRO_SECOND, getStringValue())
.plusDays(days), getDataType().getScale());
}
@Override
public Expression plusHours(int hours) {
public Expression plusHours(long hours) {
return fromJavaDateType(DateUtils.getTime(DATE_TIME_FORMATTER_TO_MICRO_SECOND, getStringValue())
.plusHours(hours), getDataType().getScale());
}
@Override
public Expression plusMinutes(int minutes) {
public Expression plusMinutes(long minutes) {
return fromJavaDateType(DateUtils.getTime(DATE_TIME_FORMATTER_TO_MICRO_SECOND, getStringValue())
.plusMinutes(minutes), getDataType().getScale());
}
@ -117,7 +117,7 @@ public class DateTimeV2Literal extends DateTimeLiteral {
.plusSeconds(seconds), getDataType().getScale());
}
public Expression plusMicroSeconds(int microSeconds) {
public Expression plusMicroSeconds(long microSeconds) {
return fromJavaDateType(DateUtils.getTime(DATE_TIME_FORMATTER_TO_MICRO_SECOND, getStringValue())
.plusNanos(microSeconds * 1000L), getDataType().getScale());
}

View File

@ -50,15 +50,15 @@ public class DateV2Literal extends DateLiteral {
return visitor.visitDateV2Literal(this, context);
}
public Expression plusDays(int days) {
public Expression plusDays(long days) {
return fromJavaDateType(DateUtils.getTime(DATE_FORMATTER, getStringValue()).plusDays(days));
}
public Expression plusMonths(int months) {
public Expression plusMonths(long months) {
return fromJavaDateType(DateUtils.getTime(DATE_FORMATTER, getStringValue()).plusMonths(months));
}
public Expression plusYears(int years) {
public Expression plusYears(long years) {
return fromJavaDateType(DateUtils.getTime(DATE_FORMATTER, getStringValue()).plusYears(years));
}

View File

@ -26,6 +26,7 @@ import org.apache.doris.analysis.LargeIntLiteral;
import org.apache.doris.analysis.LiteralExpr;
import org.apache.doris.analysis.NullLiteral;
import org.apache.doris.analysis.StringLiteral;
import org.apache.doris.analysis.TimestampArithmeticExpr.TimeUnit;
import org.apache.doris.catalog.Type;
import org.apache.doris.common.AnalysisException;
import org.apache.doris.common.InvalidFormatException;
@ -111,37 +112,37 @@ public class FEFunctions {
@FEFunction(name = "years_add", argTypes = { "DATETIME", "INT" }, returnType = "DATETIME")
public static DateLiteral yearsAdd(LiteralExpr date, LiteralExpr year) throws AnalysisException {
DateLiteral dateLiteral = (DateLiteral) date;
return dateLiteral.plusYears((int) year.getLongValue());
return dateLiteral.plusYears(year.getLongValue());
}
@FEFunction(name = "months_add", argTypes = { "DATETIME", "INT" }, returnType = "DATETIME")
public static DateLiteral monthsAdd(LiteralExpr date, LiteralExpr month) throws AnalysisException {
DateLiteral dateLiteral = (DateLiteral) date;
return dateLiteral.plusMonths((int) month.getLongValue());
return dateLiteral.plusMonths(month.getLongValue());
}
@FEFunction(name = "days_add", argTypes = { "DATETIME", "INT" }, returnType = "DATETIME")
public static DateLiteral daysAdd(LiteralExpr date, LiteralExpr day) throws AnalysisException {
DateLiteral dateLiteral = (DateLiteral) date;
return dateLiteral.plusDays((int) day.getLongValue());
return dateLiteral.plusDays(day.getLongValue());
}
@FEFunction(name = "hours_add", argTypes = { "DATETIME", "INT" }, returnType = "DATETIME")
public static DateLiteral hoursAdd(LiteralExpr date, LiteralExpr hour) throws AnalysisException {
DateLiteral dateLiteral = (DateLiteral) date;
return dateLiteral.plusHours((int) hour.getLongValue());
return dateLiteral.plusHours(hour.getLongValue());
}
@FEFunction(name = "minutes_add", argTypes = { "DATETIME", "INT" }, returnType = "DATETIME")
public static DateLiteral minutesAdd(LiteralExpr date, LiteralExpr minute) throws AnalysisException {
DateLiteral dateLiteral = (DateLiteral) date;
return dateLiteral.plusMinutes((int) minute.getLongValue());
return dateLiteral.plusMinutes(minute.getLongValue());
}
@FEFunction(name = "seconds_add", argTypes = { "DATETIME", "INT" }, returnType = "DATETIME")
public static DateLiteral secondsAdd(LiteralExpr date, LiteralExpr second) throws AnalysisException {
DateLiteral dateLiteral = (DateLiteral) date;
return dateLiteral.plusSeconds((int) second.getLongValue());
return dateLiteral.plusSeconds(second.getLongValue());
}
@FEFunction(name = "date_format", argTypes = { "DATETIME", "VARCHAR" }, returnType = "VARCHAR")
@ -350,6 +351,177 @@ public class FEFunctions {
return null;
}
@FEFunction(name = "second_floor", argTypes = { "DATETIMEV2", "INT", "DATETIMEV2" }, returnType = "DATETIMEV2")
public static DateLiteral second_floor(LiteralExpr datetime, LiteralExpr period, LiteralExpr defaultDatetime)
throws AnalysisException {
return getFloorCeilDateLiteral(datetime, period, defaultDatetime, false, TimeUnit.SECOND);
}
@FEFunction(name = "second_ceil", argTypes = { "DATETIMEV2", "INT", "DATETIMEV2" }, returnType = "DATETIMEV2")
public static DateLiteral second_ceil(LiteralExpr datetime, LiteralExpr period, LiteralExpr defaultDatetime)
throws AnalysisException {
return getFloorCeilDateLiteral(datetime, period, defaultDatetime, true, TimeUnit.SECOND);
}
@FEFunction(name = "minute_floor", argTypes = { "DATETIMEV2", "INT", "DATETIMEV2" }, returnType = "DATETIMEV2")
public static DateLiteral minute_floor(LiteralExpr datetime, LiteralExpr period, LiteralExpr defaultDatetime)
throws AnalysisException {
return getFloorCeilDateLiteral(datetime, period, defaultDatetime, false, TimeUnit.MINUTE);
}
@FEFunction(name = "minute_ceil", argTypes = { "DATETIMEV2", "INT", "DATETIMEV2" }, returnType = "DATETIMEV2")
public static DateLiteral minute_ceil(LiteralExpr datetime, LiteralExpr period, LiteralExpr defaultDatetime)
throws AnalysisException {
return getFloorCeilDateLiteral(datetime, period, defaultDatetime, true, TimeUnit.MINUTE);
}
@FEFunction(name = "hour_floor", argTypes = { "DATETIMEV2", "INT", "DATETIMEV2" }, returnType = "DATETIMEV2")
public static DateLiteral hour_floor(LiteralExpr datetime, LiteralExpr period, LiteralExpr defaultDatetime)
throws AnalysisException {
return getFloorCeilDateLiteral(datetime, period, defaultDatetime, false, TimeUnit.HOUR);
}
@FEFunction(name = "hour_ceil", argTypes = { "DATETIMEV2", "INT", "DATETIMEV2" }, returnType = "DATETIMEV2")
public static DateLiteral hour_ceil(LiteralExpr datetime, LiteralExpr period, LiteralExpr defaultDatetime)
throws AnalysisException {
return getFloorCeilDateLiteral(datetime, period, defaultDatetime, true, TimeUnit.HOUR);
}
@FEFunction(name = "day_floor", argTypes = { "DATETIMEV2", "INT", "DATETIMEV2" }, returnType = "DATETIMEV2")
public static DateLiteral day_floor(LiteralExpr datetime, LiteralExpr period, LiteralExpr defaultDatetime)
throws AnalysisException {
return getFloorCeilDateLiteral(datetime, period, defaultDatetime, false, TimeUnit.DAY);
}
@FEFunction(name = "day_ceil", argTypes = { "DATETIMEV2", "INT", "DATETIMEV2" }, returnType = "DATETIMEV2")
public static DateLiteral day_ceil(LiteralExpr datetime, LiteralExpr period, LiteralExpr defaultDatetime)
throws AnalysisException {
return getFloorCeilDateLiteral(datetime, period, defaultDatetime, true, TimeUnit.DAY);
}
// get it's from be/src/vec/functions/function_datetime_floor_ceil.cpp##time_round
public static DateLiteral getFloorCeilDateLiteral(LiteralExpr datetime, LiteralExpr period,
LiteralExpr defaultDatetime, boolean isCeil, TimeUnit type) throws AnalysisException {
DateLiteral dt = ((DateLiteral) datetime);
DateLiteral start = ((DateLiteral) defaultDatetime);
long periodValue = ((IntLiteral) period).getValue();
long diff = 0;
long trivialPart = 0;
switch (type) {
case YEAR: {
diff = dt.getYear() - start.getYear();
trivialPart = (dt.getLongValue() % 10000000000L) - (start.getLongValue() % 10000000000L);
break;
}
case MONTH: {
diff = (dt.getYear() - start.getYear()) * 12 + (dt.getMonth() - start.getMonth());
trivialPart = (dt.getLongValue() % 100000000L) - (start.getLongValue() % 100000000L);
break;
}
case WEEK: {
diff = (dt.daynr() / 7) - (start.daynr() / 7);
long part2 = (dt.daynr() % 7) * 24 * 3600 + dt.getHour() * 3600 + dt.getMinute() * 60 + dt.getSecond();
long part1 = (start.daynr() % 7) * 24 * 3600 + start.getHour() * 3600 + start.getMinute() * 60
+ start.getSecond();
trivialPart = part2 - part1;
break;
}
case DAY: {
diff = dt.daynr() - start.daynr();
long part2 = dt.getHour() * 3600 + dt.getMinute() * 60 + dt.getSecond();
long part1 = start.getHour() * 3600 + start.getMinute() * 60 + start.getSecond();
trivialPart = part2 - part1;
break;
}
case HOUR: {
diff = (dt.daynr() - start.daynr()) * 24 + (dt.getHour() - start.getHour());
trivialPart = (dt.getMinute() * 60 + dt.getSecond()) - (start.getMinute() * 60 + start.getSecond());
break;
}
case MINUTE: {
diff = (dt.daynr() - start.daynr()) * 24 * 60 + (dt.getHour() - start.getHour()) * 60
+ (dt.getMinute() - start.getMinute());
trivialPart = dt.getSecond() - start.getSecond();
break;
}
case SECOND: {
diff = (dt.daynr() - start.daynr()) * 24 * 60 * 60 + (dt.getHour() - start.getHour()) * 60 * 60
+ (dt.getMinute() - start.getMinute()) * 60 + (dt.getSecond() - start.getSecond());
trivialPart = 0;
break;
}
default:
break;
}
if (isCeil) {
diff = diff + (trivialPart > 0 ? 1 : 0);
} else {
diff = diff - (trivialPart < 0 ? 1 : 0);
}
long deltaInsidePeriod = (diff % periodValue + periodValue) % periodValue;
long step = diff - deltaInsidePeriod;
if (isCeil) {
step = step + (deltaInsidePeriod == 0 ? 0 : periodValue);
}
switch (type) {
case YEAR:
return start.plusYears(step);
case MONTH:
return start.plusMonths(step);
case WEEK:
return start.plusDays(step * 7);
case DAY:
return start.plusDays(step);
case HOUR:
return start.plusHours(step);
case MINUTE:
return start.plusMinutes(step);
case SECOND:
return start.plusSeconds(step);
default:
break;
}
return null;
}
@FEFunction(name = "week_floor", argTypes = { "DATETIMEV2", "INT", "DATETIMEV2" }, returnType = "DATETIMEV2")
public static DateLiteral week_floor(LiteralExpr datetime, LiteralExpr period, LiteralExpr defaultDatetime)
throws AnalysisException {
return getFloorCeilDateLiteral(datetime, period, defaultDatetime, false, TimeUnit.WEEK);
}
@FEFunction(name = "week_ceil", argTypes = { "DATETIMEV2", "INT", "DATETIMEV2" }, returnType = "DATETIMEV2")
public static DateLiteral week_ceil(LiteralExpr datetime, LiteralExpr period, LiteralExpr defaultDatetime)
throws AnalysisException {
return getFloorCeilDateLiteral(datetime, period, defaultDatetime, true, TimeUnit.WEEK);
}
@FEFunction(name = "month_floor", argTypes = { "DATETIMEV2", "INT", "DATETIMEV2" }, returnType = "DATETIMEV2")
public static DateLiteral month_floor(LiteralExpr datetime, LiteralExpr period, LiteralExpr defaultDatetime)
throws AnalysisException {
return getFloorCeilDateLiteral(datetime, period, defaultDatetime, false, TimeUnit.MONTH);
}
@FEFunction(name = "month_ceil", argTypes = { "DATETIMEV2", "INT", "DATETIMEV2" }, returnType = "DATETIMEV2")
public static DateLiteral month_ceil(LiteralExpr datetime, LiteralExpr period, LiteralExpr defaultDatetime)
throws AnalysisException {
return getFloorCeilDateLiteral(datetime, period, defaultDatetime, true, TimeUnit.MONTH);
}
@FEFunction(name = "year_floor", argTypes = { "DATETIMEV2", "INT", "DATETIMEV2" }, returnType = "DATETIMEV2")
public static DateLiteral year_floor(LiteralExpr datetime, LiteralExpr period, LiteralExpr defaultDatetime)
throws AnalysisException {
return getFloorCeilDateLiteral(datetime, period, defaultDatetime, false, TimeUnit.YEAR);
}
@FEFunction(name = "year_ceil", argTypes = { "DATETIMEV2", "INT", "DATETIMEV2" }, returnType = "DATETIMEV2")
public static DateLiteral year_ceil(LiteralExpr datetime, LiteralExpr period, LiteralExpr defaultDatetime)
throws AnalysisException {
return getFloorCeilDateLiteral(datetime, period, defaultDatetime, true, TimeUnit.YEAR);
}
@FEFunction(name = "date_trunc", argTypes = {"DATETIME", "VARCHAR"}, returnType = "DATETIME")
public static DateLiteral dateTrunc(LiteralExpr date, LiteralExpr truncate) {
if (date.getType().isDateLike()) {