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  }