From ca70d74a284d96e1206021ad7de1fa5aa62d8556 Mon Sep 17 00:00:00 2001 From: cfzjywxk Date: Tue, 23 Jul 2019 17:02:24 +0800 Subject: [PATCH] =?UTF-8?q?executor,=20expression:=20fix=20current=5Ftimes?= =?UTF-8?q?tamp/now=20not=20consistent=E2=80=A6=20(#11342)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- executor/write_test.go | 12 +++++++ expression/builtin_time.go | 62 +++++++++++++++------------------ expression/builtin_time_test.go | 3 +- expression/helper.go | 27 +++++++------- sessionctx/stmtctx/stmtctx.go | 19 ++++++++-- 5 files changed, 74 insertions(+), 49 deletions(-) diff --git a/executor/write_test.go b/executor/write_test.go index 3a60e95c97bd0..43408d57ab03a 100644 --- a/executor/write_test.go +++ b/executor/write_test.go @@ -2538,3 +2538,15 @@ func (s *testSuite4) TestSetWithRefGenCol(c *C) { _, err = tk.Exec(`insert into t3 set j = i + 1`) c.Assert(err, NotNil) } + +func (s *testSuite4) TestSetWithCurrentTimestampAndNow(c *C) { + tk := testkit.NewTestKitWithInit(c, s.store) + tk.MustExec("use test") + tk.MustExec(`drop table if exists tbl;`) + tk.MustExec(`create table t1(c1 timestamp default current_timestamp, c2 int, c3 timestamp default current_timestamp);`) + //c1 insert using now() function result, c3 using default value calculation, should be same + tk.MustExec(`insert into t1 set c1 = current_timestamp, c2 = sleep(2);`) + tk.MustQuery("select c1 = c3 from t1").Check(testkit.Rows("1")) + tk.MustExec(`insert into t1 set c1 = current_timestamp, c2 = sleep(1);`) + tk.MustQuery("select c1 = c3 from t1").Check(testkit.Rows("1", "1")) +} diff --git a/expression/builtin_time.go b/expression/builtin_time.go index 9b302873eb852..7a085453fdb06 100644 --- a/expression/builtin_time.go +++ b/expression/builtin_time.go @@ -2028,9 +2028,9 @@ func (b *builtinCurrentDateSig) Clone() builtinFunc { // See https://dev.mysql.com/doc/refman/5.7/en/date-and-time-functions.html#function_curdate func (b *builtinCurrentDateSig) evalTime(row chunk.Row) (d types.Time, isNull bool, err error) { tz := b.ctx.GetSessionVars().Location() - var nowTs = &b.ctx.GetSessionVars().StmtCtx.NowTs - if nowTs.Equal(time.Time{}) { - *nowTs = time.Now() + nowTs, err := getStmtTimestamp(b.ctx) + if err != nil { + return types.Time{}, true, err } year, month, day := nowTs.In(tz).Date() result := types.Time{ @@ -2087,9 +2087,9 @@ func (b *builtinCurrentTime0ArgSig) Clone() builtinFunc { func (b *builtinCurrentTime0ArgSig) evalDuration(row chunk.Row) (types.Duration, bool, error) { tz := b.ctx.GetSessionVars().Location() - var nowTs = &b.ctx.GetSessionVars().StmtCtx.NowTs - if nowTs.Equal(time.Time{}) { - *nowTs = time.Now() + nowTs, err := getStmtTimestamp(b.ctx) + if err != nil { + return types.Duration{}, true, err } dur := nowTs.In(tz).Format(types.TimeFormat) res, err := types.ParseDuration(b.ctx.GetSessionVars().StmtCtx, dur, types.MinFsp) @@ -2115,9 +2115,9 @@ func (b *builtinCurrentTime1ArgSig) evalDuration(row chunk.Row) (types.Duration, return types.Duration{}, true, err } tz := b.ctx.GetSessionVars().Location() - var nowTs = &b.ctx.GetSessionVars().StmtCtx.NowTs - if nowTs.Equal(time.Time{}) { - *nowTs = time.Now() + nowTs, err := getStmtTimestamp(b.ctx) + if err != nil { + return types.Duration{}, true, err } dur := nowTs.In(tz).Format(types.TimeFSPFormat) res, err := types.ParseDuration(b.ctx.GetSessionVars().StmtCtx, dur, int(fsp)) @@ -2257,9 +2257,9 @@ func (b *builtinUTCDateSig) Clone() builtinFunc { // evalTime evals UTC_DATE, UTC_DATE(). // See https://dev.mysql.com/doc/refman/5.7/en/date-and-time-functions.html#function_utc-date func (b *builtinUTCDateSig) evalTime(row chunk.Row) (types.Time, bool, error) { - var nowTs = &b.ctx.GetSessionVars().StmtCtx.NowTs - if nowTs.Equal(time.Time{}) { - *nowTs = time.Now() + nowTs, err := getStmtTimestamp(b.ctx) + if err != nil { + return types.Time{}, true, err } year, month, day := nowTs.UTC().Date() result := types.Time{ @@ -2318,9 +2318,9 @@ func (c *utcTimestampFunctionClass) getFunction(ctx sessionctx.Context, args []E } func evalUTCTimestampWithFsp(ctx sessionctx.Context, fsp int) (types.Time, bool, error) { - var nowTs = &ctx.GetSessionVars().StmtCtx.NowTs - if nowTs.Equal(time.Time{}) { - *nowTs = time.Now() + nowTs, err := getStmtTimestamp(ctx) + if err != nil { + return types.Time{}, true, err } result, err := convertTimeToMysqlTime(nowTs.UTC(), fsp, types.ModeHalfEven) if err != nil { @@ -2405,13 +2405,9 @@ func (c *nowFunctionClass) getFunction(ctx sessionctx.Context, args []Expression } func evalNowWithFsp(ctx sessionctx.Context, fsp int) (types.Time, bool, error) { - var sysTs = &ctx.GetSessionVars().StmtCtx.SysTs - if sysTs.Equal(time.Time{}) { - var err error - *sysTs, err = getSystemTimestamp(ctx) - if err != nil { - return types.Time{}, true, err - } + nowTs, err := getStmtTimestamp(ctx) + if err != nil { + return types.Time{}, true, err } // In MySQL's implementation, now() will truncate the result instead of rounding it. @@ -2422,7 +2418,7 @@ func evalNowWithFsp(ctx sessionctx.Context, fsp int) (types.Time, bool, error) { // +----------------------------+-------------------------+---------------------+ // | 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) + result, err := convertTimeToMysqlTime(nowTs, fsp, types.ModeTruncate) if err != nil { return types.Time{}, true, err } @@ -4278,11 +4274,11 @@ func (b *builtinUnixTimestampCurrentSig) Clone() builtinFunc { // evalInt evals a UNIX_TIMESTAMP(). // See https://dev.mysql.com/doc/refman/5.7/en/date-and-time-functions.html#function_unix-timestamp func (b *builtinUnixTimestampCurrentSig) evalInt(row chunk.Row) (int64, bool, error) { - var nowTs = &b.ctx.GetSessionVars().StmtCtx.NowTs - if nowTs.Equal(time.Time{}) { - *nowTs = time.Now() + nowTs, err := getStmtTimestamp(b.ctx) + if err != nil { + return 0, true, err } - dec, err := goTimeToMysqlUnixTimestamp(*nowTs, 1) + dec, err := goTimeToMysqlUnixTimestamp(nowTs, 1) if err != nil { return 0, true, err } @@ -6340,9 +6336,9 @@ func (b *builtinUTCTimeWithoutArgSig) Clone() builtinFunc { // evalDuration evals a builtinUTCTimeWithoutArgSig. // See https://dev.mysql.com/doc/refman/5.7/en/date-and-time-functions.html#function_utc-time func (b *builtinUTCTimeWithoutArgSig) evalDuration(row chunk.Row) (types.Duration, bool, error) { - var nowTs = &b.ctx.GetSessionVars().StmtCtx.NowTs - if nowTs.Equal(time.Time{}) { - *nowTs = time.Now() + nowTs, err := getStmtTimestamp(b.ctx) + if err != nil { + return types.Duration{}, true, err } v, err := types.ParseDuration(b.ctx.GetSessionVars().StmtCtx, nowTs.UTC().Format(types.TimeFormat), 0) return v, false, err @@ -6371,9 +6367,9 @@ func (b *builtinUTCTimeWithArgSig) evalDuration(row chunk.Row) (types.Duration, if fsp < int64(types.MinFsp) { return types.Duration{}, true, errors.Errorf("Invalid negative %d specified, must in [0, 6].", fsp) } - var nowTs = &b.ctx.GetSessionVars().StmtCtx.NowTs - if nowTs.Equal(time.Time{}) { - *nowTs = time.Now() + nowTs, err := getStmtTimestamp(b.ctx) + if err != nil { + return types.Duration{}, true, err } v, err := types.ParseDuration(b.ctx.GetSessionVars().StmtCtx, nowTs.UTC().Format(types.TimeFSPFormat), int(fsp)) return v, false, err diff --git a/expression/builtin_time_test.go b/expression/builtin_time_test.go index eebb80b524d51..c225ba7bd28c5 100644 --- a/expression/builtin_time_test.go +++ b/expression/builtin_time_test.go @@ -764,8 +764,7 @@ func (s *testEvaluatorSuite) TestTime(c *C) { } func resetStmtContext(ctx sessionctx.Context) { - ctx.GetSessionVars().StmtCtx.NowTs = time.Time{} - ctx.GetSessionVars().StmtCtx.SysTs = time.Time{} + ctx.GetSessionVars().StmtCtx.ResetNowTs() } func (s *testEvaluatorSuite) TestNowAndUTCTimestamp(c *C) { diff --git a/expression/helper.go b/expression/helper.go index cd150fcb6cc84..7b0f49e71a71d 100644 --- a/expression/helper.go +++ b/expression/helper.go @@ -54,7 +54,7 @@ func GetTimeValue(ctx sessionctx.Context, v interface{}, tp byte, fsp int) (d ty case string: upperX := strings.ToUpper(x) if upperX == strings.ToUpper(ast.CurrentTimestamp) { - defaultTime, err := getSystemTimestamp(ctx) + defaultTime, err := getStmtTimestamp(ctx) if err != nil { return d, err } @@ -120,7 +120,9 @@ func GetTimeValue(ctx sessionctx.Context, v interface{}, tp byte, fsp int) (d ty return d, nil } -func getSystemTimestamp(ctx sessionctx.Context) (time.Time, error) { +// if timestamp session variable set, use session variable as current time, otherwise use cached time +// during one sql statement, the "current_time" should be the same +func getStmtTimestamp(ctx sessionctx.Context) (time.Time, error) { now := time.Now() if ctx == nil { @@ -133,15 +135,16 @@ func getSystemTimestamp(ctx sessionctx.Context) (time.Time, error) { return now, err } - if timestampStr == "" { - return now, nil - } - timestamp, err := types.StrToInt(sessionVars.StmtCtx, timestampStr) - if err != nil { - return time.Time{}, err - } - if timestamp <= 0 { - return now, nil + if timestampStr != "" { + timestamp, err := types.StrToInt(sessionVars.StmtCtx, timestampStr) + if err != nil { + return time.Time{}, err + } + if timestamp <= 0 { + return now, nil + } + return time.Unix(timestamp, 0), nil } - return time.Unix(timestamp, 0), nil + stmtCtx := ctx.GetSessionVars().StmtCtx + return stmtCtx.GetNowTsCached(), nil } diff --git a/sessionctx/stmtctx/stmtctx.go b/sessionctx/stmtctx/stmtctx.go index fdbc0f9ec6653..e19345d469ffd 100644 --- a/sessionctx/stmtctx/stmtctx.go +++ b/sessionctx/stmtctx/stmtctx.go @@ -120,8 +120,8 @@ type StatementContext struct { RuntimeStatsColl *execdetails.RuntimeStatsColl TableIDs []int64 IndexIDs []int64 - NowTs time.Time - SysTs time.Time + nowTs time.Time // use this variable for now/current_timestamp calculation/cache for one stmt + stmtTimeCached bool StmtType string OriginalSQL string digestMemo struct { @@ -132,6 +132,21 @@ type StatementContext struct { Tables []TableEntry } +// GetNowTsCached getter for nowTs, if not set get now time and cache it +func (sc *StatementContext) GetNowTsCached() time.Time { + if !sc.stmtTimeCached { + now := time.Now() + sc.nowTs = now + sc.stmtTimeCached = true + } + return sc.nowTs +} + +// ResetNowTs resetter for nowTs, clear cached time flag +func (sc *StatementContext) ResetNowTs() { + sc.stmtTimeCached = false +} + // SQLDigest gets normalized and digest for provided sql. // it will cache result after first calling. func (sc *StatementContext) SQLDigest() (normalized, sqlDigest string) {