tidb variable 源码

2022-09-19 浏览 (571)

tidb variable 代码

文件路径:/sessionctx/variable/variable.go

/ Copyright 2021 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 variable

import (
	"strconv"
	"strings"
	"sync"
	"time"

	"github.com/pingcap/errors"
	"github.com/pingcap/tidb/parser/mysql"
	"github.com/pingcap/tidb/types"
)

/ ScopeFlag is for system variable whether can be changed in global/session dynamically or not.
type ScopeFlag uint8

/ TypeFlag is the SysVar type, which doesn't exactly match MySQL types.
type TypeFlag byte

const (
	/ ScopeNone means the system variable can not be changed dynamically.
	ScopeNone ScopeFlag = 0
	/ ScopeGlobal means the system variable can be changed globally.
	ScopeGlobal ScopeFlag = 1 << 0
	/ ScopeSession means the system variable can only be changed in current session.
	ScopeSession ScopeFlag = 1 << 1
	/ ScopeInstance means it is similar to global but doesn't propagate to other TiDB servers.
	ScopeInstance ScopeFlag = 1 << 2

	/ TypeStr is the default
	TypeStr TypeFlag = 0
	/ TypeBool for boolean
	TypeBool TypeFlag = 1
	/ TypeInt for integer
	TypeInt TypeFlag = 2
	/ TypeEnum for Enum
	TypeEnum TypeFlag = 3
	/ TypeFloat for Double
	TypeFloat TypeFlag = 4
	/ TypeUnsigned for Unsigned integer
	TypeUnsigned TypeFlag = 5
	/ TypeTime for time of day (a TiDB extension)
	TypeTime TypeFlag = 6
	/ TypeDuration for a golang duration (a TiDB extension)
	TypeDuration TypeFlag = 7

	/ On is the canonical string for ON
	On = "ON"
	/ Off is the canonical string for OFF
	Off = "OFF"
	/ Warn means return warnings
	Warn = "WARN"
	/ IntOnly means enable for int type
	IntOnly = "INT_ONLY"

	/ AssertionStrictStr is a choice of variable TiDBTxnAssertionLevel that means full assertions should be performed,
	/ even if the performance might be slowed down.
	AssertionStrictStr = "STRICT"
	/ AssertionFastStr is a choice of variable TiDBTxnAssertionLevel that means assertions that doesn't affect
	/ performance should be performed.
	AssertionFastStr = "FAST"
	/ AssertionOffStr is a choice of variable TiDBTxnAssertionLevel that means no assertion should be performed.
	AssertionOffStr = "OFF"
	/ OOMActionCancel constants represents the valid action configurations for OOMAction "CANCEL".
	OOMActionCancel = "CANCEL"
	/ OOMActionLog constants represents the valid action configurations for OOMAction "LOG".
	OOMActionLog = "LOG"
)

/ Global config name list.
const (
	GlobalConfigEnableTopSQL = "enable_resource_metering"
)

