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  }