Big refactor of generated code

- Stop generating helper functions for each model
- Move Insert/Update/Upsert query generation helpers to strmangle
- Add tests for query generation helpers
- Delete a lot of the Insert/Upsert tests that test the query generation
  helpers.
- Use tx for more of the tests.
This commit is contained in:
Aaron L 2016-08-13 21:21:26 -07:00
parent 8a7a9a35e8
commit efa1fbb80a
13 changed files with 231 additions and 227 deletions

View file

@ -97,7 +97,7 @@ func FilterColumnsByValidated(columns []Column) []Column {
var cols []Column
for _, c := range columns {
if c.Validated == true {
if c.Validated {
cols = append(cols, c)
}
}
@ -110,7 +110,7 @@ func FilterColumnsByUnique(columns []Column) []Column {
var cols []Column
for _, c := range columns {
if c.Unique == true {
if c.Unique {
cols = append(cols, c)
}
}

View file

@ -320,7 +320,7 @@ func identifierMapping(q *Query) map[string]string {
return ids
}
// parseBits takes a set of tokens and looks for something of the form:
// parseIdentifierClause takes a set of tokens and looks for something of the form:
// a b
// a as b
// where 'a' and 'b' are valid SQL identifiers

View file

@ -157,9 +157,14 @@ var defaultTemplateImports = imports{
var defaultSingletonTemplateImports = map[string]imports{
"boil_helpers": {
standard: importList{
`"fmt"`,
`"strings"`,
},
thirdParty: importList{
`"github.com/vattle/sqlboiler/boil"`,
`"github.com/vattle/sqlboiler/boil/qm"`,
`"github.com/vattle/sqlboiler/strmangle"`,
},
},
"boil_types": {

View file

@ -1,5 +1,43 @@
package strmangle
// UpdateColumnSet generates the set of columns to update for an update statement.
// if a whitelist is supplied, it's returned
// if a whitelist is missing then we begin with all columns
// then we remove the primary key columns
func UpdateColumnSet(allColumns, pkeyCols, whitelist []string) []string {
if len(whitelist) != 0 {
return whitelist
}
return SetComplement(allColumns, pkeyCols)
}
// InsertColumnSet generates the set of columns to insert and return for an insert statement
// the return columns are used to get values that are assigned within the database during the
// insert to keep the struct in sync with what's in the db.
// with a whitelist:
// - the whitelist is used for the insert columns
// - the return columns are the result of (columns with default values - the whitelist)
// without a whitelist:
// - start with columns without a default as these always need to be inserted
// - add all columns that have a default in the database but that are non-zero in the struct
// - the return columns are the result of (columns with default values - the previous set)
func InsertColumnSet(cols, defaults, noDefaults, nonZeroDefaults, whitelist []string) ([]string, []string) {
if len(whitelist) > 0 {
return whitelist, SetComplement(defaults, whitelist)
}
var wl []string
wl = append(wl, noDefaults...)
wl = SetMerge(nonZeroDefaults, wl)
wl = SortByKeys(cols, wl)
// Only return the columns with default values that are not in the insert whitelist
rc := SetComplement(defaults, wl)
return wl, rc
}
// SetInclude checks to see if the string is found in the string slice
func SetInclude(str string, slice []string) bool {
for _, s := range slice {

View file

@ -5,6 +5,86 @@ import (
"testing"
)
func TestUpdateColumnSet(t *testing.T) {
t.Parallel()
tests := []struct {
Cols []string
PKeys []string
Whitelist []string
Out []string
}{
{Cols: []string{"a", "b"}, PKeys: []string{"a"}, Out: []string{"b"}},
{Cols: []string{"a", "b"}, PKeys: []string{"a"}, Whitelist: []string{"a"}, Out: []string{"a"}},
{Cols: []string{"a", "b"}, PKeys: []string{"a"}, Whitelist: []string{"a", "b"}, Out: []string{"a", "b"}},
}
for i, test := range tests {
set := UpdateColumnSet(test.Cols, test.PKeys, test.Whitelist)
if !reflect.DeepEqual(set, test.Out) {
t.Errorf("%d) set was wrong\nwant: %v\ngot: %v", i, test.Out, set)
}
}
}
func TestInsertColumnSet(t *testing.T) {
t.Parallel()
columns := []string{"a", "b", "c"}
defaults := []string{"a", "c"}
nodefaults := []string{"b"}
tests := []struct {
Cols []string
Defaults []string
NoDefaults []string
NonZeroDefaults []string
Whitelist []string
Set []string
Ret []string
}{
// No whitelist
{Set: []string{"b"}, Ret: []string{"a", "c"}},
{Defaults: []string{}, NoDefaults: []string{"a", "b", "c"}, Set: []string{"a", "b", "c"}, Ret: []string{}},
// No whitelist + Nonzero defaults
{NonZeroDefaults: []string{"a"}, Set: []string{"a", "b"}, Ret: []string{"c"}},
{NonZeroDefaults: []string{"c"}, Set: []string{"b", "c"}, Ret: []string{"a"}},
// Whitelist
{Whitelist: []string{"a"}, Set: []string{"a"}, Ret: []string{"c"}},
{Whitelist: []string{"c"}, Set: []string{"c"}, Ret: []string{"a"}},
{Whitelist: []string{"a", "c"}, Set: []string{"a", "c"}, Ret: []string{}},
{Whitelist: []string{"a", "b", "c"}, Set: []string{"a", "b", "c"}, Ret: []string{}},
// Whitelist + Nonzero defaults (shouldn't care, same results as above)
{Whitelist: []string{"a"}, NonZeroDefaults: []string{"c"}, Set: []string{"a"}, Ret: []string{"c"}},
{Whitelist: []string{"c"}, NonZeroDefaults: []string{"b"}, Set: []string{"c"}, Ret: []string{"a"}},
}
for i, test := range tests {
if test.Cols == nil {
test.Cols = columns
}
if test.Defaults == nil {
test.Defaults = defaults
}
if test.NoDefaults == nil {
test.NoDefaults = nodefaults
}
set, ret := InsertColumnSet(test.Cols, test.Defaults, test.NoDefaults, test.NonZeroDefaults, test.Whitelist)
if !reflect.DeepEqual(set, test.Set) {
t.Errorf("%d) set was wrong\nwant: %v\ngot: %v", i, test.Set, set)
}
if !reflect.DeepEqual(ret, test.Ret) {
t.Errorf("%d) ret was wrong\nwant: %v\ngot: %v", i, test.Ret, ret)
}
}
}
func TestSetInclude(t *testing.T) {
t.Parallel()

View file

@ -31,7 +31,13 @@ func (o *{{$tableNameSingular}}) Insert(exec boil.Executor, whitelist ... string
return errors.New("{{.PkgName}}: no {{.Table.Name}} provided for insertion")
}
wl, returnColumns := o.generateInsertColumns(whitelist...)
wl, returnColumns := strmangle.InsertColumnSet(
{{$varNameSingular}}Columns,
{{$varNameSingular}}ColumnsWithDefault,
{{$varNameSingular}}ColumnsWithoutDefault,
boil.NonZeroDefaultSet({{$varNameSingular}}ColumnsWithDefault, o),
whitelist,
)
var err error
if err := o.doBeforeCreateHooks(); err != nil {
@ -85,31 +91,3 @@ func (o *{{$tableNameSingular}}) Insert(exec boil.Executor, whitelist ... string
return o.doAfterCreateHooks()
}
// generateInsertColumns generates the whitelist columns and return columns for an insert statement
// the return columns are used to get values that are assigned within the database during the
// insert to keep the struct in sync with what's in the db.
// with a whitelist:
// - the whitelist is used for the insert columns
// - the return columns are the result of (columns with default values - the whitelist)
// without a whitelist:
// - start with columns without a default as these always need to be inserted
// - add all columns that have a default in the database but that are non-zero in the struct
// - the return columns are the result of (columns with default values - the previous set)
func (o *{{$tableNameSingular}}) generateInsertColumns(whitelist ...string) ([]string, []string) {
if len(whitelist) > 0 {
return whitelist, strmangle.SetComplement({{$varNameSingular}}ColumnsWithDefault, whitelist)
}
var wl []string
wl = append(wl, {{$varNameSingular}}ColumnsWithoutDefault...)
wl = strmangle.SetMerge(boil.NonZeroDefaultSet({{$varNameSingular}}ColumnsWithDefault, o), wl)
wl = strmangle.SortByKeys({{$varNameSingular}}Columns, wl)
// Only return the columns with default values that are not in the insert whitelist
rc := strmangle.SetComplement({{$varNameSingular}}ColumnsWithDefault, wl)
return wl, rc
}

View file

@ -41,7 +41,7 @@ func (o *{{$tableNameSingular}}) Update(exec boil.Executor, whitelist ... string
var query string
var values []interface{}
wl := o.generateUpdateColumns(whitelist...)
wl := strmangle.UpdateColumnSet({{$varNameSingular}}Columns, {{$varNameSingular}}PrimaryKeyColumns, whitelist)
if len(wl) == 0 {
return errors.New("{{.PkgName}}: unable to update {{.Table.Name}}, could not build whitelist")
@ -145,15 +145,3 @@ func (o {{$tableNameSingular}}Slice) UpdateAll(exec boil.Executor, cols M) error
return nil
}
// generateUpdateColumns generates the whitelist columns for an update statement
// if a whitelist is supplied, it's returned
// if a whitelist is missing then we begin with all columns
// then we remove the primary key columns
func (o *{{$tableNameSingular}}) generateUpdateColumns(whitelist ...string) []string {
if len(whitelist) != 0 {
return whitelist
}
return strmangle.SetComplement({{$varNameSingular}}Columns, {{$varNameSingular}}PrimaryKeyColumns)
}

View file

@ -21,13 +21,31 @@ func (o *{{$tableNameSingular}}) UpsertP(exec boil.Executor, update bool, confli
}
// Upsert attempts an insert using an executor, and does an update or ignore on conflict.
func (o *{{$tableNameSingular}}) Upsert(exec boil.Executor, update bool, conflictColumns []string, updateColumns []string, whitelist ...string) error {
func (o *{{$tableNameSingular}}) Upsert(exec boil.Executor, updateOnConflict bool, conflictColumns []string, updateColumns []string, whitelist ...string) error {
if o == nil {
return errors.New("{{.PkgName}}: no {{.Table.Name}} provided for upsert")
}
columns := o.generateUpsertColumns(conflictColumns, updateColumns, whitelist)
query := o.generateUpsertQuery(update, columns)
var ret []string
whitelist, ret = strmangle.InsertColumnSet(
{{$varNameSingular}}Columns,
{{$varNameSingular}}ColumnsWithDefault,
{{$varNameSingular}}ColumnsWithoutDefault,
boil.NonZeroDefaultSet({{$varNameSingular}}ColumnsWithDefault, o),
whitelist,
)
update := strmangle.UpdateColumnSet(
{{$varNameSingular}}Columns,
{{$varNameSingular}}PrimaryKeyColumns,
updateColumns,
)
conflict := conflictColumns
if len(conflict) == 0 {
conflict = make([]string, len({{$varNameSingular}}PrimaryKeyColumns))
copy(conflict, {{$varNameSingular}}PrimaryKeyColumns)
}
query := generateUpsertQuery("{{.Table.Name}}", updateOnConflict, ret, update, conflict, whitelist)
var err error
if err := o.doBeforeUpsertHooks(); err != nil {
@ -36,14 +54,18 @@ func (o *{{$tableNameSingular}}) Upsert(exec boil.Executor, update bool, conflic
if boil.DebugMode {
fmt.Fprintln(boil.DebugWriter, query)
fmt.Fprintln(boil.DebugWriter, boil.GetStructValues(o, columns.whitelist...))
fmt.Fprintln(boil.DebugWriter, boil.GetStructValues(o, whitelist...))
}
if len(columns.returning) != 0 {
err = exec.QueryRow(query, boil.GetStructValues(o, columns.whitelist...)...).Scan(boil.GetStructPointers(o, columns.returning...)...)
{{- if .UseLastInsertID}}
return errors.New("don't know how to do this yet")
{{- else}}
if len(ret) != 0 {
err = exec.QueryRow(query, boil.GetStructValues(o, whitelist...)...).Scan(boil.GetStructPointers(o, ret...)...)
} else {
_, err = exec.Exec(query, {{.Table.Columns | columnNames | stringMap .StringFuncs.titleCase | prefixStringSlice "o." | join ", "}})
_, err = exec.Exec(query, boil.GetStructValues(o, whitelist...)...)
}
{{- end}}
if err != nil {
return errors.Wrap(err, "{{.PkgName}}: unable to upsert for {{.Table.Name}}")
@ -55,69 +77,3 @@ func (o *{{$tableNameSingular}}) Upsert(exec boil.Executor, update bool, conflic
return nil
}
// generateUpsertColumns builds an upsertData object, using generated values when necessary.
func (o *{{$tableNameSingular}}) generateUpsertColumns(conflict []string, update []string, whitelist []string) upsertData {
var upsertCols upsertData
upsertCols.whitelist, upsertCols.returning = o.generateInsertColumns(whitelist...)
upsertCols.conflict = make([]string, len(conflict))
upsertCols.update = make([]string, len(update))
// generates the ON CONFLICT() columns if none are provided
upsertCols.conflict = o.generateConflictColumns(conflict...)
// generate the UPDATE SET columns if none are provided
upsertCols.update = o.generateUpdateColumns(update...)
return upsertCols
}
// generateConflictColumns returns the user provided columns.
// If no columns are provided, it returns the primary key columns.
func (o *{{$tableNameSingular}}) generateConflictColumns(columns ...string) []string {
if len(columns) != 0 {
return columns
}
c := make([]string, len({{$varNameSingular}}PrimaryKeyColumns))
copy(c, {{$varNameSingular}}PrimaryKeyColumns)
return c
}
// generateUpsertQuery builds a SQL statement string using the upsertData provided.
func (o *{{$tableNameSingular}}) generateUpsertQuery(update bool, columns upsertData) string {
var set, query string
conflict := strmangle.IdentQuoteSlice(columns.conflict)
whitelist := strmangle.IdentQuoteSlice(columns.whitelist)
returning := strmangle.IdentQuoteSlice(columns.returning)
var sets []string
// Generate the UPDATE SET clause
for _, v := range columns.update {
quoted := strmangle.IdentQuote(v)
sets = append(sets, fmt.Sprintf("%s = EXCLUDED.%s", quoted, quoted))
}
set = strings.Join(sets, ", ")
query = fmt.Sprintf(
"INSERT INTO {{.Table.Name}} (%s) VALUES (%s) ON CONFLICT",
strings.Join(whitelist, ", "),
strmangle.Placeholders(len(whitelist), 1, 1),
)
if !update {
query = query + " DO NOTHING"
} else {
query = fmt.Sprintf("%s (%s) DO UPDATE SET %s", query, strings.Join(conflict, ", "), set)
}
if len(columns.returning) != 0 {
query = fmt.Sprintf("%s RETURNING %s", query, strings.Join(returning, ", "))
}
return query
}

View file

@ -11,3 +11,46 @@ func NewQuery(exec boil.Executor, mods ...qm.QueryMod) *boil.Query {
return q
}
// generateUpsertQuery builds a SQL statement string using the upsertData provided.
func generateUpsertQuery(tableName string, updateOnConflict bool, ret, update, conflict, whitelist []string) string {
conflict = strmangle.IdentQuoteSlice(conflict)
whitelist = strmangle.IdentQuoteSlice(whitelist)
ret = strmangle.IdentQuoteSlice(ret)
buf := strmangle.GetBuffer()
defer strmangle.PutBuffer(buf)
fmt.Fprintf(
buf,
"INSERT INTO %s (%s) VALUES (%s) ON CONFLICT ",
tableName,
strings.Join(whitelist, ", "),
strmangle.Placeholders(len(whitelist), 1, 1),
)
if !updateOnConflict {
buf.WriteString("DO NOTHING")
} else {
buf.WriteByte('(')
buf.WriteString(strings.Join(conflict, ", "))
buf.WriteString(") DO UPDATE SET")
for i, v := range update {
if i != 0 {
buf.WriteByte(',')
}
quoted := strmangle.IdentQuote(v)
buf.WriteString(quoted)
buf.WriteString(" = EXCLUDED.")
buf.WriteString(quoted)
}
}
if len(ret) != 0 {
buf.WriteString(" RETURNING ")
buf.WriteString(strings.Join(ret, ", "))
}
return buf.String()
}

View file

@ -1,13 +1,6 @@
// M type is for providing columns and column values to UpdateAll.
type M map[string]interface{}
type upsertData struct {
conflict []string
update []string
whitelist []string
returning []string
}
// ErrSyncFail occurs during insert when the record could not be retrieved in
// order to populate default value information. This usually happens when LastInsertId
// fails or there was a primary key configuration that was not resolvable.

View file

@ -20,8 +20,13 @@ func Test{{$tableNamePlural}}Insert(t *testing.T) {
t.Errorf("Unable to randomize {{$tableNameSingular}} slice: %s", err)
}
tx, err := boil.Begin()
if err != nil {
t.Fatal(err)
}
for i := 0; i < len(o); i++ {
if err = o[i].InsertG(); err != nil {
if err = o[i].Insert(tx); err != nil {
t.Errorf("Unable to insert {{$tableNameSingular}}:\n%#v\nErr: %s", o[i], err)
}
}
@ -29,20 +34,25 @@ func Test{{$tableNamePlural}}Insert(t *testing.T) {
j := make({{$tableNameSingular}}Slice, 3)
// Perform all Find queries and assign result objects to slice for comparison
for i := 0; i < len(o); i++ {
j[i], err = {{$tableNameSingular}}FindG({{.Table.PKey.Columns | stringMap .StringFuncs.titleCase | prefixStringSlice "o[i]." | join ", "}})
j[i], err = {{$tableNameSingular}}Find(tx, {{.Table.PKey.Columns | stringMap .StringFuncs.titleCase | prefixStringSlice "o[i]." | join ", "}})
if err != nil {
t.Errorf("Unable to find {{$tableNameSingular}} row: %s", err)
}
err = {{$varNameSingular}}CompareVals(o[i], j[i], true); if err != nil {
err = {{$varNameSingular}}CompareVals(o[i], j[i], true); if err != nil {
t.Error(err)
}
}
{{$varNamePlural}}DeleteAllRows(t)
_ = tx.Rollback()
tx, err = boil.Begin()
if err != nil {
t.Fatal(err)
}
defer tx.Rollback()
item := &{{$tableNameSingular}}{}
boil.RandomizeValidatedStruct(item, {{$varNameSingular}}ValidatedColumns, {{$varNameSingular}}DBTypes)
if err = item.InsertG(); err != nil {
if err = item.Insert(tx); err != nil {
t.Errorf("Unable to insert zero-value item {{$tableNameSingular}}:\n%#v\nErr: %s", item, err)
}
@ -87,30 +97,4 @@ func Test{{$tableNamePlural}}Insert(t *testing.T) {
t.Errorf("Expected column %s to be zero value, got: %v, wanted: %v", c, fv, zv)
}
}
item = &{{$tableNameSingular}}{}
wl, rc := item.generateInsertColumns()
if !reflect.DeepEqual(rc, {{$varNameSingular}}ColumnsWithDefault) {
t.Errorf("Expected return columns to contain all columns with default values:\n\nGot: %v\nWanted: %v", rc, {{$varNameSingular}}ColumnsWithDefault)
}
if !reflect.DeepEqual(wl, {{$varNameSingular}}ColumnsWithoutDefault) {
t.Errorf("Expected whitelist to contain all columns without default values:\n\nGot: %v\nWanted: %v", wl, {{$varNameSingular}}ColumnsWithoutDefault)
}
if err = boil.RandomizeStruct(item, {{$varNameSingular}}DBTypes, false); err != nil {
t.Errorf("Unable to randomize item: %s", err)
}
wl, rc = item.generateInsertColumns()
if len(rc) > 0 {
t.Errorf("Expected return columns to contain no columns:\n\nGot: %v", rc)
}
if !reflect.DeepEqual(wl, {{$varNameSingular}}Columns) {
t.Errorf("Expected whitelist to contain all columns values:\n\nGot: %v\nWanted: %v", wl, {{$varNameSingular}}Columns)
}
{{$varNamePlural}}DeleteAllRows(t)
}

View file

@ -31,16 +31,6 @@ func Test{{$tableNamePlural}}Update(t *testing.T) {
t.Error(err)
}
wl := item.generateUpdateColumns("test")
if len(wl) != 1 && wl[0] != "test" {
t.Errorf("Expected generateUpdateColumns whitelist to match expected whitelist")
}
wl = item.generateUpdateColumns()
if len(wl) == 0 && len({{$varNameSingular}}ColumnsWithoutDefault) > 0 {
t.Errorf("Expected generateUpdateColumns to build a whitelist for {{$tableNameSingular}}, but got 0 results")
}
{{$varNamePlural}}DeleteAllRows(t)
}

View file

@ -7,71 +7,22 @@ func Test{{$tableNamePlural}}Upsert(t *testing.T) {
o := {{$tableNameSingular}}{}
columns := o.generateUpsertColumns([]string{"one", "two"}, []string{"three", "four"}, []string{"five", "six"})
if columns.conflict[0] != "one" || columns.conflict[1] != "two" {
t.Errorf("Expected conflict to be %v, got %v", []string{"one", "two"}, columns.conflict)
}
if columns.update[0] != "three" || columns.update[1] != "four" {
t.Errorf("Expected update to be %v, got %v", []string{"three", "four"}, columns.update)
}
if columns.whitelist[0] != "five" || columns.whitelist[1] != "six" {
t.Errorf("Expected whitelist to be %v, got %v", []string{"five", "six"}, columns.whitelist)
}
columns = o.generateUpsertColumns(nil, nil, nil)
if len(columns.whitelist) == 0 {
t.Errorf("Expected whitelist to contain columns, but got len 0")
}
if len(columns.conflict) == 0 {
t.Errorf("Expected conflict to contain columns, but got len 0")
}
if len(columns.update) == 0 {
t.Errorf("expected update to contain columns, but got len 0")
}
upsertCols := upsertData{
conflict: []string{"key1", `"key2"`},
update: []string{"aaa", `"bbb"`},
whitelist: []string{"thing", `"stuff"`},
returning: []string{},
}
query := o.generateUpsertQuery(false, upsertCols)
expectedQuery := `INSERT INTO {{.Table.Name}} ("thing", "stuff") VALUES ($1,$2) ON CONFLICT DO NOTHING`
if query != expectedQuery {
t.Errorf("Expected query mismatch:\n\n%s\n%s\n", query, expectedQuery)
}
query = o.generateUpsertQuery(true, upsertCols)
expectedQuery = `INSERT INTO {{.Table.Name}} ("thing", "stuff") VALUES ($1,$2) ON CONFLICT ("key1", "key2") DO UPDATE SET "aaa" = EXCLUDED."aaa", "bbb" = EXCLUDED."bbb"`
if query != expectedQuery {
t.Errorf("Expected query mismatch:\n\n%s\n%s\n", query, expectedQuery)
}
upsertCols.returning = []string{"stuff"}
query = o.generateUpsertQuery(true, upsertCols)
expectedQuery = expectedQuery + ` RETURNING "stuff"`
if query != expectedQuery {
t.Errorf("Expected query mismatch:\n\n%s\n%s\n", query, expectedQuery)
}
// Attempt the INSERT side of an UPSERT
if err = boil.RandomizeStruct(&o, {{$varNameSingular}}DBTypes, true); err != nil {
t.Errorf("Unable to randomize {{$tableNameSingular}} struct: %s", err)
}
if err = o.UpsertG(false, nil, nil); err != nil {
tx, err := boil.Begin()
if err != nil {
t.Fatal(err)
}
defer tx.Rollback()
if err = o.Upsert(tx, false, nil, nil); err != nil {
t.Errorf("Unable to upsert {{$tableNameSingular}}: %s", err)
}
compare, err := {{$tableNameSingular}}FindG({{.Table.PKey.Columns | stringMap .StringFuncs.titleCase | prefixStringSlice "o." | join ", "}})
compare, err := {{$tableNameSingular}}Find(tx, {{.Table.PKey.Columns | stringMap .StringFuncs.titleCase | prefixStringSlice "o." | join ", "}})
if err != nil {
t.Errorf("Unable to find {{$tableNameSingular}}: %s", err)
}
@ -84,17 +35,15 @@ func Test{{$tableNamePlural}}Upsert(t *testing.T) {
t.Errorf("Unable to randomize {{$tableNameSingular}} struct: %s", err)
}
if err = o.UpsertG(true, nil, nil); err != nil {
if err = o.Upsert(tx, true, nil, nil); err != nil {
t.Errorf("Unable to upsert {{$tableNameSingular}}: %s", err)
}
compare, err = {{$tableNameSingular}}FindG({{.Table.PKey.Columns | stringMap .StringFuncs.titleCase | prefixStringSlice "o." | join ", "}})
compare, err = {{$tableNameSingular}}Find(tx, {{.Table.PKey.Columns | stringMap .StringFuncs.titleCase | prefixStringSlice "o." | join ", "}})
if err != nil {
t.Errorf("Unable to find {{$tableNameSingular}}: %s", err)
}
err = {{$varNameSingular}}CompareVals(&o, compare, true); if err != nil {
t.Error(err)
}
{{$varNamePlural}}DeleteAllRows(t)
}