Refactor ParseSchema
This commit is contained in:
		
							parent
							
								
									325bb5ac96
								
							
						
					
					
						commit
						bf0cec734f
					
				@ -6,6 +6,11 @@ import (
 | 
			
		||||
	"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
 | 
			
		||||
func GetCreatingAssignments(stmt *builder.Statement, errs *gorm.Errors) chan []model.Field {
 | 
			
		||||
	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 {
 | 
			
		||||
	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