diff --git a/go.mod b/go.mod index 7397de709..d93216f64 100644 --- a/go.mod +++ b/go.mod @@ -9,10 +9,10 @@ require ( github.com/cznic/y v0.0.0-20170802143616-045f81c6662a github.com/go-sql-driver/mysql v1.5.0 github.com/pingcap/check v0.0.0-20190102082844-67f458068fc8 - github.com/pingcap/errors v0.11.4 - github.com/pingcap/log v0.0.0-20191012051959-b742a5d432e9 + github.com/pingcap/errors v0.11.5-0.20200902104258-eba4f1d8f6de + github.com/pingcap/log v0.0.0-20200511115504-543df19646ad github.com/remyoudompheng/bigfft v0.0.0-20190728182440-6a916e37a237 // indirect - go.uber.org/zap v1.12.0 + go.uber.org/zap v1.15.0 golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2 ) diff --git a/go.sum b/go.sum index fd5d8ba48..f13422fb4 100644 --- a/go.sum +++ b/go.sum @@ -29,10 +29,15 @@ github.com/pingcap/check v0.0.0-20190102082844-67f458068fc8/go.mod h1:B1+S9LNcuM github.com/pingcap/errors v0.11.0/go.mod h1:Oi8TUi2kEtXXLMJk9l1cGmz20kV3TaQ0usTwv5KuLY8= github.com/pingcap/errors v0.11.4 h1:lFuQV/oaUMGcD2tqt+01ROSmJs75VG1ToEOkZIZ4nE4= github.com/pingcap/errors v0.11.4/go.mod h1:Oi8TUi2kEtXXLMJk9l1cGmz20kV3TaQ0usTwv5KuLY8= +github.com/pingcap/errors v0.11.5-0.20200902104258-eba4f1d8f6de h1:mW8hC2yXTpflfyTeJgcN4aJQfwcYODde8YgjBgAy6do= +github.com/pingcap/errors v0.11.5-0.20200902104258-eba4f1d8f6de/go.mod h1:g4vx//d6VakjJ0mk7iLBlKA8LFavV/sAVINT/1PFxeQ= github.com/pingcap/log v0.0.0-20191012051959-b742a5d432e9 h1:AJD9pZYm72vMgPcQDww9rkZ1DnWfl0pXV3BOWlkYIjA= github.com/pingcap/log v0.0.0-20191012051959-b742a5d432e9/go.mod h1:4rbK1p9ILyIfb6hU7OG2CiWSqMXnp3JMbiaVJ6mvoY8= +github.com/pingcap/log v0.0.0-20200511115504-543df19646ad h1:SveG82rmu/GFxYanffxsSF503SiQV+2JLnWEiGiF+Tc= +github.com/pingcap/log v0.0.0-20200511115504-543df19646ad/go.mod h1:4rbK1p9ILyIfb6hU7OG2CiWSqMXnp3JMbiaVJ6mvoY8= github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/remyoudompheng/bigfft v0.0.0-20190728182440-6a916e37a237 h1:HQagqIiBmr8YXawX/le3+O26N+vPPC1PtjaF3mwnook= @@ -45,14 +50,20 @@ github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81P go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.5.0 h1:OI5t8sDa1Or+q8AeE+yKeB/SDYioSHAgcVljj9JIETY= go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= +go.uber.org/atomic v1.6.0 h1:Ezj3JGmsOnG1MoRWQkPBsKLe9DwWD9QeXzTRzzldNVk= +go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= go.uber.org/multierr v1.3.0 h1:sFPn2GLc3poCkfrpIXGhBD2X0CMIo4Q/zSULXrj/+uc= go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4= +go.uber.org/multierr v1.5.0 h1:KCa4XfM8CWFCpxXRGok+Q0SS/0XBhMDbHHGABQLvD2A= +go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU= go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee h1:0mgffUl7nfd+FpvXMVz4IDEaUSmT1ysygQC7qYo7sG4= go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= go.uber.org/zap v1.12.0 h1:dySoUQPFBGj6xwjmBzageVL8jGi8uxc6bEmJQjA06bw= go.uber.org/zap v1.12.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM= +go.uber.org/zap v1.15.0 h1:ZZCA22JRF2gQE5FoNmhmrf7jeJJ2uhqDUNRYKm8dvmM= +go.uber.org/zap v1.15.0/go.mod h1:Mb2vm2krFEG5DV0W9qcHBYFtp/Wku1cvYaqPsS/WYfc= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/lint v0.0.0-20190930215403-16217165b5de h1:5hukYrvBGR8/eNkX5mdUezrA6JiaEZDtJb9Ei+1LlBs= diff --git a/terror/terror.go b/terror/terror.go index f15415637..67e3a13bf 100644 --- a/terror/terror.go +++ b/terror/terror.go @@ -14,10 +14,9 @@ package terror import ( - "encoding/json" "fmt" - "os" "strconv" + "strings" "github.com/pingcap/errors" "github.com/pingcap/log" @@ -51,6 +50,8 @@ const ( // ErrClass represents a class of errors. type ErrClass int +type Error = errors.Error + // Error classes. var ( ClassAutoid = RegisterErrorClass(1, "autoid") @@ -85,6 +86,7 @@ var ( var errClass2Desc = make(map[ErrClass]string) var errCodeMap = make(map[ErrCode]*Error) +var rfcCode2errClass = make(map[string]ErrClass) // RegisterErrorClass registers new error class for terror. func RegisterErrorClass(classCode int, desc string) ErrClass { @@ -111,7 +113,12 @@ func (ec ErrClass) EqualClass(err error) bool { return false } if te, ok := e.(*Error); ok { - return te.class == ec + rfcCode := te.RFCCode() + if index := strings.Index(string(rfcCode), ":"); index > 0 { + if class, has := rfcCode2errClass[string(rfcCode)[:index]]; has { + return class == ec + } + } } return false } @@ -121,23 +128,36 @@ func (ec ErrClass) NotEqualClass(err error) bool { return !ec.EqualClass(err) } -// New defines an *Error with an error code and an error message. -// Usually used to create base *Error. -// Attention: -// this method is not goroutine-safe and -// usually be used in global variable initializer -func (ec ErrClass) New(code ErrCode, message string) *Error { +func (ec ErrClass) initError(code ErrCode) string { clsMap, ok := ErrClassToMySQLCodes[ec] if !ok { clsMap = make(map[ErrCode]struct{}) ErrClassToMySQLCodes[ec] = clsMap } clsMap[code] = struct{}{} - err := &Error{ - class: ec, - code: code, - message: message, - } + class := errClass2Desc[ec] + rfcCode := fmt.Sprintf("%s:%d", class, code) + rfcCode2errClass[class] = ec + return rfcCode +} + +// New defines an *Error with an error code and an error message. +// Usually used to create base *Error. +// Attention: +// this method is not goroutine-safe and +// usually be used in global variable initializer +func (ec ErrClass) New(code ErrCode, message string) *Error { + rfcCode := ec.initError(code) + err := errors.Normalize(message, errors.MySQLErrorCode(int(code)), errors.RFCCodeText(rfcCode)) + errCodeMap[code] = err + return err +} + +// NewStdErr defines an *Error with an error code, an error +// message and workaround to create standard error. +func (ec ErrClass) NewStdErr(code ErrCode, message string, desc string, workaround string) *Error { + rfcCode := ec.initError(code) + err := errors.Normalize(message, errors.MySQLErrorCode(int(code)), errors.RFCCodeText(rfcCode), errors.Description(desc), errors.Workaround(workaround)) errCodeMap[code] = err return err } @@ -155,162 +175,39 @@ func (ec ErrClass) NewStd(code ErrCode) *Error { // so it's goroutine-safe // and often be used to create Error came from other systems like TiKV. func (ec ErrClass) Synthesize(code ErrCode, message string) *Error { - return &Error{ - class: ec, - code: code, - message: message, - } -} - -// Error implements error interface and adds integer Class and Code, so -// errors with different message can be compared. -type Error struct { - class ErrClass - code ErrCode - message string - workaround string - args []interface{} - file string - line int -} - -// Class returns ErrClass -func (e *Error) Class() ErrClass { - return e.class -} - -// Code returns ErrCode -func (e *Error) Code() ErrCode { - return e.code -} - -// SetWorkaround is a decorator like method which add a workaround to -// error which is convenient for user to search. -func (e *Error) SetWorkaround(workaround string) *Error { - e.workaround = workaround - return e -} - -// MarshalJSON implements json.Marshaler interface. -func (e *Error) MarshalJSON() ([]byte, error) { - return json.Marshal(&struct { - Class ErrClass `json:"class"` - Code ErrCode `json:"code"` - Msg string `json:"message"` - }{ - Class: e.class, - Code: e.code, - Msg: e.getMsg(), - }) -} - -// UnmarshalJSON implements json.Unmarshaler interface. -func (e *Error) UnmarshalJSON(data []byte) error { - err := &struct { - Class ErrClass `json:"class"` - Code ErrCode `json:"code"` - Msg string `json:"message"` - }{} - - if err := json.Unmarshal(data, &err); err != nil { - return errors.Trace(err) - } - - e.class = err.Class - e.code = err.Code - e.message = err.Msg - return nil -} - -// Location returns the location where the error is created, -// implements juju/errors locationer interface. -func (e *Error) Location() (file string, line int) { - return e.file, e.line -} - -// Error implements error interface. -func (e *Error) Error() string { - return fmt.Sprintf("[%s:%d]%s", e.class, e.code, e.getMsg()) -} - -func (e *Error) getMsg() string { - if len(e.args) > 0 { - return fmt.Sprintf(e.message, e.args...) - } - return e.message -} - -// GenWithStack generates a new *Error with the same class and code, and a new formatted message. -func (e *Error) GenWithStack(format string, args ...interface{}) error { - err := *e - err.message = format - err.args = args - return errors.AddStack(&err) -} - -// GenWithStackByArgs generates a new *Error with the same class and code, and new arguments. -func (e *Error) GenWithStackByArgs(args ...interface{}) error { - err := *e - err.args = args - return errors.AddStack(&err) -} - -// FastGen generates a new *Error with the same class and code, and a new formatted message. -// This will not call runtime.Caller to get file and line. -func (e *Error) FastGen(format string, args ...interface{}) error { - err := *e - err.message = format - err.args = args - return errors.SuspendStack(&err) -} - -// FastGen generates a new *Error with the same class and code, and a new arguments. -// This will not call runtime.Caller to get file and line. -func (e *Error) FastGenByArgs(args ...interface{}) error { - err := *e - err.args = args - return errors.SuspendStack(&err) -} - -// Equal checks if err is equal to e. -func (e *Error) Equal(err error) bool { - originErr := errors.Cause(err) - if originErr == nil { - return false - } - - if error(e) == originErr { - return true - } - inErr, ok := originErr.(*Error) - return ok && e.class == inErr.class && e.code == inErr.code -} - -// NotEqual checks if err is not equal to e. -func (e *Error) NotEqual(err error) bool { - return !e.Equal(err) + return errors.Normalize(message, errors.MySQLErrorCode(int(code)), errors.RFCCodeText(fmt.Sprintf("%s:%d", errClass2Desc[ec], code))) } // ToSQLError convert Error to mysql.SQLError. -func (e *Error) ToSQLError() *mysql.SQLError { - code := e.getMySQLErrorCode() - return mysql.NewErrf(code, "%s", e.getMsg()) +func ToSQLError(e *Error) *mysql.SQLError { + code := getMySQLErrorCode(e) + return mysql.NewErrf(code, "%s", e.GetMsg()) } var defaultMySQLErrorCode uint16 -func (e *Error) getMySQLErrorCode() uint16 { - codeMap, ok := ErrClassToMySQLCodes[e.class] +func getMySQLErrorCode(e *Error) uint16 { + rfcCode := e.RFCCode() + var class ErrClass + if index := strings.Index(string(rfcCode), ":"); index > 0 { + if ec, has := rfcCode2errClass[string(rfcCode)[:index]]; has { + class = ec + } else { + log.Warn("Unknown error class", zap.String("class", string(rfcCode)[:index])) + return defaultMySQLErrorCode + } + } + codeMap, ok := ErrClassToMySQLCodes[class] if !ok { - log.Warn("Unknown error class", zap.Int("class", int(e.class))) + log.Warn("Unknown error class", zap.Int("class", int(class))) return defaultMySQLErrorCode } - _, ok = codeMap[e.code] + _, ok = codeMap[ErrCode(e.Code())] if !ok { - log.Debug("Unknown error code", zap.Int("class", int(e.class)), zap.Int("code", int(e.code))) + log.Debug("Unknown error code", zap.Int("class", int(class)), zap.Int("code", int(e.Code()))) return defaultMySQLErrorCode } - return uint16(e.code) + return uint16(e.Code()) } var ( @@ -340,7 +237,7 @@ func ErrorEqual(err1, err2 error) bool { te1, ok1 := e1.(*Error) te2, ok2 := e2.(*Error) if ok1 && ok2 { - return te1.class == te2.class && te1.code == te2.code + return te1.RFCCode() == te2.RFCCode() } return e1.Error() == e2.Error() @@ -376,20 +273,12 @@ func Log(err error) { } } -// ExportErrorCodeAndWorkaround is used to produce error workaround. -func ExportErrorCodeAndWorkaround(fileName string) error { - file, err := os.Create(fileName) - if err != nil { - return err - } - for code, e := range errCodeMap { - workaround := fmt.Sprintf( - "[error.%v]\nerror = '''%v'''\nworkaround = '''%v'''\n\n", - code, e.message, e.workaround) - _, err = file.WriteString(workaround) - if err != nil { - return err +func GetErrClass(e *Error) ErrClass { + rfcCode := e.RFCCode() + if index := strings.Index(string(rfcCode), ":"); index > 0 { + if class, has := rfcCode2errClass[string(rfcCode)[:index]]; has { + return class } } - return nil + return ErrClass(-1) } diff --git a/terror/terror_test.go b/terror/terror_test.go index 5a109ca4e..450e91727 100644 --- a/terror/terror_test.go +++ b/terror/terror_test.go @@ -65,7 +65,7 @@ func (s *testTErrorSuite) TestTError(c *C) { kvErr := ClassKV.New(1062, "key already exist") e := kvErr.FastGen("Duplicate entry '%d' for key 'PRIMARY'", 1) c.Assert(e.Error(), Equals, "[kv:1062]Duplicate entry '1' for key 'PRIMARY'") - sqlErr := errors.Cause(e).(*Error).ToSQLError() + sqlErr := ToSQLError(errors.Cause(e).(*Error)) c.Assert(sqlErr.Message, Equals, "Duplicate entry '1' for key 'PRIMARY'") c.Assert(sqlErr.Code, Equals, uint16(1062)) @@ -77,14 +77,10 @@ func (s *testTErrorSuite) TestTError(c *C) { } func (s *testTErrorSuite) TestJson(c *C) { - prevTErr := &Error{ - class: ClassTable, - code: CodeExecResultIsEmpty, - message: "json test", - } + prevTErr := errors.Normalize("json test", errors.MySQLErrorCode(int(CodeExecResultIsEmpty))) buf, err := json.Marshal(prevTErr) c.Assert(err, IsNil) - var curTErr Error + var curTErr errors.Error err = json.Unmarshal(buf, &curTErr) c.Assert(err, IsNil) isEqual := prevTErr.Equal(&curTErr)