github.com/pingcap/tiflow@v0.0.0-20240520035814-5bf52d54e205/dm/pkg/binlog/event/event.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 // binlog events generator for MySQL used to generate some binlog events for tests. 15 // Readability takes precedence over performance. 16 17 package event 18 19 import ( 20 "bytes" 21 "encoding/binary" 22 23 gmysql "github.com/go-mysql-org/go-mysql/mysql" 24 "github.com/go-mysql-org/go-mysql/replication" 25 "github.com/pingcap/tiflow/dm/pkg/gtid" 26 "github.com/pingcap/tiflow/dm/pkg/terror" 27 ) 28 29 // flags used in RowsEvent. 30 const ( 31 RowFlagsEndOfStatement uint16 = 0x0001 32 RowFlagsNoForeignKeyChecks uint16 = 0x0002 33 RowFlagsNoUniqueKeyChecks uint16 = 0x0004 34 RowFlagsRowHasAColumns uint16 = 0x0008 35 ) 36 37 const ( 38 // GTIDFlagsCommitYes represents a GTID flag with [commit=yes]. 39 // in `Row` binlog format, this will appear in GTID event before DDL query event. 40 GTIDFlagsCommitYes uint8 = 1 41 42 binlogVersion uint16 = 4 // only binlog-version 4 supported now 43 mysqlVersion = "5.7.22-log" 44 mysqlVersionLen = 50 // fix-length 45 eventHeaderLen = uint8(replication.EventHeaderSize) // always 19 46 crc32Len uint32 = 4 // CRC32-length 47 tableMapFlags uint16 = 1 // flags in TableMapEvent's post-header, not used yet 48 49 // MinUserVarEventLen represents the minimum event length for a USER_VAR_EVENT with checksum. 50 MinUserVarEventLen = uint32(eventHeaderLen+4+1+1) + crc32Len // 29 bytes 51 // MinQueryEventLen represents the minimum event length for a QueryEvent with checksum. 52 MinQueryEventLen = uint32(eventHeaderLen+4+4+1+2+2+1+1) + crc32Len // 38 bytes 53 ) 54 55 var ( 56 // A array indexed by `Binlog-Event-Type - 1` to extract the length of the event specific header. 57 // It is copied from a binlog file generated by MySQL 5.7.22-log. 58 // The doc at https://dev.mysql.com/doc/internals/en/format-description-event.html does not include all of them. 59 eventTypeHeaderLen = []byte{ 60 0x38, 0x0d, 0x00, 0x08, 0x00, 0x12, 0x00, 0x04, 0x04, 0x04, 0x04, 0x12, 0x00, 0x00, 0x5f, 0x00, 61 0x04, 0x1a, 0x08, 0x00, 0x00, 0x00, 0x08, 0x08, 0x08, 0x02, 0x00, 0x00, 0x00, 0x0a, 0x0a, 0x0a, 62 0x2a, 0x2a, 0x00, 0x12, 0x34, 0x00, 63 } 64 // user var name used in dummy USER_VAR_EVENT. 65 dummyUserVarName = []byte("!dummyvar") 66 // dummy (commented) query in a QueryEvent. 67 dummyQuery = []byte("# dummy query generated by DM, often used to fill a hole in a binlog file") 68 ) 69 70 // GenEventHeader generates a EventHeader's raw data according to a passed-in EventHeader struct. 71 // ref: https://dev.mysql.com/doc/internals/en/binlog-event-header.html 72 func GenEventHeader(header *replication.EventHeader) ([]byte, error) { 73 buf := new(bytes.Buffer) 74 75 // timestamp, 4 bytes 76 err := binary.Write(buf, binary.LittleEndian, header.Timestamp) 77 if err != nil { 78 return nil, terror.ErrBinlogWriteBinaryData.AnnotateDelegate(err, "write timestamp %d", header.Timestamp) 79 } 80 81 // event_type, 1 byte 82 err = binary.Write(buf, binary.LittleEndian, header.EventType) 83 if err != nil { 84 return nil, terror.ErrBinlogWriteBinaryData.AnnotateDelegate(err, "write event_type %v", header.EventType) 85 } 86 87 // server_id, 4 bytes 88 err = binary.Write(buf, binary.LittleEndian, header.ServerID) 89 if err != nil { 90 return nil, terror.ErrBinlogWriteBinaryData.AnnotateDelegate(err, "write server_id %d", header.ServerID) 91 } 92 93 // event_size, 4 bytes 94 err = binary.Write(buf, binary.LittleEndian, header.EventSize) 95 if err != nil { 96 return nil, terror.ErrBinlogWriteBinaryData.AnnotateDelegate(err, "write event_size %d", header.EventSize) 97 } 98 99 // log_pos, 4 bytes 100 err = binary.Write(buf, binary.LittleEndian, header.LogPos) 101 if err != nil { 102 return nil, terror.ErrBinlogWriteBinaryData.AnnotateDelegate(err, "write log_pos %d", header.LogPos) 103 } 104 105 // flags, 2 bytes 106 err = binary.Write(buf, binary.LittleEndian, header.Flags) 107 if err != nil { 108 return nil, terror.ErrBinlogWriteBinaryData.AnnotateDelegate(err, "write flags % X", header.Flags) 109 } 110 111 // try to decode the data 112 eh := replication.EventHeader{} 113 err = eh.Decode(buf.Bytes()) 114 if err != nil { 115 return nil, terror.ErrBinlogWriteBinaryData.AnnotateDelegate(err, "decode % X", buf.Bytes()) 116 } 117 118 return buf.Bytes(), nil 119 } 120 121 // GenFormatDescriptionEvent generates a FormatDescriptionEvent. 122 // ref: https://dev.mysql.com/doc/internals/en/format-description-event.html. 123 func GenFormatDescriptionEvent(header *replication.EventHeader, latestPos uint32) (*replication.BinlogEvent, error) { 124 payload := new(bytes.Buffer) 125 126 // binlog-version, 2 bytes 127 err := binary.Write(payload, binary.LittleEndian, binlogVersion) 128 if err != nil { 129 return nil, terror.ErrBinlogWriteBinaryData.AnnotateDelegate(err, "write binlog-version %d", binlogVersion) 130 } 131 132 // mysql-server version, 50 bytes 133 serverVer := make([]byte, mysqlVersionLen) 134 copy(serverVer, mysqlVersion) 135 err = binary.Write(payload, binary.LittleEndian, serverVer) 136 if err != nil { 137 return nil, terror.ErrBinlogWriteBinaryData.AnnotateDelegate(err, "write mysql-server version %v", serverVer) 138 } 139 140 // create_timestamp, 4 bytes 141 err = binary.Write(payload, binary.LittleEndian, header.Timestamp) 142 if err != nil { 143 return nil, terror.ErrBinlogWriteBinaryData.AnnotateDelegate(err, "write create_timestamp %d", header.Timestamp) 144 } 145 146 // event_header_length, 1 byte 147 err = binary.Write(payload, binary.LittleEndian, eventHeaderLen) 148 if err != nil { 149 return nil, terror.ErrBinlogWriteBinaryData.AnnotateDelegate(err, "write event_header_length %d", eventHeaderLen) 150 } 151 152 // event type header length, 38 bytes now 153 err = binary.Write(payload, binary.LittleEndian, eventTypeHeaderLen) 154 if err != nil { 155 return nil, terror.ErrBinlogWriteBinaryData.AnnotateDelegate(err, "write event type header length % X", eventTypeHeaderLen) 156 } 157 158 // checksum algorithm, 1 byte 159 err = binary.Write(payload, binary.LittleEndian, replication.BINLOG_CHECKSUM_ALG_CRC32) 160 if err != nil { 161 return nil, terror.ErrBinlogWriteBinaryData.AnnotateDelegate(err, "write checksum algorithm % X", replication.BINLOG_CHECKSUM_ALG_CRC32) 162 } 163 164 buf := new(bytes.Buffer) 165 event := &replication.FormatDescriptionEvent{} 166 ev, err := assembleEvent(buf, event, true, *header, replication.FORMAT_DESCRIPTION_EVENT, latestPos, nil, payload.Bytes()) 167 return ev, err 168 } 169 170 // GenRotateEvent generates a RotateEvent. 171 // ref: https://dev.mysql.com/doc/internals/en/rotate-event.html 172 func GenRotateEvent(header *replication.EventHeader, latestPos uint32, nextLogName []byte, position uint64) (*replication.BinlogEvent, error) { 173 if len(nextLogName) == 0 { 174 return nil, terror.ErrBinlogEmptyNextBinName.Generate() 175 } 176 177 // Post-header 178 postHeader := new(bytes.Buffer) 179 err := binary.Write(postHeader, binary.LittleEndian, position) 180 if err != nil { 181 return nil, terror.ErrBinlogWriteBinaryData.AnnotateDelegate(err, "write position %d", position) 182 } 183 184 // Payload 185 payload := new(bytes.Buffer) 186 err = binary.Write(payload, binary.LittleEndian, nextLogName) 187 if err != nil { 188 return nil, terror.ErrBinlogWriteBinaryData.AnnotateDelegate(err, "write next binlog name % X", nextLogName) 189 } 190 191 buf := new(bytes.Buffer) 192 event := &replication.RotateEvent{} 193 ev, err := assembleEvent(buf, event, false, *header, replication.ROTATE_EVENT, latestPos, postHeader.Bytes(), payload.Bytes()) 194 return ev, err 195 } 196 197 // GenPreviousGTIDsEvent generates a PreviousGTIDsEvent. 198 // MySQL has no internal doc for PREVIOUS_GTIDS_EVENT. 199 // we ref: 200 // 201 // a. https://github.com/vitessio/vitess/blob/28e7e5503a6c3d3b18d4925d95f23ebcb6f25c8e/go/mysql/binlog_event_mysql56.go#L56 202 // b. https://dev.mysql.com/doc/internals/en/com-binlog-dump-gtid.html 203 func GenPreviousGTIDsEvent(header *replication.EventHeader, latestPos uint32, gSet gmysql.GTIDSet) (*replication.BinlogEvent, error) { 204 if gSet == nil { 205 return nil, terror.ErrBinlogEmptyGTID.Generate() 206 } 207 208 // event payload, GTID set encoded in it 209 payload := gSet.Encode() 210 211 buf := new(bytes.Buffer) 212 event := &replication.PreviousGTIDsEvent{} 213 ev, err := assembleEvent(buf, event, false, *header, replication.PREVIOUS_GTIDS_EVENT, latestPos, nil, payload) 214 return ev, err 215 } 216 217 // GenGTIDEvent generates a GTIDEvent. 218 // MySQL has no internal doc for GTID_EVENT. 219 // we ref the `GTIDEvent.Decode` in go-mysql. 220 // `uuid` is the UUID part of the GTID, like `9f61c5f9-1eef-11e9-b6cf-0242ac140003`. 221 // `gno` is the GNO part of the GTID, like `6`. 222 func GenGTIDEvent(header *replication.EventHeader, latestPos uint32, gtidFlags uint8, uuid string, gno int64, lastCommitted int64, sequenceNumber int64) (*replication.BinlogEvent, error) { 223 return genGTIDEventInner(header, latestPos, gtidFlags, uuid, gno, lastCommitted, sequenceNumber, replication.GTID_EVENT) 224 } 225 226 func GenAnonymousGTIDEvent(header *replication.EventHeader, latestPos uint32, gtidFlags uint8, lastCommitted int64, sequenceNumber int64) (*replication.BinlogEvent, error) { 227 return genGTIDEventInner(header, latestPos, gtidFlags, "00000000-0000-0000-0000-000000000000", 0, lastCommitted, sequenceNumber, replication.ANONYMOUS_GTID_EVENT) 228 } 229 230 func genGTIDEventInner(header *replication.EventHeader, latestPos uint32, gtidFlags uint8, uuid string, gno int64, lastCommitted int64, sequenceNumber int64, eventType replication.EventType) (*replication.BinlogEvent, error) { 231 payload := new(bytes.Buffer) 232 233 // GTID flags, 1 byte 234 err := binary.Write(payload, binary.LittleEndian, gtidFlags) 235 if err != nil { 236 return nil, terror.ErrBinlogWriteBinaryData.AnnotateDelegate(err, "write GTID flags % X", gtidFlags) 237 } 238 239 // SID, 16 bytes 240 sid, err := ParseSID(uuid) 241 if err != nil { 242 return nil, terror.Annotatef(err, "parse UUID %s to SID", uuid) 243 } 244 err = binary.Write(payload, binary.LittleEndian, sid.Bytes()) 245 if err != nil { 246 return nil, terror.ErrBinlogWriteBinaryData.AnnotateDelegate(err, "write SID % X", sid.Bytes()) 247 } 248 249 // GNO, 8 bytes 250 err = binary.Write(payload, binary.LittleEndian, gno) 251 if err != nil { 252 return nil, terror.ErrBinlogWriteBinaryData.AnnotateDelegate(err, "write GNO %d", gno) 253 } 254 255 // length of TypeCode, 1 byte 256 err = binary.Write(payload, binary.LittleEndian, uint8(replication.LogicalTimestampTypeCode)) 257 if err != nil { 258 return nil, terror.ErrBinlogWriteBinaryData.AnnotateDelegate(err, "write length of TypeCode %d", replication.LogicalTimestampTypeCode) 259 } 260 261 // lastCommitted, 8 bytes 262 err = binary.Write(payload, binary.LittleEndian, lastCommitted) 263 if err != nil { 264 return nil, terror.ErrBinlogWriteBinaryData.AnnotateDelegate(err, "write last committed sequence number %d", lastCommitted) 265 } 266 267 // sequenceNumber, 8 bytes 268 err = binary.Write(payload, binary.LittleEndian, sequenceNumber) 269 if err != nil { 270 return nil, terror.ErrBinlogWriteBinaryData.AnnotateDelegate(err, "write sequence number %d", sequenceNumber) 271 } 272 273 buf := new(bytes.Buffer) 274 event := &replication.GTIDEvent{} 275 ev, err := assembleEvent(buf, event, false, *header, eventType, latestPos, nil, payload.Bytes()) 276 return ev, err 277 } 278 279 // GenQueryEvent generates a QueryEvent. 280 // ref: https://dev.mysql.com/doc/internals/en/query-event.html 281 // ref: http://blog.51cto.com/yanzongshuai/2087782 282 // `statusVars` should be generated out of this function, we can implement it later. 283 // `len(query)` must > 0. 284 func GenQueryEvent(header *replication.EventHeader, latestPos uint32, slaveProxyID uint32, executionTime uint32, errorCode uint16, statusVars []byte, schema []byte, query []byte) (*replication.BinlogEvent, error) { 285 if len(query) == 0 { 286 return nil, terror.ErrBinlogEmptyQuery.Generate() 287 } 288 289 // Post-header 290 postHeader := new(bytes.Buffer) 291 292 // slave_proxy_id (thread_id), 4 bytes 293 err := binary.Write(postHeader, binary.LittleEndian, slaveProxyID) 294 if err != nil { 295 return nil, terror.ErrBinlogWriteBinaryData.AnnotateDelegate(err, "write slave_proxy_id %d", slaveProxyID) 296 } 297 298 // executionTime, 4 bytes 299 err = binary.Write(postHeader, binary.LittleEndian, executionTime) 300 if err != nil { 301 return nil, terror.ErrBinlogWriteBinaryData.AnnotateDelegate(err, "write execution %d", executionTime) 302 } 303 304 // schema length, 1 byte 305 schemaLength := uint8(len(schema)) 306 err = binary.Write(postHeader, binary.LittleEndian, schemaLength) 307 if err != nil { 308 return nil, terror.ErrBinlogWriteBinaryData.AnnotateDelegate(err, "write schema length %d", schemaLength) 309 } 310 311 // error code, 2 bytes 312 err = binary.Write(postHeader, binary.LittleEndian, errorCode) 313 if err != nil { 314 return nil, terror.ErrBinlogWriteBinaryData.AnnotateDelegate(err, "write error code %d", errorCode) 315 } 316 317 // status-vars length, 2 bytes 318 statusVarsLength := uint16(len(statusVars)) 319 err = binary.Write(postHeader, binary.LittleEndian, statusVarsLength) 320 if err != nil { 321 return nil, terror.ErrBinlogWriteBinaryData.AnnotateDelegate(err, "write status-vars length %d", statusVarsLength) 322 } 323 324 // Payload 325 payload := new(bytes.Buffer) 326 327 // status-vars, status-vars length bytes 328 if statusVarsLength > 0 { 329 err = binary.Write(payload, binary.LittleEndian, statusVars) 330 if err != nil { 331 return nil, terror.ErrBinlogWriteBinaryData.AnnotateDelegate(err, "write status-vars % X", statusVars) 332 } 333 } 334 335 // schema, schema length bytes 336 if schemaLength > 0 { 337 err = binary.Write(payload, binary.LittleEndian, schema) 338 if err != nil { 339 return nil, terror.ErrBinlogWriteBinaryData.AnnotateDelegate(err, "write schema % X", schema) 340 } 341 } 342 343 // 0x00, 1 byte 344 err = binary.Write(payload, binary.LittleEndian, uint8(0x00)) 345 if err != nil { 346 return nil, terror.ErrBinlogWriteBinaryData.AnnotateDelegate(err, "write 0x00") 347 } 348 349 // query, len(query) bytes 350 err = binary.Write(payload, binary.LittleEndian, query) 351 if err != nil { 352 return nil, terror.ErrBinlogWriteBinaryData.AnnotateDelegate(err, "write query % X", query) 353 } 354 355 buf := new(bytes.Buffer) 356 event := &replication.QueryEvent{} 357 ev, err := assembleEvent(buf, event, false, *header, replication.QUERY_EVENT, latestPos, postHeader.Bytes(), payload.Bytes()) 358 return ev, err 359 } 360 361 // GenTableMapEvent generates a TableMapEvent. 362 // ref: https://dev.mysql.com/doc/internals/en/table-map-event.html 363 // ref: https://dev.mysql.com/doc/internals/en/describing-packets.html#type-lenenc_int 364 // ref: http://blog.51cto.com/yanzongshuai/2090758 365 // `len(schema)` must > 0, `len(table)` must > 0, `len(columnType)` must > 0. 366 // `columnType` should be generated out of this function, we can implement it later. 367 func GenTableMapEvent(header *replication.EventHeader, latestPos uint32, tableID uint64, schema []byte, table []byte, columnType []byte) (*replication.BinlogEvent, error) { 368 if len(schema) == 0 || len(table) == 0 || len(columnType) == 0 { 369 return nil, terror.ErrBinlogTableMapEvNotValid.Generate(schema, table, columnType) 370 } 371 372 // Post-header 373 postHeader := new(bytes.Buffer) 374 375 tableIDSize := 6 // for binlog V4, this should be 6. 376 if eventTypeHeaderLen[replication.TABLE_MAP_EVENT-1] == 6 { 377 tableIDSize = 4 378 } 379 380 // table id, tableIDSize bytes 381 err := binary.Write(postHeader, binary.LittleEndian, tableID) 382 if err != nil { 383 return nil, terror.ErrBinlogWriteBinaryData.AnnotateDelegate(err, "write table id %d", tableID) 384 } 385 postHeader.Truncate(tableIDSize) // truncate the unwanted bytes 386 387 // flags, 2 bytes 388 err = binary.Write(postHeader, binary.LittleEndian, tableMapFlags) 389 if err != nil { 390 return nil, terror.ErrBinlogWriteBinaryData.AnnotateDelegate(err, "write flags %d", tableMapFlags) 391 } 392 393 // Payload 394 payload := new(bytes.Buffer) 395 396 // schema name length, 1 byte 397 schemaLen := uint8(len(schema)) 398 err = binary.Write(payload, binary.LittleEndian, schemaLen) 399 if err != nil { 400 return nil, terror.ErrBinlogWriteBinaryData.AnnotateDelegate(err, "write schema name length %d", schemaLen) 401 } 402 403 // schema name, schema name length bytes 404 err = binary.Write(payload, binary.LittleEndian, schema) 405 if err != nil { 406 return nil, terror.ErrBinlogWriteBinaryData.AnnotateDelegate(err, "write schema name % X", schema) 407 } 408 409 // 0x00, 1 byte 410 err = binary.Write(payload, binary.LittleEndian, uint8(0x00)) 411 if err != nil { 412 return nil, terror.ErrBinlogWriteBinaryData.AnnotateDelegate(err, "write 0x00") 413 } 414 415 // table name length, 1 byte 416 tableLen := uint8(len(table)) 417 err = binary.Write(payload, binary.LittleEndian, tableLen) 418 if err != nil { 419 return nil, terror.ErrBinlogWriteBinaryData.AnnotateDelegate(err, "write table name length %d", tableLen) 420 } 421 422 // table name, table name length bytes 423 err = binary.Write(payload, binary.LittleEndian, table) 424 if err != nil { 425 return nil, terror.ErrBinlogWriteBinaryData.AnnotateDelegate(err, "write table name % X", table) 426 } 427 428 // 0x00, 1 byte 429 err = binary.Write(payload, binary.LittleEndian, uint8(0x00)) 430 if err != nil { 431 return nil, terror.ErrBinlogWriteBinaryData.AnnotateDelegate(err, "write 0x00") 432 } 433 434 // column-count, lenenc-int 435 columnCount := gmysql.PutLengthEncodedInt(uint64(len(columnType))) 436 err = binary.Write(payload, binary.LittleEndian, columnCount) 437 if err != nil { 438 return nil, terror.ErrBinlogWriteBinaryData.AnnotateDelegate(err, "write column-count % X", columnCount) 439 } 440 441 // column-type-def, column-count bytes 442 err = binary.Write(payload, binary.LittleEndian, columnType) 443 if err != nil { 444 return nil, terror.ErrBinlogWriteBinaryData.AnnotateDelegate(err, "write column-type-def % X", columnType) 445 } 446 447 // column-meta-def, lenenc-str 448 columnMeta, err := encodeTableMapColumnMeta(columnType) 449 if err != nil { 450 return nil, terror.ErrBinlogWriteBinaryData.AnnotateDelegate(err, "generate column-meta-def for column-type-def % X", columnType) 451 } 452 err = binary.Write(payload, binary.LittleEndian, columnMeta) 453 if err != nil { 454 return nil, terror.ErrBinlogWriteBinaryData.AnnotateDelegate(err, "write column-meta-def % X", columnMeta) 455 } 456 457 // NULL-bitmask, (column-count + 8) / 7 bytes 458 bitMaskLen := bitmapByteSize(len(columnType)) 459 bitMask := nullBytes(bitMaskLen) 460 err = binary.Write(payload, binary.LittleEndian, bitMask) 461 if err != nil { 462 return nil, terror.ErrBinlogWriteBinaryData.AnnotateDelegate(err, "write NULL-bitmask % X", bitMask) 463 } 464 465 buf := new(bytes.Buffer) 466 _, err = assembleEvent(buf, nil, false, *header, replication.TABLE_MAP_EVENT, latestPos, postHeader.Bytes(), payload.Bytes()) 467 if err != nil { 468 return nil, terror.Annotate(err, "combine event data") 469 } 470 471 // sad, in order to Decode a TableMapEvent, we need to set `tableIDSize` first, but it's a private field. 472 // so, we need to use a BinlogParser to parse a FormatDescriptionEvent first. 473 formatDescEv, err := GenFormatDescriptionEvent(header, 4) 474 if err != nil { 475 return nil, terror.Annotate(err, "generate FormatDescriptionEvent") 476 } 477 478 var tableMapEvent *replication.BinlogEvent 479 count := 0 480 onEventFunc := func(e *replication.BinlogEvent) error { 481 count++ 482 switch count { 483 case 1: // FormatDescriptionEvent 484 if e.Header.EventType != replication.FORMAT_DESCRIPTION_EVENT { 485 return terror.ErrBinlogExpectFormatDescEv.Generate(e) 486 } 487 case 2: // TableMapEvent 488 if e.Header.EventType != replication.TABLE_MAP_EVENT { 489 return terror.ErrBinlogExpectTableMapEv.Generate(e) 490 } 491 tableMapEvent = e 492 default: 493 return terror.ErrBinlogUnexpectedEv.Generate(e) 494 } 495 return nil 496 } 497 498 parse2 := replication.NewBinlogParser() 499 parse2.SetVerifyChecksum(true) 500 // parse FormatDescriptionEvent 501 _, err = parse2.ParseSingleEvent(bytes.NewReader(formatDescEv.RawData), onEventFunc) 502 if err != nil { 503 return nil, terror.ErrBinlogParseSingleEv.AnnotateDelegate(err, "parse FormatDescriptionEvent % X", formatDescEv.RawData) 504 } 505 506 // parse TableMapEvent 507 _, err = parse2.ParseSingleEvent(bytes.NewReader(buf.Bytes()), onEventFunc) 508 if err != nil { 509 return nil, terror.ErrBinlogParseSingleEv.AnnotateDelegate(err, "parse TableMapEvent % X", buf.Bytes()) 510 } 511 512 return tableMapEvent, nil 513 } 514 515 // GenRowsEvent generates a RowsEvent. 516 // RowsEvent includes: 517 // 518 // WRITE_ROWS_EVENTv0, WRITE_ROWS_EVENTv1, WRITE_ROWS_EVENTv2 519 // UPDATE_ROWS_EVENTv0, UPDATE_ROWS_EVENTv1, UPDATE_ROWS_EVENTv2 520 // DELETE_ROWS_EVENTv0, DELETE_ROWS_EVENTv1, DELETE_ROWS_EVENTv2 521 // 522 // ref: https://dev.mysql.com/doc/internals/en/rows-event.html 523 // ref: http://blog.51cto.com/yanzongshuai/2090894 524 func GenRowsEvent(header *replication.EventHeader, latestPos uint32, eventType replication.EventType, tableID uint64, rowsFlags uint16, rows [][]interface{}, columnType []byte, tableMapEv *replication.BinlogEvent) (*replication.BinlogEvent, error) { 525 switch eventType { 526 case replication.WRITE_ROWS_EVENTv0, replication.WRITE_ROWS_EVENTv1, replication.WRITE_ROWS_EVENTv2, 527 replication.UPDATE_ROWS_EVENTv0, replication.UPDATE_ROWS_EVENTv1, replication.UPDATE_ROWS_EVENTv2, 528 replication.DELETE_ROWS_EVENTv0, replication.DELETE_ROWS_EVENTv1, replication.DELETE_ROWS_EVENTv2: 529 default: 530 return nil, terror.ErrBinlogEventTypeNotValid.Generate(eventType) 531 } 532 533 if len(rows) == 0 { 534 return nil, terror.ErrBinlogEventNoRows.Generate() 535 } 536 if len(columnType) == 0 { 537 return nil, terror.ErrBinlogEventNoColumns.Generate() 538 } 539 for _, row := range rows { 540 if len(row) != len(columnType) { 541 // all rows have the same length (no nil), and equal to the length of column-type 542 return nil, terror.ErrBinlogEventRowLengthNotEq.Generate(len(row), len(columnType)) 543 } 544 } 545 546 postHeader := new(bytes.Buffer) 547 548 tableIDSize := 6 549 if eventTypeHeaderLen[eventType] == 6 { 550 tableIDSize = 4 551 } 552 // table id, tableIDSize bytes 553 err := binary.Write(postHeader, binary.LittleEndian, tableID) 554 if err != nil { 555 return nil, terror.ErrBinlogWriteBinaryData.AnnotateDelegate(err, "write table id %d", tableID) 556 } 557 postHeader.Truncate(tableIDSize) // truncate the unwanted bytes 558 559 // flags, 2 bytes 560 err = binary.Write(postHeader, binary.LittleEndian, rowsFlags) 561 if err != nil { 562 return nil, terror.ErrBinlogWriteBinaryData.AnnotateDelegate(err, "write flags %d", rowsFlags) 563 } 564 565 // extra-data 566 switch eventType { 567 case replication.WRITE_ROWS_EVENTv2, replication.UPDATE_ROWS_EVENTv2, replication.DELETE_ROWS_EVENTv2: 568 // if version=2, extra data exist. 569 // NOTE: we do not support to write any meaningful extra data yet. 570 var extraDataLen uint16 = 2 // two bytes, but with value `2` (no extra data, only this len variable) 571 err = binary.Write(postHeader, binary.LittleEndian, extraDataLen) 572 if err != nil { 573 return nil, terror.ErrBinlogWriteBinaryData.AnnotateDelegate(err, "write extra data length %d", extraDataLen) 574 } 575 default: 576 } 577 578 payload := new(bytes.Buffer) 579 580 // number of columns, lenenc-int 581 columnCount := gmysql.PutLengthEncodedInt(uint64(len(rows[0]))) 582 err = binary.Write(payload, binary.LittleEndian, columnCount) 583 if err != nil { 584 return nil, terror.ErrBinlogWriteBinaryData.AnnotateDelegate(err, "write number of columns % X", columnCount) 585 } 586 587 // columns-present-bitmap1, (num of columns+7)/8 bytes 588 byteCount := bitmapByteSize(len(rows[0])) 589 bitmap := fullBytes(byteCount) // NOTE: only support to write full columns now 590 err = binary.Write(payload, binary.LittleEndian, bitmap) 591 if err != nil { 592 return nil, terror.ErrBinlogWriteBinaryData.AnnotateDelegate(err, "write columns-present-bitmap1 % X", bitmap) 593 } 594 595 isUpdateV1V2 := eventType == replication.UPDATE_ROWS_EVENTv1 || eventType == replication.UPDATE_ROWS_EVENTv2 596 if isUpdateV1V2 { 597 // NOTE: use columns-present-bitmap1 as columns-present-bitmap2 now 598 err = binary.Write(payload, binary.LittleEndian, bitmap) 599 if err != nil { 600 return nil, terror.ErrBinlogWriteBinaryData.AnnotateDelegate(err, "write columns-present-bitmap2 % X", bitmap) 601 } 602 } 603 604 columnMetaData, err := encodeTableMapColumnMeta(columnType) 605 if err != nil { 606 return nil, terror.Annotatef(err, "encode column-meta-def from column-type % X", columnType) 607 } 608 columnMeta, err := decodeTableMapColumnMeta(columnMetaData, columnType) 609 if err != nil { 610 return nil, terror.Annotatef(err, "decode column-meta-def %X", columnMetaData) 611 } 612 613 // for UPDATE, two rows for one statement 614 // currently, columns-present-bitmap2 is just columns-present-bitmap1 615 for _, row := range rows { 616 // nul-bitmap, (num of columns+7)/8 bytes 617 nulBitmap := nullBytes(byteCount) // NOTE: no column with nil value now 618 err = binary.Write(payload, binary.LittleEndian, nulBitmap) 619 if err != nil { 620 return nil, terror.ErrBinlogWriteBinaryData.AnnotateDelegate(err, "write nul-bitmap % X for row %v", nulBitmap, row) 621 } 622 623 for i, col := range row { 624 var colData []byte 625 colData, err = encodeColumnValue(col, columnType[i], columnMeta[i]) 626 if err != nil { 627 return nil, terror.Annotatef(err, "encode column value %v to bytes", col) 628 } 629 err = binary.Write(payload, binary.LittleEndian, colData) 630 if err != nil { 631 return nil, terror.ErrBinlogWriteBinaryData.AnnotateDelegate(err, "write column data % X", colData) 632 } 633 } 634 } 635 636 buf := new(bytes.Buffer) 637 _, err = assembleEvent(buf, nil, false, *header, eventType, latestPos, postHeader.Bytes(), payload.Bytes()) 638 if err != nil { 639 return nil, err 640 } 641 642 // in order to Decode RowsEvent, we need to set `tableIDSize` and `tables` first, but they are private fields. 643 // so we should parse a FormatDescriptionEvent and a TableMapEvent first. 644 var rowsEvent *replication.BinlogEvent 645 count := 0 646 onEventFunc := func(e *replication.BinlogEvent) error { 647 count++ 648 switch count { 649 case 1: // FormatDescriptionEvent 650 if e.Header.EventType != replication.FORMAT_DESCRIPTION_EVENT { 651 return terror.ErrBinlogExpectFormatDescEv.Generate(e) 652 } 653 case 2: // TableMapEvent 654 if e.Header.EventType != replication.TABLE_MAP_EVENT { 655 return terror.ErrBinlogExpectTableMapEv.Generate(e) 656 } 657 case 3: // RowsEvent 658 if e.Header.EventType != eventType { 659 return terror.ErrBinlogExpectRowsEv.Generate(eventType, e) 660 } 661 rowsEvent = e 662 default: 663 return terror.ErrBinlogUnexpectedEv.Generate(e) 664 } 665 return nil 666 } 667 668 parse2 := replication.NewBinlogParser() 669 parse2.SetVerifyChecksum(true) 670 671 // parse FormatDescriptionEvent 672 formatDescEv, err := GenFormatDescriptionEvent(header, 4) 673 if err != nil { 674 return nil, terror.Annotate(err, "generate FormatDescriptionEvent") 675 } 676 _, err = parse2.ParseSingleEvent(bytes.NewReader(formatDescEv.RawData), onEventFunc) 677 if err != nil { 678 return nil, terror.ErrBinlogParseSingleEv.AnnotateDelegate(err, "parse FormatDescriptionEvent % X", formatDescEv.RawData) 679 } 680 681 // parse TableMapEvent 682 if tableMapEv == nil { 683 tableMapEv, err = GenTableMapEvent(header, latestPos, tableID, []byte("schema-placeholder"), []byte("table-placeholder"), columnType) 684 if err != nil { 685 return nil, terror.Annotate(err, "generate TableMapEvent") 686 } 687 } 688 _, err = parse2.ParseSingleEvent(bytes.NewReader(tableMapEv.RawData), onEventFunc) 689 if err != nil { 690 return nil, terror.ErrBinlogParseSingleEv.AnnotateDelegate(err, "parse TableMapEvent % x", tableMapEv.RawData) 691 } 692 693 // parse RowsEvent 694 _, err = parse2.ParseSingleEvent(bytes.NewReader(buf.Bytes()), onEventFunc) 695 if err != nil { 696 return nil, terror.ErrBinlogParseSingleEv.AnnotateDelegate(err, "parse RowsEvent % X", buf.Bytes()) 697 } 698 699 return rowsEvent, nil 700 } 701 702 // GenXIDEvent generates a XIDEvent. 703 // ref: https://dev.mysql.com/doc/internals/en/xid-event.html 704 func GenXIDEvent(header *replication.EventHeader, latestPos uint32, xid uint64) (*replication.BinlogEvent, error) { 705 // Payload 706 payload := new(bytes.Buffer) 707 err := binary.Write(payload, binary.LittleEndian, xid) 708 if err != nil { 709 return nil, terror.ErrBinlogWriteBinaryData.AnnotateDelegate(err, "write XID %d", xid) 710 } 711 712 buf := new(bytes.Buffer) 713 event := &replication.XIDEvent{} 714 ev, err := assembleEvent(buf, event, false, *header, replication.XID_EVENT, latestPos, nil, payload.Bytes()) 715 return ev, err 716 } 717 718 // GenMariaDBGTIDListEvent generates a MariadbGTIDListEvent. 719 // ref: https://mariadb.com/kb/en/library/gtid_list_event/ 720 func GenMariaDBGTIDListEvent(header *replication.EventHeader, latestPos uint32, gSet gmysql.GTIDSet) (*replication.BinlogEvent, error) { 721 if gtid.CheckGTIDSetEmpty(gSet) { 722 return nil, terror.ErrBinlogEmptyGTID.Generate() 723 } 724 725 mariaDBGSet, ok := gSet.(*gmysql.MariadbGTIDSet) 726 if !ok { 727 return nil, terror.ErrBinlogGTIDMariaDBNotValid.Generate(gSet.String()) 728 } 729 730 payload := new(bytes.Buffer) 731 732 // Number of GTIDs, 4 bytes 733 numOfGTIDs := uint32(0) 734 for _, set := range mariaDBGSet.Sets { 735 numOfGTIDs += uint32(len(set)) 736 } 737 err := binary.Write(payload, binary.LittleEndian, numOfGTIDs) 738 if err != nil { 739 return nil, terror.ErrBinlogWriteBinaryData.AnnotateDelegate(err, "write Number of GTIDs %d", numOfGTIDs) 740 } 741 742 for _, set := range mariaDBGSet.Sets { 743 for _, mGTID := range set { 744 // Replication Domain ID, 4 bytes 745 err = binary.Write(payload, binary.LittleEndian, mGTID.DomainID) 746 if err != nil { 747 return nil, terror.ErrBinlogWriteBinaryData.AnnotateDelegate(err, "write Replication Domain ID %d", mGTID.DomainID) 748 } 749 // Server_ID, 4 bytes 750 err = binary.Write(payload, binary.LittleEndian, mGTID.ServerID) 751 if err != nil { 752 return nil, terror.ErrBinlogWriteBinaryData.AnnotateDelegate(err, "write Server_ID %d", mGTID.ServerID) 753 } 754 // GTID sequence, 8 bytes 755 err = binary.Write(payload, binary.LittleEndian, mGTID.SequenceNumber) 756 if err != nil { 757 return nil, terror.ErrBinlogWriteBinaryData.AnnotateDelegate(err, "write GTID sequence %d", mGTID.SequenceNumber) 758 } 759 } 760 } 761 762 buf := new(bytes.Buffer) 763 event := &replication.MariadbGTIDListEvent{} 764 ev, err := assembleEvent(buf, event, false, *header, replication.MARIADB_GTID_LIST_EVENT, latestPos, nil, payload.Bytes()) 765 return ev, err 766 } 767 768 // GenMariaDBGTIDEvent generates a MariadbGTIDEvent. 769 // ref: https://mariadb.com/kb/en/library/gtid_event/ 770 func GenMariaDBGTIDEvent(header *replication.EventHeader, latestPos uint32, sequenceNum uint64, domainID uint32) (*replication.BinlogEvent, error) { 771 payload := new(bytes.Buffer) 772 773 // GTID sequence, 8 bytes 774 err := binary.Write(payload, binary.LittleEndian, sequenceNum) 775 if err != nil { 776 return nil, terror.ErrBinlogWriteBinaryData.AnnotateDelegate(err, "write GTID sequence %d", sequenceNum) 777 } 778 779 // Replication Domain ID, 4 bytes 780 err = binary.Write(payload, binary.LittleEndian, domainID) 781 if err != nil { 782 return nil, terror.ErrBinlogWriteBinaryData.AnnotateDelegate(err, "write Replication Domain ID %d", domainID) 783 } 784 785 // Flags, 1 byte, keep zero now. 786 xidFlags := uint8(0x00) 787 err = binary.Write(payload, binary.LittleEndian, xidFlags) 788 if err != nil { 789 return nil, terror.ErrBinlogWriteBinaryData.AnnotateDelegate(err, "write Flags %d", xidFlags) 790 } 791 792 // "if flag & FL_GROUP_COMMIT_ID" is FALSE 793 // commit_id, 6 bytes with zero value 794 err = binary.Write(payload, binary.LittleEndian, uint64(0x00)) 795 if err != nil { 796 return nil, terror.ErrBinlogWriteBinaryData.AnnotateDelegate(err, "write 6 bytes commit_id with zero value") 797 } 798 payload.Truncate(payload.Len() - 2) // len(uint64) - 2 == 6 bytes 799 800 buf := new(bytes.Buffer) 801 event := &replication.MariadbGTIDEvent{} 802 ev, err := assembleEvent(buf, event, false, *header, replication.MARIADB_GTID_EVENT, latestPos, nil, payload.Bytes()) 803 return ev, err 804 } 805 806 // GenDummyEvent generates a dummy QueryEvent or a dummy USER_VAR_EVENT. 807 // Dummy events often used to fill the holes in a relay log file which lacking some events from the master. 808 // The minimum size is 29 bytes (19 bytes header + 6 bytes body for a USER_VAR_EVENT + 4 bytes checksum). 809 // ref: https://dev.mysql.com/doc/internals/en/user-var-event.html 810 // ref: https://github.com/MariaDB/server/blob/a765b19e5ca31a3d866cdbc8bef3a6f4e5e44688/sql/log_event.cc#L4950 811 func GenDummyEvent(header *replication.EventHeader, latestPos uint32, eventSize uint32) (*replication.BinlogEvent, error) { 812 if eventSize < MinUserVarEventLen { 813 return nil, terror.ErrBinlogDummyEvSizeTooSmall.Generate(eventSize, MinUserVarEventLen) 814 } 815 816 // modify flag in the header 817 headerClone := *header // do a copy 818 headerClone.Flags &= ^replication.LOG_EVENT_THREAD_SPECIFIC_F 819 headerClone.Flags |= replication.LOG_EVENT_SUPPRESS_USE_F 820 headerClone.Flags |= replication.LOG_EVENT_RELAY_LOG_F // now, the dummy event created by relay only 821 822 if eventSize < MinQueryEventLen { 823 // generate a USER_VAR_EVENT 824 var ( 825 payload = new(bytes.Buffer) 826 buf = new(bytes.Buffer) 827 event = &replication.GenericEvent{} 828 eventType = replication.USER_VAR_EVENT 829 nameLen = eventSize - (MinUserVarEventLen - 1) 830 nameBytes = make([]byte, nameLen) 831 ) 832 copy(nameBytes, dummyUserVarName) 833 // name_length, 4 bytes 834 err := binary.Write(payload, binary.LittleEndian, nameLen) 835 if err != nil { 836 return nil, terror.ErrBinlogWriteBinaryData.AnnotateDelegate(err, "write USER_VAR_EVENT name length %d", nameLen) 837 } 838 // name, name_length bytes (now, at least 1 byte) 839 err = binary.Write(payload, binary.LittleEndian, nameBytes) 840 if err != nil { 841 return nil, terror.ErrBinlogWriteBinaryData.AnnotateDelegate(err, "write USER_VAR_EVENT name % X", nameBytes) 842 } 843 // is_null, 1 byte 844 isNull := byte(1) // always is null (no `value` part) 845 err = binary.Write(payload, binary.LittleEndian, isNull) 846 if err != nil { 847 return nil, terror.ErrBinlogWriteBinaryData.AnnotateDelegate(err, "write USER_VAR_EVENT is-null % X", isNull) 848 } 849 ev, err := assembleEvent(buf, event, false, headerClone, eventType, latestPos, nil, payload.Bytes()) 850 return ev, err 851 } 852 853 // generate a QueryEvent 854 queryLen := eventSize - (MinQueryEventLen - 1) 855 queryBytes := make([]byte, queryLen) 856 copy(queryBytes, dummyQuery) 857 ev, err := GenQueryEvent(&headerClone, latestPos, 0, 0, 0, nil, nil, queryBytes) 858 return ev, err 859 } 860 861 // GenHeartbeatEvent generates a heartbeat event. 862 // ref: https://dev.mysql.com/doc/internals/en/heartbeat-event.html 863 func GenHeartbeatEvent(header *replication.EventHeader) *replication.BinlogEvent { 864 // modify header 865 headerClone := *header // do a copy 866 headerClone.Flags = 0 867 headerClone.EventSize = 39 868 headerClone.Timestamp = 0 869 headerClone.EventType = replication.HEARTBEAT_EVENT 870 871 eventBytes := make([]byte, 39) 872 ev := &replication.BinlogEvent{Header: &headerClone, Event: &replication.GenericEvent{Data: eventBytes}} 873 874 return ev 875 }