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 }