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  }