github.com/pingcap/tidb-lightning@v5.0.0-rc.0.20210428090220-84b649866577+incompatible/lightning/restore/tidb_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 restore 15 16 import ( 17 "context" 18 "net/http" 19 "testing" 20 21 "github.com/DATA-DOG/go-sqlmock" 22 "github.com/go-sql-driver/mysql" 23 . "github.com/pingcap/check" 24 "github.com/pingcap/errors" 25 "github.com/pingcap/parser/ast" 26 "github.com/pingcap/parser/model" 27 tmysql "github.com/pingcap/parser/mysql" 28 "github.com/pingcap/tidb/ddl" 29 "github.com/pingcap/tidb/util/mock" 30 31 "github.com/pingcap/tidb-lightning/lightning/glue" 32 33 "github.com/pingcap/tidb-lightning/lightning/checkpoints" 34 "github.com/pingcap/tidb-lightning/lightning/mydump" 35 ) 36 37 var _ = Suite(&tidbSuite{}) 38 39 type tidbSuite struct { 40 mockDB sqlmock.Sqlmock 41 handler http.Handler 42 timgr *TiDBManager 43 tiGlue glue.Glue 44 } 45 46 func TestTiDB(t *testing.T) { 47 TestingT(t) 48 } 49 50 func (s *tidbSuite) SetUpTest(c *C) { 51 db, mock, err := sqlmock.New() 52 c.Assert(err, IsNil) 53 54 s.mockDB = mock 55 defaultSQLMode, err := tmysql.GetSQLMode(tmysql.DefaultSQLMode) 56 c.Assert(err, IsNil) 57 58 s.timgr = NewTiDBManagerWithDB(db, defaultSQLMode) 59 s.tiGlue = glue.NewExternalTiDBGlue(db, defaultSQLMode) 60 } 61 62 func (s *tidbSuite) TearDownTest(c *C) { 63 s.timgr.Close() 64 c.Assert(s.mockDB.ExpectationsWereMet(), IsNil) 65 } 66 67 func (s *tidbSuite) TestCreateTableIfNotExistsStmt(c *C) { 68 dbName := "testdb" 69 createTableIfNotExistsStmt := func(createTable, tableName string) []string { 70 res, err := createTableIfNotExistsStmt(s.tiGlue.GetParser(), createTable, dbName, tableName) 71 c.Assert(err, IsNil) 72 return res 73 } 74 75 c.Assert( 76 createTableIfNotExistsStmt("CREATE TABLE `foo`(`bar` TINYINT(1));", "foo"), 77 DeepEquals, 78 []string{"CREATE TABLE IF NOT EXISTS `testdb`.`foo` (`bar` TINYINT(1));"}, 79 ) 80 81 c.Assert( 82 createTableIfNotExistsStmt("CREATE TABLE IF NOT EXISTS `foo`(`bar` TINYINT(1));", "foo"), 83 DeepEquals, 84 []string{"CREATE TABLE IF NOT EXISTS `testdb`.`foo` (`bar` TINYINT(1));"}, 85 ) 86 87 // case insensitive 88 c.Assert( 89 createTableIfNotExistsStmt("/* cOmmEnt */ creAte tablE `fOo`(`bar` TinyinT(1));", "fOo"), 90 DeepEquals, 91 []string{"CREATE TABLE IF NOT EXISTS `testdb`.`fOo` (`bar` TINYINT(1));"}, 92 ) 93 94 c.Assert( 95 createTableIfNotExistsStmt("/* coMMenT */ crEatE tAble If not EXISts `FoO`(`bAR` tiNyInT(1));", "FoO"), 96 DeepEquals, 97 []string{"CREATE TABLE IF NOT EXISTS `testdb`.`FoO` (`bAR` TINYINT(1));"}, 98 ) 99 100 // only one "CREATE TABLE" is replaced 101 c.Assert( 102 createTableIfNotExistsStmt("CREATE TABLE `foo`(`bar` INT(1) COMMENT 'CREATE TABLE');", "foo"), 103 DeepEquals, 104 []string{"CREATE TABLE IF NOT EXISTS `testdb`.`foo` (`bar` INT(1) COMMENT 'CREATE TABLE');"}, 105 ) 106 107 // upper case becomes shorter 108 c.Assert( 109 createTableIfNotExistsStmt("CREATE TABLE `ſ`(`ı` TINYINT(1));", "ſ"), 110 DeepEquals, 111 []string{"CREATE TABLE IF NOT EXISTS `testdb`.`ſ` (`ı` TINYINT(1));"}, 112 ) 113 114 // upper case becomes longer 115 c.Assert( 116 createTableIfNotExistsStmt("CREATE TABLE `ɑ`(`ȿ` TINYINT(1));", "ɑ"), 117 DeepEquals, 118 []string{"CREATE TABLE IF NOT EXISTS `testdb`.`ɑ` (`ȿ` TINYINT(1));"}, 119 ) 120 121 // non-utf-8 122 c.Assert( 123 createTableIfNotExistsStmt("CREATE TABLE `\xcc\xcc\xcc`(`\xdd\xdd\xdd` TINYINT(1));", "\xcc\xcc\xcc"), 124 DeepEquals, 125 []string{"CREATE TABLE IF NOT EXISTS `testdb`.`\xcc\xcc\xcc` (`ÝÝÝ` TINYINT(1));"}, 126 ) 127 128 // renaming a table 129 c.Assert( 130 createTableIfNotExistsStmt("create table foo(x int);", "ba`r"), 131 DeepEquals, 132 []string{"CREATE TABLE IF NOT EXISTS `testdb`.`ba``r` (`x` INT);"}, 133 ) 134 135 // conditional comments 136 c.Assert( 137 createTableIfNotExistsStmt(` 138 /*!40101 SET NAMES binary*/; 139 /*!40014 SET FOREIGN_KEY_CHECKS=0*/; 140 CREATE TABLE x.y (z double) ENGINE=InnoDB AUTO_INCREMENT=8343230 DEFAULT CHARSET=utf8; 141 `, "m"), 142 DeepEquals, 143 []string{ 144 "SET NAMES 'binary';", 145 "SET @@SESSION.`FOREIGN_KEY_CHECKS`=0;", 146 "CREATE TABLE IF NOT EXISTS `testdb`.`m` (`z` DOUBLE) ENGINE = InnoDB AUTO_INCREMENT = 8343230 DEFAULT CHARACTER SET = UTF8;", 147 }, 148 ) 149 150 // create view 151 c.Assert( 152 createTableIfNotExistsStmt(` 153 /*!40101 SET NAMES binary*/; 154 DROP TABLE IF EXISTS v2; 155 DROP VIEW IF EXISTS v2; 156 SET @PREV_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT; 157 SET @PREV_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS; 158 SET @PREV_COLLATION_CONNECTION=@@COLLATION_CONNECTION; 159 SET character_set_client = utf8; 160 SET character_set_results = utf8; 161 SET collation_connection = utf8_general_ci; 162 CREATE ALGORITHM=UNDEFINED DEFINER=root@192.168.198.178 SQL SECURITY DEFINER VIEW v2 (s) AS SELECT s FROM db1.v1 WHERE i<2; 163 SET character_set_client = @PREV_CHARACTER_SET_CLIENT; 164 SET character_set_results = @PREV_CHARACTER_SET_RESULTS; 165 SET collation_connection = @PREV_COLLATION_CONNECTION; 166 `, "m"), 167 DeepEquals, 168 []string{ 169 "SET NAMES 'binary';", 170 "DROP TABLE IF EXISTS `testdb`.`m`;", 171 "DROP VIEW IF EXISTS `testdb`.`m`;", 172 "SET @`PREV_CHARACTER_SET_CLIENT`=@@`character_set_client`;", 173 "SET @`PREV_CHARACTER_SET_RESULTS`=@@`character_set_results`;", 174 "SET @`PREV_COLLATION_CONNECTION`=@@`collation_connection`;", 175 "SET @@SESSION.`character_set_client`=`utf8`;", 176 "SET @@SESSION.`character_set_results`=`utf8`;", 177 "SET @@SESSION.`collation_connection`=`utf8_general_ci`;", 178 "CREATE ALGORITHM = UNDEFINED DEFINER = `root`@`192.168.198.178` SQL SECURITY DEFINER VIEW `testdb`.`m` (`s`) AS SELECT `s` FROM `db1`.`v1` WHERE `i`<2;", 179 "SET @@SESSION.`character_set_client`=@`PREV_CHARACTER_SET_CLIENT`;", 180 "SET @@SESSION.`character_set_results`=@`PREV_CHARACTER_SET_RESULTS`;", 181 "SET @@SESSION.`collation_connection`=@`PREV_COLLATION_CONNECTION`;", 182 }, 183 ) 184 } 185 186 func (s *tidbSuite) TestInitSchema(c *C) { 187 ctx := context.Background() 188 189 s.mockDB. 190 ExpectExec("CREATE DATABASE IF NOT EXISTS `db`"). 191 WillReturnResult(sqlmock.NewResult(1, 1)) 192 s.mockDB. 193 ExpectExec("\\QCREATE TABLE IF NOT EXISTS `db`.`t1` (`a` INT PRIMARY KEY,`b` VARCHAR(200));\\E"). 194 WillReturnResult(sqlmock.NewResult(2, 1)) 195 s.mockDB. 196 ExpectExec("\\QSET @@SESSION.`FOREIGN_KEY_CHECKS`=0;\\E"). 197 WillReturnResult(sqlmock.NewResult(0, 0)) 198 s.mockDB. 199 ExpectExec("\\QCREATE TABLE IF NOT EXISTS `db`.`t2` (`xx` TEXT) AUTO_INCREMENT = 11203;\\E"). 200 WillReturnResult(sqlmock.NewResult(2, 1)) 201 s.mockDB. 202 ExpectClose() 203 204 s.mockDB.MatchExpectationsInOrder(false) // maps are unordered. 205 err := InitSchema(ctx, s.tiGlue, "db", map[string]string{ 206 "t1": "create table t1 (a int primary key, b varchar(200));", 207 "t2": "/*!40014 SET FOREIGN_KEY_CHECKS=0*/;CREATE TABLE `db`.`t2` (xx TEXT) AUTO_INCREMENT=11203;", 208 }) 209 s.mockDB.MatchExpectationsInOrder(true) 210 c.Assert(err, IsNil) 211 } 212 213 func (s *tidbSuite) TestInitSchemaSyntaxError(c *C) { 214 ctx := context.Background() 215 216 s.mockDB. 217 ExpectExec("CREATE DATABASE IF NOT EXISTS `db`"). 218 WillReturnResult(sqlmock.NewResult(1, 1)) 219 s.mockDB. 220 ExpectClose() 221 222 err := InitSchema(ctx, s.tiGlue, "db", map[string]string{ 223 "t1": "create table `t1` with invalid syntax;", 224 }) 225 c.Assert(err, NotNil) 226 } 227 228 func (s *tidbSuite) TestInitSchemaErrorLost(c *C) { 229 ctx := context.Background() 230 231 s.mockDB. 232 ExpectExec("CREATE DATABASE IF NOT EXISTS `db`"). 233 WillReturnResult(sqlmock.NewResult(1, 1)) 234 235 s.mockDB. 236 ExpectExec("CREATE TABLE IF NOT EXISTS.*"). 237 WillReturnError(&mysql.MySQLError{ 238 Number: tmysql.ErrTooBigFieldlength, 239 Message: "Column length too big", 240 }) 241 242 s.mockDB. 243 ExpectClose() 244 245 err := InitSchema(ctx, s.tiGlue, "db", map[string]string{ 246 "t1": "create table `t1` (a int);", 247 "t2": "create table t2 (a int primary key, b varchar(200));", 248 }) 249 c.Assert(err, ErrorMatches, ".*Column length too big.*") 250 } 251 252 func (s *tidbSuite) TestInitSchemaUnsupportedSchemaError(c *C) { 253 ctx := context.Background() 254 255 s.mockDB. 256 ExpectExec("CREATE DATABASE IF NOT EXISTS `db`"). 257 WillReturnResult(sqlmock.NewResult(1, 1)) 258 s.mockDB. 259 ExpectExec("CREATE TABLE IF NOT EXISTS `db`.`t1`.*"). 260 WillReturnError(&mysql.MySQLError{ 261 Number: tmysql.ErrTooBigFieldlength, 262 Message: "Column length too big", 263 }) 264 s.mockDB. 265 ExpectClose() 266 267 err := InitSchema(ctx, s.tiGlue, "db", map[string]string{ 268 "t1": "create table `t1` (a VARCHAR(999999999));", 269 }) 270 c.Assert(err, ErrorMatches, ".*Column length too big.*") 271 } 272 273 func (s *tidbSuite) TestDropTable(c *C) { 274 ctx := context.Background() 275 276 s.mockDB. 277 ExpectExec("DROP TABLE `db`.`table`"). 278 WillReturnResult(sqlmock.NewResult(1, 1)) 279 s.mockDB. 280 ExpectClose() 281 282 err := s.timgr.DropTable(ctx, "`db`.`table`") 283 c.Assert(err, IsNil) 284 } 285 286 func (s *tidbSuite) TestLoadSchemaInfo(c *C) { 287 ctx := context.Background() 288 289 // Prepare the mock reply. 290 nodes, _, err := s.timgr.parser.Parse( 291 "CREATE TABLE `t1` (`a` INT PRIMARY KEY);"+ 292 "CREATE TABLE `t2` (`b` VARCHAR(20), `c` BOOL, KEY (`b`, `c`))", 293 "", "") 294 c.Assert(err, IsNil) 295 tableInfos := make([]*model.TableInfo, 0, len(nodes)) 296 sctx := mock.NewContext() 297 for i, node := range nodes { 298 c.Assert(node, FitsTypeOf, &ast.CreateTableStmt{}) 299 info, err := ddl.MockTableInfo(sctx, node.(*ast.CreateTableStmt), int64(i+100)) 300 c.Assert(err, IsNil) 301 info.State = model.StatePublic 302 tableInfos = append(tableInfos, info) 303 } 304 305 loaded, err := LoadSchemaInfo(ctx, []*mydump.MDDatabaseMeta{{Name: "db"}}, func(ctx context.Context, schema string) ([]*model.TableInfo, error) { 306 c.Assert(schema, Equals, "db") 307 return tableInfos, nil 308 }) 309 c.Assert(err, IsNil) 310 c.Assert(loaded, DeepEquals, map[string]*checkpoints.TidbDBInfo{ 311 "db": { 312 Name: "db", 313 Tables: map[string]*checkpoints.TidbTableInfo{ 314 "t1": { 315 ID: 100, 316 DB: "db", 317 Name: "t1", 318 Core: tableInfos[0], 319 }, 320 "t2": { 321 ID: 101, 322 DB: "db", 323 Name: "t2", 324 Core: tableInfos[1], 325 }, 326 }, 327 }, 328 }) 329 } 330 331 func (s *tidbSuite) TestLoadSchemaInfoMissing(c *C) { 332 ctx := context.Background() 333 334 _, err := LoadSchemaInfo(ctx, []*mydump.MDDatabaseMeta{{Name: "asdjalsjdlas"}}, func(ctx context.Context, schema string) ([]*model.TableInfo, error) { 335 return nil, errors.Errorf("[schema:1049]Unknown database '%s'", schema) 336 }) 337 c.Assert(err, ErrorMatches, ".*Unknown database.*") 338 } 339 340 func (s *tidbSuite) TestGetGCLifetime(c *C) { 341 ctx := context.Background() 342 343 s.mockDB. 344 ExpectQuery("\\QSELECT VARIABLE_VALUE FROM mysql.tidb WHERE VARIABLE_NAME = 'tikv_gc_life_time'\\E"). 345 WillReturnRows(sqlmock.NewRows([]string{"VARIABLE_VALUE"}).AddRow("10m")) 346 s.mockDB. 347 ExpectClose() 348 349 res, err := ObtainGCLifeTime(ctx, s.timgr.db) 350 c.Assert(err, IsNil) 351 c.Assert(res, Equals, "10m") 352 } 353 354 func (s *tidbSuite) TestSetGCLifetime(c *C) { 355 ctx := context.Background() 356 357 s.mockDB. 358 ExpectExec("\\QUPDATE mysql.tidb SET VARIABLE_VALUE = ? WHERE VARIABLE_NAME = 'tikv_gc_life_time'\\E"). 359 WithArgs("12m"). 360 WillReturnResult(sqlmock.NewResult(1, 1)) 361 s.mockDB. 362 ExpectClose() 363 364 err := UpdateGCLifeTime(ctx, s.timgr.db, "12m") 365 c.Assert(err, IsNil) 366 } 367 368 func (s *tidbSuite) TestAlterAutoInc(c *C) { 369 ctx := context.Background() 370 371 s.mockDB. 372 ExpectExec("\\QALTER TABLE `db`.`table` AUTO_INCREMENT=12345\\E"). 373 WillReturnResult(sqlmock.NewResult(1, 1)) 374 s.mockDB. 375 ExpectClose() 376 377 err := AlterAutoIncrement(ctx, s.tiGlue.GetSQLExecutor(), "`db`.`table`", 12345) 378 c.Assert(err, IsNil) 379 } 380 381 func (s *tidbSuite) TestAlterAutoRandom(c *C) { 382 ctx := context.Background() 383 384 s.mockDB. 385 ExpectExec("\\QALTER TABLE `db`.`table` AUTO_RANDOM_BASE=12345\\E"). 386 WillReturnResult(sqlmock.NewResult(1, 1)) 387 s.mockDB. 388 ExpectClose() 389 390 err := AlterAutoRandom(ctx, s.tiGlue.GetSQLExecutor(), "`db`.`table`", 12345) 391 c.Assert(err, IsNil) 392 } 393 394 func (s *tidbSuite) TestObtainRowFormatVersionSucceed(c *C) { 395 ctx := context.Background() 396 397 s.mockDB. 398 ExpectBegin() 399 s.mockDB. 400 ExpectQuery(`SHOW VARIABLES WHERE Variable_name IN \(.*'tidb_row_format_version'.*\)`). 401 WillReturnRows(sqlmock.NewRows([]string{"Variable_name", "Value"}). 402 AddRow("tidb_row_format_version", "2"). 403 AddRow("max_allowed_packet", "1073741824"). 404 AddRow("div_precision_increment", "10"). 405 AddRow("time_zone", "-08:00"). 406 AddRow("lc_time_names", "ja_JP"). 407 AddRow("default_week_format", "1"). 408 AddRow("block_encryption_mode", "aes-256-cbc"). 409 AddRow("group_concat_max_len", "1073741824")) 410 s.mockDB. 411 ExpectCommit() 412 s.mockDB. 413 ExpectClose() 414 415 sysVars := ObtainImportantVariables(ctx, s.tiGlue.GetSQLExecutor()) 416 c.Assert(sysVars, DeepEquals, map[string]string{ 417 "tidb_row_format_version": "2", 418 "max_allowed_packet": "1073741824", 419 "div_precision_increment": "10", 420 "time_zone": "-08:00", 421 "lc_time_names": "ja_JP", 422 "default_week_format": "1", 423 "block_encryption_mode": "aes-256-cbc", 424 "group_concat_max_len": "1073741824", 425 }) 426 } 427 428 func (s *tidbSuite) TestObtainRowFormatVersionFailure(c *C) { 429 ctx := context.Background() 430 431 s.mockDB. 432 ExpectBegin() 433 s.mockDB. 434 ExpectQuery(`SHOW VARIABLES WHERE Variable_name IN \(.*'tidb_row_format_version'.*\)`). 435 WillReturnRows(sqlmock.NewRows([]string{"Variable_name", "Value"}).AddRow("time_zone", "+00:00")) 436 s.mockDB. 437 ExpectCommit() 438 s.mockDB. 439 ExpectClose() 440 441 sysVars := ObtainImportantVariables(ctx, s.tiGlue.GetSQLExecutor()) 442 c.Assert(sysVars, DeepEquals, map[string]string{ 443 "tidb_row_format_version": "1", 444 "max_allowed_packet": "67108864", 445 "div_precision_increment": "4", 446 "time_zone": "+00:00", 447 "lc_time_names": "en_US", 448 "default_week_format": "0", 449 "block_encryption_mode": "aes-128-ecb", 450 "group_concat_max_len": "1024", 451 }) 452 } 453 454 func (s *tidbSuite) TestObtainNewCollationEnabled(c *C) { 455 ctx := context.Background() 456 457 s.mockDB. 458 ExpectQuery("\\QSELECT variable_value FROM mysql.tidb WHERE variable_name = 'new_collation_enabled'\\E") 459 version := ObtainNewCollationEnabled(ctx, s.tiGlue.GetSQLExecutor()) 460 c.Assert(version, Equals, false) 461 462 kvMap := map[string]bool{ 463 "True": true, 464 "False": false, 465 } 466 for k, v := range kvMap { 467 s.mockDB. 468 ExpectQuery("\\QSELECT variable_value FROM mysql.tidb WHERE variable_name = 'new_collation_enabled'\\E"). 469 WillReturnRows(sqlmock.NewRows([]string{"variable_value"}).AddRow(k)) 470 471 version := ObtainNewCollationEnabled(ctx, s.tiGlue.GetSQLExecutor()) 472 c.Assert(version, Equals, v) 473 } 474 s.mockDB. 475 ExpectClose() 476 477 }