Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

GORM create record that might already exist

I'm using gorm with postgres in my Go app.

I want to create a new user in the database, but there is a good chance that that user will already exist. If so, I want to not do anything with the database, but I want know about it so I can tell the user.

The good news is, that's already what gorm.Create(..) does. Trying to create a record with a duplicate unique key will return an error. There are two problems:

  1. I want better error messages. I want to write custom user-facing error messages that are different for "This email address already exists" than "There was an actual internal error." I don't know how to diferentiate between these two events other than trying to parse the error string returned by Create() which seems bug prone.
  2. I don't want to clutter my logs. Calling Create() with an object that already exists logs an error message to stdout. I don't really consider this an "error" since I was expecting it to happen and I don't want to flood my logs with a bunch of these warnings.

I know I could use a transaction to first check for a user with the given id and then create them if one does not already exist, but it seems like there should be a simpler solution to such a basic thing. How are you supposed to do this?


I'm currently doing this:

func (self databaseWrapper) CreateUser(user *User) error {
    db := self.db
    db.NewRecord(*user)
    err := db.Create(user).Error
    if err != nil {
        if db.Where(user.ID).Take(&User{}).Error == nil {
            return fmt.Errorf("A user already exists with id %v", user.ID)
        }

        if db.Where(User{Email: user.Email}).Take(&User{}).Error == nil {
            return fmt.Errorf("A user already exists with the given email address: %v", user.Email)
        }

        return fmt.Errorf("Error creating user")
    }
    return nil
}

Which is a little inefficient and gives the ugly output:

go test

(/home/quinn/workspace/aev/sensor/backend/server/database.go:125)
[2019-09-01 14:45:40]  pq: duplicate key value violates unique constraint "users_pkey"

(/home/quinn/workspace/aev/sensor/backend/server/database.go:125)
[2019-09-01 14:45:40]  pq: duplicate key value violates unique constraint "uix_users_email"
PASS
ok          3.215s

even when everything worked as expected.

like image 354
QuinnFreedman Avatar asked Sep 01 '19 21:09

QuinnFreedman


People also ask

Is Gorm slow?

Gorm seem to be extremely slow. The last time takes almost 60s to query. Whereas if I used "database/sql" mysql. QueryRow() its less than 500ms.

What is ORM in Golang?

Golang ORMs Luckily, the Go community has built a number of Object Relational Mapping libraries (ORMs) to allow Go developers to use JSON key:value pair syntax and encoding to map directly to a SQL database like PostgreSQL. ORMs enable developers to use their native programming paradigm to map data to SQL.

What is Gorm?

Gorm is an Irish (Gaelic) word meaning "blue". Ignorance is bliss => Ignorance means not having the blues. => Ignorance = Gormless.


1 Answers

lib/pq is the standard postgres driver. If there is a query error, it will return a pq.Error object (even if you're using GORM). The pq.Error type has a Code field you can inspect to see the cause of the error.

if err, ok := err.(*pq.Error); ok && err.Code.Name() == "unique_violation" {
    // handle error
}

Error code reference

lib/pq Go doc

like image 141
Jessie Avatar answered Oct 12 '22 06:10

Jessie