Files
tidb/sessionctx/variable/varsutil.go
2020-10-19 14:13:43 +08:00

731 lines
22 KiB
Go

// Copyright 2016 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,
// See the License for the specific language governing permissions and
// limitations under the License.
package variable
import (
"encoding/json"
"fmt"
"math"
"strconv"
"strings"
"sync"
"sync/atomic"
"time"
"github.com/cznic/mathutil"
"github.com/pingcap/errors"
"github.com/pingcap/parser/ast"
"github.com/pingcap/parser/mysql"
"github.com/pingcap/tidb/config"
"github.com/pingcap/tidb/kv"
"github.com/pingcap/tidb/types"
"github.com/pingcap/tidb/util/collate"
"github.com/pingcap/tidb/util/timeutil"
)
// secondsPerYear represents seconds in a normal year. Leap year is not considered here.
const secondsPerYear = 60 * 60 * 24 * 365
// SetDDLReorgWorkerCounter sets ddlReorgWorkerCounter count.
// Max worker count is maxDDLReorgWorkerCount.
func SetDDLReorgWorkerCounter(cnt int32) {
if cnt > maxDDLReorgWorkerCount {
cnt = maxDDLReorgWorkerCount
}
atomic.StoreInt32(&ddlReorgWorkerCounter, cnt)
}
// GetDDLReorgWorkerCounter gets ddlReorgWorkerCounter.
func GetDDLReorgWorkerCounter() int32 {
return atomic.LoadInt32(&ddlReorgWorkerCounter)
}
// SetDDLReorgBatchSize sets ddlReorgBatchSize size.
// Max batch size is MaxDDLReorgBatchSize.
func SetDDLReorgBatchSize(cnt int32) {
if cnt > MaxDDLReorgBatchSize {
cnt = MaxDDLReorgBatchSize
}
if cnt < MinDDLReorgBatchSize {
cnt = MinDDLReorgBatchSize
}
atomic.StoreInt32(&ddlReorgBatchSize, cnt)
}
// GetDDLReorgBatchSize gets ddlReorgBatchSize.
func GetDDLReorgBatchSize() int32 {
return atomic.LoadInt32(&ddlReorgBatchSize)
}
// SetDDLErrorCountLimit sets ddlErrorCountlimit size.
func SetDDLErrorCountLimit(cnt int64) {
atomic.StoreInt64(&ddlErrorCountlimit, cnt)
}
// GetDDLErrorCountLimit gets ddlErrorCountlimit size.
func GetDDLErrorCountLimit() int64 {
return atomic.LoadInt64(&ddlErrorCountlimit)
}
// SetMaxDeltaSchemaCount sets maxDeltaSchemaCount size.
func SetMaxDeltaSchemaCount(cnt int64) {
atomic.StoreInt64(&maxDeltaSchemaCount, cnt)
}
// GetMaxDeltaSchemaCount gets maxDeltaSchemaCount size.
func GetMaxDeltaSchemaCount() int64 {
return atomic.LoadInt64(&maxDeltaSchemaCount)
}
// GetSessionSystemVar gets a system variable.
// If it is a session only variable, use the default value defined in code.
// Returns error if there is no such variable.
func GetSessionSystemVar(s *SessionVars, key string) (string, error) {
key = strings.ToLower(key)
gVal, ok, err := GetSessionOnlySysVars(s, key)
if err != nil || ok {
return gVal, err
}
gVal, err = s.GlobalVarsAccessor.GetGlobalSysVar(key)
if err != nil {
return "", err
}
s.systems[key] = gVal
return gVal, nil
}
// GetSessionOnlySysVars get the default value defined in code for session only variable.
// The return bool value indicates whether it's a session only variable.
func GetSessionOnlySysVars(s *SessionVars, key string) (string, bool, error) {
sysVar := GetSysVar(key)
if sysVar == nil {
return "", false, ErrUnknownSystemVar.GenWithStackByArgs(key)
}
// For virtual system variables:
switch sysVar.Name {
case TiDBCurrentTS:
return fmt.Sprintf("%d", s.TxnCtx.StartTS), true, nil
case TiDBLastTxnInfo:
info, err := json.Marshal(s.LastTxnInfo)
if err != nil {
return "", true, err
}
return string(info), true, nil
case TiDBGeneralLog:
return fmt.Sprintf("%d", atomic.LoadUint32(&ProcessGeneralLog)), true, nil
case TiDBPProfSQLCPU:
val := "0"
if EnablePProfSQLCPU.Load() {
val = "1"
}
return val, true, nil
case TiDBExpensiveQueryTimeThreshold:
return fmt.Sprintf("%d", atomic.LoadUint64(&ExpensiveQueryTimeThreshold)), true, nil
case TiDBConfig:
conf := config.GetGlobalConfig()
j, err := json.MarshalIndent(conf, "", "\t")
if err != nil {
return "", false, err
}
return string(j), true, nil
case TiDBForcePriority:
return mysql.Priority2Str[mysql.PriorityEnum(atomic.LoadInt32(&ForcePriority))], true, nil
case TiDBDDLSlowOprThreshold:
return strconv.FormatUint(uint64(atomic.LoadUint32(&DDLSlowOprThreshold)), 10), true, nil
case PluginDir:
return config.GetGlobalConfig().Plugin.Dir, true, nil
case PluginLoad:
return config.GetGlobalConfig().Plugin.Load, true, nil
case TiDBSlowLogThreshold:
return strconv.FormatUint(atomic.LoadUint64(&config.GetGlobalConfig().Log.SlowThreshold), 10), true, nil
case TiDBRecordPlanInSlowLog:
return strconv.FormatUint(uint64(atomic.LoadUint32(&config.GetGlobalConfig().Log.RecordPlanInSlowLog)), 10), true, nil
case TiDBEnableSlowLog:
return BoolToIntStr(config.GetGlobalConfig().Log.EnableSlowLog), true, nil
case TiDBQueryLogMaxLen:
return strconv.FormatUint(atomic.LoadUint64(&config.GetGlobalConfig().Log.QueryLogMaxLen), 10), true, nil
case TiDBCheckMb4ValueInUTF8:
return BoolToIntStr(config.GetGlobalConfig().CheckMb4ValueInUTF8), true, nil
case TiDBCapturePlanBaseline:
return CapturePlanBaseline.GetVal(), true, nil
case TiDBFoundInPlanCache:
return BoolToIntStr(s.PrevFoundInPlanCache), true, nil
case TiDBEnableCollectExecutionInfo:
return BoolToIntStr(config.GetGlobalConfig().EnableCollectExecutionInfo), true, nil
}
sVal, ok := s.GetSystemVar(key)
if ok {
return sVal, true, nil
}
if sysVar.Scope&ScopeGlobal == 0 {
// None-Global variable can use pre-defined default value.
return sysVar.Value, true, nil
}
return "", false, nil
}
// GetGlobalSystemVar gets a global system variable.
func GetGlobalSystemVar(s *SessionVars, key string) (string, error) {
key = strings.ToLower(key)
gVal, ok, err := GetScopeNoneSystemVar(key)
if err != nil || ok {
return gVal, err
}
gVal, err = s.GlobalVarsAccessor.GetGlobalSysVar(key)
if err != nil {
return "", err
}
return gVal, nil
}
// GetScopeNoneSystemVar checks the validation of `key`,
// and return the default value if its scope is `ScopeNone`.
func GetScopeNoneSystemVar(key string) (string, bool, error) {
sysVar := GetSysVar(key)
if sysVar == nil {
return "", false, ErrUnknownSystemVar.GenWithStackByArgs(key)
}
if sysVar.Scope == ScopeNone {
return sysVar.Value, true, nil
}
return "", false, nil
}
// epochShiftBits is used to reserve logical part of the timestamp.
const epochShiftBits = 18
// SetSessionSystemVar sets system variable and updates SessionVars states.
func SetSessionSystemVar(vars *SessionVars, name string, value types.Datum) error {
sysVar := GetSysVar(name)
if sysVar == nil {
return ErrUnknownSystemVar.GenWithStackByArgs(name)
}
sVal := ""
var err error
if !value.IsNull() {
sVal, err = value.ToString()
}
if err != nil {
return err
}
sVal, err = ValidateSetSystemVar(vars, name, sVal, ScopeSession)
if err != nil {
return err
}
CheckDeprecationSetSystemVar(vars, name)
return vars.SetSystemVar(name, sVal)
}
// ValidateGetSystemVar checks if system variable exists and validates its scope when get system variable.
func ValidateGetSystemVar(name string, isGlobal bool) error {
sysVar := GetSysVar(name)
if sysVar == nil {
return ErrUnknownSystemVar.GenWithStackByArgs(name)
}
switch sysVar.Scope {
case ScopeGlobal:
if !isGlobal {
return ErrIncorrectScope.GenWithStackByArgs(name, "GLOBAL")
}
case ScopeSession:
if isGlobal {
return ErrIncorrectScope.GenWithStackByArgs(name, "SESSION")
}
}
return nil
}
func checkUInt64SystemVar(name, value string, min, max uint64, vars *SessionVars) (string, error) {
// There are two types of validation behaviors for integer values. The default
// is to return an error saying the value is out of range. For MySQL compatibility, some
// values prefer convert the value to the min/max and return a warning.
sv := GetSysVar(name)
if sv != nil && !sv.AutoConvertOutOfRange {
return checkUint64SystemVarWithError(name, value, min, max)
}
if len(value) == 0 {
return value, ErrWrongTypeForVar.GenWithStackByArgs(name)
}
if value[0] == '-' {
_, err := strconv.ParseInt(value, 10, 64)
if err != nil {
return value, ErrWrongTypeForVar.GenWithStackByArgs(name)
}
vars.StmtCtx.AppendWarning(ErrTruncatedWrongValue.GenWithStackByArgs(name, value))
return fmt.Sprintf("%d", min), nil
}
val, err := strconv.ParseUint(value, 10, 64)
if err != nil {
return value, ErrWrongTypeForVar.GenWithStackByArgs(name)
}
if val < min {
vars.StmtCtx.AppendWarning(ErrTruncatedWrongValue.GenWithStackByArgs(name, value))
return fmt.Sprintf("%d", min), nil
}
if val > max {
vars.StmtCtx.AppendWarning(ErrTruncatedWrongValue.GenWithStackByArgs(name, value))
return fmt.Sprintf("%d", max), nil
}
return value, nil
}
func checkInt64SystemVar(name, value string, min, max int64, vars *SessionVars) (string, error) {
// There are two types of validation behaviors for integer values. The default
// is to return an error saying the value is out of range. For MySQL compatibility, some
// values prefer convert the value to the min/max and return a warning.
sv := GetSysVar(name)
if sv != nil && !sv.AutoConvertOutOfRange {
return checkInt64SystemVarWithError(name, value, min, max)
}
val, err := strconv.ParseInt(value, 10, 64)
if err != nil {
return value, ErrWrongTypeForVar.GenWithStackByArgs(name)
}
if val < min {
vars.StmtCtx.AppendWarning(ErrTruncatedWrongValue.GenWithStackByArgs(name, value))
return fmt.Sprintf("%d", min), nil
}
if val > max {
vars.StmtCtx.AppendWarning(ErrTruncatedWrongValue.GenWithStackByArgs(name, value))
return fmt.Sprintf("%d", max), nil
}
return value, nil
}
func checkEnumSystemVar(name, value string, vars *SessionVars) (string, error) {
sv := GetSysVar(name)
// 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 = fmt.Sprintf("%d", i)
if strings.EqualFold(value, v) || strings.EqualFold(value, iStr) {
return v, nil
}
}
return value, ErrWrongValueForVar.GenWithStackByArgs(name, value)
}
func checkFloatSystemVar(name, value string, min, max float64, vars *SessionVars) (string, error) {
if len(value) == 0 {
return value, ErrWrongTypeForVar.GenWithStackByArgs(name)
}
val, err := strconv.ParseFloat(value, 64)
if err != nil {
return value, ErrWrongTypeForVar.GenWithStackByArgs(name)
}
if val < min || val > max {
return value, ErrWrongValueForVar.GenWithStackByArgs(name, value)
}
return value, nil
}
func checkBoolSystemVar(name, value string, vars *SessionVars) (string, error) {
if strings.EqualFold(value, "ON") {
return BoolOn, nil
} else if strings.EqualFold(value, "OFF") {
return BoolOff, nil
}
val, err := strconv.ParseInt(value, 10, 64)
if err == nil {
// Confusingly, 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.
sv := GetSysVar(name)
if !sv.AutoConvertNegativeBool {
if val == 0 {
return BoolOff, nil
} else if val == 1 {
return BoolOn, nil
}
} else {
if val == 1 || val < 0 {
return BoolOn, nil
} else if val == 0 {
return BoolOff, nil
}
}
}
return value, ErrWrongValueForVar.GenWithStackByArgs(name, value)
}
func checkUint64SystemVarWithError(name, value string, min, max uint64) (string, error) {
if len(value) == 0 {
return value, ErrWrongTypeForVar.GenWithStackByArgs(name)
}
if value[0] == '-' {
// // in strict it expects the error WrongValue, but in non-strict it returns WrongType
return value, ErrWrongValueForVar.GenWithStackByArgs(name, value)
}
val, err := strconv.ParseUint(value, 10, 64)
if err != nil {
return value, ErrWrongTypeForVar.GenWithStackByArgs(name)
}
if val < min || val > max {
return value, ErrWrongValueForVar.GenWithStackByArgs(name, value)
}
return value, nil
}
func checkInt64SystemVarWithError(name, value string, min, max int64) (string, error) {
if len(value) == 0 {
return value, ErrWrongTypeForVar.GenWithStackByArgs(name)
}
val, err := strconv.ParseInt(value, 10, 64)
if err != nil {
return value, ErrWrongTypeForVar.GenWithStackByArgs(name)
}
if val < min || val > max {
return value, ErrWrongValueForVar.GenWithStackByArgs(name, value)
}
return value, nil
}
const (
// initChunkSizeUpperBound indicates upper bound value of tidb_init_chunk_size.
initChunkSizeUpperBound = 32
// maxChunkSizeLowerBound indicates lower bound value of tidb_max_chunk_size.
maxChunkSizeLowerBound = 32
)
// CheckDeprecationSetSystemVar checks if the system variable is deprecated.
func CheckDeprecationSetSystemVar(s *SessionVars, name string) {
switch name {
case TiDBIndexLookupConcurrency, TiDBIndexLookupJoinConcurrency,
TiDBHashJoinConcurrency, TiDBHashAggPartialConcurrency, TiDBHashAggFinalConcurrency,
TiDBProjectionConcurrency, TiDBWindowConcurrency:
s.StmtCtx.AppendWarning(errWarnDeprecatedSyntax.FastGenByArgs(name, TiDBExecutorConcurrency))
case TIDBMemQuotaHashJoin, TIDBMemQuotaMergeJoin,
TIDBMemQuotaSort, TIDBMemQuotaTopn,
TIDBMemQuotaIndexLookupReader, TIDBMemQuotaIndexLookupJoin,
TIDBMemQuotaNestedLoopApply:
s.StmtCtx.AppendWarning(errWarnDeprecatedSyntax.FastGenByArgs(name, TIDBMemQuotaQuery))
}
}
// ValidateSetSystemVar checks if system variable satisfies specific restriction.
func ValidateSetSystemVar(vars *SessionVars, name string, value string, scope ScopeFlag) (string, error) {
sv := GetSysVar(name)
// Some sysvars are read-only. Attempting to set should always fail.
if sv.ReadOnly || sv.Scope == ScopeNone {
return value, ErrReadOnly.GenWithStackByArgs(name)
}
// The string "DEFAULT" is a special keyword in MySQL, which restores
// the compiled sysvar value. In which case we can skip further validation.
if strings.EqualFold(value, "DEFAULT") {
if sv != nil {
return sv.Value, nil
}
return value, ErrUnknownSystemVar.GenWithStackByArgs(name)
}
// 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
}
// Attempt to provide validation using the SysVar struct.
// Eventually the struct should handle all validation
var err error
if sv != nil {
switch sv.Type {
case TypeUnsigned:
value, err = checkUInt64SystemVar(name, value, uint64(sv.MinValue), sv.MaxValue, vars)
case TypeInt:
value, err = checkInt64SystemVar(name, value, sv.MinValue, int64(sv.MaxValue), vars)
case TypeBool:
value, err = checkBoolSystemVar(name, value, vars)
case TypeFloat:
value, err = checkFloatSystemVar(name, value, float64(sv.MinValue), float64(sv.MaxValue), vars)
case TypeEnum:
value, err = checkEnumSystemVar(name, value, vars)
}
// If there is no error, follow through and handle legacy cases of validation that are not handled by the type.
// TODO: Move each of these validations into the SysVar as an anonymous function.
if err != nil {
return value, err
}
}
switch name {
case ForeignKeyChecks:
if TiDBOptOn(value) {
// TiDB does not yet support foreign keys.
// For now, resist the change and show a warning.
vars.StmtCtx.AppendWarning(ErrUnsupportedValueForVar.GenWithStackByArgs(name, value))
return BoolOff, nil
} else if !TiDBOptOn(value) {
return BoolOff, nil
}
return value, ErrWrongValueForVar.GenWithStackByArgs(name, value)
case GroupConcatMaxLen:
// https://dev.mysql.com/doc/refman/8.0/en/server-system-variables.html#sysvar_group_concat_max_len
// Minimum Value 4
// Maximum Value (64-bit platforms) 18446744073709551615
// Maximum Value (32-bit platforms) 4294967295
maxLen := uint64(math.MaxUint64)
if mathutil.IntBits == 32 {
maxLen = uint64(math.MaxUint32)
}
return checkUInt64SystemVar(name, value, 4, maxLen, vars)
case TimeZone:
if strings.EqualFold(value, "SYSTEM") {
return "SYSTEM", nil
}
_, err := parseTimeZone(value)
return value, err
case SecureAuth:
if TiDBOptOn(value) {
return BoolOn, nil
}
return value, ErrWrongValueForVar.GenWithStackByArgs(name, value)
case TiDBOptBCJ:
if TiDBOptOn(value) && vars.AllowBatchCop == 0 {
return value, ErrWrongValueForVar.GenWithStackByArgs("Can't set Broadcast Join to 1 but tidb_allow_batch_cop is 0, please active batch cop at first.")
}
return value, nil
case TiDBIndexLookupConcurrency,
TiDBIndexLookupJoinConcurrency,
TiDBHashJoinConcurrency,
TiDBHashAggPartialConcurrency,
TiDBHashAggFinalConcurrency,
TiDBWindowConcurrency:
v, err := strconv.Atoi(value)
if err != nil {
return value, ErrWrongTypeForVar.GenWithStackByArgs(name)
}
if v <= 0 && v != ConcurrencyUnset {
return value, ErrWrongValueForVar.GenWithStackByArgs(name, value)
}
return value, nil
case TiDBAllowBatchCop:
v, err := strconv.ParseInt(value, 10, 64)
if err != nil {
return value, ErrWrongTypeForVar.GenWithStackByArgs(name)
}
if v == 0 && vars.AllowBCJ {
return value, ErrWrongValueForVar.GenWithStackByArgs("Can't set batch cop 0 but tidb_opt_broadcast_join is 1, please set tidb_opt_broadcast_join 0 at first")
}
if v < 0 || v > 2 {
return value, ErrWrongValueForVar.GenWithStackByArgs(name, value)
}
return value, nil
case TiDBAutoAnalyzeStartTime, TiDBAutoAnalyzeEndTime, TiDBEvolvePlanTaskStartTime, TiDBEvolvePlanTaskEndTime:
v, err := setDayTime(vars, value)
if err != nil {
return "", err
}
return v, nil
case TxnIsolation, TransactionIsolation:
upVal := strings.ToUpper(value)
_, exists := TxIsolationNames[upVal]
if !exists {
return "", ErrWrongValueForVar.GenWithStackByArgs(name, value)
}
switch upVal {
case "SERIALIZABLE", "READ-UNCOMMITTED":
skipIsolationLevelCheck, err := GetSessionSystemVar(vars, TiDBSkipIsolationLevelCheck)
returnErr := ErrUnsupportedIsolationLevel.GenWithStackByArgs(value)
if err != nil {
returnErr = err
}
if !TiDBOptOn(skipIsolationLevelCheck) || err != nil {
return "", returnErr
}
//SET TRANSACTION ISOLATION LEVEL will affect two internal variables:
// 1. tx_isolation
// 2. transaction_isolation
// The following if condition is used to deduplicate two same warnings.
if name == "transaction_isolation" {
vars.StmtCtx.AppendWarning(returnErr)
}
}
return upVal, nil
case TiDBTxnMode:
switch strings.ToUpper(value) {
case ast.Pessimistic, ast.Optimistic, "":
default:
return value, ErrWrongValueForVar.GenWithStackByArgs(TiDBTxnMode, value)
}
case TiDBPartitionPruneMode:
if !PartitionPruneMode(value).Valid() {
return value, ErrWrongTypeForVar.GenWithStackByArgs(name)
}
case TiDBIsolationReadEngines:
engines := strings.Split(value, ",")
var formatVal string
for i, engine := range engines {
engine = strings.TrimSpace(engine)
if i != 0 {
formatVal += ","
}
switch {
case strings.EqualFold(engine, kv.TiKV.Name()):
formatVal += kv.TiKV.Name()
case strings.EqualFold(engine, kv.TiFlash.Name()):
formatVal += kv.TiFlash.Name()
case strings.EqualFold(engine, kv.TiDB.Name()):
formatVal += kv.TiDB.Name()
default:
return value, ErrWrongValueForVar.GenWithStackByArgs(name, value)
}
}
return formatVal, nil
case CollationConnection, CollationDatabase, CollationServer:
if _, err := collate.GetCollationByName(value); err != nil {
return value, errors.Trace(err)
}
}
return value, nil
}
// TiDBOptOn could be used for all tidb session variable options, we use "ON"/1 to turn on those options.
func TiDBOptOn(opt string) bool {
return strings.EqualFold(opt, "ON") || opt == "1"
}
func tidbOptPositiveInt32(opt string, defaultVal int) int {
val, err := strconv.Atoi(opt)
if err != nil || val <= 0 {
return defaultVal
}
return val
}
func tidbOptInt64(opt string, defaultVal int64) int64 {
val, err := strconv.ParseInt(opt, 10, 64)
if err != nil {
return defaultVal
}
return val
}
func tidbOptFloat64(opt string, defaultVal float64) float64 {
val, err := strconv.ParseFloat(opt, 64)
if err != nil {
return defaultVal
}
return val
}
func parseTimeZone(s string) (*time.Location, error) {
if strings.EqualFold(s, "SYSTEM") {
return timeutil.SystemLocation(), nil
}
loc, err := time.LoadLocation(s)
if err == nil {
return loc, nil
}
// The value can be given as a string indicating an offset from UTC, such as '+10:00' or '-6:00'.
// The time zone's value should in [-12:59,+14:00].
if strings.HasPrefix(s, "+") || strings.HasPrefix(s, "-") {
d, err := types.ParseDuration(nil, s[1:], 0)
if err == nil {
if s[0] == '-' {
if d.Duration > 12*time.Hour+59*time.Minute {
return nil, ErrUnknownTimeZone.GenWithStackByArgs(s)
}
} else {
if d.Duration > 14*time.Hour {
return nil, ErrUnknownTimeZone.GenWithStackByArgs(s)
}
}
ofst := int(d.Duration / time.Second)
if s[0] == '-' {
ofst = -ofst
}
return time.FixedZone("", ofst), nil
}
}
return nil, ErrUnknownTimeZone.GenWithStackByArgs(s)
}
func setSnapshotTS(s *SessionVars, sVal string) error {
if sVal == "" {
s.SnapshotTS = 0
return nil
}
if tso, err := strconv.ParseUint(sVal, 10, 64); err == nil {
s.SnapshotTS = tso
return nil
}
t, err := types.ParseTime(s.StmtCtx, sVal, mysql.TypeTimestamp, types.MaxFsp)
if err != nil {
return err
}
t1, err := t.GoTime(s.TimeZone)
s.SnapshotTS = GoTimeToTS(t1)
return err
}
// GoTimeToTS converts a Go time to uint64 timestamp.
func GoTimeToTS(t time.Time) uint64 {
ts := (t.UnixNano() / int64(time.Millisecond)) << epochShiftBits
return uint64(ts)
}
const (
localDayTimeFormat = "15:04"
// FullDayTimeFormat is the full format of analyze start time and end time.
FullDayTimeFormat = "15:04 -0700"
)
func setDayTime(s *SessionVars, val string) (string, error) {
var t time.Time
var err error
if len(val) <= len(localDayTimeFormat) {
t, err = time.ParseInLocation(localDayTimeFormat, val, s.TimeZone)
} else {
t, err = time.ParseInLocation(FullDayTimeFormat, val, s.TimeZone)
}
if err != nil {
return "", err
}
return t.Format(FullDayTimeFormat), nil
}
// serverGlobalVariable is used to handle variables that acts in server and global scope.
type serverGlobalVariable struct {
sync.Mutex
serverVal string
globalVal string
}
// Set sets the value according to variable scope.
func (v *serverGlobalVariable) Set(val string, isServer bool) {
v.Lock()
if isServer {
v.serverVal = val
} else {
v.globalVal = val
}
v.Unlock()
}
// GetVal gets the value.
func (v *serverGlobalVariable) GetVal() string {
v.Lock()
defer v.Unlock()
if v.serverVal != "" {
return v.serverVal
}
return v.globalVal
}