Skip to content

Commit

Permalink
expression: fix infinity loop in timestampadd (#55143)
Browse files Browse the repository at this point in the history
close #54908
  • Loading branch information
xzhangxian1008 committed Aug 2, 2024
1 parent ff2feb6 commit b89e951
Show file tree
Hide file tree
Showing 5 changed files with 98 additions and 35 deletions.
15 changes: 8 additions & 7 deletions expression/builtin_time.go
Original file line number Diff line number Diff line change
Expand Up @@ -2966,7 +2966,10 @@ func (du *baseDateArithmetical) addDate(ctx sessionctx.Context, date types.Time,
}

goTime = goTime.Add(time.Duration(nano))
goTime = types.AddDate(year, month, day, goTime)
goTime, err = types.AddDate(year, month, day, goTime)
if err != nil {
return types.ZeroTime, true, handleInvalidTimeError(ctx, types.ErrDatetimeFunctionOverflow.GenWithStackByArgs("datetime"))
}

// Adjust fsp as required by outer - always respect type inference.
date.SetFsp(resultFsp)
Expand Down Expand Up @@ -6179,13 +6182,11 @@ func addUnitToTime(unit string, t time.Time, v float64) (time.Time, bool, error)
if !validAddMonth(v, t.Year(), int(t.Month())) {
return tb, true, nil
}
tb = t.AddDate(0, int(v), 0)

// For corner case: timestampadd(month,1,date '2024-01-31') = "2024-02-29", timestampadd(month,1,date '2024-01-30') = "2024-02-29"
// `tb.Month()` refers to the actual result, `t.Month()+v` refers to the expect result.
// Actual result may be greater than expect result, we need to judge and modify it.
for int(tb.Month())%12 != (int(t.Month())+int(v))%12 {
tb = tb.AddDate(0, 0, -1)
var err error
tb, err = types.AddDate(0, int64(v), 0, t)
if err != nil {
return tb, true, err
}
case "QUARTER":
if !validAddMonth(v*3, t.Year(), int(t.Month())) {
Expand Down
42 changes: 41 additions & 1 deletion expression/builtin_time_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2526,7 +2526,7 @@ func TestTimestampAdd(t *testing.T) {
{"MINUTE", 1.5, "1995-05-01 00:00:00.000000", "1995-05-01 00:02:00"},
{"MICROSECOND", -100, "1995-05-01 00:00:00.0001", "1995-05-01 00:00:00"},

// issue41052
// issue 41052
{"MONTH", 1, "2024-01-31", "2024-02-29 00:00:00"},
{"MONTH", 1, "2024-01-30", "2024-02-29 00:00:00"},
{"MONTH", 1, "2024-01-29", "2024-02-29 00:00:00"},
Expand All @@ -2537,6 +2537,46 @@ func TestTimestampAdd(t *testing.T) {
{"MONTH", 10, "2024-10-31", "2025-08-31 00:00:00"},
{"MONTH", 1, "2024-11-30", "2024-12-30 00:00:00"},
{"MONTH", 13, "2024-11-30", "2025-12-30 00:00:00"},

// issue 54908
{"MONTH", 0, "2024-09-01", "2024-09-01 00:00:00"},
{"MONTH", -10, "2024-09-01", "2023-11-01 00:00:00"},
{"MONTH", -2, "2024-04-28", "2024-02-28 00:00:00"},
{"MONTH", -2, "2024-04-29", "2024-02-29 00:00:00"},
{"MONTH", -2, "2024-04-30", "2024-02-29 00:00:00"},
{"MONTH", -1, "2024-03-28", "2024-02-28 00:00:00"},
{"MONTH", -1, "2024-03-29", "2024-02-29 00:00:00"},
{"MONTH", -1, "2024-03-30", "2024-02-29 00:00:00"},
{"MONTH", -1, "2024-03-31", "2024-02-29 00:00:00"},
{"MONTH", -1, "2024-03-25", "2024-02-25 00:00:00"},
{"MONTH", -12, "2024-03-31", "2023-03-31 00:00:00"},
{"MONTH", -13, "2024-03-31", "2023-02-28 00:00:00"},
{"MONTH", -14, "2024-03-31", "2023-01-31 00:00:00"},
{"MONTH", -24, "2024-03-31", "2022-03-31 00:00:00"},
{"MONTH", -25, "2024-03-31", "2022-02-28 00:00:00"},
{"MONTH", -26, "2024-03-31", "2022-01-31 00:00:00"},
{"MONTH", -1, "2024-02-25", "2024-01-25 00:00:00"},
{"MONTH", -11, "2025-02-28", "2024-03-28 00:00:00"},
{"MONTH", -12, "2025-02-28", "2024-02-28 00:00:00"},
{"MONTH", -13, "2025-02-28", "2024-01-28 00:00:00"},
{"MONTH", -11, "2024-02-29", "2023-03-29 00:00:00"},
{"MONTH", -12, "2024-02-29", "2023-02-28 00:00:00"},
{"MONTH", -13, "2024-02-29", "2023-01-29 00:00:00"},
{"MONTH", -11, "2023-02-28", "2022-03-28 00:00:00"},
{"MONTH", -12, "2023-02-28", "2022-02-28 00:00:00"},
{"MONTH", -13, "2023-02-28", "2022-01-28 00:00:00"},
{"MONTH", -2, "2023-02-28", "2022-12-28 00:00:00"},
{"MONTH", -14, "2023-02-28", "2021-12-28 00:00:00"},
{"MONTH", -3, "2023-03-20", "2022-12-20 00:00:00"},
{"MONTH", -3, "2023-03-31", "2022-12-31 00:00:00"},
{"MONTH", -15, "2023-03-20", "2021-12-20 00:00:00"},
{"MONTH", -15, "2023-03-31", "2021-12-31 00:00:00"},
{"MONTH", 12, "2020-02-29", "2021-02-28 00:00:00"},
{"MONTH", -12, "2020-02-29", "2019-02-28 00:00:00"},
{"MONTH", 10000*365 + 1, "2024-10-29", ""},
{"MONTH", -10000*365 - 1, "2024-10-29", ""},
{"MONTH", 3, "9999-10-29", ""},
{"MONTH", -3, "0001-01-29", ""},
}

fc := funcs[ast.TimestampAdd]
Expand Down
20 changes: 18 additions & 2 deletions types/core_time.go
Original file line number Diff line number Diff line change
Expand Up @@ -280,14 +280,30 @@ func compareTime(a, b CoreTime) int {
// Dig it and we found it's caused by golang api time.Date(year int, month Month, day, hour, min, sec, nsec int, loc *Location) Time ,
// it says October 32 converts to November 1 ,it conflicts with mysql.
// See https://dev.mysql.com/doc/refman/5.7/en/date-and-time-functions.html#function_date-add
func AddDate(year, month, day int64, ot gotime.Time) (nt gotime.Time) {
func AddDate(year, month, day int64, ot gotime.Time) (nt gotime.Time, _ error) {
// We must limit the range of year, month and day to avoid overflow.
// The datetime range is from '1000-01-01 00:00:00.000000' to '9999-12-31 23:59:59.499999',
// so it is safe to limit the added value from -10000*365 to 10000*365.
const maxAdd = 10000 * 365
const minAdd = -maxAdd
if year > maxAdd || year < minAdd ||
month > maxAdd || month < minAdd ||
day > maxAdd || day < minAdd {
return nt, ErrDatetimeFunctionOverflow.GenWithStackByArgs("datetime")
}

df := getFixDays(int(year), int(month), int(day), ot)
if df != 0 {
nt = ot.AddDate(int(year), int(month), df)
} else {
nt = ot.AddDate(int(year), int(month), int(day))
}
return nt

if nt.Year() < 0 || nt.Year() > 9999 {
return nt, ErrDatetimeFunctionOverflow.GenWithStackByArgs("datetime")
}

return nt, nil
}

func calcTimeFromSec(to *CoreTime, seconds, microseconds int) {
Expand Down
2 changes: 1 addition & 1 deletion types/core_time_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -272,7 +272,7 @@ func TestAddDate(t *testing.T) {
}

for _, tt := range tests {
res := AddDate(int64(tt.year), int64(tt.month), int64(tt.day), tt.ot)
res, _ := AddDate(int64(tt.year), int64(tt.month), int64(tt.day), tt.ot)
require.Equal(t, tt.year+tt.ot.Year(), res.Year())
}
}
Expand Down
54 changes: 30 additions & 24 deletions types/time.go
Original file line number Diff line number Diff line change
Expand Up @@ -2319,16 +2319,17 @@ func parseSingleTimeValue(unit string, format string, strictCheck bool) (year in
lf := len(format) - 1
// Has fraction part
if decimalPointPos < lf {
var tmpErr error
dvPre := oneToSixDigitRegex.FindString(format[decimalPointPos+1:]) // the numberical prefix of the fraction part
decimalLen = len(dvPre)
if decimalLen >= 6 {
// MySQL rounds down to 1e-6.
if dv, err = strconv.ParseInt(dvPre[0:6], 10, 64); err != nil {
return 0, 0, 0, 0, 0, ErrWrongValue.GenWithStackByArgs(DateTimeStr, format)
if dv, tmpErr = strconv.ParseInt(dvPre[0:6], 10, 64); tmpErr != nil {
err = ErrWrongValue.GenWithStackByArgs(DateTimeStr, format)
}
} else {
if dv, err = strconv.ParseInt(dvPre+"000000"[:6-decimalLen], 10, 64); err != nil {
return 0, 0, 0, 0, 0, ErrWrongValue.GenWithStackByArgs(DateTimeStr, format)
if dv, tmpErr = strconv.ParseInt(dvPre+"000000"[:6-decimalLen], 10, 64); tmpErr != nil {
err = ErrWrongValue.GenWithStackByArgs(DateTimeStr, format)
}
}
if dv >= 500000 { // Round up, and we should keep 6 digits for microsecond, so dv should in [000000, 999999].
Expand Down Expand Up @@ -2428,39 +2429,44 @@ func parseTimeValue(format string, index, cnt int) (years int64, months int64, d
index--
}

// ParseInt may return an error when overflowed, but we should continue to parse the rest of the string because
// the caller may ignore the error and use the return value.
// In this case, we should return a big value to make sure the result date after adding this interval
// is also overflowed and NULL is returned to the user.
years, err = strconv.ParseInt(fields[YearIndex], 10, 64)
if err != nil {
return 0, 0, 0, 0, 0, ErrWrongValue.GenWithStackByArgs(DateTimeStr, originalFmt)
err = ErrWrongValue.GenWithStackByArgs(DateTimeStr, originalFmt)
}
months, err = strconv.ParseInt(fields[MonthIndex], 10, 64)
if err != nil {
return 0, 0, 0, 0, 0, ErrWrongValue.GenWithStackByArgs(DateTimeStr, originalFmt)
var tmpErr error
months, tmpErr = strconv.ParseInt(fields[MonthIndex], 10, 64)
if err == nil && tmpErr != nil {
err = ErrWrongValue.GenWithStackByArgs(DateTimeStr, originalFmt)
}
days, err = strconv.ParseInt(fields[DayIndex], 10, 64)
if err != nil {
return 0, 0, 0, 0, 0, ErrWrongValue.GenWithStackByArgs(DateTimeStr, originalFmt)
days, tmpErr = strconv.ParseInt(fields[DayIndex], 10, 64)
if err == nil && tmpErr != nil {
err = ErrWrongValue.GenWithStackByArgs(DateTimeStr, originalFmt)
}

hours, err := strconv.ParseInt(fields[HourIndex], 10, 64)
if err != nil {
return 0, 0, 0, 0, 0, ErrWrongValue.GenWithStackByArgs(DateTimeStr, originalFmt)
hours, tmpErr := strconv.ParseInt(fields[HourIndex], 10, 64)
if err == nil && tmpErr != nil {
err = ErrWrongValue.GenWithStackByArgs(DateTimeStr, originalFmt)
}
minutes, err := strconv.ParseInt(fields[MinuteIndex], 10, 64)
if err != nil {
return 0, 0, 0, 0, 0, ErrWrongValue.GenWithStackByArgs(DateTimeStr, originalFmt)
minutes, tmpErr := strconv.ParseInt(fields[MinuteIndex], 10, 64)
if err == nil && tmpErr != nil {
err = ErrWrongValue.GenWithStackByArgs(DateTimeStr, originalFmt)
}
seconds, err := strconv.ParseInt(fields[SecondIndex], 10, 64)
if err != nil {
return 0, 0, 0, 0, 0, ErrWrongValue.GenWithStackByArgs(DateTimeStr, originalFmt)
seconds, tmpErr := strconv.ParseInt(fields[SecondIndex], 10, 64)
if err == nil && tmpErr != nil {
err = ErrWrongValue.GenWithStackByArgs(DateTimeStr, originalFmt)
}
microseconds, err := strconv.ParseInt(alignFrac(fields[MicrosecondIndex], MaxFsp), 10, 64)
if err != nil {
return 0, 0, 0, 0, 0, ErrWrongValue.GenWithStackByArgs(DateTimeStr, originalFmt)
microseconds, tmpErr := strconv.ParseInt(alignFrac(fields[MicrosecondIndex], MaxFsp), 10, 64)
if err == nil && tmpErr != nil {
err = ErrWrongValue.GenWithStackByArgs(DateTimeStr, originalFmt)
}
seconds = hours*3600 + minutes*60 + seconds
days += seconds / (3600 * 24)
seconds %= 3600 * 24
return years, months, days, seconds*int64(gotime.Second) + microseconds*int64(gotime.Microsecond), fsp, nil
return years, months, days, seconds*int64(gotime.Second) + microseconds*int64(gotime.Microsecond), fsp, err
}

func parseAndValidateDurationValue(format string, index, cnt int) (int64, int, error) {
Expand Down

0 comments on commit b89e951

Please sign in to comment.