github.com/pingcap/tiflow@v0.0.0-20240520035814-5bf52d54e205/dm/pkg/checker/table_structure_test.go (about) 1 // Copyright 2021 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 checker 15 16 import ( 17 "context" 18 "strings" 19 "testing" 20 21 "github.com/DATA-DOG/go-sqlmock" 22 "github.com/go-sql-driver/mysql" 23 "github.com/pingcap/tidb/pkg/util/filter" 24 "github.com/pingcap/tiflow/dm/pkg/conn" 25 "github.com/stretchr/testify/require" 26 ) 27 28 var errNoSuchTable = &mysql.MySQLError{Number: 1146, Message: "Table 'xxx' doesn't exist"} 29 30 func TestShardingTablesChecker(t *testing.T) { 31 db, mock, err := sqlmock.New() 32 require.NoError(t, err) 33 ctx := context.Background() 34 35 // 1. test a success check 36 mock = initShardingMock(mock) 37 createTableRow2 := sqlmock.NewRows([]string{"Table", "Create Table"}). 38 AddRow("test-table-2", `CREATE TABLE "test-table-2" ( 39 "c" int(11) NOT NULL, 40 PRIMARY KEY ("c") 41 ) ENGINE=InnoDB DEFAULT CHARSET=latin1`) 42 mock.ExpectQuery("SHOW CREATE TABLE `test-db`.`test-table-2`").WillReturnRows(createTableRow2) 43 44 checker := NewShardingTablesChecker("test-name", 45 map[string]*conn.BaseDB{"test-source": conn.NewBaseDBForTest(db)}, 46 map[string][]filter.Table{"test-source": { 47 {Schema: "test-db", Name: "test-table-1"}, 48 {Schema: "test-db", Name: "test-table-2"}, 49 }}, 50 false, 51 1) 52 result := checker.Check(ctx) 53 require.Equal(t, StateSuccess, result.State) 54 require.NoError(t, mock.ExpectationsWereMet()) 55 56 // 2. check different column number 57 checker = NewShardingTablesChecker("test-name", 58 map[string]*conn.BaseDB{"test-source": conn.NewBaseDBForTest(db)}, 59 map[string][]filter.Table{"test-source": { 60 {Schema: "test-db", Name: "test-table-1"}, 61 {Schema: "test-db", Name: "test-table-2"}, 62 }}, 63 false, 64 1) 65 mock = initShardingMock(mock) 66 createTableRow2 = sqlmock.NewRows([]string{"Table", "Create Table"}). 67 AddRow("test-table-2", `CREATE TABLE "test-table-2" ( 68 "c" int(11) NOT NULL, 69 "d" int(11) NOT NULL, 70 PRIMARY KEY ("c") 71 ) ENGINE=InnoDB DEFAULT CHARSET=latin1`) 72 mock.ExpectQuery("SHOW CREATE TABLE `test-db`.`test-table-2`").WillReturnRows(createTableRow2) 73 74 result = checker.Check(ctx) 75 require.Equal(t, StateFailure, result.State) 76 require.Len(t, result.Errors, 1) 77 require.NoError(t, mock.ExpectationsWereMet()) 78 79 // 3. check different column def 80 checker = NewShardingTablesChecker("test-name", 81 map[string]*conn.BaseDB{"test-source": conn.NewBaseDBForTest(db)}, 82 map[string][]filter.Table{"test-source": { 83 {Schema: "test-db", Name: "test-table-1"}, 84 {Schema: "test-db", Name: "test-table-2"}, 85 }}, 86 false, 87 1) 88 mock = initShardingMock(mock) 89 createTableRow2 = sqlmock.NewRows([]string{"Table", "Create Table"}). 90 AddRow("test-table-2", `CREATE TABLE "test-table-2" ( 91 "c" varchar(20) NOT NULL, 92 PRIMARY KEY ("c") 93 ) ENGINE=InnoDB DEFAULT CHARSET=latin1`) 94 mock.ExpectQuery("SHOW CREATE TABLE `test-db`.`test-table-2`").WillReturnRows(createTableRow2) 95 96 result = checker.Check(ctx) 97 require.Equal(t, StateFailure, result.State) 98 require.Len(t, result.Errors, 1) 99 require.NoError(t, mock.ExpectationsWereMet()) 100 101 // 4. test tiflow#5759 102 checker = NewShardingTablesChecker("test-name", 103 map[string]*conn.BaseDB{"test-source": conn.NewBaseDBForTest(db)}, 104 map[string][]filter.Table{"test-source": { 105 {Schema: "test-db", Name: "test-table-1"}, 106 {Schema: "test-db", Name: "test-table-2"}, 107 {Schema: "test-db", Name: "test-table-3"}, 108 {Schema: "test-db", Name: "test-table-4"}, 109 }}, 110 false, 111 1) 112 mock = initShardingMock(mock) 113 createTableRow2 = sqlmock.NewRows([]string{"Table", "Create Table"}). 114 AddRow("test-table-2", `CREATE TABLE "test-table-2" ( 115 "c" varchar(20) NOT NULL, 116 PRIMARY KEY ("c") 117 ) ENGINE=InnoDB DEFAULT CHARSET=latin1`) 118 mock.ExpectQuery("SHOW CREATE TABLE `test-db`.`test-table-2`").WillReturnRows(createTableRow2) 119 createTableRow3 := sqlmock.NewRows([]string{"Table", "Create Table"}). 120 AddRow("test-table-3", `CREATE TABLE "test-table-3" ( 121 "c" varchar(20) NOT NULL, 122 "c2" INT, 123 PRIMARY KEY ("c") 124 ) ENGINE=InnoDB DEFAULT CHARSET=latin1`) 125 mock.ExpectQuery("SHOW CREATE TABLE `test-db`.`test-table-3`").WillReturnRows(createTableRow3) 126 createTableRow4 := sqlmock.NewRows([]string{"Table", "Create Table"}). 127 AddRow("test-table-4", `CREATE TABLE "test-table-4" ( 128 "c" varchar(20) NOT NULL, 129 "c2" INT, 130 "c3" INT, 131 PRIMARY KEY ("c") 132 ) ENGINE=InnoDB DEFAULT CHARSET=latin1`) 133 mock.ExpectQuery("SHOW CREATE TABLE `test-db`.`test-table-4`").WillReturnRows(createTableRow4) 134 135 // in tiflow#5759, this function will enter deadlock 136 result = checker.Check(ctx) 137 require.Equal(t, StateFailure, result.State) 138 require.Len(t, result.Errors, 3) 139 } 140 141 func TestTablesChecker(t *testing.T) { 142 db, mock, err := sqlmock.New() 143 require.NoError(t, err) 144 downDB, downMock, err := sqlmock.New() 145 require.NoError(t, err) 146 ctx := context.Background() 147 148 commonMock := func() { 149 maxConnectionsRow := sqlmock.NewRows([]string{"Variable_name", "Value"}). 150 AddRow("max_connections", "2") 151 mock.ExpectQuery("SHOW VARIABLES LIKE 'max_connections'").WillReturnRows(maxConnectionsRow) 152 sqlModeRow := sqlmock.NewRows([]string{"Variable_name", "Value"}). 153 AddRow("sql_mode", "ANSI_QUOTES") 154 mock.ExpectQuery("SHOW VARIABLES LIKE 'sql_mode'").WillReturnRows(sqlModeRow) 155 sqlModeRow2 := sqlmock.NewRows([]string{"Variable_name", "Value"}). 156 AddRow("sql_mode", "ANSI_QUOTES") 157 downMock.ExpectQuery("SHOW VARIABLES LIKE 'sql_mode'").WillReturnRows(sqlModeRow2) 158 } 159 160 // 1. test a success check 161 162 commonMock() 163 createTableRowUp := sqlmock.NewRows([]string{"Table", "Create Table"}). 164 AddRow("test-table-1", `CREATE TABLE "test-table-1" ( 165 "c" int(11) NOT NULL, 166 PRIMARY KEY ("c") 167 ) ENGINE=InnoDB DEFAULT CHARSET=latin1`) 168 mock.ExpectQuery("SHOW CREATE TABLE `test-db`.`test-table-1`").WillReturnRows(createTableRowUp) 169 downMock.ExpectQuery("SHOW CREATE TABLE `test-db`.`test-table-1`").WillReturnError(errNoSuchTable) 170 171 checker := NewTablesChecker( 172 map[string]*conn.BaseDB{"test-source": conn.NewBaseDBForTest(db)}, 173 conn.NewBaseDBForTest(downDB), 174 map[string]map[filter.Table][]filter.Table{ 175 "test-source": { 176 {Schema: "test-db", Name: "test-table-1"}: { 177 {Schema: "test-db", Name: "test-table-1"}, 178 }, 179 }, 180 }, 181 nil, 182 1) 183 result := checker.Check(ctx) 184 require.Equal(t, StateSuccess, result.State) 185 require.NoError(t, mock.ExpectationsWereMet()) 186 require.NoError(t, downMock.ExpectationsWereMet()) 187 188 // 2. check many errors 189 190 commonMock() 191 createTableRowUp = sqlmock.NewRows([]string{"Table", "Create Table"}). 192 AddRow("test-table-1", `CREATE TABLE "test-table-1" ( 193 "c" int(11) NOT NULL, 194 CONSTRAINT "fk" FOREIGN KEY ("c") REFERENCES "t" ("c") 195 ) ENGINE=InnoDB DEFAULT CHARSET=latin1`) 196 mock.ExpectQuery("SHOW CREATE TABLE `test-db`.`test-table-1`").WillReturnRows(createTableRowUp) 197 createTableRow2 := sqlmock.NewRows([]string{"Table", "Create Table"}). 198 AddRow("test-table-1", `CREATE TABLE "test-table-1" ( 199 "c" int(11) NOT NULL, 200 CONSTRAINT "fk" FOREIGN KEY ("c") REFERENCES "t" ("c") 201 ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4`) 202 downMock.ExpectQuery("SHOW CREATE TABLE `test-db`.`test-table-1`").WillReturnRows(createTableRow2) 203 204 checker = NewTablesChecker( 205 map[string]*conn.BaseDB{"test-source": conn.NewBaseDBForTest(db)}, 206 conn.NewBaseDBForTest(downDB), 207 map[string]map[filter.Table][]filter.Table{ 208 "test-source": { 209 {Schema: "test-db", Name: "test-table-1"}: { 210 {Schema: "test-db", Name: "test-table-1"}, 211 }, 212 }, 213 }, 214 nil, 215 1) 216 result = checker.Check(ctx) 217 require.Equal(t, StateWarning, result.State) 218 require.Len(t, result.Errors, 2) 219 require.NoError(t, mock.ExpectationsWereMet()) 220 require.NoError(t, downMock.ExpectationsWereMet()) 221 require.False(t, strings.HasSuffix(result.Instruction, "; ")) 222 223 // 3. test #5759 224 225 commonMock() 226 createTableRow1 := sqlmock.NewRows([]string{"Table", "Create Table"}). 227 AddRow("test-table-1", `CREATE TABLE "test-table-1" ( 228 "c" int(11) NOT NULL 229 ) ENGINE=InnoDB`) 230 createTableRow2 = sqlmock.NewRows([]string{"Table", "Create Table"}). 231 AddRow("test-table-2", `CREATE TABLE "test-table-2" ( 232 "c" int(11) NOT NULL 233 ) ENGINE=InnoDB`) 234 createTableRow3 := sqlmock.NewRows([]string{"Table", "Create Table"}). 235 AddRow("test-table-3", `CREATE TABLE "test-table-3" ( 236 "c" int(11) NOT NULL 237 ) ENGINE=InnoDB`) 238 mock.ExpectQuery("SHOW CREATE TABLE `test-db`.`test-table-1`").WillReturnRows(createTableRow1) 239 downMock.ExpectQuery("SHOW CREATE TABLE `test-db`.`test-table`").WillReturnError(errNoSuchTable) 240 mock.ExpectQuery("SHOW CREATE TABLE `test-db`.`test-table-2`").WillReturnRows(createTableRow2) 241 mock.ExpectQuery("SHOW CREATE TABLE `test-db`.`test-table-3`").WillReturnRows(createTableRow3) 242 243 checker = NewTablesChecker( 244 map[string]*conn.BaseDB{"test-source": conn.NewBaseDBForTest(db)}, 245 conn.NewBaseDBForTest(downDB), 246 map[string]map[filter.Table][]filter.Table{ 247 "test-source": { 248 {Schema: "test-db", Name: "test-table"}: { 249 {Schema: "test-db", Name: "test-table-1"}, 250 {Schema: "test-db", Name: "test-table-2"}, 251 {Schema: "test-db", Name: "test-table-3"}, 252 }, 253 }, 254 }, 255 nil, 256 1) 257 result = checker.Check(ctx) 258 require.Equal(t, StateWarning, result.State) 259 require.Len(t, result.Errors, 3) 260 require.NoError(t, mock.ExpectationsWereMet()) 261 require.NoError(t, downMock.ExpectationsWereMet()) 262 263 // 4. check warning from mismatching of upstream/downstream 264 commonMock() 265 createTableRowUp = sqlmock.NewRows([]string{"Table", "Create Table"}). 266 AddRow("test-table-1", `CREATE TABLE "test-table-1" ( 267 "c" int(11) NOT NULL, 268 "d" int(11) NOT NULL, 269 "e" int(11) NOT NULL, 270 PRIMARY KEY ("c"), 271 UNIQUE KEY "idx_d" ("d") 272 ) ENGINE=InnoDB DEFAULT CHARSET=latin1 COLLATE=latin1_bin`) 273 mock.ExpectQuery("SHOW CREATE TABLE `test-db`.`test-table-1`").WillReturnRows(createTableRowUp) 274 createTableRow2 = sqlmock.NewRows([]string{"Table", "Create Table"}). 275 AddRow("test-table", `CREATE TABLE "test-table" ( 276 "c" int(11) NOT NULL, 277 "d" int(11) NOT NULL, 278 "f" int(11) DEFAULT NULL, 279 "g" int(11) NOT NULL, 280 PRIMARY KEY ("c") 281 ) ENGINE=InnoDB DEFAULT CHARSET=gbk COLLATE=gbk_chinese_ci`) 282 downMock.ExpectQuery("SHOW CREATE TABLE `test-db`.`test-table`").WillReturnRows(createTableRow2) 283 284 checker = NewTablesChecker( 285 map[string]*conn.BaseDB{"test-source": conn.NewBaseDBForTest(db)}, 286 conn.NewBaseDBForTest(downDB), 287 map[string]map[filter.Table][]filter.Table{ 288 "test-source": { 289 {Schema: "test-db", Name: "test-table"}: { 290 {Schema: "test-db", Name: "test-table-1"}, 291 }, 292 }, 293 }, 294 nil, 295 1) 296 result = checker.Check(ctx) 297 require.Equal(t, StateWarning, result.State) 298 require.Len(t, result.Errors, 5) 299 require.Equal(t, 300 "table `test-db`.`test-table-1` charset is not same, upstream: (test-table-1 latin1), downstream: (test-table gbk)", 301 result.Errors[0].ShortErr) 302 require.Equal(t, 303 "table `test-db`.`test-table-1` collation is not same, upstream: (test-table-1 latin1_bin), downstream: (test-table gbk_chinese_ci)", 304 result.Errors[1].ShortErr) 305 require.Equal(t, 306 "table `test-db`.`test-table-1` upstream has more PK or NOT NULL UK than downstream, index name: idx_d, columns: [d]", 307 result.Errors[2].ShortErr) 308 require.Equal(t, 309 "table `test-db`.`test-table-1` upstream has more columns than downstream, columns: [e]", 310 result.Errors[3].ShortErr) 311 require.Equal(t, 312 "table `test-db`.`test-table-1` downstream has more columns than upstream that require values to insert records, table name: test-table, columns: [g]", 313 result.Errors[4].ShortErr) 314 require.NoError(t, mock.ExpectationsWereMet()) 315 require.NoError(t, downMock.ExpectationsWereMet()) 316 require.False(t, strings.HasSuffix(result.Instruction, "; ")) 317 318 // 5. check extended columns 319 commonMock() 320 createTableRowUp = sqlmock.NewRows([]string{"Table", "Create Table"}). 321 AddRow("test-table-1", `CREATE TABLE "test-table-1" ( 322 "c" int(11) NOT NULL, 323 "ext1" int(11) NOT NULL, 324 PRIMARY KEY ("c") 325 ) ENGINE=InnoDB DEFAULT CHARSET=latin1 COLLATE=latin1_bin`) 326 mock.ExpectQuery("SHOW CREATE TABLE `test-db`.`test-table-1`").WillReturnRows(createTableRowUp) 327 createTableRowDown := sqlmock.NewRows([]string{"Table", "Create Table"}). 328 AddRow("test-table-1", `CREATE TABLE "test-table-1" ( 329 "c" int(11) NOT NULL, 330 "ext3" int(11) NOT NULL, 331 PRIMARY KEY ("c") 332 ) ENGINE=InnoDB DEFAULT CHARSET=latin1 COLLATE=latin1_bin`) 333 downMock.ExpectQuery("SHOW CREATE TABLE `test-db`.`test-table-1`").WillReturnRows(createTableRowDown) 334 335 checker = NewTablesChecker( 336 map[string]*conn.BaseDB{"test-source": conn.NewBaseDBForTest(db)}, 337 conn.NewBaseDBForTest(downDB), 338 map[string]map[filter.Table][]filter.Table{ 339 "test-source": { 340 {Schema: "test-db", Name: "test-table-1"}: { 341 {Schema: "test-db", Name: "test-table-1"}, 342 }, 343 }, 344 }, 345 map[filter.Table][]string{ 346 {Schema: "test-db", Name: "test-table-1"}: {"ext1", "ext2", "ext3"}, 347 }, 348 1) 349 result = checker.Check(ctx) 350 require.Equal(t, StateFailure, result.State) 351 require.Len(t, result.Errors, 2) 352 require.False(t, strings.HasSuffix(result.Instruction, "; ")) 353 require.Equal(t, 354 "table `test-db`.`test-table-1` upstream table must not contain extended column [ext1]", 355 result.Errors[0].ShortErr) 356 require.Equal(t, 357 "table `test-db`.`test-table-1` downstream table must contain extended columns [ext1 ext2]", 358 result.Errors[1].ShortErr) 359 require.Contains(t, result.Instruction, "DM automatically fills the values of extended columns. You need to remove these columns or change configuration.") 360 require.NoError(t, mock.ExpectationsWereMet()) 361 require.NoError(t, downMock.ExpectationsWereMet()) 362 } 363 364 func TestCombineInstruction(t *testing.T) { 365 db, mock, err := sqlmock.New() 366 require.NoError(t, err) 367 downDB, downMock, err := sqlmock.New() 368 require.NoError(t, err) 369 ctx := context.Background() 370 commonMock := func() { 371 maxConnectionsRow := sqlmock.NewRows([]string{"Variable_name", "Value"}). 372 AddRow("max_connections", "2") 373 mock.ExpectQuery("SHOW VARIABLES LIKE 'max_connections'").WillReturnRows(maxConnectionsRow) 374 sqlModeRow := sqlmock.NewRows([]string{"Variable_name", "Value"}). 375 AddRow("sql_mode", "ANSI_QUOTES") 376 mock.ExpectQuery("SHOW VARIABLES LIKE 'sql_mode'").WillReturnRows(sqlModeRow) 377 sqlModeRow2 := sqlmock.NewRows([]string{"Variable_name", "Value"}). 378 AddRow("sql_mode", "ANSI_QUOTES") 379 downMock.ExpectQuery("SHOW VARIABLES LIKE 'sql_mode'").WillReturnRows(sqlModeRow2) 380 } 381 382 // 1. table with foreign key & no primary key 383 commonMock() 384 createTableRowUp := sqlmock.NewRows([]string{"Table", "Create Table"}). 385 AddRow("test-table-1", `CREATE TABLE "test-table-1" ( 386 "c" int(11) NOT NULL, 387 "b" int(11) NOT NULL, 388 FOREIGN KEY ("b") REFERENCES "test-table-2" ("c") 389 ) ENGINE=InnoDB DEFAULT CHARSET=latin1`) 390 mock.ExpectQuery("SHOW CREATE TABLE `test-db`.`test-table-1`").WillReturnRows(createTableRowUp) 391 downMock.ExpectQuery("SHOW CREATE TABLE `test-db`.`test-table-1`").WillReturnError(errNoSuchTable) 392 checker := NewTablesChecker( 393 map[string]*conn.BaseDB{"test-source": conn.NewBaseDBForTest(db)}, 394 conn.NewBaseDBForTest(downDB), 395 map[string]map[filter.Table][]filter.Table{ 396 "test-source": { 397 {Schema: "test-db", Name: "test-table-1"}: { 398 {Schema: "test-db", Name: "test-table-1"}, 399 }, 400 }, 401 }, 402 nil, 403 1) 404 result := checker.Check(ctx) 405 require.Equal(t, StateWarning, result.State) 406 require.Contains(t, result.Instruction, "TiDB does not support foreign key constraints. See the document: https://docs.pingcap.com/tidb/stable/mysql-compatibility#unsupported-features") 407 require.Contains(t, result.Instruction, "You need to set primary/unique keys for the table. Otherwise replication efficiency might become very low and exactly-once replication cannot be guaranteed.") 408 require.Contains(t, result.Errors[0].ShortErr, "is parsed but ignored by TiDB.") 409 require.Contains(t, result.Errors[1].ShortErr, "primary/unique key does not exist") 410 require.NoError(t, mock.ExpectationsWereMet()) 411 require.NoError(t, downMock.ExpectationsWereMet()) 412 413 // 2. mismatched index columns 414 commonMock() 415 createTableRowUp = sqlmock.NewRows([]string{"Table", "Create Table"}). 416 AddRow("test-table-1", `CREATE TABLE "test-table-1" ( 417 "c" int(11) NOT NULL, 418 "b" int(11) NOT NULL, 419 "d" int(11) NOT NULL, 420 PRIMARY KEY("c", "b", "d") 421 ) ENGINE=InnoDB DEFAULT CHARSET=latin1`) 422 createTableDown := sqlmock.NewRows([]string{"Table", "Create Table"}). 423 AddRow("test-table-1", `CREATE TABLE "test-table-1" ( 424 "c" int(11) NOT NULL, 425 "b" int(11) NOT NULL, 426 "d" int(11) NOT NULL, 427 PRIMARY KEY("c") 428 ) ENGINE=InnoDB DEFAULT CHARSET=latin1`) 429 mock.ExpectQuery("SHOW CREATE TABLE `test-db`.`test-table-1`").WillReturnRows(createTableRowUp) 430 downMock.ExpectQuery("SHOW CREATE TABLE `test-db`.`test-table-1`").WillReturnRows(createTableDown) 431 checker = NewTablesChecker( 432 map[string]*conn.BaseDB{"test-source": conn.NewBaseDBForTest(db)}, 433 conn.NewBaseDBForTest(downDB), 434 map[string]map[filter.Table][]filter.Table{ 435 "test-source": { 436 {Schema: "test-db", Name: "test-table-1"}: { 437 {Schema: "test-db", Name: "test-table-1"}, 438 }, 439 }, 440 }, 441 nil, 442 1) 443 result = checker.Check(ctx) 444 require.Equal(t, StateWarning, result.State) 445 require.Equal(t, result.State, StateWarning) 446 require.Contains(t, result.Errors[0].ShortErr, "upstream has more PK or NOT NULL UK than downstream") 447 require.Contains(t, result.Instruction, "Ensure that you use the same index columns for both upstream and downstream databases. Otherwise the migration job might fail or data inconsistency might occur.") 448 require.NoError(t, mock.ExpectationsWereMet()) 449 require.NoError(t, downMock.ExpectationsWereMet()) 450 451 // 3. charset not same or collation not same 452 commonMock() 453 createTableRowUp = sqlmock.NewRows([]string{"Table", "Create Table"}). 454 AddRow("test-table-1", `CREATE TABLE "test-table-1" ( 455 "c" int(11) NOT NULL, 456 "b" int(11) NOT NULL, 457 "d" int(11) NOT NULL, 458 PRIMARY KEY("c") 459 ) ENGINE=InnoDB DEFAULT CHARSET=latin1 COLLATE=latin1_bin`) 460 createTableDown = sqlmock.NewRows([]string{"Table", "Create Table"}). 461 AddRow("test-table-1", `CREATE TABLE "test-table-1" ( 462 "c" int(11) NOT NULL, 463 "b" int(11) NOT NULL, 464 "d" int(11) NOT NULL, 465 PRIMARY KEY("c") 466 ) ENGINE=InnoDB DEFAULT CHARSET=binary COLLATE=binary`) 467 mock.ExpectQuery("SHOW CREATE TABLE `test-db`.`test-table-1`").WillReturnRows(createTableRowUp) 468 downMock.ExpectQuery("SHOW CREATE TABLE `test-db`.`test-table-1`").WillReturnRows(createTableDown) 469 checker = NewTablesChecker( 470 map[string]*conn.BaseDB{"test-source": conn.NewBaseDBForTest(db)}, 471 conn.NewBaseDBForTest(downDB), 472 map[string]map[filter.Table][]filter.Table{ 473 "test-source": { 474 {Schema: "test-db", Name: "test-table-1"}: { 475 {Schema: "test-db", Name: "test-table-1"}, 476 }, 477 }, 478 }, 479 nil, 480 1) 481 result = checker.Check(ctx) 482 require.Equal(t, result.State, StateWarning) 483 require.Contains(t, result.Errors[0].ShortErr, "charset is not same") 484 require.Contains(t, result.Errors[1].ShortErr, "collation is not same") 485 require.Contains(t, result.Instruction, "Ensure that you use the same charsets for both upstream and downstream databases. Different charsets might cause data inconsistency.") 486 require.Contains(t, result.Instruction, "Ensure that you use the same collations for both upstream and downstream databases. Otherwise the query results from the two databases might be inconsistent.") 487 require.NoError(t, mock.ExpectationsWereMet()) 488 require.NoError(t, downMock.ExpectationsWereMet()) 489 490 // 4. different column number 491 commonMock() 492 createTableRowUp = sqlmock.NewRows([]string{"Table", "Create Table"}). 493 AddRow("test-table-1", `CREATE TABLE "test-table-1" ( 494 "c" int(11) NOT NULL, 495 "b" int(11) NOT NULL, 496 "d" int(11) NOT NULL, 497 PRIMARY KEY("c") 498 ) ENGINE=InnoDB`) 499 createTableDown = sqlmock.NewRows([]string{"Table", "Create Table"}). 500 AddRow("test-table-1", `CREATE TABLE "test-table-1" ( 501 "c" int(11) NOT NULL, 502 "b" int(11) NOT NULL, 503 PRIMARY KEY("c") 504 ) ENGINE=InnoDB`) 505 mock.ExpectQuery("SHOW CREATE TABLE `test-db`.`test-table-1`").WillReturnRows(createTableRowUp) 506 downMock.ExpectQuery("SHOW CREATE TABLE `test-db`.`test-table-1`").WillReturnRows(createTableDown) 507 checker = NewTablesChecker( 508 map[string]*conn.BaseDB{"test-source": conn.NewBaseDBForTest(db)}, 509 conn.NewBaseDBForTest(downDB), 510 map[string]map[filter.Table][]filter.Table{ 511 "test-source": { 512 {Schema: "test-db", Name: "test-table-1"}: { 513 {Schema: "test-db", Name: "test-table-1"}, 514 }, 515 }, 516 }, 517 nil, 518 1) 519 result = checker.Check(ctx) 520 require.Equal(t, StateWarning, result.State) 521 require.Contains(t, result.Errors[0].ShortErr, "upstream has more columns than downstream") 522 require.Contains(t, result.Instruction, "Ensure that the column numbers are the same between upstream and downstream databases. Otherwise the migration job may fail.") 523 require.NoError(t, mock.ExpectationsWereMet()) 524 require.NoError(t, downMock.ExpectationsWereMet()) 525 526 // 5. upstream has extended column & downstream doesn't have extended column 527 commonMock() 528 createTableRowUp = sqlmock.NewRows([]string{"Table", "Create Table"}). 529 AddRow("test-table-1", `CREATE TABLE "test-table-1" ( 530 "c" int(11) NOT NULL, 531 "b" int(11) NOT NULL, 532 "d" int(11) NOT NULL, 533 PRIMARY KEY("c") 534 ) ENGINE=InnoDB`) 535 createTableDown = sqlmock.NewRows([]string{"Table", "Create Table"}). 536 AddRow("test-table-1", `CREATE TABLE "test-table-1" ( 537 "c" int(11) NOT NULL, 538 "b" int(11) NOT NULL, 539 "e" int(11) NOT NULL, 540 PRIMARY KEY("c") 541 ) ENGINE=InnoDB`) 542 mock.ExpectQuery("SHOW CREATE TABLE `test-db`.`test-table-1`").WillReturnRows(createTableRowUp) 543 downMock.ExpectQuery("SHOW CREATE TABLE `test-db`.`test-table-1`").WillReturnRows(createTableDown) 544 checker = NewTablesChecker( 545 map[string]*conn.BaseDB{"test-source": conn.NewBaseDBForTest(db)}, 546 conn.NewBaseDBForTest(downDB), 547 map[string]map[filter.Table][]filter.Table{ 548 "test-source": { 549 {Schema: "test-db", Name: "test-table-1"}: { 550 {Schema: "test-db", Name: "test-table-1"}, 551 }, 552 }, 553 }, 554 map[filter.Table][]string{ 555 {Schema: "test-db", Name: "test-table-1"}: {"d"}, 556 }, 557 1) // extended column "d" 558 result = checker.Check(ctx) 559 require.Equal(t, StateFailure, result.State) 560 require.Contains(t, result.Errors[0].ShortErr, "upstream table must not contain extended column") 561 require.Contains(t, result.Errors[1].ShortErr, "downstream table must contain extended columns") 562 require.Contains(t, result.Instruction, "DM automatically fills the values of extended columns. You need to remove these columns or change configuration.") 563 require.Contains(t, result.Instruction, "You need to manually add extended columns to the downstream table.") 564 require.NoError(t, mock.ExpectationsWereMet()) 565 require.NoError(t, downMock.ExpectationsWereMet()) 566 567 // 6. no downstream table with extended column 568 commonMock() 569 createTableRowUp = sqlmock.NewRows([]string{"Table", "Create Table"}). 570 AddRow("test-table-1", `CREATE TABLE "test-table-1" ( 571 "c" int(11) NOT NULL, 572 "b" int(11) NOT NULL, 573 PRIMARY KEY("c") 574 ) ENGINE=InnoDB`) 575 mock.ExpectQuery("SHOW CREATE TABLE `test-db`.`test-table-1`").WillReturnRows(createTableRowUp) 576 downMock.ExpectQuery("SHOW CREATE TABLE `test-db`.`test-table-1`").WillReturnError(errNoSuchTable) 577 checker = NewTablesChecker( 578 map[string]*conn.BaseDB{"test-source": conn.NewBaseDBForTest(db)}, 579 conn.NewBaseDBForTest(downDB), 580 map[string]map[filter.Table][]filter.Table{ 581 "test-source": { 582 {Schema: "test-db", Name: "test-table-1"}: { 583 {Schema: "test-db", Name: "test-table-1"}, 584 }, 585 }, 586 }, 587 map[filter.Table][]string{ 588 {Schema: "test-db", Name: "test-table-1"}: {"d"}, 589 }, 590 1) // extended column "d" 591 result = checker.Check(ctx) 592 require.Equal(t, StateFailure, result.State) 593 require.Contains(t, result.Errors[0].ShortErr, "does not exist in downstream table") 594 require.Contains(t, result.Instruction, "You need to create a table with extended columns before replication.") 595 require.NoError(t, mock.ExpectationsWereMet()) 596 require.NoError(t, downMock.ExpectationsWereMet()) 597 } 598 599 func TestOptimisticShardingTablesChecker(t *testing.T) { 600 db, mock, err := sqlmock.New() 601 require.NoError(t, err) 602 ctx := context.Background() 603 604 cases := []struct { 605 createTable1SQL string 606 createTable2SQL string 607 expectState State 608 errLen int 609 }{ 610 // optimistic check different column number 611 { 612 createTable1SQL: `CREATE TABLE "test-table-1" ( 613 "c" int(11) NOT NULL, 614 PRIMARY KEY ("c") 615 ) ENGINE=InnoDB DEFAULT CHARSET=latin1`, 616 createTable2SQL: `CREATE TABLE "test-table-2" ( 617 "c" int(11) NOT NULL, 618 "d" int(11) NOT NULL, 619 PRIMARY KEY ("c") 620 ) ENGINE=InnoDB DEFAULT CHARSET=latin1`, 621 expectState: StateSuccess, 622 }, 623 // optimistic check auto_increment conflict 624 { 625 createTable1SQL: `CREATE TABLE "test-table-1" ( 626 "c" int(11) NOT NULL AUTO_INCREMENT, 627 PRIMARY KEY ("c") 628 ) ENGINE=InnoDB DEFAULT CHARSET=latin1`, 629 createTable2SQL: `CREATE TABLE "test-table-2" ( 630 "c" int(11) NOT NULL AUTO_INCREMENT, 631 "d" int(11) NOT NULL, 632 PRIMARY KEY ("c") 633 ) ENGINE=InnoDB DEFAULT CHARSET=latin1`, 634 expectState: StateWarning, 635 errLen: 2, // 2 auto_increment warning 636 }, 637 { 638 createTable1SQL: `CREATE TABLE "test-table-1" ( 639 "c" int(11) NOT NULL AUTO_INCREMENT, 640 PRIMARY KEY ("c") 641 ) ENGINE=InnoDB DEFAULT CHARSET=latin1`, 642 createTable2SQL: `CREATE TABLE "test-table-2" ( 643 "c" int(11) NOT NULL, 644 "d" int(11) NOT NULL, 645 PRIMARY KEY ("c") 646 ) ENGINE=InnoDB DEFAULT CHARSET=latin1`, 647 expectState: StateWarning, 648 errLen: 1, // 1 auto_increment warning 649 }, 650 // must set auto_increment with key(failure) 651 { 652 createTable1SQL: `CREATE TABLE "test-table-1" ( 653 "c" int(11) NOT NULL AUTO_INCREMENT 654 ) ENGINE=InnoDB DEFAULT CHARSET=latin1`, 655 createTable2SQL: `CREATE TABLE "test-table-2" ( 656 "c" int(11) NOT NULL, 657 "d" int(11) NOT NULL 658 ) ENGINE=InnoDB DEFAULT CHARSET=latin1`, 659 expectState: StateFailure, 660 errLen: 2, // 1 auto_increment warning 661 }, 662 { 663 createTable1SQL: `CREATE TABLE "test-table-1" ( 664 "c" int(11) NOT NULL AUTO_INCREMENT, 665 PRIMARY KEY ("c") 666 ) ENGINE=InnoDB DEFAULT CHARSET=latin1`, 667 createTable2SQL: `CREATE TABLE "test-table-2" ( 668 "c" int(11) NOT NULL, 669 "d" int(11) NOT NULL 670 ) ENGINE=InnoDB DEFAULT CHARSET=latin1`, 671 expectState: StateFailure, 672 errLen: 2, // 1 auto_increment warning 673 }, 674 // different auto_increment 675 { 676 createTable1SQL: `CREATE TABLE "test-table-1" ( 677 "c" int(11) NOT NULL AUTO_INCREMENT, 678 PRIMARY KEY ("c") 679 ) ENGINE=InnoDB DEFAULT CHARSET=latin1`, 680 createTable2SQL: `CREATE TABLE "test-table-2" ( 681 "c" int(11) NOT NULL, 682 "d" int(11) NOT NULL AUTO_INCREMENT, 683 PRIMARY KEY ("d") 684 ) ENGINE=InnoDB DEFAULT CHARSET=latin1`, 685 expectState: StateFailure, 686 errLen: 3, // 2 auto_increment warning 687 }, 688 } 689 690 for _, cs := range cases { 691 maxConnecionsRow := sqlmock.NewRows([]string{"Variable_name", "Value"}).AddRow("max_connections", "2") 692 mock.ExpectQuery("SHOW VARIABLES LIKE 'max_connections'").WillReturnRows(maxConnecionsRow) 693 sqlModeRow := sqlmock.NewRows([]string{"Variable_name", "Value"}).AddRow("sql_mode", "ANSI_QUOTES") 694 mock.ExpectQuery("SHOW VARIABLES LIKE 'sql_mode'").WillReturnRows(sqlModeRow) 695 createTableRow1 := sqlmock.NewRows([]string{"Table", "Create Table"}).AddRow("test-table-1", cs.createTable1SQL) 696 mock.ExpectQuery("SHOW CREATE TABLE `test-db`.`test-table-1`").WillReturnRows(createTableRow1) 697 createTableRow2 := sqlmock.NewRows([]string{"Table", "Create Table"}).AddRow("test-table-2", cs.createTable2SQL) 698 mock.ExpectQuery("SHOW CREATE TABLE `test-db`.`test-table-2`").WillReturnRows(createTableRow2) 699 checker := NewOptimisticShardingTablesChecker( 700 "test-name", 701 map[string]*conn.BaseDB{"test-source": conn.NewBaseDBForTest(db)}, 702 map[string][]filter.Table{"test-source": { 703 {Schema: "test-db", Name: "test-table-1"}, 704 {Schema: "test-db", Name: "test-table-2"}, 705 }}, 706 0) 707 result := checker.Check(ctx) 708 require.Equal(t, cs.expectState, result.State) 709 require.Len(t, result.Errors, cs.errLen) 710 require.NoError(t, mock.ExpectationsWereMet()) 711 } 712 } 713 714 func TestUnknownCharsetCollation(t *testing.T) { 715 db, mock, err := sqlmock.New() 716 require.NoError(t, err) 717 downDB, downMock, err := sqlmock.New() 718 require.NoError(t, err) 719 ctx := context.Background() 720 721 // 1. test TablesChecker 722 723 maxConnectionsRow := sqlmock.NewRows([]string{"Variable_name", "Value"}). 724 AddRow("max_connections", "2") 725 mock.ExpectQuery("SHOW VARIABLES LIKE 'max_connections'").WillReturnRows(maxConnectionsRow) 726 sqlModeRow := sqlmock.NewRows([]string{"Variable_name", "Value"}). 727 AddRow("sql_mode", "ANSI_QUOTES") 728 mock.ExpectQuery("SHOW VARIABLES LIKE 'sql_mode'").WillReturnRows(sqlModeRow) 729 createTableRow := sqlmock.NewRows([]string{"Table", "Create Table"}). 730 AddRow("test-table-1", `CREATE TABLE "test-table-1" ( 731 "c" int(11) NOT NULL, 732 PRIMARY KEY ("c") 733 ) ENGINE=InnoDB DEFAULT CHARSET=utf32`) 734 mock.ExpectQuery("SHOW CREATE TABLE `test-db`.`test-table-1`").WillReturnRows(createTableRow) 735 sqlModeRow2 := sqlmock.NewRows([]string{"Variable_name", "Value"}). 736 AddRow("sql_mode", "ANSI_QUOTES") 737 downMock.ExpectQuery("SHOW VARIABLES LIKE 'sql_mode'").WillReturnRows(sqlModeRow2) 738 739 checker := NewTablesChecker( 740 map[string]*conn.BaseDB{"test-source": conn.NewBaseDBForTest(db)}, 741 conn.NewBaseDBForTest(downDB), 742 map[string]map[filter.Table][]filter.Table{ 743 "test-source": { 744 {Schema: "test-db", Name: "test-table-1"}: { 745 {Schema: "test-db", Name: "test-table-1"}, 746 }, 747 }, 748 }, 749 nil, 750 1) 751 result := checker.Check(ctx) 752 require.Equal(t, StateWarning, result.State) 753 require.Len(t, result.Errors, 1) 754 require.Contains(t, result.Errors[0].ShortErr, "Unknown character set: 'utf32'") 755 require.NoError(t, mock.ExpectationsWereMet()) 756 require.NoError(t, downMock.ExpectationsWereMet()) 757 758 // 2. test ShardingTablesChecker 759 // 2.1 the first table has unknown charset 760 761 sqlModeRow = sqlmock.NewRows([]string{"Variable_name", "Value"}). 762 AddRow("sql_mode", "ANSI_QUOTES") 763 mock.ExpectQuery("SHOW VARIABLES LIKE 'sql_mode'").WillReturnRows(sqlModeRow) 764 createTableRow = sqlmock.NewRows([]string{"Table", "Create Table"}). 765 AddRow("test-table-1", `CREATE TABLE "test-table-1" ( 766 "c" int(11) NOT NULL, 767 PRIMARY KEY ("c") 768 ) ENGINE=InnoDB DEFAULT CHARSET=utf16`) 769 mock.ExpectQuery("SHOW CREATE TABLE `test-db`.`test-table-1`").WillReturnRows(createTableRow) 770 771 checker = NewShardingTablesChecker("test-name", 772 map[string]*conn.BaseDB{"test-source": conn.NewBaseDBForTest(db)}, 773 map[string][]filter.Table{"test-source": { 774 {Schema: "test-db", Name: "test-table-1"}, 775 {Schema: "test-db", Name: "test-table-2"}, 776 }}, 777 false, 778 1) 779 result = checker.Check(ctx) 780 require.Equal(t, StateWarning, result.State) 781 require.Len(t, result.Errors, 1) 782 require.Contains(t, result.Errors[0].ShortErr, "Unknown character set: 'utf16'") 783 require.NoError(t, mock.ExpectationsWereMet()) 784 785 // 2.2 not the first table has unknown charset 786 787 mock = initShardingMock(mock) 788 createTableRow2 := sqlmock.NewRows([]string{"Table", "Create Table"}). 789 AddRow("test-table-2", `CREATE TABLE "test-table-2" ( 790 "c" int(11) NOT NULL, 791 PRIMARY KEY ("c") 792 ) ENGINE=InnoDB DEFAULT CHARSET=utf16`) 793 mock.ExpectQuery("SHOW CREATE TABLE `test-db`.`test-table-2`").WillReturnRows(createTableRow2) 794 795 checker = NewShardingTablesChecker("test-name", 796 map[string]*conn.BaseDB{"test-source": conn.NewBaseDBForTest(db)}, 797 map[string][]filter.Table{"test-source": { 798 {Schema: "test-db", Name: "test-table-1"}, 799 {Schema: "test-db", Name: "test-table-2"}, 800 }}, 801 false, 802 1) 803 result = checker.Check(ctx) 804 require.Equal(t, StateWarning, result.State) 805 require.Len(t, result.Errors, 1) 806 require.Contains(t, result.Errors[0].ShortErr, "Unknown character set: 'utf16'") 807 require.NoError(t, mock.ExpectationsWereMet()) 808 809 // 2.3 not the first table has unknown collation 810 811 mock = initShardingMock(mock) 812 createTableRow2 = sqlmock.NewRows([]string{"Table", "Create Table"}). 813 AddRow("test-table-2", `CREATE TABLE "test-table-2" ( 814 "c" int(11) NOT NULL, 815 PRIMARY KEY ("c") 816 ) ENGINE=InnoDB DEFAULT CHARSET=latin1 COLLATE=latin1_german1_ci`) 817 mock.ExpectQuery("SHOW CREATE TABLE `test-db`.`test-table-2`").WillReturnRows(createTableRow2) 818 819 checker = NewShardingTablesChecker("test-name", 820 map[string]*conn.BaseDB{"test-source": conn.NewBaseDBForTest(db)}, 821 map[string][]filter.Table{"test-source": { 822 {Schema: "test-db", Name: "test-table-1"}, 823 {Schema: "test-db", Name: "test-table-2"}, 824 }}, 825 false, 826 1) 827 result = checker.Check(ctx) 828 // unknown collation will not raise error during parsing 829 require.Equal(t, StateSuccess, result.State) 830 require.NoError(t, mock.ExpectationsWereMet()) 831 832 // 3. test OptimisticShardingTablesChecker 833 834 maxConnecionsRow := sqlmock.NewRows([]string{"Variable_name", "Value"}).AddRow("max_connections", "2") 835 mock.ExpectQuery("SHOW VARIABLES LIKE 'max_connections'").WillReturnRows(maxConnecionsRow) 836 sqlModeRow = sqlmock.NewRows([]string{"Variable_name", "Value"}).AddRow("sql_mode", "ANSI_QUOTES") 837 mock.ExpectQuery("SHOW VARIABLES LIKE 'sql_mode'").WillReturnRows(sqlModeRow) 838 createTableRow = sqlmock.NewRows([]string{"Table", "Create Table"}).AddRow("test-table-1", ` 839 CREATE TABLE "test-table-1" ( 840 "c" int(11) NOT NULL, 841 PRIMARY KEY ("c") 842 ) ENGINE=InnoDB DEFAULT CHARSET=utf16`) 843 mock.ExpectQuery("SHOW CREATE TABLE `test-db`.`test-table-1`").WillReturnRows(createTableRow) 844 createTableRow2 = sqlmock.NewRows([]string{"Table", "Create Table"}).AddRow("test-table-2", ` 845 CREATE TABLE "test-table-2" ( 846 "c" int(11) NOT NULL, 847 PRIMARY KEY ("c") 848 ) ENGINE=InnoDB DEFAULT CHARSET=utf16`) 849 mock.ExpectQuery("SHOW CREATE TABLE `test-db`.`test-table-2`").WillReturnRows(createTableRow2) 850 checker = NewOptimisticShardingTablesChecker( 851 "test-name", 852 map[string]*conn.BaseDB{"test-source": conn.NewBaseDBForTest(db)}, 853 map[string][]filter.Table{"test-source": { 854 {Schema: "test-db", Name: "test-table-1"}, 855 {Schema: "test-db", Name: "test-table-2"}, 856 }}, 857 0) 858 result = checker.Check(ctx) 859 require.Equal(t, StateWarning, result.State) 860 require.Len(t, result.Errors, 2) 861 require.Contains(t, result.Errors[0].ShortErr, "Unknown character set: 'utf16'") 862 require.Contains(t, result.Errors[1].ShortErr, "Unknown character set: 'utf16'") 863 require.NoError(t, mock.ExpectationsWereMet()) 864 } 865 866 func initShardingMock(mock sqlmock.Sqlmock) sqlmock.Sqlmock { 867 sqlModeRow := sqlmock.NewRows([]string{"Variable_name", "Value"}). 868 AddRow("sql_mode", "ANSI_QUOTES") 869 mock.ExpectQuery("SHOW VARIABLES LIKE 'sql_mode'").WillReturnRows(sqlModeRow) 870 createTableRow := sqlmock.NewRows([]string{"Table", "Create Table"}). 871 AddRow("test-table-1", `CREATE TABLE "test-table-1" ( 872 "c" int(11) NOT NULL, 873 PRIMARY KEY ("c") 874 ) ENGINE=InnoDB DEFAULT CHARSET=latin1`) 875 mock.ExpectQuery("SHOW CREATE TABLE `test-db`.`test-table-1`").WillReturnRows(createTableRow) 876 877 maxConnecionsRow := sqlmock.NewRows([]string{"Variable_name", "Value"}). 878 AddRow("max_connections", "2") 879 mock.ExpectQuery("SHOW VARIABLES LIKE 'max_connections'").WillReturnRows(maxConnecionsRow) 880 sqlModeRow = sqlmock.NewRows([]string{"Variable_name", "Value"}). 881 AddRow("sql_mode", "ANSI_QUOTES") 882 mock.ExpectQuery("SHOW VARIABLES LIKE 'sql_mode'").WillReturnRows(sqlModeRow) 883 createTableRow = sqlmock.NewRows([]string{"Table", "Create Table"}). 884 AddRow("test-table-1", `CREATE TABLE "test-table-1" ( 885 "c" int(11) NOT NULL, 886 PRIMARY KEY ("c") 887 ) ENGINE=InnoDB DEFAULT CHARSET=latin1`) 888 mock.ExpectQuery("SHOW CREATE TABLE `test-db`.`test-table-1`").WillReturnRows(createTableRow) 889 return mock 890 } 891 892 func TestExpressionUK(t *testing.T) { 893 db, mock, err := sqlmock.New() 894 require.NoError(t, err) 895 downDB, downMock, err := sqlmock.New() 896 require.NoError(t, err) 897 ctx := context.Background() 898 899 // test same table structure 900 901 maxConnectionsRow := sqlmock.NewRows([]string{"Variable_name", "Value"}). 902 AddRow("max_connections", "2") 903 mock.ExpectQuery("SHOW VARIABLES LIKE 'max_connections'").WillReturnRows(maxConnectionsRow) 904 sqlModeRow := sqlmock.NewRows([]string{"Variable_name", "Value"}). 905 AddRow("sql_mode", "ANSI_QUOTES") 906 mock.ExpectQuery("SHOW VARIABLES LIKE 'sql_mode'").WillReturnRows(sqlModeRow) 907 createTableRow := sqlmock.NewRows([]string{"Table", "Create Table"}). 908 AddRow("test-table-1", `CREATE TABLE "test-table-1" ( 909 "c" int(11) NOT NULL, 910 "c2" int(11) NOT NULL, 911 PRIMARY KEY ("c"), 912 UNIQUE KEY "uk" (("c2"+1), "c") 913 ) ENGINE=InnoDB`) 914 mock.ExpectQuery("SHOW CREATE TABLE `test-db`.`test-table-1`").WillReturnRows(createTableRow) 915 sqlModeRow2 := sqlmock.NewRows([]string{"Variable_name", "Value"}). 916 AddRow("sql_mode", "ANSI_QUOTES") 917 downMock.ExpectQuery("SHOW VARIABLES LIKE 'sql_mode'").WillReturnRows(sqlModeRow2) 918 createTableRow2 := sqlmock.NewRows([]string{"Table", "Create Table"}). 919 AddRow("test-table-1", `CREATE TABLE "test-table-1" ( 920 "c" int(11) NOT NULL, 921 "c2" int(11) NOT NULL, 922 PRIMARY KEY ("c"), 923 UNIQUE KEY "uk" (("c2"+1), "c") 924 ) ENGINE=InnoDB`) 925 downMock.ExpectQuery("SHOW CREATE TABLE `test-db`.`test-table-1`").WillReturnRows(createTableRow2) 926 927 checker := NewTablesChecker( 928 map[string]*conn.BaseDB{"test-source": conn.NewBaseDBForTest(db)}, 929 conn.NewBaseDBForTest(downDB), 930 map[string]map[filter.Table][]filter.Table{ 931 "test-source": { 932 {Schema: "test-db", Name: "test-table-1"}: { 933 {Schema: "test-db", Name: "test-table-1"}, 934 }, 935 }, 936 }, 937 nil, 938 1) 939 result := checker.Check(ctx) 940 require.Equal(t, StateSuccess, result.State) 941 require.NoError(t, mock.ExpectationsWereMet()) 942 require.NoError(t, downMock.ExpectationsWereMet()) 943 944 // test different table structure 945 946 maxConnectionsRow = sqlmock.NewRows([]string{"Variable_name", "Value"}). 947 AddRow("max_connections", "2") 948 mock.ExpectQuery("SHOW VARIABLES LIKE 'max_connections'").WillReturnRows(maxConnectionsRow) 949 sqlModeRow = sqlmock.NewRows([]string{"Variable_name", "Value"}). 950 AddRow("sql_mode", "ANSI_QUOTES") 951 mock.ExpectQuery("SHOW VARIABLES LIKE 'sql_mode'").WillReturnRows(sqlModeRow) 952 createTableRow = sqlmock.NewRows([]string{"Table", "Create Table"}). 953 AddRow("test-table-1", `CREATE TABLE "test-table-1" ( 954 "c" int(11) NOT NULL, 955 "c2" int(11) NOT NULL, 956 PRIMARY KEY ("c"), 957 UNIQUE KEY "uk" (("c2"+1), "c") 958 ) ENGINE=InnoDB`) 959 mock.ExpectQuery("SHOW CREATE TABLE `test-db`.`test-table-1`").WillReturnRows(createTableRow) 960 sqlModeRow2 = sqlmock.NewRows([]string{"Variable_name", "Value"}). 961 AddRow("sql_mode", "ANSI_QUOTES") 962 downMock.ExpectQuery("SHOW VARIABLES LIKE 'sql_mode'").WillReturnRows(sqlModeRow2) 963 createTableRow2 = sqlmock.NewRows([]string{"Table", "Create Table"}). 964 AddRow("test-table-1", `CREATE TABLE "test-table-1" ( 965 "c" int(11) NOT NULL, 966 "c2" int(11) NOT NULL, 967 PRIMARY KEY ("c"), 968 UNIQUE KEY "uk" (("c2"+3), "c") 969 ) ENGINE=InnoDB`) 970 downMock.ExpectQuery("SHOW CREATE TABLE `test-db`.`test-table-1`").WillReturnRows(createTableRow2) 971 972 checker = NewTablesChecker( 973 map[string]*conn.BaseDB{"test-source": conn.NewBaseDBForTest(db)}, 974 conn.NewBaseDBForTest(downDB), 975 map[string]map[filter.Table][]filter.Table{ 976 "test-source": { 977 {Schema: "test-db", Name: "test-table-1"}: { 978 {Schema: "test-db", Name: "test-table-1"}, 979 }, 980 }, 981 }, 982 nil, 983 1) 984 result = checker.Check(ctx) 985 require.Equal(t, StateWarning, result.State) 986 require.Len(t, result.Errors, 2) 987 // maybe [`c2`+1 c] or [c `c2`+1] 988 require.Contains(t, result.Errors[0].ShortErr, "upstream has more PK or NOT NULL UK than downstream") 989 require.Contains(t, result.Errors[0].ShortErr, "`c2`+1") 990 require.Contains(t, result.Errors[1].ShortErr, "downstream has more PK or NOT NULL UK than upstream") 991 require.Contains(t, result.Errors[1].ShortErr, "`c2`+3") 992 require.NoError(t, mock.ExpectationsWereMet()) 993 require.NoError(t, downMock.ExpectationsWereMet()) 994 }