Merge branch 'master' of github.com:vattle/sqlboiler

This commit is contained in:
Patrick O'brien 2016-09-04 17:40:43 +10:00
commit 8f0bfe8c9e
11 changed files with 197 additions and 86 deletions

View file

@ -14,22 +14,23 @@ or some other migration tool to manage this part of the database's life-cycle.
## Why another ORM ## Why another ORM
Whilst using the standard SQL library is efficient (cpu/mem wise), it can be cumbersome. We found ourselves remaking the While attempting to migrate a legacy Rails database, we realized how much ActiveRecord benefitted us in terms of development velocity.
same SQL helpers and wrappers for every project we were creating, but did not want to deal in the existing ORM options out Coming over to the Go `database/sql` package after using ActiveRecord feels extremely repetitive, super long-winded and down-right boring.
there that utilize the "code-first" approach. `sqlx` is a great library, but very minimalistic and still requires a Being Go veterans we knew the state of ORMs was shaky, and after a quick review we found what our fears confirmed. Most packages out
considerable amount of boilerplate for every project. Originally this project started as a SQL boilerplate generator (hence the name) there are code-first, reflect-based and have a very weak story around relationships between models. So with that we set out with these goals:
that generated simple helper functions, but we found that we could accomplish the same task by turning it into a
(mostly) fully fledged ORM generator, without any sacrifice in performance or congruency, but generous gains in flexibility.
The approach we've taken has afforded us the following benefits: * Work with existing databases: Don't be the tool to define the schema, that's better left to other tools.
* ActiveRecord-like productivity: Eliminate all sql boilerplate, have relationships as a first-class concept.
* Go-like feel: Work with normal structs, call functions, no hyper-magical struct tags, small interfaces.
* Go-like performance: Benchmark and optimize the hot-paths, perform like hand-rolled `sql.DB` code.
* Thorough relationship story. No unnecessary struct tags, no unnecessary configuration. We believe with SQLBoiler and our database-first code-generation approach we've been able to successfully meet all of these goals. On top
* High performance and memory efficiency by minimizing run-time reflection. of that SQLBoiler also confers the following benefits:
* The models package is type safe. This means no chance of random panics due to passing in the wrong type. No need for any type assertions.
* The models package is type safe. This means no chance of random panics due to passing in the wrong type. No need for interface{}.
* Our types closely correlate to your database column types. This is expanded by our extended null package which supports nearly all Go data types. * Our types closely correlate to your database column types. This is expanded by our extended null package which supports nearly all Go data types.
* Extensive auto-completion provides work-flow efficiency gains. * A system that is easy to debug. Your ORM is tailored to your schema, the code paths should be easy to trace since it's not all buried in reflect.
* A system that is easier to debug. Your ORM is tailored to your schema, the code paths are easy to trace and generally very lucid. * Auto-completion provides work-flow efficiency gains.
* An API you would write for yourself (we hope), that is compatible with most-any database schema.
Table of Contents Table of Contents
================= =================

View file

