github.com/pingcap/ticdc@v0.0.0-20220526033649-485a10ef2652/cdc/entry/schema_storage.go (about)

     1  // Copyright 2020 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 entry
    15  
    16  import (
    17  	"context"
    18  	"sort"
    19  	"sync"
    20  	"sync/atomic"
    21  	"time"
    22  
    23  	"github.com/pingcap/errors"
    24  	"github.com/pingcap/log"
    25  	timodel "github.com/pingcap/parser/model"
    26  	"github.com/pingcap/ticdc/cdc/model"
    27  	cerror "github.com/pingcap/ticdc/pkg/errors"
    28  	"github.com/pingcap/ticdc/pkg/filter"
    29  	"github.com/pingcap/ticdc/pkg/retry"
    30  	timeta "github.com/pingcap/tidb/meta"
    31  	"go.uber.org/zap"
    32  	"go.uber.org/zap/zapcore"
    33  )
    34  
    35  // schemaSnapshot stores the source TiDB all schema information
    36  // schemaSnapshot is a READ ONLY struct
    37  type schemaSnapshot struct {
    38  	tableNameToID  map[model.TableName]int64
    39  	schemaNameToID map[string]int64
    40  
    41  	schemas        map[int64]*timodel.DBInfo
    42  	tables         map[int64]*model.TableInfo
    43  	partitionTable map[int64]*model.TableInfo
    44  
    45  	// key is schemaID and value is tableIDs
    46  	tableInSchema map[int64][]int64
    47  
    48  	truncateTableID   map[int64]struct{}
    49  	ineligibleTableID map[int64]struct{}
    50  
    51  	currentTs uint64
    52  
    53  	// if explicit is true, treat tables without explicit row id as eligible
    54  	explicitTables bool
    55  }
    56  
    57  // SingleSchemaSnapshot is a single schema snapshot independent of schema storage
    58  type SingleSchemaSnapshot = schemaSnapshot
    59  
    60  // HandleDDL handles the ddl job
    61  func (s *SingleSchemaSnapshot) HandleDDL(job *timodel.Job) error {
    62  	return s.handleDDL(job)
    63  }
    64  
    65  // PreTableInfo returns the table info which will be overwritten by the specified job
    66  func (s *SingleSchemaSnapshot) PreTableInfo(job *timodel.Job) (*model.TableInfo, error) {
    67  	switch job.Type {
    68  	case timodel.ActionCreateSchema, timodel.ActionModifySchemaCharsetAndCollate, timodel.ActionDropSchema:
    69  		return nil, nil
    70  	case timodel.ActionCreateTable, timodel.ActionCreateView, timodel.ActionRecoverTable:
    71  		// no pre table info
    72  		return nil, nil
    73  	case timodel.ActionRenameTable, timodel.ActionDropTable, timodel.ActionDropView, timodel.ActionTruncateTable:
    74  		// get the table will be dropped
    75  		table, ok := s.TableByID(job.TableID)
    76  		if !ok {
    77  			return nil, cerror.ErrSchemaStorageTableMiss.GenWithStackByArgs(job.TableID)
    78  		}
    79  		return table, nil
    80  	default:
    81  		binlogInfo := job.BinlogInfo
    82  		if binlogInfo == nil {
    83  			log.Warn("ignore a invalid DDL job", zap.Reflect("job", job))
    84  			return nil, nil
    85  		}
    86  		tbInfo := binlogInfo.TableInfo
    87  		if tbInfo == nil {
    88  			log.Warn("ignore a invalid DDL job", zap.Reflect("job", job))
    89  			return nil, nil
    90  		}
    91  		tableID := tbInfo.ID
    92  		table, ok := s.TableByID(tableID)
    93  		if !ok {
    94  			return nil, cerror.ErrSchemaStorageTableMiss.GenWithStackByArgs(job.TableID)
    95  		}
    96  		return table, nil
    97  	}
    98  }
    99  
   100  // NewSingleSchemaSnapshotFromMeta creates a new single schema snapshot from a tidb meta
   101  func NewSingleSchemaSnapshotFromMeta(meta *timeta.Meta, currentTs uint64, explicitTables bool) (*SingleSchemaSnapshot, error) {
   102  	// meta is nil only in unit tests
   103  	if meta == nil {
   104  		snap := newEmptySchemaSnapshot(explicitTables)
   105  		snap.currentTs = currentTs
   106  		return snap, nil
   107  	}
   108  	return newSchemaSnapshotFromMeta(meta, currentTs, explicitTables)
   109  }
   110  
   111  func newEmptySchemaSnapshot(explicitTables bool) *schemaSnapshot {
   112  	return &schemaSnapshot{
   113  		tableNameToID:  make(map[model.TableName]int64),
   114  		schemaNameToID: make(map[string]int64),
   115  
   116  		schemas:        make(map[int64]*timodel.DBInfo),
   117  		tables:         make(map[int64]*model.TableInfo),
   118  		partitionTable: make(map[int64]*model.TableInfo),
   119  
   120  		tableInSchema:     make(map[int64][]int64),
   121  		truncateTableID:   make(map[int64]struct{}),
   122  		ineligibleTableID: make(map[int64]struct{}),
   123  
   124  		explicitTables: explicitTables,
   125  	}
   126  }
   127  
   128  func newSchemaSnapshotFromMeta(meta *timeta.Meta, currentTs uint64, explicitTables bool) (*schemaSnapshot, error) {
   129  	snap := newEmptySchemaSnapshot(explicitTables)
   130  	dbinfos, err := meta.ListDatabases()
   131  	if err != nil {
   132  		return nil, cerror.WrapError(cerror.ErrMetaListDatabases, err)
   133  	}
   134  	for _, dbinfo := range dbinfos {
   135  		snap.schemas[dbinfo.ID] = dbinfo
   136  		snap.schemaNameToID[dbinfo.Name.O] = dbinfo.ID
   137  	}
   138  	for schemaID, dbinfo := range snap.schemas {
   139  		tableInfos, err := meta.ListTables(schemaID)
   140  		if err != nil {
   141  			return nil, cerror.WrapError(cerror.ErrMetaListDatabases, err)
   142  		}
   143  		snap.tableInSchema[schemaID] = make([]int64, 0, len(tableInfos))
   144  		for _, tableInfo := range tableInfos {
   145  			snap.tableInSchema[schemaID] = append(snap.tableInSchema[schemaID], tableInfo.ID)
   146  			tableInfo := model.WrapTableInfo(dbinfo.ID, dbinfo.Name.O, currentTs, tableInfo)
   147  			snap.tables[tableInfo.ID] = tableInfo
   148  			snap.tableNameToID[model.TableName{Schema: dbinfo.Name.O, Table: tableInfo.Name.O}] = tableInfo.ID
   149  			isEligible := tableInfo.IsEligible(explicitTables)
   150  			if !isEligible {
   151  				snap.ineligibleTableID[tableInfo.ID] = struct{}{}
   152  			}
   153  			if pi := tableInfo.GetPartitionInfo(); pi != nil {
   154  				for _, partition := range pi.Definitions {
   155  					snap.partitionTable[partition.ID] = tableInfo
   156  					if !isEligible {
   157  						snap.ineligibleTableID[partition.ID] = struct{}{}
   158  					}
   159  				}
   160  			}
   161  		}
   162  	}
   163  	snap.currentTs = currentTs
   164  	return snap, nil
   165  }
   166  
   167  func (s *schemaSnapshot) PrintStatus(logger func(msg string, fields ...zap.Field)) {
   168  	logger("[SchemaSnap] Start to print status", zap.Uint64("currentTs", s.currentTs))
   169  	for id, dbInfo := range s.schemas {
   170  		logger("[SchemaSnap] --> Schemas", zap.Int64("schemaID", id), zap.Reflect("dbInfo", dbInfo))
   171  		// check schemaNameToID
   172  		if schemaID, exist := s.schemaNameToID[dbInfo.Name.O]; !exist || schemaID != id {
   173  			logger("[SchemaSnap] ----> schemaNameToID item lost", zap.String("name", dbInfo.Name.O), zap.Int64("schemaNameToID", s.schemaNameToID[dbInfo.Name.O]))
   174  		}
   175  	}
   176  	if len(s.schemaNameToID) != len(s.schemas) {
   177  		logger("[SchemaSnap] schemaNameToID length mismatch schemas")
   178  		for schemaName, schemaID := range s.schemaNameToID {
   179  			logger("[SchemaSnap] --> schemaNameToID", zap.String("schemaName", schemaName), zap.Int64("schemaID", schemaID))
   180  		}
   181  	}
   182  	for id, tableInfo := range s.tables {
   183  		logger("[SchemaSnap] --> Tables", zap.Int64("tableID", id), zap.Stringer("tableInfo", tableInfo))
   184  		// check tableNameToID
   185  		if tableID, exist := s.tableNameToID[tableInfo.TableName]; !exist || tableID != id {
   186  			logger("[SchemaSnap] ----> tableNameToID item lost", zap.Stringer("name", tableInfo.TableName), zap.Int64("tableNameToID", s.tableNameToID[tableInfo.TableName]))
   187  		}
   188  	}
   189  	if len(s.tableNameToID) != len(s.tables) {
   190  		logger("[SchemaSnap] tableNameToID length mismatch tables")
   191  		for tableName, tableID := range s.tableNameToID {
   192  			logger("[SchemaSnap] --> tableNameToID", zap.Stringer("tableName", tableName), zap.Int64("tableID", tableID))
   193  		}
   194  	}
   195  	for pid, table := range s.partitionTable {
   196  		logger("[SchemaSnap] --> Partitions", zap.Int64("partitionID", pid), zap.Int64("tableID", table.ID))
   197  	}
   198  	truncateTableID := make([]int64, 0, len(s.truncateTableID))
   199  	for id := range s.truncateTableID {
   200  		truncateTableID = append(truncateTableID, id)
   201  	}
   202  	logger("[SchemaSnap] TruncateTableIDs", zap.Int64s("ids", truncateTableID))
   203  
   204  	ineligibleTableID := make([]int64, 0, len(s.ineligibleTableID))
   205  	for id := range s.ineligibleTableID {
   206  		ineligibleTableID = append(ineligibleTableID, id)
   207  	}
   208  	logger("[SchemaSnap] IneligibleTableIDs", zap.Int64s("ids", ineligibleTableID))
   209  }
   210  
   211  // Clone clones Storage
   212  func (s *schemaSnapshot) Clone() *schemaSnapshot {
   213  	clone := *s
   214  
   215  	tableNameToID := make(map[model.TableName]int64, len(s.tableNameToID))
   216  	for k, v := range s.tableNameToID {
   217  		tableNameToID[k] = v
   218  	}
   219  	clone.tableNameToID = tableNameToID
   220  
   221  	schemaNameToID := make(map[string]int64, len(s.schemaNameToID))
   222  	for k, v := range s.schemaNameToID {
   223  		schemaNameToID[k] = v
   224  	}
   225  	clone.schemaNameToID = schemaNameToID
   226  
   227  	schemas := make(map[int64]*timodel.DBInfo, len(s.schemas))
   228  	for k, v := range s.schemas {
   229  		schemas[k] = v.Clone()
   230  	}
   231  	clone.schemas = schemas
   232  
   233  	tables := make(map[int64]*model.TableInfo, len(s.tables))
   234  	for k, v := range s.tables {
   235  		tables[k] = v
   236  	}
   237  	clone.tables = tables
   238  
   239  	tableInSchema := make(map[int64][]int64, len(s.tableInSchema))
   240  	for k, v := range s.tableInSchema {
   241  		cloneV := make([]int64, len(v))
   242  		copy(cloneV, v)
   243  		tableInSchema[k] = cloneV
   244  	}
   245  	clone.tableInSchema = tableInSchema
   246  
   247  	partitionTable := make(map[int64]*model.TableInfo, len(s.partitionTable))
   248  	for k, v := range s.partitionTable {
   249  		partitionTable[k] = v
   250  	}
   251  	clone.partitionTable = partitionTable
   252  
   253  	truncateTableID := make(map[int64]struct{}, len(s.truncateTableID))
   254  	for k, v := range s.truncateTableID {
   255  		truncateTableID[k] = v
   256  	}
   257  	clone.truncateTableID = truncateTableID
   258  
   259  	ineligibleTableID := make(map[int64]struct{}, len(s.ineligibleTableID))
   260  	for k, v := range s.ineligibleTableID {
   261  		ineligibleTableID[k] = v
   262  	}
   263  	clone.ineligibleTableID = ineligibleTableID
   264  
   265  	return &clone
   266  }
   267  
   268  // GetTableNameByID looks up a TableName with the given table id
   269  func (s *schemaSnapshot) GetTableNameByID(id int64) (model.TableName, bool) {
   270  	tableInfo, ok := s.tables[id]
   271  	if !ok {
   272  		// Try partition, it could be a partition table.
   273  		partInfo, ok := s.partitionTable[id]
   274  		if !ok {
   275  			return model.TableName{}, false
   276  		}
   277  		// Must exists an table that contains the partition.
   278  		tableInfo = s.tables[partInfo.ID]
   279  	}
   280  	return tableInfo.TableName, true
   281  }
   282  
   283  // GetTableIDByName returns the tableID by table schemaName and tableName
   284  func (s *schemaSnapshot) GetTableIDByName(schemaName string, tableName string) (int64, bool) {
   285  	id, ok := s.tableNameToID[model.TableName{
   286  		Schema: schemaName,
   287  		Table:  tableName,
   288  	}]
   289  	return id, ok
   290  }
   291  
   292  // GetTableByName queries a table by name,
   293  // the second returned value is false if no table with the specified name is found.
   294  func (s *schemaSnapshot) GetTableByName(schema, table string) (info *model.TableInfo, ok bool) {
   295  	id, ok := s.GetTableIDByName(schema, table)
   296  	if !ok {
   297  		return nil, ok
   298  	}
   299  	return s.TableByID(id)
   300  }
   301  
   302  // SchemaByID returns the DBInfo by schema id
   303  func (s *schemaSnapshot) SchemaByID(id int64) (val *timodel.DBInfo, ok bool) {
   304  	val, ok = s.schemas[id]
   305  	return
   306  }
   307  
   308  // SchemaByTableID returns the schema ID by table ID
   309  func (s *schemaSnapshot) SchemaByTableID(tableID int64) (*timodel.DBInfo, bool) {
   310  	tableInfo, ok := s.tables[tableID]
   311  	if !ok {
   312  		return nil, false
   313  	}
   314  	schemaID, ok := s.schemaNameToID[tableInfo.TableName.Schema]
   315  	if !ok {
   316  		return nil, false
   317  	}
   318  	return s.SchemaByID(schemaID)
   319  }
   320  
   321  // TableByID returns the TableInfo by table id
   322  func (s *schemaSnapshot) TableByID(id int64) (val *model.TableInfo, ok bool) {
   323  	val, ok = s.tables[id]
   324  	return
   325  }
   326  
   327  // PhysicalTableByID returns the TableInfo by table id or partition ID.
   328  func (s *schemaSnapshot) PhysicalTableByID(id int64) (val *model.TableInfo, ok bool) {
   329  	val, ok = s.tables[id]
   330  	if !ok {
   331  		val, ok = s.partitionTable[id]
   332  	}
   333  	return
   334  }
   335  
   336  // IsTruncateTableID returns true if the table id have been truncated by truncate table DDL
   337  func (s *schemaSnapshot) IsTruncateTableID(id int64) bool {
   338  	_, ok := s.truncateTableID[id]
   339  	return ok
   340  }
   341  
   342  // IsIneligibleTableID returns true if the table is ineligible
   343  func (s *schemaSnapshot) IsIneligibleTableID(id int64) bool {
   344  	_, ok := s.ineligibleTableID[id]
   345  	return ok
   346  }
   347  
   348  // FillSchemaName fills the schema name in ddl job
   349  func (s *schemaSnapshot) FillSchemaName(job *timodel.Job) error {
   350  	if job.Type == timodel.ActionCreateSchema ||
   351  		job.Type == timodel.ActionDropSchema {
   352  		job.SchemaName = job.BinlogInfo.DBInfo.Name.O
   353  		return nil
   354  	}
   355  	dbInfo, exist := s.SchemaByID(job.SchemaID)
   356  	if !exist {
   357  		return cerror.ErrSnapshotSchemaNotFound.GenWithStackByArgs(job.SchemaID)
   358  	}
   359  	job.SchemaName = dbInfo.Name.O
   360  	return nil
   361  }
   362  
   363  func (s *schemaSnapshot) dropSchema(id int64) error {
   364  	schema, ok := s.schemas[id]
   365  	if !ok {
   366  		return cerror.ErrSnapshotSchemaNotFound.GenWithStackByArgs(id)
   367  	}
   368  
   369  	for _, tableID := range s.tableInSchema[id] {
   370  		tableName := s.tables[tableID].TableName
   371  		if pi := s.tables[tableID].GetPartitionInfo(); pi != nil {
   372  			for _, partition := range pi.Definitions {
   373  				delete(s.partitionTable, partition.ID)
   374  			}
   375  		}
   376  		delete(s.tables, tableID)
   377  		delete(s.tableNameToID, tableName)
   378  	}
   379  
   380  	delete(s.schemas, id)
   381  	delete(s.tableInSchema, id)
   382  	delete(s.schemaNameToID, schema.Name.O)
   383  
   384  	return nil
   385  }
   386  
   387  func (s *schemaSnapshot) createSchema(db *timodel.DBInfo) error {
   388  	if _, ok := s.schemas[db.ID]; ok {
   389  		return cerror.ErrSnapshotSchemaExists.GenWithStackByArgs(db.Name, db.ID)
   390  	}
   391  
   392  	s.schemas[db.ID] = db.Clone()
   393  	s.schemaNameToID[db.Name.O] = db.ID
   394  	s.tableInSchema[db.ID] = []int64{}
   395  
   396  	log.Debug("create schema success, schema id", zap.String("name", db.Name.O), zap.Int64("id", db.ID))
   397  	return nil
   398  }
   399  
   400  func (s *schemaSnapshot) replaceSchema(db *timodel.DBInfo) error {
   401  	_, ok := s.schemas[db.ID]
   402  	if !ok {
   403  		return cerror.ErrSnapshotSchemaNotFound.GenWithStack("schema %s(%d) not found", db.Name, db.ID)
   404  	}
   405  	s.schemas[db.ID] = db.Clone()
   406  	s.schemaNameToID[db.Name.O] = db.ID
   407  	return nil
   408  }
   409  
   410  func (s *schemaSnapshot) dropTable(id int64) error {
   411  	table, ok := s.tables[id]
   412  	if !ok {
   413  		return cerror.ErrSnapshotTableNotFound.GenWithStackByArgs(id)
   414  	}
   415  	tableInSchema, ok := s.tableInSchema[table.SchemaID]
   416  	if !ok {
   417  		return cerror.ErrSnapshotSchemaNotFound.GenWithStack("table(%d)'s schema", id)
   418  	}
   419  
   420  	for i, tableID := range tableInSchema {
   421  		if tableID == id {
   422  			copy(tableInSchema[i:], tableInSchema[i+1:])
   423  			s.tableInSchema[table.SchemaID] = tableInSchema[:len(tableInSchema)-1]
   424  			break
   425  		}
   426  	}
   427  
   428  	tableName := s.tables[id].TableName
   429  	delete(s.tables, id)
   430  	if pi := table.GetPartitionInfo(); pi != nil {
   431  		for _, partition := range pi.Definitions {
   432  			delete(s.partitionTable, partition.ID)
   433  			delete(s.ineligibleTableID, partition.ID)
   434  		}
   435  	}
   436  	delete(s.tableNameToID, tableName)
   437  	delete(s.ineligibleTableID, id)
   438  
   439  	log.Debug("drop table success", zap.String("name", table.Name.O), zap.Int64("id", id))
   440  	return nil
   441  }
   442  
   443  func (s *schemaSnapshot) updatePartition(tbl *model.TableInfo) error {
   444  	id := tbl.ID
   445  	table, ok := s.tables[id]
   446  	if !ok {
   447  		return cerror.ErrSnapshotTableNotFound.GenWithStackByArgs(id)
   448  	}
   449  	oldPi := table.GetPartitionInfo()
   450  	if oldPi == nil {
   451  		return cerror.ErrSnapshotTableNotFound.GenWithStack("table %d is not a partition table", id)
   452  	}
   453  	oldIDs := make(map[int64]struct{}, len(oldPi.Definitions))
   454  	for _, p := range oldPi.Definitions {
   455  		oldIDs[p.ID] = struct{}{}
   456  	}
   457  
   458  	newPi := tbl.GetPartitionInfo()
   459  	if newPi == nil {
   460  		return cerror.ErrSnapshotTableNotFound.GenWithStack("table %d is not a partition table", id)
   461  	}
   462  	s.tables[id] = tbl
   463  	for _, partition := range newPi.Definitions {
   464  		// update table info.
   465  		if _, ok := s.partitionTable[partition.ID]; ok {
   466  			log.Debug("add table partition success", zap.String("name", tbl.Name.O), zap.Int64("tid", id), zap.Reflect("add partition id", partition.ID))
   467  		}
   468  		s.partitionTable[partition.ID] = tbl
   469  		if !tbl.ExistTableUniqueColumn() {
   470  			s.ineligibleTableID[partition.ID] = struct{}{}
   471  		}
   472  		delete(oldIDs, partition.ID)
   473  	}
   474  
   475  	// drop old partition.
   476  	for pid := range oldIDs {
   477  		s.truncateTableID[pid] = struct{}{}
   478  		delete(s.partitionTable, pid)
   479  		delete(s.ineligibleTableID, pid)
   480  		log.Debug("drop table partition success", zap.String("name", tbl.Name.O), zap.Int64("tid", id), zap.Reflect("truncated partition id", pid))
   481  	}
   482  
   483  	return nil
   484  }
   485  
   486  func (s *schemaSnapshot) createTable(table *model.TableInfo) error {
   487  	schema, ok := s.schemas[table.SchemaID]
   488  	if !ok {
   489  		return cerror.ErrSnapshotSchemaNotFound.GenWithStack("table's schema(%d)", table.SchemaID)
   490  	}
   491  	tableInSchema, ok := s.tableInSchema[table.SchemaID]
   492  	if !ok {
   493  		return cerror.ErrSnapshotSchemaNotFound.GenWithStack("table's schema(%d)", table.SchemaID)
   494  	}
   495  	_, ok = s.tables[table.ID]
   496  	if ok {
   497  		return cerror.ErrSnapshotTableExists.GenWithStackByArgs(schema.Name, table.Name)
   498  	}
   499  	tableInSchema = append(tableInSchema, table.ID)
   500  	s.tableInSchema[table.SchemaID] = tableInSchema
   501  
   502  	s.tables[table.ID] = table
   503  	if !table.IsEligible(s.explicitTables) {
   504  		log.Warn("this table is not eligible to replicate", zap.String("tableName", table.Name.O), zap.Int64("tableID", table.ID))
   505  		s.ineligibleTableID[table.ID] = struct{}{}
   506  	}
   507  	if pi := table.GetPartitionInfo(); pi != nil {
   508  		for _, partition := range pi.Definitions {
   509  			s.partitionTable[partition.ID] = table
   510  			if !table.IsEligible(s.explicitTables) {
   511  				s.ineligibleTableID[partition.ID] = struct{}{}
   512  			}
   513  		}
   514  	}
   515  	s.tableNameToID[table.TableName] = table.ID
   516  
   517  	log.Debug("create table success", zap.String("name", schema.Name.O+"."+table.Name.O), zap.Int64("id", table.ID))
   518  	return nil
   519  }
   520  
   521  // ReplaceTable replace the table by new tableInfo
   522  func (s *schemaSnapshot) replaceTable(table *model.TableInfo) error {
   523  	_, ok := s.tables[table.ID]
   524  	if !ok {
   525  		return cerror.ErrSnapshotTableNotFound.GenWithStack("table %s(%d)", table.Name, table.ID)
   526  	}
   527  	s.tables[table.ID] = table
   528  	if !table.IsEligible(s.explicitTables) {
   529  		log.Warn("this table is not eligible to replicate", zap.String("tableName", table.Name.O), zap.Int64("tableID", table.ID))
   530  		s.ineligibleTableID[table.ID] = struct{}{}
   531  	}
   532  	if pi := table.GetPartitionInfo(); pi != nil {
   533  		for _, partition := range pi.Definitions {
   534  			s.partitionTable[partition.ID] = table
   535  			if !table.IsEligible(s.explicitTables) {
   536  				s.ineligibleTableID[partition.ID] = struct{}{}
   537  			}
   538  		}
   539  	}
   540  
   541  	return nil
   542  }
   543  
   544  func (s *schemaSnapshot) handleDDL(job *timodel.Job) error {
   545  	if err := s.FillSchemaName(job); err != nil {
   546  		return errors.Trace(err)
   547  	}
   548  	log.Debug("handle job: ", zap.String("sql query", job.Query), zap.Stringer("job", job))
   549  	getWrapTableInfo := func(job *timodel.Job) *model.TableInfo {
   550  		return model.WrapTableInfo(job.SchemaID, job.SchemaName,
   551  			job.BinlogInfo.FinishedTS,
   552  			job.BinlogInfo.TableInfo)
   553  	}
   554  	switch job.Type {
   555  	case timodel.ActionCreateSchema:
   556  		// get the DBInfo from job rawArgs
   557  		err := s.createSchema(job.BinlogInfo.DBInfo)
   558  		if err != nil {
   559  			return errors.Trace(err)
   560  		}
   561  	case timodel.ActionModifySchemaCharsetAndCollate:
   562  		err := s.replaceSchema(job.BinlogInfo.DBInfo)
   563  		if err != nil {
   564  			return errors.Trace(err)
   565  		}
   566  	case timodel.ActionDropSchema:
   567  		err := s.dropSchema(job.SchemaID)
   568  		if err != nil {
   569  			return errors.Trace(err)
   570  		}
   571  	case timodel.ActionRenameTable:
   572  		// first drop the table
   573  		err := s.dropTable(job.TableID)
   574  		if err != nil {
   575  			return errors.Trace(err)
   576  		}
   577  		// create table
   578  		err = s.createTable(getWrapTableInfo(job))
   579  		if err != nil {
   580  			return errors.Trace(err)
   581  		}
   582  	case timodel.ActionCreateTable, timodel.ActionCreateView, timodel.ActionRecoverTable:
   583  		err := s.createTable(getWrapTableInfo(job))
   584  		if err != nil {
   585  			return errors.Trace(err)
   586  		}
   587  	case timodel.ActionDropTable, timodel.ActionDropView:
   588  		err := s.dropTable(job.TableID)
   589  		if err != nil {
   590  			return errors.Trace(err)
   591  		}
   592  
   593  	case timodel.ActionTruncateTable:
   594  		// job.TableID is the old table id, different from table.ID
   595  		err := s.dropTable(job.TableID)
   596  		if err != nil {
   597  			return errors.Trace(err)
   598  		}
   599  
   600  		err = s.createTable(getWrapTableInfo(job))
   601  		if err != nil {
   602  			return errors.Trace(err)
   603  		}
   604  
   605  		s.truncateTableID[job.TableID] = struct{}{}
   606  	case timodel.ActionTruncateTablePartition, timodel.ActionAddTablePartition, timodel.ActionDropTablePartition:
   607  		err := s.updatePartition(getWrapTableInfo(job))
   608  		if err != nil {
   609  			return errors.Trace(err)
   610  		}
   611  	default:
   612  		binlogInfo := job.BinlogInfo
   613  		if binlogInfo == nil {
   614  			log.Warn("ignore a invalid DDL job", zap.Reflect("job", job))
   615  			return nil
   616  		}
   617  		tbInfo := binlogInfo.TableInfo
   618  		if tbInfo == nil {
   619  			log.Warn("ignore a invalid DDL job", zap.Reflect("job", job))
   620  			return nil
   621  		}
   622  		err := s.replaceTable(getWrapTableInfo(job))
   623  		if err != nil {
   624  			return errors.Trace(err)
   625  		}
   626  	}
   627  	s.currentTs = job.BinlogInfo.FinishedTS
   628  	return nil
   629  }
   630  
   631  // CloneTables return a clone of the existing tables.
   632  func (s *schemaSnapshot) CloneTables() map[model.TableID]model.TableName {
   633  	mp := make(map[model.TableID]model.TableName, len(s.tables))
   634  
   635  	for id, table := range s.tables {
   636  		mp[id] = table.TableName
   637  	}
   638  
   639  	return mp
   640  }
   641  
   642  // Tables return a map between table id and table info
   643  // the returned map must be READ-ONLY. Any modified of this map will lead to the internal state confusion in schema storage
   644  func (s *schemaSnapshot) Tables() map[model.TableID]*model.TableInfo {
   645  	return s.tables
   646  }
   647  
   648  // SchemaStorage stores the schema information with multi-version
   649  type SchemaStorage interface {
   650  	// GetSnapshot returns the snapshot which of ts is specified
   651  	GetSnapshot(ctx context.Context, ts uint64) (*schemaSnapshot, error)
   652  	// GetLastSnapshot returns the last snapshot
   653  	GetLastSnapshot() *schemaSnapshot
   654  	// HandleDDLJob creates a new snapshot in storage and handles the ddl job
   655  	HandleDDLJob(job *timodel.Job) error
   656  	// AdvanceResolvedTs advances the resolved
   657  	AdvanceResolvedTs(ts uint64)
   658  	// ResolvedTs returns the resolved ts of the schema storage
   659  	ResolvedTs() uint64
   660  	// DoGC removes snaps which of ts less than this specified ts
   661  	DoGC(ts uint64)
   662  }
   663  
   664  type schemaStorageImpl struct {
   665  	snaps      []*schemaSnapshot
   666  	snapsMu    sync.RWMutex
   667  	gcTs       uint64
   668  	resolvedTs uint64
   669  
   670  	filter         *filter.Filter
   671  	explicitTables bool
   672  }
   673  
   674  // NewSchemaStorage creates a new schema storage
   675  func NewSchemaStorage(meta *timeta.Meta, startTs uint64, filter *filter.Filter, forceReplicate bool) (SchemaStorage, error) {
   676  	var snap *schemaSnapshot
   677  	var err error
   678  	if meta == nil {
   679  		snap = newEmptySchemaSnapshot(forceReplicate)
   680  	} else {
   681  		snap, err = newSchemaSnapshotFromMeta(meta, startTs, forceReplicate)
   682  	}
   683  	if err != nil {
   684  		return nil, errors.Trace(err)
   685  	}
   686  	schema := &schemaStorageImpl{
   687  		snaps:          []*schemaSnapshot{snap},
   688  		resolvedTs:     startTs,
   689  		filter:         filter,
   690  		explicitTables: forceReplicate,
   691  	}
   692  	return schema, nil
   693  }
   694  
   695  func (s *schemaStorageImpl) getSnapshot(ts uint64) (*schemaSnapshot, error) {
   696  	gcTs := atomic.LoadUint64(&s.gcTs)
   697  	if ts < gcTs {
   698  		return nil, cerror.ErrSchemaStorageGCed.GenWithStackByArgs(ts, gcTs)
   699  	}
   700  	resolvedTs := atomic.LoadUint64(&s.resolvedTs)
   701  	if ts > resolvedTs {
   702  		return nil, cerror.ErrSchemaStorageUnresolved.GenWithStackByArgs(ts, resolvedTs)
   703  	}
   704  	s.snapsMu.RLock()
   705  	defer s.snapsMu.RUnlock()
   706  	i := sort.Search(len(s.snaps), func(i int) bool {
   707  		return s.snaps[i].currentTs > ts
   708  	})
   709  	if i <= 0 {
   710  		return nil, cerror.ErrSchemaSnapshotNotFound.GenWithStackByArgs(ts)
   711  	}
   712  	return s.snaps[i-1], nil
   713  }
   714  
   715  // GetSnapshot returns the snapshot which of ts is specified
   716  func (s *schemaStorageImpl) GetSnapshot(ctx context.Context, ts uint64) (*schemaSnapshot, error) {
   717  	var snap *schemaSnapshot
   718  
   719  	// The infinite retry here is a temporary solution to the `ErrSchemaStorageUnresolved` caused by
   720  	// DDL puller lagging too much.
   721  	startTime := time.Now()
   722  	err := retry.Do(ctx, func() error {
   723  		var err error
   724  		snap, err = s.getSnapshot(ts)
   725  		if time.Since(startTime) >= 5*time.Minute && isRetryable(err) {
   726  			log.Warn("GetSnapshot is taking too long, DDL puller stuck?", zap.Uint64("ts", ts))
   727  		}
   728  		return err
   729  	}, retry.WithBackoffBaseDelay(10), retry.WithInfiniteTries(), retry.WithIsRetryableErr(isRetryable))
   730  
   731  	return snap, err
   732  }
   733  
   734  func isRetryable(err error) bool {
   735  	return cerror.IsRetryableError(err) && cerror.ErrSchemaStorageUnresolved.Equal(err)
   736  }
   737  
   738  // GetLastSnapshot returns the last snapshot
   739  func (s *schemaStorageImpl) GetLastSnapshot() *schemaSnapshot {
   740  	s.snapsMu.RLock()
   741  	defer s.snapsMu.RUnlock()
   742  	return s.snaps[len(s.snaps)-1]
   743  }
   744  
   745  // HandleDDLJob creates a new snapshot in storage and handles the ddl job
   746  func (s *schemaStorageImpl) HandleDDLJob(job *timodel.Job) error {
   747  	if s.skipJob(job) {
   748  		s.AdvanceResolvedTs(job.BinlogInfo.FinishedTS)
   749  		return nil
   750  	}
   751  	s.snapsMu.Lock()
   752  	defer s.snapsMu.Unlock()
   753  	var snap *schemaSnapshot
   754  	if len(s.snaps) > 0 {
   755  		lastSnap := s.snaps[len(s.snaps)-1]
   756  		if job.BinlogInfo.FinishedTS <= lastSnap.currentTs {
   757  			log.Debug("ignore foregone DDL job", zap.Reflect("job", job))
   758  			return nil
   759  		}
   760  		snap = lastSnap.Clone()
   761  	} else {
   762  		snap = newEmptySchemaSnapshot(s.explicitTables)
   763  	}
   764  	if err := snap.handleDDL(job); err != nil {
   765  		return errors.Trace(err)
   766  	}
   767  	s.snaps = append(s.snaps, snap)
   768  	s.AdvanceResolvedTs(job.BinlogInfo.FinishedTS)
   769  	return nil
   770  }
   771  
   772  // AdvanceResolvedTs advances the resolved
   773  func (s *schemaStorageImpl) AdvanceResolvedTs(ts uint64) {
   774  	var swapped bool
   775  	for !swapped {
   776  		oldResolvedTs := atomic.LoadUint64(&s.resolvedTs)
   777  		if ts < oldResolvedTs {
   778  			return
   779  		}
   780  		swapped = atomic.CompareAndSwapUint64(&s.resolvedTs, oldResolvedTs, ts)
   781  	}
   782  }
   783  
   784  // ResolvedTs returns the resolved ts of the schema storage
   785  func (s *schemaStorageImpl) ResolvedTs() uint64 {
   786  	return atomic.LoadUint64(&s.resolvedTs)
   787  }
   788  
   789  // DoGC removes snaps which of ts less than this specified ts
   790  func (s *schemaStorageImpl) DoGC(ts uint64) {
   791  	s.snapsMu.Lock()
   792  	defer s.snapsMu.Unlock()
   793  	var startIdx int
   794  	for i, snap := range s.snaps {
   795  		if snap.currentTs > ts {
   796  			break
   797  		}
   798  		startIdx = i
   799  	}
   800  	if startIdx == 0 {
   801  		return
   802  	}
   803  	if log.GetLevel() == zapcore.DebugLevel {
   804  		log.Debug("Do GC in schema storage")
   805  		for i := 0; i < startIdx; i++ {
   806  			s.snaps[i].PrintStatus(log.Debug)
   807  		}
   808  	}
   809  	s.snaps = s.snaps[startIdx:]
   810  	atomic.StoreUint64(&s.gcTs, s.snaps[0].currentTs)
   811  	log.Info("finished gc in schema storage", zap.Uint64("gcTs", s.snaps[0].currentTs))
   812  }
   813  
   814  // SkipJob skip the job should not be executed
   815  // TiDB write DDL Binlog for every DDL Job, we must ignore jobs that are cancelled or rollback
   816  // For older version TiDB, it write DDL Binlog in the txn that the state of job is changed to *synced*
   817  // Now, it write DDL Binlog in the txn that the state of job is changed to *done* (before change to *synced*)
   818  // At state *done*, it will be always and only changed to *synced*.
   819  func (s *schemaStorageImpl) skipJob(job *timodel.Job) bool {
   820  	if s.filter != nil && s.filter.ShouldDiscardDDL(job.Type) {
   821  		log.Info("discard the ddl job", zap.Int64("jobID", job.ID), zap.String("query", job.Query))
   822  		return true
   823  	}
   824  	return !job.IsSynced() && !job.IsDone()
   825  }