I need to store Point type in my database.
I read documentation of custom types and suggestion for scan Point type from MySQL/MariaDB.
Then i write custom type Point with GormDataType
, GormValue
and Scan
functions, but when a trying to get data from db i've receive error panic: reflect: call of reflect.Value.Field on slice Value
.
What i can fix it?
package models
// ...imports
type User struct {
gorm.Model
Id uint64 `gorm:"primaryKey;autoIncrement:true"`
Name string `gorm:"size:255;index:idx_name,unique"`
Password string `gorm:"size:64"`
CreatedAt time.Time
UpdatedAt time.Time
DeletedAt *time.Time
Walks []Walk `gorm:"constraint:OnUpdate:CASCADE,OnDelete:SET NULL"`
}
type Walk struct {
gorm.Model
Id uint64 `gorm:"primaryKey;autoIncrement:true"`
UserId uint64
Coords types.Point
Altitude float64
CreatedAt time.Time
}
package types
// ...imports
type Point struct {
Latitude, Longitude float64
}
func (p Point) GormDataType() string {
return "point"
}
func (p Point) GormValue(ctx context.Context, db *gorm.DB) clause.Expr {
return clause.Expr{
SQL: "ST_PointFromText(?)",
Vars: []interface{}{fmt.Sprintf("POINT(%f %f)", p.Latitude, p.Longitude)},
}
}
func (p *Point) Scan(src interface{}) (err error) {
switch src.(type) {
case []byte:
data, ok := src.([]byte)
if !ok {
return errors.New(fmt.Sprintf("(*Point).Scan: Error of scan source value of type %T", src))
}
if len(data) != 25 {
return errors.New(fmt.Sprintf("(*Point).Scan: Expected []bytes with length 25, got %d", len(data)))
}
var latitude, longitude float64
buf := bytes.NewReader(data[9:17])
err := binary.Read(buf, binary.LittleEndian, &latitude)
if err != nil {
return err
}
buf = bytes.NewReader(data[17:25])
err = binary.Read(buf, binary.LittleEndian, &longitude)
if err != nil {
return err
}
*p = Point{latitude, longitude}
default:
return errors.New(fmt.Sprintf("(*Point).Scan: Expected []byte for Point type, got %T", src))
}
return nil
}
db.Model(&models.User{}).Joins("Walks").First(&user, "users.name = ?", "***")
Problem are solved. Point type are correct, but load one-to-many data from database is wrong.
For load many-to-many relations need to use Preload
instead Joins
(documentation).
Example.
Replace
db.Model(&models.User{}).Joins("Walks").First(&user, "users.name = ?", "***")
to
db.Model(&models.User{}).Preload("Walks").First(&user, "users.name = ?", "***")