Test ForeignKeyConstraints

This commit is contained in:
Jinzhu 2020-06-20 00:48:15 +08:00
parent d4d339f3b5
commit 4f19e2a7b3
7 changed files with 165 additions and 41 deletions

View File

@ -137,6 +137,32 @@ func ConvertToAssignments(stmt *gorm.Statement) (set clause.Set) {
updatingValue = updatingValue.Elem() updatingValue = updatingValue.Elem()
} }
if !updatingValue.CanAddr() || stmt.Dest != stmt.Model {
switch stmt.ReflectValue.Kind() {
case reflect.Slice, reflect.Array:
var priamryKeyExprs []clause.Expression
for i := 0; i < stmt.ReflectValue.Len(); i++ {
var exprs = make([]clause.Expression, len(stmt.Schema.PrimaryFields))
var notZero bool
for idx, field := range stmt.Schema.PrimaryFields {
value, isZero := field.ValueOf(stmt.ReflectValue.Index(i))
exprs[idx] = clause.Eq{Column: field.DBName, Value: value}
notZero = notZero || !isZero
}
if notZero {
priamryKeyExprs = append(priamryKeyExprs, clause.And(exprs...))
}
}
stmt.AddClause(clause.Where{Exprs: []clause.Expression{clause.Or(priamryKeyExprs...)}})
case reflect.Struct:
for _, field := range stmt.Schema.PrimaryFields {
if value, isZero := field.ValueOf(stmt.ReflectValue); !isZero {
stmt.AddClause(clause.Where{Exprs: []clause.Expression{clause.Eq{Column: field.DBName, Value: value}}})
}
}
}
}
switch value := updatingValue.Interface().(type) { switch value := updatingValue.Interface().(type) {
case map[string]interface{}: case map[string]interface{}:
set = make([]clause.Assignment, 0, len(value)) set = make([]clause.Assignment, 0, len(value))
@ -218,31 +244,5 @@ func ConvertToAssignments(stmt *gorm.Statement) (set clause.Set) {
} }
} }
if !updatingValue.CanAddr() || stmt.Dest != stmt.Model {
switch stmt.ReflectValue.Kind() {
case reflect.Slice, reflect.Array:
var priamryKeyExprs []clause.Expression
for i := 0; i < stmt.ReflectValue.Len(); i++ {
var exprs = make([]clause.Expression, len(stmt.Schema.PrimaryFields))
var notZero bool
for idx, field := range stmt.Schema.PrimaryFields {
value, isZero := field.ValueOf(stmt.ReflectValue.Index(i))
exprs[idx] = clause.Eq{Column: field.DBName, Value: value}
notZero = notZero || !isZero
}
if notZero {
priamryKeyExprs = append(priamryKeyExprs, clause.And(exprs...))
}
}
stmt.AddClause(clause.Where{Exprs: []clause.Expression{clause.Or(priamryKeyExprs...)}})
case reflect.Struct:
for _, field := range stmt.Schema.PrimaryFields {
if value, isZero := field.ValueOf(stmt.ReflectValue); !isZero {
stmt.AddClause(clause.Where{Exprs: []clause.Expression{clause.Eq{Column: field.DBName, Value: value}}})
}
}
}
}
return return
} }

View File

