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 }