add struct support for *Insert/*Save/*Replace/*Update/Where/Data functions

This commit is contained in:
John
2019-02-26 01:19:01 +08:00
parent 49a1308875
commit 66efbe63f0
8 changed files with 129 additions and 58 deletions

View File

@ -37,7 +37,7 @@ type DB interface {
doQuery(link dbLink, query string, args ...interface{}) (rows *sql.Rows, err error)
doExec(link dbLink, query string, args ...interface{}) (result sql.Result, err error)
doPrepare(link dbLink, query string) (*sql.Stmt, error)
doInsert(link dbLink, table string, data Map, option int) (result sql.Result, err error)
doInsert(link dbLink, table string, data interface{}, option int) (result sql.Result, err error)
doBatchInsert(link dbLink, table string, list List, batch int, option int) (result sql.Result, err error)
doUpdate(link dbLink, table string, data interface{}, condition interface{}, args ...interface{}) (result sql.Result, err error)
doDelete(link dbLink, table string, condition interface{}, args ...interface{}) (result sql.Result, err error)
@ -61,9 +61,9 @@ type DB interface {
Begin() (*TX, error)
// 数据表插入/更新/保存操作
Insert(table string, data Map) (sql.Result, error)
Replace(table string, data Map) (sql.Result, error)
Save(table string, data Map) (sql.Result, error)
Insert(table string, data interface{}) (sql.Result, error)
Replace(table string, data interface{}) (sql.Result, error)
Save(table string, data interface{}) (sql.Result, error)
// 数据表插入/更新/保存操作(批量)
BatchInsert(table string, list List, batch int) (sql.Result, error)

View File

@ -14,9 +14,8 @@ import (
"github.com/gogf/gf/g/container/gvar"
"github.com/gogf/gf/g/os/gcache"
"github.com/gogf/gf/g/os/gtime"
"github.com/gogf/gf/g/util/gconv"
"github.com/gogf/gf/g/text/gregex"
"reflect"
"github.com/gogf/gf/g/util/gconv"
"strings"
)
@ -235,32 +234,35 @@ func (bs *dbBase) Begin() (*TX, error) {
}
// CURD操作:单条数据写入, 仅仅执行写入操作,如果存在冲突的主键或者唯一索引,那么报错返回
func (bs *dbBase) Insert(table string, data Map) (sql.Result, error) {
func (bs *dbBase) Insert(table string, data interface{}) (sql.Result, error) {
return bs.db.doInsert(nil, table, data, OPTION_INSERT)
}
// CURD操作:单条数据写入, 如果数据存在(主键或者唯一索引),那么删除后重新写入一条
func (bs *dbBase) Replace(table string, data Map) (sql.Result, error) {
func (bs *dbBase) Replace(table string, data interface{}) (sql.Result, error) {
return bs.db.doInsert(nil, table, data, OPTION_REPLACE)
}
// CURD操作:单条数据写入, 如果数据存在(主键或者唯一索引),那么更新,否则写入一条新数据
func (bs *dbBase) Save(table string, data Map) (sql.Result, error) {
func (bs *dbBase) Save(table string, data interface{}) (sql.Result, error) {
return bs.db.doInsert(nil, table, data, OPTION_SAVE)
}
// insert、replace, save ignore操作
// 0: insert: 仅仅执行写入操作,如果存在冲突的主键或者唯一索引,那么报错返回
// 1: replace: 如果数据存在(主键或者唯一索引),那么删除后重新写入一条
// 2: save: 如果数据存在(主键或者唯一索引),那么更新,否则写入一条新数据
// 3: ignore: 如果数据存在(主键或者唯一索引),那么什么也不做
func (bs *dbBase) doInsert(link dbLink, table string, data Map, option int) (result sql.Result, err error) {
// insert、replace, save ignore操作
// 0: insert: 仅仅执行写入操作,如果存在冲突的主键或者唯一索引,那么报错返回;
// 1: replace: 如果数据存在(主键或者唯一索引),那么删除后重新写入一条;
// 2: save: 如果数据存在(主键或者唯一索引),那么更新,否则写入一条新数据;
// 3: ignore: 如果数据存在(主键或者唯一索引),那么什么也不做;
//
// 参数data支持任意map类型或者struct类型。
func (bs *dbBase) doInsert(link dbLink, table string, data interface{}, option int) (result sql.Result, err error) {
var fields []string
var values []string
var params []interface{}
charl, charr := bs.db.getChars()
for k, v := range data {
fields = append(fields, charl + k + charr)
charL, charR := bs.db.getChars()
dataMap := gconv.Map(data)
for k, v := range dataMap {
fields = append(fields, charL + k + charR)
values = append(values, "?")
params = append(params, v)
}
@ -268,11 +270,11 @@ func (bs *dbBase) doInsert(link dbLink, table string, data Map, option int) (res
updateStr := ""
if option == OPTION_SAVE {
var updates []string
for k, _ := range data {
for k, _ := range dataMap {
updates = append(updates,
fmt.Sprintf("%s%s%s=VALUES(%s%s%s)",
charl, k, charr,
charl, k, charr,
charL, k, charR,
charL, k, charR,
),
)
}
@ -325,8 +327,8 @@ func (bs *dbBase) doBatchInsert(link dbLink, table string, list List, batch int,
values = append(values, "?")
}
batchResult := new(batchSqlResult)
charl, charr := bs.db.getChars()
keyStr := charl + strings.Join(keys, charl + "," + charr) + charr
charL, charR := bs.db.getChars()
keyStr := charL + strings.Join(keys, charL + "," + charR) + charR
valueHolderStr := "(" + strings.Join(values, ",") + ")"
// 操作判断
operation := getInsertOperationByOption(option)
@ -336,8 +338,8 @@ func (bs *dbBase) doBatchInsert(link dbLink, table string, list List, batch int,
for _, k := range keys {
updates = append(updates,
fmt.Sprintf("%s%s%s=VALUES(%s%s%s)",
charl, k, charr,
charl, k, charr,
charL, k, charR,
charL, k, charR,
),
)
}
@ -401,18 +403,16 @@ func (bs *dbBase) Update(table string, data interface{}, condition interface{},
func (bs *dbBase) doUpdate(link dbLink, table string, data interface{}, condition interface{}, args ...interface{}) (result sql.Result, err error) {
params := ([]interface{})(nil)
updates := ""
charl, charr := bs.db.getChars()
refValue := reflect.ValueOf(data)
if refValue.Kind() == reflect.Map {
charL, charR := bs.db.getChars()
if s, ok := data.(string); ok {
updates = s
} else {
var fields []string
keys := refValue.MapKeys()
for _, k := range keys {
fields = append(fields, fmt.Sprintf("%s%s%s=?", charl, k, charr))
params = append(params, gconv.String(refValue.MapIndex(k).Interface()))
for k, v := range gconv.Map(data) {
fields = append(fields, fmt.Sprintf("%s%s%s=?", charL, k, charR))
params = append(params, gconv.String(v))
}
updates = strings.Join(fields, ",")
} else {
updates = gconv.String(data)
}
for _, v := range args {
params = append(params, gconv.String(v))

View File

@ -24,12 +24,12 @@ import (
func formatCondition(where interface{}, args []interface{}) (string, []interface{}) {
// 条件字符串处理
buffer := bytes.NewBuffer(nil)
if reflect.ValueOf(where).Kind() == reflect.Map {
ks := reflect.ValueOf(where).MapKeys()
vs := reflect.ValueOf(where)
for _, k := range ks {
key := gconv.String(k.Interface())
value := gconv.String(vs.MapIndex(k).Interface())
if s, ok := where.(string); ok {
buffer.WriteString(s)
} else {
for k, v := range gconv.Map(where) {
key := gconv.String(k)
value := gconv.String(v)
if buffer.Len() > 0 {
buffer.WriteString(" AND ")
}
@ -39,8 +39,6 @@ func formatCondition(where interface{}, args []interface{}) (string, []interface
buffer.WriteString(key + "='" + value + "'")
}
}
} else {
buffer.Write(gconv.Bytes(where))
}
if buffer.Len() == 0 {
buffer.WriteString("1=1")
@ -57,6 +55,7 @@ func formatCondition(where interface{}, args []interface{}) (string, []interface
kind = rv.Kind()
}
switch kind {
// Where条件参数支持slice类型
case reflect.Slice: fallthrough
case reflect.Array:
for i := 0; i < rv.Len(); i++ {
@ -109,13 +108,13 @@ func formatError(err error, query string, args ...interface{}) error {
// 根据insert选项获得操作名称
func getInsertOperationByOption(option int) string {
oper := "INSERT"
operator := "INSERT"
switch option {
case OPTION_REPLACE:
oper = "REPLACE"
operator = "REPLACE"
case OPTION_SAVE:
case OPTION_IGNORE:
oper = "INSERT IGNORE"
operator = "INSERT IGNORE"
}
return oper
return operator
}

View File

@ -200,13 +200,14 @@ func (md *Model) Cache(time int, name ... string) *Model {
return model
}
// 链式操作操作数据记录项可以是string/Map, 也可以是key,value,key,value,...
// 链式操作,操作数据记录项,参数data类型可以是 string/map/struct/*struct ,
// 也可以是key,value,key,value,...
func (md *Model) Data(data ...interface{}) (*Model) {
model := md.Clone()
if len(data) > 1 {
m := make(map[string]interface{})
for i := 0; i < len(data); i += 2 {
m[gconv.String(data[i])] = data[i+1]
m[gconv.String(data[i])] = data[i + 1]
}
model.data = m
} else {
@ -223,6 +224,7 @@ func (md *Model) Data(data ...interface{}) (*Model) {
kind = rv.Kind()
}
switch kind {
// 如果是slice那么转换为List类型
case reflect.Slice: fallthrough
case reflect.Array:
list := make(List, rv.Len())
@ -230,8 +232,9 @@ func (md *Model) Data(data ...interface{}) (*Model) {
list[i] = gconv.Map(rv.Index(i).Interface())
}
model.data = list
case reflect.Map:
model.data = gconv.Map(data[0])
case reflect.Map: fallthrough
case reflect.Struct:
model.data = Map(gconv.Map(data[0]))
default:
model.data = data[0]
}

View File

@ -85,8 +85,8 @@ func (bs *dbBase) getTableFields(table string) (fields map[string]string, err er
// 缓存不存在时会查询数据表结构,缓存后不过期,直至程序重启(重新部署)
v := bs.cache.GetOrSetFunc("table_fields_" + table, func() interface{} {
result := (Result)(nil)
charl, charr := bs.db.getChars()
result, err = bs.GetAll(fmt.Sprintf(`SHOW COLUMNS FROM %s%s%s`, charl, table, charr))
charL, charR := bs.db.getChars()
result, err = bs.GetAll(fmt.Sprintf(`SHOW COLUMNS FROM %s%s%s`, charL, table, charR))
if err != nil {
return nil
}

View File

@ -100,17 +100,17 @@ func (tx *TX) GetCount(query string, args ...interface{}) (int, error) {
}
// CURD操作:单条数据写入, 仅仅执行写入操作,如果存在冲突的主键或者唯一索引,那么报错返回
func (tx *TX) Insert(table string, data Map) (sql.Result, error) {
func (tx *TX) Insert(table string, data interface{}) (sql.Result, error) {
return tx.db.doInsert(tx.tx, table, data, OPTION_INSERT)
}
// CURD操作:单条数据写入, 如果数据存在(主键或者唯一索引),那么删除后重新写入一条
func (tx *TX) Replace(table string, data Map) (sql.Result, error) {
func (tx *TX) Replace(table string, data interface{}) (sql.Result, error) {
return tx.db.doInsert(tx.tx, table, data, OPTION_REPLACE)
}
// CURD操作:单条数据写入, 如果数据存在(主键或者唯一索引),那么更新,否则写入一条新数据
func (tx *TX) Save(table string, data Map) (sql.Result, error) {
func (tx *TX) Save(table string, data interface{}) (sql.Result, error) {
return tx.db.doInsert(tx.tx, table, data, OPTION_SAVE)
}

View File

@ -9,6 +9,7 @@ import (
"testing"
)
// 基本测试
func TestModel_Insert(t *testing.T) {
result, err := db.Table("user").Filter().Data(g.Map{
"id" : 1,
@ -23,6 +24,69 @@ func TestModel_Insert(t *testing.T) {
}
n, _ := result.LastInsertId()
gtest.Assert(n, 1)
result, err = db.Table("user").Filter().Data(map[interface{}]interface{} {
"id" : "2",
"uid" : "2",
"passport" : "t2",
"password" : "25d55ad283aa400af464c76d713c07ad",
"nickname" : "T2",
"create_time" : gtime.Now().String(),
}).Insert()
if err != nil {
gtest.Fatal(err)
}
n, _ = result.RowsAffected()
gtest.Assert(n, 1)
type User struct {
Id int `gconv:"id"`
Uid int `gconv:"uid"`
Passport string `json:"passport"`
Password string `gconv:"password"`
Nickname string `gconv:"nickname"`
CreateTime string `json:"create_time"`
}
result, err = db.Table("user").Filter().Data(User{
Id : 3,
Uid : 3,
Passport : "t3",
Password : "25d55ad283aa400af464c76d713c07ad",
Nickname : "T3",
CreateTime : gtime.Now().String(),
}).Insert()
if err != nil {
gtest.Fatal(err)
}
n, _ = result.RowsAffected()
gtest.Assert(n, 1)
value, err := db.Table("user").Fields("passport").Where("id=3").Value()
gtest.Assert(err, nil)
gtest.Assert(value.String(), "t3")
result, err = db.Table("user").Filter().Data(&User{
Id : 4,
Uid : 4,
Passport : "t4",
Password : "25d55ad283aa400af464c76d713c07ad",
Nickname : "T4",
CreateTime : gtime.Now().String(),
}).Insert()
if err != nil {
gtest.Fatal(err)
}
n, _ = result.RowsAffected()
gtest.Assert(n, 1)
value, err = db.Table("user").Fields("passport").Where("id=4").Value()
gtest.Assert(err, nil)
gtest.Assert(value.String(), "t4")
result, err = db.Table("user").Where("id>?", 1).Delete()
if err != nil {
gtest.Fatal(err)
}
n, _ = result.RowsAffected()
gtest.Assert(n, 3)
}
func TestModel_Batch(t *testing.T) {
@ -191,6 +255,7 @@ func TestModel_GroupBy(t *testing.T) {
gtest.Assert(result[0]["nickname"].String(), "T111")
}
// slice
func TestModel_Where1(t *testing.T) {
result, err := db.Table("user").Where("id IN(?)", g.Slice{1,3}).OrderBy("id ASC").All()
if err != nil {
@ -201,6 +266,7 @@ func TestModel_Where1(t *testing.T) {
gtest.Assert(result[1]["id"].Int(), 3)
}
// slice
func TestModel_Where2(t *testing.T) {
result, err := db.Table("user").Where("nickname=? AND id IN(?)", "T3", g.Slice{1,3}).OrderBy("id ASC").All()
if err != nil {

View File

@ -99,10 +99,13 @@ func Map(i interface{}, noTagCheck...bool) map[string]interface{} {
rt := rv.Type()
name := ""
for i := 0; i < rv.NumField(); i++ {
// 检查json tag
// 检查tag, 支持gconv, json标签, 优先使用gconv
if len(noTagCheck) == 0 || !noTagCheck[0] {
if name = rt.Field(i).Tag.Get("json"); name == "" {
name = rt.Field(i).Name
tag := rt.Field(i).Tag
if name = tag.Get("gconv"); name == "" {
if name = tag.Get("json"); name == "" {
name = rt.Field(i).Name
}
}
}
m[name] = rv.Field(i).Interface()