Files
tidb/pkg/util/context/warn.go

311 lines
9.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 context
import (
"encoding/json"
"math"
"sync"
"github.com/pingcap/errors"
"github.com/pingcap/tidb/pkg/parser/terror"
)
const (
// WarnLevelError represents level "Error" for 'SHOW WARNINGS' syntax.
WarnLevelError = "Error"
// WarnLevelWarning represents level "Warning" for 'SHOW WARNINGS' syntax.
WarnLevelWarning = "Warning"
// WarnLevelNote represents level "Note" for 'SHOW WARNINGS' syntax.
WarnLevelNote = "Note"
)
// SQLWarn relates a sql warning and it's level.
type SQLWarn struct {
Level string
Err error
}
type jsonSQLWarn struct {
Level string `json:"level"`
SQLErr *terror.Error `json:"err,omitempty"`
Msg string `json:"msg,omitempty"`
}
// MarshalJSON implements the Marshaler.MarshalJSON interface.
func (warn *SQLWarn) MarshalJSON() ([]byte, error) {
w := &jsonSQLWarn{
Level: warn.Level,
}
e := errors.Cause(warn.Err)
switch x := e.(type) {
case *terror.Error:
// Omit outter errors because only the most inner error matters.
w.SQLErr = x
default:
w.Msg = e.Error()
}
return json.Marshal(w)
}
// UnmarshalJSON implements the Unmarshaler.UnmarshalJSON interface.
func (warn *SQLWarn) UnmarshalJSON(data []byte) error {
var w jsonSQLWarn
if err := json.Unmarshal(data, &w); err != nil {
return err
}
warn.Level = w.Level
if w.SQLErr != nil {
warn.Err = w.SQLErr
} else {
warn.Err = errors.New(w.Msg)
}
return nil
}
// WarnAppender provides a function to add a warning.
// Using interface rather than a simple function/closure can avoid memory allocation in some cases.
// See https://github.com/pingcap/tidb/issues/49277
type WarnAppender interface {
// AppendWarning appends a warning
AppendWarning(err error)
// AppendNote appends a warning with level 'Note'.
AppendNote(msg error)
}
// WarnHandler provides a handler to append and get warnings.
type WarnHandler interface {
WarnAppender
// WarningCount gets warning count.
WarningCount() int
// TruncateWarnings truncates warnings begin from start and returns the truncated warnings.
//
// Deprecated: This method is deprecated. Because it's unsafe to read the warnings returned by `GetWarnings`
// after truncate and append the warnings.
//
// Currently it's used in two cases and they all have better alternatives:
// 1. Read warnings count, do some operation and truncate warnings to read new warnings. In this case, we
// can use a new temporary WarnHandler to do the operation and get the warnings without touching the
// global `WarnHandler` in the statement context.
// 2. Read warnings count, do some operation and truncate warnings to see whether new warnings are appended.
// In this case, we can use a specially designed `WarnHandler` which doesn't actually record warnings, but
// just counts whether new warnings are appended.
// It's understandable to use `TruncateWarnings` as it's not always easy to assign a new `WarnHandler` to the
// context now.
// TODO: Make it easier to assign a new `WarnHandler` to the context (of `table` and other packages) and remove
// this method.
TruncateWarnings(start int) []SQLWarn
// CopyWarnings copies warnings to another slice.
// The input argument provides target warnings that copy to.
// If the dist capacity is not enough, it will allocate a new slice.
CopyWarnings(dst []SQLWarn) []SQLWarn
}
// WarnHandlerExt includes more methods for WarnHandler. It allows more detailed control over warnings.
// TODO: it's a standalone interface, because it's not necessary for all WarnHandler to implement these methods.
// However, it's still needed for many executors, so we'll see whether it's good to merge it with `WarnAppender`
// and `WarnHandler` in the future.
type WarnHandlerExt interface {
WarnHandler
// AppendWarnings appends multiple warnings
AppendWarnings(warns []SQLWarn)
// AppendNote appends a warning with level 'Note'.
AppendNote(warn error)
// AppendError appends a warning with level 'Error'.
AppendError(warn error)
// GetWarnings gets all warnings. The slice is not copied, so it should not be modified.
GetWarnings() []SQLWarn
// SetWarnings resets all warnings in the handler directly. The handler may ignore the given warnings.
SetWarnings(warns []SQLWarn)
// NumErrorWarnings returns the number of warnings with level 'Error' and the total number of warnings.
NumErrorWarnings() (uint16, int)
}
var _ WarnHandler = &StaticWarnHandler{}
// StaticWarnHandler implements the WarnHandler interface.
type StaticWarnHandler struct {
sync.Mutex
warnings []SQLWarn
}
// NewStaticWarnHandler creates a new StaticWarnHandler.
func NewStaticWarnHandler(sliceCap int) *StaticWarnHandler {
var warnings []SQLWarn
if sliceCap > 0 {
warnings = make([]SQLWarn, 0, sliceCap)
}
return &StaticWarnHandler{warnings: warnings}
}
// NewStaticWarnHandlerWithHandler creates a new StaticWarnHandler with copying the warnings from the given WarnHandler.
func NewStaticWarnHandlerWithHandler(h WarnHandler) *StaticWarnHandler {
if h == nil {
return NewStaticWarnHandler(0)
}
cnt := h.WarningCount()
newHandler := NewStaticWarnHandler(cnt)
if cnt > 0 {
newHandler.warnings = h.CopyWarnings(newHandler.warnings)
}
return newHandler
}
// Reset resets the warnings of this handler.
func (h *StaticWarnHandler) Reset() {
if h.warnings != nil {
h.warnings = h.warnings[:0]
}
}
// AppendWarning implements the StaticWarnHandler.AppendWarning.
func (h *StaticWarnHandler) AppendWarning(warn error) {
h.Lock()
defer h.Unlock()
h.appendWarningWithLevel(WarnLevelWarning, warn)
}
// AppendWarnings appends multiple warnings
func (h *StaticWarnHandler) AppendWarnings(warns []SQLWarn) {
h.Lock()
defer h.Unlock()
if len(h.warnings) < math.MaxUint16 {
h.warnings = append(h.warnings, warns...)
}
}
// AppendNote appends a warning with level 'Note'.
func (h *StaticWarnHandler) AppendNote(warn error) {
h.Lock()
defer h.Unlock()
h.appendWarningWithLevel(WarnLevelNote, warn)
}
// AppendError appends a warning with level 'Error'.
func (h *StaticWarnHandler) AppendError(warn error) {
h.Lock()
defer h.Unlock()
h.appendWarningWithLevel(WarnLevelError, warn)
}
// WarningCount implements the StaticWarnHandler.WarningCount.
func (h *StaticWarnHandler) WarningCount() int {
h.Lock()
defer h.Unlock()
return len(h.warnings)
}
// TruncateWarnings implements the StaticWarnHandler.TruncateWarnings.
func (h *StaticWarnHandler) TruncateWarnings(start int) []SQLWarn {
h.Lock()
defer h.Unlock()
sz := len(h.warnings) - start
if sz <= 0 {
return nil
}
ret := make([]SQLWarn, sz)
copy(ret, h.warnings[start:])
h.warnings = h.warnings[:start]
return ret
}
// CopyWarnings implements the StaticWarnHandler.CopyWarnings.
func (h *StaticWarnHandler) CopyWarnings(dst []SQLWarn) []SQLWarn {
h.Lock()
defer h.Unlock()
if cnt := len(h.warnings); cap(dst) < cnt {
dst = make([]SQLWarn, cnt)
} else {
dst = dst[:cnt]
}
copy(dst, h.warnings)
return dst
}
// GetWarnings returns all warnings in the handler. It's not safe to modify the returned slice.
func (h *StaticWarnHandler) GetWarnings() []SQLWarn {
h.Mutex.Lock()
defer h.Mutex.Unlock()
return h.warnings
}
func (h *StaticWarnHandler) appendWarningWithLevel(level string, warn error) {
if len(h.warnings) < math.MaxUint16 {
h.warnings = append(h.warnings, SQLWarn{level, warn})
}
}
// SetWarnings sets the internal warnings directly.
func (h *StaticWarnHandler) SetWarnings(warns []SQLWarn) {
h.Lock()
defer h.Unlock()
h.warnings = warns
}
// NumErrorWarnings returns the number of warnings with level 'Error' and the total number of warnings.
func (h *StaticWarnHandler) NumErrorWarnings() (uint16, int) {
h.Lock()
defer h.Unlock()
var numError uint16
for _, w := range h.warnings {
if w.Level == WarnLevelError {
numError++
}
}
return numError, len(h.warnings)
}
type ignoreWarn struct{}
func (*ignoreWarn) AppendWarning(_ error) {}
func (*ignoreWarn) AppendNote(_ error) {}
func (*ignoreWarn) WarningCount() int { return 0 }
func (*ignoreWarn) TruncateWarnings(_ int) []SQLWarn { return nil }
func (*ignoreWarn) CopyWarnings(_ []SQLWarn) []SQLWarn { return nil }
// IgnoreWarn is WarnHandler which does nothing
var IgnoreWarn WarnHandler = &ignoreWarn{}
type funcWarnAppender struct {
fn func(level string, err error)
}
func (r *funcWarnAppender) AppendWarning(err error) {
r.fn(WarnLevelWarning, err)
}
func (r *funcWarnAppender) AppendNote(err error) {
r.fn(WarnLevelNote, err)
}
// NewFuncWarnAppenderForTest creates a `WarnHandler` which will use the function to handle warn
// To have a better performance, it's not suggested to use this function in production.
func NewFuncWarnAppenderForTest(fn func(level string, err error)) WarnAppender {
return &funcWarnAppender{fn}
}