github.com/pingcap/tiflow@v0.0.0-20240520035814-5bf52d54e205/cdc/entry/schema/snapshot.go (about)

     1  // Copyright 2022 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 schema
    15  
    16  import (
    17  	"fmt"
    18  	"math"
    19  	"strings"
    20  	"sync"
    21  	"time"
    22  
    23  	"github.com/google/btree"
    24  	"github.com/pingcap/errors"
    25  	"github.com/pingcap/log"
    26  	timeta "github.com/pingcap/tidb/pkg/meta"
    27  	timodel "github.com/pingcap/tidb/pkg/parser/model"
    28  	"github.com/pingcap/tiflow/cdc/model"
    29  	cerror "github.com/pingcap/tiflow/pkg/errors"
    30  	"github.com/pingcap/tiflow/pkg/filter"
    31  	"go.uber.org/zap"
    32  )
    33  
    34  // Snapshot stores the source TiDB all schema information.
    35  // If no special comments, all public methods are thread-safe.
    36  type Snapshot struct {
    37  	inner  snapshot
    38  	rwlock *sync.RWMutex
    39  }
    40  
    41  // PreTableInfo returns the table info which will be overwritten by the specified job
    42  func (s *Snapshot) PreTableInfo(job *timodel.Job) (*model.TableInfo, error) {
    43  	switch job.Type {
    44  	case timodel.ActionCreateSchema, timodel.ActionModifySchemaCharsetAndCollate, timodel.ActionDropSchema:
    45  		return nil, nil
    46  	case timodel.ActionCreateTable, timodel.ActionCreateView, timodel.ActionRecoverTable:
    47  		// no pre table info
    48  		return nil, nil
    49  	case timodel.ActionRenameTable, timodel.ActionDropTable, timodel.ActionDropView, timodel.ActionTruncateTable, timodel.ActionAlterTablePartitioning, timodel.ActionRemovePartitioning:
    50  		// get the table will be dropped
    51  		table, ok := s.PhysicalTableByID(job.TableID)
    52  		if !ok {
    53  			return nil, cerror.ErrSchemaStorageTableMiss.GenWithStackByArgs(job.TableID)
    54  		}
    55  		return table, nil
    56  	case timodel.ActionRenameTables:
    57  		// DDL on multiple tables, ignore pre table info
    58  		return nil, nil
    59  	case timodel.ActionExchangeTablePartition:
    60  		// get the table will be exchanged
    61  		table, _, err := s.inner.getSourceTable(job.BinlogInfo.TableInfo)
    62  		return table, err
    63  	default:
    64  		binlogInfo := job.BinlogInfo
    65  		if binlogInfo == nil {
    66  			log.Warn("ignore a invalid DDL job", zap.Any("job", job))
    67  			return nil, nil
    68  		}
    69  		tbInfo := binlogInfo.TableInfo
    70  		if tbInfo == nil {
    71  			log.Warn("ignore a invalid DDL job", zap.Any("job", job))
    72  			return nil, nil
    73  		}
    74  		tableID := tbInfo.ID
    75  		table, ok := s.PhysicalTableByID(tableID)
    76  		if !ok {
    77  			return nil, cerror.ErrSchemaStorageTableMiss.GenWithStackByArgs(job.TableID)
    78  		}
    79  		return table, nil
    80  	}
    81  }
    82  
    83  // FillSchemaName fills the schema name in ddl job.
    84  func (s *Snapshot) FillSchemaName(job *timodel.Job) error {
    85  	if job.Type == timodel.ActionRenameTables {
    86  		// DDLs on multiple schema or tables, ignore them.
    87  		return nil
    88  	}
    89  	if job.Type == timodel.ActionRenameTable && job.SchemaName != "" {
    90  		// DDL on single table with schema name, ignore it.
    91  		return nil
    92  	}
    93  
    94  	if job.Type == timodel.ActionCreateSchema ||
    95  		job.Type == timodel.ActionDropSchema {
    96  		job.SchemaName = job.BinlogInfo.DBInfo.Name.O
    97  		return nil
    98  	}
    99  	dbInfo, exist := s.SchemaByID(job.SchemaID)
   100  	if !exist {
   101  		return cerror.ErrSnapshotSchemaNotFound.GenWithStackByArgs(job.SchemaID)
   102  	}
   103  	job.SchemaName = dbInfo.Name.O
   104  	return nil
   105  }
   106  
   107  // GetSchemaVersion returns the schema version of the meta.
   108  func GetSchemaVersion(meta *timeta.Meta) (int64, error) {
   109  	// After we get the schema version at startTs, if the diff corresponding to that version does not exist,
   110  	// it means that the job is not committed yet, so we should subtract one from the version, i.e., version--.
   111  	version, err := meta.GetSchemaVersion()
   112  	if err != nil {
   113  		return 0, errors.Trace(err)
   114  	}
   115  	diff, err := meta.GetSchemaDiff(version)
   116  	if err != nil {
   117  		return 0, errors.Trace(err)
   118  	}
   119  	if diff == nil {
   120  		version--
   121  	}
   122  	return version, nil
   123  }
   124  
   125  // NewSingleSnapshotFromMeta creates a new single schema snapshot from a tidb meta
   126  func NewSingleSnapshotFromMeta(
   127  	id model.ChangeFeedID,
   128  	meta *timeta.Meta,
   129  	currentTs uint64,
   130  	forceReplicate bool,
   131  	filter filter.Filter,
   132  ) (*Snapshot, error) {
   133  	// meta is nil only in unit tests
   134  	if meta == nil {
   135  		snap := NewEmptySnapshot(forceReplicate)
   136  		snap.inner.currentTs = currentTs
   137  		return snap, nil
   138  	}
   139  	return NewSnapshotFromMeta(id, meta, currentTs, forceReplicate, filter)
   140  }
   141  
   142  // NewSnapshotFromMeta creates a schema snapshot from meta.
   143  func NewSnapshotFromMeta(
   144  	id model.ChangeFeedID,
   145  	meta *timeta.Meta,
   146  	currentTs uint64,
   147  	forceReplicate bool,
   148  	filter filter.Filter,
   149  ) (*Snapshot, error) {
   150  	start := time.Now()
   151  	snap := NewEmptySnapshot(forceReplicate)
   152  	dbinfos, err := meta.ListDatabases()
   153  	if err != nil {
   154  		return nil, cerror.WrapError(cerror.ErrMetaListDatabases, err)
   155  	}
   156  	// `tag` is used to reverse sort all versions in the generated snapshot.
   157  	tag := negative(currentTs)
   158  	for _, dbinfo := range dbinfos {
   159  		if filter.ShouldIgnoreSchema(dbinfo.Name.O) {
   160  			log.Debug("ignore database", zap.String("db", dbinfo.Name.O))
   161  			continue
   162  		}
   163  		vid := newVersionedID(dbinfo.ID, tag)
   164  		vid.target = dbinfo
   165  		snap.inner.schemas.ReplaceOrInsert(vid)
   166  
   167  		vname := newVersionedEntityName(-1, dbinfo.Name.O, tag) // -1 means the entity is a schema.
   168  		vname.target = dbinfo.ID
   169  		snap.inner.schemaNameToID.ReplaceOrInsert(vname)
   170  		// get all tables Name
   171  		tableNames, err := meta.ListSimpleTables(dbinfo.ID)
   172  		if err != nil {
   173  			return nil, cerror.WrapError(cerror.ErrMetaListDatabases, err)
   174  		}
   175  		tableNeeded := make([]*timodel.TableNameInfo, 0, len(tableNames))
   176  		// filter tables
   177  		for _, table := range tableNames {
   178  			if filter.ShouldIgnoreTable(dbinfo.Name.O, table.Name.O) {
   179  				log.Debug("ignore table", zap.String("table", table.Name.O))
   180  				continue
   181  			}
   182  			tableNeeded = append(tableNeeded, table)
   183  		}
   184  		tableInfos := make([]*timodel.TableInfo, 0, len(tableNeeded))
   185  		for _, table := range tableNeeded {
   186  			tableInfo, err := meta.GetTable(dbinfo.ID, table.ID)
   187  			if err != nil {
   188  				return nil, errors.Trace(err)
   189  			}
   190  			tableInfos = append(tableInfos, tableInfo)
   191  		}
   192  
   193  		for _, tableInfo := range tableInfos {
   194  			tableInfo := model.WrapTableInfo(dbinfo.ID, dbinfo.Name.O, currentTs, tableInfo)
   195  			snap.inner.tables.ReplaceOrInsert(versionedID{
   196  				id:     tableInfo.ID,
   197  				tag:    tag,
   198  				target: tableInfo,
   199  			})
   200  			snap.inner.tableNameToID.ReplaceOrInsert(versionedEntityName{
   201  				prefix: dbinfo.ID,
   202  				entity: tableInfo.Name.O,
   203  				tag:    tag,
   204  				target: tableInfo.ID,
   205  			})
   206  
   207  			ineligible := !tableInfo.IsEligible(forceReplicate)
   208  			if ineligible {
   209  				snap.inner.ineligibleTables.ReplaceOrInsert(versionedID{id: tableInfo.ID, tag: tag})
   210  			}
   211  			if pi := tableInfo.GetPartitionInfo(); pi != nil {
   212  				for _, partition := range pi.Definitions {
   213  					vid := newVersionedID(partition.ID, tag)
   214  					vid.target = tableInfo
   215  					snap.inner.partitions.ReplaceOrInsert(vid)
   216  					if ineligible {
   217  						snap.inner.ineligibleTables.ReplaceOrInsert(versionedID{id: partition.ID, tag: tag})
   218  					}
   219  				}
   220  			}
   221  		}
   222  	}
   223  	snap.inner.currentTs = currentTs
   224  	log.Info("schema snapshot created",
   225  		zap.Stringer("changefeed", id),
   226  		zap.Uint64("currentTs", currentTs),
   227  		zap.Any("duration", time.Since(start).Seconds()))
   228  	return snap, nil
   229  }
   230  
   231  // NewEmptySnapshot creates an empty schema snapshot.
   232  func NewEmptySnapshot(forceReplicate bool) *Snapshot {
   233  	inner := snapshot{
   234  		tableNameToID:    btree.NewG[versionedEntityName](16, versionedEntityNameLess),
   235  		schemaNameToID:   btree.NewG[versionedEntityName](16, versionedEntityNameLess),
   236  		schemas:          btree.NewG[versionedID](16, versionedIDLess),
   237  		tables:           btree.NewG[versionedID](16, versionedIDLess),
   238  		partitions:       btree.NewG[versionedID](16, versionedIDLess),
   239  		truncatedTables:  btree.NewG[versionedID](16, versionedIDLess),
   240  		ineligibleTables: btree.NewG[versionedID](16, versionedIDLess),
   241  		forceReplicate:   forceReplicate,
   242  		currentTs:        0,
   243  	}
   244  
   245  	return &Snapshot{inner: inner, rwlock: new(sync.RWMutex)}
   246  }
   247  
   248  // Copy creates a new schema snapshot based on the given one. The copied one shares same internal
   249  // data structures with the old one to save memory usage.
   250  func (s *Snapshot) Copy() *Snapshot {
   251  	s.rwlock.RLock()
   252  	defer s.rwlock.RUnlock()
   253  	return &Snapshot{inner: s.inner, rwlock: s.rwlock}
   254  }
   255  
   256  // PrintStatus prints the schema snapshot.
   257  func (s *Snapshot) PrintStatus(logger func(msg string, fields ...zap.Field)) {
   258  	logger("[SchemaSnap] Start to print status", zap.Uint64("currentTs", s.CurrentTs()))
   259  
   260  	availableSchemas := make(map[int64]string, s.inner.schemas.Len())
   261  	s.IterSchemas(func(dbInfo *timodel.DBInfo) {
   262  		availableSchemas[dbInfo.ID] = dbInfo.Name.O
   263  		logger("[SchemaSnap] --> Schemas", zap.Int64("schemaID", dbInfo.ID), zap.Reflect("dbInfo", dbInfo))
   264  		// check schemaNameToID
   265  		id, ok := s.inner.schemaIDByName(dbInfo.Name.O)
   266  		if !ok || id != dbInfo.ID {
   267  			logger("[SchemaSnap] ----> schemaNameToID item lost", zap.String("name", dbInfo.Name.O), zap.Int64("schemaNameToID", id))
   268  		}
   269  	})
   270  	s.IterSchemaNames(func(schema string, target int64) {
   271  		if _, ok := availableSchemas[target]; !ok {
   272  			logger("[SchemaSnap] ----> schemas item lost", zap.String("name", schema), zap.Int64("schema", target))
   273  		}
   274  	})
   275  
   276  	availableTables := make(map[int64]struct{}, s.inner.tables.Len())
   277  	s.IterTables(true, func(tableInfo *model.TableInfo) {
   278  		availableTables[tableInfo.ID] = struct{}{}
   279  		logger("[SchemaSnap] --> Tables", zap.Int64("tableID", tableInfo.ID),
   280  			zap.Stringer("tableInfo", tableInfo),
   281  			zap.Bool("ineligible", s.inner.isIneligibleTableID(tableInfo.ID)))
   282  		id, ok := s.inner.tableIDByName(tableInfo.TableName.Schema, tableInfo.TableName.Table)
   283  		if !ok || id != tableInfo.ID {
   284  			logger("[SchemaSnap] ----> tableNameToID item lost", zap.Stringer("name", tableInfo.TableName), zap.Int64("tableNameToID", id))
   285  		}
   286  	})
   287  	s.IterTableNames(func(schemaID int64, table string, target int64) {
   288  		if _, ok := availableTables[target]; !ok {
   289  			name := fmt.Sprintf("%s.%s", availableSchemas[schemaID], table)
   290  			logger("[SchemaSnap] ----> tables item lost", zap.String("name", name), zap.Int64("table", target))
   291  		}
   292  	})
   293  
   294  	s.IterPartitions(true, func(pid int64, table *model.TableInfo) {
   295  		logger("[SchemaSnap] --> Partitions", zap.Int64("partitionID", pid), zap.Int64("tableID", table.ID),
   296  			zap.Bool("ineligible", s.inner.isIneligibleTableID(pid)))
   297  	})
   298  }
   299  
   300  // IterSchemas iterates all schemas in the snapshot.
   301  func (s *Snapshot) IterSchemas(f func(i *timodel.DBInfo)) {
   302  	s.rwlock.RLock()
   303  	defer s.rwlock.RUnlock()
   304  	s.inner.iterSchemas(f)
   305  }
   306  
   307  // IterSchemaNames iterates all schema names in the snapshot.
   308  func (s *Snapshot) IterSchemaNames(f func(schema string, target int64)) {
   309  	s.rwlock.RLock()
   310  	defer s.rwlock.RUnlock()
   311  	s.inner.iterSchemaNames(f)
   312  }
   313  
   314  // IterTables iterates all tables in the snapshot.
   315  func (s *Snapshot) IterTables(includeIneligible bool, f func(i *model.TableInfo)) {
   316  	s.rwlock.RLock()
   317  	defer s.rwlock.RUnlock()
   318  	s.inner.iterTables(includeIneligible, f)
   319  }
   320  
   321  // IterTableNames iterates all table names in the snapshot.
   322  func (s *Snapshot) IterTableNames(f func(schema int64, table string, target int64)) {
   323  	s.rwlock.RLock()
   324  	defer s.rwlock.RUnlock()
   325  	s.inner.iterTableNames(f)
   326  }
   327  
   328  // IterPartitions iterates all partitions in the snapshot.
   329  func (s *Snapshot) IterPartitions(includeIneligible bool, f func(id int64, i *model.TableInfo)) {
   330  	s.rwlock.RLock()
   331  	defer s.rwlock.RUnlock()
   332  	s.inner.iterPartitions(includeIneligible, f)
   333  }
   334  
   335  // SchemaByID returns the DBInfo by schema id.
   336  // The second returned value is false if no schema with the specified id is found.
   337  // NOTE: The returned table info should always be READ-ONLY!
   338  func (s *Snapshot) SchemaByID(id int64) (*timodel.DBInfo, bool) {
   339  	s.rwlock.RLock()
   340  	defer s.rwlock.RUnlock()
   341  	return s.inner.schemaByID(id)
   342  }
   343  
   344  // PhysicalTableByID returns the TableInfo by table id or partition id.
   345  // The second returned value is false if no table with the specified id is found.
   346  // NOTE: The returned table info should always be READ-ONLY!
   347  func (s *Snapshot) PhysicalTableByID(id int64) (*model.TableInfo, bool) {
   348  	s.rwlock.RLock()
   349  	defer s.rwlock.RUnlock()
   350  	return s.inner.physicalTableByID(id)
   351  }
   352  
   353  // SchemaIDByName gets the schema id from the given schema name.
   354  func (s *Snapshot) SchemaIDByName(schema string) (int64, bool) {
   355  	s.rwlock.RLock()
   356  	defer s.rwlock.RUnlock()
   357  	return s.inner.schemaIDByName(schema)
   358  }
   359  
   360  // TableIDByName returns the tableID by table schemaName and tableName.
   361  // The second returned value is false if no table with the specified name is found.
   362  func (s *Snapshot) TableIDByName(schema string, table string) (int64, bool) {
   363  	s.rwlock.RLock()
   364  	defer s.rwlock.RUnlock()
   365  	return s.inner.tableIDByName(schema, table)
   366  }
   367  
   368  // TableByName queries a table by name,
   369  // The second returned value is false if no table with the specified name is found.
   370  // NOTE: The returned table info should always be READ-ONLY!
   371  func (s *Snapshot) TableByName(schema, table string) (*model.TableInfo, bool) {
   372  	s.rwlock.RLock()
   373  	defer s.rwlock.RUnlock()
   374  	return s.inner.tableByName(schema, table)
   375  }
   376  
   377  // SchemaByTableID returns the schema ID by table ID.
   378  func (s *Snapshot) SchemaByTableID(tableID int64) (*timodel.DBInfo, bool) {
   379  	s.rwlock.RLock()
   380  	defer s.rwlock.RUnlock()
   381  	tableInfo, ok := s.inner.physicalTableByID(tableID)
   382  	if !ok {
   383  		return nil, false
   384  	}
   385  	return s.inner.schemaByID(tableInfo.SchemaID)
   386  }
   387  
   388  // IsTruncateTableID returns true if the table id have been truncated by truncate table DDL.
   389  func (s *Snapshot) IsTruncateTableID(id int64) bool {
   390  	s.rwlock.RLock()
   391  	defer s.rwlock.RUnlock()
   392  	tag, ok := s.inner.tableTagByID(id, true)
   393  	return ok && s.inner.truncatedTables.Has(newVersionedID(id, tag))
   394  }
   395  
   396  // IsIneligibleTableID returns true if the table is ineligible.
   397  func (s *Snapshot) IsIneligibleTableID(id int64) bool {
   398  	s.rwlock.RLock()
   399  	defer s.rwlock.RUnlock()
   400  	return s.inner.isIneligibleTableID(id)
   401  }
   402  
   403  // HandleDDL handles the given job.
   404  func (s *Snapshot) HandleDDL(job *timodel.Job) error {
   405  	if err := s.FillSchemaName(job); err != nil {
   406  		return errors.Trace(err)
   407  	}
   408  	return s.DoHandleDDL(job)
   409  }
   410  
   411  // CurrentTs returns the finish timestamp of the schema snapshot.
   412  func (s *Snapshot) CurrentTs() uint64 {
   413  	return s.inner.currentTs
   414  }
   415  
   416  // Drop drops the snapshot. It must be called when GC some snapshots.
   417  // Drop a snapshot will also drop all snapshots with a less timestamp.
   418  func (s *Snapshot) Drop() {
   419  	s.rwlock.Lock()
   420  	defer s.rwlock.Unlock()
   421  	s.inner.drop()
   422  }
   423  
   424  func getWrapTableInfo(job *timodel.Job) *model.TableInfo {
   425  	return model.WrapTableInfo(job.SchemaID, job.SchemaName,
   426  		job.BinlogInfo.FinishedTS,
   427  		job.BinlogInfo.TableInfo)
   428  }
   429  
   430  // DoHandleDDL is like HandleDDL but doesn't fill schema name into job.
   431  // NOTE: it's public because some tests in the upper package need this.
   432  func (s *Snapshot) DoHandleDDL(job *timodel.Job) error {
   433  	s.rwlock.Lock()
   434  	defer s.rwlock.Unlock()
   435  
   436  	switch job.Type {
   437  	case timodel.ActionCreateSchema:
   438  		// get the DBInfo from job rawArgs
   439  		err := s.inner.createSchema(job.BinlogInfo.DBInfo, job.BinlogInfo.FinishedTS)
   440  		if err != nil {
   441  			return errors.Trace(err)
   442  		}
   443  	case timodel.ActionModifySchemaCharsetAndCollate:
   444  		err := s.inner.replaceSchema(job.BinlogInfo.DBInfo, job.BinlogInfo.FinishedTS)
   445  		if err != nil {
   446  			return errors.Trace(err)
   447  		}
   448  	case timodel.ActionDropSchema:
   449  		err := s.inner.dropSchema(job.SchemaID, job.BinlogInfo.FinishedTS)
   450  		if err != nil {
   451  			return errors.Trace(err)
   452  		}
   453  	case timodel.ActionRenameTable:
   454  		// first drop the table
   455  		err := s.inner.dropTable(job.TableID, job.BinlogInfo.FinishedTS)
   456  		if err != nil {
   457  			return errors.Trace(err)
   458  		}
   459  		// If it a rename table job and the schema does not exist,
   460  		// there is no need to create the table, since this table
   461  		// will not be replicated in the future.
   462  		if _, ok := s.inner.schemaByID(job.SchemaID); !ok {
   463  			return nil
   464  		}
   465  		// create table
   466  		err = s.inner.createTable(getWrapTableInfo(job), job.BinlogInfo.FinishedTS)
   467  		if err != nil {
   468  			return errors.Trace(err)
   469  		}
   470  	case timodel.ActionRenameTables:
   471  		err := s.inner.renameTables(job, job.BinlogInfo.FinishedTS)
   472  		if err != nil {
   473  			return errors.Trace(err)
   474  		}
   475  	case timodel.ActionCreateTable, timodel.ActionCreateView, timodel.ActionRecoverTable:
   476  		err := s.inner.createTable(getWrapTableInfo(job), job.BinlogInfo.FinishedTS)
   477  		if err != nil {
   478  			return errors.Trace(err)
   479  		}
   480  	case timodel.ActionDropTable, timodel.ActionDropView:
   481  		err := s.inner.dropTable(job.TableID, job.BinlogInfo.FinishedTS)
   482  		if err != nil {
   483  			return errors.Trace(err)
   484  		}
   485  
   486  	case timodel.ActionTruncateTable:
   487  		// job.TableID is the old table id, different from table.ID
   488  		err := s.inner.truncateTable(job.TableID, getWrapTableInfo(job), job.BinlogInfo.FinishedTS)
   489  		if err != nil {
   490  			return errors.Trace(err)
   491  		}
   492  	case timodel.ActionTruncateTablePartition:
   493  		err := s.inner.updatePartition(getWrapTableInfo(job), true, job.BinlogInfo.FinishedTS)
   494  		if err != nil {
   495  			return errors.Trace(err)
   496  		}
   497  	case
   498  		timodel.ActionAddTablePartition,
   499  		timodel.ActionDropTablePartition,
   500  		timodel.ActionReorganizePartition:
   501  		err := s.inner.updatePartition(getWrapTableInfo(job), false, job.BinlogInfo.FinishedTS)
   502  		if err != nil {
   503  			return errors.Trace(err)
   504  		}
   505  	case timodel.ActionExchangeTablePartition:
   506  		err := s.inner.exchangePartition(getWrapTableInfo(job), job.BinlogInfo.FinishedTS)
   507  		if err != nil {
   508  			return errors.Trace(err)
   509  		}
   510  	case timodel.ActionRemovePartitioning, timodel.ActionAlterTablePartitioning:
   511  		err := s.inner.alterPartitioning(job)
   512  		if err != nil {
   513  			return errors.Trace(err)
   514  		}
   515  	default:
   516  		binlogInfo := job.BinlogInfo
   517  		if binlogInfo == nil {
   518  			log.Warn("ignore a invalid DDL job", zap.Any("job", job))
   519  			return nil
   520  		}
   521  		if binlogInfo.TableInfo == nil {
   522  			log.Warn("ignore a invalid DDL job", zap.Any("job", job))
   523  			return nil
   524  		}
   525  		err := s.inner.replaceTable(getWrapTableInfo(job), job.BinlogInfo.FinishedTS)
   526  		if err != nil {
   527  			return errors.Trace(err)
   528  		}
   529  	}
   530  	if s.inner.currentTs != job.BinlogInfo.FinishedTS {
   531  		panic("HandleDDL should update currentTs")
   532  	}
   533  	return nil
   534  }
   535  
   536  // TableCount counts tables in the snapshot. It's only for tests.
   537  func (s *Snapshot) TableCount(includeIneligible bool,
   538  	filter func(schema, table string) bool,
   539  ) (count int) {
   540  	s.IterTables(includeIneligible, func(i *model.TableInfo) {
   541  		if filter(i.TableName.Schema, i.TableName.Table) {
   542  			count++
   543  		}
   544  	})
   545  	return
   546  }
   547  
   548  // SchemaCount counts schemas in the snapshot. It's only for tests.
   549  func (s *Snapshot) SchemaCount() (count int) {
   550  	s.IterSchemas(func(i *timodel.DBInfo) { count += 1 })
   551  	return
   552  }
   553  
   554  // DumpToString dumps the snapshot to a string.
   555  func (s *Snapshot) DumpToString() string {
   556  	schemas := make([]string, 0, s.inner.schemas.Len())
   557  	s.IterSchemas(func(dbInfo *timodel.DBInfo) {
   558  		schemas = append(schemas, fmt.Sprintf("%v", dbInfo))
   559  	})
   560  
   561  	tables := make([]string, 0, s.inner.tables.Len())
   562  	s.IterTables(true, func(tbInfo *model.TableInfo) {
   563  		tables = append(tables, fmt.Sprintf("%v", tbInfo))
   564  	})
   565  
   566  	partitions := make([]string, 0, s.inner.partitions.Len())
   567  	s.IterPartitions(true, func(id int64, _ *model.TableInfo) {
   568  		partitions = append(partitions, fmt.Sprintf("%d", id))
   569  	})
   570  
   571  	schemaNames := make([]string, 0, s.inner.schemaNameToID.Len())
   572  	s.IterSchemaNames(func(schema string, target int64) {
   573  		schemaNames = append(schemaNames, fmt.Sprintf("%s:%d", schema, target))
   574  	})
   575  
   576  	tableNames := make([]string, 0, s.inner.tableNameToID.Len())
   577  	s.IterTableNames(func(schemaID int64, table string, target int64) {
   578  		schema, _ := s.inner.schemaByID(schemaID)
   579  		tableNames = append(tableNames, fmt.Sprintf("%s.%s:%d", schema.Name.O, table, target))
   580  	})
   581  	return fmt.Sprintf("%s\n%s\n%s\n%s\n%s",
   582  		strings.Join(schemas, "\t"),
   583  		strings.Join(tables, "\t"),
   584  		strings.Join(partitions, "\t"),
   585  		strings.Join(schemaNames, "\t"),
   586  		strings.Join(tableNames, "\t"))
   587  }
   588  
   589  type snapshot struct {
   590  	// map[versionedEntityName] -> int64
   591  	// The ID can be `-1` which means the table is deleted.
   592  	tableNameToID *btree.BTreeG[versionedEntityName]
   593  
   594  	// map[versionedEntityName] -> int64
   595  	// The ID can be `-1` which means the table is deleted.
   596  	schemaNameToID *btree.BTreeG[versionedEntityName]
   597  
   598  	// map[versionedID] -> *timodel.DBInfo
   599  	// The target can be `nil` which means the entity is deleted.
   600  	schemas *btree.BTreeG[versionedID]
   601  
   602  	// map[versionedID] -> *model.TableInfo
   603  	// The target can be `nil` which means the entity is deleted.
   604  	tables *btree.BTreeG[versionedID]
   605  
   606  	// map[versionedID] -> *model.TableInfo
   607  	partitions *btree.BTreeG[versionedID]
   608  
   609  	// map[versionedID] -> struct{}
   610  	truncatedTables *btree.BTreeG[versionedID]
   611  
   612  	// map[versionedID] -> struct{}
   613  	// Partitions and tables share ineligibleTables because their IDs won't conflict.
   614  	ineligibleTables *btree.BTreeG[versionedID]
   615  
   616  	// if forceReplicate is true, treat ineligible tables as eligible.
   617  	forceReplicate bool
   618  
   619  	currentTs uint64
   620  }
   621  
   622  func (s *snapshot) schemaByID(id int64) (val *timodel.DBInfo, ok bool) {
   623  	tag := negative(s.currentTs)
   624  	start := versionedID{id: id, tag: tag, target: nil}
   625  	end := versionedID{id: id, tag: negative(uint64(0)), target: nil}
   626  	s.schemas.AscendRange(start, end, func(i versionedID) bool {
   627  		val = targetToDBInfo(i.target)
   628  		ok = val != nil
   629  		return false
   630  	})
   631  	return
   632  }
   633  
   634  func (s *snapshot) physicalTableByID(id int64) (tableInfo *model.TableInfo, ok bool) {
   635  	tag := negative(s.currentTs)
   636  	start := versionedID{id: id, tag: tag, target: nil}
   637  	end := versionedID{id: id, tag: negative(uint64(0)), target: nil}
   638  	s.tables.AscendRange(start, end, func(i versionedID) bool {
   639  		tableInfo = targetToTableInfo(i.target)
   640  		ok = tableInfo != nil
   641  		return false
   642  	})
   643  	if !ok {
   644  		// Try partition, it could be a partition table.
   645  		s.partitions.AscendRange(start, end, func(i versionedID) bool {
   646  			tableInfo = targetToTableInfo(i.target)
   647  			ok = tableInfo != nil
   648  			return false
   649  		})
   650  	}
   651  	return
   652  }
   653  
   654  func (s *snapshot) schemaIDByName(schema string) (id int64, ok bool) {
   655  	tag := negative(s.currentTs)
   656  	start := newVersionedEntityName(-1, schema, tag)
   657  	end := newVersionedEntityName(-1, schema, negative(uint64(0)))
   658  	s.schemaNameToID.AscendRange(start, end, func(i versionedEntityName) bool {
   659  		id = i.target
   660  		ok = id >= 0 // negative values are treated as invalid.
   661  		return false
   662  	})
   663  	return
   664  }
   665  
   666  func (s *snapshot) tableIDByName(schema string, table string) (id int64, ok bool) {
   667  	var prefix int64
   668  	prefix, ok = s.schemaIDByName(schema)
   669  	if ok {
   670  		tag := negative(s.currentTs)
   671  		start := newVersionedEntityName(prefix, table, tag)
   672  		end := newVersionedEntityName(prefix, table, negative(uint64(0)))
   673  		s.tableNameToID.AscendRange(start, end, func(i versionedEntityName) bool {
   674  			id = i.target
   675  			ok = id >= 0 // negative values are treated as invalid.
   676  			return false
   677  		})
   678  	}
   679  	return
   680  }
   681  
   682  func (s *snapshot) tableByName(schema, table string) (info *model.TableInfo, ok bool) {
   683  	id, ok := s.tableIDByName(schema, table)
   684  	if !ok {
   685  		return nil, ok
   686  	}
   687  	return s.physicalTableByID(id)
   688  }
   689  
   690  func (s *snapshot) isIneligibleTableID(id int64) (ok bool) {
   691  	tag, ok := s.tableTagByID(id, false)
   692  	return ok && s.ineligibleTables.Has(newVersionedID(id, tag))
   693  }
   694  
   695  func (s *snapshot) tableTagByID(id int64, nilAcceptable bool) (foundTag uint64, ok bool) {
   696  	tag := negative(s.currentTs)
   697  	start := newVersionedID(id, tag)
   698  	end := newVersionedID(id, negative(uint64(0)))
   699  	s.tables.AscendRange(start, end, func(i versionedID) bool {
   700  		tableInfo := targetToTableInfo(i.target)
   701  		if nilAcceptable || tableInfo != nil {
   702  			foundTag = i.tag
   703  			ok = true
   704  		}
   705  		return false
   706  	})
   707  	if !ok {
   708  		// Try partition, it could be a partition table.
   709  		s.partitions.AscendRange(start, end, func(i versionedID) bool {
   710  			tableInfo := targetToTableInfo(i.target)
   711  			if nilAcceptable || tableInfo != nil {
   712  				foundTag = i.tag
   713  				ok = true
   714  			}
   715  			return false
   716  		})
   717  	}
   718  	return
   719  }
   720  
   721  // dropSchema removes a schema from the snapshot.
   722  // Tables in the schema will also be dropped.
   723  func (s *snapshot) dropSchema(id int64, currentTs uint64) error {
   724  	dbInfo, ok := s.schemaByID(id)
   725  	if !ok {
   726  		return cerror.ErrSnapshotSchemaNotFound.GenWithStackByArgs(id)
   727  	}
   728  	tag := negative(currentTs)
   729  	s.schemas.ReplaceOrInsert(newVersionedID(id, tag))
   730  	s.schemaNameToID.ReplaceOrInsert(newVersionedEntityName(-1, dbInfo.Name.O, tag))
   731  	for _, id := range s.tablesInSchema(dbInfo.Name.O) {
   732  		tbInfo, _ := s.physicalTableByID(id)
   733  		s.doDropTable(tbInfo, currentTs)
   734  	}
   735  	s.currentTs = currentTs
   736  	log.Debug("drop schema success", zap.String("name", dbInfo.Name.O), zap.Int64("id", dbInfo.ID))
   737  	return nil
   738  }
   739  
   740  // Create a new schema in the snapshot. `dbInfo` will be deeply copied.
   741  func (s *snapshot) createSchema(dbInfo *timodel.DBInfo, currentTs uint64) error {
   742  	x, ok := s.schemaByID(dbInfo.ID)
   743  	if ok {
   744  		return cerror.ErrSnapshotSchemaExists.GenWithStackByArgs(x.Name, x.ID)
   745  	}
   746  	if id, ok := s.schemaIDByName(dbInfo.Name.O); ok {
   747  		return cerror.ErrSnapshotSchemaExists.GenWithStackByArgs(dbInfo.Name.O, id)
   748  	}
   749  	s.doCreateSchema(dbInfo, currentTs)
   750  	s.currentTs = currentTs
   751  	log.Debug("create schema success", zap.String("name", dbInfo.Name.O), zap.Int64("id", dbInfo.ID))
   752  	return nil
   753  }
   754  
   755  // Replace a schema. dbInfo will be deeply copied.
   756  // Callers should ensure `dbInfo` information not conflict with other schemas.
   757  func (s *snapshot) replaceSchema(dbInfo *timodel.DBInfo, currentTs uint64) error {
   758  	old, ok := s.schemaByID(dbInfo.ID)
   759  	if !ok {
   760  		return cerror.ErrSnapshotSchemaNotFound.GenWithStack("schema %s(%d) not found", dbInfo.Name, dbInfo.ID)
   761  	}
   762  	s.doCreateSchema(dbInfo, currentTs)
   763  	if old.Name.O != dbInfo.Name.O {
   764  		tag := negative(currentTs)
   765  		s.schemaNameToID.ReplaceOrInsert(newVersionedEntityName(-1, old.Name.O, tag))
   766  	}
   767  	s.currentTs = currentTs
   768  	log.Debug("replace schema success", zap.String("name", dbInfo.Name.O), zap.Int64("id", dbInfo.ID))
   769  	return nil
   770  }
   771  
   772  func (s *snapshot) doCreateSchema(dbInfo *timodel.DBInfo, currentTs uint64) {
   773  	tag := negative(currentTs)
   774  	vid := newVersionedID(dbInfo.ID, tag)
   775  	vid.target = dbInfo.Clone()
   776  	s.schemas.ReplaceOrInsert(vid)
   777  	vname := newVersionedEntityName(-1, dbInfo.Name.O, tag)
   778  	vname.target = dbInfo.ID
   779  	s.schemaNameToID.ReplaceOrInsert(vname)
   780  }
   781  
   782  // dropTable removes a table(NOT partition) from the snapshot.
   783  func (s *snapshot) dropTable(id int64, currentTs uint64) error {
   784  	tbInfo, ok := s.physicalTableByID(id)
   785  	if !ok {
   786  		return cerror.ErrSnapshotTableNotFound.GenWithStackByArgs(id)
   787  	}
   788  	s.doDropTable(tbInfo, currentTs)
   789  	s.currentTs = currentTs
   790  	log.Debug("drop table success",
   791  		zap.String("schema", tbInfo.TableName.Schema),
   792  		zap.String("table", tbInfo.TableName.Table),
   793  		zap.Int64("id", tbInfo.ID))
   794  	return nil
   795  }
   796  
   797  func (s *snapshot) doDropTable(tbInfo *model.TableInfo, currentTs uint64) {
   798  	tag := negative(currentTs)
   799  	s.tables.ReplaceOrInsert(newVersionedID(tbInfo.ID, tag))
   800  	s.tableNameToID.ReplaceOrInsert(newVersionedEntityName(tbInfo.SchemaID, tbInfo.TableName.Table, tag))
   801  	if pi := tbInfo.GetPartitionInfo(); pi != nil {
   802  		for _, partition := range pi.Definitions {
   803  			s.partitions.ReplaceOrInsert(newVersionedID(partition.ID, tag))
   804  		}
   805  	}
   806  }
   807  
   808  // truncateTable truncate the table with the given ID, and replace it with a new `tbInfo`.
   809  // NOTE: after a table is truncated:
   810  //   - physicalTableByID(id) will return nil;
   811  //   - IsTruncateTableID(id) should return true.
   812  func (s *snapshot) truncateTable(id int64, tbInfo *model.TableInfo, currentTs uint64) (err error) {
   813  	old, ok := s.physicalTableByID(id)
   814  	if !ok {
   815  		return cerror.ErrSnapshotTableNotFound.GenWithStackByArgs(id)
   816  	}
   817  	s.doDropTable(old, currentTs)
   818  	s.doCreateTable(tbInfo, currentTs)
   819  	s.truncatedTables.ReplaceOrInsert(newVersionedID(id, negative(currentTs)))
   820  	s.currentTs = currentTs
   821  	log.Debug("truncate table success",
   822  		zap.String("schema", tbInfo.TableName.Schema),
   823  		zap.String("table", tbInfo.TableName.Table),
   824  		zap.Int64("id", tbInfo.ID))
   825  	return
   826  }
   827  
   828  // Create a new table in the snapshot. `tbInfo` will be deeply copied.
   829  func (s *snapshot) createTable(tbInfo *model.TableInfo, currentTs uint64) error {
   830  	if _, ok := s.schemaByID(tbInfo.SchemaID); !ok {
   831  		return cerror.ErrSnapshotSchemaNotFound.GenWithStack("table's schema(%d)", tbInfo.SchemaID)
   832  	}
   833  	if _, ok := s.physicalTableByID(tbInfo.ID); ok {
   834  		return cerror.ErrSnapshotTableExists.GenWithStackByArgs(tbInfo.TableName.Schema, tbInfo.TableName.Table)
   835  	}
   836  	s.doCreateTable(tbInfo, currentTs)
   837  	s.currentTs = currentTs
   838  	log.Debug("create table success", zap.Int64("id", tbInfo.ID),
   839  		zap.String("name", fmt.Sprintf("%s.%s", tbInfo.TableName.Schema, tbInfo.TableName.Table)))
   840  	return nil
   841  }
   842  
   843  // ReplaceTable replace the table by new tableInfo
   844  func (s *snapshot) replaceTable(tbInfo *model.TableInfo, currentTs uint64) error {
   845  	if _, ok := s.schemaByID(tbInfo.SchemaID); !ok {
   846  		return cerror.ErrSnapshotSchemaNotFound.GenWithStack("table's schema(%d)", tbInfo.SchemaID)
   847  	}
   848  	if _, ok := s.physicalTableByID(tbInfo.ID); !ok {
   849  		return cerror.ErrSnapshotTableNotFound.GenWithStack("table %s(%d)", tbInfo.Name, tbInfo.ID)
   850  	}
   851  	s.doCreateTable(tbInfo, currentTs)
   852  	s.currentTs = currentTs
   853  	log.Debug("replace table success", zap.String("name", tbInfo.Name.O), zap.Int64("id", tbInfo.ID))
   854  	return nil
   855  }
   856  
   857  func (s *snapshot) doCreateTable(tbInfo *model.TableInfo, currentTs uint64) {
   858  	tbInfo = tbInfo.Clone()
   859  	tag := negative(currentTs)
   860  	vid := newVersionedID(tbInfo.ID, tag)
   861  	vid.target = tbInfo
   862  	s.tables.ReplaceOrInsert(vid)
   863  
   864  	vname := newVersionedEntityName(tbInfo.SchemaID, tbInfo.TableName.Table, tag)
   865  	vname.target = tbInfo.ID
   866  	s.tableNameToID.ReplaceOrInsert(vname)
   867  
   868  	ineligible := !tbInfo.IsEligible(s.forceReplicate)
   869  	if ineligible {
   870  		// Sequence is not supported yet, and always ineligible.
   871  		// Skip Warn to avoid confusion.
   872  		// See https://github.com/pingcap/tiflow/issues/4559
   873  		if !tbInfo.IsSequence() {
   874  			log.Warn("this table is ineligible to replicate",
   875  				zap.String("tableName", tbInfo.Name.O), zap.Int64("tableID", tbInfo.ID))
   876  		}
   877  		s.ineligibleTables.ReplaceOrInsert(newVersionedID(tbInfo.ID, tag))
   878  	}
   879  	if pi := tbInfo.GetPartitionInfo(); pi != nil {
   880  		for _, partition := range pi.Definitions {
   881  			vid := newVersionedID(partition.ID, tag)
   882  			vid.target = tbInfo
   883  			s.partitions.ReplaceOrInsert(vid)
   884  			if ineligible {
   885  				s.ineligibleTables.ReplaceOrInsert(newVersionedID(partition.ID, tag))
   886  			}
   887  		}
   888  	}
   889  }
   890  
   891  // updatePartition updates partition info for `tbInfo`.
   892  func (s *snapshot) updatePartition(tbInfo *model.TableInfo, isTruncate bool, currentTs uint64) error {
   893  	oldTbInfo, ok := s.physicalTableByID(tbInfo.ID)
   894  	if !ok {
   895  		return cerror.ErrSnapshotTableNotFound.GenWithStackByArgs(tbInfo.ID)
   896  	}
   897  	oldPi := oldTbInfo.GetPartitionInfo()
   898  	if oldPi == nil {
   899  		return cerror.ErrSnapshotTableNotFound.GenWithStack("table %d is not a partition table", tbInfo.ID)
   900  	}
   901  	newPi := tbInfo.GetPartitionInfo()
   902  	if newPi == nil {
   903  		return cerror.ErrSnapshotTableNotFound.GenWithStack("table %d is not a partition table", tbInfo.ID)
   904  	}
   905  
   906  	tag := negative(currentTs)
   907  	vid := newVersionedID(tbInfo.ID, tag)
   908  	vid.target = tbInfo.Clone()
   909  	s.tables.ReplaceOrInsert(vid)
   910  	ineligible := !tbInfo.IsEligible(s.forceReplicate)
   911  	if ineligible {
   912  		s.ineligibleTables.ReplaceOrInsert(newVersionedID(tbInfo.ID, tag))
   913  	}
   914  	for _, partition := range oldPi.Definitions {
   915  		s.partitions.ReplaceOrInsert(newVersionedID(partition.ID, tag))
   916  	}
   917  	newPartitionIDMap := make(map[int64]struct{}, len(newPi.NewPartitionIDs))
   918  	for _, partition := range newPi.Definitions {
   919  		vid := newVersionedID(partition.ID, tag)
   920  		vid.target = tbInfo
   921  		s.partitions.ReplaceOrInsert(vid)
   922  		if ineligible {
   923  			s.ineligibleTables.ReplaceOrInsert(newVersionedID(partition.ID, tag))
   924  		}
   925  		newPartitionIDMap[partition.ID] = struct{}{}
   926  	}
   927  	if isTruncate {
   928  		for _, partition := range oldPi.Definitions {
   929  			if _, ok := newPartitionIDMap[partition.ID]; !ok {
   930  				s.truncatedTables.ReplaceOrInsert(newVersionedID(partition.ID, tag))
   931  			}
   932  		}
   933  	}
   934  	s.currentTs = currentTs
   935  
   936  	log.Debug("adjust partition success",
   937  		zap.String("schema", tbInfo.TableName.Schema),
   938  		zap.String("table", tbInfo.TableName.Table),
   939  		zap.Any("partitions", newPi.Definitions),
   940  	)
   941  	return nil
   942  }
   943  
   944  func (s *snapshot) getSourceTable(targetTable *timodel.TableInfo) (*model.TableInfo, int64, error) {
   945  	oldTable, ok := s.physicalTableByID(targetTable.ID)
   946  	if !ok {
   947  		return nil, 0, cerror.ErrSnapshotTableNotFound.GenWithStackByArgs(targetTable.ID)
   948  	}
   949  
   950  	oldPartitions := oldTable.GetPartitionInfo()
   951  	if oldPartitions == nil {
   952  		return nil, 0, cerror.ErrSnapshotTableNotFound.
   953  			GenWithStack("table %d is not a partitioned table", oldTable.ID)
   954  	}
   955  
   956  	newPartitions := targetTable.GetPartitionInfo()
   957  	if newPartitions == nil {
   958  		return nil, 0, cerror.ErrSnapshotTableNotFound.
   959  			GenWithStack("table %d is not a partitioned table", targetTable.ID)
   960  	}
   961  
   962  	oldIDs := make(map[int64]struct{}, len(oldPartitions.Definitions))
   963  	for _, p := range oldPartitions.Definitions {
   964  		oldIDs[p.ID] = struct{}{}
   965  	}
   966  
   967  	newIDs := make(map[int64]struct{}, len(oldPartitions.Definitions))
   968  	for _, p := range newPartitions.Definitions {
   969  		newIDs[p.ID] = struct{}{}
   970  	}
   971  
   972  	// 1. find the source table info
   973  	var diff []int64
   974  	for id := range newIDs {
   975  		if _, ok := oldIDs[id]; !ok {
   976  			diff = append(diff, id)
   977  		}
   978  	}
   979  	if len(diff) != 1 {
   980  		return nil, 0, cerror.ErrExchangePartition.
   981  			GenWithStackByArgs(fmt.Sprintf("The exchanged source table number must be 1, but found %v", diff))
   982  	}
   983  	sourceTable, ok := s.physicalTableByID(diff[0])
   984  	if !ok {
   985  		return nil, 0, cerror.ErrSnapshotTableNotFound.GenWithStackByArgs(diff[0])
   986  	}
   987  	// 3.find the exchanged partition info
   988  	diff = diff[:0]
   989  	for id := range oldIDs {
   990  		if _, ok := newIDs[id]; !ok {
   991  			diff = append(diff, id)
   992  		}
   993  	}
   994  	if len(diff) != 1 {
   995  		return nil, 0, cerror.ErrExchangePartition.
   996  			GenWithStackByArgs(fmt.Sprintf("The exchanged source table number must be 1, but found %v", diff))
   997  	}
   998  
   999  	exchangedPartitionID := diff[0]
  1000  	return sourceTable, exchangedPartitionID, nil
  1001  }
  1002  
  1003  // exchangePartition find the partition's id in the old table info of targetTable,
  1004  // and find the sourceTable's id in the new table info of targetTable.
  1005  // Then set sourceTable's id to the partition's id, which make the exchange happen in snapshot.
  1006  // Finally, update both the targetTable's info and the sourceTable's info in snapshot.
  1007  func (s *snapshot) exchangePartition(targetTable *model.TableInfo, currentTS uint64) error {
  1008  	var sourceTable *model.TableInfo
  1009  	sourceTable, exchangedPartitionID, err := s.getSourceTable(targetTable.TableInfo)
  1010  	if err != nil {
  1011  		return errors.Trace(err)
  1012  	}
  1013  	// 4.update the targetTable
  1014  	oldTable, ok := s.physicalTableByID(targetTable.ID)
  1015  	if !ok {
  1016  		return cerror.ErrSnapshotTableNotFound.GenWithStackByArgs(targetTable.ID)
  1017  	}
  1018  	// TODO: remove this after job is fixed by TiDB.
  1019  	// ref: https://github.com/pingcap/tidb/issues/43819
  1020  	targetTable.SchemaID = oldTable.SchemaID
  1021  	targetTable.TableName = oldTable.TableName
  1022  	err = s.updatePartition(targetTable, false, currentTS)
  1023  	if err != nil {
  1024  		return errors.Trace(err)
  1025  	}
  1026  
  1027  	newSourceTable := model.WrapTableInfo(sourceTable.SchemaID, sourceTable.TableName.Schema,
  1028  		currentTS, sourceTable.TableInfo.Clone())
  1029  	// 5.update the sourceTable
  1030  	err = s.dropTable(sourceTable.ID, currentTS)
  1031  	if err != nil {
  1032  		return errors.Trace(err)
  1033  	}
  1034  	newSourceTable.ID = exchangedPartitionID
  1035  	err = s.createTable(newSourceTable, currentTS)
  1036  	if err != nil {
  1037  		return errors.Trace(err)
  1038  	}
  1039  
  1040  	log.Info("handle exchange partition success",
  1041  		zap.String("sourceTable", sourceTable.TableName.String()),
  1042  		zap.Int64("exchangedPartition", exchangedPartitionID),
  1043  		zap.String("targetTable", targetTable.TableName.String()),
  1044  		zap.Any("partition", targetTable.GetPartitionInfo().Definitions))
  1045  	return nil
  1046  }
  1047  
  1048  // alterPartitioning changes the table id and updates the TableInfo (including the partitioning info)
  1049  func (s *snapshot) alterPartitioning(job *timodel.Job) error {
  1050  	// first drop the table (will work with both partitioned and non-partitioned tables
  1051  	err := s.dropTable(job.TableID, job.BinlogInfo.FinishedTS)
  1052  	if err != nil {
  1053  		return errors.Trace(err)
  1054  	}
  1055  	// (re)create table, again will work with both partitioned and non-paritioned tables
  1056  	// it uses the model.TableInfo written to the job.BinlogInfo, which is the final one
  1057  	err = s.createTable(getWrapTableInfo(job), job.BinlogInfo.FinishedTS)
  1058  	if err != nil {
  1059  		return errors.Trace(err)
  1060  	}
  1061  
  1062  	log.Info("handle alter partitioning success",
  1063  		zap.Int64("OldID", job.TableID),
  1064  		zap.Int64("NewID", job.BinlogInfo.TableInfo.ID),
  1065  		zap.String("Name", job.TableName))
  1066  	return nil
  1067  }
  1068  
  1069  func (s *snapshot) renameTables(job *timodel.Job, currentTs uint64) error {
  1070  	var oldSchemaIDs, newSchemaIDs, oldTableIDs []int64
  1071  	var newTableNames, oldSchemaNames []*timodel.CIStr
  1072  	err := job.DecodeArgs(&oldSchemaIDs, &newSchemaIDs, &newTableNames, &oldTableIDs, &oldSchemaNames)
  1073  	if err != nil {
  1074  		return errors.Trace(err)
  1075  	}
  1076  	if len(job.BinlogInfo.MultipleTableInfos) < len(newTableNames) {
  1077  		return cerror.ErrInvalidDDLJob.GenWithStackByArgs(job.ID)
  1078  	}
  1079  	// NOTE: should handle failures in halfway better.
  1080  	for _, tableID := range oldTableIDs {
  1081  		if err := s.dropTable(tableID, currentTs); err != nil {
  1082  			return errors.Trace(err)
  1083  		}
  1084  	}
  1085  	for i, tableInfo := range job.BinlogInfo.MultipleTableInfos {
  1086  		newSchema, ok := s.schemaByID(newSchemaIDs[i])
  1087  		if !ok {
  1088  			return cerror.ErrSnapshotSchemaNotFound.GenWithStackByArgs(newSchemaIDs[i])
  1089  		}
  1090  		newSchemaName := newSchema.Name.O
  1091  		tbInfo := model.WrapTableInfo(newSchemaIDs[i], newSchemaName, job.BinlogInfo.FinishedTS, tableInfo)
  1092  		err = s.createTable(tbInfo, currentTs)
  1093  		if err != nil {
  1094  			return errors.Trace(err)
  1095  		}
  1096  	}
  1097  	return nil
  1098  }
  1099  
  1100  func (s *snapshot) iterTables(includeIneligible bool, f func(i *model.TableInfo)) {
  1101  	tag := negative(s.currentTs)
  1102  	var tableID int64 = -1
  1103  	s.tables.Ascend(func(x versionedID) bool {
  1104  		if x.id != tableID && x.tag >= tag {
  1105  			tableID = x.id
  1106  			if x.target != nil && (includeIneligible ||
  1107  				!s.ineligibleTables.Has(newVersionedID(x.id, x.tag))) {
  1108  				f(targetToTableInfo(x.target))
  1109  			}
  1110  		}
  1111  		return true
  1112  	})
  1113  }
  1114  
  1115  func (s *snapshot) iterPartitions(includeIneligible bool, f func(id int64, i *model.TableInfo)) {
  1116  	tag := negative(s.currentTs)
  1117  	var partitionID int64 = -1
  1118  	s.partitions.Ascend(func(x versionedID) bool {
  1119  		if x.id != partitionID && x.tag >= tag {
  1120  			partitionID = x.id
  1121  			if x.target != nil && (includeIneligible ||
  1122  				!s.ineligibleTables.Has(newVersionedID(x.id, x.tag))) {
  1123  				f(partitionID, targetToTableInfo(x.target))
  1124  			}
  1125  		}
  1126  		return true
  1127  	})
  1128  }
  1129  
  1130  func (s *snapshot) iterSchemas(f func(i *timodel.DBInfo)) {
  1131  	tag := negative(s.currentTs)
  1132  	var schemaID int64 = -1
  1133  	s.schemas.Ascend(func(x versionedID) bool {
  1134  		if x.id != schemaID && x.tag >= tag {
  1135  			schemaID = x.id
  1136  			if x.target != nil {
  1137  				f(targetToDBInfo(x.target))
  1138  			}
  1139  		}
  1140  		return true
  1141  	})
  1142  }
  1143  
  1144  func (s *snapshot) iterTableNames(f func(schema int64, table string, target int64)) {
  1145  	tag := negative(s.currentTs)
  1146  	var prefix int64 = -1
  1147  	entity := ""
  1148  	s.tableNameToID.Ascend(func(x versionedEntityName) bool {
  1149  		if (x.prefix != prefix || x.entity != entity) && x.tag >= tag {
  1150  			prefix = x.prefix
  1151  			entity = x.entity
  1152  			if x.target > 0 {
  1153  				f(prefix, entity, x.target)
  1154  			}
  1155  		}
  1156  		return true
  1157  	})
  1158  }
  1159  
  1160  func (s *snapshot) iterSchemaNames(f func(schema string, target int64)) {
  1161  	tag := negative(s.currentTs)
  1162  	entity := ""
  1163  	s.schemaNameToID.Ascend(func(x versionedEntityName) bool {
  1164  		if x.entity != entity && x.tag >= tag {
  1165  			entity = x.entity
  1166  			if x.target > 0 {
  1167  				f(entity, x.target)
  1168  			}
  1169  		}
  1170  		return true
  1171  	})
  1172  }
  1173  
  1174  func (s *snapshot) tablesInSchema(schema string) (tables []int64) {
  1175  	schemaID, ok := s.schemaIDByName(schema)
  1176  	if !ok {
  1177  		return
  1178  	}
  1179  	start := newVersionedEntityName(schemaID, "", 0)
  1180  	end := newVersionedEntityName(schemaID+1, "", 0)
  1181  	tag := negative(s.currentTs)
  1182  	currTable := ""
  1183  	s.tableNameToID.AscendRange(start, end, func(x versionedEntityName) bool {
  1184  		if x.tag >= tag && x.entity != currTable {
  1185  			currTable = x.entity
  1186  			if x.target > 0 {
  1187  				tables = append(tables, x.target)
  1188  			}
  1189  		}
  1190  		return true
  1191  	})
  1192  	return
  1193  }
  1194  
  1195  func (s *snapshot) drop() {
  1196  	tag := negative(s.currentTs)
  1197  
  1198  	schemas := make([]versionedID, 0, s.schemas.Len())
  1199  	var schemaID int64 = -1
  1200  	schemaDroped := false
  1201  	s.schemas.Ascend(func(x versionedID) bool {
  1202  		if x.tag >= tag {
  1203  			if x.id != schemaID {
  1204  				schemaID = x.id
  1205  				schemaDroped = false
  1206  			}
  1207  			if schemaDroped || x.target == nil {
  1208  				schemas = append(schemas, newVersionedID(x.id, x.tag))
  1209  			}
  1210  			schemaDroped = true
  1211  		}
  1212  		return true
  1213  	})
  1214  	for _, vid := range schemas {
  1215  		s.schemas.Delete(vid)
  1216  	}
  1217  
  1218  	tables := make([]versionedID, 0, s.tables.Len())
  1219  	var tableID int64 = -1
  1220  	tableDroped := false
  1221  	s.tables.Ascend(func(x versionedID) bool {
  1222  		if x.tag >= tag {
  1223  			if x.id != tableID {
  1224  				tableID = x.id
  1225  				tableDroped = false
  1226  			}
  1227  			if tableDroped || x.target == nil {
  1228  				tables = append(tables, newVersionedID(x.id, x.tag))
  1229  			}
  1230  			tableDroped = true
  1231  		}
  1232  		return true
  1233  	})
  1234  	for _, vid := range tables {
  1235  		x, _ := s.tables.Delete(vid)
  1236  		info := targetToTableInfo(x.target)
  1237  		if info != nil {
  1238  			ineligible := !info.IsEligible(s.forceReplicate)
  1239  			if ineligible {
  1240  				s.ineligibleTables.Delete(vid)
  1241  			}
  1242  		} else {
  1243  			// Maybe the table is truncated.
  1244  			s.truncatedTables.Delete(vid)
  1245  		}
  1246  	}
  1247  
  1248  	partitions := make([]versionedID, 0, s.partitions.Len())
  1249  	var partitionID int64 = -1
  1250  	partitionDroped := false
  1251  	s.partitions.Ascend(func(x versionedID) bool {
  1252  		if x.tag >= tag {
  1253  			if x.id != partitionID {
  1254  				partitionID = x.id
  1255  				partitionDroped = false
  1256  			}
  1257  			if partitionDroped || x.target == nil {
  1258  				partitions = append(partitions, newVersionedID(x.id, x.tag))
  1259  			}
  1260  			partitionDroped = true
  1261  		}
  1262  		return true
  1263  	})
  1264  	for _, vid := range partitions {
  1265  		x, _ := s.partitions.Delete(vid)
  1266  		info := targetToTableInfo(x.target)
  1267  		if info != nil {
  1268  			ineligible := !info.IsEligible(s.forceReplicate)
  1269  			if ineligible {
  1270  				s.ineligibleTables.Delete(vid)
  1271  			}
  1272  		}
  1273  	}
  1274  
  1275  	schemaNames := make([]versionedEntityName, 0, s.schemaNameToID.Len())
  1276  	schemaName := ""
  1277  	schemaNameDroped := false
  1278  	s.schemaNameToID.Ascend(func(x versionedEntityName) bool {
  1279  		if x.tag >= tag {
  1280  			if x.entity != schemaName {
  1281  				schemaName = x.entity
  1282  				schemaNameDroped = false
  1283  			}
  1284  			if schemaNameDroped || x.target < 0 {
  1285  				schemaNames = append(schemaNames, newVersionedEntityName(x.prefix, x.entity, x.tag))
  1286  			}
  1287  			schemaNameDroped = true
  1288  		}
  1289  		return true
  1290  	})
  1291  	for _, vname := range schemaNames {
  1292  		s.schemaNameToID.Delete(vname)
  1293  	}
  1294  
  1295  	tableNames := make([]versionedEntityName, 0, s.tableNameToID.Len())
  1296  	schemaID = -1
  1297  	tableName := ""
  1298  	tableNameDroped := false
  1299  	s.tableNameToID.Ascend(func(x versionedEntityName) bool {
  1300  		if x.tag >= tag {
  1301  			if x.prefix != schemaID || x.entity != tableName {
  1302  				schemaID = x.prefix
  1303  				tableName = x.entity
  1304  				tableNameDroped = false
  1305  			}
  1306  			if tableNameDroped || x.target < 0 {
  1307  				tableNames = append(tableNames, newVersionedEntityName(x.prefix, x.entity, x.tag))
  1308  			}
  1309  			tableNameDroped = true
  1310  		}
  1311  		return true
  1312  	})
  1313  	for _, vname := range tableNames {
  1314  		s.tableNameToID.Delete(vname)
  1315  	}
  1316  }
  1317  
  1318  // Entity(schema or table) name with finish timestamp of the associated DDL job.
  1319  type versionedEntityName struct {
  1320  	prefix int64 // schema ID if the entity is a table, or -1 if it's a schema.
  1321  	entity string
  1322  	tag    uint64 // A transform of timestamp to reverse sort versions.
  1323  	// the associated entity id, negative values are treated as invalid.
  1324  	target int64
  1325  }
  1326  
  1327  // ID with finish timestamp of the associated DDL job.
  1328  type versionedID struct {
  1329  	id  int64
  1330  	tag uint64 // A transform of timestamp to reverse sort versions.
  1331  	// the associated entity pointer.
  1332  	target interface{}
  1333  }
  1334  
  1335  func versionedEntityNameLess(v1, v2 versionedEntityName) bool {
  1336  	return v1.prefix < v2.prefix || (v1.prefix == v2.prefix && v1.entity < v2.entity) ||
  1337  		(v1.prefix == v2.prefix && v1.entity == v2.entity && v1.tag < v2.tag)
  1338  }
  1339  
  1340  func versionedIDLess(v1, v2 versionedID) bool {
  1341  	return v1.id < v2.id || (v1.id == v2.id && v1.tag < v2.tag)
  1342  }
  1343  
  1344  // negative transforms `x` for reverse sorting based on it.
  1345  func negative(x uint64) uint64 {
  1346  	return math.MaxUint64 - x
  1347  }
  1348  
  1349  // newVersionedEntityName creates an instance with target -1, which means it's deleted from
  1350  // the associated snapshot.
  1351  func newVersionedEntityName(prefix int64, entity string, tag uint64) versionedEntityName {
  1352  	var target int64 = -1
  1353  	return versionedEntityName{prefix, entity, tag, target}
  1354  }
  1355  
  1356  // newVersionedID creates an instance with target nil, which means it's deleted from the
  1357  // associated snapshot.
  1358  func newVersionedID(id int64, tag uint64) versionedID {
  1359  	var target interface{} = nil
  1360  	return versionedID{id, tag, target}
  1361  }
  1362  
  1363  func targetToTableInfo(target interface{}) *model.TableInfo {
  1364  	if target == nil {
  1365  		return nil
  1366  	}
  1367  	return target.(*model.TableInfo)
  1368  }
  1369  
  1370  func targetToDBInfo(target interface{}) *timodel.DBInfo {
  1371  	if target == nil {
  1372  		return nil
  1373  	}
  1374  	return target.(*timodel.DBInfo)
  1375  }