mirror of https://github.com/go-gorm/gorm.git
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:
parent
02b7e26f6b
commit
532e9cf4cc
|
@ -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 {
|
||||||
|
|
|
@ -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},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
7
model.go
7
model.go
|
@ -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
|
||||||
|
|
|
@ -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"]
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -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,
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
||||||
|
|
|
@ -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{})
|
||||||
|
|
Loading…
Reference in New Issue