Nearly finished relationship bind helper

* If only reflection would be nice
This commit is contained in:
Patrick O'brien 2016-08-18 03:56:00 +10:00
parent 22cf8091a9
commit 8806e76d9f
5 changed files with 143 additions and 12 deletions

View file

@ -19,17 +19,14 @@ func SQL(sql string, args ...interface{}) QueryMod {
}
}
// Limit the number of returned rows
func Limit(limit int) QueryMod {
// Load allows you to specify foreign key relationships to eager load
// for your query. Passed in relationships need to be in the format
// MyThing or MyThings.
// Relationship name plurality is important, if your relationship is
// singular, you need to specify the singular form and vice versa.
func Load(relationships ...string) QueryMod {
return func(q *boil.Query) {
boil.SetLimit(q, limit)
}
}
// Offset into the results
func Offset(offset int) QueryMod {
return func(q *boil.Query) {
boil.SetOffset(q, offset)
boil.SetLoad(q, relationships...)
}
}
@ -99,6 +96,20 @@ func From(from string) QueryMod {
}
}
// Limit the number of returned rows
func Limit(limit int) QueryMod {
return func(q *boil.Query) {
boil.SetLimit(q, limit)
}
}
// Offset into the results
func Offset(offset int) QueryMod {
return func(q *boil.Query) {
boil.SetOffset(q, offset)
}
}
// Count turns the query into a counting calculation
func Count() QueryMod {
return func(q *boil.Query) {

View file

@ -20,6 +20,7 @@ const (
type Query struct {
executor Executor
plainSQL plainSQL
load []string
delete bool
update map[string]interface{}
selectCols []string
@ -101,6 +102,11 @@ func SetSQL(q *Query, sql string, args ...interface{}) {
q.plainSQL = plainSQL{sql: sql, args: args}
}
// SetLoad on the query.
func SetLoad(q *Query, relationships ...string) {
q.load = append([]string(nil), relationships...)
}
// SetCount on the query.
func SetCount(q *Query) {
q.modFunction = "COUNT"

View file

@ -45,6 +45,21 @@ func TestSetSQL(t *testing.T) {
}
}
func TestSetLoad(t *testing.T) {
t.Parallel()
q := &Query{}
SetLoad(q, "one", "two")
if len(q.load) != 2 {
t.Errorf("Expected len 2, got %d", len(q.load))
}
if q.load[0] != "one" || q.load[1] != "two" {
t.Errorf("Was not expected string, got %s", q.load)
}
}
func TestWhere(t *testing.T) {
t.Parallel()

View file

@ -6,6 +6,7 @@ import (
"reflect"
"strings"
"github.com/davecgh/go-spew/spew"
"github.com/pkg/errors"
"github.com/vattle/sqlboiler/strmangle"
)
@ -29,7 +30,8 @@ func (q *Query) BindP(obj interface{}) {
// Bind rules:
// - Struct tags control bind, in the form of: `boil:"name,bind"`
// - If the "name" part of the struct tag is specified, that will be used
// for binding instead of the snake_cased field name.
// - f the "name" part of the tag is specified, it is used for binding
// (the columns returned are title cased and matched).
// - If the ",bind" option is specified on an struct field, it will be recursed
// into to look for fields for binding.
// - If the name of the struct tag is "-", this field will not be bound to
@ -75,7 +77,55 @@ func (q *Query) Bind(obj interface{}) error {
}
defer rows.Close()
return bind(rows, obj, structType, sliceType, singular)
if res := bind(rows, obj, structType, sliceType, singular); res != nil {
return res
}
if len(q.load) == 0 {
return nil
}
return q.loadRelationships(obj, singular)
}
// loadRelationships calls the template generated eager load functions
// (LoadTableName()) using reflection, to eager load the relationships
// into the users Relationships struct attached to their object.
func (q *Query) loadRelationships(obj interface{}, singular bool) error {
typ := reflect.TypeOf(obj).Elem().Elem()
if !singular {
typ = typ.Elem()
}
rel, found := typ.FieldByName("Relationships")
// If the users object has no Relationships struct, it must be
// a custom object and we should not attempt to load any relationships.
if !found {
return nil
}
for _, relationship := range q.load {
// Attempt to find the LoadRelationshipName function
loadMethod, found := rel.Type.MethodByName("Load" + relationship)
if !found {
return errors.Errorf("could not find Load%s method for eager loading", relationship)
}
spew.Dump(reflect.New(rel.Type).Interface().(**testRelationshipsStruct))
spew.Dump(reflect.Indirect(reflect.New(rel.Type)).Interface().(*testRelationshipsStruct))
methodArgs := []reflect.Value{
reflect.New(rel.Type),
reflect.ValueOf(q.executor),
reflect.ValueOf(singular),
reflect.ValueOf(obj),
}
resp := loadMethod.Func.Call(methodArgs)
if resp[0].Interface() != nil {
return resp[0].Interface().(error)
}
}
return nil
}
// bindChecks resolves information about the bind target, and errors if it's not an object

View file

@ -103,6 +103,55 @@ func TestBindSingular(t *testing.T) {
}
}
var loadFunctionCalled bool
type testRelationshipsStruct struct{}
func (r *testRelationshipsStruct) LoadTestOne(exec Executor, singular bool, obj interface{}) error {
loadFunctionCalled = true
return nil
}
func TestLoadRelationships(t *testing.T) {
t.Parallel()
testSingular := []*struct {
ID int
Relationships *testRelationshipsStruct
}{}
testSlice := []*struct {
ID int
Relationships *testRelationshipsStruct
}{}
exec, _, err := sqlmock.New()
if err != nil {
t.Error(err)
}
q := Query{
load: []string{"TestOne"},
executor: exec,
}
if err := q.loadRelationships(testSlice, true); err != nil {
t.Error(err)
}
if loadFunctionCalled == false {
t.Errorf("Load function was not called for testSlice")
}
if err := q.loadRelationships(testSingular, false); err != nil {
t.Error(err)
}
if loadFunctionCalled == false {
t.Errorf("Load function was not called for testSlice")
}
}
func TestBind_InnerJoin(t *testing.T) {
t.Parallel()