github.com/pingcap/tiflow@v0.0.0-20240520035814-5bf52d54e205/engine/jobmaster/dm/ddl_coordinator_test.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  	"encoding/json"
    19  	"fmt"
    20  	"testing"
    21  
    22  	"github.com/pingcap/log"
    23  	dmconfig "github.com/pingcap/tiflow/dm/config"
    24  	"github.com/pingcap/tiflow/dm/pkg/shardddl/optimism"
    25  	"github.com/pingcap/tiflow/engine/jobmaster/dm/config"
    26  	"github.com/pingcap/tiflow/engine/jobmaster/dm/metadata"
    27  	"github.com/pingcap/tiflow/engine/pkg/meta/mock"
    28  	"github.com/pingcap/tiflow/pkg/errors"
    29  	"github.com/stretchr/testify/require"
    30  )
    31  
    32  func TestDDLCoordinator(t *testing.T) {
    33  	var (
    34  		checkpointAgent = &MockCheckpointAgent{}
    35  		metaClient      = mock.NewMetaMock()
    36  		jobStore        = metadata.NewJobStore(metaClient, log.L())
    37  		ddlCoordinator  = NewDDLCoordinator("", metaClient, checkpointAgent, jobStore, log.L())
    38  
    39  		tb1         = metadata.SourceTable{Source: "source", Schema: "schema", Table: "tb1"}
    40  		tb2         = metadata.SourceTable{Source: "source", Schema: "schema", Table: "tb2"}
    41  		tb3         = metadata.SourceTable{Source: "source", Schema: "schema", Table: "tb3"}
    42  		targetTable = metadata.TargetTable{Schema: "schema", Table: "tb"}
    43  		tables      = map[metadata.TargetTable][]metadata.SourceTable{
    44  			targetTable: {tb1},
    45  		}
    46  	)
    47  
    48  	require.EqualError(t, ddlCoordinator.Reset(context.Background()), "state not found")
    49  	ddls, conflictStage, err := ddlCoordinator.Coordinate(context.Background(), nil)
    50  	require.EqualError(t, err, "state not found")
    51  	require.Len(t, ddls, 0)
    52  	require.Equal(t, optimism.ConflictError, conflictStage)
    53  
    54  	jobCfg := &config.JobCfg{Upstreams: []*config.UpstreamCfg{{MySQLInstance: dmconfig.MySQLInstance{SourceID: "source"}}}}
    55  	require.NoError(t, jobStore.Put(context.Background(), metadata.NewJob(jobCfg)))
    56  	require.NoError(t, ddlCoordinator.Reset(context.Background()))
    57  	ddls, conflictStage, err = ddlCoordinator.Coordinate(context.Background(), nil)
    58  	require.EqualError(t, err, "coordinate error with non-shard-mode")
    59  	require.Len(t, ddls, 0)
    60  	require.Equal(t, optimism.ConflictError, conflictStage)
    61  
    62  	jobCfg.ShardMode = dmconfig.ShardOptimistic
    63  	require.NoError(t, jobStore.UpdateConfig(context.Background(), jobCfg))
    64  	checkpointAgent.On("FetchAllDoTables").Return(nil, context.DeadlineExceeded).Once()
    65  	require.Error(t, ddlCoordinator.Reset(context.Background()))
    66  
    67  	checkpointAgent.On("FetchAllDoTables").Return(tables, nil).Once()
    68  	require.NoError(t, ddlCoordinator.Reset(context.Background()))
    69  	require.Contains(t, ddlCoordinator.tables, targetTable)
    70  	require.Len(t, ddlCoordinator.tables[targetTable], 1)
    71  
    72  	item := &metadata.DDLItem{
    73  		SourceTable: tb2,
    74  		DDLs:        []string{genCreateStmt("col1 int", "col2 int")},
    75  		Tables:      []string{genCreateStmt("col1 int", "col2 int")},
    76  		TargetTable: targetTable,
    77  		Type:        metadata.CreateTable,
    78  	}
    79  	checkpointAgent.On("FetchTableStmt").Return("", context.DeadlineExceeded).Once()
    80  	ddls, conflictStage, err = ddlCoordinator.Coordinate(context.Background(), item)
    81  	require.EqualError(t, err, "fetch table stmt from checkpoint failed, sourceTable: {source schema tb1}, err: context deadline exceeded")
    82  	require.Len(t, ddls, 0)
    83  	require.Equal(t, optimism.ConflictError, conflictStage)
    84  	require.Len(t, ddlCoordinator.tables[targetTable], 1)
    85  
    86  	checkpointAgent.On("FetchTableStmt").Return(genCreateStmt("col1 int", "col2 int"), nil).Once()
    87  	ddls, conflictStage, err = ddlCoordinator.Coordinate(context.Background(), item)
    88  	require.NoError(t, err)
    89  	require.Equal(t, item.DDLs, ddls)
    90  	require.Equal(t, optimism.ConflictNone, conflictStage)
    91  	require.Len(t, ddlCoordinator.tables, 1)
    92  
    93  	item = &metadata.DDLItem{
    94  		SourceTable: tb1,
    95  		DDLs:        []string{"alter table tb1 modify column col2 varhar(255)"},
    96  		Tables:      []string{genCreateStmt("col1 int", "col2 int"), genCreateStmt("col1 int", "col2 varchar(255)")},
    97  		TargetTable: targetTable,
    98  		Type:        metadata.OtherDDL,
    99  	}
   100  	checkpointAgent.On("FetchTableStmt").Return(genCreateStmt("col1 int", "col2 int"), nil).Twice()
   101  	ddls, conflictStage, err = ddlCoordinator.Coordinate(context.Background(), item)
   102  	require.NoError(t, err)
   103  	require.Len(t, ddls, 0)
   104  	require.Equal(t, optimism.ConflictSkipWaitRedirect, conflictStage)
   105  
   106  	item = &metadata.DDLItem{
   107  		SourceTable: tb2,
   108  		DDLs:        []string{"alter table tb1 modify column col1 varhar(255)"},
   109  		Tables:      []string{genCreateStmt("col1 int", "col2 int"), genCreateStmt("col1 varchar(255)", "col2 int")},
   110  		TargetTable: targetTable,
   111  		Type:        metadata.OtherDDL,
   112  	}
   113  	ddls, conflictStage, err = ddlCoordinator.Coordinate(context.Background(), item)
   114  	require.EqualError(t, err, fmt.Sprintf("conflict detected for table %v", item.SourceTable))
   115  	require.Len(t, ddls, 0)
   116  	require.Equal(t, optimism.ConflictDetected, conflictStage)
   117  
   118  	item = &metadata.DDLItem{
   119  		SourceTable: tb1,
   120  		DDLs:        []string{"drop table tb1"},
   121  		TargetTable: targetTable,
   122  		Type:        metadata.DropTable,
   123  	}
   124  	ddls, conflictStage, err = ddlCoordinator.Coordinate(context.Background(), item)
   125  	require.NoError(t, err)
   126  	require.Equal(t, item.DDLs, ddls)
   127  	require.Equal(t, optimism.ConflictNone, conflictStage)
   128  	require.Contains(t, ddlCoordinator.tables, targetTable)
   129  	require.Len(t, ddlCoordinator.tables[targetTable], 1)
   130  
   131  	item = &metadata.DDLItem{
   132  		SourceTable: tb2,
   133  		DDLs:        []string{"alter table tb1 modify column col1 varhar(255)"},
   134  		Tables:      []string{genCreateStmt("col1 int", "col2 int"), genCreateStmt("col1 varchar(255)", "col2 int")},
   135  		TargetTable: targetTable,
   136  		Type:        metadata.OtherDDL,
   137  	}
   138  	ddls, conflictStage, err = ddlCoordinator.Coordinate(context.Background(), item)
   139  	require.NoError(t, err)
   140  	require.Equal(t, item.DDLs, ddls)
   141  	require.Equal(t, optimism.ConflictNone, conflictStage)
   142  
   143  	item = &metadata.DDLItem{
   144  		SourceTable: tb2,
   145  		DDLs:        []string{"drop table tb2"},
   146  		TargetTable: targetTable,
   147  		Type:        metadata.DropTable,
   148  	}
   149  	checkpointAgent.On("FetchTableStmt").Return(genCreateStmt("col1 int", "col2 int"), nil).Once()
   150  	ddls, conflictStage, err = ddlCoordinator.Coordinate(context.Background(), item)
   151  	require.NoError(t, err)
   152  	require.Equal(t, item.DDLs, ddls)
   153  	require.Equal(t, optimism.ConflictNone, conflictStage)
   154  	require.Len(t, ddlCoordinator.tables, 0)
   155  
   156  	item = &metadata.DDLItem{
   157  		SourceTable: tb3,
   158  		DDLs:        []string{genCreateStmt("col1 varchar(255)", "col2 int")},
   159  		Tables:      []string{genCreateStmt("col1 varchar(255)", "col2 int")},
   160  		TargetTable: targetTable,
   161  		Type:        metadata.CreateTable,
   162  	}
   163  	ddls, conflictStage, err = ddlCoordinator.Coordinate(context.Background(), item)
   164  	require.NoError(t, err)
   165  	require.Equal(t, item.DDLs, ddls)
   166  	require.Equal(t, optimism.ConflictNone, conflictStage)
   167  	require.Contains(t, ddlCoordinator.tables, targetTable)
   168  	require.Len(t, ddlCoordinator.tables[targetTable], 1)
   169  
   170  	require.NoError(t, ddlCoordinator.ClearMetadata(context.Background()))
   171  
   172  	checkpointAgent.AssertExpectations(t)
   173  }
   174  
   175  func TestShowDDLLocks(t *testing.T) {
   176  	ddlCoordinator := &DDLCoordinator{
   177  		shardGroups: map[metadata.TargetTable]*shardGroup{
   178  			{Schema: "db", Table: "tb"}: {
   179  				normalTables: map[metadata.SourceTable]string{
   180  					{Source: "source1", Schema: "db", Table: "tb"}: genCreateStmt("col1 int", "col2 int"),
   181  					{Source: "source2", Schema: "db", Table: "tb"}: genCreateStmt("col1 int", "col2 int"),
   182  				},
   183  				conflictTables: map[metadata.SourceTable]string{
   184  					{Source: "source2", Schema: "db", Table: "tb"}: genCreateStmt("col1 int", "col2 varchar(255)"),
   185  				},
   186  			},
   187  			{Schema: "database", Table: "table"}: {
   188  				normalTables: map[metadata.SourceTable]string{
   189  					{Source: "source1", Schema: "database1", Table: "table1"}: genCreateStmt("col1 int", "col2 int", "col3 int"),
   190  					{Source: "source1", Schema: "database2", Table: "table2"}: genCreateStmt("col1 int", "col2 int"),
   191  				},
   192  				conflictTables: map[metadata.SourceTable]string{
   193  					{Source: "source1", Schema: "database1", Table: "table1"}: genCreateStmt("col1 int", "col2 varchar(255)", "col3 int"),
   194  				},
   195  			},
   196  		},
   197  	}
   198  	resp := ddlCoordinator.ShowDDLLocks(context.Background())
   199  	bs, err := json.MarshalIndent(resp, "", "\t")
   200  	require.NoError(t, err)
   201  	expectedResp := `{
   202  	"Locks": {
   203  		"{\"Schema\":\"database\",\"Table\":\"table\"}": {
   204  			"ShardTables": {
   205  				"{\"Source\":\"source1\",\"Schema\":\"database1\",\"Table\":\"table1\"}": {
   206  					"Current": "CREATE TABLE tbl(col1 int, col2 int, col3 int)",
   207  					"Next": "CREATE TABLE tbl(col1 int, col2 varchar(255), col3 int)"
   208  				},
   209  				"{\"Source\":\"source1\",\"Schema\":\"database2\",\"Table\":\"table2\"}": {
   210  					"Current": "CREATE TABLE tbl(col1 int, col2 int)",
   211  					"Next": ""
   212  				}
   213  			}
   214  		},
   215  		"{\"Schema\":\"db\",\"Table\":\"tb\"}": {
   216  			"ShardTables": {
   217  				"{\"Source\":\"source1\",\"Schema\":\"db\",\"Table\":\"tb\"}": {
   218  					"Current": "CREATE TABLE tbl(col1 int, col2 int)",
   219  					"Next": ""
   220  				},
   221  				"{\"Source\":\"source2\",\"Schema\":\"db\",\"Table\":\"tb\"}": {
   222  					"Current": "CREATE TABLE tbl(col1 int, col2 int)",
   223  					"Next": "CREATE TABLE tbl(col1 int, col2 varchar(255))"
   224  				}
   225  			}
   226  		}
   227  	}
   228  }`
   229  	require.Equal(t, expectedResp, string(bs))
   230  }
   231  
   232  func TestHandle(t *testing.T) {
   233  	var (
   234  		tb1        = metadata.SourceTable{Source: "source", Schema: "schema", Table: "tb1"}
   235  		tb2        = metadata.SourceTable{Source: "source", Schema: "schema", Table: "tb2"}
   236  		tb         = metadata.TargetTable{Schema: "schema", Table: "tb"}
   237  		tableAgent = &MockCheckpointAgent{}
   238  		g          = &shardGroup{
   239  			normalTables: map[metadata.SourceTable]string{
   240  				tb1: "",
   241  			},
   242  			conflictTables:      make(map[metadata.SourceTable]string),
   243  			droppedColumnsStore: metadata.NewDroppedColumnsStore(mock.NewMetaMock(), tb),
   244  			tableAgent:          tableAgent,
   245  		}
   246  	)
   247  
   248  	item := &metadata.DDLItem{
   249  		SourceTable: tb2,
   250  		DDLs:        []string{genCreateStmt("col1 int", "col2 int")},
   251  		Tables:      []string{genCreateStmt("col1 int", "col2 int")},
   252  	}
   253  
   254  	ddls, conflictStage, err := g.handle(context.Background(), item)
   255  	require.EqualError(t, err, fmt.Sprintf("unknown ddl type %v", item.Type))
   256  	require.Len(t, ddls, 0)
   257  	require.Equal(t, optimism.ConflictError, conflictStage)
   258  	require.True(t, g.isResolved(context.Background()))
   259  
   260  	item.Type = metadata.CreateTable
   261  	ddls, conflictStage, err = g.handle(context.Background(), item)
   262  	require.NoError(t, err)
   263  	require.Equal(t, item.DDLs, ddls)
   264  	require.Equal(t, optimism.ConflictNone, conflictStage)
   265  	require.True(t, g.isResolved(context.Background()))
   266  
   267  	item = &metadata.DDLItem{
   268  		SourceTable: tb1,
   269  		DDLs:        []string{"alter table tb1 modify column col2 varhar(255)"},
   270  		Tables:      []string{genCreateStmt("col1 int", "col2 int"), genCreateStmt("col1 int", "col2 varchar(255)")},
   271  		Type:        metadata.OtherDDL,
   272  	}
   273  	ddls, conflictStage, err = g.handle(context.Background(), item)
   274  	require.NoError(t, err)
   275  	require.Len(t, ddls, 0)
   276  	require.Equal(t, optimism.ConflictSkipWaitRedirect, conflictStage)
   277  	require.False(t, g.isResolved(context.Background()))
   278  
   279  	item = &metadata.DDLItem{
   280  		SourceTable: tb1,
   281  		DDLs:        []string{"drop table tb1"},
   282  		Type:        metadata.DropTable,
   283  	}
   284  	ddls, conflictStage, err = g.handle(context.Background(), item)
   285  	require.NoError(t, err)
   286  	require.Equal(t, item.DDLs, ddls)
   287  	require.Equal(t, optimism.ConflictNone, conflictStage)
   288  	require.True(t, g.isResolved(context.Background()))
   289  
   290  	item = &metadata.DDLItem{
   291  		SourceTable: tb2,
   292  		DDLs:        []string{"alter table tb1 drop column col2"},
   293  		Tables:      []string{genCreateStmt("col1 int", "col2 int", "col3 int"), genCreateStmt("col1 int", "col3 int")},
   294  		Type:        metadata.OtherDDL,
   295  	}
   296  	ddls, conflictStage, err = g.handle(context.Background(), item)
   297  	require.NoError(t, err)
   298  	require.Equal(t, item.DDLs, ddls)
   299  	require.Equal(t, optimism.ConflictNone, conflictStage)
   300  	require.False(t, g.isResolved(context.Background()))
   301  
   302  	item = &metadata.DDLItem{
   303  		SourceTable: tb2,
   304  		DDLs:        []string{"drop table tb2"},
   305  		Type:        metadata.DropTable,
   306  	}
   307  	tableAgent.On("FetchTableStmt").Return(genCreateStmt("col1 int", "col3 int"), nil).Once()
   308  	ddls, conflictStage, err = g.handle(context.Background(), item)
   309  	require.NoError(t, err)
   310  	require.Equal(t, item.DDLs, ddls)
   311  	require.Equal(t, optimism.ConflictNone, conflictStage)
   312  	require.True(t, g.isResolved(context.Background()))
   313  }
   314  
   315  func TestHandleCreateTable(t *testing.T) {
   316  	var (
   317  		tb1 = metadata.SourceTable{Source: "source", Schema: "schema", Table: "tb1"}
   318  		g   = &shardGroup{
   319  			normalTables: make(map[metadata.SourceTable]string),
   320  		}
   321  	)
   322  
   323  	item := &metadata.DDLItem{
   324  		SourceTable: tb1,
   325  		Tables:      []string{genCreateStmt("col1 int", "col2 int")},
   326  	}
   327  	// tb1 create table
   328  	g.handleCreateTable(item)
   329  	require.Len(t, g.normalTables, 1)
   330  	require.Equal(t, item.Tables[0], g.normalTables[tb1])
   331  
   332  	// tb1 idempotent
   333  	g.handleCreateTable(item)
   334  	require.Len(t, g.normalTables, 1)
   335  	require.Equal(t, item.Tables[0], g.normalTables[tb1])
   336  
   337  	// new create table will override old one
   338  	item.Tables[0] = genCreateStmt("col1 int", "col2 int", "col3 int")
   339  	g.handleCreateTable(item)
   340  	require.Len(t, g.normalTables, 1)
   341  	require.Equal(t, item.Tables[0], g.normalTables[tb1])
   342  }
   343  
   344  func TestHandleDropTable(t *testing.T) {
   345  	var (
   346  		tb1 = metadata.SourceTable{Source: "source", Schema: "schema", Table: "tb1"}
   347  		tb2 = metadata.SourceTable{Source: "source", Schema: "schema", Table: "tb2"}
   348  		g   = &shardGroup{
   349  			normalTables: map[metadata.SourceTable]string{
   350  				tb1: genCreateStmt("col1 int", "col2 int"),
   351  				tb2: "",
   352  			},
   353  			droppedColumnsStore: metadata.NewDroppedColumnsStore(mock.NewMetaMock(), metadata.TargetTable{Schema: "schema", Table: "tb"}),
   354  		}
   355  	)
   356  
   357  	item := &metadata.DDLItem{
   358  		SourceTable: metadata.SourceTable{},
   359  		Tables:      []string{genCreateStmt("col1 int", "col2 int")},
   360  	}
   361  	g.handleDropTable(context.Background(), item)
   362  	require.Len(t, g.normalTables, 2)
   363  
   364  	item.SourceTable = tb1
   365  	g.handleDropTable(context.Background(), item)
   366  	require.Len(t, g.normalTables, 1)
   367  
   368  	item.SourceTable = tb2
   369  	g.handleDropTable(context.Background(), item)
   370  	require.Len(t, g.normalTables, 0)
   371  
   372  	item.SourceTable = tb1
   373  	g.handleDropTable(context.Background(), item)
   374  	require.Len(t, g.normalTables, 0)
   375  }
   376  
   377  func TestHandleDDLs(t *testing.T) {
   378  	var (
   379  		tb1 = metadata.SourceTable{Source: "source", Schema: "schema", Table: "tb1"}
   380  		tb2 = metadata.SourceTable{Source: "source", Schema: "schema", Table: "tb2"}
   381  		tb3 = metadata.SourceTable{Source: "source", Schema: "schema", Table: "tb3"}
   382  		tb  = metadata.TargetTable{Schema: "schema", Table: "tb"}
   383  		g   = &shardGroup{
   384  			normalTables: map[metadata.SourceTable]string{
   385  				tb1: "",
   386  				tb3: genCreateStmt("col1 int", "col2 int"),
   387  			},
   388  			conflictTables:      make(map[metadata.SourceTable]string),
   389  			droppedColumnsStore: metadata.NewDroppedColumnsStore(mock.NewMetaMock(), tb),
   390  		}
   391  	)
   392  
   393  	item := &metadata.DDLItem{
   394  		SourceTable: tb2,
   395  		DDLs:        []string{"alter table tb add column col2 int"},
   396  		Tables:      []string{genCreateStmt("col1 int"), genCreateStmt("col1 int", "col2 int")},
   397  	}
   398  	ddls, conflictStage, err := g.handleDDLs(context.Background(), item)
   399  	require.NoError(t, err)
   400  	require.Len(t, ddls, 1)
   401  	require.Equal(t, item.DDLs, ddls)
   402  	require.Equal(t, optimism.ConflictNone, conflictStage)
   403  
   404  	item = &metadata.DDLItem{
   405  		SourceTable: tb2,
   406  		DDLs:        []string{"alter table tb modify column col1 varchar(255)"},
   407  		Tables:      []string{genCreateStmt("col1 int", "col2 int"), genCreateStmt("col1 varchar(255)", "col2 int")},
   408  	}
   409  	ddls, conflictStage, err = g.handleDDLs(context.Background(), item)
   410  	require.NoError(t, err)
   411  	require.Len(t, ddls, 0)
   412  	require.Equal(t, optimism.ConflictSkipWaitRedirect, conflictStage)
   413  
   414  	item = &metadata.DDLItem{
   415  		SourceTable: tb3,
   416  		DDLs:        []string{"alter table tb modify column col2 varchar(255)"},
   417  		Tables:      []string{genCreateStmt("col1 int", "col2 int"), genCreateStmt("col1 int", "col2 varchar(255)")},
   418  	}
   419  	ddls, conflictStage, err = g.handleDDLs(context.Background(), item)
   420  	require.EqualError(t, err, fmt.Sprintf("conflict detected for table %v", item.SourceTable))
   421  	require.Len(t, ddls, 0)
   422  	require.Equal(t, optimism.ConflictDetected, conflictStage)
   423  }
   424  
   425  func TestHandleDDL(t *testing.T) {
   426  	var (
   427  		tb1 = metadata.SourceTable{Source: "source", Schema: "schema", Table: "tb1"}
   428  		tb2 = metadata.SourceTable{Source: "source", Schema: "schema", Table: "tb2"}
   429  		tb3 = metadata.SourceTable{Source: "source", Schema: "schema", Table: "tb3"}
   430  		g   = &shardGroup{
   431  			normalTables: map[metadata.SourceTable]string{
   432  				tb1: "",
   433  				tb2: genCreateStmt("col1 int"),
   434  				tb3: genCreateStmt("col1 int", "col2 int"),
   435  			},
   436  			conflictTables: make(map[metadata.SourceTable]string),
   437  		}
   438  	)
   439  
   440  	// tb2 add col2
   441  	schemaChanged, conflictStage := g.handleDDL(tb2, genCreateStmt("col1 int"), genCreateStmt("col1 int", "col2 int"))
   442  	require.True(t, schemaChanged)
   443  	require.Equal(t, conflictStage, optimism.ConflictNone)
   444  	// idempotent
   445  	schemaChanged, conflictStage = g.handleDDL(tb2, genCreateStmt("col1 int"), genCreateStmt("col1 int", "col2 int"))
   446  	require.True(t, schemaChanged)
   447  	require.Equal(t, conflictStage, optimism.ConflictNone)
   448  
   449  	// tb2 modify col1
   450  	schemaChanged, conflictStage = g.handleDDL(tb2, genCreateStmt("col1 int", "col2 int"), genCreateStmt("col1 varchar(255), col2 int"))
   451  	require.False(t, schemaChanged)
   452  	require.Equal(t, conflictStage, optimism.ConflictSkipWaitRedirect)
   453  	// idempotent
   454  	schemaChanged, conflictStage = g.handleDDL(tb2, genCreateStmt("col1 int", "col2 int"), genCreateStmt("col1 varchar(255), col2 int"))
   455  	require.False(t, schemaChanged)
   456  	require.Equal(t, conflictStage, optimism.ConflictSkipWaitRedirect)
   457  
   458  	// tb3 modify col1
   459  	schemaChanged, conflictStage = g.handleDDL(tb3, genCreateStmt("col1 int", "col2 int"), genCreateStmt("col1 varchar(255), col2 int"))
   460  	require.True(t, schemaChanged)
   461  	require.Equal(t, conflictStage, optimism.ConflictNone)
   462  	// tb2 idempotent
   463  	schemaChanged, conflictStage = g.handleDDL(tb2, genCreateStmt("col1 int", "col2 int"), genCreateStmt("col1 varchar(255), col2 int"))
   464  	require.True(t, schemaChanged)
   465  	require.Equal(t, conflictStage, optimism.ConflictNone)
   466  
   467  	// tb3 add column with wrong name
   468  	schemaChanged, conflictStage = g.handleDDL(tb3, genCreateStmt("col1 varchar(255), col2 int"), genCreateStmt("col1 varchar(255), col2 int, col3 int"))
   469  	require.True(t, schemaChanged)
   470  	require.Equal(t, conflictStage, optimism.ConflictNone)
   471  	// tb3 rename column
   472  	schemaChanged, conflictStage = g.handleDDL(tb3, genCreateStmt("col1 varchar(255), col2 int, col3 int"), genCreateStmt("col1 varchar(255), col2 int, col4 int"))
   473  	require.False(t, schemaChanged)
   474  	require.Equal(t, conflictStage, optimism.ConflictSkipWaitRedirect)
   475  	// tb2 add column with true name directly
   476  	schemaChanged, conflictStage = g.handleDDL(tb2, genCreateStmt("col1 varchar(255), col2 int"), genCreateStmt("col1 varchar(255), col2 int, col4 int"))
   477  	require.True(t, schemaChanged)
   478  	require.Equal(t, conflictStage, optimism.ConflictNone)
   479  	// tb3 idempotent
   480  	// NOTE: rename column will be executed but retrun column already exists
   481  	// data may insistent
   482  	schemaChanged, conflictStage = g.handleDDL(tb3, genCreateStmt("col1 varchar(255), col2 int, col3 int"), genCreateStmt("col1 varchar(255), col2 int, col4 int"))
   483  	require.True(t, schemaChanged)
   484  	require.Equal(t, conflictStage, optimism.ConflictNone)
   485  
   486  	// tb3 add column not null no default
   487  	schemaChanged, conflictStage = g.handleDDL(tb3, genCreateStmt("col1 varchar(255), col2 int, col4 int"), genCreateStmt("col1 varchar(255), col2 int, col4 int, col5 int not null"))
   488  	require.False(t, schemaChanged)
   489  	require.Equal(t, conflictStage, optimism.ConflictSkipWaitRedirect)
   490  	// tb2 rename column
   491  	schemaChanged, conflictStage = g.handleDDL(tb2, genCreateStmt("col1 varchar(255), col2 int, col4 int"), genCreateStmt("col1 varchar(255), col3 int, col4 int"))
   492  	require.False(t, schemaChanged)
   493  	require.Equal(t, conflictStage, optimism.ConflictDetected)
   494  }
   495  
   496  func TestJoinTables(t *testing.T) {
   497  	var (
   498  		tb1 = metadata.SourceTable{Source: "source", Schema: "schema", Table: "tb1"}
   499  		tb2 = metadata.SourceTable{Source: "source", Schema: "schema", Table: "tb2"}
   500  		tb3 = metadata.SourceTable{Source: "source", Schema: "schema", Table: "tb3"}
   501  		g   = &shardGroup{
   502  			normalTables: map[metadata.SourceTable]string{
   503  				tb1: "",
   504  				tb2: genCreateStmt("col1 int"),
   505  				tb3: genCreateStmt("col1 int", "col2 int"),
   506  			},
   507  			conflictTables: make(map[metadata.SourceTable]string),
   508  		}
   509  	)
   510  
   511  	// no conflict
   512  	joined, err := g.joinTables(normal)
   513  	require.NoError(t, err)
   514  	require.Equal(t, "CREATE TABLE `tbl`(`col1` INT(11), `col2` INT(11)) CHARSET UTF8MB4 COLLATE UTF8MB4_BIN", joined.String())
   515  	joined, err = g.joinTables(final)
   516  	require.NoError(t, err)
   517  	require.Equal(t, "CREATE TABLE `tbl`(`col1` INT(11), `col2` INT(11)) CHARSET UTF8MB4 COLLATE UTF8MB4_BIN", joined.String())
   518  
   519  	// has conflict
   520  	g.conflictTables[tb3] = genCreateStmt("col1 varchar(255)", "col2 int")
   521  	joined, err = g.joinTables(normal)
   522  	require.NoError(t, err)
   523  	require.Equal(t, "CREATE TABLE `tbl`(`col1` INT(11), `col2` INT(11)) CHARSET UTF8MB4 COLLATE UTF8MB4_BIN", joined.String())
   524  	joined, err = g.joinTables(conflict)
   525  	require.NoError(t, err)
   526  	require.Equal(t, "CREATE TABLE `tbl`(`col1` VARCHAR(255) CHARACTER SET UTF8MB4 COLLATE utf8mb4_bin, `col2` INT(11)) CHARSET UTF8MB4 COLLATE UTF8MB4_BIN", joined.String())
   527  	_, err = g.joinTables(final)
   528  	require.Error(t, err)
   529  }
   530  
   531  func TestNoConflictForTables(t *testing.T) {
   532  	var (
   533  		tb1 = metadata.SourceTable{Source: "source", Schema: "schema", Table: "tb1"}
   534  		tb2 = metadata.SourceTable{Source: "source", Schema: "schema", Table: "tb2"}
   535  		tb3 = metadata.SourceTable{Source: "source", Schema: "schema", Table: "tb3"}
   536  		g   = &shardGroup{
   537  			normalTables: map[metadata.SourceTable]string{
   538  				tb1: "",
   539  				tb2: genCreateStmt("col1 int"),
   540  				tb3: genCreateStmt("col1 int", "col2 int"),
   541  			},
   542  			conflictTables: make(map[metadata.SourceTable]string),
   543  		}
   544  	)
   545  	require.True(t, g.noConflictForTables(conflict))
   546  	require.True(t, g.noConflictForTables(final))
   547  
   548  	// tb2 modify col1
   549  	g.conflictTables[tb2] = genCreateStmt("col1 varchar(255)")
   550  	require.True(t, g.noConflictForTables(conflict))
   551  	require.False(t, g.noConflictForTables(final))
   552  
   553  	// tb3 modify col2
   554  	g.conflictTables[tb2] = genCreateStmt("col1 int", "col2 varchar(255)")
   555  	require.False(t, g.noConflictForTables(conflict))
   556  	require.False(t, g.noConflictForTables(final))
   557  
   558  	// tb2 rename col1 to col3, tb3 rename col1 to col4
   559  	g.conflictTables[tb2] = genCreateStmt("col3 int")
   560  	g.conflictTables[tb3] = genCreateStmt("col4 int", "col2 int")
   561  	require.False(t, g.noConflictForTables(conflict))
   562  	require.False(t, g.noConflictForTables(final))
   563  }
   564  
   565  func TestNoConflictWithOneNormalTable(t *testing.T) {
   566  	var (
   567  		tb1 = metadata.SourceTable{Source: "source", Schema: "schema", Table: "tb1"}
   568  		tb2 = metadata.SourceTable{Source: "source", Schema: "schema", Table: "tb2"}
   569  		tb3 = metadata.SourceTable{Source: "source", Schema: "schema", Table: "tb3"}
   570  		g   = &shardGroup{
   571  			normalTables: map[metadata.SourceTable]string{
   572  				tb1: "",
   573  				tb2: genCreateStmt("col1 int"),
   574  				tb3: genCreateStmt("col1 int", "col2 int"),
   575  			},
   576  			conflictTables: make(map[metadata.SourceTable]string),
   577  		}
   578  	)
   579  	require.True(t, g.noConflictForTables(conflict))
   580  	require.True(t, g.noConflictForTables(final))
   581  
   582  	// tb2 rename col1
   583  	prevTable := genCmpTable(genCreateStmt("col1 int"))
   584  	postTable := genCmpTable(genCreateStmt("col3 int"))
   585  	require.False(t, g.noConflictWithOneNormalTable(tb2, prevTable, postTable))
   586  
   587  	// tb2 modify col1
   588  	prevTable = genCmpTable(genCreateStmt("col1 int"))
   589  	postTable = genCmpTable(genCreateStmt("col1 varchar(255)"))
   590  	require.False(t, g.noConflictWithOneNormalTable(tb2, prevTable, postTable))
   591  
   592  	// tb2 add not null no default col
   593  	prevTable = genCmpTable(genCreateStmt("col1 int"))
   594  	postTable = genCmpTable(genCreateStmt("col1 int", "col3 int not null"))
   595  	require.False(t, g.noConflictWithOneNormalTable(tb2, prevTable, postTable))
   596  
   597  	// tb3 rename col2
   598  	prevTable = genCmpTable(genCreateStmt("col1 int", "col2 int"))
   599  	postTable = genCmpTable(genCreateStmt("col1 int", "col4 int"))
   600  	require.False(t, g.noConflictWithOneNormalTable(tb3, prevTable, postTable))
   601  
   602  	// tb2 modify col1 forcely
   603  	g.normalTables[tb2] = genCreateStmt("col1 varchar(255)")
   604  	// tb3 modify col1
   605  	prevTable = genCmpTable(genCreateStmt("col1 int", "col2 int"))
   606  	postTable = genCmpTable(genCreateStmt("col1 varchar(255)", "col2 int"))
   607  	require.True(t, g.noConflictWithOneNormalTable(tb3, prevTable, postTable))
   608  
   609  	// tb2 rename col1 forcely
   610  	g.normalTables[tb2] = genCreateStmt("col3 int")
   611  	// tb3 rename col1
   612  	prevTable = genCmpTable(genCreateStmt("col1 int", "col2 int"))
   613  	postTable = genCmpTable(genCreateStmt("col3 int", "col2 int"))
   614  	require.True(t, g.noConflictWithOneNormalTable(tb3, prevTable, postTable))
   615  
   616  	// tb2 add not null no default col forcely
   617  	g.normalTables[tb2] = genCreateStmt("col1 int", "col3 int not null")
   618  	// tb3 add not null no default col
   619  	prevTable = genCmpTable(genCreateStmt("col1 int", "col2 int"))
   620  	postTable = genCmpTable(genCreateStmt("col1 int", "col2 int", "col3 int not null"))
   621  	require.True(t, g.noConflictWithOneNormalTable(tb3, prevTable, postTable))
   622  }
   623  
   624  func TestAllTableSmaller(t *testing.T) {
   625  	var (
   626  		tb1 = metadata.SourceTable{Source: "source", Schema: "schema", Table: "tb1"}
   627  		tb2 = metadata.SourceTable{Source: "source", Schema: "schema", Table: "tb2"}
   628  		tb3 = metadata.SourceTable{Source: "source", Schema: "schema", Table: "tb3"}
   629  		g   = &shardGroup{
   630  			normalTables: map[metadata.SourceTable]string{
   631  				tb1: "",
   632  				tb2: genCreateStmt("col1 int"),
   633  				tb3: genCreateStmt("col1 int", "col2 int"),
   634  			},
   635  			conflictTables: make(map[metadata.SourceTable]string),
   636  		}
   637  	)
   638  	require.True(t, g.allTableSmaller(conflict))
   639  	require.True(t, g.allTableSmaller(final))
   640  
   641  	// tb2 modify col1
   642  	g.conflictTables[tb2] = genCreateStmt("col1 varchar(255)")
   643  	require.True(t, g.allTableSmaller(conflict))
   644  	require.False(t, g.allTableSmaller(final))
   645  
   646  	// tb3 modify col1
   647  	g.conflictTables[tb3] = genCreateStmt("col1 varchar(255)", "col2 int")
   648  	require.True(t, g.allTableSmaller(conflict))
   649  	require.True(t, g.allTableSmaller(final))
   650  
   651  	g.resolveTables()
   652  
   653  	// tb3 rename col2
   654  	g.conflictTables[tb3] = genCreateStmt("col3 varchar(255)", "col2 int")
   655  	require.True(t, g.allTableSmaller(conflict))
   656  	require.False(t, g.allTableSmaller(final))
   657  	// tb2 rename col2
   658  	g.conflictTables[tb2] = genCreateStmt("col3 varchar(255)")
   659  	require.True(t, g.allTableSmaller(conflict))
   660  	require.True(t, g.allTableSmaller(final))
   661  
   662  	g.resolveTables()
   663  
   664  	// tb3 add not null no default
   665  	g.conflictTables[tb3] = genCreateStmt("col3 varchar(255)", "col2 int", "col4 int not null")
   666  	require.True(t, g.allTableSmaller(conflict))
   667  	require.False(t, g.allTableSmaller(final))
   668  	// tb2 add not null no default
   669  	g.conflictTables[tb2] = genCreateStmt("col3 varchar(255)", "col4 int not null")
   670  	require.True(t, g.allTableSmaller(conflict))
   671  	require.True(t, g.allTableSmaller(final))
   672  
   673  	g.resolveTables()
   674  
   675  	// tb2 modify column
   676  	g.conflictTables[tb2] = genCreateStmt("col3 int", "col4 int not null")
   677  	require.True(t, g.allTableSmaller(conflict))
   678  	require.False(t, g.allTableSmaller(final))
   679  	// tb3 rename column
   680  	g.conflictTables[tb3] = genCreateStmt("col3 varchar(255)", "col2 int", "col5 int not null")
   681  	require.False(t, g.allTableSmaller(conflict))
   682  	require.False(t, g.allTableSmaller(final))
   683  }
   684  
   685  func TestAllTableLarger(t *testing.T) {
   686  	var (
   687  		tb1 = metadata.SourceTable{Source: "source", Schema: "schema", Table: "tb1"}
   688  		tb2 = metadata.SourceTable{Source: "source", Schema: "schema", Table: "tb2"}
   689  		tb3 = metadata.SourceTable{Source: "source", Schema: "schema", Table: "tb3"}
   690  		g   = &shardGroup{
   691  			normalTables: map[metadata.SourceTable]string{
   692  				tb1: "",
   693  				tb2: genCreateStmt("col1 int"),
   694  				tb3: genCreateStmt("col1 int", "col2 int"),
   695  			},
   696  			conflictTables: make(map[metadata.SourceTable]string),
   697  		}
   698  	)
   699  	require.True(t, g.allTableLarger(conflict))
   700  	require.True(t, g.allTableLarger(final))
   701  
   702  	// tb2 modify col1
   703  	g.conflictTables[tb2] = genCreateStmt("col1 varchar(255)")
   704  	require.True(t, g.allTableLarger(conflict))
   705  	require.False(t, g.allTableLarger(final))
   706  
   707  	// tb3 modify col1
   708  	g.conflictTables[tb3] = genCreateStmt("col1 varchar(255)", "col2 int")
   709  	require.True(t, g.allTableLarger(conflict))
   710  	require.True(t, g.allTableLarger(final))
   711  
   712  	g.resolveTables()
   713  
   714  	// tb3 rename col2
   715  	g.conflictTables[tb3] = genCreateStmt("col3 varchar(255)", "col2 int")
   716  	require.True(t, g.allTableLarger(conflict))
   717  	require.False(t, g.allTableLarger(final))
   718  	// tb2 rename col2
   719  	g.conflictTables[tb2] = genCreateStmt("col3 varchar(255)")
   720  	require.True(t, g.allTableLarger(conflict))
   721  	require.True(t, g.allTableLarger(final))
   722  
   723  	g.resolveTables()
   724  
   725  	// tb3 add not null no default
   726  	g.conflictTables[tb3] = genCreateStmt("col3 varchar(255)", "col2 int", "col4 int not null")
   727  	require.True(t, g.allTableLarger(conflict))
   728  	require.False(t, g.allTableLarger(final))
   729  	// tb2 add not null no default
   730  	g.conflictTables[tb2] = genCreateStmt("col3 varchar(255)", "col4 int not null")
   731  	require.True(t, g.allTableLarger(conflict))
   732  	require.True(t, g.allTableLarger(final))
   733  
   734  	g.resolveTables()
   735  
   736  	// tb2 modify column
   737  	g.conflictTables[tb2] = genCreateStmt("col3 int", "col4 int not null")
   738  	require.True(t, g.allTableLarger(conflict))
   739  	require.False(t, g.allTableLarger(final))
   740  	// tb3 rename column
   741  	g.conflictTables[tb3] = genCreateStmt("col3 varchar(255)", "col2 int", "col5 int not null")
   742  	require.False(t, g.allTableLarger(conflict))
   743  	require.False(t, g.allTableLarger(final))
   744  	// tb3 modify another column
   745  	g.conflictTables[tb3] = genCreateStmt("col3 varchar(255)", "col2 int", "col4 varchar(255) not null")
   746  	require.False(t, g.allTableLarger(conflict))
   747  	require.False(t, g.allTableLarger(final))
   748  }
   749  
   750  func TestDroppedColumn(t *testing.T) {
   751  	var (
   752  		tb1 = metadata.SourceTable{Source: "source", Schema: "schema", Table: "tb1"}
   753  		tb2 = metadata.SourceTable{Source: "source", Schema: "schema", Table: "tb2"}
   754  		tb3 = metadata.SourceTable{Source: "source", Schema: "schema", Table: "tb3"}
   755  		g   = &shardGroup{
   756  			normalTables: map[metadata.SourceTable]string{
   757  				tb1: genCreateStmt("col1 int", "col2 int"),
   758  				tb2: genCreateStmt("col1 int", "col2 int"),
   759  				tb3: genCreateStmt("col1 int", "col2 int", "col3 int"),
   760  			},
   761  			conflictTables:      make(map[metadata.SourceTable]string),
   762  			droppedColumnsStore: metadata.NewDroppedColumnsStore(mock.NewMetaMock(), metadata.TargetTable{Schema: "schema", Table: "table"}),
   763  		}
   764  	)
   765  
   766  	// drop column
   767  	col, err := g.checkAddDroppedColumn(context.Background(), tb1, "ALTER TABLE `tb1` DROP COLUMN `col1`", genCreateStmt("col1 int", "col2 int"), genCreateStmt("col2 int"), []string{"col1"})
   768  	require.NoError(t, err)
   769  	require.Equal(t, "col1", col)
   770  
   771  	// add column with a larger field len
   772  	col, err = g.checkAddDroppedColumn(context.Background(), tb1, "ALTER TABLE `tb3` ADD COLUMN `col3` BIGINT", genCreateStmt("col1 int", "col2 int"), genCreateStmt("col1 int", "col2 int", "col3 bigint"), nil)
   773  	require.Contains(t, err.Error(), "add columns with different field lengths")
   774  	require.Equal(t, "", col)
   775  
   776  	// add column with a smaller field len
   777  	col, err = g.checkAddDroppedColumn(context.Background(), tb1, "ALTER TABLE `tb3` ADD COLUMN `col3` SMALLINT", genCreateStmt("col1 int", "col2 int"), genCreateStmt("col1 int", "col2 int", "col3 smallint"), nil)
   778  	require.Contains(t, err.Error(), "add columns with different field lengths")
   779  	require.Equal(t, "", col)
   780  
   781  	// add dropped column
   782  	g.droppedColumnsStore.AddDroppedColumns(context.Background(), []string{"col3"}, tb2)
   783  	col, err = g.checkAddDroppedColumn(context.Background(), tb2, "ALTER TABLE `tb2` ADD COLUMN `col3` INT", genCreateStmt("col1 int", "col2 int"), genCreateStmt("col1 int", "col2 int", "col3 int"), nil)
   784  	require.Contains(t, err.Error(), "wasn't fully dropped in downstream")
   785  	require.Equal(t, "", col)
   786  
   787  	col, err = g.checkAddDroppedColumn(context.Background(), tb1, "ALTER TABLE `tb1` ADD INDEX uk(col1)", genCreateStmt("col1 int", "col2 int"), genCreateStmt("col1 int", "col2 int"), nil)
   788  	require.NoError(t, err)
   789  	require.Equal(t, "", col)
   790  }
   791  
   792  func TestGCDroppedColumns(t *testing.T) {
   793  	var (
   794  		tb1        = metadata.SourceTable{Source: "source", Schema: "schema", Table: "tb1"}
   795  		tb2        = metadata.SourceTable{Source: "source", Schema: "schema", Table: "tb2"}
   796  		tableAgent = &MockCheckpointAgent{}
   797  		g          = &shardGroup{
   798  			normalTables: map[metadata.SourceTable]string{
   799  				tb1: genCreateStmt("col1 int", "col2 int"),
   800  				tb2: genCreateStmt("col1 int", "col2 int", "col3 int"),
   801  			},
   802  			conflictTables:      make(map[metadata.SourceTable]string),
   803  			droppedColumnsStore: metadata.NewDroppedColumnsStore(mock.NewMetaMock(), metadata.TargetTable{Schema: "schema", Table: "table"}),
   804  			tableAgent:          tableAgent,
   805  		}
   806  	)
   807  
   808  	require.NoError(t, g.gcDroppedColumns(context.Background()))
   809  
   810  	// add dropped column
   811  	g.droppedColumnsStore.AddDroppedColumns(context.Background(), []string{"col3"}, tb1)
   812  	g.droppedColumnsStore.AddDroppedColumns(context.Background(), []string{"col2"}, tb1)
   813  
   814  	require.NoError(t, g.gcDroppedColumns(context.Background()))
   815  
   816  	g.normalTables[tb2] = genCreateStmt("col1 int", "col2 int")
   817  	tableAgent.On("FetchTableStmt").Return("", errors.New("table info not found")).Once()
   818  	tableAgent.On("FetchTableStmt").Return(genCreateStmt("col1 int", "col2 int", "col3 int"), nil).Once()
   819  	require.NoError(t, g.gcDroppedColumns(context.Background()))
   820  
   821  	tableAgent.On("FetchTableStmt").Return(genCreateStmt("col1 int", "col2 int"), nil).Twice()
   822  	require.NoError(t, g.gcDroppedColumns(context.Background()))
   823  	state, err := g.droppedColumnsStore.Get(context.Background())
   824  	require.NoError(t, err)
   825  	droppedColumns := state.(*metadata.DroppedColumns)
   826  	require.Len(t, droppedColumns.Cols, 1)
   827  	require.Contains(t, droppedColumns.Cols, "col2")
   828  
   829  	g.normalTables[tb1] = genCreateStmt("col1 int", "col3 int")
   830  	g.normalTables[tb2] = genCreateStmt("col1 int", "col3 int")
   831  	tableAgent.On("FetchTableStmt").Return(genCreateStmt("col1 int", "col3 int"), nil).Twice()
   832  	require.NoError(t, g.gcDroppedColumns(context.Background()))
   833  	state, err = g.droppedColumnsStore.Get(context.Background())
   834  	require.Equal(t, errors.Cause(err), metadata.ErrStateNotFound)
   835  	require.Nil(t, state)
   836  
   837  	tableAgent.AssertExpectations(t)
   838  }
   839  
   840  func genCreateStmt(cols ...string) string {
   841  	str := "CREATE TABLE tbl("
   842  	for idx, col := range cols {
   843  		if idx == 0 {
   844  			str += col
   845  		} else {
   846  			str += ", " + col
   847  		}
   848  	}
   849  	str += ")"
   850  	return str
   851  }