/ SysVar is for system variable.
/ All the fields of SysVar should be READ ONLY after created.
type SysVar struct {
	/ Scope is for whether can be changed or not
	Scope ScopeFlag
	/ Name is the variable name.
	Name string
	/ Value is the variable value.
	Value string
	/ Type is the MySQL type (optional)
	Type TypeFlag
	/ MinValue will automatically be validated when specified (optional)
	MinValue int64
	/ MaxValue will automatically be validated when specified (optional)
	MaxValue uint64
	/ AutoConvertNegativeBool applies to boolean types (optional)
	AutoConvertNegativeBool bool
	/ ReadOnly applies to all types
	ReadOnly bool
	/ PossibleValues applies to ENUM type
	PossibleValues []string
	/ AllowEmpty is a special TiDB behavior which means "read value from config" (do not use)
	AllowEmpty bool
	/ AllowEmptyAll is a special behavior that only applies to TiDBCapturePlanBaseline, TiDBTxnMode (do not use)
	AllowEmptyAll bool
	/ AllowAutoValue means that the special value "-1" is permitted, even when outside of range.
	AllowAutoValue bool
	/ Validation is a callback after the type validation has been performed, but before the Set function
	Validation func(*SessionVars, string, string, ScopeFlag) (string, error)
	/ SetSession is called after validation but before updating systems[]. It also doubles as an Init function
	/ and will be called on all variables in builtinGlobalVariable, regardless of their scope.
	SetSession func(*SessionVars, string) error
	/ SetGlobal is called after validation
	SetGlobal func(*SessionVars, string) error
	/ IsHintUpdatable indicate whether it's updatable via SET_VAR() hint (optional)
	IsHintUpdatable bool
	/ Deprecated: Hidden previously meant that the variable still responds to SET but doesn't show up in SHOW VARIABLES
	/ However, this feature is no longer used. All variables are visble.
	Hidden bool
	/ Aliases is a list of sysvars that should also be updated when this sysvar is updated.
	/ Updating aliases calls the SET function of the aliases, but does not update their aliases (preventing SET recursion)
	Aliases []string
	/ GetSession is a getter function for session scope.
	/ It can be used by instance-scoped variables to overwrite the previously expected value.
	GetSession func(*SessionVars) (string, error)
	/ GetGlobal is a getter function for global scope.
	GetGlobal func(*SessionVars) (string, error)
	/ GetStateValue gets the value for session states, which is used for migrating sessions.
	/ We need a function to override GetSession sometimes, because GetSession may not return the real value.
	GetStateValue func(*SessionVars) (string, bool, error)
	/ skipInit defines if the sysvar should be loaded into the session on init.
	/ This is only important to set for sysvars that include session scope,
	/ since global scoped sysvars are not-applicable.
	skipInit bool
	/ IsNoop defines if the sysvar is a noop included for MySQL compatibility
	IsNoop bool
	/ GlobalConfigName is the global config name of this global variable.
	/ If the global variable has the global config name,
	/ it should store the global config into PD(etcd) too when set global variable.
	GlobalConfigName string
}

/ GetGlobalFromHook calls the GetSession func if it exists.
func (sv *SysVar) GetGlobalFromHook(s *SessionVars) (string, error) {
	/ Call the Getter if there is one defined.
	if sv.GetGlobal != nil {
		val, err := sv.GetGlobal(s)
		if err != nil {
			return val, err
		}
		/ Ensure that the results from the getter are validated
		/ Since some are read directly from tables.
		return sv.ValidateWithRelaxedValidation(s, val, ScopeGlobal), nil
	}
	if sv.HasNoneScope() {
		return sv.Value, nil
	}
	return s.GlobalVarsAccessor.GetGlobalSysVar(sv.Name)
}

/ GetSessionFromHook calls the GetSession func if it exists.
func (sv *SysVar) GetSessionFromHook(s *SessionVars) (string, error) {
	if sv.HasNoneScope() {
		return sv.Value, nil
	}
	/ Call the Getter if there is one defined.
	if sv.GetSession != nil {
		val, err := sv.GetSession(s)
		if err != nil {
			return val, err
		}
		/ Ensure that the results from the getter are validated
		/ Since some are read directly from tables.
		return sv.ValidateWithRelaxedValidation(s, val, ScopeSession), nil
	}
	var (
		ok  bool
		val string
	)
	if val, ok = s.stmtVars[sv.Name]; ok {
		return val, nil
	}
	if val, ok = s.systems[sv.Name]; !ok {
		return val, errors.New("sysvar has not yet loaded")
	}
	return val, nil
}

/ SetSessionFromHook calls the SetSession func if it exists.
func (sv *SysVar) SetSessionFromHook(s *SessionVars, val string) error {
	if sv.SetSession != nil {
		if err := sv.SetSession(s, val); err != nil {
			return err
		}
	}
	s.systems[sv.Name] = val

	/ Call the Set function on all the aliases for this sysVar
	/ Skipping the validation function, and not calling aliases of
	/ aliases. By skipping the validation function it means that things
	/ like duplicate warnings should not appear.

	if sv.Aliases != nil {
		for _, aliasName := range sv.Aliases {
			aliasSv := GetSysVar(aliasName)
			if aliasSv.SetSession != nil {
				if err := aliasSv.SetSession(s, val); err != nil {
					return err
				}
			}
			s.systems[aliasSv.Name] = val
		}
	}
	return nil
}

