MSSQL Upsert statement

This commit is contained in:
Sergey Kurt 2017-03-21 13:04:07 +03:00
parent 6bc6b1690c
commit 9bafa2f158
7 changed files with 89 additions and 41 deletions

View file

@ -283,6 +283,49 @@ func BuildUpsertQueryPostgres(dia Dialect, tableName string, updateOnConflict bo
return buf.String() return buf.String()
} }
// BuildUpsertQueryMSSQL builds a SQL statement string using the upsertData provided.
func BuildUpsertQueryMSSQL(dia Dialect, tableName string, primary, update, insert []string, output []string) string {
insert = strmangle.IdentQuoteSlice(dia.LQ, dia.RQ, insert)
buf := strmangle.GetBuffer()
defer strmangle.PutBuffer(buf)
startIndex := 1
fmt.Fprintf(buf, "MERGE INTO %s as [t]\n", tableName)
fmt.Fprintf(buf, "USING (SELECT %s) as [s] ([%s])\n",
strmangle.Placeholders(dia.IndexPlaceholders, len(primary), startIndex, 1),
strings.Join(primary, string(dia.RQ)+","+string(dia.LQ)))
fmt.Fprint(buf, "ON (")
for i, v := range primary {
if i != 0 {
fmt.Fprint(buf, " AND ")
}
fmt.Fprintf(buf, "[s].[%s] = [t].[%s]", v, v)
}
fmt.Fprint(buf, ")\n")
startIndex = len(primary) + 1
fmt.Fprint(buf, "WHEN MATCHED THEN ")
fmt.Fprintf(buf, "UPDATE SET %s\n", strmangle.SetParamNames(string(dia.LQ), string(dia.RQ), startIndex, update))
startIndex = len(primary) + 1 + len(update)
fmt.Fprint(buf, "WHEN NOT MATCHED THEN ")
fmt.Fprintf(buf, "INSERT (%s) VALUES (%s)",
strings.Join(insert, ", "),
strmangle.Placeholders(dia.IndexPlaceholders, len(insert), startIndex, 1))
if len(output) > 0 {
fmt.Fprintf(buf, "\nOUTPUT INSERTED.[%s];", strings.Join(output, "],INSERTED.["))
} else {
fmt.Fprint(buf, ";")
}
return buf.String()
}
func writeModifiers(q *Query, buf *bytes.Buffer, args *[]interface{}) { func writeModifiers(q *Query, buf *bytes.Buffer, args *[]interface{}) {
if len(q.groupBy) != 0 { if len(q.groupBy) != 0 {
fmt.Fprintf(buf, " GROUP BY %s", strings.Join(q.groupBy, ", ")) fmt.Fprintf(buf, " GROUP BY %s", strings.Join(q.groupBy, ", "))

View file

@ -35,10 +35,8 @@ var (
{{$varNameSingular}}InsertCache = make(map[string]insertCache) {{$varNameSingular}}InsertCache = make(map[string]insertCache)
{{$varNameSingular}}UpdateCacheMut sync.RWMutex {{$varNameSingular}}UpdateCacheMut sync.RWMutex
{{$varNameSingular}}UpdateCache = make(map[string]updateCache) {{$varNameSingular}}UpdateCache = make(map[string]updateCache)
{{if ne .DriverName "mssql"}}
{{$varNameSingular}}UpsertCacheMut sync.RWMutex {{$varNameSingular}}UpsertCacheMut sync.RWMutex
{{$varNameSingular}}UpsertCache = make(map[string]insertCache) {{$varNameSingular}}UpsertCache = make(map[string]insertCache)
{{end}}
) )
var ( var (

View file

@ -4,17 +4,13 @@
var {{$varNameSingular}}BeforeInsertHooks []{{$tableNameSingular}}Hook var {{$varNameSingular}}BeforeInsertHooks []{{$tableNameSingular}}Hook
var {{$varNameSingular}}BeforeUpdateHooks []{{$tableNameSingular}}Hook var {{$varNameSingular}}BeforeUpdateHooks []{{$tableNameSingular}}Hook
var {{$varNameSingular}}BeforeDeleteHooks []{{$tableNameSingular}}Hook var {{$varNameSingular}}BeforeDeleteHooks []{{$tableNameSingular}}Hook
{{if ne .DriverName "mssql" -}}
var {{$varNameSingular}}BeforeUpsertHooks []{{$tableNameSingular}}Hook var {{$varNameSingular}}BeforeUpsertHooks []{{$tableNameSingular}}Hook
{{- end}}
var {{$varNameSingular}}AfterInsertHooks []{{$tableNameSingular}}Hook var {{$varNameSingular}}AfterInsertHooks []{{$tableNameSingular}}Hook
var {{$varNameSingular}}AfterSelectHooks []{{$tableNameSingular}}Hook var {{$varNameSingular}}AfterSelectHooks []{{$tableNameSingular}}Hook
var {{$varNameSingular}}AfterUpdateHooks []{{$tableNameSingular}}Hook var {{$varNameSingular}}AfterUpdateHooks []{{$tableNameSingular}}Hook
var {{$varNameSingular}}AfterDeleteHooks []{{$tableNameSingular}}Hook var {{$varNameSingular}}AfterDeleteHooks []{{$tableNameSingular}}Hook
{{if ne .DriverName "mssql" -}}
var {{$varNameSingular}}AfterUpsertHooks []{{$tableNameSingular}}Hook var {{$varNameSingular}}AfterUpsertHooks []{{$tableNameSingular}}Hook
{{- end}}
// doBeforeInsertHooks executes all "before insert" hooks. // doBeforeInsertHooks executes all "before insert" hooks.
func (o *{{$tableNameSingular}}) doBeforeInsertHooks(exec boil.Executor) (err error) { func (o *{{$tableNameSingular}}) doBeforeInsertHooks(exec boil.Executor) (err error) {
@ -49,7 +45,6 @@ func (o *{{$tableNameSingular}}) doBeforeDeleteHooks(exec boil.Executor) (err er
return nil return nil
} }
{{- if ne .DriverName "mssql" -}}
// doBeforeUpsertHooks executes all "before Upsert" hooks. // doBeforeUpsertHooks executes all "before Upsert" hooks.
func (o *{{$tableNameSingular}}) doBeforeUpsertHooks(exec boil.Executor) (err error) { func (o *{{$tableNameSingular}}) doBeforeUpsertHooks(exec boil.Executor) (err error) {
for _, hook := range {{$varNameSingular}}BeforeUpsertHooks { for _, hook := range {{$varNameSingular}}BeforeUpsertHooks {
@ -60,7 +55,6 @@ func (o *{{$tableNameSingular}}) doBeforeUpsertHooks(exec boil.Executor) (err er
return nil return nil
} }
{{- end}}
// doAfterInsertHooks executes all "after Insert" hooks. // doAfterInsertHooks executes all "after Insert" hooks.
func (o *{{$tableNameSingular}}) doAfterInsertHooks(exec boil.Executor) (err error) { func (o *{{$tableNameSingular}}) doAfterInsertHooks(exec boil.Executor) (err error) {
@ -106,7 +100,6 @@ func (o *{{$tableNameSingular}}) doAfterDeleteHooks(exec boil.Executor) (err err
return nil return nil
} }
{{- if ne .DriverName "mssql" -}}
// doAfterUpsertHooks executes all "after Upsert" hooks. // doAfterUpsertHooks executes all "after Upsert" hooks.
func (o *{{$tableNameSingular}}) doAfterUpsertHooks(exec boil.Executor) (err error) { func (o *{{$tableNameSingular}}) doAfterUpsertHooks(exec boil.Executor) (err error) {
for _, hook := range {{$varNameSingular}}AfterUpsertHooks { for _, hook := range {{$varNameSingular}}AfterUpsertHooks {
@ -117,7 +110,6 @@ func (o *{{$tableNameSingular}}) doAfterUpsertHooks(exec boil.Executor) (err err
return nil return nil
} }
{{- end}}
// Add{{$tableNameSingular}}Hook registers your hook function for all future operations. // Add{{$tableNameSingular}}Hook registers your hook function for all future operations.
func Add{{$tableNameSingular}}Hook(hookPoint boil.HookPoint, {{$varNameSingular}}Hook {{$tableNameSingular}}Hook) { func Add{{$tableNameSingular}}Hook(hookPoint boil.HookPoint, {{$varNameSingular}}Hook {{$tableNameSingular}}Hook) {
@ -128,10 +120,8 @@ func Add{{$tableNameSingular}}Hook(hookPoint boil.HookPoint, {{$varNameSingular}
{{$varNameSingular}}BeforeUpdateHooks = append({{$varNameSingular}}BeforeUpdateHooks, {{$varNameSingular}}Hook) {{$varNameSingular}}BeforeUpdateHooks = append({{$varNameSingular}}BeforeUpdateHooks, {{$varNameSingular}}Hook)
case boil.BeforeDeleteHook: case boil.BeforeDeleteHook:
{{$varNameSingular}}BeforeDeleteHooks = append({{$varNameSingular}}BeforeDeleteHooks, {{$varNameSingular}}Hook) {{$varNameSingular}}BeforeDeleteHooks = append({{$varNameSingular}}BeforeDeleteHooks, {{$varNameSingular}}Hook)
{{if ne .DriverName "mssql" -}}
case boil.BeforeUpsertHook: case boil.BeforeUpsertHook:
{{$varNameSingular}}BeforeUpsertHooks = append({{$varNameSingular}}BeforeUpsertHooks, {{$varNameSingular}}Hook) {{$varNameSingular}}BeforeUpsertHooks = append({{$varNameSingular}}BeforeUpsertHooks, {{$varNameSingular}}Hook)
{{- end}}
case boil.AfterInsertHook: case boil.AfterInsertHook:
{{$varNameSingular}}AfterInsertHooks = append({{$varNameSingular}}AfterInsertHooks, {{$varNameSingular}}Hook) {{$varNameSingular}}AfterInsertHooks = append({{$varNameSingular}}AfterInsertHooks, {{$varNameSingular}}Hook)
case boil.AfterSelectHook: case boil.AfterSelectHook:
@ -140,10 +130,8 @@ func Add{{$tableNameSingular}}Hook(hookPoint boil.HookPoint, {{$varNameSingular}
{{$varNameSingular}}AfterUpdateHooks = append({{$varNameSingular}}AfterUpdateHooks, {{$varNameSingular}}Hook) {{$varNameSingular}}AfterUpdateHooks = append({{$varNameSingular}}AfterUpdateHooks, {{$varNameSingular}}Hook)
case boil.AfterDeleteHook: case boil.AfterDeleteHook:
{{$varNameSingular}}AfterDeleteHooks = append({{$varNameSingular}}AfterDeleteHooks, {{$varNameSingular}}Hook) {{$varNameSingular}}AfterDeleteHooks = append({{$varNameSingular}}AfterDeleteHooks, {{$varNameSingular}}Hook)
{{if ne .DriverName "mssql" -}}
case boil.AfterUpsertHook: case boil.AfterUpsertHook:
{{$varNameSingular}}AfterUpsertHooks = append({{$varNameSingular}}AfterUpsertHooks, {{$varNameSingular}}Hook) {{$varNameSingular}}AfterUpsertHooks = append({{$varNameSingular}}AfterUpsertHooks, {{$varNameSingular}}Hook)
{{- end}}
} }
} }
{{- end}} {{- end}}

View file

@ -1,29 +1,28 @@
{{- if ne .DriverName "mssql" -}}
{{- $tableNameSingular := .Table.Name | singular | titleCase -}} {{- $tableNameSingular := .Table.Name | singular | titleCase -}}
{{- $varNameSingular := .Table.Name | singular | camelCase -}} {{- $varNameSingular := .Table.Name | singular | camelCase -}}
{{- $schemaTable := .Table.Name | .SchemaTable}} {{- $schemaTable := .Table.Name | .SchemaTable}}
// 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({{if ne .DriverName "mysql"}}updateOnConflict bool, conflictColumns []string, {{end}}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(), {{if ne .DriverName "mysql"}}updateOnConflict, conflictColumns, {{end}}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({{if ne .DriverName "mysql"}}updateOnConflict bool, conflictColumns []string, {{end}}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(), {{if ne .DriverName "mysql"}}updateOnConflict, conflictColumns, {{end}}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, {{if ne .DriverName "mysql"}}updateOnConflict bool, conflictColumns []string, {{end}}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, {{if ne .DriverName "mysql"}}updateOnConflict, conflictColumns, {{end}}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, {{if ne .DriverName "mysql"}}updateOnConflict bool, conflictColumns []string, {{end}}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")
} }
@ -40,7 +39,7 @@ func (o *{{$tableNameSingular}}) Upsert(exec boil.Executor, {{if ne .DriverName
// Build cache key in-line uglily - mysql vs postgres problems // Build cache key in-line uglily - mysql vs postgres problems
buf := strmangle.GetBuffer() buf := strmangle.GetBuffer()
{{if ne .DriverName "mysql" -}} {{if eq .DriverName "postgres"}}
if updateOnConflict { if updateOnConflict {
buf.WriteByte('t') buf.WriteByte('t')
} else { } else {
@ -73,36 +72,64 @@ func (o *{{$tableNameSingular}}) Upsert(exec boil.Executor, {{if ne .DriverName
var err error var err error
if !cached { if !cached {
var ret []string insert, ret := strmangle.InsertColumnSet(
whitelist, ret = strmangle.InsertColumnSet(
{{$varNameSingular}}Columns, {{$varNameSingular}}Columns,
{{$varNameSingular}}ColumnsWithDefault, {{$varNameSingular}}ColumnsWithDefault,
{{$varNameSingular}}ColumnsWithoutDefault, {{$varNameSingular}}ColumnsWithoutDefault,
nzDefaults, nzDefaults,
whitelist, whitelist,
) )
{{- if eq .DriverName "mssql"}}
insert = strmangle.SetComplement(insert, {{$varNameSingular}}ColumnsWithAuto)
for i, v := range insert {
if strmangle.ContainsAny({{$varNameSingular}}PrimaryKeyColumns, v) && strmangle.ContainsAny({{$varNameSingular}}ColumnsWithDefault, v) {
insert = append(insert[:i], insert[i+1:]...)
}
}
if len(insert) == 0 {
return errors.New("models: unable to upsert {{.Table.Name}}, could not build insert column list")
}
ret = strmangle.SetMerge(ret, {{$varNameSingular}}ColumnsWithAuto)
ret = strmangle.SetMerge(ret, {{$varNameSingular}}ColumnsWithDefault)
{{end -}}
update := strmangle.UpdateColumnSet( update := strmangle.UpdateColumnSet(
{{$varNameSingular}}Columns, {{$varNameSingular}}Columns,
{{$varNameSingular}}PrimaryKeyColumns, {{$varNameSingular}}PrimaryKeyColumns,
updateColumns, updateColumns,
) )
{{- if eq .DriverName "mssql"}}
update = strmangle.SetComplement(update, {{$varNameSingular}}ColumnsWithAuto)
{{end -}}
if len(update) == 0 { if len(update) == 0 {
return errors.New("{{.PkgName}}: unable to upsert {{.Table.Name}}, could not build update column list") return errors.New("{{.PkgName}}: unable to upsert {{.Table.Name}}, could not build update column list")
} }
{{if ne .DriverName "mysql" -}} {{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)
} }
cache.query = queries.BuildUpsertQueryPostgres(dialect, "{{$schemaTable}}", updateOnConflict, ret, update, conflict, whitelist) cache.query = queries.BuildUpsertQueryPostgres(dialect, "{{$schemaTable}}", updateOnConflict, ret, update, conflict, insert)
{{- else -}} {{- end -}}
cache.query = queries.BuildUpsertQueryMySQL(dialect, "{{.Table.Name}}", update, whitelist)
{{if eq .DriverName "mysql"}}
cache.query = queries.BuildUpsertQueryMySQL(dialect, "{{.Table.Name}}", update, insert)
cache.retQuery = fmt.Sprintf( cache.retQuery = fmt.Sprintf(
"SELECT %s FROM {{.LQ}}{{.Table.Name}}{{.RQ}} WHERE {{whereClause .LQ .RQ 0 .Table.PKey.Columns}}", "SELECT %s FROM {{.LQ}}{{.Table.Name}}{{.RQ}} WHERE {{whereClause .LQ .RQ 0 .Table.PKey.Columns}}",
strings.Join(strmangle.IdentQuoteSlice(dialect.LQ, dialect.RQ, ret), ","), strings.Join(strmangle.IdentQuoteSlice(dialect.LQ, dialect.RQ, ret), ","),
) )
{{- end -}}
{{if eq .DriverName "mssql"}}
cache.query = queries.BuildUpsertQueryMSSQL(dialect, "{{.Table.Name}}", {{$varNameSingular}}PrimaryKeyColumns, update, insert, ret)
whitelist = append({{$varNameSingular}}PrimaryKeyColumns, update...)
whitelist = append(whitelist, insert...)
{{- end}} {{- end}}
cache.valueMapping, err = queries.BindMapping({{$varNameSingular}}Type, {{$varNameSingular}}Mapping, whitelist) cache.valueMapping, err = queries.BindMapping({{$varNameSingular}}Type, {{$varNameSingular}}Mapping, whitelist)
@ -207,5 +234,4 @@ CacheNoHooks:
{{- else -}} {{- else -}}
return nil return nil
{{- end}} {{- end}}
} }
{{- end}}

View file

@ -38,7 +38,6 @@ func {{$varNameSingular}}AfterDeleteHook(e boil.Executor, o *{{$tableNameSingula
return nil return nil
} }
{{if ne .DriverName "mssql" -}}
func {{$varNameSingular}}BeforeUpsertHook(e boil.Executor, o *{{$tableNameSingular}}) error { func {{$varNameSingular}}BeforeUpsertHook(e boil.Executor, o *{{$tableNameSingular}}) error {
*o = {{$tableNameSingular}}{} *o = {{$tableNameSingular}}{}
return nil return nil
@ -48,7 +47,6 @@ func {{$varNameSingular}}AfterUpsertHook(e boil.Executor, o *{{$tableNameSingula
*o = {{$tableNameSingular}}{} *o = {{$tableNameSingular}}{}
return nil return nil
} }
{{- end}}
func test{{$tableNamePlural}}Hooks(t *testing.T) { func test{{$tableNamePlural}}Hooks(t *testing.T) {
t.Parallel() t.Parallel()
@ -126,7 +124,6 @@ func test{{$tableNamePlural}}Hooks(t *testing.T) {
} }
{{$varNameSingular}}AfterDeleteHooks = []{{$tableNameSingular}}Hook{} {{$varNameSingular}}AfterDeleteHooks = []{{$tableNameSingular}}Hook{}
{{if ne .DriverName "mssql" -}}
Add{{$tableNameSingular}}Hook(boil.BeforeUpsertHook, {{$varNameSingular}}BeforeUpsertHook) Add{{$tableNameSingular}}Hook(boil.BeforeUpsertHook, {{$varNameSingular}}BeforeUpsertHook)
if err = o.doBeforeUpsertHooks(nil); err != nil { if err = o.doBeforeUpsertHooks(nil); err != nil {
t.Errorf("Unable to execute doBeforeUpsertHooks: %s", err) t.Errorf("Unable to execute doBeforeUpsertHooks: %s", err)
@ -144,6 +141,5 @@ func test{{$tableNamePlural}}Hooks(t *testing.T) {
t.Errorf("Expected AfterUpsertHook function to empty object, but got: %#v", o) t.Errorf("Expected AfterUpsertHook function to empty object, but got: %#v", o)
} }
{{$varNameSingular}}AfterUpsertHooks = []{{$tableNameSingular}}Hook{} {{$varNameSingular}}AfterUpsertHooks = []{{$tableNameSingular}}Hook{}
{{- end}}
} }
{{- end}} {{- end}}

View file

@ -327,7 +327,7 @@ func TestSliceUpdateAll(t *testing.T) {
{{end -}} {{end -}}
{{- end -}} {{- end -}}
} }
{{if ne .DriverName "mssql" -}}
func TestUpsert(t *testing.T) { func TestUpsert(t *testing.T) {
{{- range $index, $table := .Tables}} {{- range $index, $table := .Tables}}
{{- if $table.IsJoinTable -}} {{- if $table.IsJoinTable -}}
@ -336,5 +336,4 @@ func TestUpsert(t *testing.T) {
t.Run("{{$tableName}}", test{{$tableName}}Upsert) t.Run("{{$tableName}}", test{{$tableName}}Upsert)
{{end -}} {{end -}}
{{- end -}} {{- end -}}
} }
{{- end -}}

View file

@ -1,4 +1,3 @@
{{- if ne .DriverName "mssql" -}}
{{- $tableNameSingular := .Table.Name | singular | titleCase -}} {{- $tableNameSingular := .Table.Name | singular | titleCase -}}
{{- $tableNamePlural := .Table.Name | plural | titleCase -}} {{- $tableNamePlural := .Table.Name | plural | titleCase -}}
{{- $varNamePlural := .Table.Name | plural | camelCase -}} {{- $varNamePlural := .Table.Name | plural | camelCase -}}
@ -48,5 +47,4 @@ func test{{$tableNamePlural}}Upsert(t *testing.T) {
if count != 1 { if count != 1 {
t.Error("want one record, got:", count) t.Error("want one record, got:", count)
} }
} }
{{- end}}