github.com/dolthub/go-mysql-server@v0.18.0/sql/types/enum.go (about) 1 // Copyright 2022 Dolthub, 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 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package types 16 17 import ( 18 "fmt" 19 "reflect" 20 "strconv" 21 "strings" 22 "unicode/utf8" 23 24 "github.com/dolthub/vitess/go/sqltypes" 25 "github.com/dolthub/vitess/go/vt/proto/query" 26 "github.com/shopspring/decimal" 27 "gopkg.in/src-d/go-errors.v1" 28 29 "github.com/dolthub/go-mysql-server/sql" 30 "github.com/dolthub/go-mysql-server/sql/encodings" 31 ) 32 33 const ( 34 // EnumTypeMinElements returns the minimum number of enumerations for the Enum type. 35 EnumTypeMinElements = 1 36 // EnumTypeMaxElements returns the maximum number of enumerations for the Enum type. 37 EnumTypeMaxElements = 65535 38 // / An ENUM column can have a maximum of 65,535 distinct elements. 39 ) 40 41 var ( 42 ErrConvertingToEnum = errors.NewKind("value %v is not valid for this Enum") 43 44 enumValueType = reflect.TypeOf(uint16(0)) 45 ) 46 47 type EnumType struct { 48 collation sql.CollationID 49 hashedValToIndex map[uint64]int 50 indexToVal []string 51 maxResponseByteLength uint32 52 } 53 54 var _ sql.EnumType = EnumType{} 55 var _ sql.CollationCoercible = EnumType{} 56 var _ sql.TypeWithCollation = EnumType{} 57 58 // CreateEnumType creates a EnumType. 59 func CreateEnumType(values []string, collation sql.CollationID) (sql.EnumType, error) { 60 if len(values) < EnumTypeMinElements { 61 return nil, fmt.Errorf("number of values may not be zero") 62 } 63 if len(values) > EnumTypeMaxElements { 64 return nil, fmt.Errorf("number of values is too large") 65 } 66 67 // maxResponseByteLength for an enum type is the bytes required to send back the largest enum value, 68 // including accounting for multibyte character representations. 69 var maxResponseByteLength uint32 70 maxCharLength := collation.Collation().CharacterSet.MaxLength() 71 valToIndex := make(map[uint64]int) 72 for i, value := range values { 73 if !collation.Equals(sql.Collation_binary) { 74 // Trailing spaces are automatically deleted from ENUM member values in the table definition when a table 75 // is created, unless the binary charset and collation is in use 76 value = strings.TrimRight(value, " ") 77 } 78 values[i] = value 79 hashedVal, err := collation.HashToUint(value) 80 if err != nil { 81 return nil, err 82 } 83 if _, ok := valToIndex[hashedVal]; ok { 84 return nil, fmt.Errorf("duplicate entry: %v", value) 85 } 86 // The elements listed in the column specification are assigned index numbers, beginning with 1. 87 valToIndex[hashedVal] = i + 1 88 89 byteLength := uint32(utf8.RuneCountInString(value) * int(maxCharLength)) 90 if byteLength > maxResponseByteLength { 91 maxResponseByteLength = byteLength 92 } 93 } 94 return EnumType{ 95 collation: collation, 96 hashedValToIndex: valToIndex, 97 indexToVal: values, 98 maxResponseByteLength: maxResponseByteLength, 99 }, nil 100 } 101 102 // MustCreateEnumType is the same as CreateEnumType except it panics on errors. 103 func MustCreateEnumType(values []string, collation sql.CollationID) sql.EnumType { 104 et, err := CreateEnumType(values, collation) 105 if err != nil { 106 panic(err) 107 } 108 return et 109 } 110 111 // MaxTextResponseByteLength implements the Type interface 112 func (t EnumType) MaxTextResponseByteLength(_ *sql.Context) uint32 { 113 return t.maxResponseByteLength 114 } 115 116 // Compare implements Type interface. 117 func (t EnumType) Compare(a interface{}, b interface{}) (int, error) { 118 if hasNulls, res := CompareNulls(a, b); hasNulls { 119 return res, nil 120 } 121 122 // Attempt to convert the values to their enum values, but don't error 123 // out if they aren't valid enum values. 124 ai, _, err := t.Convert(a) 125 if err != nil && !ErrConvertingToEnum.Is(err) { 126 return 0, err 127 } 128 bi, _, err := t.Convert(b) 129 if err != nil && !ErrConvertingToEnum.Is(err) { 130 return 0, err 131 } 132 133 if ai == nil && bi == nil { 134 return 0, nil 135 } else if ai == nil { 136 return -1, nil 137 } else if bi == nil { 138 return 1, nil 139 } 140 141 au := ai.(uint16) 142 bu := bi.(uint16) 143 144 if au < bu { 145 return -1, nil 146 } else if au > bu { 147 return 1, nil 148 } 149 return 0, nil 150 } 151 152 // Convert implements Type interface. 153 func (t EnumType) Convert(v interface{}) (interface{}, sql.ConvertInRange, error) { 154 if v == nil { 155 return nil, sql.InRange, nil 156 } 157 158 switch value := v.(type) { 159 case int: 160 if _, ok := t.At(value); ok { 161 return uint16(value), sql.InRange, nil 162 } 163 case uint: 164 return t.Convert(int(value)) 165 case int8: 166 return t.Convert(int(value)) 167 case uint8: 168 return t.Convert(int(value)) 169 case int16: 170 return t.Convert(int(value)) 171 case uint16: 172 return t.Convert(int(value)) 173 case int32: 174 return t.Convert(int(value)) 175 case uint32: 176 return t.Convert(int(value)) 177 case int64: 178 return t.Convert(int(value)) 179 case uint64: 180 return t.Convert(int(value)) 181 case float32: 182 return t.Convert(int(value)) 183 case float64: 184 return t.Convert(int(value)) 185 case decimal.Decimal: 186 return t.Convert(value.IntPart()) 187 case decimal.NullDecimal: 188 if !value.Valid { 189 return nil, sql.InRange, nil 190 } 191 return t.Convert(value.Decimal.IntPart()) 192 case string: 193 if index := t.IndexOf(value); index != -1 { 194 return uint16(index), sql.InRange, nil 195 } 196 case []byte: 197 return t.Convert(string(value)) 198 } 199 200 return nil, sql.InRange, ErrConvertingToEnum.New(v) 201 } 202 203 // MustConvert implements the Type interface. 204 func (t EnumType) MustConvert(v interface{}) interface{} { 205 value, _, err := t.Convert(v) 206 if err != nil { 207 panic(err) 208 } 209 return value 210 } 211 212 // Equals implements the Type interface. 213 func (t EnumType) Equals(otherType sql.Type) bool { 214 if ot, ok := otherType.(EnumType); ok && t.collation.Equals(ot.collation) && len(t.indexToVal) == len(ot.indexToVal) { 215 for i, val := range t.indexToVal { 216 if ot.indexToVal[i] != val { 217 return false 218 } 219 } 220 return true 221 } 222 return false 223 } 224 225 // Promote implements the Type interface. 226 func (t EnumType) Promote() sql.Type { 227 return t 228 } 229 230 // SQL implements Type interface. 231 func (t EnumType) SQL(ctx *sql.Context, dest []byte, v interface{}) (sqltypes.Value, error) { 232 if v == nil { 233 return sqltypes.NULL, nil 234 } 235 convertedValue, _, err := t.Convert(v) 236 if err != nil { 237 return sqltypes.Value{}, err 238 } 239 value, _ := t.At(int(convertedValue.(uint16))) 240 241 resultCharset := ctx.GetCharacterSetResults() 242 if resultCharset == sql.CharacterSet_Unspecified || resultCharset == sql.CharacterSet_binary { 243 resultCharset = t.collation.CharacterSet() 244 } 245 encodedBytes, ok := resultCharset.Encoder().Encode(encodings.StringToBytes(value)) 246 if !ok { 247 snippet := value 248 if len(snippet) > 50 { 249 snippet = snippet[:50] 250 } 251 snippet = strings.ToValidUTF8(snippet, string(utf8.RuneError)) 252 return sqltypes.Value{}, sql.ErrCharSetFailedToEncode.New(resultCharset.Name(), utf8.ValidString(value), snippet) 253 } 254 val := AppendAndSliceBytes(dest, encodedBytes) 255 256 return sqltypes.MakeTrusted(sqltypes.Enum, val), nil 257 } 258 259 // String implements Type interface. 260 func (t EnumType) String() string { 261 return t.StringWithTableCollation(sql.Collation_Default) 262 } 263 264 // Type implements Type interface. 265 func (t EnumType) Type() query.Type { 266 return sqltypes.Enum 267 } 268 269 // ValueType implements Type interface. 270 func (t EnumType) ValueType() reflect.Type { 271 return enumValueType 272 } 273 274 // CollationCoercibility implements sql.CollationCoercible interface. 275 func (t EnumType) CollationCoercibility(ctx *sql.Context) (collation sql.CollationID, coercibility byte) { 276 return t.collation, 4 277 } 278 279 // Zero implements Type interface. 280 func (t EnumType) Zero() interface{} { 281 // / If an ENUM column is declared NOT NULL, its default value is the first element of the list of permitted values. 282 return uint16(1) 283 } 284 285 // At implements EnumType interface. 286 func (t EnumType) At(index int) (string, bool) { 287 // / The elements listed in the column specification are assigned index numbers, beginning with 1. 288 index -= 1 289 if index < 0 || index >= len(t.indexToVal) { 290 return "", false 291 } 292 return t.indexToVal[index], true 293 } 294 295 // CharacterSet implements EnumType interface. 296 func (t EnumType) CharacterSet() sql.CharacterSetID { 297 return t.collation.CharacterSet() 298 } 299 300 // Collation implements EnumType interface. 301 func (t EnumType) Collation() sql.CollationID { 302 return t.collation 303 } 304 305 // IndexOf implements EnumType interface. 306 func (t EnumType) IndexOf(v string) int { 307 hashedVal, err := t.collation.HashToUint(v) 308 if err == nil { 309 if index, ok := t.hashedValToIndex[hashedVal]; ok { 310 return index 311 } 312 } 313 // / ENUM('0','1','2') 314 // / If you store '3', it does not match any enumeration value, so it is treated as an index and becomes '2' (the value with index 3). 315 if parsedIndex, err := strconv.ParseInt(v, 10, 32); err == nil { 316 if _, ok := t.At(int(parsedIndex)); ok { 317 return int(parsedIndex) 318 } 319 } 320 return -1 321 } 322 323 // NumberOfElements implements EnumType interface. 324 func (t EnumType) NumberOfElements() uint16 { 325 return uint16(len(t.indexToVal)) 326 } 327 328 // Values implements EnumType interface. 329 func (t EnumType) Values() []string { 330 vals := make([]string, len(t.indexToVal)) 331 copy(vals, t.indexToVal) 332 return vals 333 } 334 335 // WithNewCollation implements sql.TypeWithCollation interface. 336 func (t EnumType) WithNewCollation(collation sql.CollationID) (sql.Type, error) { 337 return CreateEnumType(t.indexToVal, collation) 338 } 339 340 // StringWithTableCollation implements sql.TypeWithCollation interface. 341 func (t EnumType) StringWithTableCollation(tableCollation sql.CollationID) string { 342 s := fmt.Sprintf("enum('%v')", strings.Join(t.indexToVal, `','`)) 343 if t.CharacterSet() != tableCollation.CharacterSet() { 344 s += " CHARACTER SET " + t.CharacterSet().String() 345 } 346 if t.collation != tableCollation { 347 s += " COLLATE " + t.collation.String() 348 } 349 return s 350 }