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 }