github.com/XiaoMi/Gaea@v1.2.5/parser/types/field_type.go (about)

     1  // Copyright 2015 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 types
    15  
    16  import (
    17  	"fmt"
    18  	"io"
    19  	"strings"
    20  
    21  	"github.com/XiaoMi/Gaea/mysql"
    22  	"github.com/XiaoMi/Gaea/parser/format"
    23  )
    24  
    25  // UnspecifiedLength is unspecified length.
    26  const (
    27  	UnspecifiedLength = -1
    28  )
    29  
    30  // FieldType records field type information.
    31  type FieldType struct {
    32  	Tp      byte
    33  	Flag    uint
    34  	Flen    int
    35  	Decimal int
    36  	Charset string
    37  	Collate string
    38  	// Elems is the element list for enum and set type.
    39  	Elems []string
    40  }
    41  
    42  // NewFieldType returns a FieldType,
    43  // with a type and other information about field type.
    44  func NewFieldType(tp byte) *FieldType {
    45  	return &FieldType{
    46  		Tp:      tp,
    47  		Flen:    UnspecifiedLength,
    48  		Decimal: UnspecifiedLength,
    49  	}
    50  }
    51  
    52  // Clone returns a copy of itself.
    53  func (ft *FieldType) Clone() *FieldType {
    54  	ret := *ft
    55  	return &ret
    56  }
    57  
    58  // Equal checks whether two FieldType objects are equal.
    59  func (ft *FieldType) Equal(other *FieldType) bool {
    60  	// We do not need to compare whole `ft.Flag == other.Flag` when wrapping cast upon an Expression.
    61  	// but need compare unsigned_flag of ft.Flag.
    62  	partialEqual := ft.Tp == other.Tp &&
    63  		ft.Flen == other.Flen &&
    64  		ft.Decimal == other.Decimal &&
    65  		ft.Charset == other.Charset &&
    66  		ft.Collate == other.Collate &&
    67  		mysql.HasUnsignedFlag(ft.Flag) == mysql.HasUnsignedFlag(other.Flag)
    68  	if !partialEqual || len(ft.Elems) != len(other.Elems) {
    69  		return false
    70  	}
    71  	for i := range ft.Elems {
    72  		if ft.Elems[i] != other.Elems[i] {
    73  			return false
    74  		}
    75  	}
    76  	return true
    77  }
    78  
    79  // EvalType gets the type in evaluation.
    80  func (ft *FieldType) EvalType() EvalType {
    81  	switch ft.Tp {
    82  	case mysql.TypeTiny, mysql.TypeShort, mysql.TypeInt24, mysql.TypeLong, mysql.TypeLonglong,
    83  		mysql.TypeBit, mysql.TypeYear:
    84  		return ETInt
    85  	case mysql.TypeFloat, mysql.TypeDouble:
    86  		return ETReal
    87  	case mysql.TypeNewDecimal:
    88  		return ETDecimal
    89  	case mysql.TypeDate, mysql.TypeDatetime:
    90  		return ETDatetime
    91  	case mysql.TypeTimestamp:
    92  		return ETTimestamp
    93  	case mysql.TypeDuration:
    94  		return ETDuration
    95  	case mysql.TypeJSON:
    96  		return ETJson
    97  	}
    98  	return ETString
    99  }
   100  
   101  // Hybrid checks whether a type is a hybrid type, which can represent different types of value in specific context.
   102  func (ft *FieldType) Hybrid() bool {
   103  	return ft.Tp == mysql.TypeEnum || ft.Tp == mysql.TypeBit || ft.Tp == mysql.TypeSet
   104  }
   105  
   106  // Init initializes the FieldType data.
   107  func (ft *FieldType) Init(tp byte) {
   108  	ft.Tp = tp
   109  	ft.Flen = UnspecifiedLength
   110  	ft.Decimal = UnspecifiedLength
   111  }
   112  
   113  // CompactStr only considers Tp/CharsetBin/Flen/Deimal.
   114  // This is used for showing column type in infoschema.
   115  func (ft *FieldType) CompactStr() string {
   116  	ts := TypeToStr(ft.Tp, ft.Charset)
   117  	suffix := ""
   118  
   119  	defaultFlen, defaultDecimal := mysql.GetDefaultFieldLengthAndDecimal(ft.Tp)
   120  	isDecimalNotDefault := ft.Decimal != defaultDecimal && ft.Decimal != 0 && ft.Decimal != UnspecifiedLength
   121  
   122  	// displayFlen and displayDecimal are flen and decimal values with `-1` substituted with default value.
   123  	displayFlen, displayDecimal := ft.Flen, ft.Decimal
   124  	if displayFlen == 0 || displayFlen == UnspecifiedLength {
   125  		displayFlen = defaultFlen
   126  	}
   127  	if displayDecimal == 0 || displayDecimal == UnspecifiedLength {
   128  		displayDecimal = defaultDecimal
   129  	}
   130  
   131  	switch ft.Tp {
   132  	case mysql.TypeEnum, mysql.TypeSet:
   133  		// Format is ENUM ('e1', 'e2') or SET ('e1', 'e2')
   134  		es := make([]string, 0, len(ft.Elems))
   135  		for _, e := range ft.Elems {
   136  			e = format.OutputFormat(e)
   137  			es = append(es, e)
   138  		}
   139  		suffix = fmt.Sprintf("('%s')", strings.Join(es, "','"))
   140  	case mysql.TypeTimestamp, mysql.TypeDatetime, mysql.TypeDuration:
   141  		if isDecimalNotDefault {
   142  			suffix = fmt.Sprintf("(%d)", displayDecimal)
   143  		}
   144  	case mysql.TypeDouble, mysql.TypeFloat:
   145  		// 1. Flen Not Default, Decimal Not Default -> Valid
   146  		// 2. Flen Not Default, Decimal Default (-1) -> Invalid
   147  		// 3. Flen Default, Decimal Not Default -> Valid
   148  		// 4. Flen Default, Decimal Default -> Valid (hide)
   149  		if isDecimalNotDefault {
   150  			suffix = fmt.Sprintf("(%d,%d)", displayFlen, displayDecimal)
   151  		}
   152  	case mysql.TypeNewDecimal:
   153  		suffix = fmt.Sprintf("(%d,%d)", displayFlen, displayDecimal)
   154  	case mysql.TypeBit, mysql.TypeShort, mysql.TypeTiny, mysql.TypeInt24, mysql.TypeLong, mysql.TypeLonglong, mysql.TypeVarchar, mysql.TypeString, mysql.TypeVarString:
   155  		// Flen is always shown.
   156  		suffix = fmt.Sprintf("(%d)", displayFlen)
   157  	}
   158  	return ts + suffix
   159  }
   160  
   161  // InfoSchemaStr joins the CompactStr with unsigned flag and
   162  // returns a string.
   163  func (ft *FieldType) InfoSchemaStr() string {
   164  	suffix := ""
   165  	if mysql.HasUnsignedFlag(ft.Flag) {
   166  		suffix = " unsigned"
   167  	}
   168  	return ft.CompactStr() + suffix
   169  }
   170  
   171  // String joins the information of FieldType and returns a string.
   172  // Note: when flen or decimal is unspecified, this function will use the default value instead of -1.
   173  func (ft *FieldType) String() string {
   174  	strs := []string{ft.CompactStr()}
   175  	if mysql.HasUnsignedFlag(ft.Flag) {
   176  		strs = append(strs, "UNSIGNED")
   177  	}
   178  	if mysql.HasZerofillFlag(ft.Flag) {
   179  		strs = append(strs, "ZEROFILL")
   180  	}
   181  	if mysql.HasBinaryFlag(ft.Flag) && ft.Tp != mysql.TypeString {
   182  		strs = append(strs, "BINARY")
   183  	}
   184  
   185  	if IsTypeChar(ft.Tp) || IsTypeBlob(ft.Tp) {
   186  		if ft.Charset != "" && ft.Charset != mysql.CharsetBin {
   187  			strs = append(strs, fmt.Sprintf("CHARACTER SET %s", ft.Charset))
   188  		}
   189  		if ft.Collate != "" && ft.Collate != mysql.CharsetBin {
   190  			strs = append(strs, fmt.Sprintf("COLLATE %s", ft.Collate))
   191  		}
   192  	}
   193  
   194  	return strings.Join(strs, " ")
   195  }
   196  
   197  // Restore implements Node interface.
   198  func (ft *FieldType) Restore(ctx *format.RestoreCtx) error {
   199  	ctx.WriteKeyWord(TypeToStr(ft.Tp, ft.Charset))
   200  
   201  	switch ft.Tp {
   202  	case mysql.TypeEnum, mysql.TypeSet:
   203  		ctx.WritePlain("(")
   204  		for i, e := range ft.Elems {
   205  			if i != 0 {
   206  				ctx.WritePlain(",")
   207  			}
   208  			ctx.WriteString(e)
   209  		}
   210  		ctx.WritePlain(")")
   211  	case mysql.TypeTimestamp, mysql.TypeDatetime, mysql.TypeDuration:
   212  		if ft.Flen > 0 && ft.Decimal > 0 {
   213  			ctx.WritePlainf("(%d)", ft.Decimal)
   214  		}
   215  	case mysql.TypeDouble, mysql.TypeFloat:
   216  		if ft.Flen > 0 && ft.Decimal > 0 {
   217  			ctx.WritePlainf("(%d,%d)", ft.Flen, ft.Decimal)
   218  		}
   219  	case mysql.TypeNewDecimal:
   220  		if ft.Flen > 0 && ft.Decimal > 0 {
   221  			ctx.WritePlainf("(%d,%d)", ft.Flen, ft.Decimal)
   222  		}
   223  	case mysql.TypeBit, mysql.TypeShort, mysql.TypeTiny, mysql.TypeInt24, mysql.TypeLong, mysql.TypeLonglong, mysql.TypeVarchar, mysql.TypeString, mysql.TypeVarString, mysql.TypeTinyBlob, mysql.TypeMediumBlob, mysql.TypeBlob, mysql.TypeLongBlob, mysql.TypeYear:
   224  		if ft.Flen > 0 {
   225  			ctx.WritePlainf("(%d)", ft.Flen)
   226  		}
   227  	}
   228  
   229  	if mysql.HasUnsignedFlag(ft.Flag) {
   230  		ctx.WriteKeyWord(" UNSIGNED")
   231  	}
   232  	if mysql.HasZerofillFlag(ft.Flag) {
   233  		ctx.WriteKeyWord(" ZEROFILL")
   234  	}
   235  	if mysql.HasBinaryFlag(ft.Flag) && ft.Charset != mysql.CharsetBin {
   236  		ctx.WriteKeyWord(" BINARY")
   237  	}
   238  
   239  	if IsTypeChar(ft.Tp) || IsTypeBlob(ft.Tp) {
   240  		if ft.Charset != "" && ft.Charset != mysql.CharsetBin {
   241  			ctx.WriteKeyWord(" CHARACTER SET " + ft.Charset)
   242  		}
   243  		if ft.Collate != "" && ft.Collate != mysql.CharsetBin {
   244  			ctx.WriteKeyWord(" COLLATE ")
   245  			ctx.WritePlain(ft.Collate)
   246  		}
   247  	}
   248  
   249  	return nil
   250  }
   251  
   252  // FormatAsCastType is used for write AST back to string.
   253  func (ft *FieldType) FormatAsCastType(w io.Writer) {
   254  	switch ft.Tp {
   255  	case mysql.TypeVarString:
   256  		if ft.Charset == mysql.CharsetBin && ft.Collate == mysql.CollationBin {
   257  			fmt.Fprint(w, "BINARY")
   258  		} else {
   259  			fmt.Fprint(w, "CHAR")
   260  		}
   261  		if ft.Flen != UnspecifiedLength {
   262  			fmt.Fprintf(w, "(%d)", ft.Flen)
   263  		}
   264  		if ft.Flag&mysql.BinaryFlag != 0 {
   265  			fmt.Fprint(w, " BINARY")
   266  		}
   267  		if ft.Charset != mysql.CharsetBin && ft.Charset != mysql.DefaultCharset {
   268  			fmt.Fprintf(w, " CHARACTER SET %s", ft.Charset)
   269  		}
   270  	case mysql.TypeDate:
   271  		fmt.Fprint(w, "DATE")
   272  	case mysql.TypeDatetime:
   273  		fmt.Fprint(w, "DATETIME")
   274  		if ft.Decimal > 0 {
   275  			fmt.Fprintf(w, "(%d)", ft.Decimal)
   276  		}
   277  	case mysql.TypeNewDecimal:
   278  		fmt.Fprint(w, "DECIMAL")
   279  		if ft.Flen > 0 && ft.Decimal > 0 {
   280  			fmt.Fprintf(w, "(%d, %d)", ft.Flen, ft.Decimal)
   281  		} else if ft.Flen > 0 {
   282  			fmt.Fprintf(w, "(%d)", ft.Flen)
   283  		}
   284  	case mysql.TypeDuration:
   285  		fmt.Fprint(w, "TIME")
   286  		if ft.Decimal > 0 {
   287  			fmt.Fprintf(w, "(%d)", ft.Decimal)
   288  		}
   289  	case mysql.TypeLonglong:
   290  		if ft.Flag&mysql.UnsignedFlag != 0 {
   291  			fmt.Fprint(w, "UNSIGNED")
   292  		} else {
   293  			fmt.Fprint(w, "SIGNED")
   294  		}
   295  	case mysql.TypeJSON:
   296  		fmt.Fprint(w, "JSON")
   297  	}
   298  }
   299  
   300  // VarStorageLen indicates this column is a variable length column.
   301  const VarStorageLen = -1
   302  
   303  // StorageLength is the length of stored value for the type.
   304  func (ft *FieldType) StorageLength() int {
   305  	switch ft.Tp {
   306  	case mysql.TypeTiny, mysql.TypeShort, mysql.TypeInt24, mysql.TypeLong,
   307  		mysql.TypeLonglong, mysql.TypeDouble, mysql.TypeFloat, mysql.TypeYear, mysql.TypeDuration,
   308  		mysql.TypeDate, mysql.TypeDatetime, mysql.TypeTimestamp, mysql.TypeEnum, mysql.TypeSet,
   309  		mysql.TypeBit:
   310  		// This may not be the accurate length, because we may encode them as varint.
   311  		return 8
   312  	case mysql.TypeNewDecimal:
   313  		precision, frac := ft.Flen-ft.Decimal, ft.Decimal
   314  		return precision/digitsPerWord*wordSize + dig2bytes[precision%digitsPerWord] + frac/digitsPerWord*wordSize + dig2bytes[frac%digitsPerWord]
   315  	default:
   316  		return VarStorageLen
   317  	}
   318  }