"reflect: Field index out of range" panic on update - go

I get a panic implementing a basic update with Gorm and I couldn't find any information about it. Following Gorm's documentation, I'm not looking like doing wrong.
module achiever
go 1.15
require (
gorm.io/driver/postgres v1.0.5
gorm.io/gorm v1.20.7
)
type Task struct {
ID uint `json:"id" gorm:"primary_key"`
Title string `json:"title"`
}
type UpdateTaskInput struct {
Title string `json:"title"`
}
func UpdateTask(id uint, input UpdateTaskInput) bool {
var task Task
if err := models.DB.Where("id = ?", id).First(&task).Error; err != nil {
return false
}
models.DB.Model(&task).Updates(input)
return true
}
reflect: Field index out of range
/usr/local/go/src/reflect/value.go:854 (0x4bf758)
Value.Field: panic("reflect: Field index out of range")
/home/raphael/gows/pkg/mod/gorm.io/gorm#v1.20.7/schema/field.go:388 (0x57b984)
(*Field).setupValuerAndSetter.func1: fieldValue := reflect.Indirect(value).Field(field.StructField.Index[0])
/home/raphael/gows/pkg/mod/gorm.io/gorm#v1.20.7/callbacks/update.go:230 (0x881670)
ConvertToAssignments: value, isZero := field.ValueOf(updatingValue)
/home/raphael/gows/pkg/mod/gorm.io/gorm#v1.20.7/callbacks/update.go:64 (0x87f259)
Update: if set := ConvertToAssignments(db.Statement); len(set) != 0 {
/home/raphael/gows/pkg/mod/gorm.io/gorm#v1.20.7/callbacks.go:105 (0x5a65bc)
(*processor).Execute: f(db)
/home/raphael/gows/pkg/mod/gorm.io/gorm#v1.20.7/finisher_api.go:303 (0x5ae2c6)
(*DB).Updates: tx.callbacks.Update().Execute(tx)
/home/raphael/ws/achiever/server/controllers/tasks.go:74 (0xad62cd)
UpdateTask: models.DB.Model(&task).Updates(input)
I can't figure out where the error is coming from.

I was stuck with the same problem, as jugendhacker says: you need to construct the model's struct inside the update function:
models.DB.Model(&task).Updates(&Task{Title: input.Title})
This is stated in the GORM docs, if you are building an API, it would be more adequate to implement this as a PUT method, as you are passing a new object instead of patching an existing one.

Adding ID field to the UpdateTaskInput struct can fix this issue
type UpdateTaskInput struct {
ID uint `json:"-"`
Title string `json:"title"`
}

As far as I know the Update does not support "Smart Select Fields" so you need to construct the original struct first.
models.DB.Model(&task).Updates(&Task{Title: input.Title})

Related

How to compose snake case binding tag with go-playground/validator?

As title, since I'm a newbie to golang, I'm a little bit confused of the binding tag for some custom format.
For example,
there's a struct with a few of fields like this
type user struct {
name `json:"name" binding:"required"`
hobby `json:"name" binding:"required"`
}
and the name field is supposed to support lowercase and underscore only (e.g. john_cage, david)
but after I read the document of validator, still have no idea about that.
validator github
Is there's any good suggestion or solution for my case? Thanks in advance.
Read the document, google similar questions, try to compose the customer binding tag, etc.
binding tag is from gin, correct struct tag for validator is validate. Since there is no vaildation for snake_case, you should make your own. And don't forget to export the fields(Hobby, Name). If you not, (ex: hobby, name) validator will ignore the fields.
package main
import (
"fmt"
"strings"
"github.com/go-playground/validator/v10"
)
type user struct {
Hobby string `json:"name" validate:"required,snakecase"`
}
func main() {
v := validator.New()
_ = v.RegisterValidation("snakecase", validateSnakeCase)
correct := user{"playing_game"}
Incorrect := user{"playingGame"}
err := v.Struct(correct)
fmt.Println(err) // nil
err = v.Struct(Incorrect)
fmt.Println(err) // error
}
const allows = "abcdefghijklmnopqrstuvwxyz_"
func validateSnakeCase(fl validator.FieldLevel) bool {
str := fl.Field().String()
for i := range str {
if !strings.Contains(allows, str[i:i+1]) {
return false
}
}
return true
}
Playground
If you want to register the function via gin, check this out

How return null value to empty string field

I have such struct in Golang application:
type Employee struct {
ID int `json:"employee_id"`
Email *string `json:"employee_email"`
LastName *string `json:"employee_last_name"`
FirstName *string `json:"employee_first_name"`
Sex *string `json:"employee_sex"`
}
Some string fields of this struct can be empty. If I use *string application return me "". If use sql.NullString it return me for example such result:
"employee_last_name": {
String: "",
Valid: true
}
I want to show null is string field is empty.
In documentation I found such code:
type NullString struct {
String string
Valid bool
}
func (ns *NullString) Scan(value interface{}) error {
if value == nil {
ns.String, ns.Valid = "", false
return nil
}
ns.Valid = true
return convertAssign(&ns.String, value)
}
func (ns NullString) Value() (driver.Value, error) {
if !ns.Valid {
return nil, nil
}
return ns.String, nil
}
As I understand this code could help me to solve my problem, right?! What I need to import in my application to use convertAssign function?
In the Go language specification nil is not a valid value for type string. the default, zero value is "".
The top answer for this question goes into more detail.
Edit:
What you want to do is not possible according to the oracle go driver docs:
sql.NullString
sql.NullString is not supported: Oracle DB does not
differentiate between an empty string ("") and a NULL
Original Answer:
Need more details on the SQL database type you are connecting to. I know the go SQLite3 pkg - github.com/mattn/go-sqlite3 - supports setting nil for values of type *string.
Check the details of the driver you are using to connect to the database. For instance with MySQL, getting SQL date fields into go's native time.Time struct type is not set by default - it must be turned on at the driver level.

Trying to implement Redigo ScanStruct but limited examples to follow

I have a code:
values, err := redis.Values(c.Do("hgetall", value))
if err != nil {
fmt.Println("HGETALL", err)
}
/*
type UD struct {
created_at string
B time.Time
ended_at string
data string
status string
}
*/
if err := redis.ScanStruct(values, &UD); err != nil {
fmt.Println(err)
}
The error I got is
redigo.ScanStruct: cannot assign field B: cannot convert from Redis
bulk string to time.Time
How do I resolve this? Any examples of ScanStruct in detail for a variety of field types for Struct for reference?
The documentation for ScanStruct is quite clear:
Integer, float, boolean, string and []byte fields are supported.
Other field types are not supported time.Time included.
To resolve this, I'd go and make my own version of ScanStruct that can deal with the conversion between Redis' and whatever types I need to throw at it.
You can simply add an ignore tag to make time field avoid being marshaled.
type UD struct {
created_at string
B time.Time `redis:"-"`
ended_at string
data string
status string
}

Golang RethinkDB ChangeFeed Structure

I was wondering if someone could explain how to unmarshal my changefeed cursor value to a specific struct type.
var message map[string]interface{}
for chatFeedCursor.Next(&message) {
fmt.Println(message)
}
map[new_val:map[club_id:ea2eb6e2-755f-4dad-922d-e3693b6e55c6
date:2017-04-07 14:48:17.714 +0100 +01:00
id:e389ab54-963e-4b33-9b34-adcb6ec5b17e message:what is the meaning of life?
user_id:00ff679f-9421-4b8b-ae7f-d11cf2adaee2] old_val:]
However, I would like the response to be mapped to struct ChatMessage.
Update:
I've tried:
var message ChatMessage
However, it doesn't seem like any of my data gets set in the struct.
{ 0001-01-01 00:00:00 +0000 UTC}
My struct:
type ChatMessage struct {
ID string `json:"id" gorethink:"id,omitempty"`
UserID string `json:"user_id" gorethink:"user_id"`
ClubID string `json:"club_id" gorethink:"club_id"`
Message string `json:"message" gorethink:"message"`
Date time.Time `json:"date" gorethink:"date"`
}
Thanks.
I figured it out!
The problem was that I didn't specify a field on the rethinkdb change request.
Previous code:
chatFeedCursor, _ := gorethink.Table("club_chat").Changes().Run(gorethinkSession)
Working Code:
chatFeedCursor, _ := gorethink.Table("club_chat").Changes().Field("new_val").Run(gorethinkSession)
Now the .Next() value maps to my struct with no issues.
If you want to unmarshal the changefeed cursor value to a specific struct type, but keep both the new and the old value, you could use a library for decoding generic map values into native Go structures, like mapstructure, to obtain a neat result:
Import mapstructure:
import (
...
"github.com/mitchellh/mapstructure"
...
)
Now you can do something like:
cursor, err := r.Table("club_chat").
Changes().
Run(db.Session)
if err != nil {
// manage error
}
var changeFeed map[string]interface{}
for cursor.Next(&changeFeed) {
var oldVal, newVal ChatMessage
if changeFeed["old_val"] != nil {
mapstructure.Decode(changeFeed["old_val"], &oldVal)
}
if changeFeed["new_val"] != nil {
mapstructure.Decode(changeFeed["new_val"], &newVal)
}
// Do what you want with the values

Preload can't find field fieldName in *models.Catalog

I can't find an answer to my question. I am using jinzhu/gorm in a golang project:)
I have the following structs:
type Catalog struct {
ID int64 `gorm:"primary_key" form:"id"`
SubDomainID int64 `form:"sub_domain_id"`
ServiceTypeID int64 `form:"service_type_id"`
Checked bool `form:"checked"`
CreatedAt time.Time `form:"created_at"`
UpdatedAt time.Time `form:"updated_at"`
SubDomain SubDomain
}
type SubDomain struct {
Id int64 `gorm:"primary_key" form:"id"`
NameRu string `form:name_ru`
url string `form:url`
}
When I try to get catalog with preloading of subdomain:
var catalog Catalog
fmt.Println(catalog.SubDomain)
err := db.Preload("SubDomain").Where("checked = 0").First(&catalog).Error
if err != nil {
return &catalog, err
}
I get the following error: can't find field SubDomain in *models.Catalog
Why is this happening?
I expect there will be 2 queries:
select * from catalogs where checked = 0;
select * from sub_domains where id = (catalog.sub_domain_id)
I'm still new to gorm, but I think I know your problem and also have a (partial) solution.
As stated before (and you said so yourself), when applying "select * from..." it also looks for the field SubDomain (because it is in your struct).
So I believe this should work:
var catalog Catalog
fmt.Println(catalog.SubDomain)
err := db.Preload("SubDomain").Select("ID","SubDomainID", "ServiceTypeID").Where("checked = 0").First(&catalog).Error
if err != nil {
return &catalog, err
}
note how i specified the exact fields. A better solution will be to write a function to exclude member which are fields, using reflection. I am using a similar solution myself. it kinda looks like this:
for each member of Domain:
if member is string or boolean
fields.append(member).
return db.Select(fields) // actual gormdb's Select

Resources