add auto-json support for slice/struct attribute for data inserting of package gdb

This commit is contained in:
John
2020-10-10 17:29:38 +08:00
parent 09ce105eee
commit 9c48dd172c
8 changed files with 121 additions and 22 deletions

View File

@ -431,10 +431,10 @@ func (c *Core) DoInsert(link Link, table string, data interface{}, option int, b
if _, ok := data.(apiInterfaces); ok {
return c.DB.DoBatchInsert(link, table, data, option, batch...)
} else {
dataMap = DataToMapDeep(data)
dataMap = ConvertDataForTableRecord(data)
}
case reflect.Map:
dataMap = DataToMapDeep(data)
dataMap = ConvertDataForTableRecord(data)
default:
return result, errors.New(fmt.Sprint("unsupported data type:", reflectKind))
}
@ -526,10 +526,10 @@ func (c *Core) BatchSave(table string, list interface{}, batch ...int) (sql.Resu
func (c *Core) DoBatchInsert(link Link, table string, list interface{}, option int, batch ...int) (result sql.Result, err error) {
table = c.DB.QuotePrefixTableName(table)
var (
keys []string
values []string
params []interface{}
listMap List
keys []string // Field names.
values []string // Value holder string array, like: (?,?,?)
params []interface{} // Values that will be committed to underlying database driver.
listMap List // The data list that passed from caller.
)
switch value := list.(type) {
case Result:
@ -554,10 +554,10 @@ func (c *Core) DoBatchInsert(link Link, table string, list interface{}, option i
case reflect.Slice, reflect.Array:
listMap = make(List, rv.Len())
for i := 0; i < rv.Len(); i++ {
listMap[i] = DataToMapDeep(rv.Index(i).Interface())
listMap[i] = ConvertDataForTableRecord(rv.Index(i).Interface())
}
case reflect.Map:
listMap = List{DataToMapDeep(value)}
listMap = List{ConvertDataForTableRecord(value)}
case reflect.Struct:
if v, ok := value.(apiInterfaces); ok {
var (
@ -565,11 +565,11 @@ func (c *Core) DoBatchInsert(link Link, table string, list interface{}, option i
list = make(List, len(array))
)
for i := 0; i < len(array); i++ {
list[i] = DataToMapDeep(array[i])
list[i] = ConvertDataForTableRecord(array[i])
}
listMap = list
} else {
listMap = List{DataToMapDeep(value)}
listMap = List{ConvertDataForTableRecord(value)}
}
default:
return result, errors.New(fmt.Sprint("unsupported list type:", kind))
@ -695,7 +695,7 @@ func (c *Core) DoUpdate(link Link, table string, data interface{}, condition str
case reflect.Map, reflect.Struct:
var (
fields []string
dataMap = DataToMapDeep(data)
dataMap = ConvertDataForTableRecord(data)
)
for k, v := range dataMap {
fields = append(fields, c.DB.QuoteWord(k)+"=?")

View File

@ -235,7 +235,7 @@ func (d *DriverOracle) DoInsert(link Link, table string, data interface{}, optio
case reflect.Map:
fallthrough
case reflect.Struct:
dataMap = DataToMapDeep(data)
dataMap = ConvertDataForTableRecord(data)
default:
return result, errors.New(fmt.Sprint("unsupported data type:", kind))
}
@ -355,12 +355,12 @@ func (d *DriverOracle) DoBatchInsert(link Link, table string, list interface{},
case reflect.Array:
listMap = make(List, rv.Len())
for i := 0; i < rv.Len(); i++ {
listMap[i] = DataToMapDeep(rv.Index(i).Interface())
listMap[i] = ConvertDataForTableRecord(rv.Index(i).Interface())
}
case reflect.Map:
fallthrough
case reflect.Struct:
listMap = List{Map(DataToMapDeep(list))}
listMap = List{Map(ConvertDataForTableRecord(list))}
default:
return result, errors.New(fmt.Sprint("unsupported list type:", kind))
}

View File

@ -11,6 +11,7 @@ import (
"errors"
"fmt"
"github.com/gogf/gf/internal/empty"
"github.com/gogf/gf/internal/json"
"github.com/gogf/gf/internal/utils"
"github.com/gogf/gf/os/gtime"
"github.com/gogf/gf/util/gutil"
@ -97,9 +98,52 @@ func GetInsertOperationByOption(option int) string {
return operator
}
// DataToMapDeep converts struct object to map type recursively.
// ConvertDataForTableRecord is a very important function, which does converting for any data that
// will be inserted into table as a record.
//
// The parameter <obj> should be type of *map/map/*struct/struct.
// It supports inherit struct definition for struct.
func ConvertDataForTableRecord(value interface{}) map[string]interface{} {
var (
rvValue reflect.Value
rvKind reflect.Kind
data = DataToMapDeep(value)
)
for k, v := range data {
rvValue = reflect.ValueOf(v)
rvKind = rvValue.Kind()
for rvKind == reflect.Ptr {
rvValue = rvValue.Elem()
rvKind = rvValue.Kind()
}
switch rvKind {
case reflect.Slice, reflect.Array, reflect.Map:
// It should ignore the bytes type.
if _, ok := v.([]byte); !ok {
// Convert the value to JSON.
data[k], _ = json.Marshal(v)
}
case reflect.Struct:
switch v.(type) {
case time.Time, *time.Time, gtime.Time, *gtime.Time:
continue
default:
// Use string conversion in default.
if s, ok := v.(apiString); ok {
data[k] = s.String()
} else {
// Convert the value to JSON.
data[k], _ = json.Marshal(v)
}
}
}
}
return data
}
// DataToMapDeep converts <value> to map type recursively.
// The parameter <value> should be type of *map/map/*struct/struct.
// It supports inherit struct definition for struct.
func DataToMapDeep(value interface{}) map[string]interface{} {
if v, ok := value.(apiMapStrAny); ok {
return v.MapStrAny()

View File

@ -68,11 +68,11 @@ func (m *Model) Data(data ...interface{}) *Model {
case reflect.Slice, reflect.Array:
list := make(List, rv.Len())
for i := 0; i < rv.Len(); i++ {
list[i] = DataToMapDeep(rv.Index(i).Interface())
list[i] = ConvertDataForTableRecord(rv.Index(i).Interface())
}
model.data = list
case reflect.Map:
model.data = DataToMapDeep(data[0])
model.data = ConvertDataForTableRecord(data[0])
case reflect.Struct:
if v, ok := data[0].(apiInterfaces); ok {
var (
@ -80,11 +80,11 @@ func (m *Model) Data(data ...interface{}) *Model {
list = make(List, len(array))
)
for i := 0; i < len(array); i++ {
list[i] = DataToMapDeep(array[i])
list[i] = ConvertDataForTableRecord(array[i])
}
model.data = list
} else {
model.data = DataToMapDeep(data[0])
model.data = ConvertDataForTableRecord(data[0])
}
default:
model.data = data[0]

View File

@ -59,7 +59,7 @@ func (m *Model) Update(dataAndWhere ...interface{}) (result sql.Result, err erro
}
switch refKind {
case reflect.Map, reflect.Struct:
dataMap := DataToMapDeep(m.data)
dataMap := ConvertDataForTableRecord(m.data)
gutil.MapDelete(dataMap, fieldNameCreate, fieldNameUpdate, fieldNameDelete)
if fieldNameUpdate != "" {
dataMap[fieldNameUpdate] = gtime.Now().String()

View File

@ -292,12 +292,12 @@ CREATE TABLE %s (
}
// Fix issue: https://github.com/gogf/gf/issues/819
func Test_Func_DataToMapDeep(t *testing.T) {
func Test_Func_ConvertDataForTableRecord(t *testing.T) {
type Test struct {
ResetPasswordTokenAt mysql.NullTime `orm:"reset_password_token_at"`
}
gtest.C(t, func(t *gtest.T) {
m := DataToMapDeep(new(Test))
m := ConvertDataForTableRecord(new(Test))
t.Assert(len(m), 1)
t.AssertNE(m["reset_password_token_at"], nil)
t.Assert(m["reset_password_token_at"], new(mysql.NullTime))

View File

@ -9,6 +9,7 @@ package gdb_test
import (
"fmt"
"github.com/gogf/gf/container/garray"
"github.com/gogf/gf/encoding/gparser"
"testing"
"time"
@ -183,6 +184,33 @@ func Test_DB_Insert(t *testing.T) {
})
}
func Test_DB_Insert_WithStructAndSliceAttribute(t *testing.T) {
table := createTable()
defer dropTable(table)
gtest.C(t, func(t *gtest.T) {
type Password struct {
Salt string `json:"salt"`
Pass string `json:"pass"`
}
data := g.Map{
"id": 1,
"passport": "t1",
"password": &Password{"123", "456"},
"nickname": []string{"A", "B", "C"},
"create_time": gtime.Now().String(),
}
_, err := db.Insert(table, data)
t.Assert(err, nil)
one, err := db.GetOne(fmt.Sprintf("SELECT * FROM %s WHERE id=?", table), 1)
t.Assert(err, nil)
t.Assert(one["passport"], data["passport"])
t.Assert(one["create_time"], data["create_time"])
t.Assert(one["nickname"], gparser.MustToJson(data["nickname"]))
})
}
func Test_DB_InsertIgnore(t *testing.T) {
table := createInitTable()
defer dropTable(table)

View File

@ -11,6 +11,7 @@ import (
"fmt"
"github.com/gogf/gf/container/garray"
"github.com/gogf/gf/container/gmap"
"github.com/gogf/gf/encoding/gparser"
"github.com/gogf/gf/util/gutil"
"testing"
"time"
@ -96,6 +97,32 @@ func Test_Model_Insert(t *testing.T) {
})
}
func Test_Model_Insert_WithStructAndSliceAttribute(t *testing.T) {
table := createTable()
defer dropTable(table)
gtest.C(t, func(t *gtest.T) {
type Password struct {
Salt string `json:"salt"`
Pass string `json:"pass"`
}
data := g.Map{
"id": 1,
"passport": "t1",
"password": &Password{"123", "456"},
"nickname": []string{"A", "B", "C"},
"create_time": gtime.Now().String(),
}
_, err := db.Table(table).Data(data).Insert()
t.Assert(err, nil)
one, err := db.Table(table).One("id", 1)
t.Assert(err, nil)
t.Assert(one["passport"], data["passport"])
t.Assert(one["create_time"], data["create_time"])
t.Assert(one["nickname"], gparser.MustToJson(data["nickname"]))
})
}
func Test_Model_BatchInsertWithArrayStruct(t *testing.T) {
table := createTable()
defer dropTable(table)