765 lines
		
	
	
		
			22 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			765 lines
		
	
	
		
			22 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
package schema
 | 
						|
 | 
						|
import (
 | 
						|
	"context"
 | 
						|
	"fmt"
 | 
						|
	"reflect"
 | 
						|
	"strings"
 | 
						|
 | 
						|
	"github.com/jinzhu/inflection"
 | 
						|
	"gorm.io/gorm/clause"
 | 
						|
)
 | 
						|
 | 
						|
// RelationshipType relationship type
 | 
						|
type RelationshipType string
 | 
						|
 | 
						|
const (
 | 
						|
	HasOne    RelationshipType = "has_one"      // HasOneRel has one relationship
 | 
						|
	HasMany   RelationshipType = "has_many"     // HasManyRel has many relationship
 | 
						|
	BelongsTo RelationshipType = "belongs_to"   // BelongsToRel belongs to relationship
 | 
						|
	Many2Many RelationshipType = "many_to_many" // Many2ManyRel many to many relationship
 | 
						|
	has       RelationshipType = "has"
 | 
						|
)
 | 
						|
 | 
						|
type Relationships struct {
 | 
						|
	HasOne    []*Relationship
 | 
						|
	BelongsTo []*Relationship
 | 
						|
	HasMany   []*Relationship
 | 
						|
	Many2Many []*Relationship
 | 
						|
	Relations map[string]*Relationship
 | 
						|
 | 
						|
	EmbeddedRelations map[string]*Relationships
 | 
						|
}
 | 
						|
 | 
						|
type Relationship struct {
 | 
						|
	Name                     string
 | 
						|
	Type                     RelationshipType
 | 
						|
	Field                    *Field
 | 
						|
	Polymorphic              *Polymorphic
 | 
						|
	References               []*Reference
 | 
						|
	Schema                   *Schema
 | 
						|
	FieldSchema              *Schema
 | 
						|
	JoinTable                *Schema
 | 
						|
	foreignKeys, primaryKeys []string
 | 
						|
}
 | 
						|
 | 
						|
type Polymorphic struct {
 | 
						|
	PolymorphicID   *Field
 | 
						|
	PolymorphicType *Field
 | 
						|
	Value           string
 | 
						|
}
 | 
						|
 | 
						|
type Reference struct {
 | 
						|
	PrimaryKey    *Field
 | 
						|
	PrimaryValue  string
 | 
						|
	ForeignKey    *Field
 | 
						|
	OwnPrimaryKey bool
 | 
						|
}
 | 
						|
 | 
						|
