Add MySQL Upsert, fix identation in all tpls
This commit is contained in:
parent
4f1565147a
commit
83f7092dc6
43 changed files with 2457 additions and 2360 deletions
|
@ -1003,6 +1003,12 @@ p1.Name = "Hogan"
|
|||
err := p1.Upsert(db, true, []string{"id"}, []string{"name"}, "id", "name")
|
||||
```
|
||||
|
||||
The `updateOnConflict` argument allows you to specify whether you would like Postgres
|
||||
to perform a `DO NOTHING` on conflict, opposed to a `DO UPDATE`. For MySQL, this param will not be generated.
|
||||
|
||||
The `conflictColumns` argument allows you to specify the `ON CONFLICT` columns for Postgres.
|
||||
For MySQL, this param will not be generated.
|
||||
|
||||
Note: Passing a different set of column values to the update component is not currently supported.
|
||||
If this feature is important to you let us know and we can consider adding something for this.
|
||||
|
||||
|
|
28
bdb/keys.go
28
bdb/keys.go
|
@ -3,6 +3,7 @@ package bdb
|
|||
import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var rgxAutoIncColumn = regexp.MustCompile(`^nextval\(.*\)`)
|
||||
|
@ -79,3 +80,30 @@ func SQLColDefinitions(cols []Column, names []string) SQLColumnDefs {
|
|||
|
||||
return ret
|
||||
}
|
||||
|
||||
// AutoIncPrimaryKey returns the auto-increment primary key column name or an
|
||||
// empty string. Primary key columns with default values are presumed
|
||||
// to be auto-increment, because pkeys need to be unique and a static
|
||||
// default value would cause collisions.
|
||||
func AutoIncPrimaryKey(cols []Column, pkey *PrimaryKey) *Column {
|
||||
if pkey == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
for _, pkeyColumn := range pkey.Columns {
|
||||
for _, c := range cols {
|
||||
if c.Name != pkeyColumn {
|
||||
continue
|
||||
}
|
||||
|
||||
if c.Default != "auto_increment" || c.Nullable ||
|
||||
!(strings.HasPrefix(c.Type, "int") || strings.HasPrefix(c.Type, "uint")) {
|
||||
continue
|
||||
}
|
||||
|
||||
return &c
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -183,8 +183,37 @@ func buildUpdateQuery(q *Query) (*bytes.Buffer, []interface{}) {
|
|||
return buf, args
|
||||
}
|
||||
|
||||
// BuildUpsertQuery builds a SQL statement string using the upsertData provided.
|
||||
func BuildUpsertQuery(dia Dialect, tableName string, updateOnConflict bool, ret, update, conflict, whitelist []string) string {
|
||||
// BuildUpsertQueryMySQL builds a SQL statement string using the upsertData provided.
|
||||
func BuildUpsertQueryMySQL(dia Dialect, tableName string, update, whitelist []string) string {
|
||||
whitelist = strmangle.IdentQuoteSlice(dia.LQ, dia.RQ, whitelist)
|
||||
|
||||
buf := strmangle.GetBuffer()
|
||||
defer strmangle.PutBuffer(buf)
|
||||
|
||||
fmt.Fprintf(
|
||||
buf,
|
||||
"INSERT INTO %s (%s) VALUES (%s) ON DUPLICATE KEY UPDATE ",
|
||||
tableName,
|
||||
strings.Join(whitelist, ", "),
|
||||
strmangle.Placeholders(dia.IndexPlaceholders, len(whitelist), 1, 1),
|
||||
)
|
||||
|
||||
for i, v := range update {
|
||||
if i != 0 {
|
||||
buf.WriteByte(',')
|
||||
}
|
||||
quoted := strmangle.IdentQuote(dia.LQ, dia.RQ, v)
|
||||
buf.WriteString(quoted)
|
||||
buf.WriteString(" = VALUES(")
|
||||
buf.WriteString(quoted)
|
||||
buf.WriteByte(')')
|
||||
}
|
||||
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
// BuildUpsertQueryPostgres builds a SQL statement string using the upsertData provided.
|
||||
func BuildUpsertQueryPostgres(dia Dialect, tableName string, updateOnConflict bool, ret, update, conflict, whitelist []string) string {
|
||||
conflict = strmangle.IdentQuoteSlice(dia.LQ, dia.RQ, conflict)
|
||||
whitelist = strmangle.IdentQuoteSlice(dia.LQ, dia.RQ, whitelist)
|
||||
ret = strmangle.IdentQuoteSlice(dia.LQ, dia.RQ, ret)
|
||||
|
|
|
@ -174,6 +174,7 @@ var templateFunctions = template.FuncMap{
|
|||
|
||||
// dbdrivers ops
|
||||
"filterColumnsByDefault": bdb.FilterColumnsByDefault,
|
||||
"autoIncPrimaryKey": bdb.AutoIncPrimaryKey,
|
||||
"sqlColDefinitions": bdb.SQLColDefinitions,
|
||||
"columnNames": bdb.ColumnNames,
|
||||
"columnDBTypes": bdb.ColumnDBTypes,
|
||||
|
|
|
@ -1,27 +1,27 @@
|
|||
{{- $tableNameSingular := .Table.Name | singular | titleCase -}}
|
||||
{{- $varNameSingular := .Table.Name | singular | camelCase -}}
|
||||
// UpsertG attempts an insert, and does an update or ignore on conflict.
|
||||
func (o *{{$tableNameSingular}}) UpsertG(updateOnConflict bool, conflictColumns []string, updateColumns []string, whitelist ...string) error {
|
||||
return o.Upsert(boil.GetDB(), updateOnConflict, conflictColumns, updateColumns, whitelist...)
|
||||
func (o *{{$tableNameSingular}}) UpsertG({{if eq .DriverName "postgres"}}updateOnConflict bool, conflictColumns []string, {{end}}updateColumns []string, whitelist ...string) error {
|
||||
return o.Upsert(boil.GetDB(), {{if eq .DriverName "postgres"}}updateOnConflict, conflictColumns, {{end}}updateColumns, whitelist...)
|
||||
}
|
||||
|
||||
// UpsertGP attempts an insert, and does an update or ignore on conflict. Panics on error.
|
||||
func (o *{{$tableNameSingular}}) UpsertGP(updateOnConflict bool, conflictColumns []string, updateColumns []string, whitelist ...string) {
|
||||
if err := o.Upsert(boil.GetDB(), updateOnConflict, conflictColumns, updateColumns, whitelist...); err != nil {
|
||||
func (o *{{$tableNameSingular}}) UpsertGP({{if eq .DriverName "postgres"}}updateOnConflict bool, conflictColumns []string, {{end}}updateColumns []string, whitelist ...string) {
|
||||
if err := o.Upsert(boil.GetDB(), {{if eq .DriverName "postgres"}}updateOnConflict, conflictColumns, {{end}}updateColumns, whitelist...); err != nil {
|
||||
panic(boil.WrapErr(err))
|
||||
}
|
||||
}
|
||||
|
||||
// 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, updateOnConflict bool, conflictColumns []string, updateColumns []string, whitelist ...string) {
|
||||
if err := o.Upsert(exec, updateOnConflict, conflictColumns, updateColumns, whitelist...); err != nil {
|
||||
func (o *{{$tableNameSingular}}) UpsertP(exec boil.Executor, {{if eq .DriverName "postgres"}}updateOnConflict bool, conflictColumns []string, {{end}}updateColumns []string, whitelist ...string) {
|
||||
if err := o.Upsert(exec, {{if eq .DriverName "postgres"}}updateOnConflict, conflictColumns, {{end}}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, updateOnConflict bool, conflictColumns []string, updateColumns []string, whitelist ...string) error {
|
||||
func (o *{{$tableNameSingular}}) Upsert(exec boil.Executor, {{if eq .DriverName "postgres"}}updateOnConflict bool, conflictColumns []string, {{end}}updateColumns []string, whitelist ...string) error {
|
||||
if o == nil {
|
||||
return errors.New("{{.PkgName}}: no {{.Table.Name}} provided for upsert")
|
||||
}
|
||||
|
@ -48,13 +48,20 @@ func (o *{{$tableNameSingular}}) Upsert(exec boil.Executor, updateOnConflict boo
|
|||
{{$varNameSingular}}PrimaryKeyColumns,
|
||||
updateColumns,
|
||||
)
|
||||
|
||||
{{if eq .DriverName "postgres" -}}
|
||||
conflict := conflictColumns
|
||||
if len(conflict) == 0 {
|
||||
conflict = make([]string, len({{$varNameSingular}}PrimaryKeyColumns))
|
||||
copy(conflict, {{$varNameSingular}}PrimaryKeyColumns)
|
||||
}
|
||||
{{- end}}
|
||||
|
||||
query := boil.BuildUpsertQuery(dialect, "{{.Table.Name}}", updateOnConflict, ret, update, conflict, whitelist)
|
||||
{{if eq .DriverName "postgres" -}}
|
||||
query := boil.BuildUpsertQueryPostgres(dialect, "{{.Table.Name}}", updateOnConflict, ret, update, conflict, whitelist)
|
||||
{{- else if eq .DriverName "mysql" -}}
|
||||
query := boil.BuildUpsertQueryMySQL(dialect, "{{.Table.Name}}", update, whitelist)
|
||||
{{- end}}
|
||||
|
||||
if boil.DebugMode {
|
||||
fmt.Fprintln(boil.DebugWriter, query)
|
||||
|
@ -62,7 +69,7 @@ func (o *{{$tableNameSingular}}) Upsert(exec boil.Executor, updateOnConflict boo
|
|||
}
|
||||
|
||||
{{- if .UseLastInsertID}}
|
||||
return errors.New("don't know how to do this yet")
|
||||
res, err := exec.Exec(query, boil.GetStructValues(o, whitelist...)...)
|
||||
{{- else}}
|
||||
if len(ret) != 0 {
|
||||
err = exec.QueryRow(query, boil.GetStructValues(o, whitelist...)...).Scan(boil.GetStructPointers(o, ret...)...)
|
||||
|
@ -75,6 +82,23 @@ func (o *{{$tableNameSingular}}) Upsert(exec boil.Executor, updateOnConflict boo
|
|||
return errors.Wrap(err, "{{.PkgName}}: unable to upsert for {{.Table.Name}}")
|
||||
}
|
||||
|
||||
{{if .UseLastInsertID -}}
|
||||
if len(ret) != 0 {
|
||||
lid, err := res.LastInsertId()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "{{.PkgName}}: unable to get last insert id for {{.Table.Name}}")
|
||||
}
|
||||
{{$aipk := autoIncPrimaryKey .Table.Columns .Table.PKey}}
|
||||
aipk := "{{$aipk.Name}}"
|
||||
// if the update did not change anything, lid will be 0
|
||||
if lid == 0 && aipk == "" {
|
||||
// do a select using all pkeys
|
||||
} else if lid != 0 {
|
||||
// do a select using all pkeys + lid
|
||||
}
|
||||
}
|
||||
{{- end}}
|
||||
|
||||
{{if not .NoHooks -}}
|
||||
if err := o.doAfterUpsertHooks(exec); err != nil {
|
||||
return err
|
||||
|
|
|
@ -69,6 +69,17 @@ func (m *mysqlTester) setup() error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (m *mysqlTester) sslMode(mode string) string {
|
||||
switch mode {
|
||||
case "true":
|
||||
return "REQUIRED"
|
||||
case "false":
|
||||
return "DISABLED"
|
||||
default:
|
||||
return "PREFERRED"
|
||||
}
|
||||
}
|
||||
|
||||
func (m *mysqlTester) defaultsFile() string {
|
||||
return fmt.Sprintf("--defaults-file=%s", m.optionFile)
|
||||
}
|
||||
|
@ -84,16 +95,14 @@ func (m *mysqlTester) makeOptionFile() error {
|
|||
fmt.Fprintf(tmp, "port=%d\n", m.port)
|
||||
fmt.Fprintf(tmp, "user=%s\n", m.user)
|
||||
fmt.Fprintf(tmp, "password=%s\n", m.pass)
|
||||
// BUG: SSL Mode for whatever reason is backwards in the mysql driver
|
||||
// taking options like true or false, but here taking options like
|
||||
// required/disabled. Until this gets sorted, ignore this.
|
||||
//fmt.Fprintf("ssl-mode=%s\n", m.password)
|
||||
fmt.Fprintf(tmp, "ssl-mode=%s\n", m.sslMode(m.sslmode))
|
||||
|
||||
fmt.Fprintln(tmp, "[mysqldump]")
|
||||
fmt.Fprintf(tmp, "host=%s\n", m.host)
|
||||
fmt.Fprintf(tmp, "port=%d\n", m.port)
|
||||
fmt.Fprintf(tmp, "user=%s\n", m.user)
|
||||
fmt.Fprintf(tmp, "password=%s\n", m.pass)
|
||||
fmt.Fprintf(tmp, "ssl-mode=%s\n", m.sslMode(m.sslmode))
|
||||
|
||||
m.optionFile = tmp.Name()
|
||||
|
||||
|
|
|
@ -18,7 +18,7 @@ func test{{$tableNamePlural}}Upsert(t *testing.T) {
|
|||
|
||||
tx := MustTx(boil.Begin())
|
||||
defer tx.Rollback()
|
||||
if err = {{$varNameSingular}}.Upsert(tx, false, nil, nil); err != nil {
|
||||
if err = {{$varNameSingular}}.Upsert(tx, {{if eq .DriverName "postgres"}}false, nil, {{end}}nil); err != nil {
|
||||
t.Errorf("Unable to upsert {{$tableNameSingular}}: %s", err)
|
||||
}
|
||||
|
||||
|
@ -35,7 +35,7 @@ func test{{$tableNamePlural}}Upsert(t *testing.T) {
|
|||
t.Errorf("Unable to randomize {{$tableNameSingular}} struct: %s", err)
|
||||
}
|
||||
|
||||
if err = {{$varNameSingular}}.Upsert(tx, true, nil, nil); err != nil {
|
||||
if err = {{$varNameSingular}}.Upsert(tx, {{if eq .DriverName "postgres"}}true, nil, {{end}}nil); err != nil {
|
||||
t.Errorf("Unable to upsert {{$tableNameSingular}}: %s", err)
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue