vitess.io/vitess@v0.16.2/go/mysql/sql_error.go (about) 1 /* 2 Copyright 2019 The Vitess Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package mysql 18 19 import ( 20 "bytes" 21 "fmt" 22 "regexp" 23 "strconv" 24 25 "vitess.io/vitess/go/vt/sqlparser" 26 "vitess.io/vitess/go/vt/vterrors" 27 28 vtrpcpb "vitess.io/vitess/go/vt/proto/vtrpc" 29 ) 30 31 // SQLError is the error structure returned from calling a db library function 32 type SQLError struct { 33 Num int 34 State string 35 Message string 36 Query string 37 } 38 39 // NewSQLError creates a new SQLError. 40 // If sqlState is left empty, it will default to "HY000" (general error). 41 // TODO: Should be aligned with vterrors, stack traces and wrapping 42 func NewSQLError(number int, sqlState string, format string, args ...any) *SQLError { 43 if sqlState == "" { 44 sqlState = SSUnknownSQLState 45 } 46 return &SQLError{ 47 Num: number, 48 State: sqlState, 49 Message: fmt.Sprintf(format, args...), 50 } 51 } 52 53 // Error implements the error interface 54 func (se *SQLError) Error() string { 55 buf := &bytes.Buffer{} 56 buf.WriteString(se.Message) 57 58 // Add MySQL errno and SQLSTATE in a format that we can later parse. 59 // There's no avoiding string parsing because all errors 60 // are converted to strings anyway at RPC boundaries. 61 // See NewSQLErrorFromError. 62 fmt.Fprintf(buf, " (errno %v) (sqlstate %v)", se.Num, se.State) 63 64 if se.Query != "" { 65 fmt.Fprintf(buf, " during query: %s", sqlparser.TruncateForLog(se.Query)) 66 } 67 68 return buf.String() 69 } 70 71 // Number returns the internal MySQL error code. 72 func (se *SQLError) Number() int { 73 return se.Num 74 } 75 76 // SQLState returns the SQLSTATE value. 77 func (se *SQLError) SQLState() string { 78 return se.State 79 } 80 81 var errExtract = regexp.MustCompile(`.*\(errno ([0-9]*)\) \(sqlstate ([0-9a-zA-Z]{5})\).*`) 82 83 // NewSQLErrorFromError returns a *SQLError from the provided error. 84 // If it's not the right type, it still tries to get it from a regexp. 85 func NewSQLErrorFromError(err error) error { 86 if err == nil { 87 return nil 88 } 89 90 if serr, ok := err.(*SQLError); ok { 91 return serr 92 } 93 94 sErr := convertToMysqlError(err) 95 if serr, ok := sErr.(*SQLError); ok { 96 return serr 97 } 98 99 msg := err.Error() 100 match := errExtract.FindStringSubmatch(msg) 101 if len(match) >= 2 { 102 return extractSQLErrorFromMessage(match, msg) 103 } 104 105 return mapToSQLErrorFromErrorCode(err, msg) 106 } 107 108 func extractSQLErrorFromMessage(match []string, msg string) *SQLError { 109 num, err := strconv.Atoi(match[1]) 110 if err != nil { 111 return &SQLError{ 112 Num: ERUnknownError, 113 State: SSUnknownSQLState, 114 Message: msg, 115 } 116 } 117 118 return &SQLError{ 119 Num: num, 120 State: match[2], 121 Message: msg, 122 } 123 } 124 125 func mapToSQLErrorFromErrorCode(err error, msg string) *SQLError { 126 // Map vitess error codes into the mysql equivalent 127 num := ERUnknownError 128 ss := SSUnknownSQLState 129 switch vterrors.Code(err) { 130 case vtrpcpb.Code_CANCELED, vtrpcpb.Code_DEADLINE_EXCEEDED, vtrpcpb.Code_ABORTED: 131 num = ERQueryInterrupted 132 ss = SSQueryInterrupted 133 case vtrpcpb.Code_PERMISSION_DENIED, vtrpcpb.Code_UNAUTHENTICATED: 134 num = ERAccessDeniedError 135 ss = SSAccessDeniedError 136 case vtrpcpb.Code_RESOURCE_EXHAUSTED: 137 num = demuxResourceExhaustedErrors(err.Error()) 138 ss = SSClientError 139 case vtrpcpb.Code_UNIMPLEMENTED: 140 num = ERNotSupportedYet 141 ss = SSClientError 142 case vtrpcpb.Code_INTERNAL: 143 num = ERInternalError 144 ss = SSUnknownSQLState 145 } 146 147 // Not found, build a generic SQLError. 148 return &SQLError{ 149 Num: num, 150 State: ss, 151 Message: msg, 152 } 153 } 154 155 type mysqlCode struct { 156 num int 157 state string 158 } 159 160 var stateToMysqlCode = map[vterrors.State]mysqlCode{ 161 vterrors.Undefined: {num: ERUnknownError, state: SSUnknownSQLState}, 162 vterrors.AccessDeniedError: {num: ERAccessDeniedError, state: SSAccessDeniedError}, 163 vterrors.BadDb: {num: ERBadDb, state: SSClientError}, 164 vterrors.BadFieldError: {num: ERBadFieldError, state: SSBadFieldError}, 165 vterrors.BadTableError: {num: ERBadTable, state: SSUnknownTable}, 166 vterrors.CantUseOptionHere: {num: ERCantUseOptionHere, state: SSClientError}, 167 vterrors.DataOutOfRange: {num: ERDataOutOfRange, state: SSDataOutOfRange}, 168 vterrors.DbCreateExists: {num: ERDbCreateExists, state: SSUnknownSQLState}, 169 vterrors.DbDropExists: {num: ERDbDropExists, state: SSUnknownSQLState}, 170 vterrors.DupFieldName: {num: ERDupFieldName, state: SSDupFieldName}, 171 vterrors.EmptyQuery: {num: EREmptyQuery, state: SSClientError}, 172 vterrors.IncorrectGlobalLocalVar: {num: ERIncorrectGlobalLocalVar, state: SSUnknownSQLState}, 173 vterrors.InnodbReadOnly: {num: ERInnodbReadOnly, state: SSUnknownSQLState}, 174 vterrors.LockOrActiveTransaction: {num: ERLockOrActiveTransaction, state: SSUnknownSQLState}, 175 vterrors.NoDB: {num: ERNoDb, state: SSNoDB}, 176 vterrors.NoSuchTable: {num: ERNoSuchTable, state: SSUnknownTable}, 177 vterrors.NotSupportedYet: {num: ERNotSupportedYet, state: SSClientError}, 178 vterrors.ForbidSchemaChange: {num: ERForbidSchemaChange, state: SSUnknownSQLState}, 179 vterrors.MixOfGroupFuncAndFields: {num: ERMixOfGroupFuncAndFields, state: SSClientError}, 180 vterrors.NetPacketTooLarge: {num: ERNetPacketTooLarge, state: SSNetError}, 181 vterrors.NonUniqError: {num: ERNonUniq, state: SSConstraintViolation}, 182 vterrors.NonUniqTable: {num: ERNonUniqTable, state: SSClientError}, 183 vterrors.NonUpdateableTable: {num: ERNonUpdateableTable, state: SSUnknownSQLState}, 184 vterrors.QueryInterrupted: {num: ERQueryInterrupted, state: SSQueryInterrupted}, 185 vterrors.SPDoesNotExist: {num: ERSPDoesNotExist, state: SSClientError}, 186 vterrors.SyntaxError: {num: ERSyntaxError, state: SSClientError}, 187 vterrors.UnsupportedPS: {num: ERUnsupportedPS, state: SSUnknownSQLState}, 188 vterrors.UnknownSystemVariable: {num: ERUnknownSystemVariable, state: SSUnknownSQLState}, 189 vterrors.UnknownTable: {num: ERUnknownTable, state: SSUnknownTable}, 190 vterrors.WrongGroupField: {num: ERWrongGroupField, state: SSClientError}, 191 vterrors.WrongNumberOfColumnsInSelect: {num: ERWrongNumberOfColumnsInSelect, state: SSWrongNumberOfColumns}, 192 vterrors.WrongTypeForVar: {num: ERWrongTypeForVar, state: SSClientError}, 193 vterrors.WrongValueForVar: {num: ERWrongValueForVar, state: SSClientError}, 194 vterrors.WrongValue: {num: ERWrongValue, state: SSUnknownSQLState}, 195 vterrors.WrongFieldWithGroup: {num: ERWrongFieldWithGroup, state: SSClientError}, 196 vterrors.ServerNotAvailable: {num: ERServerIsntAvailable, state: SSNetError}, 197 vterrors.CantDoThisInTransaction: {num: ERCantDoThisDuringAnTransaction, state: SSCantDoThisDuringAnTransaction}, 198 vterrors.RequiresPrimaryKey: {num: ERRequiresPrimaryKey, state: SSClientError}, 199 vterrors.NoSuchSession: {num: ERUnknownComError, state: SSNetError}, 200 vterrors.OperandColumns: {num: EROperandColumns, state: SSWrongNumberOfColumns}, 201 vterrors.WrongValueCountOnRow: {num: ERWrongValueCountOnRow, state: SSWrongValueCountOnRow}, 202 } 203 204 func getStateToMySQLState(state vterrors.State) mysqlCode { 205 if state == 0 { 206 return mysqlCode{} 207 } 208 s := stateToMysqlCode[state] 209 return s 210 } 211 212 // ConvertStateToMySQLErrorCode returns MySQL error code for the given vterrors.State 213 // If the state is == 0, an empty string is returned 214 func ConvertStateToMySQLErrorCode(state vterrors.State) string { 215 s := getStateToMySQLState(state) 216 return strconv.Itoa(s.num) 217 } 218 219 // ConvertStateToMySQLState returns MySQL state for the given vterrors.State 220 // If the state is == 0, an empty string is returned 221 func ConvertStateToMySQLState(state vterrors.State) string { 222 s := getStateToMySQLState(state) 223 return s.state 224 } 225 226 func init() { 227 if len(stateToMysqlCode) != int(vterrors.NumOfStates) { 228 panic("all vterrors states are not mapped to mysql errors") 229 } 230 } 231 232 func convertToMysqlError(err error) error { 233 errState := vterrors.ErrState(err) 234 if errState == vterrors.Undefined { 235 return err 236 } 237 mysqlCode, ok := stateToMysqlCode[errState] 238 if !ok { 239 return err 240 } 241 return NewSQLError(mysqlCode.num, mysqlCode.state, err.Error()) 242 } 243 244 var isGRPCOverflowRE = regexp.MustCompile(`.*?grpc: (received|trying to send) message larger than max \(\d+ vs. \d+\)`) 245 246 func demuxResourceExhaustedErrors(msg string) int { 247 switch { 248 case isGRPCOverflowRE.Match([]byte(msg)): 249 return ERNetPacketTooLarge 250 default: 251 return ERTooManyUserConnections 252 } 253 }