diff --git a/config/embed.go b/config/embed.go index 6660e749aa6..b138184c5a3 100644 --- a/config/embed.go +++ b/config/embed.go @@ -22,3 +22,6 @@ var MycnfMySQL8026 string //go:embed mycnf/mysql84.cnf var MycnfMySQL84 string + +//go:embed mycnf/mysql90.cnf +var MycnfMySQL90 string diff --git a/config/mycnf/mysql90.cnf b/config/mycnf/mysql90.cnf new file mode 100644 index 00000000000..71f308144ee --- /dev/null +++ b/config/mycnf/mysql90.cnf @@ -0,0 +1,39 @@ +# This file is auto-included when MySQL 9.0 or later is detected. + +# all db instances should skip starting replication threads - that way we can do any +# additional configuration (like enabling semi-sync) before we connect to +# the source. +skip_replica_start + +# MySQL 8.0 enables binlog by default with sync_binlog and TABLE info repositories +# It does not enable GTIDs or enforced GTID consistency + +gtid_mode = ON +enforce_gtid_consistency +relay_log_recovery = 1 +binlog_expire_logs_seconds = 259200 + +# disable mysqlx +mysqlx = 0 + +# Semi-sync replication is required for automated unplanned failover +# (when the primary goes away). Here we just load the plugin so it's +# available if desired, but it's disabled at startup. +# +# VTTablet will enable semi-sync at the proper time when replication is set up, +# or when a primary is promoted or demoted based on the durability policy configured. +plugin-load = rpl_semi_sync_source=semisync_source.so;rpl_semi_sync_replica=semisync_replica.so + +# MySQL 8.0.26 and later will not load plugins during --initialize +# which makes these options unknown. Prefixing with --loose +# tells the server it's fine if they are not understood. +loose_rpl_semi_sync_source_timeout = 1000000000000000000 +loose_rpl_semi_sync_source_wait_no_replica = 1 + +# In order to protect against any errand GTIDs we will start the mysql instance +# in super-read-only mode. +super-read-only + +# Replication parameters to ensure reparents are fast. +replica_net_timeout = 8 + diff --git a/go/mysql/flavor.go b/go/mysql/flavor.go index b4b0b817c4b..af81c4dd049 100644 --- a/go/mysql/flavor.go +++ b/go/mysql/flavor.go @@ -50,6 +50,9 @@ const ( // mysql8VersionPrefix is the prefix for 8.x mysql version, such as 8.0.19, // but also newer ones like 8.4.0. mysql8VersionPrefix = "8." + // mysql9VersionPrefix is the prefix for 9.x mysql version, such as 9.0.0, + // 9.1.0, 9.2.0, etc. + mysql9VersionPrefix = "9." ) // flavor is the abstract interface for a flavor. @@ -200,6 +203,8 @@ func GetFlavor(serverVersion string, flavorFunc func(serverVersion string) flavo } else { f = mysqlFlavor8Legacy{mysqlFlavorLegacy{mysqlFlavor{serverVersion: serverVersion}}} } + case strings.HasPrefix(serverVersion, mysql9VersionPrefix): + f = mysqlFlavor9{mysqlFlavor{serverVersion: serverVersion}} default: // If unknown, return the most basic flavor: MySQL 57. f = mysqlFlavor57{mysqlFlavorLegacy{mysqlFlavor{serverVersion: serverVersion}}} diff --git a/go/mysql/flavor_mysql.go b/go/mysql/flavor_mysql.go index d6af750bb89..f4a53e920ad 100644 --- a/go/mysql/flavor_mysql.go +++ b/go/mysql/flavor_mysql.go @@ -55,6 +55,13 @@ type mysqlFlavor82 struct { mysqlFlavor } +// mysqlFlavor9 is for MySQL 9.x.y and later. It's the most modern +// flavor but has an explicit name so that it's clear what versions +// it is for. +type mysqlFlavor9 struct { + mysqlFlavor +} + var _ flavor = (*mysqlFlavor8)(nil) var _ flavor = (*mysqlFlavor82)(nil) diff --git a/go/mysql/flavor_mysql_test.go b/go/mysql/flavor_mysql_test.go index 6658caa10c7..a3ed8d44615 100644 --- a/go/mysql/flavor_mysql_test.go +++ b/go/mysql/flavor_mysql_test.go @@ -116,3 +116,83 @@ func TestMysql82ResetReplicationParametersCommands(t *testing.T) { queries := conn.ResetReplicationParametersCommands() assert.Equal(t, []string{"RESET REPLICA ALL"}, queries) } + +func TestMysql9SetReplicationPositionCommands(t *testing.T) { + pos := replication.Position{GTIDSet: replication.Mysql56GTIDSet{}} + conn := &Conn{flavor: mysqlFlavor9{}} + queries := conn.SetReplicationPositionCommands(pos) + assert.Equal(t, []string{"RESET BINARY LOGS AND GTIDS", "SET GLOBAL gtid_purged = ''"}, queries) +} + +func TestMysql9ResetReplicationParametersCommands(t *testing.T) { + conn := &Conn{flavor: mysqlFlavor9{}} + queries := conn.ResetReplicationParametersCommands() + assert.Equal(t, []string{"RESET REPLICA ALL"}, queries) +} + +func TestMysql9SetReplicationSourceCommand(t *testing.T) { + params := &ConnParams{ + Uname: "username", + Pass: "password", + } + host := "localhost" + port := int32(123) + connectRetry := 1234 + want := `CHANGE REPLICATION SOURCE TO + SOURCE_HOST = 'localhost', + SOURCE_PORT = 123, + SOURCE_USER = 'username', + SOURCE_PASSWORD = 'password', + SOURCE_CONNECT_RETRY = 1234, + GET_SOURCE_PUBLIC_KEY = 1, + SOURCE_AUTO_POSITION = 1` + + conn := &Conn{flavor: mysqlFlavor9{}} + got := conn.SetReplicationSourceCommand(params, host, port, 0, connectRetry) + assert.Equal(t, want, got, "mysqlFlavor9.SetReplicationSourceCommand(%#v, %#v, %#v, %#v) = %#v, want %#v", params, host, port, connectRetry, got, want) + + var heartbeatInterval = 5.4 + want = `CHANGE REPLICATION SOURCE TO + SOURCE_HOST = 'localhost', + SOURCE_PORT = 123, + SOURCE_USER = 'username', + SOURCE_PASSWORD = 'password', + SOURCE_CONNECT_RETRY = 1234, + GET_SOURCE_PUBLIC_KEY = 1, + SOURCE_HEARTBEAT_PERIOD = 5.4, + SOURCE_AUTO_POSITION = 1` + + got = conn.SetReplicationSourceCommand(params, host, port, heartbeatInterval, connectRetry) + assert.Equal(t, want, got, "mysqlFlavor9.SetReplicationSourceCommand(%#v, %#v, %#v, %#v, %#v) = %#v, want %#v", params, host, port, heartbeatInterval, connectRetry, got, want) +} + +func TestMysql9SetReplicationSourceCommandSSL(t *testing.T) { + params := &ConnParams{ + Uname: "username", + Pass: "password", + SslCa: "ssl-ca", + SslCaPath: "ssl-ca-path", + SslCert: "ssl-cert", + SslKey: "ssl-key", + } + params.EnableSSL() + host := "localhost" + port := int32(123) + connectRetry := 1234 + want := `CHANGE REPLICATION SOURCE TO + SOURCE_HOST = 'localhost', + SOURCE_PORT = 123, + SOURCE_USER = 'username', + SOURCE_PASSWORD = 'password', + SOURCE_CONNECT_RETRY = 1234, + SOURCE_SSL = 1, + SOURCE_SSL_CA = 'ssl-ca', + SOURCE_SSL_CAPATH = 'ssl-ca-path', + SOURCE_SSL_CERT = 'ssl-cert', + SOURCE_SSL_KEY = 'ssl-key', + SOURCE_AUTO_POSITION = 1` + + conn := &Conn{flavor: mysqlFlavor9{}} + got := conn.SetReplicationSourceCommand(params, host, port, 0, connectRetry) + assert.Equal(t, want, got, "mysqlFlavor9.SetReplicationSourceCommand(%#v, %#v, %#v, %#v) = %#v, want %#v", params, host, port, connectRetry, got, want) +} diff --git a/go/mysql/flavor_test.go b/go/mysql/flavor_test.go index 219b9803933..2793fc6ebfa 100644 --- a/go/mysql/flavor_test.go +++ b/go/mysql/flavor_test.go @@ -118,6 +118,30 @@ func TestServerVersionCapableOf(t *testing.T) { capability: capabilities.CheckConstraintsCapability, isCapable: true, }, + { + // MySQL 9.0.0 should support modern capabilities + version: "9.0.0", + capability: capabilities.InstantDDLFlavorCapability, + isCapable: true, + }, + { + // MySQL 9.1.0 should support transactional GTID executed + version: "9.1.0", + capability: capabilities.TransactionalGtidExecutedFlavorCapability, + isCapable: true, + }, + { + // MySQL 9.0.5 should support check constraints + version: "9.0.5", + capability: capabilities.CheckConstraintsCapability, + isCapable: true, + }, + { + // MySQL 9.2.1-log should support performance schema data locks + version: "9.2.1-log", + capability: capabilities.PerformanceSchemaDataLocksTableCapability, + isCapable: true, + }, } for _, tc := range testcases { name := fmt.Sprintf("%s %v", tc.version, tc.capability) @@ -129,3 +153,155 @@ func TestServerVersionCapableOf(t *testing.T) { }) } } + +func TestGetFlavor(t *testing.T) { + testcases := []struct { + version string + expectedType string + description string + }{ + // MySQL 5.7.x versions should get mysqlFlavor57 + { + version: "5.7.29", + expectedType: "mysqlFlavor57", + description: "MySQL 5.7 should use mysqlFlavor57", + }, + { + version: "5.7.38-log", + expectedType: "mysqlFlavor57", + description: "MySQL 5.7 with suffix should use mysqlFlavor57", + }, + // MySQL 8.0.x versions with different capabilities + { + version: "8.0.11", + expectedType: "mysqlFlavor8Legacy", + description: "MySQL 8.0.11 should use mysqlFlavor8Legacy (no replica terminology)", + }, + { + version: "8.0.22", + expectedType: "mysqlFlavor8Legacy", + description: "MySQL 8.0.22 should use mysqlFlavor8Legacy (no replica terminology, < 8.0.26)", + }, + { + version: "8.0.26", + expectedType: "mysqlFlavor8", + description: "MySQL 8.0.26 should use mysqlFlavor8 (has replica terminology, < 8.2.0)", + }, + { + version: "8.1.0", + expectedType: "mysqlFlavor8", + description: "MySQL 8.1.0 should use mysqlFlavor8 (has replica terminology, < 8.2.0)", + }, + { + version: "8.2.0", + expectedType: "mysqlFlavor82", + description: "MySQL 8.2.0 should use mysqlFlavor82", + }, + { + version: "8.3.0", + expectedType: "mysqlFlavor82", + description: "MySQL 8.3.0 should use mysqlFlavor82", + }, + { + version: "8.0.30-log", + expectedType: "mysqlFlavor8", + description: "MySQL 8.0.30 with suffix should use mysqlFlavor8 (has replica terminology, < 8.2.0)", + }, + // MySQL 9.x versions should get mysqlFlavor9 + { + version: "9.0.0", + expectedType: "mysqlFlavor9", + description: "MySQL 9.0.0 should use mysqlFlavor9", + }, + { + version: "9.1.0", + expectedType: "mysqlFlavor9", + description: "MySQL 9.1.0 should use mysqlFlavor9", + }, + { + version: "9.0.5-log", + expectedType: "mysqlFlavor9", + description: "MySQL 9.0.5 with suffix should use mysqlFlavor9", + }, + { + version: "9.2.1-debug", + expectedType: "mysqlFlavor9", + description: "MySQL 9.2.1 with debug suffix should use mysqlFlavor9", + }, + { + version: "9.10.15", + expectedType: "mysqlFlavor9", + description: "MySQL 9.10.15 should use mysqlFlavor9", + }, + // MariaDB versions + { + version: "5.5.5-10.1.48-MariaDB", + expectedType: "mariadbFlavor101", + description: "MariaDB 10.1 with replication hack prefix should use mariadbFlavor101", + }, + { + version: "10.1.48-MariaDB", + expectedType: "mariadbFlavor101", + description: "MariaDB 10.1 should use mariadbFlavor101", + }, + { + version: "10.2.0-MariaDB", + expectedType: "mariadbFlavor102", + description: "MariaDB 10.2 should use mariadbFlavor102", + }, + { + version: "10.5.15-MariaDB-log", + expectedType: "mariadbFlavor102", + description: "MariaDB 10.5 with suffix should use mariadbFlavor102", + }, + // Default/unknown versions should get mysqlFlavor57 + { + version: "5.6.45", + expectedType: "mysqlFlavor57", + description: "MySQL 5.6 should default to mysqlFlavor57", + }, + { + version: "unknown-version", + expectedType: "mysqlFlavor57", + description: "Unknown version should default to mysqlFlavor57", + }, + { + version: "4.1.22", + expectedType: "mysqlFlavor57", + description: "Very old MySQL version should default to mysqlFlavor57", + }, + } + + for _, tc := range testcases { + t.Run(fmt.Sprintf("%s_%s", tc.version, tc.expectedType), func(t *testing.T) { + flavor, _, _ := GetFlavor(tc.version, nil) + + // Check the flavor type matches expected + switch tc.expectedType { + case "mysqlFlavor57": + _, ok := flavor.(mysqlFlavor57) + assert.True(t, ok, "Expected mysqlFlavor57 for version %s, but got %T. %s", tc.version, flavor, tc.description) + case "mysqlFlavor8Legacy": + _, ok := flavor.(mysqlFlavor8Legacy) + assert.True(t, ok, "Expected mysqlFlavor8Legacy for version %s, but got %T. %s", tc.version, flavor, tc.description) + case "mysqlFlavor8": + _, ok := flavor.(mysqlFlavor8) + assert.True(t, ok, "Expected mysqlFlavor8 for version %s, but got %T. %s", tc.version, flavor, tc.description) + case "mysqlFlavor82": + _, ok := flavor.(mysqlFlavor82) + assert.True(t, ok, "Expected mysqlFlavor82 for version %s, but got %T. %s", tc.version, flavor, tc.description) + case "mysqlFlavor9": + _, ok := flavor.(mysqlFlavor9) + assert.True(t, ok, "Expected mysqlFlavor9 for version %s, but got %T. %s", tc.version, flavor, tc.description) + case "mariadbFlavor101": + _, ok := flavor.(mariadbFlavor101) + assert.True(t, ok, "Expected mariadbFlavor101 for version %s, but got %T. %s", tc.version, flavor, tc.description) + case "mariadbFlavor102": + _, ok := flavor.(mariadbFlavor102) + assert.True(t, ok, "Expected mariadbFlavor102 for version %s, but got %T. %s", tc.version, flavor, tc.description) + default: + t.Errorf("Unknown expected type: %s", tc.expectedType) + } + }) + } +} diff --git a/go/vt/mysqlctl/mysqld.go b/go/vt/mysqlctl/mysqld.go index 983ec705af3..bcdd2dd4d48 100644 --- a/go/vt/mysqlctl/mysqld.go +++ b/go/vt/mysqlctl/mysqld.go @@ -954,6 +954,8 @@ func (mysqld *Mysqld) getMycnfTemplate() string { } else { versionConfig = config.MycnfMySQL80 } + case 9: + versionConfig = config.MycnfMySQL90 default: log.Infof("this version of Vitess does not include built-in support for %v %v", mysqld.capabilities.flavor, mysqld.capabilities.version) } diff --git a/go/vt/mysqlctl/mysqld_test.go b/go/vt/mysqlctl/mysqld_test.go index e2a0c4acadc..a304e9e9f4c 100644 --- a/go/vt/mysqlctl/mysqld_test.go +++ b/go/vt/mysqlctl/mysqld_test.go @@ -322,3 +322,31 @@ func TestHostMetrics(t *testing.T) { assert.Equal(t, "datadir-used-ratio", metric.Name) assert.Empty(t, metric.Error) } + +func TestGetMycnfTemplateMySQL9(t *testing.T) { + db := fakesqldb.New(t) + defer db.Close() + + params := db.ConnParams() + cp := *params + dbc := dbconfigs.NewTestDBConfigs(cp, cp, "fakesqldb") + + testMysqld := NewMysqld(dbc) + defer testMysqld.Close() + + // Test MySQL 9.0 + testMysqld.capabilities = newCapabilitySet(FlavorMySQL, ServerVersion{Major: 9, Minor: 0, Patch: 0}) + template := testMysqld.getMycnfTemplate() + assert.Contains(t, template, "[mysqld]") + // Should use MySQL 9.0 config for MySQL 9.x + assert.Contains(t, template, "# This file is auto-included when MySQL 9.0 or later is detected.") + assert.NotContains(t, template, "mysql_native_password = ON") + + // Test MySQL 9.1 + testMysqld.capabilities = newCapabilitySet(FlavorMySQL, ServerVersion{Major: 9, Minor: 1, Patch: 5}) + template = testMysqld.getMycnfTemplate() + assert.Contains(t, template, "[mysqld]") + // Should use MySQL 9.0 config for MySQL 9.x + assert.Contains(t, template, "# This file is auto-included when MySQL 9.0 or later is detected.") + assert.NotContains(t, template, "mysql_native_password = ON") +}