/ SetGlobalFromHook calls the SetGlobal func if it exists.
func (sv *SysVar) SetGlobalFromHook(s *SessionVars, val string, skipAliases bool) error {
	if sv.SetGlobal != nil {
		return sv.SetGlobal(s, val)
	}

	/ Call the SetGlobalSysVarOnly function on all the aliases for this sysVar
	/ which skips the validation function and when SetGlobalFromHook is called again
	/ it will be with skipAliases=true. This helps break recursion because
	/ most aliases are reciprocal.

	if !skipAliases && sv.Aliases != nil {
		for _, aliasName := range sv.Aliases {
			if err := s.GlobalVarsAccessor.SetGlobalSysVarOnly(aliasName, val); err != nil {
				return err
			}
		}
	}
	return nil
}

/ HasNoneScope returns true if the scope for the sysVar is None.
func (sv *SysVar) HasNoneScope() bool {
	return sv.Scope == ScopeNone
}

/ HasSessionScope returns true if the scope for the sysVar includes session.
func (sv *SysVar) HasSessionScope() bool {
	return sv.Scope&ScopeSession != 0
}

/ HasGlobalScope returns true if the scope for the sysVar includes global.
func (sv *SysVar) HasGlobalScope() bool {
	return sv.Scope&ScopeGlobal != 0
}

/ HasInstanceScope returns true if the scope for the sysVar includes instance
func (sv *SysVar) HasInstanceScope() bool {
	return sv.Scope&ScopeInstance != 0
}

/ Validate checks if system variable satisfies specific restriction.
func (sv *SysVar) Validate(vars *SessionVars, value string, scope ScopeFlag) (string, error) {
	/ Check that the scope is correct first.
	if err := sv.validateScope(scope); err != nil {
		return value, err
	}
	/ Normalize the value and apply validation based on type.
	/ i.e. TypeBool converts 1/on/ON to ON.
	normalizedValue, err := sv.ValidateFromType(vars, value, scope)
	if err != nil {
		return normalizedValue, err
	}
	/ If type validation was successful, call the (optional) validation function
	if sv.Validation != nil {
		return sv.Validation(vars, normalizedValue, value, scope)
	}
	return normalizedValue, nil
}

/ ValidateFromType provides automatic validation based on the SysVar's type
func (sv *SysVar) ValidateFromType(vars *SessionVars, value string, scope ScopeFlag) (string, error) {
	/ Some sysvars in TiDB have a special behavior where the empty string means
	/ "use the config file value". This needs to be cleaned up once the behavior
	/ for instance variables is determined.
	if value == "" && ((sv.AllowEmpty && scope == ScopeSession) || sv.AllowEmptyAll) {
		return value, nil
	}
	/ Provide validation using the SysVar struct
	switch sv.Type {
	case TypeUnsigned:
		return sv.checkUInt64SystemVar(value, vars)
	case TypeInt:
		return sv.checkInt64SystemVar(value, vars)
	case TypeBool:
		return sv.checkBoolSystemVar(value, vars)
	case TypeFloat:
		return sv.checkFloatSystemVar(value, vars)
	case TypeEnum:
		return sv.checkEnumSystemVar(value, vars)
	case TypeTime:
		return sv.checkTimeSystemVar(value, vars)
	case TypeDuration:
		return sv.checkDurationSystemVar(value, vars)
	}
	return value, nil / typeString
}

