github.com/pingcap/ticdc@v0.0.0-20220526033649-485a10ef2652/cdc/model/schema_storage.go (about) 1 // Copyright 2020 PingCAP, Inc. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // See the License for the specific language governing permissions and 12 // limitations under the License. 13 14 package model 15 16 import ( 17 "fmt" 18 19 "github.com/pingcap/log" 20 21 "go.uber.org/zap" 22 23 "github.com/pingcap/parser/model" 24 "github.com/pingcap/parser/mysql" 25 "github.com/pingcap/parser/types" 26 "github.com/pingcap/tidb/table/tables" 27 "github.com/pingcap/tidb/util/rowcodec" 28 ) 29 30 const ( 31 // HandleIndexPKIsHandle represents that the handle index is the pk and the pk is the handle 32 HandleIndexPKIsHandle = -1 33 // HandleIndexTableIneligible represents that the table is ineligible 34 HandleIndexTableIneligible = -2 35 ) 36 37 // TableInfo provides meta data describing a DB table. 38 type TableInfo struct { 39 *model.TableInfo 40 SchemaID int64 41 TableName TableName 42 TableInfoVersion uint64 43 columnsOffset map[int64]int 44 indicesOffset map[int64]int 45 uniqueColumns map[int64]struct{} 46 47 // It's a mapping from ColumnID to the offset of the columns in row changed events. 48 RowColumnsOffset map[int64]int 49 50 ColumnsFlag map[int64]ColumnFlagType 51 52 // only for new row format decoder 53 handleColID []int64 54 55 // the mounter will choose this index to output delete events 56 // special value: 57 // HandleIndexPKIsHandle(-1) : pk is handle 58 // HandleIndexTableIneligible(-2) : the table is not eligible 59 HandleIndexID int64 60 61 IndexColumnsOffset [][]int 62 rowColInfos []rowcodec.ColInfo 63 rowColFieldTps map[int64]*types.FieldType 64 } 65 66 // WrapTableInfo creates a TableInfo from a timodel.TableInfo 67 func WrapTableInfo(schemaID int64, schemaName string, version uint64, info *model.TableInfo) *TableInfo { 68 ti := &TableInfo{ 69 TableInfo: info, 70 SchemaID: schemaID, 71 TableName: TableName{Schema: schemaName, Table: info.Name.O}, 72 TableInfoVersion: version, 73 columnsOffset: make(map[int64]int, len(info.Columns)), 74 indicesOffset: make(map[int64]int, len(info.Indices)), 75 uniqueColumns: make(map[int64]struct{}), 76 RowColumnsOffset: make(map[int64]int, len(info.Columns)), 77 ColumnsFlag: make(map[int64]ColumnFlagType, len(info.Columns)), 78 handleColID: []int64{-1}, 79 HandleIndexID: HandleIndexTableIneligible, 80 rowColInfos: make([]rowcodec.ColInfo, len(info.Columns)), 81 rowColFieldTps: make(map[int64]*types.FieldType, len(info.Columns)), 82 } 83 84 rowColumnsCurrentOffset := 0 85 86 for i, col := range ti.Columns { 87 ti.columnsOffset[col.ID] = i 88 pkIsHandle := false 89 if IsColCDCVisible(col) { 90 ti.RowColumnsOffset[col.ID] = rowColumnsCurrentOffset 91 rowColumnsCurrentOffset++ 92 pkIsHandle = (ti.PKIsHandle && mysql.HasPriKeyFlag(col.Flag)) || col.ID == model.ExtraHandleID 93 if pkIsHandle { 94 // pk is handle 95 ti.handleColID = []int64{col.ID} 96 ti.HandleIndexID = HandleIndexPKIsHandle 97 ti.uniqueColumns[col.ID] = struct{}{} 98 ti.IndexColumnsOffset = append(ti.IndexColumnsOffset, []int{ti.RowColumnsOffset[col.ID]}) 99 } else if ti.IsCommonHandle { 100 ti.HandleIndexID = HandleIndexPKIsHandle 101 ti.handleColID = ti.handleColID[:0] 102 pkIdx := tables.FindPrimaryIndex(info) 103 for _, pkCol := range pkIdx.Columns { 104 id := info.Columns[pkCol.Offset].ID 105 ti.handleColID = append(ti.handleColID, id) 106 } 107 } 108 109 } 110 ti.rowColInfos[i] = rowcodec.ColInfo{ 111 ID: col.ID, 112 IsPKHandle: pkIsHandle, 113 Ft: col.FieldType.Clone(), 114 VirtualGenCol: col.IsGenerated(), 115 } 116 ti.rowColFieldTps[col.ID] = ti.rowColInfos[i].Ft 117 } 118 119 for i, idx := range ti.Indices { 120 ti.indicesOffset[idx.ID] = i 121 if ti.IsIndexUnique(idx) { 122 for _, col := range idx.Columns { 123 ti.uniqueColumns[ti.Columns[col.Offset].ID] = struct{}{} 124 } 125 } 126 if idx.Primary || idx.Unique { 127 indexColOffset := make([]int, 0, len(idx.Columns)) 128 for _, idxCol := range idx.Columns { 129 colInfo := ti.Columns[idxCol.Offset] 130 if IsColCDCVisible(colInfo) { 131 indexColOffset = append(indexColOffset, ti.RowColumnsOffset[colInfo.ID]) 132 } 133 } 134 if len(indexColOffset) > 0 { 135 ti.IndexColumnsOffset = append(ti.IndexColumnsOffset, indexColOffset) 136 } 137 } 138 } 139 140 ti.findHandleIndex() 141 ti.initColumnsFlag() 142 log.Debug("warpped table info", zap.Reflect("tableInfo", ti)) 143 return ti 144 } 145 146 // TODO(hi-rustin): After we don't need to subscribe index update, 147 // findHandleIndex may be not necessary any more. 148 func (ti *TableInfo) findHandleIndex() { 149 if ti.HandleIndexID == HandleIndexPKIsHandle { 150 // pk is handle 151 return 152 } 153 handleIndexOffset := -1 154 for i, idx := range ti.Indices { 155 if !ti.IsIndexUnique(idx) { 156 continue 157 } 158 if idx.Primary { 159 handleIndexOffset = i 160 break 161 } 162 if handleIndexOffset < 0 { 163 handleIndexOffset = i 164 } else { 165 if len(ti.Indices[handleIndexOffset].Columns) > len(ti.Indices[i].Columns) || 166 (len(ti.Indices[handleIndexOffset].Columns) == len(ti.Indices[i].Columns) && 167 ti.Indices[handleIndexOffset].ID > ti.Indices[i].ID) { 168 handleIndexOffset = i 169 } 170 } 171 } 172 if handleIndexOffset >= 0 { 173 ti.HandleIndexID = ti.Indices[handleIndexOffset].ID 174 } 175 } 176 177 func (ti *TableInfo) initColumnsFlag() { 178 for _, colInfo := range ti.Columns { 179 var flag ColumnFlagType 180 if colInfo.Charset == "binary" { 181 flag.SetIsBinary() 182 } 183 if colInfo.IsGenerated() { 184 flag.SetIsGeneratedColumn() 185 } 186 if mysql.HasPriKeyFlag(colInfo.Flag) { 187 flag.SetIsPrimaryKey() 188 if ti.HandleIndexID == HandleIndexPKIsHandle { 189 flag.SetIsHandleKey() 190 } 191 } 192 if mysql.HasUniKeyFlag(colInfo.Flag) { 193 flag.SetIsUniqueKey() 194 } 195 if !mysql.HasNotNullFlag(colInfo.Flag) { 196 flag.SetIsNullable() 197 } 198 if mysql.HasMultipleKeyFlag(colInfo.Flag) { 199 flag.SetIsMultipleKey() 200 } 201 if mysql.HasUnsignedFlag(colInfo.Flag) { 202 flag.SetIsUnsigned() 203 } 204 ti.ColumnsFlag[colInfo.ID] = flag 205 } 206 207 // In TiDB, just as in MySQL, only the first column of an index can be marked as "multiple key" or "unique key", 208 // and only the first column of a unique index may be marked as "unique key". 209 // See https://dev.mysql.com/doc/refman/5.7/en/show-columns.html. 210 // Yet if an index has multiple columns, we would like to easily determine that all those columns are indexed, 211 // which is crucial for the completeness of the information we pass to the downstream. 212 // Therefore, instead of using the MySql standard, 213 // we made our own decision to mark all columns in an index with the appropriate flag(s). 214 for _, idxInfo := range ti.Indices { 215 for _, idxCol := range idxInfo.Columns { 216 colInfo := ti.Columns[idxCol.Offset] 217 flag := ti.ColumnsFlag[colInfo.ID] 218 if idxInfo.Primary { 219 flag.SetIsPrimaryKey() 220 } 221 if idxInfo.Unique { 222 flag.SetIsUniqueKey() 223 } 224 if len(idxInfo.Columns) > 1 { 225 flag.SetIsMultipleKey() 226 } 227 if idxInfo.ID == ti.HandleIndexID && ti.HandleIndexID >= 0 { 228 flag.SetIsHandleKey() 229 } 230 ti.ColumnsFlag[colInfo.ID] = flag 231 } 232 } 233 } 234 235 // GetColumnInfo returns the column info by ID 236 func (ti *TableInfo) GetColumnInfo(colID int64) (info *model.ColumnInfo, exist bool) { 237 colOffset, exist := ti.columnsOffset[colID] 238 if !exist { 239 return nil, false 240 } 241 return ti.Columns[colOffset], true 242 } 243 244 func (ti *TableInfo) String() string { 245 return fmt.Sprintf("TableInfo, ID: %d, Name:%s, ColNum: %d, IdxNum: %d, PKIsHandle: %t", ti.ID, ti.TableName, len(ti.Columns), len(ti.Indices), ti.PKIsHandle) 246 } 247 248 // GetIndexInfo returns the index info by ID 249 func (ti *TableInfo) GetIndexInfo(indexID int64) (info *model.IndexInfo, exist bool) { 250 indexOffset, exist := ti.indicesOffset[indexID] 251 if !exist { 252 return nil, false 253 } 254 return ti.Indices[indexOffset], true 255 } 256 257 // GetRowColInfos returns all column infos for rowcodec 258 func (ti *TableInfo) GetRowColInfos() ([]int64, map[int64]*types.FieldType, []rowcodec.ColInfo) { 259 return ti.handleColID, ti.rowColFieldTps, ti.rowColInfos 260 } 261 262 // IsColCDCVisible returns whether the col is visible for CDC 263 func IsColCDCVisible(col *model.ColumnInfo) bool { 264 // this column is a virtual generated column 265 if col.IsGenerated() && !col.GeneratedStored { 266 return false 267 } 268 return col.State == model.StatePublic 269 } 270 271 // GetUniqueKeys returns all unique keys of the table as a slice of column names 272 func (ti *TableInfo) GetUniqueKeys() [][]string { 273 var uniqueKeys [][]string 274 if ti.PKIsHandle { 275 for _, col := range ti.Columns { 276 if mysql.HasPriKeyFlag(col.Flag) { 277 // Prepend to make sure the primary key ends up at the front 278 uniqueKeys = [][]string{{col.Name.O}} 279 break 280 } 281 } 282 } 283 for _, idx := range ti.Indices { 284 if ti.IsIndexUnique(idx) { 285 colNames := make([]string, 0, len(idx.Columns)) 286 for _, col := range idx.Columns { 287 colNames = append(colNames, col.Name.O) 288 } 289 if idx.Primary { 290 uniqueKeys = append([][]string{colNames}, uniqueKeys...) 291 } else { 292 uniqueKeys = append(uniqueKeys, colNames) 293 } 294 } 295 } 296 return uniqueKeys 297 } 298 299 // IsColumnUnique returns whether the column is unique 300 func (ti *TableInfo) IsColumnUnique(colID int64) bool { 301 _, exist := ti.uniqueColumns[colID] 302 return exist 303 } 304 305 // ExistTableUniqueColumn returns whether the table has a unique column 306 func (ti *TableInfo) ExistTableUniqueColumn() bool { 307 return len(ti.uniqueColumns) != 0 308 } 309 310 // IsEligible returns whether the table is a eligible table 311 func (ti *TableInfo) IsEligible(forceReplicate bool) bool { 312 if forceReplicate { 313 return true 314 } 315 if ti.IsView() { 316 return true 317 } 318 return ti.ExistTableUniqueColumn() 319 } 320 321 // IsIndexUnique returns whether the index is unique 322 func (ti *TableInfo) IsIndexUnique(indexInfo *model.IndexInfo) bool { 323 if indexInfo.Primary { 324 return true 325 } 326 if indexInfo.Unique { 327 for _, col := range indexInfo.Columns { 328 colInfo := ti.Columns[col.Offset] 329 if !mysql.HasNotNullFlag(colInfo.Flag) { 330 return false 331 } 332 // this column is a virtual generated column 333 if colInfo.IsGenerated() && !colInfo.GeneratedStored { 334 return false 335 } 336 } 337 return true 338 } 339 return false 340 } 341 342 // Clone clones the TableInfo 343 func (ti *TableInfo) Clone() *TableInfo { 344 return WrapTableInfo(ti.SchemaID, ti.TableName.Schema, ti.TableInfoVersion, ti.TableInfo.Clone()) 345 }