// 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} }