github.com/pingcap/tidb-lightning@v5.0.0-rc.0.20210428090220-84b649866577+incompatible/lightning/backend/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 backend_test
    15  
    16  import (
    17  	"context"
    18  	"database/sql"
    19  	"fmt"
    20  
    21  	"github.com/pingcap/parser/charset"
    22  
    23  	"github.com/DATA-DOG/go-sqlmock"
    24  	. "github.com/pingcap/check"
    25  	"github.com/pingcap/parser/model"
    26  	"github.com/pingcap/parser/mysql"
    27  	"github.com/pingcap/tidb/table"
    28  	"github.com/pingcap/tidb/table/tables"
    29  	"github.com/pingcap/tidb/types"
    30  
    31  	kv "github.com/pingcap/tidb-lightning/lightning/backend"
    32  	"github.com/pingcap/tidb-lightning/lightning/config"
    33  	"github.com/pingcap/tidb-lightning/lightning/log"
    34  	"github.com/pingcap/tidb-lightning/lightning/verification"
    35  )
    36  
    37  var _ = Suite(&mysqlSuite{})
    38  
    39  type mysqlSuite struct {
    40  	dbHandle *sql.DB
    41  	mockDB   sqlmock.Sqlmock
    42  	backend  kv.Backend
    43  	tbl      table.Table
    44  }
    45  
    46  func (s *mysqlSuite) SetUpTest(c *C) {
    47  	db, mock, err := sqlmock.New()
    48  	c.Assert(err, IsNil)
    49  
    50  	tys := []byte{mysql.TypeLong, mysql.TypeLong, mysql.TypeTiny, mysql.TypeInt24, mysql.TypeFloat, mysql.TypeDouble,
    51  		mysql.TypeDouble, mysql.TypeDouble, mysql.TypeVarchar, mysql.TypeBlob, mysql.TypeBit, mysql.TypeNewDecimal, mysql.TypeEnum}
    52  	cols := make([]*model.ColumnInfo, 0, len(tys))
    53  	for i, ty := range tys {
    54  		col := &model.ColumnInfo{ID: int64(i + 1), Name: model.NewCIStr(fmt.Sprintf("c%d", i)), State: model.StatePublic, Offset: i, FieldType: *types.NewFieldType(ty)}
    55  		cols = append(cols, col)
    56  	}
    57  	tblInfo := &model.TableInfo{ID: 1, Columns: cols, PKIsHandle: false, State: model.StatePublic}
    58  	tbl, err := tables.TableFromMeta(kv.NewPanickingAllocators(0), tblInfo)
    59  	c.Assert(err, IsNil)
    60  
    61  	s.dbHandle = db
    62  	s.mockDB = mock
    63  	s.backend = kv.NewTiDBBackend(db, config.ReplaceOnDup)
    64  	s.tbl = tbl
    65  }
    66  
    67  func (s *mysqlSuite) TearDownTest(c *C) {
    68  	s.backend.Close()
    69  	c.Assert(s.mockDB.ExpectationsWereMet(), IsNil)
    70  }
    71  
    72  func (s *mysqlSuite) TestWriteRowsReplaceOnDup(c *C) {
    73  	s.mockDB.
    74  		ExpectExec("\\QREPLACE INTO `foo`.`bar`(`a`,`b`,`c`,`d`,`e`,`f`,`g`,`h`,`i`,`j`,`k`,`l`,`m`,`n`,`o`) VALUES(18446744073709551615,-9223372036854775808,0,NULL,7.5,5e-324,1.7976931348623157e+308,0,'甲乙丙\\r\\n\\0\\Z''\"\\\\`',x'000000abcdef',2557891634,'12.5',51)\\E").
    75  		WillReturnResult(sqlmock.NewResult(1, 1))
    76  
    77  	ctx := context.Background()
    78  	logger := log.L()
    79  
    80  	engine, err := s.backend.OpenEngine(ctx, "`foo`.`bar`", 1)
    81  	c.Assert(err, IsNil)
    82  
    83  	dataRows := s.backend.MakeEmptyRows()
    84  	dataChecksum := verification.MakeKVChecksum(0, 0, 0)
    85  	indexRows := s.backend.MakeEmptyRows()
    86  	indexChecksum := verification.MakeKVChecksum(0, 0, 0)
    87  
    88  	cols := s.tbl.Cols()
    89  	perms := make([]int, 0, len(s.tbl.Cols())+1)
    90  	for i := 0; i < len(cols); i++ {
    91  		perms = append(perms, i)
    92  	}
    93  	perms = append(perms, -1)
    94  	encoder, err := s.backend.NewEncoder(s.tbl, &kv.SessionOptions{SQLMode: 0, Timestamp: 1234567890})
    95  	c.Assert(err, IsNil)
    96  	row, err := encoder.Encode(logger, []types.Datum{
    97  		types.NewUintDatum(18446744073709551615),
    98  		types.NewIntDatum(-9223372036854775808),
    99  		types.NewUintDatum(0),
   100  		{},
   101  		types.NewFloat32Datum(7.5),
   102  		types.NewFloat64Datum(5e-324),
   103  		types.NewFloat64Datum(1.7976931348623157e+308),
   104  		types.NewFloat64Datum(-0.0),
   105  		types.NewStringDatum("甲乙丙\r\n\x00\x1a'\"\\`"),
   106  		types.NewBinaryLiteralDatum(types.NewBinaryLiteralFromUint(0xabcdef, 6)),
   107  		types.NewMysqlBitDatum(types.NewBinaryLiteralFromUint(0x98765432, 4)),
   108  		types.NewDecimalDatum(types.NewDecFromFloatForTest(12.5)),
   109  		types.NewMysqlEnumDatum(types.Enum{Name: "ENUM_NAME", Value: 51}),
   110  	}, 1, perms)
   111  	c.Assert(err, IsNil)
   112  	row.ClassifyAndAppend(&dataRows, &dataChecksum, &indexRows, &indexChecksum)
   113  
   114  	err = engine.WriteRows(ctx, []string{"a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o"}, dataRows)
   115  	c.Assert(err, IsNil)
   116  }
   117  
   118  func (s *mysqlSuite) TestWriteRowsIgnoreOnDup(c *C) {
   119  	s.mockDB.
   120  		ExpectExec("\\QINSERT IGNORE INTO `foo`.`bar`(`a`) VALUES(1)\\E").
   121  		WillReturnResult(sqlmock.NewResult(1, 1))
   122  
   123  	ctx := context.Background()
   124  	logger := log.L()
   125  
   126  	ignoreBackend := kv.NewTiDBBackend(s.dbHandle, config.IgnoreOnDup)
   127  	engine, err := ignoreBackend.OpenEngine(ctx, "`foo`.`bar`", 1)
   128  	c.Assert(err, IsNil)
   129  
   130  	dataRows := ignoreBackend.MakeEmptyRows()
   131  	dataChecksum := verification.MakeKVChecksum(0, 0, 0)
   132  	indexRows := ignoreBackend.MakeEmptyRows()
   133  	indexChecksum := verification.MakeKVChecksum(0, 0, 0)
   134  
   135  	encoder, err := ignoreBackend.NewEncoder(s.tbl, &kv.SessionOptions{})
   136  	c.Assert(err, IsNil)
   137  	row, err := encoder.Encode(logger, []types.Datum{
   138  		types.NewIntDatum(1),
   139  	}, 1, []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, -1})
   140  	c.Assert(err, IsNil)
   141  	row.ClassifyAndAppend(&dataRows, &dataChecksum, &indexRows, &indexChecksum)
   142  
   143  	err = engine.WriteRows(ctx, []string{"a"}, dataRows)
   144  	c.Assert(err, IsNil)
   145  
   146  	// test encode rows with _tidb_rowid
   147  	encoder, err = ignoreBackend.NewEncoder(s.tbl, &kv.SessionOptions{})
   148  	c.Assert(err, IsNil)
   149  	row, err = encoder.Encode(logger, []types.Datum{
   150  		types.NewIntDatum(1),
   151  		types.NewIntDatum(1), // _tidb_rowid field
   152  	}, 1, []int{0, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 1})
   153  	c.Assert(err, IsNil)
   154  }
   155  
   156  func (s *mysqlSuite) TestWriteRowsErrorOnDup(c *C) {
   157  	s.mockDB.
   158  		ExpectExec("\\QINSERT INTO `foo`.`bar`(`a`) VALUES(1)\\E").
   159  		WillReturnResult(sqlmock.NewResult(1, 1))
   160  
   161  	ctx := context.Background()
   162  	logger := log.L()
   163  
   164  	ignoreBackend := kv.NewTiDBBackend(s.dbHandle, config.ErrorOnDup)
   165  	engine, err := ignoreBackend.OpenEngine(ctx, "`foo`.`bar`", 1)
   166  	c.Assert(err, IsNil)
   167  
   168  	dataRows := ignoreBackend.MakeEmptyRows()
   169  	dataChecksum := verification.MakeKVChecksum(0, 0, 0)
   170  	indexRows := ignoreBackend.MakeEmptyRows()
   171  	indexChecksum := verification.MakeKVChecksum(0, 0, 0)
   172  
   173  	encoder, err := ignoreBackend.NewEncoder(s.tbl, &kv.SessionOptions{})
   174  	c.Assert(err, IsNil)
   175  	row, err := encoder.Encode(logger, []types.Datum{
   176  		types.NewIntDatum(1),
   177  	}, 1, []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, -1})
   178  	c.Assert(err, IsNil)
   179  
   180  	row.ClassifyAndAppend(&dataRows, &dataChecksum, &indexRows, &indexChecksum)
   181  
   182  	err = engine.WriteRows(ctx, []string{"a"}, dataRows)
   183  	c.Assert(err, IsNil)
   184  }
   185  
   186  // TODO: temporarily disable this test before we fix strict mode
   187  func (s *mysqlSuite) testStrictMode(c *C) {
   188  	ft := *types.NewFieldType(mysql.TypeVarchar)
   189  	ft.Charset = charset.CharsetUTF8MB4
   190  	col0 := &model.ColumnInfo{ID: 1, Name: model.NewCIStr("s0"), State: model.StatePublic, Offset: 0, FieldType: ft}
   191  	ft = *types.NewFieldType(mysql.TypeString)
   192  	ft.Charset = charset.CharsetASCII
   193  	col1 := &model.ColumnInfo{ID: 2, Name: model.NewCIStr("s1"), State: model.StatePublic, Offset: 1, FieldType: ft}
   194  	tblInfo := &model.TableInfo{ID: 1, Columns: []*model.ColumnInfo{col0, col1}, PKIsHandle: false, State: model.StatePublic}
   195  	tbl, err := tables.TableFromMeta(kv.NewPanickingAllocators(0), tblInfo)
   196  	c.Assert(err, IsNil)
   197  
   198  	bk := kv.NewTiDBBackend(s.dbHandle, config.ErrorOnDup)
   199  	encoder, err := bk.NewEncoder(tbl, &kv.SessionOptions{SQLMode: mysql.ModeStrictAllTables})
   200  	c.Assert(err, IsNil)
   201  
   202  	logger := log.L()
   203  	_, err = encoder.Encode(logger, []types.Datum{
   204  		types.NewStringDatum("test"),
   205  	}, 1, []int{0, -1, -1})
   206  	c.Assert(err, IsNil)
   207  
   208  	_, err = encoder.Encode(logger, []types.Datum{
   209  		types.NewStringDatum("\xff\xff\xff\xff"),
   210  	}, 1, []int{0, -1, -1})
   211  	c.Assert(err, ErrorMatches, `.*incorrect utf8 value .* for column s0`)
   212  
   213  	// oepn a new encode because column count changed.
   214  	encoder, err = bk.NewEncoder(tbl, &kv.SessionOptions{SQLMode: mysql.ModeStrictAllTables})
   215  	c.Assert(err, IsNil)
   216  	_, err = encoder.Encode(logger, []types.Datum{
   217  		types.NewStringDatum(""),
   218  		types.NewStringDatum("非 ASCII 字符串"),
   219  	}, 1, []int{0, 1, -1})
   220  	c.Assert(err, ErrorMatches, ".*incorrect ascii value .* for column s1")
   221  }
   222  
   223  func (s *mysqlSuite) TestFetchRemoteTableModels_3_x(c *C) {
   224  	s.mockDB.ExpectBegin()
   225  	s.mockDB.ExpectQuery("SELECT version()").
   226  		WillReturnRows(sqlmock.NewRows([]string{"version()"}).AddRow("5.7.25-TiDB-v3.0.18"))
   227  	s.mockDB.ExpectQuery("\\QSELECT table_name, column_name, column_type, extra FROM information_schema.columns WHERE table_schema = ? ORDER BY table_name, ordinal_position;\\E").
   228  		WithArgs("test").
   229  		WillReturnRows(sqlmock.NewRows([]string{"table_name", "column_name", "column_type", "extra"}).
   230  			AddRow("t", "id", "int(10)", "auto_increment"))
   231  	s.mockDB.ExpectCommit()
   232  
   233  	bk := kv.NewTiDBBackend(s.dbHandle, config.ErrorOnDup)
   234  	tableInfos, err := bk.FetchRemoteTableModels(context.Background(), "test")
   235  	c.Assert(err, IsNil)
   236  	c.Assert(tableInfos, DeepEquals, []*model.TableInfo{
   237  		{
   238  			Name:       model.NewCIStr("t"),
   239  			State:      model.StatePublic,
   240  			PKIsHandle: true,
   241  			Columns: []*model.ColumnInfo{
   242  				{
   243  					Name:   model.NewCIStr("id"),
   244  					Offset: 0,
   245  					State:  model.StatePublic,
   246  					FieldType: types.FieldType{
   247  						Flag: mysql.AutoIncrementFlag,
   248  					},
   249  				},
   250  			},
   251  		},
   252  	})
   253  }
   254  
   255  func (s *mysqlSuite) TestFetchRemoteTableModels_4_0(c *C) {
   256  	s.mockDB.ExpectBegin()
   257  	s.mockDB.ExpectQuery("SELECT version()").
   258  		WillReturnRows(sqlmock.NewRows([]string{"version()"}).AddRow("5.7.25-TiDB-v4.0.0"))
   259  	s.mockDB.ExpectQuery("\\QSELECT table_name, column_name, column_type, extra FROM information_schema.columns WHERE table_schema = ? ORDER BY table_name, ordinal_position;\\E").
   260  		WithArgs("test").
   261  		WillReturnRows(sqlmock.NewRows([]string{"table_name", "column_name", "column_type", "extra"}).
   262  			AddRow("t", "id", "bigint(20) unsigned", "auto_increment"))
   263  	s.mockDB.ExpectQuery("SHOW TABLE `test`.`t` NEXT_ROW_ID").
   264  		WillReturnRows(sqlmock.NewRows([]string{"DB_NAME", "TABLE_NAME", "COLUMN_NAME", "NEXT_GLOBAL_ROW_ID"}).
   265  			AddRow("test", "t", "id", int64(1)))
   266  	s.mockDB.ExpectCommit()
   267  
   268  	bk := kv.NewTiDBBackend(s.dbHandle, config.ErrorOnDup)
   269  	tableInfos, err := bk.FetchRemoteTableModels(context.Background(), "test")
   270  	c.Assert(err, IsNil)
   271  	c.Assert(tableInfos, DeepEquals, []*model.TableInfo{
   272  		{
   273  			Name:       model.NewCIStr("t"),
   274  			State:      model.StatePublic,
   275  			PKIsHandle: true,
   276  			Columns: []*model.ColumnInfo{
   277  				{
   278  					Name:   model.NewCIStr("id"),
   279  					Offset: 0,
   280  					State:  model.StatePublic,
   281  					FieldType: types.FieldType{
   282  						Flag: mysql.AutoIncrementFlag | mysql.UnsignedFlag,
   283  					},
   284  				},
   285  			},
   286  		},
   287  	})
   288  }
   289  
   290  func (s *mysqlSuite) TestFetchRemoteTableModels_4_x_auto_increment(c *C) {
   291  	s.mockDB.ExpectBegin()
   292  	s.mockDB.ExpectQuery("SELECT version()").
   293  		WillReturnRows(sqlmock.NewRows([]string{"version()"}).AddRow("5.7.25-TiDB-v4.0.7"))
   294  	s.mockDB.ExpectQuery("\\QSELECT table_name, column_name, column_type, extra FROM information_schema.columns WHERE table_schema = ? ORDER BY table_name, ordinal_position;\\E").
   295  		WithArgs("test").
   296  		WillReturnRows(sqlmock.NewRows([]string{"table_name", "column_name", "column_type", "extra"}).
   297  			AddRow("t", "id", "bigint(20)", ""))
   298  	s.mockDB.ExpectQuery("SHOW TABLE `test`.`t` NEXT_ROW_ID").
   299  		WillReturnRows(sqlmock.NewRows([]string{"DB_NAME", "TABLE_NAME", "COLUMN_NAME", "NEXT_GLOBAL_ROW_ID", "ID_TYPE"}).
   300  			AddRow("test", "t", "id", int64(1), "AUTO_INCREMENT"))
   301  	s.mockDB.ExpectCommit()
   302  
   303  	bk := kv.NewTiDBBackend(s.dbHandle, config.ErrorOnDup)
   304  	tableInfos, err := bk.FetchRemoteTableModels(context.Background(), "test")
   305  	c.Assert(err, IsNil)
   306  	c.Assert(tableInfos, DeepEquals, []*model.TableInfo{
   307  		{
   308  			Name:       model.NewCIStr("t"),
   309  			State:      model.StatePublic,
   310  			PKIsHandle: true,
   311  			Columns: []*model.ColumnInfo{
   312  				{
   313  					Name:   model.NewCIStr("id"),
   314  					Offset: 0,
   315  					State:  model.StatePublic,
   316  					FieldType: types.FieldType{
   317  						Flag: mysql.AutoIncrementFlag,
   318  					},
   319  				},
   320  			},
   321  		},
   322  	})
   323  }
   324  
   325  func (s *mysqlSuite) TestFetchRemoteTableModels_4_x_auto_random(c *C) {
   326  	s.mockDB.ExpectBegin()
   327  	s.mockDB.ExpectQuery("SELECT version()").
   328  		WillReturnRows(sqlmock.NewRows([]string{"version()"}).AddRow("5.7.25-TiDB-v4.0.7"))
   329  	s.mockDB.ExpectQuery("\\QSELECT table_name, column_name, column_type, extra FROM information_schema.columns WHERE table_schema = ? ORDER BY table_name, ordinal_position;\\E").
   330  		WithArgs("test").
   331  		WillReturnRows(sqlmock.NewRows([]string{"table_name", "column_name", "column_type", "extra"}).
   332  			AddRow("t", "id", "bigint(20)", ""))
   333  	s.mockDB.ExpectQuery("SHOW TABLE `test`.`t` NEXT_ROW_ID").
   334  		WillReturnRows(sqlmock.NewRows([]string{"DB_NAME", "TABLE_NAME", "COLUMN_NAME", "NEXT_GLOBAL_ROW_ID", "ID_TYPE"}).
   335  			AddRow("test", "t", "id", int64(1), "AUTO_RANDOM"))
   336  	s.mockDB.ExpectCommit()
   337  
   338  	bk := kv.NewTiDBBackend(s.dbHandle, config.ErrorOnDup)
   339  	tableInfos, err := bk.FetchRemoteTableModels(context.Background(), "test")
   340  	c.Assert(err, IsNil)
   341  	c.Assert(tableInfos, DeepEquals, []*model.TableInfo{
   342  		{
   343  			Name:           model.NewCIStr("t"),
   344  			State:          model.StatePublic,
   345  			PKIsHandle:     true,
   346  			AutoRandomBits: 1,
   347  			Columns: []*model.ColumnInfo{
   348  				{
   349  					Name:   model.NewCIStr("id"),
   350  					Offset: 0,
   351  					State:  model.StatePublic,
   352  					FieldType: types.FieldType{
   353  						Flag: mysql.PriKeyFlag,
   354  					},
   355  				},
   356  			},
   357  		},
   358  	})
   359  }