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 }