vitess.io/vitess@v0.16.2/go/mysql/mysql56_gtid_set.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 "bytes" 21 "encoding/binary" 22 "strconv" 23 "strings" 24 25 "golang.org/x/exp/slices" 26 27 "vitess.io/vitess/go/vt/proto/vtrpc" 28 "vitess.io/vitess/go/vt/vterrors" 29 ) 30 31 type interval struct { 32 start, end int64 33 } 34 35 func (iv interval) contains(other interval) bool { 36 return iv.start <= other.start && other.end <= iv.end 37 } 38 39 func parseInterval(s string) (interval, error) { 40 part0, part1, twoParts := strings.Cut(s, "-") 41 42 start, err := strconv.ParseUint(part0, 10, 63) 43 if err != nil { 44 return interval{}, vterrors.Wrapf(err, "invalid interval (%q)", s) 45 } 46 if start < 1 { 47 return interval{}, vterrors.Errorf(vtrpc.Code_INTERNAL, "invalid interval (%q): start must be > 0", s) 48 } 49 50 if twoParts { 51 end, err := strconv.ParseUint(part1, 10, 63) 52 if err != nil { 53 return interval{}, vterrors.Wrapf(err, "invalid interval (%q)", s) 54 } 55 return interval{start: int64(start), end: int64(end)}, nil 56 } 57 return interval{start: int64(start), end: int64(start)}, nil 58 } 59 60 // ParseMysql56GTIDSet is registered as a GTIDSet parser. 61 // 62 // https://dev.mysql.com/doc/refman/5.6/en/replication-gtids-concepts.html 63 func ParseMysql56GTIDSet(s string) (Mysql56GTIDSet, error) { 64 set := make(Mysql56GTIDSet) 65 input := s 66 67 // gtid_set: uuid_set [, uuid_set] ... 68 for len(input) > 0 { 69 var uuidSet string 70 if idx := strings.IndexByte(input, ','); idx >= 0 { 71 uuidSet = input[:idx] 72 input = input[idx+1:] 73 } else { 74 uuidSet = input 75 input = "" 76 } 77 78 uuidSet = strings.TrimSpace(uuidSet) 79 if uuidSet == "" { 80 continue 81 } 82 83 // uuid_set: uuid:interval[:interval]... 84 head, tail, ok := strings.Cut(uuidSet, ":") 85 if !ok { 86 return nil, vterrors.Errorf(vtrpc.Code_INTERNAL, "invalid MySQL 5.6 GTID set (%q): expected uuid:interval", s) 87 } 88 89 // Parse Server ID. 90 sid, err := ParseSID(head) 91 if err != nil { 92 return nil, vterrors.Wrapf(err, "invalid MySQL 5.6 GTID set (%q)", s) 93 } 94 95 intervals := make([]interval, 0, strings.Count(tail, ":")+1) 96 for len(tail) > 0 { 97 if idx := strings.IndexByte(tail, ':'); idx >= 0 { 98 head = tail[:idx] 99 tail = tail[idx+1:] 100 } else { 101 head = tail 102 tail = "" 103 } 104 105 iv, err := parseInterval(head) 106 if err != nil { 107 return nil, vterrors.Wrapf(err, "invalid MySQL 5.6 GTID set (%q)", s) 108 } 109 if iv.end < iv.start { 110 // According to MySQL 5.6 code: 111 // "The end of an interval may be 0, but any interval that has an 112 // endpoint that is smaller than the start is discarded." 113 continue 114 } 115 intervals = append(intervals, iv) 116 } 117 if len(intervals) == 0 { 118 // We might have discarded all the intervals. 119 continue 120 } 121 122 if sidIntervals, ok := set[sid]; ok { 123 // SID already exists, we append 124 // Example: "00010203-0405-0607-0809-0a0b0c0d0e0f:1-5,00010203-0405-0607-0809-0a0b0c0d0e0f:10-20" 125 // turns to: "00010203-0405-0607-0809-0a0b0c0d0e0f:1-5:10-20" 126 intervals = append(sidIntervals, intervals...) 127 } 128 // Internally we expect intervals to be stored in order. 129 slices.SortFunc(intervals, func(a, b interval) bool { 130 return a.start < b.start 131 }) 132 set[sid] = intervals 133 } 134 135 return set, nil 136 } 137 138 // Mysql56GTIDSet implements GTIDSet for MySQL 5.6. 139 type Mysql56GTIDSet map[SID][]interval 140 141 // SIDs returns a sorted list of SIDs in the set. 142 func (set Mysql56GTIDSet) SIDs() []SID { 143 sids := make([]SID, 0, len(set)) 144 for sid := range set { 145 sids = append(sids, sid) 146 } 147 sortSIDs(sids) 148 return sids 149 } 150 151 func sortSIDs(sids []SID) { 152 slices.SortFunc(sids, func(a, b SID) bool { 153 return bytes.Compare(a[:], b[:]) < 0 154 }) 155 } 156 157 // String implements GTIDSet. 158 func (set Mysql56GTIDSet) String() string { 159 var buf strings.Builder 160 for i, sid := range set.SIDs() { 161 if i != 0 { 162 buf.WriteByte(',') 163 } 164 buf.WriteString(sid.String()) 165 166 for _, interval := range set[sid] { 167 buf.WriteByte(':') 168 buf.WriteString(strconv.FormatInt(interval.start, 10)) 169 170 if interval.end != interval.start { 171 buf.WriteByte('-') 172 buf.WriteString(strconv.FormatInt(interval.end, 10)) 173 } 174 } 175 } 176 return buf.String() 177 } 178 179 // Last returns the last gtid as string 180 // For gtidset having multiple SIDs or multiple intervals 181 // it just returns the last SID with last interval 182 func (set Mysql56GTIDSet) Last() string { 183 var buf strings.Builder 184 if len(set.SIDs()) > 0 { 185 sid := set.SIDs()[len(set.SIDs())-1] 186 buf.WriteString(sid.String()) 187 sequences := set[sid] 188 if len(sequences) > 0 { 189 buf.WriteByte(':') 190 lastInterval := sequences[len(sequences)-1] 191 buf.WriteString(strconv.FormatInt(lastInterval.end, 10)) 192 } 193 } 194 return buf.String() 195 } 196 197 // Flavor implements GTIDSet. 198 func (Mysql56GTIDSet) Flavor() string { return Mysql56FlavorID } 199 200 // ContainsGTID implements GTIDSet. 201 func (set Mysql56GTIDSet) ContainsGTID(gtid GTID) bool { 202 gtid56, ok := gtid.(Mysql56GTID) 203 if !ok { 204 return false 205 } 206 207 for _, iv := range set[gtid56.Server] { 208 if iv.start > gtid56.Sequence { 209 // We assume intervals are sorted, so we can skip the rest. 210 return false 211 } 212 if gtid56.Sequence <= iv.end { 213 // Now we know that: start <= Sequence <= end. 214 return true 215 } 216 } 217 // Server wasn't in the set, or no interval contained gtid. 218 return false 219 } 220 221 // Contains implements GTIDSet. 222 func (set Mysql56GTIDSet) Contains(other GTIDSet) bool { 223 if other == nil { 224 return false 225 } 226 227 other56, ok := other.(Mysql56GTIDSet) 228 if !ok { 229 return false 230 } 231 232 // Check each SID in the other set. 233 for sid, otherIntervals := range other56 { 234 i := 0 235 intervals := set[sid] 236 count := len(intervals) 237 238 // Check each interval for this SID in the other set. 239 for _, iv := range otherIntervals { 240 // Check that interval against each of our intervals. 241 // Intervals are monotonically increasing, 242 // so we don't need to reset the index each time. 243 for { 244 if i >= count { 245 // We ran out of intervals to check against. 246 return false 247 } 248 if intervals[i].contains(iv) { 249 // Yes it's covered. Go on to the next one. 250 break 251 } 252 i++ 253 } 254 } 255 } 256 257 // No uncovered intervals were found. 258 return true 259 } 260 261 // Equal implements GTIDSet. 262 func (set Mysql56GTIDSet) Equal(other GTIDSet) bool { 263 other56, ok := other.(Mysql56GTIDSet) 264 if !ok { 265 return false 266 } 267 268 // Check for same number of SIDs. 269 if len(set) != len(other56) { 270 return false 271 } 272 273 // Compare each SID. 274 for sid, intervals := range set { 275 otherIntervals := other56[sid] 276 277 // Check for same number of intervals. 278 if len(intervals) != len(otherIntervals) { 279 return false 280 } 281 282 // Compare each interval. 283 // Since intervals are sorted, they have to be in the same order. 284 for i, iv := range intervals { 285 if iv != otherIntervals[i] { 286 return false 287 } 288 } 289 } 290 291 // No discrepancies were found. 292 return true 293 } 294 295 // AddGTID implements GTIDSet. 296 func (set Mysql56GTIDSet) AddGTID(gtid GTID) GTIDSet { 297 gtid56, ok := gtid.(Mysql56GTID) 298 if !ok { 299 return set 300 } 301 302 // If it's already in the set, we can return the same instance. 303 // This is safe because GTIDSets are immutable. 304 if set.ContainsGTID(gtid) { 305 return set 306 } 307 308 // Make a copy and add the new GTID in the proper place. 309 // This function is not supposed to modify the original set. 310 newSet := make(Mysql56GTIDSet) 311 312 added := false 313 314 for sid, intervals := range set { 315 newIntervals := make([]interval, 0, len(intervals)) 316 317 if sid == gtid56.Server { 318 // Look for the right place to add this GTID. 319 for _, iv := range intervals { 320 if !added { 321 switch { 322 case gtid56.Sequence == iv.start-1: 323 // Expand the interval at the beginning. 324 iv.start = gtid56.Sequence 325 added = true 326 case gtid56.Sequence == iv.end+1: 327 // Expand the interval at the end. 328 iv.end = gtid56.Sequence 329 added = true 330 case gtid56.Sequence < iv.start-1: 331 // The next interval is beyond the new GTID, but it can't 332 // be expanded, so we have to insert a new interval. 333 newIntervals = append(newIntervals, interval{start: gtid56.Sequence, end: gtid56.Sequence}) 334 added = true 335 } 336 } 337 // Check if this interval can be merged with the previous one. 338 count := len(newIntervals) 339 if count != 0 && iv.start == newIntervals[count-1].end+1 { 340 // Merge instead of appending. 341 newIntervals[count-1].end = iv.end 342 } else { 343 // Can't be merged. 344 newIntervals = append(newIntervals, iv) 345 } 346 } 347 } else { 348 // Just copy everything. 349 newIntervals = append(newIntervals, intervals...) 350 } 351 352 newSet[sid] = newIntervals 353 } 354 355 if !added { 356 // There wasn't any place to insert the new GTID, so just append it 357 // as a new interval. 358 newSet[gtid56.Server] = append(newSet[gtid56.Server], interval{start: gtid56.Sequence, end: gtid56.Sequence}) 359 } 360 361 return newSet 362 } 363 364 // Union implements GTIDSet.Union(). 365 func (set Mysql56GTIDSet) Union(other GTIDSet) GTIDSet { 366 if set == nil && other != nil { 367 return other 368 } 369 if set == nil || other == nil { 370 return set 371 } 372 mydbOther, ok := other.(Mysql56GTIDSet) 373 if !ok { 374 return set 375 } 376 377 // Make a copy and add the new GTID in the proper place. 378 // This function is not supposed to modify the original set. 379 newSet := make(Mysql56GTIDSet) 380 381 for otherSID, otherIntervals := range mydbOther { 382 intervals, ok := set[otherSID] 383 if !ok { 384 // No matching server id, so we must add it from other set. 385 newSet[otherSID] = otherIntervals 386 continue 387 } 388 389 // Found server id match between sets, so now we need to add each interval. 390 s1 := intervals 391 s2 := otherIntervals 392 var nextInterval interval 393 var newIntervals []interval 394 395 // While our stacks have intervals to process, do work. 396 for popInterval(&nextInterval, &s1, &s2) { 397 if len(newIntervals) == 0 { 398 newIntervals = append(newIntervals, nextInterval) 399 continue 400 } 401 402 activeInterval := &newIntervals[len(newIntervals)-1] 403 404 if nextInterval.end <= activeInterval.end { 405 // We hit an interval whose start was after or equal to the previous interval's start, but whose 406 // end is prior to the active intervals end. Skip to next interval. 407 continue 408 } 409 410 if nextInterval.start > activeInterval.end+1 { 411 // We found a gap, so we need to start a new interval. 412 newIntervals = append(newIntervals, nextInterval) 413 continue 414 } 415 416 // Extend our active interval. 417 activeInterval.end = nextInterval.end 418 } 419 420 newSet[otherSID] = newIntervals 421 } 422 423 // Add any intervals from SIDs that exist in caller set, but don't exist in other set. 424 for sid, intervals := range set { 425 if _, ok := newSet[sid]; !ok { 426 newSet[sid] = intervals 427 } 428 } 429 430 return newSet 431 } 432 433 // SIDBlock returns the binary encoding of a MySQL 5.6 GTID set as expected 434 // by internal commands that refer to an "SID block". 435 // 436 // e.g. https://dev.mysql.com/doc/internals/en/com-binlog-dump-gtid.html 437 func (set Mysql56GTIDSet) SIDBlock() []byte { 438 buf := &bytes.Buffer{} 439 440 // Number of SIDs. 441 binary.Write(buf, binary.LittleEndian, uint64(len(set))) 442 443 for _, sid := range set.SIDs() { 444 buf.Write(sid[:]) 445 446 // Number of intervals. 447 intervals := set[sid] 448 binary.Write(buf, binary.LittleEndian, uint64(len(intervals))) 449 450 for _, iv := range intervals { 451 binary.Write(buf, binary.LittleEndian, iv.start) 452 // MySQL's internal form for intervals adds 1 to the end value. 453 // See Gtid_set::add_gtid_text() in rpl_gtid_set.cc for example. 454 binary.Write(buf, binary.LittleEndian, iv.end+1) 455 } 456 } 457 458 return buf.Bytes() 459 } 460 461 // Difference will supply the difference between the receiver and supplied Mysql56GTIDSets, and supply the result 462 // as a Mysql56GTIDSet. 463 func (set Mysql56GTIDSet) Difference(other Mysql56GTIDSet) Mysql56GTIDSet { 464 if other == nil || set == nil { 465 return set 466 } 467 468 // Make a fresh, empty set to hold the new value. 469 // This function is not supposed to modify the original set. 470 differenceSet := make(Mysql56GTIDSet) 471 472 for sid, intervals := range set { 473 otherIntervals, ok := other[sid] 474 if !ok { 475 // We didn't find SID in other set, so diff should include all intervals for sid unique to receiver. 476 differenceSet[sid] = intervals 477 continue 478 } 479 480 // Found server id match between sets, so now we need to subtract each interval. 481 var diffIntervals []interval 482 advance := func() bool { 483 if len(intervals) == 0 { 484 return false 485 } 486 diffIntervals = append(diffIntervals, intervals[0]) 487 intervals = intervals[1:] 488 return true 489 } 490 491 var otherInterval interval 492 advanceOther := func() bool { 493 if len(otherIntervals) == 0 { 494 return false 495 } 496 otherInterval = otherIntervals[0] 497 otherIntervals = otherIntervals[1:] 498 return true 499 } 500 501 if !advance() { 502 continue 503 } 504 if !advanceOther() { 505 differenceSet[sid] = intervals 506 continue 507 } 508 509 diffLoop: 510 for { 511 iv := diffIntervals[len(diffIntervals)-1] 512 513 switch { 514 case iv.end < otherInterval.start: 515 // [1, 2] - [3, 5] 516 // Need to skip to next s1 interval. This one is completely before otherInterval even starts. It's a diff in whole. 517 if !advance() { 518 break diffLoop 519 } 520 521 case iv.start > otherInterval.end: 522 // [3, 5] - [1, 2] 523 // Interval is completely past other interval. We need a valid other to compare against. 524 if !advanceOther() { 525 break diffLoop 526 } 527 528 case iv.start >= otherInterval.start && iv.end <= otherInterval.end: 529 // [3, 4] - [1, 5] 530 // Interval is completed contained. Pop off diffIntervals, and advance to next s1. 531 diffIntervals = diffIntervals[:len(diffIntervals)-1] 532 if !advance() { 533 break diffLoop 534 } 535 536 case iv.start < otherInterval.start && iv.end >= otherInterval.start && iv.end <= otherInterval.end: 537 // [1, 4] - [3, 5] 538 // We have a unique interval prior to where otherInterval starts and should adjust end to match this piece. 539 diffIntervals[len(diffIntervals)-1].end = otherInterval.start - 1 540 541 if !advance() { 542 break diffLoop 543 } 544 545 case iv.start >= otherInterval.start && iv.start <= otherInterval.end && iv.end > otherInterval.end: 546 // [3, 7] - [1, 5] 547 // We have an end piece to deal with. 548 diffIntervals[len(diffIntervals)-1].start = otherInterval.end + 1 549 550 // We need to pop s2 at this point. s1's new interval is fully past otherInterval, so no point in comparing 551 // this one next round. 552 if !advanceOther() { 553 break diffLoop 554 } 555 556 case iv.start < otherInterval.start && iv.end > otherInterval.end: 557 // [1, 7] - [3, 4] 558 // End is strictly greater. In this case we need to create an extra diff interval. We'll deal with any necessary trimming of it next round. 559 diffIntervals[len(diffIntervals)-1].end = otherInterval.start - 1 560 diffIntervals = append(diffIntervals, interval{start: otherInterval.end + 1, end: iv.end}) 561 562 // We need to pop s2 at this point. s1's new interval is fully past otherInterval, so no point in comparing 563 // this one next round. 564 if !advanceOther() { 565 break diffLoop 566 } 567 568 default: 569 panic("This should never happen.") 570 } 571 } 572 573 if len(intervals) != 0 { 574 // If we've gotten to this point, then we have intervals that exist beyond the bounds of any intervals in otherIntervals, and they 575 // are all diffs and should be added in whole. 576 diffIntervals = append(diffIntervals, intervals...) 577 } 578 579 if len(diffIntervals) == 0 { 580 delete(differenceSet, sid) 581 } else { 582 differenceSet[sid] = diffIntervals 583 } 584 } 585 586 return differenceSet 587 } 588 589 // NewMysql56GTIDSetFromSIDBlock builds a Mysql56GTIDSet from parsing a SID Block. 590 // This is the reverse of the SIDBlock method. 591 // 592 // Expected format: 593 // 594 // # bytes field 595 // 8 nSIDs 596 // 597 // (nSIDs times) 598 // 599 // 16 SID 600 // 8 nIntervals 601 // 602 // (nIntervals times) 603 // 604 // 8 start 605 // 8 end 606 func NewMysql56GTIDSetFromSIDBlock(data []byte) (Mysql56GTIDSet, error) { 607 buf := bytes.NewReader(data) 608 var set Mysql56GTIDSet = make(map[SID][]interval) 609 var nSIDs uint64 610 if err := binary.Read(buf, binary.LittleEndian, &nSIDs); err != nil { 611 return nil, vterrors.Wrapf(err, "cannot read nSIDs") 612 } 613 for i := uint64(0); i < nSIDs; i++ { 614 var sid SID 615 if c, err := buf.Read(sid[:]); err != nil || c != 16 { 616 return nil, vterrors.Errorf(vtrpc.Code_INTERNAL, "cannot read SID %v: %v %v", i, err, c) 617 } 618 var nIntervals uint64 619 if err := binary.Read(buf, binary.LittleEndian, &nIntervals); err != nil { 620 return nil, vterrors.Wrapf(err, "cannot read nIntervals %v", i) 621 } 622 for j := uint64(0); j < nIntervals; j++ { 623 var start, end uint64 624 if err := binary.Read(buf, binary.LittleEndian, &start); err != nil { 625 return nil, vterrors.Wrapf(err, "cannot read start %v/%v", i, j) 626 } 627 if err := binary.Read(buf, binary.LittleEndian, &end); err != nil { 628 return nil, vterrors.Wrapf(err, "cannot read end %v/%v", i, j) 629 } 630 set[sid] = append(set[sid], interval{ 631 start: int64(start), 632 end: int64(end - 1), 633 }) 634 } 635 } 636 return set, nil 637 } 638 639 // popInterval will look at the two pre-sorted interval stacks supplied, and if at least one of the stacks is non-empty 640 // will mutate the destination interval with the next earliest interval based on start sequence. 641 // popInterval will return true if we were able to pop, or false if both stacks are now empty. 642 func popInterval(dst *interval, s1, s2 *[]interval) bool { 643 if len(*s1) == 0 && len(*s2) == 0 { 644 return false 645 } 646 647 // Find which intervals list has earliest start. 648 if len(*s2) == 0 || (len(*s1) != 0 && (*s1)[0].start <= (*s2)[0].start) { 649 *dst = (*s1)[0] 650 // Progress pointer since this stack has earliest. 651 *s1 = (*s1)[1:] 652 } else { 653 *dst = (*s2)[0] 654 // Progress pointer since this stack has earliest. 655 *s2 = (*s2)[1:] 656 } 657 658 return true 659 } 660 661 func init() { 662 gtidSetParsers[Mysql56FlavorID] = func(s string) (GTIDSet, error) { 663 return ParseMysql56GTIDSet(s) 664 } 665 } 666 667 // Subtract takes in two Mysql56GTIDSets as strings and subtracts the second from the first 668 // The result is also a string. 669 // An error is thrown if parsing is not possible for either GTIDSets 670 func Subtract(lhs, rhs string) (string, error) { 671 lhsSet, err := ParseMysql56GTIDSet(lhs) 672 if err != nil { 673 return "", err 674 } 675 rhsSet, err := ParseMysql56GTIDSet(rhs) 676 if err != nil { 677 return "", err 678 } 679 diffSet := lhsSet.Difference(rhsSet) 680 return diffSet.String(), nil 681 }