@ -9,18 +9,13 @@ import (
// NonZeroDefaultSet returns the fields included in the // NonZeroDefaultSet returns the fields included in the
// defaults slice that are non zero values // defaults slice that are non zero values
func NonZeroDefaultSet(defaults []string, titleCases map[string]string, obj interface{}) []string { func NonZeroDefaultSet(defaults []string, obj interface{}) []string {
c := make([]string, 0, len(defaults)) c := make([]string, 0, len(defaults))
val := reflect.Indirect(reflect.ValueOf(obj)) val := reflect.Indirect(reflect.ValueOf(obj))
for _, d := range defaults { for _, d := range defaults {
var fieldName string fieldName := strmangle.TitleCase(d)
if titleCases == nil {
fieldName = strmangle.TitleCase(d)
} else {
fieldName = titleCases[d]
}
field := val.FieldByName(fieldName) field := val.FieldByName(fieldName)
if !field.IsValid() { if !field.IsValid() {
panic(fmt.Sprintf("Could not find field name %s in type %T", fieldName, obj)) panic(fmt.Sprintf("Could not find field name %s in type %T", fieldName, obj))

View file

@ -59,7 +59,7 @@ func TestNonZeroDefaultSet(t *testing.T) {
} }
for i, test := range tests { for i, test := range tests {
z := NonZeroDefaultSet(test.Defaults, nil, test.Obj) z := NonZeroDefaultSet(test.Defaults, test.Obj)
if !reflect.DeepEqual(test.Ret, z) { if !reflect.DeepEqual(test.Ret, z) {
t.Errorf("[%d] mismatch:\nWant: %#v\nGot: %#v", i, test.Ret, z) t.Errorf("[%d] mismatch:\nWant: %#v\nGot: %#v", i, test.Ret, z)
} }

View file

@ -16,6 +16,7 @@ var (
mut sync.RWMutex mut sync.RWMutex
bindingMaps = make(map[string][]uint64) bindingMaps = make(map[string][]uint64)
structMaps = make(map[string]map[string]uint64)
) )
// Identifies what kind of object we're binding to // Identifies what kind of object we're binding to
@ -192,21 +193,33 @@ func bind(rows *sql.Rows, obj interface{}, structType, sliceType reflect.Type, b
ptrSlice = reflect.Indirect(reflect.ValueOf(obj)) ptrSlice = reflect.Indirect(reflect.ValueOf(obj))
} }
var strMapping map[string]uint64
var sok bool
var mapping []uint64 var mapping []uint64
var ok bool var ok bool
mapKey := makeCacheKey(structType.String(), cols) typStr := structType.String()
mapKey := makeCacheKey(typStr, cols)
mut.RLock() mut.RLock()
mapping, ok = bindingMaps[mapKey] mapping, ok = bindingMaps[mapKey]
if !ok {
if strMapping, sok = structMaps[typStr]; !sok {
strMapping = MakeStructMapping(structType)
}
}
mut.RUnlock() mut.RUnlock()
if !ok { if !ok {
mapping, err = bindMapping(structType, cols) mapping, err = BindMapping(structType, strMapping, cols)
if err != nil { if err != nil {
return err return err
} }
mut.Lock() mut.Lock()
if !sok {
structMaps[typStr] = strMapping
}
bindingMaps[mapKey] = mapping bindingMaps[mapKey] = mapping
mut.Unlock() mut.Unlock()
} }
@ -224,12 +237,12 @@ func bind(rows *sql.Rows, obj interface{}, structType, sliceType reflect.Type, b
switch bkind { switch bkind {
case kindStruct: case kindStruct:
pointers = ptrsFromMapping(reflect.Indirect(reflect.ValueOf(obj)), mapping) pointers = PtrsFromMapping(reflect.Indirect(reflect.ValueOf(obj)), mapping)
case kindSliceStruct: case kindSliceStruct:
pointers = ptrsFromMapping(oneStruct, mapping) pointers = PtrsFromMapping(oneStruct, mapping)
case kindPtrSliceStruct: case kindPtrSliceStruct:
newStruct = reflect.New(structType) newStruct = reflect.New(structType)
pointers = ptrsFromMapping(reflect.Indirect(newStruct), mapping) pointers = PtrsFromMapping(reflect.Indirect(newStruct), mapping)
} }
if err != nil { if err != nil {
return err return err
@ -254,11 +267,10 @@ func bind(rows *sql.Rows, obj interface{}, structType, sliceType reflect.Type, b
return nil return nil
} }
// bindMapping creates a mapping that helps look up the pointer for the // BindMapping creates a mapping that helps look up the pointer for the
// column given. // column given.
func bindMapping(typ reflect.Type, cols []string) ([]uint64, error) { func BindMapping(typ reflect.Type, mapping map[string]uint64, cols []string) ([]uint64, error) {
ptrs := make([]uint64, len(cols)) ptrs := make([]uint64, len(cols))
mapping := makeStructMapping(typ)
ColLoop: ColLoop:
for i, c := range cols { for i, c := range cols {
@ -283,19 +295,29 @@ ColLoop:
return ptrs, nil return ptrs, nil
} }
// ptrsFromMapping expects to be passed an addressable struct that it's looking // PtrsFromMapping expects to be passed an addressable struct and a mapping
// for things on. // of where to find things. It pulls the pointers out referred to by the mapping.
func ptrsFromMapping(val reflect.Value, mapping []uint64) []interface{} { func PtrsFromMapping(val reflect.Value, mapping []uint64) []interface{} {
ptrs := make([]interface{}, len(mapping)) ptrs := make([]interface{}, len(mapping))
for i, m := range mapping { for i, m := range mapping {
ptrs[i] = ptrFromMapping(val, m).Interface() ptrs[i] = ptrFromMapping(val, m, true).Interface()
}
return ptrs
}
// ValuesFromMapping expects to be passed an addressable struct and a mapping
// of where to find things. It pulls the pointers out referred to by the mapping.
func ValuesFromMapping(val reflect.Value, mapping []uint64) []interface{} {
ptrs := make([]interface{}, len(mapping))
for i, m := range mapping {
ptrs[i] = ptrFromMapping(val, m, false).Interface()
} }
return ptrs return ptrs
} }
// ptrFromMapping expects to be passed an addressable struct that it's looking // ptrFromMapping expects to be passed an addressable struct that it's looking
// for things on. // for things on.
func ptrFromMapping(val reflect.Value, mapping uint64) reflect.Value { func ptrFromMapping(val reflect.Value, mapping uint64, addressOf bool) reflect.Value {
for i := 0; i < 8; i++ { for i := 0; i < 8; i++ {
v := (mapping >> uint(i*8)) & sentinel v := (mapping >> uint(i*8)) & sentinel
@ -315,7 +337,9 @@ func ptrFromMapping(val reflect.Value, mapping uint64) reflect.Value {
panic("could not find pointer from mapping") panic("could not find pointer from mapping")
} }
func makeStructMapping(typ reflect.Type) map[string]uint64 { // MakeStructMapping creates a map of the struct to be able to quickly look
// up its pointers and values by name.
func MakeStructMapping(typ reflect.Type) map[string]uint64 {
fieldMaps := make(map[string]uint64) fieldMaps := make(map[string]uint64)
makeStructMappingHelper(typ, "", 0, 0, fieldMaps) makeStructMappingHelper(typ, "", 0, 0, fieldMaps)
return fieldMaps return fieldMaps

View file

@ -204,7 +204,7 @@ func TestMakeStructMapping(t *testing.T) {
} `boil:",bind"` } `boil:",bind"`
}{} }{}
got := makeStructMapping(reflect.TypeOf(testStruct)) got := MakeStructMapping(reflect.TypeOf(testStruct))
expectMap := map[string]uint64{ expectMap := map[string]uint64{
"Different": testMakeMapping(0), "Different": testMakeMapping(0),
@ -247,19 +247,19 @@ func TestPtrFromMapping(t *testing.T) {
}, },
} }
v := ptrFromMapping(reflect.Indirect(reflect.ValueOf(val)), testMakeMapping(0)) v := ptrFromMapping(reflect.Indirect(reflect.ValueOf(val)), testMakeMapping(0), true)
if got := *v.Interface().(*int); got != 5 { if got := *v.Interface().(*int); got != 5 {
t.Error("flat int was wrong:", got) t.Error("flat int was wrong:", got)
} }
v = ptrFromMapping(reflect.Indirect(reflect.ValueOf(val)), testMakeMapping(1)) v = ptrFromMapping(reflect.Indirect(reflect.ValueOf(val)), testMakeMapping(1), true)
if got := *v.Interface().(*int); got != 0 { if got := *v.Interface().(*int); got != 0 {
t.Error("flat pointer was wrong:", got) t.Error("flat pointer was wrong:", got)
} }
v = ptrFromMapping(reflect.Indirect(reflect.ValueOf(val)), testMakeMapping(2, 0)) v = ptrFromMapping(reflect.Indirect(reflect.ValueOf(val)), testMakeMapping(2, 0), true)
if got := *v.Interface().(*int); got != 6 { if got := *v.Interface().(*int); got != 6 {
t.Error("nested int was wrong:", got) t.Error("nested int was wrong:", got)
} }
v = ptrFromMapping(reflect.Indirect(reflect.ValueOf(val)), testMakeMapping(2, 1)) v = ptrFromMapping(reflect.Indirect(reflect.ValueOf(val)), testMakeMapping(2, 1), true)
if got := *v.Interface().(*int); got != 0 { if got := *v.Interface().(*int); got != 0 {
t.Error("nested pointer was wrong:", got) t.Error("nested pointer was wrong:", got)
} }

View file

@ -146,6 +146,8 @@ var defaultTemplateImports = imports{
`"fmt"`, `"fmt"`,
`"strings"`, `"strings"`,
`"database/sql"`, `"database/sql"`,
`"reflect"`,
`"sync"`,
`"time"`, `"time"`,
}, },
thirdParty: importList{ thirdParty: importList{
@ -166,6 +168,7 @@ var defaultSingletonTemplateImports = map[string]imports{
"boil_types": { "boil_types": {
thirdParty: importList{ thirdParty: importList{
`"github.com/pkg/errors"`, `"github.com/pkg/errors"`,
`"github.com/vattle/sqlboiler/strmangle"`,
}, },
}, },
} }

View file

@ -1,3 +1,5 @@
{{if .Table.IsJoinTable -}}
{{else -}}
{{- $varNameSingular := .Table.Name | singular | camelCase -}} {{- $varNameSingular := .Table.Name | singular | camelCase -}}
{{- $tableNameSingular := .Table.Name | singular | titleCase -}} {{- $tableNameSingular := .Table.Name | singular | titleCase -}}
var ( var (
@ -5,11 +7,6 @@ var (
{{$varNameSingular}}ColumnsWithoutDefault = []string{{"{"}}{{.Table.Columns | filterColumnsByDefault false | columnNames | stringMap .StringFuncs.quoteWrap | join ","}}{{"}"}} {{$varNameSingular}}ColumnsWithoutDefault = []string{{"{"}}{{.Table.Columns | filterColumnsByDefault false | columnNames | stringMap .StringFuncs.quoteWrap | join ","}}{{"}"}}
{{$varNameSingular}}ColumnsWithDefault = []string{{"{"}}{{.Table.Columns | filterColumnsByDefault true | columnNames | stringMap .StringFuncs.quoteWrap | join ","}}{{"}"}} {{$varNameSingular}}ColumnsWithDefault = []string{{"{"}}{{.Table.Columns | filterColumnsByDefault true | columnNames | stringMap .StringFuncs.quoteWrap | join ","}}{{"}"}}
{{$varNameSingular}}PrimaryKeyColumns = []string{{"{"}}{{.Table.PKey.Columns | stringMap .StringFuncs.quoteWrap | join ", "}}{{"}"}} {{$varNameSingular}}PrimaryKeyColumns = []string{{"{"}}{{.Table.PKey.Columns | stringMap .StringFuncs.quoteWrap | join ", "}}{{"}"}}
{{$varNameSingular}}TitleCases = map[string]string{
{{range $col := .Table.Columns | columnNames -}}
"{{$col}}": "{{titleCase $col}}",
{{end -}}
}
) )
type ( type (
@ -23,5 +20,16 @@ type (
} }
) )
// Cache for insert and update
var (
{{$varNameSingular}}Type = reflect.TypeOf(&{{$tableNameSingular}}{})
{{$varNameSingular}}Mapping = boil.MakeStructMapping({{$varNameSingular}}Type)
{{$varNameSingular}}InsertCacheMut sync.RWMutex
{{$varNameSingular}}InsertCache = make(map[string]insertCache)
{{$varNameSingular}}UpdateCacheMut sync.RWMutex
{{$varNameSingular}}UpdateCache = make(map[string]updateCache)
)
// Force time package dependency for automated UpdatedAt/CreatedAt. // Force time package dependency for automated UpdatedAt/CreatedAt.
var _ = time.Second var _ = time.Second
{{end -}}

View file

@ -40,58 +40,86 @@ func (o *{{$tableNameSingular}}) Insert(exec boil.Executor, whitelist ... string
} }
{{- end}} {{- end}}
nzDefaults := boil.NonZeroDefaultSet({{$varNameSingular}}ColumnsWithDefault, o)
key := makeCacheKey(whitelist, nzDefaults)
{{$varNameSingular}}InsertCacheMut.RLock()
cache, cached := {{$varNameSingular}}InsertCache[key]
{{$varNameSingular}}InsertCacheMut.RUnlock()
if !cached {
wl, returnColumns := strmangle.InsertColumnSet( wl, returnColumns := strmangle.InsertColumnSet(
{{$varNameSingular}}Columns, {{$varNameSingular}}Columns,
{{$varNameSingular}}ColumnsWithDefault, {{$varNameSingular}}ColumnsWithDefault,
{{$varNameSingular}}ColumnsWithoutDefault, {{$varNameSingular}}ColumnsWithoutDefault,
boil.NonZeroDefaultSet({{$varNameSingular}}ColumnsWithDefault, {{$varNameSingular}}TitleCases, o), nzDefaults,
whitelist, whitelist,
) )
ins := fmt.Sprintf(`INSERT INTO {{.Table.Name}} ("%s") VALUES (%s)`, strings.Join(wl, `","`), strmangle.Placeholders(len(wl), 1, 1)) cache.valueMapping, err = boil.BindMapping({{$varNameSingular}}Type, {{$varNameSingular}}Mapping, wl)
if err != nil {
return err
}
cache.retMapping, err = boil.BindMapping({{$varNameSingular}}Type, {{$varNameSingular}}Mapping, returnColumns)
if err != nil {
return err
}
cache.query = fmt.Sprintf(`INSERT INTO {{.Table.Name}} ("%s") VALUES (%s)`, strings.Join(wl, `","`), strmangle.Placeholders(len(wl), 1, 1))
{{if .UseLastInsertID}} if len(cache.retMapping) != 0 {
if boil.DebugMode { {{if .UseLastInsertID -}}
fmt.Fprintln(boil.DebugWriter, ins) cache.retQuery = fmt.Sprintf(`SELECT %s FROM {{.Table.Name}} WHERE %s`, strings.Join(returnColumns, `","`), strmangle.WhereClause(1, {{$varNameSingular}}PrimaryKeyColumns))
fmt.Fprintln(boil.DebugWriter, boil.GetStructValues(o, wl...)) {{else -}}
cache.query += fmt.Sprintf(` RETURNING %s`, strings.Join(returnColumns, ","))
{{end -}}
}
} }
result, err := exec.Exec(ins, boil.GetStructValues(o, wl...)...) value := reflect.Indirect(reflect.ValueOf(o))
vals := boil.ValuesFromMapping(value, cache.valueMapping)
{{if .UseLastInsertID}}
if boil.DebugMode {
fmt.Fprintln(boil.DebugWriter, cache.query)
fmt.Fprintln(boil.DebugWriter, vals)
}
result, err := exec.Exec(ins, vals...)
if err != nil { if err != nil {
return errors.Wrap(err, "{{.PkgName}}: unable to insert into {{.Table.Name}}") return errors.Wrap(err, "{{.PkgName}}: unable to insert into {{.Table.Name}}")
} }
if len(cache.retMapping) == 0 {
{{if not .NoHooks -}} {{if not .NoHooks -}}
if len(returnColumns) == 0 {
return o.doAfterInsertHooks(exec) return o.doAfterInsertHooks(exec)
} {{else -}}
{{- else -}}
if len(returnColumns) == 0 {
return nil return nil
{{end -}}
} }
{{- end}}
lastID, err := result.LastInsertId() lastID, err := result.LastInsertId()
if err != nil || lastID == 0 || len({{$varNameSingular}}AutoIncPrimaryKeys) != 1 { if err != nil || lastID == 0 || len({{$varNameSingular}}PrimaryKeyColumns) != 1 {
return ErrSyncFail return ErrSyncFail
} }
sel := fmt.Sprintf(`SELECT %s FROM {{.Table.Name}} WHERE %s`, strings.Join(returnColumns, `","`), strmangle.WhereClause(1, {{$varNameSingular}}AutoIncPrimaryKeys)) if boil.DebugMode {
err = exec.QueryRow(sel, lastID).Scan(boil.GetStructPointers(o, returnColumns...)) fmt.Fprintln(boil.DebugWriter, cache.retQuery)
fmt.Fprintln(boil.DebugWriter, lastID)
}
err = exec.QueryRow(cache.retQuery, lastID).Scan(boil.PtrsFromMapping(value, cache.retMapping)...)
if err != nil { if err != nil {
return errors.Wrap(err, "{{.PkgName}}: unable to populate default values for {{.Table.Name}}") return errors.Wrap(err, "{{.PkgName}}: unable to populate default values for {{.Table.Name}}")
} }
{{else}} {{else}}
if len(returnColumns) != 0 { if len(cache.retMapping) != 0 {
ins = ins + fmt.Sprintf(` RETURNING %s`, strings.Join(returnColumns, ",")) err = exec.QueryRow(cache.query, vals...).Scan(boil.PtrsFromMapping(value, cache.retMapping)...)
err = exec.QueryRow(ins, boil.GetStructValues(o, wl...)...).Scan(boil.GetStructPointers(o, returnColumns...)...)
} else { } else {
_, err = exec.Exec(ins, boil.GetStructValues(o, wl...)...) _, err = exec.Exec(cache.query, vals...)
} }
if boil.DebugMode { if boil.DebugMode {
fmt.Fprintln(boil.DebugWriter, ins) fmt.Fprintln(boil.DebugWriter, cache.query)
fmt.Fprintln(boil.DebugWriter, boil.GetStructValues(o, wl...)) fmt.Fprintln(boil.DebugWriter, vals)
} }
if err != nil { if err != nil {
@ -99,6 +127,12 @@ func (o *{{$tableNameSingular}}) Insert(exec boil.Executor, whitelist ... string
} }
{{end}} {{end}}
if !cached {
{{$varNameSingular}}InsertCacheMut.Lock()
{{$varNameSingular}}InsertCache[key] = cache
{{$varNameSingular}}InsertCacheMut.Unlock()
}
{{if not .NoHooks -}} {{if not .NoHooks -}}
return o.doAfterInsertHooks(exec) return o.doAfterInsertHooks(exec)
{{- else -}} {{- else -}}

View file

@ -37,31 +37,40 @@ func (o *{{$tableNameSingular}}) UpdateP(exec boil.Executor, whitelist ... strin
func (o *{{$tableNameSingular}}) Update(exec boil.Executor, whitelist ... string) error { func (o *{{$tableNameSingular}}) Update(exec boil.Executor, whitelist ... string) error {
{{- template "timestamp_update_helper" . -}} {{- template "timestamp_update_helper" . -}}
var err error
{{if not .NoHooks -}} {{if not .NoHooks -}}
if err := o.doBeforeUpdateHooks(exec); err != nil { if err = o.doBeforeUpdateHooks(exec); err != nil {
return err return err
} }
{{- end}} {{end -}}
var err error key := makeCacheKey(whitelist, nil)
var query string {{$varNameSingular}}UpdateCacheMut.RLock()
var values []interface{} cache, cached := {{$varNameSingular}}UpdateCache[key]
{{$varNameSingular}}UpdateCacheMut.RUnlock()
if !cached {
wl := strmangle.UpdateColumnSet({{$varNameSingular}}Columns, {{$varNameSingular}}PrimaryKeyColumns, whitelist) wl := strmangle.UpdateColumnSet({{$varNameSingular}}Columns, {{$varNameSingular}}PrimaryKeyColumns, whitelist)
if len(wl) == 0 {
cache.query = fmt.Sprintf(`UPDATE "{{.Table.Name}}" SET %s WHERE %s`, strmangle.SetParamNames(wl), strmangle.WhereClause(len(wl)+1, {{$varNameSingular}}PrimaryKeyColumns))
cache.valueMapping, err = boil.BindMapping({{$varNameSingular}}Type, {{$varNameSingular}}Mapping, append(wl, {{$varNameSingular}}PrimaryKeyColumns...))
if err != nil {
return err
}
}
if len(cache.valueMapping) == 0 {
return errors.New("{{.PkgName}}: unable to update {{.Table.Name}}, could not build whitelist") return errors.New("{{.PkgName}}: unable to update {{.Table.Name}}, could not build whitelist")
} }
query = fmt.Sprintf(`UPDATE {{.Table.Name}} SET %s WHERE %s`, strmangle.SetParamNames(wl), strmangle.WhereClause(len(wl)+1, {{$varNameSingular}}PrimaryKeyColumns)) values := boil.ValuesFromMapping(reflect.Indirect(reflect.ValueOf(o)), cache.valueMapping)
values = boil.GetStructValues(o, wl...)
values = append(values, {{.Table.PKey.Columns | stringMap .StringFuncs.titleCase | prefixStringSlice "o." | join ", "}})
if boil.DebugMode { if boil.DebugMode {
fmt.Fprintln(boil.DebugWriter, query) fmt.Fprintln(boil.DebugWriter, cache.query)
fmt.Fprintln(boil.DebugWriter, values) fmt.Fprintln(boil.DebugWriter, values)
} }
result, err := exec.Exec(query, values...) result, err := exec.Exec(cache.query, values...)
if err != nil { if err != nil {
return errors.Wrap(err, "{{.PkgName}}: unable to update {{.Table.Name}} row") return errors.Wrap(err, "{{.PkgName}}: unable to update {{.Table.Name}} row")
} }
@ -70,6 +79,12 @@ func (o *{{$tableNameSingular}}) Update(exec boil.Executor, whitelist ... string
return errors.Errorf("failed to update single row, updated %d rows", r) return errors.Errorf("failed to update single row, updated %d rows", r)
} }
if !cached {
{{$varNameSingular}}UpdateCacheMut.Lock()
{{$varNameSingular}}UpdateCache[key] = cache
{{$varNameSingular}}UpdateCacheMut.Unlock()
}
{{if not .NoHooks -}} {{if not .NoHooks -}}
return o.doAfterUpdateHooks(exec) return o.doAfterUpdateHooks(exec)
{{- else -}} {{- else -}}

View file

@ -40,7 +40,7 @@ func (o *{{$tableNameSingular}}) Upsert(exec boil.Executor, updateOnConflict boo
{{$varNameSingular}}Columns, {{$varNameSingular}}Columns,
{{$varNameSingular}}ColumnsWithDefault, {{$varNameSingular}}ColumnsWithDefault,
{{$varNameSingular}}ColumnsWithoutDefault, {{$varNameSingular}}ColumnsWithoutDefault,
boil.NonZeroDefaultSet({{$varNameSingular}}ColumnsWithDefault, {{$varNameSingular}}TitleCases, o), boil.NonZeroDefaultSet({{$varNameSingular}}ColumnsWithDefault, o),
whitelist, whitelist,
) )
update := strmangle.UpdateColumnSet( update := strmangle.UpdateColumnSet(

View file

@ -5,3 +5,34 @@ type M map[string]interface{}
// order to populate default value information. This usually happens when LastInsertId // order to populate default value information. This usually happens when LastInsertId
// fails or there was a primary key configuration that was not resolvable. // fails or there was a primary key configuration that was not resolvable.
var ErrSyncFail = errors.New("{{.PkgName}}: failed to synchronize data after insert") var ErrSyncFail = errors.New("{{.PkgName}}: failed to synchronize data after insert")
type insertCache struct{
query string
retQuery string
valueMapping []uint64
retMapping []uint64
}
type updateCache struct{
query string
valueMapping []uint64
}
func makeCacheKey(wl, nzDefaults []string) string {
buf := strmangle.GetBuffer()
for _, w := range wl {
buf.WriteString(w)
}
if len(nzDefaults) != 0 {
buf.WriteByte('.')
}
for _, nz := range nzDefaults {
buf.WriteString(nz)
}
str := buf.String()
strmangle.PutBuffer(buf)
return str
}