github.com/pingcap/tiflow@v0.0.0-20240520035814-5bf52d54e205/dm/config/task.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 config
    15  
    16  import (
    17  	"encoding/json"
    18  	"flag"
    19  	"fmt"
    20  	"math"
    21  	"os"
    22  	"sort"
    23  	"strconv"
    24  	"strings"
    25  	"sync"
    26  	"time"
    27  
    28  	"github.com/coreos/go-semver/semver"
    29  	"github.com/docker/go-units"
    30  	"github.com/dustin/go-humanize"
    31  	"github.com/pingcap/tidb/pkg/lightning/config"
    32  	"github.com/pingcap/tidb/pkg/parser"
    33  	"github.com/pingcap/tidb/pkg/util/filter"
    34  	router "github.com/pingcap/tidb/pkg/util/table-router"
    35  	"github.com/pingcap/tiflow/dm/config/dbconfig"
    36  	"github.com/pingcap/tiflow/dm/pkg/log"
    37  	"github.com/pingcap/tiflow/dm/pkg/terror"
    38  	"github.com/pingcap/tiflow/dm/pkg/utils"
    39  	bf "github.com/pingcap/tiflow/pkg/binlog-filter"
    40  	"github.com/pingcap/tiflow/pkg/column-mapping"
    41  	"go.uber.org/zap"
    42  	"gopkg.in/yaml.v2"
    43  )
    44  
    45  // Online DDL Scheme.
    46  const (
    47  	GHOST = "gh-ost"
    48  	PT    = "pt"
    49  )
    50  
    51  // shard DDL mode.
    52  const (
    53  	ShardPessimistic  = "pessimistic"
    54  	ShardOptimistic   = "optimistic"
    55  	tidbTxnMode       = "tidb_txn_mode"
    56  	tidbTxnOptimistic = "optimistic"
    57  )
    58  
    59  // collation_compatible.
    60  const (
    61  	LooseCollationCompatible  = "loose"
    62  	StrictCollationCompatible = "strict"
    63  )
    64  
    65  const (
    66  	ValidationNone = "none"
    67  	ValidationFast = "fast"
    68  	ValidationFull = "full"
    69  
    70  	DefaultValidatorWorkerCount       = 4
    71  	DefaultValidatorValidateInterval  = 10 * time.Second
    72  	DefaultValidatorCheckInterval     = 5 * time.Second
    73  	DefaultValidatorRowErrorDelay     = 30 * time.Minute
    74  	DefaultValidatorMetaFlushInterval = 5 * time.Minute
    75  	DefaultValidatorBatchQuerySize    = 100
    76  	DefaultValidatorMaxPendingRowSize = "500m"
    77  
    78  	ValidatorMaxAccumulatedRow = 100000
    79  	// PendingRow is substantial in this version (in sysbench test)
    80  	// set to MaxInt temporaly and reset in the future.
    81  	DefaultValidatorMaxPendingRow = math.MaxInt32
    82  )
    83  
    84  // default config item values.
    85  var (
    86  	// TaskConfig.
    87  	defaultMetaSchema          = "dm_meta"
    88  	defaultEnableHeartbeat     = false
    89  	defaultIsSharding          = false
    90  	defaultUpdateInterval      = 1
    91  	defaultReportInterval      = 10
    92  	defaultCollationCompatible = "loose"
    93  	// MydumperConfig.
    94  	defaultMydumperPath  = "./bin/mydumper"
    95  	defaultThreads       = 4
    96  	defaultChunkFilesize = "64"
    97  	defaultSkipTzUTC     = true
    98  	// LoaderConfig.
    99  	defaultPoolSize = 16
   100  	defaultDir      = "./dumped_data"
   101  	// SyncerConfig.
   102  	defaultWorkerCount             = 16
   103  	defaultBatch                   = 100
   104  	defaultQueueSize               = 1024 // do not give too large default value to avoid OOM
   105  	defaultCheckpointFlushInterval = 30   // in seconds
   106  	defaultSafeModeDuration        = strconv.Itoa(2*defaultCheckpointFlushInterval) + "s"
   107  
   108  	// TargetDBConfig.
   109  	defaultSessionCfg = []struct {
   110  		key        string
   111  		val        string
   112  		minVersion *semver.Version
   113  	}{
   114  		{tidbTxnMode, tidbTxnOptimistic, semver.New("3.0.0")},
   115  	}
   116  )
   117  
   118  // Meta represents binlog's meta pos
   119  // NOTE: refine to put these config structs into pkgs
   120  // NOTE: now, syncer does not support GTID mode and which is supported by relay.
   121  type Meta struct {
   122  	BinLogName string `toml:"binlog-name" yaml:"binlog-name"`
   123  	BinLogPos  uint32 `toml:"binlog-pos" yaml:"binlog-pos"`
   124  	BinLogGTID string `toml:"binlog-gtid" yaml:"binlog-gtid"`
   125  }
   126  
   127  // Verify does verification on configs
   128  // NOTE: we can't decide to verify `binlog-name` or `binlog-gtid` until being bound to a source (with `enable-gtid` set).
   129  func (m *Meta) Verify() error {
   130  	if m != nil && len(m.BinLogName) == 0 && len(m.BinLogGTID) == 0 {
   131  		return terror.ErrConfigMetaInvalid.Generate()
   132  	}
   133  
   134  	return nil
   135  }
   136  
   137  // MySQLInstance represents a sync config of a MySQL instance.
   138  type MySQLInstance struct {
   139  	// it represents a MySQL/MariaDB instance or a replica group
   140  	SourceID    string   `yaml:"source-id"`
   141  	Meta        *Meta    `yaml:"meta"`
   142  	FilterRules []string `yaml:"filter-rules"`
   143  	// deprecated
   144  	ColumnMappingRules []string `yaml:"column-mapping-rules"`
   145  	RouteRules         []string `yaml:"route-rules"`
   146  	ExpressionFilters  []string `yaml:"expression-filters"`
   147  
   148  	// black-white-list is deprecated, use block-allow-list instead
   149  	BWListName string `yaml:"black-white-list"`
   150  	BAListName string `yaml:"block-allow-list"`
   151  
   152  	MydumperConfigName string          `yaml:"mydumper-config-name"`
   153  	Mydumper           *MydumperConfig `yaml:"mydumper"`
   154  	// MydumperThread is alias for Threads in MydumperConfig, and its priority is higher than Threads
   155  	MydumperThread int `yaml:"mydumper-thread"`
   156  
   157  	LoaderConfigName string        `yaml:"loader-config-name"`
   158  	Loader           *LoaderConfig `yaml:"loader"`
   159  	// LoaderThread is alias for PoolSize in LoaderConfig, and its priority is higher than PoolSize
   160  	LoaderThread int `yaml:"loader-thread"`
   161  
   162  	SyncerConfigName string        `yaml:"syncer-config-name"`
   163  	Syncer           *SyncerConfig `yaml:"syncer"`
   164  	// SyncerThread is alias for WorkerCount in SyncerConfig, and its priority is higher than WorkerCount
   165  	SyncerThread int `yaml:"syncer-thread"`
   166  
   167  	ContinuousValidatorConfigName string          `yaml:"validator-config-name"`
   168  	ContinuousValidator           ValidatorConfig `yaml:"-"`
   169  }
   170  
   171  // VerifyAndAdjust does verification on configs, and adjust some configs.
   172  func (m *MySQLInstance) VerifyAndAdjust() error {
   173  	if m == nil {
   174  		return terror.ErrConfigMySQLInstNotFound.Generate()
   175  	}
   176  
   177  	if m.SourceID == "" {
   178  		return terror.ErrConfigEmptySourceID.Generate()
   179  	}
   180  
   181  	if err := m.Meta.Verify(); err != nil {
   182  		return terror.Annotatef(err, "source %s", m.SourceID)
   183  	}
   184  
   185  	if len(m.MydumperConfigName) > 0 && m.Mydumper != nil {
   186  		return terror.ErrConfigMydumperCfgConflict.Generate()
   187  	}
   188  	if len(m.LoaderConfigName) > 0 && m.Loader != nil {
   189  		return terror.ErrConfigLoaderCfgConflict.Generate()
   190  	}
   191  	if len(m.SyncerConfigName) > 0 && m.Syncer != nil {
   192  		return terror.ErrConfigSyncerCfgConflict.Generate()
   193  	}
   194  
   195  	if len(m.BAListName) == 0 && len(m.BWListName) != 0 {
   196  		m.BAListName = m.BWListName
   197  	}
   198  
   199  	return nil
   200  }
   201  
   202  // MydumperConfig represents mydumper process unit's specific config.
   203  type MydumperConfig struct {
   204  	MydumperPath  string `yaml:"mydumper-path" toml:"mydumper-path" json:"mydumper-path"`    // mydumper binary path
   205  	Threads       int    `yaml:"threads" toml:"threads" json:"threads"`                      // -t, --threads
   206  	ChunkFilesize string `yaml:"chunk-filesize" toml:"chunk-filesize" json:"chunk-filesize"` // -F, --chunk-filesize
   207  	StatementSize uint64 `yaml:"statement-size" toml:"statement-size" json:"statement-size"` // -S, --statement-size
   208  	Rows          uint64 `yaml:"rows" toml:"rows" json:"rows"`                               // -r, --rows
   209  	Where         string `yaml:"where" toml:"where" json:"where"`                            // --where
   210  
   211  	SkipTzUTC bool   `yaml:"skip-tz-utc" toml:"skip-tz-utc" json:"skip-tz-utc"` // --skip-tz-utc
   212  	ExtraArgs string `yaml:"extra-args" toml:"extra-args" json:"extra-args"`    // other extra args
   213  	// NOTE: use LoaderConfig.Dir as --outputdir
   214  	// TODO zxc: combine -B -T --regex with filter rules?
   215  }
   216  
   217  // DefaultMydumperConfig return default mydumper config for task.
   218  func DefaultMydumperConfig() MydumperConfig {
   219  	return MydumperConfig{
   220  		MydumperPath:  defaultMydumperPath,
   221  		Threads:       defaultThreads,
   222  		ChunkFilesize: defaultChunkFilesize,
   223  		SkipTzUTC:     defaultSkipTzUTC,
   224  	}
   225  }
   226  
   227  // alias to avoid infinite recursion for UnmarshalYAML.
   228  type rawMydumperConfig MydumperConfig
   229  
   230  // UnmarshalYAML implements Unmarshaler.UnmarshalYAML.
   231  func (m *MydumperConfig) UnmarshalYAML(unmarshal func(interface{}) error) error {
   232  	raw := rawMydumperConfig(DefaultMydumperConfig())
   233  	if err := unmarshal(&raw); err != nil {
   234  		return terror.ErrConfigYamlTransform.Delegate(err, "unmarshal mydumper config")
   235  	}
   236  	*m = MydumperConfig(raw) // raw used only internal, so no deep copy
   237  	return nil
   238  }
   239  
   240  // LoadMode defines different mode used in load phase.
   241  type LoadMode string
   242  
   243  const (
   244  	// LoadModeSQL means write data by sql statements, uses tidb-lightning tidb backend to load data.
   245  	// deprecated, use LoadModeLogical instead.
   246  	LoadModeSQL LoadMode = "sql"
   247  	// LoadModeLoader is the legacy sql mode, use loader to load data. this should be replaced by LoadModeLogical mode.
   248  	// deprecated, use LoadModeLogical instead.
   249  	LoadModeLoader LoadMode = "loader"
   250  	// LoadModeLogical means use tidb backend of lightning to load data, which uses SQL to load data.
   251  	LoadModeLogical LoadMode = "logical"
   252  	// LoadModePhysical means use local backend of lightning to load data, which ingest SST files to load data.
   253  	LoadModePhysical LoadMode = "physical"
   254  )
   255  
   256  // LogicalDuplicateResolveType defines the duplication resolution when meet duplicate rows for logical import.
   257  type LogicalDuplicateResolveType string
   258  
   259  const (
   260  	// OnDuplicateReplace represents replace the old row with new data.
   261  	OnDuplicateReplace LogicalDuplicateResolveType = "replace"
   262  	// OnDuplicateError represents return an error when meet duplicate row.
   263  	OnDuplicateError LogicalDuplicateResolveType = "error"
   264  	// OnDuplicateIgnore represents ignore the new data when meet duplicate row.
   265  	OnDuplicateIgnore LogicalDuplicateResolveType = "ignore"
   266  )
   267  
   268  // PhysicalDuplicateResolveType defines the duplication resolution when meet duplicate rows for physical import.
   269  type PhysicalDuplicateResolveType string
   270  
   271  const (
   272  	// OnDuplicateNone represents do nothing when meet duplicate row and the task will continue.
   273  	OnDuplicateNone PhysicalDuplicateResolveType = "none"
   274  	// OnDuplicateManual represents that task should be paused when meet duplicate row to let user handle it manually.
   275  	OnDuplicateManual PhysicalDuplicateResolveType = "manual"
   276  )
   277  
   278  // PhysicalPostOpLevel defines the configuration of checksum/analyze of physical import.
   279  type PhysicalPostOpLevel string
   280  
   281  const (
   282  	OpLevelRequired = "required"
   283  	OpLevelOptional = "optional"
   284  	OpLevelOff      = "off"
   285  )
   286  
   287  // LoaderConfig represents loader process unit's specific config.
   288  type LoaderConfig struct {
   289  	PoolSize           int      `yaml:"pool-size" toml:"pool-size" json:"pool-size"`
   290  	Dir                string   `yaml:"dir" toml:"dir" json:"dir"`
   291  	SortingDirPhysical string   `yaml:"sorting-dir-physical" toml:"sorting-dir-physical" json:"sorting-dir-physical"`
   292  	SQLMode            string   `yaml:"-" toml:"-" json:"-"` // wrote by dump unit (DM op) or jobmaster (DM in engine)
   293  	ImportMode         LoadMode `yaml:"import-mode" toml:"import-mode" json:"import-mode"`
   294  	// deprecated, use OnDuplicateLogical instead.
   295  	OnDuplicate         LogicalDuplicateResolveType  `yaml:"on-duplicate" toml:"on-duplicate" json:"on-duplicate"`
   296  	OnDuplicateLogical  LogicalDuplicateResolveType  `yaml:"on-duplicate-logical" toml:"on-duplicate-logical" json:"on-duplicate-logical"`
   297  	OnDuplicatePhysical PhysicalDuplicateResolveType `yaml:"on-duplicate-physical" toml:"on-duplicate-physical" json:"on-duplicate-physical"`
   298  	DiskQuotaPhysical   config.ByteSize              `yaml:"disk-quota-physical" toml:"disk-quota-physical" json:"disk-quota-physical"`
   299  	ChecksumPhysical    PhysicalPostOpLevel          `yaml:"checksum-physical" toml:"checksum-physical" json:"checksum-physical"`
   300  	Analyze             PhysicalPostOpLevel          `yaml:"analyze" toml:"analyze" json:"analyze"`
   301  	RangeConcurrency    int                          `yaml:"range-concurrency" toml:"range-concurrency" json:"range-concurrency"`
   302  	CompressKVPairs     string                       `yaml:"compress-kv-pairs" toml:"compress-kv-pairs" json:"compress-kv-pairs"`
   303  	PDAddr              string                       `yaml:"pd-addr" toml:"pd-addr" json:"pd-addr"`
   304  }
   305  
   306  // DefaultLoaderConfig return default loader config for task.
   307  func DefaultLoaderConfig() LoaderConfig {
   308  	return LoaderConfig{
   309  		PoolSize:           defaultPoolSize,
   310  		Dir:                defaultDir,
   311  		ImportMode:         LoadModeLogical,
   312  		OnDuplicateLogical: OnDuplicateReplace,
   313  	}
   314  }
   315  
   316  // alias to avoid infinite recursion for UnmarshalYAML.
   317  type rawLoaderConfig LoaderConfig
   318  
   319  // UnmarshalYAML implements Unmarshaler.UnmarshalYAML.
   320  func (m *LoaderConfig) UnmarshalYAML(unmarshal func(interface{}) error) error {
   321  	raw := rawLoaderConfig(DefaultLoaderConfig())
   322  	if err := unmarshal(&raw); err != nil {
   323  		return terror.ErrConfigYamlTransform.Delegate(err, "unmarshal loader config")
   324  	}
   325  	*m = LoaderConfig(raw) // raw used only internal, so no deep copy
   326  	return nil
   327  }
   328  
   329  func (m *LoaderConfig) adjust() error {
   330  	if m.ImportMode == "" {
   331  		m.ImportMode = LoadModeLogical
   332  	}
   333  	if strings.EqualFold(string(m.ImportMode), string(LoadModeSQL)) ||
   334  		strings.EqualFold(string(m.ImportMode), string(LoadModeLoader)) {
   335  		m.ImportMode = LoadModeLogical
   336  	}
   337  	m.ImportMode = LoadMode(strings.ToLower(string(m.ImportMode)))
   338  	switch m.ImportMode {
   339  	case LoadModeLoader, LoadModeSQL, LoadModeLogical, LoadModePhysical:
   340  	default:
   341  		return terror.ErrConfigInvalidLoadMode.Generate(m.ImportMode)
   342  	}
   343  
   344  	if m.PoolSize == 0 {
   345  		m.PoolSize = defaultPoolSize
   346  	}
   347  
   348  	if m.OnDuplicateLogical == "" {
   349  		m.OnDuplicateLogical = OnDuplicateReplace
   350  	}
   351  	m.OnDuplicateLogical = LogicalDuplicateResolveType(strings.ToLower(string(m.OnDuplicateLogical)))
   352  	switch m.OnDuplicateLogical {
   353  	case OnDuplicateReplace, OnDuplicateError, OnDuplicateIgnore:
   354  	default:
   355  		return terror.ErrConfigInvalidDuplicateResolution.Generate(m.OnDuplicateLogical)
   356  	}
   357  
   358  	if m.OnDuplicatePhysical == "" {
   359  		m.OnDuplicatePhysical = OnDuplicateNone
   360  	}
   361  	m.OnDuplicatePhysical = PhysicalDuplicateResolveType(strings.ToLower(string(m.OnDuplicatePhysical)))
   362  	switch m.OnDuplicatePhysical {
   363  	case OnDuplicateNone, OnDuplicateManual:
   364  	default:
   365  		return terror.ErrConfigInvalidPhysicalDuplicateResolution.Generate(m.OnDuplicatePhysical)
   366  	}
   367  
   368  	if m.ChecksumPhysical == "" {
   369  		m.ChecksumPhysical = OpLevelRequired
   370  	}
   371  	m.ChecksumPhysical = PhysicalPostOpLevel(strings.ToLower(string(m.ChecksumPhysical)))
   372  	switch m.ChecksumPhysical {
   373  	case OpLevelRequired, OpLevelOptional, OpLevelOff:
   374  	default:
   375  		return terror.ErrConfigInvalidPhysicalChecksum.Generate(m.ChecksumPhysical)
   376  	}
   377  
   378  	if m.Analyze == "" {
   379  		m.Analyze = OpLevelOptional
   380  	}
   381  	m.Analyze = PhysicalPostOpLevel(strings.ToLower(string(m.Analyze)))
   382  	switch m.Analyze {
   383  	case OpLevelRequired, OpLevelOptional, OpLevelOff:
   384  	default:
   385  		return terror.ErrConfigInvalidLoadAnalyze.Generate(m.Analyze)
   386  	}
   387  
   388  	return nil
   389  }
   390  
   391  // SyncerConfig represents syncer process unit's specific config.
   392  type SyncerConfig struct {
   393  	MetaFile    string `yaml:"meta-file" toml:"meta-file" json:"meta-file"` // meta filename, used only when load SubConfig directly
   394  	WorkerCount int    `yaml:"worker-count" toml:"worker-count" json:"worker-count"`
   395  	Batch       int    `yaml:"batch" toml:"batch" json:"batch"`
   396  	QueueSize   int    `yaml:"queue-size" toml:"queue-size" json:"queue-size"`
   397  	// checkpoint flush interval in seconds.
   398  	CheckpointFlushInterval int `yaml:"checkpoint-flush-interval" toml:"checkpoint-flush-interval" json:"checkpoint-flush-interval"`
   399  	// TODO: add this two new config items for openapi.
   400  	Compact      bool `yaml:"compact" toml:"compact" json:"compact"`
   401  	MultipleRows bool `yaml:"multiple-rows" toml:"multiple-rows" json:"multiple-rows"`
   402  
   403  	// deprecated
   404  	MaxRetry int `yaml:"max-retry" toml:"max-retry" json:"max-retry"`
   405  
   406  	// deprecated
   407  	AutoFixGTID bool `yaml:"auto-fix-gtid" toml:"auto-fix-gtid" json:"auto-fix-gtid"`
   408  	EnableGTID  bool `yaml:"enable-gtid" toml:"enable-gtid" json:"enable-gtid"`
   409  	// deprecated
   410  	DisableCausality bool   `yaml:"disable-detect" toml:"disable-detect" json:"disable-detect"`
   411  	SafeMode         bool   `yaml:"safe-mode" toml:"safe-mode" json:"safe-mode"`
   412  	SafeModeDuration string `yaml:"safe-mode-duration" toml:"safe-mode-duration" json:"safe-mode-duration"`
   413  	// deprecated, use `ansi-quotes` in top level config instead
   414  	EnableANSIQuotes bool `yaml:"enable-ansi-quotes" toml:"enable-ansi-quotes" json:"enable-ansi-quotes"`
   415  }
   416  
   417  // DefaultSyncerConfig return default syncer config for task.
   418  func DefaultSyncerConfig() SyncerConfig {
   419  	return SyncerConfig{
   420  		WorkerCount:             defaultWorkerCount,
   421  		Batch:                   defaultBatch,
   422  		QueueSize:               defaultQueueSize,
   423  		CheckpointFlushInterval: defaultCheckpointFlushInterval,
   424  		SafeModeDuration:        defaultSafeModeDuration,
   425  	}
   426  }
   427  
   428  // alias to avoid infinite recursion for UnmarshalYAML.
   429  type rawSyncerConfig SyncerConfig
   430  
   431  // UnmarshalYAML implements Unmarshaler.UnmarshalYAML.
   432  func (m *SyncerConfig) UnmarshalYAML(unmarshal func(interface{}) error) error {
   433  	raw := rawSyncerConfig(DefaultSyncerConfig())
   434  	if err := unmarshal(&raw); err != nil {
   435  		return terror.ErrConfigYamlTransform.Delegate(err, "unmarshal syncer config")
   436  	}
   437  	*m = SyncerConfig(raw) // raw used only internal, so no deep copy
   438  	return nil
   439  }
   440  
   441  type ValidatorConfig struct {
   442  	Mode               string   `yaml:"mode" toml:"mode" json:"mode"`
   443  	WorkerCount        int      `yaml:"worker-count" toml:"worker-count" json:"worker-count"`
   444  	ValidateInterval   Duration `yaml:"validate-interval" toml:"validate-interval" json:"validate-interval"`
   445  	CheckInterval      Duration `yaml:"check-interval" toml:"check-interval" json:"check-interval"`
   446  	RowErrorDelay      Duration `yaml:"row-error-delay" toml:"row-error-delay" json:"row-error-delay"`
   447  	MetaFlushInterval  Duration `yaml:"meta-flush-interval" toml:"meta-flush-interval" json:"meta-flush-interval"`
   448  	BatchQuerySize     int      `yaml:"batch-query-size" toml:"batch-query-size" json:"batch-query-size"`
   449  	MaxPendingRowSize  string   `yaml:"max-pending-row-size" toml:"max-pending-row-size" json:"max-pending-row-size"`
   450  	MaxPendingRowCount int      `yaml:"max-pending-row-count" toml:"max-pending-row-count" json:"max-pending-row-count"`
   451  	StartTime          string   `yaml:"-" toml:"start-time" json:"-"`
   452  }
   453  
   454  func (v *ValidatorConfig) Adjust() error {
   455  	if v.Mode == "" {
   456  		v.Mode = ValidationNone
   457  	}
   458  	if v.Mode != ValidationNone && v.Mode != ValidationFast && v.Mode != ValidationFull {
   459  		return terror.ErrConfigValidationMode
   460  	}
   461  	if v.WorkerCount <= 0 {
   462  		v.WorkerCount = DefaultValidatorWorkerCount
   463  	}
   464  	if v.ValidateInterval.Duration == 0 {
   465  		v.ValidateInterval.Duration = DefaultValidatorValidateInterval
   466  	}
   467  	if v.CheckInterval.Duration == 0 {
   468  		v.CheckInterval.Duration = DefaultValidatorCheckInterval
   469  	}
   470  	if v.RowErrorDelay.Duration == 0 {
   471  		v.RowErrorDelay.Duration = DefaultValidatorRowErrorDelay
   472  	}
   473  	if v.MetaFlushInterval.Duration == 0 {
   474  		v.MetaFlushInterval.Duration = DefaultValidatorMetaFlushInterval
   475  	}
   476  	if v.BatchQuerySize == 0 {
   477  		v.BatchQuerySize = DefaultValidatorBatchQuerySize
   478  	}
   479  	if v.MaxPendingRowSize == "" {
   480  		v.MaxPendingRowSize = DefaultValidatorMaxPendingRowSize
   481  	}
   482  
   483  	_, err := units.RAMInBytes(v.MaxPendingRowSize)
   484  	if err != nil {
   485  		return err
   486  	}
   487  	if v.MaxPendingRowCount == 0 {
   488  		v.MaxPendingRowCount = DefaultValidatorMaxPendingRow
   489  	}
   490  	return nil
   491  }
   492  
   493  func defaultValidatorConfig() ValidatorConfig {
   494  	return ValidatorConfig{
   495  		Mode: ValidationNone,
   496  	}
   497  }
   498  
   499  // TaskConfig is the configuration for Task.
   500  type TaskConfig struct {
   501  	*flag.FlagSet `yaml:"-" toml:"-" json:"-"`
   502  
   503  	Name                      string `yaml:"name" toml:"name" json:"name"`
   504  	TaskMode                  string `yaml:"task-mode" toml:"task-mode" json:"task-mode"`
   505  	IsSharding                bool   `yaml:"is-sharding" toml:"is-sharding" json:"is-sharding"`
   506  	ShardMode                 string `yaml:"shard-mode" toml:"shard-mode" json:"shard-mode"` // when `shard-mode` set, we always enable sharding support.
   507  	StrictOptimisticShardMode bool   `yaml:"strict-optimistic-shard-mode" toml:"strict-optimistic-shard-mode" json:"strict-optimistic-shard-mode"`
   508  	// treat it as hidden configuration
   509  	IgnoreCheckingItems []string `yaml:"ignore-checking-items" toml:"ignore-checking-items" json:"ignore-checking-items"`
   510  	// we store detail status in meta
   511  	// don't save configuration into it
   512  	MetaSchema string `yaml:"meta-schema" toml:"meta-schema" json:"meta-schema"`
   513  	// deprecated
   514  	EnableHeartbeat bool `yaml:"enable-heartbeat" toml:"enable-heartbeat" json:"enable-heartbeat"`
   515  	// deprecated
   516  	HeartbeatUpdateInterval int `yaml:"heartbeat-update-interval" toml:"heartbeat-update-interval" json:"heartbeat-update-interval"`
   517  	// deprecated
   518  	HeartbeatReportInterval int    `yaml:"heartbeat-report-interval" toml:"heartbeat-report-interval" json:"heartbeat-report-interval"`
   519  	Timezone                string `yaml:"timezone" toml:"timezone" json:"timezone"`
   520  
   521  	// handle schema/table name mode, and only for schema/table name
   522  	// if case insensitive, we would convert schema/table name to lower case
   523  	CaseSensitive bool `yaml:"case-sensitive" toml:"case-sensitive" json:"case-sensitive"`
   524  
   525  	// default "loose" handle create sql by original sql, will not add default collation as upstream
   526  	// "strict" will add default collation as upstream, and downstream will occur error when downstream don't support
   527  	CollationCompatible string `yaml:"collation_compatible" toml:"collation_compatible" json:"collation_compatible"`
   528  
   529  	TargetDB *dbconfig.DBConfig `yaml:"target-database" toml:"target-database" json:"target-database"`
   530  
   531  	MySQLInstances []*MySQLInstance `yaml:"mysql-instances" toml:"mysql-instances" json:"mysql-instances"`
   532  
   533  	OnlineDDL bool `yaml:"online-ddl" toml:"online-ddl" json:"online-ddl"`
   534  	// pt/gh-ost name rule,support regex
   535  	ShadowTableRules []string `yaml:"shadow-table-rules" toml:"shadow-table-rules" json:"shadow-table-rules"`
   536  	TrashTableRules  []string `yaml:"trash-table-rules" toml:"trash-table-rules" json:"trash-table-rules"`
   537  
   538  	// deprecated
   539  	OnlineDDLScheme string `yaml:"online-ddl-scheme" toml:"online-ddl-scheme" json:"online-ddl-scheme"`
   540  
   541  	Routes  map[string]*router.TableRule   `yaml:"routes" toml:"routes" json:"routes"`
   542  	Filters map[string]*bf.BinlogEventRule `yaml:"filters" toml:"filters" json:"filters"`
   543  	// deprecated
   544  	ColumnMappings map[string]*column.Rule      `yaml:"column-mappings" toml:"column-mappings" json:"column-mappings"`
   545  	ExprFilter     map[string]*ExpressionFilter `yaml:"expression-filter" toml:"expression-filter" json:"expression-filter"`
   546  
   547  	// black-white-list is deprecated, use block-allow-list instead
   548  	BWList map[string]*filter.Rules `yaml:"black-white-list" toml:"black-white-list" json:"black-white-list"`
   549  	BAList map[string]*filter.Rules `yaml:"block-allow-list" toml:"block-allow-list" json:"block-allow-list"`
   550  
   551  	Mydumpers  map[string]*MydumperConfig  `yaml:"mydumpers" toml:"mydumpers" json:"mydumpers"`
   552  	Loaders    map[string]*LoaderConfig    `yaml:"loaders" toml:"loaders" json:"loaders"`
   553  	Syncers    map[string]*SyncerConfig    `yaml:"syncers" toml:"syncers" json:"syncers"`
   554  	Validators map[string]*ValidatorConfig `yaml:"validators" toml:"validators" json:"validators"`
   555  
   556  	CleanDumpFile bool `yaml:"clean-dump-file" toml:"clean-dump-file" json:"clean-dump-file"`
   557  	// deprecated
   558  	EnableANSIQuotes bool `yaml:"ansi-quotes" toml:"ansi-quotes" json:"ansi-quotes"`
   559  
   560  	// deprecated, replaced by `start-task --remove-meta`
   561  	RemoveMeta bool `yaml:"remove-meta"`
   562  
   563  	// task experimental configs
   564  	Experimental struct {
   565  		AsyncCheckpointFlush bool `yaml:"async-checkpoint-flush" toml:"async-checkpoint-flush" json:"async-checkpoint-flush"`
   566  	} `yaml:"experimental" toml:"experimental" json:"experimental"`
   567  }
   568  
   569  // NewTaskConfig creates a TaskConfig.
   570  func NewTaskConfig() *TaskConfig {
   571  	cfg := &TaskConfig{
   572  		// explicitly set default value
   573  		MetaSchema:              defaultMetaSchema,
   574  		EnableHeartbeat:         defaultEnableHeartbeat,
   575  		HeartbeatUpdateInterval: defaultUpdateInterval,
   576  		HeartbeatReportInterval: defaultReportInterval,
   577  		MySQLInstances:          make([]*MySQLInstance, 0, 5),
   578  		IsSharding:              defaultIsSharding,
   579  		Routes:                  make(map[string]*router.TableRule),
   580  		Filters:                 make(map[string]*bf.BinlogEventRule),
   581  		ColumnMappings:          make(map[string]*column.Rule),
   582  		ExprFilter:              make(map[string]*ExpressionFilter),
   583  		BWList:                  make(map[string]*filter.Rules),
   584  		BAList:                  make(map[string]*filter.Rules),
   585  		Mydumpers:               make(map[string]*MydumperConfig),
   586  		Loaders:                 make(map[string]*LoaderConfig),
   587  		Syncers:                 make(map[string]*SyncerConfig),
   588  		Validators:              make(map[string]*ValidatorConfig),
   589  		CleanDumpFile:           true,
   590  		OnlineDDL:               true,
   591  		CollationCompatible:     defaultCollationCompatible,
   592  	}
   593  	cfg.FlagSet = flag.NewFlagSet("task", flag.ContinueOnError)
   594  	return cfg
   595  }
   596  
   597  // String returns the config's yaml string.
   598  func (c *TaskConfig) String() string {
   599  	cfg, err := yaml.Marshal(c)
   600  	if err != nil {
   601  		log.L().Error("marshal task config to yaml", zap.String("task", c.Name), log.ShortError(err))
   602  	}
   603  	return string(cfg)
   604  }
   605  
   606  // JSON returns the config's json string.
   607  func (c *TaskConfig) JSON() string {
   608  	//nolint:staticcheck
   609  	cfg, err := json.Marshal(c)
   610  	if err != nil {
   611  		log.L().Error("marshal task config to json", zap.String("task", c.Name), log.ShortError(err))
   612  	}
   613  	return string(cfg)
   614  }
   615  
   616  // DecodeFile loads and decodes config from file.
   617  func (c *TaskConfig) DecodeFile(fpath string) error {
   618  	bs, err := os.ReadFile(fpath)
   619  	if err != nil {
   620  		return terror.ErrConfigReadCfgFromFile.Delegate(err, fpath)
   621  	}
   622  
   623  	err = yaml.UnmarshalStrict(bs, c)
   624  	if err != nil {
   625  		return terror.ErrConfigYamlTransform.Delegate(err)
   626  	}
   627  
   628  	return c.adjust()
   629  }
   630  
   631  // FromYaml loads config from file data.
   632  func (c *TaskConfig) FromYaml(data string) error {
   633  	err := yaml.UnmarshalStrict([]byte(data), c)
   634  	if err != nil {
   635  		return terror.ErrConfigYamlTransform.Delegate(err, "decode task config failed")
   636  	}
   637  
   638  	return c.adjust()
   639  }
   640  
   641  // RawDecode loads config from file data.
   642  func (c *TaskConfig) RawDecode(data string) error {
   643  	return terror.ErrConfigYamlTransform.Delegate(yaml.UnmarshalStrict([]byte(data), c), "decode task config failed")
   644  }
   645  
   646  // find unused items in config.
   647  var configRefPrefixes = []string{"RouteRules", "FilterRules", "Mydumper", "Loader", "Syncer", "ExprFilter", "Validator"}
   648  
   649  const (
   650  	routeRulesIdx = iota
   651  	filterRulesIdx
   652  	mydumperIdx
   653  	loaderIdx
   654  	syncerIdx
   655  	exprFilterIdx
   656  	validatorIdx
   657  )
   658  
   659  // Adjust adjusts and verifies config.
   660  func (c *TaskConfig) Adjust() error {
   661  	if c == nil {
   662  		return terror.ErrConfigYamlTransform.New("task config is nil")
   663  	}
   664  	return c.adjust()
   665  }
   666  
   667  func (c *TaskConfig) adjust() error {
   668  	if len(c.Name) == 0 {
   669  		return terror.ErrConfigNeedUniqueTaskName.Generate()
   670  	}
   671  	switch c.TaskMode {
   672  	case ModeFull, ModeIncrement, ModeAll, ModeDump, ModeLoadSync:
   673  	default:
   674  		return terror.ErrConfigInvalidTaskMode.Generate()
   675  	}
   676  	if c.MetaSchema == "" {
   677  		c.MetaSchema = defaultMetaSchema
   678  	}
   679  
   680  	if c.ShardMode != "" && c.ShardMode != ShardPessimistic && c.ShardMode != ShardOptimistic {
   681  		return terror.ErrConfigShardModeNotSupport.Generate(c.ShardMode)
   682  	} else if c.ShardMode == "" && c.IsSharding {
   683  		c.ShardMode = ShardPessimistic // use the pessimistic mode as default for back compatible.
   684  	}
   685  	if c.StrictOptimisticShardMode && c.ShardMode != ShardOptimistic {
   686  		return terror.ErrConfigStrictOptimisticShardMode.Generate()
   687  	}
   688  
   689  	if len(c.ColumnMappings) > 0 {
   690  		return terror.ErrConfigColumnMappingDeprecated.Generate()
   691  	}
   692  
   693  	if c.CollationCompatible != "" && c.CollationCompatible != LooseCollationCompatible && c.CollationCompatible != StrictCollationCompatible {
   694  		return terror.ErrConfigCollationCompatibleNotSupport.Generate(c.CollationCompatible)
   695  	} else if c.CollationCompatible == "" {
   696  		c.CollationCompatible = LooseCollationCompatible
   697  	}
   698  
   699  	for _, item := range c.IgnoreCheckingItems {
   700  		if err := ValidateCheckingItem(item); err != nil {
   701  			return err
   702  		}
   703  	}
   704  
   705  	if c.OnlineDDLScheme != "" && c.OnlineDDLScheme != PT && c.OnlineDDLScheme != GHOST {
   706  		return terror.ErrConfigOnlineSchemeNotSupport.Generate(c.OnlineDDLScheme)
   707  	} else if c.OnlineDDLScheme == PT || c.OnlineDDLScheme == GHOST {
   708  		c.OnlineDDL = true
   709  		log.L().Warn("'online-ddl-scheme' will be deprecated soon. Recommend that use online-ddl instead of online-ddl-scheme.")
   710  	}
   711  
   712  	if c.TargetDB == nil {
   713  		return terror.ErrConfigNeedTargetDB.Generate()
   714  	}
   715  
   716  	if len(c.MySQLInstances) == 0 {
   717  		return terror.ErrConfigMySQLInstsAtLeastOne.Generate()
   718  	}
   719  
   720  	for name, exprFilter := range c.ExprFilter {
   721  		if exprFilter.Schema == "" {
   722  			return terror.ErrConfigExprFilterEmptyName.Generate(name, "schema")
   723  		}
   724  		if exprFilter.Table == "" {
   725  			return terror.ErrConfigExprFilterEmptyName.Generate(name, "table")
   726  		}
   727  		setFields := make([]string, 0, 1)
   728  		if exprFilter.InsertValueExpr != "" {
   729  			if err := checkValidExpr(exprFilter.InsertValueExpr); err != nil {
   730  				return terror.ErrConfigExprFilterWrongGrammar.Generate(name, exprFilter.InsertValueExpr, err)
   731  			}
   732  			setFields = append(setFields, "insert: ["+exprFilter.InsertValueExpr+"]")
   733  		}
   734  		if exprFilter.UpdateOldValueExpr != "" || exprFilter.UpdateNewValueExpr != "" {
   735  			if exprFilter.UpdateOldValueExpr != "" {
   736  				if err := checkValidExpr(exprFilter.UpdateOldValueExpr); err != nil {
   737  					return terror.ErrConfigExprFilterWrongGrammar.Generate(name, exprFilter.UpdateOldValueExpr, err)
   738  				}
   739  			}
   740  			if exprFilter.UpdateNewValueExpr != "" {
   741  				if err := checkValidExpr(exprFilter.UpdateNewValueExpr); err != nil {
   742  					return terror.ErrConfigExprFilterWrongGrammar.Generate(name, exprFilter.UpdateNewValueExpr, err)
   743  				}
   744  			}
   745  			setFields = append(setFields, "update (old value): ["+exprFilter.UpdateOldValueExpr+"] update (new value): ["+exprFilter.UpdateNewValueExpr+"]")
   746  		}
   747  		if exprFilter.DeleteValueExpr != "" {
   748  			if err := checkValidExpr(exprFilter.DeleteValueExpr); err != nil {
   749  				return terror.ErrConfigExprFilterWrongGrammar.Generate(name, exprFilter.DeleteValueExpr, err)
   750  			}
   751  			setFields = append(setFields, "delete: ["+exprFilter.DeleteValueExpr+"]")
   752  		}
   753  		if len(setFields) > 1 {
   754  			return terror.ErrConfigExprFilterManyExpr.Generate(name, setFields)
   755  		}
   756  	}
   757  
   758  	for _, validatorCfg := range c.Validators {
   759  		if err := validatorCfg.Adjust(); err != nil {
   760  			return err
   761  		}
   762  	}
   763  
   764  	instanceIDs := make(map[string]int) // source-id -> instance-index
   765  	globalConfigReferCount := map[string]int{}
   766  	duplicateErrorStrings := make([]string, 0)
   767  	for i, inst := range c.MySQLInstances {
   768  		if err := inst.VerifyAndAdjust(); err != nil {
   769  			return terror.Annotatef(err, "mysql-instance: %s", humanize.Ordinal(i))
   770  		}
   771  		if iid, ok := instanceIDs[inst.SourceID]; ok {
   772  			return terror.ErrConfigMySQLInstSameSourceID.Generate(iid, i, inst.SourceID)
   773  		}
   774  		instanceIDs[inst.SourceID] = i
   775  
   776  		switch c.TaskMode {
   777  		case ModeFull, ModeAll, ModeDump:
   778  			if inst.Meta != nil {
   779  				log.L().Warn("metadata will not be used. for Full mode, incremental sync will never occur; for All mode, the meta dumped by MyDumper will be used", zap.Int("mysql instance", i), zap.String("task mode", c.TaskMode))
   780  			}
   781  		case ModeIncrement:
   782  			if inst.Meta == nil {
   783  				log.L().Warn("mysql-instance doesn't set meta for incremental mode, user should specify start_time to start task.", zap.String("sourceID", inst.SourceID))
   784  			} else {
   785  				err := inst.Meta.Verify()
   786  				if err != nil {
   787  					return terror.Annotatef(err, "mysql-instance: %d", i)
   788  				}
   789  			}
   790  		}
   791  
   792  		for _, name := range inst.RouteRules {
   793  			if _, ok := c.Routes[name]; !ok {
   794  				return terror.ErrConfigRouteRuleNotFound.Generate(i, name)
   795  			}
   796  			globalConfigReferCount[configRefPrefixes[routeRulesIdx]+name]++
   797  		}
   798  		for _, name := range inst.FilterRules {
   799  			if _, ok := c.Filters[name]; !ok {
   800  				return terror.ErrConfigFilterRuleNotFound.Generate(i, name)
   801  			}
   802  			globalConfigReferCount[configRefPrefixes[filterRulesIdx]+name]++
   803  		}
   804  
   805  		// only when BAList is empty use BWList
   806  		if len(c.BAList) == 0 && len(c.BWList) != 0 {
   807  			c.BAList = c.BWList
   808  		}
   809  		if _, ok := c.BAList[inst.BAListName]; len(inst.BAListName) > 0 && !ok {
   810  			return terror.ErrConfigBAListNotFound.Generate(i, inst.BAListName)
   811  		}
   812  
   813  		if len(inst.MydumperConfigName) > 0 {
   814  			rule, ok := c.Mydumpers[inst.MydumperConfigName]
   815  			if !ok {
   816  				return terror.ErrConfigMydumperCfgNotFound.Generate(i, inst.MydumperConfigName)
   817  			}
   818  			globalConfigReferCount[configRefPrefixes[mydumperIdx]+inst.MydumperConfigName]++
   819  			if rule != nil {
   820  				inst.Mydumper = new(MydumperConfig)
   821  				*inst.Mydumper = *rule // ref mydumper config
   822  			}
   823  		}
   824  		if inst.Mydumper == nil {
   825  			if len(c.Mydumpers) != 0 {
   826  				log.L().Warn("mysql instance don't refer mydumper's configuration with mydumper-config-name, the default configuration will be used", zap.String("mysql instance", inst.SourceID))
   827  			}
   828  			defaultCfg := DefaultMydumperConfig()
   829  			inst.Mydumper = &defaultCfg
   830  		} else if inst.Mydumper.ChunkFilesize == "" {
   831  			// avoid too big dump file that can't sent concurrently
   832  			inst.Mydumper.ChunkFilesize = defaultChunkFilesize
   833  		}
   834  		if inst.MydumperThread != 0 {
   835  			inst.Mydumper.Threads = inst.MydumperThread
   836  		}
   837  
   838  		if HasDump(c.TaskMode) && len(inst.Mydumper.MydumperPath) == 0 {
   839  			// only verify if set, whether is valid can only be verify when we run it
   840  			return terror.ErrConfigMydumperPathNotValid.Generate(i)
   841  		}
   842  
   843  		if len(inst.LoaderConfigName) > 0 {
   844  			rule, ok := c.Loaders[inst.LoaderConfigName]
   845  			if !ok {
   846  				return terror.ErrConfigLoaderCfgNotFound.Generate(i, inst.LoaderConfigName)
   847  			}
   848  			globalConfigReferCount[configRefPrefixes[loaderIdx]+inst.LoaderConfigName]++
   849  			if rule != nil {
   850  				inst.Loader = new(LoaderConfig)
   851  				*inst.Loader = *rule // ref loader config
   852  			}
   853  		}
   854  		if inst.Loader == nil {
   855  			if len(c.Loaders) != 0 {
   856  				log.L().Warn("mysql instance don't refer loader's configuration with loader-config-name, the default configuration will be used", zap.String("mysql instance", inst.SourceID))
   857  			}
   858  			defaultCfg := DefaultLoaderConfig()
   859  			inst.Loader = &defaultCfg
   860  		}
   861  		if inst.LoaderThread != 0 {
   862  			inst.Loader.PoolSize = inst.LoaderThread
   863  		}
   864  
   865  		if len(inst.SyncerConfigName) > 0 {
   866  			rule, ok := c.Syncers[inst.SyncerConfigName]
   867  			if !ok {
   868  				return terror.ErrConfigSyncerCfgNotFound.Generate(i, inst.SyncerConfigName)
   869  			}
   870  			globalConfigReferCount[configRefPrefixes[syncerIdx]+inst.SyncerConfigName]++
   871  			if rule != nil {
   872  				inst.Syncer = new(SyncerConfig)
   873  				*inst.Syncer = *rule // ref syncer config
   874  			}
   875  		}
   876  		if inst.Syncer == nil {
   877  			if len(c.Syncers) != 0 {
   878  				log.L().Warn("mysql instance don't refer syncer's configuration with syncer-config-name, the default configuration will be used", zap.String("mysql instance", inst.SourceID))
   879  			}
   880  			defaultCfg := DefaultSyncerConfig()
   881  			inst.Syncer = &defaultCfg
   882  		}
   883  		if inst.Syncer.QueueSize == 0 {
   884  			inst.Syncer.QueueSize = defaultQueueSize
   885  		}
   886  		if inst.Syncer.CheckpointFlushInterval == 0 {
   887  			inst.Syncer.CheckpointFlushInterval = defaultCheckpointFlushInterval
   888  		}
   889  		if inst.Syncer.SafeModeDuration == "" {
   890  			inst.Syncer.SafeModeDuration = strconv.Itoa(2*inst.Syncer.CheckpointFlushInterval) + "s"
   891  		}
   892  		if duration, err := time.ParseDuration(inst.Syncer.SafeModeDuration); err != nil {
   893  			return terror.ErrConfigInvalidSafeModeDuration.Generate(inst.Syncer.SafeModeDuration, err)
   894  		} else if inst.Syncer.SafeMode && duration == 0 {
   895  			return terror.ErrConfigConfictSafeModeDurationAndSafeMode.Generate()
   896  		}
   897  		if inst.SyncerThread != 0 {
   898  			inst.Syncer.WorkerCount = inst.SyncerThread
   899  		}
   900  
   901  		inst.ContinuousValidator = defaultValidatorConfig()
   902  		if inst.ContinuousValidatorConfigName != "" {
   903  			rule, ok := c.Validators[inst.ContinuousValidatorConfigName]
   904  			if !ok {
   905  				return terror.ErrContinuousValidatorCfgNotFound.Generate(i, inst.ContinuousValidatorConfigName)
   906  			}
   907  			globalConfigReferCount[configRefPrefixes[validatorIdx]+inst.ContinuousValidatorConfigName]++
   908  			if rule != nil {
   909  				inst.ContinuousValidator = *rule
   910  			}
   911  		}
   912  
   913  		// for backward compatible, set global config `ansi-quotes: true` if any syncer is true
   914  		if inst.Syncer.EnableANSIQuotes {
   915  			log.L().Warn("DM could discover proper ANSI_QUOTES, `enable-ansi-quotes` is no longer take effect")
   916  		}
   917  		if inst.Syncer.DisableCausality {
   918  			log.L().Warn("`disable-causality` is no longer take effect")
   919  		}
   920  
   921  		for _, name := range inst.ExpressionFilters {
   922  			if _, ok := c.ExprFilter[name]; !ok {
   923  				return terror.ErrConfigExprFilterNotFound.Generate(i, name)
   924  			}
   925  			globalConfigReferCount[configRefPrefixes[exprFilterIdx]+name]++
   926  		}
   927  
   928  		if dupeRules := checkDuplicateString(inst.RouteRules); len(dupeRules) > 0 {
   929  			duplicateErrorStrings = append(duplicateErrorStrings, fmt.Sprintf("mysql-instance(%d)'s route-rules: %s", i, strings.Join(dupeRules, ", ")))
   930  		}
   931  		if dupeRules := checkDuplicateString(inst.FilterRules); len(dupeRules) > 0 {
   932  			duplicateErrorStrings = append(duplicateErrorStrings, fmt.Sprintf("mysql-instance(%d)'s filter-rules: %s", i, strings.Join(dupeRules, ", ")))
   933  		}
   934  		if dupeRules := checkDuplicateString(inst.ColumnMappingRules); len(dupeRules) > 0 {
   935  			duplicateErrorStrings = append(duplicateErrorStrings, fmt.Sprintf("mysql-instance(%d)'s column-mapping-rules: %s", i, strings.Join(dupeRules, ", ")))
   936  		}
   937  		if dupeRules := checkDuplicateString(inst.ExpressionFilters); len(dupeRules) > 0 {
   938  			duplicateErrorStrings = append(duplicateErrorStrings, fmt.Sprintf("mysql-instance(%d)'s expression-filters: %s", i, strings.Join(dupeRules, ", ")))
   939  		}
   940  	}
   941  	if len(duplicateErrorStrings) > 0 {
   942  		return terror.ErrConfigDuplicateCfgItem.Generate(strings.Join(duplicateErrorStrings, "\n"))
   943  	}
   944  
   945  	var unusedConfigs []string
   946  	for route := range c.Routes {
   947  		if globalConfigReferCount[configRefPrefixes[routeRulesIdx]+route] == 0 {
   948  			unusedConfigs = append(unusedConfigs, route)
   949  		}
   950  	}
   951  	for filter := range c.Filters {
   952  		if globalConfigReferCount[configRefPrefixes[filterRulesIdx]+filter] == 0 {
   953  			unusedConfigs = append(unusedConfigs, filter)
   954  		}
   955  	}
   956  	for mydumper := range c.Mydumpers {
   957  		if globalConfigReferCount[configRefPrefixes[mydumperIdx]+mydumper] == 0 {
   958  			unusedConfigs = append(unusedConfigs, mydumper)
   959  		}
   960  	}
   961  
   962  	for loader, cfg := range c.Loaders {
   963  		if cfg != nil {
   964  			if err1 := cfg.adjust(); err1 != nil {
   965  				return err1
   966  			}
   967  		}
   968  		if globalConfigReferCount[configRefPrefixes[loaderIdx]+loader] == 0 {
   969  			unusedConfigs = append(unusedConfigs, loader)
   970  		}
   971  	}
   972  	for syncer := range c.Syncers {
   973  		if globalConfigReferCount[configRefPrefixes[syncerIdx]+syncer] == 0 {
   974  			unusedConfigs = append(unusedConfigs, syncer)
   975  		}
   976  	}
   977  	for exprFilter := range c.ExprFilter {
   978  		if globalConfigReferCount[configRefPrefixes[exprFilterIdx]+exprFilter] == 0 {
   979  			unusedConfigs = append(unusedConfigs, exprFilter)
   980  		}
   981  	}
   982  	for key := range c.Validators {
   983  		if globalConfigReferCount[configRefPrefixes[validatorIdx]+key] == 0 {
   984  			unusedConfigs = append(unusedConfigs, key)
   985  		}
   986  	}
   987  
   988  	if len(unusedConfigs) != 0 {
   989  		sort.Strings(unusedConfigs)
   990  		return terror.ErrConfigGlobalConfigsUnused.Generate(unusedConfigs)
   991  	}
   992  
   993  	// we postpone default time_zone init in each unit so we won't change the config value in task/sub_task config
   994  	if c.Timezone != "" {
   995  		if _, err := utils.ParseTimeZone(c.Timezone); err != nil {
   996  			return err
   997  		}
   998  	}
   999  	if c.RemoveMeta {
  1000  		log.L().Warn("`remove-meta` in task config is deprecated, please use `start-task ... --remove-meta` instead")
  1001  	}
  1002  
  1003  	if c.EnableHeartbeat || c.HeartbeatUpdateInterval != defaultUpdateInterval ||
  1004  		c.HeartbeatReportInterval != defaultReportInterval {
  1005  		c.EnableHeartbeat = false
  1006  		log.L().Warn("heartbeat is deprecated, needn't set it anymore.")
  1007  	}
  1008  	return nil
  1009  }
  1010  
  1011  // getGenerateName generates name by rule or gets name from nameMap
  1012  // if it's a new name, increase nameIdx
  1013  // otherwise return current nameIdx.
  1014  func getGenerateName(rule interface{}, nameIdx int, namePrefix string, nameMap map[string]string) (string, int) {
  1015  	// use json as key since no DeepEqual for rules now.
  1016  	ruleByte, err := json.Marshal(rule)
  1017  	if err != nil {
  1018  		log.L().Error(fmt.Sprintf("marshal %s rule to json", namePrefix), log.ShortError(err))
  1019  		return fmt.Sprintf("%s-%02d", namePrefix, nameIdx), nameIdx + 1
  1020  	} else if val, ok := nameMap[string(ruleByte)]; ok {
  1021  		return val, nameIdx
  1022  	} else {
  1023  		ruleName := fmt.Sprintf("%s-%02d", namePrefix, nameIdx+1)
  1024  		nameMap[string(ruleByte)] = ruleName
  1025  		return ruleName, nameIdx + 1
  1026  	}
  1027  }
  1028  
  1029  // checkDuplicateString checks whether the given string array has duplicate string item
  1030  // if there is duplicate, it will return **all** the duplicate strings.
  1031  func checkDuplicateString(ruleNames []string) []string {
  1032  	mp := make(map[string]bool, len(ruleNames))
  1033  	dupeArray := make([]string, 0)
  1034  	for _, name := range ruleNames {
  1035  		if added, ok := mp[name]; ok {
  1036  			if !added {
  1037  				dupeArray = append(dupeArray, name)
  1038  				mp[name] = true
  1039  			}
  1040  		} else {
  1041  			mp[name] = false
  1042  		}
  1043  	}
  1044  	return dupeArray
  1045  }
  1046  
  1047  // AdjustTargetDBSessionCfg adjust session cfg of TiDB.
  1048  func AdjustTargetDBSessionCfg(dbConfig *dbconfig.DBConfig, version *semver.Version) {
  1049  	lowerMap := make(map[string]string, len(dbConfig.Session))
  1050  	for k, v := range dbConfig.Session {
  1051  		lowerMap[strings.ToLower(k)] = v
  1052  	}
  1053  	// all cfg in defaultSessionCfg should be lower case
  1054  	for _, cfg := range defaultSessionCfg {
  1055  		if _, ok := lowerMap[cfg.key]; !ok && !version.LessThan(*cfg.minVersion) {
  1056  			lowerMap[cfg.key] = cfg.val
  1057  		}
  1058  	}
  1059  	dbConfig.Session = lowerMap
  1060  }
  1061  
  1062  var (
  1063  	defaultParser = parser.New()
  1064  	parserMu      sync.Mutex
  1065  )
  1066  
  1067  func checkValidExpr(expr string) error {
  1068  	expr = "select " + expr
  1069  	parserMu.Lock()
  1070  	_, _, err := defaultParser.Parse(expr, "", "")
  1071  	parserMu.Unlock()
  1072  	return err
  1073  }
  1074  
  1075  // YamlForDowngrade returns YAML format represents of config for downgrade.
  1076  func (c *TaskConfig) YamlForDowngrade() (string, error) {
  1077  	t := NewTaskConfigForDowngrade(c)
  1078  
  1079  	// try to encrypt password
  1080  	t.TargetDB.Password = utils.EncryptOrPlaintext(utils.DecryptOrPlaintext(t.TargetDB.Password))
  1081  
  1082  	// omit default values, so we can ignore them for later marshal
  1083  	t.omitDefaultVals()
  1084  
  1085  	return t.Yaml()
  1086  }
  1087  
  1088  // MySQLInstanceForDowngrade represents a sync config of a MySQL instance for downgrade.
  1089  type MySQLInstanceForDowngrade struct {
  1090  	SourceID           string          `yaml:"source-id"`
  1091  	Meta               *Meta           `yaml:"meta"`
  1092  	FilterRules        []string        `yaml:"filter-rules"`
  1093  	ColumnMappingRules []string        `yaml:"column-mapping-rules"`
  1094  	RouteRules         []string        `yaml:"route-rules"`
  1095  	BWListName         string          `yaml:"black-white-list"`
  1096  	BAListName         string          `yaml:"block-allow-list"`
  1097  	MydumperConfigName string          `yaml:"mydumper-config-name"`
  1098  	Mydumper           *MydumperConfig `yaml:"mydumper"`
  1099  	MydumperThread     int             `yaml:"mydumper-thread"`
  1100  	LoaderConfigName   string          `yaml:"loader-config-name"`
  1101  	Loader             *LoaderConfig   `yaml:"loader"`
  1102  	LoaderThread       int             `yaml:"loader-thread"`
  1103  	SyncerConfigName   string          `yaml:"syncer-config-name"`
  1104  	Syncer             *SyncerConfig   `yaml:"syncer"`
  1105  	SyncerThread       int             `yaml:"syncer-thread"`
  1106  	// new config item
  1107  	ExpressionFilters []string `yaml:"expression-filters,omitempty"`
  1108  }
  1109  
  1110  // NewMySQLInstancesForDowngrade creates []* MySQLInstanceForDowngrade.
  1111  func NewMySQLInstancesForDowngrade(mysqlInstances []*MySQLInstance) []*MySQLInstanceForDowngrade {
  1112  	mysqlInstancesForDowngrade := make([]*MySQLInstanceForDowngrade, 0, len(mysqlInstances))
  1113  	for _, m := range mysqlInstances {
  1114  		newMySQLInstance := &MySQLInstanceForDowngrade{
  1115  			SourceID:           m.SourceID,
  1116  			Meta:               m.Meta,
  1117  			FilterRules:        m.FilterRules,
  1118  			ColumnMappingRules: m.ColumnMappingRules,
  1119  			RouteRules:         m.RouteRules,
  1120  			BWListName:         m.BWListName,
  1121  			BAListName:         m.BAListName,
  1122  			MydumperConfigName: m.MydumperConfigName,
  1123  			Mydumper:           m.Mydumper,
  1124  			MydumperThread:     m.MydumperThread,
  1125  			LoaderConfigName:   m.LoaderConfigName,
  1126  			Loader:             m.Loader,
  1127  			LoaderThread:       m.LoaderThread,
  1128  			SyncerConfigName:   m.SyncerConfigName,
  1129  			Syncer:             m.Syncer,
  1130  			SyncerThread:       m.SyncerThread,
  1131  			ExpressionFilters:  m.ExpressionFilters,
  1132  		}
  1133  		mysqlInstancesForDowngrade = append(mysqlInstancesForDowngrade, newMySQLInstance)
  1134  	}
  1135  	return mysqlInstancesForDowngrade
  1136  }
  1137  
  1138  // LoaderConfigForDowngrade is the base configuration for loader in v2.0.
  1139  // This config is used for downgrade(config export) from a higher dmctl version.
  1140  // When we add any new config item into LoaderConfig, we should update it also.
  1141  type LoaderConfigForDowngrade struct {
  1142  	PoolSize int    `yaml:"pool-size" toml:"pool-size" json:"pool-size"`
  1143  	Dir      string `yaml:"dir" toml:"dir" json:"dir"`
  1144  }
  1145  
  1146  func NewLoaderConfigForDowngrade(loaderConfigs map[string]*LoaderConfig) map[string]*LoaderConfigForDowngrade {
  1147  	loaderConfigsForDowngrade := make(map[string]*LoaderConfigForDowngrade, len(loaderConfigs))
  1148  	for k, v := range loaderConfigs {
  1149  		loaderConfigsForDowngrade[k] = &LoaderConfigForDowngrade{
  1150  			PoolSize: v.PoolSize,
  1151  			Dir:      v.Dir,
  1152  		}
  1153  	}
  1154  	return loaderConfigsForDowngrade
  1155  }
  1156  
  1157  // SyncerConfigForDowngrade is the base configuration for syncer in v2.0.
  1158  // This config is used for downgrade(config export) from a higher dmctl version.
  1159  // When we add any new config item into SyncerConfig, we should update it also.
  1160  type SyncerConfigForDowngrade struct {
  1161  	MetaFile                string `yaml:"meta-file"`
  1162  	WorkerCount             int    `yaml:"worker-count"`
  1163  	Batch                   int    `yaml:"batch"`
  1164  	QueueSize               int    `yaml:"queue-size"`
  1165  	CheckpointFlushInterval int    `yaml:"checkpoint-flush-interval"`
  1166  	MaxRetry                int    `yaml:"max-retry"`
  1167  	EnableGTID              bool   `yaml:"enable-gtid"`
  1168  	DisableCausality        bool   `yaml:"disable-detect"`
  1169  	SafeMode                bool   `yaml:"safe-mode"`
  1170  	EnableANSIQuotes        bool   `yaml:"enable-ansi-quotes"`
  1171  
  1172  	SafeModeDuration string `yaml:"safe-mode-duration,omitempty"`
  1173  	Compact          bool   `yaml:"compact,omitempty"`
  1174  	MultipleRows     bool   `yaml:"multipleRows,omitempty"`
  1175  }
  1176  
  1177  // NewSyncerConfigsForDowngrade converts SyncerConfig to SyncerConfigForDowngrade.
  1178  func NewSyncerConfigsForDowngrade(syncerConfigs map[string]*SyncerConfig) map[string]*SyncerConfigForDowngrade {
  1179  	syncerConfigsForDowngrade := make(map[string]*SyncerConfigForDowngrade, len(syncerConfigs))
  1180  	for configName, syncerConfig := range syncerConfigs {
  1181  		newSyncerConfig := &SyncerConfigForDowngrade{
  1182  			MetaFile:                syncerConfig.MetaFile,
  1183  			WorkerCount:             syncerConfig.WorkerCount,
  1184  			Batch:                   syncerConfig.Batch,
  1185  			QueueSize:               syncerConfig.QueueSize,
  1186  			CheckpointFlushInterval: syncerConfig.CheckpointFlushInterval,
  1187  			MaxRetry:                syncerConfig.MaxRetry,
  1188  			EnableGTID:              syncerConfig.EnableGTID,
  1189  			DisableCausality:        syncerConfig.DisableCausality,
  1190  			SafeMode:                syncerConfig.SafeMode,
  1191  			SafeModeDuration:        syncerConfig.SafeModeDuration,
  1192  			EnableANSIQuotes:        syncerConfig.EnableANSIQuotes,
  1193  			Compact:                 syncerConfig.Compact,
  1194  			MultipleRows:            syncerConfig.MultipleRows,
  1195  		}
  1196  		syncerConfigsForDowngrade[configName] = newSyncerConfig
  1197  	}
  1198  	return syncerConfigsForDowngrade
  1199  }
  1200  
  1201  // omitDefaultVals change default value to empty value for new config item.
  1202  // If any default value for new config item is not empty(0 or false or nil),
  1203  // we should change it to empty.
  1204  func (c *SyncerConfigForDowngrade) omitDefaultVals() {
  1205  	if c.SafeModeDuration == strconv.Itoa(2*c.CheckpointFlushInterval)+"s" {
  1206  		c.SafeModeDuration = ""
  1207  	}
  1208  }
  1209  
  1210  // TaskConfigForDowngrade is the base configuration for task in v2.0.
  1211  // This config is used for downgrade(config export) from a higher dmctl version.
  1212  // When we add any new config item into SourceConfig, we should update it also.
  1213  type TaskConfigForDowngrade struct {
  1214  	Name                    string                               `yaml:"name"`
  1215  	TaskMode                string                               `yaml:"task-mode"`
  1216  	IsSharding              bool                                 `yaml:"is-sharding"`
  1217  	ShardMode               string                               `yaml:"shard-mode"`
  1218  	IgnoreCheckingItems     []string                             `yaml:"ignore-checking-items"`
  1219  	MetaSchema              string                               `yaml:"meta-schema"`
  1220  	EnableHeartbeat         bool                                 `yaml:"enable-heartbeat"`
  1221  	HeartbeatUpdateInterval int                                  `yaml:"heartbeat-update-interval"`
  1222  	HeartbeatReportInterval int                                  `yaml:"heartbeat-report-interval"`
  1223  	Timezone                string                               `yaml:"timezone"`
  1224  	CaseSensitive           bool                                 `yaml:"case-sensitive"`
  1225  	TargetDB                *dbconfig.DBConfig                   `yaml:"target-database"`
  1226  	OnlineDDLScheme         string                               `yaml:"online-ddl-scheme"`
  1227  	Routes                  map[string]*router.TableRule         `yaml:"routes"`
  1228  	Filters                 map[string]*bf.BinlogEventRule       `yaml:"filters"`
  1229  	ColumnMappings          map[string]*column.Rule              `yaml:"column-mappings"`
  1230  	BWList                  map[string]*filter.Rules             `yaml:"black-white-list"`
  1231  	BAList                  map[string]*filter.Rules             `yaml:"block-allow-list"`
  1232  	Mydumpers               map[string]*MydumperConfig           `yaml:"mydumpers"`
  1233  	Loaders                 map[string]*LoaderConfigForDowngrade `yaml:"loaders"`
  1234  	Syncers                 map[string]*SyncerConfigForDowngrade `yaml:"syncers"`
  1235  	CleanDumpFile           bool                                 `yaml:"clean-dump-file"`
  1236  	EnableANSIQuotes        bool                                 `yaml:"ansi-quotes"`
  1237  	RemoveMeta              bool                                 `yaml:"remove-meta"`
  1238  	// new config item
  1239  	MySQLInstances            []*MySQLInstanceForDowngrade `yaml:"mysql-instances"`
  1240  	ExprFilter                map[string]*ExpressionFilter `yaml:"expression-filter,omitempty"`
  1241  	OnlineDDL                 bool                         `yaml:"online-ddl,omitempty"`
  1242  	ShadowTableRules          []string                     `yaml:"shadow-table-rules,omitempty"`
  1243  	TrashTableRules           []string                     `yaml:"trash-table-rules,omitempty"`
  1244  	StrictOptimisticShardMode bool                         `yaml:"strict-optimistic-shard-mode,omitempty"`
  1245  }
  1246  
  1247  // NewTaskConfigForDowngrade create new TaskConfigForDowngrade.
  1248  func NewTaskConfigForDowngrade(taskConfig *TaskConfig) *TaskConfigForDowngrade {
  1249  	targetDB := *taskConfig.TargetDB
  1250  	return &TaskConfigForDowngrade{
  1251  		Name:                      taskConfig.Name,
  1252  		TaskMode:                  taskConfig.TaskMode,
  1253  		IsSharding:                taskConfig.IsSharding,
  1254  		ShardMode:                 taskConfig.ShardMode,
  1255  		StrictOptimisticShardMode: taskConfig.StrictOptimisticShardMode,
  1256  		IgnoreCheckingItems:       taskConfig.IgnoreCheckingItems,
  1257  		MetaSchema:                taskConfig.MetaSchema,
  1258  		EnableHeartbeat:           taskConfig.EnableHeartbeat,
  1259  		HeartbeatUpdateInterval:   taskConfig.HeartbeatUpdateInterval,
  1260  		HeartbeatReportInterval:   taskConfig.HeartbeatReportInterval,
  1261  		Timezone:                  taskConfig.Timezone,
  1262  		CaseSensitive:             taskConfig.CaseSensitive,
  1263  		TargetDB:                  &targetDB,
  1264  		OnlineDDLScheme:           taskConfig.OnlineDDLScheme,
  1265  		Routes:                    taskConfig.Routes,
  1266  		Filters:                   taskConfig.Filters,
  1267  		ColumnMappings:            taskConfig.ColumnMappings,
  1268  		BWList:                    taskConfig.BWList,
  1269  		BAList:                    taskConfig.BAList,
  1270  		Mydumpers:                 taskConfig.Mydumpers,
  1271  		Loaders:                   NewLoaderConfigForDowngrade(taskConfig.Loaders),
  1272  		Syncers:                   NewSyncerConfigsForDowngrade(taskConfig.Syncers),
  1273  		CleanDumpFile:             taskConfig.CleanDumpFile,
  1274  		EnableANSIQuotes:          taskConfig.EnableANSIQuotes,
  1275  		RemoveMeta:                taskConfig.RemoveMeta,
  1276  		MySQLInstances:            NewMySQLInstancesForDowngrade(taskConfig.MySQLInstances),
  1277  		ExprFilter:                taskConfig.ExprFilter,
  1278  		OnlineDDL:                 taskConfig.OnlineDDL,
  1279  		ShadowTableRules:          taskConfig.ShadowTableRules,
  1280  		TrashTableRules:           taskConfig.TrashTableRules,
  1281  	}
  1282  }
  1283  
  1284  // omitDefaultVals change default value to empty value for new config item.
  1285  // If any default value for new config item is not empty(0 or false or nil),
  1286  // we should change it to empty.
  1287  func (c *TaskConfigForDowngrade) omitDefaultVals() {
  1288  	if len(c.ShadowTableRules) == 1 && c.ShadowTableRules[0] == DefaultShadowTableRules {
  1289  		c.ShadowTableRules = nil
  1290  	}
  1291  	if len(c.TrashTableRules) == 1 && c.TrashTableRules[0] == DefaultTrashTableRules {
  1292  		c.TrashTableRules = nil
  1293  	}
  1294  	for _, s := range c.Syncers {
  1295  		s.omitDefaultVals()
  1296  	}
  1297  	c.OnlineDDL = false
  1298  }
  1299  
  1300  // Yaml returns YAML format representation of config.
  1301  func (c *TaskConfigForDowngrade) Yaml() (string, error) {
  1302  	b, err := yaml.Marshal(c)
  1303  	return string(b), err
  1304  }