github.com/systematiccaos/gorm@v1.22.6/schema/relationship.go (about) 1 package schema 2 3 import ( 4 "fmt" 5 "reflect" 6 "strings" 7 8 "github.com/jinzhu/inflection" 9 "github.com/systematiccaos/gorm/clause" 10 ) 11 12 // RelationshipType relationship type 13 type RelationshipType string 14 15 const ( 16 HasOne RelationshipType = "has_one" // HasOneRel has one relationship 17 HasMany RelationshipType = "has_many" // HasManyRel has many relationship 18 BelongsTo RelationshipType = "belongs_to" // BelongsToRel belongs to relationship 19 Many2Many RelationshipType = "many_to_many" // Many2ManyRel many to many relationship 20 has RelationshipType = "has" 21 ) 22 23 type Relationships struct { 24 HasOne []*Relationship 25 BelongsTo []*Relationship 26 HasMany []*Relationship 27 Many2Many []*Relationship 28 Relations map[string]*Relationship 29 } 30 31 type Relationship struct { 32 Name string 33 Type RelationshipType 34 Field *Field 35 Polymorphic *Polymorphic 36 References []*Reference 37 Schema *Schema 38 FieldSchema *Schema 39 JoinTable *Schema 40 foreignKeys, primaryKeys []string 41 } 42 43 type Polymorphic struct { 44 PolymorphicID *Field 45 PolymorphicType *Field 46 Value string 47 } 48 49 type Reference struct { 50 PrimaryKey *Field 51 PrimaryValue string 52 ForeignKey *Field 53 OwnPrimaryKey bool 54 } 55 56 func (schema *Schema) parseRelation(field *Field) *Relationship { 57 var ( 58 err error 59 fieldValue = reflect.New(field.IndirectFieldType).Interface() 60 relation = &Relationship{ 61 Name: field.Name, 62 Field: field, 63 Schema: schema, 64 foreignKeys: toColumns(field.TagSettings["FOREIGNKEY"]), 65 primaryKeys: toColumns(field.TagSettings["REFERENCES"]), 66 } 67 ) 68 69 cacheStore := schema.cacheStore 70 71 if relation.FieldSchema, err = getOrParse(fieldValue, cacheStore, schema.namer); err != nil { 72 schema.err = err 73 return nil 74 } 75 76 if polymorphic := field.TagSettings["POLYMORPHIC"]; polymorphic != "" { 77 schema.buildPolymorphicRelation(relation, field, polymorphic) 78 } else if many2many := field.TagSettings["MANY2MANY"]; many2many != "" { 79 schema.buildMany2ManyRelation(relation, field, many2many) 80 } else if belongsTo := field.TagSettings["BELONGSTO"]; belongsTo != "" { 81 schema.guessRelation(relation, field, guessBelongs) 82 } else { 83 switch field.IndirectFieldType.Kind() { 84 case reflect.Struct: 85 schema.guessRelation(relation, field, guessGuess) 86 case reflect.Slice: 87 schema.guessRelation(relation, field, guessHas) 88 default: 89 schema.err = fmt.Errorf("unsupported data type %v for %v on field %s", relation.FieldSchema, schema, field.Name) 90 } 91 } 92 93 if relation.Type == has { 94 // don't add relations to embedded schema, which might be shared 95 if relation.FieldSchema != relation.Schema && relation.Polymorphic == nil && field.OwnerSchema == nil { 96 relation.FieldSchema.Relationships.Relations["_"+relation.Schema.Name+"_"+relation.Name] = relation 97 } 98 99 switch field.IndirectFieldType.Kind() { 100 case reflect.Struct: 101 relation.Type = HasOne 102 case reflect.Slice: 103 relation.Type = HasMany 104 } 105 } 106 107 if schema.err == nil { 108 schema.Relationships.Relations[relation.Name] = relation 109 switch relation.Type { 110 case HasOne: 111 schema.Relationships.HasOne = append(schema.Relationships.HasOne, relation) 112 case HasMany: 113 schema.Relationships.HasMany = append(schema.Relationships.HasMany, relation) 114 case BelongsTo: 115 schema.Relationships.BelongsTo = append(schema.Relationships.BelongsTo, relation) 116 case Many2Many: 117 schema.Relationships.Many2Many = append(schema.Relationships.Many2Many, relation) 118 } 119 } 120 121 return relation 122 } 123 124 // User has many Toys, its `Polymorphic` is `Owner`, Pet has one Toy, its `Polymorphic` is `Owner` 125 // type User struct { 126 // Toys []Toy `gorm:"polymorphic:Owner;"` 127 // } 128 // type Pet struct { 129 // Toy Toy `gorm:"polymorphic:Owner;"` 130 // } 131 // type Toy struct { 132 // OwnerID int 133 // OwnerType string 134 // } 135 func (schema *Schema) buildPolymorphicRelation(relation *Relationship, field *Field, polymorphic string) { 136 relation.Polymorphic = &Polymorphic{ 137 Value: schema.Table, 138 PolymorphicType: relation.FieldSchema.FieldsByName[polymorphic+"Type"], 139 PolymorphicID: relation.FieldSchema.FieldsByName[polymorphic+"ID"], 140 } 141 142 if value, ok := field.TagSettings["POLYMORPHICVALUE"]; ok { 143 relation.Polymorphic.Value = strings.TrimSpace(value) 144 } 145 146 if relation.Polymorphic.PolymorphicType == nil { 147 schema.err = fmt.Errorf("invalid polymorphic type %v for %v on field %s, missing field %s", relation.FieldSchema, schema, field.Name, polymorphic+"Type") 148 } 149 150 if relation.Polymorphic.PolymorphicID == nil { 151 schema.err = fmt.Errorf("invalid polymorphic type %v for %v on field %s, missing field %s", relation.FieldSchema, schema, field.Name, polymorphic+"ID") 152 } 153 154 if schema.err == nil { 155 relation.References = append(relation.References, &Reference{ 156 PrimaryValue: relation.Polymorphic.Value, 157 ForeignKey: relation.Polymorphic.PolymorphicType, 158 }) 159 160 primaryKeyField := schema.PrioritizedPrimaryField 161 if len(relation.foreignKeys) > 0 { 162 if primaryKeyField = schema.LookUpField(relation.foreignKeys[0]); primaryKeyField == nil || len(relation.foreignKeys) > 1 { 163 schema.err = fmt.Errorf("invalid polymorphic foreign keys %+v for %v on field %s", relation.foreignKeys, schema, field.Name) 164 } 165 } 166 167 // use same data type for foreign keys 168 if copyableDataType(primaryKeyField.DataType) { 169 relation.Polymorphic.PolymorphicID.DataType = primaryKeyField.DataType 170 } 171 relation.Polymorphic.PolymorphicID.GORMDataType = primaryKeyField.GORMDataType 172 if relation.Polymorphic.PolymorphicID.Size == 0 { 173 relation.Polymorphic.PolymorphicID.Size = primaryKeyField.Size 174 } 175 176 relation.References = append(relation.References, &Reference{ 177 PrimaryKey: primaryKeyField, 178 ForeignKey: relation.Polymorphic.PolymorphicID, 179 OwnPrimaryKey: true, 180 }) 181 } 182 183 relation.Type = has 184 } 185 186 func (schema *Schema) buildMany2ManyRelation(relation *Relationship, field *Field, many2many string) { 187 relation.Type = Many2Many 188 189 var ( 190 err error 191 joinTableFields []reflect.StructField 192 fieldsMap = map[string]*Field{} 193 ownFieldsMap = map[string]bool{} // fix self join many2many 194 joinForeignKeys = toColumns(field.TagSettings["JOINFOREIGNKEY"]) 195 joinReferences = toColumns(field.TagSettings["JOINREFERENCES"]) 196 ) 197 198 ownForeignFields := schema.PrimaryFields 199 refForeignFields := relation.FieldSchema.PrimaryFields 200 201 if len(relation.foreignKeys) > 0 { 202 ownForeignFields = []*Field{} 203 for _, foreignKey := range relation.foreignKeys { 204 if field := schema.LookUpField(foreignKey); field != nil { 205 ownForeignFields = append(ownForeignFields, field) 206 } else { 207 schema.err = fmt.Errorf("invalid foreign key: %s", foreignKey) 208 return 209 } 210 } 211 } 212 213 if len(relation.primaryKeys) > 0 { 214 refForeignFields = []*Field{} 215 for _, foreignKey := range relation.primaryKeys { 216 if field := relation.FieldSchema.LookUpField(foreignKey); field != nil { 217 refForeignFields = append(refForeignFields, field) 218 } else { 219 schema.err = fmt.Errorf("invalid foreign key: %s", foreignKey) 220 return 221 } 222 } 223 } 224 225 for idx, ownField := range ownForeignFields { 226 joinFieldName := strings.Title(schema.Name) + ownField.Name 227 if len(joinForeignKeys) > idx { 228 joinFieldName = strings.Title(joinForeignKeys[idx]) 229 } 230 231 ownFieldsMap[joinFieldName] = true 232 fieldsMap[joinFieldName] = ownField 233 joinTableFields = append(joinTableFields, reflect.StructField{ 234 Name: joinFieldName, 235 PkgPath: ownField.StructField.PkgPath, 236 Type: ownField.StructField.Type, 237 Tag: removeSettingFromTag(ownField.StructField.Tag, "column", "autoincrement", "index", "unique", "uniqueindex"), 238 }) 239 } 240 241 for idx, relField := range refForeignFields { 242 joinFieldName := strings.Title(relation.FieldSchema.Name) + relField.Name 243 if len(joinReferences) > idx { 244 joinFieldName = strings.Title(joinReferences[idx]) 245 } 246 247 if _, ok := ownFieldsMap[joinFieldName]; ok { 248 if field.Name != relation.FieldSchema.Name { 249 joinFieldName = inflection.Singular(field.Name) + relField.Name 250 } else { 251 joinFieldName += "Reference" 252 } 253 } 254 255 fieldsMap[joinFieldName] = relField 256 joinTableFields = append(joinTableFields, reflect.StructField{ 257 Name: joinFieldName, 258 PkgPath: relField.StructField.PkgPath, 259 Type: relField.StructField.Type, 260 Tag: removeSettingFromTag(relField.StructField.Tag, "column", "autoincrement", "index", "unique", "uniqueindex"), 261 }) 262 } 263 264 joinTableFields = append(joinTableFields, reflect.StructField{ 265 Name: strings.Title(schema.Name) + field.Name, 266 Type: schema.ModelType, 267 Tag: `gorm:"-"`, 268 }) 269 270 if relation.JoinTable, err = Parse(reflect.New(reflect.StructOf(joinTableFields)).Interface(), schema.cacheStore, schema.namer); err != nil { 271 schema.err = err 272 } 273 relation.JoinTable.Name = many2many 274 relation.JoinTable.Table = schema.namer.JoinTableName(many2many) 275 relation.JoinTable.PrimaryFields = make([]*Field, 0, len(relation.JoinTable.Fields)) 276 277 relName := relation.Schema.Name 278 relRefName := relation.FieldSchema.Name 279 if relName == relRefName { 280 relRefName = relation.Field.Name 281 } 282 283 if _, ok := relation.JoinTable.Relationships.Relations[relName]; !ok { 284 relation.JoinTable.Relationships.Relations[relName] = &Relationship{ 285 Name: relName, 286 Type: BelongsTo, 287 Schema: relation.JoinTable, 288 FieldSchema: relation.Schema, 289 } 290 } else { 291 relation.JoinTable.Relationships.Relations[relName].References = []*Reference{} 292 } 293 294 if _, ok := relation.JoinTable.Relationships.Relations[relRefName]; !ok { 295 relation.JoinTable.Relationships.Relations[relRefName] = &Relationship{ 296 Name: relRefName, 297 Type: BelongsTo, 298 Schema: relation.JoinTable, 299 FieldSchema: relation.FieldSchema, 300 } 301 } else { 302 relation.JoinTable.Relationships.Relations[relRefName].References = []*Reference{} 303 } 304 305 // build references 306 for _, f := range relation.JoinTable.Fields { 307 if f.Creatable || f.Readable || f.Updatable { 308 // use same data type for foreign keys 309 if copyableDataType(fieldsMap[f.Name].DataType) { 310 f.DataType = fieldsMap[f.Name].DataType 311 } 312 f.GORMDataType = fieldsMap[f.Name].GORMDataType 313 if f.Size == 0 { 314 f.Size = fieldsMap[f.Name].Size 315 } 316 relation.JoinTable.PrimaryFields = append(relation.JoinTable.PrimaryFields, f) 317 ownPrimaryField := schema == fieldsMap[f.Name].Schema && ownFieldsMap[f.Name] 318 319 if ownPrimaryField { 320 joinRel := relation.JoinTable.Relationships.Relations[relName] 321 joinRel.Field = relation.Field 322 joinRel.References = append(joinRel.References, &Reference{ 323 PrimaryKey: fieldsMap[f.Name], 324 ForeignKey: f, 325 }) 326 } else { 327 joinRefRel := relation.JoinTable.Relationships.Relations[relRefName] 328 if joinRefRel.Field == nil { 329 joinRefRel.Field = relation.Field 330 } 331 joinRefRel.References = append(joinRefRel.References, &Reference{ 332 PrimaryKey: fieldsMap[f.Name], 333 ForeignKey: f, 334 }) 335 } 336 337 relation.References = append(relation.References, &Reference{ 338 PrimaryKey: fieldsMap[f.Name], 339 ForeignKey: f, 340 OwnPrimaryKey: ownPrimaryField, 341 }) 342 } 343 } 344 } 345 346 type guessLevel int 347 348 const ( 349 guessGuess guessLevel = iota 350 guessBelongs 351 guessEmbeddedBelongs 352 guessHas 353 guessEmbeddedHas 354 ) 355 356 func (schema *Schema) guessRelation(relation *Relationship, field *Field, cgl guessLevel) { 357 var ( 358 primaryFields, foreignFields []*Field 359 primarySchema, foreignSchema = schema, relation.FieldSchema 360 gl = cgl 361 ) 362 363 if gl == guessGuess { 364 if field.Schema == relation.FieldSchema { 365 gl = guessBelongs 366 } else { 367 gl = guessHas 368 } 369 } 370 371 reguessOrErr := func() { 372 switch cgl { 373 case guessGuess: 374 schema.guessRelation(relation, field, guessBelongs) 375 case guessBelongs: 376 schema.guessRelation(relation, field, guessEmbeddedBelongs) 377 case guessEmbeddedBelongs: 378 schema.guessRelation(relation, field, guessHas) 379 case guessHas: 380 schema.guessRelation(relation, field, guessEmbeddedHas) 381 // case guessEmbeddedHas: 382 default: 383 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) 384 } 385 } 386 387 switch gl { 388 case guessBelongs: 389 primarySchema, foreignSchema = relation.FieldSchema, schema 390 case guessEmbeddedBelongs: 391 if field.OwnerSchema != nil { 392 primarySchema, foreignSchema = relation.FieldSchema, field.OwnerSchema 393 } else { 394 reguessOrErr() 395 return 396 } 397 case guessHas: 398 case guessEmbeddedHas: 399 if field.OwnerSchema != nil { 400 primarySchema, foreignSchema = field.OwnerSchema, relation.FieldSchema 401 } else { 402 reguessOrErr() 403 return 404 } 405 } 406 407 if len(relation.foreignKeys) > 0 { 408 for _, foreignKey := range relation.foreignKeys { 409 if f := foreignSchema.LookUpField(foreignKey); f != nil { 410 foreignFields = append(foreignFields, f) 411 } else { 412 reguessOrErr() 413 return 414 } 415 } 416 } else { 417 var primaryFields []*Field 418 419 if len(relation.primaryKeys) > 0 { 420 for _, primaryKey := range relation.primaryKeys { 421 if f := primarySchema.LookUpField(primaryKey); f != nil { 422 primaryFields = append(primaryFields, f) 423 } 424 } 425 } else { 426 primaryFields = primarySchema.PrimaryFields 427 } 428 429 for _, primaryField := range primaryFields { 430 lookUpName := primarySchema.Name + primaryField.Name 431 if gl == guessBelongs { 432 lookUpName = field.Name + primaryField.Name 433 } 434 435 lookUpNames := []string{lookUpName} 436 if len(primaryFields) == 1 { 437 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")) 438 } 439 440 for _, name := range lookUpNames { 441 if f := foreignSchema.LookUpField(name); f != nil { 442 foreignFields = append(foreignFields, f) 443 primaryFields = append(primaryFields, primaryField) 444 break 445 } 446 } 447 } 448 } 449 450 if len(foreignFields) == 0 { 451 reguessOrErr() 452 return 453 } else if len(relation.primaryKeys) > 0 { 454 for idx, primaryKey := range relation.primaryKeys { 455 if f := primarySchema.LookUpField(primaryKey); f != nil { 456 if len(primaryFields) < idx+1 { 457 primaryFields = append(primaryFields, f) 458 } else if f != primaryFields[idx] { 459 reguessOrErr() 460 return 461 } 462 } else { 463 reguessOrErr() 464 return 465 } 466 } 467 } else if len(primaryFields) == 0 { 468 if len(foreignFields) == 1 && primarySchema.PrioritizedPrimaryField != nil { 469 primaryFields = append(primaryFields, primarySchema.PrioritizedPrimaryField) 470 } else if len(primarySchema.PrimaryFields) == len(foreignFields) { 471 primaryFields = append(primaryFields, primarySchema.PrimaryFields...) 472 } else { 473 reguessOrErr() 474 return 475 } 476 } 477 478 // build references 479 for idx, foreignField := range foreignFields { 480 // use same data type for foreign keys 481 if copyableDataType(primaryFields[idx].DataType) { 482 foreignField.DataType = primaryFields[idx].DataType 483 } 484 foreignField.GORMDataType = primaryFields[idx].GORMDataType 485 if foreignField.Size == 0 { 486 foreignField.Size = primaryFields[idx].Size 487 } 488 489 relation.References = append(relation.References, &Reference{ 490 PrimaryKey: primaryFields[idx], 491 ForeignKey: foreignField, 492 OwnPrimaryKey: (schema == primarySchema && gl == guessHas) || (field.OwnerSchema == primarySchema && gl == guessEmbeddedHas), 493 }) 494 } 495 496 if gl == guessHas || gl == guessEmbeddedHas { 497 relation.Type = has 498 } else { 499 relation.Type = BelongsTo 500 } 501 } 502 503 type Constraint struct { 504 Name string 505 Field *Field 506 Schema *Schema 507 ForeignKeys []*Field 508 ReferenceSchema *Schema 509 References []*Field 510 OnDelete string 511 OnUpdate string 512 } 513 514 func (rel *Relationship) ParseConstraint() *Constraint { 515 str := rel.Field.TagSettings["CONSTRAINT"] 516 if str == "-" { 517 return nil 518 } 519 520 if rel.Type == BelongsTo { 521 for _, r := range rel.FieldSchema.Relationships.Relations { 522 if r != rel && r.FieldSchema == rel.Schema && len(rel.References) == len(r.References) { 523 matched := true 524 for idx, ref := range r.References { 525 if !(rel.References[idx].PrimaryKey == ref.PrimaryKey && rel.References[idx].ForeignKey == ref.ForeignKey && 526 rel.References[idx].PrimaryValue == ref.PrimaryValue) { 527 matched = false 528 } 529 } 530 531 if matched { 532 return nil 533 } 534 } 535 } 536 } 537 538 var ( 539 name string 540 idx = strings.Index(str, ",") 541 settings = ParseTagSetting(str, ",") 542 ) 543 544 // optimize match english letters and midline 545 // The following code is basically called in for. 546 // In order to avoid the performance problems caused by repeated compilation of regular expressions, 547 // it only needs to be done once outside, so optimization is done here. 548 if idx != -1 && regEnLetterAndMidline.MatchString(str[0:idx]) { 549 name = str[0:idx] 550 } else { 551 name = rel.Schema.namer.RelationshipFKName(*rel) 552 } 553 554 constraint := Constraint{ 555 Name: name, 556 Field: rel.Field, 557 OnUpdate: settings["ONUPDATE"], 558 OnDelete: settings["ONDELETE"], 559 } 560 561 for _, ref := range rel.References { 562 if ref.PrimaryKey != nil && (rel.JoinTable == nil || ref.OwnPrimaryKey) { 563 constraint.ForeignKeys = append(constraint.ForeignKeys, ref.ForeignKey) 564 constraint.References = append(constraint.References, ref.PrimaryKey) 565 566 if ref.OwnPrimaryKey { 567 constraint.Schema = ref.ForeignKey.Schema 568 constraint.ReferenceSchema = rel.Schema 569 } else { 570 constraint.Schema = rel.Schema 571 constraint.ReferenceSchema = ref.PrimaryKey.Schema 572 } 573 } 574 } 575 576 return &constraint 577 } 578 579 func (rel *Relationship) ToQueryConditions(reflectValue reflect.Value) (conds []clause.Expression) { 580 table := rel.FieldSchema.Table 581 foreignFields := []*Field{} 582 relForeignKeys := []string{} 583 584 if rel.JoinTable != nil { 585 table = rel.JoinTable.Table 586 for _, ref := range rel.References { 587 if ref.OwnPrimaryKey { 588 foreignFields = append(foreignFields, ref.PrimaryKey) 589 relForeignKeys = append(relForeignKeys, ref.ForeignKey.DBName) 590 } else if ref.PrimaryValue != "" { 591 conds = append(conds, clause.Eq{ 592 Column: clause.Column{Table: rel.JoinTable.Table, Name: ref.ForeignKey.DBName}, 593 Value: ref.PrimaryValue, 594 }) 595 } else { 596 conds = append(conds, clause.Eq{ 597 Column: clause.Column{Table: rel.JoinTable.Table, Name: ref.ForeignKey.DBName}, 598 Value: clause.Column{Table: rel.FieldSchema.Table, Name: ref.PrimaryKey.DBName}, 599 }) 600 } 601 } 602 } else { 603 for _, ref := range rel.References { 604 if ref.OwnPrimaryKey { 605 relForeignKeys = append(relForeignKeys, ref.ForeignKey.DBName) 606 foreignFields = append(foreignFields, ref.PrimaryKey) 607 } else if ref.PrimaryValue != "" { 608 conds = append(conds, clause.Eq{ 609 Column: clause.Column{Table: rel.FieldSchema.Table, Name: ref.ForeignKey.DBName}, 610 Value: ref.PrimaryValue, 611 }) 612 } else { 613 relForeignKeys = append(relForeignKeys, ref.PrimaryKey.DBName) 614 foreignFields = append(foreignFields, ref.ForeignKey) 615 } 616 } 617 } 618 619 _, foreignValues := GetIdentityFieldValuesMap(reflectValue, foreignFields) 620 column, values := ToQueryValues(table, relForeignKeys, foreignValues) 621 622 conds = append(conds, clause.IN{Column: column, Values: values}) 623 return 624 } 625 626 func copyableDataType(str DataType) bool { 627 for _, s := range []string{"auto_increment", "primary key"} { 628 if strings.Contains(strings.ToLower(string(str)), s) { 629 return false 630 } 631 } 632 return true 633 }