Skip to content

Commit

Permalink
types: fix string to integer cast (pingcap#11295)
Browse files Browse the repository at this point in the history
  • Loading branch information
amyangfei committed Aug 27, 2019
1 parent e2b434e commit 59b7604
Show file tree
Hide file tree
Showing 6 changed files with 73 additions and 24 deletions.
1 change: 1 addition & 0 deletions executor/executor.go
Original file line number Diff line number Diff line change
Expand Up @@ -1413,6 +1413,7 @@ func ResetContextOfStmt(ctx sessionctx.Context, s ast.StmtNode) (err error) {
sc.NotFillCache = !opts.SQLCache
}
sc.PadCharToFullLength = ctx.GetSessionVars().SQLMode.HasPadCharToFullLengthMode()
sc.CastStrToIntStrict = true
case *ast.ShowStmt:
sc.IgnoreTruncate = true
sc.IgnoreZeroInDate = true
Expand Down
4 changes: 4 additions & 0 deletions expression/builtin_cast_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,11 @@ func (s *testEvaluatorSuite) TestCast(c *C) {
c.Assert(terror.ErrorEqual(errWarnAllowedPacketOverflowed, lastWarn.Err), IsTrue, Commentf("err %v", lastWarn.Err))

origSc := sc
oldInSelectStmt := sc.InSelectStmt
sc.InSelectStmt = true
defer func() {
sc.InSelectStmt = oldInSelectStmt
}()
sc.OverflowAsWarning = true

// cast('18446744073709551616' as unsigned);
Expand Down
5 changes: 5 additions & 0 deletions sessionctx/stmtctx/stmtctx.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,11 @@ type StatementContext struct {
BatchCheck bool
InNullRejectCheck bool
AllowInvalidDate bool
// CastStrToIntStrict is used to control the way we cast float format string to int.
// If ConvertStrToIntStrict is false, we convert it to a valid float string first,
// then cast the float string to int string. Otherwise, we cast string to integer
// prefix in a strict way, only extract 0-9 and (+ or - in first bit).
CastStrToIntStrict bool

// mu struct holds variables that change during execution.
mu struct {
Expand Down
38 changes: 32 additions & 6 deletions types/convert.go
Original file line number Diff line number Diff line change
Expand Up @@ -333,11 +333,37 @@ func NumberToDuration(number int64, fsp int) (Duration, error) {

// getValidIntPrefix gets prefix of the string which can be successfully parsed as int.
func getValidIntPrefix(sc *stmtctx.StatementContext, str string) (string, error) {
floatPrefix, err := getValidFloatPrefix(sc, str)
if err != nil {
return floatPrefix, errors.Trace(err)
if !sc.CastStrToIntStrict {
floatPrefix, err := getValidFloatPrefix(sc, str)
if err != nil {
return floatPrefix, errors.Trace(err)
}
return floatStrToIntStr(sc, floatPrefix, str)
}

validLen := 0

for i := 0; i < len(str); i++ {
c := str[i]
if (c == '+' || c == '-') && i == 0 {
continue
}

if c >= '0' && c <= '9' {
validLen = i + 1
continue
}

break
}
valid := str[:validLen]
if valid == "" {
valid = "0"
}
if validLen == 0 || validLen != len(str) {
return valid, errors.Trace(handleTruncateError(sc, ErrTruncatedWrongVal.GenWithStackByArgs("INTEGER", str)))
}
return floatStrToIntStr(sc, floatPrefix, str)
return valid, nil
}

// roundIntStr is to round a **valid int string** base on the number following dot.
Expand Down Expand Up @@ -557,7 +583,7 @@ func ConvertJSONToDecimal(sc *stmtctx.StatementContext, j json.BinaryJSON) (*MyD

// getValidFloatPrefix gets prefix of string which can be successfully parsed as float.
func getValidFloatPrefix(sc *stmtctx.StatementContext, s string) (valid string, err error) {
if sc.InDeleteStmt && s == "" {
if (sc.InDeleteStmt || sc.InSelectStmt || sc.InUpdateStmt) && s == "" {
return "0", nil
}

Expand Down Expand Up @@ -601,7 +627,7 @@ func getValidFloatPrefix(sc *stmtctx.StatementContext, s string) (valid string,
valid = "0"
}
if validLen == 0 || validLen != len(s) {
err = errors.Trace(handleTruncateError(sc))
err = errors.Trace(handleTruncateError(sc, ErrTruncated))
}
return valid, err
}
Expand Down
43 changes: 28 additions & 15 deletions types/convert_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -460,28 +460,41 @@ func (s *testTypeConvertSuite) TestStrToNum(c *C) {
testStrToFloat(c, "-1e649", -math.MaxFloat64, true, ErrTruncatedWrongVal)
testStrToFloat(c, "-1e649", -math.MaxFloat64, false, nil)

// for issue #10806
testDeleteEmptyStringError(c)
// for issue #10806, #11179
testSelectUpdateDeleteEmptyStringError(c)
}

func testDeleteEmptyStringError(c *C) {
func testSelectUpdateDeleteEmptyStringError(c *C) {
testCases := []struct {
inSelect bool
inUpdate bool
inDelete bool
}{
{true, false, false},
{false, true, false},
{false, false, true},
}
sc := new(stmtctx.StatementContext)
sc.InDeleteStmt = true
for _, tc := range testCases {
sc.InSelectStmt = tc.inSelect
sc.InUpdateStmt = tc.inUpdate
sc.InDeleteStmt = tc.inDelete

str := ""
expect := 0
str := ""
expect := 0

val, err := StrToInt(sc, str)
c.Assert(err, IsNil)
c.Assert(val, Equals, int64(expect))
val, err := StrToInt(sc, str)
c.Assert(err, IsNil)
c.Assert(val, Equals, int64(expect))

val1, err := StrToUint(sc, str)
c.Assert(err, IsNil)
c.Assert(val1, Equals, uint64(expect))
val1, err := StrToUint(sc, str)
c.Assert(err, IsNil)
c.Assert(val1, Equals, uint64(expect))

val2, err := StrToFloat(sc, str)
c.Assert(err, IsNil)
c.Assert(val2, Equals, float64(expect))
val2, err := StrToFloat(sc, str)
c.Assert(err, IsNil)
c.Assert(val2, Equals, float64(expect))
}
}

func (s *testTypeConvertSuite) TestFieldTypeToStr(c *C) {
Expand Down
6 changes: 3 additions & 3 deletions types/datum.go
Original file line number Diff line number Diff line change
Expand Up @@ -1841,14 +1841,14 @@ func (ds *datumsSorter) Swap(i, j int) {
ds.datums[i], ds.datums[j] = ds.datums[j], ds.datums[i]
}

func handleTruncateError(sc *stmtctx.StatementContext) error {
func handleTruncateError(sc *stmtctx.StatementContext, err error) error {
if sc.IgnoreTruncate {
return nil
}
if !sc.TruncateAsWarning {
return ErrTruncated
return err
}
sc.AppendWarning(ErrTruncated)
sc.AppendWarning(err)
return nil
}

Expand Down

0 comments on commit 59b7604

Please sign in to comment.