github.com/pingcap/tiflow@v0.0.0-20240520035814-5bf52d54e205/cdc/entry/schema_storage_test.go (about) 1 // Copyright 2020 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 entry 15 16 import ( 17 "context" 18 "encoding/json" 19 "fmt" 20 "testing" 21 22 "github.com/pingcap/errors" 23 "github.com/pingcap/log" 24 ticonfig "github.com/pingcap/tidb/pkg/config" 25 "github.com/pingcap/tidb/pkg/ddl" 26 "github.com/pingcap/tidb/pkg/domain" 27 tidbkv "github.com/pingcap/tidb/pkg/kv" 28 timeta "github.com/pingcap/tidb/pkg/meta" 29 timodel "github.com/pingcap/tidb/pkg/parser/model" 30 "github.com/pingcap/tidb/pkg/parser/mysql" 31 "github.com/pingcap/tidb/pkg/session" 32 "github.com/pingcap/tidb/pkg/sessionctx" 33 "github.com/pingcap/tidb/pkg/store/mockstore" 34 "github.com/pingcap/tidb/pkg/testkit" 35 "github.com/pingcap/tidb/pkg/types" 36 "github.com/pingcap/tiflow/cdc/entry/schema" 37 "github.com/pingcap/tiflow/cdc/kv" 38 "github.com/pingcap/tiflow/cdc/model" 39 "github.com/pingcap/tiflow/pkg/config" 40 "github.com/pingcap/tiflow/pkg/filter" 41 "github.com/pingcap/tiflow/pkg/util" 42 "github.com/stretchr/testify/require" 43 "github.com/tikv/client-go/v2/oracle" 44 "go.uber.org/zap" 45 ) 46 47 func TestSchema(t *testing.T) { 48 dbName := timodel.NewCIStr("Test") 49 // db and ignoreDB info 50 dbInfo := &timodel.DBInfo{ 51 ID: 1, 52 Name: dbName, 53 State: timodel.StatePublic, 54 } 55 // `createSchema` job1 56 job := &timodel.Job{ 57 ID: 3, 58 State: timodel.JobStateDone, 59 SchemaID: 1, 60 Type: timodel.ActionCreateSchema, 61 BinlogInfo: &timodel.HistoryInfo{SchemaVersion: 1, DBInfo: dbInfo, FinishedTS: 123}, 62 Query: "create database test", 63 } 64 // reconstruct the local schema 65 snap := schema.NewEmptySnapshot(false) 66 err := snap.HandleDDL(job) 67 require.Nil(t, err) 68 _, exist := snap.SchemaByID(job.SchemaID) 69 require.True(t, exist) 70 71 // test drop schema 72 job = &timodel.Job{ 73 ID: 6, 74 State: timodel.JobStateDone, 75 SchemaID: 1, 76 Type: timodel.ActionDropSchema, 77 BinlogInfo: &timodel.HistoryInfo{SchemaVersion: 3, DBInfo: dbInfo, FinishedTS: 124}, 78 Query: "drop database test", 79 } 80 err = snap.HandleDDL(job) 81 require.Nil(t, err) 82 _, exist = snap.SchemaByID(job.SchemaID) 83 require.False(t, exist) 84 85 job = &timodel.Job{ 86 ID: 3, 87 State: timodel.JobStateDone, 88 SchemaID: 1, 89 Type: timodel.ActionCreateSchema, 90 BinlogInfo: &timodel.HistoryInfo{SchemaVersion: 2, DBInfo: dbInfo, FinishedTS: 124}, 91 Query: "create database test", 92 } 93 94 err = snap.HandleDDL(job) 95 require.Nil(t, err) 96 err = snap.HandleDDL(job) 97 require.True(t, errors.IsAlreadyExists(err)) 98 99 // test schema drop schema error 100 job = &timodel.Job{ 101 ID: 9, 102 State: timodel.JobStateDone, 103 SchemaID: 1, 104 Type: timodel.ActionDropSchema, 105 BinlogInfo: &timodel.HistoryInfo{SchemaVersion: 1, DBInfo: dbInfo, FinishedTS: 123}, 106 Query: "drop database test", 107 } 108 err = snap.HandleDDL(job) 109 require.Nil(t, err) 110 err = snap.HandleDDL(job) 111 require.True(t, errors.IsNotFound(err)) 112 } 113 114 func TestTable(t *testing.T) { 115 var jobs []*timodel.Job 116 dbName := timodel.NewCIStr("Test") 117 tbName := timodel.NewCIStr("T") 118 colName := timodel.NewCIStr("A") 119 idxName := timodel.NewCIStr("idx") 120 // column info 121 colInfo := &timodel.ColumnInfo{ 122 ID: 1, 123 Name: colName, 124 Offset: 0, 125 FieldType: *types.NewFieldType(mysql.TypeLonglong), 126 State: timodel.StatePublic, 127 } 128 // index info 129 idxInfo := &timodel.IndexInfo{ 130 Name: idxName, 131 Table: tbName, 132 Columns: []*timodel.IndexColumn{ 133 { 134 Name: colName, 135 Offset: 0, 136 Length: 10, 137 }, 138 }, 139 Unique: false, 140 Primary: false, 141 State: timodel.StatePublic, 142 } 143 // table info 144 tblInfo := &timodel.TableInfo{ 145 ID: 2, 146 Name: tbName, 147 State: timodel.StatePublic, 148 } 149 // db info 150 dbInfo := &timodel.DBInfo{ 151 ID: 3, 152 Name: dbName, 153 State: timodel.StatePublic, 154 } 155 156 // `createSchema` job 157 job := &timodel.Job{ 158 ID: 5, 159 State: timodel.JobStateDone, 160 SchemaID: 3, 161 Type: timodel.ActionCreateSchema, 162 BinlogInfo: &timodel.HistoryInfo{SchemaVersion: 1, DBInfo: dbInfo, FinishedTS: 123}, 163 Query: "create database " + dbName.O, 164 } 165 jobs = append(jobs, job) 166 167 // `createTable` job 168 job = &timodel.Job{ 169 ID: 6, 170 State: timodel.JobStateDone, 171 SchemaID: 3, 172 TableID: 2, 173 Type: timodel.ActionCreateTable, 174 BinlogInfo: &timodel.HistoryInfo{SchemaVersion: 2, TableInfo: tblInfo, FinishedTS: 124}, 175 Query: "create table " + tbName.O, 176 } 177 jobs = append(jobs, job) 178 179 // `addColumn` job 180 tblInfo.Columns = []*timodel.ColumnInfo{colInfo} 181 job = &timodel.Job{ 182 ID: 7, 183 State: timodel.JobStateDone, 184 SchemaID: 3, 185 TableID: 2, 186 Type: timodel.ActionAddColumn, 187 BinlogInfo: &timodel.HistoryInfo{SchemaVersion: 3, TableInfo: tblInfo, FinishedTS: 125}, 188 Query: "alter table " + tbName.O + " add column " + colName.O, 189 } 190 jobs = append(jobs, job) 191 192 // construct a historical `addIndex` job 193 tblInfo = tblInfo.Clone() 194 tblInfo.Indices = []*timodel.IndexInfo{idxInfo} 195 job = &timodel.Job{ 196 ID: 8, 197 State: timodel.JobStateDone, 198 SchemaID: 3, 199 TableID: 2, 200 Type: timodel.ActionAddIndex, 201 BinlogInfo: &timodel.HistoryInfo{SchemaVersion: 4, TableInfo: tblInfo, FinishedTS: 126}, 202 Query: fmt.Sprintf("alter table %s add index %s(%s)", tbName, idxName, colName), 203 } 204 jobs = append(jobs, job) 205 206 // reconstruct the local schema 207 snap := schema.NewEmptySnapshot(false) 208 for _, job := range jobs { 209 err := snap.HandleDDL(job) 210 require.Nil(t, err) 211 } 212 213 // check the historical db that constructed above whether in the schema list of local schema 214 _, ok := snap.SchemaByID(dbInfo.ID) 215 require.True(t, ok) 216 // check the historical table that constructed above whether in the table list of local schema 217 table, ok := snap.PhysicalTableByID(tblInfo.ID) 218 require.True(t, ok) 219 require.Len(t, table.Columns, 1) 220 require.Len(t, table.Indices, 1) 221 222 // test ineligible tables 223 require.True(t, snap.IsIneligibleTableID(2)) 224 225 // check truncate table 226 tblInfo1 := &timodel.TableInfo{ 227 ID: 9, 228 Name: tbName, 229 State: timodel.StatePublic, 230 } 231 job = &timodel.Job{ 232 ID: 9, 233 State: timodel.JobStateDone, 234 SchemaID: 3, 235 TableID: 2, 236 Type: timodel.ActionTruncateTable, 237 BinlogInfo: &timodel.HistoryInfo{SchemaVersion: 5, TableInfo: tblInfo1, FinishedTS: 127}, 238 Query: "truncate table " + tbName.O, 239 } 240 preTableInfo, err := snap.PreTableInfo(job) 241 require.Nil(t, err) 242 require.Equal(t, preTableInfo.TableName, model.TableName{Schema: "Test", Table: "T", TableID: 2}) 243 require.Equal(t, preTableInfo.ID, int64(2)) 244 245 err = snap.HandleDDL(job) 246 require.Nil(t, err) 247 248 _, ok = snap.PhysicalTableByID(tblInfo1.ID) 249 require.True(t, ok) 250 251 _, ok = snap.PhysicalTableByID(2) 252 require.False(t, ok) 253 254 // test ineligible tables 255 require.True(t, snap.IsIneligibleTableID(9)) 256 require.False(t, snap.IsIneligibleTableID(2)) 257 // check drop table 258 job = &timodel.Job{ 259 ID: 9, 260 State: timodel.JobStateDone, 261 SchemaID: 3, 262 TableID: 9, 263 Type: timodel.ActionDropTable, 264 BinlogInfo: &timodel.HistoryInfo{SchemaVersion: 6, FinishedTS: 128}, 265 Query: "drop table " + tbName.O, 266 } 267 preTableInfo, err = snap.PreTableInfo(job) 268 require.Nil(t, err) 269 require.Equal(t, preTableInfo.TableName, model.TableName{Schema: "Test", Table: "T", TableID: 9}) 270 require.Equal(t, preTableInfo.ID, int64(9)) 271 272 err = snap.HandleDDL(job) 273 require.Nil(t, err) 274 275 _, ok = snap.PhysicalTableByID(tblInfo.ID) 276 require.False(t, ok) 277 278 // test ineligible tables 279 require.False(t, snap.IsIneligibleTableID(9)) 280 281 // drop schema 282 job = &timodel.Job{ 283 ID: 10, 284 State: timodel.JobStateDone, 285 SchemaID: 3, 286 Type: timodel.ActionDropSchema, 287 BinlogInfo: &timodel.HistoryInfo{SchemaVersion: 7, FinishedTS: 129}, 288 Query: "drop table " + dbName.O, 289 } 290 err = snap.DoHandleDDL(job) 291 require.Nil(t, err) 292 } 293 294 func TestHandleDDL(t *testing.T) { 295 snap := schema.NewEmptySnapshot(false) 296 dbName := timodel.NewCIStr("Test") 297 colName := timodel.NewCIStr("A") 298 tbName := timodel.NewCIStr("T") 299 newTbName := timodel.NewCIStr("RT") 300 301 // db info 302 dbInfo := &timodel.DBInfo{ 303 ID: 2, 304 Name: dbName, 305 State: timodel.StatePublic, 306 } 307 // table Info 308 tblInfo := &timodel.TableInfo{ 309 ID: 6, 310 Name: tbName, 311 State: timodel.StatePublic, 312 } 313 // column info 314 colInfo := &timodel.ColumnInfo{ 315 ID: 8, 316 Name: colName, 317 Offset: 0, 318 FieldType: *types.NewFieldType(mysql.TypeLonglong), 319 State: timodel.StatePublic, 320 } 321 tblInfo.Columns = []*timodel.ColumnInfo{colInfo} 322 323 testCases := []struct { 324 name string 325 jobID int64 326 schemaID int64 327 tableID int64 328 jobType timodel.ActionType 329 binlogInfo *timodel.HistoryInfo 330 query string 331 resultQuery string 332 schemaName string 333 tableName string 334 }{ 335 {name: "createSchema", jobID: 3, schemaID: 2, tableID: 0, jobType: timodel.ActionCreateSchema, binlogInfo: &timodel.HistoryInfo{SchemaVersion: 1, DBInfo: dbInfo, TableInfo: nil, FinishedTS: 123}, query: "create database Test", resultQuery: "create database Test", schemaName: dbInfo.Name.O, tableName: ""}, 336 {name: "updateSchema", jobID: 4, schemaID: 2, tableID: 0, jobType: timodel.ActionModifySchemaCharsetAndCollate, binlogInfo: &timodel.HistoryInfo{SchemaVersion: 8, DBInfo: dbInfo, TableInfo: nil, FinishedTS: 123}, query: "ALTER DATABASE Test CHARACTER SET utf8mb4;", resultQuery: "ALTER DATABASE Test CHARACTER SET utf8mb4;", schemaName: dbInfo.Name.O}, 337 {name: "createTable", jobID: 7, schemaID: 2, tableID: 6, jobType: timodel.ActionCreateTable, binlogInfo: &timodel.HistoryInfo{SchemaVersion: 3, DBInfo: nil, TableInfo: tblInfo, FinishedTS: 123}, query: "create table T(id int);", resultQuery: "create table T(id int);", schemaName: dbInfo.Name.O, tableName: tblInfo.Name.O}, 338 {name: "addColumn", jobID: 9, schemaID: 2, tableID: 6, jobType: timodel.ActionAddColumn, binlogInfo: &timodel.HistoryInfo{SchemaVersion: 4, DBInfo: nil, TableInfo: tblInfo, FinishedTS: 123}, query: "alter table T add a varchar(45);", resultQuery: "alter table T add a varchar(45);", schemaName: dbInfo.Name.O, tableName: tblInfo.Name.O}, 339 {name: "truncateTable", jobID: 10, schemaID: 2, tableID: 6, jobType: timodel.ActionTruncateTable, binlogInfo: &timodel.HistoryInfo{SchemaVersion: 5, DBInfo: nil, TableInfo: tblInfo, FinishedTS: 123}, query: "truncate table T;", resultQuery: "truncate table T;", schemaName: dbInfo.Name.O, tableName: tblInfo.Name.O}, 340 {name: "renameTable", jobID: 11, schemaID: 2, tableID: 10, jobType: timodel.ActionRenameTable, binlogInfo: &timodel.HistoryInfo{SchemaVersion: 6, DBInfo: nil, TableInfo: tblInfo, FinishedTS: 123}, query: "rename table T to RT;", resultQuery: "rename table T to RT;", schemaName: dbInfo.Name.O, tableName: newTbName.O}, 341 {name: "dropTable", jobID: 12, schemaID: 2, tableID: 12, jobType: timodel.ActionDropTable, binlogInfo: &timodel.HistoryInfo{SchemaVersion: 7, DBInfo: nil, TableInfo: nil, FinishedTS: 123}, query: "drop table RT;", resultQuery: "drop table RT;", schemaName: dbInfo.Name.O, tableName: newTbName.O}, 342 {name: "dropSchema", jobID: 13, schemaID: 2, tableID: 0, jobType: timodel.ActionDropSchema, binlogInfo: &timodel.HistoryInfo{SchemaVersion: 8, DBInfo: dbInfo, TableInfo: nil, FinishedTS: 123}, query: "drop database test;", resultQuery: "drop database test;", schemaName: dbInfo.Name.O, tableName: ""}, 343 } 344 345 for _, testCase := range testCases { 346 // prepare for ddl 347 switch testCase.name { 348 case "addColumn": 349 tblInfo.Columns = []*timodel.ColumnInfo{colInfo} 350 case "truncateTable": 351 tblInfo.ID = 10 352 case "renameTable": 353 tblInfo.ID = 12 354 tblInfo.Name = newTbName 355 } 356 357 job := &timodel.Job{ 358 ID: testCase.jobID, 359 State: timodel.JobStateDone, 360 SchemaID: testCase.schemaID, 361 TableID: testCase.tableID, 362 Type: testCase.jobType, 363 BinlogInfo: testCase.binlogInfo, 364 Query: testCase.query, 365 } 366 testDoDDLAndCheck(t, snap, job, false) 367 368 // custom check after ddl 369 switch testCase.name { 370 case "createSchema": 371 _, ok := snap.SchemaByID(dbInfo.ID) 372 require.True(t, ok) 373 case "createTable": 374 _, ok := snap.PhysicalTableByID(tblInfo.ID) 375 require.True(t, ok) 376 case "renameTable": 377 tb, ok := snap.PhysicalTableByID(tblInfo.ID) 378 require.True(t, ok) 379 require.Equal(t, tblInfo.Name, tb.Name) 380 case "addColumn", "truncateTable": 381 tb, ok := snap.PhysicalTableByID(tblInfo.ID) 382 require.True(t, ok) 383 require.Len(t, tb.Columns, 1) 384 case "dropTable": 385 _, ok := snap.PhysicalTableByID(tblInfo.ID) 386 require.False(t, ok) 387 case "dropSchema": 388 _, ok := snap.SchemaByID(job.SchemaID) 389 require.False(t, ok) 390 } 391 } 392 } 393 394 func TestHandleRenameTables(t *testing.T) { 395 // Initial schema: db_1.table_1 and db_2.table_2. 396 snap := schema.NewEmptySnapshot(true) 397 var i int64 398 for i = 1; i < 3; i++ { 399 dbInfo := &timodel.DBInfo{ 400 ID: i, 401 Name: timodel.NewCIStr(fmt.Sprintf("db_%d", i)), 402 State: timodel.StatePublic, 403 } 404 job := &timodel.Job{ 405 ID: i, 406 State: timodel.JobStateDone, 407 SchemaID: i, 408 Type: timodel.ActionCreateSchema, 409 BinlogInfo: &timodel.HistoryInfo{SchemaVersion: i, DBInfo: dbInfo, FinishedTS: uint64(i)}, 410 Query: fmt.Sprintf("create database %s", dbInfo.Name.O), 411 } 412 err := snap.HandleDDL(job) 413 require.Nil(t, err) 414 } 415 for i = 1; i < 3; i++ { 416 tblInfo := &timodel.TableInfo{ 417 ID: 10 + i, 418 Name: timodel.NewCIStr(fmt.Sprintf("table_%d", i)), 419 State: timodel.StatePublic, 420 } 421 job := &timodel.Job{ 422 ID: i, 423 State: timodel.JobStateDone, 424 SchemaID: i, 425 TableID: 10 + i, 426 Type: timodel.ActionCreateTable, 427 BinlogInfo: &timodel.HistoryInfo{SchemaVersion: i, TableInfo: tblInfo, FinishedTS: uint64(10 + i)}, 428 Query: "create table " + tblInfo.Name.O, 429 } 430 err := snap.HandleDDL(job) 431 require.Nil(t, err) 432 } 433 434 // rename table db1.table_1 to db2.x, db2.table_2 to db1.y 435 oldSchemaIDs := []int64{1, 2} 436 newSchemaIDs := []int64{2, 1} 437 oldTableIDs := []int64{11, 12} 438 newTableNames := []timodel.CIStr{timodel.NewCIStr("x"), timodel.NewCIStr("y")} 439 oldSchemaNames := []timodel.CIStr{timodel.NewCIStr("db_1"), timodel.NewCIStr("db_2")} 440 args := []interface{}{oldSchemaIDs, newSchemaIDs, newTableNames, oldTableIDs, oldSchemaNames} 441 rawArgs, err := json.Marshal(args) 442 require.Nil(t, err) 443 var job *timodel.Job = &timodel.Job{ 444 Type: timodel.ActionRenameTables, 445 RawArgs: rawArgs, 446 BinlogInfo: &timodel.HistoryInfo{ 447 FinishedTS: 11112222, 448 }, 449 } 450 job.BinlogInfo.MultipleTableInfos = append(job.BinlogInfo.MultipleTableInfos, 451 &timodel.TableInfo{ 452 ID: 13, 453 Name: timodel.NewCIStr("x"), 454 State: timodel.StatePublic, 455 }) 456 job.BinlogInfo.MultipleTableInfos = append(job.BinlogInfo.MultipleTableInfos, 457 &timodel.TableInfo{ 458 ID: 14, 459 Name: timodel.NewCIStr("y"), 460 State: timodel.StatePublic, 461 }) 462 testDoDDLAndCheck(t, snap, job, false) 463 464 var ok bool 465 _, ok = snap.PhysicalTableByID(13) 466 require.True(t, ok) 467 _, ok = snap.PhysicalTableByID(14) 468 require.True(t, ok) 469 _, ok = snap.PhysicalTableByID(11) 470 require.False(t, ok) 471 _, ok = snap.PhysicalTableByID(12) 472 require.False(t, ok) 473 474 n1, _ := snap.TableIDByName("db_2", "x") 475 require.Equal(t, n1, int64(13)) 476 n2, _ := snap.TableIDByName("db_1", "y") 477 require.Equal(t, n2, int64(14)) 478 require.Equal(t, uint64(11112222), snap.CurrentTs()) 479 } 480 481 func testDoDDLAndCheck(t *testing.T, snap *schema.Snapshot, job *timodel.Job, isErr bool) { 482 err := snap.HandleDDL(job) 483 require.Equal(t, err != nil, isErr) 484 } 485 486 func TestMultiVersionStorage(t *testing.T) { 487 ctx, cancel := context.WithCancel(context.Background()) 488 dbName := timodel.NewCIStr("Test") 489 tbName := timodel.NewCIStr("T1") 490 // db and ignoreDB info 491 dbInfo := &timodel.DBInfo{ 492 ID: 11, 493 Name: dbName, 494 State: timodel.StatePublic, 495 } 496 var jobs []*timodel.Job 497 // `createSchema` job1 498 job := &timodel.Job{ 499 ID: 13, 500 State: timodel.JobStateDone, 501 SchemaID: 11, 502 Type: timodel.ActionCreateSchema, 503 BinlogInfo: &timodel.HistoryInfo{SchemaVersion: 1, DBInfo: dbInfo, FinishedTS: 100}, 504 Query: "create database test", 505 } 506 jobs = append(jobs, job) 507 508 // table info 509 tblInfo := &timodel.TableInfo{ 510 ID: 12, 511 Name: tbName, 512 State: timodel.StatePublic, 513 } 514 515 // `createTable` job 516 job = &timodel.Job{ 517 ID: 16, 518 State: timodel.JobStateDone, 519 SchemaID: 11, 520 TableID: 12, 521 Type: timodel.ActionCreateTable, 522 BinlogInfo: &timodel.HistoryInfo{SchemaVersion: 2, TableInfo: tblInfo, FinishedTS: 110}, 523 Query: "create table " + tbName.O, 524 } 525 526 jobs = append(jobs, job) 527 528 tbName = timodel.NewCIStr("T2") 529 // table info 530 tblInfo = &timodel.TableInfo{ 531 ID: 13, 532 Name: tbName, 533 State: timodel.StatePublic, 534 } 535 // `createTable` job 536 job = &timodel.Job{ 537 ID: 16, 538 State: timodel.JobStateDone, 539 SchemaID: 11, 540 TableID: 13, 541 Type: timodel.ActionCreateTable, 542 BinlogInfo: &timodel.HistoryInfo{SchemaVersion: 3, TableInfo: tblInfo, FinishedTS: 120}, 543 Query: "create table " + tbName.O, 544 } 545 546 jobs = append(jobs, job) 547 f, err := filter.NewFilter(config.GetDefaultReplicaConfig(), "") 548 require.Nil(t, err) 549 storage, err := NewSchemaStorage(nil, 0, false, model.DefaultChangeFeedID("dummy"), util.RoleTester, f) 550 require.Nil(t, err) 551 for _, job := range jobs { 552 err := storage.HandleDDLJob(job) 553 require.Nil(t, err) 554 } 555 556 // `dropTable` job 557 job = &timodel.Job{ 558 ID: 16, 559 State: timodel.JobStateDone, 560 SchemaID: 11, 561 TableID: 12, 562 Type: timodel.ActionDropTable, 563 BinlogInfo: &timodel.HistoryInfo{SchemaVersion: 4, FinishedTS: 130}, 564 } 565 566 err = storage.HandleDDLJob(job) 567 require.Nil(t, err) 568 569 // `dropSchema` job 570 job = &timodel.Job{ 571 ID: 16, 572 State: timodel.JobStateDone, 573 SchemaID: 11, 574 Type: timodel.ActionDropSchema, 575 BinlogInfo: &timodel.HistoryInfo{SchemaVersion: 5, FinishedTS: 140, DBInfo: dbInfo}, 576 } 577 578 err = storage.HandleDDLJob(job) 579 require.Nil(t, err) 580 581 require.Equal(t, storage.(*schemaStorage).resolvedTs, uint64(140)) 582 snap, err := storage.GetSnapshot(ctx, 100) 583 require.Nil(t, err) 584 _, exist := snap.SchemaByID(11) 585 require.True(t, exist) 586 _, exist = snap.PhysicalTableByID(12) 587 require.False(t, exist) 588 _, exist = snap.PhysicalTableByID(13) 589 require.False(t, exist) 590 591 snap, err = storage.GetSnapshot(ctx, 115) 592 require.Nil(t, err) 593 _, exist = snap.SchemaByID(11) 594 require.True(t, exist) 595 _, exist = snap.PhysicalTableByID(12) 596 require.True(t, exist) 597 _, exist = snap.PhysicalTableByID(13) 598 require.False(t, exist) 599 600 snap, err = storage.GetSnapshot(ctx, 125) 601 require.Nil(t, err) 602 _, exist = snap.SchemaByID(11) 603 require.True(t, exist) 604 _, exist = snap.PhysicalTableByID(12) 605 require.True(t, exist) 606 _, exist = snap.PhysicalTableByID(13) 607 require.True(t, exist) 608 609 snap, err = storage.GetSnapshot(ctx, 135) 610 require.Nil(t, err) 611 _, exist = snap.SchemaByID(11) 612 require.True(t, exist) 613 _, exist = snap.PhysicalTableByID(12) 614 require.False(t, exist) 615 _, exist = snap.PhysicalTableByID(13) 616 require.True(t, exist) 617 618 snap, err = storage.GetSnapshot(ctx, 140) 619 require.Nil(t, err) 620 _, exist = snap.SchemaByID(11) 621 require.False(t, exist) 622 _, exist = snap.PhysicalTableByID(12) 623 require.False(t, exist) 624 _, exist = snap.PhysicalTableByID(13) 625 require.False(t, exist) 626 627 lastSchemaTs := storage.DoGC(0) 628 require.Equal(t, uint64(0), lastSchemaTs) 629 630 snap, err = storage.GetSnapshot(ctx, 100) 631 require.Nil(t, err) 632 _, exist = snap.SchemaByID(11) 633 require.True(t, exist) 634 _, exist = snap.PhysicalTableByID(12) 635 require.False(t, exist) 636 _, exist = snap.PhysicalTableByID(13) 637 require.False(t, exist) 638 storage.DoGC(115) 639 _, err = storage.GetSnapshot(ctx, 100) 640 require.NotNil(t, err) 641 snap, err = storage.GetSnapshot(ctx, 115) 642 require.Nil(t, err) 643 _, exist = snap.SchemaByID(11) 644 require.True(t, exist) 645 _, exist = snap.PhysicalTableByID(12) 646 require.True(t, exist) 647 _, exist = snap.PhysicalTableByID(13) 648 require.False(t, exist) 649 650 lastSchemaTs = storage.DoGC(155) 651 require.Equal(t, uint64(140), lastSchemaTs) 652 653 storage.AdvanceResolvedTs(185) 654 655 snap, err = storage.GetSnapshot(ctx, 180) 656 require.Nil(t, err) 657 _, exist = snap.SchemaByID(11) 658 require.False(t, exist) 659 _, exist = snap.PhysicalTableByID(12) 660 require.False(t, exist) 661 _, exist = snap.PhysicalTableByID(13) 662 require.False(t, exist) 663 _, err = storage.GetSnapshot(ctx, 130) 664 require.NotNil(t, err) 665 666 cancel() 667 _, err = storage.GetSnapshot(ctx, 200) 668 require.Equal(t, errors.Cause(err), context.Canceled) 669 } 670 671 func TestCreateSnapFromMeta(t *testing.T) { 672 store, err := mockstore.NewMockStore() 673 require.Nil(t, err) 674 defer store.Close() //nolint:errcheck 675 676 session.SetSchemaLease(0) 677 session.DisableStats4Test() 678 domain, err := session.BootstrapSession(store) 679 require.Nil(t, err) 680 defer domain.Close() 681 domain.SetStatsUpdating(true) 682 tk := testkit.NewTestKit(t, store) 683 tk.MustExec("create database test2") 684 tk.MustExec("create table test.simple_test1 (id bigint primary key)") 685 tk.MustExec("create table test.simple_test2 (id bigint primary key)") 686 tk.MustExec("create table test2.simple_test3 (id bigint primary key)") 687 tk.MustExec("create table test2.simple_test4 (id bigint primary key)") 688 tk.MustExec("create table test2.simple_test5 (a bigint)") 689 ver, err := store.CurrentVersion(oracle.GlobalTxnScope) 690 require.Nil(t, err) 691 meta := kv.GetSnapshotMeta(store, ver.Ver) 692 f, err := filter.NewFilter(config.GetDefaultReplicaConfig(), "") 693 require.Nil(t, err) 694 snap, err := schema.NewSnapshotFromMeta(model.DefaultChangeFeedID("test"), meta, ver.Ver, false, f) 695 require.Nil(t, err) 696 _, ok := snap.TableByName("test", "simple_test1") 697 require.True(t, ok) 698 tableID, ok := snap.TableIDByName("test2", "simple_test5") 699 require.True(t, ok) 700 require.True(t, snap.IsIneligibleTableID(tableID)) 701 dbInfo, ok := snap.SchemaByTableID(tableID) 702 require.True(t, ok) 703 require.Equal(t, dbInfo.Name.O, "test2") 704 } 705 706 func TestExplicitTables(t *testing.T) { 707 store, err := mockstore.NewMockStore() 708 require.Nil(t, err) 709 defer store.Close() //nolint:errcheck 710 711 session.SetSchemaLease(0) 712 session.DisableStats4Test() 713 domain, err := session.BootstrapSession(store) 714 require.Nil(t, err) 715 defer domain.Close() 716 domain.SetStatsUpdating(true) 717 tk := testkit.NewTestKit(t, store) 718 ver1, err := store.CurrentVersion(oracle.GlobalTxnScope) 719 require.Nil(t, err) 720 tk.MustExec("create database test2") 721 tk.MustExec("create table test.simple_test1 (id bigint primary key)") 722 tk.MustExec("create table test.simple_test2 (id bigint unique key)") 723 tk.MustExec("create table test2.simple_test3 (a bigint)") 724 tk.MustExec("create table test2.simple_test4 (a varchar(20) unique key)") 725 tk.MustExec("create table test2.simple_test5 (a varchar(20))") 726 ver2, err := store.CurrentVersion(oracle.GlobalTxnScope) 727 require.Nil(t, err) 728 meta1 := kv.GetSnapshotMeta(store, ver1.Ver) 729 f, err := filter.NewFilter(config.GetDefaultReplicaConfig(), "") 730 require.Nil(t, err) 731 snap1, err := schema.NewSnapshotFromMeta(model.DefaultChangeFeedID("test"), meta1, ver1.Ver, true /* forceReplicate */, f) 732 require.Nil(t, err) 733 meta2 := kv.GetSnapshotMeta(store, ver2.Ver) 734 snap2, err := schema.NewSnapshotFromMeta(model.DefaultChangeFeedID("test"), meta2, ver2.Ver, false /* forceReplicate */, f) 735 require.Nil(t, err) 736 snap3, err := schema.NewSnapshotFromMeta(model.DefaultChangeFeedID("test"), meta2, ver2.Ver, true /* forceReplicate */, f) 737 require.Nil(t, err) 738 739 // we don't need to count system tables since TiCDC 740 // don't replicate them and TiDB change them frequently, 741 // so we don't need to consider them in the table count 742 systemTablesFilter := func(dbName, tableName string) bool { 743 return dbName != "mysql" && dbName != "information_schema" 744 } 745 require.Equal(t, 5, snap2.TableCount(true, 746 systemTablesFilter)-snap1.TableCount(true, systemTablesFilter)) 747 // only test simple_test1 included 748 require.Equal(t, 1, snap2.TableCount(false, systemTablesFilter)) 749 750 require.Equal(t, 5, snap3.TableCount(true, 751 systemTablesFilter)-snap1.TableCount(true, systemTablesFilter)) 752 // since we create a snapshot from meta2 and forceReplicate is true, so all tables are included 753 require.Equal(t, 5, snap3.TableCount(false, systemTablesFilter)) 754 } 755 756 /* 757 TODO: Untested Action: 758 759 ActionAddForeignKey ActionType = 9 760 ActionDropForeignKey ActionType = 10 761 ActionRebaseAutoID ActionType = 13 762 ActionShardRowID ActionType = 16 763 ActionLockTable ActionType = 27 764 ActionUnlockTable ActionType = 28 765 ActionRepairTable ActionType = 29 766 ActionSetTiFlashReplica ActionType = 30 767 ActionUpdateTiFlashReplicaStatus ActionType = 31 768 ActionCreateSequence ActionType = 34 769 ActionAlterSequence ActionType = 35 770 ActionDropSequence ActionType = 36 771 ActionModifyTableAutoIdCache ActionType = 39 772 ActionRebaseAutoRandomBase ActionType = 40 773 ActionExchangeTablePartition ActionType = 42 774 ActionAddCheckConstraint ActionType = 43 775 ActionDropCheckConstraint ActionType = 44 776 ActionAlterCheckConstraint ActionType = 45 777 ActionAlterTableAlterPartition ActionType = 46 778 779 ... Any Action which of value is greater than 46 ... 780 */ 781 func TestSchemaStorage(t *testing.T) { 782 ctx := context.Background() 783 testCases := [][]string{{ 784 "create database test_ddl1", // ActionCreateSchema 785 "create table test_ddl1.simple_test1 (id bigint primary key)", // ActionCreateTable 786 "create table test_ddl1.simple_test2 (id bigint)", // ActionCreateTable 787 "create table test_ddl1.simple_test3 (id bigint primary key)", // ActionCreateTable 788 "create table test_ddl1.simple_test4 (id bigint primary key)", // ActionCreateTable 789 "DROP TABLE test_ddl1.simple_test3", // ActionDropTable 790 "ALTER TABLE test_ddl1.simple_test1 ADD COLUMN c1 INT NOT NULL", // ActionAddColumn 791 "ALTER TABLE test_ddl1.simple_test1 ADD c2 INT NOT NULL AFTER id", // ActionAddColumn 792 "ALTER TABLE test_ddl1.simple_test1 ADD c3 INT NOT NULL, ADD c4 INT NOT NULL", // ActionAddColumns 793 "ALTER TABLE test_ddl1.simple_test1 DROP c1", // ActionDropColumn 794 "ALTER TABLE test_ddl1.simple_test1 DROP c2, DROP c3", // ActionDropColumns 795 "ALTER TABLE test_ddl1.simple_test1 ADD INDEX (c4)", // ActionAddIndex 796 "ALTER TABLE test_ddl1.simple_test1 DROP INDEX c4", // ActionDropIndex 797 "TRUNCATE test_ddl1.simple_test1", // ActionTruncateTable 798 "ALTER DATABASE test_ddl1 CHARACTER SET = binary COLLATE binary", // ActionModifySchemaCharsetAndCollate 799 "ALTER TABLE test_ddl1.simple_test2 ADD c1 INT NOT NULL, ADD c2 INT NOT NULL", // ActionAddColumns 800 "ALTER TABLE test_ddl1.simple_test2 ADD INDEX (c1)", // ActionAddIndex 801 "ALTER TABLE test_ddl1.simple_test2 ALTER INDEX c1 INVISIBLE", // ActionAlterIndexVisibility 802 "ALTER TABLE test_ddl1.simple_test2 RENAME INDEX c1 TO idx_c1", // ActionRenameIndex 803 "ALTER TABLE test_ddl1.simple_test2 MODIFY c2 BIGINT", // ActionModifyColumn 804 "CREATE VIEW test_ddl1.view_test2 AS SELECT * FROM test_ddl1.simple_test2 WHERE id > 2", // ActionCreateView 805 "DROP VIEW test_ddl1.view_test2", // ActionDropView 806 "RENAME TABLE test_ddl1.simple_test2 TO test_ddl1.simple_test5", // ActionRenameTable 807 "DROP DATABASE test_ddl1", // ActionDropSchema 808 "create database test_ddl2", // ActionCreateSchema 809 "create table test_ddl2.simple_test1 (id bigint primary key nonclustered, c1 int not null unique key)", // ActionCreateTable 810 `CREATE TABLE test_ddl2.employees ( 811 id INT NOT NULL AUTO_INCREMENT PRIMARY KEY, 812 fname VARCHAR(25) NOT NULL, 813 lname VARCHAR(25) NOT NULL, 814 store_id INT NOT NULL, 815 department_id INT NOT NULL 816 ) 817 818 PARTITION BY RANGE(id) ( 819 PARTITION p0 VALUES LESS THAN (5), 820 PARTITION p1 VALUES LESS THAN (10), 821 PARTITION p2 VALUES LESS THAN (15), 822 PARTITION p3 VALUES LESS THAN (20) 823 )`, // ActionCreateTable 824 "ALTER TABLE test_ddl2.employees DROP PARTITION p2", // ActionDropTablePartition 825 "ALTER TABLE test_ddl2.employees ADD PARTITION (PARTITION p4 VALUES LESS THAN (25))", // ActionAddTablePartition 826 "ALTER TABLE test_ddl2.employees TRUNCATE PARTITION p3", // ActionTruncateTablePartition 827 "alter table test_ddl2.employees comment='modify comment'", // ActionModifyTableComment 828 "alter table test_ddl2.simple_test1 drop primary key", // ActionDropPrimaryKey 829 "alter table test_ddl2.simple_test1 add primary key pk(id)", // ActionAddPrimaryKey 830 "ALTER TABLE test_ddl2.simple_test1 ALTER id SET DEFAULT 18", // ActionSetDefaultValue 831 "ALTER TABLE test_ddl2.simple_test1 CHARACTER SET = utf8mb4", // ActionModifyTableCharsetAndCollate 832 833 "ALTER TABLE test_ddl2.employees REORGANIZE PARTITION p3 INTO (PARTITION p2 VALUES LESS THAN (15), PARTITION p3 VALUES LESS THAN (20))", // ActionReorganizePartition 834 // "recover table test_ddl2.employees", // ActionRecoverTable this ddl can't work on mock tikv 835 836 "DROP TABLE test_ddl2.employees", 837 `CREATE TABLE test_ddl2.employees2 ( 838 id INT NOT NULL, 839 fname VARCHAR(25) NOT NULL, 840 lname VARCHAR(25) NOT NULL, 841 store_id INT NOT NULL, 842 department_id INT NOT NULL 843 ) 844 845 PARTITION BY RANGE(id) ( 846 PARTITION p0 VALUES LESS THAN (5), 847 PARTITION p1 VALUES LESS THAN (10), 848 PARTITION p2 VALUES LESS THAN (15), 849 PARTITION p3 VALUES LESS THAN (20) 850 )`, 851 "ALTER TABLE test_ddl2.employees2 CHARACTER SET = utf8mb4", 852 "DROP DATABASE test_ddl2", 853 }} 854 855 testOneGroup := func(tc []string) { 856 store, err := mockstore.NewMockStore() 857 require.Nil(t, err) 858 defer store.Close() //nolint:errcheck 859 ticonfig.UpdateGlobal(func(conf *ticonfig.Config) { 860 conf.AlterPrimaryKey = true 861 }) 862 session.SetSchemaLease(0) 863 session.DisableStats4Test() 864 domain, err := session.BootstrapSession(store) 865 require.Nil(t, err) 866 defer domain.Close() 867 domain.SetStatsUpdating(true) 868 tk := testkit.NewTestKit(t, store) 869 tk.MustExec("set global tidb_enable_clustered_index = 'int_only';") 870 for _, ddlSQL := range tc { 871 tk.MustExec(ddlSQL) 872 } 873 874 f, err := filter.NewFilter(config.GetDefaultReplicaConfig(), "") 875 require.Nil(t, err) 876 877 jobs, err := getAllHistoryDDLJob(store, f) 878 require.Nil(t, err) 879 880 schemaStorage, err := NewSchemaStorage(nil, 0, false, model.DefaultChangeFeedID("dummy"), util.RoleTester, f) 881 require.NoError(t, err) 882 for _, job := range jobs { 883 err := schemaStorage.HandleDDLJob(job) 884 require.NoError(t, err) 885 } 886 887 for _, job := range jobs { 888 ts := job.BinlogInfo.FinishedTS 889 meta := kv.GetSnapshotMeta(store, ts) 890 snapFromMeta, err := schema.NewSnapshotFromMeta(model.DefaultChangeFeedID("test"), meta, ts, false, f) 891 require.Nil(t, err) 892 snapFromSchemaStore, err := schemaStorage.GetSnapshot(ctx, ts) 893 require.Nil(t, err) 894 895 s1 := snapFromMeta.DumpToString() 896 s2 := snapFromSchemaStore.DumpToString() 897 require.Equal(t, s1, s2) 898 } 899 } 900 901 for _, tc := range testCases { 902 testOneGroup(tc) 903 } 904 } 905 906 func getAllHistoryDDLJob(storage tidbkv.Storage, f filter.Filter) ([]*timodel.Job, error) { 907 s, err := session.CreateSession(storage) 908 if err != nil { 909 return nil, errors.Trace(err) 910 } 911 912 if s != nil { 913 defer s.Close() 914 } 915 916 store := domain.GetDomain(s.(sessionctx.Context)).Store() 917 txn, err := store.Begin() 918 if err != nil { 919 return nil, errors.Trace(err) 920 } 921 defer txn.Rollback() //nolint:errcheck 922 txnMeta := timeta.NewMeta(txn) 923 924 jobs, err := ddl.GetAllHistoryDDLJobs(txnMeta) 925 res := make([]*timodel.Job, 0) 926 if err != nil { 927 return nil, errors.Trace(err) 928 } 929 for i, job := range jobs { 930 ignoreSchema := f.ShouldIgnoreSchema(job.SchemaName) 931 ignoreTable := f.ShouldIgnoreTable(job.SchemaName, job.TableName) 932 if ignoreSchema || ignoreTable { 933 log.Info("Ignore ddl job", zap.Stringer("job", job)) 934 continue 935 } 936 // Set State from Synced to Done. 937 // Because jobs are put to history queue after TiDB alter its state from 938 // Done to Synced. 939 jobs[i].State = timodel.JobStateDone 940 res = append(res, job) 941 } 942 return jobs, nil 943 } 944 945 // This test is used to show how the schemaStorage choose a handleKey of a table. 946 // The handleKey is chosen by the following rules: 947 // 1. If the table has a primary key, the handleKey is the first column of the primary key. 948 // 2. If the table has not null unique key, the handleKey is the first column of the unique key. 949 // 3. If the table has no primary key and no not null unique key, it has no handleKey. 950 func TestHandleKey(t *testing.T) { 951 store, err := mockstore.NewMockStore() 952 require.Nil(t, err) 953 defer store.Close() //nolint:errcheck 954 955 session.SetSchemaLease(0) 956 session.DisableStats4Test() 957 domain, err := session.BootstrapSession(store) 958 require.Nil(t, err) 959 defer domain.Close() 960 domain.SetStatsUpdating(true) 961 tk := testkit.NewTestKit(t, store) 962 tk.MustExec("create database test2") 963 tk.MustExec("create table test.simple_test1 (id bigint primary key)") 964 tk.MustExec("create table test.simple_test2 (id bigint, age int NOT NULL, " + 965 "name char NOT NULL, UNIQUE KEY(age, name))") 966 tk.MustExec("create table test.simple_test3 (id bigint, age int)") 967 ver, err := store.CurrentVersion(oracle.GlobalTxnScope) 968 require.Nil(t, err) 969 meta := kv.GetSnapshotMeta(store, ver.Ver) 970 f, err := filter.NewFilter(config.GetDefaultReplicaConfig(), "") 971 require.Nil(t, err) 972 snap, err := schema.NewSnapshotFromMeta(model.DefaultChangeFeedID("test"), meta, ver.Ver, false, f) 973 require.Nil(t, err) 974 tb1, ok := snap.TableByName("test", "simple_test1") 975 require.True(t, ok) 976 require.Equal(t, int64(-1), tb1.HandleIndexID) // pk is handleKey 977 columnID := int64(1) 978 flag := tb1.ColumnsFlag[columnID] 979 require.True(t, flag.IsHandleKey()) 980 for _, column := range tb1.Columns { 981 if column.ID == columnID { 982 require.True(t, column.Name.O == "id") 983 } 984 } 985 986 // unique key is handleKey 987 tb2, ok := snap.TableByName("test", "simple_test2") 988 require.True(t, ok) 989 require.Equal(t, int64(1), tb2.HandleIndexID) 990 columnID = int64(2) 991 flag = tb2.ColumnsFlag[columnID] 992 require.True(t, flag.IsHandleKey()) 993 for _, column := range tb2.Columns { 994 if column.ID == columnID { 995 require.True(t, column.Name.O == "age") 996 } 997 } 998 999 // has no handleKey 1000 tb3, ok := snap.TableByName("test", "simple_test3") 1001 require.True(t, ok) 1002 require.Equal(t, int64(-2), tb3.HandleIndexID) 1003 } 1004 1005 func TestGetPrimaryKey(t *testing.T) { 1006 helper := NewSchemaTestHelper(t) 1007 defer helper.Close() 1008 // PKISHandle is true, primary key is also the handle, since it's integer type. 1009 sql := `create table test.t1(a int primary key, b int)` 1010 event := helper.DDL2Event(sql) 1011 1012 names := event.TableInfo.GetPrimaryKeyColumnNames() 1013 require.Equal(t, names, []string{"a"}) 1014 1015 // IsCommonHandle is true, primary key is not the handle, since it contains multiple fields. 1016 sql = `create table test.t2(a int, b int, c int, primary key(a, b))` 1017 event = helper.DDL2Event(sql) 1018 names = event.TableInfo.GetPrimaryKeyColumnNames() 1019 require.Equal(t, names, []string{"a", "b"}) 1020 1021 // IsCommonHandle is true, primary key is not the handle, since it's not integer type. 1022 sql = `create table test.t3(a varchar(10) primary key, b int)` 1023 event = helper.DDL2Event(sql) 1024 names = event.TableInfo.GetPrimaryKeyColumnNames() 1025 require.Equal(t, names, []string{"a"}) 1026 }