Issue 6054: Unscoped not working with PreLoad on Joins (#6058)

* Issue 6054: Unscoped not working with PreLoad on Joins

* Formatting

---------

Co-authored-by: Michael Anstis <manstis@redhat.com>
This commit is contained in:
Michael Anstis 2023-02-18 01:06:43 +00:00 committed by GitHub
parent 02b7e26f6b
commit 532e9cf4cc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 106 additions and 42 deletions

View File

@ -257,6 +257,7 @@ func Preload(db *gorm.DB) {
return return
} }
preloadDB.Statement.ReflectValue = db.Statement.ReflectValue preloadDB.Statement.ReflectValue = db.Statement.ReflectValue
preloadDB.Statement.Unscoped = db.Statement.Unscoped
for _, name := range preloadNames { for _, name := range preloadNames {
if rel := preloadDB.Statement.Schema.Relationships.Relations[name]; rel != nil { if rel := preloadDB.Statement.Schema.Relationships.Relations[name]; rel != nil {

View File

@ -49,16 +49,18 @@ func TestSelect(t *testing.T) {
Exprs: []clause.Expression{ Exprs: []clause.Expression{
clause.Expr{ clause.Expr{
SQL: "? as name", SQL: "? as name",
Vars: []interface{}{clause.Eq{ Vars: []interface{}{
Column: clause.Column{Name: "age"}, clause.Eq{
Value: 18, Column: clause.Column{Name: "age"},
}, Value: 18,
},
}, },
}, },
}, },
}, },
}, clause.From{}}, }, clause.From{}},
"SELECT `age` = ? as name FROM `users`", []interface{}{18}, "SELECT `age` = ? as name FROM `users`",
[]interface{}{18},
}, },
} }

View File

