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 }