diff --git a/executor/write_test.go b/executor/write_test.go index 344a720022fbd..e664fe6a2dcb9 100644 --- a/executor/write_test.go +++ b/executor/write_test.go @@ -2435,3 +2435,15 @@ func (s *testSuite4) TestDefEnumInsert(c *C) { tk.MustExec("insert into test (id) values (1)") tk.MustQuery("select prescription_type from test").Check(testkit.Rows("a")) } + +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 24eb91ff03644..8775db281da4e 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 } @@ -4288,11 +4284,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 } @@ -6350,9 +6346,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 @@ -6381,9 +6377,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 ea3398c52b414..ed7e784cc231a 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) { @@ -784,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) @@ -796,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) @@ -1072,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) @@ -1082,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() @@ -1214,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() @@ -1228,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() @@ -1236,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() @@ -1244,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() @@ -1270,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) @@ -1286,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() @@ -1297,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() @@ -1636,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)) @@ -1653,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() @@ -2715,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) diff --git a/expression/helper.go b/expression/helper.go index 307c2aba9cad3..f5268a6619107 100644 --- a/expression/helper.go +++ b/expression/helper.go @@ -66,7 +66,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 } @@ -132,7 +132,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 { @@ -145,15 +147,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 8e08cb30a0a43..2db88bc3fbe08 100644 --- a/sessionctx/stmtctx/stmtctx.go +++ b/sessionctx/stmtctx/stmtctx.go @@ -124,8 +124,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 { @@ -136,6 +136,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) {