diff --git a/schema/relationship_test.go b/schema/relationship_test.go index 5b37a784..b6d2f9b1 100644 --- a/schema/relationship_test.go +++ b/schema/relationship_test.go @@ -2,11 +2,6 @@ package schema import "testing" -type BelongsTo struct { - ID int - Name string -} - type HasOne struct { ID int MyStructID uint @@ -24,13 +19,94 @@ type Many2Many struct { } func TestBelongsToRel(t *testing.T) { - type MyStruct struct { - ID int - Name string - BelongsTo BelongsTo + type BelongsTo struct { + ID int + Name string } - Parse(&MyStruct{}) + type MyStruct struct { + ID int + Name string + BelongsToID uint + BelongsTo BelongsTo + } + + schema := Parse(&MyStruct{}) + compareFields(schema.Fields, []*Field{ + {DBName: "id", Name: "ID", BindNames: []string{"ID"}, IsNormal: true, IsPrimaryKey: true}, + {DBName: "name", Name: "Name", BindNames: []string{"Name"}, IsNormal: true}, + {DBName: "belongs_to_id", Name: "BelongsToID", BindNames: []string{"BelongsToID"}, IsNormal: true, IsForeignKey: true}, + {DBName: "belongs_to", Name: "BelongsTo", BindNames: []string{"BelongsTo"}, Relationship: &Relationship{Kind: "belongs_to", ForeignKey: []string{"belongs_to_id"}, AssociationForeignKey: []string{"id"}}}, + }, t) + + type MyStruct2 struct { + ID int `gorm:"column:my_id"` + Name string + BelongsToKey uint + BelongsTo BelongsTo `gorm:"foreignkey:BelongsToKey"` + } + + schema2 := Parse(&MyStruct2{}) + compareFields(schema2.Fields, []*Field{ + {DBName: "my_id", Name: "ID", BindNames: []string{"ID"}, IsNormal: true, IsPrimaryKey: true, TagSettings: map[string]string{"COLUMN": "my_id"}}, + {DBName: "name", Name: "Name", BindNames: []string{"Name"}, IsNormal: true}, + {DBName: "belongs_to_key", Name: "BelongsToKey", BindNames: []string{"BelongsToKey"}, IsNormal: true, IsForeignKey: true}, + {DBName: "belongs_to", Name: "BelongsTo", BindNames: []string{"BelongsTo"}, Relationship: &Relationship{Kind: "belongs_to", ForeignKey: []string{"belongs_to_key"}, AssociationForeignKey: []string{"id"}}, TagSettings: map[string]string{"FOREIGNKEY": "BelongsToKey"}}, + }, t) + + type BelongsTo3 struct { + ID int `gorm:"column:my_id"` + Name string + } + + type MyStruct3 struct { + ID int + Name string + BelongsToKey uint + BelongsTo BelongsTo3 `gorm:"foreignkey:BelongsToKey"` + } + + schema3 := Parse(&MyStruct3{}) + compareFields(schema3.Fields, []*Field{ + {DBName: "id", Name: "ID", BindNames: []string{"ID"}, IsNormal: true, IsPrimaryKey: true}, + {DBName: "name", Name: "Name", BindNames: []string{"Name"}, IsNormal: true}, + {DBName: "belongs_to_key", Name: "BelongsToKey", BindNames: []string{"BelongsToKey"}, IsNormal: true, IsForeignKey: true}, + {DBName: "belongs_to", Name: "BelongsTo", BindNames: []string{"BelongsTo"}, Relationship: &Relationship{Kind: "belongs_to", ForeignKey: []string{"belongs_to_key"}, AssociationForeignKey: []string{"my_id"}}, TagSettings: map[string]string{"FOREIGNKEY": "BelongsToKey"}}, + }, t) +} + +func TestSelfReferenceBelongsToRel(t *testing.T) { + type MyStruct struct { + ID int + Name string + BelongsToID int + BelongsTo *MyStruct + } + + // user1 belongs to user2, when creating, will create user2 first + schema := Parse(&MyStruct{}) + compareFields(schema.Fields, []*Field{ + {DBName: "id", Name: "ID", BindNames: []string{"ID"}, IsNormal: true, IsPrimaryKey: true}, + {DBName: "name", Name: "Name", BindNames: []string{"Name"}, IsNormal: true}, + {DBName: "belongs_to_id", Name: "BelongsToID", BindNames: []string{"BelongsToID"}, IsNormal: true, IsForeignKey: true}, + {DBName: "belongs_to", Name: "BelongsTo", BindNames: []string{"BelongsTo"}, Relationship: &Relationship{Kind: "belongs_to", ForeignKey: []string{"belongs_to_id"}, AssociationForeignKey: []string{"id"}}}, + }, t) + + type MyStruct2 struct { + ID int + Name string + BelongsToKey int + BelongsTo *MyStruct2 `gorm:"rel:belongs_to;foreignkey:BelongsToKey"` + } + + // user1 belongs to user2, when creating, will create user2 first + schema2 := Parse(&MyStruct2{}) + compareFields(schema2.Fields, []*Field{ + {DBName: "id", Name: "ID", BindNames: []string{"ID"}, IsNormal: true, IsPrimaryKey: true}, + {DBName: "name", Name: "Name", BindNames: []string{"Name"}, IsNormal: true}, + {DBName: "belongs_to_key", Name: "BelongsToKey", BindNames: []string{"BelongsToKey"}, IsNormal: true, IsForeignKey: true}, + {DBName: "belongs_to", Name: "BelongsTo", BindNames: []string{"BelongsTo"}, Relationship: &Relationship{Kind: "belongs_to", ForeignKey: []string{"belongs_to_key"}, AssociationForeignKey: []string{"id"}}, TagSettings: map[string]string{"FOREIGNKEY": "BelongsToKey"}}, + }, t) } func TestHasOneRel(t *testing.T) { @@ -43,6 +119,46 @@ func TestHasOneRel(t *testing.T) { Parse(&MyStruct{}) } +func TestSelfReferenceHasOneRel(t *testing.T) { + type MyStruct struct { + ID int + Name string + BelongsToID int + BelongsTo *MyStruct + } + + // user1 belongs to user2, when creating, will create user2 first + schema := Parse(&MyStruct{}) + compareFields(schema.Fields, []*Field{ + {DBName: "id", Name: "ID", BindNames: []string{"ID"}, IsNormal: true, IsPrimaryKey: true}, + {DBName: "name", Name: "Name", BindNames: []string{"Name"}, IsNormal: true}, + {DBName: "belongs_to_id", Name: "BelongsToID", BindNames: []string{"BelongsToID"}, IsNormal: true, IsForeignKey: true}, + {DBName: "belongs_to", Name: "BelongsTo", BindNames: []string{"BelongsTo"}, Relationship: &Relationship{Kind: "belongs_to", ForeignKey: []string{"belongs_to_id"}, AssociationForeignKey: []string{"id"}}}, + }, t) +} + +func TestPolymorphicHasOneRel(t *testing.T) { + type HasOne struct { + ID int + Name string + OwnerType string + OwnerID string + } + + type MyStruct struct { + ID int + Name string + HasOne HasOne `gorm:"polymorphic:Owner"` + } + + schema := Parse(&MyStruct{}) + compareFields(schema.Fields, []*Field{ + {DBName: "id", Name: "ID", BindNames: []string{"ID"}, IsNormal: true, IsPrimaryKey: true}, + {DBName: "name", Name: "Name", BindNames: []string{"Name"}, IsNormal: true}, + {DBName: "has_one", Name: "HasOne", BindNames: []string{"HasOne"}, Relationship: &Relationship{Kind: "has_one", PolymorphicType: "OwnerType", PolymorphicDBName: "owner_type", PolymorphicValue: "my_struct", ForeignKey: []string{"owner_id"}, AssociationForeignKey: []string{"id"}}, TagSettings: map[string]string{"POLYMORPHIC": "Owner"}}, + }, t) +} + func TestHasManyRel(t *testing.T) { type MyStruct struct { ID int @@ -62,4 +178,3 @@ func TestManyToManyRel(t *testing.T) { Parse(&MyStruct{}) } - diff --git a/schema/schema.go b/schema/schema.go index 1420d997..c01b67cc 100644 --- a/schema/schema.go +++ b/schema/schema.go @@ -5,9 +5,13 @@ import ( "go/ast" "reflect" "sort" + "strings" + "sync" "time" ) +var schemaMap = sync.Map{} + // Schema model schema definition type Schema struct { ModelType reflect.Type @@ -48,6 +52,10 @@ func Parse(dest interface{}) *Schema { return nil } + if m, ok := schemaMap.Load(reflectType); ok { + return m.(*Schema) + } + schema.ModelType = reflectType onConflictFields := map[string]int{} @@ -221,12 +229,16 @@ func Parse(dest interface{}) *Schema { } if len(schema.PrimaryFields) == 0 { - if field := getSchemaField("id", schema.Fields); field != nil { - field.IsPrimaryKey = true - schema.PrimaryFields = append(schema.PrimaryFields, field) + for _, field := range schema.Fields { + if strings.ToUpper(field.Name) == "ID" || field.DBName == "id" { + field.IsPrimaryKey = true + schema.PrimaryFields = append(schema.PrimaryFields, field) + break + } } } + schemaMap.Store(reflectType, &schema) return &schema } diff --git a/schema/schema_test.go b/schema/schema_test.go index a2f1d24c..405a0070 100644 --- a/schema/schema_test.go +++ b/schema/schema_test.go @@ -293,5 +293,9 @@ func fieldEqual(got, expected *Field) error { if expected.HasDefaultValue != got.HasDefaultValue { return fmt.Errorf("field HasDefaultValue should be %v, got %v", expected.HasDefaultValue, got.HasDefaultValue) } + + if !reflect.DeepEqual(expected.Relationship, got.Relationship) { + return fmt.Errorf("field Relationship should be %#v, got %#v", expected.Relationship, got.Relationship) + } return nil }