[gogf/gf]Struct字段如果为指针类型,查询数据库报错

2024-06-25 682 views
4

go1.14 windows/amd64 goFrame v1.11.4 Struct字段如果为指针类型,db.GetStructs()或者model.Structs()报错,结构体部分字段设置为指针的原因是,数据库里某些字段为NULL,例如下面的纬度字段是小数类型,但是数据库表的这个字段可以为NULL,查询会报错,因为float64无法设置为nil,它的零值是0。还有地址字段数据库表该字段为NULL时,结构体Addr字段映射的值是“”空字符串,期望得到的是nil,所以只能将这些可以为NULL的结构体字段设置为指针类型 例如:

type Test struct {
    DeviceId string `orm:"device_id" json:"deviceId"`
    Addr *string `orm:"addr" json:"addr"` //地址
    Latitude       *float64 `orm:"latitude"         json:"latitude"`              // 纬度
    Longitude      *float64 `orm:"longitude"        json:"longitude"`             // 经度
}

目前个人的解决方法是: 1.在gconv.go的Convert增加指针类型判断

func Convert(i interface{}, t string, params ...interface{}) interface{} {
    switch t {
    case "int":
        return Int(i)
    case "int8":
        return Int8(i)
    case "int16":
        return Int16(i)
    case "int32":
        return Int32(i)
    case "int64":
        return Int64(i)
    case "uint":
        return Uint(i)
    case "uint8":
        return Uint8(i)
    case "uint16":
        return Uint16(i)
    case "uint32":
        return Uint32(i)
    case "uint64":
        return Uint64(i)
    case "float32":
        return Float32(i)
    case "float64":
        return Float64(i)
    case "bool":
        return Bool(i)
    case "string":
        return String(i)
    case "*int":
        if i==nil {
            return i
        }
        r:=Int(i)
        return &r
    case "*int8":
        if i==nil {
            return i
        }
        r:=Int8(i)
        return &r
    case "*int16":
        if i==nil {
            return i
        }
        r:=Int16(i)
        return &r
    case "*int32":
        if i==nil {
            return i
        }
        r:=Int32(i)
        return &r
    case "*int64":
        if i==nil {
            return i
        }
        r:=Int64(i)
        return &r
    case "*uint":
        if i==nil {
            return i
        }
        r:=Uint(i)
        return &r
    case "*uint8":
        if i==nil {
            return i
        }
        r:=Uint8(i)
        return &r
    case "*uint16":
        if i==nil {
            return i
        }
        r:=Uint16(i)
        return &r
    case "*uint32":
        if i==nil {
            return i
        }
        r:=Uint32(i)
        return &r
    case "*uint64":
        if i==nil {
            return i
        }
        r:=Uint64(i)
        return &r
    case "*float32":
        if i==nil {
            return i
        }
        r:=Float32(i)
        return &r
    case "*float64":
        if i==nil {
            return i
        }
        r:=Float64(i)
        return &r
    case "*bool":
        if i==nil {
            return i
        }
        r:=Bool(i)
        return &r
    case "*string":
        if i==nil {
            return i
        }
        r:=String(i)
        return &r
    case "[]byte":
        return Bytes(i)
    case "[]int":
        return Ints(i)
    case "[]int32":
        return Int32s(i)
    case "[]int64":
        return Int64s(i)
    case "[]uint":
        return Uints(i)
    case "[]uint32":
        return Uint32s(i)
    case "[]uint64":
        return Uint64s(i)
    case "[]float32":
        return Float32s(i)
    case "[]float64":
        return Float64s(i)
    case "[]string":
        return Strings(i)

    case "Time", "time.Time":
        if len(params) > 0 {
            return Time(i, String(params[0]))
        }
        return Time(i)

    case "gtime.Time":
        if len(params) > 0 {
            return GTime(i, String(params[0]))
        }
        return *GTime(i)

    case "GTime", "*gtime.Time":
        if len(params) > 0 {
            return GTime(i, String(params[0]))
        }
        return GTime(i)

    case "Duration", "time.Duration":
        return Duration(i)
    default:
        return i
    }
}

2.goconv_struct.go的方法bindVarToStructAttr里这一行

structFieldValue.Set(reflect.ValueOf(Convert(value, structFieldValue.Type().String())))改为
if v:=Convert(value, structFieldValue.Type().String());v!=nil {
    structFieldValue.Set(reflect.ValueOf(v))
}

因为structFieldValue.Set(nil)会报错 不知道这样改是否可行? 暂时未发现insert不支持指针类型字段,只有select不支持指针类型字段

回答

9

@PamaleJP 你试过最新版本v1.12.2或者master分支么?试试看

7

