2020-01-31 07:22:37 +03:00
package schema
2020-01-29 14:22:44 +03:00
2020-02-01 07:46:52 +03:00
import (
2022-02-16 10:30:43 +03:00
"context"
2020-02-01 07:46:52 +03:00
"fmt"
"reflect"
"strings"
2020-02-01 19:03:56 +03:00
2020-06-02 04:25:55 +03:00
"github.com/jinzhu/inflection"
2020-06-02 04:16:07 +03:00
"gorm.io/gorm/clause"
2020-02-01 07:46:52 +03:00
)
2020-01-29 14:22:44 +03:00
// RelationshipType relationship type
type RelationshipType string
const (
2020-02-01 07:46:52 +03:00
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
2021-02-01 05:42:13 +03:00
has RelationshipType = "has"
2020-01-29 14:22:44 +03:00
)
type Relationships struct {
2020-02-01 07:46:52 +03:00
HasOne [ ] * Relationship
BelongsTo [ ] * Relationship
HasMany [ ] * Relationship
Many2Many [ ] * Relationship
Relations map [ string ] * Relationship
2023-04-11 08:10:38 +03:00
EmbeddedRelations map [ string ] * Relationships
2020-01-29 14:22:44 +03:00
}
type Relationship struct {
2020-02-01 10:23:45 +03:00
Name string
Type RelationshipType
Field * Field
Polymorphic * Polymorphic
2020-06-01 17:31:50 +03:00
References [ ] * Reference
2020-02-01 10:23:45 +03:00
Schema * Schema
FieldSchema * Schema
JoinTable * Schema
2020-02-01 16:48:06 +03:00
foreignKeys , primaryKeys [ ] string
2020-02-01 07:46:52 +03:00
}
type Polymorphic struct {
PolymorphicID * Field
PolymorphicType * Field
Value string
2020-01-29 14:22:44 +03:00
}
2020-02-01 07:46:52 +03:00
type Reference struct {
2020-02-01 19:03:56 +03:00
PrimaryKey * Field
PrimaryValue string
2020-02-01 07:46:52 +03:00
ForeignKey * Field
2020-02-01 19:03:56 +03:00
OwnPrimaryKey bool
2020-01-29 14:22:44 +03:00
}
2021-01-19 10:40:04 +03:00
func ( schema * Schema ) parseRelation ( field * Field ) * Relationship {
2020-02-01 07:46:52 +03:00
var (
2020-02-01 16:48:06 +03:00
err error
2020-02-15 14:45:27 +03:00
fieldValue = reflect . New ( field . IndirectFieldType ) . Interface ( )
2020-02-01 07:46:52 +03:00
relation = & Relationship {
2020-02-01 10:23:45 +03:00
Name : field . Name ,
Field : field ,
Schema : schema ,
2020-02-01 16:48:06 +03:00
foreignKeys : toColumns ( field . TagSettings [ "FOREIGNKEY" ] ) ,
primaryKeys : toColumns ( field . TagSettings [ "REFERENCES" ] ) ,
2020-02-01 07:46:52 +03:00
}
)
2020-08-17 07:02:41 +03:00
cacheStore := schema . cacheStore
2020-11-27 09:32:20 +03:00
if relation . FieldSchema , err = getOrParse ( fieldValue , cacheStore , schema . namer ) ; err != nil {
2020-08-17 07:02:41 +03:00
schema . err = err
2021-01-19 10:40:04 +03:00
return nil
2020-02-01 07:46:52 +03:00
}
2023-12-15 11:36:08 +03:00
if hasPolymorphicRelation ( field . TagSettings ) {
schema . buildPolymorphicRelation ( relation , field )
2020-07-16 06:27:04 +03:00
} else if many2many := field . TagSettings [ "MANY2MANY" ] ; many2many != "" {
2020-02-01 13:02:19 +03:00
schema . buildMany2ManyRelation ( relation , field , many2many )
2021-10-08 05:59:55 +03:00
} else if belongsTo := field . TagSettings [ "BELONGSTO" ] ; belongsTo != "" {
schema . guessRelation ( relation , field , guessBelongs )
2020-02-01 10:23:45 +03:00
} else {
2020-02-15 14:45:27 +03:00
switch field . IndirectFieldType . Kind ( ) {
2020-08-28 06:31:13 +03:00
case reflect . Struct :
2021-02-15 04:10:51 +03:00
schema . guessRelation ( relation , field , guessGuess )
2020-08-28 06:31:13 +03:00
case reflect . Slice :
2020-08-04 07:10:19 +03:00
schema . guessRelation ( relation , field , guessHas )
2020-02-01 10:23:45 +03:00
default :
2023-12-15 11:36:08 +03:00
schema . err = fmt . Errorf ( "unsupported data type %v for %v on field %s" , relation . FieldSchema , schema ,
field . Name )
2020-02-01 10:23:45 +03:00
}
}
2021-02-01 05:42:13 +03:00
if relation . Type == has {
2021-04-19 16:03:39 +03:00
// don't add relations to embedded schema, which might be shared
2021-01-19 10:40:04 +03:00
if relation . FieldSchema != relation . Schema && relation . Polymorphic == nil && field . OwnerSchema == nil {
2020-06-19 19:48:15 +03:00
relation . FieldSchema . Relationships . Relations [ "_" + relation . Schema . Name + "_" + relation . Name ] = relation
}
2020-02-15 14:45:27 +03:00
switch field . IndirectFieldType . Kind ( ) {
2020-02-01 07:46:52 +03:00
case reflect . Struct :
relation . Type = HasOne
case reflect . Slice :
relation . Type = HasMany
}
2020-02-01 10:23:45 +03:00
}
2020-02-01 16:48:06 +03:00
if schema . err == nil {
2023-04-11 08:10:38 +03:00
schema . setRelation ( relation )
2020-02-01 16:48:06 +03:00
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 )
}
}
2021-01-19 10:40:04 +03:00
return relation
2020-02-01 10:23:45 +03:00
}
2023-12-15 11:36:08 +03:00
// 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
}
2023-04-11 08:10:38 +03:00
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 . BindNames ) <= 1 {
return
}
relationships := & schema . Relationships
for i , name := range relation . Field . BindNames {
if i < len ( relation . Field . BindNames ) - 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
}
}
}
2020-02-01 13:02:19 +03:00
// User has many Toys, its `Polymorphic` is `Owner`, Pet has one Toy, its `Polymorphic` is `Owner`
2023-02-18 04:06:43 +03:00
//
// type User struct {
// Toys []Toy `gorm:"polymorphic:Owner;"`
// }
// type Pet struct {
// Toy Toy `gorm:"polymorphic:Owner;"`
// }
// type Toy struct {
// OwnerID int
// OwnerType string
// }
2023-12-15 11:36:08 +03:00
func ( schema * Schema ) buildPolymorphicRelation ( relation * Relationship , field * Field ) {
polymorphic := field . TagSettings [ "POLYMORPHIC" ]
2020-02-01 13:02:19 +03:00
relation . Polymorphic = & Polymorphic {
2023-12-15 11:36:08 +03:00
Value : schema . Table ,
}
var (
typeName = polymorphic + "Type"
typeId = polymorphic + "ID"
)
if value , ok := field . TagSettings [ "POLYMORPHICTYPE" ] ; ok {
typeName = strings . TrimSpace ( value )
2020-02-01 13:02:19 +03:00
}
2023-12-15 11:36:08 +03:00
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 ]
2020-07-08 12:59:40 +03:00
if value , ok := field . TagSettings [ "POLYMORPHICVALUE" ] ; ok {
2020-02-01 13:02:19 +03:00
relation . Polymorphic . Value = strings . TrimSpace ( value )
}
if relation . Polymorphic . PolymorphicType == nil {
2023-12-15 11:36:08 +03:00
schema . err = fmt . Errorf ( "invalid polymorphic type %v for %v on field %s, missing field %s" ,
relation . FieldSchema , schema , field . Name , polymorphic + "Type" )
2020-02-01 13:02:19 +03:00
}
if relation . Polymorphic . PolymorphicID == nil {
2023-12-15 11:36:08 +03:00
schema . err = fmt . Errorf ( "invalid polymorphic type %v for %v on field %s, missing field %s" ,
relation . FieldSchema , schema , field . Name , polymorphic + "ID" )
2020-02-01 13:02:19 +03:00
}
if schema . err == nil {
2020-06-01 17:31:50 +03:00
relation . References = append ( relation . References , & Reference {
2020-02-01 19:03:56 +03:00
PrimaryValue : relation . Polymorphic . Value ,
2020-02-01 13:02:19 +03:00
ForeignKey : relation . Polymorphic . PolymorphicType ,
} )
primaryKeyField := schema . PrioritizedPrimaryField
2020-02-01 16:48:06 +03:00
if len ( relation . foreignKeys ) > 0 {
if primaryKeyField = schema . LookUpField ( relation . foreignKeys [ 0 ] ) ; primaryKeyField == nil || len ( relation . foreignKeys ) > 1 {
2023-12-15 11:36:08 +03:00
schema . err = fmt . Errorf ( "invalid polymorphic foreign keys %+v for %v on field %s" , relation . foreignKeys ,
schema , field . Name )
2020-02-01 13:02:19 +03:00
}
}
2020-06-02 02:28:29 +03:00
2023-04-11 08:10:38 +03:00
if primaryKeyField == nil {
2023-12-15 11:36:08 +03:00
schema . err = fmt . Errorf ( "invalid polymorphic type %v for %v on field %s, missing primaryKey field" ,
relation . FieldSchema , schema , field . Name )
2023-04-11 08:10:38 +03:00
return
}
2020-06-02 02:28:29 +03:00
// use same data type for foreign keys
2021-04-30 11:25:56 +03:00
if copyableDataType ( primaryKeyField . DataType ) {
relation . Polymorphic . PolymorphicID . DataType = primaryKeyField . DataType
}
2020-07-20 13:59:28 +03:00
relation . Polymorphic . PolymorphicID . GORMDataType = primaryKeyField . GORMDataType
2020-09-03 11:11:15 +03:00
if relation . Polymorphic . PolymorphicID . Size == 0 {
relation . Polymorphic . PolymorphicID . Size = primaryKeyField . Size
}
2020-06-02 02:28:29 +03:00
2020-06-01 17:31:50 +03:00
relation . References = append ( relation . References , & Reference {
2020-02-01 19:03:56 +03:00
PrimaryKey : primaryKeyField ,
ForeignKey : relation . Polymorphic . PolymorphicID ,
OwnPrimaryKey : true ,
2020-02-01 13:02:19 +03:00
} )
}
2021-02-01 05:42:13 +03:00
relation . Type = has
2020-02-01 13:02:19 +03:00
}
func ( schema * Schema ) buildMany2ManyRelation ( relation * Relationship , field * Field , many2many string ) {
relation . Type = Many2Many
var (
2020-02-01 16:48:06 +03:00
err error
2020-02-01 13:02:19 +03:00
joinTableFields [ ] reflect . StructField
fieldsMap = map [ string ] * Field { }
2022-09-21 12:29:38 +03:00
ownFieldsMap = map [ string ] * Field { } // fix self join many2many
referFieldsMap = map [ string ] * Field { }
2020-05-31 12:42:21 +03:00
joinForeignKeys = toColumns ( field . TagSettings [ "JOINFOREIGNKEY" ] )
joinReferences = toColumns ( field . TagSettings [ "JOINREFERENCES" ] )
2020-02-01 13:02:19 +03:00
)
2020-05-31 12:42:21 +03:00
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 {
2021-06-10 05:21:28 +03:00
schema . err = fmt . Errorf ( "invalid foreign key: %s" , foreignKey )
2020-05-31 12:42:21 +03:00
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 )
2020-02-01 19:03:56 +03:00
} else {
2021-06-10 05:21:28 +03:00
schema . err = fmt . Errorf ( "invalid foreign key: %s" , foreignKey )
2020-05-31 12:42:21 +03:00
return
2020-02-01 13:02:19 +03:00
}
2020-05-31 12:42:21 +03:00
}
}
2020-02-01 13:02:19 +03:00
2020-05-31 12:42:21 +03:00
for idx , ownField := range ownForeignFields {
2021-01-13 08:05:05 +03:00
joinFieldName := strings . Title ( schema . Name ) + ownField . Name
2020-05-31 12:42:21 +03:00
if len ( joinForeignKeys ) > idx {
2020-07-04 02:24:30 +03:00
joinFieldName = strings . Title ( joinForeignKeys [ idx ] )
2020-02-01 13:02:19 +03:00
}
2020-05-31 12:42:21 +03:00
2022-09-21 12:29:38 +03:00
ownFieldsMap [ joinFieldName ] = ownField
2020-05-31 12:42:21 +03:00
fieldsMap [ joinFieldName ] = ownField
joinTableFields = append ( joinTableFields , reflect . StructField {
Name : joinFieldName ,
PkgPath : ownField . StructField . PkgPath ,
Type : ownField . StructField . Type ,
2022-05-09 05:07:18 +03:00
Tag : removeSettingFromTag ( appendSettingFromTag ( ownField . StructField . Tag , "primaryKey" ) ,
"column" , "autoincrement" , "index" , "unique" , "uniqueindex" ) ,
2020-05-31 12:42:21 +03:00
} )
}
for idx , relField := range refForeignFields {
2021-07-13 11:36:22 +03:00
joinFieldName := strings . Title ( relation . FieldSchema . Name ) + relField . Name
2020-05-31 12:42:21 +03:00
if _ , ok := ownFieldsMap [ joinFieldName ] ; ok {
if field . Name != relation . FieldSchema . Name {
joinFieldName = inflection . Singular ( field . Name ) + relField . Name
} else {
joinFieldName += "Reference"
}
}
2022-09-21 12:29:38 +03:00
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" ) ,
} )
}
2020-02-01 13:02:19 +03:00
}
2020-09-01 06:30:16 +03:00
joinTableFields = append ( joinTableFields , reflect . StructField {
2021-01-13 08:05:05 +03:00
Name : strings . Title ( schema . Name ) + field . Name ,
2020-09-01 06:30:16 +03:00
Type : schema . ModelType ,
Tag : ` gorm:"-" ` ,
} )
2023-12-15 11:36:08 +03:00
if relation . JoinTable , err = Parse ( reflect . New ( reflect . StructOf ( joinTableFields ) ) . Interface ( ) , schema . cacheStore ,
schema . namer ) ; err != nil {
2020-02-01 16:48:06 +03:00
schema . err = err
}
2020-02-01 13:02:19 +03:00
relation . JoinTable . Name = many2many
relation . JoinTable . Table = schema . namer . JoinTableName ( many2many )
2020-09-01 06:30:16 +03:00
relation . JoinTable . PrimaryFields = make ( [ ] * Field , 0 , len ( relation . JoinTable . Fields ) )
2020-02-01 13:02:19 +03:00
2020-06-21 05:19:16 +03:00
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 { }
}
2020-02-01 13:02:19 +03:00
// build references
2020-09-01 06:30:16 +03:00
for _ , f := range relation . JoinTable . Fields {
2020-09-09 11:32:29 +03:00
if f . Creatable || f . Readable || f . Updatable {
2020-09-01 06:30:16 +03:00
// use same data type for foreign keys
2021-04-30 11:25:56 +03:00
if copyableDataType ( fieldsMap [ f . Name ] . DataType ) {
f . DataType = fieldsMap [ f . Name ] . DataType
}
2020-09-01 06:30:16 +03:00
f . GORMDataType = fieldsMap [ f . Name ] . GORMDataType
2020-09-03 11:11:15 +03:00
if f . Size == 0 {
f . Size = fieldsMap [ f . Name ] . Size
}
2020-09-01 06:30:16 +03:00
relation . JoinTable . PrimaryFields = append ( relation . JoinTable . PrimaryFields , f )
2022-09-21 12:29:38 +03:00
if of , ok := ownFieldsMap [ f . Name ] ; ok {
2020-09-01 06:30:16 +03:00
joinRel := relation . JoinTable . Relationships . Relations [ relName ]
joinRel . Field = relation . Field
joinRel . References = append ( joinRel . References , & Reference {
2022-09-21 12:29:38 +03:00
PrimaryKey : of ,
2020-09-01 06:30:16 +03:00
ForeignKey : f ,
} )
2022-09-21 12:29:38 +03:00
relation . References = append ( relation . References , & Reference {
PrimaryKey : of ,
ForeignKey : f ,
OwnPrimaryKey : true ,
} )
}
if rf , ok := referFieldsMap [ f . Name ] ; ok {
2020-09-01 06:30:16 +03:00
joinRefRel := relation . JoinTable . Relationships . Relations [ relRefName ]
if joinRefRel . Field == nil {
joinRefRel . Field = relation . Field
}
joinRefRel . References = append ( joinRefRel . References , & Reference {
2022-09-21 12:29:38 +03:00
PrimaryKey : rf ,
2020-09-01 06:30:16 +03:00
ForeignKey : f ,
} )
2022-09-21 12:29:38 +03:00
relation . References = append ( relation . References , & Reference {
PrimaryKey : rf ,
ForeignKey : f ,
} )
}
2020-06-21 05:19:16 +03:00
}
2020-02-01 13:02:19 +03:00
}
}
2020-08-04 07:10:19 +03:00
type guessLevel int
const (
2021-02-15 04:10:51 +03:00
guessGuess guessLevel = iota
guessBelongs
2020-08-04 07:10:19 +03:00
guessEmbeddedBelongs
2020-08-28 06:31:13 +03:00
guessHas
guessEmbeddedHas
2020-08-04 07:10:19 +03:00
)
2021-02-15 04:10:51 +03:00
func ( schema * Schema ) guessRelation ( relation * Relationship , field * Field , cgl guessLevel ) {
2020-02-01 10:23:45 +03:00
var (
primaryFields , foreignFields [ ] * Field
primarySchema , foreignSchema = schema , relation . FieldSchema
2021-02-15 04:10:51 +03:00
gl = cgl
2020-02-01 10:23:45 +03:00
)
2021-02-15 04:10:51 +03:00
if gl == guessGuess {
if field . Schema == relation . FieldSchema {
gl = guessBelongs
} else {
gl = guessHas
}
}
2020-08-25 13:18:16 +03:00
reguessOrErr := func ( ) {
2021-02-15 04:10:51 +03:00
switch cgl {
case guessGuess :
schema . guessRelation ( relation , field , guessBelongs )
2020-08-04 07:10:19 +03:00
case guessBelongs :
schema . guessRelation ( relation , field , guessEmbeddedBelongs )
2020-08-28 06:31:13 +03:00
case guessEmbeddedBelongs :
schema . guessRelation ( relation , field , guessHas )
case guessHas :
schema . guessRelation ( relation , field , guessEmbeddedHas )
// case guessEmbeddedHas:
2020-08-04 07:10:19 +03:00
default :
2023-12-15 11:36:08 +03:00
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 )
2020-08-04 07:10:19 +03:00
}
2020-02-01 10:23:45 +03:00
}
2020-08-04 07:10:19 +03:00
switch gl {
2020-08-28 06:31:13 +03:00
case guessBelongs :
primarySchema , foreignSchema = relation . FieldSchema , schema
case guessEmbeddedBelongs :
2022-10-07 13:29:28 +03:00
if field . OwnerSchema == nil {
2020-08-25 13:18:16 +03:00
reguessOrErr ( )
2020-08-04 07:10:19 +03:00
return
}
2022-10-07 13:29:28 +03:00
primarySchema , foreignSchema = relation . FieldSchema , field . OwnerSchema
2020-08-28 06:31:13 +03:00
case guessHas :
case guessEmbeddedHas :
2022-10-07 13:29:28 +03:00
if field . OwnerSchema == nil {
2020-08-25 13:18:16 +03:00
reguessOrErr ( )
2020-08-04 07:10:19 +03:00
return
2020-02-01 10:23:45 +03:00
}
2022-10-07 13:29:28 +03:00
primarySchema , foreignSchema = field . OwnerSchema , relation . FieldSchema
2020-02-01 10:23:45 +03:00
}
2020-02-01 16:48:06 +03:00
if len ( relation . foreignKeys ) > 0 {
for _ , foreignKey := range relation . foreignKeys {
2022-10-07 13:29:28 +03:00
f := foreignSchema . LookUpField ( foreignKey )
if f == nil {
2020-08-25 13:18:16 +03:00
reguessOrErr ( )
2020-02-01 10:23:45 +03:00
return
}
2022-10-07 13:29:28 +03:00
foreignFields = append ( foreignFields , f )
2020-02-01 10:23:45 +03:00
}
} else {
2023-02-18 04:06:43 +03:00
primarySchemaName := primarySchema . Name
2022-03-18 09:30:30 +03:00
if primarySchemaName == "" {
primarySchemaName = relation . FieldSchema . Name
}
2020-12-28 12:58:12 +03:00
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
}
2023-04-11 08:10:38 +03:00
primaryFieldLoop :
2020-12-28 12:58:12 +03:00
for _ , primaryField := range primaryFields {
2022-03-18 09:30:30 +03:00
lookUpName := primarySchemaName + primaryField . Name
2020-08-04 07:10:19 +03:00
if gl == guessBelongs {
2020-02-01 16:48:06 +03:00
lookUpName = field . Name + primaryField . Name
}
2020-12-28 13:20:42 +03:00
lookUpNames := [ ] string { lookUpName }
if len ( primaryFields ) == 1 {
2023-12-15 11:36:08 +03:00
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" ) )
2020-12-28 13:20:42 +03:00
}
2023-04-11 08:10:38 +03:00
for _ , name := range lookUpNames {
if f := foreignSchema . LookUpFieldByBindName ( field . BindNames , name ) ; f != nil {
foreignFields = append ( foreignFields , f )
primaryFields = append ( primaryFields , primaryField )
continue primaryFieldLoop
}
}
2020-12-28 13:20:42 +03:00
for _ , name := range lookUpNames {
if f := foreignSchema . LookUpField ( name ) ; f != nil {
foreignFields = append ( foreignFields , f )
primaryFields = append ( primaryFields , primaryField )
2023-04-11 08:10:38 +03:00
continue primaryFieldLoop
2020-12-28 13:20:42 +03:00
}
2020-02-01 10:23:45 +03:00
}
}
}
2022-10-07 13:29:28 +03:00
switch {
case len ( foreignFields ) == 0 :
2020-08-25 13:18:16 +03:00
reguessOrErr ( )
2020-02-01 07:46:52 +03:00
return
2022-10-07 13:29:28 +03:00
case len ( relation . primaryKeys ) > 0 :
2020-02-01 16:48:06 +03:00
for idx , primaryKey := range relation . primaryKeys {
2020-02-01 10:23:45 +03:00
if f := primarySchema . LookUpField ( primaryKey ) ; f != nil {
if len ( primaryFields ) < idx + 1 {
primaryFields = append ( primaryFields , f )
} else if f != primaryFields [ idx ] {
2020-08-25 13:18:16 +03:00
reguessOrErr ( )
2020-02-01 10:23:45 +03:00
return
}
} else {
2020-08-25 13:18:16 +03:00
reguessOrErr ( )
2020-02-01 10:23:45 +03:00
return
}
}
2022-10-07 13:29:28 +03:00
case len ( primaryFields ) == 0 :
2020-12-06 13:07:12 +03:00
if len ( foreignFields ) == 1 && primarySchema . PrioritizedPrimaryField != nil {
2020-02-01 10:23:45 +03:00
primaryFields = append ( primaryFields , primarySchema . PrioritizedPrimaryField )
} else if len ( primarySchema . PrimaryFields ) == len ( foreignFields ) {
primaryFields = append ( primaryFields , primarySchema . PrimaryFields ... )
} else {
2020-08-25 13:18:16 +03:00
reguessOrErr ( )
2020-02-01 10:23:45 +03:00
return
}
2020-02-01 07:46:52 +03:00
}
2020-02-01 10:23:45 +03:00
// build references
for idx , foreignField := range foreignFields {
2020-06-02 02:28:29 +03:00
// use same data type for foreign keys
2021-04-30 11:25:56 +03:00
if copyableDataType ( primaryFields [ idx ] . DataType ) {
foreignField . DataType = primaryFields [ idx ] . DataType
}
2020-07-20 13:59:28 +03:00
foreignField . GORMDataType = primaryFields [ idx ] . GORMDataType
2020-09-03 11:11:15 +03:00
if foreignField . Size == 0 {
foreignField . Size = primaryFields [ idx ] . Size
}
2020-06-02 02:28:29 +03:00
2020-06-01 17:31:50 +03:00
relation . References = append ( relation . References , & Reference {
2020-02-01 19:03:56 +03:00
PrimaryKey : primaryFields [ idx ] ,
2020-02-01 10:23:45 +03:00
ForeignKey : foreignField ,
2020-08-04 07:10:19 +03:00
OwnPrimaryKey : ( schema == primarySchema && gl == guessHas ) || ( field . OwnerSchema == primarySchema && gl == guessEmbeddedHas ) ,
2020-02-01 10:23:45 +03:00
} )
2020-02-01 07:46:52 +03:00
}
2020-01-31 07:22:37 +03:00
2020-08-04 07:10:19 +03:00
if gl == guessHas || gl == guessEmbeddedHas {
2021-02-01 05:42:13 +03:00
relation . Type = has
2020-02-01 10:23:45 +03:00
} else {
2020-02-01 13:02:19 +03:00
relation . Type = BelongsTo
2020-02-01 10:23:45 +03:00
}
2020-01-31 07:22:37 +03:00
}
2020-02-22 06:15:51 +03:00
2024-02-04 10:49:19 +03:00
// Constraint is ForeignKey Constraint
2020-02-22 06:15:51 +03:00
type Constraint struct {
Name string
Field * Field
Schema * Schema
ForeignKeys [ ] * Field
ReferenceSchema * Schema
References [ ] * Field
OnDelete string
OnUpdate string
}
2024-02-04 10:49:19 +03:00
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
}
2020-02-22 06:15:51 +03:00
func ( rel * Relationship ) ParseConstraint ( ) * Constraint {
str := rel . Field . TagSettings [ "CONSTRAINT" ]
if str == "-" {
return nil
}
2021-02-16 03:35:19 +03:00
if rel . Type == BelongsTo {
for _ , r := range rel . FieldSchema . Relationships . Relations {
2021-11-08 04:47:29 +03:00
if r != rel && r . FieldSchema == rel . Schema && len ( rel . References ) == len ( r . References ) {
2021-02-16 03:35:19 +03:00
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
}
}
}
}
2020-02-22 06:15:51 +03:00
var (
name string
idx = strings . Index ( str , "," )
settings = ParseTagSetting ( str , "," )
)
2021-03-08 05:21:33 +03:00
// 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 ] ) {
2020-02-22 06:15:51 +03:00
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 {
2021-01-26 08:39:34 +03:00
if ref . PrimaryKey != nil && ( rel . JoinTable == nil || ref . OwnPrimaryKey ) {
2020-02-22 06:15:51 +03:00
constraint . ForeignKeys = append ( constraint . ForeignKeys , ref . ForeignKey )
constraint . References = append ( constraint . References , ref . PrimaryKey )
2020-06-19 19:48:15 +03:00
if ref . OwnPrimaryKey {
constraint . Schema = ref . ForeignKey . Schema
constraint . ReferenceSchema = rel . Schema
} else {
constraint . Schema = rel . Schema
constraint . ReferenceSchema = ref . PrimaryKey . Schema
}
2020-02-22 06:15:51 +03:00
}
}
return & constraint
}
2020-05-19 16:50:06 +03:00
2022-02-16 10:30:43 +03:00
func ( rel * Relationship ) ToQueryConditions ( ctx context . Context , reflectValue reflect . Value ) ( conds [ ] clause . Expression ) {
2020-07-09 04:03:48 +03:00
table := rel . FieldSchema . Table
2020-05-19 16:50:06 +03:00
foreignFields := [ ] * Field { }
relForeignKeys := [ ] string { }
if rel . JoinTable != nil {
2020-07-09 04:03:48 +03:00
table = rel . JoinTable . Table
2020-05-19 16:50:06 +03:00
for _ , ref := range rel . References {
if ref . OwnPrimaryKey {
foreignFields = append ( foreignFields , ref . PrimaryKey )
2020-05-25 18:11:42 +03:00
relForeignKeys = append ( relForeignKeys , ref . ForeignKey . DBName )
2020-05-19 16:50:06 +03:00
} 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 )
}
}
}
2022-02-16 10:30:43 +03:00
_ , foreignValues := GetIdentityFieldValuesMap ( ctx , reflectValue , foreignFields )
2020-07-09 04:03:48 +03:00
column , values := ToQueryValues ( table , relForeignKeys , foreignValues )
2020-05-24 12:24:23 +03:00
2020-05-19 16:50:06 +03:00
conds = append ( conds , clause . IN { Column : column , Values : values } )
return
}
2021-04-30 11:25:56 +03:00
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
}