Skip to content

Commit 6cb7bd1

Browse files
erjiaqingzz-jason
authored andcommitted
expression: addition between datetime and interval is not compatible with Mysql (#10329) (#10416)
1 parent d22ecd0 commit 6cb7bd1

File tree

4 files changed

+191
-92
lines changed

4 files changed

+191
-92
lines changed

expression/builtin_time.go

+18-19
Original file line numberDiff line numberDiff line change
@@ -2605,14 +2605,14 @@ func (du *baseDateArithmitical) getIntervalFromDecimal(ctx sessionctx.Context, a
26052605
interval = "00:" + interval
26062606
case "SECOND_MICROSECOND":
26072607
/* keep interval as original decimal */
2608-
case "SECOND", "MICROSECOND":
2609-
args[1] = WrapWithCastAsReal(ctx, args[1])
2608+
case "SECOND":
2609+
// Decimal's EvalString is like %f format.
26102610
interval, isNull, err = args[1].EvalString(ctx, row)
26112611
if isNull || err != nil {
26122612
return "", true, errors.Trace(err)
26132613
}
26142614
default:
2615-
// YEAR, QUARTER, MONTH, WEEK, DAY, HOUR, MINUTE
2615+
// YEAR, QUARTER, MONTH, WEEK, DAY, HOUR, MINUTE, MICROSECOND
26162616
args[1] = WrapWithCastAsInt(ctx, args[1])
26172617
interval, isNull, err = args[1].EvalString(ctx, row)
26182618
if isNull || err != nil {
@@ -2632,18 +2632,17 @@ func (du *baseDateArithmitical) getIntervalFromInt(ctx sessionctx.Context, args
26322632
}
26332633

26342634
func (du *baseDateArithmitical) add(ctx sessionctx.Context, date types.Time, interval string, unit string) (types.Time, bool, error) {
2635-
year, month, day, dur, err := types.ExtractTimeValue(unit, interval)
2636-
if err != nil {
2637-
return types.Time{}, true, errors.Trace(handleInvalidTimeError(ctx, err))
2635+
year, month, day, nano, err := types.ParseDurationValue(unit, interval)
2636+
if err := handleInvalidTimeError(ctx, err); err != nil {
2637+
return types.Time{}, true, err
26382638
}
26392639

26402640
goTime, err := date.Time.GoTime(time.Local)
2641-
if err != nil {
2642-
return types.Time{}, true, errors.Trace(err)
2641+
if err := handleInvalidTimeError(ctx, err); err != nil {
2642+
return types.Time{}, true, err
26432643
}
26442644

2645-
duration := time.Duration(dur)
2646-
goTime = goTime.Add(duration)
2645+
goTime = goTime.Add(time.Duration(nano))
26472646
goTime = types.AddDate(year, month, day, goTime)
26482647

26492648
if goTime.Nanosecond() == 0 {
@@ -2654,7 +2653,7 @@ func (du *baseDateArithmitical) add(ctx sessionctx.Context, date types.Time, int
26542653

26552654
date.Time = types.FromGoTime(goTime)
26562655
overflow, err := types.DateTimeIsOverflow(ctx.GetSessionVars().StmtCtx, date)
2657-
if err != nil {
2656+
if err := handleInvalidTimeError(ctx, err); err != nil {
26582657
return types.Time{}, true, err
26592658
}
26602659
if overflow {
@@ -2664,18 +2663,18 @@ func (du *baseDateArithmitical) add(ctx sessionctx.Context, date types.Time, int
26642663
}
26652664

26662665
func (du *baseDateArithmitical) sub(ctx sessionctx.Context, date types.Time, interval string, unit string) (types.Time, bool, error) {
2667-
year, month, day, dur, err := types.ExtractTimeValue(unit, interval)
2668-
if err != nil {
2669-
return types.Time{}, true, errors.Trace(handleInvalidTimeError(ctx, err))
2666+
year, month, day, nano, err := types.ParseDurationValue(unit, interval)
2667+
if err := handleInvalidTimeError(ctx, err); err != nil {
2668+
return types.Time{}, true, err
26702669
}
2671-
year, month, day, dur = -year, -month, -day, -dur
2670+
year, month, day, nano = -year, -month, -day, -nano
26722671

26732672
goTime, err := date.Time.GoTime(time.Local)
2674-
if err != nil {
2675-
return types.Time{}, true, errors.Trace(err)
2673+
if err := handleInvalidTimeError(ctx, err); err != nil {
2674+
return types.Time{}, true, err
26762675
}
26772676

2678-
duration := time.Duration(dur)
2677+
duration := time.Duration(nano)
26792678
goTime = goTime.Add(duration)
26802679
goTime = types.AddDate(year, month, day, goTime)
26812680

@@ -2687,7 +2686,7 @@ func (du *baseDateArithmitical) sub(ctx sessionctx.Context, date types.Time, int
26872686

26882687
date.Time = types.FromGoTime(goTime)
26892688
overflow, err := types.DateTimeIsOverflow(ctx.GetSessionVars().StmtCtx, date)
2690-
if err != nil {
2689+
if err := handleInvalidTimeError(ctx, err); err != nil {
26912690
return types.Time{}, true, err
26922691
}
26932692
if overflow {

expression/integration_test.go

+50-1
Original file line numberDiff line numberDiff line change
@@ -1768,7 +1768,7 @@ func (s *testIntegrationSuite) TestTimeBuiltin(c *C) {
17681768
{"\"2011-11-11 00:00:00\"", "10", "MINUTE", "2011-11-11 00:10:00", "2011-11-10 23:50:00"},
17691769
{"\"2011-11-11 00:00:00\"", "10", "SECOND", "2011-11-11 00:00:10", "2011-11-10 23:59:50"},
17701770

1771-
{"\"2011-11-11\"", "\"abc1000\"", "MICROSECOND", "<nil>", "<nil>"},
1771+
{"\"2011-11-11\"", "\"abc1000\"", "MICROSECOND", "2011-11-11 00:00:00", "2011-11-11 00:00:00"},
17721772
{"\"20111111 10:10:10\"", "\"1\"", "DAY", "<nil>", "<nil>"},
17731773
{"\"2011-11-11\"", "\"10\"", "SECOND_MICROSECOND", "2011-11-11 00:00:00.100000", "2011-11-10 23:59:59.900000"},
17741774
{"\"2011-11-11\"", "\"10.0000\"", "MINUTE_MICROSECOND", "2011-11-11 00:00:10", "2011-11-10 23:59:50"},
@@ -3895,6 +3895,55 @@ where
38953895
tk.MustQuery(q)
38963896
}
38973897

3898+
func (s *testIntegrationSuite) TestIssue9727(c *C) {
3899+
tk := testkit.NewTestKit(c, s.store)
3900+
defer s.cleanEnv(c)
3901+
3902+
cases := []struct {
3903+
sql string
3904+
result string
3905+
}{
3906+
{`SELECT "1900-01-01 00:00:00" + INTERVAL "100000000:214748364700" MINUTE_SECOND;`, "8895-03-27 22:11:40"},
3907+
{`SELECT "1900-01-01 00:00:00" + INTERVAL 1 << 37 SECOND;`, "6255-04-08 15:04:32"},
3908+
{`SELECT "1900-01-01 00:00:00" + INTERVAL 1 << 31 MINUTE;`, "5983-01-24 02:08:00"},
3909+
{`SELECT "1900-01-01 00:00:00" + INTERVAL 1 << 38 SECOND;`, "<nil>"},
3910+
{`SELECT "1900-01-01 00:00:00" + INTERVAL 1 << 33 MINUTE;`, "<nil>"},
3911+
{`SELECT "1900-01-01 00:00:00" + INTERVAL 1 << 30 HOUR;`, "<nil>"},
3912+
{`SELECT "1900-01-01 00:00:00" + INTERVAL "1000000000:214748364700" MINUTE_SECOND;`, "<nil>"},
3913+
{`SELECT 19000101000000 + INTERVAL "100000000:214748364700" MINUTE_SECOND;`, "8895-03-27 22:11:40"},
3914+
{`SELECT 19000101000000 + INTERVAL 1 << 37 SECOND;`, "6255-04-08 15:04:32"},
3915+
{`SELECT 19000101000000 + INTERVAL 1 << 31 MINUTE;`, "5983-01-24 02:08:00"},
3916+
3917+
{`SELECT "8895-03-27 22:11:40" - INTERVAL "100000000:214748364700" MINUTE_SECOND;`, "1900-01-01 00:00:00"},
3918+
{`SELECT "6255-04-08 15:04:32" - INTERVAL 1 << 37 SECOND;`, "1900-01-01 00:00:00"},
3919+
{`SELECT "5983-01-24 02:08:00" - INTERVAL 1 << 31 MINUTE;`, "1900-01-01 00:00:00"},
3920+
{`SELECT "9999-01-01 00:00:00" - INTERVAL 1 << 39 SECOND;`, "<nil>"},
3921+
{`SELECT "9999-01-01 00:00:00" - INTERVAL 1 << 33 MINUTE;`, "<nil>"},
3922+
{`SELECT "9999-01-01 00:00:00" - INTERVAL 1 << 30 HOUR;`, "<nil>"},
3923+
{`SELECT "9999-01-01 00:00:00" - INTERVAL "10000000000:214748364700" MINUTE_SECOND;`, "<nil>"},
3924+
{`SELECT 88950327221140 - INTERVAL "100000000:214748364700" MINUTE_SECOND ;`, "1900-01-01 00:00:00"},
3925+
{`SELECT 62550408150432 - INTERVAL 1 << 37 SECOND;`, "1900-01-01 00:00:00"},
3926+
{`SELECT 59830124020800 - INTERVAL 1 << 31 MINUTE;`, "1900-01-01 00:00:00"},
3927+
3928+
{`SELECT 10000101000000 + INTERVAL "111111111111111111" MICROSECOND;`, `4520-12-21 05:31:51.111111`},
3929+
{`SELECT 10000101000000 + INTERVAL "111111111111.111111" SECOND;`, `4520-12-21 05:31:51.111111`},
3930+
{`SELECT 10000101000000 + INTERVAL "111111111111.111111111" SECOND;`, `4520-12-21 05:31:51.111111`},
3931+
{`SELECT 10000101000000 + INTERVAL "111111111111.111" SECOND;`, `4520-12-21 05:31:51.111000`},
3932+
{`SELECT 10000101000000 + INTERVAL "111111111111." SECOND;`, `4520-12-21 05:31:51`},
3933+
{`SELECT 10000101000000 + INTERVAL "111111111111111111.5" MICROSECOND;`, `4520-12-21 05:31:51.111112`},
3934+
{`SELECT 10000101000000 + INTERVAL "111111111111111112.5" MICROSECOND;`, `4520-12-21 05:31:51.111113`},
3935+
{`SELECT 10000101000000 + INTERVAL "111111111111111111.500000" MICROSECOND;`, `4520-12-21 05:31:51.111112`},
3936+
{`SELECT 10000101000000 + INTERVAL "111111111111111111.50000000" MICROSECOND;`, `4520-12-21 05:31:51.111112`},
3937+
{`SELECT 10000101000000 + INTERVAL "111111111111111111.6" MICROSECOND;`, `4520-12-21 05:31:51.111112`},
3938+
{`SELECT 10000101000000 + INTERVAL "111111111111111111.499999" MICROSECOND;`, `4520-12-21 05:31:51.111111`},
3939+
{`SELECT 10000101000000 + INTERVAL "111111111111111111.499999999999" MICROSECOND;`, `4520-12-21 05:31:51.111111`},
3940+
}
3941+
3942+
for _, c := range cases {
3943+
tk.MustQuery(c.sql).Check(testkit.Rows(c.result))
3944+
}
3945+
}
3946+
38983947
func (s *testIntegrationSuite) TestTimestampDatumEncode(c *C) {
38993948
tk := testkit.NewTestKit(c, s.store)
39003949
tk.MustExec("use test")

types/time.go

+78-36
Original file line numberDiff line numberDiff line change
@@ -1602,41 +1602,81 @@ func ExtractDurationNum(d *Duration, unit string) (int64, error) {
16021602
}
16031603
}
16041604

1605-
func extractSingleTimeValue(unit string, format string) (int64, int64, int64, float64, error) {
1606-
fv, err := strconv.ParseFloat(format, 64)
1605+
func parseSingleTimeValue(unit string, format string) (int64, int64, int64, int64, error) {
1606+
// Format is a preformatted number, it format should be A[.[B]].
1607+
decimalPointPos := strings.IndexRune(format, '.')
1608+
if decimalPointPos == -1 {
1609+
decimalPointPos = len(format)
1610+
}
1611+
sign := int64(1)
1612+
if len(format) > 0 && format[0] == '-' {
1613+
sign = int64(-1)
1614+
}
1615+
iv, err := strconv.ParseInt(format[0:decimalPointPos], 10, 64)
16071616
if err != nil {
16081617
return 0, 0, 0, 0, ErrIncorrectDatetimeValue.GenWithStackByArgs(format)
16091618
}
1610-
iv := int64(math.Round(fv))
1619+
riv := iv // Rounded integer value
16111620

1621+
dv := int64(0)
1622+
lf := len(format) - 1
1623+
// Has fraction part
1624+
if decimalPointPos < lf {
1625+
if lf-decimalPointPos >= 6 {
1626+
// MySQL rounds down to 1e-6.
1627+
if dv, err = strconv.ParseInt(format[decimalPointPos+1:decimalPointPos+7], 10, 64); err != nil {
1628+
return 0, 0, 0, 0, ErrIncorrectDatetimeValue.GenWithStackByArgs(format)
1629+
}
1630+
} else {
1631+
if dv, err = strconv.ParseInt(format[decimalPointPos+1:]+"000000"[:6-(lf-decimalPointPos)], 10, 64); err != nil {
1632+
return 0, 0, 0, 0, ErrIncorrectDatetimeValue.GenWithStackByArgs(format)
1633+
}
1634+
}
1635+
if dv >= 500000 { // Round up, and we should keep 6 digits for microsecond, so dv should in [000000, 999999].
1636+
riv += sign
1637+
}
1638+
if unit != "SECOND" {
1639+
err = ErrTruncatedWrongValue.GenWithStackByArgs(format)
1640+
}
1641+
}
1642+
const gotimeDay = 24 * gotime.Hour
16121643
switch strings.ToUpper(unit) {
16131644
case "MICROSECOND":
1614-
return 0, 0, 0, fv * float64(gotime.Microsecond), nil
1645+
dayCount := riv / int64(gotimeDay/gotime.Microsecond)
1646+
riv %= int64(gotimeDay / gotime.Microsecond)
1647+
return 0, 0, dayCount, riv * int64(gotime.Microsecond), err
16151648
case "SECOND":
1616-
return 0, 0, 0, fv * float64(gotime.Second), nil
1649+
dayCount := iv / int64(gotimeDay/gotime.Second)
1650+
iv %= int64(gotimeDay / gotime.Second)
1651+
return 0, 0, dayCount, iv*int64(gotime.Second) + dv*int64(gotime.Microsecond), err
16171652
case "MINUTE":
1618-
return 0, 0, 0, float64(iv * int64(gotime.Minute)), nil
1653+
dayCount := riv / int64(gotimeDay/gotime.Minute)
1654+
riv %= int64(gotimeDay / gotime.Minute)
1655+
return 0, 0, dayCount, riv * int64(gotime.Minute), err
16191656
case "HOUR":
1620-
return 0, 0, 0, float64(iv * int64(gotime.Hour)), nil
1657+
dayCount := riv / 24
1658+
riv %= 24
1659+
return 0, 0, dayCount, riv * int64(gotime.Hour), err
16211660
case "DAY":
1622-
return 0, 0, iv, 0, nil
1661+
return 0, 0, riv, 0, err
16231662
case "WEEK":
1624-
return 0, 0, 7 * iv, 0, nil
1663+
return 0, 0, 7 * riv, 0, err
16251664
case "MONTH":
1626-
return 0, iv, 0, 0, nil
1665+
return 0, riv, 0, 0, err
16271666
case "QUARTER":
1628-
return 0, 3 * iv, 0, 0, nil
1667+
return 0, 3 * riv, 0, 0, err
16291668
case "YEAR":
1630-
return iv, 0, 0, 0, nil
1669+
return riv, 0, 0, 0, err
16311670
}
16321671

16331672
return 0, 0, 0, 0, errors.Errorf("invalid singel timeunit - %s", unit)
16341673
}
16351674

1636-
// extractTimeValue extracts years, months, days, microseconds from a string
1675+
// parseTimeValue gets years, months, days, nanoseconds from a string
1676+
// nanosecond will not exceed length of single day
16371677
// MySQL permits any punctuation delimiter in the expr format.
16381678
// See https://dev.mysql.com/doc/refman/8.0/en/expressions.html#temporal-intervals
1639-
func extractTimeValue(format string, index, cnt int) (int64, int64, int64, float64, error) {
1679+
func parseTimeValue(format string, index, cnt int) (int64, int64, int64, int64, error) {
16401680
neg := false
16411681
originalFmt := format
16421682
format = strings.TrimSpace(format)
@@ -1674,55 +1714,57 @@ func extractTimeValue(format string, index, cnt int) (int64, int64, int64, float
16741714
return 0, 0, 0, 0, ErrIncorrectDatetimeValue.GenWithStackByArgs(originalFmt)
16751715
}
16761716

1677-
hours, err := strconv.ParseFloat(fields[HourIndex], 64)
1717+
hours, err := strconv.ParseInt(fields[HourIndex], 10, 64)
16781718
if err != nil {
16791719
return 0, 0, 0, 0, ErrIncorrectDatetimeValue.GenWithStackByArgs(originalFmt)
16801720
}
1681-
minutes, err := strconv.ParseFloat(fields[MinuteIndex], 64)
1721+
minutes, err := strconv.ParseInt(fields[MinuteIndex], 10, 64)
16821722
if err != nil {
16831723
return 0, 0, 0, 0, ErrIncorrectDatetimeValue.GenWithStackByArgs(originalFmt)
16841724
}
1685-
seconds, err := strconv.ParseFloat(fields[SecondIndex], 64)
1725+
seconds, err := strconv.ParseInt(fields[SecondIndex], 10, 64)
16861726
if err != nil {
16871727
return 0, 0, 0, 0, ErrIncorrectDatetimeValue.GenWithStackByArgs(originalFmt)
16881728
}
1689-
microseconds, err := strconv.ParseFloat(alignFrac(fields[MicrosecondIndex], MaxFsp), 64)
1729+
microseconds, err := strconv.ParseInt(alignFrac(fields[MicrosecondIndex], MaxFsp), 10, 64)
16901730
if err != nil {
16911731
return 0, 0, 0, 0, ErrIncorrectDatetimeValue.GenWithStackByArgs(originalFmt)
16921732
}
1693-
durations := hours*float64(gotime.Hour) + minutes*float64(gotime.Minute) +
1694-
seconds*float64(gotime.Second) + microseconds*float64(gotime.Microsecond)
1695-
1696-
return years, months, days, durations, nil
1733+
seconds = hours*3600 + minutes*60 + seconds
1734+
days += seconds / (3600 * 24)
1735+
seconds %= 3600 * 24
1736+
return years, months, days, seconds*int64(gotime.Second) + microseconds*int64(gotime.Microsecond), nil
16971737
}
16981738

1699-
// ExtractTimeValue extracts time value from time unit and format.
1700-
func ExtractTimeValue(unit string, format string) (int64, int64, int64, float64, error) {
1739+
// ParseDurationValue parses time value from time unit and format.
1740+
// Returns y years m months d days + n nanoseconds
1741+
// Nanoseconds will no longer than one day.
1742+
func ParseDurationValue(unit string, format string) (y int64, m int64, d int64, n int64, _ error) {
17011743
switch strings.ToUpper(unit) {
17021744
case "MICROSECOND", "SECOND", "MINUTE", "HOUR", "DAY", "WEEK", "MONTH", "QUARTER", "YEAR":
1703-
return extractSingleTimeValue(unit, format)
1745+
return parseSingleTimeValue(unit, format)
17041746
case "SECOND_MICROSECOND":
1705-
return extractTimeValue(format, MicrosecondIndex, SecondMicrosecondMaxCnt)
1747+
return parseTimeValue(format, MicrosecondIndex, SecondMicrosecondMaxCnt)
17061748
case "MINUTE_MICROSECOND":
1707-
return extractTimeValue(format, MicrosecondIndex, MinuteMicrosecondMaxCnt)
1749+
return parseTimeValue(format, MicrosecondIndex, MinuteMicrosecondMaxCnt)
17081750
case "MINUTE_SECOND":
1709-
return extractTimeValue(format, SecondIndex, MinuteSecondMaxCnt)
1751+
return parseTimeValue(format, SecondIndex, MinuteSecondMaxCnt)
17101752
case "HOUR_MICROSECOND":
1711-
return extractTimeValue(format, MicrosecondIndex, HourMicrosecondMaxCnt)
1753+
return parseTimeValue(format, MicrosecondIndex, HourMicrosecondMaxCnt)
17121754
case "HOUR_SECOND":
1713-
return extractTimeValue(format, SecondIndex, HourSecondMaxCnt)
1755+
return parseTimeValue(format, SecondIndex, HourSecondMaxCnt)
17141756
case "HOUR_MINUTE":
1715-
return extractTimeValue(format, MinuteIndex, HourMinuteMaxCnt)
1757+
return parseTimeValue(format, MinuteIndex, HourMinuteMaxCnt)
17161758
case "DAY_MICROSECOND":
1717-
return extractTimeValue(format, MicrosecondIndex, DayMicrosecondMaxCnt)
1759+
return parseTimeValue(format, MicrosecondIndex, DayMicrosecondMaxCnt)
17181760
case "DAY_SECOND":
1719-
return extractTimeValue(format, SecondIndex, DaySecondMaxCnt)
1761+
return parseTimeValue(format, SecondIndex, DaySecondMaxCnt)
17201762
case "DAY_MINUTE":
1721-
return extractTimeValue(format, MinuteIndex, DayMinuteMaxCnt)
1763+
return parseTimeValue(format, MinuteIndex, DayMinuteMaxCnt)
17221764
case "DAY_HOUR":
1723-
return extractTimeValue(format, HourIndex, DayHourMaxCnt)
1765+
return parseTimeValue(format, HourIndex, DayHourMaxCnt)
17241766
case "YEAR_MONTH":
1725-
return extractTimeValue(format, MonthIndex, YearMonthMaxCnt)
1767+
return parseTimeValue(format, MonthIndex, YearMonthMaxCnt)
17261768
default:
17271769
return 0, 0, 0, 0, errors.Errorf("invalid singel timeunit - %s", unit)
17281770
}

0 commit comments

Comments
 (0)