func (schema *Schema) parseRelation(field *Field) *Relationship {
 | 
						|
	var (
 | 
						|
		err        error
 | 
						|
		fieldValue = reflect.New(field.IndirectFieldType).Interface()
 | 
						|
		relation   = &Relationship{
 | 
						|
			Name:        field.Name,
 | 
						|
			Field:       field,
 | 
						|
			Schema:      schema,
 | 
						|
			foreignKeys: toColumns(field.TagSettings["FOREIGNKEY"]),
 | 
						|
			primaryKeys: toColumns(field.TagSettings["REFERENCES"]),
 | 
						|
		}
 | 
						|
	)
 | 
						|
 | 
						|
	cacheStore := schema.cacheStore
 | 
						|
 | 
						|
	if relation.FieldSchema, err = getOrParse(fieldValue, cacheStore, schema.namer); err != nil {
 | 
						|
		schema.err = err
 | 
						|
		return nil
 | 
						|
	}
 | 
						|
 | 
						|
	if hasPolymorphicRelation(field.TagSettings) {
 | 
						|
		schema.buildPolymorphicRelation(relation, field)
 | 
						|
	} else if many2many := field.TagSettings["MANY2MANY"]; many2many != "" {
 | 
						|
		schema.buildMany2ManyRelation(relation, field, many2many)
 | 
						|
	} else if belongsTo := field.TagSettings["BELONGSTO"]; belongsTo != "" {
 | 
						|
		schema.guessRelation(relation, field, guessBelongs)
 | 
						|
	} else {
 | 
						|
		switch field.IndirectFieldType.Kind() {
 | 
						|
		case reflect.Struct:
 | 
						|
			schema.guessRelation(relation, field, guessGuess)
 | 
						|
		case reflect.Slice:
 | 
						|
			schema.guessRelation(relation, field, guessHas)
 | 
						|
		default:
 | 
						|
			schema.err = fmt.Errorf("unsupported data type %v for %v on field %s", relation.FieldSchema, schema,
 | 
						|
				field.Name)
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	if relation.Type == has {
 | 
						|
		// don't add relations to embedded schema, which might be shared
 | 
						|
		if relation.FieldSchema != relation.Schema && relation.Polymorphic == nil && field.OwnerSchema == nil {
 | 
						|
			relation.FieldSchema.Relationships.Relations["_"+relation.Schema.Name+"_"+relation.Name] = relation
 | 
						|
		}
 | 
						|
 | 
						|
		switch field.IndirectFieldType.Kind() {
 | 
						|
		case reflect.Struct:
 | 
						|
			relation.Type = HasOne
 | 
						|
		case reflect.Slice:
 | 
						|
			relation.Type = HasMany
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	if schema.err == nil {
 | 
						|
		schema.setRelation(relation)
 | 
						|
		switch relation.Type {
 | 
						|
		case HasOne:
 | 
						|
			schema.Relationships.HasOne = append(schema.Relationships.HasOne, relation)
 | 
						|
		case HasMany:
 | 
						|
			schema.Relationships.HasMany = append(schema.Relationships.HasMany, relation)
 | 
						|
		case BelongsTo:
 | 
						|
			schema.Relationships.BelongsTo = append(schema.Relationships.BelongsTo, relation)
 | 
						|
		case Many2Many:
 | 
						|
			schema.Relationships.Many2Many = append(schema.Relationships.Many2Many, relation)
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	return relation
 | 
						|
}
 | 
						|
 | 
						|
// hasPolymorphicRelation check if has polymorphic relation
 | 
						|
// 1. `POLYMORPHIC` tag
 | 
						|
// 2. `POLYMORPHICTYPE` and `POLYMORPHICID` tag
 | 
						|
func hasPolymorphicRelation(tagSettings map[string]string) bool {
 | 
						|
	if _, ok := tagSettings["POLYMORPHIC"]; ok {
 | 
						|
		return true
 | 
						|
	}
 | 
						|
 | 
						|
	_, hasType := tagSettings["POLYMORPHICTYPE"]
 | 
						|
	_, hasId := tagSettings["POLYMORPHICID"]
 | 
						|
 | 
						|
	return hasType && hasId
 | 
						|
}
 | 
						|
 | 
						|
func (schema *Schema) setRelation(relation *Relationship) {
 | 
						|
	// set non-embedded relation
 | 
						|
	if rel := schema.Relationships.Relations[relation.Name]; rel != nil {
 | 
						|
		if len(rel.Field.BindNames) > 1 {
 | 
						|
			schema.Relationships.Relations[relation.Name] = relation
 | 
						|
		}
 | 
						|
	} else {
 | 
						|
		schema.Relationships.Relations[relation.Name] = relation
 | 
						|
	}
 | 
						|
 | 
						|
	// set embedded relation
 | 
						|
	if len(relation.Field.EmbeddedBindNames) <= 1 {
 | 
						|
		return
 | 
						|
	}
 | 
						|
	relationships := &schema.Relationships
 | 
						|
	for i, name := range relation.Field.EmbeddedBindNames {
 | 
						|
		if i < len(relation.Field.EmbeddedBindNames)-1 {
 | 
						|
			if relationships.EmbeddedRelations == nil {
 | 
						|
				relationships.EmbeddedRelations = map[string]*Relationships{}
 | 
						|
			}
 | 
						|
			if r := relationships.EmbeddedRelations[name]; r == nil {
 | 
						|
				relationships.EmbeddedRelations[name] = &Relationships{}
 | 
						|
			}
 | 
						|
			relationships = relationships.EmbeddedRelations[name]
 | 
						|
		} else {
 | 
						|
			if relationships.Relations == nil {
 | 
						|
				relationships.Relations = map[string]*Relationship{}
 | 
						|
			}
 | 
						|
			relationships.Relations[relation.Name] = relation
 | 
						|
		}
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
// User has many Toys, its `Polymorphic` is `Owner`, Pet has one Toy, its `Polymorphic` is `Owner`
 | 
						|
//
 | 
						|
//	type User struct {
 | 
						|
//	  Toys []Toy `gorm:"polymorphic:Owner;"`
 | 
						|
//	}
 | 
						|
//	type Pet struct {
 | 
						|
//	  Toy Toy `gorm:"polymorphic:Owner;"`
 | 
						|
//	}
 | 
						|
//	type Toy struct {
 | 
						|
//	  OwnerID   int
 | 
						|
//	  OwnerType string
 | 
						|
//	}
 | 
						|
func (schema *Schema) buildPolymorphicRelation(relation *Relationship, field *Field) {
 | 
						|
	polymorphic := field.TagSettings["POLYMORPHIC"]
 | 
						|
 | 
						|
	relation.Polymorphic = &Polymorphic{
 | 
						|
		Value: schema.Table,
 | 
						|
	}
 | 
						|
 | 
						|
	var (
 | 
						|
		typeName = polymorphic + "Type"
 | 
						|
		typeId   = polymorphic + "ID"
 | 
						|
	)
 | 
						|
 | 
						|
	if value, ok := field.TagSettings["POLYMORPHICTYPE"]; ok {
 | 
						|
		typeName = strings.TrimSpace(value)
 | 
						|
	}
 | 
						|
 | 
						|
	if value, ok := field.TagSettings["POLYMORPHICID"]; ok {
 | 
						|
		typeId = strings.TrimSpace(value)
 | 
						|
	}
 | 
						|
 | 
						|
	relation.Polymorphic.PolymorphicType = relation.FieldSchema.FieldsByName[typeName]
 | 
						|
	relation.Polymorphic.PolymorphicID = relation.FieldSchema.FieldsByName[typeId]
 | 
						|
 | 
						|
	if value, ok := field.TagSettings["POLYMORPHICVALUE"]; ok {
 | 
						|
		relation.Polymorphic.Value = strings.TrimSpace(value)
 | 
						|
	}
 | 
						|
 | 
						|
	if relation.Polymorphic.PolymorphicType == nil {
 | 
						|
		schema.err = fmt.Errorf("invalid polymorphic type %v for %v on field %s, missing field %s",
 | 
						|
			relation.FieldSchema, schema, field.Name, polymorphic+"Type")
 | 
						|
	}
 | 
						|
 | 
						|
	if relation.Polymorphic.PolymorphicID == nil {
 | 
						|
		schema.err = fmt.Errorf("invalid polymorphic type %v for %v on field %s, missing field %s",
 | 
						|
			relation.FieldSchema, schema, field.Name, polymorphic+"ID")
 | 
						|
	}
 | 
						|
 | 
						|
	if schema.err == nil {
 | 
						|
		relation.References = append(relation.References, &Reference{
 | 
						|
			PrimaryValue: relation.Polymorphic.Value,
 | 
						|
			ForeignKey:   relation.Polymorphic.PolymorphicType,
 | 
						|
		})
 | 
						|
 | 
						|
		primaryKeyField := schema.PrioritizedPrimaryField
 | 
						|
		if len(relation.foreignKeys) > 0 {
 | 
						|
			if primaryKeyField = schema.LookUpField(relation.foreignKeys[0]); primaryKeyField == nil || len(relation.foreignKeys) > 1 {
 | 
						|
				schema.err = fmt.Errorf("invalid polymorphic foreign keys %+v for %v on field %s", relation.foreignKeys,
 | 
						|
					schema, field.Name)
 | 
						|
			}
 | 
						|
		}
 | 
						|
 | 
						|
		if primaryKeyField == nil {
 | 
						|
			schema.err = fmt.Errorf("invalid polymorphic type %v for %v on field %s, missing primaryKey field",
 | 
						|
				relation.FieldSchema, schema, field.Name)
 | 
						|
			return
 | 
						|
		}
 | 
						|
 | 
						|
		// use same data type for foreign keys
 | 
						|
		if copyableDataType(primaryKeyField.DataType) {
 | 
						|
			relation.Polymorphic.PolymorphicID.DataType = primaryKeyField.DataType
 | 
						|
		}
 | 
						|
		relation.Polymorphic.PolymorphicID.GORMDataType = primaryKeyField.GORMDataType
 | 
						|
		if relation.Polymorphic.PolymorphicID.Size == 0 {
 | 
						|
			relation.Polymorphic.PolymorphicID.Size = primaryKeyField.Size
 | 
						|
		}
 | 
						|
 | 
						|
		relation.References = append(relation.References, &Reference{
 | 
						|
			PrimaryKey:    primaryKeyField,
 | 
						|
			ForeignKey:    relation.Polymorphic.PolymorphicID,
 | 
						|
			OwnPrimaryKey: true,
 | 
						|
		})
 | 
						|
	}
 | 
						|
 | 
						|
	relation.Type = has
 | 
						|
}
 | 
						|
 | 
						|
func (schema *Schema) buildMany2ManyRelation(relation *Relationship, field *Field, many2many string) {
 | 
						|
	relation.Type = Many2Many
 | 
						|
 | 
						|
	var (
 | 
						|
		err             error
 | 
						|
		joinTableFields []reflect.StructField
 | 
						|
		fieldsMap       = map[string]*Field{}
 | 
						|
		ownFieldsMap    = map[string]*Field{} // fix self join many2many
 | 
						|
		referFieldsMap  = map[string]*Field{}
 | 
						|
		joinForeignKeys = toColumns(field.TagSettings["JOINFOREIGNKEY"])
 | 
						|
		joinReferences  = toColumns(field.TagSettings["JOINREFERENCES"])
 | 
						|
	)
 | 
						|
 | 
						|
	ownForeignFields := schema.PrimaryFields
 | 
						|
	refForeignFields := relation.FieldSchema.PrimaryFields
 | 
						|
 | 
						|
	if len(relation.foreignKeys) > 0 {
 | 
						|
		ownForeignFields = []*Field{}
 | 
						|
		for _, foreignKey := range relation.foreignKeys {
 | 
						|
			if field := schema.LookUpField(foreignKey); field != nil {
 | 
						|
				ownForeignFields = append(ownForeignFields, field)
 | 
						|
			} else {
 | 
						|
				schema.err = fmt.Errorf("invalid foreign key: %s", foreignKey)
 | 
						|
				return
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	if len(relation.primaryKeys) > 0 {
 | 
						|
		refForeignFields = []*Field{}
 | 
						|
		for _, foreignKey := range relation.primaryKeys {
 | 
						|
			if field := relation.FieldSchema.LookUpField(foreignKey); field != nil {
 | 
						|
				refForeignFields = append(refForeignFields, field)
 | 
						|
			} else {
 | 
						|
				schema.err = fmt.Errorf("invalid foreign key: %s", foreignKey)
 | 
						|
				return
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	for idx, ownField := range ownForeignFields {
 | 
						|
		joinFieldName := strings.Title(schema.Name) + ownField.Name
 | 
						|
		if len(joinForeignKeys) > idx {
 | 
						|
			joinFieldName = strings.Title(joinForeignKeys[idx])
 | 
						|
		}
 | 
						|
 | 
						|
		ownFieldsMap[joinFieldName] = ownField
 | 
						|
		fieldsMap[joinFieldName] = ownField
 | 
						|
		joinTableFields = append(joinTableFields, reflect.StructField{
 | 
						|
			Name:    joinFieldName,
 | 
						|
			PkgPath: ownField.StructField.PkgPath,
 | 
						|
			Type:    ownField.StructField.Type,
 | 
						|
			Tag: removeSettingFromTag(appendSettingFromTag(ownField.StructField.Tag, "primaryKey"),
 | 
						|
				"column", "autoincrement", "index", "unique", "uniqueindex"),
 | 
						|
		})
 | 
						|
	}
 | 
						|
 | 
						|
	for idx, relField := range refForeignFields {
 | 
						|
		joinFieldName := strings.Title(relation.FieldSchema.Name) + relField.Name
 | 
						|
 | 
						|
		if _, ok := ownFieldsMap[joinFieldName]; ok {
 | 
						|
			if field.Name != relation.FieldSchema.Name {
 | 
						|
				joinFieldName = inflection.Singular(field.Name) + relField.Name
 | 
						|
			} else {
 | 
						|
				joinFieldName += "Reference"
 | 
						|
			}
 | 
						|
		}
 | 
						|
 | 
						|
		if len(joinReferences) > idx {
 | 
						|
			joinFieldName = strings.Title(joinReferences[idx])
 | 
						|
		}
 | 
						|
 | 
						|
		referFieldsMap[joinFieldName] = relField
 | 
						|
 | 
						|
		if _, ok := fieldsMap[joinFieldName]; !ok {
 | 
						|
			fieldsMap[joinFieldName] = relField
 | 
						|
			joinTableFields = append(joinTableFields, reflect.StructField{
 | 
						|
				Name:    joinFieldName,
 | 
						|
				PkgPath: relField.StructField.PkgPath,
 | 
						|
				Type:    relField.StructField.Type,
 | 
						|
				Tag: removeSettingFromTag(appendSettingFromTag(relField.StructField.Tag, "primaryKey"),
 | 
						|
					"column", "autoincrement", "index", "unique", "uniqueindex"),
 | 
						|
			})
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	joinTableFields = append(joinTableFields, reflect.StructField{
 | 
						|
		Name: strings.Title(schema.Name) + field.Name,
 | 
						|
		Type: schema.ModelType,
 | 
						|
		Tag:  `gorm:"-"`,
 | 
						|
	})
 | 
						|
 | 
						|
	if relation.JoinTable, err = Parse(reflect.New(reflect.StructOf(joinTableFields)).Interface(), schema.cacheStore,
 | 
						|
		schema.namer); err != nil {
 | 
						|
		schema.err = err
 | 
						|
	}
 | 
						|
	relation.JoinTable.Name = many2many
 | 
						|
	relation.JoinTable.Table = schema.namer.JoinTableName(many2many)
 | 
						|
	relation.JoinTable.PrimaryFields = make([]*Field, 0, len(relation.JoinTable.Fields))
 | 
						|
 | 
						|
	relName := relation.Schema.Name
 | 
						|
	relRefName := relation.FieldSchema.Name
 | 
						|
	if relName == relRefName {
 | 
						|
		relRefName = relation.Field.Name
 | 
						|
	}
 | 
						|
 | 
						|
	if _, ok := relation.JoinTable.Relationships.Relations[relName]; !ok {
 | 
						|
		relation.JoinTable.Relationships.Relations[relName] = &Relationship{
 | 
						|
			Name:        relName,
 | 
						|
			Type:        BelongsTo,
 | 
						|
			Schema:      relation.JoinTable,
 | 
						|
			FieldSchema: relation.Schema,
 | 
						|
		}
 | 
						|
	} else {
 | 
						|
		relation.JoinTable.Relationships.Relations[relName].References = []*Reference{}
 | 
						|
	}
 | 
						|
 | 
						|
	if _, ok := relation.JoinTable.Relationships.Relations[relRefName]; !ok {
 | 
						|
		relation.JoinTable.Relationships.Relations[relRefName] = &Relationship{
 | 
						|
			Name:        relRefName,
 | 
						|
			Type:        BelongsTo,
 | 
						|
			Schema:      relation.JoinTable,
 | 
						|
			FieldSchema: relation.FieldSchema,
 | 
						|
		}
 | 
						|
	} else {
 | 
						|
		relation.JoinTable.Relationships.Relations[relRefName].References = []*Reference{}
 | 
						|
	}
 | 
						|
 | 
						|
	// build references
 | 
						|
	for _, f := range relation.JoinTable.Fields {
 | 
						|
		if f.Creatable || f.Readable || f.Updatable {
 | 
						|
			// use same data type for foreign keys
 | 
						|
			if copyableDataType(fieldsMap[f.Name].DataType) {
 | 
						|
				f.DataType = fieldsMap[f.Name].DataType
 | 
						|
			}
 | 
						|
			f.GORMDataType = fieldsMap[f.Name].GORMDataType
 | 
						|
			if f.Size == 0 {
 | 
						|
				f.Size = fieldsMap[f.Name].Size
 | 
						|
			}
 | 
						|
			relation.JoinTable.PrimaryFields = append(relation.JoinTable.PrimaryFields, f)
 | 
						|
 | 
						|
			if of, ok := ownFieldsMap[f.Name]; ok {
 | 
						|
				joinRel := relation.JoinTable.Relationships.Relations[relName]
 | 
						|
				joinRel.Field = relation.Field
 | 
						|
				joinRel.References = append(joinRel.References, &Reference{
 | 
						|
					PrimaryKey: of,
 | 
						|
					ForeignKey: f,
 | 
						|
				})
 | 
						|
 | 
						|
				relation.References = append(relation.References, &Reference{
 | 
						|
					PrimaryKey:    of,
 | 
						|
					ForeignKey:    f,
 | 
						|
					OwnPrimaryKey: true,
 | 
						|
				})
 | 
						|
			}
 | 
						|
 | 
						|
			if rf, ok := referFieldsMap[f.Name]; ok {
 | 
						|
				joinRefRel := relation.JoinTable.Relationships.Relations[relRefName]
 | 
						|
				if joinRefRel.Field == nil {
 | 
						|
					joinRefRel.Field = relation.Field
 | 
						|
				}
 | 
						|
				joinRefRel.References = append(joinRefRel.References, &Reference{
 | 
						|
					PrimaryKey: rf,
 | 
						|
					ForeignKey: f,
 | 
						|
				})
 | 
						|
 | 
						|
				relation.References = append(relation.References, &Reference{
 | 
						|
					PrimaryKey: rf,
 | 
						|
					ForeignKey: f,
 | 
						|
				})
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
type guessLevel int
 | 
						|
 | 
						|
const (
 | 
						|
	guessGuess guessLevel = iota
 | 
						|
	guessBelongs
 | 
						|
	guessEmbeddedBelongs
 | 
						|
	guessHas
 | 
						|
	guessEmbeddedHas
 | 
						|
)
 | 
						|
 | 
						|
func (schema *Schema) guessRelation(relation *Relationship, field *Field, cgl guessLevel) {
 | 
						|
	var (
 | 
						|
		primaryFields, foreignFields []*Field
 | 
						|
		primarySchema, foreignSchema = schema, relation.FieldSchema
 | 
						|
		gl                           = cgl
 | 
						|
	)
 | 
						|
 | 
						|
	if gl == guessGuess {
 | 
						|
		if field.Schema == relation.FieldSchema {
 | 
						|
			gl = guessBelongs
 | 
						|
		} else {
 | 
						|
			gl = guessHas
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	reguessOrErr := func() {
 | 
						|
		switch cgl {
 | 
						|
		case guessGuess:
 | 
						|
			schema.guessRelation(relation, field, guessBelongs)
 | 
						|
		case guessBelongs:
 | 
						|
			schema.guessRelation(relation, field, guessEmbeddedBelongs)
 | 
						|
		case guessEmbeddedBelongs:
 | 
						|
			schema.guessRelation(relation, field, guessHas)
 | 
						|
		case guessHas:
 | 
						|
			schema.guessRelation(relation, field, guessEmbeddedHas)
 | 
						|
		// case guessEmbeddedHas:
 | 
						|
		default:
 | 
						|
			schema.err = fmt.Errorf("invalid field found for struct %v's field %s: define a valid foreign key for relations or implement the Valuer/Scanner interface",
 | 
						|
				schema, field.Name)
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	switch gl {
 | 
						|
	case guessBelongs:
 | 
						|
		primarySchema, foreignSchema = relation.FieldSchema, schema
 | 
						|
	case guessEmbeddedBelongs:
 | 
						|
		if field.OwnerSchema == nil {
 | 
						|
			reguessOrErr()
 | 
						|
			return
 | 
						|
		}
 | 
						|
		primarySchema, foreignSchema = relation.FieldSchema, field.OwnerSchema
 | 
						|
	case guessHas:
 | 
						|
	case guessEmbeddedHas:
 | 
						|
		if field.OwnerSchema == nil {
 | 
						|
			reguessOrErr()
 | 
						|
			return
 | 
						|
		}
 | 
						|
		primarySchema, foreignSchema = field.OwnerSchema, relation.FieldSchema
 | 
						|
	}
 | 
						|
 | 
						|
	if len(relation.foreignKeys) > 0 {
 | 
						|
		for _, foreignKey := range relation.foreignKeys {
 | 
						|
			f := foreignSchema.LookUpField(foreignKey)
 | 
						|
			if f == nil {
 | 
						|
				reguessOrErr()
 | 
						|
				return
 | 
						|
			}
 | 
						|
			foreignFields = append(foreignFields, f)
 | 
						|
		}
 | 
						|
	} else {
 | 
						|
		primarySchemaName := primarySchema.Name
 | 
						|
		if primarySchemaName == "" {
 | 
						|
			primarySchemaName = relation.FieldSchema.Name
 | 
						|
		}
 | 
						|
 | 
						|
		if len(relation.primaryKeys) > 0 {
 | 
						|
			for _, primaryKey := range relation.primaryKeys {
 | 
						|
				if f := primarySchema.LookUpField(primaryKey); f != nil {
 | 
						|
					primaryFields = append(primaryFields, f)
 | 
						|
				}
 | 
						|
			}
 | 
						|
		} else {
 | 
						|
			primaryFields = primarySchema.PrimaryFields
 | 
						|
		}
 | 
						|
 | 
						|
	primaryFieldLoop:
 | 
						|
		for _, primaryField := range primaryFields {
 | 
						|
			lookUpName := primarySchemaName + primaryField.Name
 | 
						|
			if gl == guessBelongs {
 | 
						|
				lookUpName = field.Name + primaryField.Name
 | 
						|
			}
 | 
						|
 | 
						|
			lookUpNames := []string{lookUpName}
 | 
						|
			if len(primaryFields) == 1 {
 | 
						|
				lookUpNames = append(lookUpNames, strings.TrimSuffix(lookUpName, primaryField.Name)+"ID",
 | 
						|
					strings.TrimSuffix(lookUpName, primaryField.Name)+"Id", schema.namer.ColumnName(foreignSchema.Table,
 | 
						|
						strings.TrimSuffix(lookUpName, primaryField.Name)+"ID"))
 | 
						|
			}
 | 
						|
 | 
						|
			for _, name := range lookUpNames {
 | 
						|
				if f := foreignSchema.LookUpFieldByBindName(field.BindNames, name); f != nil {
 | 
						|
					foreignFields = append(foreignFields, f)
 | 
						|
					primaryFields = append(primaryFields, primaryField)
 | 
						|
					continue primaryFieldLoop
 | 
						|
				}
 | 
						|
			}
 | 
						|
			for _, name := range lookUpNames {
 | 
						|
				if f := foreignSchema.LookUpField(name); f != nil {
 | 
						|
					foreignFields = append(foreignFields, f)
 | 
						|
					primaryFields = append(primaryFields, primaryField)
 | 
						|
					continue primaryFieldLoop
 | 
						|
				}
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	switch {
 | 
						|
	case len(foreignFields) == 0:
 | 
						|
		reguessOrErr()
 | 
						|
		return
 | 
						|
	case len(relation.primaryKeys) > 0:
 | 
						|
		for idx, primaryKey := range relation.primaryKeys {
 | 
						|
			if f := primarySchema.LookUpField(primaryKey); f != nil {
 | 
						|
				if len(primaryFields) < idx+1 {
 | 
						|
					primaryFields = append(primaryFields, f)
 | 
						|
				} else if f != primaryFields[idx] {
 | 
						|
					reguessOrErr()
 | 
						|
					return
 | 
						|
				}
 | 
						|
			} else {
 | 
						|
				reguessOrErr()
 | 
						|
				return
 | 
						|
			}
 | 
						|
		}
 | 
						|
	case len(primaryFields) == 0:
 | 
						|
		if len(foreignFields) == 1 && primarySchema.PrioritizedPrimaryField != nil {
 | 
						|
			primaryFields = append(primaryFields, primarySchema.PrioritizedPrimaryField)
 | 
						|
		} else if len(primarySchema.PrimaryFields) == len(foreignFields) {
 | 
						|
			primaryFields = append(primaryFields, primarySchema.PrimaryFields...)
 | 
						|
		} else {
 | 
						|
			reguessOrErr()
 | 
						|
			return
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	// build references
 | 
						|
	for idx, foreignField := range foreignFields {
 | 
						|
		// use same data type for foreign keys
 | 
						|
		if copyableDataType(primaryFields[idx].DataType) {
 | 
						|
			foreignField.DataType = primaryFields[idx].DataType
 | 
						|
		}
 | 
						|
		foreignField.GORMDataType = primaryFields[idx].GORMDataType
 | 
						|
		if foreignField.Size == 0 {
 | 
						|
			foreignField.Size = primaryFields[idx].Size
 | 
						|
		}
 | 
						|
 | 
						|
		relation.References = append(relation.References, &Reference{
 | 
						|
			PrimaryKey:    primaryFields[idx],
 | 
						|
			ForeignKey:    foreignField,
 | 
						|
			OwnPrimaryKey: (schema == primarySchema && gl == guessHas) || (field.OwnerSchema == primarySchema && gl == guessEmbeddedHas),
 | 
						|
		})
 | 
						|
	}
 | 
						|
 | 
						|
	if gl == guessHas || gl == guessEmbeddedHas {
 | 
						|
		relation.Type = has
 | 
						|
	} else {
 | 
						|
		relation.Type = BelongsTo
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
// Constraint is ForeignKey Constraint
 | 
						|
type Constraint struct {
 | 
						|
	Name            string
 | 
						|
	Field           *Field
 | 
						|
	Schema          *Schema
 | 
						|
	ForeignKeys     []*Field
 | 
						|
	ReferenceSchema *Schema
 | 
						|
	References      []*Field
 | 
						|
	OnDelete        string
 | 
						|
	OnUpdate        string
 | 
						|
}
 | 
						|
 | 
						|
func (constraint *Constraint) GetName() string { return constraint.Name }
 | 
						|
 | 
						|
func (constraint *Constraint) Build() (sql string, vars []interface{}) {
 | 
						|
	sql = "CONSTRAINT ? FOREIGN KEY ? REFERENCES ??"
 | 
						|
	if constraint.OnDelete != "" {
 | 
						|
		sql += " ON DELETE " + constraint.OnDelete
 | 
						|
	}
 | 
						|
 | 
						|
	if constraint.OnUpdate != "" {
 | 
						|
		sql += " ON UPDATE " + constraint.OnUpdate
 | 
						|
	}
 | 
						|
 | 
						|
	foreignKeys := make([]interface{}, 0, len(constraint.ForeignKeys))
 | 
						|
	for _, field := range constraint.ForeignKeys {
 | 
						|
		foreignKeys = append(foreignKeys, clause.Column{Name: field.DBName})
 | 
						|
	}
 | 
						|
 | 
						|
	references := make([]interface{}, 0, len(constraint.References))
 | 
						|
	for _, field := range constraint.References {
 | 
						|
		references = append(references, clause.Column{Name: field.DBName})
 | 
						|
	}
 | 
						|
	vars = append(vars, clause.Table{Name: constraint.Name}, foreignKeys, clause.Table{Name: constraint.ReferenceSchema.Table}, references)
 | 
						|
	return
 | 
						|
}
 | 
						|
 | 
						|
func (rel *Relationship) ParseConstraint() *Constraint {
 | 
						|
	str := rel.Field.TagSettings["CONSTRAINT"]
 | 
						|
	if str == "-" {
 | 
						|
		return nil
 | 
						|
	}
 | 
						|
 | 
						|
	if rel.Type == BelongsTo {
 | 
						|
		for _, r := range rel.FieldSchema.Relationships.Relations {
 | 
						|
			if r != rel && r.FieldSchema == rel.Schema && len(rel.References) == len(r.References) {
 | 
						|
				matched := true
 | 
						|
				for idx, ref := range r.References {
 | 
						|
					if !(rel.References[idx].PrimaryKey == ref.PrimaryKey && rel.References[idx].ForeignKey == ref.ForeignKey &&
 | 
						|
						rel.References[idx].PrimaryValue == ref.PrimaryValue) {
 | 
						|
						matched = false
 | 
						|
					}
 | 
						|
				}
 | 
						|
 | 
						|
				if matched {
 | 
						|
					return nil
 | 
						|
				}
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	var (
 | 
						|
		name     string
 | 
						|
		idx      = strings.Index(str, ",")
 | 
						|
		settings = ParseTagSetting(str, ",")
 | 
						|
	)
 | 
						|
 | 
						|
	// optimize match english letters and midline
 | 
						|
	// The following code is basically called in for.
 | 
						|
	// In order to avoid the performance problems caused by repeated compilation of regular expressions,
 | 
						|
	// it only needs to be done once outside, so optimization is done here.
 | 
						|
	if idx != -1 && regEnLetterAndMidline.MatchString(str[0:idx]) {
 | 
						|
		name = str[0:idx]
 | 
						|
	} else {
 | 
						|
		name = rel.Schema.namer.RelationshipFKName(*rel)
 | 
						|
	}
 | 
						|
 | 
						|
	constraint := Constraint{
 | 
						|
		Name:     name,
 | 
						|
		Field:    rel.Field,
 | 
						|
		OnUpdate: settings["ONUPDATE"],
 | 
						|
		OnDelete: settings["ONDELETE"],
 | 
						|
	}
 | 
						|
 | 
						|
	for _, ref := range rel.References {
 | 
						|
		if ref.PrimaryKey != nil && (rel.JoinTable == nil || ref.OwnPrimaryKey) {
 | 
						|
			constraint.ForeignKeys = append(constraint.ForeignKeys, ref.ForeignKey)
 | 
						|
			constraint.References = append(constraint.References, ref.PrimaryKey)
 | 
						|
 | 
						|
			if ref.OwnPrimaryKey {
 | 
						|
				constraint.Schema = ref.ForeignKey.Schema
 | 
						|
				constraint.ReferenceSchema = rel.Schema
 | 
						|
			} else {
 | 
						|
				constraint.Schema = rel.Schema
 | 
						|
				constraint.ReferenceSchema = ref.PrimaryKey.Schema
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	return &constraint
 | 
						|
}
 | 
						|
 | 
						|
func (rel *Relationship) ToQueryConditions(ctx context.Context, reflectValue reflect.Value) (conds []clause.Expression) {
 | 
						|
	table := rel.FieldSchema.Table
 | 
						|
	foreignFields := []*Field{}
 | 
						|
	relForeignKeys := []string{}
 | 
						|
 | 
						|
	if rel.JoinTable != nil {
 | 
						|
		table = rel.JoinTable.Table
 | 
						|
		for _, ref := range rel.References {
 | 
						|
			if ref.OwnPrimaryKey {
 | 
						|
				foreignFields = append(foreignFields, ref.PrimaryKey)
 | 
						|
				relForeignKeys = append(relForeignKeys, ref.ForeignKey.DBName)
 | 
						|
			} else if ref.PrimaryValue != "" {
 | 
						|
				conds = append(conds, clause.Eq{
 | 
						|
					Column: clause.Column{Table: rel.JoinTable.Table, Name: ref.ForeignKey.DBName},
 | 
						|
					Value:  ref.PrimaryValue,
 | 
						|
				})
 | 
						|
			} else {
 | 
						|
				conds = append(conds, clause.Eq{
 | 
						|
					Column: clause.Column{Table: rel.JoinTable.Table, Name: ref.ForeignKey.DBName},
 | 
						|
					Value:  clause.Column{Table: rel.FieldSchema.Table, Name: ref.PrimaryKey.DBName},
 | 
						|
				})
 | 
						|
			}
 | 
						|
		}
 | 
						|
	} else {
 | 
						|
		for _, ref := range rel.References {
 | 
						|
			if ref.OwnPrimaryKey {
 | 
						|
				relForeignKeys = append(relForeignKeys, ref.ForeignKey.DBName)
 | 
						|
				foreignFields = append(foreignFields, ref.PrimaryKey)
 | 
						|
			} else if ref.PrimaryValue != "" {
 | 
						|
				conds = append(conds, clause.Eq{
 | 
						|
					Column: clause.Column{Table: rel.FieldSchema.Table, Name: ref.ForeignKey.DBName},
 | 
						|
					Value:  ref.PrimaryValue,
 | 
						|
				})
 | 
						|
			} else {
 | 
						|
				relForeignKeys = append(relForeignKeys, ref.PrimaryKey.DBName)
 | 
						|
				foreignFields = append(foreignFields, ref.ForeignKey)
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	_, foreignValues := GetIdentityFieldValuesMap(ctx, reflectValue, foreignFields)
 | 
						|
	column, values := ToQueryValues(table, relForeignKeys, foreignValues)
 | 
						|
 | 
						|
	conds = append(conds, clause.IN{Column: column, Values: values})
 | 
						|
	return
 | 
						|
}
 | 
						|
 | 
						|
func copyableDataType(str DataType) bool {
 | 
						|
	for _, s := range []string{"auto_increment", "primary key"} {
 | 
						|
		if strings.Contains(strings.ToLower(string(str)), s) {
 | 
						|
			return false
 | 
						|
		}
 | 
						|
	}
 | 
						|
	return true
 | 
						|
}
 |