Skip to content

Commit

Permalink
types: fix the behavior when inserting a big scientific notation numb…
Browse files Browse the repository at this point in the history
…er to keep it same with mysql (#47803)

close #47787
  • Loading branch information
lcwangchao authored Oct 20, 2023
1 parent 3c461dd commit 5e3210b
Show file tree
Hide file tree
Showing 3 changed files with 66 additions and 23 deletions.
22 changes: 22 additions & 0 deletions pkg/executor/insert_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1593,3 +1593,25 @@ func TestInsertLockUnchangedKeys(t *testing.T) {
}
}
}

// see issue https://github.com/pingcap/tidb/issues/47787
func TestInsertBigScientificNotation(t *testing.T) {
store := testkit.CreateMockStore(t)
tk := testkit.NewTestKit(t, store)
tk.MustExec(`use test`)
tk.MustExec("create table t1(id int, a int)")

tk.MustExec("set @@SQL_MODE='STRICT_TRANS_TABLES'")
err := tk.ExecToErr("insert into t1 values(1, '1e100')")
require.EqualError(t, err, "[types:1264]Out of range value for column 'a' at row 1")
err = tk.ExecToErr("insert into t1 values(2, '-1e100')")
require.EqualError(t, err, "[types:1264]Out of range value for column 'a' at row 1")
tk.MustQuery("select id, a from t1").Check(testkit.Rows())

tk.MustExec("set @@SQL_MODE=''")
tk.MustExec("insert into t1 values(1, '1e100')")
tk.MustQuery("show warnings").Check(testkit.Rows("Warning 1264 Out of range value for column 'a' at row 1"))
tk.MustExec("insert into t1 values(2, '-1e100')")
tk.MustQuery("show warnings").Check(testkit.Rows("Warning 1264 Out of range value for column 'a' at row 1"))
tk.MustQuery("select id, a from t1 order by id asc").Check(testkit.Rows("1 2147483647", "2 -2147483648"))
}
22 changes: 17 additions & 5 deletions pkg/types/convert.go
Original file line number Diff line number Diff line change
Expand Up @@ -389,7 +389,7 @@ func getValidIntPrefix(ctx Context, str string, isFuncCast bool) (string, error)
if err != nil {
return floatPrefix, errors.Trace(err)
}
return floatStrToIntStr(ctx, floatPrefix, str)
return floatStrToIntStr(floatPrefix, str)
}

validLen := 0
Expand Down Expand Up @@ -445,14 +445,17 @@ func roundIntStr(numNextDot byte, intStr string) string {
return string(retStr)
}

var maxUintStr = strconv.FormatUint(math.MaxUint64, 10)
var minIntStr = strconv.FormatInt(math.MinInt64, 10)

// floatStrToIntStr converts a valid float string into valid integer string which can be parsed by
// strconv.ParseInt, we can't parse float first then convert it to string because precision will
// be lost. For example, the string value "18446744073709551615" which is the max number of unsigned
// int will cause some precision to lose. intStr[0] may be a positive and negative sign like '+' or '-'.
//
// This func will find serious overflow such as the len of intStr > 20 (without prefix `+/-`)
// however, it will not check whether the intStr overflow BIGINT.
func floatStrToIntStr(ctx Context, validFloat string, oriStr string) (intStr string, _ error) {
func floatStrToIntStr(validFloat string, oriStr string) (intStr string, _ error) {
var dotIdx = -1
var eIdx = -1
for i := 0; i < len(validFloat); i++ {
Expand Down Expand Up @@ -500,16 +503,25 @@ func floatStrToIntStr(ctx Context, validFloat string, oriStr string) (intStr str
}
exp, err := strconv.Atoi(validFloat[eIdx+1:])
if err != nil {
return validFloat, errors.Trace(err)
if digits[0] == '-' {
intStr = minIntStr
} else {
intStr = maxUintStr
}
return intStr, ErrOverflow.GenWithStackByArgs("BIGINT", oriStr)
}
intCnt += exp
if exp >= 0 && (intCnt > 21 || intCnt < 0) {
// MaxInt64 has 19 decimal digits.
// MaxUint64 has 20 decimal digits.
// And the intCnt may contain the len of `+/-`,
// so I use 21 here as the early detection.
ctx.AppendWarning(ErrOverflow.GenWithStackByArgs("BIGINT", oriStr))
return validFloat[:eIdx], nil
if digits[0] == '-' {
intStr = minIntStr
} else {
intStr = maxUintStr
}
return intStr, ErrOverflow.GenWithStackByArgs("BIGINT", oriStr)
}
if intCnt <= 0 {
intStr = "0"
Expand Down
45 changes: 27 additions & 18 deletions pkg/types/convert_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -974,26 +974,35 @@ func TestGetValidFloat(t *testing.T) {
tests2 := []struct {
origin string
expected string
overflow bool
}{
{"1e9223372036854775807", "1"},
{"125e342", "125"},
{"1e21", "1"},
{"1e5", "100000"},
{"-123.45678e5", "-12345678"},
{"+0.5", "1"},
{"-0.5", "-1"},
{".5e0", "1"},
{"+.5e0", "+1"},
{"-.5e0", "-1"},
{".5", "1"},
{"123.456789e5", "12345679"},
{"123.456784e5", "12345678"},
{"+999.9999e2", "+100000"},
{"1e29223372036854775807", "18446744073709551615", true},
{"1e9223372036854775807", "18446744073709551615", true},
{"125e342", "18446744073709551615", true},
{"1e21", "18446744073709551615", true},
{"-1e29223372036854775807", "-9223372036854775808", true},
{"-1e9223372036854775807", "-9223372036854775808", true},
{"1e5", "100000", false},
{"-123.45678e5", "-12345678", false},
{"+0.5", "1", false},
{"-0.5", "-1", false},
{".5e0", "1", false},
{"+.5e0", "+1", false},
{"-.5e0", "-1", false},
{".5", "1", false},
{"123.456789e5", "12345679", false},
{"123.456784e5", "12345678", false},
{"+999.9999e2", "+100000", false},
}
for _, tt := range tests2 {
str, err := floatStrToIntStr(ctx, tt.origin, tt.origin)
require.NoError(t, err)
require.Equalf(t, tt.expected, str, "%v, %v", tt.origin, tt.expected)
for i, tt := range tests2 {
msg := fmt.Sprintf("%d: %v, %v", i, tt.origin, tt.expected)
str, err := floatStrToIntStr(tt.origin, tt.origin)
if tt.overflow {
require.True(t, terror.ErrorEqual(err, ErrOverflow), msg)
} else {
require.NoError(t, err, msg)
}
require.Equalf(t, tt.expected, str, msg)
}
}

Expand Down

0 comments on commit 5e3210b

Please sign in to comment.