diff --git a/helpers.go b/helpers.go index 8f9df009..77bbece8 100644 --- a/helpers.go +++ b/helpers.go @@ -22,7 +22,7 @@ var ( // gorm.Model // } type Model struct { - ID uint `gorm:"primary_key"` + ID uint `gorm:"primarykey"` CreatedAt time.Time UpdatedAt time.Time DeletedAt *time.Time `gorm:"index"` diff --git a/schema/field.go b/schema/field.go index d2747100..47250aa8 100644 --- a/schema/field.go +++ b/schema/field.go @@ -54,7 +54,7 @@ func (schema *Schema) ParseField(fieldStruct reflect.StructField) *Field { Creatable: true, Updatable: true, Tag: fieldStruct.Tag, - TagSettings: parseTagSetting(fieldStruct.Tag), + TagSettings: ParseTagSetting(fieldStruct.Tag), } for field.FieldType.Kind() == reflect.Ptr { @@ -84,7 +84,7 @@ func (schema *Schema) ParseField(fieldStruct reflect.StructField) *Field { } // copy tag settings from valuer - for key, value := range parseTagSetting(field.FieldType.Field(i).Tag) { + for key, value := range ParseTagSetting(field.FieldType.Field(i).Tag) { if _, ok := field.TagSettings[key]; !ok { field.TagSettings[key] = value } @@ -141,7 +141,7 @@ func (schema *Schema) ParseField(fieldStruct reflect.StructField) *Field { field.DBDataType = val } - switch fieldValue.Kind() { + switch fieldValue.Elem().Kind() { case reflect.Bool: field.DataType = Bool case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: @@ -153,7 +153,7 @@ func (schema *Schema) ParseField(fieldStruct reflect.StructField) *Field { case reflect.String: field.DataType = String case reflect.Struct: - if _, ok := fieldValue.Interface().(time.Time); ok { + if _, ok := fieldValue.Interface().(*time.Time); ok { field.DataType = Time } case reflect.Array, reflect.Slice: @@ -176,7 +176,7 @@ func (schema *Schema) ParseField(fieldStruct reflect.StructField) *Field { } if _, ok := field.TagSettings["EMBEDDED"]; ok || fieldStruct.Anonymous { - field.EmbeddedSchema, schema.err = Parse(fieldValue, sync.Map{}, schema.namer) + field.EmbeddedSchema, schema.err = Parse(fieldValue.Interface(), &sync.Map{}, schema.namer) for _, ef := range field.EmbeddedSchema.Fields { ef.BindNames = append([]string{fieldStruct.Name}, ef.BindNames...) diff --git a/schema/schema.go b/schema/schema.go index f18cb7a6..0b5548e3 100644 --- a/schema/schema.go +++ b/schema/schema.go @@ -6,6 +6,8 @@ import ( "reflect" "strings" "sync" + + "github.com/jinzhu/gorm/logger" ) type Schema struct { @@ -20,7 +22,7 @@ type Schema struct { Relationships Relationships err error namer Namer - cacheStore sync.Map + cacheStore *sync.Map } func (schema Schema) String() string { @@ -38,7 +40,7 @@ func (schema Schema) LookUpField(name string) *Field { } // get data type from dialector -func Parse(dest interface{}, cacheStore sync.Map, namer Namer) (*Schema, error) { +func Parse(dest interface{}, cacheStore *sync.Map, namer Namer) (*Schema, error) { modelType := reflect.ValueOf(dest).Type() for modelType.Kind() == reflect.Slice || modelType.Kind() == reflect.Ptr { modelType = modelType.Elem() @@ -62,10 +64,12 @@ func Parse(dest interface{}, cacheStore sync.Map, namer Namer) (*Schema, error) FieldsByName: map[string]*Field{}, FieldsByDBName: map[string]*Field{}, cacheStore: cacheStore, + namer: namer, } defer func() { if schema.err != nil { + logger.Default.Error(schema.err.Error()) cacheStore.Delete(modelType) } }() diff --git a/schema/schema_test.go b/schema/schema_test.go new file mode 100644 index 00000000..eefac98b --- /dev/null +++ b/schema/schema_test.go @@ -0,0 +1,78 @@ +package schema_test + +import ( + "reflect" + "sync" + "testing" + + "github.com/jinzhu/gorm/schema" + "github.com/jinzhu/gorm/tests" +) + +func TestParseSchema(t *testing.T) { + cacheMap := sync.Map{} + user, err := schema.Parse(&tests.User{}, &cacheMap, schema.NamingStrategy{}) + + if err != nil { + t.Fatalf("failed to parse user, got error %v", err) + } + + checkSchemaFields(t, user) +} + +func checkSchemaFields(t *testing.T, s *schema.Schema) { + fields := []schema.Field{ + schema.Field{ + Name: "ID", DBName: "id", BindNames: []string{"Model", "ID"}, DataType: schema.Uint, + PrimaryKey: true, Tag: `gorm:"primarykey"`, TagSettings: map[string]string{"PRIMARYKEY": "PRIMARYKEY"}, + }, + schema.Field{Name: "CreatedAt", DBName: "created_at", BindNames: []string{"Model", "CreatedAt"}, DataType: schema.Time}, + schema.Field{Name: "UpdatedAt", DBName: "updated_at", BindNames: []string{"Model", "UpdatedAt"}, DataType: schema.Time}, + schema.Field{Name: "DeletedAt", DBName: "deleted_at", BindNames: []string{"Model", "DeletedAt"}, Tag: `gorm:"index"`, DataType: schema.Time}, + schema.Field{Name: "Name", DBName: "name", BindNames: []string{"Name"}, DataType: schema.String}, + schema.Field{Name: "Age", DBName: "age", BindNames: []string{"Age"}, DataType: schema.Uint}, + schema.Field{Name: "Birthday", DBName: "birthday", BindNames: []string{"Birthday"}, DataType: schema.Time}, + schema.Field{Name: "CompanyID", DBName: "company_id", BindNames: []string{"CompanyID"}, DataType: schema.Int}, + schema.Field{Name: "ManagerID", DBName: "manager_id", BindNames: []string{"ManagerID"}, DataType: schema.Uint}, + } + + for _, f := range fields { + f.Creatable = true + f.Updatable = true + if f.TagSettings == nil { + if f.Tag != "" { + f.TagSettings = schema.ParseTagSetting(f.Tag) + } else { + f.TagSettings = map[string]string{} + } + } + + if foundField, ok := s.FieldsByName[f.Name]; !ok { + t.Errorf("schema %v failed to look up field with name %v", s, f.Name) + } else { + checkSchemaField(t, foundField, f) + + if field, ok := s.FieldsByDBName[f.DBName]; !ok || foundField != field { + t.Errorf("schema %v failed to look up field with dbname %v", s, f.DBName) + } + + for _, name := range []string{f.DBName, f.Name} { + if field := s.LookUpField(name); field == nil || foundField != field { + t.Errorf("schema %v failed to look up field with dbname %v", s, f.DBName) + } + } + } + } +} + +func checkSchemaField(t *testing.T, parsedField *schema.Field, field schema.Field) { + equalFieldNames := []string{"Name", "DBName", "BindNames", "DataType", "DBDataType", "PrimaryKey", "AutoIncrement", "Creatable", "Updatable", "HasDefaultValue", "DefaultValue", "NotNull", "Unique", "Comment", "Size", "Precision", "Tag", "TagSettings"} + + for _, name := range equalFieldNames { + got := reflect.ValueOf(parsedField).Elem().FieldByName(name).Interface() + expects := reflect.ValueOf(field).FieldByName(name).Interface() + if !reflect.DeepEqual(got, expects) { + t.Errorf("%v is not equal, expects: %v, got %v", name, expects, got) + } + } +} diff --git a/schema/utils.go b/schema/utils.go index f2dd90af..4774fd75 100644 --- a/schema/utils.go +++ b/schema/utils.go @@ -6,7 +6,7 @@ import ( "strings" ) -func parseTagSetting(tags reflect.StructTag) map[string]string { +func ParseTagSetting(tags reflect.StructTag) map[string]string { setting := map[string]string{} for _, value := range strings.Split(tags.Get("gorm"), ";") { diff --git a/tests/callbacks_test.go b/tests/callbacks_test.go index 878384a7..af975a55 100644 --- a/tests/callbacks_test.go +++ b/tests/callbacks_test.go @@ -1,4 +1,4 @@ -package gorm_test +package tests_test import ( "fmt" diff --git a/tests/model.go b/tests/model.go new file mode 100644 index 00000000..0be3e97a --- /dev/null +++ b/tests/model.go @@ -0,0 +1,58 @@ +package tests + +import ( + "database/sql" + "time" + + "github.com/jinzhu/gorm" +) + +// User has one `Account` (has one), many `Pets` (has many) and `Toys` (has many - polymorphic) +// He works in a Company (belongs to), he has a Manager (belongs to - single-table), and also managed a Team (has many - single-table) +// He speaks many languages (many to many) and has many friends (many to many - single-table) +// His pet also has one Toy (has one - polymorphic) +type User struct { + gorm.Model + Name string + Age uint + Birthday *time.Time + Account Account + Pets []*Pet + Toys []Toy `gorm:"polymorphic:Owner"` + CompanyID *int + Company Company + ManagerID uint + Manager *User + Team []User `foreignkey:ManagerID` + Friends []*User `gorm:"many2many:user_friends"` + Languages []Language `gorm:"many2many:user_speaks"` +} + +type Account struct { + gorm.Model + UserID sql.NullInt64 + Number string +} + +type Pet struct { + gorm.Model + UserID uint + Name string + Toy Toy `gorm:"polymorphic:Owner;"` +} + +type Toy struct { + gorm.Model + OwnerID string + OwnerType string +} + +type Company struct { + ID uint + Name string +} + +type Language struct { + Code string `gorm:primarykey` + Name string +}