vitess.io/vitess@v0.16.2/go/mysql/binlog_event_make.go (about)

     1  /*
     2  Copyright 2019 The Vitess Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package mysql
    18  
    19  import (
    20  	"encoding/binary"
    21  	"hash/crc32"
    22  )
    23  
    24  const (
    25  	FlagLogEventArtificial = 0x20
    26  )
    27  
    28  // This file contains utility methods to create binlog replication
    29  // packets. They are mostly used for testing.
    30  
    31  // NewMySQL56BinlogFormat returns a typical BinlogFormat for MySQL 5.6.
    32  func NewMySQL56BinlogFormat() BinlogFormat {
    33  	return BinlogFormat{
    34  		FormatVersion:     4,
    35  		ServerVersion:     "5.6.33-0ubuntu0.14.04.1-log",
    36  		HeaderLength:      19,
    37  		ChecksumAlgorithm: BinlogChecksumAlgCRC32, // most commonly used.
    38  		HeaderSizes: []byte{
    39  			56, 13, 0, 8, 0, 18, 0, 4, 4, 4,
    40  			4, 18, 0, 0, 92, 0, 4, 26, 8, 0,
    41  			0, 0, 8, 8, 8, 2, 0, 0, 0, 10,
    42  			10, 10, 25, 25, 0},
    43  	}
    44  }
    45  
    46  // NewMariaDBBinlogFormat returns a typical BinlogFormat for MariaDB 10.0.
    47  func NewMariaDBBinlogFormat() BinlogFormat {
    48  	return BinlogFormat{
    49  		FormatVersion:     4,
    50  		ServerVersion:     "10.0.13-MariaDB-1~precise-log",
    51  		HeaderLength:      19,
    52  		ChecksumAlgorithm: BinlogChecksumAlgOff,
    53  		// HeaderSizes is very long because the MariaDB specific events are indexed at 160+
    54  		HeaderSizes: []byte{
    55  			56, 13, 0, 8, 0, 18, 0, 4, 4, 4,
    56  			4, 18, 0, 0, 220, 0, 4, 26, 8, 0,
    57  			0, 0, 8, 8, 8, 2, 0, 0, 0, 10,
    58  			10, 10, 0, 0, 0, 0, 0, 0, 0, 0,
    59  			0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
    60  			0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
    61  			0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
    62  			0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
    63  			0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
    64  			0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
    65  			0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
    66  			0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
    67  			0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
    68  			0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
    69  			0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
    70  			0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
    71  			4, 19, 4},
    72  	}
    73  }
    74  
    75  // FakeBinlogStream is used to generate consistent BinlogEvent packets
    76  // for a stream. It makes sure the ServerID and log positions are
    77  // reasonable.
    78  type FakeBinlogStream struct {
    79  	// ServerID is the server ID of the originating mysql-server.
    80  	ServerID uint32
    81  
    82  	// LogPosition is an incrementing log position.
    83  	LogPosition uint32
    84  
    85  	// Timestamp is a uint32 of when the events occur. It is not changed.
    86  	Timestamp uint32
    87  }
    88  
    89  // NewFakeBinlogStream returns a simple FakeBinlogStream.
    90  func NewFakeBinlogStream() *FakeBinlogStream {
    91  	return &FakeBinlogStream{
    92  		ServerID:    1,
    93  		LogPosition: 4,
    94  		Timestamp:   1407805592,
    95  	}
    96  }
    97  
    98  // Packetize adds the binlog event header to a packet, and optionally
    99  // the checksum.
   100  func (s *FakeBinlogStream) Packetize(f BinlogFormat, typ byte, flags uint16, data []byte) []byte {
   101  	length := int(f.HeaderLength) + len(data)
   102  	if typ == eFormatDescriptionEvent || f.ChecksumAlgorithm == BinlogChecksumAlgCRC32 {
   103  		// Just add 4 zeroes to the end.
   104  		length += 4
   105  	}
   106  
   107  	result := make([]byte, length)
   108  	switch typ {
   109  	case eRotateEvent, eHeartbeatEvent:
   110  		// timestamp remains zero
   111  	default:
   112  		binary.LittleEndian.PutUint32(result[0:4], s.Timestamp)
   113  	}
   114  	result[4] = typ
   115  	binary.LittleEndian.PutUint32(result[5:9], s.ServerID)
   116  	binary.LittleEndian.PutUint32(result[9:13], uint32(length))
   117  	if f.HeaderLength >= 19 {
   118  		binary.LittleEndian.PutUint32(result[13:17], s.LogPosition)
   119  		binary.LittleEndian.PutUint16(result[17:19], flags)
   120  	}
   121  	copy(result[f.HeaderLength:], data)
   122  
   123  	switch f.ChecksumAlgorithm {
   124  	case BinlogChecksumAlgCRC32:
   125  		checksum := crc32.ChecksumIEEE(result[0 : length-4])
   126  		binary.LittleEndian.PutUint32(result[length-4:], checksum)
   127  	}
   128  
   129  	return result
   130  }
   131  
   132  // NewInvalidEvent returns an invalid event (its size is <19).
   133  func NewInvalidEvent() BinlogEvent {
   134  	return NewMysql56BinlogEvent([]byte{0})
   135  }
   136  
   137  // NewFormatDescriptionEvent creates a new FormatDescriptionEvent
   138  // based on the provided BinlogFormat. It uses a mysql56BinlogEvent
   139  // but could use a MariaDB one.
   140  func NewFormatDescriptionEvent(f BinlogFormat, s *FakeBinlogStream) BinlogEvent {
   141  	length := 2 + // binlog-version
   142  		50 + // server version
   143  		4 + // create timestamp
   144  		1 + // event header length
   145  		len(f.HeaderSizes) + // event type header lengths
   146  		1 // (undocumented) checksum algorithm
   147  	data := make([]byte, length)
   148  	binary.LittleEndian.PutUint16(data[0:2], f.FormatVersion)
   149  	copy(data[2:52], f.ServerVersion)
   150  	binary.LittleEndian.PutUint32(data[52:56], s.Timestamp)
   151  	data[56] = f.HeaderLength
   152  	copy(data[57:], f.HeaderSizes)
   153  	data[57+len(f.HeaderSizes)] = f.ChecksumAlgorithm
   154  
   155  	ev := s.Packetize(f, eFormatDescriptionEvent, 0, data)
   156  	return NewMysql56BinlogEvent(ev)
   157  }
   158  
   159  // NewInvalidFormatDescriptionEvent returns an invalid FormatDescriptionEvent.
   160  // The binlog version is set to 3. It IsValid() though.
   161  func NewInvalidFormatDescriptionEvent(f BinlogFormat, s *FakeBinlogStream) BinlogEvent {
   162  	length := 75
   163  	data := make([]byte, length)
   164  	data[0] = 3
   165  
   166  	ev := s.Packetize(f, eFormatDescriptionEvent, 0, data)
   167  	return NewMysql56BinlogEvent(ev)
   168  }
   169  
   170  // NewRotateEvent returns a RotateEvent.
   171  // The timestamp of such an event should be zero, so we patch it in.
   172  func NewRotateEvent(f BinlogFormat, s *FakeBinlogStream, position uint64, filename string) BinlogEvent {
   173  	length := 8 + // position
   174  		len(filename)
   175  	data := make([]byte, length)
   176  	binary.LittleEndian.PutUint64(data[0:8], position)
   177  	copy(data[8:], filename)
   178  
   179  	ev := s.Packetize(f, eRotateEvent, 0, data)
   180  	return NewMysql56BinlogEvent(ev)
   181  }
   182  
   183  func NewFakeRotateEvent(f BinlogFormat, s *FakeBinlogStream, filename string) BinlogEvent {
   184  	length := 8 + // position
   185  		len(filename)
   186  	data := make([]byte, length)
   187  	binary.LittleEndian.PutUint64(data[0:8], 4)
   188  	copy(data[8:], filename)
   189  
   190  	ev := s.Packetize(f, eRotateEvent, FlagLogEventArtificial, data)
   191  	return NewMysql56BinlogEvent(ev)
   192  }
   193  
   194  // NewHeartbeatEvent returns a HeartbeatEvent.
   195  // see https://dev.mysql.com/doc/internals/en/heartbeat-event.html
   196  func NewHeartbeatEvent(f BinlogFormat, s *FakeBinlogStream) BinlogEvent {
   197  	ev := s.Packetize(f, eHeartbeatEvent, 0, []byte{})
   198  	return NewMysql56BinlogEvent(ev)
   199  }
   200  
   201  // NewHeartbeatEvent returns a HeartbeatEvent.
   202  // see https://dev.mysql.com/doc/internals/en/heartbeat-event.html
   203  func NewHeartbeatEventWithLogFile(f BinlogFormat, s *FakeBinlogStream, filename string) BinlogEvent {
   204  	length := len(filename)
   205  	data := make([]byte, length)
   206  	copy(data, filename)
   207  
   208  	ev := s.Packetize(f, eHeartbeatEvent, 0, data)
   209  	return NewMysql56BinlogEvent(ev)
   210  }
   211  
   212  // NewQueryEvent makes up a QueryEvent based on the Query structure.
   213  func NewQueryEvent(f BinlogFormat, s *FakeBinlogStream, q Query) BinlogEvent {
   214  	statusVarLength := 0
   215  	if q.Charset != nil {
   216  		statusVarLength += 1 + 2 + 2 + 2
   217  	}
   218  	length := 4 + // proxy id
   219  		4 + // execution time
   220  		1 + // schema length
   221  		2 + // error code
   222  		2 + // status vars length
   223  		statusVarLength +
   224  		len(q.Database) + // schema
   225  		1 + // [00]
   226  		len(q.SQL) // query
   227  	data := make([]byte, length)
   228  
   229  	pos := 8
   230  	data[pos] = byte(len(q.Database))
   231  	pos += 1 + 2
   232  	data[pos] = byte(statusVarLength)
   233  	data[pos+1] = byte(statusVarLength >> 8)
   234  	pos += 2
   235  	if q.Charset != nil {
   236  		data[pos] = QCharsetCode
   237  		data[pos+1] = byte(q.Charset.Client)
   238  		data[pos+2] = byte(q.Charset.Client >> 8)
   239  		data[pos+3] = byte(q.Charset.Conn)
   240  		data[pos+4] = byte(q.Charset.Conn >> 8)
   241  		data[pos+5] = byte(q.Charset.Server)
   242  		data[pos+6] = byte(q.Charset.Server >> 8)
   243  		pos += 7
   244  	}
   245  	pos += copy(data[pos:pos+len(q.Database)], q.Database)
   246  	data[pos] = 0
   247  	pos++
   248  	copy(data[pos:], q.SQL)
   249  
   250  	ev := s.Packetize(f, eQueryEvent, 0, data)
   251  	return NewMysql56BinlogEvent(ev)
   252  }
   253  
   254  // NewInvalidQueryEvent returns an invalid QueryEvent. IsValid is however true.
   255  // sqlPos is out of bounds.
   256  func NewInvalidQueryEvent(f BinlogFormat, s *FakeBinlogStream) BinlogEvent {
   257  	length := 100
   258  	data := make([]byte, length)
   259  	data[4+4] = 200 // > 100
   260  
   261  	ev := s.Packetize(f, eQueryEvent, 0, data)
   262  	return NewMysql56BinlogEvent(ev)
   263  }
   264  
   265  // NewXIDEvent returns a XID event. We do not use the data, so keep it 0.
   266  func NewXIDEvent(f BinlogFormat, s *FakeBinlogStream) BinlogEvent {
   267  	length := 8
   268  	data := make([]byte, length)
   269  
   270  	ev := s.Packetize(f, eXIDEvent, 0, data)
   271  	return NewMysql56BinlogEvent(ev)
   272  }
   273  
   274  // NewIntVarEvent returns an IntVar event.
   275  func NewIntVarEvent(f BinlogFormat, s *FakeBinlogStream, typ byte, value uint64) BinlogEvent {
   276  	length := 9
   277  	data := make([]byte, length)
   278  
   279  	data[0] = typ
   280  	data[1] = byte(value)
   281  	data[2] = byte(value >> 8)
   282  	data[3] = byte(value >> 16)
   283  	data[4] = byte(value >> 24)
   284  	data[5] = byte(value >> 32)
   285  	data[6] = byte(value >> 40)
   286  	data[7] = byte(value >> 48)
   287  	data[8] = byte(value >> 56)
   288  
   289  	ev := s.Packetize(f, eIntVarEvent, 0, data)
   290  	return NewMysql56BinlogEvent(ev)
   291  }
   292  
   293  // NewMariaDBGTIDEvent returns a MariaDB specific GTID event.
   294  // It ignores the Server in the gtid, instead uses the FakeBinlogStream.ServerID.
   295  func NewMariaDBGTIDEvent(f BinlogFormat, s *FakeBinlogStream, gtid MariadbGTID, hasBegin bool) BinlogEvent {
   296  	length := 8 + // sequence
   297  		4 + // domain
   298  		1 // flags2
   299  	data := make([]byte, length)
   300  
   301  	data[0] = byte(gtid.Sequence)
   302  	data[1] = byte(gtid.Sequence >> 8)
   303  	data[2] = byte(gtid.Sequence >> 16)
   304  	data[3] = byte(gtid.Sequence >> 24)
   305  	data[4] = byte(gtid.Sequence >> 32)
   306  	data[5] = byte(gtid.Sequence >> 40)
   307  	data[6] = byte(gtid.Sequence >> 48)
   308  	data[7] = byte(gtid.Sequence >> 56)
   309  	data[8] = byte(gtid.Domain)
   310  	data[9] = byte(gtid.Domain >> 8)
   311  	data[10] = byte(gtid.Domain >> 16)
   312  	data[11] = byte(gtid.Domain >> 24)
   313  
   314  	const FLStandalone = 1
   315  	var flags2 byte
   316  	if !hasBegin {
   317  		flags2 |= FLStandalone
   318  	}
   319  	data[12] = flags2
   320  
   321  	ev := s.Packetize(f, eMariaGTIDEvent, 0, data)
   322  	return NewMariadbBinlogEvent(ev)
   323  }
   324  
   325  // NewTableMapEvent returns a TableMap event.
   326  // Only works with post_header_length=8.
   327  func NewTableMapEvent(f BinlogFormat, s *FakeBinlogStream, tableID uint64, tm *TableMap) BinlogEvent {
   328  	if f.HeaderSize(eTableMapEvent) != 8 {
   329  		panic("Not implemented, post_header_length!=8")
   330  	}
   331  
   332  	metadataLength := metadataTotalLength(tm.Types)
   333  
   334  	length := 6 + // table_id
   335  		2 + // flags
   336  		1 + // schema name length
   337  		len(tm.Database) +
   338  		1 + // [00]
   339  		1 + // table name length
   340  		len(tm.Name) +
   341  		1 + // [00]
   342  		lenEncIntSize(uint64(len(tm.Types))) + // column-count len enc
   343  		len(tm.Types) +
   344  		lenEncIntSize(uint64(metadataLength)) + // lenenc-str column-meta-def
   345  		metadataLength +
   346  		len(tm.CanBeNull.data)
   347  	data := make([]byte, length)
   348  
   349  	data[0] = byte(tableID)
   350  	data[1] = byte(tableID >> 8)
   351  	data[2] = byte(tableID >> 16)
   352  	data[3] = byte(tableID >> 24)
   353  	data[4] = byte(tableID >> 32)
   354  	data[5] = byte(tableID >> 40)
   355  	data[6] = byte(tm.Flags)
   356  	data[7] = byte(tm.Flags >> 8)
   357  	data[8] = byte(len(tm.Database))
   358  	pos := 6 + 2 + 1 + copy(data[9:], tm.Database)
   359  	data[pos] = 0
   360  	pos++
   361  	data[pos] = byte(len(tm.Name))
   362  	pos += 1 + copy(data[pos+1:], tm.Name)
   363  	data[pos] = 0
   364  	pos++
   365  
   366  	pos = writeLenEncInt(data, pos, uint64(len(tm.Types)))
   367  	pos += copy(data[pos:], tm.Types)
   368  
   369  	pos = writeLenEncInt(data, pos, uint64(metadataLength))
   370  	for c, typ := range tm.Types {
   371  		pos = metadataWrite(data, pos, typ, tm.Metadata[c])
   372  	}
   373  
   374  	pos += copy(data[pos:], tm.CanBeNull.data)
   375  	if pos != len(data) {
   376  		panic("bad encoding")
   377  	}
   378  
   379  	ev := s.Packetize(f, eTableMapEvent, 0, data)
   380  	return NewMariadbBinlogEvent(ev)
   381  }
   382  
   383  // NewWriteRowsEvent returns a WriteRows event. Uses v2.
   384  func NewWriteRowsEvent(f BinlogFormat, s *FakeBinlogStream, tableID uint64, rows Rows) BinlogEvent {
   385  	return newRowsEvent(f, s, eWriteRowsEventV2, tableID, rows)
   386  }
   387  
   388  // NewUpdateRowsEvent returns an UpdateRows event. Uses v2.
   389  func NewUpdateRowsEvent(f BinlogFormat, s *FakeBinlogStream, tableID uint64, rows Rows) BinlogEvent {
   390  	return newRowsEvent(f, s, eUpdateRowsEventV2, tableID, rows)
   391  }
   392  
   393  // NewDeleteRowsEvent returns an DeleteRows event. Uses v2.
   394  func NewDeleteRowsEvent(f BinlogFormat, s *FakeBinlogStream, tableID uint64, rows Rows) BinlogEvent {
   395  	return newRowsEvent(f, s, eDeleteRowsEventV2, tableID, rows)
   396  }
   397  
   398  // newRowsEvent can create an event of type:
   399  // eWriteRowsEventV1, eWriteRowsEventV2,
   400  // eUpdateRowsEventV1, eUpdateRowsEventV2,
   401  // eDeleteRowsEventV1, eDeleteRowsEventV2.
   402  func newRowsEvent(f BinlogFormat, s *FakeBinlogStream, typ byte, tableID uint64, rows Rows) BinlogEvent {
   403  	if f.HeaderSize(typ) == 6 {
   404  		panic("Not implemented, post_header_length==6")
   405  	}
   406  
   407  	hasIdentify := typ == eUpdateRowsEventV1 || typ == eUpdateRowsEventV2 ||
   408  		typ == eDeleteRowsEventV1 || typ == eDeleteRowsEventV2
   409  	hasData := typ == eWriteRowsEventV1 || typ == eWriteRowsEventV2 ||
   410  		typ == eUpdateRowsEventV1 || typ == eUpdateRowsEventV2
   411  
   412  	rowLen := rows.DataColumns.Count()
   413  	if hasIdentify {
   414  		rowLen = rows.IdentifyColumns.Count()
   415  	}
   416  
   417  	length := 6 + // table id
   418  		2 + // flags
   419  		2 + // extra data length, no extra data.
   420  		lenEncIntSize(uint64(rowLen)) + // num columns
   421  		len(rows.IdentifyColumns.data) + // only > 0 for Update & Delete
   422  		len(rows.DataColumns.data) // only > 0 for Write & Update
   423  	for _, row := range rows.Rows {
   424  		length += len(row.NullIdentifyColumns.data) +
   425  			len(row.NullColumns.data) +
   426  			len(row.Identify) +
   427  			len(row.Data)
   428  	}
   429  	data := make([]byte, length)
   430  
   431  	data[0] = byte(tableID)
   432  	data[1] = byte(tableID >> 8)
   433  	data[2] = byte(tableID >> 16)
   434  	data[3] = byte(tableID >> 24)
   435  	data[4] = byte(tableID >> 32)
   436  	data[5] = byte(tableID >> 40)
   437  	data[6] = byte(rows.Flags)
   438  	data[7] = byte(rows.Flags >> 8)
   439  	data[8] = 0x02
   440  	data[9] = 0x00
   441  
   442  	pos := writeLenEncInt(data, 10, uint64(rowLen))
   443  
   444  	if hasIdentify {
   445  		pos += copy(data[pos:], rows.IdentifyColumns.data)
   446  	}
   447  	if hasData {
   448  		pos += copy(data[pos:], rows.DataColumns.data)
   449  	}
   450  
   451  	for _, row := range rows.Rows {
   452  		if hasIdentify {
   453  			pos += copy(data[pos:], row.NullIdentifyColumns.data)
   454  			pos += copy(data[pos:], row.Identify)
   455  		}
   456  		if hasData {
   457  			pos += copy(data[pos:], row.NullColumns.data)
   458  			pos += copy(data[pos:], row.Data)
   459  		}
   460  	}
   461  
   462  	ev := s.Packetize(f, typ, 0, data)
   463  	return NewMysql56BinlogEvent(ev)
   464  }