vitess.io/vitess@v0.16.2/go/mysql/flavor_mariadb.go (about) 1 /* 2 3 Copyright 2019 The Vitess Authors. 4 5 Licensed under the Apache License, Version 2.0 (the "License"); 6 you may not use this file except in compliance with the License. 7 You may obtain a copy of the License at 8 9 http://www.apache.org/licenses/LICENSE-2.0 10 11 Unless required by applicable law or agreed to in writing, software 12 distributed under the License is distributed on an "AS IS" BASIS, 13 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 See the License for the specific language governing permissions and 15 limitations under the License. 16 */ 17 18 package mysql 19 20 import ( 21 "fmt" 22 "io" 23 "time" 24 25 "context" 26 27 "vitess.io/vitess/go/vt/proto/vtrpc" 28 "vitess.io/vitess/go/vt/vterrors" 29 ) 30 31 // mariadbFlavor implements the Flavor interface for MariaDB. 32 type mariadbFlavor struct{} 33 type mariadbFlavor101 struct { 34 mariadbFlavor 35 } 36 type mariadbFlavor102 struct { 37 mariadbFlavor 38 } 39 40 var _ flavor = (*mariadbFlavor101)(nil) 41 var _ flavor = (*mariadbFlavor102)(nil) 42 43 // primaryGTIDSet is part of the Flavor interface. 44 func (mariadbFlavor) primaryGTIDSet(c *Conn) (GTIDSet, error) { 45 qr, err := c.ExecuteFetch("SELECT @@GLOBAL.gtid_binlog_pos", 1, false) 46 if err != nil { 47 return nil, err 48 } 49 if len(qr.Rows) != 1 || len(qr.Rows[0]) != 1 { 50 return nil, vterrors.Errorf(vtrpc.Code_INTERNAL, "unexpected result format for gtid_binlog_pos: %#v", qr) 51 } 52 53 return parseMariadbGTIDSet(qr.Rows[0][0].ToString()) 54 } 55 56 // purgedGTIDSet is part of the Flavor interface. 57 func (mariadbFlavor) purgedGTIDSet(c *Conn) (GTIDSet, error) { 58 return nil, nil 59 } 60 61 // serverUUID is part of the Flavor interface. 62 func (mariadbFlavor) serverUUID(c *Conn) (string, error) { 63 return "", nil 64 } 65 66 // gtidMode is part of the Flavor interface. 67 func (mariadbFlavor) gtidMode(c *Conn) (string, error) { 68 return "", nil 69 } 70 71 func (mariadbFlavor) startReplicationUntilAfter(pos Position) string { 72 return fmt.Sprintf("START SLAVE UNTIL master_gtid_pos = \"%s\"", pos) 73 } 74 75 func (mariadbFlavor) startSQLThreadUntilAfter(pos Position) string { 76 return fmt.Sprintf("START SLAVE SQL_THREAD UNTIL master_gtid_pos = \"%s\"", pos) 77 } 78 79 func (mariadbFlavor) startReplicationCommand() string { 80 return "START SLAVE" 81 } 82 83 func (mariadbFlavor) restartReplicationCommands() []string { 84 return []string{ 85 "STOP SLAVE", 86 "RESET SLAVE", 87 "START SLAVE", 88 } 89 } 90 91 func (mariadbFlavor) stopReplicationCommand() string { 92 return "STOP SLAVE" 93 } 94 95 func (mariadbFlavor) stopIOThreadCommand() string { 96 return "STOP SLAVE IO_THREAD" 97 } 98 99 func (mariadbFlavor) stopSQLThreadCommand() string { 100 return "STOP SLAVE SQL_THREAD" 101 } 102 103 func (mariadbFlavor) startSQLThreadCommand() string { 104 return "START SLAVE SQL_THREAD" 105 } 106 107 // sendBinlogDumpCommand is part of the Flavor interface. 108 func (mariadbFlavor) sendBinlogDumpCommand(c *Conn, serverID uint32, binlogFilename string, startPos Position) error { 109 // Tell the server that we understand GTIDs by setting 110 // mariadb_slave_capability to MARIA_SLAVE_CAPABILITY_GTID = 4 (MariaDB >= 10.0.1). 111 if _, err := c.ExecuteFetch("SET @mariadb_slave_capability=4", 0, false); err != nil { 112 return vterrors.Wrapf(err, "failed to set @mariadb_slave_capability=4") 113 } 114 115 // Set the slave_connect_state variable before issuing COM_BINLOG_DUMP 116 // to provide the start position in GTID form. 117 query := fmt.Sprintf("SET @slave_connect_state='%s'", startPos) 118 if _, err := c.ExecuteFetch(query, 0, false); err != nil { 119 return vterrors.Wrapf(err, "failed to set @slave_connect_state='%s'", startPos) 120 } 121 122 // Real replicas set this upon connecting if their gtid_strict_mode option 123 // was enabled. We always use gtid_strict_mode because we need it to 124 // make our internal GTID comparisons safe. 125 if _, err := c.ExecuteFetch("SET @slave_gtid_strict_mode=1", 0, false); err != nil { 126 return vterrors.Wrapf(err, "failed to set @slave_gtid_strict_mode=1") 127 } 128 129 // Since we use @slave_connect_state, the file and position here are 130 // ignored. 131 return c.WriteComBinlogDump(serverID, "", 0, 0) 132 } 133 134 // resetReplicationCommands is part of the Flavor interface. 135 func (mariadbFlavor) resetReplicationCommands(c *Conn) []string { 136 resetCommands := []string{ 137 "STOP SLAVE", 138 "RESET SLAVE ALL", // "ALL" makes it forget source host:port. 139 "RESET MASTER", 140 "SET GLOBAL gtid_slave_pos = ''", 141 } 142 if c.SemiSyncExtensionLoaded() { 143 resetCommands = append(resetCommands, "SET GLOBAL rpl_semi_sync_master_enabled = false, GLOBAL rpl_semi_sync_slave_enabled = false") // semi-sync will be enabled if needed when replica is started. 144 } 145 return resetCommands 146 } 147 148 // resetReplicationParametersCommands is part of the Flavor interface. 149 func (mariadbFlavor) resetReplicationParametersCommands(c *Conn) []string { 150 resetCommands := []string{ 151 "RESET SLAVE ALL", // "ALL" makes it forget source host:port. 152 } 153 return resetCommands 154 } 155 156 // setReplicationPositionCommands is part of the Flavor interface. 157 func (mariadbFlavor) setReplicationPositionCommands(pos Position) []string { 158 return []string{ 159 // RESET MASTER will clear out gtid_binlog_pos, 160 // which then guarantees that gtid_current_pos = gtid_slave_pos, 161 // since gtid_current_pos = MAX(gtid_binlog_pos,gtid_slave_pos). 162 // This also emptys the binlogs, which allows us to set 163 // gtid_binlog_state. 164 "RESET MASTER", 165 // Set gtid_slave_pos to tell the replica where to start 166 // replicating. 167 fmt.Sprintf("SET GLOBAL gtid_slave_pos = '%s'", pos), 168 // Set gtid_binlog_state so that if this server later becomes the 169 // primary, it will know that it has seen everything up to and 170 // including 'pos'. Otherwise, if another replica asks this 171 // server to replicate starting at exactly 'pos', this server 172 // will throw an error when in gtid_strict_mode, since it 173 // doesn't see 'pos' in its binlog - it only has everything 174 // AFTER. 175 fmt.Sprintf("SET GLOBAL gtid_binlog_state = '%s'", pos), 176 } 177 } 178 179 // setReplicationPositionCommands is part of the Flavor interface. 180 func (mariadbFlavor) changeReplicationSourceArg() string { 181 return "MASTER_USE_GTID = current_pos" 182 } 183 184 // status is part of the Flavor interface. 185 func (mariadbFlavor) status(c *Conn) (ReplicationStatus, error) { 186 qr, err := c.ExecuteFetch("SHOW ALL SLAVES STATUS", 100, true /* wantfields */) 187 if err != nil { 188 return ReplicationStatus{}, err 189 } 190 if len(qr.Rows) == 0 { 191 // The query returned no data, meaning the server 192 // is not configured as a replica. 193 return ReplicationStatus{}, ErrNotReplica 194 } 195 196 resultMap, err := resultToMap(qr) 197 if err != nil { 198 return ReplicationStatus{}, err 199 } 200 201 return parseMariadbReplicationStatus(resultMap) 202 } 203 204 func parseMariadbReplicationStatus(resultMap map[string]string) (ReplicationStatus, error) { 205 status := parseReplicationStatus(resultMap) 206 207 var err error 208 status.Position.GTIDSet, err = parseMariadbGTIDSet(resultMap["Gtid_Slave_Pos"]) 209 if err != nil { 210 return ReplicationStatus{}, vterrors.Wrapf(err, "ReplicationStatus can't parse MariaDB GTID (Gtid_Slave_Pos: %#v)", resultMap["Gtid_Slave_Pos"]) 211 } 212 213 return status, nil 214 } 215 216 // primaryStatus is part of the Flavor interface. 217 func (m mariadbFlavor) primaryStatus(c *Conn) (PrimaryStatus, error) { 218 qr, err := c.ExecuteFetch("SHOW MASTER STATUS", 100, true /* wantfields */) 219 if err != nil { 220 return PrimaryStatus{}, err 221 } 222 if len(qr.Rows) == 0 { 223 // The query returned no data. We don't know how this could happen. 224 return PrimaryStatus{}, ErrNoPrimaryStatus 225 } 226 227 resultMap, err := resultToMap(qr) 228 if err != nil { 229 return PrimaryStatus{}, err 230 } 231 232 status := parsePrimaryStatus(resultMap) 233 status.Position.GTIDSet, err = m.primaryGTIDSet(c) 234 return status, err 235 } 236 237 // waitUntilPositionCommand is part of the Flavor interface. 238 // 239 // Note: Unlike MASTER_POS_WAIT(), MASTER_GTID_WAIT() will continue waiting even 240 // if the sql thread stops. If that is a problem, we'll have to change this. 241 func (mariadbFlavor) waitUntilPositionCommand(ctx context.Context, pos Position) (string, error) { 242 if deadline, ok := ctx.Deadline(); ok { 243 timeout := time.Until(deadline) 244 if timeout <= 0 { 245 return "", vterrors.Errorf(vtrpc.Code_DEADLINE_EXCEEDED, "timed out waiting for position %v", pos) 246 } 247 return fmt.Sprintf("SELECT MASTER_GTID_WAIT('%s', %.6f)", pos, timeout.Seconds()), nil 248 } 249 250 // Omit the timeout to wait indefinitely. In MariaDB, a timeout of 0 means 251 // return immediately. 252 return fmt.Sprintf("SELECT MASTER_GTID_WAIT('%s')", pos), nil 253 } 254 255 // readBinlogEvent is part of the Flavor interface. 256 func (mariadbFlavor) readBinlogEvent(c *Conn) (BinlogEvent, error) { 257 result, err := c.ReadPacket() 258 if err != nil { 259 return nil, err 260 } 261 switch result[0] { 262 case EOFPacket: 263 return nil, NewSQLError(CRServerLost, SSUnknownSQLState, "%v", io.EOF) 264 case ErrPacket: 265 return nil, ParseErrorPacket(result) 266 } 267 buf, semiSyncAckRequested, err := c.AnalyzeSemiSyncAckRequest(result[1:]) 268 if err != nil { 269 return nil, err 270 } 271 ev := NewMariadbBinlogEventWithSemiSyncInfo(buf, semiSyncAckRequested) 272 return ev, nil 273 } 274 275 // supportsCapability is part of the Flavor interface. 276 func (mariadbFlavor) supportsCapability(serverVersion string, capability FlavorCapability) (bool, error) { 277 switch capability { 278 default: 279 return false, nil 280 } 281 }