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  }