github.com/pingcap/tiflow@v0.0.0-20240520035814-5bf52d54e205/cdc/owner/ddl_manager_test.go (about) 1 // Copyright 2023 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 owner 15 16 import ( 17 "context" 18 "encoding/json" 19 "fmt" 20 "testing" 21 22 timodel "github.com/pingcap/tidb/pkg/parser/model" 23 "github.com/pingcap/tiflow/cdc/entry" 24 "github.com/pingcap/tiflow/cdc/model" 25 "github.com/pingcap/tiflow/cdc/redo" 26 "github.com/pingcap/tiflow/cdc/scheduler/schedulepb" 27 config2 "github.com/pingcap/tiflow/pkg/config" 28 "github.com/pingcap/tiflow/pkg/filter" 29 "github.com/pingcap/tiflow/pkg/util" 30 "github.com/stretchr/testify/require" 31 ) 32 33 func createDDLManagerForTest(t *testing.T) *ddlManager { 34 startTs, checkpointTs := model.Ts(0), model.Ts(1) 35 changefeedID := model.DefaultChangeFeedID("ddl-manager-test") 36 ddlSink := &mockDDLSink{} 37 ddlPuller := &mockDDLPuller{} 38 cfg := config2.GetDefaultReplicaConfig() 39 f, err := filter.NewFilter(cfg, "") 40 require.Nil(t, err) 41 schema, err := entry.NewSchemaStorage(nil, startTs, cfg.ForceReplicate, changefeedID, util.RoleTester, f) 42 require.Equal(t, nil, err) 43 res := newDDLManager( 44 changefeedID, 45 startTs, 46 checkpointTs, 47 ddlSink, 48 f, 49 ddlPuller, 50 schema, 51 redo.NewDisabledDDLManager(), 52 redo.NewDisabledMetaManager(), 53 false) 54 return res 55 } 56 57 func newFakeDDLEvent( 58 tableID int64, 59 tableName string, 60 actionType timodel.ActionType, 61 commitTs uint64, 62 ) *model.DDLEvent { 63 info := &model.TableInfo{ 64 TableName: model.TableName{Table: tableName, TableID: tableID}, 65 } 66 info.TableInfo = &timodel.TableInfo{ 67 ID: tableID, 68 Name: timodel.NewCIStr(tableName), 69 } 70 return &model.DDLEvent{ 71 TableInfo: info, 72 Type: actionType, 73 CommitTs: commitTs, 74 } 75 } 76 77 func TestGetNextDDL(t *testing.T) { 78 dm := createDDLManagerForTest(t) 79 dm.executingDDL = newFakeDDLEvent(1, 80 "test_1", timodel.ActionDropColumn, 1) 81 require.Equal(t, dm.executingDDL, dm.getNextDDL()) 82 83 dm.executingDDL = nil 84 ddl1 := newFakeDDLEvent(1, 85 "test_1", timodel.ActionDropColumn, 1) 86 ddl2 := newFakeDDLEvent(2, 87 "test_2", timodel.ActionDropColumn, 2) 88 dm.pendingDDLs[ddl1.TableInfo.TableName] = append(dm. 89 pendingDDLs[ddl1.TableInfo.TableName], ddl1) 90 dm.pendingDDLs[ddl2.TableInfo.TableName] = append(dm. 91 pendingDDLs[ddl2.TableInfo.TableName], ddl2) 92 require.Equal(t, ddl1, dm.getNextDDL()) 93 } 94 95 func TestBarriers(t *testing.T) { 96 dm := createDDLManagerForTest(t) 97 98 tableID1 := int64(1) 99 tableName1 := model.TableName{Table: "test_1", TableID: tableID1} 100 // this ddl commitTs will be minTableBarrierTs 101 dm.justSentDDL = newFakeDDLEvent(tableID1, 102 "test_1", timodel.ActionDropColumn, 1) 103 dm.pendingDDLs[tableName1] = append(dm.pendingDDLs[tableName1], 104 newFakeDDLEvent(tableID1, tableName1.Table, timodel.ActionAddColumn, 2)) 105 106 tableID2 := int64(2) 107 tableName2 := model.TableName{Table: "test_2", TableID: tableID2} 108 dm.pendingDDLs[tableName2] = append(dm.pendingDDLs[tableName2], 109 // this ddl commitTs will become globalBarrierTs 110 newFakeDDLEvent(tableID2, tableName2.Table, timodel.ActionCreateTable, 4)) 111 112 expectedMinTableBarrier := uint64(1) 113 expectedBarrier := &schedulepb.Barrier{ 114 GlobalBarrierTs: 4, 115 TableBarriers: []*schedulepb.TableBarrier{ 116 { 117 TableID: tableID1, 118 BarrierTs: 1, 119 }, 120 }, 121 } 122 // advance the ddlResolvedTs 123 dm.ddlResolvedTs = 6 124 ddlBarrier := dm.barrier() 125 minTableBarrierTs, barrier := ddlBarrier.MinTableBarrierTs, ddlBarrier.Barrier 126 require.Equal(t, expectedMinTableBarrier, minTableBarrierTs) 127 require.Equal(t, expectedBarrier, barrier) 128 129 // test tableBarrier limit 130 dm.pendingDDLs = make(map[model.TableName][]*model.DDLEvent) 131 dm.ddlResolvedTs = 1024 132 for i := 0; i < 512; i++ { 133 tableID := int64(i) 134 tableName := model.TableName{Table: fmt.Sprintf("test_%d", i), TableID: tableID} 135 dm.pendingDDLs[tableName] = append(dm.pendingDDLs[tableName], 136 newFakeDDLEvent(tableID, tableName.Table, timodel.ActionAddColumn, uint64(i))) 137 } 138 ddlBarrier = dm.barrier() 139 minTableBarrierTs, barrier = ddlBarrier.MinTableBarrierTs, ddlBarrier.Barrier 140 require.Equal(t, uint64(0), minTableBarrierTs) 141 require.Equal(t, uint64(256), barrier.GlobalBarrierTs) 142 require.Equal(t, 256, len(barrier.TableBarriers)) 143 } 144 145 func TestGetSnapshotTs(t *testing.T) { 146 dm := createDDLManagerForTest(t) 147 dm.startTs = 0 148 dm.checkpointTs = 1 149 require.Equal(t, dm.startTs, dm.getSnapshotTs()) 150 151 dm.startTs = 1 152 dm.checkpointTs = 10 153 dm.BDRMode = true 154 dm.ddlResolvedTs = 15 155 require.Equal(t, dm.checkpointTs, dm.getSnapshotTs()) 156 157 dm.startTs = 1 158 dm.checkpointTs = 10 159 dm.BDRMode = false 160 require.Equal(t, dm.checkpointTs, dm.getSnapshotTs()) 161 } 162 163 func TestExecRenameTablesDDL(t *testing.T) { 164 helper := entry.NewSchemaTestHelper(t) 165 defer helper.Close() 166 ctx := context.Background() 167 dm := createDDLManagerForTest(t) 168 mockDDLSink := dm.ddlSink.(*mockDDLSink) 169 170 var oldSchemaIDs, newSchemaIDs, oldTableIDs []int64 171 var newTableNames, oldSchemaNames []timodel.CIStr 172 173 execCreateStmt := func(tp, actualDDL, expectedDDL string) { 174 mockDDLSink.ddlDone = false 175 job := helper.DDL2Job(actualDDL) 176 dm.schema.AdvanceResolvedTs(job.BinlogInfo.FinishedTS - 1) 177 events, err := dm.schema.BuildDDLEvents(ctx, job) 178 require.Nil(t, err) 179 err = dm.schema.HandleDDLJob(job) 180 require.Nil(t, err) 181 182 for _, event := range events { 183 done, err := dm.ddlSink.emitDDLEvent(ctx, event) 184 if tp == "database" { 185 oldSchemaIDs = append(oldSchemaIDs, job.SchemaID) 186 } else { 187 oldTableIDs = append(oldTableIDs, job.TableID) 188 } 189 require.Nil(t, err) 190 require.Equal(t, false, done) 191 require.Equal(t, expectedDDL, mockDDLSink.ddlExecuting.Query) 192 193 mockDDLSink.ddlDone = true 194 done, err = dm.ddlSink.emitDDLEvent(ctx, event) 195 require.Nil(t, err) 196 require.Equal(t, true, done) 197 require.Equal(t, expectedDDL, mockDDLSink.ddlExecuting.Query) 198 } 199 } 200 201 execCreateStmt("database", "create database test1", 202 "create database test1") 203 execCreateStmt("table", "create table test1.tb1(id int primary key)", 204 "create table test1.tb1(id int primary key)") 205 execCreateStmt("database", "create database test2", 206 "create database test2") 207 execCreateStmt("table", "create table test2.tb2(id int primary key)", 208 "create table test2.tb2(id int primary key)") 209 210 require.Len(t, oldSchemaIDs, 2) 211 require.Len(t, oldTableIDs, 2) 212 newSchemaIDs = []int64{oldSchemaIDs[1], oldSchemaIDs[0]} 213 oldSchemaNames = []timodel.CIStr{ 214 timodel.NewCIStr("test1"), 215 timodel.NewCIStr("test2"), 216 } 217 newTableNames = []timodel.CIStr{ 218 timodel.NewCIStr("tb20"), 219 timodel.NewCIStr("tb10"), 220 } 221 require.Len(t, newSchemaIDs, 2) 222 require.Len(t, oldSchemaNames, 2) 223 require.Len(t, newTableNames, 2) 224 args := []interface{}{ 225 oldSchemaIDs, newSchemaIDs, newTableNames, 226 oldTableIDs, oldSchemaNames, 227 } 228 rawArgs, err := json.Marshal(args) 229 require.Nil(t, err) 230 job := helper.DDL2Job( 231 "rename table test1.tb1 to test2.tb10, test2.tb2 to test1.tb20") 232 // the RawArgs field in job fetched from tidb snapshot meta is incorrent, 233 // so we manually construct `job.RawArgs` to do the workaround. 234 job.RawArgs = rawArgs 235 236 mockDDLSink.recordDDLHistory = true 237 mockDDLSink.ddlDone = false 238 dm.schema.AdvanceResolvedTs(job.BinlogInfo.FinishedTS - 1) 239 events, err := dm.schema.BuildDDLEvents(ctx, job) 240 require.Nil(t, err) 241 for _, event := range events { 242 done, err := dm.ddlSink.emitDDLEvent(ctx, event) 243 require.Nil(t, err) 244 require.Equal(t, false, done) 245 246 } 247 require.Len(t, mockDDLSink.ddlHistory, 2) 248 require.Equal(t, "RENAME TABLE `test1`.`tb1` TO `test2`.`tb10`", 249 mockDDLSink.ddlHistory[0]) 250 require.Equal(t, "RENAME TABLE `test2`.`tb2` TO `test1`.`tb20`", 251 mockDDLSink.ddlHistory[1]) 252 253 // mock all rename table statements have been done 254 mockDDLSink.resetDDLDone = false 255 mockDDLSink.ddlDone = true 256 for _, event := range events { 257 done, err := dm.ddlSink.emitDDLEvent(ctx, event) 258 require.Nil(t, err) 259 require.Equal(t, true, done) 260 } 261 } 262 263 func TestExecDropTablesDDL(t *testing.T) { 264 helper := entry.NewSchemaTestHelper(t) 265 defer helper.Close() 266 267 ctx := context.Background() 268 dm := createDDLManagerForTest(t) 269 mockDDLSink := dm.ddlSink.(*mockDDLSink) 270 271 execCreateStmt := func(actualDDL, expectedDDL string) { 272 job := helper.DDL2Job(actualDDL) 273 dm.schema.AdvanceResolvedTs(job.BinlogInfo.FinishedTS - 1) 274 events, err := dm.schema.BuildDDLEvents(ctx, job) 275 require.Nil(t, err) 276 err = dm.schema.HandleDDLJob(job) 277 require.Nil(t, err) 278 mockDDLSink.ddlDone = false 279 280 for _, event := range events { 281 done, err := dm.ddlSink.emitDDLEvent(ctx, event) 282 require.Nil(t, err) 283 require.Equal(t, false, done) 284 require.Equal(t, expectedDDL, mockDDLSink.ddlExecuting.Query) 285 mockDDLSink.ddlDone = true 286 done, err = dm.ddlSink.emitDDLEvent(ctx, event) 287 require.Nil(t, err) 288 require.Equal(t, true, done) 289 } 290 } 291 292 execCreateStmt("create database test1", 293 "create database test1") 294 execCreateStmt("create table test1.tb1(id int primary key)", 295 "create table test1.tb1(id int primary key)") 296 execCreateStmt("create table test1.tb2(id int primary key)", 297 "create table test1.tb2(id int primary key)") 298 299 // drop tables is different from rename tables, it will generate 300 // multiple DDL jobs instead of one. 301 jobs := helper.DDL2Jobs("drop table test1.tb1, test1.tb2", 2) 302 require.Len(t, jobs, 2) 303 304 execDropStmt := func(job *timodel.Job, expectedDDL string) { 305 dm.schema.AdvanceResolvedTs(job.BinlogInfo.FinishedTS - 1) 306 events, err := dm.schema.BuildDDLEvents(ctx, job) 307 require.Nil(t, err) 308 err = dm.schema.HandleDDLJob(job) 309 require.Nil(t, err) 310 mockDDLSink.ddlDone = false 311 312 for _, event := range events { 313 done, err := dm.ddlSink.emitDDLEvent(ctx, event) 314 require.Nil(t, err) 315 require.Equal(t, false, done) 316 require.Equal(t, expectedDDL, mockDDLSink.ddlExecuting.Query) 317 mockDDLSink.ddlDone = true 318 done, err = dm.ddlSink.emitDDLEvent(ctx, event) 319 require.Nil(t, err) 320 require.Equal(t, true, done) 321 } 322 } 323 324 execDropStmt(jobs[0], "DROP TABLE `test1`.`tb2`") 325 execDropStmt(jobs[1], "DROP TABLE `test1`.`tb1`") 326 } 327 328 func TestExecDropViewsDDL(t *testing.T) { 329 helper := entry.NewSchemaTestHelper(t) 330 defer helper.Close() 331 332 ctx := context.Background() 333 dm := createDDLManagerForTest(t) 334 mockDDLSink := dm.ddlSink.(*mockDDLSink) 335 336 execCreateStmt := func(actualDDL, expectedDDL string) { 337 job := helper.DDL2Job(actualDDL) 338 dm.schema.AdvanceResolvedTs(job.BinlogInfo.FinishedTS - 1) 339 events, err := dm.schema.BuildDDLEvents(ctx, job) 340 require.Nil(t, err) 341 err = dm.schema.HandleDDLJob(job) 342 require.Nil(t, err) 343 mockDDLSink.ddlDone = false 344 for _, event := range events { 345 done, err := dm.ddlSink.emitDDLEvent(ctx, event) 346 require.Nil(t, err) 347 require.Equal(t, false, done) 348 require.Equal(t, expectedDDL, mockDDLSink.ddlExecuting.Query) 349 mockDDLSink.ddlDone = true 350 done, err = dm.ddlSink.emitDDLEvent(ctx, event) 351 require.Nil(t, err) 352 require.Equal(t, true, done) 353 } 354 } 355 356 execCreateStmt("create database test1", 357 "create database test1") 358 execCreateStmt("create table test1.tb1(id int primary key)", 359 "create table test1.tb1(id int primary key)") 360 execCreateStmt("create view test1.view1 as "+ 361 "select * from test1.tb1 where id > 100", 362 "create view test1.view1 as "+ 363 "select * from test1.tb1 where id > 100") 364 execCreateStmt("create view test1.view2 as "+ 365 "select * from test1.tb1 where id > 200", 366 "create view test1.view2 as "+ 367 "select * from test1.tb1 where id > 200") 368 369 // drop views is similar to drop tables, it will also generate 370 // multiple DDL jobs. 371 jobs := helper.DDL2Jobs("drop view test1.view1, test1.view2", 2) 372 require.Len(t, jobs, 2) 373 374 execDropStmt := func(job *timodel.Job, expectedDDL string) { 375 dm.schema.AdvanceResolvedTs(job.BinlogInfo.FinishedTS - 1) 376 events, err := dm.schema.BuildDDLEvents(ctx, job) 377 require.Nil(t, err) 378 err = dm.schema.HandleDDLJob(job) 379 require.Nil(t, err) 380 mockDDLSink.ddlDone = false 381 for _, event := range events { 382 done, err := dm.ddlSink.emitDDLEvent(ctx, event) 383 require.Nil(t, err) 384 require.Equal(t, false, done) 385 require.Equal(t, expectedDDL, mockDDLSink.ddlExecuting.Query) 386 mockDDLSink.ddlDone = true 387 done, err = dm.ddlSink.emitDDLEvent(ctx, event) 388 require.Nil(t, err) 389 require.Equal(t, true, done) 390 } 391 } 392 393 execDropStmt(jobs[0], "DROP VIEW `test1`.`view2`") 394 execDropStmt(jobs[1], "DROP VIEW `test1`.`view1`") 395 } 396 397 func TestIsGlobalDDL(t *testing.T) { 398 cases := []struct { 399 ddl *model.DDLEvent 400 ret bool 401 }{ 402 { 403 ddl: &model.DDLEvent{ 404 Type: timodel.ActionCreateSchema, 405 }, 406 ret: true, 407 }, 408 { 409 ddl: &model.DDLEvent{ 410 Type: timodel.ActionDropSchema, 411 }, 412 ret: true, 413 }, 414 { 415 ddl: &model.DDLEvent{ 416 Type: timodel.ActionCreateTable, 417 }, 418 ret: true, 419 }, 420 { 421 ddl: &model.DDLEvent{ 422 Type: timodel.ActionRenameTables, 423 }, 424 ret: true, 425 }, 426 { 427 ddl: &model.DDLEvent{ 428 Type: timodel.ActionRenameTable, 429 }, 430 ret: true, 431 }, 432 { 433 ddl: &model.DDLEvent{ 434 Type: timodel.ActionExchangeTablePartition, 435 }, 436 ret: true, 437 }, 438 { 439 ddl: &model.DDLEvent{ 440 Type: timodel.ActionModifySchemaCharsetAndCollate, 441 }, 442 ret: true, 443 }, 444 { 445 ddl: &model.DDLEvent{ 446 Type: timodel.ActionTruncateTable, 447 }, 448 ret: false, 449 }, 450 { 451 ddl: &model.DDLEvent{ 452 Type: timodel.ActionDropColumn, 453 }, 454 ret: false, 455 }, 456 } 457 458 for _, c := range cases { 459 require.Equal(t, c.ret, isGlobalDDL(c.ddl)) 460 } 461 }