saving in case of emergency

This commit is contained in:
Aaron L 2016-09-02 23:53:37 -07:00
parent 1650c35416
commit 988cb2bf04
4 changed files with 353 additions and 263 deletions

146
boil/eager_load.go Normal file
View 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
View 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")
}
}

View file

@ -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 {

View file

@ -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()