diff --git a/templates/relationship_one_to_one.tpl b/templates/relationship_one_to_one.tpl new file mode 100644 index 0000000..b956626 --- /dev/null +++ b/templates/relationship_one_to_one.tpl @@ -0,0 +1,34 @@ +{{- define "relationship_to_one_helper" -}} + {{- $dot := .Dot -}}{{/* .Dot holds the root templateData struct, passed in through preserveDot */}} + {{- with .Rel -}}{{/* Rel holds the text helper data, passed in through preserveDot */}} + {{- $varNameSingular := .ForeignKey.ForeignTable | singular | camelCase -}} +// {{.Function.Name}}G pointed to by the foreign key. +func ({{.Function.Receiver}} *{{.LocalTable.NameGo}}) {{.Function.Name}}G(mods ...qm.QueryMod) {{$varNameSingular}}Query { + return {{.Function.Receiver}}.{{.Function.Name}}(boil.GetDB(), mods...) +} + +// {{.Function.Name}} pointed to by the foreign key. +func ({{.Function.Receiver}} *{{.LocalTable.NameGo}}) {{.Function.Name}}(exec boil.Executor, mods ...qm.QueryMod) ({{$varNameSingular}}Query) { + queryMods := []qm.QueryMod{ + qm.Where("{{.ForeignTable.ColumnName}}={{if $dot.Dialect.IndexPlaceholders}}$1{{else}}?{{end}}", {{.Function.Receiver}}.{{.LocalTable.ColumnNameGo}}), + } + + queryMods = append(queryMods, mods...) + + query := {{.ForeignTable.NamePluralGo}}(exec, queryMods...) + queries.SetFrom(query.Query, "{{.ForeignTable.Name | $dot.SchemaTable}}") + + return query +} + {{- end -}}{{/* end with */}} +{{end -}}{{/* end define */}} + +{{- /* Begin execution of template for one-to-one relationship */ -}} +{{- if .Table.IsJoinTable -}} +{{- else -}} + {{- $dot := . -}} + {{- range .Table.FKeys -}} + {{- $txt := textsFromForeignKey $dot.PkgName $dot.Tables $dot.Table . -}} +{{- template "relationship_to_one_helper" (preserveDot $dot $txt) -}} +{{- end -}} +{{- end -}} diff --git a/templates/relationship_one_to_one_eager.tpl b/templates/relationship_one_to_one_eager.tpl new file mode 100644 index 0000000..99fb0cd --- /dev/null +++ b/templates/relationship_one_to_one_eager.tpl @@ -0,0 +1,97 @@ +{{- define "relationship_to_one_eager_helper" -}} + {{- $dot := .Dot -}}{{/* .Dot holds the root templateData struct, passed in through preserveDot */}} + {{- $varNameSingular := $dot.Table.Name | singular | camelCase -}} + {{- with .Rel -}} + {{- $arg := printf "maybe%s" .LocalTable.NameGo -}} + {{- $slice := printf "%sSlice" .LocalTable.NameGo}} +// Load{{.Function.Name}} allows an eager lookup of values, cached into the +// loaded structs of the objects. +func ({{$varNameSingular}}L) 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 | $dot.SchemaTable}} where {{.ForeignKey.ForeignColumn | $dot.Quotes}} in (%s)", + strmangle.Placeholders(dialect.IndexPlaceholders, 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 = queries.Bind(results, &resultSlice); err != nil { + return errors.Wrap(err, "failed to bind eager loaded slice {{.ForeignTable.NameGo}}") + } + + {{if not $dot.NoHooks -}} + if len({{.ForeignTable.Name | singular | camelCase}}AfterSelectHooks) != 0 { + for _, obj := range resultSlice { + if err := obj.doAfterSelectHooks(e); err != nil { + return err + } + } + } + {{- end}} + + if singular && len(resultSlice) != 0 { + if object.R == nil { + object.R = &{{$varNameSingular}}R{} + } + object.R.{{.Function.Name}} = resultSlice[0] + return nil + } + + for _, foreign := range resultSlice { + for _, local := range slice { + {{if .Function.UsesBytes -}} + if 0 == bytes.Compare(local.{{.Function.LocalAssignment}}, foreign.{{.Function.ForeignAssignment}}) { + {{else -}} + if local.{{.Function.LocalAssignment}} == foreign.{{.Function.ForeignAssignment}} { + {{end -}} + if local.R == nil { + local.R = &{{$varNameSingular}}R{} + } + local.R.{{.Function.Name}} = foreign + break + } + } + } + + return nil +} + {{- end -}}{{- /* end with */ -}} +{{end -}}{{- /* end define */ -}} + +{{- /* Begin execution of template for one-to-one eager load */ -}} +{{- if .Table.IsJoinTable -}} +{{- else -}} + {{- $dot := . -}} + {{- range .Table.FKeys -}} + {{- $txt := textsFromForeignKey $dot.PkgName $dot.Tables $dot.Table . -}} + {{- template "relationship_to_one_eager_helper" (preserveDot $dot $txt) -}} + {{- end -}} +{{end}} diff --git a/templates/relationship_one_to_one_setops.tpl b/templates/relationship_one_to_one_setops.tpl new file mode 100644 index 0000000..e03803d --- /dev/null +++ b/templates/relationship_one_to_one_setops.tpl @@ -0,0 +1,130 @@ +{{- define "relationship_to_one_setops_helper" -}} + {{- $dot := .Dot -}}{{/* .Dot holds the root templateData struct, passed in through preserveDot */}} + {{- with .Rel -}} + {{- $varNameSingular := .ForeignKey.ForeignTable | singular | camelCase -}} + {{- $localNameSingular := .ForeignKey.Table | 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") + } + } + + {{if .Function.OneToOne -}} + oldVal := related.{{.Function.ForeignAssignment}} + related.{{.Function.ForeignAssignment}} = {{.Function.Receiver}}.{{.Function.LocalAssignment}} + {{if .ForeignKey.ForeignColumnNullable -}} + related.{{.ForeignTable.ColumnNameGo}}.Valid = true + {{end -}} + if err = related.Update(exec, "{{.ForeignKey.ForeignColumn}}"); err != nil { + related.{{.Function.ForeignAssignment}} = oldVal + return errors.Wrap(err, "failed to update local table") + } + {{else -}} + oldVal := {{.Function.Receiver}}.{{.Function.LocalAssignment}} + related.{{.Function.ForeignAssignment}} = {{.Function.Receiver}}.{{.Function.LocalAssignment}} + {{.Function.Receiver}}.{{.Function.LocalAssignment}} = related.{{.Function.ForeignAssignment}} + if err = {{.Function.Receiver}}.Update(exec, "{{.ForeignKey.Column}}"); err != nil { + {{.Function.Receiver}}.{{.Function.LocalAssignment}} = oldVal + return errors.Wrap(err, "failed to update local table") + } + {{end -}} + + if {{.Function.Receiver}}.R == nil { + {{.Function.Receiver}}.R = &{{$localNameSingular}}R{ + {{.Function.Name}}: related, + } + } else { + {{.Function.Receiver}}.R.{{.Function.Name}} = related + } + + {{if (or .ForeignKey.Unique .Function.OneToOne) -}} + if related.R == nil { + related.R = &{{$varNameSingular}}R{ + {{.Function.ForeignName}}: {{.Function.Receiver}}, + } + } else { + related.R.{{.Function.ForeignName}} = {{.Function.Receiver}} + } + {{else -}} + if related.R == nil { + related.R = &{{$varNameSingular}}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 or (.ForeignKey.Nullable) (and .Function.OneToOne .ForeignKey.ForeignColumnNullable)}} +// 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 + + {{if .Function.OneToOne -}} + related.{{.ForeignTable.ColumnNameGo}}.Valid = false + if err = related.Update(exec, "{{.ForeignKey.ForeignColumn}}"); err != nil { + related.{{.ForeignTable.ColumnNameGo}}.Valid = true + return errors.Wrap(err, "failed to update local table") + } + {{else -}} + {{.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") + } + {{end -}} + + {{.Function.Receiver}}.R.{{.Function.Name}} = nil + if related == nil || related.R == nil { + return nil + } + + {{if .ForeignKey.Unique -}} + related.R.{{.Function.ForeignName}} = nil + {{else -}} + for i, ri := range related.R.{{.Function.ForeignName}} { + {{if .Function.UsesBytes -}} + if 0 != bytes.Compare({{.Function.Receiver}}.{{.Function.LocalAssignment}}, ri.{{.Function.LocalAssignment}}) { + {{else -}} + if {{.Function.Receiver}}.{{.Function.LocalAssignment}} != ri.{{.Function.LocalAssignment}} { + {{end -}} + continue + } + + ln := len(related.R.{{.Function.ForeignName}}) + if ln > 1 && i < ln-1 { + related.R.{{.Function.ForeignName}}[i] = related.R.{{.Function.ForeignName}}[ln-1] + } + related.R.{{.Function.ForeignName}} = related.R.{{.Function.ForeignName}}[:ln-1] + break + } + {{end -}} + + return nil +} +{{end -}}{{/* if foreignkey nullable */}} +{{- end -}}{{/* end with */}} +{{- end -}}{{/* end define */}} + +{{- /* Begin execution of template for one-to-one setops */ -}} +{{- if .Table.IsJoinTable -}} +{{- else -}} + {{- $dot := . -}} + {{- range .Table.FKeys -}} + {{- $txt := textsFromForeignKey $dot.PkgName $dot.Tables $dot.Table . -}} + {{- template "relationship_to_one_setops_helper" (preserveDot $dot $txt) -}} + {{- end -}} +{{- end -}} diff --git a/templates_test/relationship_one_to_one.tpl b/templates_test/relationship_one_to_one.tpl new file mode 100644 index 0000000..fbced51 --- /dev/null +++ b/templates_test/relationship_one_to_one.tpl @@ -0,0 +1,76 @@ +{{- define "relationship_to_one_test_helper"}} +{{- $dot := .Dot -}} +{{- with .Rel}} +func test{{.LocalTable.NameGo}}ToOne{{.ForeignTable.NameGo}}_{{.Function.Name}}(t *testing.T) { + tx := MustTx(boil.Begin()) + defer tx.Rollback() + + var foreign {{.ForeignTable.NameGo}} + var local {{.LocalTable.NameGo}} + {{if .ForeignKey.Nullable -}} + local.{{.ForeignKey.Column | titleCase}}.Valid = true + {{end}} + {{- if .ForeignKey.ForeignColumnNullable -}} + foreign.{{.ForeignKey.ForeignColumn | titleCase}}.Valid = true + {{end}} + + {{if not .Function.OneToOne -}} + if err := foreign.Insert(tx); err != nil { + t.Fatal(err) + } + + local.{{.Function.LocalAssignment}} = foreign.{{.Function.ForeignAssignment}} + if err := local.Insert(tx); err != nil { + t.Fatal(err) + } + {{else -}} + if err := local.Insert(tx); err != nil { + t.Fatal(err) + } + + foreign.{{.Function.ForeignAssignment}} = local.{{.Function.LocalAssignment}} + if err := foreign.Insert(tx); err != nil { + t.Fatal(err) + } + {{end -}} + + check, err := local.{{.Function.Name}}(tx).One() + if err != nil { + t.Fatal(err) + } + + {{if .Function.UsesBytes -}} + if 0 != bytes.Compare(check.{{.Function.ForeignAssignment}}, foreign.{{.Function.ForeignAssignment}}) { + {{else -}} + if check.{{.Function.ForeignAssignment}} != foreign.{{.Function.ForeignAssignment}} { + {{end -}} + t.Errorf("want: %v, got %v", foreign.{{.Function.ForeignAssignment}}, check.{{.Function.ForeignAssignment}}) + } + + slice := {{.LocalTable.NameGo}}Slice{&local} + if err = local.L.Load{{.Function.Name}}(tx, false, &slice); err != nil { + t.Fatal(err) + } + if local.R.{{.Function.Name}} == nil { + t.Error("struct should have been eager loaded") + } + + local.R.{{.Function.Name}} = nil + if err = local.L.Load{{.Function.Name}}(tx, true, &local); err != nil { + t.Fatal(err) + } + if local.R.{{.Function.Name}} == nil { + t.Error("struct should have been eager loaded") + } +} + +{{end -}} +{{- end -}} +{{- if .Table.IsJoinTable -}} +{{- else -}} + {{- $dot := . -}} + {{- range .Table.FKeys -}} + {{- $txt := textsFromForeignKey $dot.PkgName $dot.Tables $dot.Table . -}} +{{- template "relationship_to_one_test_helper" (preserveDot $dot $txt) -}} +{{end -}} +{{- end -}} diff --git a/templates_test/relationship_one_to_one_setops.tpl b/templates_test/relationship_one_to_one_setops.tpl new file mode 100644 index 0000000..f78c261 --- /dev/null +++ b/templates_test/relationship_one_to_one_setops.tpl @@ -0,0 +1,165 @@ +{{- define "relationship_to_one_setops_test_helper" -}} +{{- $dot := .Dot -}} +{{- with .Rel -}} +{{- $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 {{.ForeignTable.NameGo}} + {{if not .Function.OneToOne -}} + var c {{.ForeignTable.NameGo}} + {{- end}} + + seed := randomize.NewSeed() + if err = randomize.Struct(seed, &a, {{$varNameSingular}}DBTypes, false, strmangle.SetComplement({{$varNameSingular}}PrimaryKeyColumns, {{$varNameSingular}}ColumnsWithoutDefault)...); err != nil { + t.Fatal(err) + } + if err = randomize.Struct(seed, &b, {{$foreignVarNameSingular}}DBTypes, false, strmangle.SetComplement({{$foreignVarNameSingular}}PrimaryKeyColumns, {{$foreignVarNameSingular}}ColumnsWithoutDefault)...); err != nil { + t.Fatal(err) + } + {{if not .Function.OneToOne -}} + if err = randomize.Struct(seed, &c, {{$foreignVarNameSingular}}DBTypes, false, strmangle.SetComplement({{$foreignVarNameSingular}}PrimaryKeyColumns, {{$foreignVarNameSingular}}ColumnsWithoutDefault)...); err != nil { + t.Fatal(err) + } + {{- end}} + + 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{{if not .Function.OneToOne}}, &c{{end}}} { + err = a.Set{{.Function.Name}}(tx, i != 0, x) + if err != nil { + t.Fatal(err) + } + + {{if .Function.UsesBytes -}} + if 0 != bytes.Compare(a.{{.Function.LocalAssignment}}, x.{{.Function.ForeignAssignment}}) { + {{else -}} + if a.{{.Function.LocalAssignment}} != x.{{.Function.ForeignAssignment}} { + {{end -}} + 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") + } + + {{if .Function.OneToOne -}} + zero := reflect.Zero(reflect.TypeOf(x.{{.Function.ForeignAssignment}})) + reflect.Indirect(reflect.ValueOf(&x.{{.Function.ForeignAssignment}})).Set(zero) + + xrel := x.R + if err = x.Reload(tx); err != nil { + t.Fatal("failed to reload", err) + } + x.R = xrel + {{else -}} + 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) + } + {{- end}} + + {{if .Function.UsesBytes -}} + if 0 != bytes.Compare(a.{{.Function.LocalAssignment}}, x.{{.Function.ForeignAssignment}}) { + {{else -}} + if a.{{.Function.LocalAssignment}} != x.{{.Function.ForeignAssignment}} { + {{end -}} + 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 or (.ForeignKey.Nullable) (and .Function.OneToOne .ForeignKey.ForeignColumnNullable)}} + +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, strmangle.SetComplement({{$varNameSingular}}PrimaryKeyColumns, {{$varNameSingular}}ColumnsWithoutDefault)...); err != nil { + t.Fatal(err) + } + if err = randomize.Struct(seed, &b, {{$foreignVarNameSingular}}DBTypes, false, strmangle.SetComplement({{$foreignVarNameSingular}}PrimaryKeyColumns, {{$foreignVarNameSingular}}ColumnsWithoutDefault)...); 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 .Function.OneToOne -}} + if b.{{.ForeignTable.ColumnNameGo}}.Valid { + t.Error("R struct entry should be nil") + } + {{else -}} + if a.{{.LocalTable.ColumnNameGo}}.Valid { + t.Error("R struct entry should be nil") + } + {{- end}} + + {{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 foreign key nullable */}} +{{- end -}}{{/* with rel */}} +{{- end -}}{{/* define */}} +{{- if .Table.IsJoinTable -}} +{{- else -}} + {{- $dot := . -}} + {{- range .Table.FKeys -}} + {{- $txt := textsFromForeignKey $dot.PkgName $dot.Tables $dot.Table .}} +{{template "relationship_to_one_setops_test_helper" (preserveDot $dot $txt) -}} +{{- end -}} +{{- end -}}