Try to cache struct fields

This commit is contained in:
Jinzhu 2015-02-15 23:01:09 +08:00
parent 6864d5e5bd
commit 5d692a6bf2
4 changed files with 244 additions and 234 deletions

View File

@ -4,51 +4,11 @@ import (
"database/sql"
"errors"
"reflect"
"time"
)
type relationship struct {
JoinTable string
ForeignKey string
ForeignType string
AssociationForeignKey string
Kind string
}
// FIXME
func (r relationship) ForeignDBName() string {
return ToSnake(r.ForeignKey)
}
func (r relationship) AssociationForeignDBName(name string) string {
return ToSnake(r.AssociationForeignKey)
}
type Field struct {
Name string
DBName string
*StructField
Field reflect.Value
Tag reflect.StructTag
Relationship *relationship
IsNormal bool
IsBlank bool
IsIgnored bool
IsPrimaryKey bool
DefaultValue interface{}
}
func (field *Field) IsScanner() bool {
_, isScanner := reflect.New(field.Field.Type()).Interface().(sql.Scanner)
return isScanner
}
func (field *Field) IsTime() bool {
reflectValue := field.Field
if reflectValue.Kind() == reflect.Ptr {
reflectValue = reflect.New(reflectValue.Type().Elem()).Elem()
}
_, isTime := reflectValue.Interface().(time.Time)
return isTime
}
func (field *Field) Set(value interface{}) (err error) {
@ -76,3 +36,20 @@ func (field *Field) Set(value interface{}) (err error) {
return
}
type relationship struct {
JoinTable string
ForeignKey string
ForeignType string
AssociationForeignKey string
Kind string
}
// FIXME
func (r relationship) ForeignDBName() string {
return ToSnake(r.ForeignKey)
}
func (r relationship) AssociationForeignDBName(name string) string {
return ToSnake(r.AssociationForeignKey)
}

179
scope.go
View File

@ -3,7 +3,6 @@ package gorm
import (
"errors"
"fmt"
"go/ast"
"strings"
"time"
@ -154,7 +153,7 @@ func (scope *Scope) HasColumn(column string) bool {
dbName := ToSnake(column)
field, hasColumn := clone.Fields(false)[dbName]
field, hasColumn := clone.Fields()[dbName]
return hasColumn && !field.IsIgnored
}
@ -298,177 +297,13 @@ func (scope *Scope) FieldByName(name string) (field *Field, ok bool) {
return nil, false
}
func (scope *Scope) fieldFromStruct(fieldStruct reflect.StructField, withRelation bool) []*Field {
var field Field
field.Name = fieldStruct.Name
value := scope.IndirectValue().FieldByName(fieldStruct.Name)
indirectValue := reflect.Indirect(value)
field.Field = value
field.IsBlank = isBlank(value)
// Search for primary key tag identifier
settings := parseTagSetting(fieldStruct.Tag.Get("gorm"))
if _, ok := settings["PRIMARY_KEY"]; ok {
field.IsPrimaryKey = true
}
if def, ok := parseTagSetting(fieldStruct.Tag.Get("sql"))["DEFAULT"]; ok {
field.DefaultValue = def
}
field.Tag = fieldStruct.Tag
if value, ok := settings["COLUMN"]; ok {
field.DBName = value
} else {
field.DBName = ToSnake(fieldStruct.Name)
}
tagIdentifier := "sql"
if scope.db != nil {
tagIdentifier = scope.db.parent.tagIdentifier
}
if fieldStruct.Tag.Get(tagIdentifier) == "-" {
field.IsIgnored = true
}
if !field.IsIgnored {
// parse association
if !indirectValue.IsValid() {
indirectValue = reflect.New(value.Type())
}
typ := indirectValue.Type()
scopeTyp := scope.IndirectValue().Type()
foreignKey := SnakeToUpperCamel(settings["FOREIGNKEY"])
foreignType := SnakeToUpperCamel(settings["FOREIGNTYPE"])
associationForeignKey := SnakeToUpperCamel(settings["ASSOCIATIONFOREIGNKEY"])
many2many := settings["MANY2MANY"]
polymorphic := SnakeToUpperCamel(settings["POLYMORPHIC"])
if polymorphic != "" {
foreignKey = polymorphic + "Id"
foreignType = polymorphic + "Type"
}
switch indirectValue.Kind() {
case reflect.Slice:
typ = typ.Elem()
if field.IsScanner() {
field.IsNormal = true
} else if (typ.Kind() == reflect.Struct) && withRelation {
if foreignKey == "" {
foreignKey = scopeTyp.Name() + "Id"
}
if associationForeignKey == "" {
associationForeignKey = typ.Name() + "Id"
}
// if not many to many, foreign key could be null
if many2many == "" {
if !reflect.New(typ).Elem().FieldByName(foreignKey).IsValid() {
foreignKey = ""
}
}
field.Relationship = &relationship{
JoinTable: many2many,
ForeignKey: foreignKey,
ForeignType: foreignType,
AssociationForeignKey: associationForeignKey,
Kind: "has_many",
}
if many2many != "" {
field.Relationship.Kind = "many_to_many"
}
} else {
field.IsNormal = true
}
case reflect.Struct:
if field.IsTime() || field.IsScanner() {
field.IsNormal = true
} else if _, ok := settings["EMBEDDED"]; ok || fieldStruct.Anonymous {
var fields []*Field
if field.Field.CanAddr() {
for _, field := range scope.New(field.Field.Addr().Interface()).Fields() {
field.DBName = field.DBName
fields = append(fields, field)
}
}
return fields
} else if withRelation {
var belongsToForeignKey, hasOneForeignKey, kind string
if foreignKey == "" {
belongsToForeignKey = field.Name + "Id"
hasOneForeignKey = scopeTyp.Name() + "Id"
} else {
belongsToForeignKey = foreignKey
hasOneForeignKey = foreignKey
}
if scope.HasColumn(belongsToForeignKey) {
foreignKey = belongsToForeignKey
kind = "belongs_to"
} else {
foreignKey = hasOneForeignKey
kind = "has_one"
}
field.Relationship = &relationship{ForeignKey: foreignKey, ForeignType: foreignType, Kind: kind}
}
default:
field.IsNormal = true
}
}
return []*Field{&field}
}
// Fields get value's fields
func (scope *Scope) Fields(noRelations ...bool) map[string]*Field {
var withRelation = len(noRelations) == 0
if withRelation && scope.fields != nil {
return scope.fields
}
var fields = map[string]*Field{}
if scope.IndirectValue().IsValid() && scope.IndirectValue().Kind() == reflect.Struct {
scopeTyp := scope.IndirectValue().Type()
var hasPrimaryKey = false
for i := 0; i < scopeTyp.NumField(); i++ {
fieldStruct := scopeTyp.Field(i)
if !ast.IsExported(fieldStruct.Name) {
continue
}
for _, field := range scope.fieldFromStruct(fieldStruct, withRelation) {
if field.IsPrimaryKey {
hasPrimaryKey = true
}
if value, ok := fields[field.DBName]; ok {
if value.IsIgnored {
fields[field.DBName] = field
} else {
panic(fmt.Sprintf("Duplicated column name for %v (%v)\n", scope.typeName(), fileWithLineNum()))
}
} else {
fields[field.DBName] = field
}
}
}
if !hasPrimaryKey {
if field, ok := fields["id"]; ok {
field.IsPrimaryKey = true
}
}
}
if withRelation {
scope.fields = fields
func (scope *Scope) Fields() map[string]*Field {
fields := map[string]*Field{}
structFields := scope.GetStructFields()
for _, structField := range structFields {
field := Field{StructField: structField}
fields[field.DBName] = &field
}
return fields

View File

@ -5,7 +5,6 @@ import (
"database/sql/driver"
"errors"
"fmt"
"go/ast"
"reflect"
"regexp"
"strconv"
@ -403,7 +402,7 @@ func (scope *Scope) sqlTagForField(field *Field) (typ string) {
return typ + " " + additionalType
}
case reflect.Struct:
if field.IsScanner() {
if field.IsScanner {
var getScannerValue func(reflect.Value)
getScannerValue = func(value reflect.Value) {
reflectValue = value
@ -412,7 +411,7 @@ func (scope *Scope) sqlTagForField(field *Field) (typ string) {
}
}
getScannerValue(reflectValue.Field(0))
} else if !field.IsTime() {
} else if !field.IsTime {
return typ + " " + additionalType
}
}
@ -578,24 +577,11 @@ func (scope *Scope) createJoinTable(field *Field) {
func (scope *Scope) createTable() *Scope {
var sqls []string
fields := scope.Fields()
scopeType := scope.IndirectValue().Type()
for i := 0; i < scopeType.NumField(); i++ {
if !ast.IsExported(scopeType.Field(i).Name) {
continue
}
for _, field := range scope.fieldFromStruct(scopeType.Field(i), false) {
name := field.Name
for _, field := range fields {
if field.Name == name {
if field.IsNormal {
sqlTag := scope.sqlTagForField(field)
sqls = append(sqls, scope.Quote(field.DBName)+" "+sqlTag)
}
scope.createJoinTable(field)
}
}
for _, structField := range scope.GetStructFields() {
if structField.IsNormal {
sqls = append(sqls, scope.Quote(structField.DBName)+" "+structField.SqlTag)
}
scope.createJoinTable(structField)
}
scope.Raw(fmt.Sprintf("CREATE TABLE %v (%v)", scope.QuotedTableName(), strings.Join(sqls, ","))).Exec()
return scope

212
struct_field.go Normal file
View File

@ -0,0 +1,212 @@
package gorm
import (
"database/sql"
"go/ast"
"reflect"
"strconv"
"time"
)
type StructField struct {
Name string
DBName string
IsBlank bool
IsPrimaryKey bool
IsScanner bool
IsTime bool
IsNormal bool
IsIgnored bool
DefaultValue *string
SqlTag string
Relationship *relationship
}
func (scope *Scope) GetStructFields() (fields []*StructField) {
reflectValue := reflect.Indirect(reflect.ValueOf(scope.Value))
if reflectValue.Kind() == reflect.Slice {
reflectValue = reflect.Indirect(reflect.New(reflectValue.Elem().Type()))
}
scopeTyp := reflectValue.Type()
hasPrimaryKey := false
for i := 0; i < scopeTyp.NumField(); i++ {
fieldStruct := scopeTyp.Field(i)
if !ast.IsExported(fieldStruct.Name) {
continue
}
var field *StructField
if fieldStruct.Tag.Get("sql") == "-" {
field.IsIgnored = true
} else {
sqlSettings := parseTagSetting(fieldStruct.Tag.Get("sql"))
settings := parseTagSetting(fieldStruct.Tag.Get("gorm"))
if _, ok := settings["PRIMARY_KEY"]; ok {
field.IsPrimaryKey = true
hasPrimaryKey = true
}
if value, ok := sqlSettings["DEFAULT"]; ok {
field.DefaultValue = &value
}
if value, ok := settings["COLUMN"]; ok {
field.DBName = value
} else {
field.DBName = ToSnake(fieldStruct.Name)
}
fieldType, indirectType := fieldStruct.Type, fieldStruct.Type
if indirectType.Kind() == reflect.Ptr {
indirectType = indirectType.Elem()
}
if _, isScanner := reflect.New(fieldType).Interface().(sql.Scanner); isScanner {
field.IsScanner, field.IsNormal = true, true
}
if _, isTime := reflect.New(indirectType).Interface().(time.Time); isTime {
field.IsTime, field.IsNormal = true, true
}
many2many := settings["MANY2MANY"]
foreignKey := SnakeToUpperCamel(settings["FOREIGNKEY"])
foreignType := SnakeToUpperCamel(settings["FOREIGNTYPE"])
associationForeignKey := SnakeToUpperCamel(settings["ASSOCIATIONFOREIGNKEY"])
if polymorphic := SnakeToUpperCamel(settings["POLYMORPHIC"]); polymorphic != "" {
foreignKey = polymorphic + "Id"
foreignType = polymorphic + "Type"
}
if !field.IsNormal {
switch indirectType.Kind() {
case reflect.Slice:
typ := indirectType.Elem()
if typ.Kind() == reflect.Ptr {
typ = typ.Elem()
}
if typ.Kind() == reflect.Struct {
kind := "has_many"
if foreignKey == "" {
foreignKey = indirectType.Name() + "Id"
}
if associationForeignKey == "" {
associationForeignKey = typ.Name() + "Id"
}
if many2many != "" {
kind = "many_to_many"
} else if !reflect.New(typ).FieldByName(foreignKey).IsValid() {
foreignKey = ""
}
field.Relationship = &relationship{
JoinTable: many2many,
ForeignKey: foreignKey,
ForeignType: foreignType,
AssociationForeignKey: associationForeignKey,
Kind: kind,
}
} else {
field.IsNormal = true
}
case reflect.Struct:
if _, ok := settings["EMBEDDED"]; ok || fieldStruct.Anonymous {
for _, field := range scope.New(reflect.New(indirectType).Interface()).GetStructFields() {
fields = append(fields, field)
}
break
} else {
var belongsToForeignKey, hasOneForeignKey, kind string
if foreignKey == "" {
belongsToForeignKey = indirectType.Name() + "Id"
hasOneForeignKey = scopeTyp.Name() + "Id"
} else {
belongsToForeignKey = foreignKey
hasOneForeignKey = foreignKey
}
if _, ok := scopeTyp.FieldByName(belongsToForeignKey); ok {
foreignKey = belongsToForeignKey
kind = "belongs_to"
} else {
foreignKey = hasOneForeignKey
kind = "has_one"
}
field.Relationship = &relationship{ForeignKey: foreignKey, ForeignType: foreignType, Kind: kind}
}
default:
field.IsNormal = true
}
}
}
fields = append(fields, field)
}
if !hasPrimaryKey {
for _, field := range fields {
if field.DBName == "id" {
field.IsPrimaryKey = true
}
}
}
for _, field := range fields {
var sqlType string
size := 255
sqlTag := field.Tag.Get("sql")
sqlSetting = parseTagSetting(sqlTag)
if value, ok := sqlSetting["SIZE"]; ok {
if i, err := strconv.Atoi(value); err == nil {
size = i
} else {
size = 0
}
}
if value, ok := sqlSetting["TYPE"]; ok {
typ = value
}
additionalType := sqlSetting["NOT NULL"] + " " + sqlSetting["UNIQUE"]
if value, ok := sqlSetting["DEFAULT"]; ok {
additionalType = additionalType + "DEFAULT " + value
}
if field.IsScanner {
var getScannerValue func(reflect.Value)
getScannerValue = func(reflectValue reflect.Value) {
if _, isScanner := reflect.New(reflectValue.Type()).Interface().(sql.Scanner); isScanner {
getScannerValue(reflectValue.Field(0))
}
}
getScannerValue(reflectValue.Field(0))
}
if field.IsNormal {
typ + " " + additionalType
}
} else if !field.IsTime {
return typ + " " + additionalType
}
}
if len(typ) == 0 {
if field.IsPrimaryKey {
typ = scope.Dialect().PrimaryKeyTag(reflectValue, size)
} else {
typ = scope.Dialect().SqlTag(reflectValue, size)
}
}
return typ + " " + additionalType
}
return
}