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  }