Finish upsert, most of upsert tests

* Fix Upsert hooks
* Rename singleton template files
This commit is contained in:
Patrick O'brien 2016-08-05 00:20:17 +10:00
parent 8dbf322754
commit 8a3a08baa0
9 changed files with 176 additions and 64 deletions

View file

@ -150,11 +150,12 @@ var defaultTemplateImports = imports{
thirdParty: importList{
`"github.com/nullbio/sqlboiler/boil"`,
`"github.com/nullbio/sqlboiler/boil/qm"`,
`"github.com/nullbio/sqlboiler/strmangle"`,
},
}
var defaultSingletonTemplateImports = map[string]imports{
"helpers": imports{
"boil_helpers": imports{
standard: importList{},
thirdParty: importList{
`"github.com/nullbio/sqlboiler/boil"`,
@ -178,7 +179,7 @@ var defaultTestTemplateImports = imports{
}
var defaultSingletonTestTemplateImports = map[string]imports{
"main_helper_funcs": imports{
"boil_main_helpers": imports{
standard: importList{
`"database/sql"`,
`"os"`,
@ -188,7 +189,7 @@ var defaultSingletonTestTemplateImports = map[string]imports{
`"github.com/spf13/viper"`,
},
},
"helper_funcs": imports{
"boil_helpers": imports{
standard: importList{
`"crypto/md5"`,
`"fmt"`,

View file

@ -2,8 +2,10 @@
{{- $varNameSingular := .Table.Name | singular | camelCase -}}
var {{$varNameSingular}}BeforeCreateHooks []{{$tableNameSingular}}Hook
var {{$varNameSingular}}BeforeUpdateHooks []{{$tableNameSingular}}Hook
var {{$varNameSingular}}BeforeUpsertHooks []{{$tableNameSingular}}Hook
var {{$varNameSingular}}AfterCreateHooks []{{$tableNameSingular}}Hook
var {{$varNameSingular}}AfterUpdateHooks []{{$tableNameSingular}}Hook
var {{$varNameSingular}}AfterUpsertHooks []{{$tableNameSingular}}Hook
// doBeforeCreateHooks executes all "before create" hooks.
func (o *{{$tableNameSingular}}) doBeforeCreateHooks() (err error) {

View file

@ -21,7 +21,7 @@ func (o *{{$tableNameSingular}}) UpdateGP(whitelist ...string) {
// UpdateP uses an executor to update the {{$tableNameSingular}}, and panics on error.
// See Update for whitelist behavior description.
func (o *{{$tableNameSingular}}) UpdateP(exec boil.Executor, whitelist ... string) {
err := o.UpdateAt(exec, {{.Table.PKey.Columns | stringMap .StringFuncs.titleCase | prefixStringSlice "o." | join ", "}}, whitelist...)
err := o.Update(exec, whitelist...)
if err != nil {
panic(boil.WrapErr(err))
}
@ -33,23 +33,6 @@ func (o *{{$tableNameSingular}}) UpdateP(exec boil.Executor, whitelist ... strin
// - All columns are inferred to start with
// - All primary keys are subtracted from this set
func (o *{{$tableNameSingular}}) Update(exec boil.Executor, whitelist ... string) error {
return o.UpdateAt(exec, {{.Table.PKey.Columns | stringMap .StringFuncs.titleCase | prefixStringSlice "o." | join ", "}}, whitelist...)
}
// UpdateAtG updates the {{$tableNameSingular}} using the primary key to find the row to update.
func (o *{{$tableNameSingular}}) UpdateAtG({{$pkArgs}}, whitelist ...string) error {
return o.UpdateAt(boil.GetDB(), {{$pkNames | join ", "}}, whitelist...)
}
// UpdateAtGP updates the {{$tableNameSingular}} using the primary key to find the row to update. Panics on error.
func (o *{{$tableNameSingular}}) UpdateAtGP({{$pkArgs}}, whitelist ...string) {
if err := o.UpdateAt(boil.GetDB(), {{$pkNames | join ", "}}, whitelist...); err != nil {
panic(boil.WrapErr(err))
}
}
// UpdateAt uses an executor to update the {{$tableNameSingular}} using the primary key to find the row to update.
func (o *{{$tableNameSingular}}) UpdateAt(exec boil.Executor, {{$pkArgs}}, whitelist ...string) error {
if err := o.doBeforeUpdateHooks(); err != nil {
return err
}
@ -85,14 +68,6 @@ func (o *{{$tableNameSingular}}) UpdateAt(exec boil.Executor, {{$pkArgs}}, white
return nil
}
// UpdateAtP uses an executor to update the {{$tableNameSingular}} using the primary key to find the row to update.
// Panics on error.
func (o *{{$tableNameSingular}}) UpdateAtP(exec boil.Executor, {{$pkArgs}}, whitelist ...string) {
if err := o.UpdateAt(exec, {{$pkNames | join ", "}}, whitelist...); err != nil {
panic(boil.WrapErr(err))
}
}
// UpdateAll updates all rows with matching column names.
func (q {{$varNameSingular}}Query) UpdateAll(cols M) error {
boil.SetUpdate(q.Query, cols)

View file

@ -12,51 +12,36 @@ func (o *{{$tableNameSingular}}) UpsertGP(update bool, conflictColumns []string,
}
}
// UpsertP attempts an insert using an executor, and does an update or ignore on conflict.
// UpsertP panics on error.
func (o *{{$tableNameSingular}}) UpsertP(exec boil.Executor, update bool, conflictColumns []string, updateColumns []string, whitelist ...string) {
if err := o.Upsert(exec, update, conflictColumns, updateColumns, whitelist...); err != nil {
panic(boil.WrapErr(err))
}
}
// 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 {
if o == nil {
return errors.New("{{.PkgName}}: no {{.Table.Name}} provided for upsert")
}
wl, returnColumns := o.generateInsertColumns(whitelist...)
conflict := make([]string, len(conflictColumns))
update := make([]string, len(updateColumns))
copy(conflict, conflictColumns)
copy(update, updateColumns)
for i, v := range conflict {
conflict[i] = strmangle.IdentQuote(v)
}
for i, v := range update {
update[i] = strmangle.IdentQuote(v)
}
columns := o.generateUpsertColumns(conflictColumns, updateColumns, whitelist)
query := o.generateUpsertQuery(update, columns)
var err error
if err := o.doBeforeUpsertHooks(); err != nil {
return err
}
ins := fmt.Sprintf(`INSERT INTO {{.Table.Name}} ("%s") VALUES (%s) ON CONFLICT `, strings.Join(wl, `","`), boil.GenerateParamFlags(len(wl), 1))
if !update {
ins := ins + "DO NOTHING"
} else if len(conflict) != 0 {
ins := ins + fmt.Sprintf(`("%s") DO UPDATE SET %s`, strings.Join(conflict, `","`))
if len(columns.returning) != 0 {
err = exec.QueryRow(query, boil.GetStructValues(o, columns.whitelist...)...).Scan(boil.GetStructPointers(o, columns.returning...)...)
} else {
ins := ins + fmt.Sprintf(`("%s") DO UPDATE SET %s`, strings.Join({{$varNameSingular}}PrimaryKeyColumns, `","`))
}
if len(returnColumns) != 0 {
ins = ins + fmt.Sprintf(` RETURNING %s`, strings.Join(returnColumns, ","))
err = exec.QueryRow(ins, boil.GetStructValues(o, wl...)...).Scan(boil.GetStructPointers(o, returnColumns...)...)
} else {
_, err = exec.Exec(ins, {{.Table.Columns | columnNames | stringMap .StringFuncs.titleCase | prefixStringSlice "o." | join ", "}})
_, err = exec.Exec(query, {{.Table.Columns | columnNames | stringMap .StringFuncs.titleCase | prefixStringSlice "o." | join ", "}})
}
if boil.DebugMode {
fmt.Fprintln(boil.DebugWriter, ins, boil.GetStructValues(o, wl...))
fmt.Fprintln(boil.DebugWriter, query, boil.GetStructValues(o, columns.whitelist...))
}
if err != nil {
@ -70,10 +55,70 @@ func (o *{{$tableNameSingular}}) Upsert(exec boil.Executor, update bool, conflic
return nil
}
// UpsertP attempts an insert using an executor, and does an update or ignore on conflict.
// UpsertP panics on error.
func (o *{{$tableNameSingular}}) UpsertP(exec boil.Executor, update bool, conflictColumns []string, updateColumns []string, whitelist ...string) {
if err := o.Upsert(exec, update, conflictColumns, updateColumns, whitelist...); err != nil {
panic(boil.WrapErr(err))
}
// 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
for i, v := range columns.conflict {
columns.conflict[i] = strmangle.IdentQuote(v)
}
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 DO`,
strings.Join(columns.whitelist, `","`),
boil.GenerateParamFlags(len(columns.whitelist), 1),
)
if !update {
query = query + " NOTHING"
} else if len(columns.conflict) != 0 {
query = fmt.Sprintf(`%s("%s") UPDATE SET %s`, query, strings.Join(columns.conflict, `","`), set)
} else {
query = fmt.Sprintf(`%s("%s") UPDATE SET %s`, query, strings.Join({{$varNameSingular}}PrimaryKeyColumns, `","`), set)
}
if len(columns.returning) != 0 {
query = fmt.Sprintf(`%s RETURNING %s`, query, strings.Join(columns.returning, ","))
}
return query
}

View file

@ -0,0 +1,6 @@
type upsertData struct {
conflict []string
update []string
whitelist []string
returning []string
}

View file

@ -0,0 +1,83 @@
{{- $tableNameSingular := .Table.Name | singular | titleCase -}}
{{- $dbName := singular .Table.Name -}}
{{- $tableNamePlural := .Table.Name | plural | titleCase -}}
{{- $varNamePlural := .Table.Name | plural | camelCase -}}
{{- $varNameSingular := .Table.Name | singular | camelCase -}}
{{- $parent := . -}}
func Test{{$tableNamePlural}}Upsert(t *testing.T) {
//var err error
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{},
update: []string{},
whitelist: []string{"thing"},
returning: []string{},
}
query := o.generateUpsertQuery(false, upsertCols)
expectedQuery := `INSERT INTO {{.Table.Name}} ("thing") VALUES ($1) 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)
primKeys := strings.Join(strmangle.IdentQuote())
expectedQuery = `INSERT INTO {{.Table.Name}} ("thing") VALUES ($1) ON CONFLICT DO UPDATE()`
if query != expectedQuery {
t.Errorf("Expected query mismatch:\n\n%s\n%s\n", query, expectedQuery)
}
*/
/*
create empty row
assign random values to it
attempt to insert it using upsert
make sure values come back appropriately
attempt to upsert row again, make sure comes back as prim key error
attempt upsert again, set update to false, ensure it ignores error
attempt to randomize everything except primary keys on duplicate row
attempt upsert again, set update to true, nil, nil
perform a find on the the row
check if the found row matches the upsert object to ensure returning cols worked appropriately and update worked appropriately
*/
{{$varNamePlural}}DeleteAllRows(t)
}