[FE](fucntion) add date_floor/ceil in FE function (#23539)
This commit is contained in:
@ -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';
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
{:
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@ -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, () -> {
|
||||
|
||||
@ -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()) {
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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));
|
||||
}
|
||||
|
||||
|
||||
@ -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());
|
||||
}
|
||||
|
||||
@ -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));
|
||||
}
|
||||
|
||||
|
||||
@ -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()) {
|
||||
|
||||
Reference in New Issue
Block a user