mysqlgograils-ormpoint

Error of custom Point type for GORM - panic: reflect: call of reflect.Value.Field on slice Value


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?

Details

  1. Models
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
}
  1. Point
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
}
  1. Get data
db.Model(&models.User{}).Joins("Walks").First(&user, "users.name = ?", "***")

Solution

  • 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 = ?", "***")