vitess.io/vitess@v0.16.2/go/vt/mysqlctl/tmutils/schema.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 tmutils 18 19 import ( 20 "crypto/md5" 21 "encoding/hex" 22 "fmt" 23 "regexp" 24 "strings" 25 26 "google.golang.org/protobuf/proto" 27 28 "vitess.io/vitess/go/sqlescape" 29 30 "vitess.io/vitess/go/vt/concurrency" 31 "vitess.io/vitess/go/vt/schema" 32 33 tabletmanagerdatapb "vitess.io/vitess/go/vt/proto/tabletmanagerdata" 34 ) 35 36 // This file contains helper methods to deal with Schema information. 37 38 const ( 39 // TableBaseTable indicates the table type is a base table. 40 TableBaseTable = "BASE TABLE" 41 // TableView indicates the table type is a view. 42 TableView = "VIEW" 43 ) 44 45 // TableDefinitionGetColumn returns the index of a column inside a 46 // TableDefinition. 47 func TableDefinitionGetColumn(td *tabletmanagerdatapb.TableDefinition, name string) (index int, ok bool) { 48 lowered := strings.ToLower(name) 49 for i, n := range td.Columns { 50 if lowered == strings.ToLower(n) { 51 return i, true 52 } 53 } 54 return -1, false 55 } 56 57 // TableDefinitions is a list of TableDefinition, for sorting 58 type TableDefinitions []*tabletmanagerdatapb.TableDefinition 59 60 // Len returns TableDefinitions length. 61 func (tds TableDefinitions) Len() int { 62 return len(tds) 63 } 64 65 // Swap used for sorting TableDefinitions. 66 func (tds TableDefinitions) Swap(i, j int) { 67 tds[i], tds[j] = tds[j], tds[i] 68 } 69 70 // TableFilter is a filter for table names and types. 71 type TableFilter struct { 72 includeViews bool 73 74 filterTables bool 75 tableNames []string 76 tableREs []*regexp.Regexp 77 78 filterExcludeTables bool 79 excludeTableNames []string 80 excludeTableREs []*regexp.Regexp 81 } 82 83 // NewTableFilter creates a TableFilter for whitelisted tables 84 // (tables), no denied tables (excludeTables) and optionally 85 // views (includeViews). 86 func NewTableFilter(tables, excludeTables []string, includeViews bool) (*TableFilter, error) { 87 f := &TableFilter{ 88 includeViews: includeViews, 89 } 90 91 // Build a list of regexp to match table names against. 92 // We only use regexps if the name starts and ends with '/'. 93 // Otherwise the entry in the arrays is nil, and we use the original 94 // table name. 95 if len(tables) > 0 { 96 f.filterTables = true 97 for _, table := range tables { 98 if strings.HasPrefix(table, "/") { 99 table = strings.Trim(table, "/") 100 re, err := regexp.Compile(table) 101 if err != nil { 102 return nil, fmt.Errorf("cannot compile regexp %v for table: %v", table, err) 103 } 104 105 f.tableREs = append(f.tableREs, re) 106 } else { 107 f.tableNames = append(f.tableNames, table) 108 } 109 } 110 } 111 112 if len(excludeTables) > 0 { 113 f.filterExcludeTables = true 114 for _, table := range excludeTables { 115 if strings.HasPrefix(table, "/") { 116 table = strings.Trim(table, "/") 117 re, err := regexp.Compile(table) 118 if err != nil { 119 return nil, fmt.Errorf("cannot compile regexp %v for excludeTable: %v", table, err) 120 } 121 122 f.excludeTableREs = append(f.excludeTableREs, re) 123 } else { 124 f.excludeTableNames = append(f.excludeTableNames, table) 125 } 126 } 127 } 128 129 return f, nil 130 } 131 132 // Includes returns whether a tableName/tableType should be included in this TableFilter. 133 func (f *TableFilter) Includes(tableName string, tableType string) bool { 134 if f.filterTables { 135 matches := false 136 for _, name := range f.tableNames { 137 if strings.EqualFold(name, tableName) { 138 matches = true 139 break 140 } 141 } 142 143 if !matches { 144 for _, re := range f.tableREs { 145 if re.MatchString(tableName) { 146 matches = true 147 break 148 } 149 } 150 } 151 152 if !matches { 153 return false 154 } 155 } 156 157 if f.filterExcludeTables { 158 for _, name := range f.excludeTableNames { 159 if strings.EqualFold(name, tableName) { 160 return false 161 } 162 } 163 164 for _, re := range f.excludeTableREs { 165 if re.MatchString(tableName) { 166 return false 167 } 168 } 169 } 170 171 if !f.includeViews && tableType == TableView { 172 return false 173 } 174 175 return true 176 } 177 178 // FilterTables returns a copy which includes only whitelisted tables 179 // (tables), no denied tables (excludeTables) and optionally 180 // views (includeViews). 181 func FilterTables(sd *tabletmanagerdatapb.SchemaDefinition, tables, excludeTables []string, includeViews bool) (*tabletmanagerdatapb.SchemaDefinition, error) { 182 copy := proto.Clone(sd).(*tabletmanagerdatapb.SchemaDefinition) 183 copy.TableDefinitions = make([]*tabletmanagerdatapb.TableDefinition, 0, len(sd.TableDefinitions)) 184 185 f, err := NewTableFilter(tables, excludeTables, includeViews) 186 if err != nil { 187 return nil, err 188 } 189 190 for _, table := range sd.TableDefinitions { 191 if f.Includes(table.Name, table.Type) { 192 copy.TableDefinitions = append(copy.TableDefinitions, table) 193 } 194 } 195 196 // Regenerate hash over tables because it may have changed. 197 if copy.Version != "" { 198 GenerateSchemaVersion(copy) 199 } 200 201 return copy, nil 202 } 203 204 // GenerateSchemaVersion return a unique schema version string based on 205 // its TableDefinitions. 206 func GenerateSchemaVersion(sd *tabletmanagerdatapb.SchemaDefinition) { 207 hasher := md5.New() 208 for _, td := range sd.TableDefinitions { 209 if _, err := hasher.Write([]byte(td.Schema)); err != nil { 210 panic(err) // extremely unlikely 211 } 212 } 213 sd.Version = hex.EncodeToString(hasher.Sum(nil)) 214 } 215 216 // SchemaDefinitionGetTable returns TableDefinition for a given table name. 217 func SchemaDefinitionGetTable(sd *tabletmanagerdatapb.SchemaDefinition, table string) (td *tabletmanagerdatapb.TableDefinition, ok bool) { 218 for _, td := range sd.TableDefinitions { 219 if td.Name == table { 220 return td, true 221 } 222 } 223 return nil, false 224 } 225 226 // SchemaDefinitionToSQLStrings converts a SchemaDefinition to an array of SQL strings. The array contains all 227 // the SQL statements needed for creating the database, tables, and views - in that order. 228 // All SQL statements will have {{.DatabaseName}} in place of the actual db name. 229 func SchemaDefinitionToSQLStrings(sd *tabletmanagerdatapb.SchemaDefinition) []string { 230 sqlStrings := make([]string, 0, len(sd.TableDefinitions)+1) 231 createViewSQL := make([]string, 0, len(sd.TableDefinitions)) 232 233 // Backtick database name since keyspace names appear in the routing rules, and they might need to be escaped. 234 // We unescape() them first in case we have an explicitly escaped string was specified. 235 createDatabaseSQL := strings.Replace(sd.DatabaseSchema, "`{{.DatabaseName}}`", "{{.DatabaseName}}", -1) 236 createDatabaseSQL = strings.Replace(createDatabaseSQL, "{{.DatabaseName}}", sqlescape.EscapeID("{{.DatabaseName}}"), -1) 237 sqlStrings = append(sqlStrings, createDatabaseSQL) 238 239 for _, td := range sd.TableDefinitions { 240 if schema.IsInternalOperationTableName(td.Name) { 241 continue 242 } 243 if td.Type == TableView { 244 createViewSQL = append(createViewSQL, td.Schema) 245 } else { 246 lines := strings.Split(td.Schema, "\n") 247 for i, line := range lines { 248 if strings.HasPrefix(line, "CREATE TABLE `") { 249 lines[i] = strings.Replace(line, "CREATE TABLE `", "CREATE TABLE `{{.DatabaseName}}`.`", 1) 250 } 251 } 252 sqlStrings = append(sqlStrings, strings.Join(lines, "\n")) 253 } 254 } 255 256 return append(sqlStrings, createViewSQL...) 257 } 258 259 // DiffSchema generates a report on what's different between two SchemaDefinitions 260 // including views, but Vitess internal tables are ignored. 261 func DiffSchema(leftName string, left *tabletmanagerdatapb.SchemaDefinition, rightName string, right *tabletmanagerdatapb.SchemaDefinition, er concurrency.ErrorRecorder) { 262 if left == nil && right == nil { 263 return 264 } 265 if left == nil || right == nil { 266 er.RecordError(fmt.Errorf("schemas are different:\n%s: %v, %s: %v", leftName, left, rightName, right)) 267 return 268 } 269 if left.DatabaseSchema != right.DatabaseSchema { 270 er.RecordError(fmt.Errorf("schemas are different:\n%s: %v\n differs from:\n%s: %v", leftName, left.DatabaseSchema, rightName, right.DatabaseSchema)) 271 } 272 273 leftIndex := 0 274 rightIndex := 0 275 for leftIndex < len(left.TableDefinitions) && rightIndex < len(right.TableDefinitions) { 276 // extra table on the left side 277 if left.TableDefinitions[leftIndex].Name < right.TableDefinitions[rightIndex].Name { 278 if !schema.IsInternalOperationTableName(left.TableDefinitions[leftIndex].Name) { 279 er.RecordError(fmt.Errorf("%v has an extra table named %v", leftName, left.TableDefinitions[leftIndex].Name)) 280 } 281 leftIndex++ 282 continue 283 } 284 285 // extra table on the right side 286 if left.TableDefinitions[leftIndex].Name > right.TableDefinitions[rightIndex].Name { 287 if !schema.IsInternalOperationTableName(right.TableDefinitions[rightIndex].Name) { 288 er.RecordError(fmt.Errorf("%v has an extra table named %v", rightName, right.TableDefinitions[rightIndex].Name)) 289 } 290 rightIndex++ 291 continue 292 } 293 294 // same name, let's see content 295 if left.TableDefinitions[leftIndex].Schema != right.TableDefinitions[rightIndex].Schema { 296 if !schema.IsInternalOperationTableName(left.TableDefinitions[leftIndex].Name) { 297 er.RecordError(fmt.Errorf("schemas differ on table %v:\n%s: %v\n differs from:\n%s: %v", left.TableDefinitions[leftIndex].Name, leftName, left.TableDefinitions[leftIndex].Schema, rightName, right.TableDefinitions[rightIndex].Schema)) 298 } 299 } 300 301 if left.TableDefinitions[leftIndex].Type != right.TableDefinitions[rightIndex].Type { 302 if !schema.IsInternalOperationTableName(right.TableDefinitions[rightIndex].Name) { 303 er.RecordError(fmt.Errorf("schemas differ on table type for table %v:\n%s: %v\n differs from:\n%s: %v", left.TableDefinitions[leftIndex].Name, leftName, left.TableDefinitions[leftIndex].Type, rightName, right.TableDefinitions[rightIndex].Type)) 304 } 305 } 306 307 leftIndex++ 308 rightIndex++ 309 } 310 311 for leftIndex < len(left.TableDefinitions) { 312 if left.TableDefinitions[leftIndex].Type == TableBaseTable { 313 if !schema.IsInternalOperationTableName(left.TableDefinitions[leftIndex].Name) { 314 er.RecordError(fmt.Errorf("%v has an extra table named %v", leftName, left.TableDefinitions[leftIndex].Name)) 315 } 316 } 317 if left.TableDefinitions[leftIndex].Type == TableView { 318 er.RecordError(fmt.Errorf("%v has an extra view named %v", leftName, left.TableDefinitions[leftIndex].Name)) 319 } 320 leftIndex++ 321 } 322 for rightIndex < len(right.TableDefinitions) { 323 if right.TableDefinitions[rightIndex].Type == TableBaseTable { 324 if !schema.IsInternalOperationTableName(right.TableDefinitions[rightIndex].Name) { 325 er.RecordError(fmt.Errorf("%v has an extra table named %v", rightName, right.TableDefinitions[rightIndex].Name)) 326 } 327 } 328 if right.TableDefinitions[rightIndex].Type == TableView { 329 er.RecordError(fmt.Errorf("%v has an extra view named %v", rightName, right.TableDefinitions[rightIndex].Name)) 330 } 331 rightIndex++ 332 } 333 } 334 335 // DiffSchemaToArray diffs two schemas and return the schema diffs if there is any. 336 func DiffSchemaToArray(leftName string, left *tabletmanagerdatapb.SchemaDefinition, rightName string, right *tabletmanagerdatapb.SchemaDefinition) (result []string) { 337 er := concurrency.AllErrorRecorder{} 338 DiffSchema(leftName, left, rightName, right, &er) 339 if er.HasErrors() { 340 return er.ErrorStrings() 341 } 342 return nil 343 } 344 345 // SchemaChange contains all necessary information to apply a schema change. 346 // It should not be sent over the wire, it's just a set of parameters. 347 type SchemaChange struct { 348 SQL string 349 Force bool 350 AllowReplication bool 351 BeforeSchema *tabletmanagerdatapb.SchemaDefinition 352 AfterSchema *tabletmanagerdatapb.SchemaDefinition 353 SQLMode string 354 } 355 356 // Equal compares two SchemaChange objects. 357 func (s *SchemaChange) Equal(s2 *SchemaChange) bool { 358 return s.SQL == s2.SQL && 359 s.Force == s2.Force && 360 s.AllowReplication == s2.AllowReplication && 361 proto.Equal(s.BeforeSchema, s2.BeforeSchema) && 362 proto.Equal(s.AfterSchema, s2.AfterSchema) 363 }