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")
|
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.
|
||||||
|
|
||||||
|
|
28
bdb/keys.go
28
bdb/keys.go
|
@ -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
|
||||||
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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()
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue