Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

executor, expression: fix current_timestamp/now not consistent in one stmt like MySQL (#11342) #11392

Merged
merged 4 commits into from
Jul 23, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions executor/write_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"))
}
50 changes: 39 additions & 11 deletions expression/builtin_time.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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)
Expand All @@ -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)
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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
}
Expand Down Expand Up @@ -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.
Expand All @@ -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)
}
Expand Down Expand Up @@ -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)
}
Expand Down Expand Up @@ -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
}

Expand All @@ -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
}

Expand Down
23 changes: 23 additions & 0 deletions expression/builtin_time_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)()

Expand All @@ -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{})
Expand All @@ -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{})
Expand All @@ -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{})
Expand All @@ -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{})
Expand Down Expand Up @@ -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")
Expand Down Expand Up @@ -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{})
Expand All @@ -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{})
Expand Down Expand Up @@ -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{})
Expand All @@ -1545,13 +1558,15 @@ 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")}))
c.Assert(err, IsNil)
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)
Expand All @@ -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{})
Expand All @@ -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{})
Expand All @@ -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{})
Expand Down Expand Up @@ -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{})
Expand Down Expand Up @@ -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{})
Expand All @@ -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{})
Expand Down Expand Up @@ -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)
Expand Down
27 changes: 15 additions & 12 deletions expression/helper.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand Down Expand Up @@ -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 {
Expand All @@ -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
}
17 changes: 17 additions & 0 deletions sessionctx/stmtctx/stmtctx.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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) {
Expand Down