11 // preloadCallback used to preload associations
12 func preloadCallback(scope *Scope) {
13 if _, skip := scope.InstanceGet("gorm:skip_query_callback"); skip {
17 if ap, ok := scope.Get("gorm:auto_preload"); ok {
18 // If gorm:auto_preload IS NOT a bool then auto preload.
19 // Else if it IS a bool, use the value
20 if apb, ok := ap.(bool); !ok {
27 if scope.Search.preload == nil || scope.HasError() {
32 preloadedMap = map[string]bool{}
33 fields = scope.Fields()
36 for _, preload := range scope.Search.preload {
38 preloadFields = strings.Split(preload.schema, ".")
40 currentFields = fields
43 for idx, preloadField := range preloadFields {
44 var currentPreloadConditions []interface{}
46 if currentScope == nil {
51 if preloadKey := strings.Join(preloadFields[:idx+1], "."); !preloadedMap[preloadKey] {
53 // assign search conditions to last preload
54 if idx == len(preloadFields)-1 {
55 currentPreloadConditions = preload.conditions
58 for _, field := range currentFields {
59 if field.Name != preloadField || field.Relationship == nil {
63 switch field.Relationship.Kind {
65 currentScope.handleHasOnePreload(field, currentPreloadConditions)
67 currentScope.handleHasManyPreload(field, currentPreloadConditions)
69 currentScope.handleBelongsToPreload(field, currentPreloadConditions)
71 currentScope.handleManyToManyPreload(field, currentPreloadConditions)
73 scope.Err(errors.New("unsupported relation"))
76 preloadedMap[preloadKey] = true
80 if !preloadedMap[preloadKey] {
81 scope.Err(fmt.Errorf("can't preload field %s for %s", preloadField, currentScope.GetModelStruct().ModelType))
87 if idx < len(preloadFields)-1 {
88 currentScope = currentScope.getColumnAsScope(preloadField)
89 if currentScope != nil {
90 currentFields = currentScope.Fields()
97 func autoPreload(scope *Scope) {
98 for _, field := range scope.Fields() {
99 if field.Relationship == nil {
103 if val, ok := field.TagSettingsGet("PRELOAD"); ok {
104 if preload, err := strconv.ParseBool(val); err != nil {
105 scope.Err(errors.New("invalid preload option"))
112 scope.Search.Preload(field.Name)
116 func (scope *Scope) generatePreloadDBWithConditions(conditions []interface{}) (*DB, []interface{}) {
118 preloadDB = scope.NewDB()
119 preloadConditions []interface{}
122 for _, condition := range conditions {
123 if scopes, ok := condition.(func(*DB) *DB); ok {
124 preloadDB = scopes(preloadDB)
126 preloadConditions = append(preloadConditions, condition)
130 return preloadDB, preloadConditions
133 // handleHasOnePreload used to preload has one associations
134 func (scope *Scope) handleHasOnePreload(field *Field, conditions []interface{}) {
135 relation := field.Relationship
137 // get relations's primary keys
138 primaryKeys := scope.getColumnAsArray(relation.AssociationForeignFieldNames, scope.Value)
139 if len(primaryKeys) == 0 {
143 // preload conditions
144 preloadDB, preloadConditions := scope.generatePreloadDBWithConditions(conditions)
147 query := fmt.Sprintf("%v IN (%v)", toQueryCondition(scope, relation.ForeignDBNames), toQueryMarks(primaryKeys))
148 values := toQueryValues(primaryKeys)
149 if relation.PolymorphicType != "" {
150 query += fmt.Sprintf(" AND %v = ?", scope.Quote(relation.PolymorphicDBName))
151 values = append(values, relation.PolymorphicValue)
154 results := makeSlice(field.Struct.Type)
155 scope.Err(preloadDB.Where(query, values...).Find(results, preloadConditions...).Error)
157 // assign find results
159 resultsValue = indirect(reflect.ValueOf(results))
160 indirectScopeValue = scope.IndirectValue()
163 if indirectScopeValue.Kind() == reflect.Slice {
164 foreignValuesToResults := make(map[string]reflect.Value)
165 for i := 0; i < resultsValue.Len(); i++ {
166 result := resultsValue.Index(i)
167 foreignValues := toString(getValueFromFields(result, relation.ForeignFieldNames))
168 foreignValuesToResults[foreignValues] = result
170 for j := 0; j < indirectScopeValue.Len(); j++ {
171 indirectValue := indirect(indirectScopeValue.Index(j))
172 valueString := toString(getValueFromFields(indirectValue, relation.AssociationForeignFieldNames))
173 if result, found := foreignValuesToResults[valueString]; found {
174 indirectValue.FieldByName(field.Name).Set(result)
178 for i := 0; i < resultsValue.Len(); i++ {
179 result := resultsValue.Index(i)
180 scope.Err(field.Set(result))
185 // handleHasManyPreload used to preload has many associations
186 func (scope *Scope) handleHasManyPreload(field *Field, conditions []interface{}) {
187 relation := field.Relationship
189 // get relations's primary keys
190 primaryKeys := scope.getColumnAsArray(relation.AssociationForeignFieldNames, scope.Value)
191 if len(primaryKeys) == 0 {
195 // preload conditions
196 preloadDB, preloadConditions := scope.generatePreloadDBWithConditions(conditions)
199 query := fmt.Sprintf("%v IN (%v)", toQueryCondition(scope, relation.ForeignDBNames), toQueryMarks(primaryKeys))
200 values := toQueryValues(primaryKeys)
201 if relation.PolymorphicType != "" {
202 query += fmt.Sprintf(" AND %v = ?", scope.Quote(relation.PolymorphicDBName))
203 values = append(values, relation.PolymorphicValue)
206 results := makeSlice(field.Struct.Type)
207 scope.Err(preloadDB.Where(query, values...).Find(results, preloadConditions...).Error)
209 // assign find results
211 resultsValue = indirect(reflect.ValueOf(results))
212 indirectScopeValue = scope.IndirectValue()
215 if indirectScopeValue.Kind() == reflect.Slice {
216 preloadMap := make(map[string][]reflect.Value)
217 for i := 0; i < resultsValue.Len(); i++ {
218 result := resultsValue.Index(i)
219 foreignValues := getValueFromFields(result, relation.ForeignFieldNames)
220 preloadMap[toString(foreignValues)] = append(preloadMap[toString(foreignValues)], result)
223 for j := 0; j < indirectScopeValue.Len(); j++ {
224 object := indirect(indirectScopeValue.Index(j))
225 objectRealValue := getValueFromFields(object, relation.AssociationForeignFieldNames)
226 f := object.FieldByName(field.Name)
227 if results, ok := preloadMap[toString(objectRealValue)]; ok {
228 f.Set(reflect.Append(f, results...))
230 f.Set(reflect.MakeSlice(f.Type(), 0, 0))
234 scope.Err(field.Set(resultsValue))
238 // handleBelongsToPreload used to preload belongs to associations
239 func (scope *Scope) handleBelongsToPreload(field *Field, conditions []interface{}) {
240 relation := field.Relationship
242 // preload conditions
243 preloadDB, preloadConditions := scope.generatePreloadDBWithConditions(conditions)
245 // get relations's primary keys
246 primaryKeys := scope.getColumnAsArray(relation.ForeignFieldNames, scope.Value)
247 if len(primaryKeys) == 0 {
252 results := makeSlice(field.Struct.Type)
253 scope.Err(preloadDB.Where(fmt.Sprintf("%v IN (%v)", toQueryCondition(scope, relation.AssociationForeignDBNames), toQueryMarks(primaryKeys)), toQueryValues(primaryKeys)...).Find(results, preloadConditions...).Error)
255 // assign find results
257 resultsValue = indirect(reflect.ValueOf(results))
258 indirectScopeValue = scope.IndirectValue()
261 foreignFieldToObjects := make(map[string][]*reflect.Value)
262 if indirectScopeValue.Kind() == reflect.Slice {
263 for j := 0; j < indirectScopeValue.Len(); j++ {
264 object := indirect(indirectScopeValue.Index(j))
265 valueString := toString(getValueFromFields(object, relation.ForeignFieldNames))
266 foreignFieldToObjects[valueString] = append(foreignFieldToObjects[valueString], &object)
270 for i := 0; i < resultsValue.Len(); i++ {
271 result := resultsValue.Index(i)
272 if indirectScopeValue.Kind() == reflect.Slice {
273 valueString := toString(getValueFromFields(result, relation.AssociationForeignFieldNames))
274 if objects, found := foreignFieldToObjects[valueString]; found {
275 for _, object := range objects {
276 object.FieldByName(field.Name).Set(result)
280 scope.Err(field.Set(result))
285 // handleManyToManyPreload used to preload many to many associations
286 func (scope *Scope) handleManyToManyPreload(field *Field, conditions []interface{}) {
288 relation = field.Relationship
289 joinTableHandler = relation.JoinTableHandler
290 fieldType = field.Struct.Type.Elem()
291 foreignKeyValue interface{}
292 foreignKeyType = reflect.ValueOf(&foreignKeyValue).Type()
293 linkHash = map[string][]reflect.Value{}
297 if fieldType.Kind() == reflect.Ptr {
299 fieldType = fieldType.Elem()
302 var sourceKeys = []string{}
303 for _, key := range joinTableHandler.SourceForeignKeys() {
304 sourceKeys = append(sourceKeys, key.DBName)
307 // preload conditions
308 preloadDB, preloadConditions := scope.generatePreloadDBWithConditions(conditions)
310 // generate query with join table
311 newScope := scope.New(reflect.New(fieldType).Interface())
312 preloadDB = preloadDB.Table(newScope.TableName()).Model(newScope.Value)
314 if len(preloadDB.search.selects) == 0 {
315 preloadDB = preloadDB.Select("*")
318 preloadDB = joinTableHandler.JoinWith(joinTableHandler, preloadDB, scope.Value)
320 // preload inline conditions
321 if len(preloadConditions) > 0 {
322 preloadDB = preloadDB.Where(preloadConditions[0], preloadConditions[1:]...)
325 rows, err := preloadDB.Rows()
327 if scope.Err(err) != nil {
332 columns, _ := rows.Columns()
335 elem = reflect.New(fieldType).Elem()
336 fields = scope.New(elem.Addr().Interface()).Fields()
339 // register foreign keys in join tables
340 var joinTableFields []*Field
341 for _, sourceKey := range sourceKeys {
342 joinTableFields = append(joinTableFields, &Field{StructField: &StructField{DBName: sourceKey, IsNormal: true}, Field: reflect.New(foreignKeyType).Elem()})
345 scope.scan(rows, columns, append(fields, joinTableFields...))
347 scope.New(elem.Addr().Interface()).
348 InstanceSet("gorm:skip_query_callback", true).
349 callCallbacks(scope.db.parent.callbacks.queries)
351 var foreignKeys = make([]interface{}, len(sourceKeys))
352 // generate hashed forkey keys in join table
353 for idx, joinTableField := range joinTableFields {
354 if !joinTableField.Field.IsNil() {
355 foreignKeys[idx] = joinTableField.Field.Elem().Interface()
358 hashedSourceKeys := toString(foreignKeys)
361 linkHash[hashedSourceKeys] = append(linkHash[hashedSourceKeys], elem.Addr())
363 linkHash[hashedSourceKeys] = append(linkHash[hashedSourceKeys], elem)
367 if err := rows.Err(); err != nil {
371 // assign find results
373 indirectScopeValue = scope.IndirectValue()
374 fieldsSourceMap = map[string][]reflect.Value{}
375 foreignFieldNames = []string{}
378 for _, dbName := range relation.ForeignFieldNames {
379 if field, ok := scope.FieldByName(dbName); ok {
380 foreignFieldNames = append(foreignFieldNames, field.Name)
384 if indirectScopeValue.Kind() == reflect.Slice {
385 for j := 0; j < indirectScopeValue.Len(); j++ {
386 object := indirect(indirectScopeValue.Index(j))
387 key := toString(getValueFromFields(object, foreignFieldNames))
388 fieldsSourceMap[key] = append(fieldsSourceMap[key], object.FieldByName(field.Name))
390 } else if indirectScopeValue.IsValid() {
391 key := toString(getValueFromFields(indirectScopeValue, foreignFieldNames))
392 fieldsSourceMap[key] = append(fieldsSourceMap[key], indirectScopeValue.FieldByName(field.Name))
394 for source, link := range linkHash {
395 for i, field := range fieldsSourceMap[source] {
396 //If not 0 this means Value is a pointer and we already added preloaded models to it
397 if fieldsSourceMap[source][i].Len() != 0 {
400 field.Set(reflect.Append(fieldsSourceMap[source][i], link...))