github.com/pingcap/tiflow@v0.0.0-20240520035814-5bf52d54e205/dm/checker/check_test.go (about) 1 // Copyright 2019 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 "fmt" 19 "testing" 20 21 "github.com/DATA-DOG/go-sqlmock" 22 gmysql "github.com/go-sql-driver/mysql" 23 "github.com/pingcap/tidb/pkg/parser/mysql" 24 router "github.com/pingcap/tidb/pkg/util/table-router" 25 "github.com/pingcap/tiflow/dm/config" 26 "github.com/pingcap/tiflow/dm/ctl/common" 27 "github.com/pingcap/tiflow/dm/pkg/conn" 28 "github.com/pingcap/tiflow/dm/pkg/cputil" 29 "github.com/stretchr/testify/require" 30 ) 31 32 var ( 33 schema = "db_1" 34 tb1 = "t_1" 35 tb2 = "t_2" 36 metaSchema = "dm_meta" 37 taskName = "test" 38 ) 39 40 func ignoreExcept(itemMap map[string]struct{}) []string { 41 items := []string{ 42 config.DumpPrivilegeChecking, 43 config.ReplicationPrivilegeChecking, 44 config.VersionChecking, 45 config.ServerIDChecking, 46 config.BinlogEnableChecking, 47 config.BinlogFormatChecking, 48 config.BinlogRowImageChecking, 49 config.TableSchemaChecking, 50 config.ShardTableSchemaChecking, 51 config.ShardAutoIncrementIDChecking, 52 config.OnlineDDLChecking, 53 config.BinlogDBChecking, 54 config.MetaPositionChecking, 55 config.ConnNumberChecking, 56 config.TargetDBPrivilegeChecking, 57 config.LightningEmptyRegionChecking, 58 config.LightningRegionDistributionChecking, 59 config.LightningDownstreamVersionChecking, 60 config.LightningFreeSpaceChecking, 61 config.LightningMutexFeatureChecking, 62 config.LightningTableEmptyChecking, 63 } 64 ignoreCheckingItems := make([]string, 0, len(items)-len(itemMap)) 65 for _, i := range items { 66 if _, ok := itemMap[i]; !ok { 67 ignoreCheckingItems = append(ignoreCheckingItems, i) 68 } 69 } 70 return ignoreCheckingItems 71 } 72 73 func TestIgnoreAllCheckingItems(t *testing.T) { 74 msg, err := CheckSyncConfig(context.Background(), nil, common.DefaultErrorCnt, common.DefaultWarnCnt) 75 require.NoError(t, err) 76 require.Len(t, msg, 0) 77 78 result, err := RunCheckOnConfigs(context.Background(), nil, false, 100, 100) 79 require.NoError(t, err) 80 require.Nil(t, result) 81 82 cfgs := []*config.SubTaskConfig{ 83 { 84 Mode: config.ModeAll, 85 IgnoreCheckingItems: []string{config.AllChecking}, 86 }, 87 } 88 msg, err = CheckSyncConfig(context.Background(), cfgs, common.DefaultErrorCnt, common.DefaultWarnCnt) 89 require.NoError(t, err) 90 require.Len(t, msg, 0) 91 92 result, err = RunCheckOnConfigs(context.Background(), cfgs, false, 100, 100) 93 require.NoError(t, err) 94 require.Nil(t, result) 95 } 96 97 func TestDumpPrivilegeChecking(t *testing.T) { 98 cfgs := []*config.SubTaskConfig{ 99 { 100 Mode: config.ModeAll, 101 IgnoreCheckingItems: ignoreExcept(map[string]struct{}{config.DumpPrivilegeChecking: {}}), 102 }, 103 } 104 105 // test not enough privileges 106 107 mock := initMockDB(t) 108 mock.ExpectQuery("SHOW GRANTS").WillReturnRows(sqlmock.NewRows([]string{"Grants for User"}). 109 AddRow("GRANT USAGE ON *.* TO 'haha'@'%'")) 110 msg, err := CheckSyncConfig(context.Background(), cfgs, common.DefaultErrorCnt, common.DefaultWarnCnt) 111 require.Regexp(t, "(.|\n)*lack.*Select(.|\n)*", err.Error()) 112 require.Len(t, msg, 0) 113 114 mock = initMockDB(t) 115 mock.ExpectQuery("SHOW GRANTS").WillReturnRows(sqlmock.NewRows([]string{"Grants for User"}). 116 AddRow("GRANT USAGE ON *.* TO 'haha'@'%'")) 117 result, err := RunCheckOnConfigs(context.Background(), cfgs, false, 100, 100) 118 require.NoError(t, err) 119 require.Equal(t, int64(1), result.Summary.Failed) 120 require.Contains(t, result.Results[0].Errors[0].ShortErr, "lack of Select privilege") 121 122 // test dumpWholeInstance 123 124 mock = initMockDB(t) 125 mock.ExpectQuery("SHOW GRANTS").WillReturnRows(sqlmock.NewRows([]string{"Grants for User"}). 126 AddRow("GRANT SELECT ON db.* TO 'haha'@'%'")) 127 result, err = RunCheckOnConfigs(context.Background(), cfgs, true, 100, 100) 128 require.NoError(t, err) 129 require.Equal(t, int64(1), result.Summary.Failed) 130 require.Contains(t, result.Results[0].Errors[0].ShortErr, "lack of Select global (*.*) privilege") 131 132 // happy path 133 134 checkHappyPath(t, func() { 135 mock := initMockDB(t) 136 mock.ExpectQuery("SHOW GRANTS").WillReturnRows(sqlmock.NewRows([]string{"Grants for User"}). 137 AddRow("GRANT RELOAD,SELECT ON *.* TO 'haha'@'%'")) 138 }, cfgs) 139 } 140 141 func TestReplicationPrivilegeChecking(t *testing.T) { 142 cfgs := []*config.SubTaskConfig{ 143 { 144 Mode: config.ModeAll, 145 IgnoreCheckingItems: ignoreExcept(map[string]struct{}{config.ReplicationPrivilegeChecking: {}}), 146 }, 147 } 148 149 // test not enough privileges 150 151 mock := initMockDB(t) 152 mock.ExpectQuery("SHOW GRANTS").WillReturnRows(sqlmock.NewRows([]string{"Grants for User"}). 153 AddRow("GRANT USAGE ON *.* TO 'haha'@'%'")) 154 msg, err := CheckSyncConfig(context.Background(), cfgs, common.DefaultErrorCnt, common.DefaultWarnCnt) 155 require.Regexp(t, "(.|\n)*lack.*REPLICATION SLAVE(.|\n)*", err.Error()) 156 require.Regexp(t, "(.|\n)*lack.*REPLICATION CLIENT(.|\n)*", err.Error()) 157 require.Len(t, msg, 0) 158 159 mock = initMockDB(t) 160 mock.ExpectQuery("SHOW GRANTS").WillReturnRows(sqlmock.NewRows([]string{"Grants for User"}). 161 AddRow("GRANT USAGE ON *.* TO 'haha'@'%'")) 162 result, err := RunCheckOnConfigs(context.Background(), cfgs, false, 100, 100) 163 require.NoError(t, err) 164 require.Equal(t, int64(1), result.Summary.Failed) 165 require.Contains(t, result.Results[0].Errors[0].ShortErr, "lack of REPLICATION SLAVE global (*.*) privilege") 166 require.Contains(t, result.Results[0].Errors[0].ShortErr, "lack of REPLICATION CLIENT global (*.*) privilege") 167 168 // happy path 169 170 checkHappyPath(t, func() { 171 mock := initMockDB(t) 172 mock.ExpectQuery("SHOW GRANTS").WillReturnRows(sqlmock.NewRows([]string{"Grants for User"}). 173 AddRow("GRANT REPLICATION SLAVE,REPLICATION CLIENT ON *.* TO 'haha'@'%'")) 174 }, cfgs) 175 } 176 177 func TestRunCheckOnConfigsLimit(t *testing.T) { 178 cfgs := []*config.SubTaskConfig{ 179 { 180 Mode: config.ModeAll, 181 IgnoreCheckingItems: ignoreExcept(map[string]struct{}{ 182 config.DumpPrivilegeChecking: {}, 183 config.ReplicationPrivilegeChecking: {}, 184 }), 185 }, 186 } 187 188 // with limit = 100, there should be 2 error 189 190 mock := initMockDB(t) 191 mock.ExpectQuery("SHOW GRANTS").WillReturnRows(sqlmock.NewRows([]string{"Grants for User"}). 192 AddRow("GRANT USAGE ON *.* TO 'haha'@'%'")) 193 mock.ExpectQuery("SHOW GRANTS").WillReturnRows(sqlmock.NewRows([]string{"Grants for User"}). 194 AddRow("GRANT USAGE ON *.* TO 'haha'@'%'")) 195 196 result, err := RunCheckOnConfigs(context.Background(), cfgs, false, 100, 100) 197 require.NoError(t, err) 198 require.EqualValues(t, 2, result.Summary.Failed) 199 require.Len(t, result.Results, 2) 200 201 // test limit = 1 202 203 mock = initMockDB(t) 204 mock.ExpectQuery("SHOW GRANTS").WillReturnRows(sqlmock.NewRows([]string{"Grants for User"}). 205 AddRow("GRANT USAGE ON *.* TO 'haha'@'%'")) 206 mock.ExpectQuery("SHOW GRANTS").WillReturnRows(sqlmock.NewRows([]string{"Grants for User"}). 207 AddRow("GRANT USAGE ON *.* TO 'haha'@'%'")) 208 209 result, err = RunCheckOnConfigs(context.Background(), cfgs, false, 0, 1) 210 require.NoError(t, err) 211 require.EqualValues(t, 2, result.Summary.Failed) 212 require.Len(t, result.Results, 1) 213 214 // test limit warning of sub-results 215 216 cfgs = []*config.SubTaskConfig{ 217 { 218 Mode: config.ModeAll, 219 IgnoreCheckingItems: ignoreExcept(map[string]struct{}{config.TableSchemaChecking: {}}), 220 }, 221 } 222 errNoSuchTable := &gmysql.MySQLError{Number: 1146, Message: "Table 'xxx' doesn't exist"} 223 224 createTable1 := `CREATE TABLE %s ( 225 id int(11) DEFAULT NULL, 226 b int(11) DEFAULT NULL 227 ) ENGINE=InnoDB DEFAULT CHARSET=latin1` 228 229 mock = initMockDB(t) 230 mock.MatchExpectationsInOrder(false) 231 mock.ExpectQuery("SHOW VARIABLES LIKE 'max_connections'").WillReturnRows(sqlmock.NewRows([]string{"Variable_name", "Value"}).AddRow("max_connections", "2")) 232 mock.ExpectQuery("SHOW VARIABLES LIKE 'sql_mode'").WillReturnRows(sqlmock.NewRows([]string{"Variable_name", "Value"}).AddRow("sql_mode", "")) 233 mock.ExpectQuery("SHOW VARIABLES LIKE 'sql_mode'").WillReturnRows(sqlmock.NewRows([]string{"Variable_name", "Value"}).AddRow("sql_mode", "")) 234 mock.ExpectQuery("SHOW CREATE TABLE `db_1`.`t_1`").WillReturnRows(sqlmock.NewRows([]string{"Table", "Create Table"}).AddRow(tb1, fmt.Sprintf(createTable1, tb1))) 235 mock.ExpectQuery("SHOW CREATE TABLE `db_1`.`t_1`").WillReturnError(errNoSuchTable) // downstream connection mock 236 mock.ExpectQuery("SHOW VARIABLES LIKE 'sql_mode'").WillReturnRows(sqlmock.NewRows([]string{"Variable_name", "Value"}).AddRow("sql_mode", "")) 237 mock.ExpectQuery("SHOW CREATE TABLE `db_1`.`t_2`").WillReturnRows(sqlmock.NewRows([]string{"Table", "Create Table"}).AddRow(tb1, fmt.Sprintf(createTable1, tb2))) 238 mock.ExpectQuery("SHOW CREATE TABLE `db_1`.`t_2`").WillReturnError(errNoSuchTable) // downstream connection mock 239 240 result, err = RunCheckOnConfigs(context.Background(), cfgs, false, 100, 100) 241 require.NoError(t, err) 242 require.Contains(t, result.Results[0].Errors[0].ShortErr, "primary/unique key does not exist") 243 require.Contains(t, result.Results[0].Errors[1].ShortErr, "primary/unique key does not exist") 244 require.NoError(t, mock.ExpectationsWereMet()) 245 246 mock = initMockDB(t) 247 mock.MatchExpectationsInOrder(false) 248 mock.ExpectQuery("SHOW VARIABLES LIKE 'max_connections'").WillReturnRows(sqlmock.NewRows([]string{"Variable_name", "Value"}).AddRow("max_connections", "2")) 249 mock.ExpectQuery("SHOW VARIABLES LIKE 'sql_mode'").WillReturnRows(sqlmock.NewRows([]string{"Variable_name", "Value"}).AddRow("sql_mode", "")) 250 mock.ExpectQuery("SHOW VARIABLES LIKE 'sql_mode'").WillReturnRows(sqlmock.NewRows([]string{"Variable_name", "Value"}).AddRow("sql_mode", "")) 251 mock.ExpectQuery("SHOW CREATE TABLE `db_1`.`t_1`").WillReturnRows(sqlmock.NewRows([]string{"Table", "Create Table"}).AddRow(tb1, fmt.Sprintf(createTable1, tb1))) 252 mock.ExpectQuery("SHOW CREATE TABLE `db_1`.`t_1`").WillReturnError(errNoSuchTable) // downstream connection mock 253 mock.ExpectQuery("SHOW VARIABLES LIKE 'sql_mode'").WillReturnRows(sqlmock.NewRows([]string{"Variable_name", "Value"}).AddRow("sql_mode", "")) 254 mock.ExpectQuery("SHOW CREATE TABLE `db_1`.`t_2`").WillReturnRows(sqlmock.NewRows([]string{"Table", "Create Table"}).AddRow(tb1, fmt.Sprintf(createTable1, tb2))) 255 mock.ExpectQuery("SHOW CREATE TABLE `db_1`.`t_2`").WillReturnError(errNoSuchTable) // downstream connection mock 256 257 result, err = RunCheckOnConfigs(context.Background(), cfgs, false, 1, 0) 258 require.NoError(t, err) 259 require.Len(t, result.Results[0].Errors, 1) 260 require.Contains(t, result.Results[0].Errors[0].ShortErr, "primary/unique key does not exist") 261 require.NoError(t, mock.ExpectationsWereMet()) 262 } 263 264 func TestTargetDBPrivilegeChecking(t *testing.T) { 265 cfgs := []*config.SubTaskConfig{ 266 { 267 Mode: config.ModeAll, 268 IgnoreCheckingItems: ignoreExcept(map[string]struct{}{config.TargetDBPrivilegeChecking: {}}), 269 }, 270 } 271 272 // test not enough privileges 273 274 mock := initMockDB(t) 275 mock.ExpectQuery("SHOW GRANTS").WillReturnRows(sqlmock.NewRows([]string{"Grants for User"}). 276 AddRow("GRANT SELECT,UPDATE,CREATE,DELETE,INSERT,ALTER ON *.* TO 'test'@'%'")) 277 msg, err := CheckSyncConfig(context.Background(), cfgs, common.DefaultErrorCnt, common.DefaultWarnCnt) 278 require.NoError(t, err) 279 require.Contains(t, msg, "lack of Drop global (*.*) privilege; lack of Index global (*.*) privilege; ") 280 281 mock = initMockDB(t) 282 mock.ExpectQuery("SHOW GRANTS").WillReturnRows(sqlmock.NewRows([]string{"Grants for User"}). 283 AddRow("GRANT SELECT,UPDATE,CREATE,DELETE,INSERT,ALTER ON *.* TO 'test'@'%'")) 284 result, err := RunCheckOnConfigs(context.Background(), cfgs, false, 100, 100) 285 require.NoError(t, err) 286 require.Equal(t, int64(1), result.Summary.Warning) 287 require.Contains(t, result.Results[0].Errors[0].ShortErr, "lack of Drop global (*.*) privilege") 288 require.Contains(t, result.Results[0].Errors[0].ShortErr, "lack of Index global (*.*) privilege") 289 290 // happy path 291 292 checkHappyPath(t, func() { 293 mock := initMockDB(t) 294 mock.ExpectQuery("SHOW GRANTS").WillReturnRows(sqlmock.NewRows([]string{"Grants for User"}). 295 AddRow("GRANT SELECT,UPDATE,CREATE,DELETE,INSERT,ALTER,INDEX,DROP ON *.* TO 'test'@'%'")) 296 }, cfgs) 297 298 checkHappyPath(t, func() { 299 mock := initMockDB(t) 300 mock.ExpectQuery("SHOW GRANTS").WillReturnRows(sqlmock.NewRows([]string{"Grants for User"}). 301 AddRow("GRANT ALL PRIVILEGES ON *.* TO 'test'@'%'")) 302 }, cfgs) 303 } 304 305 func TestVersionChecking(t *testing.T) { 306 cfgs := []*config.SubTaskConfig{ 307 { 308 Mode: config.ModeAll, 309 IgnoreCheckingItems: ignoreExcept(map[string]struct{}{config.VersionChecking: {}}), 310 }, 311 } 312 313 // happy path 314 315 checkHappyPath(t, func() { 316 mock := initMockDB(t) 317 mock.ExpectQuery("SHOW GLOBAL VARIABLES LIKE 'version'").WillReturnRows(sqlmock.NewRows([]string{"Variable_name", "Value"}). 318 AddRow("version", "5.7.26-log")) 319 }, cfgs) 320 321 // MariaDB should have a warning 322 323 mock := initMockDB(t) 324 mock.ExpectQuery("SHOW GLOBAL VARIABLES LIKE 'version'").WillReturnRows(sqlmock.NewRows([]string{"Variable_name", "Value"}). 325 AddRow("version", "10.1.29-MariaDB")) 326 msg, err := CheckSyncConfig(context.Background(), cfgs, common.DefaultErrorCnt, common.DefaultWarnCnt) 327 require.NoError(t, err) 328 require.Contains(t, msg, "Migrating from MariaDB is still experimental") 329 330 mock = initMockDB(t) 331 mock.ExpectQuery("SHOW GLOBAL VARIABLES LIKE 'version'").WillReturnRows(sqlmock.NewRows([]string{"Variable_name", "Value"}). 332 AddRow("version", "10.1.29-MariaDB")) 333 result, err := RunCheckOnConfigs(context.Background(), cfgs, false, 100, 100) 334 require.NoError(t, err) 335 require.True(t, result.Summary.Passed) 336 require.Equal(t, int64(1), result.Summary.Warning) 337 require.Contains(t, result.Results[0].Errors[0].ShortErr, "Migrating from MariaDB is still experimental.") 338 339 // too low MySQL version 340 341 mock = initMockDB(t) 342 mock.ExpectQuery("SHOW GLOBAL VARIABLES LIKE 'version'").WillReturnRows(sqlmock.NewRows([]string{"Variable_name", "Value"}). 343 AddRow("version", "5.5.26-log")) 344 msg, err = CheckSyncConfig(context.Background(), cfgs, common.DefaultErrorCnt, common.DefaultWarnCnt) 345 require.NoError(t, err) 346 require.Regexp(t, "(.|\n)*version suggested at least .* but got 5.5.26(.|\n)*", msg) 347 348 mock = initMockDB(t) 349 mock.ExpectQuery("SHOW GLOBAL VARIABLES LIKE 'version'").WillReturnRows(sqlmock.NewRows([]string{"Variable_name", "Value"}). 350 AddRow("version", "5.5.26-log")) 351 result, err = RunCheckOnConfigs(context.Background(), cfgs, false, 100, 100) 352 require.NoError(t, err) 353 require.True(t, result.Summary.Passed) 354 require.Equal(t, int64(1), result.Summary.Warning) 355 require.Regexp(t, "version suggested at least .* but got 5.5.26", result.Results[0].Errors[0].ShortErr) 356 } 357 358 func TestServerIDChecking(t *testing.T) { 359 cfgs := []*config.SubTaskConfig{ 360 { 361 Mode: config.ModeAll, 362 IgnoreCheckingItems: ignoreExcept(map[string]struct{}{config.ServerIDChecking: {}}), 363 }, 364 } 365 366 // not explicit server ID 367 368 mock := initMockDB(t) 369 mock.ExpectQuery("SHOW GLOBAL VARIABLES LIKE 'server_id'").WillReturnRows(sqlmock.NewRows([]string{"Variable_name", "Value"}). 370 AddRow("server_id", "0")) 371 msg, err := CheckSyncConfig(context.Background(), cfgs, common.DefaultErrorCnt, common.DefaultWarnCnt) 372 require.NoError(t, err) 373 require.Contains(t, msg, "Set server_id greater than 0") 374 375 mock = initMockDB(t) 376 mock.ExpectQuery("SHOW GLOBAL VARIABLES LIKE 'server_id'").WillReturnRows(sqlmock.NewRows([]string{"Variable_name", "Value"}). 377 AddRow("server_id", "0")) 378 result, err := RunCheckOnConfigs(context.Background(), cfgs, false, 100, 100) 379 require.NoError(t, err) 380 require.True(t, result.Summary.Passed) 381 require.Contains(t, result.Results[0].Instruction, "Set server_id greater than 0") 382 383 // happy path 384 385 checkHappyPath(t, func() { 386 mock := initMockDB(t) 387 mock.ExpectQuery("SHOW GLOBAL VARIABLES LIKE 'server_id'").WillReturnRows(sqlmock.NewRows([]string{"Variable_name", "Value"}). 388 AddRow("server_id", "1")) 389 }, cfgs) 390 } 391 392 func TestBinlogEnableChecking(t *testing.T) { 393 cfgs := []*config.SubTaskConfig{ 394 { 395 Mode: config.ModeAll, 396 IgnoreCheckingItems: ignoreExcept(map[string]struct{}{config.BinlogEnableChecking: {}}), 397 }, 398 } 399 400 // forget to turn on binlog 401 402 mock := initMockDB(t) 403 mock.ExpectQuery("SHOW GLOBAL VARIABLES LIKE 'log_bin'").WillReturnRows(sqlmock.NewRows([]string{"Variable_name", "Value"}). 404 AddRow("log_bin", "OFF")) 405 msg, err := CheckSyncConfig(context.Background(), cfgs, common.DefaultErrorCnt, common.DefaultWarnCnt) 406 require.ErrorContains(t, err, "log_bin is OFF, and should be ON") 407 require.Len(t, msg, 0) 408 409 mock = initMockDB(t) 410 mock.ExpectQuery("SHOW GLOBAL VARIABLES LIKE 'log_bin'").WillReturnRows(sqlmock.NewRows([]string{"Variable_name", "Value"}). 411 AddRow("log_bin", "OFF")) 412 result, err := RunCheckOnConfigs(context.Background(), cfgs, false, 100, 100) 413 require.NoError(t, err) 414 require.False(t, result.Summary.Passed) 415 require.Contains(t, result.Results[0].Errors[0].ShortErr, "log_bin is OFF, and should be ON") 416 417 // happy path 418 419 checkHappyPath(t, func() { 420 mock := initMockDB(t) 421 mock.ExpectQuery("SHOW GLOBAL VARIABLES LIKE 'log_bin'").WillReturnRows(sqlmock.NewRows([]string{"Variable_name", "Value"}). 422 AddRow("log_bin", "ON")) 423 }, cfgs) 424 } 425 426 func TestBinlogFormatChecking(t *testing.T) { 427 cfgs := []*config.SubTaskConfig{ 428 { 429 Mode: config.ModeAll, 430 IgnoreCheckingItems: ignoreExcept(map[string]struct{}{config.BinlogFormatChecking: {}}), 431 }, 432 } 433 434 // binlog_format is not correct 435 436 mock := initMockDB(t) 437 mock.ExpectQuery("SHOW GLOBAL VARIABLES LIKE 'binlog_format'").WillReturnRows(sqlmock.NewRows([]string{"Variable_name", "Value"}). 438 AddRow("binlog_format", "STATEMENT")) 439 msg, err := CheckSyncConfig(context.Background(), cfgs, common.DefaultErrorCnt, common.DefaultWarnCnt) 440 require.ErrorContains(t, err, "binlog_format is STATEMENT, and should be ROW") 441 require.Len(t, msg, 0) 442 443 mock = initMockDB(t) 444 mock.ExpectQuery("SHOW GLOBAL VARIABLES LIKE 'binlog_format'").WillReturnRows(sqlmock.NewRows([]string{"Variable_name", "Value"}). 445 AddRow("binlog_format", "STATEMENT")) 446 result, err := RunCheckOnConfigs(context.Background(), cfgs, false, 100, 100) 447 require.NoError(t, err) 448 require.Contains(t, result.Results[0].Errors[0].ShortErr, "binlog_format is STATEMENT, and should be ROW") 449 450 // happy path 451 452 checkHappyPath(t, func() { 453 mock := initMockDB(t) 454 mock.ExpectQuery("SHOW GLOBAL VARIABLES LIKE 'binlog_format'").WillReturnRows(sqlmock.NewRows([]string{"Variable_name", "Value"}). 455 AddRow("binlog_format", "ROW")) 456 }, cfgs) 457 } 458 459 func TestBinlogRowImageChecking(t *testing.T) { 460 cfgs := []*config.SubTaskConfig{ 461 { 462 Mode: config.ModeAll, 463 IgnoreCheckingItems: ignoreExcept(map[string]struct{}{config.BinlogRowImageChecking: {}}), 464 }, 465 } 466 467 // binlog_row_image is not correct 468 469 mock := initMockDB(t) 470 mock.ExpectQuery("SHOW GLOBAL VARIABLES LIKE 'version'").WillReturnRows(sqlmock.NewRows([]string{"Variable_name", "Value"}). 471 AddRow("version", "5.7.26-log")) 472 mock.ExpectQuery("SHOW GLOBAL VARIABLES LIKE 'binlog_row_image'").WillReturnRows(sqlmock.NewRows([]string{"Variable_name", "Value"}). 473 AddRow("binlog_row_image", "MINIMAL")) 474 msg, err := CheckSyncConfig(context.Background(), cfgs, common.DefaultErrorCnt, common.DefaultWarnCnt) 475 require.ErrorContains(t, err, "binlog_row_image is MINIMAL, and should be FULL") 476 require.Len(t, msg, 0) 477 478 mock = initMockDB(t) 479 mock.ExpectQuery("SHOW GLOBAL VARIABLES LIKE 'version'").WillReturnRows(sqlmock.NewRows([]string{"Variable_name", "Value"}). 480 AddRow("version", "5.7.26-log")) 481 mock.ExpectQuery("SHOW GLOBAL VARIABLES LIKE 'binlog_row_image'").WillReturnRows(sqlmock.NewRows([]string{"Variable_name", "Value"}). 482 AddRow("binlog_row_image", "MINIMAL")) 483 result, err := RunCheckOnConfigs(context.Background(), cfgs, false, 100, 100) 484 require.NoError(t, err) 485 require.Contains(t, result.Results[0].Errors[0].ShortErr, "binlog_row_image is MINIMAL, and should be FULL") 486 487 // happy path 488 489 checkHappyPath(t, func() { 490 mock := initMockDB(t) 491 mock.ExpectQuery("SHOW GLOBAL VARIABLES LIKE 'version'").WillReturnRows(sqlmock.NewRows([]string{"Variable_name", "Value"}). 492 AddRow("version", "10.1.29-MariaDB")) 493 mock.ExpectQuery("SHOW GLOBAL VARIABLES LIKE 'binlog_row_image'").WillReturnRows(sqlmock.NewRows([]string{"Variable_name", "Value"}). 494 AddRow("binlog_row_image", "FULL")) 495 }, cfgs) 496 } 497 498 func TestTableSchemaChecking(t *testing.T) { 499 cfgs := []*config.SubTaskConfig{ 500 { 501 Mode: config.ModeAll, 502 IgnoreCheckingItems: ignoreExcept(map[string]struct{}{config.TableSchemaChecking: {}}), 503 }, 504 } 505 errNoSuchTable := &gmysql.MySQLError{Number: 1146, Message: "Table 'xxx' doesn't exist"} 506 507 createTable1 := `CREATE TABLE %s ( 508 id int(11) DEFAULT NULL, 509 b int(11) DEFAULT NULL 510 ) ENGINE=InnoDB DEFAULT CHARSET=latin1` 511 createTable2 := `CREATE TABLE %s ( 512 id int(11) DEFAULT NULL, 513 b int(11) DEFAULT NULL, 514 UNIQUE KEY id (id) 515 ) ENGINE=InnoDB DEFAULT CHARSET=latin1` 516 517 // no PK/UK should raise error per table 518 519 mock := initMockDB(t) 520 mock.MatchExpectationsInOrder(false) 521 mock.ExpectQuery("SHOW VARIABLES LIKE 'max_connections'").WillReturnRows(sqlmock.NewRows([]string{"Variable_name", "Value"}).AddRow("max_connections", "2")) 522 mock.ExpectQuery("SHOW VARIABLES LIKE 'sql_mode'").WillReturnRows(sqlmock.NewRows([]string{"Variable_name", "Value"}).AddRow("sql_mode", "")) 523 mock.ExpectQuery("SHOW VARIABLES LIKE 'sql_mode'").WillReturnRows(sqlmock.NewRows([]string{"Variable_name", "Value"}).AddRow("sql_mode", "")) 524 mock.ExpectQuery("SHOW CREATE TABLE `db_1`.`t_1`").WillReturnRows(sqlmock.NewRows([]string{"Table", "Create Table"}).AddRow(tb1, fmt.Sprintf(createTable1, tb1))) 525 mock.ExpectQuery("SHOW CREATE TABLE `db_1`.`t_1`").WillReturnError(errNoSuchTable) // downstream connection mock 526 mock.ExpectQuery("SHOW VARIABLES LIKE 'sql_mode'").WillReturnRows(sqlmock.NewRows([]string{"Variable_name", "Value"}).AddRow("sql_mode", "")) 527 mock.ExpectQuery("SHOW CREATE TABLE `db_1`.`t_2`").WillReturnRows(sqlmock.NewRows([]string{"Table", "Create Table"}).AddRow(tb1, fmt.Sprintf(createTable1, tb2))) 528 mock.ExpectQuery("SHOW CREATE TABLE `db_1`.`t_2`").WillReturnError(errNoSuchTable) // downstream connection mock 529 530 msg, err := CheckSyncConfig(context.Background(), cfgs, common.DefaultErrorCnt, common.DefaultWarnCnt) 531 require.NoError(t, err) 532 require.Contains(t, msg, "primary/unique key does not exist") 533 require.NoError(t, mock.ExpectationsWereMet()) 534 535 mock = initMockDB(t) 536 mock.MatchExpectationsInOrder(false) 537 mock.ExpectQuery("SHOW VARIABLES LIKE 'max_connections'").WillReturnRows(sqlmock.NewRows([]string{"Variable_name", "Value"}).AddRow("max_connections", "2")) 538 mock.ExpectQuery("SHOW VARIABLES LIKE 'sql_mode'").WillReturnRows(sqlmock.NewRows([]string{"Variable_name", "Value"}).AddRow("sql_mode", "")) 539 mock.ExpectQuery("SHOW VARIABLES LIKE 'sql_mode'").WillReturnRows(sqlmock.NewRows([]string{"Variable_name", "Value"}).AddRow("sql_mode", "")) 540 mock.ExpectQuery("SHOW CREATE TABLE `db_1`.`t_1`").WillReturnRows(sqlmock.NewRows([]string{"Table", "Create Table"}).AddRow(tb1, fmt.Sprintf(createTable1, tb1))) 541 mock.ExpectQuery("SHOW CREATE TABLE `db_1`.`t_1`").WillReturnError(errNoSuchTable) // downstream connection mock 542 mock.ExpectQuery("SHOW VARIABLES LIKE 'sql_mode'").WillReturnRows(sqlmock.NewRows([]string{"Variable_name", "Value"}).AddRow("sql_mode", "")) 543 mock.ExpectQuery("SHOW CREATE TABLE `db_1`.`t_2`").WillReturnRows(sqlmock.NewRows([]string{"Table", "Create Table"}).AddRow(tb1, fmt.Sprintf(createTable1, tb2))) 544 mock.ExpectQuery("SHOW CREATE TABLE `db_1`.`t_2`").WillReturnError(errNoSuchTable) // downstream connection mock 545 546 result, err := RunCheckOnConfigs(context.Background(), cfgs, false, 100, 100) 547 require.NoError(t, err) 548 require.Contains(t, result.Results[0].Errors[0].ShortErr, "primary/unique key does not exist") 549 require.Contains(t, result.Results[0].Errors[1].ShortErr, "primary/unique key does not exist") 550 require.NoError(t, mock.ExpectationsWereMet()) 551 552 // happy path 553 554 checkHappyPath(t, func() { 555 mock := initMockDB(t) 556 mock.MatchExpectationsInOrder(false) 557 mock.ExpectQuery("SHOW VARIABLES LIKE 'max_connections'").WillReturnRows(sqlmock.NewRows([]string{"Variable_name", "Value"}).AddRow("max_connections", "2")) 558 mock.ExpectQuery("SHOW VARIABLES LIKE 'sql_mode'").WillReturnRows(sqlmock.NewRows([]string{"Variable_name", "Value"}).AddRow("sql_mode", "")) 559 mock.ExpectQuery("SHOW VARIABLES LIKE 'sql_mode'").WillReturnRows(sqlmock.NewRows([]string{"Variable_name", "Value"}).AddRow("sql_mode", "")) 560 mock.ExpectQuery("SHOW CREATE TABLE `db_1`.`t_1`").WillReturnRows(sqlmock.NewRows([]string{"Table", "Create Table"}).AddRow(tb1, fmt.Sprintf(createTable2, tb1))) 561 mock.ExpectQuery("SHOW CREATE TABLE `db_1`.`t_1`").WillReturnError(errNoSuchTable) // downstream connection mock 562 mock.ExpectQuery("SHOW VARIABLES LIKE 'sql_mode'").WillReturnRows(sqlmock.NewRows([]string{"Variable_name", "Value"}).AddRow("sql_mode", "")) 563 mock.ExpectQuery("SHOW CREATE TABLE `db_1`.`t_2`").WillReturnRows(sqlmock.NewRows([]string{"Table", "Create Table"}).AddRow(tb1, fmt.Sprintf(createTable2, tb2))) 564 mock.ExpectQuery("SHOW CREATE TABLE `db_1`.`t_2`").WillReturnError(errNoSuchTable) // downstream connection mock 565 }, cfgs) 566 } 567 568 func TestShardTableSchemaChecking(t *testing.T) { 569 cfgs := []*config.SubTaskConfig{ 570 { 571 MetaSchema: metaSchema, 572 Name: taskName, 573 ShardMode: config.ShardPessimistic, 574 RouteRules: []*router.TableRule{ 575 { 576 SchemaPattern: schema, 577 TargetSchema: "db", 578 TablePattern: "t_*", 579 TargetTable: "t", 580 }, 581 }, 582 Mode: config.ModeAll, 583 IgnoreCheckingItems: ignoreExcept(map[string]struct{}{config.ShardTableSchemaChecking: {}}), 584 }, 585 } 586 587 createTable1 := `CREATE TABLE %s ( 588 id int(11) DEFAULT NULL, 589 b int(11) DEFAULT NULL 590 ) ENGINE=InnoDB DEFAULT CHARSET=latin1` 591 createTable2 := `CREATE TABLE %s ( 592 id int(11) DEFAULT NULL, 593 c int(11) DEFAULT NULL 594 ) ENGINE=InnoDB DEFAULT CHARSET=latin1` 595 errNoSuchTable := &gmysql.MySQLError{Number: mysql.ErrNoSuchTable} 596 createTableSQL := "SHOW CREATE TABLE `%s`.`%s`" 597 598 // test different column definition 599 600 mock := initMockDB(t) 601 mock.ExpectQuery(fmt.Sprintf(createTableSQL, metaSchema, cputil.LoaderCheckpoint(taskName))).WillReturnError(errNoSuchTable) 602 mock.ExpectQuery(fmt.Sprintf(createTableSQL, metaSchema, cputil.LightningCheckpoint(taskName))).WillReturnError(errNoSuchTable) 603 mock.ExpectQuery(fmt.Sprintf(createTableSQL, metaSchema, cputil.SyncerCheckpoint(taskName))).WillReturnError(errNoSuchTable) 604 mock.ExpectQuery("SHOW VARIABLES LIKE 'sql_mode'").WillReturnRows(sqlmock.NewRows([]string{"Variable_name", "Value"}).AddRow("sql_mode", "")) 605 mock.ExpectQuery("SHOW CREATE TABLE .*").WillReturnRows(sqlmock.NewRows([]string{"Table", "Create Table"}).AddRow(tb1, fmt.Sprintf(createTable1, tb1))) 606 mock.ExpectQuery("SHOW VARIABLES LIKE 'max_connections'").WillReturnRows(sqlmock.NewRows([]string{"Variable_name", "Value"}).AddRow("max_connections", "2")) 607 mock.ExpectQuery("SHOW VARIABLES LIKE 'sql_mode'").WillReturnRows(sqlmock.NewRows([]string{"Variable_name", "Value"}).AddRow("sql_mode", "")) 608 mock.ExpectQuery("SHOW CREATE TABLE .*").WillReturnRows(sqlmock.NewRows([]string{"Table", "Create Table"}).AddRow(tb1, fmt.Sprintf(createTable1, tb1))) 609 mock.ExpectQuery("SHOW VARIABLES LIKE 'sql_mode'").WillReturnRows(sqlmock.NewRows([]string{"Variable_name", "Value"}).AddRow("sql_mode", "")) 610 mock.ExpectQuery("SHOW CREATE TABLE .*").WillReturnRows(sqlmock.NewRows([]string{"Table", "Create Table"}).AddRow(tb1, fmt.Sprintf(createTable2, tb2))) 611 msg, err := CheckSyncConfig(context.Background(), cfgs, common.DefaultErrorCnt, common.DefaultWarnCnt) 612 require.ErrorContains(t, err, "different column definition") 613 require.Len(t, msg, 0) 614 615 mock = initMockDB(t) 616 mock.ExpectQuery(fmt.Sprintf(createTableSQL, metaSchema, cputil.LoaderCheckpoint(taskName))).WillReturnError(errNoSuchTable) 617 mock.ExpectQuery(fmt.Sprintf(createTableSQL, metaSchema, cputil.LightningCheckpoint(taskName))).WillReturnError(errNoSuchTable) 618 mock.ExpectQuery(fmt.Sprintf(createTableSQL, metaSchema, cputil.SyncerCheckpoint(taskName))).WillReturnError(errNoSuchTable) 619 mock.ExpectQuery("SHOW VARIABLES LIKE 'sql_mode'").WillReturnRows(sqlmock.NewRows([]string{"Variable_name", "Value"}).AddRow("sql_mode", "")) 620 mock.ExpectQuery("SHOW CREATE TABLE .*").WillReturnRows(sqlmock.NewRows([]string{"Table", "Create Table"}).AddRow(tb1, fmt.Sprintf(createTable1, tb1))) 621 mock.ExpectQuery("SHOW VARIABLES LIKE 'max_connections'").WillReturnRows(sqlmock.NewRows([]string{"Variable_name", "Value"}).AddRow("max_connections", "2")) 622 mock.ExpectQuery("SHOW VARIABLES LIKE 'sql_mode'").WillReturnRows(sqlmock.NewRows([]string{"Variable_name", "Value"}).AddRow("sql_mode", "")) 623 mock.ExpectQuery("SHOW CREATE TABLE .*").WillReturnRows(sqlmock.NewRows([]string{"Table", "Create Table"}).AddRow(tb1, fmt.Sprintf(createTable1, tb1))) 624 mock.ExpectQuery("SHOW VARIABLES LIKE 'sql_mode'").WillReturnRows(sqlmock.NewRows([]string{"Variable_name", "Value"}).AddRow("sql_mode", "")) 625 mock.ExpectQuery("SHOW CREATE TABLE .*").WillReturnRows(sqlmock.NewRows([]string{"Table", "Create Table"}).AddRow(tb1, fmt.Sprintf(createTable2, tb2))) 626 result, err := RunCheckOnConfigs(context.Background(), cfgs, false, 100, 100) 627 require.NoError(t, err) 628 require.Contains(t, result.Results[0].Errors[0].ShortErr, "different column definition") 629 630 // test success check 631 632 checkHappyPath(t, func() { 633 mock := initMockDB(t) 634 mock.ExpectQuery(fmt.Sprintf(createTableSQL, metaSchema, cputil.LoaderCheckpoint(taskName))).WillReturnError(errNoSuchTable) 635 mock.ExpectQuery(fmt.Sprintf(createTableSQL, metaSchema, cputil.LightningCheckpoint(taskName))).WillReturnError(errNoSuchTable) 636 mock.ExpectQuery(fmt.Sprintf(createTableSQL, metaSchema, cputil.SyncerCheckpoint(taskName))).WillReturnError(errNoSuchTable) 637 mock.ExpectQuery("SHOW VARIABLES LIKE 'sql_mode'").WillReturnRows(sqlmock.NewRows([]string{"Variable_name", "Value"}).AddRow("sql_mode", "")) 638 mock.ExpectQuery("SHOW CREATE TABLE .*").WillReturnRows(sqlmock.NewRows([]string{"Table", "Create Table"}).AddRow(tb1, fmt.Sprintf(createTable1, tb1))) 639 mock.ExpectQuery("SHOW VARIABLES LIKE 'max_connections'").WillReturnRows(sqlmock.NewRows([]string{"Variable_name", "Value"}).AddRow("max_connections", "2")) 640 mock.ExpectQuery("SHOW VARIABLES LIKE 'sql_mode'").WillReturnRows(sqlmock.NewRows([]string{"Variable_name", "Value"}).AddRow("sql_mode", "")) 641 mock.ExpectQuery("SHOW CREATE TABLE .*").WillReturnRows(sqlmock.NewRows([]string{"Table", "Create Table"}).AddRow(tb1, fmt.Sprintf(createTable1, tb1))) 642 mock.ExpectQuery("SHOW VARIABLES LIKE 'sql_mode'").WillReturnRows(sqlmock.NewRows([]string{"Variable_name", "Value"}).AddRow("sql_mode", "")) 643 mock.ExpectQuery("SHOW CREATE TABLE .*").WillReturnRows(sqlmock.NewRows([]string{"Table", "Create Table"}).AddRow(tb1, fmt.Sprintf(createTable1, tb2))) 644 }, cfgs) 645 646 // test existing checkpoint 647 648 checkHappyPath(t, func() { 649 mock := initMockDB(t) 650 mock.ExpectQuery(fmt.Sprintf(createTableSQL, metaSchema, cputil.LoaderCheckpoint(taskName))).WillReturnRows(sqlmock. 651 NewRows([]string{"Table", "Create Table"}).AddRow(cputil.LoaderCheckpoint(taskName), "")) 652 mock.ExpectQuery(fmt.Sprintf(createTableSQL, metaSchema, cputil.LightningCheckpoint(taskName))).WillReturnRows(sqlmock. 653 NewRows([]string{"Table", "Create Table"}).AddRow(cputil.LightningCheckpoint(taskName), "")) 654 mock.ExpectQuery(fmt.Sprintf(createTableSQL, metaSchema, cputil.SyncerCheckpoint(taskName))).WillReturnRows(sqlmock. 655 NewRows([]string{"Table", "Create Table"}).AddRow(cputil.SyncerCheckpoint(taskName), "")) 656 }, cfgs) 657 } 658 659 func TestShardAutoIncrementIDChecking(t *testing.T) { 660 cfgs := []*config.SubTaskConfig{ 661 { 662 ShardMode: config.ShardPessimistic, 663 RouteRules: []*router.TableRule{ 664 { 665 SchemaPattern: schema, 666 TargetSchema: "db", 667 TablePattern: "t_*", 668 TargetTable: "t", 669 }, 670 }, 671 Mode: config.ModeAll, 672 IgnoreCheckingItems: ignoreExcept(map[string]struct{}{config.ShardTableSchemaChecking: {}, config.ShardAutoIncrementIDChecking: {}}), 673 }, 674 } 675 676 createTable1 := `CREATE TABLE %s ( 677 id int(11) NOT NULL AUTO_INCREMENT, 678 b int(11) DEFAULT NULL, 679 PRIMARY KEY (id), 680 UNIQUE KEY u_b(b) 681 ) ENGINE=InnoDB DEFAULT CHARSET=latin1` 682 683 createTable2 := `CREATE TABLE %s ( 684 id int(11) NOT NULL, 685 b int(11) DEFAULT NULL, 686 INDEX (id), 687 UNIQUE KEY u_b(b) 688 ) ENGINE=InnoDB DEFAULT CHARSET=latin1` 689 690 errNoSuchTable := &gmysql.MySQLError{Number: mysql.ErrNoSuchTable} 691 createTableSQL := "SHOW CREATE TABLE `%s`.`%s`" 692 mock := initMockDB(t) 693 mock.ExpectQuery(fmt.Sprintf(createTableSQL, "", cputil.LoaderCheckpoint(""))).WillReturnError(errNoSuchTable) 694 mock.ExpectQuery(fmt.Sprintf(createTableSQL, "", cputil.LightningCheckpoint(""))).WillReturnError(errNoSuchTable) 695 mock.ExpectQuery(fmt.Sprintf(createTableSQL, "", cputil.SyncerCheckpoint(""))).WillReturnError(errNoSuchTable) 696 mock.ExpectQuery("SHOW VARIABLES LIKE 'sql_mode'").WillReturnRows(sqlmock.NewRows([]string{"Variable_name", "Value"}).AddRow("sql_mode", "")) 697 mock.ExpectQuery("SHOW CREATE TABLE .*").WillReturnRows(sqlmock.NewRows([]string{"Table", "Create Table"}).AddRow(tb1, fmt.Sprintf(createTable1, tb1))) 698 mock.ExpectQuery("SHOW VARIABLES LIKE 'max_connections'").WillReturnRows(sqlmock.NewRows([]string{"Variable_name", "Value"}).AddRow("max_connections", "2")) 699 mock.ExpectQuery("SHOW VARIABLES LIKE 'sql_mode'").WillReturnRows(sqlmock.NewRows([]string{"Variable_name", "Value"}).AddRow("sql_mode", "")) 700 mock.ExpectQuery("SHOW CREATE TABLE .*").WillReturnRows(sqlmock.NewRows([]string{"Table", "Create Table"}).AddRow(tb1, fmt.Sprintf(createTable1, tb1))) 701 mock.ExpectQuery("SHOW VARIABLES LIKE 'sql_mode'").WillReturnRows(sqlmock.NewRows([]string{"Variable_name", "Value"}).AddRow("sql_mode", "")) 702 mock.ExpectQuery("SHOW CREATE TABLE .*").WillReturnRows(sqlmock.NewRows([]string{"Table", "Create Table"}).AddRow(tb1, fmt.Sprintf(createTable1, tb2))) 703 msg, err := CheckSyncConfig(context.Background(), cfgs, common.DefaultErrorCnt, common.DefaultWarnCnt) 704 require.NoError(t, err) 705 require.Regexp(t, "(.|\n)*sourceID table .* of sharding .* have auto-increment key(.|\n)*", msg) 706 707 mock = initMockDB(t) 708 mock.ExpectQuery(fmt.Sprintf(createTableSQL, "", cputil.LoaderCheckpoint(""))).WillReturnError(errNoSuchTable) 709 mock.ExpectQuery(fmt.Sprintf(createTableSQL, "", cputil.LightningCheckpoint(""))).WillReturnError(errNoSuchTable) 710 mock.ExpectQuery(fmt.Sprintf(createTableSQL, "", cputil.SyncerCheckpoint(""))).WillReturnError(errNoSuchTable) 711 mock.ExpectQuery("SHOW VARIABLES LIKE 'sql_mode'").WillReturnRows(sqlmock.NewRows([]string{"Variable_name", "Value"}).AddRow("sql_mode", "")) 712 mock.ExpectQuery("SHOW CREATE TABLE .*").WillReturnRows(sqlmock.NewRows([]string{"Table", "Create Table"}).AddRow(tb1, fmt.Sprintf(createTable1, tb1))) 713 mock.ExpectQuery("SHOW VARIABLES LIKE 'max_connections'").WillReturnRows(sqlmock.NewRows([]string{"Variable_name", "Value"}).AddRow("max_connections", "2")) 714 mock.ExpectQuery("SHOW VARIABLES LIKE 'sql_mode'").WillReturnRows(sqlmock.NewRows([]string{"Variable_name", "Value"}).AddRow("sql_mode", "")) 715 mock.ExpectQuery("SHOW CREATE TABLE .*").WillReturnRows(sqlmock.NewRows([]string{"Table", "Create Table"}).AddRow(tb1, fmt.Sprintf(createTable1, tb1))) 716 mock.ExpectQuery("SHOW VARIABLES LIKE 'sql_mode'").WillReturnRows(sqlmock.NewRows([]string{"Variable_name", "Value"}).AddRow("sql_mode", "")) 717 mock.ExpectQuery("SHOW CREATE TABLE .*").WillReturnRows(sqlmock.NewRows([]string{"Table", "Create Table"}).AddRow(tb1, fmt.Sprintf(createTable1, tb2))) 718 result, err := RunCheckOnConfigs(context.Background(), cfgs, false, 100, 100) 719 require.NoError(t, err) 720 require.Regexp(t, "sourceID table .* of sharding .* have auto-increment key", result.Results[0].Errors[0].ShortErr) 721 722 // happy path 723 724 checkHappyPath(t, func() { 725 mock := initMockDB(t) 726 mock.ExpectQuery(fmt.Sprintf(createTableSQL, "", cputil.LoaderCheckpoint(""))).WillReturnError(errNoSuchTable) 727 mock.ExpectQuery(fmt.Sprintf(createTableSQL, "", cputil.LightningCheckpoint(""))).WillReturnError(errNoSuchTable) 728 mock.ExpectQuery(fmt.Sprintf(createTableSQL, "", cputil.SyncerCheckpoint(""))).WillReturnError(errNoSuchTable) 729 mock.ExpectQuery("SHOW VARIABLES LIKE 'sql_mode'").WillReturnRows(sqlmock.NewRows([]string{"Variable_name", "Value"}).AddRow("sql_mode", "")) 730 mock.ExpectQuery("SHOW CREATE TABLE .*").WillReturnRows(sqlmock.NewRows([]string{"Table", "Create Table"}).AddRow(tb1, fmt.Sprintf(createTable2, tb1))) 731 mock.ExpectQuery("SHOW VARIABLES LIKE 'max_connections'").WillReturnRows(sqlmock.NewRows([]string{"Variable_name", "Value"}).AddRow("max_connections", "2")) 732 mock.ExpectQuery("SHOW VARIABLES LIKE 'sql_mode'").WillReturnRows(sqlmock.NewRows([]string{"Variable_name", "Value"}).AddRow("sql_mode", "")) 733 mock.ExpectQuery("SHOW CREATE TABLE .*").WillReturnRows(sqlmock.NewRows([]string{"Table", "Create Table"}).AddRow(tb1, fmt.Sprintf(createTable2, tb1))) 734 mock.ExpectQuery("SHOW VARIABLES LIKE 'sql_mode'").WillReturnRows(sqlmock.NewRows([]string{"Variable_name", "Value"}).AddRow("sql_mode", "")) 735 mock.ExpectQuery("SHOW CREATE TABLE .*").WillReturnRows(sqlmock.NewRows([]string{"Table", "Create Table"}).AddRow(tb1, fmt.Sprintf(createTable2, tb2))) 736 }, cfgs) 737 } 738 739 func TestSameTargetTableDetection(t *testing.T) { 740 cfgs := []*config.SubTaskConfig{ 741 { 742 RouteRules: []*router.TableRule{ 743 { 744 SchemaPattern: schema, 745 TargetSchema: "db", 746 TablePattern: tb1, 747 TargetTable: "t", 748 }, { 749 SchemaPattern: schema, 750 TargetSchema: "db", 751 TablePattern: tb2, 752 TargetTable: "T", 753 }, 754 }, 755 Mode: config.ModeAll, 756 IgnoreCheckingItems: ignoreExcept(map[string]struct{}{config.TableSchemaChecking: {}}), 757 }, 758 } 759 760 createTable1 := `CREATE TABLE %s ( 761 id int(11) NOT NULL AUTO_INCREMENT, 762 b int(11) DEFAULT NULL, 763 PRIMARY KEY (id), 764 UNIQUE KEY u_b(b) 765 ) ENGINE=InnoDB DEFAULT CHARSET=latin1` 766 errNoSuchTable := &gmysql.MySQLError{Number: mysql.ErrNoSuchTable} 767 createTableSQL := "SHOW CREATE TABLE `%s`.`%s`" 768 769 mock := initMockDB(t) 770 mock.ExpectQuery(fmt.Sprintf(createTableSQL, "", cputil.LoaderCheckpoint(""))).WillReturnError(errNoSuchTable) 771 mock.ExpectQuery(fmt.Sprintf(createTableSQL, "", cputil.LightningCheckpoint(""))).WillReturnError(errNoSuchTable) 772 mock.ExpectQuery(fmt.Sprintf(createTableSQL, "", cputil.SyncerCheckpoint(""))).WillReturnError(errNoSuchTable) 773 mock.ExpectQuery(fmt.Sprintf(createTableSQL, "", cputil.LoaderCheckpoint(""))).WillReturnError(errNoSuchTable) 774 mock.ExpectQuery(fmt.Sprintf(createTableSQL, "", cputil.LightningCheckpoint(""))).WillReturnError(errNoSuchTable) 775 mock.ExpectQuery(fmt.Sprintf(createTableSQL, "", cputil.SyncerCheckpoint(""))).WillReturnError(errNoSuchTable) 776 mock.ExpectQuery("SHOW VARIABLES LIKE 'sql_mode'").WillReturnRows(sqlmock.NewRows([]string{"Variable_name", "Value"}).AddRow("sql_mode", "")) 777 mock.ExpectQuery("SHOW CREATE TABLE .*").WillReturnRows(sqlmock.NewRows([]string{"Table", "Create Table"}).AddRow(tb1, fmt.Sprintf(createTable1, tb1))) 778 mock.ExpectQuery("SHOW CREATE TABLE .*").WillReturnRows(sqlmock.NewRows([]string{"Table", "Create Table"}).AddRow(tb1, fmt.Sprintf(createTable1, tb2))) 779 msg, err := CheckSyncConfig(context.Background(), cfgs, common.DefaultErrorCnt, common.DefaultWarnCnt) 780 require.ErrorContains(t, err, "same table name in case-insensitive") 781 require.Len(t, msg, 0) 782 783 mock = initMockDB(t) 784 mock.ExpectQuery(fmt.Sprintf(createTableSQL, "", cputil.LoaderCheckpoint(""))).WillReturnError(errNoSuchTable) 785 mock.ExpectQuery(fmt.Sprintf(createTableSQL, "", cputil.LightningCheckpoint(""))).WillReturnError(errNoSuchTable) 786 mock.ExpectQuery(fmt.Sprintf(createTableSQL, "", cputil.SyncerCheckpoint(""))).WillReturnError(errNoSuchTable) 787 mock.ExpectQuery(fmt.Sprintf(createTableSQL, "", cputil.LoaderCheckpoint(""))).WillReturnError(errNoSuchTable) 788 mock.ExpectQuery(fmt.Sprintf(createTableSQL, "", cputil.LightningCheckpoint(""))).WillReturnError(errNoSuchTable) 789 mock.ExpectQuery(fmt.Sprintf(createTableSQL, "", cputil.SyncerCheckpoint(""))).WillReturnError(errNoSuchTable) 790 mock.ExpectQuery("SHOW VARIABLES LIKE 'sql_mode'").WillReturnRows(sqlmock.NewRows([]string{"Variable_name", "Value"}).AddRow("sql_mode", "")) 791 mock.ExpectQuery("SHOW CREATE TABLE .*").WillReturnRows(sqlmock.NewRows([]string{"Table", "Create Table"}).AddRow(tb1, fmt.Sprintf(createTable1, tb1))) 792 mock.ExpectQuery("SHOW CREATE TABLE .*").WillReturnRows(sqlmock.NewRows([]string{"Table", "Create Table"}).AddRow(tb1, fmt.Sprintf(createTable1, tb2))) 793 _, err = RunCheckOnConfigs(context.Background(), cfgs, false, 100, 100) 794 require.ErrorContains(t, err, "same table name in case-insensitive") 795 } 796 797 func TestMetaPositionChecking(t *testing.T) { 798 cfgs := []*config.SubTaskConfig{ 799 { 800 Mode: config.ModeIncrement, 801 UseRelay: false, 802 Meta: nil, 803 IgnoreCheckingItems: ignoreExcept(map[string]struct{}{config.MetaPositionChecking: {}}), 804 }, 805 } 806 checkHappyPath(t, func() { 807 _ = initMockDB(t) 808 }, cfgs) 809 810 cfgs = []*config.SubTaskConfig{ 811 { 812 Mode: config.ModeIncrement, 813 UseRelay: false, 814 SyncerConfig: config.SyncerConfig{EnableGTID: true}, 815 Meta: &config.Meta{}, 816 IgnoreCheckingItems: ignoreExcept(map[string]struct{}{config.MetaPositionChecking: {}}), 817 }, 818 } 819 checkHappyPath(t, func() { 820 _ = initMockDB(t) 821 }, cfgs) 822 823 cfgs = []*config.SubTaskConfig{ 824 { 825 Mode: config.ModeIncrement, 826 UseRelay: false, 827 Meta: &config.Meta{BinLogGTID: "938bc44f-4acc-11ed-a147-0242ac110003:1-8"}, 828 IgnoreCheckingItems: ignoreExcept(map[string]struct{}{config.MetaPositionChecking: {}}), 829 }, 830 } 831 checkHappyPath(t, func() { 832 _ = initMockDB(t) 833 }, cfgs) 834 835 cfgs = []*config.SubTaskConfig{ 836 { 837 Mode: config.ModeIncrement, 838 UseRelay: true, 839 Meta: &config.Meta{BinLogName: "mysql-bin.000001", BinLogPos: 123}, 840 IgnoreCheckingItems: ignoreExcept(map[string]struct{}{config.MetaPositionChecking: {}}), 841 }, 842 } 843 checkHappyPath(t, func() { 844 _ = initMockDB(t) 845 }, cfgs) 846 847 cfgs = []*config.SubTaskConfig{ 848 { 849 Mode: config.ModeAll, 850 UseRelay: false, 851 Meta: &config.Meta{BinLogName: "mysql-bin.000001", BinLogPos: 123}, 852 IgnoreCheckingItems: ignoreExcept(map[string]struct{}{config.MetaPositionChecking: {}}), 853 }, 854 } 855 checkHappyPath(t, func() { 856 _ = initMockDB(t) 857 }, cfgs) 858 } 859 860 func initMockDB(t *testing.T) sqlmock.Sqlmock { 861 t.Helper() 862 863 mock, err := conn.MockDefaultDBProvider() 864 require.NoError(t, err) 865 mock.ExpectQuery("SHOW DATABASES").WillReturnRows(sqlmock.NewRows([]string{"DATABASE"}).AddRow(schema)) 866 mock.ExpectQuery("SHOW FULL TABLES").WillReturnRows(sqlmock.NewRows([]string{"Tables_in_" + schema, "Table_type"}).AddRow(tb1, "BASE TABLE").AddRow(tb2, "BASE TABLE")) 867 return mock 868 } 869 870 func checkHappyPath(t *testing.T, pre func(), cfgs []*config.SubTaskConfig) { 871 t.Helper() 872 873 pre() 874 msg, err := CheckSyncConfig(context.Background(), cfgs, common.DefaultErrorCnt, common.DefaultWarnCnt) 875 require.NoError(t, err) 876 require.Equal(t, CheckTaskSuccess, msg) 877 878 pre() 879 result, err := RunCheckOnConfigs(context.Background(), cfgs, false, 100, 100) 880 require.NoError(t, err) 881 require.True(t, result.Summary.Passed) 882 require.Equal(t, int64(0), result.Summary.Warning) 883 }