From 1aa8e2dd74ced5da0e907a5c8f06f804bde10588 Mon Sep 17 00:00:00 2001 From: siddharth16396 Date: Tue, 24 Jun 2025 13:26:19 +0530 Subject: [PATCH 1/2] Handle MySQL 9.x as MySQL 8.0 flavor in getFlavor() Vitess currently does not recognize MySQL 9.x versions and defaults them to MySQL 5.7 flavor. This causes failures when MySQL 8+ features like innodb_sys_tablespaces are expected, especially in local tests. This change treats MySQL 9.x (e.g., 9.2.0) the same as MySQL 8.0, which is known to be compatible for existing workflows. Tested locally using MySQL 9.2.0 installed via Homebrew. Signed-off-by: siddharth16396 --- go/mysql/flavor.go | 6 ++ go/mysql/flavor_test.go | 173 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 179 insertions(+) diff --git a/go/mysql/flavor.go b/go/mysql/flavor.go index b4b0b817c4b..3411a4891b3 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,9 @@ func GetFlavor(serverVersion string, flavorFunc func(serverVersion string) flavo } else { f = mysqlFlavor8Legacy{mysqlFlavorLegacy{mysqlFlavor{serverVersion: serverVersion}}} } + case strings.HasPrefix(serverVersion, mysql9VersionPrefix): + // MySQL 9.x uses the most modern flavor (mysqlFlavor82) + f = mysqlFlavor82{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_test.go b/go/mysql/flavor_test.go index 219b9803933..0ec95d11070 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,152 @@ 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 mysqlFlavor82 + { + version: "9.0.0", + expectedType: "mysqlFlavor82", + description: "MySQL 9.0.0 should use mysqlFlavor82", + }, + { + version: "9.1.0", + expectedType: "mysqlFlavor82", + description: "MySQL 9.1.0 should use mysqlFlavor82", + }, + { + version: "9.0.5-log", + expectedType: "mysqlFlavor82", + description: "MySQL 9.0.5 with suffix should use mysqlFlavor82", + }, + { + version: "9.2.1-debug", + expectedType: "mysqlFlavor82", + description: "MySQL 9.2.1 with debug suffix should use mysqlFlavor82", + }, + { + version: "9.10.15", + expectedType: "mysqlFlavor82", + description: "MySQL 9.10.15 should use mysqlFlavor82", + }, + // 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 "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) + } + }) + } +} From 6b27838aa17ea6fca09fe29f727713f2095c3aa6 Mon Sep 17 00:00:00 2001 From: siddharth16396 Date: Wed, 25 Jun 2025 22:41:25 +0530 Subject: [PATCH 2/2] Add MySQL9 Flavor and MySql9Cnf Signed-off-by: siddharth16396 --- config/embed.go | 3 ++ config/mycnf/mysql90.cnf | 39 +++++++++++++++++ go/mysql/flavor.go | 3 +- go/mysql/flavor_mysql.go | 7 +++ go/mysql/flavor_mysql_test.go | 80 +++++++++++++++++++++++++++++++++++ go/mysql/flavor_test.go | 25 ++++++----- go/vt/mysqlctl/mysqld.go | 2 + go/vt/mysqlctl/mysqld_test.go | 28 ++++++++++++ 8 files changed, 174 insertions(+), 13 deletions(-) create mode 100644 config/mycnf/mysql90.cnf 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 3411a4891b3..af81c4dd049 100644 --- a/go/mysql/flavor.go +++ b/go/mysql/flavor.go @@ -204,8 +204,7 @@ func GetFlavor(serverVersion string, flavorFunc func(serverVersion string) flavo f = mysqlFlavor8Legacy{mysqlFlavorLegacy{mysqlFlavor{serverVersion: serverVersion}}} } case strings.HasPrefix(serverVersion, mysql9VersionPrefix): - // MySQL 9.x uses the most modern flavor (mysqlFlavor82) - f = mysqlFlavor82{mysqlFlavor{serverVersion: serverVersion}} + 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 0ec95d11070..2793fc6ebfa 100644 --- a/go/mysql/flavor_test.go +++ b/go/mysql/flavor_test.go @@ -207,31 +207,31 @@ func TestGetFlavor(t *testing.T) { expectedType: "mysqlFlavor8", description: "MySQL 8.0.30 with suffix should use mysqlFlavor8 (has replica terminology, < 8.2.0)", }, - // MySQL 9.x versions should get mysqlFlavor82 + // MySQL 9.x versions should get mysqlFlavor9 { version: "9.0.0", - expectedType: "mysqlFlavor82", - description: "MySQL 9.0.0 should use mysqlFlavor82", + expectedType: "mysqlFlavor9", + description: "MySQL 9.0.0 should use mysqlFlavor9", }, { version: "9.1.0", - expectedType: "mysqlFlavor82", - description: "MySQL 9.1.0 should use mysqlFlavor82", + expectedType: "mysqlFlavor9", + description: "MySQL 9.1.0 should use mysqlFlavor9", }, { version: "9.0.5-log", - expectedType: "mysqlFlavor82", - description: "MySQL 9.0.5 with suffix should use mysqlFlavor82", + expectedType: "mysqlFlavor9", + description: "MySQL 9.0.5 with suffix should use mysqlFlavor9", }, { version: "9.2.1-debug", - expectedType: "mysqlFlavor82", - description: "MySQL 9.2.1 with debug suffix should use mysqlFlavor82", + expectedType: "mysqlFlavor9", + description: "MySQL 9.2.1 with debug suffix should use mysqlFlavor9", }, { version: "9.10.15", - expectedType: "mysqlFlavor82", - description: "MySQL 9.10.15 should use mysqlFlavor82", + expectedType: "mysqlFlavor9", + description: "MySQL 9.10.15 should use mysqlFlavor9", }, // MariaDB versions { @@ -290,6 +290,9 @@ func TestGetFlavor(t *testing.T) { 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) 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") +}