github.com/pingcap/tiflow@v0.0.0-20240520035814-5bf52d54e205/dm/pkg/binlog/event/event_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  // binlog events generator for MySQL used to generate some binlog events for tests.
    15  // Readability takes precedence over performance.
    16  
    17  package event
    18  
    19  import (
    20  	"os"
    21  	"path/filepath"
    22  	"strings"
    23  	"testing"
    24  	"time"
    25  
    26  	gmysql "github.com/go-mysql-org/go-mysql/mysql"
    27  	"github.com/go-mysql-org/go-mysql/replication"
    28  	"github.com/pingcap/tiflow/dm/pkg/gtid"
    29  	"github.com/stretchr/testify/require"
    30  )
    31  
    32  func TestGenEventHeader(t *testing.T) {
    33  	t.Parallel()
    34  	var (
    35  		latestPos uint32 = 4
    36  		header           = &replication.EventHeader{
    37  			Timestamp: uint32(time.Now().Unix()),
    38  			EventType: replication.FORMAT_DESCRIPTION_EVENT,
    39  			ServerID:  11,
    40  			Flags:     0x01,
    41  			LogPos:    latestPos + 109,
    42  			EventSize: 109, // current binlog version, 109,
    43  		}
    44  	)
    45  
    46  	data, err := GenEventHeader(header)
    47  	require.Nil(t, err)
    48  	require.Equal(t, eventHeaderLen, uint8(len(data)))
    49  
    50  	header2 := &replication.EventHeader{}
    51  	err = header2.Decode(data)
    52  	require.Nil(t, err)
    53  	verifyHeader(t, header2, header, header.EventType, latestPos, header.EventSize)
    54  }
    55  
    56  func verifyHeader(t *testing.T, obtained, excepted *replication.EventHeader, eventType replication.EventType, latestPos, eventSize uint32) {
    57  	t.Helper()
    58  	require.Equal(t, excepted.Timestamp, obtained.Timestamp)
    59  	require.Equal(t, excepted.ServerID, obtained.ServerID)
    60  	require.Equal(t, excepted.Flags, obtained.Flags)
    61  	require.Equal(t, eventType, obtained.EventType)
    62  	require.Equal(t, eventSize, obtained.EventSize)
    63  	require.Equal(t, eventSize+latestPos, obtained.LogPos)
    64  }
    65  
    66  func TestGenFormatDescriptionEvent(t *testing.T) {
    67  	t.Parallel()
    68  	var (
    69  		header = &replication.EventHeader{
    70  			Timestamp: uint32(time.Now().Unix()),
    71  			ServerID:  11,
    72  			Flags:     0x01,
    73  		}
    74  		latestPos uint32 = 4
    75  	)
    76  	ev, err := GenFormatDescriptionEvent(header, latestPos)
    77  	require.Nil(t, err)
    78  
    79  	// verify the header
    80  	verifyHeader(t, ev.Header, header, replication.FORMAT_DESCRIPTION_EVENT, latestPos, uint32(len(ev.RawData)))
    81  
    82  	// some fields of FormatDescriptionEvent are a little hard to test, so we try to parse a binlog file.
    83  	dir := t.TempDir()
    84  	name := filepath.Join(dir, "mysql-bin-test.000001")
    85  	f, err := os.Create(name)
    86  	require.Nil(t, err)
    87  	defer f.Close()
    88  
    89  	// write a binlog file header
    90  	_, err = f.Write(replication.BinLogFileHeader)
    91  	require.Nil(t, err)
    92  
    93  	// write the FormatDescriptionEvent
    94  	_, err = f.Write(ev.RawData)
    95  	require.Nil(t, err)
    96  
    97  	// should only receive one FormatDescriptionEvent
    98  	onEventFunc := func(e *replication.BinlogEvent) error {
    99  		require.Equal(t, ev.Header, e.Header)
   100  		require.Equal(t, ev.Event, e.Event)
   101  		require.Equal(t, ev.RawData, e.RawData)
   102  		return nil
   103  	}
   104  
   105  	parser2 := replication.NewBinlogParser()
   106  	parser2.SetVerifyChecksum(true)
   107  	err = parser2.ParseFile(name, 0, onEventFunc)
   108  	require.Nil(t, err)
   109  }
   110  
   111  func TestGenRotateEvent(t *testing.T) {
   112  	t.Parallel()
   113  	var (
   114  		header = &replication.EventHeader{
   115  			Timestamp: uint32(time.Now().Unix()),
   116  			ServerID:  11,
   117  			Flags:     0x01,
   118  		}
   119  		latestPos   uint32 = 4
   120  		nextLogName []byte // nil
   121  		position    uint64 = 123
   122  	)
   123  
   124  	// empty nextLogName, invalid
   125  	rotateEv, err := GenRotateEvent(header, latestPos, nextLogName, position)
   126  	require.NotNil(t, err)
   127  	require.Nil(t, rotateEv)
   128  
   129  	// valid nextLogName
   130  	nextLogName = []byte("mysql-bin.000010")
   131  	rotateEv, err = GenRotateEvent(header, latestPos, nextLogName, position)
   132  	require.Nil(t, err)
   133  	require.NotNil(t, rotateEv)
   134  
   135  	// verify the header
   136  	verifyHeader(t, rotateEv.Header, header, replication.ROTATE_EVENT, latestPos, uint32(len(rotateEv.RawData)))
   137  
   138  	// verify the body
   139  	rotateEvBody, ok := rotateEv.Event.(*replication.RotateEvent)
   140  	require.True(t, ok)
   141  	require.NotNil(t, rotateEvBody)
   142  	require.Equal(t, nextLogName, rotateEvBody.NextLogName)
   143  	require.Equal(t, position, rotateEvBody.Position)
   144  }
   145  
   146  func TestGenPreviousGTIDsEvent(t *testing.T) {
   147  	var (
   148  		header = &replication.EventHeader{
   149  			Timestamp: uint32(time.Now().Unix()),
   150  			ServerID:  11,
   151  			Flags:     0x01,
   152  		}
   153  		latestPos uint32 = 4
   154  		str              = "9f61c5f9-1eef-11e9-b6cf-0242ac140003:1-5"
   155  	)
   156  
   157  	// always needing a FormatDescriptionEvent in the binlog file.
   158  	formatDescEv, err := GenFormatDescriptionEvent(header, latestPos)
   159  	require.Nil(t, err)
   160  
   161  	// update latestPos
   162  	latestPos = formatDescEv.Header.LogPos
   163  
   164  	// generate a PreviousGTIDsEvent
   165  	gSet, err := gtid.ParserGTID(gmysql.MySQLFlavor, str)
   166  	require.Nil(t, err)
   167  
   168  	previousGTIDsEv, err := GenPreviousGTIDsEvent(header, latestPos, gSet)
   169  	require.Nil(t, err)
   170  
   171  	dir := t.TempDir()
   172  	name1 := filepath.Join(dir, "mysql-bin-test.000001")
   173  	f1, err := os.Create(name1)
   174  	require.Nil(t, err)
   175  	defer f1.Close()
   176  
   177  	// write a binlog file header
   178  	_, err = f1.Write(replication.BinLogFileHeader)
   179  	require.Nil(t, err)
   180  
   181  	// write a FormatDescriptionEvent event
   182  	_, err = f1.Write(formatDescEv.RawData)
   183  	require.Nil(t, err)
   184  
   185  	// write the PreviousGTIDsEvent
   186  	_, err = f1.Write(previousGTIDsEv.RawData)
   187  	require.Nil(t, err)
   188  
   189  	count := 0
   190  	onEventFunc := func(e *replication.BinlogEvent) error {
   191  		count++
   192  		switch count {
   193  		case 1: // FormatDescriptionEvent
   194  			require.Equal(t, formatDescEv.Header, e.Header)
   195  			require.Equal(t, formatDescEv.Event, e.Event)
   196  			require.Equal(t, formatDescEv.RawData, e.RawData)
   197  		case 2: // PreviousGTIDsEvent
   198  			require.Equal(t, previousGTIDsEv.Header, e.Header)
   199  			require.Equal(t, previousGTIDsEv.Event, e.Event)
   200  			require.Equal(t, previousGTIDsEv.RawData, e.RawData)
   201  		default:
   202  			t.Fatalf("too many binlog events got, current is %+v", e.Header)
   203  		}
   204  		return nil
   205  	}
   206  
   207  	parser2 := replication.NewBinlogParser()
   208  	parser2.SetVerifyChecksum(true)
   209  	err = parser2.ParseFile(name1, 0, onEventFunc)
   210  	require.Nil(t, err)
   211  
   212  	// multi GTID
   213  	str = "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"
   214  	gSet, err = gtid.ParserGTID(gmysql.MySQLFlavor, str)
   215  	require.Nil(t, err)
   216  
   217  	previousGTIDsEv, err = GenPreviousGTIDsEvent(header, latestPos, gSet)
   218  	require.Nil(t, err)
   219  
   220  	// write another file
   221  	name2 := filepath.Join(dir, "mysql-bin-test.000002")
   222  	f2, err := os.Create(name2)
   223  	require.Nil(t, err)
   224  	defer f2.Close()
   225  
   226  	// write a binlog file header
   227  	_, err = f2.Write(replication.BinLogFileHeader)
   228  	require.Nil(t, err)
   229  
   230  	// write a FormatDescriptionEvent event
   231  	_, err = f2.Write(formatDescEv.RawData)
   232  	require.Nil(t, err)
   233  
   234  	// write the PreviousGTIDsEvent
   235  	_, err = f2.Write(previousGTIDsEv.RawData)
   236  	require.Nil(t, err)
   237  
   238  	count = 0 // reset count
   239  	err = parser2.ParseFile(name2, 0, onEventFunc)
   240  	require.Nil(t, err)
   241  }
   242  
   243  func TestGenGTIDEvent(t *testing.T) {
   244  	var (
   245  		header = &replication.EventHeader{
   246  			Timestamp: uint32(time.Now().Unix()),
   247  			ServerID:  11,
   248  			Flags:     0x01,
   249  		}
   250  		latestPos     uint32 = 4
   251  		gtidFlags            = GTIDFlagsCommitYes
   252  		prevGTIDsStr         = "9f61c5f9-1eef-11e9-b6cf-0242ac140003:1-5"
   253  		uuid                 = "9f61c5f9-1eef-11e9-b6cf-0242ac140003"
   254  		gno           int64  = 6
   255  		lastCommitted int64
   256  	)
   257  	sid, err := ParseSID(uuid)
   258  	require.Nil(t, err)
   259  
   260  	// always needing a FormatDescriptionEvent in the binlog file.
   261  	formatDescEv, err := GenFormatDescriptionEvent(header, latestPos)
   262  	require.Nil(t, err)
   263  	latestPos = formatDescEv.Header.LogPos // update latestPos
   264  
   265  	// also needing a PreviousGTIDsEvent after FormatDescriptionEvent
   266  	gSet, err := gtid.ParserGTID(gmysql.MySQLFlavor, prevGTIDsStr)
   267  	require.Nil(t, err)
   268  	previousGTIDsEv, err := GenPreviousGTIDsEvent(header, latestPos, gSet)
   269  	require.Nil(t, err)
   270  	latestPos = previousGTIDsEv.Header.LogPos // update latestPos
   271  
   272  	gtidEv, err := GenGTIDEvent(header, latestPos, gtidFlags, uuid, gno, lastCommitted, lastCommitted+1)
   273  	require.Nil(t, err)
   274  
   275  	// verify the header
   276  	verifyHeader(t, gtidEv.Header, header, replication.GTID_EVENT, latestPos, uint32(len(gtidEv.RawData)))
   277  
   278  	// verify the body
   279  	gtidEvBody, ok := gtidEv.Event.(*replication.GTIDEvent)
   280  	require.True(t, ok)
   281  	require.NotNil(t, gtidEvBody)
   282  	require.Equal(t, gtidFlags, gtidEvBody.CommitFlag)
   283  	require.Equal(t, sid.Bytes(), gtidEvBody.SID)
   284  	require.Equal(t, gno, gtidEvBody.GNO)
   285  	require.Equal(t, lastCommitted, gtidEvBody.LastCommitted)
   286  	require.Equal(t, lastCommitted+1, gtidEvBody.SequenceNumber)
   287  
   288  	// write a binlog file, then try to parse it
   289  	dir := t.TempDir()
   290  	name := filepath.Join(dir, "mysql-bin-test.000001")
   291  	f, err := os.Create(name)
   292  	require.Nil(t, err)
   293  	defer f.Close()
   294  
   295  	// write a binlog file.
   296  	_, err = f.Write(replication.BinLogFileHeader)
   297  	require.Nil(t, err)
   298  	_, err = f.Write(formatDescEv.RawData)
   299  	require.Nil(t, err)
   300  	_, err = f.Write(previousGTIDsEv.RawData)
   301  	require.Nil(t, err)
   302  
   303  	// write GTIDEvent.
   304  	_, err = f.Write(gtidEv.RawData)
   305  	require.Nil(t, err)
   306  
   307  	count := 0
   308  	onEventFunc := func(e *replication.BinlogEvent) error {
   309  		count++
   310  		switch count {
   311  		case 1: // FormatDescriptionEvent
   312  			require.Equal(t, formatDescEv.Header, e.Header)
   313  			require.Equal(t, formatDescEv.Event, e.Event)
   314  			require.Equal(t, formatDescEv.RawData, e.RawData)
   315  		case 2: // PreviousGTIDsEvent
   316  			require.Equal(t, previousGTIDsEv.Header, e.Header)
   317  			require.Equal(t, previousGTIDsEv.Event, e.Event)
   318  			require.Equal(t, previousGTIDsEv.RawData, e.RawData)
   319  		case 3: // GTIDEvent
   320  			require.Equal(t, replication.GTID_EVENT, e.Header.EventType)
   321  			require.Equal(t, gtidEv.RawData, e.RawData)
   322  		default:
   323  			t.Fatalf("too many binlog events got, current is %+v", e.Header)
   324  		}
   325  		return nil
   326  	}
   327  	parser2 := replication.NewBinlogParser()
   328  	parser2.SetVerifyChecksum(true)
   329  	err = parser2.ParseFile(name, 0, onEventFunc)
   330  	require.Nil(t, err)
   331  }
   332  
   333  func TestGenQueryEvent(t *testing.T) {
   334  	var (
   335  		header = &replication.EventHeader{
   336  			Timestamp: uint32(time.Now().Unix()),
   337  			ServerID:  11,
   338  			Flags:     0x01,
   339  		}
   340  		latestPos     uint32 = 4
   341  		slaveProxyID  uint32 = 2
   342  		executionTime uint32 = 12
   343  		errorCode     uint16 = 13
   344  		statusVars    []byte // nil
   345  		schema        []byte // nil
   346  		query         []byte // nil
   347  	)
   348  
   349  	// empty query, invalid
   350  	queryEv, err := GenQueryEvent(header, latestPos, slaveProxyID, executionTime, errorCode, statusVars, schema, query)
   351  	require.NotNil(t, err)
   352  	require.Nil(t, queryEv)
   353  
   354  	// valid query
   355  	query = []byte("BEGIN")
   356  	queryEv, err = GenQueryEvent(header, latestPos, slaveProxyID, executionTime, errorCode, statusVars, schema, query)
   357  	require.Nil(t, err)
   358  	require.NotNil(t, queryEv)
   359  
   360  	// verify the header
   361  	verifyHeader(t, queryEv.Header, header, replication.QUERY_EVENT, latestPos, uint32(len(queryEv.RawData)))
   362  
   363  	// verify the body
   364  	queryEvBody, ok := queryEv.Event.(*replication.QueryEvent)
   365  	require.True(t, ok)
   366  	require.NotNil(t, queryEvBody)
   367  	require.Equal(t, slaveProxyID, queryEvBody.SlaveProxyID)
   368  	require.Equal(t, executionTime, queryEvBody.ExecutionTime)
   369  	require.Equal(t, errorCode, queryEvBody.ErrorCode)
   370  	require.Equal(t, []byte{}, queryEvBody.StatusVars)
   371  	require.Equal(t, []byte{}, queryEvBody.Schema)
   372  	require.Equal(t, query, queryEvBody.Query)
   373  
   374  	// non-empty schema
   375  	schema = []byte("db")
   376  	query = []byte("CREATE TABLE db.tbl (c1 int)")
   377  	queryEv, err = GenQueryEvent(header, latestPos, slaveProxyID, executionTime, errorCode, statusVars, schema, query)
   378  	require.Nil(t, err)
   379  	require.NotNil(t, queryEv)
   380  
   381  	// verify the body
   382  	queryEvBody, ok = queryEv.Event.(*replication.QueryEvent)
   383  	require.True(t, ok)
   384  	require.NotNil(t, queryEvBody)
   385  	require.Equal(t, schema, queryEvBody.Schema)
   386  	require.Equal(t, query, queryEvBody.Query)
   387  
   388  	// non-empty statusVars
   389  	statusVars = []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x20, 0x00, 0xa0, 0x55, 0x00, 0x00, 0x00, 0x00, 0x06, 0x03, 0x73, 0x74, 0x64, 0x04, 0x21, 0x00, 0x21, 0x00, 0x08, 0x00, 0x0c, 0x01, 0x73, 0x68, 0x61, 0x72, 0x64, 0x5f, 0x64, 0x62, 0x5f, 0x31, 0x00}
   390  	queryEv, err = GenQueryEvent(header, latestPos, slaveProxyID, executionTime, errorCode, statusVars, schema, query)
   391  	require.Nil(t, err)
   392  	require.NotNil(t, queryEv)
   393  
   394  	// verify the body
   395  	queryEvBody, ok = queryEv.Event.(*replication.QueryEvent)
   396  	require.True(t, ok)
   397  	require.NotNil(t, queryEvBody)
   398  	require.Equal(t, statusVars, queryEvBody.StatusVars)
   399  }
   400  
   401  func TestGenTableMapEvent(t *testing.T) {
   402  	var (
   403  		header = &replication.EventHeader{
   404  			Timestamp: uint32(time.Now().Unix()),
   405  			ServerID:  11,
   406  			Flags:     0x01,
   407  		}
   408  		latestPos  uint32 = 123
   409  		tableID    uint64 = 108
   410  		schema     []byte // nil
   411  		table      []byte // nil
   412  		columnType []byte // nil
   413  	)
   414  
   415  	// invalid schema, table and columnType
   416  	tableMapEv, err := GenTableMapEvent(header, latestPos, tableID, schema, table, columnType)
   417  	require.NotNil(t, err)
   418  	require.Nil(t, tableMapEv)
   419  
   420  	// valid schema, invalid table and columnType
   421  	schema = []byte("db")
   422  	tableMapEv, err = GenTableMapEvent(header, latestPos, tableID, schema, table, columnType)
   423  	require.NotNil(t, err)
   424  	require.Nil(t, tableMapEv)
   425  
   426  	// valid schema and table, invalid columnType
   427  	table = []byte("tbl")
   428  	tableMapEv, err = GenTableMapEvent(header, latestPos, tableID, schema, table, columnType)
   429  	require.NotNil(t, err)
   430  	require.Nil(t, tableMapEv)
   431  
   432  	// all valid
   433  	columnType = []byte{gmysql.MYSQL_TYPE_LONG}
   434  	tableMapEv, err = GenTableMapEvent(header, latestPos, tableID, schema, table, columnType)
   435  	require.Nil(t, err)
   436  	require.NotNil(t, tableMapEv)
   437  
   438  	// verify the header
   439  	verifyHeader(t, tableMapEv.Header, header, replication.TABLE_MAP_EVENT, latestPos, uint32(len(tableMapEv.RawData)))
   440  
   441  	// verify the body
   442  	tableMapEvBody, ok := tableMapEv.Event.(*replication.TableMapEvent)
   443  	require.True(t, ok)
   444  	require.NotNil(t, tableMapEvBody)
   445  	require.Equal(t, tableID, tableMapEvBody.TableID)
   446  	require.Equal(t, tableMapFlags, tableMapEvBody.Flags)
   447  	require.Equal(t, schema, tableMapEvBody.Schema)
   448  	require.Equal(t, table, tableMapEvBody.Table)
   449  	require.Equal(t, uint64(len(columnType)), tableMapEvBody.ColumnCount)
   450  	require.Equal(t, columnType, tableMapEvBody.ColumnType)
   451  
   452  	// multi column type
   453  	columnType = []byte{gmysql.MYSQL_TYPE_STRING, gmysql.MYSQL_TYPE_NEWDECIMAL, gmysql.MYSQL_TYPE_VAR_STRING, gmysql.MYSQL_TYPE_BLOB}
   454  	tableMapEv, err = GenTableMapEvent(header, latestPos, tableID, schema, table, columnType)
   455  	require.Nil(t, err)
   456  	require.NotNil(t, tableMapEv)
   457  
   458  	// verify the body
   459  	tableMapEvBody, ok = tableMapEv.Event.(*replication.TableMapEvent)
   460  	require.True(t, ok)
   461  	require.NotNil(t, tableMapEvBody)
   462  	require.Equal(t, uint64(len(columnType)), tableMapEvBody.ColumnCount)
   463  	require.Equal(t, columnType, tableMapEvBody.ColumnType)
   464  
   465  	// unsupported column type
   466  	columnType = []byte{gmysql.MYSQL_TYPE_NEWDATE}
   467  	tableMapEv, err = GenTableMapEvent(header, latestPos, tableID, schema, table, columnType)
   468  	require.NotNil(t, err)
   469  	require.Nil(t, tableMapEv)
   470  }
   471  
   472  func TestGenRowsEvent(t *testing.T) {
   473  	var (
   474  		header = &replication.EventHeader{
   475  			Timestamp: uint32(time.Now().Unix()),
   476  			ServerID:  11,
   477  			Flags:     0x01,
   478  		}
   479  		latestPos  uint32 = 123
   480  		tableID    uint64 = 108
   481  		eventType         = replication.TABLE_MAP_EVENT
   482  		rowsFlag          = RowFlagsEndOfStatement
   483  		rows       [][]interface{}
   484  		columnType []byte // nil
   485  	)
   486  
   487  	// invalid eventType, rows and columnType
   488  	rowsEv, err := GenRowsEvent(header, latestPos, eventType, tableID, rowsFlag, rows, columnType, nil)
   489  	require.NotNil(t, err)
   490  	require.Nil(t, rowsEv)
   491  
   492  	// valid eventType, invalid rows and columnType
   493  	eventType = replication.WRITE_ROWS_EVENTv0
   494  	rowsEv, err = GenRowsEvent(header, latestPos, eventType, tableID, rowsFlag, rows, columnType, nil)
   495  	require.NotNil(t, err)
   496  	require.Nil(t, rowsEv)
   497  
   498  	// valid eventType and rows, invalid columnType
   499  	row := []interface{}{int32(1)}
   500  	rows = append(rows, row)
   501  	rowsEv, err = GenRowsEvent(header, latestPos, eventType, tableID, rowsFlag, rows, columnType, nil)
   502  	require.NotNil(t, err)
   503  	require.Nil(t, rowsEv)
   504  
   505  	// all valid
   506  	columnType = []byte{gmysql.MYSQL_TYPE_LONG}
   507  	rowsEv, err = GenRowsEvent(header, latestPos, eventType, tableID, rowsFlag, rows, columnType, nil)
   508  	require.Nil(t, err)
   509  	require.NotNil(t, rowsEv)
   510  
   511  	// verify the header
   512  	verifyHeader(t, rowsEv.Header, header, eventType, latestPos, uint32(len(rowsEv.RawData)))
   513  
   514  	// verify the body
   515  	rowsEvBody, ok := rowsEv.Event.(*replication.RowsEvent)
   516  	require.True(t, ok)
   517  	require.NotNil(t, rowsEvBody)
   518  	require.Equal(t, rowsFlag, rowsEvBody.Flags)
   519  	require.Equal(t, tableID, rowsEvBody.TableID)
   520  	require.Equal(t, uint64(len(rows[0])), rowsEvBody.ColumnCount)
   521  	require.Equal(t, 0, rowsEvBody.Version) // WRITE_ROWS_EVENTv0
   522  	require.Equal(t, rows, rowsEvBody.Rows)
   523  
   524  	// multi rows, with different length, invalid
   525  	rows = append(rows, []interface{}{int32(1), int32(2)})
   526  	rowsEv, err = GenRowsEvent(header, latestPos, eventType, tableID, rowsFlag, rows, columnType, nil)
   527  	require.NotNil(t, err)
   528  	require.Nil(t, rowsEv)
   529  
   530  	// multi rows, multi columns, valid
   531  	rows = make([][]interface{}, 0, 2)
   532  	rows = append(rows, []interface{}{int32(1), int32(2)})
   533  	rows = append(rows, []interface{}{int32(3), int32(4)})
   534  	columnType = []byte{gmysql.MYSQL_TYPE_LONG, gmysql.MYSQL_TYPE_LONG}
   535  	rowsEv, err = GenRowsEvent(header, latestPos, eventType, tableID, rowsFlag, rows, columnType, nil)
   536  	require.Nil(t, err)
   537  	require.NotNil(t, rowsEv)
   538  	// verify the body
   539  	rowsEvBody, ok = rowsEv.Event.(*replication.RowsEvent)
   540  	require.True(t, ok)
   541  	require.NotNil(t, rowsEvBody)
   542  	require.Equal(t, uint64(len(rows[0])), rowsEvBody.ColumnCount)
   543  	require.Equal(t, rows, rowsEvBody.Rows)
   544  
   545  	// all valid event-type
   546  	evTypes := []replication.EventType{
   547  		replication.WRITE_ROWS_EVENTv0, replication.WRITE_ROWS_EVENTv1, replication.WRITE_ROWS_EVENTv2,
   548  		replication.UPDATE_ROWS_EVENTv0, replication.UPDATE_ROWS_EVENTv1, replication.UPDATE_ROWS_EVENTv2,
   549  		replication.DELETE_ROWS_EVENTv0, replication.DELETE_ROWS_EVENTv1, replication.DELETE_ROWS_EVENTv2,
   550  	}
   551  	for _, eventType = range evTypes {
   552  		rowsEv, err = GenRowsEvent(header, latestPos, eventType, tableID, rowsFlag, rows, columnType, nil)
   553  		require.Nil(t, err)
   554  		require.NotNil(t, rowsEv)
   555  		require.Equal(t, eventType, rowsEv.Header.EventType)
   556  	}
   557  
   558  	// more column types
   559  	rows = make([][]interface{}, 0, 1)
   560  	rows = append(rows, []interface{}{
   561  		int32(1), int8(2), int16(3), int32(4), int64(5),
   562  		float32(1.23), float64(4.56), "string with type STRING",
   563  	})
   564  	columnType = []byte{
   565  		gmysql.MYSQL_TYPE_LONG, gmysql.MYSQL_TYPE_TINY, gmysql.MYSQL_TYPE_SHORT, gmysql.MYSQL_TYPE_INT24, gmysql.MYSQL_TYPE_LONGLONG,
   566  		gmysql.MYSQL_TYPE_FLOAT, gmysql.MYSQL_TYPE_DOUBLE, gmysql.MYSQL_TYPE_STRING,
   567  	}
   568  	rowsEv, err = GenRowsEvent(header, latestPos, eventType, tableID, rowsFlag, rows, columnType, nil)
   569  	require.Nil(t, err)
   570  	require.NotNil(t, rowsEv)
   571  	// verify the body
   572  	rowsEvBody, ok = rowsEv.Event.(*replication.RowsEvent)
   573  	require.True(t, ok)
   574  	require.NotNil(t, rowsEvBody)
   575  	require.Equal(t, uint64(len(rows[0])), rowsEvBody.ColumnCount)
   576  	require.Equal(t, rows, rowsEvBody.Rows)
   577  
   578  	// column type mismatch
   579  	rows[0][0] = int8(1)
   580  	rowsEv, err = GenRowsEvent(header, latestPos, eventType, tableID, rowsFlag, rows, columnType, nil)
   581  	require.NotNil(t, err)
   582  	require.Nil(t, rowsEv)
   583  
   584  	// NotSupported column type
   585  	rows = make([][]interface{}, 0, 1)
   586  	rows = append(rows, []interface{}{int32(1)})
   587  	unsupportedTypes := []byte{
   588  		gmysql.MYSQL_TYPE_VARCHAR, gmysql.MYSQL_TYPE_VAR_STRING,
   589  		gmysql.MYSQL_TYPE_NEWDECIMAL, gmysql.MYSQL_TYPE_BIT,
   590  		gmysql.MYSQL_TYPE_TIMESTAMP, gmysql.MYSQL_TYPE_TIMESTAMP2,
   591  		gmysql.MYSQL_TYPE_DATETIME, gmysql.MYSQL_TYPE_DATETIME2,
   592  		gmysql.MYSQL_TYPE_TIME, gmysql.MYSQL_TYPE_TIME2,
   593  		gmysql.MYSQL_TYPE_YEAR, gmysql.MYSQL_TYPE_ENUM, gmysql.MYSQL_TYPE_SET,
   594  		gmysql.MYSQL_TYPE_BLOB, gmysql.MYSQL_TYPE_JSON, gmysql.MYSQL_TYPE_GEOMETRY,
   595  	}
   596  	for i := range unsupportedTypes {
   597  		columnType = unsupportedTypes[i : i+1]
   598  		rowsEv, err = GenRowsEvent(header, latestPos, eventType, tableID, rowsFlag, rows, columnType, nil)
   599  		require.NotNil(t, err)
   600  		require.True(t, strings.Contains(err.Error(), "not supported"))
   601  		require.Nil(t, rowsEv)
   602  	}
   603  }
   604  
   605  func TestGenXIDEvent(t *testing.T) {
   606  	var (
   607  		header = &replication.EventHeader{
   608  			Timestamp: uint32(time.Now().Unix()),
   609  			ServerID:  11,
   610  			Flags:     0x01,
   611  		}
   612  		latestPos uint32 = 4
   613  		xid       uint64 = 123
   614  	)
   615  
   616  	xidEv, err := GenXIDEvent(header, latestPos, xid)
   617  	require.Nil(t, err)
   618  	require.NotNil(t, xidEv)
   619  
   620  	// verify the header
   621  	verifyHeader(t, xidEv.Header, header, replication.XID_EVENT, latestPos, uint32(len(xidEv.RawData)))
   622  
   623  	// verify the body
   624  	xidEvBody, ok := xidEv.Event.(*replication.XIDEvent)
   625  	require.True(t, ok)
   626  	require.NotNil(t, xidEvBody)
   627  	require.Equal(t, xid, xidEvBody.XID)
   628  }
   629  
   630  func TestGenMariaDBGTIDListEvent(t *testing.T) {
   631  	var (
   632  		header = &replication.EventHeader{
   633  			Timestamp: uint32(time.Now().Unix()),
   634  			ServerID:  11,
   635  			Flags:     0x01,
   636  		}
   637  		latestPos uint32         = 4
   638  		gSet      gmysql.GTIDSet // invalid
   639  	)
   640  
   641  	// invalid gSet
   642  	gtidListEv, err := GenMariaDBGTIDListEvent(header, latestPos, gSet)
   643  	require.NotNil(t, err)
   644  	require.Nil(t, gtidListEv)
   645  
   646  	// valid gSet with single GTID
   647  	gSet, err = gtid.ParserGTID(gmysql.MariaDBFlavor, "1-2-3")
   648  	require.Nil(t, err)
   649  	require.NotNil(t, gSet)
   650  	mGSet, ok := gSet.(*gmysql.MariadbGTIDSet)
   651  	require.True(t, ok)
   652  	require.NotNil(t, mGSet)
   653  
   654  	gtidListEv, err = GenMariaDBGTIDListEvent(header, latestPos, gSet)
   655  	require.Nil(t, err)
   656  	require.NotNil(t, gtidListEv)
   657  
   658  	// verify the header
   659  	verifyHeader(t, gtidListEv.Header, header, replication.MARIADB_GTID_LIST_EVENT, latestPos, uint32(len(gtidListEv.RawData)))
   660  
   661  	// verify the body
   662  	gtidListEvBody, ok := gtidListEv.Event.(*replication.MariadbGTIDListEvent)
   663  	require.True(t, ok)
   664  	require.NotNil(t, gtidListEvBody)
   665  	require.Len(t, gtidListEvBody.GTIDs, 1)
   666  	require.Equal(t, *mGSet.Sets[gtidListEvBody.GTIDs[0].DomainID][gtidListEvBody.GTIDs[0].ServerID], gtidListEvBody.GTIDs[0])
   667  
   668  	// valid gSet with multi GTIDs
   669  	gSet, err = gtid.ParserGTID(gmysql.MariaDBFlavor, "1-2-12,2-2-3,3-3-8,3-4-4")
   670  	require.Nil(t, err)
   671  	require.NotNil(t, gSet)
   672  	mGSet, ok = gSet.(*gmysql.MariadbGTIDSet)
   673  	require.True(t, ok)
   674  	require.NotNil(t, mGSet)
   675  
   676  	gtidListEv, err = GenMariaDBGTIDListEvent(header, latestPos, gSet)
   677  	require.Nil(t, err)
   678  	require.NotNil(t, gtidListEv)
   679  
   680  	// verify the body
   681  	gtidListEvBody, ok = gtidListEv.Event.(*replication.MariadbGTIDListEvent)
   682  	require.True(t, ok)
   683  	require.NotNil(t, gtidListEvBody)
   684  	require.Len(t, gtidListEvBody.GTIDs, 4)
   685  	for _, mGTID := range gtidListEvBody.GTIDs {
   686  		set, ok := mGSet.Sets[mGTID.DomainID]
   687  		require.True(t, ok)
   688  		mGTID2, ok := set[mGTID.ServerID]
   689  		require.True(t, ok)
   690  		require.Equal(t, *mGTID2, mGTID)
   691  	}
   692  }
   693  
   694  func TestGenMariaDBGTIDEvent(t *testing.T) {
   695  	var (
   696  		header = &replication.EventHeader{
   697  			Timestamp: uint32(time.Now().Unix()),
   698  			ServerID:  11,
   699  			Flags:     0x01,
   700  		}
   701  		latestPos uint32 = 4
   702  		seqNum    uint64 = 123
   703  		domainID  uint32 = 456
   704  	)
   705  
   706  	gtidEv, err := GenMariaDBGTIDEvent(header, latestPos, seqNum, domainID)
   707  	require.Nil(t, err)
   708  	require.NotNil(t, gtidEv)
   709  
   710  	// verify the header
   711  	verifyHeader(t, gtidEv.Header, header, replication.MARIADB_GTID_EVENT, latestPos, uint32(len(gtidEv.RawData)))
   712  
   713  	// verify the body
   714  	gtidEvBody, ok := gtidEv.Event.(*replication.MariadbGTIDEvent)
   715  	require.True(t, ok)
   716  	require.NotNil(t, gtidEvBody)
   717  	require.Equal(t, seqNum, gtidEvBody.GTID.SequenceNumber)
   718  	require.Equal(t, domainID, gtidEvBody.GTID.DomainID)
   719  }
   720  
   721  func TestGenDummyEvent(t *testing.T) {
   722  	var (
   723  		header = &replication.EventHeader{
   724  			Timestamp: uint32(time.Now().Unix()),
   725  			ServerID:  11,
   726  			Flags:     replication.LOG_EVENT_THREAD_SPECIFIC_F | replication.LOG_EVENT_BINLOG_IN_USE_F,
   727  		}
   728  		expectedHeader = &replication.EventHeader{
   729  			Timestamp: uint32(time.Now().Unix()),
   730  			ServerID:  11,
   731  			Flags:     replication.LOG_EVENT_SUPPRESS_USE_F | replication.LOG_EVENT_RELAY_LOG_F | replication.LOG_EVENT_BINLOG_IN_USE_F,
   732  		}
   733  		latestPos uint32 = 4
   734  	)
   735  
   736  	// too small event size
   737  	eventSize := MinUserVarEventLen - 1
   738  	userVarEv, err := GenDummyEvent(header, latestPos, eventSize)
   739  	require.Regexp(t, ".*is too small.*", err)
   740  	require.Nil(t, userVarEv)
   741  
   742  	// minimum event size, USER_VAR_EVENT with name-length==1
   743  	eventSize = MinUserVarEventLen
   744  	userVarEv, err = GenDummyEvent(header, latestPos, eventSize)
   745  	require.Nil(t, err)
   746  	require.NotNil(t, userVarEv)
   747  	// verify the header
   748  	verifyHeader(t, userVarEv.Header, expectedHeader, replication.USER_VAR_EVENT, latestPos, uint32(len(userVarEv.RawData)))
   749  	// verify the body
   750  	nameStart := uint32(eventHeaderLen + 4)
   751  	nameEnd := eventSize - 1 - crc32Len
   752  	nameLen := nameEnd - nameStart
   753  	require.Equal(t, uint32(1), nameLen) // name-length==1
   754  	require.Equal(t, dummyUserVarName[:nameLen], userVarEv.RawData[nameStart:nameEnd])
   755  	require.Equal(t, []byte{0x01}, userVarEv.RawData[nameEnd:nameEnd+1]) // is-null always 1
   756  
   757  	// minimum, .., equal dummy query, longer, ...
   758  	dummyQueryLen := uint32(len(dummyQuery))
   759  	eventSizeList := []uint32{
   760  		MinQueryEventLen, MinQueryEventLen + 5,
   761  		MinQueryEventLen + dummyQueryLen - 1, MinQueryEventLen + dummyQueryLen, MinQueryEventLen + dummyQueryLen + 10,
   762  	}
   763  	for _, eventSize = range eventSizeList {
   764  		queryEv, err := GenDummyEvent(header, latestPos, eventSize)
   765  		require.Nil(t, err)
   766  		require.NotNil(t, queryEv)
   767  		// verify the header
   768  		verifyHeader(t, queryEv.Header, expectedHeader, replication.QUERY_EVENT, latestPos, uint32(len(queryEv.RawData)))
   769  		// verify the body
   770  		queryEvBody, ok := queryEv.Event.(*replication.QueryEvent)
   771  		require.True(t, ok)
   772  		require.NotNil(t, queryEvBody)
   773  		require.Equal(t, uint32(0), queryEvBody.SlaveProxyID)
   774  		require.Equal(t, uint32(0), queryEvBody.ExecutionTime)
   775  		require.Equal(t, uint16(0), queryEvBody.ErrorCode)
   776  		require.Equal(t, []byte{}, queryEvBody.StatusVars)
   777  		require.Equal(t, []byte{}, queryEvBody.Schema)
   778  		queryStart := uint32(eventHeaderLen + 4 + 4 + 1 + 2 + 2 + 1)
   779  		queryEnd := eventSize - crc32Len
   780  		queryLen := queryEnd - queryStart
   781  		require.Len(t, queryEvBody.Query, int(queryLen))
   782  		if queryLen <= dummyQueryLen {
   783  			require.Equal(t, dummyQuery[:queryLen], queryEvBody.Query)
   784  		} else {
   785  			require.Equal(t, dummyQuery, queryEvBody.Query[:dummyQueryLen])
   786  			zeroTail := make([]byte, queryLen-dummyQueryLen)
   787  			require.Equal(t, zeroTail, queryEvBody.Query[dummyQueryLen:])
   788  		}
   789  	}
   790  }