vitess.io/vitess@v0.16.2/go/test/endtoend/utils/mysql.go (about) 1 /* 2 Copyright 2022 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 utils 18 19 import ( 20 "context" 21 "fmt" 22 "os" 23 "path" 24 "testing" 25 26 "github.com/stretchr/testify/assert" 27 28 "vitess.io/vitess/go/sqltypes" 29 "vitess.io/vitess/go/vt/dbconfigs" 30 "vitess.io/vitess/go/vt/sqlparser" 31 32 "vitess.io/vitess/go/mysql" 33 "vitess.io/vitess/go/test/endtoend/cluster" 34 "vitess.io/vitess/go/vt/mysqlctl" 35 ) 36 37 // NewMySQL creates a new MySQL server using the local mysqld binary. The name of the database 38 // will be set to `dbName`. SQL queries that need to be executed on the new MySQL instance 39 // can be passed through the `schemaSQL` argument. 40 // The mysql.ConnParams to connect to the new database is returned, along with a function to 41 // teardown the database. 42 func NewMySQL(cluster *cluster.LocalProcessCluster, dbName string, schemaSQL ...string) (mysql.ConnParams, func(), error) { 43 return NewMySQLWithDetails(cluster.GetAndReservePort(), cluster.Hostname, dbName, schemaSQL...) 44 } 45 46 // CreateMysqldAndMycnf returns a Mysqld and a Mycnf object to use for working with a MySQL 47 // installation that hasn't been set up yet. 48 func CreateMysqldAndMycnf(tabletUID uint32, mysqlSocket string, mysqlPort int32) (*mysqlctl.Mysqld, *mysqlctl.Mycnf, error) { 49 mycnf := mysqlctl.NewMycnf(tabletUID, mysqlPort) 50 if err := mycnf.RandomizeMysqlServerID(); err != nil { 51 return nil, nil, fmt.Errorf("couldn't generate random MySQL server_id: %v", err) 52 } 53 if mysqlSocket != "" { 54 mycnf.SocketFile = mysqlSocket 55 } 56 var cfg dbconfigs.DBConfigs 57 // ensure the DBA username is 'root' instead of the system's default username so that mysqladmin can shutdown 58 cfg.Dba.User = "root" 59 cfg.InitWithSocket(mycnf.SocketFile) 60 return mysqlctl.NewMysqld(&cfg), mycnf, nil 61 } 62 63 func NewMySQLWithDetails(port int, hostname, dbName string, schemaSQL ...string) (mysql.ConnParams, func(), error) { 64 mysqlDir, err := createMySQLDir() 65 if err != nil { 66 return mysql.ConnParams{}, nil, err 67 } 68 initMySQLFile, err := createInitSQLFile(mysqlDir, dbName) 69 if err != nil { 70 return mysql.ConnParams{}, nil, err 71 } 72 73 mysqlPort := port 74 mysqld, mycnf, err := CreateMysqldAndMycnf(0, "", int32(mysqlPort)) 75 if err != nil { 76 return mysql.ConnParams{}, nil, err 77 } 78 err = initMysqld(mysqld, mycnf, initMySQLFile) 79 if err != nil { 80 return mysql.ConnParams{}, nil, err 81 } 82 83 params := mysql.ConnParams{ 84 UnixSocket: mycnf.SocketFile, 85 Host: hostname, 86 Uname: "root", 87 DbName: dbName, 88 } 89 for _, sql := range schemaSQL { 90 err = prepareMySQLWithSchema(params, sql) 91 if err != nil { 92 return mysql.ConnParams{}, nil, err 93 } 94 } 95 return params, func() { 96 ctx := context.Background() 97 _ = mysqld.Teardown(ctx, mycnf, true) 98 }, nil 99 } 100 101 func createMySQLDir() (string, error) { 102 mysqlDir := mysqlctl.TabletDir(0) 103 err := os.Mkdir(mysqlDir, 0700) 104 if err != nil { 105 return "", err 106 } 107 return mysqlDir, nil 108 } 109 110 func createInitSQLFile(mysqlDir, ksName string) (string, error) { 111 initSQLFile := path.Join(mysqlDir, "init.sql") 112 f, err := os.Create(initSQLFile) 113 if err != nil { 114 return "", err 115 } 116 defer f.Close() 117 118 _, err = f.WriteString(fmt.Sprintf("CREATE DATABASE IF NOT EXISTS %s;", ksName)) 119 if err != nil { 120 return "", err 121 } 122 return initSQLFile, nil 123 } 124 125 func initMysqld(mysqld *mysqlctl.Mysqld, mycnf *mysqlctl.Mycnf, initSQLFile string) error { 126 f, err := os.CreateTemp(path.Dir(mycnf.Path), "my.cnf") 127 if err != nil { 128 return err 129 } 130 f.Close() 131 132 ctx := context.Background() 133 err = mysqld.Init(ctx, mycnf, initSQLFile) 134 if err != nil { 135 return err 136 } 137 return nil 138 } 139 140 func prepareMySQLWithSchema(params mysql.ConnParams, sql string) error { 141 ctx := context.Background() 142 conn, err := mysql.Connect(ctx, ¶ms) 143 if err != nil { 144 return err 145 } 146 _, err = conn.ExecuteFetch(sql, 1, false) 147 if err != nil { 148 return err 149 } 150 return nil 151 } 152 153 func compareVitessAndMySQLResults(t *testing.T, query string, vtQr, mysqlQr *sqltypes.Result, compareColumns bool) { 154 if vtQr == nil && mysqlQr == nil { 155 return 156 } 157 if vtQr == nil { 158 t.Error("Vitess result is 'nil' while MySQL's is not.") 159 return 160 } 161 if mysqlQr == nil { 162 t.Error("MySQL result is 'nil' while Vitess' is not.") 163 return 164 } 165 if compareColumns { 166 vtColCount := len(vtQr.Fields) 167 myColCount := len(mysqlQr.Fields) 168 if vtColCount > 0 && myColCount > 0 { 169 if vtColCount != myColCount { 170 t.Errorf("column count does not match: %d vs %d", vtColCount, myColCount) 171 } 172 173 var vtCols []string 174 var myCols []string 175 for i, vtField := range vtQr.Fields { 176 vtCols = append(vtCols, vtField.Name) 177 myCols = append(myCols, mysqlQr.Fields[i].Name) 178 } 179 assert.Equal(t, myCols, vtCols, "column names do not match - the expected values are what mysql produced") 180 } 181 } 182 stmt, err := sqlparser.Parse(query) 183 if err != nil { 184 t.Error(err) 185 return 186 } 187 orderBy := false 188 if selStmt, isSelStmt := stmt.(sqlparser.SelectStatement); isSelStmt { 189 orderBy = selStmt.GetOrderBy() != nil 190 } 191 192 if orderBy && sqltypes.ResultsEqual([]sqltypes.Result{*vtQr}, []sqltypes.Result{*mysqlQr}) { 193 return 194 } else if sqltypes.ResultsEqualUnordered([]sqltypes.Result{*vtQr}, []sqltypes.Result{*mysqlQr}) { 195 return 196 } 197 198 errStr := "Query (" + query + ") results mismatched.\nVitess Results:\n" 199 for _, row := range vtQr.Rows { 200 errStr += fmt.Sprintf("%s\n", row) 201 } 202 errStr += "MySQL Results:\n" 203 for _, row := range mysqlQr.Rows { 204 errStr += fmt.Sprintf("%s\n", row) 205 } 206 t.Error(errStr) 207 } 208 209 func compareVitessAndMySQLErrors(t *testing.T, vtErr, mysqlErr error) { 210 if vtErr != nil && mysqlErr != nil || vtErr == nil && mysqlErr == nil { 211 return 212 } 213 out := fmt.Sprintf("Vitess and MySQL are not erroring the same way.\nVitess error: %v\nMySQL error: %v", vtErr, mysqlErr) 214 t.Error(out) 215 }