package callbacks import ( "reflect" "github.com/jinzhu/gorm" "github.com/jinzhu/gorm/clause" "github.com/jinzhu/gorm/schema" "github.com/jinzhu/gorm/utils" ) // getRelationsValue get relations's value from a reflect value func getRelationsValue(reflectValue reflect.Value, rels []*schema.Relationship) (reflectResults reflect.Value) { for _, rel := range rels { reflectResults = reflect.MakeSlice(reflect.SliceOf(rel.FieldSchema.ModelType), 0, 0) appendToResults := func(value reflect.Value) { if _, isZero := rel.Field.ValueOf(value); !isZero { result := reflect.Indirect(rel.Field.ReflectValueOf(value)) switch result.Kind() { case reflect.Struct: reflectResults = reflect.Append(reflectResults, result) case reflect.Slice, reflect.Array: for i := 0; i < value.Len(); i++ { reflectResults = reflect.Append(reflectResults, reflect.Indirect(value.Index(i))) } } } } switch reflectValue.Kind() { case reflect.Struct: appendToResults(reflectValue) case reflect.Slice: for i := 0; i < reflectValue.Len(); i++ { appendToResults(reflectValue.Index(i)) } } reflectValue = reflectResults } return } func getIdentityFieldValuesMap(reflectValue reflect.Value, fields []*schema.Field) (map[string][]reflect.Value, [][]interface{}) { var ( fieldValues = make([]reflect.Value, len(fields)) results = [][]interface{}{} dataResults = map[string][]reflect.Value{} ) switch reflectValue.Kind() { case reflect.Struct: results = [][]interface{}{make([]interface{}, len(fields))} for idx, field := range fields { fieldValues[idx] = field.ReflectValueOf(reflectValue) results[0][idx] = fieldValues[idx].Interface() } dataResults[utils.ToStringKey(fieldValues...)] = []reflect.Value{reflectValue} case reflect.Slice, reflect.Array: for i := 0; i < reflectValue.Len(); i++ { for idx, field := range fields { fieldValues[idx] = field.ReflectValueOf(reflectValue.Index(i)) } dataKey := utils.ToStringKey(fieldValues...) if _, ok := dataResults[dataKey]; !ok { result := make([]interface{}, len(fieldValues)) for idx, fieldValue := range fieldValues { result[idx] = fieldValue.Interface() } results = append(results, result) dataResults[dataKey] = []reflect.Value{reflectValue.Index(i)} } else { dataResults[dataKey] = append(dataResults[dataKey], reflectValue.Index(i)) } } } return dataResults, results } func preloadData(tx *gorm.DB, resultSchema *schema.Schema, foreignKeys []string, foreignValues [][]interface{}) reflect.Value { results := reflect.MakeSlice(reflect.SliceOf(resultSchema.ModelType), 0, 0) queryValues := make([]interface{}, len(foreignValues)) if len(foreignKeys) == 1 { for idx, r := range foreignValues { queryValues[idx] = r[0] } tx.Where(clause.IN{Column: foreignKeys[0], Values: queryValues}).Find(results.Addr().Interface()) } else { for idx, r := range foreignValues { queryValues[idx] = r } tx.Where(clause.IN{Column: foreignKeys, Values: queryValues}).Find(results.Addr().Interface()) } return results } func preload(tx *gorm.DB, rels []*schema.Relationship, conds []interface{}) { var ( reflectValue = tx.Statement.ReflectValue rel = rels[len(rels)-1] relForeignKeys []string relForeignFields []*schema.Field foreignFields []*schema.Field foreignValues [][]interface{} identityMap = map[string][]reflect.Value{} ) if len(rels) > 1 { reflectValue = getRelationsValue(reflectValue, rels[:len(rels)]) } if rel.JoinTable != nil { var joinForeignFields, joinRelForeignFields []*schema.Field var joinForeignKeys []string for _, ref := range rel.References { if ref.OwnPrimaryKey { joinForeignKeys = append(joinForeignKeys, ref.ForeignKey.DBName) joinForeignFields = append(joinForeignFields, ref.ForeignKey) foreignFields = append(foreignFields, ref.PrimaryKey) } else if ref.PrimaryValue != "" { tx.Where(clause.Eq{Column: ref.ForeignKey.DBName, Value: ref.PrimaryValue}) } else { joinRelForeignFields = append(joinRelForeignFields, ref.ForeignKey) relForeignKeys = append(relForeignKeys, ref.PrimaryKey.DBName) relForeignFields = append(relForeignFields, ref.PrimaryKey) } } joinIdentityMap, joinForeignValues := getIdentityFieldValuesMap(reflectValue, joinForeignFields) joinResults := preloadData(tx, rel.JoinTable, joinForeignKeys, joinForeignValues) // convert join identity map to relation identity map fieldValues := make([]reflect.Value, len(foreignFields)) joinFieldValues := make([]reflect.Value, len(joinForeignFields)) for i := 0; i < joinResults.Len(); i++ { for idx, field := range foreignFields { fieldValues[idx] = field.ReflectValueOf(joinResults.Index(i)) } for idx, field := range joinForeignFields { joinFieldValues[idx] = field.ReflectValueOf(joinResults.Index(i)) } if results, ok := joinIdentityMap[utils.ToStringKey(fieldValues...)]; ok { identityMap[utils.ToStringKey(joinFieldValues...)] = results } } _, foreignValues = getIdentityFieldValuesMap(joinResults, joinRelForeignFields) } else { for _, ref := range rel.References { if ref.OwnPrimaryKey { relForeignKeys = append(relForeignKeys, ref.ForeignKey.DBName) relForeignFields = append(relForeignFields, ref.ForeignKey) foreignFields = append(foreignFields, ref.PrimaryKey) } else if ref.PrimaryValue != "" { tx.Where(clause.Eq{Column: ref.ForeignKey.DBName, Value: ref.PrimaryValue}) } else { relForeignKeys = append(relForeignKeys, ref.PrimaryKey.DBName) relForeignFields = append(relForeignFields, ref.PrimaryKey) foreignFields = append(foreignFields, ref.ForeignKey) } } identityMap, foreignValues = getIdentityFieldValuesMap(reflectValue, foreignFields) } reflectResults := preloadData(tx, rel.FieldSchema, relForeignKeys, foreignValues) fieldValues := make([]reflect.Value, len(foreignFields)) for i := 0; i < reflectResults.Len(); i++ { for idx, field := range foreignFields { fieldValues[idx] = field.ReflectValueOf(reflectResults.Index(i)) } for _, data := range identityMap[utils.ToStringKey(fieldValues...)] { reflectFieldValue := reflect.Indirect(rel.Field.ReflectValueOf(data)) switch reflectFieldValue.Kind() { case reflect.Struct: elem := reflectResults.Index(i).Convert(reflectFieldValue.Type().Elem()) rel.Field.Set(data, elem.Interface()) case reflect.Slice, reflect.Array: elem := reflectResults.Index(i).Convert(reflectFieldValue.Type().Elem()) rel.Field.Set(data, reflect.Append(reflectFieldValue, elem).Interface()) } } } }