Merge branch 'eager'
This commit is contained in:
commit
9c2a4d7852
14 changed files with 503 additions and 38 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)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -126,6 +123,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
|
||||||
|
@ -113,6 +114,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 TestAppendWhere(t *testing.T) {
|
func TestAppendWhere(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
|
|
136
boil/reflect.go
136
boil/reflect.go
|
@ -16,7 +16,7 @@ var (
|
||||||
|
|
||||||
// BindP executes the query and inserts the
|
// BindP executes the query and inserts the
|
||||||
// result into the passed in object pointer.
|
// result into the passed in object pointer.
|
||||||
// It panics on error.
|
// It panics on error. See boil.Bind() documentation.
|
||||||
func (q *Query) BindP(obj interface{}) {
|
func (q *Query) BindP(obj interface{}) {
|
||||||
if err := q.Bind(obj); err != nil {
|
if err := q.Bind(obj); err != nil {
|
||||||
panic(WrapErr(err))
|
panic(WrapErr(err))
|
||||||
|
@ -29,7 +29,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
|
||||||
|
@ -49,14 +50,105 @@ func (q *Query) BindP(obj interface{}) {
|
||||||
// }
|
// }
|
||||||
//
|
//
|
||||||
// models.Users(qm.InnerJoin("users as friend on users.friend_id = friend.id")).Bind(&joinStruct)
|
// models.Users(qm.InnerJoin("users as friend on users.friend_id = friend.id")).Bind(&joinStruct)
|
||||||
|
//
|
||||||
|
// For custom objects that want to use eager loading, please see the
|
||||||
|
// loadRelationships function.
|
||||||
|
func Bind(rows *sql.Rows, obj interface{}) error {
|
||||||
|
structType, sliceType, singular, err := bindChecks(obj)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return bind(rows, obj, structType, sliceType, singular)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bind executes the query and inserts the
|
||||||
|
// result into the passed in object pointer
|
||||||
|
//
|
||||||
|
// See documentation for boil.Bind()
|
||||||
func (q *Query) Bind(obj interface{}) error {
|
func (q *Query) Bind(obj interface{}) error {
|
||||||
|
structType, sliceType, singular, err := bindChecks(obj)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
rows, err := ExecQueryAll(q)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err, "bind failed to execute query")
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
|
||||||
|
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 dynamically calls the template generated eager load
|
||||||
|
// functions of the form:
|
||||||
|
//
|
||||||
|
// func (t *TableRelationships) 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 relationships
|
||||||
|
// 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.
|
||||||
|
func (q *Query) loadRelationships(obj interface{}, singular bool) error {
|
||||||
|
typ := reflect.TypeOf(obj).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 errors.New("load query mod was used but bound struct contained no relationship field")
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
execArg := reflect.ValueOf(q.executor)
|
||||||
|
if !execArg.IsValid() {
|
||||||
|
execArg = reflect.ValueOf((*sql.DB)(nil))
|
||||||
|
}
|
||||||
|
|
||||||
|
methodArgs := []reflect.Value{
|
||||||
|
reflect.Indirect(reflect.New(rel.Type)),
|
||||||
|
execArg,
|
||||||
|
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
|
||||||
|
// we can bind to.
|
||||||
|
func bindChecks(obj interface{}) (structType reflect.Type, sliceType reflect.Type, singular bool, err error) {
|
||||||
typ := reflect.TypeOf(obj)
|
typ := reflect.TypeOf(obj)
|
||||||
kind := typ.Kind()
|
kind := typ.Kind()
|
||||||
|
|
||||||
var structType reflect.Type
|
|
||||||
var sliceType reflect.Type
|
|
||||||
var singular bool
|
|
||||||
|
|
||||||
for i := 0; i < len(bindAccepts); i++ {
|
for i := 0; i < len(bindAccepts); i++ {
|
||||||
exp := bindAccepts[i]
|
exp := bindAccepts[i]
|
||||||
|
|
||||||
|
@ -72,7 +164,7 @@ func (q *Query) Bind(obj interface{}) error {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
return errors.Errorf("obj type should be *[]*Type or *Type but was %q", reflect.TypeOf(obj).String())
|
return nil, nil, false, errors.Errorf("obj type should be *[]*Type or *Type but was %q", reflect.TypeOf(obj).String())
|
||||||
}
|
}
|
||||||
|
|
||||||
switch kind {
|
switch kind {
|
||||||
|
@ -83,16 +175,10 @@ func (q *Query) Bind(obj interface{}) error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return bind(q, obj, structType, sliceType, singular)
|
return structType, sliceType, singular, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func bind(q *Query, obj interface{}, structType, sliceType reflect.Type, singular bool) error {
|
func bind(rows *sql.Rows, obj interface{}, structType, sliceType reflect.Type, singular bool) error {
|
||||||
rows, err := ExecQueryAll(q)
|
|
||||||
if err != nil {
|
|
||||||
return errors.Wrap(err, "bind failed to execute query")
|
|
||||||
}
|
|
||||||
defer rows.Close()
|
|
||||||
|
|
||||||
cols, err := rows.Columns()
|
cols, err := rows.Columns()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.Wrap(err, "bind failed to get column names")
|
return errors.Wrap(err, "bind failed to get column names")
|
||||||
|
@ -228,7 +314,7 @@ func GetStructValues(obj interface{}, columns ...string) []interface{} {
|
||||||
for i, c := range columns {
|
for i, c := range columns {
|
||||||
field := val.FieldByName(strmangle.TitleCase(c))
|
field := val.FieldByName(strmangle.TitleCase(c))
|
||||||
if !field.IsValid() {
|
if !field.IsValid() {
|
||||||
panic(fmt.Sprintf("Unable to find field with name: %s\n%#v", strmangle.TitleCase(c), obj))
|
panic(fmt.Sprintf("unable to find field with name: %s\n%#v", strmangle.TitleCase(c), obj))
|
||||||
}
|
}
|
||||||
ret[i] = field.Interface()
|
ret[i] = field.Interface()
|
||||||
}
|
}
|
||||||
|
@ -236,6 +322,24 @@ func GetStructValues(obj interface{}, columns ...string) []interface{} {
|
||||||
return ret
|
return ret
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetSliceValues returns the values (as interface) of the matching columns in obj.
|
||||||
|
func GetSliceValues(slice []interface{}, columns ...string) []interface{} {
|
||||||
|
ret := make([]interface{}, len(slice)*len(columns))
|
||||||
|
|
||||||
|
for i, obj := range slice {
|
||||||
|
val := reflect.Indirect(reflect.ValueOf(obj))
|
||||||
|
for j, c := range columns {
|
||||||
|
field := val.FieldByName(strmangle.TitleCase(c))
|
||||||
|
if !field.IsValid() {
|
||||||
|
panic(fmt.Sprintf("unable to find field with name: %s\n%#v", strmangle.TitleCase(c), obj))
|
||||||
|
}
|
||||||
|
ret[i*len(columns)+j] = field.Interface()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
// GetStructPointers returns a slice of pointers to the matching columns in obj
|
// GetStructPointers returns a slice of pointers to the matching columns in obj
|
||||||
func GetStructPointers(obj interface{}, columns ...string) []interface{} {
|
func GetStructPointers(obj interface{}, columns ...string) []interface{} {
|
||||||
val := reflect.ValueOf(obj).Elem()
|
val := reflect.ValueOf(obj).Elem()
|
||||||
|
|
|
@ -103,6 +103,53 @@ 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 TestLoadRelationshipsSlice(t *testing.T) {
|
||||||
|
// t.Parallel() Function uses globals
|
||||||
|
loadFunctionCalled = false
|
||||||
|
|
||||||
|
testSlice := []*struct {
|
||||||
|
ID int
|
||||||
|
Relationships *testRelationshipsStruct
|
||||||
|
}{}
|
||||||
|
|
||||||
|
q := Query{load: []string{"TestOne"}, executor: nil}
|
||||||
|
if err := q.loadRelationships(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
|
||||||
|
Relationships *testRelationshipsStruct
|
||||||
|
}{}
|
||||||
|
|
||||||
|
q := Query{load: []string{"TestOne"}, executor: nil}
|
||||||
|
if err := q.loadRelationships(testSingular, true); err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !loadFunctionCalled {
|
||||||
|
t.Errorf("Load function was not called for singular")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestBind_InnerJoin(t *testing.T) {
|
func TestBind_InnerJoin(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
|
@ -352,6 +399,36 @@ func TestGetStructValues(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestGetSliceValues(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
o := []struct {
|
||||||
|
ID int
|
||||||
|
Name string
|
||||||
|
}{
|
||||||
|
{5, "a"},
|
||||||
|
{6, "b"},
|
||||||
|
}
|
||||||
|
|
||||||
|
in := make([]interface{}, len(o))
|
||||||
|
in[0] = o[0]
|
||||||
|
in[1] = o[1]
|
||||||
|
|
||||||
|
vals := GetSliceValues(in, "id", "name")
|
||||||
|
if got := vals[0].(int); got != 5 {
|
||||||
|
t.Error(got)
|
||||||
|
}
|
||||||
|
if got := vals[1].(string); got != "a" {
|
||||||
|
t.Error(got)
|
||||||
|
}
|
||||||
|
if got := vals[2].(int); got != 6 {
|
||||||
|
t.Error(got)
|
||||||
|
}
|
||||||
|
if got := vals[3].(string); got != "b" {
|
||||||
|
t.Error(got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestGetStructPointers(t *testing.T) {
|
func TestGetStructPointers(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
|
|
|
@ -98,6 +98,11 @@ func (s *Seed) RandomizeStruct(str interface{}, colTypes map[string]string, canB
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
tagVal, _ := getBoilTag(fieldTyp)
|
||||||
|
if tagVal == "-" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
fieldDBType := colTypes[fieldTyp.Name]
|
fieldDBType := colTypes[fieldTyp.Name]
|
||||||
if err := s.randomizeField(fieldVal, fieldDBType, canBeNull); err != nil {
|
if err := s.randomizeField(fieldVal, fieldDBType, canBeNull); err != nil {
|
||||||
return err
|
return err
|
||||||
|
|
|
@ -157,4 +157,5 @@ var templateFunctions = template.FuncMap{
|
||||||
"sqlColDefinitions": bdb.SQLColDefinitions,
|
"sqlColDefinitions": bdb.SQLColDefinitions,
|
||||||
"columnNames": bdb.ColumnNames,
|
"columnNames": bdb.ColumnNames,
|
||||||
"columnDBTypes": bdb.ColumnDBTypes,
|
"columnDBTypes": bdb.ColumnDBTypes,
|
||||||
|
"getTable": bdb.GetTable,
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,7 +11,7 @@ type {{$modelName}} struct {
|
||||||
{{end -}}
|
{{end -}}
|
||||||
{{- if .Table.IsJoinTable -}}
|
{{- if .Table.IsJoinTable -}}
|
||||||
{{- else}}
|
{{- else}}
|
||||||
//Relationships *{{$modelName}}Relationships `boil:"-" json:"-" toml:"-" yaml:"-"`
|
Relationships *{{$modelName}}Relationships `boil:"-" json:"-" toml:"-" yaml:"-"`
|
||||||
{{end -}}
|
{{end -}}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -48,7 +48,7 @@ func ({{$rel.Function.Receiver}} *{{$rel.LocalTable.NameGo}}) {{$rel.Function.Na
|
||||||
|
|
||||||
{{if .ToJoinTable -}}
|
{{if .ToJoinTable -}}
|
||||||
queryMods = append(queryMods,
|
queryMods = append(queryMods,
|
||||||
qm.InnerJoin(`"{{.JoinTable}}" as "{{id 1}}" on "{{id 1}}"."{{.JoinForeignColumn}}" = "{{id 0}}"."{{.ForeignColumn}}"`),
|
qm.InnerJoin(`"{{.JoinTable}}" as "{{id 1}}" on "{{id 0}}"."{{.ForeignColumn}}" = "{{id 1}}"."{{.JoinForeignColumn}}"`),
|
||||||
qm.Where(`"{{id 1}}"."{{.JoinLocalColumn}}"=$1`, {{.Column | titleCase | printf "%s.%s" $rel.Function.Receiver }}),
|
qm.Where(`"{{id 1}}"."{{.JoinLocalColumn}}"=$1`, {{.Column | titleCase | printf "%s.%s" $rel.Function.Receiver }}),
|
||||||
)
|
)
|
||||||
{{else -}}
|
{{else -}}
|
||||||
|
|
124
templates/relationship_to_many_eager.tpl
Normal file
124
templates/relationship_to_many_eager.tpl
Normal file
|
@ -0,0 +1,124 @@
|
||||||
|
{{- if .Table.IsJoinTable -}}
|
||||||
|
{{- else}}
|
||||||
|
{{- $dot := . -}}
|
||||||
|
{{- range .Table.ToManyRelationships -}}
|
||||||
|
{{- if (and .ForeignColumnUnique (not .ToJoinTable)) -}}
|
||||||
|
{{- template "relationship_to_one_eager_helper" (textsFromOneToOneRelationship $dot.PkgName $dot.Tables $dot.Table .) -}}
|
||||||
|
{{- else -}}
|
||||||
|
{{- $rel := textsFromRelationship $dot.Tables $dot.Table . -}}
|
||||||
|
{{- $arg := printf "maybe%s" $rel.LocalTable.NameGo -}}
|
||||||
|
{{- $slice := printf "%sSlice" $rel.LocalTable.NameGo}}
|
||||||
|
// Load{{$rel.Function.Name}} allows an eager lookup of values, cached into the
|
||||||
|
// relationships structs of the objects.
|
||||||
|
func (r *{{$rel.LocalTable.NameGo}}Relationships) Load{{$rel.Function.Name}}(e boil.Executor, singular bool, {{$arg}} interface{}) error {
|
||||||
|
var slice []*{{$rel.LocalTable.NameGo}}
|
||||||
|
var object *{{$rel.LocalTable.NameGo}}
|
||||||
|
|
||||||
|
count := 1
|
||||||
|
if singular {
|
||||||
|
object = {{$arg}}.(*{{$rel.LocalTable.NameGo}})
|
||||||
|
} else {
|
||||||
|
slice = {{$arg}}.({{$slice}})
|
||||||
|
count = len(slice)
|
||||||
|
}
|
||||||
|
|
||||||
|
args := make([]interface{}, count)
|
||||||
|
if singular {
|
||||||
|
args[0] = object.{{.Column | titleCase}}
|
||||||
|
} else {
|
||||||
|
for i, obj := range slice {
|
||||||
|
args[i] = obj.{{.Column | titleCase}}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
{{if .ToJoinTable -}}
|
||||||
|
query := fmt.Sprintf(
|
||||||
|
`select "{{id 0}}".*, "{{id 1}}"."{{.JoinLocalColumn}}" from "{{.ForeignTable}}" as "{{id 0}}" inner join "{{.JoinTable}}" as "{{id 1}}" on "{{id 0}}"."{{.ForeignColumn}}" = "{{id 1}}"."{{.JoinForeignColumn}}" where "{{id 1}}"."{{.JoinLocalColumn}}" in (%s)`,
|
||||||
|
strmangle.Placeholders(count, 1, 1),
|
||||||
|
)
|
||||||
|
{{else -}}
|
||||||
|
query := fmt.Sprintf(
|
||||||
|
`select * from "{{.ForeignTable}}" where "{{.ForeignColumn}}" in (%s)`,
|
||||||
|
strmangle.Placeholders(count, 1, 1),
|
||||||
|
)
|
||||||
|
{{end -}}
|
||||||
|
|
||||||
|
if boil.DebugMode {
|
||||||
|
fmt.Fprintf(boil.DebugWriter, "%s\n%v\n", query, args)
|
||||||
|
}
|
||||||
|
|
||||||
|
results, err := e.Query(query, args...)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err, "failed to eager load {{.ForeignTable}}")
|
||||||
|
}
|
||||||
|
defer results.Close()
|
||||||
|
|
||||||
|
var resultSlice []*{{$rel.ForeignTable.NameGo}}
|
||||||
|
{{if .ToJoinTable -}}
|
||||||
|
{{- $foreignTable := getTable $dot.Tables .ForeignTable -}}
|
||||||
|
{{- $joinTable := getTable $dot.Tables .JoinTable -}}
|
||||||
|
{{- $localCol := $joinTable.GetColumn .JoinLocalColumn}}
|
||||||
|
var localJoinCols []{{$localCol.Type}}
|
||||||
|
for results.Next() {
|
||||||
|
one := new({{$rel.ForeignTable.NameGo}})
|
||||||
|
var localJoinCol {{$localCol.Type}}
|
||||||
|
|
||||||
|
err = results.Scan({{$foreignTable.Columns | columnNames | stringMap $dot.StringFuncs.titleCase | prefixStringSlice "&one." | join ", "}}, &localJoinCol)
|
||||||
|
if err = results.Err(); err != nil {
|
||||||
|
return errors.Wrap(err, "failed to plebian-bind eager loaded slice {{.ForeignTable}}")
|
||||||
|
}
|
||||||
|
|
||||||
|
resultSlice = append(resultSlice, one)
|
||||||
|
localJoinCols = append(localJoinCols, localJoinCol)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = results.Err(); err != nil {
|
||||||
|
return errors.Wrap(err, "failed to plebian-bind eager loaded slice {{.ForeignTable}}")
|
||||||
|
}
|
||||||
|
{{else -}}
|
||||||
|
if err = boil.Bind(results, &resultSlice); err != nil {
|
||||||
|
return errors.Wrap(err, "failed to bind eager loaded slice {{.ForeignTable}}")
|
||||||
|
}
|
||||||
|
{{end}}
|
||||||
|
|
||||||
|
if singular {
|
||||||
|
if object.Relationships == nil {
|
||||||
|
object.Relationships = &{{$rel.LocalTable.NameGo}}Relationships{}
|
||||||
|
}
|
||||||
|
object.Relationships.{{$rel.Function.Name}} = resultSlice
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
{{if .ToJoinTable -}}
|
||||||
|
for i, foreign := range resultSlice {
|
||||||
|
localJoinCol := localJoinCols[i]
|
||||||
|
for _, local := range slice {
|
||||||
|
if local.{{$rel.Function.LocalAssignment}} == localJoinCol {
|
||||||
|
if local.Relationships == nil {
|
||||||
|
local.Relationships = &{{$rel.LocalTable.NameGo}}Relationships{}
|
||||||
|
}
|
||||||
|
local.Relationships.{{$rel.Function.Name}} = append(local.Relationships.{{$rel.Function.Name}}, foreign)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
{{else -}}
|
||||||
|
for _, foreign := range resultSlice {
|
||||||
|
for _, local := range slice {
|
||||||
|
if local.{{$rel.Function.LocalAssignment}} == foreign.{{$rel.Function.ForeignAssignment}} {
|
||||||
|
if local.Relationships == nil {
|
||||||
|
local.Relationships = &{{$rel.LocalTable.NameGo}}Relationships{}
|
||||||
|
}
|
||||||
|
local.Relationships.{{$rel.Function.Name}} = append(local.Relationships.{{$rel.Function.Name}}, foreign)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
{{end}}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
{{end -}}{{/* if ForeignColumnUnique */}}
|
||||||
|
{{- end -}}{{/* range tomany */}}
|
||||||
|
{{- end -}}{{/* if isjointable */}}
|
77
templates/relationship_to_one_eager.tpl
Normal file
77
templates/relationship_to_one_eager.tpl
Normal file
|
@ -0,0 +1,77 @@
|
||||||
|
{{- define "relationship_to_one_eager_helper" -}}
|
||||||
|
{{- $arg := printf "maybe%s" .LocalTable.NameGo -}}
|
||||||
|
{{- $slice := printf "%sSlice" .LocalTable.NameGo}}
|
||||||
|
// Load{{.Function.Name}} allows an eager lookup of values, cached into the
|
||||||
|
// relationships structs of the objects.
|
||||||
|
func (r *{{.LocalTable.NameGo}}Relationships) Load{{.Function.Name}}(e boil.Executor, singular bool, {{$arg}} interface{}) error {
|
||||||
|
var slice []*{{.LocalTable.NameGo}}
|
||||||
|
var object *{{.LocalTable.NameGo}}
|
||||||
|
|
||||||
|
count := 1
|
||||||
|
if singular {
|
||||||
|
object = {{$arg}}.(*{{.LocalTable.NameGo}})
|
||||||
|
} else {
|
||||||
|
slice = {{$arg}}.({{$slice}})
|
||||||
|
count = len(slice)
|
||||||
|
}
|
||||||
|
|
||||||
|
args := make([]interface{}, count)
|
||||||
|
if singular {
|
||||||
|
args[0] = object.{{.LocalTable.ColumnNameGo}}
|
||||||
|
} else {
|
||||||
|
for i, obj := range slice {
|
||||||
|
args[i] = obj.{{.LocalTable.ColumnNameGo}}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
query := fmt.Sprintf(
|
||||||
|
`select * from "{{.ForeignKey.ForeignTable}}" where "{{.ForeignKey.ForeignColumn}}" in (%s)`,
|
||||||
|
strmangle.Placeholders(count, 1, 1),
|
||||||
|
)
|
||||||
|
|
||||||
|
if boil.DebugMode {
|
||||||
|
fmt.Fprintf(boil.DebugWriter, "%s\n%v\n", query, args)
|
||||||
|
}
|
||||||
|
|
||||||
|
results, err := e.Query(query, args...)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err, "failed to eager load {{.ForeignTable.NameGo}}")
|
||||||
|
}
|
||||||
|
defer results.Close()
|
||||||
|
|
||||||
|
var resultSlice []*{{.ForeignTable.NameGo}}
|
||||||
|
if err = boil.Bind(results, &resultSlice); err != nil {
|
||||||
|
return errors.Wrap(err, "failed to bind eager loaded slice {{.ForeignTable}}")
|
||||||
|
}
|
||||||
|
|
||||||
|
if singular && len(resultSlice) != 0 {
|
||||||
|
if object.Relationships == nil {
|
||||||
|
object.Relationships = &{{.LocalTable.NameGo}}Relationships{}
|
||||||
|
}
|
||||||
|
object.Relationships.{{.Function.Name}} = resultSlice[0]
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, foreign := range resultSlice {
|
||||||
|
for _, local := range slice {
|
||||||
|
if local.{{.Function.LocalAssignment}} == foreign.{{.Function.ForeignAssignment}} {
|
||||||
|
if local.Relationships == nil {
|
||||||
|
local.Relationships = &{{.LocalTable.NameGo}}Relationships{}
|
||||||
|
}
|
||||||
|
local.Relationships.{{.Function.Name}} = foreign
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
{{- end -}}
|
||||||
|
{{- if .Table.IsJoinTable -}}
|
||||||
|
{{- else -}}
|
||||||
|
{{- $dot := . -}}
|
||||||
|
{{- range .Table.FKeys -}}
|
||||||
|
{{- $rel := textsFromForeignKey $dot.PkgName $dot.Tables $dot.Table . -}}
|
||||||
|
{{- template "relationship_to_one_eager_helper" $rel -}}
|
||||||
|
{{end -}}
|
||||||
|
{{- end -}}
|
|
@ -74,6 +74,21 @@ func test{{$rel.LocalTable.NameGo}}ToMany{{$rel.Function.Name}}(t *testing.T) {
|
||||||
t.Error("expected to find c")
|
t.Error("expected to find c")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if err = a.Relationships.Load{{$rel.Function.Name}}(tx, false, {{$rel.LocalTable.NameGo}}Slice{&a}); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if got := len(a.Relationships.{{$rel.Function.Name}}); got != 2 {
|
||||||
|
t.Error("number of eager loaded records wrong, got:", got)
|
||||||
|
}
|
||||||
|
|
||||||
|
a.Relationships.{{$rel.Function.Name}} = nil
|
||||||
|
if err = a.Relationships.Load{{$rel.Function.Name}}(tx, true, &a); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if got := len(a.Relationships.{{$rel.Function.Name}}); got != 2 {
|
||||||
|
t.Error("number of eager loaded records wrong, got:", got)
|
||||||
|
}
|
||||||
|
|
||||||
if t.Failed() {
|
if t.Failed() {
|
||||||
t.Logf("%#v", {{$varname}})
|
t.Logf("%#v", {{$varname}})
|
||||||
}
|
}
|
||||||
|
|
|
@ -40,6 +40,21 @@ func test{{.LocalTable.NameGo}}ToOne{{.ForeignTable.NameGo}}_{{.Function.Name}}(
|
||||||
if check.{{.Function.ForeignAssignment}} != foreign.{{.Function.ForeignAssignment}} {
|
if check.{{.Function.ForeignAssignment}} != foreign.{{.Function.ForeignAssignment}} {
|
||||||
t.Errorf("want: %v, got %v", foreign.{{.Function.ForeignAssignment}}, check.{{.Function.ForeignAssignment}})
|
t.Errorf("want: %v, got %v", foreign.{{.Function.ForeignAssignment}}, check.{{.Function.ForeignAssignment}})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if err = local.Relationships.Load{{.Function.Name}}(tx, false, {{.LocalTable.NameGo}}Slice{&local}); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if local.Relationships.{{.Function.Name}} == nil {
|
||||||
|
t.Error("struct should have been eager loaded")
|
||||||
|
}
|
||||||
|
|
||||||
|
local.Relationships.{{.Function.Name}} = nil
|
||||||
|
if err = local.Relationships.Load{{.Function.Name}}(tx, true, &local); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if local.Relationships.{{.Function.Name}} == nil {
|
||||||
|
t.Error("struct should have been eager loaded")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
{{end -}}
|
{{end -}}
|
||||||
|
|
|
@ -136,9 +136,9 @@ func TestInsert(t *testing.T) {
|
||||||
{{- end -}}
|
{{- end -}}
|
||||||
}
|
}
|
||||||
|
|
||||||
// The relationship tests cannot be run in parallel
|
// TestToMany tests cannot be run in parallel
|
||||||
// or postgres deadlocks will occur.
|
// or postgres deadlocks will occur.
|
||||||
func TestRelationships(t *testing.T) {
|
func TestToMany(t *testing.T) {
|
||||||
{{- $dot := .}}
|
{{- $dot := .}}
|
||||||
{{- range $index, $table := .Tables}}
|
{{- range $index, $table := .Tables}}
|
||||||
{{- $tableName := $table.Name | plural | titleCase -}}
|
{{- $tableName := $table.Name | plural | titleCase -}}
|
||||||
|
@ -147,16 +147,31 @@ func TestRelationships(t *testing.T) {
|
||||||
{{- range $table.ToManyRelationships -}}
|
{{- range $table.ToManyRelationships -}}
|
||||||
{{- $rel := textsFromRelationship $dot.Tables $table . -}}
|
{{- $rel := textsFromRelationship $dot.Tables $table . -}}
|
||||||
{{- if (and .ForeignColumnUnique (not .ToJoinTable)) -}}
|
{{- if (and .ForeignColumnUnique (not .ToJoinTable)) -}}
|
||||||
{{- $funcName := $rel.LocalTable.NameGo -}}
|
{{- $oneToOne := textsFromOneToOneRelationship $dot.PkgName $dot.Tables $table . -}}
|
||||||
t.Run("{{$rel.ForeignTable.NameGo}}ToOne", test{{$rel.ForeignTable.NameGo}}ToOne{{$rel.LocalTable.NameGo}}_{{$funcName}})
|
t.Run("{{$oneToOne.LocalTable.NameGo}}OneToOne{{$oneToOne.ForeignTable.NameGo}}_{{$oneToOne.Function.Name}}", test{{$oneToOne.LocalTable.NameGo}}ToOne{{$oneToOne.ForeignTable.NameGo}}_{{$oneToOne.Function.Name}})
|
||||||
{{else -}}
|
{{else -}}
|
||||||
t.Run("{{$rel.LocalTable.NameGo}}ToMany", test{{$rel.LocalTable.NameGo}}ToMany{{$rel.Function.Name}})
|
t.Run("{{$rel.LocalTable.NameGo}}ToMany{{$rel.Function.Name}}", test{{$rel.LocalTable.NameGo}}ToMany{{$rel.Function.Name}})
|
||||||
{{end -}}{{- /* if unique */ -}}
|
{{end -}}{{- /* if unique */ -}}
|
||||||
{{- end -}}{{- /* range */ -}}
|
{{- end -}}{{- /* range */ -}}
|
||||||
{{- end -}}{{- /* outer if join table */ -}}
|
{{- end -}}{{- /* outer if join table */ -}}
|
||||||
{{- end -}}{{- /* outer tables range */ -}}
|
{{- end -}}{{- /* outer tables range */ -}}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestToOne tests cannot be run in parallel
|
||||||
|
// or postgres deadlocks will occur.
|
||||||
|
func TestToOne(t *testing.T) {
|
||||||
|
{{- $dot := . -}}
|
||||||
|
{{- range $index, $table := .Tables}}
|
||||||
|
{{- if $table.IsJoinTable -}}
|
||||||
|
{{- else -}}
|
||||||
|
{{- range $table.FKeys -}}
|
||||||
|
{{- $rel := textsFromForeignKey $dot.PkgName $dot.Tables $table . -}}
|
||||||
|
t.Run("{{$rel.LocalTable.NameGo}}To{{$rel.ForeignTable.NameGo}}_{{$rel.Function.Name}}", test{{$rel.LocalTable.NameGo}}ToOne{{$rel.ForeignTable.NameGo}}_{{$rel.Function.Name}})
|
||||||
|
{{end -}}{{- /* fkey range */ -}}
|
||||||
|
{{- end -}}{{- /* if join table */ -}}
|
||||||
|
{{- end -}}{{- /* tables range */ -}}
|
||||||
|
}
|
||||||
|
|
||||||
func TestReload(t *testing.T) {
|
func TestReload(t *testing.T) {
|
||||||
{{- range $index, $table := .Tables}}
|
{{- range $index, $table := .Tables}}
|
||||||
{{- if $table.IsJoinTable -}}
|
{{- if $table.IsJoinTable -}}
|
||||||
|
|
Loading…
Reference in a new issue