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.
255 lines
6.2 KiB
Go
255 lines
6.2 KiB
Go
2 years ago
|
package mysql
|
||
|
|
||
|
import (
|
||
|
"math"
|
||
|
"strconv"
|
||
|
|
||
|
"github.com/pingcap/errors"
|
||
|
"github.com/siddontang/go/hack"
|
||
|
)
|
||
|
|
||
|
func FormatTextValue(value interface{}) ([]byte, error) {
|
||
|
switch v := value.(type) {
|
||
|
case int8:
|
||
|
return strconv.AppendInt(nil, int64(v), 10), nil
|
||
|
case int16:
|
||
|
return strconv.AppendInt(nil, int64(v), 10), nil
|
||
|
case int32:
|
||
|
return strconv.AppendInt(nil, int64(v), 10), nil
|
||
|
case int64:
|
||
|
return strconv.AppendInt(nil, v, 10), nil
|
||
|
case int:
|
||
|
return strconv.AppendInt(nil, int64(v), 10), nil
|
||
|
case uint8:
|
||
|
return strconv.AppendUint(nil, uint64(v), 10), nil
|
||
|
case uint16:
|
||
|
return strconv.AppendUint(nil, uint64(v), 10), nil
|
||
|
case uint32:
|
||
|
return strconv.AppendUint(nil, uint64(v), 10), nil
|
||
|
case uint64:
|
||
|
return strconv.AppendUint(nil, v, 10), nil
|
||
|
case uint:
|
||
|
return strconv.AppendUint(nil, uint64(v), 10), nil
|
||
|
case float32:
|
||
|
return strconv.AppendFloat(nil, float64(v), 'f', -1, 64), nil
|
||
|
case float64:
|
||
|
return strconv.AppendFloat(nil, v, 'f', -1, 64), nil
|
||
|
case []byte:
|
||
|
return v, nil
|
||
|
case string:
|
||
|
return hack.Slice(v), nil
|
||
|
case nil:
|
||
|
return nil, nil
|
||
|
default:
|
||
|
return nil, errors.Errorf("invalid type %T", value)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func formatBinaryValue(value interface{}) ([]byte, error) {
|
||
|
switch v := value.(type) {
|
||
|
case int8:
|
||
|
return Uint64ToBytes(uint64(v)), nil
|
||
|
case int16:
|
||
|
return Uint64ToBytes(uint64(v)), nil
|
||
|
case int32:
|
||
|
return Uint64ToBytes(uint64(v)), nil
|
||
|
case int64:
|
||
|
return Uint64ToBytes(uint64(v)), nil
|
||
|
case int:
|
||
|
return Uint64ToBytes(uint64(v)), nil
|
||
|
case uint8:
|
||
|
return Uint64ToBytes(uint64(v)), nil
|
||
|
case uint16:
|
||
|
return Uint64ToBytes(uint64(v)), nil
|
||
|
case uint32:
|
||
|
return Uint64ToBytes(uint64(v)), nil
|
||
|
case uint64:
|
||
|
return Uint64ToBytes(v), nil
|
||
|
case uint:
|
||
|
return Uint64ToBytes(uint64(v)), nil
|
||
|
case float32:
|
||
|
return Uint64ToBytes(math.Float64bits(float64(v))), nil
|
||
|
case float64:
|
||
|
return Uint64ToBytes(math.Float64bits(v)), nil
|
||
|
case []byte:
|
||
|
return v, nil
|
||
|
case string:
|
||
|
return hack.Slice(v), nil
|
||
|
default:
|
||
|
return nil, errors.Errorf("invalid type %T", value)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func fieldType(value interface{}) (typ uint8, err error) {
|
||
|
switch value.(type) {
|
||
|
case int8, int16, int32, int64, int:
|
||
|
typ = MYSQL_TYPE_LONGLONG
|
||
|
case uint8, uint16, uint32, uint64, uint:
|
||
|
typ = MYSQL_TYPE_LONGLONG
|
||
|
case float32, float64:
|
||
|
typ = MYSQL_TYPE_DOUBLE
|
||
|
case string, []byte:
|
||
|
typ = MYSQL_TYPE_VAR_STRING
|
||
|
case nil:
|
||
|
typ = MYSQL_TYPE_NULL
|
||
|
default:
|
||
|
err = errors.Errorf("unsupport type %T for resultset", value)
|
||
|
}
|
||
|
return
|
||
|
}
|
||
|
|
||
|
func formatField(field *Field, value interface{}) error {
|
||
|
switch value.(type) {
|
||
|
case int8, int16, int32, int64, int:
|
||
|
field.Charset = 63
|
||
|
field.Flag = BINARY_FLAG | NOT_NULL_FLAG
|
||
|
case uint8, uint16, uint32, uint64, uint:
|
||
|
field.Charset = 63
|
||
|
field.Flag = BINARY_FLAG | NOT_NULL_FLAG | UNSIGNED_FLAG
|
||
|
case float32, float64:
|
||
|
field.Charset = 63
|
||
|
field.Flag = BINARY_FLAG | NOT_NULL_FLAG
|
||
|
case string, []byte:
|
||
|
field.Charset = 33
|
||
|
case nil:
|
||
|
field.Charset = 33
|
||
|
default:
|
||
|
return errors.Errorf("unsupport type %T for resultset", value)
|
||
|
}
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
func BuildSimpleTextResultset(names []string, values [][]interface{}) (*Resultset, error) {
|
||
|
r := new(Resultset)
|
||
|
|
||
|
r.Fields = make([]*Field, len(names))
|
||
|
|
||
|
var b []byte
|
||
|
|
||
|
if len(values) == 0 {
|
||
|
for i, name := range names {
|
||
|
r.Fields[i] = &Field{Name: hack.Slice(name), Charset: 33, Type: MYSQL_TYPE_NULL}
|
||
|
}
|
||
|
return r, nil
|
||
|
}
|
||
|
|
||
|
for i, vs := range values {
|
||
|
if len(vs) != len(r.Fields) {
|
||
|
return nil, errors.Errorf("row %d has %d column not equal %d", i, len(vs), len(r.Fields))
|
||
|
}
|
||
|
|
||
|
var row []byte
|
||
|
for j, value := range vs {
|
||
|
typ, err := fieldType(value)
|
||
|
if err != nil {
|
||
|
return nil, errors.Trace(err)
|
||
|
}
|
||
|
if r.Fields[j] == nil {
|
||
|
r.Fields[j] = &Field{Name: hack.Slice(names[j]), Type: typ}
|
||
|
err = formatField(r.Fields[j], value)
|
||
|
if err != nil {
|
||
|
return nil, errors.Trace(err)
|
||
|
}
|
||
|
} else if typ != r.Fields[j].Type {
|
||
|
// we got another type in the same column. in general, we treat it as an error, except
|
||
|
// the case, when old value was null, and the new one isn't null, so we can update
|
||
|
// type info for fields.
|
||
|
oldIsNull, newIsNull := r.Fields[j].Type == MYSQL_TYPE_NULL, typ == MYSQL_TYPE_NULL
|
||
|
if oldIsNull && !newIsNull { // old is null, new isn't, update type info.
|
||
|
r.Fields[j].Type = typ
|
||
|
err = formatField(r.Fields[j], value)
|
||
|
if err != nil {
|
||
|
return nil, errors.Trace(err)
|
||
|
}
|
||
|
} else if !oldIsNull && !newIsNull { // different non-null types, that's an error.
|
||
|
return nil, errors.Errorf("row types aren't consistent")
|
||
|
}
|
||
|
}
|
||
|
b, err = FormatTextValue(value)
|
||
|
|
||
|
if err != nil {
|
||
|
return nil, errors.Trace(err)
|
||
|
}
|
||
|
|
||
|
if b == nil {
|
||
|
// NULL value is encoded as 0xfb here (without additional info about length)
|
||
|
row = append(row, 0xfb)
|
||
|
} else {
|
||
|
row = append(row, PutLengthEncodedString(b)...)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
r.RowDatas = append(r.RowDatas, row)
|
||
|
}
|
||
|
|
||
|
return r, nil
|
||
|
}
|
||
|
|
||
|
func BuildSimpleBinaryResultset(names []string, values [][]interface{}) (*Resultset, error) {
|
||
|
r := new(Resultset)
|
||
|
|
||
|
r.Fields = make([]*Field, len(names))
|
||
|
|
||
|
var b []byte
|
||
|
|
||
|
bitmapLen := (len(names) + 7 + 2) >> 3
|
||
|
|
||
|
for i, vs := range values {
|
||
|
if len(vs) != len(r.Fields) {
|
||
|
return nil, errors.Errorf("row %d has %d column not equal %d", i, len(vs), len(r.Fields))
|
||
|
}
|
||
|
|
||
|
var row []byte
|
||
|
nullBitmap := make([]byte, bitmapLen)
|
||
|
|
||
|
row = append(row, 0)
|
||
|
row = append(row, nullBitmap...)
|
||
|
|
||
|
for j, value := range vs {
|
||
|
typ, err := fieldType(value)
|
||
|
if err != nil {
|
||
|
return nil, errors.Trace(err)
|
||
|
}
|
||
|
if i == 0 {
|
||
|
field := &Field{Type: typ}
|
||
|
r.Fields[j] = field
|
||
|
field.Name = hack.Slice(names[j])
|
||
|
|
||
|
if err = formatField(field, value); err != nil {
|
||
|
return nil, errors.Trace(err)
|
||
|
}
|
||
|
}
|
||
|
if value == nil {
|
||
|
nullBitmap[(i+2)/8] |= 1 << (uint(i+2) % 8)
|
||
|
continue
|
||
|
}
|
||
|
|
||
|
b, err = formatBinaryValue(value)
|
||
|
|
||
|
if err != nil {
|
||
|
return nil, errors.Trace(err)
|
||
|
}
|
||
|
|
||
|
if r.Fields[j].Type == MYSQL_TYPE_VAR_STRING {
|
||
|
row = append(row, PutLengthEncodedString(b)...)
|
||
|
} else {
|
||
|
row = append(row, b...)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
copy(row[1:], nullBitmap)
|
||
|
|
||
|
r.RowDatas = append(r.RowDatas, row)
|
||
|
}
|
||
|
|
||
|
return r, nil
|
||
|
}
|
||
|
|
||
|
func BuildSimpleResultset(names []string, values [][]interface{}, binary bool) (*Resultset, error) {
|
||
|
if binary {
|
||
|
return BuildSimpleBinaryResultset(names, values)
|
||
|
} else {
|
||
|
return BuildSimpleTextResultset(names, values)
|
||
|
}
|
||
|
}
|