From 4fb5126a4b91fde12eb08dd833ef5425148f6077 Mon Sep 17 00:00:00 2001 From: cncal Date: Fri, 21 Aug 2020 22:40:15 +0800 Subject: [PATCH] feat: support building INSERT ON DUPLICATE KEY UPDATE clause (#102) * feat: support building INSERT ON DUPLICATE KEY UPDATE clause * update builder README.md about BuildInsertOnDuplicate --- builder/README.md | 25 +++++++++++++++++++++++++ builder/builder.go | 5 +++++ builder/builder_test.go | 28 ++++++++++++++++++++++++---- builder/dao.go | 21 +++++++++++++++++++-- builder/dao_test.go | 41 +++++++++++++++++++++++++++++++++++++++++ 5 files changed, 114 insertions(+), 6 deletions(-) diff --git a/builder/README.md b/builder/README.md index 989a928..83ad9c3 100644 --- a/builder/README.md +++ b/builder/README.md @@ -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)` diff --git a/builder/builder.go b/builder/builder.go index 6fdb667..6e43ce8 100644 --- a/builder/builder.go +++ b/builder/builder.go @@ -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 { diff --git a/builder/builder_test.go b/builder/builder_test.go index e14c038..987a6bc 100644 --- a/builder/builder_test.go +++ b/builder/builder_test.go @@ -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) -} \ No newline at end of file + 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) +} diff --git a/builder/dao.go b/builder/dao.go index 81fe7ec..c1ffdf7 100644 --- a/builder/dao.go +++ b/builder/dao.go @@ -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 { diff --git a/builder/dao_test.go b/builder/dao_test.go index eb3a080..0d08005 100644 --- a/builder/dao_test.go +++ b/builder/dao_test.go @@ -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