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  }