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