
Improved questionmark parameter placeholder replacementw Added support for subqueries in Where and Having clauses Queries can be transformed into subqueries by calling .Subquery() on a db object See main_test.go:TestQueryBuilderSubselectInWhere Fixed comment spacing Refactoring, adding Having Subquery support, allowing db.T for tablenames Removed quoting from tablename in db.T, use db.QT for that Refactoring, adding Having Subquery support, allowing db.T for tablenames Added changes Started with expression extension Refactoring, adding Having Subquery support, allowing db.T for tablenames Added method to easily update fields of the Model struct Added column comparison and Join support Added subquery support for InnerJoin querybuilder Fixed column comparison Added support for column prefixes Models can set their column prefix by implementing the method ColumnPrefix() string Fixed multi-parameter subselects and introduced aliasing Improved Related method Improved Related method to search for foreign key struct fields with the suffix "ID" (additional to "Id") Got QueryExpr support from upstream Added support for subqueries in Where and Having clauses Queries can be transformed into subqueries by calling .Subquery() on a db object See main_test.go:TestQueryBuilderSubselectInWhere Improved questionmark parameter placeholder replacementw Refactoring, adding Having Subquery support, allowing db.T for tablenames Removed quoting from tablename in db.T, use db.QT for that Removed quoting from tablename in db.T, use db.QT for that Added changes Added method to easily update fields of the Model struct Fixed column comparison Added support for column prefixes Models can set their column prefix by implementing the method ColumnPrefix() string Fixed multi-parameter subselects and introduced aliasing Improved Related method Improved Related method to search for foreign key struct fields with the suffix "ID" (additional to "Id") Added select extension for multiple columns Added support for LEFT RIGHT OUTER joins Fixed slice support for lexpr.In() Publizised LExpr Added DateFormatting for all dialects Added SUM function for columns Fixed FormatDate Added count for column Removed literal expressions LExpr Rewrote LExpr methods to work with expr structs. Added methods BAnd and BOr (bitwise & and | ) Added SetLogWriter method Added NotIn query expression Added Distinct query expression Added DistinctColumn query expression Same as Distinct but returns a string Added method OnExp to jexpr Improved query expression .Eq() method for nil pointers Fixed rebase errors
635 lines
24 KiB
Go
635 lines
24 KiB
Go
package gorm
|
|
|
|
import (
|
|
"database/sql"
|
|
"errors"
|
|
"go/ast"
|
|
"reflect"
|
|
"strings"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/jinzhu/inflection"
|
|
)
|
|
|
|
// DefaultTableNameHandler default table name handler
|
|
var DefaultTableNameHandler = func(db *DB, 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
|
|
|
|
var columnPrefix string
|
|
if columnPrefixer, ok := reflect.New(modelStruct.ModelType).Interface().(columnPrefixer); ok {
|
|
columnPrefix = columnPrefixer.ColumnPrefix()
|
|
}
|
|
|
|
// 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 = columnPrefix + 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
|
|
}
|