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  }