// Copyright 2015 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 ( "encoding/json" "fmt" "runtime" "strconv" "strings" "sync/atomic" "testing" "github.com/pingcap/tidb/config" "github.com/pingcap/tidb/parser/mysql" "github.com/pingcap/tidb/parser/terror" "github.com/stretchr/testify/require" ) func TestSysVar(t *testing.T) { f := GetSysVar("autocommit") require.NotNil(t, f) f = GetSysVar("wrong-var-name") require.Nil(t, f) f = GetSysVar("explicit_defaults_for_timestamp") require.NotNil(t, f) require.Equal(t, "ON", f.Value) f = GetSysVar("port") require.NotNil(t, f) require.Equal(t, "4000", f.Value) f = GetSysVar("tidb_low_resolution_tso") require.Equal(t, "OFF", f.Value) f = GetSysVar("tidb_replica_read") require.Equal(t, "leader", f.Value) f = GetSysVar("tidb_enable_table_partition") require.Equal(t, "ON", f.Value) f = GetSysVar("version_compile_os") require.Equal(t, runtime.GOOS, f.Value) f = GetSysVar("version_compile_machine") require.Equal(t, runtime.GOARCH, f.Value) } func TestError(t *testing.T) { kvErrs := []*terror.Error{ ErrUnsupportedValueForVar, ErrUnknownSystemVar, ErrIncorrectScope, ErrUnknownTimeZone, ErrReadOnly, ErrWrongValueForVar, ErrWrongTypeForVar, ErrTruncatedWrongValue, ErrMaxPreparedStmtCountReached, ErrUnsupportedIsolationLevel, } for _, err := range kvErrs { require.True(t, terror.ToSQLError(err).Code != mysql.ErrUnknown) } } func TestRegistrationOfNewSysVar(t *testing.T) { count := len(GetSysVars()) sv := SysVar{Scope: ScopeGlobal | ScopeSession, Name: "mynewsysvar", Value: On, Type: TypeBool, SetSession: func(s *SessionVars, val string) error { return fmt.Errorf("set should fail") }} RegisterSysVar(&sv) require.Len(t, GetSysVars(), count+1) sysVar := GetSysVar("mynewsysvar") require.NotNil(t, sysVar) vars := NewSessionVars() // It is a boolean, try to set it to a bogus value _, err := sysVar.Validate(vars, "ABCD", ScopeSession) require.Error(t, err) // Boolean oN or 1 converts to canonical ON or OFF normalizedVal, err := sysVar.Validate(vars, "oN", ScopeSession) require.Equal(t, "ON", normalizedVal) require.NoError(t, err) normalizedVal, err = sysVar.Validate(vars, "0", ScopeSession) require.Equal(t, "OFF", normalizedVal) require.NoError(t, err) err = sysVar.SetSessionFromHook(vars, "OFF") // default is on require.Equal(t, "set should fail", err.Error()) // Test unregistration restores previous count UnregisterSysVar("mynewsysvar") require.Equal(t, len(GetSysVars()), count) } func TestIntValidation(t *testing.T) { sv := SysVar{Scope: ScopeGlobal | ScopeSession, Name: "mynewsysvar", Value: "123", Type: TypeInt, MinValue: 10, MaxValue: 300, AllowAutoValue: true} vars := NewSessionVars() _, err := sv.Validate(vars, "oN", ScopeSession) require.Equal(t, "[variable:1232]Incorrect argument type to variable 'mynewsysvar'", err.Error()) val, err := sv.Validate(vars, "301", ScopeSession) require.NoError(t, err) require.Equal(t, "300", val) val, err = sv.Validate(vars, "5", ScopeSession) require.NoError(t, err) require.Equal(t, "10", val) val, err = sv.Validate(vars, "300", ScopeSession) require.NoError(t, err) require.Equal(t, "300", val) // out of range but permitted due to auto value val, err = sv.Validate(vars, "-1", ScopeSession) require.NoError(t, err) require.Equal(t, "-1", val) } func TestUintValidation(t *testing.T) { sv := SysVar{Scope: ScopeGlobal | ScopeSession, Name: "mynewsysvar", Value: "123", Type: TypeUnsigned, MinValue: 10, MaxValue: 300, AllowAutoValue: true} vars := NewSessionVars() _, err := sv.Validate(vars, "oN", ScopeSession) require.Equal(t, "[variable:1232]Incorrect argument type to variable 'mynewsysvar'", err.Error()) _, err = sv.Validate(vars, "", ScopeSession) require.Equal(t, "[variable:1232]Incorrect argument type to variable 'mynewsysvar'", err.Error()) val, err := sv.Validate(vars, "301", ScopeSession) require.NoError(t, err) require.Equal(t, "300", val) val, err = sv.Validate(vars, "-301", ScopeSession) require.NoError(t, err) require.Equal(t, "10", val) _, err = sv.Validate(vars, "-ERR", ScopeSession) require.Equal(t, "[variable:1232]Incorrect argument type to variable 'mynewsysvar'", err.Error()) val, err = sv.Validate(vars, "5", ScopeSession) require.NoError(t, err) require.Equal(t, "10", val) val, err = sv.Validate(vars, "300", ScopeSession) require.NoError(t, err) require.Equal(t, "300", val) // out of range but permitted due to auto value val, err = sv.Validate(vars, "-1", ScopeSession) require.NoError(t, err) require.Equal(t, "-1", val) } func TestEnumValidation(t *testing.T) { sv := SysVar{Scope: ScopeGlobal | ScopeSession, Name: "mynewsysvar", Value: On, Type: TypeEnum, PossibleValues: []string{"OFF", "ON", "AUTO"}} vars := NewSessionVars() _, err := sv.Validate(vars, "randomstring", ScopeSession) require.Equal(t, "[variable:1231]Variable 'mynewsysvar' can't be set to the value of 'randomstring'", err.Error()) val, err := sv.Validate(vars, "oFf", ScopeSession) require.NoError(t, err) require.Equal(t, "OFF", val) val, err = sv.Validate(vars, "On", ScopeSession) require.NoError(t, err) require.Equal(t, "ON", val) val, err = sv.Validate(vars, "auto", ScopeSession) require.NoError(t, err) require.Equal(t, "AUTO", val) // Also settable by numeric offset. val, err = sv.Validate(vars, "2", ScopeSession) require.NoError(t, err) require.Equal(t, "AUTO", val) } func TestSynonyms(t *testing.T) { sysVar := GetSysVar(TxnIsolation) require.NotNil(t, sysVar) vars := NewSessionVars() // It does not permit SERIALIZABLE by default. _, err := sysVar.Validate(vars, "SERIALIZABLE", ScopeSession) require.Error(t, err) require.Equal(t, "[variable:8048]The isolation level 'SERIALIZABLE' is not supported. Set tidb_skip_isolation_level_check=1 to skip this error", err.Error()) // Enable Skip isolation check require.Nil(t, GetSysVar(TiDBSkipIsolationLevelCheck).SetSessionFromHook(vars, "ON")) // Serializable is now permitted. _, err = sysVar.Validate(vars, "SERIALIZABLE", ScopeSession) require.NoError(t, err) // Currently TiDB returns a warning because of SERIALIZABLE, but in future // it may also return a warning because TxnIsolation is deprecated. warn := vars.StmtCtx.GetWarnings()[0].Err require.Equal(t, "[variable:8048]The isolation level 'SERIALIZABLE' is not supported. Set tidb_skip_isolation_level_check=1 to skip this error", warn.Error()) require.Nil(t, sysVar.SetSessionFromHook(vars, "SERIALIZABLE")) // When we set TxnIsolation, it also updates TransactionIsolation. require.Equal(t, "SERIALIZABLE", vars.systems[TxnIsolation]) require.Equal(t, vars.systems[TxnIsolation], vars.systems[TransactionIsolation]) } func TestDeprecation(t *testing.T) { sysVar := GetSysVar(TiDBIndexLookupConcurrency) require.NotNil(t, sysVar) vars := NewSessionVars() _, err := sysVar.Validate(vars, "123", ScopeSession) require.NoError(t, err) // There was no error but there is a deprecation warning. warn := vars.StmtCtx.GetWarnings()[0].Err require.Equal(t, "[variable:1287]'tidb_index_lookup_concurrency' is deprecated and will be removed in a future release. Please use tidb_executor_concurrency instead", warn.Error()) } func TestScope(t *testing.T) { sv := SysVar{Scope: ScopeGlobal | ScopeSession, Name: "mynewsysvar", Value: On, Type: TypeEnum, PossibleValues: []string{"OFF", "ON", "AUTO"}} require.True(t, sv.HasSessionScope()) require.True(t, sv.HasGlobalScope()) require.False(t, sv.HasInstanceScope()) require.False(t, sv.HasNoneScope()) sv = SysVar{Scope: ScopeGlobal, Name: "mynewsysvar", Value: On, Type: TypeEnum, PossibleValues: []string{"OFF", "ON", "AUTO"}} require.False(t, sv.HasSessionScope()) require.True(t, sv.HasGlobalScope()) require.False(t, sv.HasInstanceScope()) require.False(t, sv.HasNoneScope()) sv = SysVar{Scope: ScopeSession, Name: "mynewsysvar", Value: On, Type: TypeEnum, PossibleValues: []string{"OFF", "ON", "AUTO"}} require.True(t, sv.HasSessionScope()) require.False(t, sv.HasGlobalScope()) require.False(t, sv.HasInstanceScope()) require.False(t, sv.HasNoneScope()) sv = SysVar{Scope: ScopeNone, Name: "mynewsysvar", Value: On, Type: TypeEnum, PossibleValues: []string{"OFF", "ON", "AUTO"}} require.False(t, sv.HasSessionScope()) require.False(t, sv.HasGlobalScope()) require.False(t, sv.HasInstanceScope()) require.True(t, sv.HasNoneScope()) sv = SysVar{Scope: ScopeInstance, Name: "mynewsysvar", Value: On, Type: TypeEnum, PossibleValues: []string{"OFF", "ON", "AUTO"}} require.False(t, sv.HasSessionScope()) require.False(t, sv.HasGlobalScope()) require.True(t, sv.HasInstanceScope()) require.False(t, sv.HasNoneScope()) } func TestBuiltInCase(t *testing.T) { // All Sysvars should have lower case names. // This tests builtins. for name := range GetSysVars() { require.Equal(t, strings.ToLower(name), name) } } func TestSQLSelectLimit(t *testing.T) { sv := GetSysVar(SQLSelectLimit) vars := NewSessionVars() val, err := sv.Validate(vars, "-10", ScopeSession) require.NoError(t, err) // it has autoconvert out of range. require.Equal(t, "0", val) val, err = sv.Validate(vars, "9999", ScopeSession) require.NoError(t, err) require.Equal(t, "9999", val) require.Nil(t, sv.SetSessionFromHook(vars, "9999")) // sets require.Equal(t, uint64(9999), vars.SelectLimit) } func TestSQLModeVar(t *testing.T) { sv := GetSysVar(SQLModeVar) vars := NewSessionVars() val, err := sv.Validate(vars, "strict_trans_tabLES ", ScopeSession) require.NoError(t, err) require.Equal(t, "STRICT_TRANS_TABLES", val) _, err = sv.Validate(vars, "strict_trans_tabLES,nonsense_option", ScopeSession) require.Equal(t, "ERROR 1231 (42000): Variable 'sql_mode' can't be set to the value of 'NONSENSE_OPTION'", err.Error()) val, err = sv.Validate(vars, "ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION", ScopeSession) require.NoError(t, err) require.Equal(t, "ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION", val) require.Nil(t, sv.SetSessionFromHook(vars, val)) // sets to strict from above require.True(t, vars.StrictSQLMode) sqlMode, err := mysql.GetSQLMode(val) require.NoError(t, err) require.Equal(t, sqlMode, vars.SQLMode) // Set it to non strict. val, err = sv.Validate(vars, "ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION", ScopeSession) require.NoError(t, err) require.Equal(t, "ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION", val) require.Nil(t, sv.SetSessionFromHook(vars, val)) // sets to non-strict from above require.False(t, vars.StrictSQLMode) sqlMode, err = mysql.GetSQLMode(val) require.NoError(t, err) require.Equal(t, sqlMode, vars.SQLMode) } func TestMaxExecutionTime(t *testing.T) { sv := GetSysVar(MaxExecutionTime) vars := NewSessionVars() val, err := sv.Validate(vars, "-10", ScopeSession) require.NoError(t, err) // it has autoconvert out of range. require.Equal(t, "0", val) val, err = sv.Validate(vars, "99999", ScopeSession) require.NoError(t, err) // it has autoconvert out of range. require.Equal(t, "99999", val) require.Nil(t, sv.SetSessionFromHook(vars, "99999")) // sets require.Equal(t, uint64(99999), vars.MaxExecutionTime) } func TestCollationServer(t *testing.T) { sv := GetSysVar(CollationServer) vars := NewSessionVars() val, err := sv.Validate(vars, "LATIN1_bin", ScopeSession) require.NoError(t, err) require.Equal(t, "latin1_bin", val) // test normalization _, err = sv.Validate(vars, "BOGUSCOLLation", ScopeSession) require.Equal(t, "[ddl:1273]Unknown collation: 'BOGUSCOLLation'", err.Error()) require.Nil(t, sv.SetSessionFromHook(vars, "latin1_bin")) require.Equal(t, "latin1", vars.systems[CharacterSetServer]) // check it also changes charset. require.Nil(t, sv.SetSessionFromHook(vars, "utf8mb4_bin")) require.Equal(t, "utf8mb4", vars.systems[CharacterSetServer]) // check it also changes charset. } func TestTimeZone(t *testing.T) { sv := GetSysVar(TimeZone) vars := NewSessionVars() // TiDB uses the Golang TZ library, so TZs are case-sensitive. // Unfortunately this is not strictly MySQL compatible. i.e. // This should not fail: // val, err := sv.Validate(vars, "America/EDMONTON", ScopeSession) // See: https://github.com/pingcap/tidb/issues/8087 val, err := sv.Validate(vars, "America/Edmonton", ScopeSession) require.NoError(t, err) require.Equal(t, "America/Edmonton", val) val, err = sv.Validate(vars, "+10:00", ScopeSession) require.NoError(t, err) require.Equal(t, "+10:00", val) val, err = sv.Validate(vars, "UTC", ScopeSession) require.NoError(t, err) require.Equal(t, "UTC", val) val, err = sv.Validate(vars, "+00:00", ScopeSession) require.NoError(t, err) require.Equal(t, "+00:00", val) require.Nil(t, sv.SetSessionFromHook(vars, "UTC")) // sets tz, err := parseTimeZone("UTC") require.NoError(t, err) require.Equal(t, tz, vars.TimeZone) } func TestForeignKeyChecks(t *testing.T) { sv := GetSysVar(ForeignKeyChecks) vars := NewSessionVars() val, err := sv.Validate(vars, "on", ScopeSession) require.NoError(t, err) require.Equal(t, "OFF", val) // warns and refuses to set ON. warn := vars.StmtCtx.GetWarnings()[0].Err require.Equal(t, "[variable:8047]variable 'foreign_key_checks' does not yet support value: on", warn.Error()) } func TestTxnIsolation(t *testing.T) { sv := GetSysVar(TxnIsolation) vars := NewSessionVars() _, err := sv.Validate(vars, "on", ScopeSession) require.Equal(t, "[variable:1231]Variable 'tx_isolation' can't be set to the value of 'on'", err.Error()) val, err := sv.Validate(vars, "read-COMMitted", ScopeSession) require.NoError(t, err) require.Equal(t, "READ-COMMITTED", val) _, err = sv.Validate(vars, "Serializable", ScopeSession) require.Equal(t, "[variable:8048]The isolation level 'SERIALIZABLE' is not supported. Set tidb_skip_isolation_level_check=1 to skip this error", err.Error()) _, err = sv.Validate(vars, "read-uncommitted", ScopeSession) require.Equal(t, "[variable:8048]The isolation level 'READ-UNCOMMITTED' is not supported. Set tidb_skip_isolation_level_check=1 to skip this error", err.Error()) // Enable global skip isolation check doesn't affect current session require.Nil(t, GetSysVar(TiDBSkipIsolationLevelCheck).SetGlobalFromHook(vars, "ON", true)) _, err = sv.Validate(vars, "Serializable", ScopeSession) require.Equal(t, "[variable:8048]The isolation level 'SERIALIZABLE' is not supported. Set tidb_skip_isolation_level_check=1 to skip this error", err.Error()) // Enable session skip isolation check require.Nil(t, GetSysVar(TiDBSkipIsolationLevelCheck).SetSessionFromHook(vars, "ON")) val, err = sv.Validate(vars, "Serializable", ScopeSession) require.NoError(t, err) require.Equal(t, "SERIALIZABLE", val) // Init TiDBSkipIsolationLevelCheck like what loadCommonGlobalVariables does vars = NewSessionVars() require.NoError(t, vars.SetSystemVarWithRelaxedValidation(TiDBSkipIsolationLevelCheck, "1")) val, err = sv.Validate(vars, "Serializable", ScopeSession) require.NoError(t, err) require.Equal(t, "SERIALIZABLE", val) } func TestTiDBMultiStatementMode(t *testing.T) { sv := GetSysVar(TiDBMultiStatementMode) vars := NewSessionVars() val, err := sv.Validate(vars, "on", ScopeSession) require.NoError(t, err) require.Equal(t, "ON", val) require.Nil(t, sv.SetSessionFromHook(vars, val)) require.Equal(t, 1, vars.MultiStatementMode) val, err = sv.Validate(vars, "0", ScopeSession) require.NoError(t, err) require.Equal(t, "OFF", val) require.Nil(t, sv.SetSessionFromHook(vars, val)) require.Equal(t, 0, vars.MultiStatementMode) val, err = sv.Validate(vars, "Warn", ScopeSession) require.NoError(t, err) require.Equal(t, "WARN", val) require.Nil(t, sv.SetSessionFromHook(vars, val)) require.Equal(t, 2, vars.MultiStatementMode) } func TestReadOnlyNoop(t *testing.T) { vars := NewSessionVars() mock := NewMockGlobalAccessor4Tests() mock.SessionVars = vars vars.GlobalVarsAccessor = mock noopFuncs := GetSysVar(TiDBEnableNoopFuncs) // For session scope for _, name := range []string{TxReadOnly, TransactionReadOnly} { sv := GetSysVar(name) val, err := sv.Validate(vars, "on", ScopeSession) require.Equal(t, "[variable:1235]function READ ONLY has only noop implementation in tidb now, use tidb_enable_noop_functions to enable these functions", err.Error()) require.Equal(t, "OFF", val) require.NoError(t, noopFuncs.SetSessionFromHook(vars, "ON")) _, err = sv.Validate(vars, "on", ScopeSession) require.NoError(t, err) require.NoError(t, noopFuncs.SetSessionFromHook(vars, "OFF")) // restore default. } // For global scope for _, name := range []string{TxReadOnly, TransactionReadOnly, OfflineMode, SuperReadOnly, ReadOnly} { sv := GetSysVar(name) val, err := sv.Validate(vars, "on", ScopeGlobal) if name == OfflineMode { require.Equal(t, "[variable:1235]function OFFLINE MODE has only noop implementation in tidb now, use tidb_enable_noop_functions to enable these functions", err.Error()) } else { require.Equal(t, "[variable:1235]function READ ONLY has only noop implementation in tidb now, use tidb_enable_noop_functions to enable these functions", err.Error()) } require.Equal(t, "OFF", val) require.NoError(t, vars.GlobalVarsAccessor.SetGlobalSysVar(TiDBEnableNoopFuncs, "ON")) _, err = sv.Validate(vars, "on", ScopeGlobal) require.NoError(t, err) require.NoError(t, vars.GlobalVarsAccessor.SetGlobalSysVar(TiDBEnableNoopFuncs, "OFF")) } } func TestSkipInit(t *testing.T) { sv := SysVar{Scope: ScopeGlobal, Name: "skipinit1", Value: On, Type: TypeBool} require.True(t, sv.SkipInit()) sv = SysVar{Scope: ScopeGlobal | ScopeSession, Name: "skipinit1", Value: On, Type: TypeBool} require.False(t, sv.SkipInit()) sv = SysVar{Scope: ScopeSession, Name: "skipinit1", Value: On, Type: TypeBool} require.False(t, sv.SkipInit()) sv = SysVar{Scope: ScopeSession, Name: "skipinit1", Value: On, Type: TypeBool, skipInit: true} require.True(t, sv.SkipInit()) } // TestIsNoop is used by the documentation to auto-generate docs for real sysvars. func TestIsNoop(t *testing.T) { sv := GetSysVar(TiDBMultiStatementMode) require.False(t, sv.IsNoop) sv = GetSysVar(InnodbLockWaitTimeout) require.False(t, sv.IsNoop) sv = GetSysVar(InnodbFastShutdown) require.True(t, sv.IsNoop) sv = GetSysVar(ReadOnly) require.True(t, sv.IsNoop) } func TestSessionGetterFuncs(t *testing.T) { vars := NewSessionVars() val, err := GetSessionOrGlobalSystemVar(vars, TiDBCurrentTS) require.NoError(t, err) require.Equal(t, fmt.Sprintf("%d", vars.TxnCtx.StartTS), val) val, err = GetSessionOrGlobalSystemVar(vars, TiDBLastTxnInfo) require.NoError(t, err) require.Equal(t, vars.LastTxnInfo, val) val, err = GetSessionOrGlobalSystemVar(vars, TiDBLastQueryInfo) require.NoError(t, err) info, err := json.Marshal(vars.LastQueryInfo) require.NoError(t, err) require.Equal(t, string(info), val) val, err = GetSessionOrGlobalSystemVar(vars, TiDBFoundInPlanCache) require.NoError(t, err) require.Equal(t, BoolToOnOff(vars.PrevFoundInPlanCache), val) val, err = GetSessionOrGlobalSystemVar(vars, TiDBFoundInBinding) require.NoError(t, err) require.Equal(t, BoolToOnOff(vars.PrevFoundInBinding), val) val, err = GetSessionOrGlobalSystemVar(vars, TiDBTxnScope) require.NoError(t, err) require.Equal(t, vars.TxnScope.GetVarValue(), val) } func TestInstanceScopedVars(t *testing.T) { vars := NewSessionVars() val, err := GetSessionOrGlobalSystemVar(vars, TiDBGeneralLog) require.NoError(t, err) require.Equal(t, BoolToOnOff(ProcessGeneralLog.Load()), val) val, err = GetSessionOrGlobalSystemVar(vars, TiDBPProfSQLCPU) require.NoError(t, err) expected := "0" if EnablePProfSQLCPU.Load() { expected = "1" } require.Equal(t, expected, val) val, err = GetSessionOrGlobalSystemVar(vars, TiDBExpensiveQueryTimeThreshold) require.NoError(t, err) require.Equal(t, fmt.Sprintf("%d", atomic.LoadUint64(&ExpensiveQueryTimeThreshold)), val) val, err = GetSessionOrGlobalSystemVar(vars, TiDBMemoryUsageAlarmRatio) require.NoError(t, err) require.Equal(t, fmt.Sprintf("%g", MemoryUsageAlarmRatio.Load()), val) val, err = GetSessionOrGlobalSystemVar(vars, TiDBForcePriority) require.NoError(t, err) require.Equal(t, mysql.Priority2Str[mysql.PriorityEnum(atomic.LoadInt32(&ForcePriority))], val) val, err = GetSessionOrGlobalSystemVar(vars, TiDBDDLSlowOprThreshold) require.NoError(t, err) require.Equal(t, strconv.FormatUint(uint64(atomic.LoadUint32(&DDLSlowOprThreshold)), 10), val) val, err = GetSessionOrGlobalSystemVar(vars, PluginDir) require.NoError(t, err) require.Equal(t, config.GetGlobalConfig().Instance.PluginDir, val) val, err = GetSessionOrGlobalSystemVar(vars, PluginLoad) require.NoError(t, err) require.Equal(t, config.GetGlobalConfig().Instance.PluginLoad, val) val, err = GetSessionOrGlobalSystemVar(vars, TiDBSlowLogThreshold) require.NoError(t, err) require.Equal(t, strconv.FormatUint(atomic.LoadUint64(&config.GetGlobalConfig().Instance.SlowThreshold), 10), val) val, err = GetSessionOrGlobalSystemVar(vars, TiDBRecordPlanInSlowLog) require.NoError(t, err) enabled := atomic.LoadUint32(&config.GetGlobalConfig().Instance.RecordPlanInSlowLog) == 1 require.Equal(t, BoolToOnOff(enabled), val) val, err = GetSessionOrGlobalSystemVar(vars, TiDBEnableSlowLog) require.NoError(t, err) require.Equal(t, BoolToOnOff(config.GetGlobalConfig().Instance.EnableSlowLog.Load()), val) val, err = GetSessionOrGlobalSystemVar(vars, TiDBCheckMb4ValueInUTF8) require.NoError(t, err) require.Equal(t, BoolToOnOff(config.GetGlobalConfig().Instance.CheckMb4ValueInUTF8.Load()), val) val, err = GetSessionOrGlobalSystemVar(vars, TiDBEnableCollectExecutionInfo) require.NoError(t, err) require.Equal(t, BoolToOnOff(config.GetGlobalConfig().Instance.EnableCollectExecutionInfo), val) val, err = GetSessionOrGlobalSystemVar(vars, TiDBConfig) require.NoError(t, err) expected, err = config.GetJSONConfig() require.NoError(t, err) require.Equal(t, expected, val) val, err = GetSessionOrGlobalSystemVar(vars, TiDBLogFileMaxDays) require.NoError(t, err) require.Equal(t, fmt.Sprint(GlobalLogMaxDays.Load()), val) } // TestDefaultValuesAreSettable that sysvars defaults are logically valid. i.e. // the default itself must validate without error provided the scope and read-only is correct. // The default values should also be normalized for consistency. func TestDefaultValuesAreSettable(t *testing.T) { vars := NewSessionVars() vars.GlobalVarsAccessor = NewMockGlobalAccessor4Tests() for _, sv := range GetSysVars() { if sv.HasSessionScope() && !sv.ReadOnly { val, err := sv.Validate(vars, sv.Value, ScopeSession) require.Equal(t, val, sv.Value) require.NoError(t, err) } if sv.HasGlobalScope() && !sv.ReadOnly { val, err := sv.Validate(vars, sv.Value, ScopeGlobal) require.Equal(t, val, sv.Value) require.NoError(t, err) } } } // TestSysVarNameIsLowerCase tests that no new sysvars are added with uppercase characters. // In MySQL variables are always lowercase, and can be set in a case-insensitive way. func TestSysVarNameIsLowerCase(t *testing.T) { for _, sv := range GetSysVars() { require.Equal(t, strings.ToLower(sv.Name), sv.Name, "sysvar name contains uppercase characters") } } // TestSettersandGetters tests that sysvars are logically correct with getter and setter functions. // i.e. it doesn't make sense to have a SetSession function on a variable that is only globally scoped. func TestSettersandGetters(t *testing.T) { for _, sv := range GetSysVars() { if !sv.HasSessionScope() { // There are some historial exceptions where global variables are loaded into the session. // Please don't add to this list, the behavior is not MySQL compatible. switch sv.Name { case TiDBRowFormatVersion: continue } require.Nil(t, sv.SetSession) require.Nil(t, sv.GetSession) } if !sv.HasGlobalScope() && !sv.HasInstanceScope() { require.Nil(t, sv.SetGlobal) if sv.Name == Timestamp { // The Timestamp sysvar will have GetGlobal func even though it does not have global scope. // It's GetGlobal func will only be called when "set timestamp = default". continue } require.Nil(t, sv.GetGlobal) } } } // TestSkipInitIsUsed ensures that no new variables are added with skipInit: true. // This feature is deprecated, and if you need to run code to differentiate between init and "SET" (rare), // you can instead check if s.StmtCtx.StmtType == "Set". // The reason it is deprecated is that the behavior is typically wrong: // it means session settings won't inherit from global and don't apply until you first set // them in each session. This is a very weird behavior. // See: https://github.com/pingcap/tidb/issues/35051 func TestSkipInitIsUsed(t *testing.T) { for _, sv := range GetSysVars() { if sv.skipInit { // skipInit only ever applied to session scope, so if anyone is setting it on // a variable without session, that doesn't make sense. require.True(t, sv.HasSessionScope(), fmt.Sprintf("skipInit has no effect on a variable without session scope: %s", sv.Name)) // Since SetSession is the "init function" there is no init function to skip. require.NotNil(t, sv.SetSession, fmt.Sprintf("skipInit has no effect on variables without an init (setsession) func: %s", sv.Name)) // Skipinit has no use on noop funcs, since noop funcs always skipinit. require.False(t, sv.IsNoop, fmt.Sprintf("skipInit has no effect on noop variables: %s", sv.Name)) // Many of these variables might allow skipInit to be removed, // they need to be checked first. The purpose of this test is to make // sure we don't introduce any new variables with skipInit, which seems // to be a problem. switch sv.Name { case TiDBTxnScope, TiDBSnapshot, TiDBOptDistinctAggPushDown, TiDBOptWriteRowID, TiDBBatchInsert, TiDBBatchDelete, TiDBBatchCommit, TiDBEnableChunkRPC, TxnIsolationOneShot, TiDBOptimizerSelectivityLevel, TiDBOptimizerEnableOuterJoinReorder, TiDBDDLReorgPriority, TiDBSlowQueryFile, TiDBWaitSplitRegionFinish, TiDBWaitSplitRegionTimeout, TiDBLowResolutionTSO, TiDBAllowRemoveAutoInc, TiDBMetricSchemaStep, TiDBMetricSchemaRangeDuration, RandSeed1, RandSeed2, CollationDatabase, CollationConnection, CharsetDatabase, CharacterSetConnection, CharacterSetServer, TiDBOptTiFlashConcurrencyFactor, TiDBOptSeekFactor, TiDBOptJoinReorderThreshold, TiDBStatsLoadSyncWait: continue } require.Equal(t, false, sv.skipInit, fmt.Sprintf("skipInit should not be set on new system variables. variable %s is in violation", sv.Name)) } } } func TestSecureAuth(t *testing.T) { sv := GetSysVar(SecureAuth) vars := NewSessionVars() _, err := sv.Validate(vars, "OFF", ScopeGlobal) require.Equal(t, "[variable:1231]Variable 'secure_auth' can't be set to the value of 'OFF'", err.Error()) val, err := sv.Validate(vars, "ON", ScopeGlobal) require.NoError(t, err) require.Equal(t, "ON", val) } func TestValidateWithRelaxedValidation(t *testing.T) { sv := GetSysVar(SecureAuth) vars := NewSessionVars() val := sv.ValidateWithRelaxedValidation(vars, "1", ScopeGlobal) require.Equal(t, "ON", val) } func TestTiDBReplicaRead(t *testing.T) { sv := GetSysVar(TiDBReplicaRead) vars := NewSessionVars() val, err := sv.Validate(vars, "follower", ScopeGlobal) require.Equal(t, val, "follower") require.NoError(t, err) } func TestSQLAutoIsNull(t *testing.T) { svSQL, svNoop := GetSysVar(SQLAutoIsNull), GetSysVar(TiDBEnableNoopFuncs) vars := NewSessionVars() vars.GlobalVarsAccessor = NewMockGlobalAccessor4Tests() _, err := svSQL.Validate(vars, "ON", ScopeSession) require.True(t, terror.ErrorEqual(err, ErrFunctionsNoopImpl)) // change tidb_enable_noop_functions to 1, it will success require.NoError(t, svNoop.SetSessionFromHook(vars, "ON")) _, err = svSQL.Validate(vars, "ON", ScopeSession) require.NoError(t, err) require.NoError(t, svSQL.SetSessionFromHook(vars, "ON")) res, ok := vars.GetSystemVar(SQLAutoIsNull) require.True(t, ok) require.Equal(t, "ON", res) // restore tidb_enable_noop_functions to 0 failed, as sql_auto_is_null is 1 _, err = svNoop.Validate(vars, "OFF", ScopeSession) require.True(t, terror.ErrorEqual(err, errValueNotSupportedWhen)) // after set sql_auto_is_null to 0, restore success require.NoError(t, svSQL.SetSessionFromHook(vars, "OFF")) require.NoError(t, svNoop.SetSessionFromHook(vars, "OFF")) // Only test validate as MockGlobalAccessor do not support SetGlobalSysVar _, err = svSQL.Validate(vars, "ON", ScopeGlobal) require.True(t, terror.ErrorEqual(err, ErrFunctionsNoopImpl)) } func TestLastInsertID(t *testing.T) { vars := NewSessionVars() val, err := GetSessionOrGlobalSystemVar(vars, LastInsertID) require.NoError(t, err) require.Equal(t, val, "0") vars.StmtCtx.PrevLastInsertID = 21 val, err = GetSessionOrGlobalSystemVar(vars, LastInsertID) require.NoError(t, err) require.Equal(t, val, "21") } func TestTimestamp(t *testing.T) { vars := NewSessionVars() val, err := GetSessionOrGlobalSystemVar(vars, Timestamp) require.NoError(t, err) require.NotEqual(t, "", val) vars.systems[Timestamp] = "10" val, err = GetSessionOrGlobalSystemVar(vars, Timestamp) require.NoError(t, err) require.Equal(t, "10", val) vars.systems[Timestamp] = "0" // set to default val, err = GetSessionOrGlobalSystemVar(vars, Timestamp) require.NoError(t, err) require.NotEqual(t, "", val) require.NotEqual(t, "10", val) } func TestIdentity(t *testing.T) { vars := NewSessionVars() val, err := GetSessionOrGlobalSystemVar(vars, Identity) require.NoError(t, err) require.Equal(t, val, "0") vars.StmtCtx.PrevLastInsertID = 21 val, err = GetSessionOrGlobalSystemVar(vars, Identity) require.NoError(t, err) require.Equal(t, val, "21") } func TestLcTimeNamesReadOnly(t *testing.T) { sv := GetSysVar("lc_time_names") vars := NewSessionVars() vars.GlobalVarsAccessor = NewMockGlobalAccessor4Tests() _, err := sv.Validate(vars, "newvalue", ScopeGlobal) require.Error(t, err) } func TestLcMessagesReadOnly(t *testing.T) { sv := GetSysVar("lc_messages") vars := NewSessionVars() vars.GlobalVarsAccessor = NewMockGlobalAccessor4Tests() _, err := sv.Validate(vars, "newvalue", ScopeGlobal) require.Error(t, err) } func TestDDLWorkers(t *testing.T) { svWorkerCount, svBatchSize := GetSysVar(TiDBDDLReorgWorkerCount), GetSysVar(TiDBDDLReorgBatchSize) vars := NewSessionVars() vars.GlobalVarsAccessor = NewMockGlobalAccessor4Tests() val, err := svWorkerCount.Validate(vars, "-100", ScopeGlobal) require.NoError(t, err) require.Equal(t, val, "1") // converts it to min value val, err = svWorkerCount.Validate(vars, "1234", ScopeGlobal) require.NoError(t, err) require.Equal(t, val, "256") // converts it to max value val, err = svWorkerCount.Validate(vars, "100", ScopeGlobal) require.NoError(t, err) require.Equal(t, val, "100") // unchanged val, err = svBatchSize.Validate(vars, "10", ScopeGlobal) require.NoError(t, err) require.Equal(t, val, fmt.Sprint(MinDDLReorgBatchSize)) // converts it to min value val, err = svBatchSize.Validate(vars, "999999", ScopeGlobal) require.NoError(t, err) require.Equal(t, val, fmt.Sprint(MaxDDLReorgBatchSize)) // converts it to max value val, err = svBatchSize.Validate(vars, "100", ScopeGlobal) require.NoError(t, err) require.Equal(t, val, "100") // unchanged } func TestDefaultCharsetAndCollation(t *testing.T) { vars := NewSessionVars() val, err := GetSessionOrGlobalSystemVar(vars, CharacterSetConnection) require.NoError(t, err) require.Equal(t, val, mysql.DefaultCharset) val, err = GetSessionOrGlobalSystemVar(vars, CollationConnection) require.NoError(t, err) require.Equal(t, val, mysql.DefaultCollationName) } func TestInstanceScope(t *testing.T) { // Instance scope used to be settable via "SET SESSION", which is weird to any MySQL user. // It is now settable via SET GLOBAL, but to work correctly a sysvar can only ever // be INSTANCE scoped or GLOBAL scoped, never *both* at the same time (at least for now). // Otherwise the semantics are confusing to users for how precedence applies. for _, sv := range GetSysVars() { require.False(t, sv.HasGlobalScope() && sv.HasInstanceScope(), "sysvar %s has both instance and global scope", sv.Name) if sv.HasInstanceScope() { require.Nil(t, sv.GetSession) require.Nil(t, sv.SetSession) } } count := len(GetSysVars()) sv := SysVar{Scope: ScopeInstance, Name: "newinstancesysvar", Value: On, Type: TypeBool, SetGlobal: func(s *SessionVars, val string) error { return fmt.Errorf("set should fail") }, GetGlobal: func(s *SessionVars) (string, error) { return "", fmt.Errorf("get should fail") }, } RegisterSysVar(&sv) require.Len(t, GetSysVars(), count+1) sysVar := GetSysVar("newinstancesysvar") require.NotNil(t, sysVar) vars := NewSessionVars() // It is a boolean, try to set it to a bogus value _, err := sysVar.Validate(vars, "ABCD", ScopeInstance) require.Error(t, err) // Boolean oN or 1 converts to canonical ON or OFF normalizedVal, err := sysVar.Validate(vars, "oN", ScopeInstance) require.Equal(t, "ON", normalizedVal) require.NoError(t, err) normalizedVal, err = sysVar.Validate(vars, "0", ScopeInstance) require.Equal(t, "OFF", normalizedVal) require.NoError(t, err) err = sysVar.SetGlobalFromHook(vars, "OFF", true) // default is on require.Equal(t, "set should fail", err.Error()) // Test unregistration restores previous count UnregisterSysVar("newinstancesysvar") require.Equal(t, len(GetSysVars()), count) } func TestIndexMergeSwitcher(t *testing.T) { vars := NewSessionVars() vars.GlobalVarsAccessor = NewMockGlobalAccessor4Tests() val, err := GetSessionOrGlobalSystemVar(vars, TiDBEnableIndexMerge) require.NoError(t, err) require.Equal(t, DefTiDBEnableIndexMerge, true) require.Equal(t, BoolToOnOff(DefTiDBEnableIndexMerge), val) } func TestNetBufferLength(t *testing.T) { netBufferLength := GetSysVar(NetBufferLength) vars := NewSessionVars() vars.GlobalVarsAccessor = NewMockGlobalAccessor4Tests() val, err := netBufferLength.Validate(vars, "1", ScopeGlobal) require.NoError(t, err) require.Equal(t, "1024", val) // converts it to min value val, err = netBufferLength.Validate(vars, "10485760", ScopeGlobal) require.NoError(t, err) require.Equal(t, "1048576", val) // converts it to max value val, err = netBufferLength.Validate(vars, "524288", ScopeGlobal) require.NoError(t, err) require.Equal(t, "524288", val) // unchanged } func TestTiDBBatchPendingTiFlashCount(t *testing.T) { sv := GetSysVar(TiDBBatchPendingTiFlashCount) vars := NewSessionVars() val, err := sv.Validate(vars, "-10", ScopeSession) require.NoError(t, err) // it has autoconvert out of range. require.Equal(t, "0", val) val, err = sv.Validate(vars, "9999", ScopeSession) require.NoError(t, err) require.Equal(t, "9999", val) _, err = sv.Validate(vars, "1.5", ScopeSession) require.Error(t, err) require.EqualError(t, err, "[variable:1232]Incorrect argument type to variable 'tidb_batch_pending_tiflash_count'") } func TestTiDBMemQuotaQuery(t *testing.T) { sv := GetSysVar(TiDBMemQuotaQuery) vars := NewSessionVars() for _, scope := range []ScopeFlag{ScopeGlobal, ScopeSession} { newVal := 32 * 1024 * 1024 val, err := sv.Validate(vars, fmt.Sprintf("%d", newVal), scope) require.Equal(t, val, "33554432") require.NoError(t, err) // min value out of range newVal = -2 expected := -1 val, err = sv.Validate(vars, fmt.Sprintf("%d", newVal), scope) // expected to truncate require.Equal(t, val, fmt.Sprintf("%d", expected)) require.NoError(t, err) } } func TestTiDBQueryLogMaxLen(t *testing.T) { sv := GetSysVar(TiDBQueryLogMaxLen) vars := NewSessionVars() newVal := 32 * 1024 * 1024 val, err := sv.Validate(vars, fmt.Sprintf("%d", newVal), ScopeGlobal) require.Equal(t, val, "33554432") require.NoError(t, err) // out of range newVal = 1073741825 expected := 1073741824 val, err = sv.Validate(vars, fmt.Sprintf("%d", newVal), ScopeGlobal) // expected to truncate require.Equal(t, val, fmt.Sprintf("%d", expected)) require.NoError(t, err) // min value out of range newVal = -2 expected = 0 val, err = sv.Validate(vars, fmt.Sprintf("%d", newVal), ScopeGlobal) // expected to set to min value require.Equal(t, val, fmt.Sprintf("%d", expected)) require.NoError(t, err) } func TestTiDBCommitterConcurrency(t *testing.T) { sv := GetSysVar(TiDBCommitterConcurrency) vars := NewSessionVars() newVal := 1024 val, err := sv.Validate(vars, fmt.Sprintf("%d", newVal), ScopeGlobal) require.Equal(t, val, "1024") require.NoError(t, err) // out of range newVal = 10001 expected := 10000 val, err = sv.Validate(vars, fmt.Sprintf("%d", newVal), ScopeGlobal) // expected to truncate require.Equal(t, val, fmt.Sprintf("%d", expected)) require.NoError(t, err) // min value out of range newVal = 0 expected = 1 val, err = sv.Validate(vars, fmt.Sprintf("%d", newVal), ScopeGlobal) // expected to set to min value require.Equal(t, val, fmt.Sprintf("%d", expected)) require.NoError(t, err) } func TestDefaultMemoryDebugModeValue(t *testing.T) { vars := NewSessionVars() val, err := GetSessionOrGlobalSystemVar(vars, TiDBMemoryDebugModeMinHeapInUse) require.NoError(t, err) require.Equal(t, val, "0") val, err = GetSessionOrGlobalSystemVar(vars, TiDBMemoryDebugModeAlarmRatio) require.NoError(t, err) require.Equal(t, val, "0") }