func (sv *SysVar) validateScope(scope ScopeFlag) error {
	if sv.ReadOnly || sv.Scope == ScopeNone {
		return ErrIncorrectScope.FastGenByArgs(sv.Name, "read only")
	}
	if scope == ScopeGlobal && !(sv.HasGlobalScope() || sv.HasInstanceScope()) {
		return errLocalVariable.FastGenByArgs(sv.Name)
	}
	if scope == ScopeSession && !sv.HasSessionScope() {
		return errGlobalVariable.FastGenByArgs(sv.Name)
	}
	return nil
}

/ ValidateWithRelaxedValidation normalizes values but can not return errors.
/ Normalization+validation needs to be applied when reading values because older versions of TiDB
/ may be less sophisticated in normalizing values. But errors should be caught and handled,
/ because otherwise there will be upgrade issues.
func (sv *SysVar) ValidateWithRelaxedValidation(vars *SessionVars, value string, scope ScopeFlag) string {
	warns := vars.StmtCtx.GetWarnings()
	defer func() {
		vars.StmtCtx.SetWarnings(warns) / RelaxedValidation = trim warnings too.
	}()
	normalizedValue, err := sv.ValidateFromType(vars, value, scope)
	if err != nil {
		return normalizedValue
	}
	if sv.Validation != nil {
		normalizedValue, err = sv.Validation(vars, normalizedValue, value, scope)
		if err != nil {
			return normalizedValue
		}
	}
	return normalizedValue
}

const (
	localDayTimeFormat = "15:04"
	/ FullDayTimeFormat is the full format of analyze start time and end time.
	FullDayTimeFormat = "15:04 -0700"
)

func (sv *SysVar) checkTimeSystemVar(value string, vars *SessionVars) (string, error) {
	var t time.Time
	var err error
	if len(value) <= len(localDayTimeFormat) {
		t, err = time.ParseInLocation(localDayTimeFormat, value, vars.Location())
	} else {
		t, err = time.ParseInLocation(FullDayTimeFormat, value, vars.Location())
	}
	if err != nil {
		return "", err
	}
	return t.Format(FullDayTimeFormat), nil
}

func (sv *SysVar) checkDurationSystemVar(value string, vars *SessionVars) (string, error) {
	d, err := time.ParseDuration(value)
	if err != nil {
		return value, ErrWrongTypeForVar.GenWithStackByArgs(sv.Name)
	}
	/ Check for min/max violations
	if int64(d) < sv.MinValue {
		vars.StmtCtx.AppendWarning(ErrTruncatedWrongValue.GenWithStackByArgs(sv.Name, value))
		return time.Duration(sv.MinValue).String(), nil
	}
	if uint64(d) > sv.MaxValue {
		vars.StmtCtx.AppendWarning(ErrTruncatedWrongValue.GenWithStackByArgs(sv.Name, value))
		return time.Duration(sv.MaxValue).String(), nil
	}
	/ return a string representation of the duration
	return d.String(), nil
}

func (sv *SysVar) checkUInt64SystemVar(value string, vars *SessionVars) (string, error) {
	if sv.AllowAutoValue && value == "-1" {
		return value, nil
	}
	if len(value) == 0 {
		return value, ErrWrongTypeForVar.GenWithStackByArgs(sv.Name)
	}
	if value[0] == '-' {
		_, err := strconv.ParseInt(value, 10, 64)
		if err != nil {
			return value, ErrWrongTypeForVar.GenWithStackByArgs(sv.Name)
		}
		vars.StmtCtx.AppendWarning(ErrTruncatedWrongValue.GenWithStackByArgs(sv.Name, value))
		return strconv.FormatInt(sv.MinValue, 10), nil
	}
	val, err := strconv.ParseUint(value, 10, 64)
	if err != nil {
		return value, ErrWrongTypeForVar.GenWithStackByArgs(sv.Name)
	}
	if val < uint64(sv.MinValue) {
		vars.StmtCtx.AppendWarning(ErrTruncatedWrongValue.GenWithStackByArgs(sv.Name, value))
		return strconv.FormatInt(sv.MinValue, 10), nil
	}
	if val > sv.MaxValue {
		vars.StmtCtx.AppendWarning(ErrTruncatedWrongValue.GenWithStackByArgs(sv.Name, value))
		return strconv.FormatUint(sv.MaxValue, 10), nil
	}
	return value, nil
}

