Skip to content

Commit

Permalink
feature: Add datatype for uuid.UUID (Google UUID)
Browse files Browse the repository at this point in the history
This PR adds datatype for uuid.UUID along with supporting tests.
  • Loading branch information
omkar-foss committed Jul 10, 2024
1 parent 6a271a4 commit 5637ec8
Show file tree
Hide file tree
Showing 4 changed files with 262 additions and 0 deletions.
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -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=
Expand Down
73 changes: 73 additions & 0 deletions uuid.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
package datatypes

import (
"database/sql/driver"

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

type UUID uuid.UUID

// 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 ""
}
}

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
}

func (u UUID) Value() (driver.Value, error) {
return uuid.UUID(u).Value()
}

func (u UUID) String() string {
return uuid.UUID(u).String()
}

func (u UUID) Equals(other UUID) bool {
return u.String() == other.String()
}

func (u UUID) Length() int {
return len(u.String())
}

func (u UUID) IsNil() bool {
return uuid.UUID(u) == uuid.Nil
}

func (u UUID) IsEmpty() bool {
return u.IsNil() || u.Length() == 0
}

func (u *UUID) IsNilPtr() bool {
return u == nil
}

func (u *UUID) IsEmptyPtr() bool {
return u.IsNilPtr() || u.IsEmpty()
}
187 changes: 187 additions & 0 deletions uuid_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
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)
}

user1UUID, uuidGenErr := uuid.NewUUID()
if uuidGenErr != nil {
t.Fatalf("failed to generate a new UUID, got error %v", uuidGenErr)
}
user2UUID, uuidGenErr := uuid.NewUUID()
if uuidGenErr != nil {
t.Fatalf("failed to generate a new UUID, got error %v", uuidGenErr)
}
user3UUID, uuidGenErr := uuid.NewUUID()
if uuidGenErr != nil {
t.Fatalf("failed to generate a new UUID, got error %v", uuidGenErr)
}
user4UUID, uuidGenErr := uuid.NewUUID()
if uuidGenErr != nil {
t.Fatalf("failed to generate a new UUID, got error %v", uuidGenErr)
}

users := []UserWithUUID{{
Name: "uuid-1",
UserUUID: datatypes.UUID(user1UUID),
}, {
Name: "uuid-2",
UserUUID: datatypes.UUID(user2UUID),
}, {
Name: "uuid-3",
UserUUID: datatypes.UUID(user3UUID),
}, {
Name: "uuid-4",
UserUUID: datatypes.UUID(user4UUID),
}}

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)
}

user1UUID, uuidGenErr := uuid.NewUUID()
if uuidGenErr != nil {
t.Fatalf("failed to generate a new UUID, got error %v", uuidGenErr)
}
user2UUID, uuidGenErr := uuid.NewUUID()
if uuidGenErr != nil {
t.Fatalf("failed to generate a new UUID, got error %v", uuidGenErr)
}
user3UUID, uuidGenErr := uuid.NewUUID()
if uuidGenErr != nil {
t.Fatalf("failed to generate a new UUID, got error %v", uuidGenErr)
}
user4UUID, uuidGenErr := uuid.NewUUID()
if uuidGenErr != nil {
t.Fatalf("failed to generate a new UUID, got error %v", uuidGenErr)
}

uuid1 := datatypes.UUID(user1UUID)
uuid2 := datatypes.UUID(user2UUID)
uuid3 := datatypes.UUID(user3UUID)
uuid4 := datatypes.UUID(user4UUID)

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)
}
}

0 comments on commit 5637ec8

Please sign in to comment.