Add MySQL Upsert, fix identation in all tpls

This commit is contained in:
Patrick O'brien 2016-09-14 18:08:30 +10:00
parent 4f1565147a
commit 83f7092dc6
43 changed files with 2457 additions and 2360 deletions

View file

@ -1003,6 +1003,12 @@ p1.Name = "Hogan"
err := p1.Upsert(db, true, []string{"id"}, []string{"name"}, "id", "name") 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. 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. If this feature is important to you let us know and we can consider adding something for this.

View file

@ -3,6 +3,7 @@ package bdb
import ( import (
"fmt" "fmt"
"regexp" "regexp"
"strings"
) )
var rgxAutoIncColumn = regexp.MustCompile(`^nextval\(.*\)`) var rgxAutoIncColumn = regexp.MustCompile(`^nextval\(.*\)`)
@ -79,3 +80,30 @@ func SQLColDefinitions(cols []Column, names []string) SQLColumnDefs {
return ret 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
}

View file

@ -183,8 +183,37 @@ func buildUpdateQuery(q *Query) (*bytes.Buffer, []interface{}) {
return buf, args return buf, args
} }
// BuildUpsertQuery builds a SQL statement string using the upsertData provided. // BuildUpsertQueryMySQL builds a SQL statement string using the upsertData provided.
func BuildUpsertQuery(dia Dialect, tableName string, updateOnConflict bool, ret, update, conflict, whitelist []string) string { 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) conflict = strmangle.IdentQuoteSlice(dia.LQ, dia.RQ, conflict)
whitelist = strmangle.IdentQuoteSlice(dia.LQ, dia.RQ, whitelist) whitelist = strmangle.IdentQuoteSlice(dia.LQ, dia.RQ, whitelist)
ret = strmangle.IdentQuoteSlice(dia.LQ, dia.RQ, ret) ret = strmangle.IdentQuoteSlice(dia.LQ, dia.RQ, ret)

View file

