github.com/pingcap/tiflow@v0.0.0-20240520035814-5bf52d54e205/dm/pkg/utils/common.go (about) 1 // Copyright 2019 PingCAP, Inc. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // See the License for the specific language governing permissions and 12 // limitations under the License. 13 14 package utils 15 16 import ( 17 "fmt" 18 "regexp" 19 "strings" 20 "sync" 21 22 exprctx "github.com/pingcap/tidb/pkg/expression/context" 23 "github.com/pingcap/tidb/pkg/expression/contextsession" 24 infoschema "github.com/pingcap/tidb/pkg/infoschema/context" 25 "github.com/pingcap/tidb/pkg/parser/model" 26 "github.com/pingcap/tidb/pkg/sessionctx" 27 "github.com/pingcap/tidb/pkg/sessionctx/variable" 28 "github.com/pingcap/tidb/pkg/table" 29 "github.com/pingcap/tidb/pkg/types" 30 "github.com/pingcap/tidb/pkg/util/dbutil" 31 "github.com/pingcap/tidb/pkg/util/filter" 32 "github.com/pingcap/tiflow/dm/pkg/log" 33 "go.uber.org/zap" 34 ) 35 36 func init() { 37 ZeroSessionCtx = NewSessionCtx(nil) 38 } 39 40 // TrimCtrlChars returns a slice of the string s with all leading 41 // and trailing control characters removed. 42 func TrimCtrlChars(s string) string { 43 f := func(r rune) bool { 44 // All entries in the ASCII table below code 32 (technically the C0 control code set) are of this kind, 45 // including CR and LF used to separate lines of text. The code 127 (DEL) is also a control character. 46 // Reference: https://en.wikipedia.org/wiki/Control_character 47 return r < 32 || r == 127 48 } 49 50 return strings.TrimFunc(s, f) 51 } 52 53 // TrimQuoteMark tries to trim leading and tailing quote(") mark if exists 54 // only trim if leading and tailing quote matched as a pair. 55 func TrimQuoteMark(s string) string { 56 if len(s) > 2 && s[0] == '"' && s[len(s)-1] == '"' { 57 return s[1 : len(s)-1] 58 } 59 return s 60 } 61 62 // CompareShardingDDLs compares s and t ddls 63 // only concern in content, ignore order of ddl. 64 func CompareShardingDDLs(s, t []string) bool { 65 if len(s) != len(t) { 66 return false 67 } 68 69 ddls := make(map[string]struct{}) 70 for _, ddl := range s { 71 ddls[ddl] = struct{}{} 72 } 73 74 for _, ddl := range t { 75 if _, ok := ddls[ddl]; !ok { 76 return false 77 } 78 } 79 80 return true 81 } 82 83 // GenDDLLockID returns lock ID used in shard-DDL. 84 func GenDDLLockID(task, schema, table string) string { 85 return fmt.Sprintf("%s-%s", task, dbutil.TableName(schema, table)) 86 } 87 88 var lockIDPattern = regexp.MustCompile("(.*)\\-\\`(.*)\\`.\\`(.*)\\`") 89 90 // ExtractTaskFromLockID extract task from lockID. 91 func ExtractTaskFromLockID(lockID string) string { 92 strs := lockIDPattern.FindStringSubmatch(lockID) 93 // strs should be [full-lock-ID, task, db, table] if successful matched 94 if len(strs) < 4 { 95 return "" 96 } 97 return strs[1] 98 } 99 100 // ExtractDBAndTableFromLockID extract schema and table from lockID. 101 func ExtractDBAndTableFromLockID(lockID string) (string, string) { 102 strs := lockIDPattern.FindStringSubmatch(lockID) 103 // strs should be [full-lock-ID, task, db, table] if successful matched 104 if len(strs) < 4 { 105 return "", "" 106 } 107 return strs[2], strs[3] 108 } 109 110 // NonRepeatStringsEqual is used to compare two un-ordered, non-repeat-element string slice is equal. 111 func NonRepeatStringsEqual(a, b []string) bool { 112 if len(a) != len(b) { 113 return false 114 } 115 m := make(map[string]struct{}, len(a)) 116 for _, s := range a { 117 m[s] = struct{}{} 118 } 119 for _, s := range b { 120 if _, ok := m[s]; !ok { 121 return false 122 } 123 } 124 return true 125 } 126 127 // GenTableID generates table ID. 128 func GenTableID(table *filter.Table) string { 129 return table.String() 130 } 131 132 // GenSchemaID generates schema ID. 133 func GenSchemaID(table *filter.Table) string { 134 return "`" + table.Schema + "`" 135 } 136 137 // GenTableIDAndCheckSchemaOnly generates table ID and check if schema only. 138 func GenTableIDAndCheckSchemaOnly(table *filter.Table) (id string, isSchemaOnly bool) { 139 return GenTableID(table), len(table.Name) == 0 140 } 141 142 // UnpackTableID unpacks table ID to <schema, table> pair. 143 func UnpackTableID(id string) *filter.Table { 144 parts := strings.Split(id, "`.`") 145 schema := strings.TrimLeft(parts[0], "`") 146 table := strings.TrimRight(parts[1], "`") 147 return &filter.Table{ 148 Schema: schema, 149 Name: table, 150 } 151 } 152 153 type session struct { 154 sessionctx.Context 155 vars *variable.SessionVars 156 exprctx exprctx.ExprContext 157 values map[fmt.Stringer]interface{} 158 builtinFunctionUsage map[string]uint32 159 mu sync.RWMutex 160 } 161 162 // GetSessionVars implements the sessionctx.Context interface. 163 func (se *session) GetSessionVars() *variable.SessionVars { 164 return se.vars 165 } 166 167 func (se *session) GetExprCtx() exprctx.ExprContext { 168 return se.exprctx 169 } 170 171 // SetValue implements the sessionctx.Context interface. 172 func (se *session) SetValue(key fmt.Stringer, value interface{}) { 173 se.mu.Lock() 174 se.values[key] = value 175 se.mu.Unlock() 176 } 177 178 // Value implements the sessionctx.Context interface. 179 func (se *session) Value(key fmt.Stringer) interface{} { 180 se.mu.RLock() 181 value := se.values[key] 182 se.mu.RUnlock() 183 return value 184 } 185 186 // GetInfoSchema implements the sessionctx.Context interface. 187 func (se *session) GetInfoSchema() infoschema.MetaOnlyInfoSchema { 188 return nil 189 } 190 191 // GetBuiltinFunctionUsage implements the sessionctx.Context interface. 192 func (se *session) GetBuiltinFunctionUsage() map[string]uint32 { 193 return se.builtinFunctionUsage 194 } 195 196 func (se *session) BuiltinFunctionUsageInc(scalarFuncSigName string) {} 197 198 // ZeroSessionCtx is used when the session variables is not important. 199 var ZeroSessionCtx sessionctx.Context 200 201 // NewSessionCtx return a session context with specified session variables. 202 func NewSessionCtx(vars map[string]string) sessionctx.Context { 203 variables := variable.NewSessionVars(nil) 204 for k, v := range vars { 205 _ = variables.SetSystemVar(k, v) 206 if strings.EqualFold(k, "time_zone") { 207 loc, _ := ParseTimeZone(v) 208 variables.StmtCtx.SetTimeZone(loc) 209 variables.TimeZone = loc 210 } 211 } 212 sessionCtx := session{ 213 vars: variables, 214 values: make(map[fmt.Stringer]interface{}, 1), 215 builtinFunctionUsage: make(map[string]uint32), 216 } 217 sessionCtx.exprctx = contextsession.NewExprExtendedImpl(&sessionCtx) 218 return &sessionCtx 219 } 220 221 // AdjustBinaryProtocolForDatum converts the data in binlog to TiDB datum. 222 func AdjustBinaryProtocolForDatum(ctx sessionctx.Context, data []interface{}, cols []*model.ColumnInfo) ([]types.Datum, error) { 223 ret := make([]types.Datum, 0, len(data)) 224 for i, d := range data { 225 datum := types.NewDatum(d) 226 castDatum, err := table.CastValue(ctx, datum, cols[i], false, false) 227 if err != nil { 228 return nil, err 229 } 230 ret = append(ret, castDatum) 231 } 232 return ret, nil 233 } 234 235 // GoLogWrapper go routine wrapper, log error on panic. 236 func GoLogWrapper(logger log.Logger, fn func()) { 237 defer func() { 238 if err := recover(); err != nil { 239 logger.Error("routine panic", zap.Any("err", err)) 240 } 241 }() 242 243 fn() 244 }