mirror of https://github.com/go-gorm/gorm.git
Test ForeignKeyConstraints
This commit is contained in:
parent
d4d339f3b5
commit
4f19e2a7b3
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -103,12 +103,14 @@ 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 constraint.Schema == stmt.Schema {
|
||||||
if !tx.Migrator().HasConstraint(value, constraint.Name) {
|
if !tx.Migrator().HasConstraint(value, constraint.Name) {
|
||||||
if err := tx.Migrator().CreateConstraint(value, constraint.Name); err != nil {
|
if err := tx.Migrator().CreateConstraint(value, constraint.Name); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for _, chk := range stmt.Schema.ParseCheckConstraints() {
|
for _, chk := range stmt.Schema.ParseCheckConstraints() {
|
||||||
if !tx.Migrator().HasConstraint(value, chk.Name) {
|
if !tx.Migrator().HasConstraint(value, chk.Name) {
|
||||||
|
@ -177,10 +179,12 @@ 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 {
|
||||||
|
if constraint.Schema == stmt.Schema {
|
||||||
sql, vars := buildConstraint(constraint)
|
sql, vars := buildConstraint(constraint)
|
||||||
createTableSQL += sql + ","
|
createTableSQL += sql + ","
|
||||||
values = append(values, vars...)
|
values = append(values, vars...)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// create join table
|
// create join table
|
||||||
if rel.JoinTable != nil {
|
if rel.JoinTable != nil {
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
|
||||||
|
if ref.OwnPrimaryKey {
|
||||||
|
constraint.Schema = ref.ForeignKey.Schema
|
||||||
|
constraint.ReferenceSchema = rel.Schema
|
||||||
|
} else {
|
||||||
|
constraint.Schema = rel.Schema
|
||||||
constraint.ReferenceSchema = ref.PrimaryKey.Schema
|
constraint.ReferenceSchema = ref.PrimaryKey.Schema
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if rel.JoinTable != nil || constraint.ReferenceSchema == nil {
|
if rel.JoinTable != nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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" {
|
||||||
|
|
|
@ -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;"`
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue