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  }