Merge branch 'sets'

This commit is contained in:
Aaron L 2016-08-28 23:29:47 -07:00
commit 9553f462c7
20 changed files with 1104 additions and 136 deletions

View file

@ -100,7 +100,7 @@ users, err := models.Users(db, Load("FavoriteMovies")).All()
if err != nil {
return err
}
fmt.Println(len(users.Loaded.FavoriteMovies))
fmt.Println(len(users.R.FavoriteMovies))
```
## How to boil your database

View file

@ -59,7 +59,7 @@ func (m *MockDriver) Columns(tableName string) ([]bdb.Column, error) {
func (m *MockDriver) ForeignKeyInfo(tableName string) ([]bdb.ForeignKey, error) {
return map[string][]bdb.ForeignKey{
"jets": {
{Table: "jets", Name: "jets_pilot_id_fk", Column: "pilot_id", ForeignTable: "pilots", ForeignColumn: "id"},
{Table: "jets", Name: "jets_pilot_id_fk", Column: "pilot_id", ForeignTable: "pilots", ForeignColumn: "id", ForeignColumnUnique: true},
{Table: "jets", Name: "jets_airport_id_fk", Column: "airport_id", ForeignTable: "airports", ForeignColumn: "id"},
},
"licenses": {

View file

@ -1,98 +1,140 @@
package bdb
import (
"reflect"
"testing"
"github.com/vattle/sqlboiler/strmangle"
)
type testInterface struct{}
type mockDriver struct{}
func (t testInterface) TableNames(exclude []string) ([]string, error) {
return []string{"table1", "table2"}, nil
func (m mockDriver) TranslateColumnType(c Column) Column { return c }
func (m mockDriver) UseLastInsertID() bool { return false }
func (m mockDriver) Open() error { return nil }
func (m mockDriver) Close() {}
func (m mockDriver) TableNames(exclude []string) ([]string, error) {
tables := []string{"pilots", "jets", "airports", "licenses", "hangars", "languages", "pilot_languages"}
return strmangle.SetComplement(tables, exclude), nil
}
var testCols = []Column{
{Name: "col1", Type: "character varying"},
{Name: "col2", Type: "character varying", Nullable: true},
// Columns returns a list of mock columns
func (m mockDriver) Columns(tableName string) ([]Column, error) {
return map[string][]Column{
"pilots": {
{Name: "id", Type: "int", DBType: "integer"},
{Name: "name", Type: "string", DBType: "character"},
},
"airports": {
{Name: "id", Type: "int", DBType: "integer"},
{Name: "size", Type: "null.Int", DBType: "integer", Nullable: true},
},
"jets": {
{Name: "id", Type: "int", DBType: "integer"},
{Name: "pilot_id", Type: "int", DBType: "integer", Nullable: true, Unique: true},
{Name: "airport_id", Type: "int", DBType: "integer"},
{Name: "name", Type: "string", DBType: "character", Nullable: false},
{Name: "color", Type: "null.String", DBType: "character", Nullable: true},
{Name: "uuid", Type: "string", DBType: "uuid", Nullable: true},
{Name: "identifier", Type: "string", DBType: "uuid", Nullable: false},
{Name: "cargo", Type: "[]byte", DBType: "bytea", Nullable: false},
{Name: "manifest", Type: "[]byte", DBType: "bytea", Nullable: true, Unique: true},
},
"licenses": {
{Name: "id", Type: "int", DBType: "integer"},
{Name: "pilot_id", Type: "int", DBType: "integer"},
},
"hangars": {
{Name: "id", Type: "int", DBType: "integer"},
{Name: "name", Type: "string", DBType: "character", Nullable: true, Unique: true},
{Name: "hangar_id", Type: "int", DBType: "integer", Nullable: true},
},
"languages": {
{Name: "id", Type: "int", DBType: "integer"},
{Name: "language", Type: "string", DBType: "character", Nullable: false, Unique: true},
},
"pilot_languages": {
{Name: "pilot_id", Type: "int", DBType: "integer"},
{Name: "language_id", Type: "int", DBType: "integer"},
},
}[tableName], nil
}
func (t testInterface) Columns(tableName string) ([]Column, error) {
return testCols, nil
// ForeignKeyInfo returns a list of mock foreignkeys
func (m mockDriver) ForeignKeyInfo(tableName string) ([]ForeignKey, error) {
return map[string][]ForeignKey{
"jets": {
{Table: "jets", Name: "jets_pilot_id_fk", Column: "pilot_id", ForeignTable: "pilots", ForeignColumn: "id", ForeignColumnUnique: true},
{Table: "jets", Name: "jets_airport_id_fk", Column: "airport_id", ForeignTable: "airports", ForeignColumn: "id"},
},
"licenses": {
{Table: "licenses", Name: "licenses_pilot_id_fk", Column: "pilot_id", ForeignTable: "pilots", ForeignColumn: "id"},
},
"pilot_languages": {
{Table: "pilot_languages", Name: "pilot_id_fk", Column: "pilot_id", ForeignTable: "pilots", ForeignColumn: "id"},
{Table: "pilot_languages", Name: "jet_id_fk", Column: "language_id", ForeignTable: "languages", ForeignColumn: "id"},
},
"hangars": {
{Table: "hangars", Name: "hangar_fk_id", Column: "hangar_id", ForeignTable: "hangars", ForeignColumn: "id"},
},
}[tableName], nil
}
func (t testInterface) UseLastInsertID() bool {
return false
// PrimaryKeyInfo returns mock primary key info for the passed in table name
func (m mockDriver) PrimaryKeyInfo(tableName string) (*PrimaryKey, error) {
return map[string]*PrimaryKey{
"pilots": {Name: "pilot_id_pkey", Columns: []string{"id"}},
"airports": {Name: "airport_id_pkey", Columns: []string{"id"}},
"jets": {Name: "jet_id_pkey", Columns: []string{"id"}},
"licenses": {Name: "license_id_pkey", Columns: []string{"id"}},
"hangars": {Name: "hangar_id_pkey", Columns: []string{"id"}},
"languages": {Name: "language_id_pkey", Columns: []string{"id"}},
"pilot_languages": {Name: "pilot_languages_pkey", Columns: []string{"pilot_id", "language_id"}},
}[tableName], nil
}
var testPkey = &PrimaryKey{Name: "pkey1", Columns: []string{"col1", "col2"}}
func (t testInterface) PrimaryKeyInfo(tableName string) (*PrimaryKey, error) {
return testPkey, nil
}
var testFkeys = []ForeignKey{
{
Name: "fkey1",
Column: "col1",
ForeignTable: "table2",
ForeignColumn: "col2",
},
{
Name: "fkey2",
Column: "col2",
ForeignTable: "table1",
ForeignColumn: "col1",
},
}
func (t testInterface) ForeignKeyInfo(tableName string) ([]ForeignKey, error) {
return testFkeys, nil
}
func (t testInterface) TranslateColumnType(column Column) Column {
column.Type = "string"
return column
}
func (t testInterface) Open() error {
return nil
}
func (t testInterface) Close() {}
func TestTables(t *testing.T) {
t.Parallel()
tables, err := Tables(testInterface{})
tables, err := Tables(mockDriver{})
if err != nil {
t.Error(err)
}
if len(tables) != 2 {
t.Errorf("Expected len 2, got: %d\n", len(tables))
if len(tables) != 7 {
t.Errorf("Expected len 7, got: %d\n", len(tables))
}
if !reflect.DeepEqual(tables[0].Columns, testCols) {
t.Errorf("Did not get expected columns, got:\n%#v\n%#v", tables[0].Columns, testCols)
pilots := GetTable(tables, "pilots")
if len(pilots.Columns) != 2 {
t.Error()
}
if pilots.ToManyRelationships[0].ForeignTable != "jets" {
t.Error("want a to many to jets")
}
if pilots.ToManyRelationships[1].ForeignTable != "licenses" {
t.Error("want a to many to languages")
}
if pilots.ToManyRelationships[2].ForeignTable != "languages" {
t.Error("want a to many to languages")
}
if !tables[0].IsJoinTable || !tables[1].IsJoinTable {
t.Errorf("Expected IsJoinTable to be true")
jets := GetTable(tables, "jets")
if len(jets.ToManyRelationships) != 0 {
t.Error("want no to many relationships")
}
if !reflect.DeepEqual(tables[0].PKey, testPkey) {
t.Errorf("Did not get expected PKey, got:\n#%v\n%#v", tables[0].PKey, testPkey)
languages := GetTable(tables, "pilot_languages")
if !languages.IsJoinTable {
t.Error("languages is a join table")
}
if !reflect.DeepEqual(tables[0].FKeys, testFkeys) {
t.Errorf("Did not get expected Fkey, got:\n%#v\n%#v", tables[0].FKeys, testFkeys)
hangars := GetTable(tables, "hangars")
if len(hangars.ToManyRelationships) != 1 || hangars.ToManyRelationships[0].ForeignTable != "hangars" {
t.Error("want 1 to many relationships")
}
if len(tables[0].ToManyRelationships) != 1 {
t.Error("wanted a to many relationship")
}
if len(tables[1].ToManyRelationships) != 1 {
t.Error("wanted a to many relationship")
if len(hangars.FKeys) != 1 || hangars.FKeys[0].ForeignTable != "hangars" {
t.Error("want one hangar foreign key to itself")
}
}

View file

@ -38,10 +38,6 @@ func toManyRelationships(table Table, tables []Table) []ToManyRelationship {
var relationships []ToManyRelationship
for _, t := range tables {
if t.Name == table.Name {
continue
}
for _, f := range t.FKeys {
if f.ForeignTable != table.Name {
continue
@ -58,7 +54,7 @@ func buildRelationship(localTable Table, foreignKey ForeignKey, foreignTable Tab
if !foreignTable.IsJoinTable {
col := localTable.GetColumn(foreignKey.ForeignColumn)
return ToManyRelationship{
Table: foreignKey.Table,
Table: localTable.Name,
Column: foreignKey.ForeignColumn,
Nullable: col.Nullable,
Unique: col.Unique,
@ -72,7 +68,7 @@ func buildRelationship(localTable Table, foreignKey ForeignKey, foreignTable Tab
col := foreignTable.GetColumn(foreignKey.Column)
relationship := ToManyRelationship{
Table: foreignKey.Table,
Table: localTable.Name,
Column: foreignKey.ForeignColumn,
Nullable: col.Nullable,
Unique: col.Unique,

View file

@ -77,6 +77,7 @@ func TestToManyRelationships(t *testing.T) {
expected := []ToManyRelationship{
{
Table: "pilots",
Column: "id",
Nullable: false,
Unique: false,
@ -89,6 +90,7 @@ func TestToManyRelationships(t *testing.T) {
ToJoinTable: false,
},
{
Table: "pilots",
Column: "id",
Nullable: false,
Unique: false,
@ -101,6 +103,7 @@ func TestToManyRelationships(t *testing.T) {
ToJoinTable: false,
},
{
Table: "pilots",
Column: "id",
Nullable: false,
Unique: false,
@ -209,6 +212,7 @@ func TestToManyRelationshipsNull(t *testing.T) {
expected := []ToManyRelationship{
{
Table: "pilots",
Column: "id",
Nullable: true,
Unique: true,
@ -221,6 +225,7 @@ func TestToManyRelationshipsNull(t *testing.T) {
ToJoinTable: false,
},
{
Table: "pilots",
Column: "id",
Nullable: true,
Unique: true,
@ -233,6 +238,7 @@ func TestToManyRelationshipsNull(t *testing.T) {
ToJoinTable: false,
},
{
Table: "pilots",
Column: "id",
Nullable: true,
Unique: true,

View file

@ -110,7 +110,7 @@ func (q *Query) BindFast(obj interface{}, titleCases map[string]string) error {
// loadRelationships dynamically calls the template generated eager load
// functions of the form:
//
// func (t *TableLoaded) LoadRelationshipName(exec Executor, singular bool, obj interface{})
// 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
@ -125,11 +125,11 @@ func (q *Query) loadRelationships(obj interface{}, singular bool) error {
typ = typ.Elem().Elem()
}
rel, found := typ.FieldByName("Loaded")
rel, found := typ.FieldByName("R")
// If the users object has no loaded 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 Loaded field")
return errors.New("load query mod was used but bound struct contained no R field")
}
for _, relationship := range q.load {

View file

@ -166,9 +166,9 @@ func TestBindSingular(t *testing.T) {
var loadFunctionCalled bool
type testLoadedStruct struct{}
type testRStruct struct{}
func (r *testLoadedStruct) LoadTestOne(exec Executor, singular bool, obj interface{}) error {
func (r *testRStruct) LoadTestOne(exec Executor, singular bool, obj interface{}) error {
loadFunctionCalled = true
return nil
}
@ -179,7 +179,7 @@ func TestLoadRelationshipsSlice(t *testing.T) {
testSlice := []*struct {
ID int
Loaded *testLoadedStruct
R *testRStruct
}{}
q := Query{load: []string{"TestOne"}, executor: nil}
@ -198,7 +198,7 @@ func TestLoadRelationshipsSingular(t *testing.T) {
testSingular := struct {
ID int
Loaded *testLoadedStruct
R *testRStruct
}{}
q := Query{load: []string{"TestOne"}, executor: nil}

View file

@ -11,15 +11,15 @@ type {{$modelName}} struct {
{{end -}}
{{- if .Table.IsJoinTable -}}
{{- else}}
Loaded *{{$modelName}}Loaded `boil:"-" json:"-" toml:"-" yaml:"-"`
R *{{$modelName}}R `boil:"-" json:"-" toml:"-" yaml:"-"`
{{end -}}
}
{{- $dot := . -}}
{{- if .Table.IsJoinTable -}}
{{- else}}
// {{$modelName}}Loaded are where relationships are eagerly loaded.
type {{$modelName}}Loaded struct {
// {{$modelName}}R is where relationships are stored.
type {{$modelName}}R struct {
{{range .Table.FKeys -}}
{{- $rel := textsFromForeignKey $dot.PkgName $dot.Tables $dot.Table . -}}
{{- template "relationship_to_one_struct_helper" $rel}}

View file

@ -28,11 +28,11 @@ func ({{$rel.Function.Receiver}} *{{$rel.LocalTable.NameGo}}) {{$rel.Function.Na
{{if .ToJoinTable -}}
queryMods = append(queryMods,
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`, {{$rel.Function.Receiver}}.{{$rel.LocalTable.ColumnNameGo}}),
)
{{else -}}
queryMods = append(queryMods,
qm.Where(`"{{id 0}}"."{{.ForeignColumn}}"=$1`, {{.Column | titleCase | printf "%s.%s" $rel.Function.Receiver }}),
qm.Where(`"{{id 0}}"."{{.ForeignColumn}}"=$1`, {{$rel.Function.Receiver}}.{{$rel.LocalTable.ColumnNameGo}}),
)
{{end}}

