对于网络传输数据而言,在go中只要是[]byte的格式就能进行传输,对于最简单的client->server->mysql,然后数据再传回来的简单流程来分析(其实对于一个进程来说,没有client和server之分,一个进程即可能是客户端,也可能是服务端,mysql本身就是一个server,只是功能不同,笼统来说,没有本质上的区别),本篇以decimal的数据来分析,它的github地址 "github.com/shopspring/decimal"
1.简述decimal的存储格式
type Decimal struct {
value *big.Int
// NOTE(vadim): this must be an int32, because we cast it to float64 during
// calculations. If exp is 64 bit, we might lose precision.
// If we cared about being able to represent every possible decimal, we
// could make exp a *big.Int but it would hurt performance and numbers
// like that are unrealistic.
exp int32
}
type Int struct {
neg bool // sign
abs nat // absolute value of the integer
}
type nat []Word
其中decimal的包括一个big.Int的结构体来存储数据,big.Int的结构体主要是来存储大数的,比如uint64,用nat来存储,就是[2]uint,长度为2的uint数组,一个存32位,两个存64,如果是uint32,那就只用1个uint,就能存储,依次类推,越大,数组越大,neg是符号位,表示正负的,那decimal是怎么表示的了
data, _ := decimal.NewFromString("123.456")
fmt.Println(data.String())
//打印 123.456
func NewFromString(value string) (Decimal, error) func (d Decimal) string(trimTrailingZeros bool) string
对于上面的两个源码函数,简要的分析了一下,就是找到其中符号.的索引,转成负数,然后将其中的123.456的字符串转成123456的数,并存入big.Int的nat的数组中,并返回exp=-3的索引,然后String()显示的时候,将big.Int也调用String(),其实就是将nat数组的数,又转成123456,然后根据下面的exp的索引得出整数部分和小数部分,然后+个.,拼接起来
intPart = str[:len(str)+dExpInt] fractionalPart = str[len(str)+dExpInt:]
上面就是decimal的简要的存储格式,很清晰,简单,其他的部分读者们可以自己去看看
2.decimal的client-server的传输
对于client和server的data传输,一般是json,probuf,还有自定义的格式。这里我们以json格式为例。
对于js客户端,我们对于精度要求特别高的数据,用string类型进行传输,然后server通过decimal.NewFromString 函数进行获取(当然json也能反序列化将string转成decimal类型,获取可能有其他格式的传参form类的),然后进行decimal的数据的运算,最后通过json的序列化传回给client
st := &struct {
A int
B decimal.Decimal
}{
1,
data,
}
j, err := json.Marshal(st)
if err != nil {
t.Error(err)
}
fmt.Println(string(j))
//打印
{"A":1,"B":"123.456"}
json进行序列化的时候,将decimal的格式转换成了string的格式,我们解释一下怎么分析
json的Marshal-》由于我们这里是struct,对于每个字段都会调用newTypeEncoder{
if t.Kind() != reflect.Pointer && allowAddr && reflect.PointerTo(t).Implements(marshalerType) {
return newCondAddrEncoder(addrMarshalerEncoder, newTypeEncoder(t, false))
}
if t.Implements(marshalerType) {
return marshalerEncoder
}
if t.Kind() != reflect.Pointer && allowAddr && reflect.PointerTo(t).Implements(textMarshalerType) {
return newCondAddrEncoder(addrTextMarshalerEncoder, newTypeEncoder(t, false))
}
if t.Implements(textMarshalerType) {
return textMarshalerEncoder
}
.
.
.
}
其中marshalerType = reflect.TypeOf((*Marshaler)(nil)).Elem(),是接口
type Marshaler interface {
MarshalJSON() ([]byte, error)
}
在decimal中,我们能找到这个接口
func (d Decimal) MarshalJSON() ([]byte, error) {
var str string
if MarshalJSONWithoutQuotes {
str = d.String()
} else {
str = "\"" + d.String() + "\""
}
return []byte(str), nil
}
上面解析出了原因。
3.decimal的server-mysql的传输
由于mysql有自己的数据传输格式和数据定义,所以先看获取mysql的数据
RealAmount decimal.Decimal
mydb.QueryRow("SELECT realmount FROM repli_table.new_table1 WHERE id=?", 6).Scan(&RealAmount)
下面进行分析
mysql的Scan()-》调用func convertAssignRows(dest, src any, rows *Rows) error {
.
.
.
if scanner, ok := dest.(Scanner); ok {
return scanner.Scan(src)
}
.
.
.
}
其中的Scanner是type Scanner interface {接口
Scan(src any) error
}
,所以decimal能实现这个接口就可以了
func (d *Decimal) Scan(value interface{}) error {
// first try to see if the data is stored in database as a Numeric datatype
switch v := value.(type) {
case float32:
*d = NewFromFloat(float64(v))
return nil
case float64:
// numeric in sqlite3 sends us float64
*d = NewFromFloat(v)
return nil
case int64:
// at least in sqlite3 when the value is 0 in db, the data is sent
// to us as an int64 instead of a float64 ...
*d = New(v, 0)
return nil
default:
// default is trying to interpret value stored as string
str, err := unquoteIfQuoted(v)
if err != nil {
return err
}
*d, err = NewFromString(str)
return err
}
}
那是怎么传入数据的了,就是将数据拼接起来
RealAmount decimal.Decimal
mydb.QueryRow("SELECT *FROM repli_table.new_table1 WHERE realmount >=?", RealAmount)
我们也进行下面的分析
QueryRow-》queryDC-》driverArgsConnLocked-》checker{nvc.CheckNamedValue}-》func (c converter) ConvertValue(v interface{}) (driver.Value, error) {
if driver.IsValue(v) {
return v, nil
}
if vr, ok := v.(driver.Valuer); ok {
sv, err := callValuerValue(vr)
if err != nil {
return nil, err
}
if driver.IsValue(sv) {
return sv, nil
}
// A value returend from the Valuer interface can be "a type handled by
// a database driver's NamedValueChecker interface" so we should accept
// uint64 here as well.
if u, ok := sv.(uint64); ok {
return u, nil
}
return nil, fmt.Errorf("non-Value type %T returned from Value", sv)
}
rv := reflect.ValueOf(v)
switch rv.Kind() {
case reflect.Ptr:
// indirect pointers
if rv.IsNil() {
return nil, nil
} else {
return c.ConvertValue(rv.Elem().Interface())
}
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
return rv.Int(), nil
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
return rv.Uint(), nil
case reflect.Float32, reflect.Float64:
return rv.Float(), nil
case reflect.Bool:
return rv.Bool(), nil
case reflect.Slice:
switch t := rv.Type(); {
case t == jsonType:
return v, nil
case t.Elem().Kind() == reflect.Uint8:
return rv.Bytes(), nil
default:
return nil, fmt.Errorf("unsupported type %T, a slice of %s", v, t.Elem().Kind())
}
case reflect.String:
return rv.String(), nil
}
return nil, fmt.Errorf("unsupported type %T, a %s", v, rv.Kind())
}
上面的执行逻辑最后ConvertValue这个转换成driver.Value值,这个值只能有IsValue函数返回true的类型,才能最后拼接最后的sql,func IsValue(v any) bool {
if v == nil {
return true
}
switch v.(type) {
case []byte, bool, float64, int64, string, time.Time:
return true
case decimalDecompose:
return true
}
return false
}
因为在queryDC-》driverArgsConnLocked执行完后执行sql的conn的函数
queryquery(query string, args []driver.Value) (*textRows, error) ,然后再执行,
func (mc *mysqlConn) interpolateParams(query string, args []driver.Value) (string, error)
最终将参数覆盖?号的,拼接成sql的string的是interpolateParams函数,这个函数能解析的类型才是最终
的类型,看到这里,是不是明白了所有。
也就是传入的参数如果是整数型ConvertValue会将他转成int64,浮点型转成float64就可以了,也就是转
成IsValue函数是true的类型
对于自定义的接口,我们也要将他转成driver.Value类型,所以在ConvertValue函数里,有这段函数
if vr, ok := v.(driver.Valuer); ok {
sv, err := callValuerValue(vr)
if err != nil {
return nil, err
}
if driver.IsValue(sv) {
return sv, nil
}
其中的 driver.Valuer是type Valuer interface {
Value() (Value, error)
}接口
所以我们只要看decimal是否实现这个接口,然后转成 driver.Value就可以了
// Value implements the driver.Valuer interface for database serialization.
func (d Decimal) Value() (driver.Value, error) {
return d.String(), nil
}
对此自定义的mysql的获取和传入就解析清楚了,我们可以看到decimal还有很多其他的转换的接口函数,GobEncode的gob的系列化,MarshalBinary的probuf的接口,我们就不一一分析了。
总结对于写好的序列化库,一般会留个接口给自定义的类型来实现自己的功能,比如上面分析的三个接口,由于上面的三个太重要了,我们就从源码分析并找到了它的接口,以后我们自己实现就轻车熟路,原理很简单,但一定要熟悉。