From 4fc0ae388b5af590e64612d26366b58b1a3ee2a1 Mon Sep 17 00:00:00 2001 From: Andrea Limoli Date: Mon, 19 Feb 2024 16:54:05 +0100 Subject: [PATCH 1/5] Add tests. --- db.go | 4 ++- go.mod | 2 +- main_test.go | 82 +++++++++++++++++++++++++++++++++++++++++++++++++--- 3 files changed, 82 insertions(+), 6 deletions(-) diff --git a/db.go b/db.go index ccab03ed..f368270e 100644 --- a/db.go +++ b/db.go @@ -55,7 +55,9 @@ func OpenTestConnection() (db *gorm.DB, err error) { if dbDSN == "" { dbDSN = "user=gorm password=gorm host=localhost dbname=gorm port=9920 sslmode=disable TimeZone=Asia/Shanghai" } - db, err = gorm.Open(postgres.Open(dbDSN), &gorm.Config{}) + db, err = gorm.Open(postgres.Open(dbDSN), &gorm.Config{ + SkipDefaultTransaction: true, + }) case "sqlserver": // CREATE LOGIN gorm WITH PASSWORD = 'LoremIpsum86'; // CREATE DATABASE gorm; diff --git a/go.mod b/go.mod index 159394d4..1cd0fa8f 100644 --- a/go.mod +++ b/go.mod @@ -32,4 +32,4 @@ require ( gorm.io/plugin/dbresolver v1.5.0 // indirect ) -replace gorm.io/gorm => ./gorm +// replace gorm.io/gorm => ./gorm diff --git a/main_test.go b/main_test.go index 60a388f7..68c6981b 100644 --- a/main_test.go +++ b/main_test.go @@ -1,20 +1,94 @@ package main import ( + "context" + "gorm.io/gorm" + "sync" "testing" + "time" ) // GORM_REPO: https://github.com/go-gorm/gorm.git // GORM_BRANCH: master // TEST_DRIVERS: sqlite, mysql, postgres, sqlserver +// Use this command to test: +// GORM_DIALECT=postgres go test + func TestGORM(t *testing.T) { - user := User{Name: "jinzhu"} + user := User{Active: true, Name: "jinzhu"} + + session := DB.Session(&gorm.Session{}) + for i := 0; i < 1000000; i++ { + user.ID = uint(i + 1) + session.WithContext(context.Background()).Create(&user) + } + + err := DB.Transaction(func(tx *gorm.DB) error { + + var wg sync.WaitGroup + + ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) + cancel() + + for i := 0; i < 1000000; i++ { + i := i + wg.Add(1) + go func() { + defer wg.Done() + + var userName string + + result := session. + WithContext(ctx). + Table("users"). + Where("id = ?", i). + Pluck("name", &userName) + + // When the driver returns `conn busy` and the context is canceled, Pluck panics: + // panic: runtime error: index out of range [0] with length 0 + // github.com/jackc/pgx/v4/stdlib.(*Rows).Next(0x140000a2320, {0x10549fb80, 0x0, 0x104896cdc?}) + // external/com_github_jackc_pgx_v4/stdlib/sql.go:767 +0x14d0 + // database/sql.(*Rows).nextLocked(0x140004a4d00) + // GOROOT/src/database/sql/sql.go:2974 +0x160 + // database/sql.(*Rows).Next.func1() + // GOROOT/src/database/sql/sql.go:2952 +0x30 + // database/sql.withLock({0x104ea0a58, 0x140004a4d30}, 0x140005692a8) + // GOROOT/src/database/sql/sql.go:3405 +0x7c + // database/sql.(*Rows).Next(0x140004a4d00) + // GOROOT/src/database/sql/sql.go:2951 +0x64 + // gorm.io/gorm.Scan({0x104ea5890, 0x140004a4d00}, 0x140003b0540, 0x0) + // external/io_gorm_gorm/scan.go:159 +0x15c8 + // gorm.io/gorm/callbacks.Query(0x140003b0540) + // external/io_gorm_gorm/callbacks/query.go:26 +0xec + // gorm.io/gorm.(*processor).Execute(0x14000447130, 0x1400001aab0?) + // external/io_gorm_gorm/callbacks.go:130 +0x3d0 + // gorm.io/gorm.(*DB).Pluck(0x140007a2180?, {0x104b663b1, 0x2}, {0x104d3a3c0?, 0x140006228c0}) + // external/io_gorm_gorm/finisher_api.go:542 +0x24c + + if result.Error != nil { + t.Errorf("Failed during read, got error: %v", result.Error) + return + } + + result = session.Table("users"). + WithContext(ctx). + Where("active = ?", true). + Update("name", "new_name") + if result.Error != nil { + t.Errorf("Failed during update, got error: %v", result.Error) + return + } + + }() + } + + wg.Wait() - DB.Create(&user) + return nil + }) - var result User - if err := DB.First(&result, user.ID).Error; err != nil { + if err != nil { t.Errorf("Failed, got error: %v", err) } } From 239c74e00eececabc648bbf0a6326ec7e1a15d89 Mon Sep 17 00:00:00 2001 From: Andrea Limoli Date: Mon, 19 Feb 2024 17:05:01 +0100 Subject: [PATCH 2/5] Add errgroup. --- go.mod | 1 + main_test.go | 119 +++++++++++++++++++++++++++------------------------ 2 files changed, 63 insertions(+), 57 deletions(-) diff --git a/go.mod b/go.mod index 1cd0fa8f..c1df00e2 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,7 @@ module gorm.io/playground go 1.20 require ( + golang.org/x/sync v0.5.0 gorm.io/driver/mysql v1.5.2 gorm.io/driver/postgres v1.5.2 gorm.io/driver/sqlite v1.5.3 diff --git a/main_test.go b/main_test.go index 68c6981b..ca6ea5f4 100644 --- a/main_test.go +++ b/main_test.go @@ -2,10 +2,10 @@ package main import ( "context" + "errors" + "golang.org/x/sync/errgroup" "gorm.io/gorm" - "sync" "testing" - "time" ) // GORM_REPO: https://github.com/go-gorm/gorm.git @@ -19,71 +19,76 @@ func TestGORM(t *testing.T) { user := User{Active: true, Name: "jinzhu"} session := DB.Session(&gorm.Session{}) - for i := 0; i < 1000000; i++ { + for i := 0; i < 8000; i++ { user.ID = uint(i + 1) session.WithContext(context.Background()).Create(&user) } err := DB.Transaction(func(tx *gorm.DB) error { - var wg sync.WaitGroup + g, ctx := errgroup.WithContext(context.Background()) - ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) - cancel() - - for i := 0; i < 1000000; i++ { + for i := 0; i < 8000; i++ { i := i - wg.Add(1) - go func() { - defer wg.Done() - - var userName string - - result := session. - WithContext(ctx). - Table("users"). - Where("id = ?", i). - Pluck("name", &userName) - - // When the driver returns `conn busy` and the context is canceled, Pluck panics: - // panic: runtime error: index out of range [0] with length 0 - // github.com/jackc/pgx/v4/stdlib.(*Rows).Next(0x140000a2320, {0x10549fb80, 0x0, 0x104896cdc?}) - // external/com_github_jackc_pgx_v4/stdlib/sql.go:767 +0x14d0 - // database/sql.(*Rows).nextLocked(0x140004a4d00) - // GOROOT/src/database/sql/sql.go:2974 +0x160 - // database/sql.(*Rows).Next.func1() - // GOROOT/src/database/sql/sql.go:2952 +0x30 - // database/sql.withLock({0x104ea0a58, 0x140004a4d30}, 0x140005692a8) - // GOROOT/src/database/sql/sql.go:3405 +0x7c - // database/sql.(*Rows).Next(0x140004a4d00) - // GOROOT/src/database/sql/sql.go:2951 +0x64 - // gorm.io/gorm.Scan({0x104ea5890, 0x140004a4d00}, 0x140003b0540, 0x0) - // external/io_gorm_gorm/scan.go:159 +0x15c8 - // gorm.io/gorm/callbacks.Query(0x140003b0540) - // external/io_gorm_gorm/callbacks/query.go:26 +0xec - // gorm.io/gorm.(*processor).Execute(0x14000447130, 0x1400001aab0?) - // external/io_gorm_gorm/callbacks.go:130 +0x3d0 - // gorm.io/gorm.(*DB).Pluck(0x140007a2180?, {0x104b663b1, 0x2}, {0x104d3a3c0?, 0x140006228c0}) - // external/io_gorm_gorm/finisher_api.go:542 +0x24c - - if result.Error != nil { - t.Errorf("Failed during read, got error: %v", result.Error) - return - } - - result = session.Table("users"). - WithContext(ctx). - Where("active = ?", true). - Update("name", "new_name") - if result.Error != nil { - t.Errorf("Failed during update, got error: %v", result.Error) - return - } - - }() + + g.Go( + func() error { + + if i == 6000 { + return errors.New("fake error") + } + + var userName string + + result := session. + WithContext(ctx). + Table("users"). + Where("id = ?", i). + Pluck("name", &userName) + + // When the driver returns `conn busy` and the context is canceled, Pluck panics: + // panic: runtime error: index out of range [0] with length 0 + // github.com/jackc/pgx/v4/stdlib.(*Rows).Next(0x140000a2320, {0x10549fb80, 0x0, 0x104896cdc?}) + // external/com_github_jackc_pgx_v4/stdlib/sql.go:767 +0x14d0 + // database/sql.(*Rows).nextLocked(0x140004a4d00) + // GOROOT/src/database/sql/sql.go:2974 +0x160 + // database/sql.(*Rows).Next.func1() + // GOROOT/src/database/sql/sql.go:2952 +0x30 + // database/sql.withLock({0x104ea0a58, 0x140004a4d30}, 0x140005692a8) + // GOROOT/src/database/sql/sql.go:3405 +0x7c + // database/sql.(*Rows).Next(0x140004a4d00) + // GOROOT/src/database/sql/sql.go:2951 +0x64 + // gorm.io/gorm.Scan({0x104ea5890, 0x140004a4d00}, 0x140003b0540, 0x0) + // external/io_gorm_gorm/scan.go:159 +0x15c8 + // gorm.io/gorm/callbacks.Query(0x140003b0540) + // external/io_gorm_gorm/callbacks/query.go:26 +0xec + // gorm.io/gorm.(*processor).Execute(0x14000447130, 0x1400001aab0?) + // external/io_gorm_gorm/callbacks.go:130 +0x3d0 + // gorm.io/gorm.(*DB).Pluck(0x140007a2180?, {0x104b663b1, 0x2}, {0x104d3a3c0?, 0x140006228c0}) + // external/io_gorm_gorm/finisher_api.go:542 +0x24c + + if result.Error != nil { + t.Errorf("Failed during read, got error: %v", result.Error) + return result.Error + } + + result = session.Table("users"). + WithContext(ctx). + Where("active = ?", true). + Update("name", "new_name") + if result.Error != nil { + t.Errorf("Failed during update, got error: %v", result.Error) + return result.Error + } + + return nil + + }) } - wg.Wait() + if err := g.Wait(); err != nil { + t.Errorf("Failed, got error: %v", err) + } return nil }) From 98e8504f5a94771ca3e0c5b4d117702ddcef1bfa Mon Sep 17 00:00:00 2001 From: Andrea Limoli Date: Mon, 19 Feb 2024 17:18:18 +0100 Subject: [PATCH 3/5] Fix tx. --- main_test.go | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/main_test.go b/main_test.go index ca6ea5f4..dee85b32 100644 --- a/main_test.go +++ b/main_test.go @@ -2,7 +2,6 @@ package main import ( "context" - "errors" "golang.org/x/sync/errgroup" "gorm.io/gorm" "testing" @@ -34,13 +33,9 @@ func TestGORM(t *testing.T) { g.Go( func() error { - if i == 6000 { - return errors.New("fake error") - } - var userName string - result := session. + result := tx. WithContext(ctx). Table("users"). Where("id = ?", i). @@ -72,7 +67,7 @@ func TestGORM(t *testing.T) { return result.Error } - result = session.Table("users"). + result = tx.Table("users"). WithContext(ctx). Where("active = ?", true). Update("name", "new_name") From ce6046036ebdff7670df42e252a3929474d760d2 Mon Sep 17 00:00:00 2001 From: Andrea Limoli Date: Mon, 19 Feb 2024 17:24:22 +0100 Subject: [PATCH 4/5] Fix update example. --- main_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/main_test.go b/main_test.go index dee85b32..ca593290 100644 --- a/main_test.go +++ b/main_test.go @@ -69,8 +69,8 @@ func TestGORM(t *testing.T) { result = tx.Table("users"). WithContext(ctx). - Where("active = ?", true). - Update("name", "new_name") + Where("id = ?", i). + Update("active", true) if result.Error != nil { t.Errorf("Failed during update, got error: %v", result.Error) return result.Error From 624a1cce70ff5908590f287bc60a5acaf6b9692d Mon Sep 17 00:00:00 2001 From: Andrea Limoli Date: Wed, 29 May 2024 12:00:50 +0200 Subject: [PATCH 5/5] fix: fix tests --- db.go | 2 +- gen.go | 1 - go.mod | 4 +-- main_test.go | 88 +++++++--------------------------------------------- models.go | 65 ++++++++++++++------------------------ 5 files changed, 37 insertions(+), 123 deletions(-) diff --git a/db.go b/db.go index f368270e..edf835ed 100644 --- a/db.go +++ b/db.go @@ -85,7 +85,7 @@ func OpenTestConnection() (db *gorm.DB, err error) { func RunMigrations() { var err error - allModels := []interface{}{&User{}, &Account{}, &Pet{}, &Company{}, &Toy{}, &Language{}} + allModels := []interface{}{&User{}} rand.Seed(time.Now().UnixNano()) rand.Shuffle(len(allModels), func(i, j int) { allModels[i], allModels[j] = allModels[j], allModels[i] }) diff --git a/gen.go b/gen.go index 3127d5d6..a82f9442 100644 --- a/gen.go +++ b/gen.go @@ -14,7 +14,6 @@ func generate() { }) g.UseDB(dal.DB) - g.ApplyBasic(Company{}, Language{}) // Associations g.ApplyBasic(g.GenerateModel("user"), g.GenerateModelAs("account", "AccountInfo")) g.Execute() diff --git a/go.mod b/go.mod index c1df00e2..64698d9e 100644 --- a/go.mod +++ b/go.mod @@ -3,13 +3,13 @@ module gorm.io/playground go 1.20 require ( - golang.org/x/sync v0.5.0 + github.com/qmuntal/stateless v1.7.0 gorm.io/driver/mysql v1.5.2 gorm.io/driver/postgres v1.5.2 gorm.io/driver/sqlite v1.5.3 gorm.io/driver/sqlserver v1.5.1 gorm.io/gen v0.3.25 - gorm.io/gorm v1.25.4 + gorm.io/gorm v1.25.10 ) require ( diff --git a/main_test.go b/main_test.go index ca593290..fb9d6309 100644 --- a/main_test.go +++ b/main_test.go @@ -1,9 +1,6 @@ package main import ( - "context" - "golang.org/x/sync/errgroup" - "gorm.io/gorm" "testing" ) @@ -11,84 +8,21 @@ import ( // GORM_BRANCH: master // TEST_DRIVERS: sqlite, mysql, postgres, sqlserver -// Use this command to test: -// GORM_DIALECT=postgres go test - func TestGORM(t *testing.T) { - user := User{Active: true, Name: "jinzhu"} - - session := DB.Session(&gorm.Session{}) - for i := 0; i < 8000; i++ { - user.ID = uint(i + 1) - session.WithContext(context.Background()).Create(&user) - } - - err := DB.Transaction(func(tx *gorm.DB) error { - - g, ctx := errgroup.WithContext(context.Background()) - - for i := 0; i < 8000; i++ { - i := i - - g.Go( - func() error { - - var userName string - - result := tx. - WithContext(ctx). - Table("users"). - Where("id = ?", i). - Pluck("name", &userName) + user := User{Name: "jinzhu", State: NewCustomType("active")} - // When the driver returns `conn busy` and the context is canceled, Pluck panics: - // panic: runtime error: index out of range [0] with length 0 - // github.com/jackc/pgx/v4/stdlib.(*Rows).Next(0x140000a2320, {0x10549fb80, 0x0, 0x104896cdc?}) - // external/com_github_jackc_pgx_v4/stdlib/sql.go:767 +0x14d0 - // database/sql.(*Rows).nextLocked(0x140004a4d00) - // GOROOT/src/database/sql/sql.go:2974 +0x160 - // database/sql.(*Rows).Next.func1() - // GOROOT/src/database/sql/sql.go:2952 +0x30 - // database/sql.withLock({0x104ea0a58, 0x140004a4d30}, 0x140005692a8) - // GOROOT/src/database/sql/sql.go:3405 +0x7c - // database/sql.(*Rows).Next(0x140004a4d00) - // GOROOT/src/database/sql/sql.go:2951 +0x64 - // gorm.io/gorm.Scan({0x104ea5890, 0x140004a4d00}, 0x140003b0540, 0x0) - // external/io_gorm_gorm/scan.go:159 +0x15c8 - // gorm.io/gorm/callbacks.Query(0x140003b0540) - // external/io_gorm_gorm/callbacks/query.go:26 +0xec - // gorm.io/gorm.(*processor).Execute(0x14000447130, 0x1400001aab0?) - // external/io_gorm_gorm/callbacks.go:130 +0x3d0 - // gorm.io/gorm.(*DB).Pluck(0x140007a2180?, {0x104b663b1, 0x2}, {0x104d3a3c0?, 0x140006228c0}) - // external/io_gorm_gorm/finisher_api.go:542 +0x24c + DB.Create(&user) - if result.Error != nil { - t.Errorf("Failed during read, got error: %v", result.Error) - return result.Error - } - - result = tx.Table("users"). - WithContext(ctx). - Where("id = ?", i). - Update("active", true) - if result.Error != nil { - t.Errorf("Failed during update, got error: %v", result.Error) - return result.Error - } - - return nil - - }) - } - - if err := g.Wait(); err != nil { - t.Errorf("Failed, got error: %v", err) - } + var result []User + if err := DB.Find(&result).Error; err != nil { + t.Errorf("Failed, got error: %v", err) + } - return nil - }) + if len(result) == 0 { + t.Errorf("Failed, got empty result") + } - if err != nil { - t.Errorf("Failed, got error: %v", err) + if result[0].State.Machine == nil { + t.Errorf("Failed, expected: active") } } diff --git a/models.go b/models.go index 692a6842..27e6d0e0 100644 --- a/models.go +++ b/models.go @@ -1,60 +1,41 @@ package main import ( - "database/sql" - "time" - + "database/sql/driver" "gorm.io/gorm" + "log" ) -// User has one `Account` (has one), many `Pets` (has many) and `Toys` (has many - polymorphic) -// He works in a Company (belongs to), he has a Manager (belongs to - single-table), and also managed a Team (has many - single-table) -// He speaks many languages (many to many) and has many friends (many to many - single-table) -// His pet also has one Toy (has one - polymorphic) type User struct { gorm.Model - Name string - Age uint - Birthday *time.Time - Account Account - Pets []*Pet - Toys []Toy `gorm:"polymorphic:Owner"` - CompanyID *int - Company Company - ManagerID *uint - Manager *User - Team []User `gorm:"foreignkey:ManagerID"` - Languages []Language `gorm:"many2many:UserSpeak"` - Friends []*User `gorm:"many2many:user_friends"` - Active bool + Name string + State CustomType } -type Account struct { - gorm.Model - UserID sql.NullInt64 - Number string +type CustomType struct { + Machine *Machine } -type Pet struct { - gorm.Model - UserID *uint - Name string - Toy Toy `gorm:"polymorphic:Owner;"` -} +type Machine string -type Toy struct { - gorm.Model - Name string - OwnerID string - OwnerType string +func NewCustomType(state Machine) CustomType { + return CustomType{&state} } -type Company struct { - ID int - Name string +// Implement scanner and value interfaces as described in tests https://gorm.io/docs/data_types.html#Scanner-x2F-Valuer +// https://github.com/go-gorm/gorm/blob/master/tests/scanner_valuer_test.go + +func (sm *CustomType) Scan(value interface{}) error { + log.Printf("Scanning state %v", value) + switch vt := value.(type) { + case string: + state := vt + *sm = NewCustomType(Machine(state)) + } + return nil } -type Language struct { - Code string `gorm:"primarykey"` - Name string +func (sm CustomType) Value() (driver.Value, error) { + log.Printf("Value of state %v", sm) + return &sm.Machine, nil }