github.com/pingcap/tiflow@v0.0.0-20240520035814-5bf52d54e205/dm/relay/relay_writer_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 relay
    15  
    16  import (
    17  	"bytes"
    18  	"os"
    19  	"path"
    20  	"path/filepath"
    21  	"time"
    22  
    23  	gmysql "github.com/go-mysql-org/go-mysql/mysql"
    24  	"github.com/go-mysql-org/go-mysql/replication"
    25  	"github.com/pingcap/check"
    26  	"github.com/pingcap/tiflow/dm/pkg/binlog/event"
    27  	"github.com/pingcap/tiflow/dm/pkg/gtid"
    28  	"github.com/pingcap/tiflow/dm/pkg/log"
    29  )
    30  
    31  var _ = check.Suite(&testFileWriterSuite{})
    32  
    33  type testFileWriterSuite struct{}
    34  
    35  func (t *testFileWriterSuite) TestInterfaceMethods(c *check.C) {
    36  	var (
    37  		relayDir = c.MkDir()
    38  		uuid     = "3ccc475b-2343-11e7-be21-6c0b84d59f30.000001"
    39  		filename = "test-mysql-bin.000001"
    40  		header   = &replication.EventHeader{
    41  			Timestamp: uint32(time.Now().Unix()),
    42  			ServerID:  11,
    43  			Flags:     0x01,
    44  		}
    45  		latestPos uint32 = 4
    46  		ev, _            = event.GenFormatDescriptionEvent(header, latestPos)
    47  	)
    48  
    49  	c.Assert(os.MkdirAll(path.Join(relayDir, uuid), 0o755), check.IsNil)
    50  
    51  	w := NewFileWriter(log.L(), relayDir)
    52  	c.Assert(w, check.NotNil)
    53  
    54  	// not prepared
    55  	_, err := w.WriteEvent(ev)
    56  	c.Assert(err, check.ErrorMatches, ".*not valid.*")
    57  
    58  	w.Init(uuid, filename)
    59  
    60  	// write event
    61  	res, err := w.WriteEvent(ev)
    62  	c.Assert(err, check.IsNil)
    63  	c.Assert(res.Ignore, check.IsFalse)
    64  
    65  	// close the writer
    66  	c.Assert(w.Close(), check.IsNil)
    67  }
    68  
    69  func (t *testFileWriterSuite) TestRelayDir(c *check.C) {
    70  	var (
    71  		relayDir = c.MkDir()
    72  		uuid     = "3ccc475b-2343-11e7-be21-6c0b84d59f30.000001"
    73  		header   = &replication.EventHeader{
    74  			Timestamp: uint32(time.Now().Unix()),
    75  			ServerID:  11,
    76  			Flags:     0x01,
    77  		}
    78  		latestPos uint32 = 4
    79  	)
    80  	ev, err := event.GenFormatDescriptionEvent(header, latestPos)
    81  	c.Assert(err, check.IsNil)
    82  
    83  	// not inited
    84  	w1 := NewFileWriter(log.L(), relayDir)
    85  	defer w1.Close()
    86  	_, err = w1.WriteEvent(ev)
    87  	c.Assert(err, check.ErrorMatches, ".*not valid.*")
    88  
    89  	// invalid dir
    90  	w2 := NewFileWriter(log.L(), relayDir)
    91  	defer w2.Close()
    92  	w2.Init("invalid\x00uuid", "bin.000001")
    93  	_, err = w2.WriteEvent(ev)
    94  	c.Assert(err, check.ErrorMatches, ".*invalid argument.*")
    95  
    96  	// valid directory, but no filename specified
    97  	w3 := NewFileWriter(log.L(), relayDir)
    98  	defer w3.Close()
    99  	w3.Init(uuid, "")
   100  	_, err = w3.WriteEvent(ev)
   101  	c.Assert(err, check.ErrorMatches, ".*not valid.*")
   102  
   103  	// valid directory, but invalid filename
   104  	w4 := NewFileWriter(log.L(), relayDir)
   105  	defer w4.Close()
   106  	w4.Init(uuid, "test-mysql-bin.666abc")
   107  	_, err = w4.WriteEvent(ev)
   108  	c.Assert(err, check.ErrorMatches, ".*not valid.*")
   109  
   110  	c.Assert(os.MkdirAll(filepath.Join(relayDir, uuid), 0o755), check.IsNil)
   111  
   112  	// valid directory, valid filename
   113  	w5 := NewFileWriter(log.L(), relayDir)
   114  	defer w5.Close()
   115  	w5.Init(uuid, "test-mysql-bin.000001")
   116  	result, err := w5.WriteEvent(ev)
   117  	c.Assert(err, check.IsNil)
   118  	c.Assert(result.Ignore, check.IsFalse)
   119  }
   120  
   121  func (t *testFileWriterSuite) TestFormatDescriptionEvent(c *check.C) {
   122  	var (
   123  		relayDir = c.MkDir()
   124  		filename = "test-mysql-bin.000001"
   125  		uuid     = "3ccc475b-2343-11e7-be21-6c0b84d59f30.000001"
   126  		header   = &replication.EventHeader{
   127  			Timestamp: uint32(time.Now().Unix()),
   128  			ServerID:  11,
   129  			Flags:     0x01,
   130  		}
   131  		latestPos uint32 = 4
   132  	)
   133  	formatDescEv, err := event.GenFormatDescriptionEvent(header, latestPos)
   134  	c.Assert(err, check.IsNil)
   135  	c.Assert(os.Mkdir(path.Join(relayDir, uuid), 0o755), check.IsNil)
   136  
   137  	// write FormatDescriptionEvent to empty file
   138  	w := NewFileWriter(log.L(), relayDir)
   139  	defer w.Close()
   140  	w.Init(uuid, filename)
   141  	result, err := w.WriteEvent(formatDescEv)
   142  	c.Assert(err, check.IsNil)
   143  	c.Assert(result.Ignore, check.IsFalse)
   144  	fileSize := int64(len(replication.BinLogFileHeader) + len(formatDescEv.RawData))
   145  	t.verifyFilenameOffset(c, w, filename, fileSize)
   146  	latestPos = formatDescEv.Header.LogPos
   147  
   148  	// write FormatDescriptionEvent again, ignore
   149  	result, err = w.WriteEvent(formatDescEv)
   150  	c.Assert(err, check.IsNil)
   151  	c.Assert(result.Ignore, check.IsTrue)
   152  	c.Assert(result.IgnoreReason, check.Equals, ignoreReasonAlreadyExists)
   153  	t.verifyFilenameOffset(c, w, filename, fileSize)
   154  
   155  	// write another event
   156  	queryEv, err := event.GenQueryEvent(header, latestPos, 0, 0, 0, nil, []byte("schema"), []byte("BEGIN"))
   157  	c.Assert(err, check.IsNil)
   158  	result, err = w.WriteEvent(queryEv)
   159  	c.Assert(err, check.IsNil)
   160  	c.Assert(result.Ignore, check.IsFalse)
   161  	fileSize += int64(len(queryEv.RawData))
   162  	t.verifyFilenameOffset(c, w, filename, fileSize)
   163  
   164  	// write FormatDescriptionEvent again, ignore
   165  	result, err = w.WriteEvent(formatDescEv)
   166  	c.Assert(err, check.IsNil)
   167  	c.Assert(result.Ignore, check.IsTrue)
   168  	c.Assert(result.IgnoreReason, check.Equals, ignoreReasonAlreadyExists)
   169  	t.verifyFilenameOffset(c, w, filename, fileSize)
   170  
   171  	// check events by reading them back
   172  	events := make([]*replication.BinlogEvent, 0, 2)
   173  	count := 0
   174  	onEventFunc := func(e *replication.BinlogEvent) error {
   175  		count++
   176  		if count > 2 {
   177  			c.Fatalf("too many events received, %+v", e.Header)
   178  		}
   179  		events = append(events, e)
   180  		return nil
   181  	}
   182  	fullName := filepath.Join(relayDir, uuid, filename)
   183  	err = replication.NewBinlogParser().ParseFile(fullName, 0, onEventFunc)
   184  	c.Assert(err, check.IsNil)
   185  	c.Assert(events, check.HasLen, 2)
   186  	c.Assert(events[0], check.DeepEquals, formatDescEv)
   187  	c.Assert(events[1], check.DeepEquals, queryEv)
   188  }
   189  
   190  func (t *testFileWriterSuite) verifyFilenameOffset(c *check.C, w Writer, filename string, offset int64) {
   191  	wf, ok := w.(*FileWriter)
   192  	c.Assert(ok, check.IsTrue)
   193  	c.Assert(wf.filename.Load(), check.Equals, filename)
   194  	c.Assert(wf.offset(), check.Equals, offset)
   195  }
   196  
   197  func (t *testFileWriterSuite) TestRotateEventWithFormatDescriptionEvent(c *check.C) {
   198  	var (
   199  		relayDir            = c.MkDir()
   200  		uuid                = "3ccc475b-2343-11e7-be21-6c0b84d59f30.000001"
   201  		filename            = "test-mysql-bin.000001"
   202  		nextFilename        = "test-mysql-bin.000002"
   203  		nextFilePos  uint64 = 4
   204  		header              = &replication.EventHeader{
   205  			Timestamp: uint32(time.Now().Unix()),
   206  			ServerID:  11,
   207  			Flags:     0x01,
   208  		}
   209  		fakeHeader = &replication.EventHeader{
   210  			Timestamp: 0, // mark as fake
   211  			ServerID:  11,
   212  			Flags:     0x01,
   213  		}
   214  		latestPos uint32 = 4
   215  	)
   216  
   217  	formatDescEv, err := event.GenFormatDescriptionEvent(header, latestPos)
   218  	c.Assert(err, check.IsNil)
   219  	c.Assert(formatDescEv, check.NotNil)
   220  	latestPos = formatDescEv.Header.LogPos
   221  
   222  	rotateEv, err := event.GenRotateEvent(header, latestPos, []byte(nextFilename), nextFilePos)
   223  	c.Assert(err, check.IsNil)
   224  	c.Assert(rotateEv, check.NotNil)
   225  
   226  	fakeRotateEv, err := event.GenRotateEvent(fakeHeader, latestPos, []byte(nextFilename), nextFilePos)
   227  	c.Assert(err, check.IsNil)
   228  	c.Assert(fakeRotateEv, check.NotNil)
   229  
   230  	// hole exists between formatDescEv and holeRotateEv, but the size is too small to fill
   231  	holeRotateEv, err := event.GenRotateEvent(header, latestPos+event.MinUserVarEventLen-1, []byte(nextFilename), nextFilePos)
   232  	c.Assert(err, check.IsNil)
   233  	c.Assert(holeRotateEv, check.NotNil)
   234  
   235  	// 1: non-fake RotateEvent before FormatDescriptionEvent, invalid
   236  	w1 := NewFileWriter(log.L(), relayDir)
   237  	defer w1.Close()
   238  	w1.Init(uuid, filename)
   239  	_, err = w1.WriteEvent(rotateEv)
   240  	c.Assert(err, check.ErrorMatches, ".*no underlying writer opened")
   241  
   242  	// 2. fake RotateEvent before FormatDescriptionEvent
   243  	relayDir = c.MkDir() // use a new relay directory
   244  	c.Assert(os.MkdirAll(filepath.Join(relayDir, uuid), 0o755), check.IsNil)
   245  	w2 := NewFileWriter(log.L(), relayDir)
   246  	defer w2.Close()
   247  	w2.Init(uuid, filename)
   248  	result, err := w2.WriteEvent(fakeRotateEv)
   249  	c.Assert(err, check.IsNil)
   250  	c.Assert(result.Ignore, check.IsTrue) // ignore fake RotateEvent
   251  	c.Assert(result.IgnoreReason, check.Equals, ignoreReasonFakeRotate)
   252  
   253  	result, err = w2.WriteEvent(formatDescEv)
   254  	c.Assert(err, check.IsNil)
   255  	c.Assert(result.Ignore, check.IsFalse)
   256  
   257  	fileSize := int64(len(replication.BinLogFileHeader) + len(formatDescEv.RawData))
   258  	t.verifyFilenameOffset(c, w2, nextFilename, fileSize)
   259  
   260  	// filename should be empty, next file should contain only one FormatDescriptionEvent
   261  	filename1 := filepath.Join(relayDir, uuid, filename)
   262  	filename2 := filepath.Join(relayDir, uuid, nextFilename)
   263  	_, err = os.Stat(filename1)
   264  	c.Assert(os.IsNotExist(err), check.IsTrue)
   265  	c.Assert(w2.Flush(), check.IsNil)
   266  	data, err := os.ReadFile(filename2)
   267  	c.Assert(err, check.IsNil)
   268  	fileHeaderLen := len(replication.BinLogFileHeader)
   269  	c.Assert(len(data), check.Equals, fileHeaderLen+len(formatDescEv.RawData))
   270  	c.Assert(data[fileHeaderLen:], check.DeepEquals, formatDescEv.RawData)
   271  
   272  	// 3. FormatDescriptionEvent before fake RotateEvent
   273  	relayDir = c.MkDir() // use a new relay directory
   274  	c.Assert(os.MkdirAll(filepath.Join(relayDir, uuid), 0o755), check.IsNil)
   275  	w3 := NewFileWriter(log.L(), relayDir)
   276  	defer w3.Close()
   277  	w3.Init(uuid, filename)
   278  	result, err = w3.WriteEvent(formatDescEv)
   279  	c.Assert(err, check.IsNil)
   280  	c.Assert(result, check.NotNil)
   281  	c.Assert(result.Ignore, check.IsFalse)
   282  
   283  	result, err = w3.WriteEvent(fakeRotateEv)
   284  	c.Assert(err, check.IsNil)
   285  	c.Assert(result, check.NotNil)
   286  	c.Assert(result.Ignore, check.IsTrue)
   287  	c.Assert(result.IgnoreReason, check.Equals, ignoreReasonFakeRotate)
   288  
   289  	t.verifyFilenameOffset(c, w3, nextFilename, fileSize)
   290  
   291  	// filename should contain only one FormatDescriptionEvent, next file should be empty
   292  	filename1 = filepath.Join(relayDir, uuid, filename)
   293  	filename2 = filepath.Join(relayDir, uuid, nextFilename)
   294  	_, err = os.Stat(filename2)
   295  	c.Assert(os.IsNotExist(err), check.IsTrue)
   296  	c.Assert(w3.Flush(), check.IsNil)
   297  	data, err = os.ReadFile(filename1)
   298  	c.Assert(err, check.IsNil)
   299  	c.Assert(len(data), check.Equals, fileHeaderLen+len(formatDescEv.RawData))
   300  	c.Assert(data[fileHeaderLen:], check.DeepEquals, formatDescEv.RawData)
   301  
   302  	// 4. FormatDescriptionEvent before non-fake RotateEvent
   303  	relayDir = c.MkDir() // use a new relay directory
   304  	c.Assert(os.MkdirAll(filepath.Join(relayDir, uuid), 0o755), check.IsNil)
   305  	w4 := NewFileWriter(log.L(), relayDir)
   306  	defer w4.Close()
   307  	w4.Init(uuid, filename)
   308  	result, err = w4.WriteEvent(formatDescEv)
   309  	c.Assert(err, check.IsNil)
   310  	c.Assert(result, check.NotNil)
   311  	c.Assert(result.Ignore, check.IsFalse)
   312  
   313  	// try to write a rotateEv with hole exists
   314  	_, err = w4.WriteEvent(holeRotateEv)
   315  	c.Assert(err, check.ErrorMatches, ".*required dummy event size.*is too small.*")
   316  
   317  	result, err = w4.WriteEvent(rotateEv)
   318  	c.Assert(err, check.IsNil)
   319  	c.Assert(result.Ignore, check.IsFalse)
   320  
   321  	fileSize += int64(len(rotateEv.RawData))
   322  	t.verifyFilenameOffset(c, w4, nextFilename, fileSize)
   323  
   324  	// write again, duplicate, but we already rotated and new binlog file not created
   325  	_, err = w4.WriteEvent(rotateEv)
   326  	c.Assert(err, check.ErrorMatches, ".*(no such file or directory|The system cannot find the file specified).*")
   327  
   328  	// filename should contain both one FormatDescriptionEvent and one RotateEvent, next file should be empty
   329  	filename1 = filepath.Join(relayDir, uuid, filename)
   330  	filename2 = filepath.Join(relayDir, uuid, nextFilename)
   331  	_, err = os.Stat(filename2)
   332  	c.Assert(os.IsNotExist(err), check.IsTrue)
   333  	data, err = os.ReadFile(filename1)
   334  	c.Assert(err, check.IsNil)
   335  	c.Assert(len(data), check.Equals, fileHeaderLen+len(formatDescEv.RawData)+len(rotateEv.RawData))
   336  	c.Assert(data[fileHeaderLen:fileHeaderLen+len(formatDescEv.RawData)], check.DeepEquals, formatDescEv.RawData)
   337  	c.Assert(data[fileHeaderLen+len(formatDescEv.RawData):], check.DeepEquals, rotateEv.RawData)
   338  }
   339  
   340  func (t *testFileWriterSuite) TestWriteMultiEvents(c *check.C) {
   341  	var (
   342  		flavor                    = gmysql.MySQLFlavor
   343  		serverID           uint32 = 11
   344  		latestPos          uint32
   345  		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"
   346  		latestGTIDStr             = "3ccc475b-2343-11e7-be21-6c0b84d59f30:14"
   347  		latestXID          uint64 = 10
   348  
   349  		relayDir = c.MkDir()
   350  		uuid     = "3ccc475b-2343-11e7-be21-6c0b84d59f30.000001"
   351  		filename = "test-mysql-bin.000001"
   352  	)
   353  	previousGTIDSet, err := gtid.ParserGTID(flavor, previousGTIDSetStr)
   354  	c.Assert(err, check.IsNil)
   355  	latestGTID, err := gtid.ParserGTID(flavor, latestGTIDStr)
   356  	c.Assert(err, check.IsNil)
   357  
   358  	// use a binlog event generator to generate some binlog events.
   359  	allEvents := make([]*replication.BinlogEvent, 0, 10)
   360  	var allData bytes.Buffer
   361  	g, err := event.NewGenerator(flavor, serverID, latestPos, latestGTID, previousGTIDSet, latestXID)
   362  	c.Assert(err, check.IsNil)
   363  
   364  	// file header with FormatDescriptionEvent and PreviousGTIDsEvent
   365  	events, data, err := g.GenFileHeader(0)
   366  	c.Assert(err, check.IsNil)
   367  	allEvents = append(allEvents, events...)
   368  	allData.Write(data)
   369  
   370  	// CREATE DATABASE/TABLE
   371  	queries := []string{"CRATE DATABASE `db`", "CREATE TABLE `db`.`tbl` (c1 INT)"}
   372  	for _, query := range queries {
   373  		events, data, err = g.GenDDLEvents("db", query, 0)
   374  		c.Assert(err, check.IsNil)
   375  		allEvents = append(allEvents, events...)
   376  		allData.Write(data)
   377  	}
   378  
   379  	// INSERT INTO `db`.`tbl` VALUES (1)
   380  	var (
   381  		tableID    uint64 = 8
   382  		columnType        = []byte{gmysql.MYSQL_TYPE_LONG}
   383  		insertRows        = make([][]interface{}, 1)
   384  	)
   385  	insertRows[0] = []interface{}{int32(1)}
   386  	events, data, err = g.GenDMLEvents(replication.WRITE_ROWS_EVENTv2, []*event.DMLData{
   387  		{TableID: tableID, Schema: "db", Table: "tbl", ColumnType: columnType, Rows: insertRows},
   388  	}, 0)
   389  	c.Assert(err, check.IsNil)
   390  	allEvents = append(allEvents, events...)
   391  	allData.Write(data)
   392  
   393  	c.Assert(os.MkdirAll(filepath.Join(relayDir, uuid), 0o755), check.IsNil)
   394  
   395  	// write the events to the file
   396  	w := NewFileWriter(log.L(), relayDir)
   397  	w.Init(uuid, filename)
   398  	for _, ev := range allEvents {
   399  		result, err2 := w.WriteEvent(ev)
   400  		c.Assert(err2, check.IsNil)
   401  		c.Assert(result.Ignore, check.IsFalse) // no event is ignored
   402  	}
   403  
   404  	c.Assert(w.Flush(), check.IsNil)
   405  	t.verifyFilenameOffset(c, w, filename, int64(allData.Len()))
   406  
   407  	// read the data back from the file
   408  	fullName := filepath.Join(relayDir, uuid, filename)
   409  	obtainData, err := os.ReadFile(fullName)
   410  	c.Assert(err, check.IsNil)
   411  	c.Assert(obtainData, check.DeepEquals, allData.Bytes())
   412  }
   413  
   414  func (t *testFileWriterSuite) TestHandleFileHoleExist(c *check.C) {
   415  	var (
   416  		relayDir = c.MkDir()
   417  		uuid     = "3ccc475b-2343-11e7-be21-6c0b84d59f30.000001"
   418  		filename = "test-mysql-bin.000001"
   419  		header   = &replication.EventHeader{
   420  			Timestamp: uint32(time.Now().Unix()),
   421  			ServerID:  11,
   422  		}
   423  		latestPos uint32 = 4
   424  	)
   425  	formatDescEv, err := event.GenFormatDescriptionEvent(header, latestPos)
   426  	c.Assert(err, check.IsNil)
   427  	c.Assert(formatDescEv, check.NotNil)
   428  
   429  	c.Assert(os.MkdirAll(filepath.Join(relayDir, uuid), 0o755), check.IsNil)
   430  
   431  	w := NewFileWriter(log.L(), relayDir)
   432  	defer w.Close()
   433  	w.Init(uuid, filename)
   434  
   435  	// write the FormatDescriptionEvent, no hole exists
   436  	result, err := w.WriteEvent(formatDescEv)
   437  	c.Assert(err, check.IsNil)
   438  	c.Assert(result.Ignore, check.IsFalse)
   439  
   440  	// hole exits, but the size is too small, invalid
   441  	latestPos = formatDescEv.Header.LogPos + event.MinUserVarEventLen - 1
   442  	queryEv, err := event.GenQueryEvent(header, latestPos, 0, 0, 0, nil, []byte("schema"), []byte("BEGIN"))
   443  	c.Assert(err, check.IsNil)
   444  	_, err = w.WriteEvent(queryEv)
   445  	c.Assert(err, check.ErrorMatches, ".*generate dummy event.*")
   446  
   447  	// hole exits, and the size is enough
   448  	latestPos = formatDescEv.Header.LogPos + event.MinUserVarEventLen
   449  	queryEv, err = event.GenQueryEvent(header, latestPos, 0, 0, 0, nil, []byte("schema"), []byte("BEGIN"))
   450  	c.Assert(err, check.IsNil)
   451  	result, err = w.WriteEvent(queryEv)
   452  	c.Assert(err, check.IsNil)
   453  	c.Assert(result.Ignore, check.IsFalse)
   454  	c.Assert(w.Flush(), check.IsNil)
   455  	fileSize := int64(queryEv.Header.LogPos)
   456  	t.verifyFilenameOffset(c, w, filename, fileSize)
   457  
   458  	// read events back from the file to check the dummy event
   459  	events := make([]*replication.BinlogEvent, 0, 3)
   460  	count := 0
   461  	onEventFunc := func(e *replication.BinlogEvent) error {
   462  		count++
   463  		if count > 3 {
   464  			c.Fatalf("too many events received, %+v", e.Header)
   465  		}
   466  		events = append(events, e)
   467  		return nil
   468  	}
   469  	fullName := filepath.Join(relayDir, uuid, filename)
   470  	err = replication.NewBinlogParser().ParseFile(fullName, 0, onEventFunc)
   471  	c.Assert(err, check.IsNil)
   472  	c.Assert(events, check.HasLen, 3)
   473  	c.Assert(events[0], check.DeepEquals, formatDescEv)
   474  	c.Assert(events[2], check.DeepEquals, queryEv)
   475  	// the second event is the dummy event
   476  	dummyEvent := events[1]
   477  	c.Assert(dummyEvent.Header.EventType, check.Equals, replication.USER_VAR_EVENT)
   478  	c.Assert(dummyEvent.Header.LogPos, check.Equals, latestPos)                               // start pos of the third event
   479  	c.Assert(dummyEvent.Header.EventSize, check.Equals, latestPos-formatDescEv.Header.LogPos) // hole size
   480  }
   481  
   482  func (t *testFileWriterSuite) TestHandleDuplicateEventsExist(c *check.C) {
   483  	// NOTE: not duplicate event already tested in other cases
   484  
   485  	var (
   486  		relayDir = c.MkDir()
   487  		uuid     = "3ccc475b-2343-11e7-be21-6c0b84d59f30.000001"
   488  		filename = "test-mysql-bin.000001"
   489  		header   = &replication.EventHeader{
   490  			Timestamp: uint32(time.Now().Unix()),
   491  			ServerID:  11,
   492  		}
   493  		latestPos uint32 = 4
   494  	)
   495  	c.Assert(os.MkdirAll(filepath.Join(relayDir, uuid), 0o755), check.IsNil)
   496  	w := NewFileWriter(log.L(), relayDir)
   497  	defer w.Close()
   498  	w.Init(uuid, filename)
   499  
   500  	// write a FormatDescriptionEvent, not duplicate
   501  	formatDescEv, err := event.GenFormatDescriptionEvent(header, latestPos)
   502  	c.Assert(err, check.IsNil)
   503  	result, err := w.WriteEvent(formatDescEv)
   504  	c.Assert(err, check.IsNil)
   505  	c.Assert(result.Ignore, check.IsFalse)
   506  	latestPos = formatDescEv.Header.LogPos
   507  
   508  	// write a QueryEvent, the first time, not duplicate
   509  	queryEv, err := event.GenQueryEvent(header, latestPos, 0, 0, 0, nil, []byte("schema"), []byte("BEGIN"))
   510  	c.Assert(err, check.IsNil)
   511  	result, err = w.WriteEvent(queryEv)
   512  	c.Assert(err, check.IsNil)
   513  	c.Assert(result.Ignore, check.IsFalse)
   514  
   515  	// write the QueryEvent again, duplicate
   516  	result, err = w.WriteEvent(queryEv)
   517  	c.Assert(err, check.IsNil)
   518  	c.Assert(result.Ignore, check.IsTrue)
   519  	c.Assert(result.IgnoreReason, check.Equals, ignoreReasonAlreadyExists)
   520  
   521  	// write a start/end pos mismatched event
   522  	latestPos--
   523  	queryEv, err = event.GenQueryEvent(header, latestPos, 0, 0, 0, nil, []byte("schema"), []byte("BEGIN"))
   524  	c.Assert(err, check.IsNil)
   525  	_, err = w.WriteEvent(queryEv)
   526  	c.Assert(err, check.ErrorMatches, ".*handle a potential duplicate event.*")
   527  }