From 5e3210b0d541c756c19df0ba0b9501d43fe44e70 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E8=B6=85?= Date: Fri, 20 Oct 2023 12:46:59 +0800 Subject: [PATCH] types: fix the behavior when inserting a big scientific notation number to keep it same with mysql (#47803) close pingcap/tidb#47787 --- pkg/executor/insert_test.go | 22 ++++++++++++++++++ pkg/types/convert.go | 22 +++++++++++++----- pkg/types/convert_test.go | 45 ++++++++++++++++++++++--------------- 3 files changed, 66 insertions(+), 23 deletions(-) diff --git a/pkg/executor/insert_test.go b/pkg/executor/insert_test.go index 7f8b628124..69376dc386 100644 --- a/pkg/executor/insert_test.go +++ b/pkg/executor/insert_test.go @@ -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")) +} diff --git a/pkg/types/convert.go b/pkg/types/convert.go index 97a859b487..23a72229fd 100644 --- a/pkg/types/convert.go +++ b/pkg/types/convert.go @@ -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 @@ -445,6 +445,9 @@ 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 @@ -452,7 +455,7 @@ func roundIntStr(numNextDot byte, intStr string) string { // // 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++ { @@ -500,7 +503,12 @@ 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) { @@ -508,8 +516,12 @@ func floatStrToIntStr(ctx Context, validFloat string, oriStr string) (intStr str // 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" diff --git a/pkg/types/convert_test.go b/pkg/types/convert_test.go index 23d5883964..ac25732b98 100644 --- a/pkg/types/convert_test.go +++ b/pkg/types/convert_test.go @@ -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) } }