vitess.io/vitess@v0.16.2/go/mysql/replication_position.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/json" 21 "fmt" 22 "strings" 23 24 "vitess.io/vitess/go/vt/proto/vtrpc" 25 "vitess.io/vitess/go/vt/vterrors" 26 ) 27 28 const ( 29 // MaximumPositionSize is the maximum size of a 30 // replication position. It is used as the maximum column size in the _vt.reparent_journal and 31 // other related tables. A row has a maximum size of 65535 bytes. So 32 // we want to stay under that. We use VARBINARY so the 33 // character set doesn't matter, we only store ascii 34 // characters anyway. 35 MaximumPositionSize = 64000 36 ) 37 38 // Position represents the information necessary to describe which 39 // transactions a server has seen, so that it can request a replication stream 40 // from a new source that picks up where it left off. 41 // 42 // This must be a concrete struct because custom Unmarshalers can't be 43 // registered on an interface. 44 // 45 // The == operator should not be used with Position, because the 46 // underlying GTIDSet might use slices, which are not comparable. Using == in 47 // those cases will result in a run-time panic. 48 type Position struct { 49 // This is a zero byte compile-time check that no one is trying to 50 // use == or != with Position. Without this, we won't know there's 51 // a problem until the runtime panic. Note that this must not be 52 // the last field of the struct, or else the Go compiler will add 53 // padding to prevent pointers to this field from becoming invalid. 54 _ [0]struct{ _ []byte } 55 56 // GTIDSet is the underlying GTID set. It must not be anonymous, 57 // or else Position would itself also implement the GTIDSet interface. 58 GTIDSet GTIDSet 59 } 60 61 // Equal returns true if this position is equal to another. 62 func (rp Position) Equal(other Position) bool { 63 if rp.GTIDSet == nil { 64 return other.GTIDSet == nil 65 } 66 return rp.GTIDSet.Equal(other.GTIDSet) 67 } 68 69 // AtLeast returns true if this position is equal to or after another. 70 func (rp Position) AtLeast(other Position) bool { 71 // The underlying call to the Contains method of the GTIDSet interface 72 // does not guarantee handling the nil cases correctly. 73 // So, we have to do nil case handling here 74 if other.GTIDSet == nil { 75 // If the other GTIDSet is nil, then it is contained 76 // in all possible GTIDSets, even nil ones. 77 return true 78 } 79 if rp.GTIDSet == nil { 80 // Here rp GTIDSet is nil but the other GTIDSet isn't. 81 // So it is not contained in the rp GTIDSet. 82 return false 83 } 84 return rp.GTIDSet.Contains(other.GTIDSet) 85 } 86 87 // String returns a string representation of the underlying GTIDSet. 88 // If the set is nil, it returns "<nil>" in the style of Sprintf("%v", nil). 89 func (rp Position) String() string { 90 if rp.GTIDSet == nil { 91 return "<nil>" 92 } 93 return rp.GTIDSet.String() 94 } 95 96 // IsZero returns true if this is the zero value, Position{}. 97 func (rp Position) IsZero() bool { 98 return rp.GTIDSet == nil 99 } 100 101 // AppendGTID returns a new Position that represents the position 102 // after the given GTID is replicated. 103 func AppendGTID(rp Position, gtid GTID) Position { 104 if gtid == nil { 105 return rp 106 } 107 if rp.GTIDSet == nil { 108 return Position{GTIDSet: gtid.GTIDSet()} 109 } 110 return Position{GTIDSet: rp.GTIDSet.AddGTID(gtid)} 111 } 112 113 // MustParsePosition calls ParsePosition and panics 114 // on error. 115 func MustParsePosition(flavor, value string) Position { 116 rp, err := ParsePosition(flavor, value) 117 if err != nil { 118 panic(err) 119 } 120 return rp 121 } 122 123 // EncodePosition returns a string that contains both the flavor 124 // and value of the Position, so that the correct parser can be 125 // selected when that string is passed to DecodePosition. 126 func EncodePosition(rp Position) string { 127 if rp.GTIDSet == nil { 128 return "" 129 } 130 return fmt.Sprintf("%s/%s", rp.GTIDSet.Flavor(), rp.GTIDSet.String()) 131 } 132 133 // DecodePosition converts a string in the format returned by 134 // EncodePosition back into a Position value with the 135 // correct underlying flavor. 136 func DecodePosition(s string) (rp Position, err error) { 137 if s == "" { 138 return rp, nil 139 } 140 141 flav, gtid, ok := strings.Cut(s, "/") 142 if !ok { 143 // There is no flavor. Try looking for a default parser. 144 return ParsePosition("", s) 145 } 146 return ParsePosition(flav, gtid) 147 } 148 149 // ParsePosition calls the parser for the specified flavor. 150 func ParsePosition(flavor, value string) (rp Position, err error) { 151 parser := gtidSetParsers[flavor] 152 if parser == nil { 153 return rp, vterrors.Errorf(vtrpc.Code_INTERNAL, "parse error: unknown GTIDSet flavor %#v", flavor) 154 } 155 gtidSet, err := parser(value) 156 if err != nil { 157 return rp, err 158 } 159 rp.GTIDSet = gtidSet 160 return rp, err 161 } 162 163 // MarshalJSON implements encoding/json.Marshaler. 164 func (rp Position) MarshalJSON() ([]byte, error) { 165 return json.Marshal(EncodePosition(rp)) 166 } 167 168 // UnmarshalJSON implements encoding/json.Unmarshaler. 169 func (rp *Position) UnmarshalJSON(buf []byte) error { 170 var s string 171 err := json.Unmarshal(buf, &s) 172 if err != nil { 173 return err 174 } 175 176 *rp, err = DecodePosition(s) 177 if err != nil { 178 return err 179 } 180 return nil 181 } 182 183 // MatchesFlavor will take a flavor string, and return whether the positions GTIDSet matches the supplied flavor. 184 // The caller should use the constants Mysql56FlavorID, MariadbFlavorID, or FilePosFlavorID when supplying the flavor string. 185 func (rp *Position) MatchesFlavor(flavor string) bool { 186 switch flavor { 187 case Mysql56FlavorID: 188 _, matches := rp.GTIDSet.(Mysql56GTIDSet) 189 return matches 190 case MariadbFlavorID: 191 _, matches := rp.GTIDSet.(MariadbGTIDSet) 192 return matches 193 case FilePosFlavorID: 194 _, matches := rp.GTIDSet.(filePosGTID) 195 return matches 196 } 197 return false 198 } 199 200 // Comparable returns whether the receiver is comparable to the supplied position, based on whether one 201 // of the two positions contains the other. 202 func (rp *Position) Comparable(other Position) bool { 203 return rp.GTIDSet.Contains(other.GTIDSet) || other.GTIDSet.Contains(rp.GTIDSet) 204 } 205 206 // AllPositionsComparable returns true if all positions in the supplied list are comparable with one another, and false 207 // if any are non-comparable. 208 func AllPositionsComparable(positions []Position) bool { 209 for i := 0; i < len(positions); i++ { 210 for j := i + 1; j < len(positions); j++ { 211 if !positions[i].Comparable(positions[j]) { 212 return false 213 } 214 } 215 } 216 217 return true 218 }