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 }