@ -103,9 +103,11 @@ func (m Migrator) AutoMigrate(values ...interface{}) error {
for _, rel := range stmt.Schema.Relationships.Relations { for _, rel := range stmt.Schema.Relationships.Relations {
if constraint := rel.ParseConstraint(); constraint != nil { if constraint := rel.ParseConstraint(); constraint != nil {
if !tx.Migrator().HasConstraint(value, constraint.Name) { if constraint.Schema == stmt.Schema {
if err := tx.Migrator().CreateConstraint(value, constraint.Name); err != nil { if !tx.Migrator().HasConstraint(value, constraint.Name) {
return err if err := tx.Migrator().CreateConstraint(value, constraint.Name); err != nil {
return err
}
} }
} }
} }
@ -177,9 +179,11 @@ func (m Migrator) CreateTable(values ...interface{}) error {
for _, rel := range stmt.Schema.Relationships.Relations { for _, rel := range stmt.Schema.Relationships.Relations {
if constraint := rel.ParseConstraint(); constraint != nil { if constraint := rel.ParseConstraint(); constraint != nil {
sql, vars := buildConstraint(constraint) if constraint.Schema == stmt.Schema {
createTableSQL += sql + "," sql, vars := buildConstraint(constraint)
values = append(values, vars...) createTableSQL += sql + ","
values = append(values, vars...)
}
} }
// create join table // create join table
@ -360,7 +364,7 @@ func buildConstraint(constraint *schema.Constraint) (sql string, results []inter
} }
if constraint.OnUpdate != "" { if constraint.OnUpdate != "" {
sql += " ON UPDATE " + constraint.OnUpdate sql += " ON UPDATE " + constraint.OnUpdate
} }
var foreignKeys, references []interface{} var foreignKeys, references []interface{}
@ -550,7 +554,7 @@ func (m Migrator) ReorderModels(values []interface{}, autoAdd bool) (results []i
dep.Parse(value) dep.Parse(value)
for _, rel := range dep.Schema.Relationships.Relations { for _, rel := range dep.Schema.Relationships.Relations {
if c := rel.ParseConstraint(); c != nil && c.Schema != c.ReferenceSchema { if c := rel.ParseConstraint(); c != nil && c.Schema == dep.Statement.Schema && c.Schema != c.ReferenceSchema {
dep.Depends = append(dep.Depends, c.ReferenceSchema) dep.Depends = append(dep.Depends, c.ReferenceSchema)
} }
} }

View File

@ -85,6 +85,10 @@ func (schema *Schema) parseRelation(field *Field) {
} }
if relation.Type == "has" { if relation.Type == "has" {
if relation.FieldSchema != relation.Schema && relation.Polymorphic == nil {
relation.FieldSchema.Relationships.Relations["_"+relation.Schema.Name+"_"+relation.Name] = relation
}
switch field.IndirectFieldType.Kind() { switch field.IndirectFieldType.Kind() {
case reflect.Struct: case reflect.Struct:
relation.Type = HasOne relation.Type = HasOne
@ -384,18 +388,24 @@ func (rel *Relationship) ParseConstraint() *Constraint {
Field: rel.Field, Field: rel.Field,
OnUpdate: settings["ONUPDATE"], OnUpdate: settings["ONUPDATE"],
OnDelete: settings["ONDELETE"], OnDelete: settings["ONDELETE"],
Schema: rel.Schema,
} }
for _, ref := range rel.References { for _, ref := range rel.References {
if ref.PrimaryKey != nil && !ref.OwnPrimaryKey { if ref.PrimaryKey != nil {
constraint.ForeignKeys = append(constraint.ForeignKeys, ref.ForeignKey) constraint.ForeignKeys = append(constraint.ForeignKeys, ref.ForeignKey)
constraint.References = append(constraint.References, ref.PrimaryKey) constraint.References = append(constraint.References, ref.PrimaryKey)
constraint.ReferenceSchema = ref.PrimaryKey.Schema
if ref.OwnPrimaryKey {
constraint.Schema = ref.ForeignKey.Schema
constraint.ReferenceSchema = rel.Schema
} else {
constraint.Schema = rel.Schema
constraint.ReferenceSchema = ref.PrimaryKey.Schema
}
} }
} }
if rel.JoinTable != nil || constraint.ReferenceSchema == nil { if rel.JoinTable != nil {
return nil return nil
} }

View File

@ -31,3 +31,112 @@ func TestInvalidAssociation(t *testing.T) {
t.Fatalf("should return errors for invalid association, but got nil") t.Fatalf("should return errors for invalid association, but got nil")
} }
} }
func TestForeignKeyConstraints(t *testing.T) {
type Profile struct {
ID uint
Name string
MemberID uint
}
type Member struct {
ID uint
Refer uint `gorm:"unique_index"`
Name string
Profile Profile `gorm:"Constraint:OnUpdate:CASCADE,OnDelete:CASCADE;FOREIGNKEY:MemberID;References:Refer"`
}
DB.Migrator().DropTable(&Profile{}, &Member{})
if err := DB.AutoMigrate(&Profile{}, &Member{}); err != nil {
t.Fatalf("Failed to migrate, got error: %v", err)
}
member := Member{Refer: 1, Name: "foreign_key_constraints", Profile: Profile{Name: "my_profile"}}
DB.Create(&member)
var profile Profile
if err := DB.First(&profile, "id = ?", member.Profile.ID).Error; err != nil {
t.Fatalf("failed to find profile, got error: %v", err)
} else if profile.MemberID != member.ID {
t.Fatalf("member id is not equal: expects: %v, got: %v", member.ID, profile.MemberID)
}
member.Profile = Profile{}
DB.Model(&member).Update("Refer", 100)
var profile2 Profile
if err := DB.First(&profile2, "id = ?", profile.ID).Error; err != nil {
t.Fatalf("failed to find profile, got error: %v", err)
} else if profile2.MemberID != 100 {
t.Fatalf("member id is not equal: expects: %v, got: %v", 100, profile2.MemberID)
}
if r := DB.Delete(&member); r.Error != nil || r.RowsAffected != 1 {
t.Fatalf("Should delete member, got error: %v, affected: %v", r.Error, r.RowsAffected)
}
var result Member
if err := DB.First(&result, member.ID).Error; err == nil {
t.Fatalf("Should not find deleted member")
}
if err := DB.First(&profile2, profile.ID).Error; err == nil {
t.Fatalf("Should not find deleted profile")
}
}
func TestForeignKeyConstraintsBelongsTo(t *testing.T) {
type Profile struct {
ID uint
Name string
Refer uint `gorm:"unique_index"`
}
type Member struct {
ID uint
Name string
ProfileID uint
Profile Profile `gorm:"Constraint:OnUpdate:CASCADE,OnDelete:CASCADE;FOREIGNKEY:ProfileID;References:Refer"`
}
DB.Migrator().DropTable(&Profile{}, &Member{})
if err := DB.AutoMigrate(&Profile{}, &Member{}); err != nil {
t.Fatalf("Failed to migrate, got error: %v", err)
}
member := Member{Name: "foreign_key_constraints_belongs_to", Profile: Profile{Name: "my_profile_belongs_to", Refer: 1}}
DB.Create(&member)
var profile Profile
if err := DB.First(&profile, "id = ?", member.Profile.ID).Error; err != nil {
t.Fatalf("failed to find profile, got error: %v", err)
} else if profile.Refer != member.ProfileID {
t.Fatalf("member id is not equal: expects: %v, got: %v", profile.Refer, member.ProfileID)
}
DB.Model(&profile).Update("Refer", 100)
var member2 Member
if err := DB.First(&member2, "id = ?", member.ID).Error; err != nil {
t.Fatalf("failed to find member, got error: %v", err)
} else if member2.ProfileID != 100 {
t.Fatalf("member id is not equal: expects: %v, got: %v", 100, member2.ProfileID)
}
if r := DB.Delete(&profile); r.Error != nil || r.RowsAffected != 1 {
t.Fatalf("Should delete member, got error: %v, affected: %v", r.Error, r.RowsAffected)
}
var result Member
if err := DB.First(&result, member.ID).Error; err == nil {
t.Fatalf("Should not find deleted member")
}
if err := DB.First(&profile, profile.ID).Error; err == nil {
t.Fatalf("Should not find deleted profile")
}
}

View File

@ -433,8 +433,8 @@ func TestNestedPreload9(t *testing.T) {
Level1 struct { Level1 struct {
ID uint ID uint
Value string Value string
Level2ID uint Level2ID *uint
Level2_1ID uint Level2_1ID *uint
Level0s []Level0 `json:",omitempty"` Level0s []Level0 `json:",omitempty"`
} }
Level2 struct { Level2 struct {

View File

@ -66,6 +66,7 @@ func OpenTestConnection() (db *gorm.DB, err error) {
default: default:
log.Println("testing sqlite3...") log.Println("testing sqlite3...")
db, err = gorm.Open(sqlite.Open(filepath.Join(os.TempDir(), "gorm.db")), &gorm.Config{}) db, err = gorm.Open(sqlite.Open(filepath.Join(os.TempDir(), "gorm.db")), &gorm.Config{})
db.Exec("PRAGMA foreign_keys = ON")
} }
if debug := os.Getenv("DEBUG"); debug == "true" { if debug := os.Getenv("DEBUG"); debug == "true" {

View File

@ -37,7 +37,7 @@ type Account struct {
type Pet struct { type Pet struct {
gorm.Model gorm.Model
UserID uint UserID *uint
Name string Name string
Toy Toy `gorm:"polymorphic:Owner;"` Toy Toy `gorm:"polymorphic:Owner;"`
} }