Nearly finished relationship bind helper
* If only reflection would be nice
This commit is contained in:
parent
22cf8091a9
commit
8806e76d9f
5 changed files with 143 additions and 12 deletions
|
@ -19,17 +19,14 @@ func SQL(sql string, args ...interface{}) QueryMod {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Limit the number of returned rows
|
// Load allows you to specify foreign key relationships to eager load
|
||||||
func Limit(limit int) QueryMod {
|
// 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) {
|
return func(q *boil.Query) {
|
||||||
boil.SetLimit(q, limit)
|
boil.SetLoad(q, relationships...)
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Offset into the results
|
|
||||||
func Offset(offset int) QueryMod {
|
|
||||||
return func(q *boil.Query) {
|
|
||||||
boil.SetOffset(q, offset)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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
|
// Count turns the query into a counting calculation
|
||||||
func Count() QueryMod {
|
func Count() QueryMod {
|
||||||
return func(q *boil.Query) {
|
return func(q *boil.Query) {
|
||||||
|
|
|
@ -20,6 +20,7 @@ const (
|
||||||
type Query struct {
|
type Query struct {
|
||||||
executor Executor
|
executor Executor
|
||||||
plainSQL plainSQL
|
plainSQL plainSQL
|
||||||
|
load []string
|
||||||
delete bool
|
delete bool
|
||||||
update map[string]interface{}
|
update map[string]interface{}
|
||||||
selectCols []string
|
selectCols []string
|
||||||
|
@ -101,6 +102,11 @@ func SetSQL(q *Query, sql string, args ...interface{}) {
|
||||||
q.plainSQL = plainSQL{sql: sql, args: args}
|
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.
|
// SetCount on the query.
|
||||||
func SetCount(q *Query) {
|
func SetCount(q *Query) {
|
||||||
q.modFunction = "COUNT"
|
q.modFunction = "COUNT"
|
||||||
|
|
|
@ -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) {
|
func TestWhere(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
|
|
|
@ -6,6 +6,7 @@ import (
|
||||||
"reflect"
|
"reflect"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/davecgh/go-spew/spew"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"github.com/vattle/sqlboiler/strmangle"
|
"github.com/vattle/sqlboiler/strmangle"
|
||||||
)
|
)
|
||||||
|
@ -29,7 +30,8 @@ func (q *Query) BindP(obj interface{}) {
|
||||||
// Bind rules:
|
// Bind rules:
|
||||||
// - Struct tags control bind, in the form of: `boil:"name,bind"`
|
// - 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
|
// - 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
|
// - If the ",bind" option is specified on an struct field, it will be recursed
|
||||||
// into to look for fields for binding.
|
// into to look for fields for binding.
|
||||||
// - If the name of the struct tag is "-", this field will not be bound to
|
// - 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()
|
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
|
// bindChecks resolves information about the bind target, and errors if it's not an object
|
||||||
|
|
|
@ -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) {
|
func TestBind_InnerJoin(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue