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  }