Skip to content

Commit 85299bf

Browse files
authored
perf: merge nested preload query when using join (#6990)
* pref: merge nest preload query * fix: preload test
1 parent 5553ff3 commit 85299bf

File tree

2 files changed

+88
-12
lines changed

2 files changed

+88
-12
lines changed

callbacks/preload.go

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -123,8 +123,18 @@ func preloadEntryPoint(db *gorm.DB, joins []string, relationships *schema.Relati
123123
if joined, nestedJoins := isJoined(name); joined {
124124
switch rv := db.Statement.ReflectValue; rv.Kind() {
125125
case reflect.Slice, reflect.Array:
126-
for i := 0; i < rv.Len(); i++ {
127-
reflectValue := rel.Field.ReflectValueOf(db.Statement.Context, rv.Index(i))
126+
if rv.Len() > 0 {
127+
reflectValue := rel.FieldSchema.MakeSlice().Elem()
128+
reflectValue.SetLen(rv.Len())
129+
for i := 0; i < rv.Len(); i++ {
130+
frv := rel.Field.ReflectValueOf(db.Statement.Context, rv.Index(i))
131+
if frv.Kind() != reflect.Ptr {
132+
reflectValue.Index(i).Set(frv.Addr())
133+
} else {
134+
reflectValue.Index(i).Set(frv)
135+
}
136+
}
137+
128138
tx := preloadDB(db, reflectValue, reflectValue.Interface())
129139
if err := preloadEntryPoint(tx, nestedJoins, &tx.Statement.Schema.Relationships, preloadMap[name], associationsConds); err != nil {
130140
return err

tests/preload_test.go

Lines changed: 76 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
11
package tests_test
22

33
import (
4+
"context"
45
"encoding/json"
56
"regexp"
67
"sort"
78
"strconv"
89
"sync"
910
"testing"
10-
11-
"github.com/stretchr/testify/require"
11+
"time"
1212

1313
"gorm.io/gorm"
1414
"gorm.io/gorm/clause"
@@ -337,7 +337,7 @@ func TestNestedPreloadWithNestedJoin(t *testing.T) {
337337
DB.Migrator().DropTable(&Preload{}, &Join{}, &Nested{}, &Value{})
338338
DB.Migrator().AutoMigrate(&Preload{}, &Join{}, &Nested{}, &Value{})
339339

340-
value := Value{
340+
value1 := Value{
341341
Name: "value",
342342
Nested: Nested{
343343
Preloads: []*Preload{
@@ -346,32 +346,98 @@ func TestNestedPreloadWithNestedJoin(t *testing.T) {
346346
Join: Join{Value: "j1"},
347347
},
348348
}
349-
if err := DB.Create(&value).Error; err != nil {
349+
value2 := Value{
350+
Name: "value2",
351+
Nested: Nested{
352+
Preloads: []*Preload{
353+
{Value: "p3"}, {Value: "p4"}, {Value: "p5"},
354+
},
355+
Join: Join{Value: "j2"},
356+
},
357+
}
358+
359+
values := []*Value{&value1, &value2}
360+
if err := DB.Create(&values).Error; err != nil {
350361
t.Errorf("failed to create value, got err: %v", err)
351362
}
352363

353364
var find1 Value
354-
err := DB.Joins("Nested").Joins("Nested.Join").Preload("Nested.Preloads").First(&find1).Error
365+
err := DB.Joins("Nested").Joins("Nested.Join").Preload("Nested.Preloads").First(&find1, value1.ID).Error
355366
if err != nil {
356367
t.Errorf("failed to find value, got err: %v", err)
357368
}
358-
AssertEqual(t, find1, value)
369+
AssertEqual(t, find1, value1)
359370

360371
var find2 Value
361372
// Joins will automatically add Nested queries.
362-
err = DB.Joins("Nested.Join").Preload("Nested.Preloads").First(&find2).Error
373+
err = DB.Joins("Nested.Join").Preload("Nested.Preloads").First(&find2, value2.ID).Error
363374
if err != nil {
364375
t.Errorf("failed to find value, got err: %v", err)
365376
}
366-
AssertEqual(t, find2, value)
377+
AssertEqual(t, find2, value2)
367378

368379
var finds []Value
369380
err = DB.Joins("Nested.Join").Joins("Nested").Preload("Nested.Preloads").Find(&finds).Error
370381
if err != nil {
371382
t.Errorf("failed to find value, got err: %v", err)
372383
}
373-
require.Len(t, finds, 1)
374-
AssertEqual(t, finds[0], value)
384+
AssertEqual(t, len(finds), 2)
385+
AssertEqual(t, finds[0], value1)
386+
AssertEqual(t, finds[1], value2)
387+
}
388+
389+
func TestMergeNestedPreloadWithNestedJoin(t *testing.T) {
390+
users := []User{
391+
{
392+
Name: "TestMergeNestedPreloadWithNestedJoin-1",
393+
Manager: &User{
394+
Name: "Alexis Manager",
395+
Tools: []Tools{
396+
{Name: "Alexis Tool 1"},
397+
{Name: "Alexis Tool 2"},
398+
},
399+
},
400+
},
401+
{
402+
Name: "TestMergeNestedPreloadWithNestedJoin-2",
403+
Manager: &User{
404+
Name: "Jinzhu Manager",
405+
Tools: []Tools{
406+
{Name: "Jinzhu Tool 1"},
407+
{Name: "Jinzhu Tool 2"},
408+
},
409+
},
410+
},
411+
}
412+
413+
DB.Create(&users)
414+
415+
query := make([]string, 0)
416+
sess := DB.Session(&gorm.Session{Logger: Tracer{
417+
Logger: DB.Config.Logger,
418+
Test: func(ctx context.Context, begin time.Time, fc func() (sql string, rowsAffected int64), err error) {
419+
sql, _ := fc()
420+
query = append(query, sql)
421+
},
422+
}})
423+
424+
var result []User
425+
err := sess.
426+
Joins("Manager").
427+
Preload("Manager.Tools").
428+
Where("users.name Like ?", "TestMergeNestedPreloadWithNestedJoin%").
429+
Find(&result).Error
430+
431+
if err != nil {
432+
t.Fatalf("failed to preload and find users: %v", err)
433+
}
434+
435+
AssertEqual(t, result, users)
436+
AssertEqual(t, len(query), 2) // Check preload queries are merged
437+
438+
if !regexp.MustCompile(`SELECT \* FROM .*tools.* WHERE .*IN.*`).MatchString(query[0]) {
439+
t.Fatalf("Expected first query to preload manager tools, got: %s", query[0])
440+
}
375441
}
376442

377443
func TestEmbedPreload(t *testing.T) {

0 commit comments

Comments
 (0)