最近项目里面需要查询某一张表,改表某个字段xxx的类型是bit,然后自以为是的使用go的bool来接收该字段值,结果不出意外就出意外了。报错:
sql/driver: couldn't convert "\x01" into type
可见,在底层解析过程中,并不能把mysql的bit类型转化成go的bool。
我们跟踪源码进去底层可以看到,在database/sql/driver/type.go 文件中对目标为bool类型的处理如下
var Bool boolType
type boolType struct{}
var _ ValueConverter = boolType{}
func (boolType) String() string { return "Bool" }
func (boolType) ConvertValue(src any) (Value, error) {
switch s := src.(type) {
case bool:
return s, nil
case string:
b, err := strconv.ParseBool(s)
if err != nil {
return nil, fmt.Errorf("sql/driver: couldn't convert %q into type bool", s)
}
return b, nil
case []byte:
b, err := strconv.ParseBool(string(s))
if err != nil {
return nil, fmt.Errorf("sql/driver: couldn't convert %q into type bool", s)
}
return b, nil
}
// XXXXXXXX 略
}
目前我们在查询mysql后拿到的src 其实是[]uint8类型(实际上就是 []byte),接着就进入了case []byte 分支,然后这里直接把原数据通过string(s) 强制转换成字符串,导致原来的值 0x01 变成了字符面值为 SOH (通过查看ASCII表)代表标题开始的字符。所以调用strconv.ParseBool 必定失败,因为s根本不符合bool字符量。从而导致错误发生。那么我们要怎么处理这种类型呢?
其实sql包提供了,scan接口:如下
type Scanner interface {
// Scan assigns a value from a database driver.
//
// The src value will be of one of the following types:
//
// int64
// float64
// bool
// []byte
// string
// time.Time
// nil - for NULL values
//
// An error should be returned if the value cannot be stored
// without loss of information.
//
// Reference types such as []byte are only valid until the next call to Scan
// and should not be retained. Their underlying memory is owned by the driver.
// If retention is necessary, copy their values before the next call to Scan.
Scan(src any) error
}
只要你的类型实现了该接口,那么就可以操作bit类型了,于是我自定义了类型来接收bit类型。
type MyBool bool
func (b *MyBool) Value() (driver.Value, error) {
result := make([]uint8, 1)
if *b {
result[0] = uint8(1)
} else {
result[0] = 0
}
return result, nil
}
func (b *MyBool) Scan(v interface{}) error {
bytes := v.([]uint8)
if bytes[0] == 0 {
*b = false
} else {
*b = true
}
return nil
}
在对应字段上使用该类型,你就可以接受mysql 的bit类型了。(注意这里类型的方法一定是指针对应的方法,否者赋值失败)