View file

@ -3,7 +3,7 @@
{{- $slice := printf "%sSlice" .LocalTable.NameGo -}}
// Load{{.Function.Name}} allows an eager lookup of values, cached into the
// loaded structs of the objects.
func (r *{{.LocalTable.NameGo}}Loaded) Load{{.Function.Name}}(e boil.Executor, singular bool, {{$arg}} interface{}) error {
func (r *{{.LocalTable.NameGo}}R) Load{{.Function.Name}}(e boil.Executor, singular bool, {{$arg}} interface{}) error {
var slice []*{{.LocalTable.NameGo}}
var object *{{.LocalTable.NameGo}}
@ -45,20 +45,20 @@ func (r *{{.LocalTable.NameGo}}Loaded) Load{{.Function.Name}}(e boil.Executor, s
}
if singular && len(resultSlice) != 0 {
if object.Loaded == nil {
object.Loaded = &{{.LocalTable.NameGo}}Loaded{}
if object.R == nil {
object.R = &{{.LocalTable.NameGo}}R{}
}
object.Loaded.{{.Function.Name}} = resultSlice[0]
object.R.{{.Function.Name}} = resultSlice[0]
return nil
}
for _, foreign := range resultSlice {
for _, local := range slice {
if local.{{.Function.LocalAssignment}} == foreign.{{.Function.ForeignAssignment}} {
if local.Loaded == nil {
local.Loaded = &{{.LocalTable.NameGo}}Loaded{}
if local.R == nil {
local.R = &{{.LocalTable.NameGo}}R{}
}
local.Loaded.{{.Function.Name}} = foreign
local.R.{{.Function.Name}} = foreign
break
}
}

View file

@ -10,7 +10,7 @@
{{- $slice := printf "%sSlice" $rel.LocalTable.NameGo -}}
// Load{{$rel.Function.Name}} allows an eager lookup of values, cached into the
// loaded structs of the objects.
func (r *{{$rel.LocalTable.NameGo}}Loaded) Load{{$rel.Function.Name}}(e boil.Executor, singular bool, {{$arg}} interface{}) error {
func (r *{{$rel.LocalTable.NameGo}}R) Load{{$rel.Function.Name}}(e boil.Executor, singular bool, {{$arg}} interface{}) error {
var slice []*{{$rel.LocalTable.NameGo}}
var object *{{$rel.LocalTable.NameGo}}
@ -82,10 +82,10 @@ func (r *{{$rel.LocalTable.NameGo}}Loaded) Load{{$rel.Function.Name}}(e boil.Exe
{{end}}
if singular {
if object.Loaded == nil {
object.Loaded = &{{$rel.LocalTable.NameGo}}Loaded{}
if object.R == nil {
object.R = &{{$rel.LocalTable.NameGo}}R{}
}
object.Loaded.{{$rel.Function.Name}} = resultSlice
object.R.{{$rel.Function.Name}} = resultSlice
return nil
}
@ -94,10 +94,10 @@ func (r *{{$rel.LocalTable.NameGo}}Loaded) Load{{$rel.Function.Name}}(e boil.Exe
localJoinCol := localJoinCols[i]
for _, local := range slice {
if local.{{$rel.Function.LocalAssignment}} == localJoinCol {
if local.Loaded == nil {
local.Loaded = &{{$rel.LocalTable.NameGo}}Loaded{}
if local.R == nil {
local.R = &{{$rel.LocalTable.NameGo}}R{}
}
local.Loaded.{{$rel.Function.Name}} = append(local.Loaded.{{$rel.Function.Name}}, foreign)
local.R.{{$rel.Function.Name}} = append(local.R.{{$rel.Function.Name}}, foreign)
break
}
}
@ -106,10 +106,10 @@ func (r *{{$rel.LocalTable.NameGo}}Loaded) Load{{$rel.Function.Name}}(e boil.Exe
for _, foreign := range resultSlice {
for _, local := range slice {
if local.{{$rel.Function.LocalAssignment}} == foreign.{{$rel.Function.ForeignAssignment}} {
if local.Loaded == nil {
local.Loaded = &{{$rel.LocalTable.NameGo}}Loaded{}
if local.R == nil {
local.R = &{{$rel.LocalTable.NameGo}}R{}
}
local.Loaded.{{$rel.Function.Name}} = append(local.Loaded.{{$rel.Function.Name}}, foreign)
local.R.{{$rel.Function.Name}} = append(local.R.{{$rel.Function.Name}}, foreign)
break
}
}

View file

@ -0,0 +1,216 @@
{{- if .Table.IsJoinTable -}}
{{- else -}}
{{- $dot := . -}}
{{- $table := .Table -}}
{{- range .Table.ToManyRelationships -}}
{{- $varNameSingular := .ForeignTable | singular | camelCase -}}
{{- if (and .ForeignColumnUnique (not .ToJoinTable)) -}}
{{- template "relationship_to_one_setops_helper" (textsFromOneToOneRelationship $dot.PkgName $dot.Tables $table .) -}}
{{- else -}}
{{- $rel := textsFromRelationship $dot.Tables $table .}}
// Add{{$rel.Function.Name}} adds the given related objects to the existing relationships
// of the {{$table.Name | singular}}, optionally inserting them as new records.
// Appends related to {{$rel.Function.Receiver}}.R.{{$rel.Function.Name}}.
// Sets related.R.{{$rel.Function.ForeignName}} appropriately.
func ({{$rel.Function.Receiver}} *{{$rel.LocalTable.NameGo}}) Add{{$rel.Function.Name}}(exec boil.Executor, insert bool, related ...*{{$rel.ForeignTable.NameGo}}) error {
var err error
for _, rel := range related {
{{if not .ToJoinTable -}}
rel.{{$rel.Function.ForeignAssignment}} = {{$rel.Function.Receiver}}.{{$rel.Function.LocalAssignment}}
{{if .ForeignColumnNullable -}}
rel.{{$rel.ForeignTable.ColumnNameGo}}.Valid = true
{{end -}}
{{end -}}
if insert {
if err = rel.Insert(exec); err != nil {
return errors.Wrap(err, "failed to insert into foreign table")
}
}{{if not .ToJoinTable}} else {
if err = rel.Update(exec, "{{.ForeignColumn}}"); err != nil {
return errors.Wrap(err, "failed to update foreign table")
}
}{{end -}}
}
{{if .ToJoinTable -}}
for _, rel := range related {
query := `insert into "{{.JoinTable}}" ({{.JoinLocalColumn}}, {{.JoinForeignColumn}}) values ($1, $2)`
values := []interface{}{{"{"}}{{$rel.Function.Receiver}}.{{$rel.LocalTable.ColumnNameGo}}, rel.{{$rel.ForeignTable.ColumnNameGo}}}
if boil.DebugMode {
fmt.Fprintln(boil.DebugWriter, query)
fmt.Fprintln(boil.DebugWriter, values)
}
_, err = exec.Exec(query, values...)
if err != nil {
return errors.Wrap(err, "failed to insert into join table")
}
}
{{end -}}
if {{$rel.Function.Receiver}}.R == nil {
{{$rel.Function.Receiver}}.R = &{{$rel.LocalTable.NameGo}}R{
{{$rel.Function.Name}}: related,
}
} else {
{{$rel.Function.Receiver}}.R.{{$rel.Function.Name}} = append({{$rel.Function.Receiver}}.R.{{$rel.Function.Name}}, related...)
}
{{if .ToJoinTable -}}
for _, rel := range related {
if rel.R == nil {
rel.R = &{{$rel.ForeignTable.NameGo}}R{
{{$rel.Function.ForeignName}}: {{$rel.LocalTable.NameGo}}Slice{{"{"}}{{$rel.Function.Receiver}}{{"}"}},
}
} else {
rel.R.{{$rel.Function.ForeignName}} = append(rel.R.{{$rel.Function.ForeignName}}, {{$rel.Function.Receiver}})
}
}
{{else -}}
for _, rel := range related {
if rel.R == nil {
rel.R = &{{$rel.ForeignTable.NameGo}}R{
{{$rel.Function.ForeignName}}: {{$rel.Function.Receiver}},
}
} else {
rel.R.{{$rel.Function.ForeignName}} = {{$rel.Function.Receiver}}
}
}
{{end -}}
return nil
}
{{- if .ForeignColumnNullable}}
// Set{{$rel.Function.Name}} removes all previously related items of the
// {{$table.Name | singular}} replacing them completely with the passed
// in related items, optionally inserting them as new records.
// Sets {{$rel.Function.Receiver}}.R.{{$rel.Function.ForeignName}}'s {{$rel.Function.Name}} accordingly.
// Replaces {{$rel.Function.Receiver}}.R.{{$rel.Function.Name}} with related.
// Sets related.R.{{$rel.Function.ForeignName}}'s {{$rel.Function.Name}} accordingly.
func ({{$rel.Function.Receiver}} *{{$rel.LocalTable.NameGo}}) Set{{$rel.Function.Name}}(exec boil.Executor, insert bool, related ...*{{$rel.ForeignTable.NameGo}}) error {
{{if .ToJoinTable -}}
query := `delete from "{{.JoinTable}}" where "{{.JoinLocalColumn}}" = $1`
values := []interface{}{{"{"}}{{$rel.Function.Receiver}}.{{$rel.LocalTable.ColumnNameGo}}}
{{else -}}
query := `update "{{.ForeignTable}}" set "{{.ForeignColumn}}" = null where "{{.ForeignColumn}}" = $1`
values := []interface{}{{"{"}}{{$rel.Function.Receiver}}.{{$rel.LocalTable.ColumnNameGo}}}
{{end -}}
if boil.DebugMode {
fmt.Fprintln(boil.DebugWriter, query)
fmt.Fprintln(boil.DebugWriter, values)
}
_, err := exec.Exec(query, values...)
if err != nil {
return errors.Wrap(err, "failed to remove relationships before set")
}
{{if .ToJoinTable -}}
remove{{$rel.LocalTable.NameGo}}From{{$rel.ForeignTable.NameGo}}Slice({{$rel.Function.Receiver}}, related)
{{$rel.Function.Receiver}}.R.{{$rel.Function.Name}} = nil
{{else -}}
if {{$rel.Function.Receiver}}.R != nil {
for _, rel := range {{$rel.Function.Receiver}}.R.{{$rel.Function.Name}} {
rel.{{$rel.ForeignTable.ColumnNameGo}}.Valid = false
if rel.R == nil {
continue
}
rel.R.{{$rel.Function.ForeignName}} = nil
}
{{$rel.Function.Receiver}}.R.{{$rel.Function.Name}} = nil
}
{{end -}}
return {{$rel.Function.Receiver}}.Add{{$rel.Function.Name}}(exec, insert, related...)
}
// Remove{{$rel.Function.Name}} relationships from objects passed in.
// Removes related items from R.{{$rel.Function.Name}} (uses pointer comparison, removal does not keep order)
// Sets related.R.{{$rel.Function.ForeignName}}.
func ({{$rel.Function.Receiver}} *{{$rel.LocalTable.NameGo}}) Remove{{$rel.Function.Name}}(exec boil.Executor, related ...*{{$rel.ForeignTable.NameGo}}) error {
var err error
{{if .ToJoinTable -}}
query := fmt.Sprintf(
`delete from "{{.JoinTable}}" where "{{.JoinLocalColumn}}" = $1 and "{{.JoinForeignColumn}}" in (%s)`,
strmangle.Placeholders(len(related), 1, 1),
)
values := []interface{}{{"{"}}{{$rel.Function.Receiver}}.{{$rel.LocalTable.ColumnNameGo}}}
if boil.DebugMode {
fmt.Fprintln(boil.DebugWriter, query)
fmt.Fprintln(boil.DebugWriter, values)
}
_, err = exec.Exec(query, values...)
if err != nil {
return errors.Wrap(err, "failed to remove relationships before set")
}
{{else -}}
for _, rel := range related {
rel.{{$rel.ForeignTable.ColumnNameGo}}.Valid = false
{{if not .ToJoinTable -}}
if rel.R != nil {
rel.R.{{$rel.Function.ForeignName}} = nil
}
{{end -}}
if err = rel.Update(exec, "{{.ForeignColumn}}"); err != nil {
return err
}
}
{{end -}}
{{if .ToJoinTable -}}
remove{{$rel.LocalTable.NameGo}}From{{$rel.ForeignTable.NameGo}}Slice({{$rel.Function.Receiver}}, related)
{{end -}}
if {{$rel.Function.Receiver}}.R == nil {
return nil
}
for _, rel := range related {
for i, ri := range {{$rel.Function.Receiver}}.R.{{$rel.Function.Name}} {
if rel != ri {
continue
}
ln := len({{$rel.Function.Receiver}}.R.{{$rel.Function.Name}})
if ln > 1 && i < ln-1 {
{{$rel.Function.Receiver}}.R.{{$rel.Function.Name}}[i] = {{$rel.Function.Receiver}}.R.{{$rel.Function.Name}}[ln-1]
}
{{$rel.Function.Receiver}}.R.{{$rel.Function.Name}} = {{$rel.Function.Receiver}}.R.{{$rel.Function.Name}}[:ln-1]
break
}
}
return nil
}
{{if .ToJoinTable -}}
func remove{{$rel.LocalTable.NameGo}}From{{$rel.ForeignTable.NameGo}}Slice({{$rel.Function.Receiver}} *{{$rel.LocalTable.NameGo}}, related []*{{$rel.ForeignTable.NameGo}}) {
for _, rel := range related {
if rel.R == nil {
continue
}
for i, ri := range rel.R.{{$rel.Function.ForeignName}} {
if {{$rel.Function.Receiver}}.{{$rel.Function.LocalAssignment}} != ri.{{$rel.Function.LocalAssignment}} {
continue
}
ln := len(rel.R.{{$rel.Function.ForeignName}})
if ln > 1 && i < ln-1 {
rel.R.{{$rel.Function.ForeignName}}[i] = rel.R.{{$rel.Function.ForeignName}}[ln-1]
}
rel.R.{{$rel.Function.ForeignName}} = rel.R.{{$rel.Function.ForeignName}}[:ln-1]
break
}
}
}
{{end -}}{{- /* if join table */ -}}
{{- end -}}{{- /* if nullable foreign key */ -}}
{{- end -}}{{- /* if unique foreign key */ -}}
{{- end -}}{{- /* range relationships */ -}}
{{- end -}}{{- /* outer if join table */ -}}

View file

@ -0,0 +1,103 @@
{{- define "relationship_to_one_setops_helper" -}}
{{- $varNameSingular := .ForeignKey.ForeignTable | singular | camelCase}}
// Set{{.Function.Name}} of the {{.ForeignKey.Table | singular}} to the related item.
// Sets {{.Function.Receiver}}.R.{{.Function.Name}} to related.
// Adds {{.Function.Receiver}} to related.R.{{.Function.ForeignName}}.
func ({{.Function.Receiver}} *{{.LocalTable.NameGo}}) Set{{.Function.Name}}(exec boil.Executor, insert bool, related *{{.ForeignTable.NameGo}}) error {
var err error
if insert {
if err = related.Insert(exec); err != nil {
return errors.Wrap(err, "failed to insert into foreign table")
}
}
oldVal := {{.Function.Receiver}}.{{.LocalTable.ColumnNameGo}}
{{.Function.Receiver}}.{{.Function.LocalAssignment}} = related.{{.Function.ForeignAssignment}}
if err = {{.Function.Receiver}}.Update(exec, "{{.ForeignKey.Column}}"); err != nil {
{{.Function.Receiver}}.{{.LocalTable.ColumnNameGo}} = oldVal
return errors.Wrap(err, "failed to update local table")
}
if {{.Function.Receiver}}.R == nil {
{{.Function.Receiver}}.R = &{{.LocalTable.NameGo}}R{
{{.Function.Name}}: related,
}
} else {
{{.Function.Receiver}}.R.{{.Function.Name}} = related
}
{{if (or .ForeignKey.Unique .Function.OneToOne) -}}
if related.R == nil {
related.R = &{{.ForeignTable.NameGo}}R{
{{.Function.ForeignName}}: {{.Function.Receiver}},
}
} else {
related.R.{{.Function.ForeignName}} = {{.Function.Receiver}}
}
{{else -}}
if related.R == nil {
related.R = &{{.ForeignTable.NameGo}}R{
{{.Function.ForeignName}}: {{.LocalTable.NameGo}}Slice{{"{"}}{{.Function.Receiver}}{{"}"}},
}
} else {
related.R.{{.Function.ForeignName}} = append(related.R.{{.Function.ForeignName}}, {{.Function.Receiver}})
}
{{end -}}
{{if .ForeignKey.Nullable}}
{{.Function.Receiver}}.{{.LocalTable.ColumnNameGo}}.Valid = true
{{end -}}
return nil
}
{{- if .ForeignKey.Nullable}}
// Remove{{.Function.Name}} relationship.
// Sets {{.Function.Receiver}}.R.{{.Function.Name}} to nil.
// Removes {{.Function.Receiver}} from all passed in related items' relationships struct (Optional).
func ({{.Function.Receiver}} *{{.LocalTable.NameGo}}) Remove{{.Function.Name}}(exec boil.Executor, related ...*{{.ForeignTable.NameGo}}) error {
var err error
{{.Function.Receiver}}.{{.LocalTable.ColumnNameGo}}.Valid = false
if err = {{.Function.Receiver}}.Update(exec, "{{.ForeignKey.Column}}"); err != nil {
{{.Function.Receiver}}.{{.LocalTable.ColumnNameGo}}.Valid = true
return errors.Wrap(err, "failed to update local table")
}
for _, rel := range related {
if rel.R == nil {
continue
}
{{if .ForeignKey.Unique -}}
rel.R.{{.Function.ForeignName}} = nil
{{else -}}
for i, ri := range rel.R.{{.Function.ForeignName}} {
if {{.Function.Receiver}}.{{.Function.LocalAssignment}} != ri.{{.Function.LocalAssignment}} {
continue
}
ln := len(rel.R.{{.Function.ForeignName}})
if ln > 1 && i < ln-1 {
rel.R.{{.Function.ForeignName}}[i], rel.R.{{.Function.ForeignName}}[ln-1] =
rel.R.{{.Function.ForeignName}}[ln-1], rel.R.{{.Function.ForeignName}}[i]
}
rel.R.{{.Function.ForeignName}} = rel.R.{{.Function.ForeignName}}[:ln-1]
break
}
{{end -}}
}
{{.Function.Receiver}}.R.{{.Function.Name}} = nil
return nil
}
{{end -}}
{{- end -}}
{{- if .Table.IsJoinTable -}}
{{- else -}}
{{- $dot := . -}}
{{- range .Table.FKeys -}}
{{- $rel := textsFromForeignKey $dot.PkgName $dot.Tables $dot.Table . -}}
{{- template "relationship_to_one_setops_helper" $rel -}}
{{- end -}}
{{- end -}}

View file

@ -41,11 +41,11 @@ func test{{$rel.LocalTable.NameGo}}ToMany{{$rel.Function.Name}}(t *testing.T) {
}
{{if .ToJoinTable -}}
_, err = tx.Exec(`insert into {{.JoinTable}} ({{.JoinLocalColumn}}, {{.JoinForeignColumn}}) values ($1, $2)`, a.{{.Column | titleCase}}, b.{{.ForeignColumn | titleCase}})
_, err = tx.Exec(`insert into "{{.JoinTable}}" ({{.JoinLocalColumn}}, {{.JoinForeignColumn}}) values ($1, $2)`, a.{{$rel.LocalTable.ColumnNameGo}}, b.{{$rel.ForeignTable.ColumnNameGo}})
if err != nil {
t.Fatal(err)
}
_, err = tx.Exec(`insert into {{.JoinTable}} ({{.JoinLocalColumn}}, {{.JoinForeignColumn}}) values ($1, $2)`, a.{{.Column | titleCase}}, c.{{.ForeignColumn | titleCase}})
_, err = tx.Exec(`insert into "{{.JoinTable}}" ({{.JoinLocalColumn}}, {{.JoinForeignColumn}}) values ($1, $2)`, a.{{$rel.LocalTable.ColumnNameGo}}, c.{{$rel.ForeignTable.ColumnNameGo}})
if err != nil {
t.Fatal(err)
}
@ -75,18 +75,18 @@ func test{{$rel.LocalTable.NameGo}}ToMany{{$rel.Function.Name}}(t *testing.T) {
}
slice := {{$rel.LocalTable.NameGo}}Slice{&a}
if err = a.Loaded.Load{{$rel.Function.Name}}(tx, false, &slice); err != nil {
if err = a.R.Load{{$rel.Function.Name}}(tx, false, &slice); err != nil {
t.Fatal(err)
}
if got := len(a.Loaded.{{$rel.Function.Name}}); got != 2 {
if got := len(a.R.{{$rel.Function.Name}}); got != 2 {
t.Error("number of eager loaded records wrong, got:", got)
}
a.Loaded.{{$rel.Function.Name}} = nil
if err = a.Loaded.Load{{$rel.Function.Name}}(tx, true, &a); err != nil {
a.R.{{$rel.Function.Name}} = nil
if err = a.R.Load{{$rel.Function.Name}}(tx, true, &a); err != nil {
t.Fatal(err)
}
if got := len(a.Loaded.{{$rel.Function.Name}}); got != 2 {
if got := len(a.R.{{$rel.Function.Name}}); got != 2 {
t.Error("number of eager loaded records wrong, got:", got)
}

View file

@ -0,0 +1,308 @@
{{- if .Table.IsJoinTable -}}
{{- else -}}
{{- $dot := . -}}
{{- $table := .Table -}}
{{- range .Table.ToManyRelationships -}}
{{- if (and .ForeignColumnUnique (not .ToJoinTable)) -}}
{{- template "relationship_to_one_setops_test_helper" (textsFromOneToOneRelationship $dot.PkgName $dot.Tables $table .) -}}
{{- else -}}
{{- $varNameSingular := .Table | singular | camelCase -}}
{{- $foreignVarNameSingular := .ForeignTable | singular | camelCase -}}
{{- $rel := textsFromRelationship $dot.Tables $table .}}
func test{{$rel.LocalTable.NameGo}}ToManyAddOp{{$rel.Function.Name}}(t *testing.T) {
var err error
tx := MustTx(boil.Begin())
defer tx.Rollback()
var a {{$rel.LocalTable.NameGo}}
var b, c, d, e {{$rel.ForeignTable.NameGo}}
seed := randomize.NewSeed()
if err = randomize.Struct(seed, &a, {{$varNameSingular}}DBTypes, false, {{$varNameSingular}}PrimaryKeyColumns...); err != nil {
t.Fatal(err)
}
foreigners := []*{{$rel.ForeignTable.NameGo}}{&b, &c, &d, &e}
for _, x := range foreigners {
if err = randomize.Struct(seed, x, {{$foreignVarNameSingular}}DBTypes, false, {{$foreignVarNameSingular}}PrimaryKeyColumns...); err != nil {
t.Fatal(err)
}
}
if err := a.Insert(tx); err != nil {
t.Fatal(err)
}
if err = b.Insert(tx); err != nil {
t.Fatal(err)
}
if err = c.Insert(tx); err != nil {
t.Fatal(err)
}
foreignersSplitByInsertion := [][]*{{$rel.ForeignTable.NameGo}}{
{&b, &c},
{&d, &e},
}
for i, x := range foreignersSplitByInsertion {
err = a.Add{{$rel.Function.Name}}(tx, i != 0, x...)
if err != nil {
t.Fatal(err)
}
first := x[0]
second := x[1]
{{- if .ToJoinTable}}
if first.R.{{$rel.Function.ForeignName}}[0] != &a {
t.Error("relationship was not added properly to the slice")
}
if second.R.{{$rel.Function.ForeignName}}[0] != &a {
t.Error("relationship was not added properly to the slice")
}
{{- else}}
if a.{{$rel.Function.LocalAssignment}} != first.{{$rel.Function.ForeignAssignment}} {
t.Error("foreign key was wrong value", a.{{$rel.Function.LocalAssignment}}, first.{{$rel.Function.ForeignAssignment}})
}
if a.{{$rel.Function.LocalAssignment}} != second.{{$rel.Function.ForeignAssignment}} {
t.Error("foreign key was wrong value", a.{{$rel.Function.LocalAssignment}}, second.{{$rel.Function.ForeignAssignment}})
}
if first.R.{{$rel.Function.ForeignName}} != &a {
t.Error("relationship was not added properly to the foreign slice")
}
if second.R.{{$rel.Function.ForeignName}} != &a {
t.Error("relationship was not added properly to the foreign slice")
}
{{- end}}
if a.R.{{$rel.Function.Name}}[i*2] != first {
t.Error("relationship struct slice not set to correct value")
}
if a.R.{{$rel.Function.Name}}[i*2+1] != second {
t.Error("relationship struct slice not set to correct value")
}
count, err := a.{{$rel.Function.Name}}(tx).Count()
if err != nil {
t.Fatal(err)
}
if want := int64((i+1)*2); count != want {
t.Error("want", want, "got", count)
}
}
}
{{if .ForeignColumnNullable}}
func test{{$rel.LocalTable.NameGo}}ToManySetOp{{$rel.Function.Name}}(t *testing.T) {
var err error
tx := MustTx(boil.Begin())
defer tx.Rollback()
var a {{$rel.LocalTable.NameGo}}
var b, c, d, e {{$rel.ForeignTable.NameGo}}
seed := randomize.NewSeed()
if err = randomize.Struct(seed, &a, {{$varNameSingular}}DBTypes, false, {{$varNameSingular}}PrimaryKeyColumns...); err != nil {
t.Fatal(err)
}
foreigners := []*{{$rel.ForeignTable.NameGo}}{&b, &c, &d, &e}
for _, x := range foreigners {
if err = randomize.Struct(seed, x, {{$foreignVarNameSingular}}DBTypes, false, {{$foreignVarNameSingular}}PrimaryKeyColumns...); err != nil {
t.Fatal(err)
}
}
if err = a.Insert(tx); err != nil {
t.Fatal(err)
}
if err = b.Insert(tx); err != nil {
t.Fatal(err)
}
if err = c.Insert(tx); err != nil {
t.Fatal(err)
}
err = a.Set{{$rel.Function.Name}}(tx, false, &b, &c)
if err != nil {
t.Fatal(err)
}
count, err := a.{{$rel.Function.Name}}(tx).Count()
if err != nil {
t.Fatal(err)
}
if count != 2 {
t.Error("count was wrong:", count)
}
err = a.Set{{$rel.Function.Name}}(tx, true, &d, &e)
if err != nil {
t.Fatal(err)
}
count, err = a.{{$rel.Function.Name}}(tx).Count()
if err != nil {
t.Fatal(err)
}
if count != 2 {
t.Error("count was wrong:", count)
}
{{- if .ToJoinTable}}
if len(b.R.{{$rel.Function.ForeignName}}) != 0 {
t.Error("relationship was not removed properly from the slice")
}
if len(c.R.{{$rel.Function.ForeignName}}) != 0 {
t.Error("relationship was not removed properly from the slice")
}
if d.R.{{$rel.Function.ForeignName}}[0] != &a {
t.Error("relationship was not added properly to the slice")
}
if e.R.{{$rel.Function.ForeignName}}[0] != &a {
t.Error("relationship was not added properly to the slice")
}
{{- else}}
if b.{{$rel.ForeignTable.ColumnNameGo}}.Valid {
t.Error("want b's foreign key value to be nil")
}
if c.{{$rel.ForeignTable.ColumnNameGo}}.Valid {
t.Error("want c's foreign key value to be nil")
}
if a.{{$rel.Function.LocalAssignment}} != d.{{$rel.Function.ForeignAssignment}} {
t.Error("foreign key was wrong value", a.{{$rel.Function.LocalAssignment}}, d.{{$rel.Function.ForeignAssignment}})
}
if a.{{$rel.Function.LocalAssignment}} != e.{{$rel.Function.ForeignAssignment}} {
t.Error("foreign key was wrong value", a.{{$rel.Function.LocalAssignment}}, e.{{$rel.Function.ForeignAssignment}})
}
if b.R.{{$rel.Function.ForeignName}} != nil {
t.Error("relationship was not removed properly from the foreign struct")
}
if c.R.{{$rel.Function.ForeignName}} != nil {
t.Error("relationship was not removed properly from the foreign struct")
}
if d.R.{{$rel.Function.ForeignName}} != &a {
t.Error("relationship was not added properly to the foreign struct")
}
if e.R.{{$rel.Function.ForeignName}} != &a {
t.Error("relationship was not added properly to the foreign struct")
}
{{- end}}
if a.R.{{$rel.Function.Name}}[0] != &d {
t.Error("relationship struct slice not set to correct value")
}
if a.R.{{$rel.Function.Name}}[1] != &e {
t.Error("relationship struct slice not set to correct value")
}
}
func test{{$rel.LocalTable.NameGo}}ToManyRemoveOp{{$rel.Function.Name}}(t *testing.T) {
var err error
tx := MustTx(boil.Begin())
defer tx.Rollback()
var a {{$rel.LocalTable.NameGo}}
var b, c, d, e {{$rel.ForeignTable.NameGo}}
seed := randomize.NewSeed()
if err = randomize.Struct(seed, &a, {{$varNameSingular}}DBTypes, false, {{$varNameSingular}}PrimaryKeyColumns...); err != nil {
t.Fatal(err)
}
foreigners := []*{{$rel.ForeignTable.NameGo}}{&b, &c, &d, &e}
for _, x := range foreigners {
if err = randomize.Struct(seed, x, {{$foreignVarNameSingular}}DBTypes, false, {{$foreignVarNameSingular}}PrimaryKeyColumns...); err != nil {
t.Fatal(err)
}
}
if err := a.Insert(tx); err != nil {
t.Fatal(err)
}
err = a.Add{{$rel.Function.Name}}(tx, true, foreigners...)
if err != nil {
t.Fatal(err)
}
count, err := a.{{$rel.Function.Name}}(tx).Count()
if err != nil {
t.Fatal(err)
}
if count != 4 {
t.Error("count was wrong:", count)
}
err = a.Remove{{$rel.Function.Name}}(tx, foreigners[:2]...)
if err != nil {
t.Fatal(err)
}
count, err = a.{{$rel.Function.Name}}(tx).Count()
if err != nil {
t.Fatal(err)
}
if count != 2 {
t.Error("count was wrong:", count)
}
{{- if .ToJoinTable}}
if len(b.R.{{$rel.Function.ForeignName}}) != 0 {
t.Error("relationship was not removed properly from the slice")
}
if len(c.R.{{$rel.Function.ForeignName}}) != 0 {
t.Error("relationship was not removed properly from the slice")
}
if d.R.{{$rel.Function.ForeignName}}[0] != &a {
t.Error("relationship was not added properly to the foreign struct")
}
if e.R.{{$rel.Function.ForeignName}}[0] != &a {
t.Error("relationship was not added properly to the foreign struct")
}
{{- else}}
if b.{{$rel.ForeignTable.ColumnNameGo}}.Valid {
t.Error("want b's foreign key value to be nil")
}
if c.{{$rel.ForeignTable.ColumnNameGo}}.Valid {
t.Error("want c's foreign key value to be nil")
}
if b.R.{{$rel.Function.ForeignName}} != nil {
t.Error("relationship was not removed properly from the foreign struct")
}
if c.R.{{$rel.Function.ForeignName}} != nil {
t.Error("relationship was not removed properly from the foreign struct")
}
if d.R.{{$rel.Function.ForeignName}} != &a {
t.Error("relationship to a should have been preserved")
}
if e.R.{{$rel.Function.ForeignName}} != &a {
t.Error("relationship to a should have been preserved")
}
{{- end}}
if len(a.R.{{$rel.Function.Name}}) != 2 {
t.Error("should have preserved two relationships")
}
// Removal doesn't do a stable deletion for performance so we have to flip the order
if a.R.{{$rel.Function.Name}}[1] != &d {
t.Error("relationship to d should have been preserved")
}
if a.R.{{$rel.Function.Name}}[0] != &e {
t.Error("relationship to e should have been preserved")
}
}
{{end -}}
{{- end -}}{{- /* if unique foreign key */ -}}
{{- end -}}{{- /* range relationships */ -}}
{{- end -}}{{- /* outer if join table */ -}}

View file

@ -12,7 +12,7 @@ func test{{.LocalTable.NameGo}}ToOne{{.ForeignTable.NameGo}}_{{.Function.Name}}(
foreign.{{.ForeignKey.ForeignColumn | titleCase}}.Valid = true
{{end}}
{{if not .Function.ReverseInserts -}}
{{if not .Function.OneToOne -}}
if err := foreign.Insert(tx); err != nil {
t.Fatal(err)
}
@ -42,18 +42,18 @@ func test{{.LocalTable.NameGo}}ToOne{{.ForeignTable.NameGo}}_{{.Function.Name}}(
}
slice := {{.LocalTable.NameGo}}Slice{&local}
if err = local.Loaded.Load{{.Function.Name}}(tx, false, &slice); err != nil {
if err = local.R.Load{{.Function.Name}}(tx, false, &slice); err != nil {
t.Fatal(err)
}
if local.Loaded.{{.Function.Name}} == nil {
if local.R.{{.Function.Name}} == nil {
t.Error("struct should have been eager loaded")
}
local.Loaded.{{.Function.Name}} = nil
if err = local.Loaded.Load{{.Function.Name}}(tx, true, &local); err != nil {
local.R.{{.Function.Name}} = nil
if err = local.R.Load{{.Function.Name}}(tx, true, &local); err != nil {
t.Fatal(err)
}
if local.Loaded.{{.Function.Name}} == nil {
if local.R.{{.Function.Name}} == nil {
t.Error("struct should have been eager loaded")
}
}

View file

@ -0,0 +1,133 @@
{{- define "relationship_to_one_setops_test_helper" -}}
{{- $varNameSingular := .ForeignKey.Table | singular | camelCase -}}
{{- $foreignVarNameSingular := .ForeignKey.ForeignTable | singular | camelCase -}}
func test{{.LocalTable.NameGo}}ToOneSetOp{{.ForeignTable.NameGo}}_{{.Function.Name}}(t *testing.T) {
var err error
tx := MustTx(boil.Begin())
defer tx.Rollback()
var a {{.LocalTable.NameGo}}
var b, c {{.ForeignTable.NameGo}}
seed := randomize.NewSeed()
if err = randomize.Struct(seed, &a, {{$varNameSingular}}DBTypes, false, {{$varNameSingular}}PrimaryKeyColumns...); err != nil {
t.Fatal(err)
}
if err = randomize.Struct(seed, &b, {{$foreignVarNameSingular}}DBTypes, false, {{$foreignVarNameSingular}}PrimaryKeyColumns...); err != nil {
t.Fatal(err)
}
if err = randomize.Struct(seed, &c, {{$foreignVarNameSingular}}DBTypes, false, {{$foreignVarNameSingular}}PrimaryKeyColumns...); err != nil {
t.Fatal(err)
}
if err := a.Insert(tx); err != nil {
t.Fatal(err)
}
if err = b.Insert(tx); err != nil {
t.Fatal(err)
}
for i, x := range []*{{.ForeignTable.NameGo}}{&b, &c} {
err = a.Set{{.Function.Name}}(tx, i != 0, x)
if err != nil {
t.Fatal(err)
}
if a.{{.Function.LocalAssignment}} != x.{{.Function.ForeignAssignment}} {
t.Error("foreign key was wrong value", a.{{.Function.LocalAssignment}})
}
if a.R.{{.Function.Name}} != x {
t.Error("relationship struct not set to correct value")
}
zero := reflect.Zero(reflect.TypeOf(a.{{.Function.LocalAssignment}}))
reflect.Indirect(reflect.ValueOf(&a.{{.Function.LocalAssignment}})).Set(zero)
if err = a.Reload(tx); err != nil {
t.Fatal("failed to reload", err)
}
if a.{{.Function.LocalAssignment}} != x.{{.Function.ForeignAssignment}} {
t.Error("foreign key was wrong value", a.{{.Function.LocalAssignment}}, x.{{.Function.ForeignAssignment}})
}
{{if .ForeignKey.Unique -}}
if x.R.{{.Function.ForeignName}} != &a {
t.Error("failed to append to foreign relationship struct")
}
{{else -}}
if x.R.{{.Function.ForeignName}}[0] != &a {
t.Error("failed to append to foreign relationship struct")
}
{{end -}}
}
}
{{- if .ForeignKey.Nullable}}
func test{{.LocalTable.NameGo}}ToOneRemoveOp{{.ForeignTable.NameGo}}_{{.Function.Name}}(t *testing.T) {
var err error
tx := MustTx(boil.Begin())
defer tx.Rollback()
var a {{.LocalTable.NameGo}}
var b {{.ForeignTable.NameGo}}
seed := randomize.NewSeed()
if err = randomize.Struct(seed, &a, {{$varNameSingular}}DBTypes, false, {{$varNameSingular}}PrimaryKeyColumns...); err != nil {
t.Fatal(err)
}
if err = randomize.Struct(seed, &b, {{$foreignVarNameSingular}}DBTypes, false, {{$foreignVarNameSingular}}PrimaryKeyColumns...); err != nil {
t.Fatal(err)
}
if err = a.Insert(tx); err != nil {
t.Fatal(err)
}
if err = a.Set{{.Function.Name}}(tx, true, &b); err != nil {
t.Fatal(err)
}
if err = a.Remove{{.Function.Name}}(tx, &b); err != nil {
t.Error("failed to remove relationship")
}
count, err := a.{{.Function.Name}}(tx).Count()
if err != nil {
t.Error(err)
}
if count != 0 {
t.Error("want no relationships remaining")
}
if a.R.{{.Function.Name}} != nil {
t.Error("R struct entry should be nil")
}
if a.{{.LocalTable.ColumnNameGo}}.Valid {
t.Error("R struct entry should be nil")
}
{{if .ForeignKey.Unique -}}
if b.R.{{.Function.ForeignName}} != nil {
t.Error("failed to remove a from b's relationships")
}
{{else -}}
if len(b.R.{{.Function.ForeignName}}) != 0 {
t.Error("failed to remove a from b's relationships")
}
{{end -}}
}
{{end -}}
{{- end -}}
{{- if .Table.IsJoinTable -}}
{{- else -}}
{{- $dot := . -}}
{{- range .Table.FKeys -}}
{{- $rel := textsFromForeignKey $dot.PkgName $dot.Tables $dot.Table .}}
{{template "relationship_to_one_setops_test_helper" $rel -}}
{{- end -}}
{{- end -}}

View file

@ -138,8 +138,23 @@ func TestInsert(t *testing.T) {
{{- end -}}
}
// TestToOne tests cannot be run in parallel
// or deadlocks can 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 */ -}}
}
// TestToMany tests cannot be run in parallel
// or postgres deadlocks will occur.
// or deadlocks can occur.
func TestToMany(t *testing.T) {
{{- $dot := .}}
{{- range $index, $table := .Tables}}
@ -159,21 +174,105 @@ func TestToMany(t *testing.T) {
{{- end -}}{{- /* outer tables range */ -}}
}
// TestToOne tests cannot be run in parallel
// or postgres deadlocks will occur.
func TestToOne(t *testing.T) {
// TestToOneSet tests cannot be run in parallel
// or deadlocks can occur.
func TestToOneSet(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}})
t.Run("{{$rel.LocalTable.NameGo}}To{{$rel.ForeignTable.NameGo}}_{{$rel.Function.Name}}", test{{$rel.LocalTable.NameGo}}ToOneSetOp{{$rel.ForeignTable.NameGo}}_{{$rel.Function.Name}})
{{end -}}{{- /* fkey range */ -}}
{{- end -}}{{- /* if join table */ -}}
{{- end -}}{{- /* tables range */ -}}
}
// TestToOneRemove tests cannot be run in parallel
// or deadlocks can occur.
func TestToOneRemove(t *testing.T) {
{{- $dot := . -}}
{{- range $index, $table := .Tables}}
{{- if $table.IsJoinTable -}}
{{- else -}}
{{- range $table.FKeys -}}
{{- $rel := textsFromForeignKey $dot.PkgName $dot.Tables $table . -}}
{{- if $rel.ForeignKey.Nullable -}}
t.Run("{{$rel.LocalTable.NameGo}}To{{$rel.ForeignTable.NameGo}}_{{$rel.Function.Name}}", test{{$rel.LocalTable.NameGo}}ToOneRemoveOp{{$rel.ForeignTable.NameGo}}_{{$rel.Function.Name}})
{{end -}}{{- /* if foreign key nullable */ -}}
{{- end -}}{{- /* fkey range */ -}}
{{- end -}}{{- /* if join table */ -}}
{{- end -}}{{- /* tables range */ -}}
}
// TestToManyAdd tests cannot be run in parallel
// or deadlocks can occur.
func TestToManyAdd(t *testing.T) {
{{- $dot := .}}
{{- range $index, $table := .Tables}}
{{- $tableName := $table.Name | plural | titleCase -}}
{{- if $table.IsJoinTable -}}
{{- else -}}
{{- range $table.ToManyRelationships -}}
{{- $rel := textsFromRelationship $dot.Tables $table . -}}
{{- if (and .ForeignColumnUnique (not .ToJoinTable)) -}}
{{- else -}}
t.Run("{{$rel.LocalTable.NameGo}}ToMany{{$rel.Function.Name}}", test{{$rel.LocalTable.NameGo}}ToManyAddOp{{$rel.Function.Name}})
{{end -}}{{- /* if unique */ -}}
{{- end -}}{{- /* range */ -}}
{{- end -}}{{- /* outer if join table */ -}}
{{- end -}}{{- /* outer tables range */ -}}
}
// TestToManySet tests cannot be run in parallel
// or deadlocks can occur.
func TestToManySet(t *testing.T) {
{{- $dot := .}}
{{- range $index, $table := .Tables}}
{{- $tableName := $table.Name | plural | titleCase -}}
{{- if $table.IsJoinTable -}}
{{- else -}}
{{- range $table.ToManyRelationships -}}
{{- if not .ForeignColumnNullable -}}
{{- else -}}
{{- $rel := textsFromRelationship $dot.Tables $table . -}}
{{- if (and .ForeignColumnUnique (not .ToJoinTable)) -}}
{{- $oneToOne := textsFromOneToOneRelationship $dot.PkgName $dot.Tables $table . -}}
t.Run("{{$oneToOne.LocalTable.NameGo}}OneToOne{{$oneToOne.ForeignTable.NameGo}}_{{$oneToOne.Function.Name}}", test{{$oneToOne.LocalTable.NameGo}}ToOneSetOp{{$oneToOne.ForeignTable.NameGo}}_{{$oneToOne.Function.Name}})
{{else -}}
t.Run("{{$rel.LocalTable.NameGo}}ToMany{{$rel.Function.Name}}", test{{$rel.LocalTable.NameGo}}ToManySetOp{{$rel.Function.Name}})
{{end -}}{{- /* if unique */ -}}
{{- end -}}{{- /* if foreign column nullable */ -}}
{{- end -}}{{- /* range */ -}}
{{- end -}}{{- /* outer if join table */ -}}
{{- end -}}{{- /* outer tables range */ -}}
}
// TestToManyRemove tests cannot be run in parallel
// or deadlocks can occur.
func TestToManyRemove(t *testing.T) {
{{- $dot := .}}
{{- range $index, $table := .Tables}}
{{- $tableName := $table.Name | plural | titleCase -}}
{{- if $table.IsJoinTable -}}
{{- else -}}
{{- range $table.ToManyRelationships -}}
{{- if not .ForeignColumnNullable -}}
{{- else -}}
{{- $rel := textsFromRelationship $dot.Tables $table . -}}
{{- if (and .ForeignColumnUnique (not .ToJoinTable)) -}}
{{- $oneToOne := textsFromOneToOneRelationship $dot.PkgName $dot.Tables $table . -}}
t.Run("{{$oneToOne.LocalTable.NameGo}}OneToOne{{$oneToOne.ForeignTable.NameGo}}_{{$oneToOne.Function.Name}}", test{{$oneToOne.LocalTable.NameGo}}ToOneRemoveOp{{$oneToOne.ForeignTable.NameGo}}_{{$oneToOne.Function.Name}})
{{else -}}
t.Run("{{$rel.LocalTable.NameGo}}ToMany{{$rel.Function.Name}}", test{{$rel.LocalTable.NameGo}}ToManyRemoveOp{{$rel.Function.Name}})
{{end -}}{{- /* if unique */ -}}
{{- end -}}{{- /* if foreign column nullable */ -}}
{{- end -}}{{- /* range */ -}}
{{- end -}}{{- /* outer if join table */ -}}
{{- end -}}{{- /* outer tables range */ -}}
}
func TestReload(t *testing.T) {
{{- range $index, $table := .Tables}}
{{- if $table.IsJoinTable -}}

View file

@ -28,10 +28,11 @@ type RelationshipToOneTexts struct {
Function struct {
PackageName string
Name string
ForeignName string
Varname string
Receiver string
ReverseInserts bool
Varname string
Receiver string
OneToOne bool
LocalAssignment string
ForeignAssignment string
@ -54,6 +55,11 @@ func textsFromForeignKey(packageName string, tables []bdb.Table, table bdb.Table
r.Function.PackageName = packageName
r.Function.Name = strmangle.TitleCase(strmangle.Singular(strings.TrimSuffix(fkey.Column, "_id")))
plurality := strmangle.Plural
if fkey.Unique {
plurality = strmangle.Singular
}
r.Function.ForeignName = mkFunctionName(strmangle.Singular(fkey.ForeignTable), strmangle.TitleCase(plurality(fkey.Table)), fkey.Column, false)
r.Function.Varname = strmangle.CamelCase(strmangle.Singular(fkey.ForeignTable))
r.Function.Receiver = strings.ToLower(table.Name[:1])
@ -91,7 +97,8 @@ func textsFromOneToOneRelationship(packageName string, tables []bdb.Table, table
rel := textsFromForeignKey(packageName, tables, table, fkey)
rel.Function.Name = strmangle.TitleCase(strmangle.Singular(toMany.ForeignTable))
rel.Function.ReverseInserts = true
rel.Function.ForeignName = mkFunctionName(strmangle.Singular(toMany.Table), strmangle.TitleCase(strmangle.Singular(toMany.Table)), toMany.ForeignColumn, false)
rel.Function.OneToOne = true
return rel
}
@ -100,6 +107,7 @@ type RelationshipToManyTexts struct {
LocalTable struct {
NameGo string
NameSingular string
ColumnNameGo string
}
ForeignTable struct {
@ -107,12 +115,14 @@ type RelationshipToManyTexts struct {
NameSingular string
NamePluralGo string
NameHumanReadable string
ColumnNameGo string
Slice string
}
Function struct {
Name string
Receiver string
Name string
ForeignName string
Receiver string
LocalAssignment string
ForeignAssignment string
@ -125,23 +135,24 @@ func textsFromRelationship(tables []bdb.Table, table bdb.Table, rel bdb.ToManyRe
r := RelationshipToManyTexts{}
r.LocalTable.NameSingular = strmangle.Singular(table.Name)
r.LocalTable.NameGo = strmangle.TitleCase(r.LocalTable.NameSingular)
r.LocalTable.ColumnNameGo = strmangle.TitleCase(rel.Column)
r.ForeignTable.NameSingular = strmangle.Singular(rel.ForeignTable)
r.ForeignTable.NamePluralGo = strmangle.TitleCase(strmangle.Plural(rel.ForeignTable))
r.ForeignTable.NameGo = strmangle.TitleCase(r.ForeignTable.NameSingular)
r.ForeignTable.ColumnNameGo = strmangle.TitleCase(rel.ForeignColumn)
r.ForeignTable.Slice = fmt.Sprintf("%sSlice", strmangle.TitleCase(r.ForeignTable.NameSingular))
r.ForeignTable.NameHumanReadable = strings.Replace(rel.ForeignTable, "_", " ", -1)
r.Function.Receiver = strings.ToLower(table.Name[:1])
// Check to see if the foreign key name is the same as the local table name.
// Simple case: yes - we can name the function the same as the plural table name
// Not simple case: We have to name the function based off the foreign key and
if colName := strings.TrimSuffix(rel.ForeignColumn, "_id"); rel.ToJoinTable || r.LocalTable.NameSingular == colName {
r.Function.Name = r.ForeignTable.NamePluralGo
} else {
r.Function.Name = strmangle.TitleCase(colName) + r.ForeignTable.NamePluralGo
r.Function.Name = mkFunctionName(r.LocalTable.NameSingular, r.ForeignTable.NamePluralGo, rel.ForeignColumn, rel.ToJoinTable)
plurality := strmangle.Singular
foreignNamingColumn := rel.ForeignColumn
if rel.ToJoinTable {
plurality = strmangle.Plural
foreignNamingColumn = rel.JoinLocalColumn
}
r.Function.ForeignName = strmangle.TitleCase(plurality(strings.TrimSuffix(foreignNamingColumn, "_id")))
if rel.Nullable {
col := table.GetColumn(rel.Column)
@ -160,3 +171,15 @@ func textsFromRelationship(tables []bdb.Table, table bdb.Table, rel bdb.ToManyRe
return r
}
// mkFunctionName checks to see if the foreign key name is the same as the local table name (minus _id suffix)
// Simple case: yes - we can name the function the same as the plural table name
// Not simple case: We have to name the function based off the foreign key and the foreign table name
func mkFunctionName(fkeyTableSingular, foreignTablePluralGo, fkeyColumn string, toJoinTable bool) string {
colName := strings.TrimSuffix(fkeyColumn, "_id")
if toJoinTable || fkeyTableSingular == colName {
return foreignTablePluralGo
}
return strmangle.TitleCase(colName) + foreignTablePluralGo
}

View file

@ -34,9 +34,10 @@ func TestTextsFromForeignKey(t *testing.T) {
expect.Function.PackageName = "models"
expect.Function.Name = "Pilot"
expect.Function.ForeignName = "Jet"
expect.Function.Varname = "pilot"
expect.Function.Receiver = "j"
expect.Function.ReverseInserts = false
expect.Function.OneToOne = false
expect.Function.LocalAssignment = "PilotID.Int"
expect.Function.ForeignAssignment = "ID"
@ -44,6 +45,37 @@ func TestTextsFromForeignKey(t *testing.T) {
if !reflect.DeepEqual(expect, texts) {
t.Errorf("Want:\n%s\nGot:\n%s\n", spew.Sdump(expect), spew.Sdump(texts))
}
texts = textsFromForeignKey("models", tables, jets, jets.FKeys[1])
expect = RelationshipToOneTexts{}
expect.ForeignKey = jets.FKeys[1]
expect.LocalTable.NameGo = "Jet"
expect.LocalTable.ColumnNameGo = "AirportID"
expect.ForeignTable.Name = "airports"
expect.ForeignTable.NameGo = "Airport"
expect.ForeignTable.NamePluralGo = "Airports"
expect.ForeignTable.ColumnName = "id"
expect.ForeignTable.ColumnNameGo = "ID"
expect.Function.PackageName = "models"
expect.Function.Name = "Airport"
expect.Function.ForeignName = "Jets"
expect.Function.Varname = "airport"
expect.Function.Receiver = "j"
expect.Function.OneToOne = false
expect.Function.LocalAssignment = "AirportID"
expect.Function.ForeignAssignment = "ID"
if !reflect.DeepEqual(expect, texts) {
t.Errorf("Want:\n%s\nGot:\n%s\n", spew.Sdump(expect), spew.Sdump(texts))
}
if !reflect.DeepEqual(expect, texts) {
t.Errorf("Want:\n%s\nGot:\n%s\n", spew.Sdump(expect), spew.Sdump(texts))
}
}
func TestTextsFromOneToOneRelationship(t *testing.T) {
@ -59,7 +91,7 @@ func TestTextsFromOneToOneRelationship(t *testing.T) {
expect := RelationshipToOneTexts{}
expect.ForeignKey = bdb.ForeignKey{
Table: "jets",
Table: "pilots",
Name: "none",
Column: "id",
Nullable: false,
@ -82,9 +114,10 @@ func TestTextsFromOneToOneRelationship(t *testing.T) {
expect.Function.PackageName = "models"
expect.Function.Name = "Jet"
expect.Function.ForeignName = "Pilot"
expect.Function.Varname = "jet"
expect.Function.Receiver = "p"
expect.Function.ReverseInserts = true
expect.Function.OneToOne = true
expect.Function.LocalAssignment = "ID"
expect.Function.ForeignAssignment = "PilotID.Int"
@ -107,14 +140,17 @@ func TestTextsFromRelationship(t *testing.T) {
expect := RelationshipToManyTexts{}
expect.LocalTable.NameGo = "Pilot"
expect.LocalTable.NameSingular = "pilot"
expect.LocalTable.ColumnNameGo = "ID"
expect.ForeignTable.NameGo = "Jet"
expect.ForeignTable.NameSingular = "jet"
expect.ForeignTable.NamePluralGo = "Jets"
expect.ForeignTable.NameHumanReadable = "jets"
expect.ForeignTable.ColumnNameGo = "PilotID"
expect.ForeignTable.Slice = "JetSlice"
expect.Function.Name = "Jets"
expect.Function.ForeignName = "Pilot"
expect.Function.Receiver = "p"
expect.Function.LocalAssignment = "ID"
expect.Function.ForeignAssignment = "PilotID.Int"
@ -127,14 +163,17 @@ func TestTextsFromRelationship(t *testing.T) {
expect = RelationshipToManyTexts{}
expect.LocalTable.NameGo = "Pilot"
expect.LocalTable.NameSingular = "pilot"
expect.LocalTable.ColumnNameGo = "ID"
expect.ForeignTable.NameGo = "License"
expect.ForeignTable.NameSingular = "license"
expect.ForeignTable.NamePluralGo = "Licenses"
expect.ForeignTable.NameHumanReadable = "licenses"
expect.ForeignTable.ColumnNameGo = "PilotID"
expect.ForeignTable.Slice = "LicenseSlice"
expect.Function.Name = "Licenses"
expect.Function.ForeignName = "Pilot"
expect.Function.Receiver = "p"
expect.Function.LocalAssignment = "ID"
expect.Function.ForeignAssignment = "PilotID"
@ -147,14 +186,17 @@ func TestTextsFromRelationship(t *testing.T) {
expect = RelationshipToManyTexts{}
expect.LocalTable.NameGo = "Pilot"
expect.LocalTable.NameSingular = "pilot"
expect.LocalTable.ColumnNameGo = "ID"
expect.ForeignTable.NameGo = "Language"
expect.ForeignTable.NameSingular = "language"
expect.ForeignTable.NamePluralGo = "Languages"
expect.ForeignTable.NameHumanReadable = "languages"
expect.ForeignTable.ColumnNameGo = "ID"
expect.ForeignTable.Slice = "LanguageSlice"
expect.Function.Name = "Languages"
expect.Function.ForeignName = "Pilots"
expect.Function.Receiver = "p"
expect.Function.LocalAssignment = "ID"
expect.Function.ForeignAssignment = "ID"