github.com/pingcap/tiflow@v0.0.0-20240520035814-5bf52d54e205/dm/pkg/binlog/event/generator_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 event
    15  
    16  import (
    17  	"fmt"
    18  	"os"
    19  	"path/filepath"
    20  	"testing"
    21  
    22  	gmysql "github.com/go-mysql-org/go-mysql/mysql"
    23  	"github.com/go-mysql-org/go-mysql/replication"
    24  	"github.com/pingcap/tiflow/dm/pkg/gtid"
    25  	"github.com/stretchr/testify/require"
    26  )
    27  
    28  func TestGenerateForMySQL(t *testing.T) {
    29  	t.Parallel()
    30  	var (
    31  		flavor           = gmysql.MySQLFlavor
    32  		serverID  uint32 = 101
    33  		latestXID uint64 = 10
    34  	)
    35  
    36  	previousGTIDSetStr := "3ccc475b-2343-11e7-be21-6c0b84d59f30:1-14,406a3f61-690d-11e7-87c5-6c92bf46f384:1-94321383,53bfca22-690d-11e7-8a62-18ded7a37b78:1-495,686e1ab6-c47e-11e7-a42c-6c92bf46f384:1-34981190,03fc0263-28c7-11e7-a653-6c0b84d59f30:1-7041423,05474d3c-28c7-11e7-8352-203db246dd3d:1-170,10b039fc-c843-11e7-8f6a-1866daf8d810:1-308290454"
    37  	previousGTIDSet, err := gtid.ParserGTID(flavor, previousGTIDSetStr)
    38  	require.Nil(t, err)
    39  	require.NotNil(t, previousGTIDSet)
    40  
    41  	// mutil GTID in latestGTID
    42  	latestGTIDStr := "3ccc475b-2343-11e7-be21-6c0b84d59f30:1-14"
    43  	latestGTID, err := gtid.ParserGTID(flavor, latestGTIDStr)
    44  	require.Nil(t, err)
    45  	require.NotNil(t, latestGTID)
    46  	_, err = NewGenerator(flavor, serverID, 0, latestGTID, previousGTIDSet, latestXID)
    47  	require.NotNil(t, err)
    48  
    49  	// latestGTID not one of the latest previousGTIDSet, UUID not found
    50  	latestGTIDStr = "11111111-2343-11e7-be21-6c0b84d59f30:14"
    51  	latestGTID, err = gtid.ParserGTID(flavor, latestGTIDStr)
    52  	require.Nil(t, err)
    53  	require.NotNil(t, latestGTID)
    54  	_, err = NewGenerator(flavor, serverID, 0, latestGTID, previousGTIDSet, latestXID)
    55  	require.NotNil(t, err)
    56  
    57  	// latestGTID not one of the latest previousGTIDSet, interval mismatch
    58  	latestGTIDStr = "3ccc475b-2343-11e7-be21-6c0b84d59f30:13"
    59  	latestGTID, err = gtid.ParserGTID(flavor, latestGTIDStr)
    60  	require.Nil(t, err)
    61  	require.NotNil(t, latestGTID)
    62  	_, err = NewGenerator(flavor, serverID, 0, latestGTID, previousGTIDSet, latestXID)
    63  	require.NotNil(t, err)
    64  
    65  	latestGTIDStr = "3ccc475b-2343-11e7-be21-6c0b84d59f30:14"
    66  	latestGTID, err = gtid.ParserGTID(flavor, latestGTIDStr)
    67  	require.Nil(t, err)
    68  	require.NotNil(t, latestGTID)
    69  
    70  	testGenerate(t, flavor, serverID, latestGTID, previousGTIDSet, latestXID)
    71  }
    72  
    73  func TestGenerateForMariaDB(t *testing.T) {
    74  	t.Parallel()
    75  	var (
    76  		flavor           = gmysql.MariaDBFlavor
    77  		serverID  uint32 = 101
    78  		latestXID uint64 = 10
    79  	)
    80  
    81  	previousGTIDSetStr := "1-101-12,2-2-3,3-3-8,4-4-4"
    82  	previousGTIDSet, err := gtid.ParserGTID(flavor, previousGTIDSetStr)
    83  	require.Nil(t, err)
    84  	require.NotNil(t, previousGTIDSet)
    85  
    86  	// multi GTID in latestGTID
    87  	latestGTIDStr := "1-101-12,2-2-23"
    88  	latestGTID, err := gtid.ParserGTID(flavor, latestGTIDStr)
    89  	require.Nil(t, err)
    90  	require.NotNil(t, latestGTID)
    91  	_, err = NewGenerator(flavor, serverID, 0, latestGTID, previousGTIDSet, latestXID)
    92  	require.NotNil(t, err)
    93  
    94  	// latestGTID not one of previousGTIDSet, domain-id mismatch
    95  	latestGTIDStr = "5-101-12"
    96  	latestGTID, err = gtid.ParserGTID(flavor, latestGTIDStr)
    97  	require.Nil(t, err)
    98  	require.NotNil(t, latestGTID)
    99  	_, err = NewGenerator(flavor, serverID, 0, latestGTID, previousGTIDSet, latestXID)
   100  	require.NotNil(t, err)
   101  
   102  	// latestGTID not one of previousGTIDSet, sequence-number not equal
   103  	latestGTIDStr = "1-101-13"
   104  	latestGTID, err = gtid.ParserGTID(flavor, latestGTIDStr)
   105  	require.Nil(t, err)
   106  	require.NotNil(t, latestGTID)
   107  	_, err = NewGenerator(flavor, serverID, 0, latestGTID, previousGTIDSet, latestXID)
   108  	require.NotNil(t, err)
   109  
   110  	latestGTIDStr = "1-101-12"
   111  	latestGTID, err = gtid.ParserGTID(flavor, latestGTIDStr)
   112  	require.Nil(t, err)
   113  	require.NotNil(t, latestGTID)
   114  
   115  	// server-id mismatch
   116  	_, err = NewGenerator(flavor, 100, 0, latestGTID, previousGTIDSet, latestXID)
   117  	require.NotNil(t, err)
   118  
   119  	testGenerate(t, flavor, serverID, latestGTID, previousGTIDSet, latestXID)
   120  }
   121  
   122  func testGenerate(t *testing.T, flavor string, serverID uint32, latestGTID gmysql.GTIDSet, previousGTIDSet gmysql.GTIDSet, latestXID uint64) {
   123  	t.Helper()
   124  	// write some events to file
   125  	dir := t.TempDir()
   126  	filename := filepath.Join(dir, "mysql-bin-test.000001")
   127  	f, err := os.Create(filename)
   128  	require.Nil(t, err)
   129  	defer f.Close()
   130  
   131  	g, err := NewGenerator(flavor, serverID, 0, latestGTID, previousGTIDSet, latestXID)
   132  	require.Nil(t, err)
   133  	allEvents := make([]*replication.BinlogEvent, 0, 20)
   134  	allEventTypes := make([]replication.EventType, 0, 50)
   135  
   136  	// file header
   137  	currentEvents, data, err := g.GenFileHeader(0)
   138  	require.Nil(t, err)
   139  	_, err = f.Write(data)
   140  	require.Nil(t, err)
   141  	allEvents = append(allEvents, currentEvents...)
   142  	allEventTypes = append(allEventTypes, replication.FORMAT_DESCRIPTION_EVENT, previousGTIDEventType(t, flavor))
   143  
   144  	// CREATE DATABASE `db`
   145  	schema := "db"
   146  	currentEvents, data, err = g.GenCreateDatabaseEvents(schema)
   147  	require.Nil(t, err)
   148  	_, err = f.Write(data)
   149  	require.Nil(t, err)
   150  	allEvents = append(allEvents, currentEvents...)
   151  	allEventTypes = append(allEventTypes, gtidEventType(t, flavor), replication.QUERY_EVENT)
   152  
   153  	// CREATE TABLE `db`.`tbl` (c1 INT, c2 TEXT)
   154  	table := "tbl"
   155  	query := fmt.Sprintf("CREATE TABLE `%s`.`%s` (c1 INT, c2 TEXT)", schema, table)
   156  	currentEvents, data, err = g.GenCreateTableEvents(schema, query)
   157  	require.Nil(t, err)
   158  	_, err = f.Write(data)
   159  	require.Nil(t, err)
   160  	allEvents = append(allEvents, currentEvents...)
   161  	allEventTypes = append(allEventTypes, gtidEventType(t, flavor), replication.QUERY_EVENT)
   162  
   163  	// INSERT INTO `db`.`tbl` VALUES (1, "string 1")
   164  	var (
   165  		tableID    uint64 = 8
   166  		columnType        = []byte{gmysql.MYSQL_TYPE_LONG, gmysql.MYSQL_TYPE_STRING}
   167  	)
   168  	insertRows := make([][]interface{}, 0, 1)
   169  	insertRows = append(insertRows, []interface{}{int32(1), "string 1"})
   170  	dmlData := []*DMLData{
   171  		{
   172  			TableID:    tableID,
   173  			Schema:     schema,
   174  			Table:      table,
   175  			ColumnType: columnType,
   176  			Rows:       insertRows,
   177  		},
   178  	}
   179  	eventType := replication.WRITE_ROWS_EVENTv2
   180  	currentEvents, data, err = g.GenDMLEvents(eventType, dmlData, 0)
   181  	require.Nil(t, err)
   182  	_, err = f.Write(data)
   183  	require.Nil(t, err)
   184  	allEvents = append(allEvents, currentEvents...)
   185  	allEventTypes = append(allEventTypes, gtidEventType(t, flavor), replication.QUERY_EVENT, replication.TABLE_MAP_EVENT, eventType, replication.XID_EVENT)
   186  
   187  	// INSERT INTO `db`.`tbl` VALUES (11, "string 11"), (12, "string 12")
   188  	// INSERT INTO `db`.`tbl` VALUES (13, "string 13"),
   189  	insertRows1 := make([][]interface{}, 0, 2)
   190  	insertRows1 = append(insertRows1, []interface{}{int32(11), "string 11"}, []interface{}{int32(12), "string 12"})
   191  	insertRows2 := make([][]interface{}, 0, 1)
   192  	insertRows2 = append(insertRows2, []interface{}{int32(13), "string 13"})
   193  	dmlData = []*DMLData{
   194  		{
   195  			TableID:    tableID,
   196  			Schema:     schema,
   197  			Table:      table,
   198  			ColumnType: columnType,
   199  			Rows:       insertRows1,
   200  		},
   201  		{
   202  			TableID:    tableID,
   203  			Schema:     schema,
   204  			Table:      table,
   205  			ColumnType: columnType,
   206  			Rows:       insertRows2,
   207  		},
   208  	}
   209  	currentEvents, data, err = g.GenDMLEvents(eventType, dmlData, 0)
   210  	require.Nil(t, err)
   211  	_, err = f.Write(data)
   212  	require.Nil(t, err)
   213  	allEvents = append(allEvents, currentEvents...)
   214  	allEventTypes = append(allEventTypes, gtidEventType(t, flavor), replication.QUERY_EVENT, replication.TABLE_MAP_EVENT, eventType, replication.TABLE_MAP_EVENT, eventType, replication.XID_EVENT)
   215  
   216  	// UPDATE `db`.`tbl` SET c2="another string 11" WHERE c1=11
   217  	// UPDATE `db`.`tbl` SET c1=120, c2="another string 120" WHERE C1=12
   218  	updateRows1 := make([][]interface{}, 0, 2)
   219  	updateRows1 = append(updateRows1, []interface{}{int32(11), "string 11"}, []interface{}{int32(11), "another string 11"})
   220  	updateRows2 := make([][]interface{}, 0, 2)
   221  	updateRows2 = append(updateRows2, []interface{}{int32(12), "string 12"}, []interface{}{int32(120), "another string 120"})
   222  	dmlData = []*DMLData{
   223  		{
   224  			TableID:    tableID,
   225  			Schema:     schema,
   226  			Table:      table,
   227  			ColumnType: columnType,
   228  			Rows:       updateRows1,
   229  		},
   230  		{
   231  			TableID:    tableID,
   232  			Schema:     schema,
   233  			Table:      table,
   234  			ColumnType: columnType,
   235  			Rows:       updateRows2,
   236  		},
   237  	}
   238  	eventType = replication.UPDATE_ROWS_EVENTv2
   239  	currentEvents, data, err = g.GenDMLEvents(eventType, dmlData, 0)
   240  	require.Nil(t, err)
   241  	_, err = f.Write(data)
   242  	require.Nil(t, err)
   243  	allEvents = append(allEvents, currentEvents...)
   244  	allEventTypes = append(allEventTypes, gtidEventType(t, flavor), replication.QUERY_EVENT, replication.TABLE_MAP_EVENT, eventType, replication.TABLE_MAP_EVENT, eventType, replication.XID_EVENT)
   245  
   246  	// DELETE FROM `db`.`tbl` WHERE c1=13
   247  	deleteRows := make([][]interface{}, 0, 1)
   248  	deleteRows = append(deleteRows, []interface{}{int32(13), "string 13"})
   249  	dmlData = []*DMLData{
   250  		{
   251  			TableID:    tableID,
   252  			Schema:     schema,
   253  			Table:      table,
   254  			ColumnType: columnType,
   255  			Rows:       deleteRows,
   256  		},
   257  	}
   258  	eventType = replication.DELETE_ROWS_EVENTv2
   259  	currentEvents, data, err = g.GenDMLEvents(eventType, dmlData, 0)
   260  	require.Nil(t, err)
   261  	_, err = f.Write(data)
   262  	require.Nil(t, err)
   263  	allEvents = append(allEvents, currentEvents...)
   264  	allEventTypes = append(allEventTypes, gtidEventType(t, flavor), replication.QUERY_EVENT, replication.TABLE_MAP_EVENT, eventType, replication.XID_EVENT)
   265  
   266  	// ALTER TABLE
   267  	query = fmt.Sprintf("ALTER TABLE `%s`.`%s` ADD COLUMN c3 INT", schema, table)
   268  	currentEvents, data, err = g.GenDDLEvents(schema, query, 0)
   269  	require.Nil(t, err)
   270  	_, err = f.Write(data)
   271  	require.Nil(t, err)
   272  	allEvents = append(allEvents, currentEvents...)
   273  	allEventTypes = append(allEventTypes, gtidEventType(t, flavor), replication.QUERY_EVENT)
   274  
   275  	// DROP TABLE `db`.`tbl`
   276  	currentEvents, data, err = g.GenDropTableEvents(schema, table)
   277  	require.Nil(t, err)
   278  	_, err = f.Write(data)
   279  	require.Nil(t, err)
   280  	allEvents = append(allEvents, currentEvents...)
   281  	allEventTypes = append(allEventTypes, gtidEventType(t, flavor), replication.QUERY_EVENT)
   282  
   283  	// DROP DATABASE `db`
   284  	currentEvents, data, err = g.GenDropDatabaseEvents(schema)
   285  	require.Nil(t, err)
   286  	_, err = f.Write(data)
   287  	require.Nil(t, err)
   288  	allEvents = append(allEvents, currentEvents...)
   289  	allEventTypes = append(allEventTypes, gtidEventType(t, flavor), replication.QUERY_EVENT)
   290  
   291  	// parse the file
   292  	count := 0
   293  	onEventFunc := func(e *replication.BinlogEvent) error {
   294  		require.Equal(t, allEventTypes[count], e.Header.EventType)
   295  		require.Equal(t, allEvents[count].RawData, e.RawData)
   296  		count++
   297  		return nil
   298  	}
   299  
   300  	parser2 := replication.NewBinlogParser()
   301  	parser2.SetVerifyChecksum(true)
   302  	err = parser2.ParseFile(filename, 0, onEventFunc)
   303  	require.Nil(t, err)
   304  }
   305  
   306  func previousGTIDEventType(t *testing.T, flavor string) replication.EventType {
   307  	t.Helper()
   308  	switch flavor {
   309  	case gmysql.MySQLFlavor:
   310  		return replication.PREVIOUS_GTIDS_EVENT
   311  	case gmysql.MariaDBFlavor:
   312  		return replication.MARIADB_GTID_LIST_EVENT
   313  	default:
   314  		t.Fatalf("unsupported flavor %s", flavor)
   315  		return replication.PREVIOUS_GTIDS_EVENT // hack for compiler
   316  	}
   317  }
   318  
   319  func gtidEventType(t *testing.T, flavor string) replication.EventType {
   320  	t.Helper()
   321  	switch flavor {
   322  	case gmysql.MySQLFlavor:
   323  		return replication.GTID_EVENT
   324  	case gmysql.MariaDBFlavor:
   325  		return replication.MARIADB_GTID_EVENT
   326  	default:
   327  		t.Fatalf("unsupported flavor %s", flavor)
   328  		return replication.GTID_EVENT // hack for compiler
   329  	}
   330  }