saving in case of emergency
This commit is contained in:
parent
1650c35416
commit
988cb2bf04
4 changed files with 353 additions and 263 deletions
146
boil/eager_load.go
Normal file
146
boil/eager_load.go
Normal file
|
@ -0,0 +1,146 @@
|
|||
package boil
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"reflect"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/vattle/sqlboiler/strmangle"
|
||||
)
|
||||
|
||||
type loadRelationshipState struct {
|
||||
exec Executor
|
||||
loaded map[string]struct{}
|
||||
toLoad []string
|
||||
}
|
||||
|
||||
func (l loadRelationshipState) hasLoaded(depth int) bool {
|
||||
_, ok := l.loaded[l.buildKey(depth)]
|
||||
return ok
|
||||
}
|
||||
|
||||
func (l loadRelationshipState) setLoaded(depth int) {
|
||||
l.loaded[l.buildKey(depth)] = struct{}{}
|
||||
}
|
||||
|
||||
func (l loadRelationshipState) buildKey(depth int) string {
|
||||
buf := strmangle.GetBuffer()
|
||||
|
||||
for i, piece := range l.toLoad[:depth+1] {
|
||||
if i != 0 {
|
||||
buf.WriteByte('.')
|
||||
}
|
||||
buf.WriteString(piece)
|
||||
}
|
||||
|
||||
str := buf.String()
|
||||
strmangle.PutBuffer(buf)
|
||||
return str
|
||||
}
|
||||
|
||||
// loadRelationships dynamically calls the template generated eager load
|
||||
// functions of the form:
|
||||
//
|
||||
// func (t *TableR) LoadRelationshipName(exec Executor, singular bool, obj interface{})
|
||||
//
|
||||
// The arguments to this function are:
|
||||
// - t is not considered here, and is always passed nil. The function exists on a loaded
|
||||
// struct to avoid a circular dependency with boil, and the receiver is ignored.
|
||||
// - exec is used to perform additional queries that might be required for loading the relationships.
|
||||
// - 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 (s loadRelationshipState) loadRelationships(depth int, obj interface{}, singular bool) error {
|
||||
typ := reflect.TypeOf(obj).Elem()
|
||||
if !singular {
|
||||
typ = typ.Elem().Elem()
|
||||
}
|
||||
|
||||
if !s.hasLoaded(depth) {
|
||||
current := s.toLoad[depth]
|
||||
l, found := typ.FieldByName(loaderStructName)
|
||||
// It's possible a Loaders struct doesn't exist on the struct.
|
||||
if !found {
|
||||
return errors.Errorf("attempted to load %s but no L struct was found", current)
|
||||
}
|
||||
|
||||
// Attempt to find the LoadRelationshipName function
|
||||
loadMethod, found := l.Type.MethodByName(loadMethodPrefix + current)
|
||||
if !found {
|
||||
return errors.Errorf("could not find %s%s method for eager loading", loadMethodPrefix, current)
|
||||
}
|
||||
|
||||
// Hack to allow nil executors
|
||||
execArg := reflect.ValueOf(s.exec)
|
||||
if !execArg.IsValid() {
|
||||
execArg = reflect.ValueOf((*sql.DB)(nil))
|
||||
}
|
||||
|
||||
val := reflect.ValueOf(obj).Elem()
|
||||
if !singular {
|
||||
val = val.Index(0).Elem()
|
||||
}
|
||||
|
||||
methodArgs := []reflect.Value{
|
||||
val.FieldByName(loaderStructName),
|
||||
execArg,
|
||||
reflect.ValueOf(singular),
|
||||
reflect.ValueOf(obj),
|
||||
}
|
||||
resp := loadMethod.Func.Call(methodArgs)
|
||||
if intf := resp[0].Interface(); intf != nil {
|
||||
return errors.Wrapf(intf.(error), "failed to eager load %s", current)
|
||||
}
|
||||
|
||||
s.setLoaded(depth)
|
||||
}
|
||||
|
||||
// Pull one off the queue, continue if there's still some to go
|
||||
depth++
|
||||
if depth >= len(s.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 singular {
|
||||
return s.loadRelationshipsRecurse(depth, singular, loadedObject)
|
||||
}
|
||||
|
||||
// Loop over all eager loaded objects
|
||||
ln := loadedObject.Len()
|
||||
if ln == 0 {
|
||||
return nil
|
||||
}
|
||||
for i := 0; i < ln; i++ {
|
||||
iter := loadedObject.Index(i).Elem()
|
||||
if err := s.loadRelationshipsRecurse(depth, singular, iter); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// 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 (s loadRelationshipState) loadRelationshipsRecurse(depth int, singular bool, obj reflect.Value) error {
|
||||
r := obj.FieldByName(relationshipStructName)
|
||||
if !r.IsValid() || r.IsNil() {
|
||||
return errors.Errorf("could not traverse into loaded %s relationship to load more things", s.toLoad[depth])
|
||||
}
|
||||
newObj := reflect.Indirect(r).FieldByName(s.toLoad[depth])
|
||||
singular = reflect.Indirect(newObj).Kind() == reflect.Struct
|
||||
if !singular {
|
||||
newObj = newObj.Addr()
|
||||
}
|
||||
return s.loadRelationships(depth, newObj.Interface(), singular)
|
||||
}
|
198
boil/eager_load_test.go
Normal file
198
boil/eager_load_test.go
Normal file
|
@ -0,0 +1,198 @@
|
|||
package boil
|
||||
|
||||
import "testing"
|
||||
|
||||
var loadFunctionCalled bool
|
||||
var loadFunctionNestedCalled int
|
||||
|
||||
type testRStruct struct {
|
||||
}
|
||||
type testLStruct struct {
|
||||
}
|
||||
|
||||
type testNestedStruct struct {
|
||||
ID int
|
||||
R *testNestedRStruct
|
||||
L testNestedLStruct
|
||||
}
|
||||
type testNestedRStruct struct {
|
||||
ToEagerLoad *testNestedStruct
|
||||
}
|
||||
type testNestedLStruct struct {
|
||||
}
|
||||
|
||||
type testNestedSlice struct {
|
||||
ID int
|
||||
R *testNestedRSlice
|
||||
L testNestedLSlice
|
||||
}
|
||||
type testNestedRSlice struct {
|
||||
ToEagerLoad []*testNestedSlice
|
||||
}
|
||||
type testNestedLSlice struct {
|
||||
}
|
||||
|
||||
func (testLStruct) LoadTestOne(exec Executor, singular bool, obj interface{}) error {
|
||||
loadFunctionCalled = true
|
||||
return nil
|
||||
}
|
||||
|
||||
func (testNestedLStruct) LoadToEagerLoad(exec Executor, singular bool, obj interface{}) error {
|
||||
switch x := obj.(type) {
|
||||
case *testNestedStruct:
|
||||
x.R = &testNestedRStruct{
|
||||
&testNestedStruct{ID: 4},
|
||||
}
|
||||
case *[]*testNestedStruct:
|
||||
for _, r := range *x {
|
||||
r.R = &testNestedRStruct{
|
||||
&testNestedStruct{ID: 4},
|
||||
}
|
||||
}
|
||||
}
|
||||
loadFunctionNestedCalled++
|
||||
return nil
|
||||
}
|
||||
|
||||
func (testNestedLSlice) LoadToEagerLoad(exec Executor, singular bool, obj interface{}) error {
|
||||
|
||||
switch x := obj.(type) {
|
||||
case *testNestedSlice:
|
||||
x.R = &testNestedRSlice{
|
||||
[]*testNestedSlice{&testNestedSlice{ID: 5}},
|
||||
}
|
||||
case *[]*testNestedSlice:
|
||||
for _, r := range *x {
|
||||
r.R = &testNestedRSlice{
|
||||
[]*testNestedSlice{&testNestedSlice{ID: 5}},
|
||||
}
|
||||
}
|
||||
}
|
||||
loadFunctionNestedCalled++
|
||||
return nil
|
||||
}
|
||||
|
||||
func testFakeState(toLoad ...string) loadRelationshipState {
|
||||
return loadRelationshipState{
|
||||
loaded: map[string]struct{}{},
|
||||
toLoad: toLoad,
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoadRelationshipsSlice(t *testing.T) {
|
||||
// t.Parallel() Function uses globals
|
||||
loadFunctionCalled = false
|
||||
|
||||
testSlice := []*struct {
|
||||
ID int
|
||||
R *testRStruct
|
||||
L testLStruct
|
||||
}{{}}
|
||||
|
||||
if err := testFakeState("TestOne").loadRelationships(0, &testSlice, false); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
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, true); 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, false); 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, false); 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, true); 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, true); 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": struct{}{},
|
||||
"ToEagerLoad.ToEagerLoad": struct{}{},
|
||||
},
|
||||
toLoad: []string{"ToEagerLoad", "ToEagerLoad"},
|
||||
}
|
||||
|
||||
if err := state.loadRelationships(0, &testSingular, true); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if loadFunctionNestedCalled != 0 {
|
||||
t.Error("didn't want this called")
|
||||
}
|
||||
}
|
110
boil/reflect.go
110
boil/reflect.go
|
@ -101,113 +101,19 @@ func (q *Query) Bind(obj interface{}) error {
|
|||
return res
|
||||
}
|
||||
|
||||
state := loadRelationshipState{
|
||||
exec: q.executor,
|
||||
loaded: map[string]struct{}{},
|
||||
}
|
||||
for _, toLoad := range q.load {
|
||||
toLoadFragments := strings.Split(toLoad, ".")
|
||||
if err = loadRelationships(q.executor, toLoadFragments, obj, singular); err != nil {
|
||||
state.toLoad = strings.Split(toLoad, ".")
|
||||
if err = state.loadRelationships(0, obj, singular); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// loadRelationships dynamically calls the template generated eager load
|
||||
// functions of the form:
|
||||
//
|
||||
// func (t *TableR) LoadRelationshipName(exec Executor, singular bool, obj interface{})
|
||||
//
|
||||
// The arguments to this function are:
|
||||
// - t is not considered here, and is always passed nil. The function exists on a loaded
|
||||
// struct to avoid a circular dependency with boil, and the receiver is ignored.
|
||||
// - exec is used to perform additional queries that might be required for loading the relationships.
|
||||
// - 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 loadRelationships(exec Executor, toLoad []string, obj interface{}, singular bool) error {
|
||||
typ := reflect.TypeOf(obj).Elem()
|
||||
if !singular {
|
||||
typ = typ.Elem().Elem()
|
||||
}
|
||||
|
||||
current := toLoad[0]
|
||||
l, found := typ.FieldByName(loaderStructName)
|
||||
// It's possible a Loaders struct doesn't exist on the struct.
|
||||
if !found {
|
||||
return errors.Errorf("attempted to load %s but no L struct was found", current)
|
||||
}
|
||||
|
||||
// Attempt to find the LoadRelationshipName function
|
||||
loadMethod, found := l.Type.MethodByName(loadMethodPrefix + current)
|
||||
if !found {
|
||||
return errors.Errorf("could not find %s%s method for eager loading", loadMethodPrefix, current)
|
||||
}
|
||||
|
||||
// Hack to allow nil executors
|
||||
execArg := reflect.ValueOf(exec)
|
||||
if !execArg.IsValid() {
|
||||
execArg = reflect.ValueOf((*sql.DB)(nil))
|
||||
}
|
||||
|
||||
methodArgs := []reflect.Value{
|
||||
reflect.Indirect(reflect.New(l.Type)),
|
||||
execArg,
|
||||
reflect.ValueOf(singular),
|
||||
reflect.ValueOf(obj),
|
||||
}
|
||||
resp := loadMethod.Func.Call(methodArgs)
|
||||
if resp[0].Interface() != nil {
|
||||
return errors.Wrapf(resp[0].Interface().(error), "failed to eager load %s", current)
|
||||
}
|
||||
|
||||
// Pull one off the queue, continue if there's still some to go
|
||||
toLoad = toLoad[1:]
|
||||
if len(toLoad) == 0 {
|
||||
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 singular {
|
||||
return loadRelationshipsRecurse(exec, current, toLoad, singular, loadedObject)
|
||||
}
|
||||
|
||||
// Loop over all eager loaded objects
|
||||
ln := loadedObject.Len()
|
||||
if ln == 0 {
|
||||
return nil
|
||||
}
|
||||
for i := 0; i < ln; i++ {
|
||||
iter := loadedObject.Index(i).Elem()
|
||||
if err := loadRelationshipsRecurse(exec, current, toLoad, singular, iter); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// 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 loadRelationshipsRecurse(exec Executor, current string, toLoad []string, singular bool, obj reflect.Value) error {
|
||||
r := obj.FieldByName(relationshipStructName)
|
||||
if !r.IsValid() || r.IsNil() {
|
||||
return errors.Errorf("could not traverse into loaded %s relationship to load more things", current)
|
||||
}
|
||||
newObj := reflect.Indirect(r).FieldByName(current)
|
||||
singular = reflect.Indirect(newObj).Kind() == reflect.Struct
|
||||
if !singular {
|
||||
newObj = newObj.Addr()
|
||||
}
|
||||
return loadRelationships(exec, toLoad, newObj.Interface(), singular)
|
||||
}
|
||||
|
||||
// bindChecks resolves information about the bind target, and errors if it's not an object
|
||||
// we can bind to.
|
||||
func bindChecks(obj interface{}) (structType reflect.Type, sliceType reflect.Type, singular bool, err error) {
|
||||
|
@ -305,6 +211,8 @@ func bind(rows *sql.Rows, obj interface{}, structType, sliceType reflect.Type, s
|
|||
return nil
|
||||
}
|
||||
|
||||
// bindMapping creates a mapping that helps look up the pointer for the
|
||||
// column given.
|
||||
func bindMapping(typ reflect.Type, cols []string) ([]uint64, error) {
|
||||
ptrs := make([]uint64, len(cols))
|
||||
mapping := makeStructMapping(typ)
|
||||
|
@ -333,7 +241,7 @@ ColLoop:
|
|||
}
|
||||
|
||||
// ptrsFromMapping expects to be passed an addressable struct that it's looking
|
||||
// for things on
|
||||
// for things on.
|
||||
func ptrsFromMapping(val reflect.Value, mapping []uint64) []interface{} {
|
||||
ptrs := make([]interface{}, len(mapping))
|
||||
for i, m := range mapping {
|
||||
|
|
|
@ -267,168 +267,6 @@ func TestBindSingular(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
var loadFunctionCalled bool
|
||||
var loadFunctionNestedCalled int
|
||||
|
||||
type testRStruct struct {
|
||||
}
|
||||
type testLStruct struct {
|
||||
}
|
||||
|
||||
type testNestedStruct struct {
|
||||
ID int
|
||||
R *testNestedRStruct
|
||||
L testNestedLStruct
|
||||
}
|
||||
type testNestedRStruct struct {
|
||||
ToEagerLoad *testNestedStruct
|
||||
}
|
||||
type testNestedLStruct struct {
|
||||
}
|
||||
|
||||
type testNestedSlice struct {
|
||||
ID int
|
||||
R *testNestedRSlice
|
||||
L testNestedLSlice
|
||||
}
|
||||
type testNestedRSlice struct {
|
||||
ToEagerLoad []*testNestedSlice
|
||||
}
|
||||
type testNestedLSlice struct {
|
||||
}
|
||||
|
||||
func (testLStruct) LoadTestOne(exec Executor, singular bool, obj interface{}) error {
|
||||
loadFunctionCalled = true
|
||||
return nil
|
||||
}
|
||||
|
||||
func (testNestedLStruct) LoadToEagerLoad(exec Executor, singular bool, obj interface{}) error {
|
||||
switch x := obj.(type) {
|
||||
case *testNestedStruct:
|
||||
x.R = &testNestedRStruct{
|
||||
&testNestedStruct{ID: 4},
|
||||
}
|
||||
case *[]*testNestedStruct:
|
||||
for _, r := range *x {
|
||||
r.R = &testNestedRStruct{
|
||||
&testNestedStruct{ID: 4},
|
||||
}
|
||||
}
|
||||
}
|
||||
loadFunctionNestedCalled++
|
||||
return nil
|
||||
}
|
||||
|
||||
func (testNestedLSlice) LoadToEagerLoad(exec Executor, singular bool, obj interface{}) error {
|
||||
|
||||
switch x := obj.(type) {
|
||||
case *testNestedSlice:
|
||||
x.R = &testNestedRSlice{
|
||||
[]*testNestedSlice{&testNestedSlice{ID: 5}},
|
||||
}
|
||||
case *[]*testNestedSlice:
|
||||
for _, r := range *x {
|
||||
r.R = &testNestedRSlice{
|
||||
[]*testNestedSlice{&testNestedSlice{ID: 5}},
|
||||
}
|
||||
}
|
||||
}
|
||||
loadFunctionNestedCalled++
|
||||
return nil
|
||||
}
|
||||
|
||||
func TestLoadRelationshipsSlice(t *testing.T) {
|
||||
// t.Parallel() Function uses globals
|
||||
loadFunctionCalled = false
|
||||
|
||||
testSlice := []*struct {
|
||||
ID int
|
||||
R *testRStruct
|
||||
L testLStruct
|
||||
}{}
|
||||
|
||||
if err := loadRelationships(nil, []string{"TestOne"}, &testSlice, false); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
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 := loadRelationships(nil, []string{"TestOne"}, &testSingular, true); 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 := loadRelationships(nil, []string{"ToEagerLoad", "ToEagerLoad", "ToEagerLoad"}, &testSlice, false); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if loadFunctionNestedCalled != 3 {
|
||||
t.Error("Load function was called:", loadFunctionNestedCalled, "times")
|
||||
}
|
||||
|
||||
testSliceSlice := []*testNestedSlice{
|
||||
{
|
||||
ID: 2,
|
||||
},
|
||||
}
|
||||
loadFunctionNestedCalled = 0
|
||||
if err := loadRelationships(nil, []string{"ToEagerLoad", "ToEagerLoad", "ToEagerLoad"}, &testSliceSlice, false); 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 := loadRelationships(nil, []string{"ToEagerLoad", "ToEagerLoad", "ToEagerLoad"}, &testSingular, true); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if loadFunctionNestedCalled != 3 {
|
||||
t.Error("Load function was called:", loadFunctionNestedCalled, "times")
|
||||
}
|
||||
|
||||
testSingularSlice := testNestedSlice{
|
||||
ID: 3,
|
||||
}
|
||||
loadFunctionNestedCalled = 0
|
||||
if err := loadRelationships(nil, []string{"ToEagerLoad", "ToEagerLoad", "ToEagerLoad"}, &testSingularSlice, true); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if loadFunctionNestedCalled != 3 {
|
||||
t.Error("Load function was called:", loadFunctionNestedCalled, "times")
|
||||
}
|
||||
}
|
||||
|
||||
func TestBind_InnerJoin(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue