github.com/pingcap/tiflow@v0.0.0-20240520035814-5bf52d54e205/dm/pkg/binlog/pos_finder_test.go (about)

     1  // Copyright 2021 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 binlog
    15  
    16  import (
    17  	"bytes"
    18  	"context"
    19  	"fmt"
    20  	"os"
    21  	"path"
    22  	"strconv"
    23  	"testing"
    24  	"time"
    25  
    26  	"github.com/go-mysql-org/go-mysql/mysql"
    27  	"github.com/go-mysql-org/go-mysql/replication"
    28  	"github.com/pingcap/tiflow/dm/pkg/binlog/event"
    29  	tcontext "github.com/pingcap/tiflow/dm/pkg/context"
    30  	"github.com/pingcap/tiflow/dm/pkg/log"
    31  	"github.com/stretchr/testify/require"
    32  )
    33  
    34  func genBinlogFile(generator *event.Generator, start time.Time, nextFile string) ([]*replication.BinlogEvent, []byte) {
    35  	insertDMLData := []*event.DMLData{
    36  		{
    37  			TableID:    uint64(1),
    38  			Schema:     fmt.Sprintf("db_%d", 1),
    39  			Table:      strconv.Itoa(1),
    40  			ColumnType: []byte{mysql.MYSQL_TYPE_INT24},
    41  			Rows:       [][]interface{}{{int32(1)}, {int32(2)}},
    42  		},
    43  	}
    44  	allEvents := make([]*replication.BinlogEvent, 0)
    45  	var buf bytes.Buffer
    46  	events, data, _ := generator.GenFileHeader(start.Add(1 * time.Second).Unix())
    47  	allEvents = append(allEvents, events...)
    48  	buf.Write(data)
    49  
    50  	events, data, _ = generator.GenDDLEvents("test", "create table t(id int)", start.Add(2*time.Second).Unix())
    51  	allEvents = append(allEvents, events...)
    52  	buf.Write(data)
    53  
    54  	events, data, _ = generator.GenDMLEvents(replication.WRITE_ROWS_EVENTv2, insertDMLData, start.Add(3*time.Second).Unix())
    55  	allEvents = append(allEvents, events...)
    56  	buf.Write(data)
    57  
    58  	events, data, _ = generator.GenDMLEvents(replication.WRITE_ROWS_EVENTv2, insertDMLData, start.Add(5*time.Second).Unix())
    59  	allEvents = append(allEvents, events...)
    60  	buf.Write(data)
    61  
    62  	ev, data, _ := generator.Rotate(nextFile, start.Add(5*time.Second).Unix())
    63  	allEvents = append(allEvents, ev)
    64  	buf.Write(data)
    65  
    66  	return allEvents, buf.Bytes()
    67  }
    68  
    69  func TestTransBoundary(t *testing.T) {
    70  	t.Parallel()
    71  	flavor := "mysql"
    72  	relayDir := t.TempDir()
    73  	beforeTime := time.Now()
    74  	latestGTIDStr := "ffffffff-ffff-ffff-ffff-ffffffffffff:1"
    75  	generator, _ := event.NewGeneratorV2(flavor, "5.6.0", latestGTIDStr, false)
    76  	insertDMLData := []*event.DMLData{
    77  		{
    78  			TableID:    uint64(1),
    79  			Schema:     fmt.Sprintf("db_%d", 1),
    80  			Table:      strconv.Itoa(1),
    81  			ColumnType: []byte{mysql.MYSQL_TYPE_INT24},
    82  			Rows:       [][]interface{}{{int32(1)}, {int32(2)}},
    83  		},
    84  	}
    85  	var buf bytes.Buffer
    86  	_, data, err := generator.GenFileHeader(beforeTime.Add(1 * time.Second).Unix())
    87  	require.Nil(t, err)
    88  	buf.Write(data)
    89  
    90  	// first transaction, timestamp of BEGIN = beforeTime.Add(2*time.Second)
    91  	// timestamp of other events inside this transaction = beforeTime.Add(3 * time.Second)
    92  	ts := beforeTime.Add(2 * time.Second).Unix()
    93  	header := &replication.EventHeader{
    94  		Timestamp: uint32(ts),
    95  		ServerID:  11,
    96  		Flags:     0x01,
    97  	}
    98  	beginEvent, _ := event.GenQueryEvent(header, generator.LatestPos, 1, 1, 0, []byte("0"), []byte("test"), []byte("BEGIN"))
    99  	buf.Write(beginEvent.RawData)
   100  
   101  	ts = beforeTime.Add(3 * time.Second).Unix()
   102  	header.Timestamp = uint32(ts)
   103  	mapEvent, _ := event.GenTableMapEvent(header, beginEvent.Header.LogPos, 1, []byte("test"), []byte("t"), []byte{mysql.MYSQL_TYPE_INT24})
   104  	buf.Write(mapEvent.RawData)
   105  	rowsEvent, _ := event.GenRowsEvent(header, mapEvent.Header.LogPos, replication.WRITE_ROWS_EVENTv2, 1, 1, [][]interface{}{{int32(1)}, {int32(2)}}, []byte{mysql.MYSQL_TYPE_INT24}, mapEvent)
   106  	buf.Write(rowsEvent.RawData)
   107  	xidEvent, _ := event.GenXIDEvent(header, rowsEvent.Header.LogPos, 1)
   108  	buf.Write(xidEvent.RawData)
   109  
   110  	// second transaction, timestamp of all events = beforeTime.Add(3 * time.Second)
   111  	generator.LatestPos = xidEvent.Header.LogPos
   112  	dmlEvents, data, _ := generator.GenDMLEvents(replication.WRITE_ROWS_EVENTv2, insertDMLData, ts)
   113  	buf.Write(data)
   114  
   115  	require.Equal(t, uint32(buf.Len()), dmlEvents[len(dmlEvents)-1].Header.LogPos)
   116  	_ = os.WriteFile(path.Join(relayDir, "mysql-bin.000001"), buf.Bytes(), 0o644)
   117  
   118  	{
   119  		tcctx := tcontext.NewContext(context.Background(), log.L())
   120  		finder := NewLocalBinlogPosFinder(tcctx, false, flavor, relayDir)
   121  		location, posType, err := finder.FindByTimestamp(ts)
   122  		require.Nil(t, err)
   123  		// start of second transaction
   124  		require.Equal(t, mysql.Position{Name: "mysql-bin.000001", Pos: xidEvent.Header.LogPos}, location.Position)
   125  		require.Equal(t, "", location.GTIDSetStr())
   126  		require.Equal(t, InRangeBinlogPos, posType)
   127  	}
   128  }
   129  
   130  func TestMySQL56NoGTID(t *testing.T) {
   131  	t.Parallel()
   132  	flavor := "mysql"
   133  	relayDir := t.TempDir()
   134  	beforeTime := time.Now()
   135  	latestGTIDStr := "ffffffff-ffff-ffff-ffff-ffffffffffff:1"
   136  
   137  	generator, _ := event.NewGeneratorV2(flavor, "5.6.0", latestGTIDStr, false)
   138  
   139  	file1Events, data := genBinlogFile(generator, beforeTime, "mysql-bin.000002")
   140  	require.Equal(t, 11, len(file1Events))
   141  	_ = os.WriteFile(path.Join(relayDir, "mysql-bin.000001"), data, 0o644)
   142  	file2Events, data := genBinlogFile(generator, beforeTime.Add(5*time.Second), "mysql-bin.000003")
   143  	require.Equal(t, 11, len(file2Events))
   144  
   145  	_ = os.WriteFile(path.Join(relayDir, "mysql-bin.000002"), data, 0o644)
   146  	file3Events, data := genBinlogFile(generator, beforeTime.Add(10*time.Second), "mysql-bin.000004")
   147  	require.Equal(t, 11, len(file3Events))
   148  	_ = os.WriteFile(path.Join(relayDir, "mysql-bin.000003"), data, 0o644)
   149  
   150  	tcctx := tcontext.NewContext(context.Background(), log.L())
   151  	{
   152  		var targetEventStart uint32
   153  		var targetEvent *replication.BinlogEvent
   154  		for _, ev := range file1Events {
   155  			if e, ok := ev.Event.(*replication.QueryEvent); ok && string(e.Query) == "BEGIN" {
   156  				targetEvent = ev
   157  				break
   158  			}
   159  			targetEventStart = ev.Header.LogPos
   160  		}
   161  		finder := NewLocalBinlogPosFinder(tcctx, false, flavor, relayDir)
   162  		location, posType, err := finder.FindByTimestamp(int64(targetEvent.Header.Timestamp))
   163  		require.Nil(t, err)
   164  		require.Equal(t, mysql.Position{Name: "mysql-bin.000001", Pos: targetEventStart}, location.Position)
   165  		require.Equal(t, "", location.GTIDSetStr())
   166  		require.Equal(t, InRangeBinlogPos, posType)
   167  	}
   168  	{
   169  		targetEventStart := file2Events[len(file2Events)-1].Header.LogPos
   170  		finder := NewLocalBinlogPosFinder(tcctx, false, flavor, relayDir)
   171  		location, posType, err := finder.FindByTimestamp(int64(file3Events[0].Header.Timestamp))
   172  		require.Nil(t, err)
   173  		require.Equal(t, mysql.Position{Name: "mysql-bin.000002", Pos: targetEventStart}, location.Position)
   174  		require.Equal(t, "", location.GTIDSetStr())
   175  		require.Equal(t, InRangeBinlogPos, posType)
   176  	}
   177  	{
   178  		var targetEventStart uint32
   179  		var targetEvent *replication.BinlogEvent
   180  		for _, ev := range file3Events {
   181  			if _, ok := ev.Event.(*replication.QueryEvent); ok {
   182  				targetEvent = ev
   183  				break
   184  			}
   185  			targetEventStart = ev.Header.LogPos
   186  		}
   187  		finder := NewLocalBinlogPosFinder(tcctx, false, flavor, relayDir)
   188  		location, posType, err := finder.FindByTimestamp(int64(targetEvent.Header.Timestamp))
   189  		require.Nil(t, err)
   190  		require.Equal(t, mysql.Position{Name: "mysql-bin.000003", Pos: targetEventStart}, location.Position)
   191  		require.Equal(t, "", location.GTIDSetStr())
   192  		require.Equal(t, InRangeBinlogPos, posType)
   193  	}
   194  }
   195  
   196  func TestMySQL57NoGTID(t *testing.T) {
   197  	t.Parallel()
   198  	flavor := "mysql"
   199  	relayDir := t.TempDir()
   200  	beforeTime := time.Now()
   201  	latestGTIDStr := "ffffffff-ffff-ffff-ffff-ffffffffffff:1"
   202  
   203  	generator, _ := event.NewGeneratorV2(flavor, "5.7.0", latestGTIDStr, false)
   204  
   205  	file1Events, data := genBinlogFile(generator, beforeTime, "mysql-bin.000002")
   206  	require.Equal(t, 15, len(file1Events))
   207  	_ = os.WriteFile(path.Join(relayDir, "mysql-bin.000001"), data, 0o644)
   208  	file2Events, data := genBinlogFile(generator, beforeTime.Add(5*time.Second), "mysql-bin.000003")
   209  	require.Equal(t, 15, len(file2Events))
   210  	_ = os.WriteFile(path.Join(relayDir, "mysql-bin.000002"), data, 0o644)
   211  	file3Events, data := genBinlogFile(generator, beforeTime.Add(10*time.Second), "mysql-bin.000004")
   212  	require.Equal(t, 15, len(file3Events))
   213  	_ = os.WriteFile(path.Join(relayDir, "mysql-bin.000003"), data, 0o644)
   214  
   215  	tcctx := tcontext.NewContext(context.Background(), log.L())
   216  	{
   217  		var targetEventStart uint32
   218  		var targetEvent *replication.BinlogEvent
   219  		cnt := 0
   220  		for _, ev := range file3Events {
   221  			if ev.Header.EventType == replication.ANONYMOUS_GTID_EVENT {
   222  				targetEvent = ev
   223  				// second GTID event
   224  				cnt++
   225  				if cnt == 2 {
   226  					break
   227  				}
   228  			}
   229  			targetEventStart = ev.Header.LogPos
   230  		}
   231  		finder := NewLocalBinlogPosFinder(tcctx, false, flavor, relayDir)
   232  		location, posType, err := finder.FindByTimestamp(int64(targetEvent.Header.Timestamp))
   233  		require.Nil(t, err)
   234  		require.Equal(t, mysql.Position{Name: "mysql-bin.000003", Pos: targetEventStart}, location.Position)
   235  		require.Equal(t, "", location.GTIDSetStr())
   236  		require.Equal(t, InRangeBinlogPos, posType)
   237  	}
   238  }
   239  
   240  func TestErrorCase(t *testing.T) {
   241  	t.Parallel()
   242  	flavor := "mysql"
   243  	relayDir := t.TempDir()
   244  	beforeTime := time.Now()
   245  	tcctx := tcontext.NewContext(context.Background(), log.L())
   246  	{
   247  		finder := NewLocalBinlogPosFinder(tcctx, true, flavor, relayDir+"not-exist")
   248  		_, _, err := finder.FindByTimestamp(beforeTime.Add(-time.Minute).Unix())
   249  		require.Regexp(t, ".*no such file or directory.*", err.Error())
   250  	}
   251  	{
   252  		finder := NewLocalBinlogPosFinder(tcctx, true, flavor, t.TempDir())
   253  		_, _, err := finder.FindByTimestamp(beforeTime.Add(-time.Minute).Unix())
   254  		require.Regexp(t, ".*cannot find binlog files.*", err.Error())
   255  	}
   256  	{
   257  		file, err := os.Create(path.Join(relayDir, "mysql-bin.000001"))
   258  		require.Nil(t, err)
   259  		file.Close()
   260  		finder := NewLocalBinlogPosFinder(tcctx, true, flavor, relayDir)
   261  		_, _, err = finder.FindByTimestamp(beforeTime.Add(-time.Minute).Unix())
   262  		require.Equal(t, "EOF", err.Error())
   263  	}
   264  }
   265  
   266  func TestMySQL57GTID(t *testing.T) {
   267  	t.Parallel()
   268  	flavor := "mysql"
   269  	relayDir := t.TempDir()
   270  	beforeTime := time.Now()
   271  	latestGTIDStr := "ffffffff-ffff-ffff-ffff-ffffffffffff:1"
   272  
   273  	generator, _ := event.NewGeneratorV2(flavor, "5.7.0", latestGTIDStr, true)
   274  
   275  	file1Events, data := genBinlogFile(generator, beforeTime, "mysql-bin.000002")
   276  	require.Equal(t, 15, len(file1Events))
   277  	_ = os.WriteFile(path.Join(relayDir, "mysql-bin.000001"), data, 0o644)
   278  	file2Events, data := genBinlogFile(generator, beforeTime.Add(5*time.Second), "mysql-bin.000003")
   279  	require.Equal(t, 15, len(file2Events))
   280  	_ = os.WriteFile(path.Join(relayDir, "mysql-bin.000002"), data, 0o644)
   281  	file3Events, data := genBinlogFile(generator, beforeTime.Add(10*time.Second), "mysql-bin.000004")
   282  	require.Equal(t, 15, len(file3Events))
   283  	_ = os.WriteFile(path.Join(relayDir, "mysql-bin.000003"), data, 0o644)
   284  
   285  	tcctx := tcontext.NewContext(context.Background(), log.L())
   286  
   287  	{
   288  		finder := NewLocalBinlogPosFinder(tcctx, true, flavor, relayDir)
   289  		location, posType, err := finder.FindByTimestamp(beforeTime.Add(-time.Minute).Unix())
   290  		require.Nil(t, err)
   291  		require.Equal(t, mysql.Position{Name: "mysql-bin.000001", Pos: 4}, location.Position)
   292  		require.Equal(t, "ffffffff-ffff-ffff-ffff-ffffffffffff:1", location.GTIDSetStr())
   293  		require.Equal(t, BelowLowerBoundBinlogPos, posType)
   294  	}
   295  	{
   296  		gtids := []string{
   297  			"ffffffff-ffff-ffff-ffff-ffffffffffff:1",
   298  			"ffffffff-ffff-ffff-ffff-ffffffffffff:1-2",
   299  			"ffffffff-ffff-ffff-ffff-ffffffffffff:1-3",
   300  		}
   301  		var targetEventStart uint32
   302  		var targetEvent *replication.BinlogEvent
   303  		cnt := 0
   304  		for _, ev := range file1Events {
   305  			if ev.Header.EventType == replication.GTID_EVENT {
   306  				targetEvent = ev
   307  
   308  				finder := NewLocalBinlogPosFinder(tcctx, true, flavor, relayDir)
   309  				location, posType, err := finder.FindByTimestamp(int64(targetEvent.Header.Timestamp))
   310  				require.Nil(t, err)
   311  				require.Equal(t, mysql.Position{Name: "mysql-bin.000001", Pos: targetEventStart}, location.Position)
   312  				require.Equal(t, gtids[cnt], location.GTIDSetStr())
   313  				require.Equal(t, InRangeBinlogPos, posType)
   314  
   315  				cnt++
   316  			}
   317  			targetEventStart = ev.Header.LogPos
   318  		}
   319  	}
   320  	{
   321  		targetEventStart := file2Events[len(file2Events)-1].Header.LogPos
   322  		finder := NewLocalBinlogPosFinder(tcctx, true, flavor, relayDir)
   323  		location, posType, err := finder.FindByTimestamp(int64(file3Events[0].Header.Timestamp))
   324  		require.Nil(t, err)
   325  		require.Equal(t, mysql.Position{Name: "mysql-bin.000002", Pos: targetEventStart}, location.Position)
   326  		require.Equal(t, "ffffffff-ffff-ffff-ffff-ffffffffffff:1-7", location.GTIDSetStr())
   327  		require.Equal(t, InRangeBinlogPos, posType)
   328  	}
   329  	{
   330  		var targetEventStart uint32
   331  		var targetEvent *replication.BinlogEvent
   332  		cnt := 0
   333  		for _, ev := range file3Events {
   334  			if ev.Header.EventType == replication.GTID_EVENT {
   335  				targetEvent = ev
   336  				// third GTID event
   337  				cnt++
   338  				if cnt == 3 {
   339  					break
   340  				}
   341  			}
   342  			targetEventStart = ev.Header.LogPos
   343  		}
   344  		finder := NewLocalBinlogPosFinder(tcctx, true, flavor, relayDir)
   345  		location, posType, err := finder.FindByTimestamp(int64(targetEvent.Header.Timestamp))
   346  		require.Nil(t, err)
   347  		require.Equal(t, mysql.Position{Name: "mysql-bin.000003", Pos: targetEventStart}, location.Position)
   348  		require.Equal(t, "ffffffff-ffff-ffff-ffff-ffffffffffff:1-9", location.GTIDSetStr())
   349  		require.Equal(t, InRangeBinlogPos, posType)
   350  	}
   351  	{
   352  		finder := NewLocalBinlogPosFinder(tcctx, true, flavor, relayDir)
   353  		location, posType, err := finder.FindByTimestamp(beforeTime.Add(+time.Minute).Unix())
   354  
   355  		require.Nil(t, err)
   356  		require.Nil(t, location)
   357  		require.Equal(t, AboveUpperBoundBinlogPos, posType)
   358  	}
   359  }
   360  
   361  func TestMariadbGTID(t *testing.T) {
   362  	t.Parallel()
   363  	flavor := "mariadb"
   364  	relayDir := t.TempDir()
   365  	beforeTime := time.Now()
   366  	latestGTIDStr := "1-1-1"
   367  
   368  	generator, _ := event.NewGeneratorV2(flavor, "10.0.2", latestGTIDStr, true)
   369  
   370  	file1Events, data := genBinlogFile(generator, beforeTime, "mysql-bin.000002")
   371  	require.Equal(t, 15, len(file1Events))
   372  	_ = os.WriteFile(path.Join(relayDir, "mysql-bin.000001"), data, 0o644)
   373  	file2Events, data := genBinlogFile(generator, beforeTime.Add(5*time.Second), "mysql-bin.000003")
   374  	require.Equal(t, 15, len(file2Events))
   375  	_ = os.WriteFile(path.Join(relayDir, "mysql-bin.000002"), data, 0o644)
   376  	file3Events, data := genBinlogFile(generator, beforeTime.Add(10*time.Second), "mysql-bin.000004")
   377  	require.Equal(t, 15, len(file3Events))
   378  	_ = os.WriteFile(path.Join(relayDir, "mysql-bin.000003"), data, 0o644)
   379  
   380  	tcctx := tcontext.NewContext(context.Background(), log.L())
   381  
   382  	{
   383  		finder := NewLocalBinlogPosFinder(tcctx, true, flavor, relayDir)
   384  		location, posType, err := finder.FindByTimestamp(beforeTime.Add(-time.Minute).Unix())
   385  		require.Nil(t, err)
   386  		require.Equal(t, mysql.Position{Name: "mysql-bin.000001", Pos: 4}, location.Position)
   387  		require.Equal(t, "1-1-1", location.GTIDSetStr())
   388  		require.Equal(t, BelowLowerBoundBinlogPos, posType)
   389  	}
   390  	{
   391  		var targetEventStart uint32
   392  		var targetEvent *replication.BinlogEvent
   393  		cnt := 0
   394  		for _, ev := range file1Events {
   395  			if ev.Header.EventType == replication.MARIADB_GTID_EVENT {
   396  				targetEvent = ev
   397  				// second GTID event
   398  				cnt++
   399  				if cnt == 2 {
   400  					break
   401  				}
   402  			}
   403  			targetEventStart = ev.Header.LogPos
   404  		}
   405  		finder := NewLocalBinlogPosFinder(tcctx, true, flavor, relayDir)
   406  		location, posType, err := finder.FindByTimestamp(int64(targetEvent.Header.Timestamp))
   407  		require.Nil(t, err)
   408  		require.Equal(t, mysql.Position{Name: "mysql-bin.000001", Pos: targetEventStart}, location.Position)
   409  		require.Equal(t, "1-1-2", location.GTIDSetStr())
   410  		require.Equal(t, InRangeBinlogPos, posType)
   411  	}
   412  	{
   413  		targetEventStart := file2Events[len(file2Events)-1].Header.LogPos
   414  		finder := NewLocalBinlogPosFinder(tcctx, true, flavor, relayDir)
   415  		location, posType, err := finder.FindByTimestamp(int64(file3Events[0].Header.Timestamp))
   416  		require.Nil(t, err)
   417  		require.Equal(t, mysql.Position{Name: "mysql-bin.000002", Pos: targetEventStart}, location.Position)
   418  		require.Equal(t, "1-1-7", location.GTIDSetStr())
   419  		require.Equal(t, InRangeBinlogPos, posType)
   420  	}
   421  	{
   422  		var targetEventStart uint32
   423  		var targetEvent *replication.BinlogEvent
   424  		cnt := 0
   425  		for _, ev := range file3Events {
   426  			if ev.Header.EventType == replication.MARIADB_GTID_EVENT {
   427  				targetEvent = ev
   428  				// second GTID event
   429  				cnt++
   430  				if cnt == 2 {
   431  					break
   432  				}
   433  			}
   434  			targetEventStart = ev.Header.LogPos
   435  		}
   436  		finder := NewLocalBinlogPosFinder(tcctx, true, flavor, relayDir)
   437  		location, posType, err := finder.FindByTimestamp(int64(targetEvent.Header.Timestamp))
   438  		require.Nil(t, err)
   439  		require.Equal(t, mysql.Position{Name: "mysql-bin.000003", Pos: targetEventStart}, location.Position)
   440  		require.Equal(t, "1-1-8", location.GTIDSetStr())
   441  		require.Equal(t, InRangeBinlogPos, posType)
   442  	}
   443  	{
   444  		finder := NewLocalBinlogPosFinder(tcctx, true, flavor, relayDir)
   445  		location, posType, err := finder.FindByTimestamp(beforeTime.Add(+time.Minute).Unix())
   446  		require.Nil(t, err)
   447  		require.Nil(t, location)
   448  		require.Equal(t, AboveUpperBoundBinlogPos, posType)
   449  	}
   450  }