From 724b88b7b681c321319fa28d9a8c6bdd238e9f19 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E8=B6=85?= Date: Tue, 12 Dec 2023 19:06:19 +0800 Subject: [PATCH] expression: fix the behavior when adding date with big interval (#49228) close pingcap/tidb#49227 --- pkg/expression/BUILD.bazel | 1 + pkg/expression/builtin_time.go | 117 ++-- .../integration_test/integration_test.go | 8 +- pkg/types/core_time.go | 20 +- pkg/types/core_time_test.go | 29 +- pkg/types/time.go | 61 +- .../r/expression/issues.result | 10 +- .../integrationtest/r/expression/time.result | 536 ++++++++++++++++++ tests/integrationtest/t/expression/time.test | 97 ++++ 9 files changed, 801 insertions(+), 78 deletions(-) diff --git a/pkg/expression/BUILD.bazel b/pkg/expression/BUILD.bazel index f368a6b98b823..a0fe2d3020191 100644 --- a/pkg/expression/BUILD.bazel +++ b/pkg/expression/BUILD.bazel @@ -70,6 +70,7 @@ go_library( visibility = ["//visibility:public"], deps = [ "//pkg/config", + "//pkg/errctx", "//pkg/errno", "//pkg/extension", "//pkg/kv", diff --git a/pkg/expression/builtin_time.go b/pkg/expression/builtin_time.go index 82f9708a34fdb..827de124c2eb0 100644 --- a/pkg/expression/builtin_time.go +++ b/pkg/expression/builtin_time.go @@ -30,6 +30,7 @@ import ( "github.com/pingcap/errors" "github.com/pingcap/failpoint" "github.com/pingcap/tidb/pkg/config" + "github.com/pingcap/tidb/pkg/errctx" "github.com/pingcap/tidb/pkg/parser/ast" "github.com/pingcap/tidb/pkg/parser/mysql" "github.com/pingcap/tidb/pkg/parser/terror" @@ -2754,7 +2755,7 @@ type baseDateArithmetical struct { func newDateArithmeticalUtil() baseDateArithmetical { return baseDateArithmetical{ - intervalRegexp: regexp.MustCompile(`-?[\d]+`), + intervalRegexp: regexp.MustCompile(`^[+-]?[\d]+`), } } @@ -2864,17 +2865,58 @@ func (du *baseDateArithmetical) getIntervalFromString(ctx sessionctx.Context, ar if isNull || err != nil { return "", true, err } - // unit "DAY" and "HOUR" has to be specially handled. - if toLower := strings.ToLower(unit); toLower == "day" || toLower == "hour" { - if strings.ToLower(interval) == "true" { - interval = "1" - } else if strings.ToLower(interval) == "false" { + + interval, err = du.intervalReformatString(ctx.GetSessionVars().StmtCtx.ErrCtx(), interval, unit) + return interval, false, err +} + +func (du *baseDateArithmetical) intervalReformatString(ec errctx.Context, str string, unit string) (interval string, err error) { + switch strings.ToUpper(unit) { + case "MICROSECOND", "MINUTE", "HOUR", "DAY", "WEEK", "MONTH", "QUARTER", "YEAR": + str = strings.TrimSpace(str) + // a single unit value has to be specially handled. + interval = du.intervalRegexp.FindString(str) + if interval == "" { interval = "0" - } else { - interval = du.intervalRegexp.FindString(interval) } + + if interval != str { + err = ec.HandleError(types.ErrTruncatedWrongVal.GenWithStackByArgs("DECIMAL", str)) + } + case "SECOND": + // The unit SECOND is specially handled, for example: + // date + INTERVAL "1e2" SECOND = date + INTERVAL 100 second + // date + INTERVAL "1.6" SECOND = date + INTERVAL 1.6 second + // But: + // date + INTERVAL "1e2" MINUTE = date + INTERVAL 1 MINUTE + // date + INTERVAL "1.6" MINUTE = date + INTERVAL 1 MINUTE + var dec types.MyDecimal + if err = dec.FromString([]byte(str)); err != nil { + truncatedErr := types.ErrTruncatedWrongVal.GenWithStackByArgs("DECIMAL", str) + err = ec.HandleErrorWithAlias(err, truncatedErr, truncatedErr) + } + interval = string(dec.ToString()) + default: + interval = str } - return interval, false, nil + return interval, err +} + +func (du *baseDateArithmetical) intervalDecimalToString(ec errctx.Context, dec *types.MyDecimal) (string, error) { + var rounded types.MyDecimal + err := dec.Round(&rounded, 0, types.ModeHalfUp) + if err != nil { + return "", err + } + + intVal, err := rounded.ToInt() + if err != nil { + if err = ec.HandleError(types.ErrTruncatedWrongVal.GenWithStackByArgs("DECIMAL", dec.String())); err != nil { + return "", err + } + } + + return strconv.FormatInt(intVal, 10), nil } func (du *baseDateArithmetical) getIntervalFromDecimal(ctx sessionctx.Context, args []Expression, row chunk.Row, unit string) (string, bool, error) { @@ -2921,9 +2963,8 @@ func (du *baseDateArithmetical) getIntervalFromDecimal(ctx sessionctx.Context, a // interval is already like the %f format. default: // YEAR, QUARTER, MONTH, WEEK, DAY, HOUR, MINUTE, MICROSECOND - castExpr := WrapWithCastAsString(ctx, WrapWithCastAsInt(ctx, args[1])) - interval, isNull, err = castExpr.EvalString(ctx, row) - if isNull || err != nil { + interval, err = du.intervalDecimalToString(ctx.GetSessionVars().StmtCtx.ErrCtx(), decimal) + if err != nil { return "", true, err } } @@ -2936,6 +2977,11 @@ func (du *baseDateArithmetical) getIntervalFromInt(ctx sessionctx.Context, args if isNull || err != nil { return "", true, err } + + if mysql.HasUnsignedFlag(args[1].GetType().GetFlag()) { + return strconv.FormatUint(uint64(interval), 10), false, nil + } + return strconv.FormatInt(interval, 10), false, nil } @@ -2962,7 +3008,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) @@ -2974,10 +3023,6 @@ func (du *baseDateArithmetical) addDate(ctx sessionctx.Context, date types.Time, return date, false, nil } - if goTime.Year() < 0 || goTime.Year() > 9999 { - return types.ZeroTime, true, handleInvalidTimeError(ctx, types.ErrDatetimeFunctionOverflow.GenWithStackByArgs("datetime")) - } - date.SetCoreTime(types.FromGoTime(goTime)) overflow, err := types.DateTimeIsOverflow(ctx.GetSessionVars().StmtCtx.TypeCtx(), date) if err := handleInvalidTimeError(ctx, err); err != nil { @@ -3236,20 +3281,7 @@ func (du *baseDateArithmetical) vecGetIntervalFromString(b *baseBuiltinFunc, ctx return err } - amendInterval := func(val string) string { - return val - } - if unitLower := strings.ToLower(unit); unitLower == "day" || unitLower == "hour" { - amendInterval = func(val string) string { - if intervalLower := strings.ToLower(val); intervalLower == "true" { - return "1" - } else if intervalLower == "false" { - return "0" - } - return du.intervalRegexp.FindString(val) - } - } - + ec := ctx.GetSessionVars().StmtCtx.ErrCtx() result.ReserveString(n) for i := 0; i < n; i++ { if buf.IsNull(i) { @@ -3257,7 +3289,11 @@ func (du *baseDateArithmetical) vecGetIntervalFromString(b *baseBuiltinFunc, ctx continue } - result.AppendString(amendInterval(buf.GetString(i))) + interval, err := du.intervalReformatString(ec, buf.GetString(i), unit) + if err != nil { + return err + } + result.AppendString(interval) } return nil } @@ -3325,10 +3361,18 @@ func (du *baseDateArithmetical) vecGetIntervalFromDecimal(b *baseBuiltinFunc, ct /* keep interval as original decimal */ default: // YEAR, QUARTER, MONTH, WEEK, DAY, HOUR, MINUTE, MICROSECOND - castExpr := WrapWithCastAsString(ctx, WrapWithCastAsInt(ctx, b.args[1])) amendInterval = func(_ string, row *chunk.Row) (string, bool, error) { - interval, isNull, err := castExpr.EvalString(ctx, *row) - return interval, isNull || err != nil, err + dec, isNull, err := b.args[1].EvalDecimal(ctx, *row) + if isNull || err != nil { + return "", true, err + } + + str, err := du.intervalDecimalToString(ctx.GetSessionVars().StmtCtx.ErrCtx(), dec) + if err != nil { + return "", true, err + } + + return str, false, nil } } @@ -3376,9 +3420,12 @@ func (du *baseDateArithmetical) vecGetIntervalFromInt(b *baseBuiltinFunc, ctx se result.ReserveString(n) i64s := buf.Int64s() + unsigned := mysql.HasUnsignedFlag(b.args[1].GetType().GetFlag()) for i := 0; i < n; i++ { if buf.IsNull(i) { result.AppendNull() + } else if unsigned { + result.AppendString(strconv.FormatUint(uint64(i64s[i]), 10)) } else { result.AppendString(strconv.FormatInt(i64s[i], 10)) } diff --git a/pkg/expression/integration_test/integration_test.go b/pkg/expression/integration_test/integration_test.go index 9b8c71da32505..a7b98c902fdff 100644 --- a/pkg/expression/integration_test/integration_test.go +++ b/pkg/expression/integration_test/integration_test.go @@ -2258,11 +2258,11 @@ func TestTimeBuiltin(t *testing.T) { {"\"2011-11-11 10:10:10\"", "\"20\"", "DAY", "2011-12-01 10:10:10", "2011-10-22 10:10:10"}, {"\"2011-11-11 10:10:10\"", "19.88", "DAY", "2011-12-01 10:10:10", "2011-10-22 10:10:10"}, {"\"2011-11-11 10:10:10\"", "\"19.88\"", "DAY", "2011-11-30 10:10:10", "2011-10-23 10:10:10"}, - {"\"2011-11-11 10:10:10\"", "\"prefix19suffix\"", "DAY", "2011-11-30 10:10:10", "2011-10-23 10:10:10"}, + {"\"2011-11-11 10:10:10\"", "\"prefix19suffix\"", "DAY", "2011-11-11 10:10:10", "2011-11-11 10:10:10"}, {"\"2011-11-11 10:10:10\"", "\"20-11\"", "DAY", "2011-12-01 10:10:10", "2011-10-22 10:10:10"}, {"\"2011-11-11 10:10:10\"", "\"20,11\"", "daY", "2011-12-01 10:10:10", "2011-10-22 10:10:10"}, {"\"2011-11-11 10:10:10\"", "\"1000\"", "dAy", "2014-08-07 10:10:10", "2009-02-14 10:10:10"}, - {"\"2011-11-11 10:10:10\"", "\"true\"", "Day", "2011-11-12 10:10:10", "2011-11-10 10:10:10"}, + {"\"2011-11-11 10:10:10\"", "\"true\"", "Day", "2011-11-11 10:10:10", "2011-11-11 10:10:10"}, {"\"2011-11-11 10:10:10\"", "true", "Day", "2011-11-12 10:10:10", "2011-11-10 10:10:10"}, {"\"2011-11-11\"", "1", "DAY", "2011-11-12", "2011-11-10"}, {"\"2011-11-11\"", "10", "HOUR", "2011-11-11 10:00:00", "2011-11-10 14:00:00"}, @@ -2340,8 +2340,8 @@ func TestTimeBuiltin(t *testing.T) { {"\"2009-01-01\"", "6/0", "HOUR_MINUTE", "", ""}, {"\"1970-01-01 12:00:00\"", "CAST(6/4 AS DECIMAL(3,1))", "HOUR_MINUTE", "1970-01-01 13:05:00", "1970-01-01 10:55:00"}, // for issue #8077 - {"\"2012-01-02\"", "\"prefix8\"", "HOUR", "2012-01-02 08:00:00", "2012-01-01 16:00:00"}, - {"\"2012-01-02\"", "\"prefix8prefix\"", "HOUR", "2012-01-02 08:00:00", "2012-01-01 16:00:00"}, + {"\"2012-01-02\"", "\"prefix8\"", "HOUR", "2012-01-02 00:00:00", "2012-01-02 00:00:00"}, + {"\"2012-01-02\"", "\"prefix8prefix\"", "HOUR", "2012-01-02 00:00:00", "2012-01-02 00:00:00"}, {"\"2012-01-02\"", "\"8:00\"", "HOUR", "2012-01-02 08:00:00", "2012-01-01 16:00:00"}, {"\"2012-01-02\"", "\"8:00:00\"", "HOUR", "2012-01-02 08:00:00", "2012-01-01 16:00:00"}, } diff --git a/pkg/types/core_time.go b/pkg/types/core_time.go index aaf3f5711fea9..1729ecb8a647d 100644 --- a/pkg/types/core_time.go +++ b/pkg/types/core_time.go @@ -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) { diff --git a/pkg/types/core_time_test.go b/pkg/types/core_time_test.go index 999a5504f0899..54a1b8e27bf9f 100644 --- a/pkg/types/core_time_test.go +++ b/pkg/types/core_time_test.go @@ -263,16 +263,33 @@ func TestAddDate(t *testing.T) { month int day int ot time.Time + err bool }{ - {01, 1, 0, time.Date(2000, 1, 01, 0, 0, 0, 0, time.UTC)}, - {02, 1, 12, time.Date(2000, 1, 01, 0, 0, 0, 0, time.UTC)}, - {03, 1, 12, time.Date(2000, 1, 01, 0, 0, 0, 0, time.UTC)}, - {04, 2, 24, time.Date(2000, 2, 10, 0, 0, 0, 0, time.UTC)}, - {01, 04, 05, time.Date(2019, 04, 01, 1, 2, 3, 4, time.UTC)}, + {01, 1, 0, time.Date(2000, 1, 01, 0, 0, 0, 0, time.UTC), false}, + {02, 1, 12, time.Date(2000, 1, 01, 0, 0, 0, 0, time.UTC), false}, + {03, 1, 12, time.Date(2000, 1, 01, 0, 0, 0, 0, time.UTC), false}, + {04, 2, 24, time.Date(2000, 2, 10, 0, 0, 0, 0, time.UTC), false}, + {01, 04, 05, time.Date(2019, 04, 01, 1, 2, 3, 4, time.UTC), false}, + {7999, 1, 1, time.Date(2000, 1, 01, 0, 0, 0, 0, time.UTC), false}, + {-2000, 1, 1, time.Date(2000, 1, 01, 0, 0, 0, 0, time.UTC), false}, + {8000, 1, 1, time.Date(2000, 1, 01, 0, 0, 0, 0, time.UTC), true}, + {10001 * 365, 1, 1, time.Date(2000, 1, 01, 0, 0, 0, 0, time.UTC), true}, + {01, 10001 * 36, 1, time.Date(2000, 1, 01, 0, 0, 0, 0, time.UTC), true}, + {01, 1, 10001 * 365, time.Date(2000, 1, 01, 0, 0, 0, 0, time.UTC), true}, + {-2001, 1, 1, time.Date(2000, 1, 01, 0, 0, 0, 0, time.UTC), true}, + {-10001 * 365, 1, 1, time.Date(2000, 1, 01, 0, 0, 0, 0, time.UTC), true}, + {01, -10001 * 36, 1, time.Date(2000, 1, 01, 0, 0, 0, 0, time.UTC), true}, + {01, 1, -10001 * 365, time.Date(2000, 1, 01, 0, 0, 0, 0, time.UTC), true}, } for _, tt := range tests { - res := AddDate(int64(tt.year), int64(tt.month), int64(tt.day), tt.ot) + res, err := AddDate(int64(tt.year), int64(tt.month), int64(tt.day), tt.ot) + if tt.err { + require.EqualError(t, err, ErrDatetimeFunctionOverflow.GenWithStackByArgs("datetime").Error()) + require.True(t, ErrDatetimeFunctionOverflow.Equal(err)) + continue + } + require.NoError(t, err) require.Equal(t, tt.year+tt.ot.Year(), res.Year()) } } diff --git a/pkg/types/time.go b/pkg/types/time.go index c4acba3b21e65..e3a8b3b3ebf0e 100644 --- a/pkg/types/time.go +++ b/pkg/types/time.go @@ -2301,9 +2301,12 @@ func parseSingleTimeValue(unit string, format string, strictCheck bool) (year in if len(format) > 0 && format[0] == '-' { sign = int64(-1) } + + // We should also continue even if an error occurs here + // because the called may ignore the error and use the return value. iv, err := strconv.ParseInt(format[0:decimalPointPos], 10, 64) if err != nil { - return 0, 0, 0, 0, 0, ErrWrongValue.GenWithStackByArgs(DateTimeStr, format) + err = ErrWrongValue.GenWithStackByArgs(DateTimeStr, format) } riv := iv // Rounded integer value @@ -2312,22 +2315,23 @@ 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 == 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 == 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]. riv += sign } - if unit != "SECOND" { + if unit != "SECOND" && err == nil { err = ErrTruncatedWrongVal.GenWithStackByArgs(format) } dv *= sign @@ -2421,39 +2425,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 tmpErr != nil && err == 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 tmpErr != nil && err == 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 tmpErr != nil && err == 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 tmpErr != nil && err == 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) { diff --git a/tests/integrationtest/r/expression/issues.result b/tests/integrationtest/r/expression/issues.result index adf6d00292199..83f268c6175db 100644 --- a/tests/integrationtest/r/expression/issues.result +++ b/tests/integrationtest/r/expression/issues.result @@ -482,19 +482,19 @@ SELECT 10000101000000 + INTERVAL "111111111111." SECOND; 4520-12-21 05:31:51 SELECT 10000101000000 + INTERVAL "111111111111111111.5" MICROSECOND; 10000101000000 + INTERVAL "111111111111111111.5" MICROSECOND -4520-12-21 05:31:51.111112 +4520-12-21 05:31:51.111111 SELECT 10000101000000 + INTERVAL "111111111111111112.5" MICROSECOND; 10000101000000 + INTERVAL "111111111111111112.5" MICROSECOND -4520-12-21 05:31:51.111113 +4520-12-21 05:31:51.111112 SELECT 10000101000000 + INTERVAL "111111111111111111.500000" MICROSECOND; 10000101000000 + INTERVAL "111111111111111111.500000" MICROSECOND -4520-12-21 05:31:51.111112 +4520-12-21 05:31:51.111111 SELECT 10000101000000 + INTERVAL "111111111111111111.50000000" MICROSECOND; 10000101000000 + INTERVAL "111111111111111111.50000000" MICROSECOND -4520-12-21 05:31:51.111112 +4520-12-21 05:31:51.111111 SELECT 10000101000000 + INTERVAL "111111111111111111.6" MICROSECOND; 10000101000000 + INTERVAL "111111111111111111.6" MICROSECOND -4520-12-21 05:31:51.111112 +4520-12-21 05:31:51.111111 SELECT 10000101000000 + INTERVAL "111111111111111111.499999" MICROSECOND; 10000101000000 + INTERVAL "111111111111111111.499999" MICROSECOND 4520-12-21 05:31:51.111111 diff --git a/tests/integrationtest/r/expression/time.result b/tests/integrationtest/r/expression/time.result index 605a611240314..0888b7c8b701f 100644 --- a/tests/integrationtest/r/expression/time.result +++ b/tests/integrationtest/r/expression/time.result @@ -132,6 +132,284 @@ date("1900-01-01") - interval 1.123456789e3 second select 19000101000000 - interval 1.123456789e3 second; 19000101000000 - interval 1.123456789e3 second 1899-12-31 23:41:16.543211 +SELECT "1900-01-01 00:00:00" + INTERVAL "2" HOUR; +"1900-01-01 00:00:00" + INTERVAL "2" HOUR +1900-01-01 02:00:00 +SELECT "1900-01-01 00:00:00" + INTERVAL "-2" HOUR; +"1900-01-01 00:00:00" + INTERVAL "-2" HOUR +1899-12-31 22:00:00 +SELECT "1900-01-01 00:00:00" + INTERVAL "128" HOUR; +"1900-01-01 00:00:00" + INTERVAL "128" HOUR +1900-01-06 08:00:00 +SELECT "1900-01-01 00:00:00" + INTERVAL "1e+3" HOUR; +"1900-01-01 00:00:00" + INTERVAL "1e+3" HOUR +1900-01-01 01:00:00 +SELECT "1900-01-01 00:00:00" + INTERVAL "1+1" HOUR; +"1900-01-01 00:00:00" + INTERVAL "1+1" HOUR +1900-01-01 01:00:00 +drop table if exists t; +create table t (id int primary key auto_increment, a varchar(32)); +insert into t (a) values(''), ('+1'), ('+1+2'), ('-1'), ('2.2'), ('2.9'), ('2.2+1'), ('2+2.2'), ('5-2'), ('1e2'), +('true'), ('false'), ('xxx'), ('xxx+1'), ('xxx1'), (' 1 '), ('xxx-1'), +('9223372036854775808'), ('-9223372036854775809'), ('9223372036854775808-02'), ('-9223372036854775809-02'); +select id, a, "1900-01-01 00:00:00" + INTERVAL a MICROSECOND as result from t order by id ASC; +id a result +1 1900-01-01 00:00:00 +2 +1 1900-01-01 00:00:00.000001 +3 +1+2 1900-01-01 00:00:00.000001 +4 -1 1899-12-31 23:59:59.999999 +5 2.2 1900-01-01 00:00:00.000002 +6 2.9 1900-01-01 00:00:00.000002 +7 2.2+1 1900-01-01 00:00:00.000002 +8 2+2.2 1900-01-01 00:00:00.000002 +9 5-2 1900-01-01 00:00:00.000005 +10 1e2 1900-01-01 00:00:00.000001 +11 true 1900-01-01 00:00:00 +12 false 1900-01-01 00:00:00 +13 xxx 1900-01-01 00:00:00 +14 xxx+1 1900-01-01 00:00:00 +15 xxx1 1900-01-01 00:00:00 +16 1 1900-01-01 00:00:00.000001 +17 xxx-1 1900-01-01 00:00:00 +18 9223372036854775808 NULL +19 -9223372036854775809 NULL +20 9223372036854775808-02 NULL +21 -9223372036854775809-02 NULL +select id, a, "1900-01-01 00:00:00" + INTERVAL a SECOND as result from t order by id ASC; +id a result +1 1900-01-01 00:00:00 +2 +1 1900-01-01 00:00:01 +3 +1+2 1900-01-01 00:00:01 +4 -1 1899-12-31 23:59:59 +5 2.2 1900-01-01 00:00:02.200000 +6 2.9 1900-01-01 00:00:02.900000 +7 2.2+1 1900-01-01 00:00:02.200000 +8 2+2.2 1900-01-01 00:00:02 +9 5-2 1900-01-01 00:00:05 +10 1e2 1900-01-01 00:01:40 +11 true 1900-01-01 00:00:00 +12 false 1900-01-01 00:00:00 +13 xxx 1900-01-01 00:00:00 +14 xxx+1 1900-01-01 00:00:00 +15 xxx1 1900-01-01 00:00:00 +16 1 1900-01-01 00:00:01 +17 xxx-1 1900-01-01 00:00:00 +18 9223372036854775808 NULL +19 -9223372036854775809 NULL +20 9223372036854775808-02 NULL +21 -9223372036854775809-02 NULL +select id, a, "1900-01-01 00:00:00" + INTERVAL a MINUTE as result from t order by id ASC; +id a result +1 1900-01-01 00:00:00 +2 +1 1900-01-01 00:01:00 +3 +1+2 1900-01-01 00:01:00 +4 -1 1899-12-31 23:59:00 +5 2.2 1900-01-01 00:02:00 +6 2.9 1900-01-01 00:02:00 +7 2.2+1 1900-01-01 00:02:00 +8 2+2.2 1900-01-01 00:02:00 +9 5-2 1900-01-01 00:05:00 +10 1e2 1900-01-01 00:01:00 +11 true 1900-01-01 00:00:00 +12 false 1900-01-01 00:00:00 +13 xxx 1900-01-01 00:00:00 +14 xxx+1 1900-01-01 00:00:00 +15 xxx1 1900-01-01 00:00:00 +16 1 1900-01-01 00:01:00 +17 xxx-1 1900-01-01 00:00:00 +18 9223372036854775808 NULL +19 -9223372036854775809 NULL +20 9223372036854775808-02 NULL +21 -9223372036854775809-02 NULL +select id, a, "1900-01-01 00:00:00" + INTERVAL a HOUR as result from t order by id ASC; +id a result +1 1900-01-01 00:00:00 +2 +1 1900-01-01 01:00:00 +3 +1+2 1900-01-01 01:00:00 +4 -1 1899-12-31 23:00:00 +5 2.2 1900-01-01 02:00:00 +6 2.9 1900-01-01 02:00:00 +7 2.2+1 1900-01-01 02:00:00 +8 2+2.2 1900-01-01 02:00:00 +9 5-2 1900-01-01 05:00:00 +10 1e2 1900-01-01 01:00:00 +11 true 1900-01-01 00:00:00 +12 false 1900-01-01 00:00:00 +13 xxx 1900-01-01 00:00:00 +14 xxx+1 1900-01-01 00:00:00 +15 xxx1 1900-01-01 00:00:00 +16 1 1900-01-01 01:00:00 +17 xxx-1 1900-01-01 00:00:00 +18 9223372036854775808 NULL +19 -9223372036854775809 NULL +20 9223372036854775808-02 NULL +21 -9223372036854775809-02 NULL +select id, a, "1900-01-01 00:00:00" + INTERVAL a DAY as result from t order by id ASC; +id a result +1 1900-01-01 00:00:00 +2 +1 1900-01-02 00:00:00 +3 +1+2 1900-01-02 00:00:00 +4 -1 1899-12-31 00:00:00 +5 2.2 1900-01-03 00:00:00 +6 2.9 1900-01-03 00:00:00 +7 2.2+1 1900-01-03 00:00:00 +8 2+2.2 1900-01-03 00:00:00 +9 5-2 1900-01-06 00:00:00 +10 1e2 1900-01-02 00:00:00 +11 true 1900-01-01 00:00:00 +12 false 1900-01-01 00:00:00 +13 xxx 1900-01-01 00:00:00 +14 xxx+1 1900-01-01 00:00:00 +15 xxx1 1900-01-01 00:00:00 +16 1 1900-01-02 00:00:00 +17 xxx-1 1900-01-01 00:00:00 +18 9223372036854775808 NULL +19 -9223372036854775809 NULL +20 9223372036854775808-02 NULL +21 -9223372036854775809-02 NULL +select id, a, "1900-01-01 00:00:00" + INTERVAL a WEEK as result from t order by id ASC; +id a result +1 1900-01-01 00:00:00 +2 +1 1900-01-08 00:00:00 +3 +1+2 1900-01-08 00:00:00 +4 -1 1899-12-25 00:00:00 +5 2.2 1900-01-15 00:00:00 +6 2.9 1900-01-15 00:00:00 +7 2.2+1 1900-01-15 00:00:00 +8 2+2.2 1900-01-15 00:00:00 +9 5-2 1900-02-05 00:00:00 +10 1e2 1900-01-08 00:00:00 +11 true 1900-01-01 00:00:00 +12 false 1900-01-01 00:00:00 +13 xxx 1900-01-01 00:00:00 +14 xxx+1 1900-01-01 00:00:00 +15 xxx1 1900-01-01 00:00:00 +16 1 1900-01-08 00:00:00 +17 xxx-1 1900-01-01 00:00:00 +18 9223372036854775808 NULL +19 -9223372036854775809 NULL +20 9223372036854775808-02 NULL +21 -9223372036854775809-02 NULL +select id, a, "1900-01-01 00:00:00" + INTERVAL a MONTH as result from t order by id ASC; +id a result +1 1900-01-01 00:00:00 +2 +1 1900-02-01 00:00:00 +3 +1+2 1900-02-01 00:00:00 +4 -1 1899-12-01 00:00:00 +5 2.2 1900-03-01 00:00:00 +6 2.9 1900-03-01 00:00:00 +7 2.2+1 1900-03-01 00:00:00 +8 2+2.2 1900-03-01 00:00:00 +9 5-2 1900-06-01 00:00:00 +10 1e2 1900-02-01 00:00:00 +11 true 1900-01-01 00:00:00 +12 false 1900-01-01 00:00:00 +13 xxx 1900-01-01 00:00:00 +14 xxx+1 1900-01-01 00:00:00 +15 xxx1 1900-01-01 00:00:00 +16 1 1900-02-01 00:00:00 +17 xxx-1 1900-01-01 00:00:00 +18 9223372036854775808 NULL +19 -9223372036854775809 NULL +20 9223372036854775808-02 NULL +21 -9223372036854775809-02 NULL +select id, a, "1900-01-01 00:00:00" + INTERVAL a QUARTER as result from t order by id ASC; +id a result +1 1900-01-01 00:00:00 +2 +1 1900-04-01 00:00:00 +3 +1+2 1900-04-01 00:00:00 +4 -1 1899-10-01 00:00:00 +5 2.2 1900-07-01 00:00:00 +6 2.9 1900-07-01 00:00:00 +7 2.2+1 1900-07-01 00:00:00 +8 2+2.2 1900-07-01 00:00:00 +9 5-2 1901-04-01 00:00:00 +10 1e2 1900-04-01 00:00:00 +11 true 1900-01-01 00:00:00 +12 false 1900-01-01 00:00:00 +13 xxx 1900-01-01 00:00:00 +14 xxx+1 1900-01-01 00:00:00 +15 xxx1 1900-01-01 00:00:00 +16 1 1900-04-01 00:00:00 +17 xxx-1 1900-01-01 00:00:00 +18 9223372036854775808 NULL +19 -9223372036854775809 NULL +20 9223372036854775808-02 NULL +21 -9223372036854775809-02 NULL +select id, a, "1900-01-01 00:00:00" + INTERVAL a YEAR as result from t order by id ASC; +id a result +1 1900-01-01 00:00:00 +2 +1 1901-01-01 00:00:00 +3 +1+2 1901-01-01 00:00:00 +4 -1 1899-01-01 00:00:00 +5 2.2 1902-01-01 00:00:00 +6 2.9 1902-01-01 00:00:00 +7 2.2+1 1902-01-01 00:00:00 +8 2+2.2 1902-01-01 00:00:00 +9 5-2 1905-01-01 00:00:00 +10 1e2 1901-01-01 00:00:00 +11 true 1900-01-01 00:00:00 +12 false 1900-01-01 00:00:00 +13 xxx 1900-01-01 00:00:00 +14 xxx+1 1900-01-01 00:00:00 +15 xxx1 1900-01-01 00:00:00 +16 1 1901-01-01 00:00:00 +17 xxx-1 1900-01-01 00:00:00 +18 9223372036854775808 NULL +19 -9223372036854775809 NULL +20 9223372036854775808-02 NULL +21 -9223372036854775809-02 NULL +select "1900-01-01 00:00:00" + INTERVAL true MICROSECOND; +"1900-01-01 00:00:00" + INTERVAL true MICROSECOND +1900-01-01 00:00:00.000001 +select "1900-01-01 00:00:00" + INTERVAL "1.2" MICROSECOND; +"1900-01-01 00:00:00" + INTERVAL "1.2" MICROSECOND +1900-01-01 00:00:00.000001 +select "1900-01-01 00:00:00" + INTERVAL "1.9" MINUTE; +"1900-01-01 00:00:00" + INTERVAL "1.9" MINUTE +1900-01-01 00:01:00 +select "1900-01-01 00:00:00" + INTERVAL 1.2 MICROSECOND; +"1900-01-01 00:00:00" + INTERVAL 1.2 MICROSECOND +1900-01-01 00:00:00.000001 +select "1900-01-01 00:00:00" + INTERVAL 1.9 MICROSECOND; +"1900-01-01 00:00:00" + INTERVAL 1.9 MICROSECOND +1900-01-01 00:00:00.000002 +select "1900-01-01 00:00:00" + INTERVAL true SECOND; +"1900-01-01 00:00:00" + INTERVAL true SECOND +1900-01-01 00:00:01 +select "1900-01-01 00:00:00" + INTERVAL 1.2 SECOND; +"1900-01-01 00:00:00" + INTERVAL 1.2 SECOND +1900-01-01 00:00:01.200000 +select "1900-01-01 00:00:00" + INTERVAL "1+2" SECOND; +"1900-01-01 00:00:00" + INTERVAL "1+2" SECOND +1900-01-01 00:00:01 +select "1900-01-01 00:00:00" + INTERVAL "1.2+2" SECOND; +"1900-01-01 00:00:00" + INTERVAL "1.2+2" SECOND +1900-01-01 00:00:01.200000 +select "1900-01-01 00:00:00" + INTERVAL "1+2.2" SECOND; +"1900-01-01 00:00:00" + INTERVAL "1+2.2" SECOND +1900-01-01 00:00:01 +select "1900-01-01 00:00:00" + INTERVAL "0.000001" SECOND; +"1900-01-01 00:00:00" + INTERVAL "0.000001" SECOND +1900-01-01 00:00:00.000001 +select "1900-01-01 00:00:00" + INTERVAL "0.0000009" SECOND; +"1900-01-01 00:00:00" + INTERVAL "0.0000009" SECOND +1900-01-01 00:00:00 +select "1900-01-01 00:00:00" + INTERVAL true MINUTE; +"1900-01-01 00:00:00" + INTERVAL true MINUTE +1900-01-01 00:01:00 +select "1900-01-01 00:00:00" + INTERVAL "1.2" MINUTE; +"1900-01-01 00:00:00" + INTERVAL "1.2" MINUTE +1900-01-01 00:01:00 +select "1900-01-01 00:00:00" + INTERVAL "1.9" MINUTE; +"1900-01-01 00:00:00" + INTERVAL "1.9" MINUTE +1900-01-01 00:01:00 +select "1900-01-01 00:00:00" + INTERVAL 1.2 MINUTE; +"1900-01-01 00:00:00" + INTERVAL 1.2 MINUTE +1900-01-01 00:01:00 +select "1900-01-01 00:00:00" + INTERVAL 1.9 MINUTE; +"1900-01-01 00:00:00" + INTERVAL 1.9 MINUTE +1900-01-01 00:02:00 drop table if exists t; create table t(a datetime(6), b timestamp); insert t values (20010101100000.123456, 20110707101112.123456); @@ -528,3 +806,261 @@ NULL NULL NULL set sql_mode=default; +select "1000-01-01 00:00:00" + INTERVAL 9223372036854775808 day; +"1000-01-01 00:00:00" + INTERVAL 9223372036854775808 day +NULL +select "1000-01-01 00:00:00" + INTERVAL 18446744073709551616 day; +"1000-01-01 00:00:00" + INTERVAL 18446744073709551616 day +NULL +drop table if exists t1; +create table t1(a decimal(65, 2)); +insert into t1 (a) values (1), (1.4), (1.5), (1.6), (-1), (-1.4), (-1.5), (-1.6), (-1000.5); +insert into t1 (a) values (315600000000000000), (9223372036854775808), (18446744073709551615), (18446744073709551616), (-9223372036854775808), (-9223372036854775809); +set @@tidb_enable_vectorized_expression=0; +select a, "1000-01-01 00:00:00" + INTERVAL a YEAR from t1 order by a ASC; +a "1000-01-01 00:00:00" + INTERVAL a YEAR +-9223372036854775809.00 NULL +-9223372036854775808.00 NULL +-1000.50 NULL +-1.60 0998-01-01 00:00:00 +-1.50 0998-01-01 00:00:00 +-1.40 0999-01-01 00:00:00 +-1.00 0999-01-01 00:00:00 +1.00 1001-01-01 00:00:00 +1.40 1001-01-01 00:00:00 +1.50 1002-01-01 00:00:00 +1.60 1002-01-01 00:00:00 +315600000000000000.00 NULL +9223372036854775808.00 NULL +18446744073709551615.00 NULL +18446744073709551616.00 NULL +select a, "1000-01-01 00:00:00" + INTERVAL a MINUTE from t1 order by a ASC; +a "1000-01-01 00:00:00" + INTERVAL a MINUTE +-9223372036854775809.00 NULL +-9223372036854775808.00 NULL +-1000.50 0999-12-31 07:19:00 +-1.60 0999-12-31 23:58:00 +-1.50 0999-12-31 23:58:00 +-1.40 0999-12-31 23:59:00 +-1.00 0999-12-31 23:59:00 +1.00 1000-01-01 00:01:00 +1.40 1000-01-01 00:01:00 +1.50 1000-01-01 00:02:00 +1.60 1000-01-01 00:02:00 +315600000000000000.00 NULL +9223372036854775808.00 NULL +18446744073709551615.00 NULL +18446744073709551616.00 NULL +select a, "1000-01-01 00:00:00" + INTERVAL a MICROSECOND from t1 order by a ASC; +a "1000-01-01 00:00:00" + INTERVAL a MICROSECOND +-9223372036854775809.00 NULL +-9223372036854775808.00 NULL +-1000.50 0999-12-31 23:59:59.998999 +-1.60 0999-12-31 23:59:59.999998 +-1.50 0999-12-31 23:59:59.999998 +-1.40 0999-12-31 23:59:59.999999 +-1.00 0999-12-31 23:59:59.999999 +1.00 1000-01-01 00:00:00.000001 +1.40 1000-01-01 00:00:00.000001 +1.50 1000-01-01 00:00:00.000002 +1.60 1000-01-01 00:00:00.000002 +315600000000000000.00 NULL +9223372036854775808.00 NULL +18446744073709551615.00 NULL +18446744073709551616.00 NULL +select a, "1000-01-01 00:00:00" + INTERVAL cast(a as char) DAY from t1 order by a ASC; +a "1000-01-01 00:00:00" + INTERVAL cast(a as char) DAY +-9223372036854775809.00 NULL +-9223372036854775808.00 NULL +-1000.50 0997-04-06 00:00:00 +-1.60 0999-12-31 00:00:00 +-1.50 0999-12-31 00:00:00 +-1.40 0999-12-31 00:00:00 +-1.00 0999-12-31 00:00:00 +1.00 1000-01-02 00:00:00 +1.40 1000-01-02 00:00:00 +1.50 1000-01-02 00:00:00 +1.60 1000-01-02 00:00:00 +315600000000000000.00 NULL +9223372036854775808.00 NULL +18446744073709551615.00 NULL +18446744073709551616.00 NULL +select a, "1000-01-01 00:00:00" + INTERVAL cast(a as signed) DAY from t1 order by a ASC; +a "1000-01-01 00:00:00" + INTERVAL cast(a as signed) DAY +-9223372036854775809.00 NULL +-9223372036854775808.00 NULL +-1000.50 0997-04-05 00:00:00 +-1.60 0999-12-30 00:00:00 +-1.50 0999-12-30 00:00:00 +-1.40 0999-12-31 00:00:00 +-1.00 0999-12-31 00:00:00 +1.00 1000-01-02 00:00:00 +1.40 1000-01-02 00:00:00 +1.50 1000-01-03 00:00:00 +1.60 1000-01-03 00:00:00 +315600000000000000.00 NULL +9223372036854775808.00 NULL +18446744073709551615.00 NULL +18446744073709551616.00 NULL +select a, "1000-01-01 00:00:00" + INTERVAL cast(a as unsigned) DAY from t1 order by a ASC; +a "1000-01-01 00:00:00" + INTERVAL cast(a as unsigned) DAY +-9223372036854775809.00 1000-01-01 00:00:00 +-9223372036854775808.00 1000-01-01 00:00:00 +-1000.50 1000-01-01 00:00:00 +-1.60 1000-01-01 00:00:00 +-1.50 1000-01-01 00:00:00 +-1.40 1000-01-01 00:00:00 +-1.00 1000-01-01 00:00:00 +1.00 1000-01-02 00:00:00 +1.40 1000-01-02 00:00:00 +1.50 1000-01-03 00:00:00 +1.60 1000-01-03 00:00:00 +315600000000000000.00 NULL +9223372036854775808.00 NULL +18446744073709551615.00 NULL +18446744073709551616.00 NULL +set @@tidb_enable_vectorized_expression=1; +select a, "1000-01-01 00:00:00" + INTERVAL a YEAR from t1 order by a ASC; +a "1000-01-01 00:00:00" + INTERVAL a YEAR +-9223372036854775809.00 NULL +-9223372036854775808.00 NULL +-1000.50 NULL +-1.60 0998-01-01 00:00:00 +-1.50 0998-01-01 00:00:00 +-1.40 0999-01-01 00:00:00 +-1.00 0999-01-01 00:00:00 +1.00 1001-01-01 00:00:00 +1.40 1001-01-01 00:00:00 +1.50 1002-01-01 00:00:00 +1.60 1002-01-01 00:00:00 +315600000000000000.00 NULL +9223372036854775808.00 NULL +18446744073709551615.00 NULL +18446744073709551616.00 NULL +select a, "1000-01-01 00:00:00" + INTERVAL a MINUTE from t1 order by a ASC; +a "1000-01-01 00:00:00" + INTERVAL a MINUTE +-9223372036854775809.00 NULL +-9223372036854775808.00 NULL +-1000.50 0999-12-31 07:19:00 +-1.60 0999-12-31 23:58:00 +-1.50 0999-12-31 23:58:00 +-1.40 0999-12-31 23:59:00 +-1.00 0999-12-31 23:59:00 +1.00 1000-01-01 00:01:00 +1.40 1000-01-01 00:01:00 +1.50 1000-01-01 00:02:00 +1.60 1000-01-01 00:02:00 +315600000000000000.00 NULL +9223372036854775808.00 NULL +18446744073709551615.00 NULL +18446744073709551616.00 NULL +select a, "1000-01-01 00:00:00" + INTERVAL a MICROSECOND from t1 order by a ASC; +a "1000-01-01 00:00:00" + INTERVAL a MICROSECOND +-9223372036854775809.00 NULL +-9223372036854775808.00 NULL +-1000.50 0999-12-31 23:59:59.998999 +-1.60 0999-12-31 23:59:59.999998 +-1.50 0999-12-31 23:59:59.999998 +-1.40 0999-12-31 23:59:59.999999 +-1.00 0999-12-31 23:59:59.999999 +1.00 1000-01-01 00:00:00.000001 +1.40 1000-01-01 00:00:00.000001 +1.50 1000-01-01 00:00:00.000002 +1.60 1000-01-01 00:00:00.000002 +315600000000000000.00 NULL +9223372036854775808.00 NULL +18446744073709551615.00 NULL +18446744073709551616.00 NULL +select a, "1000-01-01 00:00:00" + INTERVAL cast(a as char) DAY from t1 order by a ASC; +a "1000-01-01 00:00:00" + INTERVAL cast(a as char) DAY +-9223372036854775809.00 NULL +-9223372036854775808.00 NULL +-1000.50 0997-04-06 00:00:00 +-1.60 0999-12-31 00:00:00 +-1.50 0999-12-31 00:00:00 +-1.40 0999-12-31 00:00:00 +-1.00 0999-12-31 00:00:00 +1.00 1000-01-02 00:00:00 +1.40 1000-01-02 00:00:00 +1.50 1000-01-02 00:00:00 +1.60 1000-01-02 00:00:00 +315600000000000000.00 NULL +9223372036854775808.00 NULL +18446744073709551615.00 NULL +18446744073709551616.00 NULL +select a, "1000-01-01 00:00:00" + INTERVAL cast(a as signed) DAY from t1 order by a ASC; +a "1000-01-01 00:00:00" + INTERVAL cast(a as signed) DAY +-9223372036854775809.00 NULL +-9223372036854775808.00 NULL +-1000.50 0997-04-05 00:00:00 +-1.60 0999-12-30 00:00:00 +-1.50 0999-12-30 00:00:00 +-1.40 0999-12-31 00:00:00 +-1.00 0999-12-31 00:00:00 +1.00 1000-01-02 00:00:00 +1.40 1000-01-02 00:00:00 +1.50 1000-01-03 00:00:00 +1.60 1000-01-03 00:00:00 +315600000000000000.00 NULL +9223372036854775808.00 NULL +18446744073709551615.00 NULL +18446744073709551616.00 NULL +select a, "1000-01-01 00:00:00" + INTERVAL cast(a as unsigned) DAY from t1 order by a ASC; +a "1000-01-01 00:00:00" + INTERVAL cast(a as unsigned) DAY +-9223372036854775809.00 1000-01-01 00:00:00 +-9223372036854775808.00 1000-01-01 00:00:00 +-1000.50 1000-01-01 00:00:00 +-1.60 1000-01-01 00:00:00 +-1.50 1000-01-01 00:00:00 +-1.40 1000-01-01 00:00:00 +-1.00 1000-01-01 00:00:00 +1.00 1000-01-02 00:00:00 +1.40 1000-01-02 00:00:00 +1.50 1000-01-03 00:00:00 +1.60 1000-01-03 00:00:00 +315600000000000000.00 NULL +9223372036854775808.00 NULL +18446744073709551615.00 NULL +18446744073709551616.00 NULL +create table t2(a decimal(65, 2), d datetime); +set @old_sql_mode=@@sql_mode; +set @@sql_mode=''; +insert into t2 values('1', "1000-01-01 00:00:00" + INTERVAL "+1" YEAR); +insert into t2 values('-1', "1000-01-01 00:00:00" + INTERVAL "-1" YEAR); +insert into t2 values('0', "1000-01-01 00:00:00" + INTERVAL "XXX" YEAR); +insert into t2 values('99999', "1000-01-01 00:00:00" + INTERVAL 99999 YEAR); +insert into t2 values('116777216', "1000-01-01 00:00:00" + INTERVAL 116777216 YEAR); +insert into t2 values('9223372036854775809', "1000-01-01 00:00:00" + INTERVAL 9223372036854775808 YEAR); +insert into t2 values('18446744073709551616', "1000-01-01 00:00:00" + INTERVAL 18446744073709551616 YEAR); +insert into t2 values('-9223372036854775809', "1000-01-01 00:00:00" + INTERVAL -9223372036854775809 YEAR); +select a, d from t2 order by a ASC; +a d +-9223372036854775809.00 NULL +-1.00 0999-01-01 00:00:00 +0.00 1000-01-01 00:00:00 +1.00 1001-01-01 00:00:00 +99999.00 NULL +116777216.00 NULL +9223372036854775809.00 NULL +18446744073709551616.00 NULL +truncate table t2; +set @@sql_mode=@old_sql_mode; +insert into t2 values('1', "1000-01-01 00:00:00" + INTERVAL "+1" YEAR); +insert into t2 values('-1', "1000-01-01 00:00:00" + INTERVAL "-1" YEAR); +insert into t2 values('0', "1000-01-01 00:00:00" + INTERVAL "XXX" YEAR); +Error 1292 (22007): Truncated incorrect DECIMAL value: 'XXX' +insert into t2 values('99999', "1000-01-01 00:00:00" + INTERVAL 99999 YEAR); +Error 1441 (22008): Datetime function: datetime field overflow +insert into t2 values('116777216', "1000-01-01 00:00:00" + INTERVAL 116777216 YEAR); +Error 1441 (22008): Datetime function: datetime field overflow +insert into t2 values('9223372036854775809', "1000-01-01 00:00:00" + INTERVAL 9223372036854775808 YEAR); +Error 1292 (22007): Incorrect datetime value: '9223372036854775808' +insert into t2 values('18446744073709551616', "1000-01-01 00:00:00" + INTERVAL 18446744073709551616 YEAR); +Error 1292 (22007): Truncated incorrect DECIMAL value: '18446744073709551616' +insert into t2 values('-9223372036854775809', "1000-01-01 00:00:00" + INTERVAL -9223372036854775809 YEAR); +Error 1292 (22007): Truncated incorrect DECIMAL value: '-9223372036854775809' +select a, d from t2 order by a ASC; +a d +-1.00 0999-01-01 00:00:00 +1.00 1001-01-01 00:00:00 +drop table if exists t1; diff --git a/tests/integrationtest/t/expression/time.test b/tests/integrationtest/t/expression/time.test index 1a45d2a925219..50efbebbba102 100644 --- a/tests/integrationtest/t/expression/time.test +++ b/tests/integrationtest/t/expression/time.test @@ -50,6 +50,46 @@ SELECT 19000101000000.0005 + INTERVAL 0.0005 SECOND; select date("1900-01-01") - interval 1.123456789e3 second; select 19000101000000 - interval 1.123456789e3 second; +# TestDateTimeAddString +SELECT "1900-01-01 00:00:00" + INTERVAL "2" HOUR; +SELECT "1900-01-01 00:00:00" + INTERVAL "-2" HOUR; +SELECT "1900-01-01 00:00:00" + INTERVAL "128" HOUR; +SELECT "1900-01-01 00:00:00" + INTERVAL "1e+3" HOUR; +SELECT "1900-01-01 00:00:00" + INTERVAL "1+1" HOUR; +drop table if exists t; +create table t (id int primary key auto_increment, a varchar(32)); +insert into t (a) values(''), ('+1'), ('+1+2'), ('-1'), ('2.2'), ('2.9'), ('2.2+1'), ('2+2.2'), ('5-2'), ('1e2'), +('true'), ('false'), ('xxx'), ('xxx+1'), ('xxx1'), (' 1 '), ('xxx-1'), +('9223372036854775808'), ('-9223372036854775809'), ('9223372036854775808-02'), ('-9223372036854775809-02'); +select id, a, "1900-01-01 00:00:00" + INTERVAL a MICROSECOND as result from t order by id ASC; +select id, a, "1900-01-01 00:00:00" + INTERVAL a SECOND as result from t order by id ASC; +select id, a, "1900-01-01 00:00:00" + INTERVAL a MINUTE as result from t order by id ASC; +select id, a, "1900-01-01 00:00:00" + INTERVAL a HOUR as result from t order by id ASC; +select id, a, "1900-01-01 00:00:00" + INTERVAL a DAY as result from t order by id ASC; +select id, a, "1900-01-01 00:00:00" + INTERVAL a WEEK as result from t order by id ASC; +select id, a, "1900-01-01 00:00:00" + INTERVAL a MONTH as result from t order by id ASC; +select id, a, "1900-01-01 00:00:00" + INTERVAL a QUARTER as result from t order by id ASC; +select id, a, "1900-01-01 00:00:00" + INTERVAL a YEAR as result from t order by id ASC; + +# TestAddIntervalSpecialCase +select "1900-01-01 00:00:00" + INTERVAL true MICROSECOND; +select "1900-01-01 00:00:00" + INTERVAL "1.2" MICROSECOND; +select "1900-01-01 00:00:00" + INTERVAL "1.9" MINUTE; +select "1900-01-01 00:00:00" + INTERVAL 1.2 MICROSECOND; +select "1900-01-01 00:00:00" + INTERVAL 1.9 MICROSECOND; +select "1900-01-01 00:00:00" + INTERVAL true SECOND; +select "1900-01-01 00:00:00" + INTERVAL 1.2 SECOND; +select "1900-01-01 00:00:00" + INTERVAL "1+2" SECOND; +select "1900-01-01 00:00:00" + INTERVAL "1.2+2" SECOND; +select "1900-01-01 00:00:00" + INTERVAL "1+2.2" SECOND; +select "1900-01-01 00:00:00" + INTERVAL "0.000001" SECOND; +select "1900-01-01 00:00:00" + INTERVAL "0.0000009" SECOND; +select "1900-01-01 00:00:00" + INTERVAL true MINUTE; +select "1900-01-01 00:00:00" + INTERVAL "1.2" MINUTE; +select "1900-01-01 00:00:00" + INTERVAL "1.9" MINUTE; +select "1900-01-01 00:00:00" + INTERVAL 1.2 MINUTE; +select "1900-01-01 00:00:00" + INTERVAL 1.9 MINUTE; + # TestDecimalConvertToTime # for issue #9770 drop table if exists t; @@ -229,3 +269,60 @@ insert into t1 (d) select date_add('9999-12-31',interval 1 year); insert into t1 (d) select date_add('9999-12-31',interval 1 day); select * from t1; set sql_mode=default; + +# Test date add interval overflow +select "1000-01-01 00:00:00" + INTERVAL 9223372036854775808 day; +select "1000-01-01 00:00:00" + INTERVAL 18446744073709551616 day; + +drop table if exists t1; +create table t1(a decimal(65, 2)); +insert into t1 (a) values (1), (1.4), (1.5), (1.6), (-1), (-1.4), (-1.5), (-1.6), (-1000.5); + +insert into t1 (a) values (315600000000000000), (9223372036854775808), (18446744073709551615), (18446744073709551616), (-9223372036854775808), (-9223372036854775809); +set @@tidb_enable_vectorized_expression=0; +select a, "1000-01-01 00:00:00" + INTERVAL a YEAR from t1 order by a ASC; +select a, "1000-01-01 00:00:00" + INTERVAL a MINUTE from t1 order by a ASC; +select a, "1000-01-01 00:00:00" + INTERVAL a MICROSECOND from t1 order by a ASC; +select a, "1000-01-01 00:00:00" + INTERVAL cast(a as char) DAY from t1 order by a ASC; +select a, "1000-01-01 00:00:00" + INTERVAL cast(a as signed) DAY from t1 order by a ASC; +select a, "1000-01-01 00:00:00" + INTERVAL cast(a as unsigned) DAY from t1 order by a ASC; + +set @@tidb_enable_vectorized_expression=1; +select a, "1000-01-01 00:00:00" + INTERVAL a YEAR from t1 order by a ASC; +select a, "1000-01-01 00:00:00" + INTERVAL a MINUTE from t1 order by a ASC; +select a, "1000-01-01 00:00:00" + INTERVAL a MICROSECOND from t1 order by a ASC; +select a, "1000-01-01 00:00:00" + INTERVAL cast(a as char) DAY from t1 order by a ASC; +select a, "1000-01-01 00:00:00" + INTERVAL cast(a as signed) DAY from t1 order by a ASC; +select a, "1000-01-01 00:00:00" + INTERVAL cast(a as unsigned) DAY from t1 order by a ASC; + +create table t2(a decimal(65, 2), d datetime); +set @old_sql_mode=@@sql_mode; +set @@sql_mode=''; +insert into t2 values('1', "1000-01-01 00:00:00" + INTERVAL "+1" YEAR); +insert into t2 values('-1', "1000-01-01 00:00:00" + INTERVAL "-1" YEAR); +insert into t2 values('0', "1000-01-01 00:00:00" + INTERVAL "XXX" YEAR); +insert into t2 values('99999', "1000-01-01 00:00:00" + INTERVAL 99999 YEAR); +insert into t2 values('116777216', "1000-01-01 00:00:00" + INTERVAL 116777216 YEAR); +insert into t2 values('9223372036854775809', "1000-01-01 00:00:00" + INTERVAL 9223372036854775808 YEAR); +insert into t2 values('18446744073709551616', "1000-01-01 00:00:00" + INTERVAL 18446744073709551616 YEAR); +insert into t2 values('-9223372036854775809', "1000-01-01 00:00:00" + INTERVAL -9223372036854775809 YEAR); +select a, d from t2 order by a ASC; + +truncate table t2; +set @@sql_mode=@old_sql_mode; +insert into t2 values('1', "1000-01-01 00:00:00" + INTERVAL "+1" YEAR); +insert into t2 values('-1', "1000-01-01 00:00:00" + INTERVAL "-1" YEAR); +--error 1292 +insert into t2 values('0', "1000-01-01 00:00:00" + INTERVAL "XXX" YEAR); +--error 1441 +insert into t2 values('99999', "1000-01-01 00:00:00" + INTERVAL 99999 YEAR); +--error 1441 +insert into t2 values('116777216', "1000-01-01 00:00:00" + INTERVAL 116777216 YEAR); +--error 1292 +insert into t2 values('9223372036854775809', "1000-01-01 00:00:00" + INTERVAL 9223372036854775808 YEAR); +--error 1292 +insert into t2 values('18446744073709551616', "1000-01-01 00:00:00" + INTERVAL 18446744073709551616 YEAR); +--error 1292 +insert into t2 values('-9223372036854775809', "1000-01-01 00:00:00" + INTERVAL -9223372036854775809 YEAR); +select a, d from t2 order by a ASC; +drop table if exists t1;