vitess.io/vitess@v0.16.2/go/mysql/flavor_filepos.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 "context" 21 "fmt" 22 "io" 23 "strconv" 24 "strings" 25 "time" 26 27 vtrpcpb "vitess.io/vitess/go/vt/proto/vtrpc" 28 "vitess.io/vitess/go/vt/vterrors" 29 ) 30 31 type filePosFlavor struct { 32 format BinlogFormat 33 file string 34 savedEvent BinlogEvent 35 } 36 37 // newFilePosFlavor creates a new filePos flavor. 38 func newFilePosFlavor() flavor { 39 return &filePosFlavor{} 40 } 41 42 // primaryGTIDSet is part of the Flavor interface. 43 func (flv *filePosFlavor) primaryGTIDSet(c *Conn) (GTIDSet, error) { 44 qr, err := c.ExecuteFetch("SHOW MASTER STATUS", 100, true /* wantfields */) 45 if err != nil { 46 return nil, err 47 } 48 if len(qr.Rows) == 0 { 49 return nil, ErrNoPrimaryStatus 50 } 51 52 resultMap, err := resultToMap(qr) 53 if err != nil { 54 return nil, err 55 } 56 pos, err := strconv.Atoi(resultMap["Position"]) 57 if err != nil { 58 return nil, fmt.Errorf("invalid FilePos GTID (%v): expecting pos to be an integer", resultMap["Position"]) 59 } 60 61 return filePosGTID{ 62 file: resultMap["File"], 63 pos: pos, 64 }, nil 65 } 66 67 // purgedGTIDSet is part of the Flavor interface. 68 func (flv *filePosFlavor) purgedGTIDSet(c *Conn) (GTIDSet, error) { 69 return nil, nil 70 } 71 72 // gtidMode is part of the Flavor interface. 73 func (flv *filePosFlavor) gtidMode(c *Conn) (string, error) { 74 qr, err := c.ExecuteFetch("select @@global.gtid_mode", 1, false) 75 if err != nil { 76 return "", err 77 } 78 if len(qr.Rows) != 1 || len(qr.Rows[0]) != 1 { 79 return "", vterrors.Errorf(vtrpcpb.Code_INTERNAL, "unexpected result format for gtid_mode: %#v", qr) 80 } 81 return qr.Rows[0][0].ToString(), nil 82 } 83 84 // serverUUID is part of the Flavor interface. 85 func (flv *filePosFlavor) serverUUID(c *Conn) (string, error) { 86 // keep @@global as lowercase, as some servers like the Ripple binlog server only honors a lowercase `global` value 87 qr, err := c.ExecuteFetch("SELECT @@global.server_uuid", 1, false) 88 if err != nil { 89 return "", err 90 } 91 if len(qr.Rows) != 1 || len(qr.Rows[0]) != 1 { 92 return "", vterrors.Errorf(vtrpcpb.Code_INTERNAL, "unexpected result format for server_uuid: %#v", qr) 93 } 94 return qr.Rows[0][0].ToString(), nil 95 } 96 97 func (flv *filePosFlavor) startReplicationCommand() string { 98 return "unsupported" 99 } 100 101 func (flv *filePosFlavor) restartReplicationCommands() []string { 102 return []string{"unsupported"} 103 } 104 105 func (flv *filePosFlavor) stopReplicationCommand() string { 106 return "unsupported" 107 } 108 109 func (flv *filePosFlavor) stopIOThreadCommand() string { 110 return "unsupported" 111 } 112 113 func (flv *filePosFlavor) stopSQLThreadCommand() string { 114 return "unsupported" 115 } 116 117 func (flv *filePosFlavor) startSQLThreadCommand() string { 118 return "unsupported" 119 } 120 121 // sendBinlogDumpCommand is part of the Flavor interface. 122 func (flv *filePosFlavor) sendBinlogDumpCommand(c *Conn, serverID uint32, binlogFilename string, startPos Position) error { 123 rpos, ok := startPos.GTIDSet.(filePosGTID) 124 if !ok { 125 return fmt.Errorf("startPos.GTIDSet is wrong type - expected filePosGTID, got: %#v", startPos.GTIDSet) 126 } 127 128 flv.file = rpos.file 129 return c.WriteComBinlogDump(serverID, rpos.file, uint32(rpos.pos), 0) 130 } 131 132 // readBinlogEvent is part of the Flavor interface. 133 func (flv *filePosFlavor) readBinlogEvent(c *Conn) (BinlogEvent, error) { 134 if ret := flv.savedEvent; ret != nil { 135 flv.savedEvent = nil 136 return ret, nil 137 } 138 139 for { 140 result, err := c.ReadPacket() 141 if err != nil { 142 return nil, err 143 } 144 switch result[0] { 145 case EOFPacket: 146 return nil, NewSQLError(CRServerLost, SSUnknownSQLState, "%v", io.EOF) 147 case ErrPacket: 148 return nil, ParseErrorPacket(result) 149 } 150 151 buf, semiSyncAckRequested, err := c.AnalyzeSemiSyncAckRequest(result[1:]) 152 if err != nil { 153 return nil, err 154 } 155 event := newFilePosBinlogEventWithSemiSyncInfo(buf, semiSyncAckRequested) 156 switch event.Type() { 157 case eGTIDEvent, eAnonymousGTIDEvent, ePreviousGTIDsEvent, eMariaGTIDListEvent: 158 // Don't transmit fake or irrelevant events because we should not 159 // resume replication at these positions. 160 continue 161 case eMariaGTIDEvent: 162 // Copied from mariadb flavor. 163 const FLStandalone = 1 164 flags2 := result[8+4] 165 // This means that it's also a BEGIN event. 166 if flags2&FLStandalone == 0 { 167 return newFilePosQueryEvent("begin", event.Timestamp()), nil 168 } 169 // Otherwise, don't send this event. 170 continue 171 case eFormatDescriptionEvent: 172 format, err := event.Format() 173 if err != nil { 174 return nil, err 175 } 176 flv.format = format 177 case eRotateEvent: 178 if !flv.format.IsZero() { 179 stripped, _, _ := event.StripChecksum(flv.format) 180 _, flv.file = stripped.(*filePosBinlogEvent).rotate(flv.format) 181 // No need to transmit. Just update the internal position for the next event. 182 continue 183 } 184 case eXIDEvent, eTableMapEvent, 185 eWriteRowsEventV0, eWriteRowsEventV1, eWriteRowsEventV2, 186 eDeleteRowsEventV0, eDeleteRowsEventV1, eDeleteRowsEventV2, 187 eUpdateRowsEventV0, eUpdateRowsEventV1, eUpdateRowsEventV2: 188 flv.savedEvent = event 189 return newFilePosGTIDEvent(flv.file, event.nextPosition(flv.format), event.Timestamp()), nil 190 case eQueryEvent: 191 q, err := event.Query(flv.format) 192 if err == nil && strings.HasPrefix(q.SQL, "#") { 193 continue 194 } 195 flv.savedEvent = event 196 return newFilePosGTIDEvent(flv.file, event.nextPosition(flv.format), event.Timestamp()), nil 197 default: 198 // For unrecognized events, send a fake "repair" event so that 199 // the position gets transmitted. 200 if !flv.format.IsZero() { 201 if v := event.nextPosition(flv.format); v != 0 { 202 flv.savedEvent = newFilePosQueryEvent("repair", event.Timestamp()) 203 return newFilePosGTIDEvent(flv.file, v, event.Timestamp()), nil 204 } 205 } 206 } 207 return event, nil 208 } 209 } 210 211 // resetReplicationCommands is part of the Flavor interface. 212 func (flv *filePosFlavor) resetReplicationCommands(c *Conn) []string { 213 return []string{ 214 "unsupported", 215 } 216 } 217 218 // resetReplicationParametersCommands is part of the Flavor interface. 219 func (flv *filePosFlavor) resetReplicationParametersCommands(c *Conn) []string { 220 return []string{ 221 "unsupported", 222 } 223 } 224 225 // setReplicationPositionCommands is part of the Flavor interface. 226 func (flv *filePosFlavor) setReplicationPositionCommands(pos Position) []string { 227 return []string{ 228 "unsupported", 229 } 230 } 231 232 // setReplicationPositionCommands is part of the Flavor interface. 233 func (flv *filePosFlavor) changeReplicationSourceArg() string { 234 return "unsupported" 235 } 236 237 // status is part of the Flavor interface. 238 func (flv *filePosFlavor) status(c *Conn) (ReplicationStatus, error) { 239 qr, err := c.ExecuteFetch("SHOW SLAVE STATUS", 100, true /* wantfields */) 240 if err != nil { 241 return ReplicationStatus{}, err 242 } 243 if len(qr.Rows) == 0 { 244 // The query returned no data, meaning the server 245 // is not configured as a replica. 246 return ReplicationStatus{}, ErrNotReplica 247 } 248 249 resultMap, err := resultToMap(qr) 250 if err != nil { 251 return ReplicationStatus{}, err 252 } 253 254 return parseFilePosReplicationStatus(resultMap) 255 } 256 257 func parseFilePosReplicationStatus(resultMap map[string]string) (ReplicationStatus, error) { 258 status := parseReplicationStatus(resultMap) 259 260 status.Position = status.FilePosition 261 status.RelayLogPosition = status.RelayLogSourceBinlogEquivalentPosition 262 263 return status, nil 264 } 265 266 // primaryStatus is part of the Flavor interface. 267 func (flv *filePosFlavor) primaryStatus(c *Conn) (PrimaryStatus, error) { 268 qr, err := c.ExecuteFetch("SHOW MASTER STATUS", 100, true /* wantfields */) 269 if err != nil { 270 return PrimaryStatus{}, err 271 } 272 if len(qr.Rows) == 0 { 273 // The query returned no data. We don't know how this could happen. 274 return PrimaryStatus{}, ErrNoPrimaryStatus 275 } 276 277 resultMap, err := resultToMap(qr) 278 if err != nil { 279 return PrimaryStatus{}, err 280 } 281 282 return parseFilePosPrimaryStatus(resultMap) 283 } 284 285 func parseFilePosPrimaryStatus(resultMap map[string]string) (PrimaryStatus, error) { 286 status := parsePrimaryStatus(resultMap) 287 288 status.Position = status.FilePosition 289 290 return status, nil 291 } 292 293 // waitUntilPositionCommand is part of the Flavor interface. 294 func (flv *filePosFlavor) waitUntilPositionCommand(ctx context.Context, pos Position) (string, error) { 295 filePosPos, ok := pos.GTIDSet.(filePosGTID) 296 if !ok { 297 return "", fmt.Errorf("Position is not filePos compatible: %#v", pos.GTIDSet) 298 } 299 300 if deadline, ok := ctx.Deadline(); ok { 301 timeout := time.Until(deadline) 302 if timeout <= 0 { 303 return "", fmt.Errorf("timed out waiting for position %v", pos) 304 } 305 return fmt.Sprintf("SELECT MASTER_POS_WAIT('%s', %d, %.6f)", filePosPos.file, filePosPos.pos, timeout.Seconds()), nil 306 } 307 308 return fmt.Sprintf("SELECT MASTER_POS_WAIT('%s', %d)", filePosPos.file, filePosPos.pos), nil 309 } 310 311 func (*filePosFlavor) startReplicationUntilAfter(pos Position) string { 312 return "unsupported" 313 } 314 315 func (*filePosFlavor) startSQLThreadUntilAfter(pos Position) string { 316 return "unsupported" 317 } 318 319 // enableBinlogPlaybackCommand is part of the Flavor interface. 320 func (*filePosFlavor) enableBinlogPlaybackCommand() string { 321 return "" 322 } 323 324 // disableBinlogPlaybackCommand is part of the Flavor interface. 325 func (*filePosFlavor) disableBinlogPlaybackCommand() string { 326 return "" 327 } 328 329 // baseShowTables is part of the Flavor interface. 330 func (*filePosFlavor) baseShowTables() string { 331 return mysqlFlavor{}.baseShowTables() 332 } 333 334 // baseShowTablesWithSizes is part of the Flavor interface. 335 func (*filePosFlavor) baseShowTablesWithSizes() string { 336 return TablesWithSize56 337 } 338 339 // supportsCapability is part of the Flavor interface. 340 func (*filePosFlavor) supportsCapability(serverVersion string, capability FlavorCapability) (bool, error) { 341 switch capability { 342 default: 343 return false, nil 344 } 345 }