From 9c48dd172c168d43e1bc096a6997e847259315f1 Mon Sep 17 00:00:00 2001 From: John Date: Sat, 10 Oct 2020 17:29:38 +0800 Subject: [PATCH] add auto-json support for slice/struct attribute for data inserting of package gdb --- database/gdb/gdb_core.go | 22 +++++------ database/gdb/gdb_driver_oracle.go | 6 +-- database/gdb/gdb_func.go | 46 ++++++++++++++++++++++- database/gdb/gdb_model_insert.go | 8 ++-- database/gdb/gdb_model_update.go | 2 +- database/gdb/gdb_z_mysql_internal_test.go | 4 +- database/gdb/gdb_z_mysql_method_test.go | 28 ++++++++++++++ database/gdb/gdb_z_mysql_model_test.go | 27 +++++++++++++ 8 files changed, 121 insertions(+), 22 deletions(-) diff --git a/database/gdb/gdb_core.go b/database/gdb/gdb_core.go index 73ea066da..05d1fbfaf 100644 --- a/database/gdb/gdb_core.go +++ b/database/gdb/gdb_core.go @@ -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)+"=?") diff --git a/database/gdb/gdb_driver_oracle.go b/database/gdb/gdb_driver_oracle.go index ce4754bc7..47d957fe5 100644 --- a/database/gdb/gdb_driver_oracle.go +++ b/database/gdb/gdb_driver_oracle.go @@ -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)) } diff --git a/database/gdb/gdb_func.go b/database/gdb/gdb_func.go index 810b5c8bf..860f4da51 100644 --- a/database/gdb/gdb_func.go +++ b/database/gdb/gdb_func.go @@ -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 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 to map type recursively. +// The parameter 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() diff --git a/database/gdb/gdb_model_insert.go b/database/gdb/gdb_model_insert.go index 358ce6f7c..95f797d66 100644 --- a/database/gdb/gdb_model_insert.go +++ b/database/gdb/gdb_model_insert.go @@ -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] diff --git a/database/gdb/gdb_model_update.go b/database/gdb/gdb_model_update.go index e80995021..24e835d8a 100644 --- a/database/gdb/gdb_model_update.go +++ b/database/gdb/gdb_model_update.go @@ -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() diff --git a/database/gdb/gdb_z_mysql_internal_test.go b/database/gdb/gdb_z_mysql_internal_test.go index 6cef77e94..c9eed95af 100644 --- a/database/gdb/gdb_z_mysql_internal_test.go +++ b/database/gdb/gdb_z_mysql_internal_test.go @@ -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)) diff --git a/database/gdb/gdb_z_mysql_method_test.go b/database/gdb/gdb_z_mysql_method_test.go index f14e6af57..f94fe1496 100644 --- a/database/gdb/gdb_z_mysql_method_test.go +++ b/database/gdb/gdb_z_mysql_method_test.go @@ -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) diff --git a/database/gdb/gdb_z_mysql_model_test.go b/database/gdb/gdb_z_mysql_model_test.go index 4fedc49d6..167ed9021 100644 --- a/database/gdb/gdb_z_mysql_model_test.go +++ b/database/gdb/gdb_z_mysql_model_test.go @@ -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)