oklog/ulid

Incompatibility with Gorm

Closed this issue · 6 comments

I'm having some trouble with getting this library playing together with Gorm and I'm not entirely sure where the fault is at. Maybe someone here could help point me in a direction to figure out where (and how) to solve this?

The problem is that Gorm doesn't seem to understand when ulid.ULIDs are empty, so when I have such a field it tried writing to it, which causes foreign key failure.

I've seen that this library implements a SQL Valuer and it looks good but, still, it doesn't work for me.

Any ideas what could be going wrong?

Here's an example:

package main

import (
	"math/rand"
	"os"
	"time"

	"github.com/oklog/ulid/v2"
	"gorm.io/driver/mysql"
	"gorm.io/gorm"
)

type One struct {
	ID    ulid.ULID `gorm:"primaryKey;type:varbinary(255)"`
	Name  string
	Two   *Two
	TwoID ulid.ULID
}

type Two struct {
	ID ulid.ULID `gorm:"primaryKey;type:varbinary(255)"`
}

func main() {
	db, _ := gorm.Open(mysql.Open(os.Getenv("DB_DSN")), &gorm.Config{})

	db.AutoMigrate(One{})
	db.AutoMigrate(Two{})

	now := time.Now()
	entropy := ulid.Monotonic(rand.New(rand.NewSource(now.UnixNano())), 0)

	one := One{ID: ulid.MustNew(ulid.Timestamp(now), entropy), Name: "Asdf"}

	db.Create(one)
}

What is the error? Please copy and paste to here.

Thanks for the quick reply, @giautm! Here's the error I'm seeing:

2020/09/08 10:45:14 /…/main_test.go:24 FOREIGN KEY constraint failed
[0.487ms] [rows:0] INSERT INTO `ones` (`id`,`name`,`two_id`) VALUES ("<binary>","Asdf","<binary>")

I don't see relation between One and Two model. Do you add relation by hand? plz provide more detail about database schema.

  • The error said, you must insert the Two with that ID first then insert the One. Or you must set NULLABLE for that column (two_id).

Gorm infers the relation – you'll see that One has a property referencing Two and that's the relation. That is further encoded in the TwoID property, which is saved to the database.

I just heard back from the creator of Gorm and it seems that ULIDs' Value always return true, which tells Gorm that there's a value even for an emptyULID. That sounds like it could be the culprit here.

go-gorm/gorm#3427

So if my One has a null property for Two, Gorm will see that as an actual reference to a Two that doesn't exist. Hence the foreign key failure.

Does that make sense? Would that be fixable here?

Well, a zero-value ULID is a valid ULID, in the same way that 0 is a valid int. How would you solve this problem for ints?

edit: Possible paths forward, as I see them:

  • Change the Value method to report zero-value ULIDs as invalid — I don't think this is correct, but happy to discuss
  • Provide a wrapper type w/ Value method that special-cases zero-value ULIDs as invalid — but you could do this yourself?
  • Use *ulid.ULID instead of ulid.ULID in your model

Any others?

I agree about your first option and also think your second option sounds like the best way forward. I managed to solve it in another manner (see below) but yours is more generally applicable, so that's probably what most users should use.

You have already done a lot of good work here but can I still just suggest adding a line or two about this to the README? I would have found that immensely useful. :)

Either way, thank you so much for the quick help!

(In case anyone else comes looking months from now, my own solution was centralizing Gorm interactions through a set of functions where I can do null/empty-checks and use Gorm's Omit()´/Select()` to control what's being written.)