vitess.io/vitess@v0.16.2/go/vt/sidecardb/sidecardb_test.go (about) 1 /* 2 Copyright 2023 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 sidecardb 18 19 import ( 20 "context" 21 "expvar" 22 "fmt" 23 "sort" 24 "strings" 25 "testing" 26 27 "vitess.io/vitess/go/vt/sqlparser" 28 29 "github.com/stretchr/testify/require" 30 31 "vitess.io/vitess/go/stats" 32 33 "vitess.io/vitess/go/mysql/fakesqldb" 34 "vitess.io/vitess/go/sqltypes" 35 ) 36 37 // TestInitErrors validates that the schema init error stats are being correctly set 38 func TestInitErrors(t *testing.T) { 39 ctx := context.Background() 40 41 db := fakesqldb.New(t) 42 defer db.Close() 43 AddSchemaInitQueries(db, false) 44 db.AddQuery("use dbname", &sqltypes.Result{}) 45 sqlMode := sqltypes.MakeTestResult(sqltypes.MakeTestFields( 46 "sql_mode", 47 "varchar"), 48 "ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION", 49 ) 50 db.AddQuery("select @@session.sql_mode as sql_mode", sqlMode) 51 db.AddQueryPattern("set @@session.sql_mode=.*", &sqltypes.Result{}) 52 53 ddlErrorCount.Set(0) 54 ddlCount.Set(0) 55 56 cp := db.ConnParams() 57 conn, err := cp.Connect(ctx) 58 require.NoError(t, err) 59 60 type schemaError struct { 61 tableName string 62 errorValue string 63 } 64 65 // simulate two errors during table creation to validate error stats 66 schemaErrors := []schemaError{ 67 {"vreplication_log", "vreplication_log error"}, 68 {"copy_state", "copy_state error"}, 69 } 70 71 exec := func(ctx context.Context, query string, maxRows int, useDB bool) (*sqltypes.Result, error) { 72 if useDB { 73 if _, err := conn.ExecuteFetch(UseSidecarDatabaseQuery, maxRows, true); err != nil { 74 return nil, err 75 } 76 } 77 78 // simulate errors for the table creation DDLs applied for tables specified in schemaErrors 79 stmt, err := sqlparser.Parse(query) 80 if err != nil { 81 return nil, err 82 } 83 createTable, ok := stmt.(*sqlparser.CreateTable) 84 if ok { 85 for _, e := range schemaErrors { 86 if strings.EqualFold(e.tableName, createTable.Table.Name.String()) { 87 return nil, fmt.Errorf(e.errorValue) 88 } 89 } 90 } 91 return conn.ExecuteFetch(query, maxRows, true) 92 } 93 94 require.Equal(t, int64(0), GetDDLCount()) 95 err = Init(ctx, exec) 96 require.NoError(t, err) 97 require.Equal(t, int64(len(sidecarTables)-len(schemaErrors)), GetDDLCount()) 98 require.Equal(t, int64(len(schemaErrors)), GetDDLErrorCount()) 99 100 var want []string 101 for _, e := range schemaErrors { 102 want = append(want, e.errorValue) 103 } 104 // sort expected and reported errors for easy comparison 105 sort.Strings(want) 106 got := GetDDLErrorHistory() 107 sort.Slice(got, func(i, j int) bool { 108 return got[i].tableName < got[j].tableName 109 }) 110 var gotErrors string 111 stats.Register(func(name string, v expvar.Var) { 112 if name == StatsKeyErrors { 113 gotErrors = v.String() 114 } 115 }) 116 117 // for DDL errors, validate both the internal data structure and the stats endpoint 118 for i := range want { 119 if !strings.Contains(got[i].err.Error(), want[i]) { 120 require.FailNowf(t, "incorrect schema error", "got %s, want %s", got[i], want[i]) 121 } 122 if !strings.Contains(gotErrors, want[i]) { 123 require.FailNowf(t, "schema error not published", "got %s, want %s", gotErrors, want[i]) 124 } 125 } 126 } 127 128 // test the logic that confirms that the user defined schema's table name and qualifier are valid 129 func TestValidateSchema(t *testing.T) { 130 type testCase struct { 131 testName string 132 name string 133 schema string 134 mustError bool 135 } 136 testCases := []testCase{ 137 {"valid", "t1", "create table if not exists _vt.t1(i int)", false}, 138 {"no if not exists", "t1", "create table _vt.t1(i int)", true}, 139 {"invalid table name", "t2", "create table if not exists _vt.t1(i int)", true}, 140 {"invalid table name", "t1", "create table if not exists _vt.t2(i int)", true}, 141 {"invalid qualifier", "t1", "create table if not exists vt_product.t1(i int)", true}, 142 {"invalid qualifier", "t1", "create table if not exists t1(i int)", true}, 143 } 144 for _, tc := range testCases { 145 t.Run(tc.testName, func(t *testing.T) { 146 _, err := validateSchemaDefinition(tc.name, tc.schema) 147 if tc.mustError { 148 require.Error(t, err) 149 } else { 150 require.NoError(t, err) 151 } 152 }) 153 } 154 } 155 156 // TestAlterTableAlgorithm confirms that we use ALGORITHM=COPY during alter tables 157 func TestAlterTableAlgorithm(t *testing.T) { 158 type testCase struct { 159 testName string 160 tableName string 161 currentSchema string 162 desiredSchema string 163 } 164 testCases := []testCase{ 165 {"add column", "t1", "create table if not exists _vt.t1(i int)", "create table if not exists _vt.t1(i int, i1 int)"}, 166 {"modify column", "t1", "create table if not exists _vt.t1(i int)", "create table if not exists _vt.t(i float)"}, 167 } 168 si := &schemaInit{} 169 copyAlgo := sqlparser.AlgorithmValue("COPY") 170 for _, tc := range testCases { 171 t.Run(tc.testName, func(t *testing.T) { 172 diff, err := si.findTableSchemaDiff(tc.tableName, tc.currentSchema, tc.desiredSchema) 173 require.NoError(t, err) 174 stmt, err := sqlparser.Parse(diff) 175 require.NoError(t, err) 176 alterTable, ok := stmt.(*sqlparser.AlterTable) 177 require.True(t, ok) 178 require.NotNil(t, alterTable) 179 var alterAlgo sqlparser.AlterOption 180 for i, opt := range alterTable.AlterOptions { 181 if _, ok := opt.(sqlparser.AlgorithmValue); ok { 182 alterAlgo = alterTable.AlterOptions[i] 183 } 184 } 185 require.Equal(t, copyAlgo, alterAlgo) 186 }) 187 } 188 } 189 190 // Tests various non-error code paths in sidecardb 191 func TestMiscSidecarDB(t *testing.T) { 192 ctx := context.Background() 193 194 db := fakesqldb.New(t) 195 defer db.Close() 196 AddSchemaInitQueries(db, false) 197 db.AddQuery("use dbname", &sqltypes.Result{}) 198 db.AddQueryPattern("set @@session.sql_mode=.*", &sqltypes.Result{}) 199 200 cp := db.ConnParams() 201 conn, err := cp.Connect(ctx) 202 require.NoError(t, err) 203 exec := func(ctx context.Context, query string, maxRows int, useDB bool) (*sqltypes.Result, error) { 204 if useDB { 205 if _, err := conn.ExecuteFetch(UseSidecarDatabaseQuery, maxRows, true); err != nil { 206 return nil, err 207 } 208 } 209 return conn.ExecuteFetch(query, maxRows, true) 210 } 211 212 // tests init on empty db 213 ddlErrorCount.Set(0) 214 ddlCount.Set(0) 215 require.Equal(t, int64(0), GetDDLCount()) 216 err = Init(ctx, exec) 217 require.NoError(t, err) 218 require.Equal(t, int64(len(sidecarTables)), GetDDLCount()) 219 220 // tests init on already inited db 221 AddSchemaInitQueries(db, true) 222 err = Init(ctx, exec) 223 require.NoError(t, err) 224 require.Equal(t, int64(len(sidecarTables)), GetDDLCount()) 225 226 // tests misc paths not covered above 227 si := &schemaInit{ 228 ctx: ctx, 229 exec: exec, 230 } 231 result := sqltypes.MakeTestResult(sqltypes.MakeTestFields( 232 "Database", 233 "varchar"), 234 "currentDB", 235 ) 236 db.AddQuery(SelectCurrentDatabaseQuery, result) 237 238 currentDB, err := si.setCurrentDatabase("dbname") 239 require.NoError(t, err) 240 require.Equal(t, "currentDB", currentDB) 241 242 require.False(t, MatchesInitQuery("abc")) 243 require.True(t, MatchesInitQuery(SelectCurrentDatabaseQuery)) 244 require.True(t, MatchesInitQuery("CREATE TABLE IF NOT EXISTS `_vt`.vreplication")) 245 }