github.com/pingcap/tiflow@v0.0.0-20240520035814-5bf52d54e205/dm/pkg/binlog/event/util.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 "errors" 23 "fmt" 24 "hash/crc32" 25 "io" 26 "math" 27 "reflect" 28 29 gmysql "github.com/go-mysql-org/go-mysql/mysql" 30 "github.com/go-mysql-org/go-mysql/replication" 31 "github.com/google/uuid" 32 "github.com/pingcap/tidb/pkg/parser" 33 "github.com/pingcap/tidb/pkg/parser/charset" 34 "github.com/pingcap/tidb/pkg/parser/mysql" 35 "github.com/pingcap/tiflow/dm/pkg/terror" 36 "golang.org/x/text/encoding" 37 "golang.org/x/text/encoding/simplifiedchinese" 38 ) 39 40 // encodeTableMapColumnMeta generates the column_meta_def according to the column_type_def. 41 // NOTE: we should pass more arguments for some type def later, now simply hard-code them. 42 // ref: https://dev.mysql.com/doc/internals/en/table-map-event.html 43 // ref: https://github.com/go-mysql-org/go-mysql/blob/88e9cd7f6643b246b4dcc0e3206e9a169dd0ac96/replication/row_event.go#L100 44 func encodeTableMapColumnMeta(columnType []byte) ([]byte, error) { 45 buf := new(bytes.Buffer) 46 for _, t := range columnType { 47 switch t { 48 case gmysql.MYSQL_TYPE_STRING: 49 buf.WriteByte(0xfe) // real type 50 buf.WriteByte(0xff) // pack or field length 51 case gmysql.MYSQL_TYPE_NEWDECIMAL: 52 buf.WriteByte(0x12) // precision, 18 53 buf.WriteByte(0x09) // decimals, 9 54 case gmysql.MYSQL_TYPE_VAR_STRING, gmysql.MYSQL_TYPE_VARCHAR, gmysql.MYSQL_TYPE_BIT: 55 buf.WriteByte(0xff) 56 buf.WriteByte(0xff) 57 case gmysql.MYSQL_TYPE_BLOB, gmysql.MYSQL_TYPE_DOUBLE, gmysql.MYSQL_TYPE_FLOAT, gmysql.MYSQL_TYPE_GEOMETRY, gmysql.MYSQL_TYPE_JSON, 58 gmysql.MYSQL_TYPE_TIME2, gmysql.MYSQL_TYPE_DATETIME2, gmysql.MYSQL_TYPE_TIMESTAMP2: 59 buf.WriteByte(0xff) 60 case gmysql.MYSQL_TYPE_NEWDATE, gmysql.MYSQL_TYPE_ENUM, gmysql.MYSQL_TYPE_SET, gmysql.MYSQL_TYPE_TINY_BLOB, gmysql.MYSQL_TYPE_MEDIUM_BLOB, gmysql.MYSQL_TYPE_LONG_BLOB: 61 return nil, terror.ErrBinlogColumnTypeNotSupport.Generate(t) 62 } 63 } 64 return gmysql.PutLengthEncodedString(buf.Bytes()), nil 65 } 66 67 // decodeTableMapColumnMeta generates the column_meta_def to uint16 slices. 68 // ref: https://github.com/go-mysql-org/go-mysql/blob/88e9cd7f6643b246b4dcc0e3206e9a169dd0ac96/replication/row_event.go#L100 69 func decodeTableMapColumnMeta(data []byte, columnType []byte) ([]uint16, error) { 70 pos := 0 71 columnMeta := make([]uint16, len(columnType)) 72 for i, t := range columnType { 73 switch t { 74 case gmysql.MYSQL_TYPE_STRING: 75 x := uint16(data[pos]) << 8 // real type 76 x += uint16(data[pos+1]) // pack or field length 77 columnMeta[i] = x 78 pos += 2 79 case gmysql.MYSQL_TYPE_NEWDECIMAL: 80 x := uint16(data[pos]) << 8 // precision 81 x += uint16(data[pos+1]) // decimals 82 columnMeta[i] = x 83 pos += 2 84 case gmysql.MYSQL_TYPE_VAR_STRING, gmysql.MYSQL_TYPE_VARCHAR, gmysql.MYSQL_TYPE_BIT: 85 columnMeta[i] = binary.LittleEndian.Uint16(data[pos:]) 86 pos += 2 87 case gmysql.MYSQL_TYPE_BLOB, gmysql.MYSQL_TYPE_DOUBLE, gmysql.MYSQL_TYPE_FLOAT, gmysql.MYSQL_TYPE_GEOMETRY, gmysql.MYSQL_TYPE_JSON, 88 gmysql.MYSQL_TYPE_TIME2, gmysql.MYSQL_TYPE_DATETIME2, gmysql.MYSQL_TYPE_TIMESTAMP2: 89 columnMeta[i] = uint16(data[pos]) 90 pos++ 91 case gmysql.MYSQL_TYPE_NEWDATE, gmysql.MYSQL_TYPE_ENUM, gmysql.MYSQL_TYPE_SET, gmysql.MYSQL_TYPE_TINY_BLOB, gmysql.MYSQL_TYPE_MEDIUM_BLOB, gmysql.MYSQL_TYPE_LONG_BLOB: 92 return nil, terror.ErrBinlogColumnTypeNotSupport.Generate(t) 93 default: 94 columnMeta[i] = 0 95 } 96 } 97 98 return columnMeta, nil 99 } 100 101 // bitmapByteSize returns the byte length of bitmap for columnCount. 102 func bitmapByteSize(columnCount int) int { 103 return (columnCount + 7) / 8 104 } 105 106 // nullBytes returns a n-length null bytes slice. 107 func nullBytes(n int) []byte { 108 return make([]byte, n) 109 } 110 111 // fullBytes returns a n-length full bytes slice (all bits are set). 112 func fullBytes(n int) []byte { 113 buf := new(bytes.Buffer) 114 for i := 0; i < n; i++ { 115 buf.WriteByte(0xff) 116 } 117 return buf.Bytes() 118 } 119 120 // assembleEvent assembles header fields, postHeader and payload together to an event. 121 // header: pass as a struct to make a copy 122 func assembleEvent( 123 buf *bytes.Buffer, 124 event replication.Event, 125 decodeWithChecksum bool, 126 header replication.EventHeader, 127 eventType replication.EventType, 128 latestPos uint32, 129 postHeader, payload []byte, 130 ) (*replication.BinlogEvent, error) { 131 eventSize := uint32(eventHeaderLen) + uint32(len(postHeader)) + uint32(len(payload)) + crc32Len 132 // update some fields in header 133 header.EventSize = eventSize 134 header.LogPos = latestPos + eventSize 135 header.EventType = eventType 136 headerData, err := GenEventHeader(&header) 137 if err != nil { 138 return nil, terror.Annotate(err, "generate event header") 139 } 140 141 err = combineHeaderPayload(buf, headerData, postHeader, payload) 142 if err != nil { 143 return nil, terror.Annotate(err, "combine header, post-header and payload") 144 } 145 146 // CRC32 checksum, 4 bytes 147 checksum := crc32.ChecksumIEEE(buf.Bytes()) 148 err = binary.Write(buf, binary.LittleEndian, checksum) 149 if err != nil { 150 return nil, terror.ErrBinlogWriteBinaryData.AnnotateDelegate(err, "write CRC32 % X", checksum) 151 } 152 153 if event == nil { 154 return nil, nil // not need to decode the event 155 } 156 157 // decode event, some implementations of `Decode` also need checksum 158 endIdx := buf.Len() 159 if !decodeWithChecksum { 160 endIdx -= int(crc32Len) 161 } 162 err = event.Decode(buf.Bytes()[eventHeaderLen:endIdx]) 163 if err != nil { 164 return nil, terror.ErrBinlogEventDecode.Delegate(err, buf.Bytes()) 165 } 166 167 return &replication.BinlogEvent{RawData: buf.Bytes(), Header: &header, Event: event}, nil 168 } 169 170 // combineHeaderPayload combines header, postHeader and payload together. 171 func combineHeaderPayload(buf *bytes.Buffer, header, postHeader, payload []byte) error { 172 if len(header) != int(eventHeaderLen) { 173 return terror.ErrBinlogHeaderLengthNotValid.Generate(eventHeaderLen, len(header)) 174 } 175 176 err := binary.Write(buf, binary.LittleEndian, header) 177 if err != nil { 178 return terror.ErrBinlogWriteBinaryData.AnnotateDelegate(err, "write event header % X", header) 179 } 180 181 if len(postHeader) > 0 { // postHeader maybe empty 182 err = binary.Write(buf, binary.LittleEndian, postHeader) 183 if err != nil { 184 return terror.ErrBinlogWriteBinaryData.AnnotateDelegate(err, "write event post-header % X", postHeader) 185 } 186 } 187 188 err = binary.Write(buf, binary.LittleEndian, payload) 189 if err != nil { 190 return terror.ErrBinlogWriteBinaryData.AnnotateDelegate(err, "write event payload % X", payload) 191 } 192 193 return nil 194 } 195 196 // encodeColumnValue encodes value to bytes 197 // ref: https://github.com/go-mysql-org/go-mysql/blob/88e9cd7f6643b246b4dcc0e3206e9a169dd0ac96/replication/row_event.go#L368 198 // NOTE: we do not generate meaningful `meta` yet. 199 // nolint:unparam 200 func encodeColumnValue(v interface{}, tp byte, meta uint16) ([]byte, error) { 201 var ( 202 buf = new(bytes.Buffer) 203 err error 204 ) 205 switch tp { 206 case gmysql.MYSQL_TYPE_NULL: 207 return nil, nil 208 case gmysql.MYSQL_TYPE_LONG: 209 err = writeIntegerColumnValue(buf, v, reflect.TypeOf(int32(0))) 210 case gmysql.MYSQL_TYPE_TINY: 211 err = writeIntegerColumnValue(buf, v, reflect.TypeOf(int8(0))) 212 case gmysql.MYSQL_TYPE_SHORT: 213 err = writeIntegerColumnValue(buf, v, reflect.TypeOf(int16(0))) 214 case gmysql.MYSQL_TYPE_INT24: 215 err = writeIntegerColumnValue(buf, v, reflect.TypeOf(int32(0))) 216 if err == nil { 217 buf.Truncate(3) 218 } 219 case gmysql.MYSQL_TYPE_LONGLONG: 220 err = writeIntegerColumnValue(buf, v, reflect.TypeOf(int64(0))) 221 case gmysql.MYSQL_TYPE_FLOAT: 222 value, ok := v.(float32) 223 if !ok { 224 err = terror.ErrBinlogColumnTypeMisMatch.Generate(v, reflect.TypeOf(v), reflect.TypeOf(float32(0))) 225 } else { 226 bits := math.Float32bits(value) 227 err = writeIntegerColumnValue(buf, bits, reflect.TypeOf(uint32(0))) 228 } 229 case gmysql.MYSQL_TYPE_DOUBLE: 230 value, ok := v.(float64) 231 if !ok { 232 err = terror.ErrBinlogColumnTypeMisMatch.Generate(v, reflect.TypeOf(v), reflect.TypeOf(float64(0))) 233 } else { 234 bits := math.Float64bits(value) 235 err = writeIntegerColumnValue(buf, bits, reflect.TypeOf(uint64(0))) 236 } 237 case gmysql.MYSQL_TYPE_STRING: 238 err = writeStringColumnValue(buf, v) 239 case gmysql.MYSQL_TYPE_VARCHAR, gmysql.MYSQL_TYPE_VAR_STRING, 240 gmysql.MYSQL_TYPE_NEWDECIMAL, gmysql.MYSQL_TYPE_BIT, 241 gmysql.MYSQL_TYPE_TIMESTAMP, gmysql.MYSQL_TYPE_TIMESTAMP2, 242 gmysql.MYSQL_TYPE_DATETIME, gmysql.MYSQL_TYPE_DATETIME2, 243 gmysql.MYSQL_TYPE_TIME, gmysql.MYSQL_TYPE_TIME2, 244 gmysql.MYSQL_TYPE_YEAR, gmysql.MYSQL_TYPE_ENUM, gmysql.MYSQL_TYPE_SET, 245 gmysql.MYSQL_TYPE_BLOB, gmysql.MYSQL_TYPE_JSON, gmysql.MYSQL_TYPE_GEOMETRY: 246 // this generator is used for testing, so some types supporting can be added later. 247 err = terror.ErrBinlogGoMySQLTypeNotSupport.Generate(tp) 248 default: 249 err = terror.ErrBinlogGoMySQLTypeNotSupport.Generate(tp) 250 } 251 return buf.Bytes(), terror.Annotatef(err, "go-mysql type %d", tp) 252 } 253 254 // writeIntegerColumnValue writes integer value to bytes buffer. 255 func writeIntegerColumnValue(buf *bytes.Buffer, value interface{}, valueType reflect.Type) error { 256 if reflect.TypeOf(value) != valueType { 257 return terror.ErrBinlogColumnTypeMisMatch.Generate(value, reflect.TypeOf(value), valueType) 258 } 259 return terror.ErrBinlogWriteBinaryData.Delegate(binary.Write(buf, binary.LittleEndian, value)) 260 } 261 262 // writeStringColumnValue writes string value to bytes buffer. 263 func writeStringColumnValue(buf *bytes.Buffer, value interface{}) error { 264 str, ok := value.(string) 265 if !ok { 266 return terror.ErrBinlogColumnTypeMisMatch.Generate(value, reflect.TypeOf(value), reflect.TypeOf("")) 267 } 268 var ( 269 err error 270 length = len(str) 271 ) 272 if length < 256 { 273 err = binary.Write(buf, binary.LittleEndian, uint8(length)) 274 if err == nil { 275 err = binary.Write(buf, binary.LittleEndian, []byte(str)) 276 } 277 } else { 278 err = binary.Write(buf, binary.LittleEndian, uint16(length)) 279 if err != nil { 280 err = binary.Write(buf, binary.LittleEndian, []byte(str)) 281 } 282 } 283 return terror.ErrBinlogWriteBinaryData.Delegate(err) 284 } 285 286 // https://dev.mysql.com/doc/internals/en/query-event.html 287 // and after Q_COMMIT_TS could be found in 288 // https://github.com/mysql/mysql-server/blob/124c7ab1d6f914637521fd4463a993aa73403513/libbinlogevents/include/statement_events.h#L500 289 // Q_HRNOW (MariaDB) could be found in 290 // https://github.com/MariaDB/server/blob/09a1f0075a8d5752dd7b2940a20d86a040af1741/sql/log_event.h#L321 291 const ( 292 QFlags2Code = iota 293 QSqlModeCode 294 QCatalog 295 QAutoIncrement 296 QCharsetCode 297 QTimeZoneCode 298 QCatalogNzCode 299 QLcTimeNamesCode 300 QCharsetDatabaseCode 301 QTableMapForUpdateCode 302 QMasterDataWrittenCode 303 QInvokers 304 QUpdatedDBNames 305 QMicroseconds 306 QCommitTS 307 QCommitTS2 308 QExplicitDefaultsForTimestamp 309 QDdlLoggedWithXid 310 QDefaultCollationForUtf8mb4 311 QSqlRequirePrimaryKey 312 QDefaultTableEncryption 313 QHrnow = 128 314 ) 315 316 // https://dev.mysql.com/doc/internals/en/query-event.html 317 var statusVarsFixedLength = map[byte]int{ 318 QFlags2Code: 4, 319 QSqlModeCode: 8, 320 QAutoIncrement: 2 + 2, 321 QCharsetCode: 2 + 2 + 2, 322 QLcTimeNamesCode: 2, 323 QCharsetDatabaseCode: 2, 324 QTableMapForUpdateCode: 8, 325 QMasterDataWrittenCode: 4, 326 QMicroseconds: 3, 327 QCommitTS: 0, // unused now 328 QCommitTS2: 0, // unused now 329 // below variables could be find in 330 // https://github.com/mysql/mysql-server/blob/7d10c82196c8e45554f27c00681474a9fb86d137/libbinlogevents/src/statement_events.cpp#L312 331 QExplicitDefaultsForTimestamp: 1, 332 QDdlLoggedWithXid: 8, 333 QDefaultCollationForUtf8mb4: 2, 334 QSqlRequirePrimaryKey: 1, 335 QDefaultTableEncryption: 1, 336 // https://github.com/MariaDB/server/blob/94b45787045677c106a25ebb5aaf1273040b2ff6/sql/log_event.cc#L1619 337 QHrnow: 3, 338 } 339 340 // getSQLMode gets SQL mode from binlog statusVars, still could return a reasonable value if found error. 341 func getSQLMode(statusVars []byte) (mysql.SQLMode, error) { 342 vars, err := statusVarsToKV(statusVars) 343 b, ok := vars[QSqlModeCode] 344 345 if !ok { 346 if err == nil { 347 // only happen when this is a dummy event generated by DM 348 err = fmt.Errorf("Q_SQL_MODE_CODE not found in status_vars %v", statusVars) 349 } 350 return mysql.ModeNone, err 351 } 352 353 r := bytes.NewReader(b) 354 var v int64 355 _ = binary.Read(r, binary.LittleEndian, &v) 356 357 return mysql.SQLMode(v), err 358 } 359 360 // GetParserForStatusVars gets a parser for binlog which is suitable for its sql_mode in statusVars. 361 func GetParserForStatusVars(statusVars []byte) (*parser.Parser, error) { 362 parser2 := parser.New() 363 mode, err := getSQLMode(statusVars) 364 parser2.SetSQLMode(mode) 365 return parser2, err 366 } 367 368 // GetServerCollationByStatusVars gets server collation by binlog statusVars. 369 func GetServerCollationByStatusVars(statusVars []byte, idAndCollationMap map[int]string) (string, error) { 370 vars, err := statusVarsToKV(statusVars) 371 b, ok := vars[QCharsetCode] 372 373 if !ok { 374 if err == nil { 375 // only happen when this is a dummy event generated by DM 376 err = fmt.Errorf("Q_CHARSET_CODE not found in status_vars %v", statusVars) 377 } 378 // mysql 5.7.22 default 'latin1_swedish_ci' 379 return "latin1_swedish_ci", err 380 } 381 382 // QCharsetCode 2-byte character_set_client + 2-byte collation_connection + 2-byte collation_server 383 r := bytes.NewReader(b[4:]) 384 var v uint16 385 _ = binary.Read(r, binary.LittleEndian, &v) 386 return idAndCollationMap[int(v)], err 387 } 388 389 // GetCharsetCodecByStatusVars returns an encoding.Encoding to encode and decode original query if needed. 390 func GetCharsetCodecByStatusVars(statusVars []byte) (encoding.Encoding, error) { 391 vars, err := statusVarsToKV(statusVars) 392 b, ok := vars[QCharsetCode] 393 394 if !ok { 395 if err == nil { 396 // only happen when this is a dummy event generated by DM 397 err = fmt.Errorf("Q_CHARSET_CODE not found in status_vars %v", statusVars) 398 } 399 return nil, err 400 } 401 402 // QCharsetCode 2-byte character_set_client + 2-byte collation_connection + 2-byte collation_server 403 r := bytes.NewReader(b) 404 var v uint16 405 _ = binary.Read(r, binary.LittleEndian, &v) 406 407 charsetName, _, err2 := charset.GetCharsetInfoByID(int(v)) 408 409 // only handle GBK to minimize the change 410 switch charsetName { 411 case charset.CharsetGBK: 412 return simplifiedchinese.GBK, nil 413 default: 414 return nil, err2 415 } 416 } 417 418 // GetTimezoneByStatusVars returns the timezone of upstream for "datetime" type alter table commands. 419 // For "ALTER TABLE ADD COLUMN x DATETIME", not for "ALTER TABLE ADD COLUMN x TIMESTAMP". 420 func GetTimezoneByStatusVars(statusVars []byte, upstreamTZStr string) (string, error) { 421 vars, err := statusVarsToKV(statusVars) 422 b, ok := vars[QTimeZoneCode] 423 424 // here do not check err first to increase robustness 425 // when statusVarsToKV() meets errors like unrecognized key or EOF, caller may still work properly if the wanted key has been parsed 426 if !ok { 427 if err == nil { 428 // happen when this is a dummy event generated by DM 429 // or using some DDL commands like "ALTER TABLE ADD COLUMN x TIMESTAMP" which does not contain information of time zone 430 err = fmt.Errorf("timezone not found in status_vars %v", statusVars) 431 } 432 return "", err 433 } 434 435 // QTimeZoneCode 1-byte length + <length> chars of the timezone 436 s := string(b[1:]) 437 if s == "SYSTEM" { 438 // replace it with the absolute upstream time zone 439 s = upstreamTZStr 440 } 441 return s, err 442 } 443 444 // if returned error is `io.EOF`, it means UnexpectedEOF because we handled expected `io.EOF` as success 445 // returned map should not be nil for other usage. 446 func statusVarsToKV(statusVars []byte) (map[byte][]byte, error) { 447 r := bytes.NewReader(statusVars) 448 vars := make(map[byte][]byte) 449 var value []byte 450 451 // NOTE: this closure modifies variable `value` 452 appendLengthThenCharsToValue := func() error { 453 length, err := r.ReadByte() 454 if err != nil { 455 return err 456 } 457 value = append(value, length) 458 459 buf := make([]byte, length) 460 n, err := r.Read(buf) 461 if err != nil { 462 return err 463 } 464 if n != int(length) { 465 return io.EOF 466 } 467 value = append(value, buf...) 468 return nil 469 } 470 471 generateError := func(err error) (map[byte][]byte, error) { 472 offset, _ := r.Seek(0, io.SeekCurrent) 473 return vars, terror.ErrBinlogStatusVarsParse.Delegate(err, statusVars, offset) 474 } 475 476 for { 477 // reset value 478 value = make([]byte, 0) 479 key, err := r.ReadByte() 480 if err == io.EOF { 481 break 482 } 483 if err != nil { 484 return generateError(err) 485 } 486 487 if _, ok := vars[key]; ok { 488 return generateError(errors.New("duplicate key")) 489 } 490 491 if length, ok := statusVarsFixedLength[key]; ok { 492 value = make([]byte, length) 493 n, err2 := r.Read(value) 494 if err2 != nil || n != length { 495 return generateError(io.EOF) 496 } 497 498 vars[key] = value 499 continue 500 } 501 502 // get variable-length value of according key and save it in `value` 503 switch key { 504 // 1-byte length + <length> chars of the catalog + '0'-char 505 case QCatalog: 506 if err = appendLengthThenCharsToValue(); err != nil { 507 return generateError(err) 508 } 509 510 b, err2 := r.ReadByte() 511 if err2 != nil { 512 return generateError(err) 513 } 514 // nolint:makezero 515 value = append(value, b) 516 // 1-byte length + <length> chars of the timezone/catalog 517 case QTimeZoneCode, QCatalogNzCode: 518 if err = appendLengthThenCharsToValue(); err != nil { 519 return generateError(err) 520 } 521 // 1-byte length + <length> bytes username and 1-byte length + <length> bytes hostname 522 case QInvokers: 523 if err = appendLengthThenCharsToValue(); err != nil { 524 return generateError(err) 525 } 526 if err = appendLengthThenCharsToValue(); err != nil { 527 return generateError(err) 528 } 529 // 1-byte count + <count> \0 terminated string 530 case QUpdatedDBNames: 531 count, err := r.ReadByte() 532 if err != nil { 533 return generateError(err) 534 } 535 // nolint:makezero 536 value = append(value, count) 537 // if count is 254 (OVER_MAX_DBS_IN_EVENT_MTS), there's no following DB names 538 // https://github.com/mysql/mysql-server/blob/ee4455a33b10f1b1886044322e4893f587b319ed/libbinlogevents/include/binlog_event.h#L107 539 if count == 254 { 540 break 541 } 542 543 buf := make([]byte, 0, 128) 544 b := byte(1) // initialize to any non-zero value 545 for ; count > 0; count-- { 546 // read one zero-terminated string 547 for b != 0 { 548 b, err = r.ReadByte() 549 if err != nil { 550 return generateError(err) 551 } 552 buf = append(buf, b) 553 } 554 b = byte(1) // reset to any non-zero value 555 } 556 // nolint:makezero 557 value = append(value, buf...) 558 default: 559 return generateError(errors.New("unrecognized key")) 560 } 561 vars[key] = value 562 } 563 564 return vars, nil 565 } 566 567 // GetGTIDStr gets GTID string representation from a GTID event or MariaDB GTID evnets. 568 // learn from: https://github.com/go-mysql-org/go-mysql/blob/c6ab05a85eb86dc51a27ceed6d2f366a32874a24/replication/binlogsyncer.go#L732-L749 569 func GetGTIDStr(e *replication.BinlogEvent) (string, error) { 570 switch ev := e.Event.(type) { 571 case *replication.GTIDEvent: 572 u, _ := uuid.FromBytes(ev.SID) 573 return fmt.Sprintf("%s:%d", u.String(), ev.GNO), nil 574 case *replication.MariadbGTIDEvent: 575 GTID := ev.GTID 576 return fmt.Sprintf("%d-%d-%d", GTID.DomainID, GTID.ServerID, GTID.SequenceNumber), nil 577 default: 578 return "", fmt.Errorf("unsupported event type %d", e.Header.EventType) 579 } 580 }