github.com/pingcap/tiflow@v0.0.0-20240520035814-5bf52d54e205/dm/syncer/validator_checkpoint_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 "time" 22 23 "github.com/DATA-DOG/go-sqlmock" 24 "github.com/go-mysql-org/go-mysql/mysql" 25 "github.com/pingcap/errors" 26 "github.com/pingcap/failpoint" 27 "github.com/pingcap/tidb/pkg/util/filter" 28 regexprrouter "github.com/pingcap/tidb/pkg/util/regexpr-router" 29 router "github.com/pingcap/tidb/pkg/util/table-router" 30 "github.com/pingcap/tiflow/dm/pb" 31 "github.com/pingcap/tiflow/dm/pkg/binlog" 32 "github.com/pingcap/tiflow/dm/pkg/conn" 33 "github.com/pingcap/tiflow/dm/pkg/log" 34 "github.com/pingcap/tiflow/dm/pkg/retry" 35 "github.com/pingcap/tiflow/dm/pkg/schema" 36 "github.com/pingcap/tiflow/dm/syncer/dbconn" 37 "github.com/stretchr/testify/require" 38 ) 39 40 func TestValidatorCheckpointPersist(t *testing.T) { 41 var ( 42 schemaName = "test" 43 tableName = "tbl" 44 tbl = filter.Table{Schema: schemaName, Name: tableName} 45 createTableSQL = "CREATE TABLE `" + tableName + "`(id int primary key, v varchar(100))" 46 ) 47 cfg := genSubtaskConfig(t) 48 _, dbMock, err := conn.InitMockDBFull() 49 require.NoError(t, err) 50 defer func() { 51 conn.DefaultDBProvider = &conn.DefaultDBProviderImpl{} 52 }() 53 dbMock.ExpectQuery("select .* from .*_validator_checkpoint.*").WillReturnRows( 54 dbMock.NewRows([]string{"", "", "", "", "", "", ""}).AddRow("mysql-bin.000001", 100, "", 0, 0, 0, 1)) 55 dbMock.ExpectQuery("select .* from .*_validator_pending_change.*").WillReturnRows( 56 dbMock.NewRows([]string{"", "", "", "", ""}). 57 // insert with pk=11 58 AddRow(schemaName, tableName, "11", 59 "{\"key\": \"11\", \"data\": [\"11\", \"a\"], \"tp\": 0, \"first-ts\": 0, \"failed-cnt\": 0}", 1). 60 // delete with pk=12 61 AddRow(schemaName, tableName, "12", 62 "{\"key\": \"12\", \"data\": [\"12\", \"a\"], \"tp\": 1, \"first-ts\": 0, \"failed-cnt\": 0}", 1). 63 // update with pk=13 64 AddRow(schemaName, tableName, "13", 65 "{\"key\": \"13\", \"data\": [\"13\", \"a\"], \"tp\": 2, \"first-ts\": 0, \"failed-cnt\": 0}", 1), 66 ) 67 dbMock.ExpectQuery("select .* from .*_validator_table_status.*").WillReturnRows( 68 dbMock.NewRows([]string{"", "", "", "", "", ""}).AddRow(schemaName, tableName, schemaName, tableName, 2, "")) 69 70 syncerObj := NewSyncer(cfg, nil, nil) 71 syncerObj.running.Store(true) 72 syncerObj.tableRouter, err = regexprrouter.NewRegExprRouter(cfg.CaseSensitive, []*router.TableRule{}) 73 require.NoError(t, err) 74 currLoc := binlog.MustZeroLocation(cfg.Flavor) 75 currLoc.Position = mysql.Position{ 76 Name: "mysql-bin.000001", 77 Pos: 3000, 78 } 79 syncerObj.checkpoint = &mockedCheckPointForValidator{ 80 currLoc: binlog.MustZeroLocation(cfg.Flavor), 81 nextLoc: currLoc, 82 cnt: 2, 83 } 84 db, mock, err := sqlmock.New() 85 require.NoError(t, err) 86 mock.ExpectBegin() 87 mock.ExpectExec("SET SESSION SQL_MODE.*").WillReturnResult(sqlmock.NewResult(0, 0)) 88 mock.ExpectCommit() 89 mock.ExpectQuery("SHOW CREATE TABLE.*").WillReturnRows( 90 mock.NewRows([]string{"Table", "Create Table"}).AddRow(tableName, createTableSQL)) 91 dbConn, err := db.Conn(context.Background()) 92 require.NoError(t, err) 93 syncerObj.downstreamTrackConn = dbconn.NewDBConn(cfg, conn.NewBaseConnForTest(dbConn, &retry.FiniteRetryStrategy{})) 94 syncerObj.schemaTracker, err = schema.NewTestTracker(context.Background(), cfg.Name, syncerObj.downstreamTrackConn, log.L()) 95 defer syncerObj.schemaTracker.Close() 96 require.NoError(t, err) 97 require.NoError(t, syncerObj.schemaTracker.CreateSchemaIfNotExists(schemaName)) 98 stmt, err := parseSQL(createTableSQL) 99 require.NoError(t, err) 100 require.NoError(t, syncerObj.schemaTracker.Exec(context.Background(), schemaName, stmt)) 101 102 require.Nil(t, failpoint.Enable("github.com/pingcap/tiflow/dm/syncer/ValidatorMockUpstreamTZ", `return()`)) 103 defer func() { 104 require.Nil(t, failpoint.Disable("github.com/pingcap/tiflow/dm/syncer/ValidatorMockUpstreamTZ")) 105 }() 106 validator := NewContinuousDataValidator(cfg, syncerObj, false) 107 validator.validateInterval = 10 * time.Minute // we don't want worker start validate 108 validator.persistHelper.schemaInitialized.Store(true) 109 require.NoError(t, validator.initialize()) 110 validator.Stop() 111 require.NoError(t, validator.loadPersistedData()) 112 require.Equal(t, int64(1), validator.persistHelper.revision) 113 require.Equal(t, 1, len(validator.loadedPendingChanges)) 114 require.Equal(t, 3, len(validator.loadedPendingChanges[tbl.String()].jobs)) 115 116 require.NoError(t, validator.initialize()) 117 defer validator.Stop() 118 validator.persistHelper.setRevision(100) 119 validator.loadedPendingChanges = nil 120 validator.startValidateWorkers() 121 validator.tableStatus = map[string]*tableValidateStatus{tbl.String(): { 122 tbl, tbl, pb.Stage_Running, "", 123 }} 124 tblInfo := genValidateTableInfo(t, createTableSQL) 125 validator.workers[0].errorRows = append(validator.workers[0].errorRows, &validateFailedRow{ 126 tp: deletedRowExists, 127 dstData: []*sql.NullString{{String: "1", Valid: true}, {String: "a", Valid: true}}, 128 srcJob: genRowChangeJob(tbl, tblInfo, "1", rowDeleted, []interface{}{1, "a"}), 129 }) 130 validator.dispatchRowChange("1", genRowChangeJob(tbl, tblInfo, "1", rowInsert, []interface{}{1, "a"})) 131 validator.newErrorRowCount.Store(1) 132 133 // fail on first persist 134 dbMock.ExpectExec("INSERT INTO .*_validator_table_status.*ON DUPLICATE.*").WillReturnResult(driver.ResultNoRows) 135 dbMock.ExpectExec("INSERT INTO .*_validator_error_change.*ON DUPLICATE.*").WillReturnResult(driver.ResultNoRows) 136 dbMock.ExpectExec("DELETE FROM .*_validator_pending_change.*WHERE source = \\? and revision = \\?"). 137 WithArgs("", 101).WillReturnResult(driver.ResultNoRows) 138 dbMock.ExpectExec("INSERT INTO .*_validator_pending_change.*VALUES \\(\\?, \\?, \\?, \\?, \\?, \\?\\)"). 139 WillReturnResult(driver.ResultNoRows) 140 dbMock.ExpectExec("INSERT INTO .*_validator_checkpoint.*ON DUPLICATE.*"). 141 WillReturnError(errors.New("Error 1406 failed on persist checkpoint")) 142 require.Nil(t, validator.flushedLoc) 143 err2 := validator.persistCheckpointAndData(*validator.location) 144 require.EqualError(t, err2, "Error 1406 failed on persist checkpoint") 145 require.Equal(t, int64(100), validator.persistHelper.revision) 146 require.Len(t, validator.workers[0].errorRows, 1) 147 require.Nil(t, validator.flushedLoc) 148 require.Equal(t, int64(1), validator.newErrorRowCount.Load()) 149 150 // fail on last clean up, but it doesn't matter 151 dbMock.ExpectExec("INSERT INTO .*_validator_table_status.*ON DUPLICATE.*").WillReturnResult(driver.ResultNoRows) 152 dbMock.ExpectExec("INSERT INTO .*_validator_error_change.*ON DUPLICATE.*").WillReturnResult(driver.ResultNoRows) 153 dbMock.ExpectExec("DELETE FROM .*_validator_pending_change.*WHERE source = \\? and revision = \\?"). 154 WithArgs("", 101).WillReturnResult(driver.ResultNoRows) 155 dbMock.ExpectExec("INSERT INTO .*_validator_pending_change.*VALUES \\(\\?, \\?, \\?, \\?, \\?, \\?\\)"). 156 WillReturnResult(driver.ResultNoRows) 157 dbMock.ExpectExec("INSERT INTO .*_validator_checkpoint.*ON DUPLICATE.*"). 158 WillReturnResult(driver.ResultNoRows) 159 dbMock.ExpectExec("DELETE FROM .*_validator_pending_change.*WHERE source = \\? and revision != \\?"). 160 WillReturnError(errors.New("Error 1406 failed on delete pending change")) 161 err2 = validator.persistCheckpointAndData(*validator.location) 162 require.NoError(t, err2) 163 require.Equal(t, int64(101), validator.persistHelper.revision) 164 require.Len(t, validator.workers[0].errorRows, 0) 165 require.Equal(t, validator.location.String(), validator.flushedLoc.String()) 166 require.Equal(t, int64(0), validator.newErrorRowCount.Load()) 167 168 // all success 169 dbMock.ExpectExec("INSERT INTO .*_validator_table_status.*ON DUPLICATE.*").WillReturnResult(driver.ResultNoRows) 170 dbMock.ExpectExec("INSERT INTO .*_validator_error_change.*ON DUPLICATE.*").WillReturnResult(driver.ResultNoRows) 171 dbMock.ExpectExec("DELETE FROM .*_validator_pending_change.*WHERE source = \\? and revision = \\?"). 172 WithArgs("", 102).WillReturnResult(driver.ResultNoRows) 173 dbMock.ExpectExec("INSERT INTO .*_validator_pending_change.*VALUES \\(\\?, \\?, \\?, \\?, \\?, \\?\\)"). 174 WillReturnResult(driver.ResultNoRows) 175 dbMock.ExpectExec("INSERT INTO .*_validator_checkpoint.*ON DUPLICATE.*"). 176 WillReturnResult(driver.ResultNoRows) 177 dbMock.ExpectExec("DELETE FROM .*_validator_pending_change.*WHERE source = \\? and revision != \\?"). 178 WithArgs("", 102).WillReturnResult(driver.ResultNoRows) 179 validator.workers[0].errorRows = append(validator.workers[0].errorRows, &validateFailedRow{ 180 tp: deletedRowExists, 181 dstData: []*sql.NullString{{String: "1", Valid: true}, {String: "a", Valid: true}}, 182 srcJob: genRowChangeJob(tbl, tblInfo, "1", rowDeleted, []interface{}{1, "a"}), 183 }) 184 validator.newErrorRowCount.Store(1) 185 validator.flushedLoc = nil 186 err2 = validator.persistCheckpointAndData(*validator.location) 187 require.NoError(t, err2) 188 require.Equal(t, int64(102), validator.persistHelper.revision) 189 require.Len(t, validator.workers[0].errorRows, 0) 190 require.Equal(t, validator.location.String(), validator.flushedLoc.String()) 191 require.Equal(t, int64(0), validator.newErrorRowCount.Load()) 192 }