Skip to content

Commit

Permalink
feat: support building INSERT ON DUPLICATE KEY UPDATE clause (#102)
Browse files Browse the repository at this point in the history
* feat: support building INSERT ON DUPLICATE KEY UPDATE clause

* update builder README.md about BuildInsertOnDuplicate
  • Loading branch information
cncal authored Aug 21, 2020
1 parent b74c7dc commit 4fb5126
Show file tree
Hide file tree
Showing 5 changed files with 114 additions and 6 deletions.
25 changes: 25 additions & 0 deletions builder/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -264,6 +264,31 @@ cond, vals, err := qb.BuildReplaceInsert(table, data)
db.Exec(cond, vals...)
```

#### `BuildInsertOnDuplicate`

sign: `BuildInsertOnDuplicate(table string, data []map[string]interface{}, update map[string]interface{}) (string, []interface{}, error)`

data is a slice and every element(map) in it must have the same keys:

``` go
data := []map[string]interface{}{
{
"name": "deen",
"age": 23,
},
{
"name": "Tony",
"age": 30,
},
}
update := map[string]interface{}{
"role": "primaryschoolstudent",
"rank": 5,
}
cond, vals, err := qb.BuildInsertOnDuplicate(table, data, update)
db.Exec(cond, vals...)
```

#### `NamedQuery`

sign: `func NamedQuery(sql string, data map[string]interface{}) (string, []interface{}, error)`
Expand Down
5 changes: 5 additions & 0 deletions builder/builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,11 @@ func BuildReplaceInsert(table string, data []map[string]interface{}) (string, []
return buildInsert(table, data, replaceInsert)
}

// BuildInsertOnDuplicateKey builds an INSERT ... ON DUPLICATE KEY UPDATE clause.
func BuildInsertOnDuplicate(table string, data []map[string]interface{}, update map[string]interface{}) (string, []interface{}, error) {
return buildInsertOnDuplicate(table, data, update)
}

func isStringInSlice(str string, arr []string) bool {
for _, s := range arr {
if s == str {
Expand Down
28 changes: 24 additions & 4 deletions builder/builder_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1245,13 +1245,33 @@ func TestNotLike_1(t *testing.T) {
func TestFixBug_insert_quote_field(t *testing.T) {
cond, vals, err := BuildInsert("tb", []map[string]interface{}{
{
"id": 1,
"id": 1,
"`order`": 2,
"`id`": 3, // I know this is forbidden, but just for test
"`id`": 3, // I know this is forbidden, but just for test
},
})
ass := assert.New(t)
ass.NoError(err)
ass.Equal("INSERT INTO tb (`id`,`order`,id) VALUES (?,?,?)", cond)
ass.Equal([]interface{}{3,2,1}, vals)
}
ass.Equal([]interface{}{3, 2, 1}, vals)
}

func TestInsertOnDuplicate(t *testing.T) {
cond, vals, err := BuildInsertOnDuplicate(
"tb",
[]map[string]interface{}{
{
"a": 1,
"b": 2,
"c": 3,
},
},
map[string]interface{}{
"c": 4,
},
)
ass := assert.New(t)
ass.NoError(err)
ass.Equal("INSERT INTO tb (a,b,c) VALUES (?,?,?) ON DUPLICATE KEY UPDATE c=?", cond)
ass.Equal([]interface{}{1, 2, 3, 4}, vals)
}
21 changes: 19 additions & 2 deletions builder/dao.go
Original file line number Diff line number Diff line change
Expand Up @@ -397,14 +397,31 @@ func buildInsert(table string, setMap []map[string]interface{}, insertType inser
return fmt.Sprintf(format, insertType, quoteField(table), strings.Join(fields, ","), strings.Join(sets, ",")), vals, nil
}

func buildUpdate(table string, update map[string]interface{}, conditions ...Comparable) (string, []interface{}, error) {
format := "UPDATE %s SET %s"
func buildInsertOnDuplicate(table string, data []map[string]interface{}, update map[string]interface{}) (string, []interface{}, error) {
insertCond, insertVals, err := buildInsert(table, data, commonInsert)
if err != nil {
return "", nil, err
}
sets, updateVals := resolveUpdate(update)
format := "%s ON DUPLICATE KEY UPDATE %s"
cond := fmt.Sprintf(format, insertCond, sets)
vals := append(insertVals, updateVals...)
return cond, vals, nil
}

func resolveUpdate(update map[string]interface{}) (string, []interface{}) {
keys, vals := resolveKV(update)
var sets string
for _, k := range keys {
sets += fmt.Sprintf("%s=?,", quoteField(k))
}
sets = strings.TrimRight(sets, ",")
return sets, vals
}

func buildUpdate(table string, update map[string]interface{}, conditions ...Comparable) (string, []interface{}, error) {
format := "UPDATE %s SET %s"
sets, vals := resolveUpdate(update)
cond := fmt.Sprintf(format, quoteField(table), sets)
whereString, whereVals := whereConnector("AND", conditions...)
if "" != whereString {
Expand Down
41 changes: 41 additions & 0 deletions builder/dao_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,47 @@ func TestBuildInsert(t *testing.T) {
}
}

func TestBuildInsertOnDuplicate(t *testing.T) {
var data = []struct {
table string
data []map[string]interface{}
update map[string]interface{}
outErr error
outStr string
outVals []interface{}
}{
{
table: "tb",
data: []map[string]interface{}{
{
"a": 1,
"b": 2,
"c": 3,
},
{
"a": 4,
"b": 5,
"c": 6,
},
},
update: map[string]interface{}{
"b": 7,
"c": 8,
},
outErr: nil,
outStr: "INSERT INTO tb (a,b,c) VALUES (?,?,?),(?,?,?) ON DUPLICATE KEY UPDATE b=?,c=?",
outVals: []interface{}{1, 2, 3, 4, 5, 6, 7, 8},
},
}
ass := assert.New(t)
for _, tc := range data {
cond, vals, err := buildInsertOnDuplicate(tc.table, tc.data, tc.update)
ass.Equal(tc.outErr, err)
ass.Equal(tc.outStr, cond)
ass.Equal(tc.outVals, vals)
}
}

func TestBuildUpdate(t *testing.T) {
var data = []struct {
table string
Expand Down

0 comments on commit 4fb5126

Please sign in to comment.