diff --git a/executor/executor_test.go b/executor/executor_test.go index 8faddae1125b3..fbaf69e0bec6f 100644 --- a/executor/executor_test.go +++ b/executor/executor_test.go @@ -3450,7 +3450,7 @@ func (s *testSuite) TestCurrentTimestampValueSelection(c *C) { tk.MustQuery("select id from t where t0 = ?", t0).Check(testkit.Rows("1")) tk.MustQuery("select id from t where t1 = ?", t1).Check(testkit.Rows("1")) tk.MustQuery("select id from t where t2 = ?", t2).Check(testkit.Rows("1")) - time.Sleep(time.Second / 2) + time.Sleep(time.Second) tk.MustExec("update t set t0 = now() where id = 1") rs = tk.MustQuery("select t2 from t where id = 1") newT2 := rs.Rows()[0][0].(string) diff --git a/expression/builtin_time.go b/expression/builtin_time.go index 8c83f69a2c4ce..f3ea923efbf5c 100644 --- a/expression/builtin_time.go +++ b/expression/builtin_time.go @@ -240,8 +240,14 @@ var ( _ builtinFunc = &builtinSubDateDatetimeDecimalSig{} ) -func convertTimeToMysqlTime(t time.Time, fsp int) (types.Time, error) { - tr, err := types.RoundFrac(t, fsp) +func convertTimeToMysqlTime(t time.Time, fsp int, roundMode types.RoundMode) (types.Time, error) { + var tr time.Time + var err error + if roundMode == types.ModeTruncate { + tr, err = types.TruncateFrac(t, fsp) + } else { + tr, err = types.RoundFrac(t, fsp) + } if err != nil { return types.Time{}, errors.Trace(err) } @@ -1610,7 +1616,7 @@ func evalFromUnixTime(ctx sessionctx.Context, fsp int, row chunk.Row, arg Expres sc := ctx.GetSessionVars().StmtCtx tmp := time.Unix(integralPart, fractionalPart).In(sc.TimeZone) - t, err := convertTimeToMysqlTime(tmp, fsp) + t, err := convertTimeToMysqlTime(tmp, fsp, types.ModeHalfEven) if err != nil { return res, true, errors.Trace(err) } @@ -1932,7 +1938,7 @@ func (b *builtinSysDateWithFspSig) evalTime(row chunk.Row) (d types.Time, isNull loc := b.ctx.GetSessionVars().Location() now := time.Now().In(loc) - result, err := convertTimeToMysqlTime(now, int(fsp)) + result, err := convertTimeToMysqlTime(now, int(fsp), types.ModeHalfEven) if err != nil { return types.Time{}, true, errors.Trace(err) } @@ -1954,7 +1960,7 @@ func (b *builtinSysDateWithoutFspSig) Clone() builtinFunc { func (b *builtinSysDateWithoutFspSig) evalTime(row chunk.Row) (d types.Time, isNull bool, err error) { tz := b.ctx.GetSessionVars().Location() now := time.Now().In(tz) - result, err := convertTimeToMysqlTime(now, 0) + result, err := convertTimeToMysqlTime(now, 0, types.ModeHalfEven) if err != nil { return types.Time{}, true, errors.Trace(err) } @@ -2262,8 +2268,12 @@ func (c *utcTimestampFunctionClass) getFunction(ctx sessionctx.Context, args []E return sig, nil } -func evalUTCTimestampWithFsp(fsp int) (types.Time, bool, error) { - result, err := convertTimeToMysqlTime(time.Now().UTC(), fsp) +func evalUTCTimestampWithFsp(ctx sessionctx.Context, fsp int) (types.Time, bool, error) { + nowTs, err := getSystemTimestamp(ctx) + if err != nil { + return types.Time{}, true, err + } + result, err := convertTimeToMysqlTime(nowTs.UTC(), fsp, types.ModeHalfEven) if err != nil { return types.Time{}, true, errors.Trace(err) } @@ -2295,7 +2305,7 @@ func (b *builtinUTCTimestampWithArgSig) evalTime(row chunk.Row) (types.Time, boo return types.Time{}, true, errors.Errorf("Invalid negative %d specified, must in [0, 6].", num) } - result, isNull, err := evalUTCTimestampWithFsp(int(num)) + result, isNull, err := evalUTCTimestampWithFsp(b.ctx, int(num)) return result, isNull, errors.Trace(err) } @@ -2312,7 +2322,7 @@ func (b *builtinUTCTimestampWithoutArgSig) Clone() builtinFunc { // evalTime evals UTC_TIMESTAMP(). // See https://dev.mysql.com/doc/refman/5.7/en/date-and-time-functions.html#function_utc-timestamp func (b *builtinUTCTimestampWithoutArgSig) evalTime(row chunk.Row) (types.Time, bool, error) { - result, isNull, err := evalUTCTimestampWithFsp(0) + result, isNull, err := evalUTCTimestampWithFsp(b.ctx, 0) return result, isNull, errors.Trace(err) } @@ -2351,7 +2361,15 @@ func evalNowWithFsp(ctx sessionctx.Context, fsp int) (types.Time, bool, error) { return types.Time{}, true, errors.Trace(err) } - result, err := convertTimeToMysqlTime(sysTs, fsp) + // In MySQL's implementation, now() will truncate the result instead of rounding it. + // Results below are from MySQL 5.7, which can prove it. + // mysql> select now(6), now(3), now(); + // +----------------------------+-------------------------+---------------------+ + // | now(6) | now(3) | now() | + // +----------------------------+-------------------------+---------------------+ + // | 2019-03-25 15:57:56.612966 | 2019-03-25 15:57:56.612 | 2019-03-25 15:57:56 | + // +----------------------------+-------------------------+---------------------+ + result, err := convertTimeToMysqlTime(sysTs, fsp, types.ModeTruncate) if err != nil { return types.Time{}, true, errors.Trace(err) } @@ -3807,8 +3825,17 @@ func goTimeToMysqlUnixTimestamp(t time.Time, decimal int) (*types.MyDecimal, err if err != nil { return nil, errors.Trace(err) } - err = dec.Round(dec, decimal, types.ModeHalfEven) - return dec, errors.Trace(err) + + // In MySQL's implementation, unix_timestamp() will truncate the result instead of rounding it. + // Results below are from MySQL 5.7, which can prove it. + // mysql> select unix_timestamp(), unix_timestamp(now(0)), now(0), unix_timestamp(now(3)), now(3), now(6); + // +------------------+------------------------+---------------------+------------------------+-------------------------+----------------------------+ + // | unix_timestamp() | unix_timestamp(now(0)) | now(0) | unix_timestamp(now(3)) | now(3) | now(6) | + // +------------------+------------------------+---------------------+------------------------+-------------------------+----------------------------+ + // | 1553503194 | 1553503194 | 2019-03-25 16:39:54 | 1553503194.992 | 2019-03-25 16:39:54.992 | 2019-03-25 16:39:54.992969 | + // +------------------+------------------------+---------------------+------------------------+-------------------------+----------------------------+ + err = dec.Round(dec, decimal, types.ModeTruncate) + return dec, err } type builtinUnixTimestampCurrentSig struct { diff --git a/expression/integration_test.go b/expression/integration_test.go index b76d6538772a9..61d9c232d4279 100644 --- a/expression/integration_test.go +++ b/expression/integration_test.go @@ -17,6 +17,7 @@ import ( "bytes" "fmt" "sort" + "strconv" "strings" "time" @@ -4009,3 +4010,32 @@ func (s *testIntegrationSuite) TestDateTimeAddReal(c *C) { tk.MustQuery(c.sql).Check(testkit.Rows(c.result)) } } + +func (s *testIntegrationSuite) TestIssue9710(c *C) { + tk := testkit.NewTestKit(c, s.store) + getSAndMS := func(str string) (int, int) { + results := strings.Split(str, ":") + SAndMS := strings.Split(results[len(results)-1], ".") + var s, ms int + s, _ = strconv.Atoi(SAndMS[0]) + if len(SAndMS) > 1 { + ms, _ = strconv.Atoi(SAndMS[1]) + } + return s, ms + } + + for { + rs := tk.MustQuery("select now(), now(6), unix_timestamp(), unix_timestamp(now())") + s, ms := getSAndMS(rs.Rows()[0][1].(string)) + if ms < 500000 { + time.Sleep(time.Second / 10) + continue + } + + s1, _ := getSAndMS(rs.Rows()[0][0].(string)) + c.Assert(s, Equals, s1) // now() will truncate the result instead of rounding it + + c.Assert(rs.Rows()[0][2], Equals, rs.Rows()[0][3]) // unix_timestamp() will truncate the result + break + } +} diff --git a/types/time.go b/types/time.go index 6046ee5ce0f3c..2e1c0c0236b8d 100644 --- a/types/time.go +++ b/types/time.go @@ -487,6 +487,16 @@ func RoundFrac(t gotime.Time, fsp int) (gotime.Time, error) { return t.Round(gotime.Duration(math.Pow10(9-fsp)) * gotime.Nanosecond), nil } +// TruncateFrac truncates fractional seconds precision with new fsp and returns a new one. +// 2011:11:11 10:10:10.888888 round 0 -> 2011:11:11 10:10:10 +// 2011:11:11 10:10:10.111111 round 0 -> 2011:11:11 10:10:10 +func TruncateFrac(t gotime.Time, fsp int) (gotime.Time, error) { + if _, err := CheckFsp(fsp); err != nil { + return t, err + } + return t.Truncate(gotime.Duration(math.Pow10(9-fsp)) * gotime.Nanosecond), nil +} + // ToPackedUint encodes Time to a packed uint64 value. // // 1 bit 0