Refactor ParseSchema
This commit is contained in:
parent
325bb5ac96
commit
bf0cec734f
@ -6,6 +6,11 @@ import (
|
|||||||
"github.com/jinzhu/gorm/model"
|
"github.com/jinzhu/gorm/model"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// DefaultTableNameHandler default table name handler
|
||||||
|
var DefaultTableNameHandler = func(stmt *builder.Statement, tableName string) string {
|
||||||
|
return tableName
|
||||||
|
}
|
||||||
|
|
||||||
// GetCreatingAssignments get creating assignments
|
// GetCreatingAssignments get creating assignments
|
||||||
func GetCreatingAssignments(stmt *builder.Statement, errs *gorm.Errors) chan []model.Field {
|
func GetCreatingAssignments(stmt *builder.Statement, errs *gorm.Errors) chan []model.Field {
|
||||||
return nil
|
return nil
|
||||||
@ -15,3 +20,24 @@ func GetCreatingAssignments(stmt *builder.Statement, errs *gorm.Errors) chan []m
|
|||||||
func GetTable(stmt *builder.Statement, errs *gorm.Errors) chan string {
|
func GetTable(stmt *builder.Statement, errs *gorm.Errors) chan string {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// if scope.Value == nil {
|
||||||
|
// return &modelStruct
|
||||||
|
// }
|
||||||
|
// TableName get model's table name
|
||||||
|
// func (schema *Schema) TableName(stmt *builder.Statement) string {
|
||||||
|
// if s.defaultTableName == "" && db != nil && s.ModelType != nil {
|
||||||
|
// // Set default table name
|
||||||
|
// if tabler, ok := reflect.New(s.ModelType).Interface().(tabler); ok {
|
||||||
|
// s.defaultTableName = tabler.TableName()
|
||||||
|
// } else {
|
||||||
|
// tableName := ToDBName(s.ModelType.Name())
|
||||||
|
// if db == nil || !db.parent.singularTable {
|
||||||
|
// tableName = inflection.Plural(tableName)
|
||||||
|
// }
|
||||||
|
// s.defaultTableName = tableName
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// return DefaultTableNameHandler(db, s.defaultTableName)
|
||||||
|
// }
|
||||||
|
@ -1,58 +0,0 @@
|
|||||||
package model
|
|
||||||
|
|
||||||
import (
|
|
||||||
"database/sql"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"reflect"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Field model field definition
|
|
||||||
type Field struct {
|
|
||||||
*StructField
|
|
||||||
IsBlank bool
|
|
||||||
Field reflect.Value
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set set a value to the field
|
|
||||||
func (field *Field) Set(value interface{}) (err error) {
|
|
||||||
if !field.Field.IsValid() {
|
|
||||||
return errors.New("field value not valid")
|
|
||||||
}
|
|
||||||
|
|
||||||
if !field.Field.CanAddr() {
|
|
||||||
return ErrUnaddressable
|
|
||||||
}
|
|
||||||
|
|
||||||
reflectValue, ok := value.(reflect.Value)
|
|
||||||
if !ok {
|
|
||||||
reflectValue = reflect.ValueOf(value)
|
|
||||||
}
|
|
||||||
|
|
||||||
fieldValue := field.Field
|
|
||||||
if reflectValue.IsValid() {
|
|
||||||
if reflectValue.Type().ConvertibleTo(fieldValue.Type()) {
|
|
||||||
fieldValue.Set(reflectValue.Convert(fieldValue.Type()))
|
|
||||||
} else {
|
|
||||||
if fieldValue.Kind() == reflect.Ptr {
|
|
||||||
if fieldValue.IsNil() {
|
|
||||||
fieldValue.Set(reflect.New(field.Struct.Type.Elem()))
|
|
||||||
}
|
|
||||||
fieldValue = fieldValue.Elem()
|
|
||||||
}
|
|
||||||
|
|
||||||
if reflectValue.Type().ConvertibleTo(fieldValue.Type()) {
|
|
||||||
fieldValue.Set(reflectValue.Convert(fieldValue.Type()))
|
|
||||||
} else if scanner, ok := fieldValue.Addr().Interface().(sql.Scanner); ok {
|
|
||||||
err = scanner.Scan(reflectValue.Interface())
|
|
||||||
} else {
|
|
||||||
err = fmt.Errorf("could not convert argument of field %s from %s to %s", field.Name, reflectValue.Type(), fieldValue.Type())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
field.Field.Set(reflect.Zero(field.Field.Type()))
|
|
||||||
}
|
|
||||||
|
|
||||||
field.IsBlank = isBlank(field.Field)
|
|
||||||
return err
|
|
||||||
}
|
|
@ -1,211 +0,0 @@
|
|||||||
package model
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"reflect"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
// JoinTableHandlerInterface is an interface for how to handle many2many relations
|
|
||||||
type JoinTableHandlerInterface interface {
|
|
||||||
// initialize join table handler
|
|
||||||
Setup(relationship *Relationship, tableName string, source reflect.Type, destination reflect.Type)
|
|
||||||
// Table return join table's table name
|
|
||||||
Table(db *DB) string
|
|
||||||
// Add create relationship in join table for source and destination
|
|
||||||
Add(handler JoinTableHandlerInterface, db *DB, source interface{}, destination interface{}) error
|
|
||||||
// Delete delete relationship in join table for sources
|
|
||||||
Delete(handler JoinTableHandlerInterface, db *DB, sources ...interface{}) error
|
|
||||||
// JoinWith query with `Join` conditions
|
|
||||||
JoinWith(handler JoinTableHandlerInterface, db *DB, source interface{}) *DB
|
|
||||||
// SourceForeignKeys return source foreign keys
|
|
||||||
SourceForeignKeys() []JoinTableForeignKey
|
|
||||||
// DestinationForeignKeys return destination foreign keys
|
|
||||||
DestinationForeignKeys() []JoinTableForeignKey
|
|
||||||
}
|
|
||||||
|
|
||||||
// JoinTableForeignKey join table foreign key struct
|
|
||||||
type JoinTableForeignKey struct {
|
|
||||||
DBName string
|
|
||||||
AssociationDBName string
|
|
||||||
}
|
|
||||||
|
|
||||||
// JoinTableSource is a struct that contains model type and foreign keys
|
|
||||||
type JoinTableSource struct {
|
|
||||||
ModelType reflect.Type
|
|
||||||
ForeignKeys []JoinTableForeignKey
|
|
||||||
}
|
|
||||||
|
|
||||||
// JoinTableHandler default join table handler
|
|
||||||
type JoinTableHandler struct {
|
|
||||||
TableName string `sql:"-"`
|
|
||||||
Source JoinTableSource `sql:"-"`
|
|
||||||
Destination JoinTableSource `sql:"-"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// SourceForeignKeys return source foreign keys
|
|
||||||
func (s *JoinTableHandler) SourceForeignKeys() []JoinTableForeignKey {
|
|
||||||
return s.Source.ForeignKeys
|
|
||||||
}
|
|
||||||
|
|
||||||
// DestinationForeignKeys return destination foreign keys
|
|
||||||
func (s *JoinTableHandler) DestinationForeignKeys() []JoinTableForeignKey {
|
|
||||||
return s.Destination.ForeignKeys
|
|
||||||
}
|
|
||||||
|
|
||||||
// Setup initialize a default join table handler
|
|
||||||
func (s *JoinTableHandler) Setup(relationship *Relationship, tableName string, source reflect.Type, destination reflect.Type) {
|
|
||||||
s.TableName = tableName
|
|
||||||
|
|
||||||
s.Source = JoinTableSource{ModelType: source}
|
|
||||||
s.Source.ForeignKeys = []JoinTableForeignKey{}
|
|
||||||
for idx, dbName := range relationship.ForeignFieldNames {
|
|
||||||
s.Source.ForeignKeys = append(s.Source.ForeignKeys, JoinTableForeignKey{
|
|
||||||
DBName: relationship.ForeignDBNames[idx],
|
|
||||||
AssociationDBName: dbName,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
s.Destination = JoinTableSource{ModelType: destination}
|
|
||||||
s.Destination.ForeignKeys = []JoinTableForeignKey{}
|
|
||||||
for idx, dbName := range relationship.AssociationForeignFieldNames {
|
|
||||||
s.Destination.ForeignKeys = append(s.Destination.ForeignKeys, JoinTableForeignKey{
|
|
||||||
DBName: relationship.AssociationForeignDBNames[idx],
|
|
||||||
AssociationDBName: dbName,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Table return join table's table name
|
|
||||||
func (s JoinTableHandler) Table(db *DB) string {
|
|
||||||
return DefaultTableNameHandler(db, s.TableName)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s JoinTableHandler) updateConditionMap(conditionMap map[string]interface{}, db *DB, joinTableSources []JoinTableSource, sources ...interface{}) {
|
|
||||||
for _, source := range sources {
|
|
||||||
scope := db.NewScope(source)
|
|
||||||
modelType := scope.GetModelStruct().ModelType
|
|
||||||
|
|
||||||
for _, joinTableSource := range joinTableSources {
|
|
||||||
if joinTableSource.ModelType == modelType {
|
|
||||||
for _, foreignKey := range joinTableSource.ForeignKeys {
|
|
||||||
if field, ok := scope.FieldByName(foreignKey.AssociationDBName); ok {
|
|
||||||
conditionMap[foreignKey.DBName] = field.Field.Interface()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add create relationship in join table for source and destination
|
|
||||||
func (s JoinTableHandler) Add(handler JoinTableHandlerInterface, db *DB, source interface{}, destination interface{}) error {
|
|
||||||
var (
|
|
||||||
scope = db.NewScope("")
|
|
||||||
conditionMap = map[string]interface{}{}
|
|
||||||
)
|
|
||||||
|
|
||||||
// Update condition map for source
|
|
||||||
s.updateConditionMap(conditionMap, db, []JoinTableSource{s.Source}, source)
|
|
||||||
|
|
||||||
// Update condition map for destination
|
|
||||||
s.updateConditionMap(conditionMap, db, []JoinTableSource{s.Destination}, destination)
|
|
||||||
|
|
||||||
var assignColumns, binVars, conditions []string
|
|
||||||
var values []interface{}
|
|
||||||
for key, value := range conditionMap {
|
|
||||||
assignColumns = append(assignColumns, scope.Quote(key))
|
|
||||||
binVars = append(binVars, `?`)
|
|
||||||
conditions = append(conditions, fmt.Sprintf("%v = ?", scope.Quote(key)))
|
|
||||||
values = append(values, value)
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, value := range values {
|
|
||||||
values = append(values, value)
|
|
||||||
}
|
|
||||||
|
|
||||||
quotedTable := scope.Quote(handler.Table(db))
|
|
||||||
sql := fmt.Sprintf(
|
|
||||||
"INSERT INTO %v (%v) SELECT %v %v WHERE NOT EXISTS (SELECT * FROM %v WHERE %v)",
|
|
||||||
quotedTable,
|
|
||||||
strings.Join(assignColumns, ","),
|
|
||||||
strings.Join(binVars, ","),
|
|
||||||
scope.Dialect().SelectFromDummyTable(),
|
|
||||||
quotedTable,
|
|
||||||
strings.Join(conditions, " AND "),
|
|
||||||
)
|
|
||||||
|
|
||||||
return db.Exec(sql, values...).Error
|
|
||||||
}
|
|
||||||
|
|
||||||
// Delete delete relationship in join table for sources
|
|
||||||
func (s JoinTableHandler) Delete(handler JoinTableHandlerInterface, db *DB, sources ...interface{}) error {
|
|
||||||
var (
|
|
||||||
scope = db.NewScope(nil)
|
|
||||||
conditions []string
|
|
||||||
values []interface{}
|
|
||||||
conditionMap = map[string]interface{}{}
|
|
||||||
)
|
|
||||||
|
|
||||||
s.updateConditionMap(conditionMap, db, []JoinTableSource{s.Source, s.Destination}, sources...)
|
|
||||||
|
|
||||||
for key, value := range conditionMap {
|
|
||||||
conditions = append(conditions, fmt.Sprintf("%v = ?", scope.Quote(key)))
|
|
||||||
values = append(values, value)
|
|
||||||
}
|
|
||||||
|
|
||||||
return db.Table(handler.Table(db)).Where(strings.Join(conditions, " AND "), values...).Delete("").Error
|
|
||||||
}
|
|
||||||
|
|
||||||
// JoinWith query with `Join` conditions
|
|
||||||
func (s JoinTableHandler) JoinWith(handler JoinTableHandlerInterface, db *DB, source interface{}) *DB {
|
|
||||||
var (
|
|
||||||
scope = db.NewScope(source)
|
|
||||||
tableName = handler.Table(db)
|
|
||||||
quotedTableName = scope.Quote(tableName)
|
|
||||||
joinConditions []string
|
|
||||||
values []interface{}
|
|
||||||
)
|
|
||||||
|
|
||||||
if s.Source.ModelType == scope.GetModelStruct().ModelType {
|
|
||||||
destinationTableName := db.NewScope(reflect.New(s.Destination.ModelType).Interface()).QuotedTableName()
|
|
||||||
for _, foreignKey := range s.Destination.ForeignKeys {
|
|
||||||
joinConditions = append(joinConditions, fmt.Sprintf("%v.%v = %v.%v", quotedTableName, scope.Quote(foreignKey.DBName), destinationTableName, scope.Quote(foreignKey.AssociationDBName)))
|
|
||||||
}
|
|
||||||
|
|
||||||
var foreignDBNames []string
|
|
||||||
var foreignFieldNames []string
|
|
||||||
|
|
||||||
for _, foreignKey := range s.Source.ForeignKeys {
|
|
||||||
foreignDBNames = append(foreignDBNames, foreignKey.DBName)
|
|
||||||
if field, ok := scope.FieldByName(foreignKey.AssociationDBName); ok {
|
|
||||||
foreignFieldNames = append(foreignFieldNames, field.Name)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
foreignFieldValues := scope.getColumnAsArray(foreignFieldNames, scope.Value)
|
|
||||||
|
|
||||||
var condString string
|
|
||||||
if len(foreignFieldValues) > 0 {
|
|
||||||
var quotedForeignDBNames []string
|
|
||||||
for _, dbName := range foreignDBNames {
|
|
||||||
quotedForeignDBNames = append(quotedForeignDBNames, tableName+"."+dbName)
|
|
||||||
}
|
|
||||||
|
|
||||||
condString = fmt.Sprintf("%v IN (%v)", toQueryCondition(scope, quotedForeignDBNames), toQueryMarks(foreignFieldValues))
|
|
||||||
|
|
||||||
keys := scope.getColumnAsArray(foreignFieldNames, scope.Value)
|
|
||||||
values = append(values, toQueryValues(keys))
|
|
||||||
} else {
|
|
||||||
condString = fmt.Sprintf("1 <> 1")
|
|
||||||
}
|
|
||||||
|
|
||||||
return db.Joins(fmt.Sprintf("INNER JOIN %v ON %v", quotedTableName, strings.Join(joinConditions, " AND "))).
|
|
||||||
Where(condString, toQueryValues(foreignFieldValues)...)
|
|
||||||
}
|
|
||||||
|
|
||||||
db.Error = errors.New("wrong source type for join table handler")
|
|
||||||
return db
|
|
||||||
}
|
|
@ -1,630 +0,0 @@
|
|||||||
package model
|
|
||||||
|
|
||||||
import (
|
|
||||||
"database/sql"
|
|
||||||
"errors"
|
|
||||||
"go/ast"
|
|
||||||
"reflect"
|
|
||||||
"strings"
|
|
||||||
"sync"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/jinzhu/gorm/builder"
|
|
||||||
"github.com/jinzhu/inflection"
|
|
||||||
)
|
|
||||||
|
|
||||||
// DefaultTableNameHandler default table name handler
|
|
||||||
var DefaultTableNameHandler = func(db *builder.Statement, defaultTableName string) string {
|
|
||||||
return defaultTableName
|
|
||||||
}
|
|
||||||
|
|
||||||
type safeModelStructsMap struct {
|
|
||||||
m map[reflect.Type]*ModelStruct
|
|
||||||
l *sync.RWMutex
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *safeModelStructsMap) Set(key reflect.Type, value *ModelStruct) {
|
|
||||||
s.l.Lock()
|
|
||||||
defer s.l.Unlock()
|
|
||||||
s.m[key] = value
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *safeModelStructsMap) Get(key reflect.Type) *ModelStruct {
|
|
||||||
s.l.RLock()
|
|
||||||
defer s.l.RUnlock()
|
|
||||||
return s.m[key]
|
|
||||||
}
|
|
||||||
|
|
||||||
func newModelStructsMap() *safeModelStructsMap {
|
|
||||||
return &safeModelStructsMap{l: new(sync.RWMutex), m: make(map[reflect.Type]*ModelStruct)}
|
|
||||||
}
|
|
||||||
|
|
||||||
var modelStructsMap = newModelStructsMap()
|
|
||||||
|
|
||||||
// ModelStruct model definition
|
|
||||||
type ModelStruct struct {
|
|
||||||
PrimaryFields []*StructField
|
|
||||||
StructFields []*StructField
|
|
||||||
ModelType reflect.Type
|
|
||||||
defaultTableName string
|
|
||||||
}
|
|
||||||
|
|
||||||
// TableName get model's table name
|
|
||||||
func (s *ModelStruct) TableName(db *DB) string {
|
|
||||||
if s.defaultTableName == "" && db != nil && s.ModelType != nil {
|
|
||||||
// Set default table name
|
|
||||||
if tabler, ok := reflect.New(s.ModelType).Interface().(tabler); ok {
|
|
||||||
s.defaultTableName = tabler.TableName()
|
|
||||||
} else {
|
|
||||||
tableName := ToDBName(s.ModelType.Name())
|
|
||||||
if db == nil || !db.parent.singularTable {
|
|
||||||
tableName = inflection.Plural(tableName)
|
|
||||||
}
|
|
||||||
s.defaultTableName = tableName
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return DefaultTableNameHandler(db, s.defaultTableName)
|
|
||||||
}
|
|
||||||
|
|
||||||
// StructField model field's struct definition
|
|
||||||
type StructField struct {
|
|
||||||
DBName string
|
|
||||||
Name string
|
|
||||||
Names []string
|
|
||||||
IsPrimaryKey bool
|
|
||||||
IsNormal bool
|
|
||||||
IsIgnored bool
|
|
||||||
IsScanner bool
|
|
||||||
HasDefaultValue bool
|
|
||||||
Tag reflect.StructTag
|
|
||||||
TagSettings map[string]string
|
|
||||||
Struct reflect.StructField
|
|
||||||
IsForeignKey bool
|
|
||||||
Relationship *Relationship
|
|
||||||
}
|
|
||||||
|
|
||||||
func (structField *StructField) clone() *StructField {
|
|
||||||
clone := &StructField{
|
|
||||||
DBName: structField.DBName,
|
|
||||||
Name: structField.Name,
|
|
||||||
Names: structField.Names,
|
|
||||||
IsPrimaryKey: structField.IsPrimaryKey,
|
|
||||||
IsNormal: structField.IsNormal,
|
|
||||||
IsIgnored: structField.IsIgnored,
|
|
||||||
IsScanner: structField.IsScanner,
|
|
||||||
HasDefaultValue: structField.HasDefaultValue,
|
|
||||||
Tag: structField.Tag,
|
|
||||||
TagSettings: map[string]string{},
|
|
||||||
Struct: structField.Struct,
|
|
||||||
IsForeignKey: structField.IsForeignKey,
|
|
||||||
}
|
|
||||||
|
|
||||||
if structField.Relationship != nil {
|
|
||||||
relationship := *structField.Relationship
|
|
||||||
clone.Relationship = &relationship
|
|
||||||
}
|
|
||||||
|
|
||||||
for key, value := range structField.TagSettings {
|
|
||||||
clone.TagSettings[key] = value
|
|
||||||
}
|
|
||||||
|
|
||||||
return clone
|
|
||||||
}
|
|
||||||
|
|
||||||
// Relationship described the relationship between models
|
|
||||||
type Relationship struct {
|
|
||||||
Kind string
|
|
||||||
PolymorphicType string
|
|
||||||
PolymorphicDBName string
|
|
||||||
PolymorphicValue string
|
|
||||||
ForeignFieldNames []string
|
|
||||||
ForeignDBNames []string
|
|
||||||
AssociationForeignFieldNames []string
|
|
||||||
AssociationForeignDBNames []string
|
|
||||||
JoinTableHandler JoinTableHandlerInterface
|
|
||||||
}
|
|
||||||
|
|
||||||
func getForeignField(column string, fields []*StructField) *StructField {
|
|
||||||
for _, field := range fields {
|
|
||||||
if field.Name == column || field.DBName == column || field.DBName == ToDBName(column) {
|
|
||||||
return field
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetModelStruct get value's model struct, relationships based on struct and tag definition
|
|
||||||
func (scope *Scope) GetModelStruct() *ModelStruct {
|
|
||||||
var modelStruct ModelStruct
|
|
||||||
// Scope value can't be nil
|
|
||||||
if scope.Value == nil {
|
|
||||||
return &modelStruct
|
|
||||||
}
|
|
||||||
|
|
||||||
reflectType := reflect.ValueOf(scope.Value).Type()
|
|
||||||
for reflectType.Kind() == reflect.Slice || reflectType.Kind() == reflect.Ptr {
|
|
||||||
reflectType = reflectType.Elem()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Scope value need to be a struct
|
|
||||||
if reflectType.Kind() != reflect.Struct {
|
|
||||||
return &modelStruct
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get Cached model struct
|
|
||||||
if value := modelStructsMap.Get(reflectType); value != nil {
|
|
||||||
return value
|
|
||||||
}
|
|
||||||
|
|
||||||
modelStruct.ModelType = reflectType
|
|
||||||
|
|
||||||
// Get all fields
|
|
||||||
for i := 0; i < reflectType.NumField(); i++ {
|
|
||||||
if fieldStruct := reflectType.Field(i); ast.IsExported(fieldStruct.Name) {
|
|
||||||
field := &StructField{
|
|
||||||
Struct: fieldStruct,
|
|
||||||
Name: fieldStruct.Name,
|
|
||||||
Names: []string{fieldStruct.Name},
|
|
||||||
Tag: fieldStruct.Tag,
|
|
||||||
TagSettings: parseTagSetting(fieldStruct.Tag),
|
|
||||||
}
|
|
||||||
|
|
||||||
// is ignored field
|
|
||||||
if _, ok := field.TagSettings["-"]; ok {
|
|
||||||
field.IsIgnored = true
|
|
||||||
} else {
|
|
||||||
if _, ok := field.TagSettings["PRIMARY_KEY"]; ok {
|
|
||||||
field.IsPrimaryKey = true
|
|
||||||
modelStruct.PrimaryFields = append(modelStruct.PrimaryFields, field)
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, ok := field.TagSettings["DEFAULT"]; ok {
|
|
||||||
field.HasDefaultValue = true
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, ok := field.TagSettings["AUTO_INCREMENT"]; ok && !field.IsPrimaryKey {
|
|
||||||
field.HasDefaultValue = true
|
|
||||||
}
|
|
||||||
|
|
||||||
indirectType := fieldStruct.Type
|
|
||||||
for indirectType.Kind() == reflect.Ptr {
|
|
||||||
indirectType = indirectType.Elem()
|
|
||||||
}
|
|
||||||
|
|
||||||
fieldValue := reflect.New(indirectType).Interface()
|
|
||||||
if _, isScanner := fieldValue.(sql.Scanner); isScanner {
|
|
||||||
// is scanner
|
|
||||||
field.IsScanner, field.IsNormal = true, true
|
|
||||||
if indirectType.Kind() == reflect.Struct {
|
|
||||||
for i := 0; i < indirectType.NumField(); i++ {
|
|
||||||
for key, value := range parseTagSetting(indirectType.Field(i).Tag) {
|
|
||||||
if _, ok := field.TagSettings[key]; !ok {
|
|
||||||
field.TagSettings[key] = value
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if _, isTime := fieldValue.(*time.Time); isTime {
|
|
||||||
// is time
|
|
||||||
field.IsNormal = true
|
|
||||||
} else if _, ok := field.TagSettings["EMBEDDED"]; ok || fieldStruct.Anonymous {
|
|
||||||
// is embedded struct
|
|
||||||
for _, subField := range scope.New(fieldValue).GetModelStruct().StructFields {
|
|
||||||
subField = subField.clone()
|
|
||||||
subField.Names = append([]string{fieldStruct.Name}, subField.Names...)
|
|
||||||
if prefix, ok := field.TagSettings["EMBEDDED_PREFIX"]; ok {
|
|
||||||
subField.DBName = prefix + subField.DBName
|
|
||||||
}
|
|
||||||
|
|
||||||
if subField.IsPrimaryKey {
|
|
||||||
if _, ok := subField.TagSettings["PRIMARY_KEY"]; ok {
|
|
||||||
modelStruct.PrimaryFields = append(modelStruct.PrimaryFields, subField)
|
|
||||||
} else {
|
|
||||||
subField.IsPrimaryKey = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if subField.Relationship != nil && subField.Relationship.JoinTableHandler != nil {
|
|
||||||
if joinTableHandler, ok := subField.Relationship.JoinTableHandler.(*JoinTableHandler); ok {
|
|
||||||
newJoinTableHandler := &JoinTableHandler{}
|
|
||||||
newJoinTableHandler.Setup(subField.Relationship, joinTableHandler.TableName, reflectType, joinTableHandler.Destination.ModelType)
|
|
||||||
subField.Relationship.JoinTableHandler = newJoinTableHandler
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
modelStruct.StructFields = append(modelStruct.StructFields, subField)
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
} else {
|
|
||||||
// build relationships
|
|
||||||
switch indirectType.Kind() {
|
|
||||||
case reflect.Slice:
|
|
||||||
defer func(field *StructField) {
|
|
||||||
var (
|
|
||||||
relationship = &Relationship{}
|
|
||||||
toScope = scope.New(reflect.New(field.Struct.Type).Interface())
|
|
||||||
foreignKeys []string
|
|
||||||
associationForeignKeys []string
|
|
||||||
elemType = field.Struct.Type
|
|
||||||
)
|
|
||||||
|
|
||||||
if foreignKey := field.TagSettings["FOREIGNKEY"]; foreignKey != "" {
|
|
||||||
foreignKeys = strings.Split(foreignKey, ",")
|
|
||||||
}
|
|
||||||
|
|
||||||
if foreignKey := field.TagSettings["ASSOCIATION_FOREIGNKEY"]; foreignKey != "" {
|
|
||||||
associationForeignKeys = strings.Split(foreignKey, ",")
|
|
||||||
} else if foreignKey := field.TagSettings["ASSOCIATIONFOREIGNKEY"]; foreignKey != "" {
|
|
||||||
associationForeignKeys = strings.Split(foreignKey, ",")
|
|
||||||
}
|
|
||||||
|
|
||||||
for elemType.Kind() == reflect.Slice || elemType.Kind() == reflect.Ptr {
|
|
||||||
elemType = elemType.Elem()
|
|
||||||
}
|
|
||||||
|
|
||||||
if elemType.Kind() == reflect.Struct {
|
|
||||||
if many2many := field.TagSettings["MANY2MANY"]; many2many != "" {
|
|
||||||
relationship.Kind = "many_to_many"
|
|
||||||
|
|
||||||
{ // Foreign Keys for Source
|
|
||||||
joinTableDBNames := []string{}
|
|
||||||
|
|
||||||
if foreignKey := field.TagSettings["JOINTABLE_FOREIGNKEY"]; foreignKey != "" {
|
|
||||||
joinTableDBNames = strings.Split(foreignKey, ",")
|
|
||||||
}
|
|
||||||
|
|
||||||
// if no foreign keys defined with tag
|
|
||||||
if len(foreignKeys) == 0 {
|
|
||||||
for _, field := range modelStruct.PrimaryFields {
|
|
||||||
foreignKeys = append(foreignKeys, field.DBName)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for idx, foreignKey := range foreignKeys {
|
|
||||||
if foreignField := getForeignField(foreignKey, modelStruct.StructFields); foreignField != nil {
|
|
||||||
// source foreign keys (db names)
|
|
||||||
relationship.ForeignFieldNames = append(relationship.ForeignFieldNames, foreignField.DBName)
|
|
||||||
|
|
||||||
// setup join table foreign keys for source
|
|
||||||
if len(joinTableDBNames) > idx {
|
|
||||||
// if defined join table's foreign key
|
|
||||||
relationship.ForeignDBNames = append(relationship.ForeignDBNames, joinTableDBNames[idx])
|
|
||||||
} else {
|
|
||||||
defaultJointableForeignKey := ToDBName(reflectType.Name()) + "_" + foreignField.DBName
|
|
||||||
relationship.ForeignDBNames = append(relationship.ForeignDBNames, defaultJointableForeignKey)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
{ // Foreign Keys for Association (Destination)
|
|
||||||
associationJoinTableDBNames := []string{}
|
|
||||||
|
|
||||||
if foreignKey := field.TagSettings["ASSOCIATION_JOINTABLE_FOREIGNKEY"]; foreignKey != "" {
|
|
||||||
associationJoinTableDBNames = strings.Split(foreignKey, ",")
|
|
||||||
}
|
|
||||||
|
|
||||||
// if no association foreign keys defined with tag
|
|
||||||
if len(associationForeignKeys) == 0 {
|
|
||||||
for _, field := range toScope.PrimaryFields() {
|
|
||||||
associationForeignKeys = append(associationForeignKeys, field.DBName)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for idx, name := range associationForeignKeys {
|
|
||||||
if field, ok := toScope.FieldByName(name); ok {
|
|
||||||
// association foreign keys (db names)
|
|
||||||
relationship.AssociationForeignFieldNames = append(relationship.AssociationForeignFieldNames, field.DBName)
|
|
||||||
|
|
||||||
// setup join table foreign keys for association
|
|
||||||
if len(associationJoinTableDBNames) > idx {
|
|
||||||
relationship.AssociationForeignDBNames = append(relationship.AssociationForeignDBNames, associationJoinTableDBNames[idx])
|
|
||||||
} else {
|
|
||||||
// join table foreign keys for association
|
|
||||||
joinTableDBName := ToDBName(elemType.Name()) + "_" + field.DBName
|
|
||||||
relationship.AssociationForeignDBNames = append(relationship.AssociationForeignDBNames, joinTableDBName)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
joinTableHandler := JoinTableHandler{}
|
|
||||||
joinTableHandler.Setup(relationship, many2many, reflectType, elemType)
|
|
||||||
relationship.JoinTableHandler = &joinTableHandler
|
|
||||||
field.Relationship = relationship
|
|
||||||
} else {
|
|
||||||
// User has many comments, associationType is User, comment use UserID as foreign key
|
|
||||||
var associationType = reflectType.Name()
|
|
||||||
var toFields = toScope.GetStructFields()
|
|
||||||
relationship.Kind = "has_many"
|
|
||||||
|
|
||||||
if polymorphic := field.TagSettings["POLYMORPHIC"]; polymorphic != "" {
|
|
||||||
// Dog has many toys, tag polymorphic is Owner, then associationType is Owner
|
|
||||||
// Toy use OwnerID, OwnerType ('dogs') as foreign key
|
|
||||||
if polymorphicType := getForeignField(polymorphic+"Type", toFields); polymorphicType != nil {
|
|
||||||
associationType = polymorphic
|
|
||||||
relationship.PolymorphicType = polymorphicType.Name
|
|
||||||
relationship.PolymorphicDBName = polymorphicType.DBName
|
|
||||||
// if Dog has multiple set of toys set name of the set (instead of default 'dogs')
|
|
||||||
if value, ok := field.TagSettings["POLYMORPHIC_VALUE"]; ok {
|
|
||||||
relationship.PolymorphicValue = value
|
|
||||||
} else {
|
|
||||||
relationship.PolymorphicValue = scope.TableName()
|
|
||||||
}
|
|
||||||
polymorphicType.IsForeignKey = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// if no foreign keys defined with tag
|
|
||||||
if len(foreignKeys) == 0 {
|
|
||||||
// if no association foreign keys defined with tag
|
|
||||||
if len(associationForeignKeys) == 0 {
|
|
||||||
for _, field := range modelStruct.PrimaryFields {
|
|
||||||
foreignKeys = append(foreignKeys, associationType+field.Name)
|
|
||||||
associationForeignKeys = append(associationForeignKeys, field.Name)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// generate foreign keys from defined association foreign keys
|
|
||||||
for _, scopeFieldName := range associationForeignKeys {
|
|
||||||
if foreignField := getForeignField(scopeFieldName, modelStruct.StructFields); foreignField != nil {
|
|
||||||
foreignKeys = append(foreignKeys, associationType+foreignField.Name)
|
|
||||||
associationForeignKeys = append(associationForeignKeys, foreignField.Name)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// generate association foreign keys from foreign keys
|
|
||||||
if len(associationForeignKeys) == 0 {
|
|
||||||
for _, foreignKey := range foreignKeys {
|
|
||||||
if strings.HasPrefix(foreignKey, associationType) {
|
|
||||||
associationForeignKey := strings.TrimPrefix(foreignKey, associationType)
|
|
||||||
if foreignField := getForeignField(associationForeignKey, modelStruct.StructFields); foreignField != nil {
|
|
||||||
associationForeignKeys = append(associationForeignKeys, associationForeignKey)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if len(associationForeignKeys) == 0 && len(foreignKeys) == 1 {
|
|
||||||
associationForeignKeys = []string{scope.PrimaryKey()}
|
|
||||||
}
|
|
||||||
} else if len(foreignKeys) != len(associationForeignKeys) {
|
|
||||||
scope.Err(errors.New("invalid foreign keys, should have same length"))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for idx, foreignKey := range foreignKeys {
|
|
||||||
if foreignField := getForeignField(foreignKey, toFields); foreignField != nil {
|
|
||||||
if associationField := getForeignField(associationForeignKeys[idx], modelStruct.StructFields); associationField != nil {
|
|
||||||
// source foreign keys
|
|
||||||
foreignField.IsForeignKey = true
|
|
||||||
relationship.AssociationForeignFieldNames = append(relationship.AssociationForeignFieldNames, associationField.Name)
|
|
||||||
relationship.AssociationForeignDBNames = append(relationship.AssociationForeignDBNames, associationField.DBName)
|
|
||||||
|
|
||||||
// association foreign keys
|
|
||||||
relationship.ForeignFieldNames = append(relationship.ForeignFieldNames, foreignField.Name)
|
|
||||||
relationship.ForeignDBNames = append(relationship.ForeignDBNames, foreignField.DBName)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(relationship.ForeignFieldNames) != 0 {
|
|
||||||
field.Relationship = relationship
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
field.IsNormal = true
|
|
||||||
}
|
|
||||||
}(field)
|
|
||||||
case reflect.Struct:
|
|
||||||
defer func(field *StructField) {
|
|
||||||
var (
|
|
||||||
// user has one profile, associationType is User, profile use UserID as foreign key
|
|
||||||
// user belongs to profile, associationType is Profile, user use ProfileID as foreign key
|
|
||||||
associationType = reflectType.Name()
|
|
||||||
relationship = &Relationship{}
|
|
||||||
toScope = scope.New(reflect.New(field.Struct.Type).Interface())
|
|
||||||
toFields = toScope.GetStructFields()
|
|
||||||
tagForeignKeys []string
|
|
||||||
tagAssociationForeignKeys []string
|
|
||||||
)
|
|
||||||
|
|
||||||
if foreignKey := field.TagSettings["FOREIGNKEY"]; foreignKey != "" {
|
|
||||||
tagForeignKeys = strings.Split(foreignKey, ",")
|
|
||||||
}
|
|
||||||
|
|
||||||
if foreignKey := field.TagSettings["ASSOCIATION_FOREIGNKEY"]; foreignKey != "" {
|
|
||||||
tagAssociationForeignKeys = strings.Split(foreignKey, ",")
|
|
||||||
} else if foreignKey := field.TagSettings["ASSOCIATIONFOREIGNKEY"]; foreignKey != "" {
|
|
||||||
tagAssociationForeignKeys = strings.Split(foreignKey, ",")
|
|
||||||
}
|
|
||||||
|
|
||||||
if polymorphic := field.TagSettings["POLYMORPHIC"]; polymorphic != "" {
|
|
||||||
// Cat has one toy, tag polymorphic is Owner, then associationType is Owner
|
|
||||||
// Toy use OwnerID, OwnerType ('cats') as foreign key
|
|
||||||
if polymorphicType := getForeignField(polymorphic+"Type", toFields); polymorphicType != nil {
|
|
||||||
associationType = polymorphic
|
|
||||||
relationship.PolymorphicType = polymorphicType.Name
|
|
||||||
relationship.PolymorphicDBName = polymorphicType.DBName
|
|
||||||
// if Cat has several different types of toys set name for each (instead of default 'cats')
|
|
||||||
if value, ok := field.TagSettings["POLYMORPHIC_VALUE"]; ok {
|
|
||||||
relationship.PolymorphicValue = value
|
|
||||||
} else {
|
|
||||||
relationship.PolymorphicValue = scope.TableName()
|
|
||||||
}
|
|
||||||
polymorphicType.IsForeignKey = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Has One
|
|
||||||
{
|
|
||||||
var foreignKeys = tagForeignKeys
|
|
||||||
var associationForeignKeys = tagAssociationForeignKeys
|
|
||||||
// if no foreign keys defined with tag
|
|
||||||
if len(foreignKeys) == 0 {
|
|
||||||
// if no association foreign keys defined with tag
|
|
||||||
if len(associationForeignKeys) == 0 {
|
|
||||||
for _, primaryField := range modelStruct.PrimaryFields {
|
|
||||||
foreignKeys = append(foreignKeys, associationType+primaryField.Name)
|
|
||||||
associationForeignKeys = append(associationForeignKeys, primaryField.Name)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// generate foreign keys form association foreign keys
|
|
||||||
for _, associationForeignKey := range tagAssociationForeignKeys {
|
|
||||||
if foreignField := getForeignField(associationForeignKey, modelStruct.StructFields); foreignField != nil {
|
|
||||||
foreignKeys = append(foreignKeys, associationType+foreignField.Name)
|
|
||||||
associationForeignKeys = append(associationForeignKeys, foreignField.Name)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// generate association foreign keys from foreign keys
|
|
||||||
if len(associationForeignKeys) == 0 {
|
|
||||||
for _, foreignKey := range foreignKeys {
|
|
||||||
if strings.HasPrefix(foreignKey, associationType) {
|
|
||||||
associationForeignKey := strings.TrimPrefix(foreignKey, associationType)
|
|
||||||
if foreignField := getForeignField(associationForeignKey, modelStruct.StructFields); foreignField != nil {
|
|
||||||
associationForeignKeys = append(associationForeignKeys, associationForeignKey)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if len(associationForeignKeys) == 0 && len(foreignKeys) == 1 {
|
|
||||||
associationForeignKeys = []string{scope.PrimaryKey()}
|
|
||||||
}
|
|
||||||
} else if len(foreignKeys) != len(associationForeignKeys) {
|
|
||||||
scope.Err(errors.New("invalid foreign keys, should have same length"))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for idx, foreignKey := range foreignKeys {
|
|
||||||
if foreignField := getForeignField(foreignKey, toFields); foreignField != nil {
|
|
||||||
if scopeField := getForeignField(associationForeignKeys[idx], modelStruct.StructFields); scopeField != nil {
|
|
||||||
foreignField.IsForeignKey = true
|
|
||||||
// source foreign keys
|
|
||||||
relationship.AssociationForeignFieldNames = append(relationship.AssociationForeignFieldNames, scopeField.Name)
|
|
||||||
relationship.AssociationForeignDBNames = append(relationship.AssociationForeignDBNames, scopeField.DBName)
|
|
||||||
|
|
||||||
// association foreign keys
|
|
||||||
relationship.ForeignFieldNames = append(relationship.ForeignFieldNames, foreignField.Name)
|
|
||||||
relationship.ForeignDBNames = append(relationship.ForeignDBNames, foreignField.DBName)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(relationship.ForeignFieldNames) != 0 {
|
|
||||||
relationship.Kind = "has_one"
|
|
||||||
field.Relationship = relationship
|
|
||||||
} else {
|
|
||||||
var foreignKeys = tagForeignKeys
|
|
||||||
var associationForeignKeys = tagAssociationForeignKeys
|
|
||||||
|
|
||||||
if len(foreignKeys) == 0 {
|
|
||||||
// generate foreign keys & association foreign keys
|
|
||||||
if len(associationForeignKeys) == 0 {
|
|
||||||
for _, primaryField := range toScope.PrimaryFields() {
|
|
||||||
foreignKeys = append(foreignKeys, field.Name+primaryField.Name)
|
|
||||||
associationForeignKeys = append(associationForeignKeys, primaryField.Name)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// generate foreign keys with association foreign keys
|
|
||||||
for _, associationForeignKey := range associationForeignKeys {
|
|
||||||
if foreignField := getForeignField(associationForeignKey, toFields); foreignField != nil {
|
|
||||||
foreignKeys = append(foreignKeys, field.Name+foreignField.Name)
|
|
||||||
associationForeignKeys = append(associationForeignKeys, foreignField.Name)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// generate foreign keys & association foreign keys
|
|
||||||
if len(associationForeignKeys) == 0 {
|
|
||||||
for _, foreignKey := range foreignKeys {
|
|
||||||
if strings.HasPrefix(foreignKey, field.Name) {
|
|
||||||
associationForeignKey := strings.TrimPrefix(foreignKey, field.Name)
|
|
||||||
if foreignField := getForeignField(associationForeignKey, toFields); foreignField != nil {
|
|
||||||
associationForeignKeys = append(associationForeignKeys, associationForeignKey)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if len(associationForeignKeys) == 0 && len(foreignKeys) == 1 {
|
|
||||||
associationForeignKeys = []string{toScope.PrimaryKey()}
|
|
||||||
}
|
|
||||||
} else if len(foreignKeys) != len(associationForeignKeys) {
|
|
||||||
scope.Err(errors.New("invalid foreign keys, should have same length"))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for idx, foreignKey := range foreignKeys {
|
|
||||||
if foreignField := getForeignField(foreignKey, modelStruct.StructFields); foreignField != nil {
|
|
||||||
if associationField := getForeignField(associationForeignKeys[idx], toFields); associationField != nil {
|
|
||||||
foreignField.IsForeignKey = true
|
|
||||||
|
|
||||||
// association foreign keys
|
|
||||||
relationship.AssociationForeignFieldNames = append(relationship.AssociationForeignFieldNames, associationField.Name)
|
|
||||||
relationship.AssociationForeignDBNames = append(relationship.AssociationForeignDBNames, associationField.DBName)
|
|
||||||
|
|
||||||
// source foreign keys
|
|
||||||
relationship.ForeignFieldNames = append(relationship.ForeignFieldNames, foreignField.Name)
|
|
||||||
relationship.ForeignDBNames = append(relationship.ForeignDBNames, foreignField.DBName)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(relationship.ForeignFieldNames) != 0 {
|
|
||||||
relationship.Kind = "belongs_to"
|
|
||||||
field.Relationship = relationship
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}(field)
|
|
||||||
default:
|
|
||||||
field.IsNormal = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Even it is ignored, also possible to decode db value into the field
|
|
||||||
if value, ok := field.TagSettings["COLUMN"]; ok {
|
|
||||||
field.DBName = value
|
|
||||||
} else {
|
|
||||||
field.DBName = ToDBName(fieldStruct.Name)
|
|
||||||
}
|
|
||||||
|
|
||||||
modelStruct.StructFields = append(modelStruct.StructFields, field)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(modelStruct.PrimaryFields) == 0 {
|
|
||||||
if field := getForeignField("id", modelStruct.StructFields); field != nil {
|
|
||||||
field.IsPrimaryKey = true
|
|
||||||
modelStruct.PrimaryFields = append(modelStruct.PrimaryFields, field)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
modelStructsMap.Set(reflectType, &modelStruct)
|
|
||||||
|
|
||||||
return &modelStruct
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetStructFields get model's field structs
|
|
||||||
func (scope *Scope) GetStructFields() (fields []*StructField) {
|
|
||||||
return scope.GetModelStruct().StructFields
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseTagSetting(tags reflect.StructTag) map[string]string {
|
|
||||||
setting := map[string]string{}
|
|
||||||
for _, str := range []string{tags.Get("sql"), tags.Get("gorm")} {
|
|
||||||
tags := strings.Split(str, ";")
|
|
||||||
for _, value := range tags {
|
|
||||||
v := strings.Split(value, ":")
|
|
||||||
k := strings.TrimSpace(strings.ToUpper(v[0]))
|
|
||||||
if len(v) >= 2 {
|
|
||||||
setting[k] = strings.Join(v[1:], ":")
|
|
||||||
} else {
|
|
||||||
setting[k] = k
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return setting
|
|
||||||
}
|
|
349
schema/relationship.go
Normal file
349
schema/relationship.go
Normal file
@ -0,0 +1,349 @@
|
|||||||
|
package schema
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"reflect"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Relationship described the relationship between models
|
||||||
|
type Relationship struct {
|
||||||
|
Kind string
|
||||||
|
PolymorphicType string
|
||||||
|
PolymorphicDBName string
|
||||||
|
PolymorphicValue string
|
||||||
|
ForeignKey []string
|
||||||
|
AssociationForeignKey []string
|
||||||
|
JointableForeignkey []string
|
||||||
|
AssociationJointableForeignkey []string
|
||||||
|
}
|
||||||
|
|
||||||
|
func buildToOneRel(field *Field, sourceSchema *Schema) error {
|
||||||
|
var (
|
||||||
|
// user has one profile, associationType is User, profile use UserID as foreign key
|
||||||
|
// user belongs to profile, associationType is Profile, user use ProfileID as foreign key
|
||||||
|
relationship = &Relationship{}
|
||||||
|
associationType = sourceSchema.ModelType.Name()
|
||||||
|
destSchema = ParseSchema(reflect.New(field.StructField.Type).Interface())
|
||||||
|
tagForeignKeys, tagAssociationForeignKeys []string
|
||||||
|
)
|
||||||
|
|
||||||
|
if val := field.TagSettings["REL"]; val != "" {
|
||||||
|
relationship.Kind = strings.ToLower(strings.TrimSpace(val))
|
||||||
|
}
|
||||||
|
|
||||||
|
if val := field.TagSettings["FOREIGNKEY"]; val != "" {
|
||||||
|
tagForeignKeys = toColumns(val)
|
||||||
|
}
|
||||||
|
|
||||||
|
if val := field.TagSettings["ASSOCIATION_FOREIGNKEY"]; val != "" {
|
||||||
|
tagAssociationForeignKeys = toColumns(val)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cat has one Toy, its `Polymorphic` is `Owner`, then the associationType should be Owner
|
||||||
|
// Toy will use field `OwnerID`, `OwnerType` ('cats') as foreign key
|
||||||
|
if val := field.TagSettings["POLYMORPHIC"]; val != "" {
|
||||||
|
if polymorphicTypeField := getSchemaField(val+"Type", destSchema.Fields); polymorphicTypeField != nil {
|
||||||
|
associationType = val
|
||||||
|
relationship.PolymorphicType = polymorphicTypeField.Name
|
||||||
|
relationship.PolymorphicDBName = polymorphicTypeField.DBName
|
||||||
|
// if Cat has several different types of toys set name for each (instead of default 'cats')
|
||||||
|
if value, ok := field.TagSettings["POLYMORPHIC_VALUE"]; ok {
|
||||||
|
relationship.PolymorphicValue = value
|
||||||
|
} else {
|
||||||
|
relationship.PolymorphicValue = ToDBName(sourceSchema.ModelType.Name())
|
||||||
|
}
|
||||||
|
polymorphicTypeField.IsForeignKey = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Has One
|
||||||
|
if (relationship.Kind == "") || (relationship.Kind == "has_one") {
|
||||||
|
foreignKeys := tagForeignKeys
|
||||||
|
associationForeignKeys := tagAssociationForeignKeys
|
||||||
|
|
||||||
|
// if no foreign keys defined with tag
|
||||||
|
if len(foreignKeys) == 0 {
|
||||||
|
// if no association foreign keys defined with tag
|
||||||
|
if len(associationForeignKeys) == 0 {
|
||||||
|
for _, primaryField := range sourceSchema.PrimaryFields {
|
||||||
|
foreignKeys = append(foreignKeys, associationType+primaryField.Name)
|
||||||
|
associationForeignKeys = append(associationForeignKeys, primaryField.Name)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// generate foreign keys form association foreign keys
|
||||||
|
for _, associationForeignKey := range tagAssociationForeignKeys {
|
||||||
|
if foreignField := getSchemaField(associationForeignKey, sourceSchema.Fields); foreignField != nil {
|
||||||
|
foreignKeys = append(foreignKeys, associationType+foreignField.Name)
|
||||||
|
associationForeignKeys = append(associationForeignKeys, foreignField.Name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// generate association foreign keys from foreign keys
|
||||||
|
if len(associationForeignKeys) == 0 {
|
||||||
|
for _, foreignKey := range foreignKeys {
|
||||||
|
if strings.HasPrefix(foreignKey, associationType) {
|
||||||
|
associationForeignKey := strings.TrimPrefix(foreignKey, associationType)
|
||||||
|
if foreignField := getSchemaField(associationForeignKey, sourceSchema.Fields); foreignField != nil {
|
||||||
|
associationForeignKeys = append(associationForeignKeys, associationForeignKey)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(associationForeignKeys) == 0 && len(foreignKeys) == 1 {
|
||||||
|
associationForeignKeys = []string{getPrimaryPrimaryField(sourceSchema.PrimaryFields).DBName}
|
||||||
|
}
|
||||||
|
} else if len(foreignKeys) != len(associationForeignKeys) {
|
||||||
|
return errors.New("invalid foreign keys, should have same length")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for idx, foreignKey := range foreignKeys {
|
||||||
|
if foreignField := getSchemaField(foreignKey, destSchema.Fields); foreignField != nil {
|
||||||
|
if sourceField := getSchemaField(associationForeignKeys[idx], sourceSchema.Fields); sourceField != nil {
|
||||||
|
foreignField.IsForeignKey = true
|
||||||
|
// source foreign keys
|
||||||
|
relationship.AssociationForeignKey = append(relationship.AssociationForeignKey, sourceField.DBName)
|
||||||
|
|
||||||
|
// association foreign keys
|
||||||
|
relationship.ForeignKey = append(relationship.ForeignKey, foreignField.DBName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(relationship.ForeignKey) != 0 {
|
||||||
|
relationship.Kind = "has_one"
|
||||||
|
field.Relationship = relationship
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Belongs To
|
||||||
|
if (relationship.Kind == "") || (relationship.Kind == "belongs_to") {
|
||||||
|
foreignKeys := tagForeignKeys
|
||||||
|
associationForeignKeys := tagAssociationForeignKeys
|
||||||
|
|
||||||
|
if len(foreignKeys) == 0 {
|
||||||
|
// generate foreign keys & association foreign keys
|
||||||
|
if len(associationForeignKeys) == 0 {
|
||||||
|
for _, primaryField := range destSchema.PrimaryFields {
|
||||||
|
foreignKeys = append(foreignKeys, field.Name+primaryField.Name)
|
||||||
|
associationForeignKeys = append(associationForeignKeys, primaryField.Name)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// generate foreign keys with association foreign keys
|
||||||
|
for _, associationForeignKey := range associationForeignKeys {
|
||||||
|
if foreignField := getSchemaField(associationForeignKey, destSchema.Fields); foreignField != nil {
|
||||||
|
foreignKeys = append(foreignKeys, field.Name+foreignField.Name)
|
||||||
|
associationForeignKeys = append(associationForeignKeys, foreignField.Name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// generate foreign keys & association foreign keys
|
||||||
|
if len(associationForeignKeys) == 0 {
|
||||||
|
for _, foreignKey := range foreignKeys {
|
||||||
|
if strings.HasPrefix(foreignKey, field.Name) {
|
||||||
|
associationForeignKey := strings.TrimPrefix(foreignKey, field.Name)
|
||||||
|
if foreignField := getSchemaField(associationForeignKey, destSchema.Fields); foreignField != nil {
|
||||||
|
associationForeignKeys = append(associationForeignKeys, associationForeignKey)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(associationForeignKeys) == 0 && len(foreignKeys) == 1 {
|
||||||
|
associationForeignKeys = []string{getPrimaryPrimaryField(destSchema.PrimaryFields).DBName}
|
||||||
|
}
|
||||||
|
} else if len(foreignKeys) != len(associationForeignKeys) {
|
||||||
|
return errors.New("invalid foreign keys, should have same length")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for idx, foreignKey := range foreignKeys {
|
||||||
|
if foreignField := getSchemaField(foreignKey, sourceSchema.Fields); foreignField != nil {
|
||||||
|
if associationField := getSchemaField(associationForeignKeys[idx], destSchema.Fields); associationField != nil {
|
||||||
|
foreignField.IsForeignKey = true
|
||||||
|
|
||||||
|
// association foreign keys
|
||||||
|
relationship.AssociationForeignKey = append(relationship.AssociationForeignKey, associationField.DBName)
|
||||||
|
|
||||||
|
// source foreign keys
|
||||||
|
relationship.ForeignKey = append(relationship.ForeignKey, foreignField.DBName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(relationship.ForeignKey) != 0 {
|
||||||
|
relationship.Kind = "belongs_to"
|
||||||
|
field.Relationship = relationship
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func buildToManyRel(field *Field, sourceSchema *Schema) error {
|
||||||
|
var (
|
||||||
|
relationship = &Relationship{}
|
||||||
|
elemType = field.StructField.Type
|
||||||
|
destSchema = ParseSchema(reflect.New(elemType).Interface())
|
||||||
|
foreignKeys, associationForeignKeys []string
|
||||||
|
)
|
||||||
|
|
||||||
|
if val := field.TagSettings["FOREIGNKEY"]; val != "" {
|
||||||
|
foreignKeys = toColumns(val)
|
||||||
|
}
|
||||||
|
|
||||||
|
if val := field.TagSettings["ASSOCIATION_FOREIGNKEY"]; val != "" {
|
||||||
|
associationForeignKeys = toColumns(val)
|
||||||
|
}
|
||||||
|
|
||||||
|
for elemType.Kind() == reflect.Slice || elemType.Kind() == reflect.Ptr {
|
||||||
|
elemType = elemType.Elem()
|
||||||
|
}
|
||||||
|
|
||||||
|
if elemType.Kind() == reflect.Struct {
|
||||||
|
if many2many := field.TagSettings["MANY2MANY"]; many2many != "" {
|
||||||
|
relationship.Kind = "many_to_many"
|
||||||
|
|
||||||
|
{ // Foreign Keys for Source
|
||||||
|
joinTableDBNames := []string{}
|
||||||
|
|
||||||
|
if val := field.TagSettings["JOINTABLE_FOREIGNKEY"]; val != "" {
|
||||||
|
joinTableDBNames = toColumns(val)
|
||||||
|
}
|
||||||
|
|
||||||
|
// if no foreign keys defined with tag
|
||||||
|
if len(foreignKeys) == 0 {
|
||||||
|
for _, field := range sourceSchema.PrimaryFields {
|
||||||
|
foreignKeys = append(foreignKeys, field.DBName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for idx, foreignKey := range foreignKeys {
|
||||||
|
if foreignField := getSchemaField(foreignKey, sourceSchema.Fields); foreignField != nil {
|
||||||
|
// source foreign keys (db names)
|
||||||
|
relationship.ForeignKey = append(relationship.ForeignKey, foreignField.DBName)
|
||||||
|
|
||||||
|
// setup join table foreign keys for source
|
||||||
|
// if defined join table's foreign key with tag
|
||||||
|
if len(joinTableDBNames) > idx {
|
||||||
|
relationship.JointableForeignkey = append(relationship.JointableForeignkey, joinTableDBNames[idx])
|
||||||
|
} else {
|
||||||
|
defaultJointableForeignKey := ToDBName(sourceSchema.ModelType.Name()) + "_" + foreignField.DBName
|
||||||
|
relationship.JointableForeignkey = append(relationship.JointableForeignkey, defaultJointableForeignKey)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
{ // Foreign Keys for Association (Destination)
|
||||||
|
associationJoinTableDBNames := []string{}
|
||||||
|
|
||||||
|
if foreignKey := field.TagSettings["ASSOCIATION_JOINTABLE_FOREIGNKEY"]; foreignKey != "" {
|
||||||
|
associationJoinTableDBNames = strings.Split(foreignKey, ",")
|
||||||
|
}
|
||||||
|
|
||||||
|
// if no association foreign keys defined with tag
|
||||||
|
if len(associationForeignKeys) == 0 {
|
||||||
|
for _, field := range destSchema.PrimaryFields {
|
||||||
|
associationForeignKeys = append(associationForeignKeys, field.DBName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for idx, name := range associationForeignKeys {
|
||||||
|
if field := getSchemaField(name, destSchema.Fields); field != nil {
|
||||||
|
// association foreign keys (db names)
|
||||||
|
relationship.AssociationForeignKey = append(relationship.AssociationForeignKey, field.DBName)
|
||||||
|
|
||||||
|
// setup join table foreign keys for association
|
||||||
|
if len(associationJoinTableDBNames) > idx {
|
||||||
|
relationship.AssociationJointableForeignkey = append(relationship.AssociationJointableForeignkey, associationJoinTableDBNames[idx])
|
||||||
|
} else {
|
||||||
|
// join table foreign keys for association
|
||||||
|
joinTableDBName := ToDBName(elemType.Name()) + "_" + field.DBName
|
||||||
|
relationship.AssociationJointableForeignkey = append(relationship.AssociationJointableForeignkey, joinTableDBName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
field.Relationship = relationship
|
||||||
|
} else {
|
||||||
|
// User has many comments, associationType is User, comment use UserID as foreign key
|
||||||
|
associationType := sourceSchema.ModelType.Name()
|
||||||
|
relationship.Kind = "has_many"
|
||||||
|
|
||||||
|
if polymorphic := field.TagSettings["POLYMORPHIC"]; polymorphic != "" {
|
||||||
|
// Dog has many toys, tag polymorphic is Owner, then associationType is Owner
|
||||||
|
// Toy use OwnerID, OwnerType ('dogs') as foreign key
|
||||||
|
if polymorphicType := getSchemaField(polymorphic+"Type", destSchema.Fields); polymorphicType != nil {
|
||||||
|
associationType = polymorphic
|
||||||
|
relationship.PolymorphicType = polymorphicType.Name
|
||||||
|
relationship.PolymorphicDBName = polymorphicType.DBName
|
||||||
|
// if Dog has multiple set of toys set name of the set (instead of default 'dogs')
|
||||||
|
if value, ok := field.TagSettings["POLYMORPHIC_VALUE"]; ok {
|
||||||
|
relationship.PolymorphicValue = value
|
||||||
|
} else {
|
||||||
|
relationship.PolymorphicValue = ToDBName(sourceSchema.ModelType.Name())
|
||||||
|
}
|
||||||
|
polymorphicType.IsForeignKey = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// if no foreign keys defined with tag
|
||||||
|
if len(foreignKeys) == 0 {
|
||||||
|
// if no association foreign keys defined with tag
|
||||||
|
if len(associationForeignKeys) == 0 {
|
||||||
|
for _, field := range sourceSchema.PrimaryFields {
|
||||||
|
foreignKeys = append(foreignKeys, associationType+field.Name)
|
||||||
|
associationForeignKeys = append(associationForeignKeys, field.Name)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// generate foreign keys from defined association foreign keys
|
||||||
|
for _, scopeFieldName := range associationForeignKeys {
|
||||||
|
if foreignField := getSchemaField(scopeFieldName, sourceSchema.Fields); foreignField != nil {
|
||||||
|
foreignKeys = append(foreignKeys, associationType+foreignField.Name)
|
||||||
|
associationForeignKeys = append(associationForeignKeys, foreignField.Name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// generate association foreign keys from foreign keys
|
||||||
|
if len(associationForeignKeys) == 0 {
|
||||||
|
for _, foreignKey := range foreignKeys {
|
||||||
|
if strings.HasPrefix(foreignKey, associationType) {
|
||||||
|
associationForeignKey := strings.TrimPrefix(foreignKey, associationType)
|
||||||
|
if foreignField := getSchemaField(associationForeignKey, sourceSchema.Fields); foreignField != nil {
|
||||||
|
associationForeignKeys = append(associationForeignKeys, associationForeignKey)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(associationForeignKeys) == 0 && len(foreignKeys) == 1 {
|
||||||
|
associationForeignKeys = []string{getPrimaryPrimaryField(sourceSchema.PrimaryFields).DBName}
|
||||||
|
}
|
||||||
|
} else if len(foreignKeys) != len(associationForeignKeys) {
|
||||||
|
return errors.New("invalid foreign keys, should have same length")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for idx, foreignKey := range foreignKeys {
|
||||||
|
if foreignField := getSchemaField(foreignKey, destSchema.Fields); foreignField != nil {
|
||||||
|
if associationField := getSchemaField(associationForeignKeys[idx], sourceSchema.Fields); associationField != nil {
|
||||||
|
// source foreign keys
|
||||||
|
foreignField.IsForeignKey = true
|
||||||
|
relationship.AssociationForeignKey = append(relationship.AssociationForeignKey, associationField.DBName)
|
||||||
|
|
||||||
|
// association foreign keys
|
||||||
|
relationship.ForeignKey = append(relationship.ForeignKey, foreignField.DBName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(relationship.ForeignKey) != 0 {
|
||||||
|
field.Relationship = relationship
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
field.IsNormal = true
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
177
schema/schema.go
Normal file
177
schema/schema.go
Normal file
@ -0,0 +1,177 @@
|
|||||||
|
package schema
|
||||||
|
|
||||||
|
import (
|
||||||
|
"database/sql"
|
||||||
|
"go/ast"
|
||||||
|
"reflect"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Schema model schema definition
|
||||||
|
type Schema struct {
|
||||||
|
ModelType reflect.Type
|
||||||
|
PrimaryFields []*Field
|
||||||
|
Fields []*Field
|
||||||
|
TableName string
|
||||||
|
ParseErrors []error
|
||||||
|
}
|
||||||
|
|
||||||
|
// Field schema field definition
|
||||||
|
type Field struct {
|
||||||
|
DBName string
|
||||||
|
Name string
|
||||||
|
BindNames []string
|
||||||
|
Tag reflect.StructTag
|
||||||
|
TagSettings map[string]string
|
||||||
|
IsNormal bool
|
||||||
|
IsPrimaryKey bool
|
||||||
|
IsIgnored bool
|
||||||
|
IsForeignKey bool
|
||||||
|
DefaultValue string
|
||||||
|
HasDefaultValue bool
|
||||||
|
StructField reflect.StructField
|
||||||
|
Relationship *Relationship
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseSchema parse struct and generate schema based on struct and tag definition
|
||||||
|
func ParseSchema(dest interface{}) *Schema {
|
||||||
|
schema := Schema{}
|
||||||
|
|
||||||
|
// Get dest type
|
||||||
|
reflectType := reflect.ValueOf(dest).Type()
|
||||||
|
for reflectType.Kind() == reflect.Slice || reflectType.Kind() == reflect.Ptr {
|
||||||
|
reflectType = reflectType.Elem()
|
||||||
|
}
|
||||||
|
|
||||||
|
if reflectType.Kind() != reflect.Struct {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
schema.ModelType = reflectType
|
||||||
|
|
||||||
|
for i := 0; i < reflectType.NumField(); i++ {
|
||||||
|
fieldStruct := reflectType.Field(i)
|
||||||
|
if !ast.IsExported(fieldStruct.Name) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
field := &Field{
|
||||||
|
Name: fieldStruct.Name,
|
||||||
|
BindNames: []string{fieldStruct.Name},
|
||||||
|
StructField: fieldStruct,
|
||||||
|
Tag: fieldStruct.Tag,
|
||||||
|
TagSettings: parseTagSetting(fieldStruct.Tag),
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, ok := field.TagSettings["-"]; ok {
|
||||||
|
field.IsIgnored = true
|
||||||
|
} else {
|
||||||
|
if val, ok := field.TagSettings["PRIMARY_KEY"]; ok && checkTruth(val) {
|
||||||
|
field.IsPrimaryKey = true
|
||||||
|
schema.PrimaryFields = append(schema.PrimaryFields, field)
|
||||||
|
}
|
||||||
|
|
||||||
|
if val, ok := field.TagSettings["AUTO_INCREMENT"]; ok && checkTruth(val) && !field.IsPrimaryKey {
|
||||||
|
field.HasDefaultValue = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if v, ok := field.TagSettings["DEFAULT"]; ok {
|
||||||
|
field.DefaultValue = v
|
||||||
|
field.HasDefaultValue = true
|
||||||
|
}
|
||||||
|
|
||||||
|
indirectType := fieldStruct.Type
|
||||||
|
for indirectType.Kind() == reflect.Ptr {
|
||||||
|
indirectType = indirectType.Elem()
|
||||||
|
}
|
||||||
|
|
||||||
|
fieldValue := reflect.New(indirectType).Interface()
|
||||||
|
if _, isScanner := fieldValue.(sql.Scanner); isScanner {
|
||||||
|
// scanner
|
||||||
|
field.IsNormal = true
|
||||||
|
if indirectType.Kind() == reflect.Struct {
|
||||||
|
// Use tag settings from scanner
|
||||||
|
for i := 0; i < indirectType.NumField(); i++ {
|
||||||
|
for key, value := range parseTagSetting(indirectType.Field(i).Tag) {
|
||||||
|
if _, ok := field.TagSettings[key]; !ok {
|
||||||
|
field.TagSettings[key] = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if _, isTime := fieldValue.(*time.Time); isTime {
|
||||||
|
// time
|
||||||
|
field.IsNormal = true
|
||||||
|
} else if _, ok := field.TagSettings["EMBEDDED"]; ok || fieldStruct.Anonymous {
|
||||||
|
// embedded struct
|
||||||
|
if subSchema := ParseSchema(fieldValue); subSchema != nil {
|
||||||
|
for _, subField := range subSchema.Fields {
|
||||||
|
subField = subField.clone()
|
||||||
|
subField.BindNames = append([]string{fieldStruct.Name}, subField.BindNames...)
|
||||||
|
if prefix, ok := field.TagSettings["EMBEDDED_PREFIX"]; ok {
|
||||||
|
subField.DBName = prefix + subField.DBName
|
||||||
|
}
|
||||||
|
|
||||||
|
if subField.IsPrimaryKey {
|
||||||
|
if _, ok := subField.TagSettings["PRIMARY_KEY"]; ok {
|
||||||
|
schema.PrimaryFields = append(schema.PrimaryFields, subField)
|
||||||
|
} else {
|
||||||
|
subField.IsPrimaryKey = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for key, value := range field.TagSettings {
|
||||||
|
subField.TagSettings[key] = value
|
||||||
|
}
|
||||||
|
|
||||||
|
schema.Fields = append(schema.Fields, subField)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
} else {
|
||||||
|
// build relationships
|
||||||
|
switch indirectType.Kind() {
|
||||||
|
case reflect.Struct:
|
||||||
|
defer buildToOneRel(field, &schema)
|
||||||
|
case reflect.Slice:
|
||||||
|
defer buildToManyRel(field, &schema)
|
||||||
|
default:
|
||||||
|
field.IsNormal = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Even it is ignored, also possible to decode db value into the field
|
||||||
|
if dbName, ok := field.TagSettings["COLUMN"]; ok {
|
||||||
|
field.DBName = dbName
|
||||||
|
} else if field.DBName == "" {
|
||||||
|
field.DBName = ToDBName(fieldStruct.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
schema.Fields = append(schema.Fields, field)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(schema.PrimaryFields) == 0 {
|
||||||
|
if field := getSchemaField("id", schema.Fields); field != nil {
|
||||||
|
field.IsPrimaryKey = true
|
||||||
|
schema.PrimaryFields = append(schema.PrimaryFields, field)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return &schema
|
||||||
|
}
|
||||||
|
|
||||||
|
func (schemaField *Field) clone() *Field {
|
||||||
|
clone := *schemaField
|
||||||
|
|
||||||
|
if schemaField.Relationship != nil {
|
||||||
|
relationship := *schemaField.Relationship
|
||||||
|
clone.Relationship = &relationship
|
||||||
|
}
|
||||||
|
|
||||||
|
for key, value := range schemaField.TagSettings {
|
||||||
|
clone.TagSettings[key] = value
|
||||||
|
}
|
||||||
|
|
||||||
|
return &clone
|
||||||
|
}
|
20
schema/schema_test.go
Normal file
20
schema/schema_test.go
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
package schema
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/davecgh/go-spew/spew"
|
||||||
|
)
|
||||||
|
|
||||||
|
type MyStruct struct {
|
||||||
|
ID int
|
||||||
|
Name string
|
||||||
|
}
|
||||||
|
|
||||||
|
type BelongTo struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseSchema(t *testing.T) {
|
||||||
|
Schema := ParseSchema(&MyStruct{})
|
||||||
|
spew.Dump(Schema)
|
||||||
|
}
|
101
schema/utils.go
Normal file
101
schema/utils.go
Normal file
@ -0,0 +1,101 @@
|
|||||||
|
package schema
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"reflect"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type strCase bool
|
||||||
|
|
||||||
|
const (
|
||||||
|
lower strCase = false
|
||||||
|
upper strCase = true
|
||||||
|
)
|
||||||
|
|
||||||
|
// ToDBName convert str to db name
|
||||||
|
func ToDBName(name string) string {
|
||||||
|
if name == "" {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
var (
|
||||||
|
value = name
|
||||||
|
buf = bytes.NewBufferString("")
|
||||||
|
lastCase, currCase, nextCase strCase
|
||||||
|
)
|
||||||
|
|
||||||
|
for i, v := range value[:len(value)-1] {
|
||||||
|
nextCase = strCase(value[i+1] >= 'A' && value[i+1] <= 'Z')
|
||||||
|
if i > 0 {
|
||||||
|
if currCase == upper {
|
||||||
|
if lastCase == upper && nextCase == upper {
|
||||||
|
buf.WriteRune(v)
|
||||||
|
} else {
|
||||||
|
if value[i-1] != '_' && value[i+1] != '_' {
|
||||||
|
buf.WriteRune('_')
|
||||||
|
}
|
||||||
|
buf.WriteRune(v)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
buf.WriteRune(v)
|
||||||
|
if i == len(value)-2 && nextCase == upper {
|
||||||
|
buf.WriteRune('_')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
currCase = upper
|
||||||
|
buf.WriteRune(v)
|
||||||
|
}
|
||||||
|
lastCase = currCase
|
||||||
|
currCase = nextCase
|
||||||
|
}
|
||||||
|
|
||||||
|
buf.WriteByte(value[len(value)-1])
|
||||||
|
return strings.ToLower(buf.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkTruth(val string) bool {
|
||||||
|
if strings.ToLower(val) == "false" {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func toColumns(val string) []string {
|
||||||
|
return strings.Split(val, ",")
|
||||||
|
}
|
||||||
|
|
||||||
|
func getSchemaField(name string, fields []*Field) *Field {
|
||||||
|
for _, field := range fields {
|
||||||
|
if field.Name == name || field.DBName == name {
|
||||||
|
return field
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getPrimaryPrimaryField(fields []*Field) *Field {
|
||||||
|
for _, field := range fields {
|
||||||
|
if field.DBName == "id" {
|
||||||
|
return field
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return fields[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseTagSetting(tags reflect.StructTag) map[string]string {
|
||||||
|
setting := map[string]string{}
|
||||||
|
for _, str := range []string{tags.Get("sql"), tags.Get("gorm")} {
|
||||||
|
tags := strings.Split(str, ";")
|
||||||
|
for _, value := range tags {
|
||||||
|
v := strings.Split(value, ":")
|
||||||
|
k := strings.TrimSpace(strings.ToUpper(v[0]))
|
||||||
|
if len(v) >= 2 {
|
||||||
|
setting[k] = strings.Join(v[1:], ":")
|
||||||
|
} else {
|
||||||
|
setting[k] = k
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return setting
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user