github.com/pingcap/tiflow@v0.0.0-20240520035814-5bf52d54e205/dm/pkg/binlog/event/common.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  	"bytes"
    18  	"time"
    19  
    20  	gmysql "github.com/go-mysql-org/go-mysql/mysql"
    21  	"github.com/go-mysql-org/go-mysql/replication"
    22  	"github.com/pingcap/tiflow/dm/pkg/gtid"
    23  	"github.com/pingcap/tiflow/dm/pkg/terror"
    24  )
    25  
    26  // DDLDMLResult represents a binlog event result for generated DDL/DML.
    27  type DDLDMLResult struct {
    28  	Events     []*replication.BinlogEvent
    29  	Data       []byte // data contain all events
    30  	LatestPos  uint32
    31  	LatestGTID gmysql.GTIDSet
    32  }
    33  
    34  // GenCommonFileHeader generates a common binlog file header.
    35  // for MySQL:
    36  //  1. BinLogFileHeader, [ fe `bin` ]
    37  //  2. FormatDescriptionEvent
    38  //  3. PreviousGTIDsEvent, depends on genGTID
    39  //
    40  // for MariaDB:
    41  //  1. BinLogFileHeader, [ fe `bin` ]
    42  //  2. FormatDescriptionEvent
    43  //  3. MariadbGTIDListEvent, depends on genGTID
    44  func GenCommonFileHeader(flavor string, serverID uint32, gSet gmysql.GTIDSet, genGTID bool, ts int64) ([]*replication.BinlogEvent, []byte, error) {
    45  	if ts == 0 {
    46  		ts = time.Now().Unix()
    47  	}
    48  	var (
    49  		header = &replication.EventHeader{
    50  			Timestamp: uint32(ts),
    51  			ServerID:  serverID,
    52  			Flags:     defaultHeaderFlags,
    53  		}
    54  		latestPos   uint32
    55  		prevGTIDsEv *replication.BinlogEvent
    56  		buf         bytes.Buffer
    57  		events      []*replication.BinlogEvent
    58  	)
    59  
    60  	_, err := buf.Write(replication.BinLogFileHeader)
    61  	if err != nil {
    62  		return nil, nil, terror.ErrBinlogWriteDataToBuffer.AnnotateDelegate(err, "write binlog file header % X", replication.BinLogFileHeader)
    63  	}
    64  	latestPos += uint32(len(replication.BinLogFileHeader))
    65  
    66  	formatDescEv, err := GenFormatDescriptionEvent(header, latestPos)
    67  	if err != nil {
    68  		return nil, nil, terror.Annotate(err, "generate FormatDescriptionEvent")
    69  	}
    70  	_, err = buf.Write(formatDescEv.RawData)
    71  	if err != nil {
    72  		return nil, nil, terror.ErrBinlogWriteDataToBuffer.AnnotateDelegate(err, "write FormatDescriptionEvent % X", formatDescEv.RawData)
    73  	}
    74  	latestPos += uint32(len(formatDescEv.RawData)) // update latestPos
    75  	events = append(events, formatDescEv)
    76  
    77  	if genGTID {
    78  		switch flavor {
    79  		case gmysql.MySQLFlavor:
    80  			prevGTIDsEv, err = GenPreviousGTIDsEvent(header, latestPos, gSet)
    81  		case gmysql.MariaDBFlavor:
    82  			prevGTIDsEv, err = GenMariaDBGTIDListEvent(header, latestPos, gSet)
    83  		default:
    84  			return nil, nil, terror.ErrBinlogFlavorNotSupport.Generate(flavor)
    85  		}
    86  		if err != nil {
    87  			return nil, nil, terror.Annotate(err, "generate PreviousGTIDsEvent/MariadbGTIDListEvent")
    88  		}
    89  
    90  		_, err = buf.Write(prevGTIDsEv.RawData)
    91  		if err != nil {
    92  			return nil, nil, terror.ErrBinlogWriteDataToBuffer.AnnotateDelegate(err, "write PreviousGTIDsEvent/MariadbGTIDListEvent % X", prevGTIDsEv.RawData)
    93  		}
    94  		events = append(events, prevGTIDsEv)
    95  	}
    96  
    97  	return events, buf.Bytes(), nil
    98  }
    99  
   100  // GenCommonGTIDEvent generates a common GTID event.
   101  func GenCommonGTIDEvent(flavor string, serverID uint32, latestPos uint32, gSet gmysql.GTIDSet, anonymous bool, ts int64) (*replication.BinlogEvent, error) {
   102  	singleGTID, err := verifySingleGTID(flavor, gSet)
   103  	if err != nil {
   104  		return nil, terror.Annotate(err, "verify single GTID in set")
   105  	}
   106  
   107  	if ts == 0 {
   108  		ts = time.Now().Unix()
   109  	}
   110  	var (
   111  		header = &replication.EventHeader{
   112  			Timestamp: uint32(ts),
   113  			ServerID:  serverID,
   114  			Flags:     defaultHeaderFlags,
   115  		}
   116  		gtidEv *replication.BinlogEvent
   117  	)
   118  
   119  	switch flavor {
   120  	case gmysql.MySQLFlavor:
   121  		uuidSet := singleGTID.(*gmysql.UUIDSet)
   122  		interval := uuidSet.Intervals[0]
   123  		if anonymous {
   124  			gtidEv, err = GenAnonymousGTIDEvent(header, latestPos, defaultGTIDFlags, defaultLastCommitted, defaultSequenceNumber)
   125  		} else {
   126  			gtidEv, err = GenGTIDEvent(header, latestPos, defaultGTIDFlags, uuidSet.SID.String(), interval.Start, defaultLastCommitted, defaultSequenceNumber)
   127  		}
   128  	case gmysql.MariaDBFlavor:
   129  		mariaGTID := singleGTID.(*gmysql.MariadbGTID)
   130  		if mariaGTID.ServerID != header.ServerID {
   131  			return nil, terror.ErrBinlogMariaDBServerIDMismatch.Generate(mariaGTID.ServerID, header.ServerID)
   132  		}
   133  		gtidEv, err = GenMariaDBGTIDEvent(header, latestPos, mariaGTID.SequenceNumber, mariaGTID.DomainID)
   134  		if err != nil {
   135  			return gtidEv, err
   136  		}
   137  		// in go-mysql, set ServerID in parseEvent. we try to set it directly
   138  		gtidEvBody := gtidEv.Event.(*replication.MariadbGTIDEvent)
   139  		gtidEvBody.GTID.ServerID = header.ServerID
   140  	default:
   141  		err = terror.ErrBinlogGTIDSetNotValid.Generate(gSet, flavor)
   142  	}
   143  	return gtidEv, err
   144  }
   145  
   146  // GTIDIncrease returns a new GTID with GNO/SequenceNumber +1.
   147  func GTIDIncrease(flavor string, gSet gmysql.GTIDSet) (gmysql.GTIDSet, error) {
   148  	singleGTID, err := verifySingleGTID(flavor, gSet)
   149  	if err != nil {
   150  		return nil, terror.Annotate(err, "verify single GTID in set")
   151  	}
   152  	clone := gSet.Clone()
   153  
   154  	switch flavor {
   155  	case gmysql.MySQLFlavor:
   156  		uuidSet := singleGTID.(*gmysql.UUIDSet)
   157  		uuidSet.Intervals[0].Start++
   158  		uuidSet.Intervals[0].Stop++
   159  		gtidSet := new(gmysql.MysqlGTIDSet)
   160  		gtidSet.Sets = map[string]*gmysql.UUIDSet{uuidSet.SID.String(): uuidSet}
   161  		clone = gtidSet
   162  	case gmysql.MariaDBFlavor:
   163  		mariaGTID := singleGTID.(*gmysql.MariadbGTID)
   164  		mariaGTID.SequenceNumber++
   165  		gtidSet := new(gmysql.MariadbGTIDSet)
   166  		gtidSet.Sets = map[uint32]map[uint32]*gmysql.MariadbGTID{
   167  			mariaGTID.DomainID: {
   168  				mariaGTID.ServerID: mariaGTID,
   169  			},
   170  		}
   171  		clone = gtidSet
   172  	default:
   173  		err = terror.ErrBinlogGTIDSetNotValid.Generate(gSet, flavor)
   174  	}
   175  	return clone, err
   176  }
   177  
   178  // verifySingleGTID verifies gSet whether only containing a single valid GTID.
   179  func verifySingleGTID(flavor string, gSet gmysql.GTIDSet) (interface{}, error) {
   180  	if gtid.CheckGTIDSetEmpty(gSet) {
   181  		return nil, terror.ErrBinlogEmptyGTID.Generate()
   182  	}
   183  
   184  	switch flavor {
   185  	case gmysql.MySQLFlavor:
   186  		mysqlGTIDs, ok := gSet.(*gmysql.MysqlGTIDSet)
   187  		if !ok {
   188  			return nil, terror.ErrBinlogGTIDMySQLNotValid.Generate(gSet)
   189  		}
   190  		if len(mysqlGTIDs.Sets) != 1 {
   191  			return nil, terror.ErrBinlogOnlyOneGTIDSupport.Generate(len(mysqlGTIDs.Sets), gSet)
   192  		}
   193  		var uuidSet *gmysql.UUIDSet
   194  		for _, uuidSet = range mysqlGTIDs.Sets {
   195  		}
   196  		intervals := uuidSet.Intervals
   197  		if intervals.Len() != 1 {
   198  			return nil, terror.ErrBinlogOnlyOneIntervalInUUID.Generate(intervals.Len(), gSet)
   199  		}
   200  		interval := intervals[0]
   201  		if interval.Stop != interval.Start+1 {
   202  			return nil, terror.ErrBinlogIntervalValueNotValid.Generate(interval, gSet)
   203  		}
   204  		return uuidSet, nil
   205  	case gmysql.MariaDBFlavor:
   206  		mariaGTIDs, ok := gSet.(*gmysql.MariadbGTIDSet)
   207  		if !ok {
   208  			return nil, terror.ErrBinlogGTIDMariaDBNotValid.Generate(gSet)
   209  		}
   210  		gtidCount := 0
   211  		var mariaGTID *gmysql.MariadbGTID
   212  		for _, set := range mariaGTIDs.Sets {
   213  			gtidCount += len(set)
   214  			for _, mariaGTID = range set {
   215  			}
   216  		}
   217  		if gtidCount != 1 {
   218  			return nil, terror.ErrBinlogOnlyOneGTIDSupport.Generate(gtidCount, gSet)
   219  		}
   220  		return mariaGTID, nil
   221  	default:
   222  		return nil, terror.ErrBinlogGTIDSetNotValid.Generate(gSet, flavor)
   223  	}
   224  }