@ -16,9 +16,7 @@ import (
"gorm.io/gorm/schema" "gorm.io/gorm/schema"
) )
var ( var regFullDataType = regexp.MustCompile(`\D*(\d+)\D?`)
regFullDataType = regexp.MustCompile(`\D*(\d+)\D?`)
)
// Migrator m struct // Migrator m struct
type Migrator struct { type Migrator struct {

View File

@ -4,9 +4,10 @@ import "time"
// Model a basic GoLang struct which includes the following fields: ID, CreatedAt, UpdatedAt, DeletedAt // Model a basic GoLang struct which includes the following fields: ID, CreatedAt, UpdatedAt, DeletedAt
// It may be embedded into your model or you may build your own model without it // It may be embedded into your model or you may build your own model without it
// type User struct { //
// gorm.Model // type User struct {
// } // gorm.Model
// }
type Model struct { type Model struct {
ID uint `gorm:"primarykey"` ID uint `gorm:"primarykey"`
CreatedAt time.Time CreatedAt time.Time

View File

@ -174,7 +174,7 @@ func (schema *Schema) ParseField(fieldStruct reflect.StructField) *Field {
field.DataType = String field.DataType = String
field.Serializer = v field.Serializer = v
} else { } else {
var serializerName = field.TagSettings["JSON"] serializerName := field.TagSettings["JSON"]
if serializerName == "" { if serializerName == "" {
serializerName = field.TagSettings["SERIALIZER"] serializerName = field.TagSettings["SERIALIZER"]
} }

View File

@ -123,16 +123,17 @@ func (schema *Schema) parseRelation(field *Field) *Relationship {
} }
// User has many Toys, its `Polymorphic` is `Owner`, Pet has one Toy, its `Polymorphic` is `Owner` // User has many Toys, its `Polymorphic` is `Owner`, Pet has one Toy, its `Polymorphic` is `Owner`
// type User struct { //
// Toys []Toy `gorm:"polymorphic:Owner;"` // type User struct {
// } // Toys []Toy `gorm:"polymorphic:Owner;"`
// type Pet struct { // }
// Toy Toy `gorm:"polymorphic:Owner;"` // type Pet struct {
// } // Toy Toy `gorm:"polymorphic:Owner;"`
// type Toy struct { // }
// OwnerID int // type Toy struct {
// OwnerType string // OwnerID int
// } // OwnerType string
// }
func (schema *Schema) buildPolymorphicRelation(relation *Relationship, field *Field, polymorphic string) { func (schema *Schema) buildPolymorphicRelation(relation *Relationship, field *Field, polymorphic string) {
relation.Polymorphic = &Polymorphic{ relation.Polymorphic = &Polymorphic{
Value: schema.Table, Value: schema.Table,
@ -427,7 +428,7 @@ func (schema *Schema) guessRelation(relation *Relationship, field *Field, cgl gu
foreignFields = append(foreignFields, f) foreignFields = append(foreignFields, f)
} }
} else { } else {
var primarySchemaName = primarySchema.Name primarySchemaName := primarySchema.Name
if primarySchemaName == "" { if primarySchemaName == "" {
primarySchemaName = relation.FieldSchema.Name primarySchemaName = relation.FieldSchema.Name
} }

View File

@ -70,8 +70,7 @@ type SerializerValuerInterface interface {
} }
// JSONSerializer json serializer // JSONSerializer json serializer
type JSONSerializer struct { type JSONSerializer struct{}
}
// Scan implements serializer interface // Scan implements serializer interface
func (JSONSerializer) Scan(ctx context.Context, field *Field, dst reflect.Value, dbValue interface{}) (err error) { func (JSONSerializer) Scan(ctx context.Context, field *Field, dst reflect.Value, dbValue interface{}) (err error) {
@ -110,8 +109,7 @@ func (JSONSerializer) Value(ctx context.Context, field *Field, dst reflect.Value
} }
// UnixSecondSerializer json serializer // UnixSecondSerializer json serializer
type UnixSecondSerializer struct { type UnixSecondSerializer struct{}
}
// Scan implements serializer interface // Scan implements serializer interface
func (UnixSecondSerializer) Scan(ctx context.Context, field *Field, dst reflect.Value, dbValue interface{}) (err error) { func (UnixSecondSerializer) Scan(ctx context.Context, field *Field, dst reflect.Value, dbValue interface{}) (err error) {
@ -141,8 +139,7 @@ func (UnixSecondSerializer) Value(ctx context.Context, field *Field, dst reflect
} }
// GobSerializer gob serializer // GobSerializer gob serializer
type GobSerializer struct { type GobSerializer struct{}
}
// Scan implements serializer interface // Scan implements serializer interface
func (GobSerializer) Scan(ctx context.Context, field *Field, dst reflect.Value, dbValue interface{}) (err error) { func (GobSerializer) Scan(ctx context.Context, field *Field, dst reflect.Value, dbValue interface{}) (err error) {

View File

@ -48,9 +48,11 @@ func (c *wrapperConnPool) Ping() error {
} }
// If you use BeginTx returned *sql.Tx as shown below then you can't record queries in a transaction. // If you use BeginTx returned *sql.Tx as shown below then you can't record queries in a transaction.
// func (c *wrapperConnPool) BeginTx(ctx context.Context, opts *sql.TxOptions) (*sql.Tx, error) { //
// return c.db.BeginTx(ctx, opts) // func (c *wrapperConnPool) BeginTx(ctx context.Context, opts *sql.TxOptions) (*sql.Tx, error) {
// } // return c.db.BeginTx(ctx, opts)
// }
//
// You should use BeginTx returned gorm.Tx which could wrap *sql.Tx then you can record all queries. // You should use BeginTx returned gorm.Tx which could wrap *sql.Tx then you can record all queries.
func (c *wrapperConnPool) BeginTx(ctx context.Context, opts *sql.TxOptions) (gorm.ConnPool, error) { func (c *wrapperConnPool) BeginTx(ctx context.Context, opts *sql.TxOptions) (gorm.ConnPool, error) {
tx, err := c.db.BeginTx(ctx, opts) tx, err := c.db.BeginTx(ctx, opts)

View File

@ -94,7 +94,6 @@ func TestEmbeddedStruct(t *testing.T) {
t.Errorf("expected author %s got %s", want, post.Author.Name) t.Errorf("expected author %s got %s", want, post.Author.Name)
} }
} }
} }
func TestEmbeddedPointerTypeStruct(t *testing.T) { func TestEmbeddedPointerTypeStruct(t *testing.T) {

View File

@ -8,6 +8,8 @@ import (
"testing" "testing"
"time" "time"
"gorm.io/gorm"
. "gorm.io/gorm/utils/tests" . "gorm.io/gorm/utils/tests"
) )
@ -74,10 +76,18 @@ func GetUser(name string, config Config) *User {
return &user return &user
} }
func CheckPetUnscoped(t *testing.T, pet Pet, expect Pet) {
doCheckPet(t, pet, expect, true)
}
func CheckPet(t *testing.T, pet Pet, expect Pet) { func CheckPet(t *testing.T, pet Pet, expect Pet) {
doCheckPet(t, pet, expect, false)
}
func doCheckPet(t *testing.T, pet Pet, expect Pet, unscoped bool) {
if pet.ID != 0 { if pet.ID != 0 {
var newPet Pet var newPet Pet
if err := DB.Where("id = ?", pet.ID).First(&newPet).Error; err != nil { if err := db(unscoped).Where("id = ?", pet.ID).First(&newPet).Error; err != nil {
t.Fatalf("errors happened when query: %v", err) t.Fatalf("errors happened when query: %v", err)
} else { } else {
AssertObjEqual(t, newPet, pet, "ID", "CreatedAt", "UpdatedAt", "DeletedAt", "UserID", "Name") AssertObjEqual(t, newPet, pet, "ID", "CreatedAt", "UpdatedAt", "DeletedAt", "UserID", "Name")
@ -94,10 +104,18 @@ func CheckPet(t *testing.T, pet Pet, expect Pet) {
} }
} }
func CheckUserUnscoped(t *testing.T, user User, expect User) {
doCheckUser(t, user, expect, true)
}
func CheckUser(t *testing.T, user User, expect User) { func CheckUser(t *testing.T, user User, expect User) {
doCheckUser(t, user, expect, false)
}
func doCheckUser(t *testing.T, user User, expect User, unscoped bool) {
if user.ID != 0 { if user.ID != 0 {
var newUser User var newUser User
if err := DB.Where("id = ?", user.ID).First(&newUser).Error; err != nil { if err := db(unscoped).Where("id = ?", user.ID).First(&newUser).Error; err != nil {
t.Fatalf("errors happened when query: %v", err) t.Fatalf("errors happened when query: %v", err)
} else { } else {
AssertObjEqual(t, newUser, user, "ID", "CreatedAt", "UpdatedAt", "DeletedAt", "Name", "Age", "Birthday", "CompanyID", "ManagerID", "Active") AssertObjEqual(t, newUser, user, "ID", "CreatedAt", "UpdatedAt", "DeletedAt", "Name", "Age", "Birthday", "CompanyID", "ManagerID", "Active")
@ -114,7 +132,7 @@ func CheckUser(t *testing.T, user User, expect User) {
t.Errorf("Account's foreign key should be saved") t.Errorf("Account's foreign key should be saved")
} else { } else {
var account Account var account Account
DB.First(&account, "user_id = ?", user.ID) db(unscoped).First(&account, "user_id = ?", user.ID)
AssertObjEqual(t, account, user.Account, "ID", "CreatedAt", "UpdatedAt", "DeletedAt", "UserID", "Number") AssertObjEqual(t, account, user.Account, "ID", "CreatedAt", "UpdatedAt", "DeletedAt", "UserID", "Number")
} }
} }
@ -137,7 +155,7 @@ func CheckUser(t *testing.T, user User, expect User) {
if pet == nil || expect.Pets[idx] == nil { if pet == nil || expect.Pets[idx] == nil {
t.Errorf("pets#%v should equal, expect: %v, got %v", idx, expect.Pets[idx], pet) t.Errorf("pets#%v should equal, expect: %v, got %v", idx, expect.Pets[idx], pet)
} else { } else {
CheckPet(t, *pet, *expect.Pets[idx]) doCheckPet(t, *pet, *expect.Pets[idx], unscoped)
} }
} }
}) })
@ -174,7 +192,7 @@ func CheckUser(t *testing.T, user User, expect User) {
t.Errorf("Manager's foreign key should be saved") t.Errorf("Manager's foreign key should be saved")
} else { } else {
var manager User var manager User
DB.First(&manager, "id = ?", *user.ManagerID) db(unscoped).First(&manager, "id = ?", *user.ManagerID)
AssertObjEqual(t, manager, user.Manager, "ID", "CreatedAt", "UpdatedAt", "DeletedAt", "Name", "Age", "Birthday", "CompanyID", "ManagerID", "Active") AssertObjEqual(t, manager, user.Manager, "ID", "CreatedAt", "UpdatedAt", "DeletedAt", "Name", "Age", "Birthday", "CompanyID", "ManagerID", "Active")
AssertObjEqual(t, manager, expect.Manager, "ID", "CreatedAt", "UpdatedAt", "DeletedAt", "Name", "Age", "Birthday", "CompanyID", "ManagerID", "Active") AssertObjEqual(t, manager, expect.Manager, "ID", "CreatedAt", "UpdatedAt", "DeletedAt", "Name", "Age", "Birthday", "CompanyID", "ManagerID", "Active")
} }
@ -246,3 +264,11 @@ func tidbSkip(t *testing.T, reason string) {
func isTiDB() bool { func isTiDB() bool {
return os.Getenv("GORM_DIALECT") == "tidb" return os.Getenv("GORM_DIALECT") == "tidb"
} }
func db(unscoped bool) *gorm.DB {
if unscoped {
return DB.Unscoped()
} else {
return DB
}
}

View File

@ -75,7 +75,6 @@ func TestMigrate(t *testing.T) {
t.Fatalf("Failed to find index for many2many for %v %v", indexes[0], indexes[1]) t.Fatalf("Failed to find index for many2many for %v %v", indexes[0], indexes[1])
} }
} }
} }
func TestAutoMigrateInt8PG(t *testing.T) { func TestAutoMigrateInt8PG(t *testing.T) {
@ -1267,7 +1266,7 @@ func (mm mockMigrator) AlterColumn(dst interface{}, field string) error {
} }
func TestMigrateDonotAlterColumn(t *testing.T) { func TestMigrateDonotAlterColumn(t *testing.T) {
var wrapMockMigrator = func(m gorm.Migrator) mockMigrator { wrapMockMigrator := func(m gorm.Migrator) mockMigrator {
return mockMigrator{ return mockMigrator{
Migrator: m, Migrator: m,
} }

View File

@ -269,3 +269,40 @@ func TestPreloadWithDiffModel(t *testing.T) {
CheckUser(t, user, result.User) CheckUser(t, user, result.User)
} }
func TestNestedPreloadWithUnscoped(t *testing.T) {
user := *GetUser("nested_preload", Config{Pets: 1})
pet := user.Pets[0]
pet.Toy = Toy{Name: "toy_nested_preload_" + strconv.Itoa(1)}
pet.Toy = Toy{Name: "toy_nested_preload_" + strconv.Itoa(2)}
if err := DB.Create(&user).Error; err != nil {
t.Fatalf("errors happened when create: %v", err)
}
var user2 User
DB.Preload("Pets.Toy").Find(&user2, "id = ?", user.ID)
CheckUser(t, user2, user)
DB.Delete(&pet)
var user3 User
DB.Preload(clause.Associations+"."+clause.Associations).Find(&user3, "id = ?", user.ID)
if len(user3.Pets) != 0 {
t.Fatalf("User.Pet[0] was deleted and should not exist.")
}
var user4 *User
DB.Preload("Pets.Toy").Find(&user4, "id = ?", user.ID)
if len(user4.Pets) != 0 {
t.Fatalf("User.Pet[0] was deleted and should not exist.")
}
var user5 User
DB.Unscoped().Preload(clause.Associations+"."+clause.Associations).Find(&user5, "id = ?", user.ID)
CheckUserUnscoped(t, user5, user)
var user6 *User
DB.Unscoped().Preload("Pets.Toy").Find(&user6, "id = ?", user.ID)
CheckUserUnscoped(t, *user6, user)
}

View File

@ -158,10 +158,11 @@ func (UserWithTableNamer) TableName(namer schema.Namer) string {
} }
func TestTableWithNamer(t *testing.T) { func TestTableWithNamer(t *testing.T) {
var db, _ = gorm.Open(tests.DummyDialector{}, &gorm.Config{ db, _ := gorm.Open(tests.DummyDialector{}, &gorm.Config{
NamingStrategy: schema.NamingStrategy{ NamingStrategy: schema.NamingStrategy{
TablePrefix: "t_", TablePrefix: "t_",
}}) },
})
sql := db.ToSQL(func(tx *gorm.DB) *gorm.DB { sql := db.ToSQL(func(tx *gorm.DB) *gorm.DB {
return tx.Model(&UserWithTableNamer{}).Find(&UserWithTableNamer{}) return tx.Model(&UserWithTableNamer{}).Find(&UserWithTableNamer{})