Work in progress

- The eager loading is behaving correctly now
This commit is contained in:
Aaron L 2016-09-28 19:57:38 -07:00
parent b0182bead7
commit e625589ed1
3 changed files with 215 additions and 198 deletions

View file

@ -2,7 +2,9 @@ package queries
import (
"database/sql"
"fmt"
"reflect"
"strings"
"github.com/pkg/errors"
"github.com/vattle/sqlboiler/boil"
@ -39,6 +41,25 @@ func (l loadRelationshipState) buildKey(depth int) string {
return str
}
// eagerLoad loads all of the model's relationships
//
// toLoad should look like:
// []string{"Relationship", "Relationship.NestedRelationship"} ... etc
func eagerLoad(exec boil.Executor, toLoad []string, obj interface{}, bkind bindKind) error {
state := loadRelationshipState{
exec: exec,
loaded: map[string]struct{}{},
}
for _, toLoad := range toLoad {
state.toLoad = strings.Split(toLoad, ".")
if err := state.loadRelationships(0, obj, bkind); err != nil {
return err
}
}
return nil
}
// loadRelationships dynamically calls the template generated eager load
// functions of the form:
//
@ -51,15 +72,22 @@ func (l loadRelationshipState) buildKey(depth int) string {
// - singular is passed in to identify whether or not this was a single object
// or a slice that must be loaded into.
// - obj is the object or slice of objects, always of the type *obj or *[]*obj as per bind.
//
// It takes list of nested relationships to load.
func (l loadRelationshipState) loadRelationships(depth int, obj interface{}, bkind bindKind) error {
typ := reflect.TypeOf(obj).Elem()
if bkind == kindPtrSliceStruct {
typ = typ.Elem().Elem()
}
loadingFrom := reflect.ValueOf(obj)
if loadingFrom.IsNil() {
return nil
}
loadingFrom = reflect.Indirect(loadingFrom)
fmt.Println("load rels", typ.String(), l.toLoad[depth:])
if !l.hasLoaded(depth) {
fmt.Println("!loaded", l.toLoad[depth])
current := l.toLoad[depth]
ln, found := typ.FieldByName(loaderStructName)
// It's possible a Loaders struct doesn't exist on the struct.
@ -79,9 +107,10 @@ func (l loadRelationshipState) loadRelationships(depth int, obj interface{}, bki
execArg = reflect.ValueOf((*sql.DB)(nil))
}
val := reflect.ValueOf(obj).Elem()
// Get a loader instance from anything we have, *struct, or *[]*struct
val := reflect.Indirect(loadingFrom)
if bkind == kindPtrSliceStruct {
val = val.Index(0).Elem()
val = reflect.Indirect(val.Index(0))
}
methodArgs := []reflect.Value{
@ -96,33 +125,27 @@ func (l loadRelationshipState) loadRelationships(depth int, obj interface{}, bki
}
l.setLoaded(depth)
} else {
fmt.Println("!loading", l.toLoad[depth])
}
// Pull one off the queue, continue if there's still some to go
depth++
if depth >= len(l.toLoad) {
// Check if we can stop
if depth+1 >= len(l.toLoad) {
return nil
}
loadedObject := reflect.ValueOf(obj)
// If we eagerly loaded nothing
if loadedObject.IsNil() {
return nil
}
loadedObject = reflect.Indirect(loadedObject)
// If it's singular we can just immediately call without looping
if bkind == kindStruct {
return l.loadRelationshipsRecurse(depth, loadedObject)
return l.loadRelationshipsRecurse(depth, loadingFrom)
}
// Loop over all eager loaded objects
ln := loadedObject.Len()
ln := loadingFrom.Len()
if ln == 0 {
return nil
}
for i := 0; i < ln; i++ {
iter := loadedObject.Index(i).Elem()
iter := reflect.Indirect(loadingFrom.Index(i))
if err := l.loadRelationshipsRecurse(depth, iter); err != nil {
return err
}
@ -134,15 +157,22 @@ func (l loadRelationshipState) loadRelationships(depth int, obj interface{}, bki
// loadRelationshipsRecurse is a helper function for taking a reflect.Value and
// Basically calls loadRelationships with: obj.R.EagerLoadedObj, and whether it's a string or slice
func (l loadRelationshipState) loadRelationshipsRecurse(depth int, obj reflect.Value) error {
r := obj.FieldByName(relationshipStructName)
if !r.IsValid() || r.IsNil() {
// Get relationship struct, and if it's good to go, grab the value we just loaded.
relationshipStruct := obj.FieldByName(relationshipStructName)
if !relationshipStruct.IsValid() || relationshipStruct.IsNil() {
return errors.Errorf("could not traverse into loaded %s relationship to load more things", l.toLoad[depth])
}
newObj := reflect.Indirect(r).FieldByName(l.toLoad[depth])
loadedObject := reflect.Indirect(relationshipStruct).FieldByName(l.toLoad[depth])
fmt.Println("loadRecurse", l.toLoad[depth])
// Pop one off the queue
depth++
bkind := kindStruct
if reflect.Indirect(newObj).Kind() != reflect.Struct {
if reflect.Indirect(loadedObject).Kind() != reflect.Struct {
bkind = kindPtrSliceStruct
newObj = newObj.Addr()
loadedObject = loadedObject.Addr()
}
return l.loadRelationships(depth, newObj.Interface(), bkind)
return l.loadRelationships(depth, loadedObject.Interface(), bkind)
}

View file

@ -1,202 +1,199 @@
package queries
import (
"fmt"
"testing"
"github.com/vattle/sqlboiler/boil"
)
var loadFunctionCalled bool
var loadFunctionNestedCalled int
type testRStruct struct {
}
type testLStruct struct {
var testEagerCounters struct {
ChildOne int
ChildMany int
NestedOne int
NestedMany int
}
type testNestedStruct struct {
type testEager struct {
ID int
R *testNestedRStruct
L testNestedLStruct
}
type testNestedRStruct struct {
ToEagerLoad *testNestedStruct
}
type testNestedLStruct struct {
R *testEagerR
L testEagerL
}
type testNestedSlice struct {
type testEagerR struct {
ChildOne *testEagerChild
ChildMany []*testEagerChild
}
type testEagerL struct {
}
type testEagerChild struct {
ID int
R *testNestedRSlice
L testNestedLSlice
R *testEagerChildR
L testEagerChildL
}
type testNestedRSlice struct {
ToEagerLoad []*testNestedSlice
type testEagerChildR struct {
NestedOne *testEagerNested
NestedMany []*testEagerNested
}
type testNestedLSlice struct {
type testEagerChildL struct {
}
func (testLStruct) LoadTestOne(exec boil.Executor, singular bool, obj interface{}) error {
loadFunctionCalled = true
type testEagerNested struct {
ID int
R *testEagerNestedR
L testEagerNestedL
}
type testEagerNestedR struct {
}
type testEagerNestedL struct {
}
func (testEagerL) LoadChildOne(_ boil.Executor, singular bool, obj interface{}) error {
var toSetOn []*testEager
if singular {
toSetOn = []*testEager{obj.(*testEager)}
} else {
toSetOn = *obj.(*[]*testEager)
}
for _, o := range toSetOn {
if o.R == nil {
o.R = &testEagerR{}
}
o.R.ChildOne = &testEagerChild{ID: 1}
}
testEagerCounters.ChildOne++
fmt.Println("l! ChildOne")
return nil
}
func (testNestedLStruct) LoadToEagerLoad(exec boil.Executor, singular bool, obj interface{}) error {
switch x := obj.(type) {
case *testNestedStruct:
x.R = &testNestedRStruct{
&testNestedStruct{ID: 4},
func (testEagerL) LoadChildMany(_ boil.Executor, singular bool, obj interface{}) error {
var toSetOn []*testEager
if singular {
toSetOn = []*testEager{obj.(*testEager)}
} else {
toSetOn = *obj.(*[]*testEager)
}
for _, o := range toSetOn {
if o.R == nil {
o.R = &testEagerR{}
}
case *[]*testNestedStruct:
for _, r := range *x {
r.R = &testNestedRStruct{
&testNestedStruct{ID: 4},
}
o.R.ChildMany = []*testEagerChild{
&testEagerChild{ID: 2},
&testEagerChild{ID: 3},
}
}
loadFunctionNestedCalled++
testEagerCounters.ChildMany++
fmt.Println("l! ChildMany")
return nil
}
func (testNestedLSlice) LoadToEagerLoad(exec boil.Executor, singular bool, obj interface{}) error {
switch x := obj.(type) {
case *testNestedSlice:
x.R = &testNestedRSlice{
[]*testNestedSlice{{ID: 5}},
}
case *[]*testNestedSlice:
for _, r := range *x {
r.R = &testNestedRSlice{
[]*testNestedSlice{{ID: 5}},
}
}
func (testEagerChildL) LoadNestedOne(_ boil.Executor, singular bool, obj interface{}) error {
var toSetOn []*testEagerChild
if singular {
toSetOn = []*testEagerChild{obj.(*testEagerChild)}
} else {
toSetOn = *obj.(*[]*testEagerChild)
}
loadFunctionNestedCalled++
for _, o := range toSetOn {
if o.R == nil {
o.R = &testEagerChildR{}
}
o.R.NestedOne = &testEagerNested{ID: 6}
}
testEagerCounters.NestedOne++
fmt.Println("l! NestedOne")
return nil
}
func testFakeState(toLoad ...string) loadRelationshipState {
return loadRelationshipState{
loaded: map[string]struct{}{},
toLoad: toLoad,
func (testEagerChildL) LoadNestedMany(_ boil.Executor, singular bool, obj interface{}) error {
var toSetOn []*testEagerChild
if singular {
toSetOn = []*testEagerChild{obj.(*testEagerChild)}
} else {
toSetOn = *obj.(*[]*testEagerChild)
}
for _, o := range toSetOn {
if o.R == nil {
o.R = &testEagerChildR{}
}
o.R.NestedMany = []*testEagerNested{
&testEagerNested{ID: 6},
&testEagerNested{ID: 7},
}
}
testEagerCounters.NestedMany++
fmt.Println("l! NestedMany")
return nil
}
func TestEagerLoadFromOne(t *testing.T) {
testEagerCounters.ChildOne = 0
testEagerCounters.ChildMany = 0
testEagerCounters.NestedOne = 0
testEagerCounters.NestedMany = 0
obj := &testEager{}
toLoad := []string{"ChildOne", "ChildMany.NestedMany", "ChildMany.NestedOne"}
err := eagerLoad(nil, toLoad, obj, kindStruct)
if err != nil {
t.Fatal(err)
}
if testEagerCounters.ChildMany != 1 {
t.Error(testEagerCounters.ChildMany)
}
if testEagerCounters.ChildOne != 1 {
t.Error(testEagerCounters.ChildOne)
}
if testEagerCounters.NestedMany != 1 {
t.Error(testEagerCounters.NestedMany)
}
if testEagerCounters.NestedOne != 1 {
t.Error(testEagerCounters.NestedOne)
}
}
func TestLoadRelationshipsSlice(t *testing.T) {
// t.Parallel() Function uses globals
loadFunctionCalled = false
func TestEagerLoadFromMany(t *testing.T) {
testEagerCounters.ChildOne = 0
testEagerCounters.ChildMany = 0
testEagerCounters.NestedOne = 0
testEagerCounters.NestedMany = 0
testSlice := []*struct {
ID int
R *testRStruct
L testLStruct
}{{}}
if err := testFakeState("TestOne").loadRelationships(0, &testSlice, kindPtrSliceStruct); err != nil {
t.Error(err)
slice := []*testEager{
{ID: -1},
{ID: -2},
}
if !loadFunctionCalled {
t.Errorf("Load function was not called for testSlice")
}
}
func TestLoadRelationshipsSingular(t *testing.T) {
// t.Parallel() Function uses globals
loadFunctionCalled = false
testSingular := struct {
ID int
R *testRStruct
L testLStruct
}{}
if err := testFakeState("TestOne").loadRelationships(0, &testSingular, kindStruct); err != nil {
t.Error(err)
}
if !loadFunctionCalled {
t.Errorf("Load function was not called for singular")
}
}
func TestLoadRelationshipsSliceNested(t *testing.T) {
// t.Parallel() Function uses globals
testSlice := []*testNestedStruct{
{
ID: 2,
},
}
loadFunctionNestedCalled = 0
if err := testFakeState("ToEagerLoad", "ToEagerLoad", "ToEagerLoad").loadRelationships(0, &testSlice, kindPtrSliceStruct); err != nil {
t.Error(err)
}
if loadFunctionNestedCalled != 3 {
t.Error("Load function was called:", loadFunctionNestedCalled, "times")
}
testSliceSlice := []*testNestedSlice{
{
ID: 2,
},
}
loadFunctionNestedCalled = 0
if err := testFakeState("ToEagerLoad", "ToEagerLoad", "ToEagerLoad").loadRelationships(0, &testSliceSlice, kindPtrSliceStruct); err != nil {
t.Error(err)
}
if loadFunctionNestedCalled != 3 {
t.Error("Load function was called:", loadFunctionNestedCalled, "times")
}
}
func TestLoadRelationshipsSingularNested(t *testing.T) {
// t.Parallel() Function uses globals
testSingular := testNestedStruct{
ID: 3,
}
loadFunctionNestedCalled = 0
if err := testFakeState("ToEagerLoad", "ToEagerLoad", "ToEagerLoad").loadRelationships(0, &testSingular, kindStruct); err != nil {
t.Error(err)
}
if loadFunctionNestedCalled != 3 {
t.Error("Load function was called:", loadFunctionNestedCalled, "times")
}
testSingularSlice := testNestedSlice{
ID: 3,
}
loadFunctionNestedCalled = 0
if err := testFakeState("ToEagerLoad", "ToEagerLoad", "ToEagerLoad").loadRelationships(0, &testSingularSlice, kindStruct); err != nil {
t.Error(err)
}
if loadFunctionNestedCalled != 3 {
t.Error("Load function was called:", loadFunctionNestedCalled, "times")
}
}
func TestLoadRelationshipsNoReload(t *testing.T) {
// t.Parallel() Function uses globals
testSingular := testNestedStruct{
ID: 3,
R: &testNestedRStruct{
&testNestedStruct{},
},
}
loadFunctionNestedCalled = 0
state := loadRelationshipState{
loaded: map[string]struct{}{
"ToEagerLoad": {},
"ToEagerLoad.ToEagerLoad": {},
},
toLoad: []string{"ToEagerLoad", "ToEagerLoad"},
}
if err := state.loadRelationships(0, &testSingular, kindStruct); err != nil {
t.Error(err)
}
if loadFunctionNestedCalled != 0 {
t.Error("didn't want this called")
toLoad := []string{"ChildOne", "ChildMany.NestedMany", "ChildMany.NestedOne"}
err := eagerLoad(nil, toLoad, &slice, kindPtrSliceStruct)
if err != nil {
t.Fatal(err)
}
if testEagerCounters.ChildMany != 1 {
t.Error(testEagerCounters.ChildMany)
}
if testEagerCounters.ChildOne != 1 {
t.Error(testEagerCounters.ChildOne)
}
if testEagerCounters.NestedMany != 1 {
t.Error(testEagerCounters.NestedMany)
}
if testEagerCounters.NestedOne != 1 {
t.Error(testEagerCounters.NestedOne)
}
}

View file

@ -110,20 +110,10 @@ func (q *Query) Bind(obj interface{}) error {
return res
}
if len(q.load) == 0 {
return nil
if len(q.load) != 0 {
return eagerLoad(q.executor, q.load, obj, bkind)
}
state := loadRelationshipState{
exec: q.executor,
loaded: map[string]struct{}{},
}
for _, toLoad := range q.load {
state.toLoad = strings.Split(toLoad, ".")
if err = state.loadRelationships(0, obj, bkind); err != nil {
return err
}
}
return nil
}