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  }