diff --git a/executor/write_test.go b/executor/write_test.go index 69b932ce577e7..fe727a3ef1459 100644 --- a/executor/write_test.go +++ b/executor/write_test.go @@ -2337,3 +2337,15 @@ func (s *testSuite) TestBatchDML(c *C) { tk.MustExec("commit") tk.MustQuery("select * from t order by i").Check(testkit.Rows("1 d", "2 e", "3 e")) } + +func (s *testSuite) 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 eec7c08b791ac..c0ea24fcb7541 100644 --- a/expression/builtin_time.go +++ b/expression/builtin_time.go @@ -2028,7 +2028,11 @@ 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() - year, month, day := time.Now().In(tz).Date() + nowTs, err := getStmtTimestamp(b.ctx) + if err != nil { + return types.Time{}, true, err + } + year, month, day := nowTs.In(tz).Date() result := types.Time{ Time: types.FromDate(year, int(month), day, 0, 0, 0, 0), Type: mysql.TypeDate, @@ -2083,7 +2087,11 @@ func (b *builtinCurrentTime0ArgSig) Clone() builtinFunc { func (b *builtinCurrentTime0ArgSig) evalDuration(row chunk.Row) (types.Duration, bool, error) { tz := b.ctx.GetSessionVars().Location() - dur := time.Now().In(tz).Format(types.TimeFormat) + 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) if err != nil { return types.Duration{}, true, errors.Trace(err) @@ -2107,7 +2115,11 @@ func (b *builtinCurrentTime1ArgSig) evalDuration(row chunk.Row) (types.Duration, return types.Duration{}, true, errors.Trace(err) } tz := b.ctx.GetSessionVars().Location() - dur := time.Now().In(tz).Format(types.TimeFSPFormat) + 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)) if err != nil { return types.Duration{}, true, errors.Trace(err) @@ -2245,7 +2257,11 @@ 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) { - year, month, day := time.Now().UTC().Date() + nowTs, err := getStmtTimestamp(b.ctx) + if err != nil { + return types.Time{}, true, err + } + year, month, day := nowTs.UTC().Date() result := types.Time{ Time: types.FromGoTime(time.Date(year, month, day, 0, 0, 0, 0, time.UTC)), Type: mysql.TypeDate, @@ -2302,7 +2318,7 @@ func (c *utcTimestampFunctionClass) getFunction(ctx sessionctx.Context, args []E } func evalUTCTimestampWithFsp(ctx sessionctx.Context, fsp int) (types.Time, bool, error) { - nowTs, err := getSystemTimestamp(ctx) + nowTs, err := getStmtTimestamp(ctx) if err != nil { return types.Time{}, true, err } @@ -2389,9 +2405,9 @@ func (c *nowFunctionClass) getFunction(ctx sessionctx.Context, args []Expression } func evalNowWithFsp(ctx sessionctx.Context, fsp int) (types.Time, bool, error) { - sysTs, err := getSystemTimestamp(ctx) + nowTs, err := getStmtTimestamp(ctx) if err != nil { - return types.Time{}, true, errors.Trace(err) + return types.Time{}, true, err } // In MySQL's implementation, now() will truncate the result instead of rounding it. @@ -2402,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, errors.Trace(err) } @@ -4023,7 +4039,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) { - dec, err := goTimeToMysqlUnixTimestamp(time.Now(), 1) + nowTs, err := getStmtTimestamp(b.ctx) + if err != nil { + return 0, true, err + } + dec, err := goTimeToMysqlUnixTimestamp(nowTs, 1) if err != nil { return 0, true, errors.Trace(err) } @@ -6022,7 +6042,11 @@ 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) { - v, err := types.ParseDuration(b.ctx.GetSessionVars().StmtCtx, time.Now().UTC().Format(types.TimeFormat), 0) + 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 } @@ -6049,7 +6073,11 @@ 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) } - v, err := types.ParseDuration(b.ctx.GetSessionVars().StmtCtx, time.Now().UTC().Format(types.TimeFSPFormat), int(fsp)) + 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 7a7fb42199db3..23e2858feef15 100644 --- a/expression/builtin_time_test.go +++ b/expression/builtin_time_test.go @@ -762,6 +762,10 @@ func (s *testEvaluatorSuite) TestTime(c *C) { c.Assert(err, IsNil) } +func resetStmtContext(ctx sessionctx.Context) { + ctx.GetSessionVars().StmtCtx.ResetNowTs() +} + func (s *testEvaluatorSuite) TestNowAndUTCTimestamp(c *C) { defer testleak.AfterTest(c)() @@ -778,6 +782,7 @@ 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) v, err := evalBuiltinFunc(f, chunk.Row{}) @@ -789,6 +794,7 @@ 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) v, err = evalBuiltinFunc(f, chunk.Row{}) @@ -798,11 +804,13 @@ func (s *testEvaluatorSuite) TestNowAndUTCTimestamp(c *C) { c.Assert(strings.Contains(t.String(), "."), IsTrue) c.Assert(ts.Sub(gotime(t, ts.Location())), LessEqual, time.Millisecond) + resetStmtContext(s.ctx) f, err = x.fc.getFunction(s.ctx, s.datumsToConstants(types.MakeDatums(8))) c.Assert(err, IsNil) _, err = evalBuiltinFunc(f, chunk.Row{}) c.Assert(err, NotNil) + resetStmtContext(s.ctx) f, err = x.fc.getFunction(s.ctx, s.datumsToConstants(types.MakeDatums(-2))) c.Assert(err, IsNil) _, err = evalBuiltinFunc(f, chunk.Row{}) @@ -813,6 +821,7 @@ func (s *testEvaluatorSuite) TestNowAndUTCTimestamp(c *C) { variable.SetSessionSystemVar(s.ctx.GetSessionVars(), "time_zone", types.NewDatum("+00:00")) variable.SetSessionSystemVar(s.ctx.GetSessionVars(), "timestamp", types.NewDatum(1234)) fc := funcs[ast.Now] + resetStmtContext(s.ctx) f, err := fc.getFunction(s.ctx, s.datumsToConstants(nil)) c.Assert(err, IsNil) v, err := evalBuiltinFunc(f, chunk.Row{}) @@ -877,6 +886,7 @@ func (s *testEvaluatorSuite) TestAddTimeSig(c *C) { // This is a test for issue 7334 du := newDateArighmeticalUtil() + resetStmtContext(s.ctx) now, _, err := evalNowWithFsp(s.ctx, 0) c.Assert(err, IsNil) res, _, err := du.add(s.ctx, now, "1", "MICROSECOND") @@ -1203,6 +1213,7 @@ 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) v, err := evalBuiltinFunc(f, chunk.Row{}) @@ -1229,6 +1240,7 @@ 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) v, err := evalBuiltinFunc(f, chunk.Row{}) @@ -1536,6 +1548,7 @@ func (s *testEvaluatorSuite) TestTimestampDiff(c *C) { types.NewStringDatum(test.t1), types.NewStringDatum(test.t2), } + resetStmtContext(s.ctx) f, err := fc.getFunction(s.ctx, s.datumsToConstants(args)) c.Assert(err, IsNil) d, err := evalBuiltinFunc(f, chunk.Row{}) @@ -1545,6 +1558,7 @@ func (s *testEvaluatorSuite) TestTimestampDiff(c *C) { sc := s.ctx.GetSessionVars().StmtCtx sc.IgnoreTruncate = true sc.IgnoreZeroInDate = true + resetStmtContext(s.ctx) f, err := fc.getFunction(s.ctx, s.datumsToConstants([]types.Datum{types.NewStringDatum("DAY"), types.NewStringDatum("2017-01-00"), types.NewStringDatum("2017-01-01")})) @@ -1552,6 +1566,7 @@ func (s *testEvaluatorSuite) TestTimestampDiff(c *C) { d, err := evalBuiltinFunc(f, chunk.Row{}) c.Assert(d.Kind(), Equals, types.KindNull) + resetStmtContext(s.ctx) f, err = fc.getFunction(s.ctx, s.datumsToConstants([]types.Datum{types.NewStringDatum("DAY"), {}, types.NewStringDatum("2017-01-01")})) c.Assert(err, IsNil) @@ -1563,6 +1578,7 @@ 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) d, err := evalBuiltinFunc(f, chunk.Row{}) @@ -1572,12 +1588,14 @@ func (s *testEvaluatorSuite) TestUnixTimestamp(c *C) { // https://github.com/pingcap/tidb/issues/2496 // Test UNIX_TIMESTAMP(NOW()). + resetStmtContext(s.ctx) now, isNull, err := evalNowWithFsp(s.ctx, 0) c.Assert(err, IsNil) c.Assert(isNull, IsFalse) 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) d, err = evalBuiltinFunc(f, chunk.Row{}) @@ -1589,6 +1607,7 @@ func (s *testEvaluatorSuite) TestUnixTimestamp(c *C) { // https://github.com/pingcap/tidb/issues/2852 // Test UNIX_TIMESTAMP(NULL). args = []types.Datum{types.NewDatum(nil)} + resetStmtContext(s.ctx) f, err = fc.getFunction(s.ctx, s.datumsToConstants(args)) c.Assert(err, IsNil) d, err = evalBuiltinFunc(f, chunk.Row{}) @@ -1634,6 +1653,7 @@ func (s *testEvaluatorSuite) TestUnixTimestamp(c *C) { fmt.Printf("Begin Test %v\n", test) expr := s.datumsToConstants([]types.Datum{test.input}) expr[0].GetType().Decimal = test.inputDecimal + resetStmtContext(s.ctx) f, err := fc.getFunction(s.ctx, expr) c.Assert(err, IsNil, Commentf("%+v", test)) d, err := evalBuiltinFunc(f, chunk.Row{}) @@ -1803,6 +1823,7 @@ func (s *testEvaluatorSuite) TestTimestamp(c *C) { } fc := funcs[ast.Timestamp] for _, test := range tests { + resetStmtContext(s.ctx) f, err := fc.getFunction(s.ctx, s.datumsToConstants(test.t)) c.Assert(err, IsNil) d, err := evalBuiltinFunc(f, chunk.Row{}) @@ -1812,6 +1833,7 @@ func (s *testEvaluatorSuite) TestTimestamp(c *C) { } nilDatum := types.NewDatum(nil) + resetStmtContext(s.ctx) f, err := fc.getFunction(s.ctx, s.datumsToConstants([]types.Datum{nilDatum})) c.Assert(err, IsNil) d, err := evalBuiltinFunc(f, chunk.Row{}) @@ -2524,6 +2546,7 @@ 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)) d, err := evalBuiltinFunc(f, chunk.Row{}) c.Assert(err, IsNil) diff --git a/expression/helper.go b/expression/helper.go index 6c07741675e56..b3c6dfd8ba520 100644 --- a/expression/helper.go +++ b/expression/helper.go @@ -50,7 +50,7 @@ func GetTimeValue(ctx sessionctx.Context, v interface{}, tp byte, fsp int) (d ty Fsp: fsp, } - defaultTime, err := getSystemTimestamp(ctx) + defaultTime, err := getStmtTimestamp(ctx) if err != nil { return d, errors.Trace(err) } @@ -121,7 +121,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 { @@ -134,15 +136,16 @@ func getSystemTimestamp(ctx sessionctx.Context) (time.Time, error) { return now, errors.Trace(err) } - if timestampStr == "" { - return now, nil - } - timestamp, err := types.StrToInt(sessionVars.StmtCtx, timestampStr) - if err != nil { - return time.Time{}, errors.Trace(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 ed6354b719e83..75f8b494dab20 100644 --- a/sessionctx/stmtctx/stmtctx.go +++ b/sessionctx/stmtctx/stmtctx.go @@ -94,6 +94,8 @@ type StatementContext struct { RuntimeStatsColl *execdetails.RuntimeStatsColl TableIDs []int64 IndexIDs []int64 + nowTs time.Time // use this variable for now/current_timestamp calculation/cache for one stmt + stmtTimeCached bool StmtType string Tables []TableEntry OriginalSQL string @@ -104,6 +106,21 @@ type StatementContext struct { } } +// 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) {