Resolves #30779 Extend the number of errors we look for when determining whether we should invalidate the prepared statements cache.
188 lines
4.0 KiB
Go
188 lines
4.0 KiB
Go
package mysql
|
|
|
|
import (
|
|
"database/sql/driver"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
"os"
|
|
"strconv"
|
|
"syscall"
|
|
|
|
"github.com/VividCortex/mysqlerr"
|
|
"github.com/fleetdm/fleet/v4/server/contexts/ctxerr"
|
|
"github.com/fleetdm/fleet/v4/server/datastore/mysql/common_mysql"
|
|
"github.com/fleetdm/fleet/v4/server/fleet"
|
|
"github.com/go-sql-driver/mysql"
|
|
)
|
|
|
|
func notFound(kind string) *common_mysql.NotFoundError {
|
|
return &common_mysql.NotFoundError{
|
|
ResourceType: kind,
|
|
}
|
|
}
|
|
|
|
type existsError struct {
|
|
Identifier interface{}
|
|
ResourceType string
|
|
TeamID *uint
|
|
|
|
fleet.ErrorWithUUID
|
|
}
|
|
|
|
func alreadyExists(kind string, identifier interface{}) error {
|
|
if s, ok := identifier.(string); ok {
|
|
identifier = strconv.Quote(s)
|
|
}
|
|
return &existsError{
|
|
Identifier: identifier,
|
|
ResourceType: kind,
|
|
}
|
|
}
|
|
|
|
func (e *existsError) WithTeamID(teamID uint) error {
|
|
e.TeamID = &teamID
|
|
return e
|
|
}
|
|
|
|
func (e *existsError) Error() string {
|
|
msg := e.ResourceType
|
|
if e.Identifier != nil {
|
|
msg += fmt.Sprintf(" %v", e.Identifier)
|
|
}
|
|
msg += " already exists"
|
|
if e.TeamID != nil {
|
|
msg += fmt.Sprintf(" with TeamID %d", *e.TeamID)
|
|
}
|
|
return msg
|
|
}
|
|
|
|
func (e *existsError) IsExists() bool {
|
|
return true
|
|
}
|
|
|
|
func (e *existsError) Resource() string {
|
|
return e.ResourceType
|
|
}
|
|
|
|
func IsDuplicate(err error) bool {
|
|
err = ctxerr.Cause(err)
|
|
if driverErr, ok := err.(*mysql.MySQLError); ok {
|
|
if driverErr.Number == mysqlerr.ER_DUP_ENTRY {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
type foreignKeyError struct {
|
|
Name string
|
|
ResourceType string
|
|
|
|
fleet.ErrorWithUUID
|
|
}
|
|
|
|
func foreignKey(kind string, name string) error {
|
|
return &foreignKeyError{
|
|
Name: name,
|
|
ResourceType: kind,
|
|
}
|
|
}
|
|
|
|
func (e *foreignKeyError) Error() string {
|
|
return fmt.Sprintf("the operation violates a foreign key constraint on %s: %s", e.ResourceType, e.Name)
|
|
}
|
|
|
|
func (e *foreignKeyError) IsForeignKey() bool {
|
|
return true
|
|
}
|
|
|
|
func isMySQLForeignKey(err error) bool {
|
|
err = ctxerr.Cause(err)
|
|
if driverErr, ok := err.(*mysql.MySQLError); ok {
|
|
if driverErr.Number == mysqlerr.ER_ROW_IS_REFERENCED_2 {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// accessDeniedError is an error that implements StatusCode and Internal
|
|
type accessDeniedError struct {
|
|
Message string
|
|
InternalErr error
|
|
Code int
|
|
}
|
|
|
|
// Error returns the error message.
|
|
func (e *accessDeniedError) Error() string {
|
|
return e.Message
|
|
}
|
|
|
|
func (e accessDeniedError) Internal() string {
|
|
if e.InternalErr == nil {
|
|
return ""
|
|
}
|
|
return e.InternalErr.Error()
|
|
}
|
|
|
|
func (e *accessDeniedError) StatusCode() int {
|
|
if e.Code == 0 {
|
|
return http.StatusUnprocessableEntity
|
|
}
|
|
return e.Code
|
|
}
|
|
|
|
func isMySQLAccessDenied(err error) bool {
|
|
err = ctxerr.Cause(err)
|
|
var mySQLErr *mysql.MySQLError
|
|
if errors.As(
|
|
err, &mySQLErr,
|
|
) && (mySQLErr.Number == mysqlerr.ER_SPECIFIC_ACCESS_DENIED_ERROR || mySQLErr.Number == mysqlerr.ER_TABLEACCESS_DENIED_ERROR) {
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
// isBadConnection checks if the error is a connection-level error that
|
|
// justifies retrying the operation on a new connection.
|
|
func isBadConnection(err error) bool {
|
|
if err == nil {
|
|
return false
|
|
}
|
|
|
|
if errors.Is(err, driver.ErrBadConn) ||
|
|
errors.Is(err, mysql.ErrInvalidConn) ||
|
|
errors.Is(err, io.ErrUnexpectedEOF) ||
|
|
errors.Is(err, io.EOF) ||
|
|
errors.Is(err, syscall.ECONNREFUSED) ||
|
|
errors.Is(err, syscall.ECONNRESET) ||
|
|
errors.Is(err, syscall.ENETUNREACH) ||
|
|
errors.Is(err, syscall.ETIMEDOUT) {
|
|
return true
|
|
}
|
|
|
|
var mySQLErr *mysql.MySQLError
|
|
if errors.As(err, &mySQLErr) {
|
|
switch mySQLErr.Number {
|
|
case 1243, // ER_UNKNOWN_STMT_HANDLER
|
|
2006, // ER_SERVER_GONE_ERROR
|
|
1053: // ER_SERVER_SHUTDOWN
|
|
return true
|
|
}
|
|
}
|
|
|
|
// Check for underlying network errors.
|
|
var se *os.SyscallError
|
|
if errors.As(err, &se) {
|
|
return errors.Is(se.Err, syscall.ECONNRESET) || errors.Is(se.Err, syscall.EPIPE)
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
// ErrPartialResult indicates that a batch operation was completed,
|
|
// but some of the results are missing or incomplete.
|
|
var ErrPartialResult = errors.New("batch operation completed with partial results")
|