github.com/pingcap/tiflow@v0.0.0-20240520035814-5bf52d54e205/dm/syncer/validate_worker_test.go (about) 1 // Copyright 2022 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 syncer 15 16 import ( 17 "context" 18 "database/sql" 19 "database/sql/driver" 20 "testing" 21 22 "github.com/DATA-DOG/go-sqlmock" 23 gmysql "github.com/go-sql-driver/mysql" 24 "github.com/pingcap/errors" 25 "github.com/pingcap/failpoint" 26 "github.com/pingcap/tidb/pkg/errno" 27 "github.com/pingcap/tidb/pkg/parser/model" 28 "github.com/pingcap/tidb/pkg/parser/mysql" 29 "github.com/pingcap/tidb/pkg/parser/types" 30 "github.com/pingcap/tidb/pkg/util/filter" 31 cdcmodel "github.com/pingcap/tiflow/cdc/model" 32 "github.com/pingcap/tiflow/dm/config" 33 "github.com/pingcap/tiflow/dm/pkg/conn" 34 "github.com/pingcap/tiflow/dm/pkg/log" 35 "github.com/pingcap/tiflow/pkg/sqlmodel" 36 "github.com/stretchr/testify/require" 37 ) 38 39 func genRowChangeJob(tbl filter.Table, tblInfo *model.TableInfo, key string, tp rowChangeJobType, data []interface{}) *rowValidationJob { 40 var beforeImage, afterImage []interface{} 41 switch tp { 42 case rowInsert: 43 afterImage = data 44 case rowUpdated: 45 beforeImage, afterImage = data, data 46 default: 47 beforeImage = data 48 } 49 return &rowValidationJob{ 50 Key: key, 51 Tp: tp, 52 row: sqlmodel.NewRowChange( 53 &cdcmodel.TableName{Schema: tbl.Schema, Table: tbl.Name}, 54 &cdcmodel.TableName{Schema: tbl.Schema, Table: tbl.Name}, 55 beforeImage, afterImage, tblInfo, tblInfo, nil, 56 ), 57 } 58 } 59 60 func TestValidatorWorkerValidateTableChanges(t *testing.T) { 61 require.Nil(t, failpoint.Enable("github.com/pingcap/tiflow/dm/syncer/ValidatorMockUpstreamTZ", `return()`)) 62 defer func() { 63 require.Nil(t, failpoint.Disable("github.com/pingcap/tiflow/dm/syncer/ValidatorMockUpstreamTZ")) 64 }() 65 testFunc := func(t *testing.T, mode string) { 66 t.Helper() 67 tbl1 := filter.Table{Schema: "test", Name: "tbl1"} 68 tbl2 := filter.Table{Schema: "test", Name: "tbl2"} 69 tbl3 := filter.Table{Schema: "test", Name: "tbl3"} 70 tableInfo1 := genValidateTableInfo(t, "create table tbl1(a int primary key, b varchar(100))") 71 tableInfo2 := genValidateTableInfo(t, "create table tbl2(a varchar(100) primary key, b varchar(100))") 72 tableInfo3 := genValidateTableInfo(t, "create table tbl3(a varchar(100) primary key, b varchar(100))") 73 74 cfg := genSubtaskConfig(t) 75 cfg.ValidatorCfg.Mode = mode 76 _, mock, err := conn.InitMockDBFull() 77 mock.MatchExpectationsInOrder(false) 78 require.NoError(t, err) 79 defer func() { 80 conn.DefaultDBProvider = &conn.DefaultDBProviderImpl{} 81 }() 82 syncerObj := NewSyncer(cfg, nil, nil) 83 validator := NewContinuousDataValidator(cfg, syncerObj, false) 84 validator.persistHelper.schemaInitialized.Store(true) 85 require.NoError(t, validator.initialize()) 86 defer validator.cancel() 87 validator.markErrorStarted.Store(true) 88 89 worker := newValidateWorker(validator, 0) 90 91 checkInitStatus := func() { 92 require.Zero(t, worker.pendingRowCounts[rowInsert]) 93 require.Zero(t, worker.pendingRowCounts[rowUpdated]) 94 require.Zero(t, worker.pendingRowCounts[rowDeleted]) 95 require.Zero(t, validator.pendingRowCounts[rowInsert].Load()) 96 require.Zero(t, validator.pendingRowCounts[rowUpdated].Load()) 97 require.Zero(t, validator.pendingRowCounts[rowDeleted].Load()) 98 require.Zero(t, len(worker.pendingChangesMap)) 99 require.Zero(t, len(worker.errorRows)) 100 require.Zero(t, validator.newErrorRowCount.Load()) 101 } 102 103 // just created 104 checkInitStatus() 105 106 // insert & update same table, both row are validated failed 107 worker.updateRowChange(genRowChangeJob(tbl1, tableInfo1, "1", rowInsert, []interface{}{1, "a"})) 108 worker.updateRowChange(genRowChangeJob(tbl1, tableInfo1, "1", rowUpdated, []interface{}{1, "b"})) 109 worker.updateRowChange(genRowChangeJob(tbl1, tableInfo1, "2", rowInsert, []interface{}{2, "2b"})) 110 111 mock.ExpectQuery("SELECT .* FROM .*tbl1.* WHERE .*").WillReturnRows( 112 sqlmock.NewRows([]string{"a", "b"}).AddRow(2, "incorrect data")) 113 worker.validateTableChange() 114 require.Zero(t, validator.result.Errors) 115 require.Len(t, worker.pendingChangesMap, 1) 116 require.Contains(t, worker.pendingChangesMap, tbl1.String()) 117 require.Contains(t, worker.pendingChangesMap[tbl1.String()].jobs, "1") 118 require.Equal(t, rowUpdated, worker.pendingChangesMap[tbl1.String()].jobs["1"].Tp) 119 require.Equal(t, 1, worker.pendingChangesMap[tbl1.String()].jobs["1"].FailedCnt) 120 require.Len(t, worker.errorRows, 0) 121 require.Zero(t, validator.newErrorRowCount.Load()) 122 if mode == config.ValidationFull { 123 require.Len(t, worker.pendingChangesMap[tbl1.String()].jobs, 2) 124 require.Equal(t, int64(1), worker.pendingRowCounts[rowInsert]) 125 require.Equal(t, int64(1), worker.pendingRowCounts[rowUpdated]) 126 require.Equal(t, int64(0), worker.pendingRowCounts[rowDeleted]) 127 require.Equal(t, worker.pendingRowCounts[rowInsert], validator.pendingRowCounts[rowInsert].Load()) 128 require.Equal(t, worker.pendingRowCounts[rowUpdated], validator.pendingRowCounts[rowUpdated].Load()) 129 require.Equal(t, worker.pendingRowCounts[rowDeleted], validator.pendingRowCounts[rowDeleted].Load()) 130 require.Contains(t, worker.pendingChangesMap[tbl1.String()].jobs, "2") 131 require.Equal(t, rowInsert, worker.pendingChangesMap[tbl1.String()].jobs["2"].Tp) 132 require.Equal(t, 1, worker.pendingChangesMap[tbl1.String()].jobs["2"].FailedCnt) 133 } else { 134 // fast mode 135 require.Len(t, worker.pendingChangesMap[tbl1.String()].jobs, 1) 136 require.Equal(t, int64(0), worker.pendingRowCounts[rowInsert]) 137 require.Equal(t, int64(1), worker.pendingRowCounts[rowUpdated]) 138 require.Equal(t, int64(0), worker.pendingRowCounts[rowDeleted]) 139 require.Equal(t, worker.pendingRowCounts[rowInsert], validator.pendingRowCounts[rowInsert].Load()) 140 require.Equal(t, worker.pendingRowCounts[rowUpdated], validator.pendingRowCounts[rowUpdated].Load()) 141 require.Equal(t, worker.pendingRowCounts[rowDeleted], validator.pendingRowCounts[rowDeleted].Load()) 142 } 143 144 // validate again, this time row with pk=2 validate success 145 mock.ExpectQuery("SELECT .* FROM .*tbl1.* WHERE .*").WillReturnRows( 146 sqlmock.NewRows([]string{"a", "b"}).AddRow(2, "2b")) 147 worker.validateTableChange() 148 require.Zero(t, validator.result.Errors) 149 require.Equal(t, int64(0), worker.pendingRowCounts[rowInsert]) 150 require.Equal(t, int64(1), worker.pendingRowCounts[rowUpdated]) 151 require.Equal(t, int64(0), worker.pendingRowCounts[rowDeleted]) 152 require.Equal(t, worker.pendingRowCounts[rowInsert], validator.pendingRowCounts[rowInsert].Load()) 153 require.Equal(t, worker.pendingRowCounts[rowUpdated], validator.pendingRowCounts[rowUpdated].Load()) 154 require.Equal(t, worker.pendingRowCounts[rowDeleted], validator.pendingRowCounts[rowDeleted].Load()) 155 require.Len(t, worker.pendingChangesMap, 1) 156 require.Contains(t, worker.pendingChangesMap, tbl1.String()) 157 require.Len(t, worker.pendingChangesMap[tbl1.String()].jobs, 1) 158 require.Contains(t, worker.pendingChangesMap[tbl1.String()].jobs, "1") 159 require.Equal(t, rowUpdated, worker.pendingChangesMap[tbl1.String()].jobs["1"].Tp) 160 require.Equal(t, 2, worker.pendingChangesMap[tbl1.String()].jobs["1"].FailedCnt) 161 require.Len(t, worker.errorRows, 0) 162 require.Zero(t, validator.newErrorRowCount.Load()) 163 164 // 165 // add 2 delete row of tbl2 and tbl3 166 worker.updateRowChange(genRowChangeJob(tbl2, tableInfo2, "a", rowDeleted, []interface{}{"a", "b"})) 167 worker.updateRowChange(genRowChangeJob(tbl3, tableInfo3, "aa", rowDeleted, []interface{}{"aa", "b"})) 168 169 mock.ExpectQuery("SELECT .* FROM .*tbl1.* WHERE .*").WillReturnRows( 170 sqlmock.NewRows([]string{"a", "b"})) 171 mock.ExpectQuery("SELECT .* FROM .*tbl2.* WHERE .*").WillReturnRows( 172 sqlmock.NewRows([]string{"a", "b"})) 173 mock.ExpectQuery("SELECT .* FROM .*tbl3.* WHERE .*").WillReturnRows( 174 sqlmock.NewRows([]string{"a", "b"}).AddRow("aa", "b")) 175 worker.validateTableChange() 176 require.Zero(t, validator.result.Errors) 177 require.Equal(t, int64(0), worker.pendingRowCounts[rowInsert]) 178 require.Equal(t, int64(1), worker.pendingRowCounts[rowUpdated]) 179 require.Equal(t, int64(1), worker.pendingRowCounts[rowDeleted]) 180 require.Equal(t, worker.pendingRowCounts[rowInsert], validator.pendingRowCounts[rowInsert].Load()) 181 require.Equal(t, worker.pendingRowCounts[rowUpdated], validator.pendingRowCounts[rowUpdated].Load()) 182 require.Equal(t, worker.pendingRowCounts[rowDeleted], validator.pendingRowCounts[rowDeleted].Load()) 183 require.Len(t, worker.pendingChangesMap, 2) 184 require.Contains(t, worker.pendingChangesMap, tbl1.String()) 185 require.Len(t, worker.pendingChangesMap[tbl1.String()].jobs, 1) 186 require.Contains(t, worker.pendingChangesMap[tbl1.String()].jobs, "1") 187 require.Equal(t, rowUpdated, worker.pendingChangesMap[tbl1.String()].jobs["1"].Tp) 188 require.Equal(t, 3, worker.pendingChangesMap[tbl1.String()].jobs["1"].FailedCnt) 189 require.Contains(t, worker.pendingChangesMap, tbl3.String()) 190 require.Len(t, worker.pendingChangesMap[tbl3.String()].jobs, 1) 191 require.Contains(t, worker.pendingChangesMap[tbl3.String()].jobs, "aa") 192 require.Equal(t, rowDeleted, worker.pendingChangesMap[tbl3.String()].jobs["aa"].Tp) 193 require.Equal(t, 1, worker.pendingChangesMap[tbl3.String()].jobs["aa"].FailedCnt) 194 require.Len(t, worker.errorRows, 0) 195 require.Zero(t, validator.newErrorRowCount.Load()) 196 197 // for tbl1, pk=1 is synced, validate success 198 // for tbl3, pk=aa is synced, validate success 199 mock.ExpectQuery("SELECT .* FROM .*tbl1.* WHERE .*").WillReturnRows( 200 sqlmock.NewRows([]string{"a", "b"}).AddRow(1, "b")) 201 mock.ExpectQuery("SELECT .* FROM .*tbl3.* WHERE .*").WillReturnRows( 202 sqlmock.NewRows([]string{"a", "b"})) 203 worker.validateTableChange() 204 require.Zero(t, validator.result.Errors) 205 // everything is validated successfully, no error rows 206 checkInitStatus() 207 208 // 209 // validate with batch size = 2 210 worker.batchSize = 2 211 worker.updateRowChange(genRowChangeJob(tbl1, tableInfo1, "1", rowInsert, []interface{}{1, "a"})) 212 worker.updateRowChange(genRowChangeJob(tbl1, tableInfo1, "2", rowInsert, []interface{}{2, "2b"})) 213 worker.updateRowChange(genRowChangeJob(tbl1, tableInfo1, "3", rowInsert, []interface{}{3, "3c"})) 214 215 mock.ExpectQuery("SELECT .* FROM .*tbl1.* WHERE .*").WillReturnRows( 216 sqlmock.NewRows([]string{"a", "b"}).AddRow(1, "a").AddRow(2, "2b")) 217 mock.ExpectQuery("SELECT .* FROM .*tbl1.* WHERE .*").WillReturnRows( 218 sqlmock.NewRows([]string{"a", "b"}).AddRow(1, "a").AddRow(2, "2b")) 219 worker.validateTableChange() 220 require.Zero(t, validator.result.Errors) 221 require.Equal(t, int64(1), worker.pendingRowCounts[rowInsert]) 222 require.Equal(t, int64(0), worker.pendingRowCounts[rowUpdated]) 223 require.Equal(t, int64(0), worker.pendingRowCounts[rowDeleted]) 224 require.Equal(t, worker.pendingRowCounts[rowInsert], validator.pendingRowCounts[rowInsert].Load()) 225 require.Equal(t, worker.pendingRowCounts[rowUpdated], validator.pendingRowCounts[rowUpdated].Load()) 226 require.Equal(t, worker.pendingRowCounts[rowDeleted], validator.pendingRowCounts[rowDeleted].Load()) 227 require.Len(t, worker.pendingChangesMap, 1) 228 require.Contains(t, worker.pendingChangesMap, tbl1.String()) 229 require.Len(t, worker.pendingChangesMap[tbl1.String()].jobs, 1) 230 require.Contains(t, worker.pendingChangesMap[tbl1.String()].jobs, "3") 231 require.Equal(t, rowInsert, worker.pendingChangesMap[tbl1.String()].jobs["3"].Tp) 232 require.Equal(t, 1, worker.pendingChangesMap[tbl1.String()].jobs["3"].FailedCnt) 233 require.Len(t, worker.errorRows, 0) 234 require.Zero(t, validator.newErrorRowCount.Load()) 235 236 // sync row 3 but got wrong result 237 mock.ExpectQuery("SELECT .* FROM .*tbl1.* WHERE .*").WillReturnRows( 238 sqlmock.NewRows([]string{"a", "b"}).AddRow(1, "a").AddRow(2, "2b").AddRow(3, "3dd")) 239 worker.validateTableChange() 240 require.Zero(t, validator.result.Errors) 241 if mode == config.ValidationFull { 242 // remain error 243 require.Equal(t, int64(1), worker.pendingRowCounts[rowInsert]) 244 require.Equal(t, int64(0), worker.pendingRowCounts[rowUpdated]) 245 require.Equal(t, int64(0), worker.pendingRowCounts[rowDeleted]) 246 require.Equal(t, worker.pendingRowCounts[rowInsert], validator.pendingRowCounts[rowInsert].Load()) 247 require.Equal(t, worker.pendingRowCounts[rowUpdated], validator.pendingRowCounts[rowUpdated].Load()) 248 require.Equal(t, worker.pendingRowCounts[rowDeleted], validator.pendingRowCounts[rowDeleted].Load()) 249 require.Len(t, worker.pendingChangesMap, 1) 250 require.Contains(t, worker.pendingChangesMap, tbl1.String()) 251 require.Len(t, worker.pendingChangesMap[tbl1.String()].jobs, 1) 252 require.Contains(t, worker.pendingChangesMap[tbl1.String()].jobs, "3") 253 require.Equal(t, rowInsert, worker.pendingChangesMap[tbl1.String()].jobs["3"].Tp) 254 require.Equal(t, 2, worker.pendingChangesMap[tbl1.String()].jobs["3"].FailedCnt) // fail again 255 require.Len(t, worker.errorRows, 0) 256 require.Zero(t, validator.newErrorRowCount.Load()) 257 } else { 258 // everything is validated successfully, no error rows 259 checkInitStatus() 260 } 261 262 // reset batch size 263 worker.batchSize = 100 264 if mode == config.ValidationFull { 265 // sync row 3 success 266 mock.ExpectQuery("SELECT .* FROM .*tbl1.* WHERE .*").WillReturnRows( 267 sqlmock.NewRows([]string{"a", "b"}).AddRow(3, "3c")) 268 worker.validateTableChange() 269 require.Zero(t, validator.result.Errors) 270 // everything is validated successfully, no error rows 271 checkInitStatus() 272 } 273 274 // set markErrorStarted = false, there should not be any errors and failedCount=0 275 validator.markErrorStarted.Store(false) 276 worker.updateRowChange(genRowChangeJob(tbl1, tableInfo1, "1", rowInsert, []interface{}{1, "a"})) 277 278 mock.ExpectQuery("SELECT .* FROM .*tbl1.* WHERE .*").WillReturnRows( 279 sqlmock.NewRows([]string{"a", "b"})) 280 worker.validateTableChange() 281 require.Zero(t, validator.result.Errors) 282 require.Equal(t, int64(1), worker.pendingRowCounts[rowInsert]) 283 require.Equal(t, int64(0), worker.pendingRowCounts[rowUpdated]) 284 require.Equal(t, int64(0), worker.pendingRowCounts[rowDeleted]) 285 require.Equal(t, worker.pendingRowCounts[rowInsert], validator.pendingRowCounts[rowInsert].Load()) 286 require.Equal(t, worker.pendingRowCounts[rowUpdated], validator.pendingRowCounts[rowUpdated].Load()) 287 require.Equal(t, worker.pendingRowCounts[rowDeleted], validator.pendingRowCounts[rowDeleted].Load()) 288 require.Len(t, worker.pendingChangesMap, 1) 289 require.Contains(t, worker.pendingChangesMap, tbl1.String()) 290 require.Len(t, worker.pendingChangesMap[tbl1.String()].jobs, 1) 291 require.Contains(t, worker.pendingChangesMap[tbl1.String()].jobs, "1") 292 require.Equal(t, rowInsert, worker.pendingChangesMap[tbl1.String()].jobs["1"].Tp) 293 require.Zero(t, worker.pendingChangesMap[tbl1.String()].jobs["1"].FailedCnt) 294 require.Len(t, worker.errorRows, 0) 295 require.Zero(t, validator.newErrorRowCount.Load()) 296 297 mock.ExpectQuery("SELECT .* FROM .*tbl1.* WHERE .*").WillReturnRows( 298 sqlmock.NewRows([]string{"a", "b"}).AddRow(1, "a")) 299 worker.validateTableChange() 300 require.Zero(t, validator.result.Errors) 301 // everything is validated successfully, no error rows 302 checkInitStatus() 303 304 // set markErrorStarted=true, rowErrorDelayInSec = 0, failed rows became error directly 305 validator.markErrorStarted.Store(true) 306 worker.rowErrorDelayInSec = 0 307 worker.updateRowChange(genRowChangeJob(tbl1, tableInfo1, "1", rowInsert, []interface{}{1, "a"})) 308 mock.ExpectQuery("SELECT .* FROM .*tbl1.* WHERE .*").WillReturnRows( 309 sqlmock.NewRows([]string{"a", "b"})) 310 worker.validateTableChange() 311 require.Zero(t, validator.result.Errors) 312 require.Zero(t, worker.pendingRowCounts[rowInsert]) 313 require.Zero(t, worker.pendingRowCounts[rowUpdated]) 314 require.Zero(t, worker.pendingRowCounts[rowDeleted]) 315 require.Zero(t, validator.pendingRowCounts[rowInsert].Load()) 316 require.Zero(t, validator.pendingRowCounts[rowUpdated].Load()) 317 require.Zero(t, validator.pendingRowCounts[rowDeleted].Load()) 318 require.Zero(t, len(worker.pendingChangesMap)) 319 require.Len(t, worker.errorRows, 1) 320 require.Equal(t, int64(1), validator.newErrorRowCount.Load()) 321 } 322 testFunc(t, config.ValidationFast) 323 testFunc(t, config.ValidationFull) 324 } 325 326 func TestValidatorWorkerCompareData(t *testing.T) { 327 compareContext := validateCompareContext{ 328 logger: log.L(), 329 columns: []*model.ColumnInfo{{FieldType: *types.NewFieldType(mysql.TypeLong)}}, 330 } 331 eq, err := compareContext.compareData("", []*sql.NullString{{String: "1", Valid: true}}, []*sql.NullString{{Valid: false}}) 332 require.NoError(t, err) 333 require.False(t, eq) 334 335 compareContext = validateCompareContext{ 336 logger: log.L(), 337 columns: []*model.ColumnInfo{{FieldType: *types.NewFieldType(mysql.TypeFloat)}}, 338 } 339 eq, err = compareContext.compareData("", []*sql.NullString{{String: "1.1", Valid: true}}, []*sql.NullString{{String: "1.x", Valid: true}}) 340 require.Error(t, err) 341 require.False(t, eq) 342 343 compareContext = validateCompareContext{ 344 logger: log.L(), 345 columns: []*model.ColumnInfo{{FieldType: *types.NewFieldType(mysql.TypeFloat)}}, 346 } 347 eq, err = compareContext.compareData("", []*sql.NullString{{String: "1.1", Valid: true}}, []*sql.NullString{{String: "1.1000011", Valid: true}}) 348 require.NoError(t, err) 349 require.False(t, eq) 350 351 compareContext = validateCompareContext{ 352 logger: log.L(), 353 columns: []*model.ColumnInfo{{FieldType: *types.NewFieldType(mysql.TypeFloat)}}, 354 } 355 eq, err = compareContext.compareData("", []*sql.NullString{{String: "1.1", Valid: true}}, []*sql.NullString{{String: "1.1000001", Valid: true}}) 356 require.NoError(t, err) 357 require.True(t, eq) 358 359 compareContext = validateCompareContext{ 360 logger: log.L(), 361 columns: []*model.ColumnInfo{{FieldType: *types.NewFieldType(mysql.TypeDouble)}}, 362 } 363 eq, err = compareContext.compareData("", []*sql.NullString{{String: "1.1", Valid: true}}, []*sql.NullString{{String: "1.1000001", Valid: true}}) 364 require.NoError(t, err) 365 require.True(t, eq) 366 367 compareContext = validateCompareContext{ 368 logger: log.L(), 369 columns: []*model.ColumnInfo{{FieldType: *types.NewFieldType(mysql.TypeLong)}}, 370 } 371 eq, err = compareContext.compareData("", []*sql.NullString{{String: "1", Valid: true}}, []*sql.NullString{{String: "1", Valid: true}}) 372 require.NoError(t, err) 373 require.True(t, eq) 374 375 compareContext = validateCompareContext{ 376 logger: log.L(), 377 columns: []*model.ColumnInfo{{FieldType: *types.NewFieldType(mysql.TypeVarchar)}}, 378 } 379 eq, err = compareContext.compareData("", []*sql.NullString{{String: "aaa", Valid: true}}, []*sql.NullString{{String: "aaa", Valid: true}}) 380 require.NoError(t, err) 381 require.True(t, eq) 382 383 compareContext = validateCompareContext{ 384 logger: log.L(), 385 columns: []*model.ColumnInfo{{FieldType: *types.NewFieldType(mysql.TypeVarString)}}, 386 } 387 eq, err = compareContext.compareData("", []*sql.NullString{{String: "\x01\x02", Valid: true}}, []*sql.NullString{{String: "\x01\x02", Valid: true}}) 388 require.NoError(t, err) 389 require.True(t, eq) 390 } 391 392 func TestValidatorWorkerGetTargetRows(t *testing.T) { 393 type testCase struct { 394 schemaName string 395 tblName string 396 creatSQL string 397 pkValues [][]string 398 allCols []string 399 rowData [][]interface{} 400 querySQL string 401 } 402 testCases := []testCase{ 403 { 404 schemaName: "test1", 405 tblName: "tbl1", 406 creatSQL: `create table if not exists test1.tbl1( 407 a int, 408 b int, 409 c int, 410 primary key(a, b) 411 );`, 412 pkValues: [][]string{ 413 {"1", "2"}, {"3", "4"}, {"5", "6"}, 414 }, 415 allCols: []string{"a", "b", "c"}, 416 rowData: [][]interface{}{ 417 {"1", "2", "3"}, {"3", "4", "5"}, {"5", "6", "7"}, 418 }, 419 querySQL: "SELECT .* FROM .*test1.*", 420 }, 421 { 422 schemaName: "test2", 423 tblName: "tbl2", 424 creatSQL: `create table if not exists test2.tbl2( 425 a varchar(10), 426 other text, 427 b varbinary(100), 428 c int, 429 primary key(a) 430 );`, 431 pkValues: [][]string{ 432 {"a"}, {"b"}, {"c"}, 433 }, 434 allCols: []string{"a", "other", "b", "c"}, 435 rowData: [][]interface{}{ 436 {"a", "aaa", "\xdd\xcc", "1"}, {"b", "bbb", nil, "2"}, {"c", nil, nil, "3"}, 437 }, 438 querySQL: "SELECT .* FROM .*test2.*", 439 }, 440 } 441 db, mock, err := sqlmock.New() 442 require.NoError(t, err) 443 for i, tc := range testCases { 444 var args []driver.Value 445 for _, arr := range testCases[i].pkValues { 446 for _, val := range arr { 447 args = append(args, val) 448 } 449 } 450 dataRows := mock.NewRows(tc.allCols) 451 for j := range testCases[i].rowData { 452 var rowData []driver.Value 453 for _, val := range tc.rowData[j] { 454 rowData = append(rowData, val) 455 } 456 dataRows = dataRows.AddRow(rowData...) 457 } 458 mock.ExpectQuery(tc.querySQL).WithArgs(args...).WillReturnRows(dataRows) 459 tblInfo := genValidateTableInfo(t, tc.creatSQL) 460 tbl := &filter.Table{Schema: tc.schemaName, Name: tc.tblName} 461 cond := &Cond{ 462 TargetTbl: tbl.String(), 463 Columns: tblInfo.Columns, 464 PK: tblInfo.Indices[0], 465 PkValues: tc.pkValues, 466 } 467 468 worker := &validateWorker{ 469 ctx: context.Background(), 470 db: conn.NewBaseDBForTest(db, func() {}), 471 L: log.L(), 472 } 473 targetRows, err2 := worker.getTargetRows(cond) 474 require.NoError(t, err2) 475 require.Equal(t, 3, len(targetRows)) 476 for i, pkVs := range tc.pkValues { 477 key := genRowKeyByString(pkVs) 478 require.Contains(t, targetRows, key) 479 data := targetRows[key] 480 require.Equal(t, len(tc.rowData[i]), len(data)) 481 for j, val := range tc.rowData[i] { 482 if val == nil { 483 require.False(t, data[j].Valid) 484 require.Empty(t, data[j].String) 485 } else { 486 require.True(t, data[j].Valid) 487 require.Equal(t, val, data[j].String) 488 } 489 } 490 } 491 } 492 493 tblInfo := genValidateTableInfo(t, "create table tbl(a int primary key)") 494 cond := &Cond{ 495 TargetTbl: "tbl", 496 Columns: tblInfo.Columns, 497 PK: tblInfo.Indices[0], 498 PkValues: [][]string{{"1"}}, 499 } 500 worker := &validateWorker{ 501 ctx: context.Background(), 502 db: conn.NewBaseDBForTest(db, func() {}), 503 L: log.L(), 504 } 505 506 // query error 507 mock.ExpectQuery("SELECT .* FROM .*").WithArgs(sqlmock.AnyArg()).WillReturnError(errors.New("query")) 508 _, err = worker.getTargetRows(cond) 509 require.EqualError(t, errors.Cause(err), "query") 510 } 511 512 func TestValidatorWorkerGetSourceRowsForCompare(t *testing.T) { 513 tbl1 := filter.Table{Schema: "test", Name: "tbl1"} 514 tableInfo1 := genValidateTableInfo(t, "create table tbl1(a varchar(10) primary key, b int)") 515 rows := getSourceRowsForCompare([]*rowValidationJob{ 516 genRowChangeJob(tbl1, tableInfo1, "a", rowInsert, []interface{}{nil, 1}), 517 genRowChangeJob(tbl1, tableInfo1, "b", rowInsert, []interface{}{1, 2}), 518 }) 519 require.Len(t, rows, 2) 520 require.Len(t, rows["a"], 2) 521 require.Len(t, rows["b"], 2) 522 require.False(t, rows["a"][0].Valid) 523 require.Equal(t, "1", rows["a"][1].String) 524 require.Equal(t, "1", rows["b"][0].String) 525 require.Equal(t, "2", rows["b"][1].String) 526 } 527 528 func TestValidatorIsRetryableDBError(t *testing.T) { 529 require.True(t, isRetryableDBError(&gmysql.MySQLError{Number: errno.ErrPDServerTimeout})) 530 require.True(t, isRetryableDBError(gmysql.ErrInvalidConn)) 531 require.True(t, isRetryableDBError(driver.ErrBadConn)) 532 require.True(t, isRetryableDBError(errors.Annotate(driver.ErrBadConn, "test"))) 533 require.True(t, isRetryableDBError(errors.New("Error 9005: Region is unavailable"))) 534 } 535 536 func TestValidatorRowCountAndSize(t *testing.T) { 537 cfg := genSubtaskConfig(t) 538 cfg.ValidatorCfg.Mode = config.ValidationFull 539 syncerObj := NewSyncer(cfg, nil, nil) 540 validator := NewContinuousDataValidator(cfg, syncerObj, false) 541 validator.persistHelper.schemaInitialized.Store(true) 542 validator.markErrorStarted.Store(true) 543 544 worker := newValidateWorker(validator, 0) 545 worker.newJobAdded(&rowValidationJob{Tp: rowInsert, size: 100}) 546 worker.newJobAdded(&rowValidationJob{Tp: rowUpdated, size: 200}) 547 worker.newJobAdded(&rowValidationJob{Tp: rowDeleted, size: 400}) 548 worker.newJobAdded(&rowValidationJob{Tp: rowInsert, size: 800}) 549 require.Equal(t, int64(2), worker.pendingRowCounts[rowInsert]) 550 require.Equal(t, int64(1), worker.pendingRowCounts[rowUpdated]) 551 require.Equal(t, int64(1), worker.pendingRowCounts[rowDeleted]) 552 require.Equal(t, int64(1500), worker.pendingRowSize) 553 require.Equal(t, int64(2), validator.pendingRowCounts[rowInsert].Load()) 554 require.Equal(t, int64(1), validator.pendingRowCounts[rowUpdated].Load()) 555 require.Equal(t, int64(1), validator.pendingRowCounts[rowDeleted].Load()) 556 require.Equal(t, int64(1500), validator.pendingRowSize.Load()) 557 worker.setPendingRowCountsAndSize([]int64{0, 0, 0}, 300) 558 require.Equal(t, int64(0), worker.pendingRowCounts[rowInsert]) 559 require.Equal(t, int64(0), worker.pendingRowCounts[rowUpdated]) 560 require.Equal(t, int64(0), worker.pendingRowCounts[rowDeleted]) 561 require.Equal(t, int64(300), worker.pendingRowSize) 562 require.Equal(t, int64(0), validator.pendingRowCounts[rowInsert].Load()) 563 require.Equal(t, int64(0), validator.pendingRowCounts[rowUpdated].Load()) 564 require.Equal(t, int64(0), validator.pendingRowCounts[rowDeleted].Load()) 565 require.Equal(t, int64(300), validator.pendingRowSize.Load()) 566 }