还是有问题,控制台可能不会报错,但内部报错defer了,Struct字段除了gtime可以用指针,其他基本类型都不能用指针类型。我看了下最新的master版本里的goconv_struct.go的方法bindVarToStructAttr里面改成了

if empty.IsNil(value) {
    structFieldValue.Set(reflect.Zero(structFieldValue.Type()))
} else {
    structFieldValue.Set(reflect.ValueOf(Convert(value, structFieldValue.Type().String())))
}

但是还是有问题,比如结构体字段为“float64”,数据库这个字段为NULL时,进入if第一个分支,设置的是nil,是想要的结果,但是数据库不是NULL时比如3.14,进入第二个分支,Convert方法里的switch没有匹配到类型为“float64”的,进入default,返回字符串“3.14”,给“float64”类型的字段赋值字符串类型就报错了,希望支持除gtime,之外的基本指针类型

8
package main
import (
    "fmt"
    "github.com/gogf/gf/database/gdb"
    _ "github.com/lib/pq"
)
func main() {
    //注意数据库为postgresql,驱动为github.com/lib/pq,[database.test] link  = "pgsql:host=39.105.148.47 port=5432 user=test password=xxx dbname=testdb sslmode=disable"
    db,err :=gdb.Instance("test")
    if err!=nil {
        panic(err)
    }
    //清空表t_test
    _,err=db.Exec("delete from t_test")
    if err!=nil {
        panic(err)
    }
    model:=db.Table("t_test")
    type test struct {
        DeviceId string `orm:"device_id" json:"deviceId"`
        Addr *string `orm:"addr" json:"addr"`
        Latitude       *float64 `orm:"latitude"         json:"latitude"`              // 纬度
        Longitude      *float64 `orm:"longitude"        json:"longitude"`             // 经度
    }
    latitude := 3.14
    addr := "地址1"
    var one = test{DeviceId:"1",Addr:&addr,Latitude:&latitude}
    _,err=model.Insert(one)
    if err!=nil {
        panic(err)
    }
    var list []test
    err = model.Structs(&list)
    //list有一条数据,{DeviceId:"1",Addr:"地址1",Latitude:nil,Longitude:nil}
    //期望Latitude=3.14,这里的Addr获取到了值,但是实际是gconv_struct.go里方法bindVarToStructAttr里报错后被该方法里的defer里bindVarToReflectValue(structFieldValue, value)处理了
    //但是实际上float64基本类型应该在bindVarToStructAttr直接处理的,不应该报错后用bindVarToReflectValue处理
    if err!=nil {
        panic(err)
    }
    fmt.Println(list)
}
//目前我是这样改的,不知有没有更优化的方法
//在bindVarToStructAttr里加入
if !empty.IsNil(value) {
    if structFieldValue.Kind()==reflect.Ptr {
        v:=reflect.ValueOf(Convert(value, structFieldValue.Type().String()[1:]))
        ele:=reflect.New(structFieldValue.Type().Elem()).Elem()
        ele.Set(v)
        structFieldValue.Set(ele.Addr())
    }else {
        structFieldValue.Set(reflect.ValueOf(Convert(value, structFieldValue.Type().String())))
    }
}
/*if empty.IsNil(value) {
    structFieldValue.Set(reflect.Zero(structFieldValue.Type()))
} else {
    structFieldValue.Set(reflect.ValueOf(Convert(value, structFieldValue.Type().String())))
}*/
3

@PamaleJP 你的代码里面暴露了密码,我改了一下哈,我试试看。

8

@PamaleJP 感谢反馈,该问题的root cause是因为对postgresql的数据类型没有支持全面,已增加全面的支持,更新到master分支即可。

此外,无需修改gconv包,因为你这其实是通过反射将string类型赋值给*float64类型,原本就是不正确的赋值逻辑,因此不能支持。

3

谢谢,确实正确查询到结果了,但是如果是指针字段,你实际上还是先通过反射指针指向的字段类型(即转为非指针类型),给字段赋值,然后把改该字段地址赋值给指针字段。bindVarToStructAttr方法里这行报错:structFieldValue.Set(reflect.ValueOf(Convert(value, structFieldValue.Type().String())))(因为Convert方法只能返回非指针类型值,structFieldValue是指针类型,把非指针的值赋值给指针类型字段是不对的),之所以结果是正确的,还是因为该方法(bindVarToStructAttr)里defer里的bindVarToReflectValue方法通过把指针类型转为非指针反射赋值,建议如果是指针类型字段,Convert直接返回值的地址,总而言之,即Convert方法暂时不支持转换为指针

1

是的,Convert并不支持指针类型转换,只支持常见类型的值转换。你这个问题本身的root cause其实并不是Convert的问题,所以我着重解决了你这个问题本身,但并没有对Convert方法做对指针转换的支持。我还没遇到必须需要增加这种支持的场景,如果有的话你可以提。