gorm/schema/schema.go

208 lines
6.1 KiB
Go
Raw Normal View History

2020-01-31 07:22:37 +03:00
package schema
import (
2020-05-05 16:28:38 +03:00
"context"
2020-02-02 09:40:44 +03:00
"errors"
"fmt"
2020-01-31 07:22:37 +03:00
"go/ast"
"reflect"
"sync"
2020-05-19 16:50:06 +03:00
"github.com/jinzhu/gorm/clause"
"github.com/jinzhu/gorm/logger"
2020-01-31 07:22:37 +03:00
)
2020-02-02 09:40:44 +03:00
// ErrUnsupportedDataType unsupported data type
var ErrUnsupportedDataType = errors.New("unsupported data type")
2020-01-31 07:22:37 +03:00
type Schema struct {
2020-02-23 16:22:35 +03:00
Name string
ModelType reflect.Type
Table string
PrioritizedPrimaryField *Field
DBNames []string
PrimaryFields []*Field
Fields []*Field
FieldsByName map[string]*Field
FieldsByDBName map[string]*Field
FieldsWithDefaultDBValue map[string]*Field // fields with default value assigned by database
Relationships Relationships
2020-05-19 16:50:06 +03:00
CreateClauses []clause.Interface
QueryClauses []clause.Interface
UpdateClauses []clause.Interface
DeleteClauses []clause.Interface
2020-02-23 16:22:35 +03:00
BeforeCreate, AfterCreate bool
BeforeUpdate, AfterUpdate bool
BeforeDelete, AfterDelete bool
BeforeSave, AfterSave bool
AfterFind bool
err error
namer Namer
cacheStore *sync.Map
}
func (schema Schema) String() string {
if schema.ModelType.Name() == "" {
return fmt.Sprintf("%v(%v)", schema.Name, schema.Table)
}
2020-02-01 16:48:06 +03:00
return fmt.Sprintf("%v.%v", schema.ModelType.PkgPath(), schema.ModelType.Name())
}
2020-05-18 08:07:11 +03:00
func (schema Schema) MakeSlice() reflect.Value {
slice := reflect.MakeSlice(reflect.SliceOf(schema.ModelType), 0, 0)
results := reflect.New(slice.Type())
results.Elem().Set(slice)
return results
}
func (schema Schema) LookUpField(name string) *Field {
if field, ok := schema.FieldsByDBName[name]; ok {
return field
}
if field, ok := schema.FieldsByName[name]; ok {
return field
}
return nil
2020-01-31 07:22:37 +03:00
}
// get data type from dialector
2020-02-24 03:51:35 +03:00
func Parse(dest interface{}, cacheStore *sync.Map, namer Namer) (*Schema, error) {
modelType := reflect.ValueOf(dest).Type()
2020-01-31 07:22:37 +03:00
for modelType.Kind() == reflect.Slice || modelType.Kind() == reflect.Ptr {
modelType = modelType.Elem()
}
if modelType.Kind() != reflect.Struct {
if modelType.PkgPath() == "" {
2020-02-24 03:51:35 +03:00
return nil, fmt.Errorf("%w: %+v", ErrUnsupportedDataType, dest)
}
2020-02-24 03:51:35 +03:00
return nil, fmt.Errorf("%w: %v.%v", ErrUnsupportedDataType, modelType.PkgPath(), modelType.Name())
2020-01-31 07:22:37 +03:00
}
if v, ok := cacheStore.Load(modelType); ok {
2020-02-24 03:51:35 +03:00
return v.(*Schema), nil
2020-01-31 07:22:37 +03:00
}
schema := &Schema{
Name: modelType.Name(),
2020-01-31 07:22:37 +03:00
ModelType: modelType,
2020-01-31 09:31:15 +03:00
Table: namer.TableName(modelType.Name()),
2020-01-31 07:22:37 +03:00
FieldsByName: map[string]*Field{},
FieldsByDBName: map[string]*Field{},
2020-02-01 16:48:06 +03:00
Relationships: Relationships{Relations: map[string]*Relationship{}},
cacheStore: cacheStore,
namer: namer,
2020-01-31 07:22:37 +03:00
}
defer func() {
if schema.err != nil {
2020-05-05 16:28:38 +03:00
logger.Default.Error(context.Background(), schema.err.Error())
cacheStore.Delete(modelType)
2020-01-31 07:22:37 +03:00
}
}()
2020-01-31 07:22:37 +03:00
for i := 0; i < modelType.NumField(); i++ {
if fieldStruct := modelType.Field(i); ast.IsExported(fieldStruct.Name) {
2020-02-01 16:48:06 +03:00
if field := schema.ParseField(fieldStruct); field.EmbeddedSchema != nil {
schema.Fields = append(schema.Fields, field.EmbeddedSchema.Fields...)
2020-02-01 16:48:06 +03:00
} else {
schema.Fields = append(schema.Fields, field)
2020-01-31 09:31:15 +03:00
}
}
2020-01-31 07:22:37 +03:00
}
for _, field := range schema.Fields {
2020-02-02 09:40:44 +03:00
if field.DBName == "" && field.DataType != "" {
field.DBName = namer.ColumnName(schema.Table, field.Name)
2020-01-31 09:31:15 +03:00
}
2020-01-31 07:22:37 +03:00
if field.DBName != "" {
2020-01-31 09:31:15 +03:00
// nonexistence or shortest path or first appear prioritized if has permission
if v, ok := schema.FieldsByDBName[field.DBName]; !ok || (field.Creatable && len(field.BindNames) < len(v.BindNames)) {
2020-02-18 17:56:37 +03:00
if _, ok := schema.FieldsByDBName[field.DBName]; !ok {
schema.DBNames = append(schema.DBNames, field.DBName)
}
2020-01-31 07:22:37 +03:00
schema.FieldsByDBName[field.DBName] = field
schema.FieldsByName[field.Name] = field
2020-02-01 16:48:06 +03:00
if v != nil && v.PrimaryKey {
if schema.PrioritizedPrimaryField == v {
schema.PrioritizedPrimaryField = nil
}
for idx, f := range schema.PrimaryFields {
if f == v {
schema.PrimaryFields = append(schema.PrimaryFields[0:idx], schema.PrimaryFields[idx+1:]...)
} else if schema.PrioritizedPrimaryField == nil {
schema.PrioritizedPrimaryField = f
}
}
}
if field.PrimaryKey {
if schema.PrioritizedPrimaryField == nil {
schema.PrioritizedPrimaryField = field
}
schema.PrimaryFields = append(schema.PrimaryFields, field)
}
2020-01-31 07:22:37 +03:00
}
}
if _, ok := schema.FieldsByName[field.Name]; !ok {
schema.FieldsByName[field.Name] = field
}
2020-02-15 11:04:21 +03:00
field.setupValuerAndSetter()
2020-01-31 07:22:37 +03:00
}
2020-02-01 16:48:06 +03:00
if f := schema.LookUpField("id"); f != nil {
if f.PrimaryKey {
schema.PrioritizedPrimaryField = f
} else if len(schema.PrimaryFields) == 0 {
f.PrimaryKey = true
schema.PrioritizedPrimaryField = f
schema.PrimaryFields = append(schema.PrimaryFields, f)
2020-01-31 07:22:37 +03:00
}
2020-02-01 16:48:06 +03:00
}
2020-01-31 07:22:37 +03:00
2020-02-20 05:13:26 +03:00
schema.FieldsWithDefaultDBValue = map[string]*Field{}
for db, field := range schema.FieldsByDBName {
if field.HasDefaultValue && field.DefaultValueInterface == nil {
schema.FieldsWithDefaultDBValue[db] = field
}
}
if schema.PrioritizedPrimaryField != nil {
switch schema.PrioritizedPrimaryField.DataType {
case Int, Uint:
schema.FieldsWithDefaultDBValue[schema.PrioritizedPrimaryField.DBName] = schema.PrioritizedPrimaryField
}
}
2020-02-24 03:51:35 +03:00
reflectValue := reflect.Indirect(reflect.New(modelType))
2020-02-23 16:22:35 +03:00
callbacks := []string{"BeforeCreate", "AfterCreate", "BeforeUpdate", "AfterUpdate", "BeforeSave", "AfterSave", "BeforeDelete", "AfterDelete", "AfterFind"}
for _, name := range callbacks {
if methodValue := reflectValue.MethodByName(name); methodValue.IsValid() {
switch methodValue.Type().String() {
case "func(*gorm.DB)": // TODO hack
reflect.Indirect(reflect.ValueOf(schema)).FieldByName(name).SetBool(true)
default:
2020-05-05 16:28:38 +03:00
logger.Default.Warn(context.Background(), "Model %v don't match %vInterface, should be %v(*gorm.DB)", schema, name, name)
2020-02-23 16:22:35 +03:00
}
}
}
2020-02-01 16:48:06 +03:00
cacheStore.Store(modelType, schema)
2020-02-01 16:48:06 +03:00
// parse relations for unidentified fields
for _, field := range schema.Fields {
if field.DataType == "" && field.Creatable {
if schema.parseRelation(field); schema.err != nil {
2020-02-24 03:51:35 +03:00
return schema, schema.err
2020-02-01 16:48:06 +03:00
}
}
2020-01-31 07:22:37 +03:00
}
2020-02-24 03:51:35 +03:00
return schema, schema.err
2020-01-31 07:22:37 +03:00
}