377 lines
12 KiB
Go
377 lines
12 KiB
Go
// Copyright 2024 PingCAP, Inc.
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
package model
|
|
|
|
import (
|
|
"fmt"
|
|
"strings"
|
|
"unsafe"
|
|
|
|
"github.com/pingcap/tidb/pkg/parser/ast"
|
|
"github.com/pingcap/tidb/pkg/parser/charset"
|
|
"github.com/pingcap/tidb/pkg/parser/mysql"
|
|
"github.com/pingcap/tidb/pkg/parser/types"
|
|
)
|
|
|
|
const (
|
|
// ColumnInfoVersion0 means the column info version is 0.
|
|
ColumnInfoVersion0 = uint64(0)
|
|
// ColumnInfoVersion1 means the column info version is 1.
|
|
ColumnInfoVersion1 = uint64(1)
|
|
// ColumnInfoVersion2 means the column info version is 2.
|
|
// This is for v2.1.7 to Compatible with older versions charset problem.
|
|
// Old version such as v2.0.8 treat utf8 as utf8mb4, because there is no UTF8 check in v2.0.8.
|
|
// After version V2.1.2 (PR#8738) , TiDB add UTF8 check, then the user upgrade from v2.0.8 insert some UTF8MB4 characters will got error.
|
|
// This is not compatibility for user. Then we try to fix this in PR #9820, and increase the version number.
|
|
ColumnInfoVersion2 = uint64(2)
|
|
|
|
// CurrLatestColumnInfoVersion means the latest column info in the current TiDB.
|
|
CurrLatestColumnInfoVersion = ColumnInfoVersion2
|
|
)
|
|
|
|
const (
|
|
// changingColumnPrefix the prefix is used to initialize new column name created in modify column.
|
|
// The new name will be like "_Col$_<old_column_name>_n".
|
|
// TODO(joechenrh): using name to distinguish different stage of the column seems not to be a good
|
|
// idea, we can add new flag in FieldType to make it more clear later.
|
|
changingColumnPrefix = "_Col$_"
|
|
|
|
// removingObjPrefix the prefix is used to initialize the removing column/index name created in modify column.
|
|
removingObjPrefix = "_Tombstone$_"
|
|
)
|
|
|
|
// GenUniqueChangingColumnName generates a unique changing column name for modifying column.
|
|
func GenUniqueChangingColumnName(tblInfo *TableInfo, oldCol *ColumnInfo) string {
|
|
// Check whether the new column name is used.
|
|
columnNameMap := make(map[string]bool, len(tblInfo.Columns))
|
|
for _, col := range tblInfo.Columns {
|
|
columnNameMap[col.Name.L] = true
|
|
}
|
|
suffix := 0
|
|
newColumnName := fmt.Sprintf("%s%s_%d", changingColumnPrefix, oldCol.Name.O, suffix)
|
|
for columnNameMap[strings.ToLower(newColumnName)] {
|
|
suffix++
|
|
newColumnName = fmt.Sprintf("%s%s_%d", changingColumnPrefix, oldCol.Name.O, suffix)
|
|
}
|
|
return newColumnName
|
|
}
|
|
|
|
// GenRemovingObjName gets the removing object name with the prefix.
|
|
func GenRemovingObjName(name string) string {
|
|
if strings.HasPrefix(name, removingObjPrefix) {
|
|
return name
|
|
}
|
|
return fmt.Sprintf("%s%s", removingObjPrefix, name)
|
|
}
|
|
|
|
// ChangeStateInfo is used for recording the information of schema changing.
|
|
type ChangeStateInfo struct {
|
|
// DependencyColumnOffset is the changing column offset that the current column depends on when executing modify/change column.
|
|
DependencyColumnOffset int `json:"relative_col_offset"`
|
|
}
|
|
|
|
// ColumnInfo provides meta data describing of a table column.
|
|
type ColumnInfo struct {
|
|
ID int64 `json:"id"`
|
|
Name ast.CIStr `json:"name"`
|
|
Offset int `json:"offset"`
|
|
OriginDefaultValue any `json:"origin_default"`
|
|
OriginDefaultValueBit []byte `json:"origin_default_bit"`
|
|
DefaultValue any `json:"default"`
|
|
DefaultValueBit []byte `json:"default_bit"`
|
|
// DefaultIsExpr is indicates the default value string is expr.
|
|
DefaultIsExpr bool `json:"default_is_expr"`
|
|
GeneratedExprString string `json:"generated_expr_string"`
|
|
GeneratedStored bool `json:"generated_stored"`
|
|
Dependences map[string]struct{} `json:"dependences"`
|
|
FieldType types.FieldType `json:"type"`
|
|
// ChangingFieldType is used to store the new type of modify column.
|
|
ChangingFieldType *types.FieldType `json:"changing_type,omitempty"`
|
|
State SchemaState `json:"state"`
|
|
Comment string `json:"comment"`
|
|
// A hidden column is used internally(expression index) and are not accessible by users.
|
|
Hidden bool `json:"hidden"`
|
|
*ChangeStateInfo `json:"change_state_info"`
|
|
// Version means the version of the column info.
|
|
// Version = 0: For OriginDefaultValue and DefaultValue of timestamp column will stores the default time in system time zone.
|
|
// That is a bug if multiple TiDB servers in different system time zone.
|
|
// Version = 1: For OriginDefaultValue and DefaultValue of timestamp column will stores the default time in UTC time zone.
|
|
// This will fix bug in version 0. For compatibility with version 0, we add version field in column info struct.
|
|
Version uint64 `json:"version"`
|
|
}
|
|
|
|
// Clone clones ColumnInfo.
|
|
func (c *ColumnInfo) Clone() *ColumnInfo {
|
|
if c == nil {
|
|
return nil
|
|
}
|
|
nc := *c
|
|
return &nc
|
|
}
|
|
|
|
// GetType returns the type of ColumnInfo.
|
|
func (c *ColumnInfo) GetType() byte {
|
|
return c.FieldType.GetType()
|
|
}
|
|
|
|
// GetFlag returns the flag of ColumnInfo.
|
|
func (c *ColumnInfo) GetFlag() uint {
|
|
return c.FieldType.GetFlag()
|
|
}
|
|
|
|
// GetFlen returns the flen of ColumnInfo.
|
|
func (c *ColumnInfo) GetFlen() int {
|
|
return c.FieldType.GetFlen()
|
|
}
|
|
|
|
// GetDecimal returns the decimal of ColumnInfo.
|
|
func (c *ColumnInfo) GetDecimal() int {
|
|
return c.FieldType.GetDecimal()
|
|
}
|
|
|
|
// GetCharset returns the charset of ColumnInfo.
|
|
func (c *ColumnInfo) GetCharset() string {
|
|
return c.FieldType.GetCharset()
|
|
}
|
|
|
|
// GetCollate returns the collation of ColumnInfo.
|
|
func (c *ColumnInfo) GetCollate() string {
|
|
return c.FieldType.GetCollate()
|
|
}
|
|
|
|
// GetElems returns the elems of ColumnInfo.
|
|
func (c *ColumnInfo) GetElems() []string {
|
|
return c.FieldType.GetElems()
|
|
}
|
|
|
|
// SetType set the type of ColumnInfo.
|
|
func (c *ColumnInfo) SetType(tp byte) {
|
|
c.FieldType.SetType(tp)
|
|
}
|
|
|
|
// SetFlag set the flag of ColumnInfo.
|
|
func (c *ColumnInfo) SetFlag(flag uint) {
|
|
c.FieldType.SetFlag(flag)
|
|
}
|
|
|
|
// AddFlag adds the flag of ColumnInfo.
|
|
func (c *ColumnInfo) AddFlag(flag uint) {
|
|
c.FieldType.AddFlag(flag)
|
|
}
|
|
|
|
// AndFlag adds a flag to the column.
|
|
func (c *ColumnInfo) AndFlag(flag uint) {
|
|
c.FieldType.AndFlag(flag)
|
|
}
|
|
|
|
// ToggleFlag flips the flag according to the value.
|
|
func (c *ColumnInfo) ToggleFlag(flag uint) {
|
|
c.FieldType.ToggleFlag(flag)
|
|
}
|
|
|
|
// DelFlag removes the flag from the column's flag.
|
|
func (c *ColumnInfo) DelFlag(flag uint) {
|
|
c.FieldType.DelFlag(flag)
|
|
}
|
|
|
|
// SetFlen sets the flen of ColumnInfo.
|
|
func (c *ColumnInfo) SetFlen(flen int) {
|
|
c.FieldType.SetFlen(flen)
|
|
}
|
|
|
|
// SetDecimal sets the decimal of ColumnInfo.
|
|
func (c *ColumnInfo) SetDecimal(decimal int) {
|
|
c.FieldType.SetDecimal(decimal)
|
|
}
|
|
|
|
// SetCharset sets charset of the ColumnInfo
|
|
func (c *ColumnInfo) SetCharset(charset string) {
|
|
c.FieldType.SetCharset(charset)
|
|
}
|
|
|
|
// SetCollate sets the collation of the column.
|
|
func (c *ColumnInfo) SetCollate(collate string) {
|
|
c.FieldType.SetCollate(collate)
|
|
}
|
|
|
|
// SetElems set the elements of enum column.
|
|
func (c *ColumnInfo) SetElems(elems []string) {
|
|
c.FieldType.SetElems(elems)
|
|
}
|
|
|
|
// IsGenerated checks if the column is a generated column.
|
|
func (c *ColumnInfo) IsGenerated() bool {
|
|
return len(c.GeneratedExprString) != 0
|
|
}
|
|
|
|
// IsVirtualGenerated checks if the column is a virtual generated column.
|
|
func (c *ColumnInfo) IsVirtualGenerated() bool {
|
|
return c.IsGenerated() && !c.GeneratedStored
|
|
}
|
|
|
|
// IsChanging checks if the column is a new column added in modify column.
|
|
func (c *ColumnInfo) IsChanging() bool {
|
|
return strings.HasPrefix(c.Name.O, changingColumnPrefix)
|
|
}
|
|
|
|
// IsRemoving checks if the column is a column to be removed used in modify column.
|
|
func (c *ColumnInfo) IsRemoving() bool {
|
|
return strings.HasPrefix(c.Name.O, removingObjPrefix)
|
|
}
|
|
|
|
// GetRemovingOriginName gets the origin name of the removing column.
|
|
func (c *ColumnInfo) GetRemovingOriginName() string {
|
|
return strings.TrimPrefix(c.Name.O, removingObjPrefix)
|
|
}
|
|
|
|
// GetChangingOriginName gets the origin name of the changing column.
|
|
func (c *ColumnInfo) GetChangingOriginName() string {
|
|
columnName := strings.TrimPrefix(c.Name.O, changingColumnPrefix)
|
|
var pos int
|
|
if pos = strings.LastIndex(columnName, "_"); pos == -1 {
|
|
return columnName
|
|
}
|
|
return columnName[:pos]
|
|
}
|
|
|
|
// SetOriginDefaultValue sets the origin default value.
|
|
// For mysql.TypeBit type, the default value storage format must be a string.
|
|
// Other value such as int must convert to string format first.
|
|
// The mysql.TypeBit type supports the null default value.
|
|
func (c *ColumnInfo) SetOriginDefaultValue(value any) error {
|
|
c.OriginDefaultValue = value
|
|
if c.GetType() == mysql.TypeBit {
|
|
if value == nil {
|
|
return nil
|
|
}
|
|
if v, ok := value.(string); ok {
|
|
c.OriginDefaultValueBit = []byte(v)
|
|
return nil
|
|
}
|
|
return types.ErrInvalidDefault.GenWithStackByArgs(c.Name)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// GetOriginDefaultValue gets the origin default value.
|
|
func (c *ColumnInfo) GetOriginDefaultValue() any {
|
|
if c.GetType() == mysql.TypeBit && c.OriginDefaultValueBit != nil {
|
|
// If the column type is BIT, both `OriginDefaultValue` and `DefaultValue` of ColumnInfo are corrupted,
|
|
// because the content before json.Marshal is INCONSISTENT with the content after json.Unmarshal.
|
|
return string(c.OriginDefaultValueBit)
|
|
}
|
|
return c.OriginDefaultValue
|
|
}
|
|
|
|
// SetDefaultValue sets the default value.
|
|
func (c *ColumnInfo) SetDefaultValue(value any) error {
|
|
c.DefaultValue = value
|
|
if c.GetType() == mysql.TypeBit {
|
|
// For mysql.TypeBit type, the default value storage format must be a string.
|
|
// Other value such as int must convert to string format first.
|
|
// The mysql.TypeBit type supports the null default value.
|
|
if value == nil {
|
|
return nil
|
|
}
|
|
if v, ok := value.(string); ok {
|
|
c.DefaultValueBit = []byte(v)
|
|
return nil
|
|
}
|
|
return types.ErrInvalidDefault.GenWithStackByArgs(c.Name)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// GetDefaultValue gets the default value of the column.
|
|
// Default value use to stored in DefaultValue field, but now,
|
|
// bit type default value will store in DefaultValueBit for fix bit default value decode/encode bug.
|
|
func (c *ColumnInfo) GetDefaultValue() any {
|
|
if c.GetType() == mysql.TypeBit && c.DefaultValueBit != nil {
|
|
return string(c.DefaultValueBit)
|
|
}
|
|
return c.DefaultValue
|
|
}
|
|
|
|
// GetTypeDesc gets the description for column type.
|
|
func (c *ColumnInfo) GetTypeDesc() string {
|
|
desc := c.FieldType.CompactStr()
|
|
if mysql.HasUnsignedFlag(c.GetFlag()) && c.GetType() != mysql.TypeBit && c.GetType() != mysql.TypeYear {
|
|
desc += " unsigned"
|
|
}
|
|
if mysql.HasZerofillFlag(c.GetFlag()) && c.GetType() != mysql.TypeYear {
|
|
desc += " zerofill"
|
|
}
|
|
return desc
|
|
}
|
|
|
|
// EmptyColumnInfoSize is the memory usage of ColumnInfoSize
|
|
const EmptyColumnInfoSize = int64(unsafe.Sizeof(ColumnInfo{}))
|
|
|
|
// FindColumnInfo finds ColumnInfo in cols by name.
|
|
func FindColumnInfo(cols []*ColumnInfo, name string) *ColumnInfo {
|
|
name = strings.ToLower(name)
|
|
for _, col := range cols {
|
|
if col.Name.L == name {
|
|
return col
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// FindColumnInfoByID finds ColumnInfo in cols by id.
|
|
func FindColumnInfoByID(cols []*ColumnInfo, id int64) *ColumnInfo {
|
|
for _, col := range cols {
|
|
if col.ID == id {
|
|
return col
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// NewExtraHandleColInfo mocks a column info for extra handle column.
|
|
func NewExtraHandleColInfo() *ColumnInfo {
|
|
colInfo := &ColumnInfo{
|
|
ID: ExtraHandleID,
|
|
Name: ExtraHandleName,
|
|
}
|
|
|
|
colInfo.SetFlag(mysql.PriKeyFlag | mysql.NotNullFlag)
|
|
colInfo.SetType(mysql.TypeLonglong)
|
|
|
|
flen, decimal := mysql.GetDefaultFieldLengthAndDecimal(mysql.TypeLonglong)
|
|
colInfo.SetFlen(flen)
|
|
colInfo.SetDecimal(decimal)
|
|
|
|
colInfo.SetCharset(charset.CharsetBin)
|
|
colInfo.SetCollate(charset.CollationBin)
|
|
return colInfo
|
|
}
|
|
|
|
// NewExtraPhysTblIDColInfo mocks a column info for extra partition id column.
|
|
func NewExtraPhysTblIDColInfo() *ColumnInfo {
|
|
colInfo := &ColumnInfo{
|
|
ID: ExtraPhysTblID,
|
|
Name: ExtraPhysTblIDName,
|
|
}
|
|
colInfo.SetType(mysql.TypeLonglong)
|
|
colInfo.SetFlag(mysql.NotNullFlag)
|
|
flen, decimal := mysql.GetDefaultFieldLengthAndDecimal(mysql.TypeLonglong)
|
|
colInfo.SetFlen(flen)
|
|
colInfo.SetDecimal(decimal)
|
|
colInfo.SetCharset(charset.CharsetBin)
|
|
colInfo.SetCollate(charset.CollationBin)
|
|
return colInfo
|
|
}
|