vitess.io/vitess@v0.16.2/go/mysql/flavor.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  	"errors"
    21  	"fmt"
    22  	"strconv"
    23  	"strings"
    24  
    25  	"context"
    26  
    27  	"vitess.io/vitess/go/sqltypes"
    28  	"vitess.io/vitess/go/vt/proto/vtrpc"
    29  	"vitess.io/vitess/go/vt/vterrors"
    30  )
    31  
    32  var (
    33  	// ErrNotReplica means there is no replication status.
    34  	// Returned by ShowReplicationStatus().
    35  	ErrNotReplica = NewSQLError(ERNotReplica, SSUnknownSQLState, "no replication status")
    36  
    37  	// ErrNoPrimaryStatus means no status was returned by ShowPrimaryStatus().
    38  	ErrNoPrimaryStatus = errors.New("no master status")
    39  )
    40  
    41  type FlavorCapability int
    42  
    43  const (
    44  	NoneFlavorCapability          FlavorCapability = iota // default placeholder
    45  	FastDropTableFlavorCapability                         // supported in MySQL 8.0.23 and above: https://dev.mysql.com/doc/relnotes/mysql/8.0/en/news-8-0-23.html
    46  	TransactionalGtidExecutedFlavorCapability
    47  	InstantDDLFlavorCapability
    48  	InstantAddLastColumnFlavorCapability
    49  	InstantAddDropVirtualColumnFlavorCapability
    50  	InstantAddDropColumnFlavorCapability
    51  	InstantChangeColumnDefaultFlavorCapability
    52  	InstantExpandEnumCapability
    53  	MySQLJSONFlavorCapability
    54  	MySQLUpgradeInServerFlavorCapability
    55  	DynamicRedoLogCapacityFlavorCapability // supported in MySQL 8.0.30 and above: https://dev.mysql.com/doc/relnotes/mysql/8.0/en/news-8-0-30.html
    56  	DisableRedoLogFlavorCapability         // supported in MySQL 8.0.21 and above: https://dev.mysql.com/doc/relnotes/mysql/8.0/en/news-8-0-21.html
    57  )
    58  
    59  const (
    60  	// mariaDBReplicationHackPrefix is the prefix of a version for MariaDB 10.0
    61  	// versions, to work around replication bugs.
    62  	mariaDBReplicationHackPrefix = "5.5.5-"
    63  	// mariaDBVersionString is present in
    64  	mariaDBVersionString = "MariaDB"
    65  	// mysql57VersionPrefix is the prefix for 5.7 mysql version, such as 5.7.31-log
    66  	mysql57VersionPrefix = "5.7."
    67  	// mysql80VersionPrefix is the prefix for 8.0 mysql version, such as 8.0.19
    68  	mysql80VersionPrefix = "8.0."
    69  )
    70  
    71  // flavor is the abstract interface for a flavor.
    72  // Flavors are auto-detected upon connection using the server version.
    73  // We have two major implementations (the main difference is the GTID
    74  // handling):
    75  // 1. Oracle MySQL 5.6, 5.7, 8.0, ...
    76  // 2. MariaDB 10.X
    77  type flavor interface {
    78  	// primaryGTIDSet returns the current GTIDSet of a server.
    79  	primaryGTIDSet(c *Conn) (GTIDSet, error)
    80  
    81  	// purgedGTIDSet returns the purged GTIDSet of a server.
    82  	purgedGTIDSet(c *Conn) (GTIDSet, error)
    83  
    84  	// gtidMode returns the gtid mode of a server.
    85  	gtidMode(c *Conn) (string, error)
    86  
    87  	// serverUUID returns the UUID of a server.
    88  	serverUUID(c *Conn) (string, error)
    89  
    90  	// startReplicationCommand returns the command to start the replication.
    91  	startReplicationCommand() string
    92  
    93  	// restartReplicationCommands returns the commands to stop, reset and start the replication.
    94  	restartReplicationCommands() []string
    95  
    96  	// startReplicationUntilAfter will start replication, but only allow it
    97  	// to run until `pos` is reached. After reaching pos, replication will be stopped again
    98  	startReplicationUntilAfter(pos Position) string
    99  
   100  	// startSQLThreadUntilAfter will start replication's sql thread(s), but only allow it
   101  	// to run until `pos` is reached. After reaching pos, it will be stopped again
   102  	startSQLThreadUntilAfter(pos Position) string
   103  
   104  	// stopReplicationCommand returns the command to stop the replication.
   105  	stopReplicationCommand() string
   106  
   107  	// stopIOThreadCommand returns the command to stop the replica's IO thread only.
   108  	stopIOThreadCommand() string
   109  
   110  	// stopSQLThreadCommand returns the command to stop the replica's SQL thread(s) only.
   111  	stopSQLThreadCommand() string
   112  
   113  	// startSQLThreadCommand returns the command to start the replica's SQL thread only.
   114  	startSQLThreadCommand() string
   115  
   116  	// sendBinlogDumpCommand sends the packet required to start
   117  	// dumping binlogs from the specified location.
   118  	sendBinlogDumpCommand(c *Conn, serverID uint32, binlogFilename string, startPos Position) error
   119  
   120  	// readBinlogEvent reads the next BinlogEvent from the connection.
   121  	readBinlogEvent(c *Conn) (BinlogEvent, error)
   122  
   123  	// resetReplicationCommands returns the commands to completely reset
   124  	// replication on the host.
   125  	resetReplicationCommands(c *Conn) []string
   126  
   127  	// resetReplicationParametersCommands returns the commands to reset
   128  	// replication parameters on the host.
   129  	resetReplicationParametersCommands(c *Conn) []string
   130  
   131  	// setReplicationPositionCommands returns the commands to set the
   132  	// replication position at which the replica will resume.
   133  	setReplicationPositionCommands(pos Position) []string
   134  
   135  	// changeReplicationSourceArg returns the specific parameter to add to
   136  	// a "change primary" command.
   137  	changeReplicationSourceArg() string
   138  
   139  	// status returns the result of the appropriate status command,
   140  	// with parsed replication position.
   141  	status(c *Conn) (ReplicationStatus, error)
   142  
   143  	// primaryStatus returns the result of 'SHOW MASTER STATUS',
   144  	// with parsed executed position.
   145  	primaryStatus(c *Conn) (PrimaryStatus, error)
   146  
   147  	// waitUntilPositionCommand returns the SQL command to issue
   148  	// to wait until the given position, until the context
   149  	// expires.  The command returns -1 if it times out. It
   150  	// returns NULL if GTIDs are not enabled.
   151  	waitUntilPositionCommand(ctx context.Context, pos Position) (string, error)
   152  
   153  	// enableBinlogPlaybackCommand and disableBinlogPlaybackCommand return an
   154  	// optional command to run to enable or disable binlog
   155  	// playback. This is used internally in Google, as the
   156  	// timestamp cannot be set by regular clients.
   157  	enableBinlogPlaybackCommand() string
   158  	disableBinlogPlaybackCommand() string
   159  
   160  	baseShowTables() string
   161  	baseShowTablesWithSizes() string
   162  
   163  	supportsCapability(serverVersion string, capability FlavorCapability) (bool, error)
   164  }
   165  
   166  type CapableOf func(capability FlavorCapability) (bool, error)
   167  
   168  // flavors maps flavor names to their implementation.
   169  // Flavors need to register only if they support being specified in the
   170  // connection parameters.
   171  var flavors = make(map[string]func() flavor)
   172  
   173  // ServerVersionAtLeast returns true if current server is at least given value.
   174  // Example: if input is []int{8, 0, 23}... the function returns 'true' if we're on MySQL 8.0.23, 8.0.24, ...
   175  func ServerVersionAtLeast(serverVersion string, parts ...int) (bool, error) {
   176  	versionPrefix := strings.Split(serverVersion, "-")[0]
   177  	versionTokens := strings.Split(versionPrefix, ".")
   178  	for i, part := range parts {
   179  		if len(versionTokens) <= i {
   180  			return false, nil
   181  		}
   182  		tokenValue, err := strconv.Atoi(versionTokens[i])
   183  		if err != nil {
   184  			return false, err
   185  		}
   186  		if tokenValue > part {
   187  			return true, nil
   188  		}
   189  		if tokenValue < part {
   190  			return false, nil
   191  		}
   192  	}
   193  	return true, nil
   194  }
   195  
   196  // GetFlavor fills in c.Flavor. If the params specify the flavor,
   197  // that is used. Otherwise, we auto-detect.
   198  //
   199  // This is the same logic as the ConnectorJ java client. We try to recognize
   200  // MariaDB as much as we can, but default to MySQL.
   201  //
   202  // MariaDB note: the server version returned here might look like:
   203  // 5.5.5-10.0.21-MariaDB-...
   204  // If that is the case, we are removing the 5.5.5- prefix.
   205  // Note on such servers, 'select version()' would return 10.0.21-MariaDB-...
   206  // as well (not matching what c.ServerVersion is, but matching after we remove
   207  // the prefix).
   208  func GetFlavor(serverVersion string, flavorFunc func() flavor) (f flavor, capableOf CapableOf, canonicalVersion string) {
   209  	canonicalVersion = serverVersion
   210  	switch {
   211  	case flavorFunc != nil:
   212  		f = flavorFunc()
   213  	case strings.HasPrefix(serverVersion, mariaDBReplicationHackPrefix):
   214  		canonicalVersion = serverVersion[len(mariaDBReplicationHackPrefix):]
   215  		f = mariadbFlavor101{}
   216  	case strings.Contains(serverVersion, mariaDBVersionString):
   217  		mariadbVersion, err := strconv.ParseFloat(serverVersion[:4], 64)
   218  		if err != nil || mariadbVersion < 10.2 {
   219  			f = mariadbFlavor101{}
   220  		} else {
   221  			f = mariadbFlavor102{}
   222  		}
   223  	case strings.HasPrefix(serverVersion, mysql57VersionPrefix):
   224  		f = mysqlFlavor57{}
   225  	case strings.HasPrefix(serverVersion, mysql80VersionPrefix):
   226  		f = mysqlFlavor80{}
   227  	default:
   228  		f = mysqlFlavor56{}
   229  	}
   230  	return f,
   231  		func(capability FlavorCapability) (bool, error) {
   232  			return f.supportsCapability(serverVersion, capability)
   233  		}, canonicalVersion
   234  }
   235  
   236  // fillFlavor fills in c.Flavor. If the params specify the flavor,
   237  // that is used. Otherwise, we auto-detect.
   238  //
   239  // This is the same logic as the ConnectorJ java client. We try to recognize
   240  // MariaDB as much as we can, but default to MySQL.
   241  //
   242  // MariaDB note: the server version returned here might look like:
   243  // 5.5.5-10.0.21-MariaDB-...
   244  // If that is the case, we are removing the 5.5.5- prefix.
   245  // Note on such servers, 'select version()' would return 10.0.21-MariaDB-...
   246  // as well (not matching what c.ServerVersion is, but matching after we remove
   247  // the prefix).
   248  func (c *Conn) fillFlavor(params *ConnParams) {
   249  	flavorFunc := flavors[params.Flavor]
   250  	c.flavor, _, c.ServerVersion = GetFlavor(c.ServerVersion, flavorFunc)
   251  }
   252  
   253  // ServerVersionAtLeast returns 'true' if server version is equal or greater than given parts. e.g.
   254  // "8.0.14-log" is at least [8, 0, 13] and [8, 0, 14], but not [8, 0, 15]
   255  func (c *Conn) ServerVersionAtLeast(parts ...int) (bool, error) {
   256  	return ServerVersionAtLeast(c.ServerVersion, parts...)
   257  }
   258  
   259  //
   260  // The following methods are dependent on the flavor.
   261  // Only valid for client connections (will panic for server connections).
   262  //
   263  
   264  // IsMariaDB returns true iff the other side of the client connection
   265  // is identified as MariaDB. Most applications should not care, but
   266  // this is useful in tests.
   267  func (c *Conn) IsMariaDB() bool {
   268  	switch c.flavor.(type) {
   269  	case mariadbFlavor101, mariadbFlavor102:
   270  		return true
   271  	}
   272  	return false
   273  }
   274  
   275  // PrimaryPosition returns the current primary's replication position.
   276  func (c *Conn) PrimaryPosition() (Position, error) {
   277  	gtidSet, err := c.flavor.primaryGTIDSet(c)
   278  	if err != nil {
   279  		return Position{}, err
   280  	}
   281  	return Position{
   282  		GTIDSet: gtidSet,
   283  	}, nil
   284  }
   285  
   286  // GetGTIDPurged returns the tablet's GTIDs which are purged.
   287  func (c *Conn) GetGTIDPurged() (Position, error) {
   288  	gtidSet, err := c.flavor.purgedGTIDSet(c)
   289  	if err != nil {
   290  		return Position{}, err
   291  	}
   292  	return Position{
   293  		GTIDSet: gtidSet,
   294  	}, nil
   295  }
   296  
   297  // GetGTIDMode returns the tablet's GTID mode. Only available in MySQL flavour
   298  func (c *Conn) GetGTIDMode() (string, error) {
   299  	return c.flavor.gtidMode(c)
   300  }
   301  
   302  // GetServerUUID returns the server's UUID.
   303  func (c *Conn) GetServerUUID() (string, error) {
   304  	return c.flavor.serverUUID(c)
   305  }
   306  
   307  // PrimaryFilePosition returns the current primary's file based replication position.
   308  func (c *Conn) PrimaryFilePosition() (Position, error) {
   309  	filePosFlavor := filePosFlavor{}
   310  	gtidSet, err := filePosFlavor.primaryGTIDSet(c)
   311  	if err != nil {
   312  		return Position{}, err
   313  	}
   314  	return Position{
   315  		GTIDSet: gtidSet,
   316  	}, nil
   317  }
   318  
   319  // StartReplicationCommand returns the command to start replication.
   320  func (c *Conn) StartReplicationCommand() string {
   321  	return c.flavor.startReplicationCommand()
   322  }
   323  
   324  // RestartReplicationCommands returns the commands to stop, reset and start replication.
   325  func (c *Conn) RestartReplicationCommands() []string {
   326  	return c.flavor.restartReplicationCommands()
   327  }
   328  
   329  // StartReplicationUntilAfterCommand returns the command to start replication.
   330  func (c *Conn) StartReplicationUntilAfterCommand(pos Position) string {
   331  	return c.flavor.startReplicationUntilAfter(pos)
   332  }
   333  
   334  // StartSQLThreadUntilAfterCommand returns the command to start the replica's SQL
   335  // thread(s) and have it run until it has reached the given position, at which point
   336  // it will stop.
   337  func (c *Conn) StartSQLThreadUntilAfterCommand(pos Position) string {
   338  	return c.flavor.startSQLThreadUntilAfter(pos)
   339  }
   340  
   341  // StopReplicationCommand returns the command to stop the replication.
   342  func (c *Conn) StopReplicationCommand() string {
   343  	return c.flavor.stopReplicationCommand()
   344  }
   345  
   346  // StopIOThreadCommand returns the command to stop the replica's io thread.
   347  func (c *Conn) StopIOThreadCommand() string {
   348  	return c.flavor.stopIOThreadCommand()
   349  }
   350  
   351  // StopSQLThreadCommand returns the command to stop the replica's SQL thread(s).
   352  func (c *Conn) StopSQLThreadCommand() string {
   353  	return c.flavor.stopSQLThreadCommand()
   354  }
   355  
   356  // StartSQLThreadCommand returns the command to start the replica's SQL thread.
   357  func (c *Conn) StartSQLThreadCommand() string {
   358  	return c.flavor.startSQLThreadCommand()
   359  }
   360  
   361  // SendBinlogDumpCommand sends the flavor-specific version of
   362  // the COM_BINLOG_DUMP command to start dumping raw binlog
   363  // events over a server connection, starting at a given GTID.
   364  func (c *Conn) SendBinlogDumpCommand(serverID uint32, binlogFilename string, startPos Position) error {
   365  	return c.flavor.sendBinlogDumpCommand(c, serverID, binlogFilename, startPos)
   366  }
   367  
   368  // ReadBinlogEvent reads the next BinlogEvent. This must be used
   369  // in conjunction with SendBinlogDumpCommand.
   370  func (c *Conn) ReadBinlogEvent() (BinlogEvent, error) {
   371  	return c.flavor.readBinlogEvent(c)
   372  }
   373  
   374  // ResetReplicationCommands returns the commands to completely reset
   375  // replication on the host.
   376  func (c *Conn) ResetReplicationCommands() []string {
   377  	return c.flavor.resetReplicationCommands(c)
   378  }
   379  
   380  // ResetReplicationParametersCommands returns the commands to reset
   381  // replication parameters on the host.
   382  func (c *Conn) ResetReplicationParametersCommands() []string {
   383  	return c.flavor.resetReplicationParametersCommands(c)
   384  }
   385  
   386  // SetReplicationPositionCommands returns the commands to set the
   387  // replication position at which the replica will resume
   388  // when it is later reparented with SetReplicationSourceCommand.
   389  func (c *Conn) SetReplicationPositionCommands(pos Position) []string {
   390  	return c.flavor.setReplicationPositionCommands(pos)
   391  }
   392  
   393  // SetReplicationSourceCommand returns the command to use the provided host/port
   394  // as the new replication source (without changing any GTID position).
   395  // It is guaranteed to be called with replication stopped.
   396  // It should not start or stop replication.
   397  func (c *Conn) SetReplicationSourceCommand(params *ConnParams, host string, port int, connectRetry int) string {
   398  	args := []string{
   399  		fmt.Sprintf("MASTER_HOST = '%s'", host),
   400  		fmt.Sprintf("MASTER_PORT = %d", port),
   401  		fmt.Sprintf("MASTER_USER = '%s'", params.Uname),
   402  		fmt.Sprintf("MASTER_PASSWORD = '%s'", params.Pass),
   403  		fmt.Sprintf("MASTER_CONNECT_RETRY = %d", connectRetry),
   404  	}
   405  	if params.SslEnabled() {
   406  		args = append(args, "MASTER_SSL = 1")
   407  	}
   408  	if params.SslCa != "" {
   409  		args = append(args, fmt.Sprintf("MASTER_SSL_CA = '%s'", params.SslCa))
   410  	}
   411  	if params.SslCaPath != "" {
   412  		args = append(args, fmt.Sprintf("MASTER_SSL_CAPATH = '%s'", params.SslCaPath))
   413  	}
   414  	if params.SslCert != "" {
   415  		args = append(args, fmt.Sprintf("MASTER_SSL_CERT = '%s'", params.SslCert))
   416  	}
   417  	if params.SslKey != "" {
   418  		args = append(args, fmt.Sprintf("MASTER_SSL_KEY = '%s'", params.SslKey))
   419  	}
   420  	args = append(args, c.flavor.changeReplicationSourceArg())
   421  	return "CHANGE MASTER TO\n  " + strings.Join(args, ",\n  ")
   422  }
   423  
   424  // resultToMap is a helper function used by ShowReplicationStatus.
   425  func resultToMap(qr *sqltypes.Result) (map[string]string, error) {
   426  	if len(qr.Rows) == 0 {
   427  		// The query succeeded, but there is no data.
   428  		return nil, nil
   429  	}
   430  	if len(qr.Rows) > 1 {
   431  		return nil, vterrors.Errorf(vtrpc.Code_INTERNAL, "query returned %d rows, expected 1", len(qr.Rows))
   432  	}
   433  	if len(qr.Fields) != len(qr.Rows[0]) {
   434  		return nil, vterrors.Errorf(vtrpc.Code_INTERNAL, "query returned %d column names, expected %d", len(qr.Fields), len(qr.Rows[0]))
   435  	}
   436  
   437  	result := make(map[string]string, len(qr.Fields))
   438  	for i, field := range qr.Fields {
   439  		result[field.Name] = qr.Rows[0][i].ToString()
   440  	}
   441  	return result, nil
   442  }
   443  
   444  // parseReplicationStatus parses the common (non-flavor-specific) fields of ReplicationStatus
   445  func parseReplicationStatus(fields map[string]string) ReplicationStatus {
   446  	// The field names in the map are identical to what we receive from the database
   447  	// Hence the names still contain Master
   448  	status := ReplicationStatus{
   449  		SourceHost:            fields["Master_Host"],
   450  		SourceUser:            fields["Master_User"],
   451  		SSLAllowed:            fields["Master_SSL_Allowed"] == "Yes",
   452  		AutoPosition:          fields["Auto_Position"] == "1",
   453  		UsingGTID:             fields["Using_Gtid"] != "No" && fields["Using_Gtid"] != "",
   454  		HasReplicationFilters: (fields["Replicate_Do_DB"] != "") || (fields["Replicate_Ignore_DB"] != "") || (fields["Replicate_Do_Table"] != "") || (fields["Replicate_Ignore_Table"] != "") || (fields["Replicate_Wild_Do_Table"] != "") || (fields["Replicate_Wild_Ignore_Table"] != ""),
   455  		// These fields are returned from the underlying DB and cannot be renamed
   456  		IOState:      ReplicationStatusToState(fields["Slave_IO_Running"]),
   457  		LastIOError:  fields["Last_IO_Error"],
   458  		SQLState:     ReplicationStatusToState(fields["Slave_SQL_Running"]),
   459  		LastSQLError: fields["Last_SQL_Error"],
   460  	}
   461  	parseInt, _ := strconv.ParseInt(fields["Master_Port"], 10, 0)
   462  	status.SourcePort = int(parseInt)
   463  	parseInt, _ = strconv.ParseInt(fields["Connect_Retry"], 10, 0)
   464  	status.ConnectRetry = int(parseInt)
   465  	parseUint, err := strconv.ParseUint(fields["Seconds_Behind_Master"], 10, 0)
   466  	if err != nil {
   467  		// we could not parse the value into a valid uint -- most commonly because the value is NULL from the
   468  		// database -- so let's reflect that the underlying value was unknown on our last check
   469  		status.ReplicationLagUnknown = true
   470  	} else {
   471  		status.ReplicationLagUnknown = false
   472  		status.ReplicationLagSeconds = uint(parseUint)
   473  	}
   474  	parseUint, _ = strconv.ParseUint(fields["Master_Server_Id"], 10, 0)
   475  	status.SourceServerID = uint(parseUint)
   476  	parseUint, _ = strconv.ParseUint(fields["SQL_Delay"], 10, 0)
   477  	status.SQLDelay = uint(parseUint)
   478  
   479  	executedPosStr := fields["Exec_Master_Log_Pos"]
   480  	file := fields["Relay_Master_Log_File"]
   481  	if file != "" && executedPosStr != "" {
   482  		filePos, err := strconv.Atoi(executedPosStr)
   483  		if err == nil {
   484  			status.FilePosition.GTIDSet = filePosGTID{
   485  				file: file,
   486  				pos:  filePos,
   487  			}
   488  		}
   489  	}
   490  
   491  	readPosStr := fields["Read_Master_Log_Pos"]
   492  	file = fields["Master_Log_File"]
   493  	if file != "" && readPosStr != "" {
   494  		fileRelayPos, err := strconv.Atoi(readPosStr)
   495  		if err == nil {
   496  			status.RelayLogSourceBinlogEquivalentPosition.GTIDSet = filePosGTID{
   497  				file: file,
   498  				pos:  fileRelayPos,
   499  			}
   500  		}
   501  	}
   502  
   503  	relayPosStr := fields["Relay_Log_Pos"]
   504  	file = fields["Relay_Log_File"]
   505  	if file != "" && relayPosStr != "" {
   506  		relayFilePos, err := strconv.Atoi(relayPosStr)
   507  		if err == nil {
   508  			status.RelayLogFilePosition.GTIDSet = filePosGTID{
   509  				file: file,
   510  				pos:  relayFilePos,
   511  			}
   512  		}
   513  	}
   514  	return status
   515  }
   516  
   517  // ShowReplicationStatus executes the right command to fetch replication status,
   518  // and returns a parsed Position with other fields.
   519  func (c *Conn) ShowReplicationStatus() (ReplicationStatus, error) {
   520  	return c.flavor.status(c)
   521  }
   522  
   523  // parsePrimaryStatus parses the common fields of SHOW MASTER STATUS.
   524  func parsePrimaryStatus(fields map[string]string) PrimaryStatus {
   525  	status := PrimaryStatus{}
   526  
   527  	fileExecPosStr := fields["Position"]
   528  	file := fields["File"]
   529  	if file != "" && fileExecPosStr != "" {
   530  		filePos, err := strconv.Atoi(fileExecPosStr)
   531  		if err == nil {
   532  			status.FilePosition.GTIDSet = filePosGTID{
   533  				file: file,
   534  				pos:  filePos,
   535  			}
   536  		}
   537  	}
   538  
   539  	return status
   540  }
   541  
   542  // ShowPrimaryStatus executes the right SHOW MASTER STATUS command,
   543  // and returns a parsed executed Position, as well as file based Position.
   544  func (c *Conn) ShowPrimaryStatus() (PrimaryStatus, error) {
   545  	return c.flavor.primaryStatus(c)
   546  }
   547  
   548  // WaitUntilPositionCommand returns the SQL command to issue
   549  // to wait until the given position, until the context
   550  // expires.  The command returns -1 if it times out. It
   551  // returns NULL if GTIDs are not enabled.
   552  func (c *Conn) WaitUntilPositionCommand(ctx context.Context, pos Position) (string, error) {
   553  	return c.flavor.waitUntilPositionCommand(ctx, pos)
   554  }
   555  
   556  // WaitUntilFilePositionCommand returns the SQL command to issue
   557  // to wait until the given position, until the context
   558  // expires for the file position flavor.  The command returns -1 if it times out. It
   559  // returns NULL if GTIDs are not enabled.
   560  func (c *Conn) WaitUntilFilePositionCommand(ctx context.Context, pos Position) (string, error) {
   561  	filePosFlavor := filePosFlavor{}
   562  	return filePosFlavor.waitUntilPositionCommand(ctx, pos)
   563  }
   564  
   565  // EnableBinlogPlaybackCommand returns a command to run to enable
   566  // binlog playback.
   567  func (c *Conn) EnableBinlogPlaybackCommand() string {
   568  	return c.flavor.enableBinlogPlaybackCommand()
   569  }
   570  
   571  // DisableBinlogPlaybackCommand returns a command to run to disable
   572  // binlog playback.
   573  func (c *Conn) DisableBinlogPlaybackCommand() string {
   574  	return c.flavor.disableBinlogPlaybackCommand()
   575  }
   576  
   577  // BaseShowTables returns a query that shows tables
   578  func (c *Conn) BaseShowTables() string {
   579  	return c.flavor.baseShowTables()
   580  }
   581  
   582  // BaseShowTablesWithSizes returns a query that shows tables and their sizes
   583  func (c *Conn) BaseShowTablesWithSizes() string {
   584  	return c.flavor.baseShowTablesWithSizes()
   585  }
   586  
   587  // SupportsCapability checks if the database server supports the given capability
   588  func (c *Conn) SupportsCapability(capability FlavorCapability) (bool, error) {
   589  	return c.flavor.supportsCapability(c.ServerVersion, capability)
   590  }