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  }