Skip to content

Repro for postgres deep nested joins #815

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

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
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
7 changes: 4 additions & 3 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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') }}
Expand Down Expand Up @@ -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') }}
Expand Down Expand Up @@ -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

9 changes: 7 additions & 2 deletions db.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (
"gorm.io/driver/sqlserver"
"gorm.io/gorm"
"gorm.io/gorm/logger"
"gorm.io/gorm/schema"
)

var DB *gorm.DB
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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] })

Expand Down
22 changes: 11 additions & 11 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,17 @@ 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
)

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
Expand All @@ -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
)
Expand Down
70 changes: 64 additions & 6 deletions main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}
67 changes: 14 additions & 53 deletions models.go
Original file line number Diff line number Diff line change
@@ -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
}
2 changes: 1 addition & 1 deletion test.sh
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#!/bin/bash -e

dialects=("sqlite" "mysql" "postgres" "sqlserver")
dialects=("sqlite" "mysql" "postgres")

if [ "$GORM_ENABLE_CACHE" = "" ]
then
Expand Down
Loading