func (sv *SysVar) checkInt64SystemVar(value string, vars *SessionVars) (string, error) {
	if sv.AllowAutoValue && value == "-1" {
		return value, nil
	}
	val, err := strconv.ParseInt(value, 10, 64)
	if err != nil {
		return value, ErrWrongTypeForVar.GenWithStackByArgs(sv.Name)
	}
	if val < sv.MinValue {
		vars.StmtCtx.AppendWarning(ErrTruncatedWrongValue.GenWithStackByArgs(sv.Name, value))
		return strconv.FormatInt(sv.MinValue, 10), nil
	}
	if val > int64(sv.MaxValue) {
		vars.StmtCtx.AppendWarning(ErrTruncatedWrongValue.GenWithStackByArgs(sv.Name, value))
		return strconv.FormatUint(sv.MaxValue, 10), nil
	}
	return value, nil
}

func (sv *SysVar) checkEnumSystemVar(value string, vars *SessionVars) (string, error) {
	/ The value could be either a string or the ordinal position in the PossibleValues.
	/ This allows for the behavior 0 = OFF, 1 = ON, 2 = DEMAND etc.
	var iStr string
	for i, v := range sv.PossibleValues {
		iStr = strconv.Itoa(i)
		if strings.EqualFold(value, v) || strings.EqualFold(value, iStr) {
			return v, nil
		}
	}
	return value, ErrWrongValueForVar.GenWithStackByArgs(sv.Name, value)
}

func (sv *SysVar) checkFloatSystemVar(value string, vars *SessionVars) (string, error) {
	if len(value) == 0 {
		return value, ErrWrongTypeForVar.GenWithStackByArgs(sv.Name)
	}
	val, err := strconv.ParseFloat(value, 64)
	if err != nil {
		return value, ErrWrongTypeForVar.GenWithStackByArgs(sv.Name)
	}
	if val < float64(sv.MinValue) {
		vars.StmtCtx.AppendWarning(ErrTruncatedWrongValue.GenWithStackByArgs(sv.Name, value))
		return strconv.FormatInt(sv.MinValue, 10), nil
	}
	if val > float64(sv.MaxValue) {
		vars.StmtCtx.AppendWarning(ErrTruncatedWrongValue.GenWithStackByArgs(sv.Name, value))
		return strconv.FormatUint(sv.MaxValue, 10), nil
	}
	return value, nil
}

func (sv *SysVar) checkBoolSystemVar(value string, vars *SessionVars) (string, error) {
	if strings.EqualFold(value, "ON") {
		return On, nil
	} else if strings.EqualFold(value, "OFF") {
		return Off, nil
	}
	val, err := strconv.ParseInt(value, 10, 64)
	if err == nil {
		/ There are two types of conversion rules for integer values.
		/ The default only allows 0 || 1, but a subset of values convert any
		/ negative integer to 1.
		if !sv.AutoConvertNegativeBool {
			if val == 0 {
				return Off, nil
			} else if val == 1 {
				return On, nil
			}
		} else {
			if val == 1 || val < 0 {
				return On, nil
			} else if val == 0 {
				return Off, nil
			}
		}
	}
	return value, ErrWrongValueForVar.GenWithStackByArgs(sv.Name, value)
}

/ GetNativeValType attempts to convert the val to the approx MySQL non-string type
func (sv *SysVar) GetNativeValType(val string) (types.Datum, byte, uint) {
	switch sv.Type {
	case TypeUnsigned:
		u, err := strconv.ParseUint(val, 10, 64)
		if err != nil {
			u = 0
		}
		return types.NewUintDatum(u), mysql.TypeLonglong, mysql.UnsignedFlag
	case TypeBool:
		optVal := int64(0) / OFF
		if TiDBOptOn(val) {
			optVal = 1
		}
		return types.NewIntDatum(optVal), mysql.TypeLong, 0
	}
	return types.NewStringDatum(val), mysql.TypeVarString, 0
}

