diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index a1a33bf9..d34dd849 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -70,7 +70,7 @@ jobs: uses: actions/checkout@v2 - name: go mod pakcage cache - uses: actions/cache@v2 + uses: actions/cache@v4 with: path: ~/go/pkg/mod key: ${{ runner.os }}-go-${{ matrix.go }}-${{ hashFiles('go.mod') }} @@ -114,7 +114,7 @@ jobs: uses: actions/checkout@v2 - name: go mod pakcage cache - uses: actions/cache@v2 + uses: actions/cache@v4 with: path: ~/go/pkg/mod key: ${{ runner.os }}-go-${{ matrix.go }}-${{ hashFiles('go.mod') }} @@ -158,10 +158,11 @@ jobs: uses: actions/checkout@v2 - name: go mod pakcage cache - uses: actions/cache@v2 + uses: actions/cache@v4 with: path: ~/go/pkg/mod key: ${{ runner.os }}-go-${{ matrix.go }}-${{ hashFiles('go.mod') }} - name: Tests run: GORM_ENABLE_CACHE=true GORM_DIALECT=sqlserver GORM_DSN="sqlserver://gorm:LoremIpsum86@localhost:9930?database=gorm" ./test.sh + diff --git a/db.go b/db.go index ccab03ed..31ed4549 100644 --- a/db.go +++ b/db.go @@ -13,6 +13,7 @@ import ( "gorm.io/driver/sqlserver" "gorm.io/gorm" "gorm.io/gorm/logger" + "gorm.io/gorm/schema" ) var DB *gorm.DB @@ -55,7 +56,11 @@ 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{ + NamingStrategy: schema.NamingStrategy{ + IdentifierMaxLength: 63, + }, + }) case "sqlserver": // CREATE LOGIN gorm WITH PASSWORD = 'LoremIpsum86'; // CREATE DATABASE gorm; @@ -83,7 +88,7 @@ func OpenTestConnection() (db *gorm.DB, err error) { func RunMigrations() { var err error - allModels := []interface{}{&User{}, &Account{}, &Pet{}, &Company{}, &Toy{}, &Language{}} + allModels := []interface{}{&LanguageInformation{}, &CompanyInformation{}, &Person{}} rand.Seed(time.Now().UnixNano()) rand.Shuffle(len(allModels), func(i, j int) { allModels[i], allModels[j] = allModels[j], allModels[i] }) diff --git a/go.mod b/go.mod index d1ba4316..044055b1 100644 --- a/go.mod +++ b/go.mod @@ -5,9 +5,9 @@ go 1.23.0 toolchain go1.24.3 require ( - gorm.io/driver/mysql v1.5.7 - gorm.io/driver/postgres v1.5.11 - gorm.io/driver/sqlite v1.5.7 + gorm.io/driver/mysql v1.6.0 + gorm.io/driver/postgres v1.6.0 + gorm.io/driver/sqlite v1.6.0 gorm.io/driver/sqlserver v1.6.0 gorm.io/gen v0.3.27 gorm.io/gorm v1.30.0 @@ -15,7 +15,7 @@ require ( require ( filippo.io/edwards25519 v1.1.0 // indirect - github.com/go-sql-driver/mysql v1.9.2 // indirect + github.com/go-sql-driver/mysql v1.9.3 // indirect github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 // indirect github.com/golang-sql/sqlexp v0.1.0 // indirect github.com/google/uuid v1.6.0 // indirect @@ -26,13 +26,13 @@ require ( github.com/jinzhu/inflection v1.0.0 // indirect github.com/jinzhu/now v1.1.5 // indirect github.com/mattn/go-sqlite3 v1.14.28 // indirect - github.com/microsoft/go-mssqldb v1.8.1 // indirect - golang.org/x/crypto v0.38.0 // indirect - golang.org/x/mod v0.24.0 // indirect - golang.org/x/sync v0.14.0 // indirect - golang.org/x/text v0.25.0 // indirect - golang.org/x/tools v0.33.0 // indirect - gorm.io/datatypes v1.2.5 // indirect + github.com/microsoft/go-mssqldb v1.9.2 // indirect + golang.org/x/crypto v0.40.0 // indirect + golang.org/x/mod v0.26.0 // indirect + golang.org/x/sync v0.16.0 // indirect + golang.org/x/text v0.27.0 // indirect + golang.org/x/tools v0.35.0 // indirect + gorm.io/datatypes v1.2.6 // indirect gorm.io/hints v1.1.2 // indirect gorm.io/plugin/dbresolver v1.6.0 // indirect ) diff --git a/main_test.go b/main_test.go index 60a388f7..95bfb926 100644 --- a/main_test.go +++ b/main_test.go @@ -6,15 +6,73 @@ import ( // GORM_REPO: https://github.com/go-gorm/gorm.git // GORM_BRANCH: master -// TEST_DRIVERS: sqlite, mysql, postgres, sqlserver +// TEST_DRIVERS: sqlite, mysql, postgres func TestGORM(t *testing.T) { - user := User{Name: "jinzhu"} + languageEnglish := LanguageInformation{ + InternationalCode: "en-us", + Name: "English", + } + DB.Create(&languageEnglish) + + company1 := CompanyInformation{ + Name: "Company 1", + PreferredCompanyLanguageID: &languageEnglish.ID, + } + DB.Create(&company1) + + ceoPerson := Person{ + FirstAndLastName: "John Smith", + CurrentEmployerID: &company1.ID, + } + DB.Create(&ceoPerson) + + var result Person + err := DB.Model(&Person{}). + Joins("CurrentEmployerInformation"). + Joins("CurrentEmployerInformation.PreferredCompanyLanguage"). + Limit(1). + First(&result). + Error + + if err != nil { + t.Fatalf("Got DB error %v", err) + } + + // All these should pass + if result.FirstAndLastName != "John Smith" { + t.Fatal("FirstAndLastName should be John Smith") + } + if result.CurrentEmployerInformation == nil { + t.Fatal("CurrentEmployerInformation should not be nil") + } + + if result.CurrentEmployerInformation.Name != "Company 1" { + t.Fatal("CurrentEmployerInformation.Name should be Company 1") + } + + if result.CurrentEmployerInformation.PreferredCompanyLanguage == nil { + t.Fatal("CurrentEmployerInformation.PreferredCompanyLanguage should not be nil") + } + if result.CurrentEmployerInformation.PreferredCompanyLanguage.Name != "English" { + t.Fatal("CurrentEmployerInformation.PreferredCompanyLanguage should be English") + } + + /* + This is the one that is demonstrating the specific bug with Postgres: + The generated SQL query for this column is: + `CurrentEmployerInformation__PreferredCompanyLanguage`.`international_code` AS `CurrentEmployerInformation__PreferredCompanyLanguage__international_code`, + + CurrentEmployerInformation__PreferredCompanyLanguage__international_code is larger than 63 characters long. Postgres + accepts this in the query correctly, but returns a truncated identifier for this column that is 63 characters long. - DB.Create(&user) + Gorm does not map the truncated column identifier to the intended column, leaving it empty. + Note that this happens silently; other columns are still populated. - var result User - if err := DB.First(&result, user.ID).Error; err != nil { - t.Errorf("Failed, got error: %v", err) + Using the `NamingStrategy` with max length = 63 does not affect the identifier length in query building, and + thus does not address this issue. + */ + if result.CurrentEmployerInformation.PreferredCompanyLanguage.InternationalCode != "en-us" { + t.Fatalf("CurrentEmployerInformation.PreferredCompanyLanguage should be en-us, but was `%v`", result.CurrentEmployerInformation.PreferredCompanyLanguage.InternationalCode) } } diff --git a/models.go b/models.go index 692a6842..24109dd3 100644 --- a/models.go +++ b/models.go @@ -1,60 +1,21 @@ package main -import ( - "database/sql" - "time" - - "gorm.io/gorm" -) - -// 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 -} - -type Account struct { - gorm.Model - UserID sql.NullInt64 - Number string -} - -type Pet struct { - gorm.Model - UserID *uint - Name string - Toy Toy `gorm:"polymorphic:Owner;"` -} - -type Toy struct { - gorm.Model - Name string - OwnerID string - OwnerType string +type Person struct { + ID int64 `gorm:"primary_key"` + FirstAndLastName string + CurrentEmployerID *int64 + CurrentEmployerInformation *CompanyInformation `gorm:"foreignKey:CurrentEmployerID"` } -type Company struct { - ID int - Name string +type CompanyInformation struct { + ID int64 `gorm:"primary_key"` + Name string + PreferredCompanyLanguageID *int64 + PreferredCompanyLanguage *LanguageInformation `gorm:"foreignKey:PreferredCompanyLanguageID"` } -type Language struct { - Code string `gorm:"primarykey"` - Name string +type LanguageInformation struct { + ID int64 `gorm:"primarykey"` + InternationalCode string + Name string } diff --git a/test.sh b/test.sh index 65dddba4..6d3abd04 100755 --- a/test.sh +++ b/test.sh @@ -1,6 +1,6 @@ #!/bin/bash -e -dialects=("sqlite" "mysql" "postgres" "sqlserver") +dialects=("sqlite" "mysql" "postgres") if [ "$GORM_ENABLE_CACHE" = "" ] then