267 lines
8.2 KiB
Go
267 lines
8.2 KiB
Go
// Copyright 2023 PingCAP, Inc.
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
package errctx
|
|
|
|
import (
|
|
"github.com/pingcap/errors"
|
|
"github.com/pingcap/tidb/pkg/errno"
|
|
contextutil "github.com/pingcap/tidb/pkg/util/context"
|
|
"github.com/pingcap/tidb/pkg/util/intest"
|
|
)
|
|
|
|
// Level defines the behavior for each error
|
|
type Level uint8
|
|
|
|
const (
|
|
// LevelError means the error will be returned
|
|
LevelError Level = iota
|
|
// LevelWarn means it will be regarded as a warning
|
|
LevelWarn
|
|
// LevelIgnore means the error will be ignored
|
|
LevelIgnore
|
|
)
|
|
|
|
// LevelMap indicates the map from `ErrGroup` to `Level`
|
|
type LevelMap [errGroupCount]Level
|
|
|
|
// Context defines how to handle an error
|
|
type Context struct {
|
|
levelMap LevelMap
|
|
warnHandler contextutil.WarnAppender
|
|
}
|
|
|
|
// LevelMap returns the `levelMap` of the context.
|
|
func (ctx *Context) LevelMap() LevelMap {
|
|
return ctx.levelMap
|
|
}
|
|
|
|
// LevelForGroup returns the level for a specified group.
|
|
func (ctx *Context) LevelForGroup(errGroup ErrGroup) Level {
|
|
return ctx.levelMap[errGroup]
|
|
}
|
|
|
|
// WithStrictErrGroupLevel makes the context to return the error directly for any kinds of errors.
|
|
func (ctx *Context) WithStrictErrGroupLevel() Context {
|
|
newCtx := Context{
|
|
warnHandler: ctx.warnHandler,
|
|
}
|
|
|
|
return newCtx
|
|
}
|
|
|
|
// WithErrGroupLevel sets a `Level` for an `ErrGroup`
|
|
func (ctx *Context) WithErrGroupLevel(eg ErrGroup, l Level) Context {
|
|
newCtx := Context{
|
|
levelMap: ctx.levelMap,
|
|
warnHandler: ctx.warnHandler,
|
|
}
|
|
newCtx.levelMap[eg] = l
|
|
|
|
return newCtx
|
|
}
|
|
|
|
// WithErrGroupLevels sets `levelMap` for an `ErrGroup`
|
|
func (ctx *Context) WithErrGroupLevels(levels LevelMap) Context {
|
|
return Context{
|
|
levelMap: levels,
|
|
warnHandler: ctx.warnHandler,
|
|
}
|
|
}
|
|
|
|
// AppendWarning appends the error to warning. If the inner `warnHandler` is nil, do nothing.
|
|
func (ctx *Context) AppendWarning(err error) {
|
|
intest.Assert(ctx.warnHandler != nil)
|
|
if w := ctx.warnHandler; w != nil {
|
|
// warnHandler should always not be nil, check fn != nil here to just make code safe.
|
|
w.AppendWarning(err)
|
|
}
|
|
}
|
|
|
|
// AppendNote appends the error to warning with level 'Note'. If the inner `warnHandler` is nil, do nothing.
|
|
func (ctx *Context) AppendNote(err error) {
|
|
intest.Assert(ctx.warnHandler != nil)
|
|
if w := ctx.warnHandler; w != nil {
|
|
// warnHandler should always not be nil, check fn != nil here to just make code safe.
|
|
w.AppendNote(err)
|
|
}
|
|
}
|
|
|
|
// HandleError handles the error according to the contextutil. See the comment of `HandleErrorWithAlias` for detailed logic.
|
|
//
|
|
// It also allows using `errors.ErrorGroup`, in this case, it'll handle each error in order, and return the first error
|
|
// it founds.
|
|
func (ctx *Context) HandleError(err error) error {
|
|
if err == nil {
|
|
return nil
|
|
}
|
|
// The function of handling `errors.ErrorGroup` is placed in `HandleError` but not in `HandleErrorWithAlias`, because
|
|
// it's hard to give a proper error and warn alias for an error group.
|
|
if errs, ok := err.(errors.ErrorGroup); ok {
|
|
for _, singleErr := range errs.Errors() {
|
|
singleErr = ctx.HandleError(singleErr)
|
|
// If the one error is found, just return it.
|
|
// TODO: consider whether it's more appropriate to continue to handle other errors. For example, other errors
|
|
// may need to append warnings. The current behavior is same with TiDB original behavior before using
|
|
// `errctx` to handle multiple errors.
|
|
if singleErr != nil {
|
|
return singleErr
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
return ctx.HandleErrorWithAlias(err, err, err)
|
|
}
|
|
|
|
// HandleErrorWithAlias handles the error according to the contextutil.
|
|
// 1. If the `internalErr` is not `"pingcap/errors".Error`, or the error code is not defined in the `errGroupMap`, or the error
|
|
// level is set to `LevelError`(0), the `err` will be returned directly.
|
|
// 2. If the error level is set to `LevelWarn`, the `warnErr` will be appended as a warning.
|
|
// 3. If the error level is set to `LevelIgnore`, this function will return a `nil`.
|
|
//
|
|
// In most cases, these three should be the same. If there are many different kinds of error internally, but they are expected
|
|
// to give the same error to users, the `err` can be different form `internalErr`. Also, if the warning is expected to be
|
|
// different from the initial error, you can also use the `warnErr` argument.
|
|
//
|
|
// TODO: is it good to give an error code for internal only errors? Or should we use another way to distinguish different
|
|
// group of errors?
|
|
// TODO: both `types.Context` and `errctx.Context` can handle truncate error now. Refractor them.
|
|
func (ctx *Context) HandleErrorWithAlias(internalErr error, err error, warnErr error) error {
|
|
if internalErr == nil {
|
|
return nil
|
|
}
|
|
|
|
internalErr = errors.Cause(internalErr)
|
|
|
|
e, ok := internalErr.(*errors.Error)
|
|
if !ok {
|
|
return err
|
|
}
|
|
|
|
eg, ok := errGroupMap[e.Code()]
|
|
if !ok {
|
|
return err
|
|
}
|
|
|
|
switch ctx.levelMap[eg] {
|
|
case LevelError:
|
|
return err
|
|
case LevelWarn:
|
|
ctx.AppendWarning(warnErr)
|
|
case LevelIgnore:
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// NewContext creates an error context to handle the errors and warnings
|
|
func NewContext(handler contextutil.WarnAppender) Context {
|
|
return NewContextWithLevels(LevelMap{}, handler)
|
|
}
|
|
|
|
// NewContextWithLevels creates an error context to handle the errors and warnings
|
|
func NewContextWithLevels(levels LevelMap, handler contextutil.WarnAppender) Context {
|
|
intest.Assert(handler != nil)
|
|
return Context{
|
|
warnHandler: handler,
|
|
levelMap: levels,
|
|
}
|
|
}
|
|
|
|
// StrictNoWarningContext returns all errors directly, and ignore all errors
|
|
var StrictNoWarningContext = NewContext(contextutil.IgnoreWarn)
|
|
|
|
var errGroupMap = make(map[errors.ErrCode]ErrGroup)
|
|
|
|
// ErrGroup groups the error according to the behavior of handling errors
|
|
type ErrGroup int
|
|
|
|
const (
|
|
// ErrGroupTruncate is the group of truncated errors
|
|
ErrGroupTruncate ErrGroup = iota
|
|
// ErrGroupDupKey is the group of duplicate key errors
|
|
ErrGroupDupKey
|
|
// ErrGroupBadNull is the group of bad null errors
|
|
ErrGroupBadNull
|
|
// ErrGroupNoDefault is the group of no default value errors
|
|
ErrGroupNoDefault
|
|
// ErrGroupDividedByZero is the group of divided by zero errors
|
|
ErrGroupDividedByZero
|
|
// ErrGroupAutoIncReadFailed is the group of auto increment read failed errors
|
|
ErrGroupAutoIncReadFailed
|
|
// ErrGroupNoMatchedPartition is the group of no partition is matched errors.
|
|
ErrGroupNoMatchedPartition
|
|
// errGroupCount is the count of all `ErrGroup`. Please leave it at the end of the list.
|
|
errGroupCount
|
|
)
|
|
|
|
func init() {
|
|
group2Errors := map[ErrGroup][]errors.ErrCode{
|
|
ErrGroupTruncate: {
|
|
errno.ErrTruncatedWrongValue,
|
|
errno.ErrDataTooLong,
|
|
errno.ErrTruncatedWrongValueForField,
|
|
errno.ErrWarnDataOutOfRange,
|
|
errno.ErrDataOutOfRange,
|
|
errno.ErrBadNumber,
|
|
errno.ErrWrongValueForType,
|
|
errno.ErrDatetimeFunctionOverflow,
|
|
errno.WarnDataTruncated,
|
|
errno.ErrIncorrectDatetimeValue,
|
|
},
|
|
ErrGroupBadNull: {
|
|
errno.ErrBadNull,
|
|
errno.ErrWarnNullToNotnull,
|
|
},
|
|
ErrGroupNoDefault: {
|
|
errno.ErrNoDefaultForField,
|
|
},
|
|
ErrGroupDividedByZero: {
|
|
errno.ErrDivisionByZero,
|
|
},
|
|
ErrGroupAutoIncReadFailed: {
|
|
errno.ErrAutoincReadFailed,
|
|
},
|
|
ErrGroupNoMatchedPartition: {
|
|
errno.ErrNoPartitionForGivenValue,
|
|
errno.ErrRowDoesNotMatchGivenPartitionSet,
|
|
},
|
|
ErrGroupDupKey: {
|
|
errno.ErrDupEntry,
|
|
},
|
|
}
|
|
|
|
for group, codes := range group2Errors {
|
|
for _, errCode := range codes {
|
|
errGroupMap[errCode] = group
|
|
}
|
|
}
|
|
}
|
|
|
|
// ResolveErrLevel resolves the error level according to the `ignore` and `warn` flags
|
|
// if ignore is true, it will return `LevelIgnore` to ignore the error,
|
|
// otherwise, it will return `LevelWarn` or `LevelError` according to the `warn` flag
|
|
// Only one of `ignore` and `warn` can be true.
|
|
func ResolveErrLevel(ignore bool, warn bool) Level {
|
|
if ignore {
|
|
return LevelIgnore
|
|
}
|
|
if warn {
|
|
return LevelWarn
|
|
}
|
|
return LevelError
|
|
}
|