@ -174,6 +174,7 @@ var templateFunctions = template.FuncMap{
// dbdrivers ops // dbdrivers ops
"filterColumnsByDefault": bdb.FilterColumnsByDefault, "filterColumnsByDefault": bdb.FilterColumnsByDefault,
"autoIncPrimaryKey": bdb.AutoIncPrimaryKey,
"sqlColDefinitions": bdb.SQLColDefinitions, "sqlColDefinitions": bdb.SQLColDefinitions,
"columnNames": bdb.ColumnNames, "columnNames": bdb.ColumnNames,
"columnDBTypes": bdb.ColumnDBTypes, "columnDBTypes": bdb.ColumnDBTypes,

View file

@ -1,27 +1,27 @@
{{- $tableNameSingular := .Table.Name | singular | titleCase -}} {{- $tableNameSingular := .Table.Name | singular | titleCase -}}
{{- $varNameSingular := .Table.Name | singular | camelCase -}} {{- $varNameSingular := .Table.Name | singular | camelCase -}}
// UpsertG attempts an insert, and does an update or ignore on conflict. // 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 { func (o *{{$tableNameSingular}}) UpsertG({{if eq .DriverName "postgres"}}updateOnConflict bool, conflictColumns []string, {{end}}updateColumns []string, whitelist ...string) error {
return o.Upsert(boil.GetDB(), updateOnConflict, conflictColumns, updateColumns, whitelist...) 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. // 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) { func (o *{{$tableNameSingular}}) UpsertGP({{if eq .DriverName "postgres"}}updateOnConflict bool, conflictColumns []string, {{end}}updateColumns []string, whitelist ...string) {
if err := o.Upsert(boil.GetDB(), updateOnConflict, conflictColumns, updateColumns, whitelist...); err != nil { if err := o.Upsert(boil.GetDB(), {{if eq .DriverName "postgres"}}updateOnConflict, conflictColumns, {{end}}updateColumns, whitelist...); err != nil {
panic(boil.WrapErr(err)) panic(boil.WrapErr(err))
} }
} }
// UpsertP attempts an insert using an executor, and does an update or ignore on conflict. // UpsertP attempts an insert using an executor, and does an update or ignore on conflict.
// UpsertP panics on error. // UpsertP panics on error.
func (o *{{$tableNameSingular}}) UpsertP(exec boil.Executor, updateOnConflict bool, conflictColumns []string, updateColumns []string, whitelist ...string) { 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, updateOnConflict, conflictColumns, updateColumns, whitelist...); err != nil { if err := o.Upsert(exec, {{if eq .DriverName "postgres"}}updateOnConflict, conflictColumns, {{end}}updateColumns, whitelist...); err != nil {
panic(boil.WrapErr(err)) panic(boil.WrapErr(err))
} }
} }
// Upsert attempts an insert using an executor, and does an update or ignore on conflict. // 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 { if o == nil {
return errors.New("{{.PkgName}}: no {{.Table.Name}} provided for upsert") 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, {{$varNameSingular}}PrimaryKeyColumns,
updateColumns, updateColumns,
) )
{{if eq .DriverName "postgres" -}}
conflict := conflictColumns conflict := conflictColumns
if len(conflict) == 0 { if len(conflict) == 0 {
conflict = make([]string, len({{$varNameSingular}}PrimaryKeyColumns)) conflict = make([]string, len({{$varNameSingular}}PrimaryKeyColumns))
copy(conflict, {{$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 { if boil.DebugMode {
fmt.Fprintln(boil.DebugWriter, query) fmt.Fprintln(boil.DebugWriter, query)
@ -62,7 +69,7 @@ func (o *{{$tableNameSingular}}) Upsert(exec boil.Executor, updateOnConflict boo
} }
{{- if .UseLastInsertID}} {{- if .UseLastInsertID}}
return errors.New("don't know how to do this yet") res, err := exec.Exec(query, boil.GetStructValues(o, whitelist...)...)
{{- else}} {{- else}}
if len(ret) != 0 { if len(ret) != 0 {
err = exec.QueryRow(query, boil.GetStructValues(o, whitelist...)...).Scan(boil.GetStructPointers(o, ret...)...) 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}}") 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 not .NoHooks -}}
if err := o.doAfterUpsertHooks(exec); err != nil { if err := o.doAfterUpsertHooks(exec); err != nil {
return err return err

View file

@ -69,6 +69,17 @@ func (m *mysqlTester) setup() error {
return nil 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 { func (m *mysqlTester) defaultsFile() string {
return fmt.Sprintf("--defaults-file=%s", m.optionFile) 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, "port=%d\n", m.port)
fmt.Fprintf(tmp, "user=%s\n", m.user) fmt.Fprintf(tmp, "user=%s\n", m.user)
fmt.Fprintf(tmp, "password=%s\n", m.pass) fmt.Fprintf(tmp, "password=%s\n", m.pass)
// BUG: SSL Mode for whatever reason is backwards in the mysql driver fmt.Fprintf(tmp, "ssl-mode=%s\n", m.sslMode(m.sslmode))
// 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.Fprintln(tmp, "[mysqldump]") fmt.Fprintln(tmp, "[mysqldump]")
fmt.Fprintf(tmp, "host=%s\n", m.host) fmt.Fprintf(tmp, "host=%s\n", m.host)
fmt.Fprintf(tmp, "port=%d\n", m.port) fmt.Fprintf(tmp, "port=%d\n", m.port)
fmt.Fprintf(tmp, "user=%s\n", m.user) fmt.Fprintf(tmp, "user=%s\n", m.user)
fmt.Fprintf(tmp, "password=%s\n", m.pass) fmt.Fprintf(tmp, "password=%s\n", m.pass)
fmt.Fprintf(tmp, "ssl-mode=%s\n", m.sslMode(m.sslmode))
m.optionFile = tmp.Name() m.optionFile = tmp.Name()

View file

@ -18,7 +18,7 @@ func test{{$tableNamePlural}}Upsert(t *testing.T) {
tx := MustTx(boil.Begin()) tx := MustTx(boil.Begin())
defer tx.Rollback() 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) 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) 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) t.Errorf("Unable to upsert {{$tableNameSingular}}: %s", err)
} }