You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
311 lines
7.0 KiB
Go
311 lines
7.0 KiB
Go
// Copyright 2014 The ql Authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style
|
|
// license that can be found in the LICENSES/QL-LICENSE file.
|
|
|
|
// Copyright 2015 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,
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
package types
|
|
|
|
import (
|
|
"fmt"
|
|
"io"
|
|
"strings"
|
|
|
|
"github.com/juju/errors"
|
|
"github.com/pingcap/tidb/mysql"
|
|
"github.com/pingcap/tidb/parser/opcode"
|
|
"github.com/pingcap/tidb/terror"
|
|
"github.com/pingcap/tidb/util/charset"
|
|
)
|
|
|
|
// IsTypeBlob returns a boolean indicating whether the tp is a blob type.
|
|
func IsTypeBlob(tp byte) bool {
|
|
switch tp {
|
|
case mysql.TypeTinyBlob, mysql.TypeMediumBlob, mysql.TypeBlob, mysql.TypeLongBlob:
|
|
return true
|
|
default:
|
|
return false
|
|
}
|
|
}
|
|
|
|
// IsTypeChar returns a boolean indicating
|
|
// whether the tp is the char type like a string type or a varchar type.
|
|
func IsTypeChar(tp byte) bool {
|
|
switch tp {
|
|
case mysql.TypeString, mysql.TypeVarchar:
|
|
return true
|
|
default:
|
|
return false
|
|
}
|
|
}
|
|
|
|
var type2Str = map[byte]string{
|
|
mysql.TypeBit: "bit",
|
|
mysql.TypeBlob: "text",
|
|
mysql.TypeDate: "date",
|
|
mysql.TypeDatetime: "datetime",
|
|
mysql.TypeDecimal: "decimal",
|
|
mysql.TypeNewDecimal: "decimal",
|
|
mysql.TypeDouble: "double",
|
|
mysql.TypeEnum: "enum",
|
|
mysql.TypeFloat: "float",
|
|
mysql.TypeGeometry: "geometry",
|
|
mysql.TypeInt24: "mediumint",
|
|
mysql.TypeLong: "int",
|
|
mysql.TypeLonglong: "bigint",
|
|
mysql.TypeLongBlob: "longtext",
|
|
mysql.TypeMediumBlob: "mediumtext",
|
|
mysql.TypeNull: "null",
|
|
mysql.TypeSet: "set",
|
|
mysql.TypeShort: "smallint",
|
|
mysql.TypeString: "char",
|
|
mysql.TypeDuration: "time",
|
|
mysql.TypeTimestamp: "timestamp",
|
|
mysql.TypeTiny: "tinyint",
|
|
mysql.TypeTinyBlob: "tinytext",
|
|
mysql.TypeVarchar: "varchar",
|
|
mysql.TypeVarString: "var_string",
|
|
mysql.TypeYear: "year",
|
|
}
|
|
|
|
// TypeStr converts tp to a string.
|
|
func TypeStr(tp byte) (r string) {
|
|
return type2Str[tp]
|
|
}
|
|
|
|
// TypeToStr converts a field to a string.
|
|
// It is used for converting Text to Blob,
|
|
// or converting Char to Binary.
|
|
// Args:
|
|
// tp: type enum
|
|
// cs: charset
|
|
func TypeToStr(tp byte, cs string) (r string) {
|
|
ts := type2Str[tp]
|
|
if cs != charset.CharsetBin {
|
|
return ts
|
|
}
|
|
if IsTypeBlob(tp) {
|
|
ts = strings.Replace(ts, "text", "blob", 1)
|
|
} else if IsTypeChar(tp) {
|
|
ts = strings.Replace(ts, "char", "binary", 1)
|
|
}
|
|
return ts
|
|
}
|
|
|
|
// EOFAsNil filtrates errors,
|
|
// If err is equal to io.EOF returns nil.
|
|
func EOFAsNil(err error) error {
|
|
if terror.ErrorEqual(err, io.EOF) {
|
|
return nil
|
|
}
|
|
return errors.Trace(err)
|
|
}
|
|
|
|
// InvOp2 returns an invalid operation error.
|
|
func InvOp2(x, y interface{}, o opcode.Op) (interface{}, error) {
|
|
return nil, errors.Errorf("Invalid operation: %v %v %v (mismatched types %T and %T)", x, o, y, x, y)
|
|
}
|
|
|
|
// UndOp returns an undefined error.
|
|
func UndOp(x interface{}, o opcode.Op) (interface{}, error) {
|
|
return nil, errors.Errorf("Invalid operation: %v%v (operator %v not defined on %T)", o, x, o, x)
|
|
}
|
|
|
|
// Overflow returns an overflowed error.
|
|
func overflow(v interface{}, tp byte) error {
|
|
return errors.Errorf("constant %v overflows %s", v, TypeStr(tp))
|
|
}
|
|
|
|
// TODO: collate should return errors from Compare.
|
|
func collate(x, y []interface{}) (r int) {
|
|
nx, ny := len(x), len(y)
|
|
|
|
switch {
|
|
case nx == 0 && ny != 0:
|
|
return -1
|
|
case nx == 0 && ny == 0:
|
|
return 0
|
|
case nx != 0 && ny == 0:
|
|
return 1
|
|
}
|
|
|
|
r = 1
|
|
if nx > ny {
|
|
x, y, r = y, x, -r
|
|
}
|
|
|
|
for i, xi := range x {
|
|
// TODO: we may remove collate later, so here just panic error.
|
|
c, err := Compare(xi, y[i])
|
|
if err != nil {
|
|
panic(fmt.Sprintf("should never happend %v", err))
|
|
}
|
|
|
|
if c != 0 {
|
|
return c * r
|
|
}
|
|
}
|
|
|
|
if nx == ny {
|
|
return 0
|
|
}
|
|
|
|
return -r
|
|
}
|
|
|
|
// Collators maps a boolean value to a collated function.
|
|
var Collators = map[bool]func(a, b []interface{}) int{false: collateDesc, true: collate}
|
|
|
|
func collateDesc(a, b []interface{}) int {
|
|
return -collate(a, b)
|
|
}
|
|
|
|
// IsOrderedType returns a boolean
|
|
// whether the type of y can be used by order by.
|
|
func IsOrderedType(v interface{}) (r bool) {
|
|
switch v.(type) {
|
|
case int, int8, int16, int32, int64,
|
|
uint, uint8, uint16, uint32, uint64,
|
|
float32, float64, string, []byte,
|
|
mysql.Decimal, mysql.Time, mysql.Duration,
|
|
mysql.Hex, mysql.Bit, mysql.Enum, mysql.Set:
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
// Clone copies an interface to another interface.
|
|
// It does a deep copy.
|
|
func Clone(from interface{}) (interface{}, error) {
|
|
if from == nil {
|
|
return nil, nil
|
|
}
|
|
switch x := from.(type) {
|
|
case uint8, uint16, uint32, uint64, float32, float64,
|
|
int16, int8, bool, string, int, int64, int32,
|
|
mysql.Time, mysql.Duration, mysql.Decimal,
|
|
mysql.Hex, mysql.Bit, mysql.Enum, mysql.Set:
|
|
return x, nil
|
|
case []byte:
|
|
target := make([]byte, len(from.([]byte)))
|
|
copy(target, from.([]byte))
|
|
return target, nil
|
|
case []interface{}:
|
|
var r []interface{}
|
|
for _, v := range from.([]interface{}) {
|
|
vv, err := Clone(v)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
r = append(r, vv)
|
|
}
|
|
return r, nil
|
|
default:
|
|
return nil, errors.Errorf("Clone invalid type %T", from)
|
|
}
|
|
}
|
|
|
|
func convergeType(a interface{}, hasDecimal, hasFloat *bool) (x interface{}) {
|
|
x = a
|
|
switch v := a.(type) {
|
|
case bool:
|
|
// treat bool as 1 and 0
|
|
if v {
|
|
x = int64(1)
|
|
} else {
|
|
x = int64(0)
|
|
}
|
|
case int:
|
|
x = int64(v)
|
|
case int8:
|
|
x = int64(v)
|
|
case int16:
|
|
x = int64(v)
|
|
case int32:
|
|
x = int64(v)
|
|
case int64:
|
|
x = int64(v)
|
|
case uint:
|
|
x = uint64(v)
|
|
case uint8:
|
|
x = uint64(v)
|
|
case uint16:
|
|
x = uint64(v)
|
|
case uint32:
|
|
x = uint64(v)
|
|
case uint64:
|
|
x = uint64(v)
|
|
case float32:
|
|
x = float64(v)
|
|
*hasFloat = true
|
|
case float64:
|
|
x = float64(v)
|
|
*hasFloat = true
|
|
case mysql.Decimal:
|
|
x = v
|
|
*hasDecimal = true
|
|
}
|
|
return
|
|
}
|
|
|
|
// Coerce changes type.
|
|
// If a or b is Decimal, changes the both to Decimal.
|
|
// Else if a or b is Float, changes the both to Float.
|
|
func Coerce(a, b interface{}) (x, y interface{}) {
|
|
var hasDecimal bool
|
|
var hasFloat bool
|
|
x = convergeType(a, &hasDecimal, &hasFloat)
|
|
y = convergeType(b, &hasDecimal, &hasFloat)
|
|
if hasDecimal {
|
|
d, err := mysql.ConvertToDecimal(x)
|
|
if err == nil {
|
|
x = d
|
|
}
|
|
d, err = mysql.ConvertToDecimal(y)
|
|
if err == nil {
|
|
y = d
|
|
}
|
|
} else if hasFloat {
|
|
switch v := x.(type) {
|
|
case int64:
|
|
x = float64(v)
|
|
case uint64:
|
|
x = float64(v)
|
|
case mysql.Hex:
|
|
x = v.ToNumber()
|
|
case mysql.Bit:
|
|
x = v.ToNumber()
|
|
case mysql.Enum:
|
|
x = v.ToNumber()
|
|
case mysql.Set:
|
|
x = v.ToNumber()
|
|
}
|
|
switch v := y.(type) {
|
|
case int64:
|
|
y = float64(v)
|
|
case uint64:
|
|
y = float64(v)
|
|
case mysql.Hex:
|
|
y = v.ToNumber()
|
|
case mysql.Bit:
|
|
y = v.ToNumber()
|
|
case mysql.Enum:
|
|
y = v.ToNumber()
|
|
case mysql.Set:
|
|
y = v.ToNumber()
|
|
}
|
|
}
|
|
return
|
|
}
|