Skip to content

feature: Add datatype for uuid.UUID (Google UUID) #259

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Jul 12, 2024
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
@@ -3,6 +3,7 @@ module gorm.io/datatypes
go 1.19

require (
github.com/google/uuid v1.3.0
github.com/jinzhu/now v1.1.5
gorm.io/driver/mysql v1.5.6
gorm.io/driver/postgres v1.5.0
1 change: 1 addition & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -22,6 +22,7 @@ github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9/go.mod h1:8vg3r2V
github.com/golang-sql/sqlexp v0.1.0 h1:ZCD6MBpcuOVfGVqsEmY5/4FtYiKz6tSyUv9LPEDei6A=
github.com/golang-sql/sqlexp v0.1.0/go.mod h1:J4ad9Vo8ZCWQ2GMrC4UCQy1JpCbwU9m3EOqtpKwwwHI=
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
92 changes: 92 additions & 0 deletions uuid.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
package datatypes

import (
"database/sql/driver"

"github.com/google/uuid"
"gorm.io/gorm"
"gorm.io/gorm/schema"
)

type UUID uuid.UUID

// NewUUIDv1 generates a UUID version 1, panics on generation failure.
func NewUUIDv1() UUID {
return UUID(uuid.Must(uuid.NewUUID()))
}

// NewUUIDv4 generates a UUID version 4, panics on generation failure.
func NewUUIDv4() UUID {
return UUID(uuid.Must(uuid.NewRandom()))
}

// GormDataType gorm common data type.
func (UUID) GormDataType() string {
return "string"
}

// GormDBDataType gorm db data type.
func (UUID) GormDBDataType(db *gorm.DB, field *schema.Field) string {
switch db.Dialector.Name() {
case "mysql":
return "LONGTEXT"
case "postgres":
return "UUID"
case "sqlserver":
return "NVARCHAR"
case "sqlite":
return "TEXT"
default:
return ""
}
}

// Scan is the scanner function for this datatype.
func (u *UUID) Scan(value interface{}) error {
var result uuid.UUID
if err := result.Scan(value); err != nil {
return err
}
*u = UUID(result)
return nil
}

// Value is the valuer function for this datatype.
func (u UUID) Value() (driver.Value, error) {
return uuid.UUID(u).Value()
}

// String returns the string form of the UUID.
func (u UUID) String() string {
return uuid.UUID(u).String()
}

// Equals returns true if string form of UUID matches other, false otherwise.
func (u UUID) Equals(other UUID) bool {
return u.String() == other.String()
}

// Length returns the number of characters in string form of UUID.
func (u UUID) Length() int {
return len(u.String())
}

// IsNil returns true if the UUID is a nil UUID (all zeroes), false otherwise.
func (u UUID) IsNil() bool {
return uuid.UUID(u) == uuid.Nil
}

// IsEmpty returns true if UUID is nil UUID or of zero length, false otherwise.
func (u UUID) IsEmpty() bool {
return u.IsNil() || u.Length() == 0
}

// IsNilPtr returns true if caller UUID ptr is nil, false otherwise.
func (u *UUID) IsNilPtr() bool {
return u == nil
}

// IsEmptyPtr returns true if caller UUID ptr is nil or it's value is empty.
func (u *UUID) IsEmptyPtr() bool {
return u.IsNilPtr() || u.IsEmpty()
}
153 changes: 153 additions & 0 deletions uuid_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
package datatypes_test

import (
"database/sql/driver"
"testing"

"github.com/google/uuid"
"gorm.io/datatypes"
"gorm.io/gorm"
. "gorm.io/gorm/utils/tests"
)

var _ driver.Valuer = &datatypes.UUID{}

func TestUUID(t *testing.T) {
if SupportedDriver("sqlite", "mysql", "postgres", "sqlserver") {
type UserWithUUID struct {
gorm.Model
Name string
UserUUID datatypes.UUID
}

DB.Migrator().DropTable(&UserWithUUID{})
if err := DB.Migrator().AutoMigrate(&UserWithUUID{}); err != nil {
t.Errorf("failed to migrate, got error: %v", err)
}

users := []UserWithUUID{{
Name: "uuid-1",
UserUUID: datatypes.NewUUIDv1(),
}, {
Name: "uuid-2",
UserUUID: datatypes.NewUUIDv1(),
}, {
Name: "uuid-3",
UserUUID: datatypes.NewUUIDv4(),
}, {
Name: "uuid-4",
UserUUID: datatypes.NewUUIDv4(),
}}

if err := DB.Create(&users).Error; err != nil {
t.Errorf("Failed to create users %v", err)
}

for _, user := range users {
result := UserWithUUID{}
if err := DB.First(
&result, "name = ? AND user_uuid = ?",
user.Name,
user.UserUUID,
).Error; err != nil {
t.Fatalf("failed to find user with uuid, got error: %v", err)
}
AssertEqual(t, !result.UserUUID.IsEmpty(), true)
AssertEqual(t, user.UserUUID.Equals(result.UserUUID), true)
valueUser, err := user.UserUUID.Value()
if err != nil {
t.Fatalf("failed to get user value, got error: %v", err)
}
valueResult, err := result.UserUUID.Value()
if err != nil {
t.Fatalf("failed to get result value, got error: %v", err)
}
AssertEqual(t, valueUser, valueResult)
}

user1 := users[0]
AssertEqual(t, user1.UserUUID.IsNil(), false)
AssertEqual(t, user1.UserUUID.IsEmpty(), false)
DB.Model(&user1).Updates(
map[string]interface{}{"user_uuid": uuid.Nil},
)
AssertEqual(t, user1.UserUUID.IsNil(), true)
AssertEqual(t, user1.UserUUID.IsEmpty(), true)

user2 := users[1]
AssertEqual(t, user2.UserUUID.IsNil(), false)
AssertEqual(t, user2.UserUUID.IsEmpty(), false)
DB.Model(&user2).Updates(
map[string]interface{}{"user_uuid": nil},
)
AssertEqual(t, user2.UserUUID.IsNil(), true)
AssertEqual(t, user2.UserUUID.IsEmpty(), true)
}
}

func TestUUIDPtr(t *testing.T) {
if SupportedDriver("sqlite", "mysql", "postgres", "sqlserver") {
type UserWithUUIDPtr struct {
gorm.Model
Name string
UserUUID *datatypes.UUID
}

DB.Migrator().DropTable(&UserWithUUIDPtr{})
if err := DB.Migrator().AutoMigrate(&UserWithUUIDPtr{}); err != nil {
t.Errorf("failed to migrate, got error: %v", err)
}

uuid1 := datatypes.NewUUIDv1()
uuid2 := datatypes.NewUUIDv1()
uuid3 := datatypes.NewUUIDv4()
uuid4 := datatypes.NewUUIDv4()

users := []UserWithUUIDPtr{{
Name: "uuid-1",
UserUUID: &uuid1,
}, {
Name: "uuid-2",
UserUUID: &uuid2,
}, {
Name: "uuid-3",
UserUUID: &uuid3,
}, {
Name: "uuid-4",
UserUUID: &uuid4,
}}

if err := DB.Create(&users).Error; err != nil {
t.Errorf("Failed to create users %v", err)
}

for _, user := range users {
result := UserWithUUIDPtr{}
if err := DB.First(
&result, "name = ? AND user_uuid = ?",
user.Name,
*user.UserUUID,
).Error; err != nil {
t.Fatalf("failed to find user with uuid, got error: %v", err)
}
AssertEqual(t, !result.UserUUID.IsEmpty(), true)
AssertEqual(t, user.UserUUID, result.UserUUID)
valueUser, err := user.UserUUID.Value()
if err != nil {
t.Fatalf("failed to get user value, got error: %v", err)
}
valueResult, err := result.UserUUID.Value()
if err != nil {
t.Fatalf("failed to get result value, got error: %v", err)
}
AssertEqual(t, valueUser, valueResult)
}

user1 := users[0]
AssertEqual(t, user1.UserUUID.IsNilPtr(), false)
AssertEqual(t, user1.UserUUID.IsEmptyPtr(), false)
DB.Model(&user1).Updates(map[string]interface{}{"user_uuid": nil})
AssertEqual(t, user1.UserUUID.IsNilPtr(), true)
AssertEqual(t, user1.UserUUID.IsEmptyPtr(), true)
}
}
Loading