#database #development #golang #mysql
I recently started updating some projects from GORM v1 to v2.
While doing so, I found a nasty difference in behaviour which made this much more complex than anticipated.
One of the types of constructs which I use very often is:
func FindUser(id int64) (*User, error) {
var user User
err := db.Raw(`select * from user where id=?`, id).Scan(&obj).Error
if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, nil
}
return nil, err
}
return &user, nil
}
So, what the code is supposed to be doing:
- When
id
contains a valid value, a user object should be returned without an error - When
id
contains a non-existing value,nil
should be returned as the user without an error - When something else goes wrong,
nil
should be returned as the user with an error
Turns out that this no longer works in version 2. In the version 1 documentation, it's defined as follows:
GORM provides a shortcut to handle
RecordNotFound
errors. If there are several errors, it will check if any of them is aRecordNotFound
error.
In version 2 documentation, the definition changed to:
GORM returns
ErrRecordNotFound
when failed to find data withFirst
,Last
,Take
, if there are several errors happened, you can check theErrRecordNotFound
error witherrors.Is
.
Luckily, I have test coverage to check this, but I'm unsure what would be the proper fix. One option would be to change the code to:
func FindUser(id int64) (*User, error) {
var user User
err := db.Raw(`select * from user where id=?`, id).Scan(&obj).Error
if err != nil {
return nil, err
}
if user.ID == 0 {
return nil, nil
}
return &user, nil
}
There are several reasons though why I don't like this type of code. One of them is that queries don't always query for example the ID
field which means you have to check different fields. This makes the code less clear for me and make it much more difficult what I'm trying to do.
The best alternative I have found until now is:
func FindUser(id int64) (*User, error) {
var user User
res := db.Raw(`select * from user where id=?`, id).Scan(&obj)
if res.Error != nil {
return nil, res.Error
}
if res.RowsAffected <= 0 {
return nil, nil
}
return &user, nil
}
Since this is spread over several places, I reported it on the issue tracker hoping that they reconsider this change.
If this post was enjoyable or useful for you, please share it! If you have comments, questions, or feedback, you can email my personal email. To get new posts, subscribe use the RSS feed.