/ SkipInit returns true if when a new session is created we should "skip" copying
/ an initial value to it (and call the SetSession func if it exists)
func (sv *SysVar) SkipInit() bool {
	if sv.skipInit || sv.IsNoop {
		return true
	}
	return !sv.HasSessionScope()
}

/ SkipSysvarCache returns true if the sysvar should not re-execute on peers
/ This doesn't make sense for the GC variables because they are based in tikv
/ tables. We'd effectively be reading and writing to the same table, which
/ could be in an unsafe manner. In future these variables might be converted
/ to not use a different table internally, but to do that we need to first
/ fix upgrade/downgrade so we know that older servers won't be in the cluster
/ which update only these values.
func (sv *SysVar) SkipSysvarCache() bool {
	switch sv.Name {
	case TiDBGCEnable, TiDBGCRunInterval, TiDBGCLifetime,
		TiDBGCConcurrency, TiDBGCScanLockMode:
		return true
	}
	return false
}

var sysVars map[string]*SysVar
var sysVarsLock sync.RWMutex

/ RegisterSysVar adds a sysvar to the SysVars list
func RegisterSysVar(sv *SysVar) {
	name := strings.ToLower(sv.Name)
	sysVarsLock.Lock()
	sysVars[name] = sv
	sysVarsLock.Unlock()
}

/ UnregisterSysVar removes a sysvar from the SysVars list
/ currently only used in tests.
func UnregisterSysVar(name string) {
	name = strings.ToLower(name)
	sysVarsLock.Lock()
	delete(sysVars, name)
	sysVarsLock.Unlock()
}

/ GetSysVar returns sys var info for name as key.
func GetSysVar(name string) *SysVar {
	name = strings.ToLower(name)
	sysVarsLock.RLock()
	defer sysVarsLock.RUnlock()

	return sysVars[name]
}

/ SetSysVar sets a sysvar. In fact, SysVar is immutable.
/ SetSysVar is implemented by register a new SysVar with the same name again.
/ This will not propagate to the cluster, so it should only be
/ used for instance scoped AUTO variables such as system_time_zone.
func SetSysVar(name string, value string) {
	old := GetSysVar(name)
	tmp := *old
	tmp.Value = value
	RegisterSysVar(&tmp)
}

/ GetSysVars deep copies the sysVars list under a RWLock
func GetSysVars() map[string]*SysVar {
	sysVarsLock.RLock()
	defer sysVarsLock.RUnlock()
	m := make(map[string]*SysVar, len(sysVars))
	for name, sv := range sysVars {
		tmp := *sv
		m[name] = &tmp
	}
	return m
}

func init() {
	sysVars = make(map[string]*SysVar)
	for _, v := range defaultSysVars {
		RegisterSysVar(v)
	}
	for _, v := range noopSysVars {
		v.IsNoop = true
		RegisterSysVar(v)
	}
}

/ GlobalVarAccessor is the interface for accessing global scope system and status variables.
type GlobalVarAccessor interface {
	/ GetGlobalSysVar gets the global system variable value for name.
	GetGlobalSysVar(name string) (string, error)
	/ SetGlobalSysVar sets the global system variable name to value.
	SetGlobalSysVar(name string, value string) error
	/ SetGlobalSysVarOnly sets the global system variable without calling the validation function or updating aliases.
	SetGlobalSysVarOnly(name string, value string) error
	/ GetTiDBTableValue gets a value from mysql.tidb for the key 'name'
	GetTiDBTableValue(name string) (string, error)
	/ SetTiDBTableValue sets a value+comment for the mysql.tidb key 'name'
	SetTiDBTableValue(name, value, comment string) error
}

相关信息

tidb 源码目录

相关文章

tidb error 源码

tidb mock_globalaccessor 源码

tidb noop 源码

tidb removed 源码

tidb sequence_state 源码

tidb session 源码

tidb statusvar 源码

tidb sysvar 源码

tidb tidb_vars 源码

tidb varsutil 源码

^