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 }