vitess.io/vitess@v0.16.2/go/vt/vttablet/onlineddl/vrepl/columns.go (about)

     1  /*
     2     Copyright 2016 GitHub Inc.
     3  	 See https://github.com/github/gh-ost/blob/master/LICENSE
     4  */
     5  /*
     6  Copyright 2021 The Vitess Authors.
     7  
     8  Licensed under the Apache License, Version 2.0 (the "License");
     9  you may not use this file except in compliance with the License.
    10  You may obtain a copy of the License at
    11  
    12      http://www.apache.org/licenses/LICENSE-2.0
    13  
    14  Unless required by applicable law or agreed to in writing, software
    15  distributed under the License is distributed on an "AS IS" BASIS,
    16  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    17  See the License for the specific language governing permissions and
    18  limitations under the License.
    19  */
    20  
    21  package vrepl
    22  
    23  import (
    24  	"fmt"
    25  	"strings"
    26  
    27  	"vitess.io/vitess/go/vt/schema"
    28  )
    29  
    30  // expandedDataTypes maps some known and difficult-to-compute by INFORMATION_SCHEMA data types which expand other data types.
    31  // For example, in "date:datetime", datetime expands date because it has more precision. In "timestamp:date" date expands timestamp
    32  // because it can contain years not covered by timestamp.
    33  var expandedDataTypes = map[string]bool{
    34  	"time:datetime":      true,
    35  	"date:datetime":      true,
    36  	"timestamp:datetime": true,
    37  	"time:timestamp":     true,
    38  	"date:timestamp":     true,
    39  	"timestamp:date":     true,
    40  }
    41  
    42  // GetSharedColumns returns the intersection of two lists of columns in same order as the first list
    43  func GetSharedColumns(
    44  	sourceColumns, targetColumns *ColumnList,
    45  	sourceVirtualColumns, targetVirtualColumns *ColumnList,
    46  	parser *AlterTableParser,
    47  ) (
    48  	sourceSharedColumns *ColumnList,
    49  	targetSharedColumns *ColumnList,
    50  	droppedSourceNonGeneratedColumns *ColumnList,
    51  	sharedColumnsMap map[string]string,
    52  ) {
    53  	sharedColumnNames := []string{}
    54  	droppedSourceNonGeneratedColumnsNames := []string{}
    55  	for _, sourceColumn := range sourceColumns.Names() {
    56  		isSharedColumn := false
    57  		isVirtualColumnOnSource := false
    58  		for _, targetColumn := range targetColumns.Names() {
    59  			if strings.EqualFold(sourceColumn, targetColumn) {
    60  				// both tables have this column. Good start.
    61  				isSharedColumn = true
    62  				break
    63  			}
    64  			if strings.EqualFold(parser.columnRenameMap[sourceColumn], targetColumn) {
    65  				// column in source is renamed in target
    66  				isSharedColumn = true
    67  				break
    68  			}
    69  		}
    70  		for droppedColumn := range parser.DroppedColumnsMap() {
    71  			if strings.EqualFold(sourceColumn, droppedColumn) {
    72  				isSharedColumn = false
    73  				break
    74  			}
    75  		}
    76  		for _, virtualColumn := range sourceVirtualColumns.Names() {
    77  			// virtual/generated columns on source are silently skipped
    78  			if strings.EqualFold(sourceColumn, virtualColumn) {
    79  				isSharedColumn = false
    80  				isVirtualColumnOnSource = true
    81  			}
    82  		}
    83  		for _, virtualColumn := range targetVirtualColumns.Names() {
    84  			// virtual/generated columns on target are silently skipped
    85  			if strings.EqualFold(sourceColumn, virtualColumn) {
    86  				isSharedColumn = false
    87  			}
    88  		}
    89  		if isSharedColumn {
    90  			sharedColumnNames = append(sharedColumnNames, sourceColumn)
    91  		} else if !isVirtualColumnOnSource {
    92  			droppedSourceNonGeneratedColumnsNames = append(droppedSourceNonGeneratedColumnsNames, sourceColumn)
    93  		}
    94  	}
    95  	sharedColumnsMap = map[string]string{}
    96  	for _, columnName := range sharedColumnNames {
    97  		if mapped, ok := parser.columnRenameMap[columnName]; ok {
    98  			sharedColumnsMap[columnName] = mapped
    99  		} else {
   100  			sharedColumnsMap[columnName] = columnName
   101  		}
   102  	}
   103  	mappedSharedColumnNames := []string{}
   104  	for _, columnName := range sharedColumnNames {
   105  		mappedSharedColumnNames = append(mappedSharedColumnNames, sharedColumnsMap[columnName])
   106  	}
   107  	return NewColumnList(sharedColumnNames), NewColumnList(mappedSharedColumnNames), NewColumnList(droppedSourceNonGeneratedColumnsNames), sharedColumnsMap
   108  }
   109  
   110  // isExpandedColumn sees if target column has any value set/range that is impossible in source column. See GetExpandedColumns comment for examples
   111  func isExpandedColumn(sourceColumn *Column, targetColumn *Column) (bool, string) {
   112  	if targetColumn.IsNullable && !sourceColumn.IsNullable {
   113  		return true, "target is NULL-able, source is not"
   114  	}
   115  	if targetColumn.CharacterMaximumLength > sourceColumn.CharacterMaximumLength {
   116  		return true, "increased CHARACTER_MAXIMUM_LENGTH"
   117  	}
   118  	if targetColumn.NumericPrecision > sourceColumn.NumericPrecision {
   119  		return true, "increased NUMERIC_PRECISION"
   120  	}
   121  	if targetColumn.NumericScale > sourceColumn.NumericScale {
   122  		return true, "increased NUMERIC_SCALE"
   123  	}
   124  	if targetColumn.DateTimePrecision > sourceColumn.DateTimePrecision {
   125  		return true, "increased DATETIME_PRECISION"
   126  	}
   127  	if sourceColumn.IsNumeric() && targetColumn.IsNumeric() {
   128  		if sourceColumn.IsUnsigned && !targetColumn.IsUnsigned {
   129  			return true, "source is unsigned, target is signed"
   130  		}
   131  		if sourceColumn.NumericPrecision <= targetColumn.NumericPrecision && !sourceColumn.IsUnsigned && targetColumn.IsUnsigned {
   132  			// e.g. INT SIGNED => INT UNSIGNED, INT SIGNED = BIGINT UNSIGNED
   133  			return true, "target unsigned value exceeds source unsigned value"
   134  		}
   135  		if targetColumn.IsFloatingPoint() && !sourceColumn.IsFloatingPoint() {
   136  			return true, "target is floating point, source is not"
   137  		}
   138  	}
   139  	if expandedDataTypes[fmt.Sprintf("%s:%s", sourceColumn.DataType, targetColumn.DataType)] {
   140  		return true, "target is expanded data type of source"
   141  	}
   142  	if sourceColumn.Charset != targetColumn.Charset {
   143  		if targetColumn.Charset == "utf8mb4" {
   144  			return true, "expand character set to utf8mb4"
   145  		}
   146  		if strings.HasPrefix(targetColumn.Charset, "utf8") && !strings.HasPrefix(sourceColumn.Charset, "utf8") {
   147  			// not utf to utf
   148  			return true, "expand character set to utf8"
   149  		}
   150  	}
   151  	for _, colType := range []ColumnType{EnumColumnType, SetColumnType} {
   152  		// enums and sets have very similar properties, and are practically identical in our analysis
   153  		if sourceColumn.Type == colType {
   154  			// this is an enum or a set
   155  			if targetColumn.Type != colType {
   156  				return true, "conversion from enum/set to non-enum/set adds potential values"
   157  			}
   158  			// target is an enum or a set. See if all values on target exist in source
   159  			sourceEnumTokensMap := schema.ParseEnumOrSetTokensMap(sourceColumn.EnumValues)
   160  			targetEnumTokensMap := schema.ParseEnumOrSetTokensMap(targetColumn.EnumValues)
   161  			for k, v := range targetEnumTokensMap {
   162  				if sourceEnumTokensMap[k] != v {
   163  					return true, "target enum/set expands source enum/set"
   164  				}
   165  			}
   166  		}
   167  	}
   168  	return false, ""
   169  }
   170  
   171  // GetExpandedColumnNames is given source and target shared columns, and returns the list of columns whose data type is expanded.
   172  // An expanded data type is one where the target can have a value which the source does not. Examples:
   173  // - any NOT NULL to NULLable (a NULL in the target cannot appear on source)
   174  // - INT -> BIGINT (obvious)
   175  // - BIGINT UNSIGNED -> INT SIGNED (negative values)
   176  // - TIMESTAMP -> TIMESTAMP(3)
   177  // etc.
   178  func GetExpandedColumnNames(
   179  	sourceSharedColumns *ColumnList,
   180  	targetSharedColumns *ColumnList,
   181  ) (
   182  	expandedColumnNames []string,
   183  	expandedDescriptions map[string]string,
   184  ) {
   185  	expandedDescriptions = map[string]string{}
   186  	for i := range sourceSharedColumns.Columns() {
   187  		// source and target columns assumed to be mapped 1:1, same length
   188  		sourceColumn := sourceSharedColumns.Columns()[i]
   189  		targetColumn := targetSharedColumns.Columns()[i]
   190  
   191  		if isExpanded, description := isExpandedColumn(&sourceColumn, &targetColumn); isExpanded {
   192  			expandedColumnNames = append(expandedColumnNames, sourceColumn.Name)
   193  			expandedDescriptions[sourceColumn.Name] = description
   194  		}
   195  	}
   196  	return expandedColumnNames, expandedDescriptions
   197  }
   198  
   199  // GetNoDefaultColumnNames returns names of columns which have no default value, out of given list of columns
   200  func GetNoDefaultColumnNames(columns *ColumnList) (names []string) {
   201  	names = []string{}
   202  	for _, col := range columns.Columns() {
   203  		if !col.HasDefault() {
   204  			names = append(names, col.Name)
   205  		}
   206  	}
   207  	return names
   208  }