2023-03-12 11:31:08 +00:00
|
|
|
// Copyright 2020 The Xorm Authors. All rights reserved.
|
|
|
|
// Use of this source code is governed by a BSD-style
|
|
|
|
// license that can be found in the LICENSE file.
|
|
|
|
|
|
|
|
package statements
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
|
|
|
|
|
|
|
"xorm.io/builder"
|
|
|
|
"xorm.io/xorm/schemas"
|
|
|
|
)
|
|
|
|
|
|
|
|
// GenUpsertSQL generates upsert beans SQL
|
|
|
|
func (statement *Statement) GenUpsertSQL(doUpdate bool, columns []string, args []interface{}, uniqueColValMap map[string]interface{}) (string, []interface{}, error) {
|
|
|
|
if statement.dialect.URI().DBType == schemas.MSSQL ||
|
|
|
|
statement.dialect.URI().DBType == schemas.DAMENG ||
|
|
|
|
statement.dialect.URI().DBType == schemas.ORACLE {
|
|
|
|
return statement.genMergeSQL(doUpdate, columns, args, uniqueColValMap)
|
|
|
|
}
|
|
|
|
|
|
|
|
var (
|
|
|
|
buf = builder.NewWriter()
|
|
|
|
table = statement.RefTable
|
|
|
|
tableName = statement.TableName()
|
|
|
|
)
|
2023-03-13 06:34:31 +00:00
|
|
|
quote := statement.dialect.Quoter().Quote
|
|
|
|
write := func(args ...string) {
|
|
|
|
for _, arg := range args {
|
|
|
|
_, _ = buf.WriteString(arg)
|
|
|
|
}
|
|
|
|
}
|
2023-03-12 11:31:08 +00:00
|
|
|
|
2023-03-13 06:34:31 +00:00
|
|
|
var updateColumns []string
|
|
|
|
if doUpdate {
|
|
|
|
updateColumns = make([]string, 0, len(columns))
|
|
|
|
for _, column := range columns {
|
|
|
|
if _, has := uniqueColValMap[schemas.CommonQuoter.Trim(column)]; has {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
updateColumns = append(updateColumns, quote(column))
|
2023-03-12 11:31:08 +00:00
|
|
|
}
|
2023-03-13 06:34:31 +00:00
|
|
|
doUpdate = doUpdate && (len(updateColumns) > 0)
|
|
|
|
}
|
|
|
|
|
|
|
|
if statement.dialect.URI().DBType == schemas.MYSQL && !doUpdate {
|
|
|
|
write("INSERT IGNORE INTO ")
|
2023-03-12 11:31:08 +00:00
|
|
|
} else {
|
2023-03-13 06:34:31 +00:00
|
|
|
write("INSERT INTO ")
|
2023-03-12 11:31:08 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if err := statement.dialect.Quoter().QuoteTo(buf.Builder, tableName); err != nil {
|
|
|
|
return "", nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := statement.genInsertValues(buf, columns, args); err != nil {
|
|
|
|
return "", nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
switch statement.dialect.URI().DBType {
|
|
|
|
case schemas.SQLITE, schemas.POSTGRES:
|
2023-03-13 06:34:31 +00:00
|
|
|
write(" ON CONFLICT DO ")
|
2023-03-12 11:31:08 +00:00
|
|
|
if doUpdate {
|
2023-03-13 06:34:31 +00:00
|
|
|
write("UPDATE SET ", updateColumns[0], " = excluded.", updateColumns[0])
|
|
|
|
for _, column := range updateColumns[1:] {
|
|
|
|
write(", ", column, " = excluded.", column)
|
2023-03-12 11:31:08 +00:00
|
|
|
}
|
2023-03-13 06:34:31 +00:00
|
|
|
} else {
|
|
|
|
write("NOTHING")
|
2023-03-12 11:31:08 +00:00
|
|
|
}
|
|
|
|
case schemas.MYSQL:
|
|
|
|
if doUpdate {
|
2023-03-13 06:34:31 +00:00
|
|
|
// FIXME: mysql >= 8.0.19 should use table alias
|
|
|
|
write(" ON DUPLICATE KEY ")
|
2023-03-13 09:35:33 +00:00
|
|
|
write("UPDATE ", updateColumns[0], " = VALUES(", updateColumns[0], ")")
|
2023-03-13 06:34:31 +00:00
|
|
|
for _, column := range updateColumns[1:] {
|
|
|
|
write(", ", column, " = VALUES(", column, ")")
|
|
|
|
}
|
2023-03-12 11:31:08 +00:00
|
|
|
}
|
|
|
|
default:
|
|
|
|
return "", nil, fmt.Errorf("unimplemented") // FIXME: UPSERT
|
|
|
|
}
|
|
|
|
|
2023-03-13 09:35:33 +00:00
|
|
|
if len(table.AutoIncrement) > 0 &&
|
|
|
|
(statement.dialect.URI().DBType == schemas.POSTGRES ||
|
|
|
|
statement.dialect.URI().DBType == schemas.SQLITE) {
|
2023-03-13 06:34:31 +00:00
|
|
|
write(" RETURNING ")
|
2023-03-12 11:31:08 +00:00
|
|
|
if err := statement.dialect.Quoter().QuoteTo(buf.Builder, table.AutoIncrement); err != nil {
|
|
|
|
return "", nil, err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return buf.String(), buf.Args(), nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (statement *Statement) genMergeSQL(doUpdate bool, columns []string, args []interface{}, uniqueColValMap map[string]interface{}) (string, []interface{}, error) {
|
|
|
|
var (
|
|
|
|
buf = builder.NewWriter()
|
|
|
|
table = statement.RefTable
|
|
|
|
tableName = statement.TableName()
|
|
|
|
)
|
|
|
|
|
|
|
|
quote := statement.dialect.Quoter().Quote
|
|
|
|
write := func(args ...string) {
|
|
|
|
for _, arg := range args {
|
|
|
|
_, _ = buf.WriteString(arg)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
write("MERGE INTO ", quote(tableName))
|
|
|
|
if statement.dialect.URI().DBType == schemas.MSSQL {
|
2023-03-13 06:34:31 +00:00
|
|
|
write(" WITH (HOLDLOCK)")
|
2023-03-12 11:31:08 +00:00
|
|
|
}
|
2023-03-13 06:34:31 +00:00
|
|
|
write(" AS target USING (SELECT ")
|
2023-03-12 11:31:08 +00:00
|
|
|
|
|
|
|
uniqueCols := make([]string, 0, len(uniqueColValMap))
|
|
|
|
for colName := range uniqueColValMap {
|
|
|
|
uniqueCols = append(uniqueCols, colName)
|
|
|
|
}
|
|
|
|
for i, colName := range uniqueCols {
|
|
|
|
if err := statement.WriteArg(buf, uniqueColValMap[colName]); err != nil {
|
|
|
|
return "", nil, err
|
|
|
|
}
|
|
|
|
write(" AS ", quote(colName))
|
|
|
|
if i < len(uniqueCols)-1 {
|
|
|
|
write(", ")
|
|
|
|
}
|
|
|
|
}
|
2023-03-13 06:34:31 +00:00
|
|
|
|
|
|
|
var updateColumns []string
|
2023-03-13 10:30:03 +00:00
|
|
|
var updateArgs []interface{}
|
2023-03-13 06:34:31 +00:00
|
|
|
if doUpdate {
|
|
|
|
updateColumns = make([]string, 0, len(columns))
|
2023-03-13 10:30:03 +00:00
|
|
|
updateArgs = make([]interface{}, 0, len(columns))
|
|
|
|
for i, column := range columns {
|
2023-03-13 06:34:31 +00:00
|
|
|
if _, has := uniqueColValMap[schemas.CommonQuoter.Trim(column)]; has {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
updateColumns = append(updateColumns, quote(column))
|
2023-03-13 10:30:03 +00:00
|
|
|
updateArgs = append(updateArgs, args[i])
|
2023-03-13 06:34:31 +00:00
|
|
|
}
|
|
|
|
doUpdate = doUpdate && (len(updateColumns) > 0)
|
|
|
|
}
|
|
|
|
|
2023-03-12 11:31:08 +00:00
|
|
|
write(") AS src ON (")
|
|
|
|
|
|
|
|
countUniques := 0
|
|
|
|
for _, index := range table.Indexes {
|
|
|
|
if index.Type != schemas.UniqueType {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
if countUniques > 0 {
|
|
|
|
write(" OR ")
|
|
|
|
}
|
|
|
|
countUniques++
|
|
|
|
write("(")
|
2023-03-13 10:44:56 +00:00
|
|
|
write("src.", quote(index.Cols[0]), " = target.", quote(index.Cols[0]))
|
2023-03-12 11:31:08 +00:00
|
|
|
for _, col := range index.Cols[1:] {
|
2023-03-13 10:44:56 +00:00
|
|
|
write(" AND src.", quote(col), " = target.", quote(col))
|
2023-03-12 11:31:08 +00:00
|
|
|
}
|
|
|
|
write(")")
|
|
|
|
}
|
2023-03-13 06:34:31 +00:00
|
|
|
write(")")
|
2023-03-12 11:31:08 +00:00
|
|
|
if doUpdate {
|
2023-03-13 06:34:31 +00:00
|
|
|
write(" WHEN MATCHED THEN UPDATE SET ")
|
2023-03-13 10:30:03 +00:00
|
|
|
write("target.", quote(updateColumns[0]), " = ?")
|
|
|
|
buf.Append(updateArgs[0])
|
|
|
|
for i, col := range updateColumns[1:] {
|
|
|
|
write(", target.", quote(col), " = ?")
|
|
|
|
buf.Append(updateArgs[i+1])
|
2023-03-13 06:34:31 +00:00
|
|
|
}
|
2023-03-12 11:31:08 +00:00
|
|
|
}
|
2023-03-13 06:34:31 +00:00
|
|
|
write(" WHEN NOT MATCHED THEN INSERT ")
|
2023-03-13 10:30:03 +00:00
|
|
|
includeAutoIncrement := statement.includeAutoIncrement(columns)
|
2023-03-13 10:44:56 +00:00
|
|
|
if len(columns) == 0 && statement.dialect.URI().DBType == schemas.MSSQL {
|
2023-03-13 10:30:03 +00:00
|
|
|
write(" DEFAULT VALUES ")
|
|
|
|
} else {
|
|
|
|
// We have some values - Write the column names we need to insert:
|
|
|
|
write(" (")
|
|
|
|
if includeAutoIncrement {
|
|
|
|
columns = append(columns, table.AutoIncrement)
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := statement.dialect.Quoter().JoinWrite(buf.Builder, append(columns, statement.ExprColumns.ColNames()...), ","); err != nil {
|
|
|
|
return "", nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
write(")")
|
|
|
|
if err := statement.genInsertValuesValues(buf, includeAutoIncrement, columns, args); err != nil {
|
|
|
|
return "", nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
if err := statement.writeInsertOutput(buf.Builder, table); err != nil {
|
2023-03-12 11:31:08 +00:00
|
|
|
return "", nil, err
|
|
|
|
}
|
2023-03-13 10:30:03 +00:00
|
|
|
|
|
|
|
write(";")
|
2023-03-12 11:31:08 +00:00
|
|
|
return buf.String(), buf.Args(), nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// GenUpsertMapSQL generates insert map SQL
|
|
|
|
func (statement *Statement) GenUpsertMapSQL(doUpdate bool, columns []string, args []interface{}, uniqueColValMap map[string]interface{}) (string, []interface{}, error) {
|
|
|
|
if statement.dialect.URI().DBType == schemas.MSSQL ||
|
|
|
|
statement.dialect.URI().DBType == schemas.DAMENG ||
|
|
|
|
statement.dialect.URI().DBType == schemas.ORACLE {
|
|
|
|
return statement.genMergeMapSQL(doUpdate, columns, args, uniqueColValMap)
|
|
|
|
}
|
|
|
|
var (
|
|
|
|
buf = builder.NewWriter()
|
|
|
|
exprs = statement.ExprColumns
|
|
|
|
table = statement.RefTable
|
|
|
|
tableName = statement.TableName()
|
|
|
|
)
|
|
|
|
quote := statement.dialect.Quoter().Quote
|
|
|
|
write := func(args ...string) {
|
|
|
|
for _, arg := range args {
|
|
|
|
_, _ = buf.WriteString(arg)
|
|
|
|
}
|
|
|
|
}
|
2023-03-13 06:34:31 +00:00
|
|
|
var updateColumns []string
|
|
|
|
if doUpdate {
|
|
|
|
updateColumns = make([]string, 0, len(columns))
|
|
|
|
for _, column := range append(columns, exprs.ColNames()...) {
|
|
|
|
if _, has := uniqueColValMap[schemas.CommonQuoter.Trim(column)]; has {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
updateColumns = append(updateColumns, quote(column))
|
|
|
|
}
|
|
|
|
doUpdate = doUpdate && (len(updateColumns) > 0)
|
|
|
|
}
|
2023-03-12 11:31:08 +00:00
|
|
|
|
|
|
|
if statement.dialect.URI().DBType == schemas.MYSQL && !doUpdate {
|
|
|
|
write("INSERT IGNORE INTO ", quote(tableName), " (")
|
|
|
|
} else {
|
|
|
|
write("INSERT INTO ", quote(tableName), " (")
|
|
|
|
}
|
|
|
|
if err := statement.dialect.Quoter().JoinWrite(buf.Builder, append(columns, exprs.ColNames()...), ","); err != nil {
|
|
|
|
return "", nil, err
|
|
|
|
}
|
|
|
|
write(")")
|
|
|
|
|
|
|
|
if err := statement.genInsertValuesValues(buf, false, columns, args); err != nil {
|
|
|
|
return "", nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
switch statement.dialect.URI().DBType {
|
|
|
|
case schemas.SQLITE, schemas.POSTGRES:
|
2023-03-13 06:34:31 +00:00
|
|
|
write(" ON CONFLICT DO ")
|
2023-03-12 11:31:08 +00:00
|
|
|
if doUpdate {
|
2023-03-13 06:34:31 +00:00
|
|
|
write("UPDATE SET ", updateColumns[0], " = excluded.", updateColumns[0])
|
|
|
|
for _, column := range updateColumns[1:] {
|
|
|
|
write(", ", column, " = excluded.", column)
|
2023-03-12 11:31:08 +00:00
|
|
|
}
|
2023-03-13 06:34:31 +00:00
|
|
|
} else {
|
|
|
|
write("NOTHING")
|
2023-03-12 11:31:08 +00:00
|
|
|
}
|
|
|
|
case schemas.MYSQL:
|
|
|
|
if doUpdate {
|
2023-03-13 06:34:31 +00:00
|
|
|
// FIXME: mysql >= 8.0.19 should use table alias
|
|
|
|
write(" ON DUPLICATE KEY ")
|
2023-03-13 09:35:33 +00:00
|
|
|
write("UPDATE ", updateColumns[0], " = VALUES(", updateColumns[0], ")")
|
2023-03-13 06:34:31 +00:00
|
|
|
for _, column := range updateColumns[1:] {
|
|
|
|
write(", ", column, " = VALUES(", column, ")")
|
|
|
|
}
|
2023-03-13 09:35:33 +00:00
|
|
|
if len(table.AutoIncrement) > 0 {
|
|
|
|
write(", ", quote(table.AutoIncrement), " = ", quote(table.AutoIncrement))
|
|
|
|
}
|
2023-03-12 11:31:08 +00:00
|
|
|
}
|
|
|
|
default:
|
|
|
|
return "", nil, fmt.Errorf("unimplemented") // FIXME: UPSERT
|
|
|
|
}
|
|
|
|
|
2023-03-13 09:35:33 +00:00
|
|
|
if len(table.AutoIncrement) > 0 &&
|
|
|
|
(statement.dialect.URI().DBType == schemas.POSTGRES ||
|
|
|
|
statement.dialect.URI().DBType == schemas.SQLITE) {
|
|
|
|
write(" RETURNING ")
|
2023-03-12 11:31:08 +00:00
|
|
|
if err := statement.dialect.Quoter().QuoteTo(buf.Builder, table.AutoIncrement); err != nil {
|
|
|
|
return "", nil, err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return buf.String(), buf.Args(), nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (statement *Statement) genMergeMapSQL(doUpdate bool, columns []string, args []interface{}, uniqueColValMap map[string]interface{}) (string, []interface{}, error) {
|
|
|
|
var (
|
|
|
|
buf = builder.NewWriter()
|
|
|
|
table = statement.RefTable
|
|
|
|
exprs = statement.ExprColumns
|
|
|
|
tableName = statement.TableName()
|
|
|
|
)
|
|
|
|
|
|
|
|
quote := statement.dialect.Quoter().Quote
|
|
|
|
write := func(args ...string) {
|
|
|
|
for _, arg := range args {
|
|
|
|
_, _ = buf.WriteString(arg)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
write("MERGE INTO ", quote(tableName))
|
|
|
|
if statement.dialect.URI().DBType == schemas.MSSQL {
|
2023-03-13 06:34:31 +00:00
|
|
|
write(" WITH (HOLDLOCK)")
|
2023-03-12 11:31:08 +00:00
|
|
|
}
|
2023-03-13 06:34:31 +00:00
|
|
|
write(" AS target USING (SELECT ")
|
2023-03-12 11:31:08 +00:00
|
|
|
|
|
|
|
uniqueCols := make([]string, 0, len(uniqueColValMap))
|
|
|
|
for colName := range uniqueColValMap {
|
|
|
|
uniqueCols = append(uniqueCols, colName)
|
|
|
|
}
|
|
|
|
for i, colName := range uniqueCols {
|
|
|
|
if err := statement.WriteArg(buf, uniqueColValMap[colName]); err != nil {
|
|
|
|
return "", nil, err
|
|
|
|
}
|
|
|
|
write(" AS ", quote(colName))
|
|
|
|
if i < len(uniqueCols)-1 {
|
|
|
|
write(", ")
|
|
|
|
}
|
|
|
|
}
|
2023-03-13 06:34:31 +00:00
|
|
|
var updateColumns []string
|
2023-03-13 10:30:03 +00:00
|
|
|
var updateArgs []interface{}
|
2023-03-13 06:34:31 +00:00
|
|
|
if doUpdate {
|
|
|
|
updateColumns = make([]string, 0, len(columns))
|
2023-03-13 10:30:03 +00:00
|
|
|
for _, expr := range exprs {
|
|
|
|
if _, has := uniqueColValMap[schemas.CommonQuoter.Trim(expr.ColName)]; has {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
updateColumns = append(updateColumns, quote(expr.ColName))
|
|
|
|
updateArgs = append(updateArgs, expr.Arg)
|
|
|
|
}
|
|
|
|
for i, column := range columns {
|
2023-03-13 06:34:31 +00:00
|
|
|
if _, has := uniqueColValMap[schemas.CommonQuoter.Trim(column)]; has {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
updateColumns = append(updateColumns, quote(column))
|
2023-03-13 10:30:03 +00:00
|
|
|
updateArgs = append(updateArgs, args[i])
|
2023-03-13 06:34:31 +00:00
|
|
|
}
|
|
|
|
doUpdate = doUpdate && (len(updateColumns) > 0)
|
|
|
|
}
|
|
|
|
|
2023-03-12 11:31:08 +00:00
|
|
|
write(") AS src ON (")
|
|
|
|
|
|
|
|
countUniques := 0
|
|
|
|
for _, index := range table.Indexes {
|
|
|
|
if index.Type != schemas.UniqueType {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
if countUniques > 0 {
|
|
|
|
write(" OR ")
|
|
|
|
}
|
|
|
|
countUniques++
|
|
|
|
write("(")
|
2023-03-13 10:30:03 +00:00
|
|
|
write("src.", quote(index.Cols[0]), " = target.", quote(index.Cols[0]))
|
2023-03-12 11:31:08 +00:00
|
|
|
for _, col := range index.Cols[1:] {
|
2023-03-13 10:30:03 +00:00
|
|
|
write(" AND src.", quote(col), " = target.", quote(col))
|
2023-03-12 11:31:08 +00:00
|
|
|
}
|
|
|
|
write(")")
|
|
|
|
}
|
2023-03-13 06:34:31 +00:00
|
|
|
write(")")
|
2023-03-12 11:31:08 +00:00
|
|
|
if doUpdate {
|
2023-03-13 06:34:31 +00:00
|
|
|
write(" WHEN MATCHED THEN UPDATE SET ")
|
2023-03-13 10:30:03 +00:00
|
|
|
write("target.", quote(updateColumns[0]), " = ?")
|
|
|
|
buf.Append(updateArgs[0])
|
|
|
|
for i, col := range updateColumns[1:] {
|
|
|
|
write(", target.", quote(col), " = ?")
|
|
|
|
buf.Append(updateArgs[i+1])
|
2023-03-13 06:34:31 +00:00
|
|
|
}
|
2023-03-12 11:31:08 +00:00
|
|
|
}
|
2023-03-13 06:34:31 +00:00
|
|
|
write(" WHEN NOT MATCHED THEN INSERT ")
|
2023-03-12 11:31:08 +00:00
|
|
|
if err := statement.dialect.Quoter().JoinWrite(buf.Builder, append(columns, exprs.ColNames()...), ","); err != nil {
|
|
|
|
return "", nil, err
|
|
|
|
}
|
|
|
|
write(")")
|
|
|
|
|
|
|
|
if err := statement.genInsertValuesValues(buf, false, columns, args); err != nil {
|
|
|
|
return "", nil, err
|
|
|
|
}
|
2023-03-13 10:30:03 +00:00
|
|
|
write(";")
|
2023-03-12 11:31:08 +00:00
|
|
|
|
|
|
|
return buf.String(), buf.Args(), nil
|
|
|
|
}
|