github.com/pingcap/tiflow@v0.0.0-20240520035814-5bf52d54e205/engine/jobmaster/dm/ddl_coordinator.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 dm 15 16 import ( 17 "context" 18 "strings" 19 "sync" 20 21 "github.com/pingcap/tidb/pkg/ddl" 22 "github.com/pingcap/tidb/pkg/parser" 23 "github.com/pingcap/tidb/pkg/parser/ast" 24 "github.com/pingcap/tidb/pkg/parser/model" 25 "github.com/pingcap/tidb/pkg/util/schemacmp" 26 "github.com/pingcap/tiflow/dm/pkg/log" 27 "github.com/pingcap/tiflow/dm/pkg/shardddl/optimism" 28 frameModel "github.com/pingcap/tiflow/engine/framework/model" 29 "github.com/pingcap/tiflow/engine/jobmaster/dm/config" 30 "github.com/pingcap/tiflow/engine/jobmaster/dm/metadata" 31 metaModel "github.com/pingcap/tiflow/engine/pkg/meta/model" 32 "github.com/pingcap/tiflow/pkg/errors" 33 "go.uber.org/zap" 34 "golang.org/x/exp/slices" 35 ) 36 37 // Workflow: 38 // 39 // Worker1 Jobmaster DDLCoordinator TiDB 40 // │ │ │ │ 41 // │ │ Init │ │ 42 // │ ├─────────────────────────────►│ Reset │ 43 // │ │ ├───────────────────────────────►│ 44 // │ │ │ FetchAllDoTables │ 45 // │ DDLCoordinateRequest │ │ │ 46 // ├───────────────────────────►│ Coordinate │ │ 47 // │ AlterTableDropColumn ├─────────────────────────────►│ CreateShardGroup │ 48 // │ │ ├───────────────────────────────►│ 49 // │ │ │ FetchAllDoTables │ 50 // │ │ │ │ 51 // │ │ ├───┐ │ 52 // │ │ │ │ Handle │ 53 // │ │ │ │ │ 54 // │ │ │ │ AddDroppedColumn │ 55 // │ │ Response │◄──┘ │ 56 // │ DDLCoordinateResponse │◄─────────────────────────────┤ │ 57 // │◄───────────────────────────┤ Skip │ │ 58 // │ Skip │ │ │ 59 // │ │ │ │ 60 // │ │ │ │ 61 // │ DDLCoordinateRequest │ │ │ 62 // ├───────────────────────────►│ Coordinate │ │ 63 // │ AlterTableDropColumn ├─────────────────────────────►│ │ 64 // │ │ ├───┐ │ 65 // │ │ │ │ Handle │ 66 // │ │ │ │ │ 67 // │ │ │ │ AddDroppedColumn │ 68 // │ │ Response │◄──┘ │ 69 // │ DDLCoordinateResponse │◄─────────────────────────────┤ │ 70 // │◄───────────────────────────┤ Execute │ │ 71 // │ Execute │ │ │ 72 // │ │ │ │ 73 // │ │ │ │ 74 // │ DDLCoordinateRequest │ │ │ 75 // ├───────────────────────────►│ │ │ 76 // │ AlterTableRenameColumn │ Coordinate │ │ 77 // │ ├─────────────────────────────►│ │ 78 // │ │ ├───┐ │ 79 // │ │ │ │ GCDroppedColumn │ 80 // │ │ │ ├───────────────────────────►│ 81 // │ │ │ │ │ 82 // │ │ │ │ Handle │ 83 // │ │ Response │◄──┘ │ 84 // │ │◄─────────────────────────────┤ │ 85 // │ DDLCoordinateResponse │ SkipAndWaitRedirect │ │ 86 // │◄───────────────────────────┤ │ │ 87 // │ SkipAndWaitRedirect │ │ │ 88 // │ │ │ │ 89 // │ ├───┐ │ │ 90 // │ │ │ Jobmaster failover │ │ 91 // │ │ │ │ │ 92 // │ │ │ Recover │ │ 93 // │ │ ├─────────────────────────►│ Reset │ 94 // │ │ │ ├───────────────────────────────►│ 95 // │ RestartAllWorkers │◄──┘ │ FetchAllDoTables │ 96 // │◄───────────────────────────┤ │ │ 97 // │ │ │ │ 98 // ├───┐ │ │ │ 99 // │ │ Restart From Checkpoint│ │ │ 100 // │◄──┘ │ │ │ 101 // │ │ │ │ 102 // │ DDLCoordinateRequest │ │ │ 103 // ├───────────────────────────►│ Coordinate │ │ 104 // │ AlterTableRenameColumn ├─────────────────────────────►│ │ 105 // │ │ ├───┐ │ 106 // │ │ │ │ Handle │ 107 // │ │ Response │◄──┘ │ 108 // │ │◄─────────────────────────────┤ │ 109 // │ DDLCoordinateResponse │ SkipAndWaitRedirect │ │ 110 // │◄───────────────────────────┤ │ │ 111 // │ SkipAndWaitRedirect │ │ │ 112 // │ │ │ │ 113 // │ │ │ Worker2 Rename Column │ 114 // │ │ │◄──────────────────────────── │ 115 // │ DDLRedirectRequest │ │ │ 116 // │◄───────────────────────────┼──────────────────────────────┤ │ 117 // │────────────────────────────┼─────────────────────────────►│ │ 118 // │ DDLRedirectResponse │ │ │ 119 // │ │ │ │ 120 // ├────┐ │ │ │ 121 // │ │ │ │ │ 122 // │ │ Redirect │ │ │ 123 // │ │ │ │ │ 124 // │◄───┘ │ │ │ 125 // │ │ │ │ 126 127 type tableType int 128 129 const ( 130 // normal represents the type of shardGroup.normalTables. 131 normal tableType = iota 132 // conflict represents the type of shardGroup.conflictTables. 133 conflict 134 // final represents the type of shardGroup.conflictTables if exist else shardGroup.normalTables. 135 final 136 ) 137 138 // TableAgent defines an interface for checkpoint. 139 type TableAgent interface { 140 FetchAllDoTables(ctx context.Context, cfg *config.JobCfg) (map[metadata.TargetTable][]metadata.SourceTable, error) 141 FetchTableStmt(ctx context.Context, jobID string, cfg *config.JobCfg, sourceTable metadata.SourceTable) (string, error) 142 } 143 144 // DDLCoordinator is a coordinator for ddl. 145 type DDLCoordinator struct { 146 mu sync.RWMutex 147 tables map[metadata.TargetTable]map[metadata.SourceTable]struct{} 148 shardGroups map[metadata.TargetTable]*shardGroup 149 logger *zap.Logger 150 151 kvClient metaModel.KVClient 152 tableAgent TableAgent 153 jobID string 154 jobStore *metadata.JobStore 155 } 156 157 // NewDDLCoordinator creates a new DDLCoordinator. 158 func NewDDLCoordinator(jobID string, kvClient metaModel.KVClient, tableAgent TableAgent, jobStore *metadata.JobStore, pLogger *zap.Logger) *DDLCoordinator { 159 return &DDLCoordinator{ 160 tableAgent: tableAgent, 161 tables: make(map[metadata.TargetTable]map[metadata.SourceTable]struct{}), 162 jobID: jobID, 163 kvClient: kvClient, 164 shardGroups: make(map[metadata.TargetTable]*shardGroup), 165 jobStore: jobStore, 166 logger: pLogger.With(zap.String("component", "ddl_coordinator")), 167 } 168 } 169 170 // ClearMetadata clears metadata. 171 func (c *DDLCoordinator) ClearMetadata(ctx context.Context) error { 172 c.mu.Lock() 173 defer c.mu.Unlock() 174 return metadata.DelAllDroppedColumns(ctx, c.kvClient) 175 } 176 177 // Reset resets the ddl coordinator. 178 func (c *DDLCoordinator) Reset(ctx context.Context) error { 179 c.mu.Lock() 180 defer c.mu.Unlock() 181 jobCfg, err := c.jobStore.GetJobCfg(ctx) 182 if err != nil { 183 return err 184 } 185 if jobCfg.ShardMode == "" { 186 c.logger.Info("non-shard-mode, skip reset") 187 return nil 188 } 189 c.logger.Info("reset ddl coordinator") 190 191 // fetch all tables which need to be coordinated. 192 tables, err := c.tableAgent.FetchAllDoTables(ctx, jobCfg) 193 if err != nil { 194 return err 195 } 196 197 for targetTable, sourceTables := range tables { 198 c.tables[targetTable] = make(map[metadata.SourceTable]struct{}, 0) 199 for _, sourceTable := range sourceTables { 200 c.tables[targetTable][sourceTable] = struct{}{} 201 } 202 } 203 return nil 204 } 205 206 // Coordinate coordinates ddls. 207 func (c *DDLCoordinator) Coordinate(ctx context.Context, item *metadata.DDLItem) ([]string, optimism.ConflictStage, error) { 208 c.mu.Lock() 209 defer c.mu.Unlock() 210 jobCfg, err := c.jobStore.GetJobCfg(ctx) 211 if err != nil { 212 return nil, optimism.ConflictError, err 213 } else if jobCfg.ShardMode == "" { 214 return nil, optimism.ConflictError, errors.New("coordinate error with non-shard-mode") 215 } 216 217 // create shard group if not exists. 218 g, err := c.loadOrCreateShardGroup(ctx, item.TargetTable, jobCfg) 219 if err != nil { 220 return nil, optimism.ConflictError, err 221 } 222 223 ddls, conflictStage, err := g.handle(ctx, item) 224 if err != nil { 225 return ddls, conflictStage, err 226 } 227 228 // if all source table is deleted or the shard group is resolved, we should remove the shard group. 229 if g.isResolved(ctx) { 230 c.removeShardGroup(ctx, item.TargetTable) 231 } 232 233 // handle table level ddl 234 switch item.Type { 235 case metadata.CreateTable: 236 tables, ok := c.tables[item.TargetTable] 237 if !ok { 238 c.tables[item.TargetTable] = make(map[metadata.SourceTable]struct{}, 0) 239 tables = c.tables[item.TargetTable] 240 } 241 tables[item.SourceTable] = struct{}{} 242 case metadata.DropTable: 243 delete(c.tables[item.TargetTable], item.SourceTable) 244 if len(c.tables[item.TargetTable]) == 0 { 245 delete(c.tables, item.TargetTable) 246 } 247 } 248 return ddls, conflictStage, err 249 } 250 251 // ShowDDLLocks show ddl locks. 252 func (c *DDLCoordinator) ShowDDLLocks(ctx context.Context) ShowDDLLocksResponse { 253 c.mu.RLock() 254 defer c.mu.RUnlock() 255 ddlLocks := make(map[metadata.TargetTable]DDLLock) 256 for targetTable, g := range c.shardGroups { 257 tbs := g.showTables() 258 ddlLocks[targetTable] = DDLLock{ShardTables: tbs} 259 } 260 return ShowDDLLocksResponse{Locks: ddlLocks} 261 } 262 263 func (c *DDLCoordinator) loadOrCreateShardGroup(ctx context.Context, targetTable metadata.TargetTable, jobCfg *config.JobCfg) (*shardGroup, error) { 264 if g, ok := c.shardGroups[targetTable]; ok { 265 return g, nil 266 } 267 268 newGroup, err := newShardGroup(ctx, c.jobID, jobCfg, targetTable, c.tables[targetTable], c.kvClient, c.tableAgent) 269 if err != nil { 270 return nil, err 271 } 272 c.shardGroups[targetTable] = newGroup 273 return newGroup, nil 274 } 275 276 func (c *DDLCoordinator) removeShardGroup(ctx context.Context, targetTable metadata.TargetTable) { 277 if g, ok := c.shardGroups[targetTable]; ok { 278 if err := g.clear(ctx); err != nil { 279 c.logger.Error("clear shard group failed", zap.Error(err)) 280 } 281 } 282 delete(c.shardGroups, targetTable) 283 } 284 285 type shardGroup struct { 286 // normalTables represents upstream table info record in checkpoint. 287 normalTables map[metadata.SourceTable]string 288 // conflictTables represents upstream table info after executing conflict DDL. 289 conflictTables map[metadata.SourceTable]string 290 tableAgent TableAgent 291 droppedColumnsStore *metadata.DroppedColumnsStore 292 id frameModel.MasterID 293 cfg *config.JobCfg 294 } 295 296 func newShardGroup(ctx context.Context, id frameModel.MasterID, cfg *config.JobCfg, targetTable metadata.TargetTable, sourceTables map[metadata.SourceTable]struct{}, kvClient metaModel.KVClient, tableAgent TableAgent) (*shardGroup, error) { 297 g := &shardGroup{ 298 tableAgent: tableAgent, 299 normalTables: make(map[metadata.SourceTable]string), 300 conflictTables: make(map[metadata.SourceTable]string), 301 droppedColumnsStore: metadata.NewDroppedColumnsStore(kvClient, targetTable), 302 id: id, 303 cfg: cfg, 304 } 305 for sourceTable := range sourceTables { 306 stmt, err := g.tableAgent.FetchTableStmt(ctx, id, cfg, sourceTable) 307 // NOTE: There are cases where the source table exists but the table info does not, 308 // such as a table is created upstream when the coordinator starts, but the dm-worker is not yet synchronized 309 // we skip these tables in handleDDL. 310 // TODO: better error handling 311 if err != nil && !strings.HasPrefix(err.Error(), "table info not found") { 312 return nil, errors.Errorf("fetch table stmt from checkpoint failed, sourceTable: %s, err: %v", sourceTable, err) 313 } 314 g.normalTables[sourceTable] = stmt 315 } 316 return g, nil 317 } 318 319 func (g *shardGroup) handle(ctx context.Context, item *metadata.DDLItem) ([]string, optimism.ConflictStage, error) { 320 // nolint:errcheck 321 g.gcDroppedColumns(ctx) 322 323 var ( 324 ddls []string 325 err error 326 conflictStage optimism.ConflictStage = optimism.ConflictNone 327 ) 328 329 switch item.Type { 330 case metadata.CreateTable: 331 g.handleCreateTable(item) 332 ddls = append(ddls, item.DDLs...) 333 case metadata.DropTable: 334 g.handleDropTable(ctx, item) 335 ddls = append(ddls, item.DDLs...) 336 case metadata.OtherDDL: 337 ddls, conflictStage, err = g.handleDDLs(ctx, item) 338 default: 339 return nil, optimism.ConflictError, errors.Errorf("unknown ddl type %v", item.Type) 340 } 341 342 return ddls, conflictStage, err 343 } 344 345 // handleCreateTable handles create table ddl. 346 // add new source table to shard group. 347 func (g *shardGroup) handleCreateTable(item *metadata.DDLItem) { 348 stmt, ok := g.normalTables[item.SourceTable] 349 if ok && stmt != "" { 350 log.L().Warn("create table already exists", zap.Any("source table", item.SourceTable)) 351 } 352 353 g.normalTables[item.SourceTable] = item.Tables[0] 354 } 355 356 // handleDropTable handles drop table ddl. 357 // remove source table from shard group. 358 // mark shard group as deleted if all source tables are deleted. 359 func (g *shardGroup) handleDropTable(ctx context.Context, item *metadata.DDLItem) { 360 _, ok := g.normalTables[item.SourceTable] 361 if !ok { 362 log.L().Warn("drop table does not exist", zap.Any("source table", item.SourceTable)) 363 return 364 } 365 366 delete(g.normalTables, item.SourceTable) 367 delete(g.conflictTables, item.SourceTable) 368 // nolint:errcheck 369 g.droppedColumnsStore.DelDroppedColumnForTable(ctx, item.SourceTable) 370 } 371 372 // handleDDLs handles ddl. 373 func (g *shardGroup) handleDDLs(ctx context.Context, item *metadata.DDLItem) (newDDLs []string, conflictStage optimism.ConflictStage, err error) { 374 stmt, ok := g.normalTables[item.SourceTable] 375 if !ok || stmt == "" { 376 log.L().Warn("table does not exist", zap.Any("source table", item.SourceTable)) 377 g.normalTables[item.SourceTable] = item.Tables[0] 378 } 379 380 dropCols := make([]string, 0, len(item.DDLs)) 381 382 // handle ddls one by one 383 for idx, ddl := range item.DDLs { 384 prevTableStmt, postTableStmt := g.getTableForOneDDL(item, idx) 385 schemaChanged, conflictStage := g.handleDDL(item.SourceTable, prevTableStmt, postTableStmt) 386 387 switch conflictStage { 388 case optimism.ConflictDetected: 389 return nil, optimism.ConflictDetected, errors.Errorf("conflict detected for table %v", item.SourceTable) 390 case optimism.ConflictSkipWaitRedirect: 391 return newDDLs, optimism.ConflictSkipWaitRedirect, nil 392 case optimism.ConflictNone: 393 if col, err := g.checkAddDroppedColumn(ctx, item.SourceTable, ddl, prevTableStmt, postTableStmt, dropCols); err != nil { 394 return nil, optimism.ConflictError, err 395 } else if len(col) != 0 { 396 dropCols = append(dropCols, col) 397 } 398 case optimism.ConflictResolved: 399 } 400 if schemaChanged { 401 newDDLs = append(newDDLs, ddl) 402 } 403 } 404 405 if len(dropCols) > 0 { 406 if err := g.droppedColumnsStore.AddDroppedColumns(ctx, dropCols, item.SourceTable); err != nil { 407 return nil, optimism.ConflictError, err 408 } 409 } 410 411 return newDDLs, optimism.ConflictNone, nil 412 } 413 414 func (g *shardGroup) handleDDL(sourceTable metadata.SourceTable, prevTableStmt, postTableStmt string) (bool, optimism.ConflictStage) { 415 // for a new ddl, we ignore the original conflict table, just use the normal table and new ddl to calculate the ConfictStage. 416 delete(g.conflictTables, sourceTable) 417 418 prevTable := genCmpTable(prevTableStmt) 419 postTable := genCmpTable(postTableStmt) 420 421 currTable := genCmpTable(g.normalTables[sourceTable]) 422 // handle idempotent ddl 423 idempotent := false 424 if cmp, err := prevTable.Compare(currTable); err != nil || cmp != 0 { 425 if cmp, err := postTable.Compare(currTable); err == nil && cmp == 0 { 426 idempotent = true 427 } 428 // this usually happened when worker restarts and the shard group is not reset. 429 log.L().Warn("prev-table not equal table saved in master", zap.Stringer("master-table", currTable), zap.Stringer("prev-table", prevTable)) 430 g.normalTables[sourceTable] = prevTableStmt 431 } 432 433 tableCmp, tableErr := prevTable.Compare(postTable) 434 // Normal DDL 435 if tableErr == nil { 436 oldJoined, oldErr := g.joinTables(normal) 437 438 g.normalTables[sourceTable] = postTableStmt 439 newJoined, newErr := g.joinTables(normal) 440 // normal DDL can be sync if no error 441 if newErr == nil { 442 // if a normal DDL let all final tables become no conflict 443 // return ConflictNone 444 if len(g.conflictTables) > 0 && g.noConflictForTables(final) { 445 log.L().Info("all conflict resolved for the DDL", zap.Any("source table", sourceTable), zap.String("prevTable", prevTableStmt), zap.String("postTable", postTableStmt)) 446 g.resolveTables() 447 return true, optimism.ConflictNone 448 } 449 450 // should not happened 451 if oldErr != nil { 452 return true, optimism.ConflictNone 453 } 454 joinedCmp, joinedErr := oldJoined.Compare(newJoined) 455 // return schema changed in 2 cases 456 // oldJoined != newJoined 457 // prevTable < postTable 458 return (joinedErr != nil || joinedCmp != 0) || tableCmp < 0, optimism.ConflictNone 459 } 460 } 461 462 log.L().Info("found conflict for DDL", zap.Any("source table", sourceTable), zap.String("prevTable", prevTableStmt), zap.String("postTable", postTableStmt), log.ShortError(tableErr)) 463 464 if idempotent || g.noConflictWithOneNormalTable(sourceTable, prevTable, postTable) { 465 log.L().Info("directly return conflict DDL", zap.Bool("idempotent", idempotent), zap.Any("source", sourceTable), zap.Stringer("prevTable", prevTable), zap.Stringer("postTable", postTable)) 466 g.normalTables[sourceTable] = postTableStmt 467 return true, optimism.ConflictNone 468 } 469 470 // meet conflict DDL 471 g.normalTables[sourceTable] = prevTableStmt 472 g.conflictTables[sourceTable] = postTableStmt 473 474 // if any conflict happened between conflict DDLs, return error 475 // e.g. tb1: "ALTER TABLE RENAME a TO b", tb2: "ALTER TABLE RENAME c TO d" 476 if !g.noConflictForTables(conflict) { 477 log.L().Error("conflict happened with other conflict tables", zap.Any("source table", sourceTable), zap.String("prevTable", prevTableStmt), zap.String("postTable", postTableStmt)) 478 return false, optimism.ConflictDetected 479 } 480 481 if g.noConflictForTables(final) { 482 log.L().Info("all conflict resolved for the DDL", zap.Any("source table", sourceTable), zap.String("prevTable", prevTableStmt), zap.String("postTable", postTableStmt)) 483 g.resolveTables() 484 return true, optimism.ConflictNone 485 } 486 log.L().Info("conflict hasn't been resolved", zap.Any("source table", sourceTable), zap.Stringer("prevTable", prevTable), zap.Stringer("postTable", postTable)) 487 return false, optimism.ConflictSkipWaitRedirect 488 } 489 490 // joinTables join tables by tableType. 491 func (g *shardGroup) joinTables(tp tableType) (schemacmp.Table, error) { 492 var ( 493 joined schemacmp.Table 494 err error 495 firstTable = true 496 ) 497 498 for sourceTable := range g.normalTables { 499 tableStmt, ok := g.getTableBySourceTable(sourceTable, tp) 500 if !ok || tableStmt == "" { 501 continue 502 } 503 table := genCmpTable(tableStmt) 504 505 if firstTable { 506 joined = table 507 firstTable = false 508 continue 509 } 510 joined, err = joined.Join(table) 511 if err != nil { 512 return joined, err 513 } 514 } 515 return joined, nil 516 } 517 518 // noConflictForTables checks if there is no conflict for tables by tableType(conflictTable/finalTable). 519 // if there is conflict for conflictTables, we should report error to user. 520 // if there is no conflict for finalTables, we should report conflict resolved to worker. 521 func (g *shardGroup) noConflictForTables(tp tableType) bool { 522 if _, err := g.joinTables(tp); err != nil { 523 return false 524 } 525 if !g.allTableSmaller(tp) { 526 return false 527 } 528 if !g.allTableLarger(tp) { 529 return false 530 } 531 return true 532 } 533 534 func (g *shardGroup) noConflictWithOneNormalTable(sourceTable metadata.SourceTable, prevTable, postTable schemacmp.Table) bool { 535 for st, ti := range g.normalTables { 536 if st == sourceTable { 537 continue 538 } 539 if ti == "" { 540 continue 541 } 542 t := genCmpTable(ti) 543 544 // judge joined no error 545 joined, err := postTable.Join(t) 546 if err != nil { 547 continue 548 } 549 550 // judge this normal table is smaller(same as allTableSmaller) 551 if _, err = joined.Compare(prevTable); err == nil { 552 continue 553 } 554 555 // judge this normal table is larger(same as allTableLarger) 556 if joined, err = prevTable.Join(t); err != nil { 557 if _, err = t.Compare(postTable); err == nil { 558 return true 559 } 560 } 561 if cmp, err := joined.Compare(postTable); err != nil || cmp < 0 { 562 continue 563 } 564 565 return true 566 } 567 return false 568 } 569 570 // see dm/pkg/shardddl/optimism/lock.go:allTableSmaller for more detail 571 func (g *shardGroup) allTableSmaller(tp tableType) bool { 572 var ( 573 joined schemacmp.Table 574 err error 575 ) 576 joined, err = g.joinTables(tp) 577 578 if err != nil { 579 return false 580 } 581 582 for sourceTable := range g.conflictTables { 583 t := genCmpTable(g.normalTables[sourceTable]) 584 if _, err = joined.Compare(t); err == nil { 585 return false 586 } 587 } 588 return true 589 } 590 591 // see dm/pkg/shardddl/optimism/lock.go:allTableLarger for more detail 592 func (g *shardGroup) allTableLarger(tp tableType) bool { 593 for sourceTable, conflictTableStmt := range g.conflictTables { 594 conflictTable := genCmpTable(conflictTableStmt) 595 // for every conflict table's prev_table 596 normalTable := genCmpTable(g.normalTables[sourceTable]) 597 598 for s := range g.normalTables { 599 // for every judge table 600 judgeTableStmt, ok := g.getTableBySourceTable(s, tp) 601 if !ok || judgeTableStmt == "" { 602 continue 603 } 604 judgeTable := genCmpTable(judgeTableStmt) 605 606 joined, err := normalTable.Join(judgeTable) 607 if err != nil { 608 // modify column 609 if _, err := judgeTable.Join(conflictTable); err != nil { 610 return false 611 } 612 } else if cmp, err := joined.Compare(conflictTable); err != nil || cmp < 0 { 613 return false 614 } 615 } 616 } 617 return true 618 } 619 620 func (g *shardGroup) resolveTables() { 621 for sourceTable, conflictStmt := range g.conflictTables { 622 g.normalTables[sourceTable] = conflictStmt 623 } 624 g.conflictTables = make(map[metadata.SourceTable]string) 625 // TODO: redirect for conflict worker. 626 } 627 628 func (g *shardGroup) getTableForOneDDL(item *metadata.DDLItem, idx int) (string, string) { 629 return item.Tables[idx], item.Tables[idx+1] 630 } 631 632 func (g *shardGroup) getTableBySourceTable(st metadata.SourceTable, tp tableType) (string, bool) { 633 var ( 634 stmt string 635 ok bool 636 ) 637 switch tp { 638 case normal: 639 stmt, ok = g.normalTables[st] 640 case conflict: 641 stmt, ok = g.conflictTables[st] 642 case final: 643 stmt, ok = g.conflictTables[st] 644 if !ok { 645 stmt, ok = g.normalTables[st] 646 } 647 } 648 return stmt, ok 649 } 650 651 func (g *shardGroup) checkAddDroppedColumn(ctx context.Context, sourceTable metadata.SourceTable, ddl string, prevTableStmt, postTableStmt string, newDroppedColumns []string) (string, error) { 652 currTable := g.normalTables[sourceTable] 653 defer func() { 654 g.normalTables[sourceTable] = currTable 655 }() 656 657 g.normalTables[sourceTable] = prevTableStmt 658 oldJoined, err := g.joinTables(normal) 659 if err != nil { 660 // nolint:nilerr 661 return "", nil 662 } 663 664 postTable := genCmpTable(postTableStmt) 665 g.normalTables[sourceTable] = postTableStmt 666 newJoined, err := g.joinTables(normal) 667 if err != nil { 668 // nolint:nilerr 669 return "", nil 670 } 671 672 cmp, err := oldJoined.Compare(newJoined) 673 if err != nil { 674 // nolint:nilerr 675 return "", nil 676 } 677 678 if cmp <= 0 { 679 if col, err2 := optimism.AddDifferentFieldLenColumns("", ddl, oldJoined, newJoined); err2 != nil { 680 // check for add column with a larger field len 681 return "", err2 682 } else if _, err2 = optimism.AddDifferentFieldLenColumns("", ddl, postTable, newJoined); err2 != nil { 683 // check for add column with a smaller field len 684 return "", err2 685 } else if len(col) > 0 && (g.droppedColumnsStore.HasDroppedColumn(ctx, col, sourceTable) || slices.Contains(newDroppedColumns, col)) { 686 return "", errors.Errorf("add column %s that wasn't fully dropped in downstream. ddl: %s", col, ddl) 687 } 688 } 689 690 if cmp >= 0 { 691 if col, err2 := optimism.GetColumnName("", ddl, ast.AlterTableDropColumn); err2 != nil { 692 return "", err2 693 } else if len(col) > 0 { 694 return col, nil 695 } 696 } 697 return "", nil 698 } 699 700 func (g *shardGroup) gcDroppedColumns(ctx context.Context) error { 701 state, err := g.droppedColumnsStore.Get(ctx) 702 if err != nil { 703 if errors.Cause(err) == metadata.ErrStateNotFound { 704 return nil 705 } 706 return err 707 } 708 709 droppedColumns := state.(*metadata.DroppedColumns) 710 cacheStmts := make(map[metadata.SourceTable]string) 711 OutLoop: 712 for col := range droppedColumns.Cols { 713 // firstly, check the tables recorded in the ddl coordinator 714 for _, tbStmt := range g.normalTables { 715 if tbStmt == "" { 716 continue 717 } 718 if cols := getColumnNames(tbStmt); slices.Contains(cols, col) { 719 continue OutLoop 720 } 721 } 722 723 // secondly, check the tables from checkpoint 724 for sourceTable := range g.normalTables { 725 tbStmt, ok := cacheStmts[sourceTable] 726 if !ok { 727 tbStmt, err = g.tableAgent.FetchTableStmt(ctx, g.id, g.cfg, sourceTable) 728 if err != nil { 729 if strings.HasPrefix(err.Error(), "table info not found") { 730 continue 731 } 732 return err 733 } 734 cacheStmts[sourceTable] = tbStmt 735 } 736 737 if cols := getColumnNames(tbStmt); slices.Contains(cols, col) { 738 continue OutLoop 739 } 740 } 741 if err := g.droppedColumnsStore.DelDroppedColumn(ctx, col); err != nil { 742 return err 743 } 744 } 745 return nil 746 } 747 748 // isResolved means all tables in the group are resolved. 749 // 1. no conflict ddls waiting 750 // 2. all dropped column has done 751 // 3. all shard tables stmts are same. 752 func (g *shardGroup) isResolved(ctx context.Context) bool { 753 if len(g.conflictTables) != 0 { 754 return false 755 } 756 if _, err := g.droppedColumnsStore.Get(ctx); errors.Cause(err) != metadata.ErrStateNotFound { 757 return false 758 } 759 760 var ( 761 prevTable schemacmp.Table 762 first = true 763 ) 764 for _, tbStmt := range g.normalTables { 765 if tbStmt == "" { 766 continue 767 } 768 if first { 769 prevTable = genCmpTable(tbStmt) 770 first = false 771 continue 772 } 773 currTable := genCmpTable(tbStmt) 774 if cmp, err := prevTable.Compare(currTable); err != nil || cmp != 0 { 775 return false 776 } 777 prevTable = currTable 778 } 779 return true 780 } 781 782 func (g *shardGroup) clear(ctx context.Context) error { 783 return g.droppedColumnsStore.Delete(ctx) 784 } 785 786 func (g *shardGroup) showTables() map[metadata.SourceTable]ShardTable { 787 tables := make(map[metadata.SourceTable]ShardTable, 0) 788 for sourceTable, stmt := range g.normalTables { 789 tables[sourceTable] = ShardTable{ 790 Current: stmt, 791 Next: g.conflictTables[sourceTable], 792 } 793 } 794 // show dropped columns if needed. 795 return tables 796 } 797 798 func genCmpTable(createStmt string) schemacmp.Table { 799 p := parser.New() 800 stmtNode, _ := p.ParseOneStmt(createStmt, "", "") 801 ti, _ := ddl.BuildTableInfoFromAST(stmtNode.(*ast.CreateTableStmt)) 802 ti.State = model.StatePublic 803 return schemacmp.Encode(ti) 804 } 805 806 // getColumnNames and return columns' names for create table stmt. 807 func getColumnNames(createStmt string) []string { 808 p := parser.New() 809 stmtNode, _ := p.ParseOneStmt(createStmt, "", "") 810 s := stmtNode.(*ast.CreateTableStmt) 811 812 cols := make([]string, 0, len(s.Cols)) 813 for _, col := range s.Cols { 814 cols = append(cols, col.Name.Name.O) 815 } 816 return cols 817 }