Skip to content

Commit

Permalink
expression: fix TIMESTAMP func get wrong result with decimal (#15185)
Browse files Browse the repository at this point in the history
The TIMESTAMP function deal with argument of decimal/float will get
wrong result. This commit fix that issue by redirect method call of
parseDateTime() to ParseDatetimeFromNum() when the flag "isFloat" is
true.

The reason is that the parse logic of datetime is different when
dealing with strings and numbers. In short, when dealing with numbers,
if number length are not 6, 8, 12, 14, the parse logic should padded
with leading zeros to the closest length. But when dealing with strings,
the parse logic will interpreted from left to right to find year, month,
day, hour... for as many parts as are present in the string.

For more details, see
https://dev.mysql.com/doc/refman/5.7/en/date-and-time-literals.html.
  • Loading branch information
LENSHOOD committed Sep 21, 2020
1 parent cc3d9db commit d431b13
Show file tree
Hide file tree
Showing 4 changed files with 96 additions and 8 deletions.
2 changes: 1 addition & 1 deletion expression/builtin_time.go
Original file line number Diff line number Diff line change
Expand Up @@ -4871,7 +4871,7 @@ func (c *timestampFunctionClass) getFunction(ctx sessionctx.Context, args []Expr
}
isFloat := false
switch args[0].GetType().Tp {
case mysql.TypeFloat, mysql.TypeDouble, mysql.TypeNewDecimal:
case mysql.TypeFloat, mysql.TypeDouble, mysql.TypeNewDecimal, mysql.TypeLonglong:
isFloat = true
}
bf, err := newBaseBuiltinFuncWithTp(ctx, c.funcName, args, types.ETDatetime, evalTps...)
Expand Down
38 changes: 38 additions & 0 deletions expression/integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2161,6 +2161,44 @@ func (s *testIntegrationSuite2) TestTimeBuiltin(c *C) {
result = tk.MustQuery("select time(\"-- --1\");")
result.Check(testkit.Rows("00:00:00"))
tk.MustQuery("show warnings;").Check(testkit.Rows("Warning 1292 Truncated incorrect time value: '-- --1'"))

// fix issue #15185
result = tk.MustQuery(`select timestamp(11111.1111)`)
result.Check(testkit.Rows("2001-11-11 00:00:00.0000"))
result = tk.MustQuery(`select timestamp(cast(11111.1111 as decimal(60, 5)))`)
result.Check(testkit.Rows("2001-11-11 00:00:00.00000"))
result = tk.MustQuery(`select timestamp(1021121141105.4324)`)
result.Check(testkit.Rows("0102-11-21 14:11:05.4324"))
result = tk.MustQuery(`select timestamp(cast(1021121141105.4324 as decimal(60, 5)))`)
result.Check(testkit.Rows("0102-11-21 14:11:05.43240"))
result = tk.MustQuery(`select timestamp(21121141105.101)`)
result.Check(testkit.Rows("2002-11-21 14:11:05.101"))
result = tk.MustQuery(`select timestamp(cast(21121141105.101 as decimal(60, 5)))`)
result.Check(testkit.Rows("2002-11-21 14:11:05.10100"))
result = tk.MustQuery(`select timestamp(1121141105.799055)`)
result.Check(testkit.Rows("2000-11-21 14:11:05.799055"))
result = tk.MustQuery(`select timestamp(cast(1121141105.799055 as decimal(60, 5)))`)
result.Check(testkit.Rows("2000-11-21 14:11:05.79906"))
result = tk.MustQuery(`select timestamp(121141105.123)`)
result.Check(testkit.Rows("2000-01-21 14:11:05.123"))
result = tk.MustQuery(`select timestamp(cast(121141105.123 as decimal(60, 5)))`)
result.Check(testkit.Rows("2000-01-21 14:11:05.12300"))
result = tk.MustQuery(`select timestamp(1141105)`)
result.Check(testkit.Rows("0114-11-05 00:00:00"))
result = tk.MustQuery(`select timestamp(cast(1141105 as decimal(60, 5)))`)
result.Check(testkit.Rows("0114-11-05 00:00:00.00000"))
result = tk.MustQuery(`select timestamp(41105.11)`)
result.Check(testkit.Rows("2004-11-05 00:00:00.00"))
result = tk.MustQuery(`select timestamp(cast(41105.11 as decimal(60, 5)))`)
result.Check(testkit.Rows("2004-11-05 00:00:00.00000"))
result = tk.MustQuery(`select timestamp(1105.3)`)
result.Check(testkit.Rows("2000-11-05 00:00:00.0"))
result = tk.MustQuery(`select timestamp(cast(1105.3 as decimal(60, 5)))`)
result.Check(testkit.Rows("2000-11-05 00:00:00.00000"))
result = tk.MustQuery(`select timestamp(105)`)
result.Check(testkit.Rows("2000-01-05 00:00:00"))
result = tk.MustQuery(`select timestamp(cast(105 as decimal(60, 5)))`)
result.Check(testkit.Rows("2000-01-05 00:00:00.00000"))
}

func (s *testIntegrationSuite) TestOpBuiltin(c *C) {
Expand Down
27 changes: 22 additions & 5 deletions types/time.go
Original file line number Diff line number Diff line change
Expand Up @@ -816,6 +816,28 @@ func parseDatetime(sc *stmtctx.StatementContext, str string, fsp int8, isFloat b
switch len(seps) {
case 1:
l := len(seps[0])
// Values specified as numbers
if isFloat {
numOfTime, err := StrToInt(sc, seps[0], false)
if err != nil {
return ZeroDatetime, errors.Trace(ErrWrongValue.GenWithStackByArgs(DateTimeStr, str))
}

dateTime, err := ParseDatetimeFromNum(sc, numOfTime)
if err != nil {
return ZeroDatetime, errors.Trace(ErrWrongValue.GenWithStackByArgs(DateTimeStr, str))
}

year, month, day, hour, minute, second =
dateTime.Year(), dateTime.Month(), dateTime.Day(), dateTime.Hour(), dateTime.Minute(), dateTime.Second()
if l >= 9 && l <= 14 {
hhmmss = true
}

break
}

// Values specified as strings
switch l {
case 14: // No delimiter.
// YYYYMMDDHHMMSS
Expand Down Expand Up @@ -1613,11 +1635,6 @@ func parseDateTimeFromNum(sc *stmtctx.StatementContext, num int64) (Time, error)
return getTime(sc, num, t.Type())
}

// Check YYYYMMDD.
if num < 10000101 {
return t, errors.Trace(ErrWrongValue.GenWithStackByArgs(TimeStr, strconv.FormatInt(num, 10)))
}

// Adjust hour/min/second.
if num <= 99991231 {
num = num * 1000000
Expand Down
37 changes: 35 additions & 2 deletions types/time_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -614,7 +614,7 @@ func (s *testTimeSuite) TestParseTimeFromNum(c *C) {
{2010101011, true, types.ZeroDatetimeStr, true, types.ZeroDatetimeStr, true, types.ZeroDateStr},
{201010101, false, "2000-02-01 01:01:01", false, "2000-02-01 01:01:01", false, "2000-02-01"},
{20101010, false, "2010-10-10 00:00:00", false, "2010-10-10 00:00:00", false, "2010-10-10"},
{2010101, true, types.ZeroDatetimeStr, true, types.ZeroDatetimeStr, true, types.ZeroDateStr},
{2010101, false, "0201-01-01 00:00:00", true, types.ZeroDatetimeStr, false, "0201-01-01"},
{201010, false, "2020-10-10 00:00:00", false, "2020-10-10 00:00:00", false, "2020-10-10"},
{20101, false, "2002-01-01 00:00:00", false, "2002-01-01 00:00:00", false, "2002-01-01"},
{2010, true, types.ZeroDatetimeStr, true, types.ZeroDatetimeStr, true, types.ZeroDateStr},
Expand Down Expand Up @@ -747,6 +747,39 @@ func (s *testTimeSuite) TestToNumber(c *C) {
}
}

func (s *testTimeSuite) TestParseTimeFromFloatString(c *C) {
sc := mock.NewContext().GetSessionVars().StmtCtx
sc.IgnoreZeroInDate = true
defer testleak.AfterTest(c)()
table := []struct {
Input string
Fsp int8
ExpectError bool
Expect string
}{
{"20170118.123", 3, false, "2017-01-18 00:00:00.000"},
{"121231113045.123345", 6, false, "2012-12-31 11:30:45.123345"},
{"20121231113045.123345", 6, false, "2012-12-31 11:30:45.123345"},
{"121231113045.9999999", 6, false, "2012-12-31 11:30:46.000000"},
{"170105084059.575601", 6, false, "2017-01-05 08:40:59.575601"},
{"201705051315111.22", 2, true, "0000-00-00 00:00:00.00"},
{"2011110859.1111", 4, true, "0000-00-00 00:00:00.0000"},
{"2011110859.1111", 4, true, "0000-00-00 00:00:00.0000"},
{"191203081.1111", 4, true, "0000-00-00 00:00:00.0000"},
{"43128.121105", 6, true, "0000-00-00 00:00:00.000000"},
}

for _, test := range table {
t, err := types.ParseTimeFromFloatString(sc, test.Input, mysql.TypeDatetime, test.Fsp)
if test.ExpectError {
c.Assert(err, NotNil)
} else {
c.Assert(err, IsNil)
c.Assert(t.String(), Equals, test.Expect)
}
}
}

func (s *testTimeSuite) TestParseFrac(c *C) {
defer testleak.AfterTest(c)()
tbl := []struct {
Expand Down Expand Up @@ -1027,7 +1060,7 @@ func (s *testTimeSuite) TestParseDateFormat(c *C) {
}
}

func (s *testTimeSuite) TestTamestampDiff(c *C) {
func (s *testTimeSuite) TestTimestampDiff(c *C) {
tests := []struct {
unit string
t1 types.CoreTime
Expand Down

0 comments on commit d431b13

Please sign in to comment.