From 6c805b6a0e6557b04ef505037c689574a640e0cf Mon Sep 17 00:00:00 2001 From: Dmitry Yu Okunev Date: Mon, 18 May 2015 11:06:05 +0300 Subject: [PATCH 1/5] Added support of embedded interface fields --- README.md | 38 ++++++++++++++++++++++++++++++++++ field.go | 3 +++ main.go | 3 ++- model_struct.go | 54 ++++++++++++++++++++++++++++++++++++++++--------- 4 files changed, 88 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 21707a8c..1bc164ce 100644 --- a/README.md +++ b/README.md @@ -1172,6 +1172,44 @@ db.Where("email = ?", "x@example.org").Attrs(User{RegisteredIp: "111.111.111.111 //// INSERT INTO "users" (email,registered_ip) VALUES ("x@example.org", "111.111.111.111") // if record not found ``` +## Tables with embedded iterface fields + +```go +type Delta struct { + Id int; + Was interface{} `gorm:"embedded"`; + Became interface{} `gorm:"embedded"`; +} + +type Login struct { + Login string; + Comment string; +} + +login_old := Login{Login: "Login1"}; +login_new := Login{Login: "Login2", Comment: "2015-05-18"}; + +delta := Delta { + Was: &login_old, + Became: &login_new, +} + +db.SingularTable(true); + +db.CreateTable(&delta); +//// CREATE TABLE "delta__Login__Login" ("id" integer,"was__login" varchar(255),"was__comment" varchar(255),"became__login" varchar(255),"became__comment" varchar(255) , PRIMARY KEY (id)) + +db.Save(&delta); +//// INSERT INTO "delta__Login__Login" ("was__login","was__comment","became__login","became__comment") VALUES ('Login1','','Login2','2015-05-18') + +found := Delta { + Was: &Login{}, + Became: &Login{}, +} +db.Where(&Delta{Was: &Login{Login: "Login1"}, Became: &Login{}}).First(&found); +//// SELECT * FROM "delta__Login__Login" WHERE ("was__login" = 'Login1') ORDER BY "delta__Login__Login"."id" ASC LIMIT 1 +``` + ## TODO * db.Select("Languages", "Name").Update(&user) db.Omit("Languages").Update(&user) diff --git a/field.go b/field.go index 8f5efa6d..1e4353ff 100644 --- a/field.go +++ b/field.go @@ -76,6 +76,9 @@ func (scope *Scope) Fields() map[string]*Field { func getField(indirectValue reflect.Value, structField *StructField) *Field { field := &Field{StructField: structField} for _, name := range structField.Names { + if (reflect.Indirect(indirectValue).Kind() == reflect.Interface) { + indirectValue = indirectValue.Elem() + } indirectValue = reflect.Indirect(indirectValue).FieldByName(name) } field.Field = indirectValue diff --git a/main.go b/main.go index 04f59bcf..007231d7 100644 --- a/main.go +++ b/main.go @@ -126,7 +126,8 @@ func (s *DB) LogMode(enable bool) *DB { } func (s *DB) SingularTable(enable bool) { - modelStructs = map[reflect.Type]*ModelStruct{} + modelStructs_byScopeType = map[reflect.Type]*ModelStruct{} + modelStructs_byTableName = map[string ]*ModelStruct{} s.parent.singularTable = enable } diff --git a/model_struct.go b/model_struct.go index a70489fc..18610038 100644 --- a/model_struct.go +++ b/model_struct.go @@ -11,7 +11,8 @@ import ( "time" ) -var modelStructs = map[reflect.Type]*ModelStruct{} +var modelStructs_byScopeType = map[reflect.Type]*ModelStruct{} +var modelStructs_byTableName = map[string ]*ModelStruct{} type ModelStruct struct { PrimaryFields []*StructField @@ -33,6 +34,7 @@ type StructField struct { Struct reflect.StructField IsForeignKey bool Relationship *Relationship + Value reflect.Value } func (structField *StructField) clone() *StructField { @@ -49,6 +51,7 @@ func (structField *StructField) clone() *StructField { Struct: structField.Struct, IsForeignKey: structField.IsForeignKey, Relationship: structField.Relationship, + Value: structField.Value, } } @@ -67,6 +70,7 @@ var pluralMapKeys = []*regexp.Regexp{regexp.MustCompile("ch$"), regexp.MustCompi var pluralMapValues = []string{"ches", "sses", "shes", "days", "ies", "xes", "${1}s"} func (scope *Scope) GetModelStruct() *ModelStruct { + var tableName string var modelStruct ModelStruct reflectValue := reflect.Indirect(reflect.ValueOf(scope.Value)) @@ -84,7 +88,7 @@ func (scope *Scope) GetModelStruct() *ModelStruct { scopeType = scopeType.Elem() } - if value, ok := modelStructs[scopeType]; ok { + if value, ok := modelStructs_byScopeType[scopeType]; ok { return value } @@ -94,11 +98,20 @@ func (scope *Scope) GetModelStruct() *ModelStruct { } // Set tablename + for i := 0; i < scopeType.NumField(); i++ { + if fieldStruct := scopeType.Field(i); ast.IsExported(fieldStruct.Name) { + if (fieldStruct.Type.Kind() == reflect.Interface) { + value := reflect.ValueOf(reflect.ValueOf(scope.Value).Elem().Field(i).Interface()) + tableName = tableName + "__" + value.Elem().Type().Name() + } + } + } if fm := reflect.New(scopeType).MethodByName("TableName"); fm.IsValid() { if results := fm.Call([]reflect.Value{}); len(results) > 0 { if name, ok := results[0].Interface().(string); ok { + tableName = name + tableName modelStruct.TableName = func(*DB) string { - return name + return tableName } } } @@ -112,23 +125,35 @@ func (scope *Scope) GetModelStruct() *ModelStruct { } } + tableName = name + tableName modelStruct.TableName = func(*DB) string { - return name + return tableName } } + if value, ok := modelStructs_byTableName[tableName]; ok { + return value + } + // Get all fields + cachable_byScopeType := true fields := []*StructField{} for i := 0; i < scopeType.NumField(); i++ { if fieldStruct := scopeType.Field(i); ast.IsExported(fieldStruct.Name) { + var value reflect.Value + if (fieldStruct.Type.Kind() == reflect.Interface) { + value = reflect.ValueOf(reflect.ValueOf(scope.Value).Elem().Field(i).Interface()) + cachable_byScopeType = false + } field := &StructField{ Struct: fieldStruct, + Value: value, Name: fieldStruct.Name, Names: []string{fieldStruct.Name}, Tag: fieldStruct.Tag, } - if fieldStruct.Tag.Get("sql") == "-" { + if (fieldStruct.Tag.Get("sql") == "-") { field.IsIgnored = true } else { sqlSettings := parseTagSetting(field.Tag.Get("sql")) @@ -170,8 +195,15 @@ func (scope *Scope) GetModelStruct() *ModelStruct { } if !field.IsNormal { + var iface interface{} gormSettings := parseTagSetting(field.Tag.Get("gorm")) - toScope := scope.New(reflect.New(fieldStruct.Type).Interface()) + if (fieldStruct.Type.Kind() == reflect.Interface) { + indirectType = (*field).Value.Elem().Type() + iface = (*field).Value.Elem().Interface() + } else { + iface = reflect.New(fieldStruct.Type).Interface() + } + toScope := scope.New(iface) getForeignField := func(column string, fields []*StructField) *StructField { for _, field := range fields { @@ -244,7 +276,8 @@ func (scope *Scope) GetModelStruct() *ModelStruct { if _, ok := gormSettings["EMBEDDED"]; ok || fieldStruct.Anonymous { for _, toField := range toScope.GetStructFields() { toField = toField.clone() - toField.Names = append([]string{fieldStruct.Name}, toField.Names...) + toField.DBName = field.DBName+"__"+toField.DBName + toField.Names = append([]string{fieldStruct.Name}, toField.Names...) modelStruct.StructFields = append(modelStruct.StructFields, toField) if toField.IsPrimaryKey { modelStruct.PrimaryFields = append(modelStruct.PrimaryFields, toField) @@ -294,8 +327,11 @@ func (scope *Scope) GetModelStruct() *ModelStruct { } }() - modelStructs[scopeType] = &modelStruct - + if (cachable_byScopeType) { + modelStructs_byScopeType[scopeType] = &modelStruct + } else { + modelStructs_byTableName[tableName] = &modelStruct + } return &modelStruct } From 9d2c0adaa10d3ddf418c76ee0a317e46cd559bfb Mon Sep 17 00:00:00 2001 From: Dmitry Yu Okunev Date: Tue, 19 May 2015 10:08:15 +0300 Subject: [PATCH 2/5] Added support of Find() to slices of structs with embedded interface fields --- README.md | 9 +++++++-- field.go | 8 ++++++-- model_struct.go | 21 +++++++++++++++++++-- 3 files changed, 32 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 1bc164ce..177456aa 100644 --- a/README.md +++ b/README.md @@ -1172,7 +1172,7 @@ db.Where("email = ?", "x@example.org").Attrs(User{RegisteredIp: "111.111.111.111 //// INSERT INTO "users" (email,registered_ip) VALUES ("x@example.org", "111.111.111.111") // if record not found ``` -## Tables with embedded iterface fields +## Embedded interface fields ```go type Delta struct { @@ -1207,7 +1207,12 @@ found := Delta { Became: &Login{}, } db.Where(&Delta{Was: &Login{Login: "Login1"}, Became: &Login{}}).First(&found); -//// SELECT * FROM "delta__Login__Login" WHERE ("was__login" = 'Login1') ORDER BY "delta__Login__Login"."id" ASC LIMIT 1 +//// SELECT * FROM "delta__Login__Login" WHERE ("was__login" = 'Login1') ORDER BY "delta__Login__Login"."id" ASC LIMIT 1 + +deltas := []Delta{{Was: &Login{}, Became: &Login{}}}; +db.Find(&deltas) +//// SELECT * FROM "deltas__Login__Login" +deltas = append(deltas[:0], deltas[1:]...) ``` ## TODO diff --git a/field.go b/field.go index 1e4353ff..0f8d406a 100644 --- a/field.go +++ b/field.go @@ -76,8 +76,12 @@ func (scope *Scope) Fields() map[string]*Field { func getField(indirectValue reflect.Value, structField *StructField) *Field { field := &Field{StructField: structField} for _, name := range structField.Names { - if (reflect.Indirect(indirectValue).Kind() == reflect.Interface) { - indirectValue = indirectValue.Elem() + for ;reflect.Indirect(indirectValue).Kind() == reflect.Interface; { + if (indirectValue.Elem().IsValid()) { + indirectValue = indirectValue.Elem() + } else { + indirectValue.Set(reflect.New(structField.Value.Type())) + } } indirectValue = reflect.Indirect(indirectValue).FieldByName(name) } diff --git a/model_struct.go b/model_struct.go index 18610038..0bbeff76 100644 --- a/model_struct.go +++ b/model_struct.go @@ -13,6 +13,7 @@ import ( var modelStructs_byScopeType = map[reflect.Type]*ModelStruct{} var modelStructs_byTableName = map[string ]*ModelStruct{} +var modelStruct_last *ModelStruct type ModelStruct struct { PrimaryFields []*StructField @@ -94,18 +95,30 @@ func (scope *Scope) GetModelStruct() *ModelStruct { modelStruct.ModelType = scopeType if scopeType.Kind() != reflect.Struct { + modelStruct_last = &modelStruct return &modelStruct } - // Set tablename + // Getting table name appendix for i := 0; i < scopeType.NumField(); i++ { if fieldStruct := scopeType.Field(i); ast.IsExported(fieldStruct.Name) { if (fieldStruct.Type.Kind() == reflect.Interface) { - value := reflect.ValueOf(reflect.ValueOf(scope.Value).Elem().Field(i).Interface()) + // Interface field + value := reflect.ValueOf(scope.Value).Elem() + if (value.Kind() == reflect.Slice) { + // A slice, using the first element + value = value.Index(0) + } + value = reflect.ValueOf(value.Field(i).Interface()) + if (! value.IsValid()) { + // Invalid interfaces, using Model()'s result + return modelStruct_last + } tableName = tableName + "__" + value.Elem().Type().Name() } } } + // Set tablename if fm := reflect.New(scopeType).MethodByName("TableName"); fm.IsValid() { if results := fm.Call([]reflect.Value{}); len(results) > 0 { if name, ok := results[0].Interface().(string); ok { @@ -132,6 +145,7 @@ func (scope *Scope) GetModelStruct() *ModelStruct { } if value, ok := modelStructs_byTableName[tableName]; ok { + modelStruct_last = value return value } @@ -144,6 +158,8 @@ func (scope *Scope) GetModelStruct() *ModelStruct { if (fieldStruct.Type.Kind() == reflect.Interface) { value = reflect.ValueOf(reflect.ValueOf(scope.Value).Elem().Field(i).Interface()) cachable_byScopeType = false + } else { + value = reflect.Indirect(reflect.ValueOf(scope.Value)) } field := &StructField{ Struct: fieldStruct, @@ -325,6 +341,7 @@ func (scope *Scope) GetModelStruct() *ModelStruct { } modelStruct.StructFields = append(modelStruct.StructFields, field) } + modelStruct_last = &modelStruct }() if (cachable_byScopeType) { From fcbd32e58164abe62de6730cebb9294890b7f64f Mon Sep 17 00:00:00 2001 From: Dmitry Yu Okunev Date: Tue, 19 May 2015 11:44:38 +0300 Subject: [PATCH 3/5] Fixed error: panic: reflect: call of reflect.Value.Field on slice Value --- model_struct.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/model_struct.go b/model_struct.go index 0bbeff76..4ba10ac0 100644 --- a/model_struct.go +++ b/model_struct.go @@ -156,7 +156,11 @@ func (scope *Scope) GetModelStruct() *ModelStruct { if fieldStruct := scopeType.Field(i); ast.IsExported(fieldStruct.Name) { var value reflect.Value if (fieldStruct.Type.Kind() == reflect.Interface) { - value = reflect.ValueOf(reflect.ValueOf(scope.Value).Elem().Field(i).Interface()) + value = reflect.ValueOf(scope.Value).Elem() + if (value.Kind() == reflect.Slice) { + value = value.Index(0) + } + value = reflect.ValueOf(value.Field(i).Interface()) cachable_byScopeType = false } else { value = reflect.Indirect(reflect.ValueOf(scope.Value)) From 18616d53c4f1226d97520dd7931124d91d7efeba Mon Sep 17 00:00:00 2001 From: Dmitry Yu Okunev Date: Tue, 19 May 2015 12:47:28 +0300 Subject: [PATCH 4/5] Fixed an one more: panic: reflect: call of reflect.Value.Field on slice Value --- model_struct.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/model_struct.go b/model_struct.go index 4ba10ac0..0e24a614 100644 --- a/model_struct.go +++ b/model_struct.go @@ -165,6 +165,12 @@ func (scope *Scope) GetModelStruct() *ModelStruct { } else { value = reflect.Indirect(reflect.ValueOf(scope.Value)) } + if (value.Kind() == reflect.Slice) { + if (value.Len() == 0) { + value = reflect.MakeSlice(value.Type(), 1, 1); + } + value = value.Index(0); + } field := &StructField{ Struct: fieldStruct, Value: value, From 81ceeb3e9f3bfe14457ccd9f63c1fd4867e15b97 Mon Sep 17 00:00:00 2001 From: Dmitry Yu Okunev Date: Thu, 21 May 2015 12:30:16 +0300 Subject: [PATCH 5/5] Rolled back the behaviour of tag "embedded" It's now required to use "embedded:fixed" --- README.md | 4 ++-- model_struct.go | 6 ++++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 177456aa..78e6e458 100644 --- a/README.md +++ b/README.md @@ -1177,8 +1177,8 @@ db.Where("email = ?", "x@example.org").Attrs(User{RegisteredIp: "111.111.111.111 ```go type Delta struct { Id int; - Was interface{} `gorm:"embedded"`; - Became interface{} `gorm:"embedded"`; + Was interface{} `gorm:"embedded:prefixed"`; + Became interface{} `gorm:"embedded:prefixed"`; } type Login struct { diff --git a/model_struct.go b/model_struct.go index 0e24a614..0a1631a9 100644 --- a/model_struct.go +++ b/model_struct.go @@ -299,10 +299,12 @@ func (scope *Scope) GetModelStruct() *ModelStruct { field.IsNormal = true } case reflect.Struct: - if _, ok := gormSettings["EMBEDDED"]; ok || fieldStruct.Anonymous { + if embType, ok := gormSettings["EMBEDDED"]; ok || fieldStruct.Anonymous { for _, toField := range toScope.GetStructFields() { toField = toField.clone() - toField.DBName = field.DBName+"__"+toField.DBName + if (embType == "prefixed") { + toField.DBName = field.DBName+"__"+toField.DBName + } toField.Names = append([]string{fieldStruct.Name}, toField.Names...) modelStruct.StructFields = append(modelStruct.StructFields, toField) if toField.IsPrimaryKey {