From f437ce8a987a1c769146220f189e19ff53d61af7 Mon Sep 17 00:00:00 2001 From: cfzjywxk Date: Tue, 23 Jul 2019 17:02:24 +0800 Subject: [PATCH 1/5] =?UTF-8?q?executor,=20expression:=20fix=20current=5Ft?= =?UTF-8?q?imestamp/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 fa0a981a5a4eb..97d9cd0183540 100644 --- a/executor/write_test.go +++ b/executor/write_test.go @@ -2454,3 +2454,15 @@ func (s *testSuite4) TestAutoIDInRetry(c *C) { tk.MustExec("insert into t values ()") tk.MustQuery(`select * from t`).Check(testkit.Rows("1", "2", "3", "4", "5")) } + +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 8dba9aff6a7e6..a1db1a8a707b7 100644 --- a/expression/builtin_time.go +++ b/expression/builtin_time.go @@ -2029,9 +2029,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{ @@ -2088,9 +2088,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) @@ -2116,9 +2116,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)) @@ -2258,9 +2258,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{ @@ -2319,9 +2319,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 { @@ -2406,13 +2406,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. @@ -2423,7 +2419,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 } @@ -4279,11 +4275,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 } @@ -6341,9 +6337,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 @@ -6372,9 +6368,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 9be31482c77c6..72b94045a18f3 100644 --- a/sessionctx/stmtctx/stmtctx.go +++ b/sessionctx/stmtctx/stmtctx.go @@ -119,8 +119,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 { @@ -131,6 +131,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) { From 75506c8c616df8ff10eb6c84a08b34320dc85bc1 Mon Sep 17 00:00:00 2001 From: cfzjywxk Date: Tue, 23 Jul 2019 21:42:58 +0800 Subject: [PATCH 2/5] fix old unstable unit test.. --- store/tikv/client_fail_test.go | 1 + 1 file changed, 1 insertion(+) diff --git a/store/tikv/client_fail_test.go b/store/tikv/client_fail_test.go index 5dd7be707d815..feb2ea65f8a95 100644 --- a/store/tikv/client_fail_test.go +++ b/store/tikv/client_fail_test.go @@ -48,6 +48,7 @@ func (s *testClientSuite) TestPanicInRecvLoop(c *C) { time.Sleep(time.Second) c.Assert(failpoint.Disable("github.com/pingcap/tidb/store/tikv/panicInFailPendingRequests"), IsNil) c.Assert(failpoint.Disable("github.com/pingcap/tidb/store/tikv/gotErrorInRecvLoop"), IsNil) + time.Sleep(time.Second) req := &tikvrpc.Request{ Type: tikvrpc.CmdEmpty, From 53dfa700292f87ff9d47a990a14c579dc2b9460c Mon Sep 17 00:00:00 2001 From: cfzjywxk Date: Mon, 29 Jul 2019 10:29:24 +0800 Subject: [PATCH 3/5] make unit test time related stable --- expression/builtin_time_test.go | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/expression/builtin_time_test.go b/expression/builtin_time_test.go index 3ca145f52cee2..14e7e70094fc4 100644 --- a/expression/builtin_time_test.go +++ b/expression/builtin_time_test.go @@ -783,9 +783,9 @@ func (s *testEvaluatorSuite) TestNowAndUTCTimestamp(c *C) { {funcs[ast.Now], func() time.Time { return time.Now() }}, {funcs[ast.UTCTimestamp], func() time.Time { return time.Now().UTC() }}, } { - resetStmtContext(s.ctx) f, err := x.fc.getFunction(s.ctx, s.datumsToConstants(nil)) c.Assert(err, IsNil) + resetStmtContext(s.ctx) v, err := evalBuiltinFunc(f, chunk.Row{}) ts := x.now() c.Assert(err, IsNil) @@ -795,9 +795,9 @@ func (s *testEvaluatorSuite) TestNowAndUTCTimestamp(c *C) { c.Assert(strings.Contains(t.String(), "."), IsFalse) c.Assert(ts.Sub(gotime(t, ts.Location())), LessEqual, time.Second) - resetStmtContext(s.ctx) f, err = x.fc.getFunction(s.ctx, s.datumsToConstants(types.MakeDatums(6))) c.Assert(err, IsNil) + resetStmtContext(s.ctx) v, err = evalBuiltinFunc(f, chunk.Row{}) ts = x.now() c.Assert(err, IsNil) @@ -1071,6 +1071,7 @@ func (s *testEvaluatorSuite) TestSysDate(c *C) { variable.SetSessionSystemVar(ctx.GetSessionVars(), "timestamp", timezone) f, err := fc.getFunction(ctx, s.datumsToConstants(nil)) c.Assert(err, IsNil) + resetStmtContext(s.ctx) v, err := evalBuiltinFunc(f, chunk.Row{}) last := time.Now() c.Assert(err, IsNil) @@ -1081,6 +1082,7 @@ func (s *testEvaluatorSuite) TestSysDate(c *C) { last := time.Now() f, err := fc.getFunction(ctx, s.datumsToConstants(types.MakeDatums(6))) c.Assert(err, IsNil) + resetStmtContext(s.ctx) v, err := evalBuiltinFunc(f, chunk.Row{}) c.Assert(err, IsNil) n := v.GetMysqlTime() @@ -1213,6 +1215,7 @@ func (s *testEvaluatorSuite) TestCurrentDate(c *C) { fc := funcs[ast.CurrentDate] f, err := fc.getFunction(mock.NewContext(), s.datumsToConstants(nil)) c.Assert(err, IsNil) + resetStmtContext(s.ctx) v, err := evalBuiltinFunc(f, chunk.Row{}) c.Assert(err, IsNil) n := v.GetMysqlTime() @@ -1227,6 +1230,7 @@ func (s *testEvaluatorSuite) TestCurrentTime(c *C) { fc := funcs[ast.CurrentTime] f, err := fc.getFunction(s.ctx, s.datumsToConstants(types.MakeDatums(nil))) c.Assert(err, IsNil) + resetStmtContext(s.ctx) v, err := evalBuiltinFunc(f, chunk.Row{}) c.Assert(err, IsNil) n := v.GetMysqlDuration() @@ -1235,6 +1239,7 @@ func (s *testEvaluatorSuite) TestCurrentTime(c *C) { f, err = fc.getFunction(s.ctx, s.datumsToConstants(types.MakeDatums(3))) c.Assert(err, IsNil) + resetStmtContext(s.ctx) v, err = evalBuiltinFunc(f, chunk.Row{}) c.Assert(err, IsNil) n = v.GetMysqlDuration() @@ -1243,6 +1248,7 @@ func (s *testEvaluatorSuite) TestCurrentTime(c *C) { f, err = fc.getFunction(s.ctx, s.datumsToConstants(types.MakeDatums(6))) c.Assert(err, IsNil) + resetStmtContext(s.ctx) v, err = evalBuiltinFunc(f, chunk.Row{}) c.Assert(err, IsNil) n = v.GetMysqlDuration() @@ -1269,9 +1275,9 @@ func (s *testEvaluatorSuite) TestUTCTime(c *C) { }{{0, 8}, {3, 12}, {6, 15}, {-1, 0}, {7, 0}} for _, test := range tests { - resetStmtContext(s.ctx) f, err := fc.getFunction(s.ctx, s.datumsToConstants(types.MakeDatums(test.param))) c.Assert(err, IsNil) + resetStmtContext(s.ctx) v, err := evalBuiltinFunc(f, chunk.Row{}) if test.expect > 0 { c.Assert(err, IsNil) @@ -1285,6 +1291,7 @@ func (s *testEvaluatorSuite) TestUTCTime(c *C) { f, err := fc.getFunction(s.ctx, make([]Expression, 0)) c.Assert(err, IsNil) + resetStmtContext(s.ctx) v, err := evalBuiltinFunc(f, chunk.Row{}) c.Assert(err, IsNil) n := v.GetMysqlDuration() @@ -1296,9 +1303,9 @@ func (s *testEvaluatorSuite) TestUTCDate(c *C) { defer testleak.AfterTest(c)() last := time.Now().UTC() fc := funcs[ast.UTCDate] - resetStmtContext(mock.NewContext()) f, err := fc.getFunction(mock.NewContext(), s.datumsToConstants(nil)) c.Assert(err, IsNil) + resetStmtContext(mock.NewContext()) v, err := evalBuiltinFunc(f, chunk.Row{}) c.Assert(err, IsNil) n := v.GetMysqlTime() @@ -1635,9 +1642,9 @@ func (s *testEvaluatorSuite) TestTimestampDiff(c *C) { func (s *testEvaluatorSuite) TestUnixTimestamp(c *C) { // Test UNIX_TIMESTAMP(). fc := funcs[ast.UnixTimestamp] - resetStmtContext(s.ctx) f, err := fc.getFunction(s.ctx, nil) c.Assert(err, IsNil) + resetStmtContext(s.ctx) d, err := evalBuiltinFunc(f, chunk.Row{}) c.Assert(err, IsNil) c.Assert(d.GetInt64()-time.Now().Unix(), GreaterEqual, int64(-1)) @@ -1652,9 +1659,9 @@ func (s *testEvaluatorSuite) TestUnixTimestamp(c *C) { n := types.Datum{} n.SetMysqlTime(now) args := []types.Datum{n} - resetStmtContext(s.ctx) f, err = fc.getFunction(s.ctx, s.datumsToConstants(args)) c.Assert(err, IsNil) + resetStmtContext(s.ctx) d, err = evalBuiltinFunc(f, chunk.Row{}) c.Assert(err, IsNil) val, _ := d.GetMysqlDecimal().ToInt() @@ -2714,9 +2721,9 @@ func (s *testEvaluatorSuite) TestWithTimeZone(c *C) { for _, t := range tests { now := time.Now().In(sv.TimeZone) - resetStmtContext(s.ctx) f, err := funcs[t.method].getFunction(s.ctx, s.datumsToConstants(t.Input)) c.Assert(err, IsNil) + resetStmtContext(s.ctx) d, err := evalBuiltinFunc(f, chunk.Row{}) c.Assert(err, IsNil) result := t.convertToTime(d, sv.TimeZone) From 79d01ce817322699dbd8c7d6792d041258d91ad4 Mon Sep 17 00:00:00 2001 From: cfzjywxk Date: Wed, 31 Jul 2019 11:23:59 +0800 Subject: [PATCH 4/5] restore case --- store/tikv/client_fail_test.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/store/tikv/client_fail_test.go b/store/tikv/client_fail_test.go index edddf809fedd0..5dd7be707d815 100644 --- a/store/tikv/client_fail_test.go +++ b/store/tikv/client_fail_test.go @@ -45,10 +45,9 @@ func (s *testClientSuite) TestPanicInRecvLoop(c *C) { _, err := rpcClient.getConnArray(addr) c.Assert(err, IsNil) - time.Sleep(time.Second) - c.Assert(failpoint.Disable("github.com/pingcap/tidb/store/tikv/gotErrorInRecvLoop"), IsNil) time.Sleep(time.Second) c.Assert(failpoint.Disable("github.com/pingcap/tidb/store/tikv/panicInFailPendingRequests"), IsNil) + c.Assert(failpoint.Disable("github.com/pingcap/tidb/store/tikv/gotErrorInRecvLoop"), IsNil) req := &tikvrpc.Request{ Type: tikvrpc.CmdEmpty, From 38d9112fe920343dc39290c1cd255960618ced59 Mon Sep 17 00:00:00 2001 From: cfzjywxk Date: Wed, 31 Jul 2019 11:25:51 +0800 Subject: [PATCH 5/5] restore case --- store/tikv/client_fail_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/store/tikv/client_fail_test.go b/store/tikv/client_fail_test.go index 5dd7be707d815..e98bcb253d8d4 100644 --- a/store/tikv/client_fail_test.go +++ b/store/tikv/client_fail_test.go @@ -46,8 +46,8 @@ func (s *testClientSuite) TestPanicInRecvLoop(c *C) { c.Assert(err, IsNil) time.Sleep(time.Second) - c.Assert(failpoint.Disable("github.com/pingcap/tidb/store/tikv/panicInFailPendingRequests"), IsNil) c.Assert(failpoint.Disable("github.com/pingcap/tidb/store/tikv/gotErrorInRecvLoop"), IsNil) + c.Assert(failpoint.Disable("github.com/pingcap/tidb/store/tikv/panicInFailPendingRequests"), IsNil) req := &tikvrpc.Request{ Type: tikvrpc.CmdEmpty,