mirror of
https://gitee.com/johng/gf
synced 2026-06-06 16:21:40 +08:00
add automatic data key to field name mapping feature for package gdb
This commit is contained in:
@ -156,7 +156,7 @@ type DB interface {
|
||||
// Internal methods.
|
||||
// ===========================================================================
|
||||
|
||||
filterFields(schema, table string, data map[string]interface{}) map[string]interface{}
|
||||
mappingAndFilterData(schema, table string, data map[string]interface{}, filter bool) (map[string]interface{}, error)
|
||||
convertValue(fieldValue interface{}, fieldType string) interface{}
|
||||
rowsToResult(rows *sql.Rows) (Result, error)
|
||||
}
|
||||
|
||||
@ -7,6 +7,8 @@
|
||||
package gdb
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/errors/gerror"
|
||||
"github.com/gogf/gf/util/gutil"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@ -145,15 +147,50 @@ func (c *Core) convertValue(fieldValue interface{}, fieldType string) interface{
|
||||
}
|
||||
|
||||
// filterFields removes all key-value pairs which are not the field of given table.
|
||||
func (c *Core) filterFields(schema, table string, data map[string]interface{}) map[string]interface{} {
|
||||
// It must use data copy here to avoid its changing the origin data map.
|
||||
newDataMap := make(map[string]interface{}, len(data))
|
||||
if fields, err := c.DB.TableFields(table, schema); err == nil {
|
||||
for k, v := range data {
|
||||
if _, ok := fields[k]; ok {
|
||||
newDataMap[k] = v
|
||||
func (c *Core) mappingAndFilterData(schema, table string, data map[string]interface{}, filter bool) (map[string]interface{}, error) {
|
||||
if fieldsMap, err := c.DB.TableFields(table, schema); err == nil {
|
||||
fieldsKeyMap := make(map[string]interface{}, len(fieldsMap))
|
||||
for k, _ := range fieldsMap {
|
||||
fieldsKeyMap[k] = nil
|
||||
}
|
||||
// Automatic data key to table field name mapping.
|
||||
var foundKey string
|
||||
for dataKey, dataValue := range data {
|
||||
if _, ok := fieldsKeyMap[dataKey]; !ok {
|
||||
foundKey, _ = gutil.MapPossibleItemByKey(fieldsKeyMap, dataKey)
|
||||
if foundKey != "" {
|
||||
data[foundKey] = dataValue
|
||||
delete(data, dataKey)
|
||||
} else if !filter {
|
||||
if schema != "" {
|
||||
return nil, gerror.Newf(`no column of name "%s" found for table "%s" in schema "%s"`, dataKey, table, schema)
|
||||
}
|
||||
return nil, gerror.Newf(`no column of name "%s" found for table "%s"`, dataKey, table)
|
||||
}
|
||||
}
|
||||
}
|
||||
// Data filtering.
|
||||
if filter {
|
||||
for dataKey, _ := range data {
|
||||
if _, ok := fieldsMap[dataKey]; !ok {
|
||||
delete(data, dataKey)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return newDataMap
|
||||
return data, nil
|
||||
}
|
||||
|
||||
//// filterFields removes all key-value pairs which are not the field of given table.
|
||||
//func (c *Core) filterFields(schema, table string, data map[string]interface{}) map[string]interface{} {
|
||||
// // It must use data copy here to avoid its changing the origin data map.
|
||||
// newDataMap := make(map[string]interface{}, len(data))
|
||||
// if fields, err := c.DB.TableFields(table, schema); err == nil {
|
||||
// for k, v := range data {
|
||||
// if _, ok := fields[k]; ok {
|
||||
// newDataMap[k] = v
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// return newDataMap
|
||||
//}
|
||||
@ -172,10 +172,14 @@ func (m *Model) doInsertWithOption(option int, data ...interface{}) (result sql.
|
||||
list[k] = v
|
||||
}
|
||||
}
|
||||
newData, err := m.filterDataForInsertOrUpdate(list)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return m.db.DoBatchInsert(
|
||||
m.getLink(true),
|
||||
m.tables,
|
||||
m.filterDataForInsertOrUpdate(list),
|
||||
newData,
|
||||
option,
|
||||
batch,
|
||||
)
|
||||
@ -192,10 +196,14 @@ func (m *Model) doInsertWithOption(option int, data ...interface{}) (result sql.
|
||||
data[fieldNameUpdate] = nowString
|
||||
}
|
||||
}
|
||||
newData, err := m.filterDataForInsertOrUpdate(data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return m.db.DoInsert(
|
||||
m.getLink(true),
|
||||
m.tables,
|
||||
m.filterDataForInsertOrUpdate(data),
|
||||
newData,
|
||||
option,
|
||||
)
|
||||
}
|
||||
|
||||
@ -73,10 +73,14 @@ func (m *Model) Update(dataAndWhere ...interface{}) (result sql.Result, err erro
|
||||
updateData = updates
|
||||
}
|
||||
}
|
||||
newData, err := m.filterDataForInsertOrUpdate(updateData)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return m.db.DoUpdate(
|
||||
m.getLink(true),
|
||||
m.tables,
|
||||
m.filterDataForInsertOrUpdate(updateData),
|
||||
newData,
|
||||
conditionWhere+conditionExtra,
|
||||
m.mergeArguments(conditionArgs)...,
|
||||
)
|
||||
|
||||
@ -28,27 +28,33 @@ func (m *Model) getModel() *Model {
|
||||
|
||||
// filterDataForInsertOrUpdate does filter feature with data for inserting/updating operations.
|
||||
// Note that, it does not filter list item, which is also type of map, for "omit empty" feature.
|
||||
func (m *Model) filterDataForInsertOrUpdate(data interface{}) interface{} {
|
||||
func (m *Model) filterDataForInsertOrUpdate(data interface{}) (interface{}, error) {
|
||||
var err error
|
||||
switch value := data.(type) {
|
||||
case List:
|
||||
for k, item := range value {
|
||||
value[k] = m.doFilterDataMapForInsertOrUpdate(item, false)
|
||||
value[k], err = m.doMappingAndFilterForInsertOrUpdateDataMap(item, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return value
|
||||
return value, nil
|
||||
|
||||
case Map:
|
||||
return m.doFilterDataMapForInsertOrUpdate(value, true)
|
||||
return m.doMappingAndFilterForInsertOrUpdateDataMap(value, true)
|
||||
|
||||
default:
|
||||
return data
|
||||
return data, nil
|
||||
}
|
||||
}
|
||||
|
||||
// doFilterDataMapForInsertOrUpdate does the filter features for map.
|
||||
// doMappingAndFilterForInsertOrUpdateDataMap does the filter features for map.
|
||||
// Note that, it does not filter list item, which is also type of map, for "omit empty" feature.
|
||||
func (m *Model) doFilterDataMapForInsertOrUpdate(data Map, allowOmitEmpty bool) Map {
|
||||
if m.filter {
|
||||
data = m.db.filterFields(m.schema, m.tables, data)
|
||||
func (m *Model) doMappingAndFilterForInsertOrUpdateDataMap(data Map, allowOmitEmpty bool) (Map, error) {
|
||||
var err error
|
||||
data, err = m.db.mappingAndFilterData(m.schema, m.tables, data, m.filter)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Remove key-value pairs of which the value is empty.
|
||||
if allowOmitEmpty && m.option&OPTION_OMITEMPTY > 0 {
|
||||
@ -103,7 +109,7 @@ func (m *Model) doFilterDataMapForInsertOrUpdate(data Map, allowOmitEmpty bool)
|
||||
delete(data, v)
|
||||
}
|
||||
}
|
||||
return data
|
||||
return data, nil
|
||||
}
|
||||
|
||||
// getLink returns the underlying database link object with configured <linkType> attribute.
|
||||
|
||||
@ -184,6 +184,7 @@ func Test_DB_Insert(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
// Fix issue: https://github.com/gogf/gf/issues/819
|
||||
func Test_DB_Insert_WithStructAndSliceAttribute(t *testing.T) {
|
||||
table := createTable()
|
||||
defer dropTable(table)
|
||||
@ -211,6 +212,91 @@ func Test_DB_Insert_WithStructAndSliceAttribute(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func Test_DB_Insert_KeyFieldNameMapping(t *testing.T) {
|
||||
table := createTable()
|
||||
defer dropTable(table)
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
type User struct {
|
||||
Id int
|
||||
Passport string
|
||||
Password string
|
||||
Nickname string
|
||||
CreateTime string
|
||||
}
|
||||
data := User{
|
||||
Id: 1,
|
||||
Passport: "user_1",
|
||||
Password: "pass_1",
|
||||
Nickname: "name_1",
|
||||
CreateTime: "2020-10-10 12:00:01",
|
||||
}
|
||||
_, 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.CreateTime)
|
||||
t.Assert(one["nickname"], data.Nickname)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_DB_Upadte_KeyFieldNameMapping(t *testing.T) {
|
||||
table := createInitTable()
|
||||
defer dropTable(table)
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
type User struct {
|
||||
Id int
|
||||
Passport string
|
||||
Password string
|
||||
Nickname string
|
||||
CreateTime string
|
||||
}
|
||||
data := User{
|
||||
Id: 1,
|
||||
Passport: "user_10",
|
||||
Password: "pass_10",
|
||||
Nickname: "name_10",
|
||||
CreateTime: "2020-10-10 12:00:01",
|
||||
}
|
||||
_, err := db.Update(table, data, "id=1")
|
||||
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.CreateTime)
|
||||
t.Assert(one["nickname"], data.Nickname)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_DB_Insert_KeyFieldNameMapping_Error(t *testing.T) {
|
||||
table := createTable()
|
||||
defer dropTable(table)
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
type User struct {
|
||||
Id int
|
||||
Passport string
|
||||
Password string
|
||||
Nickname string
|
||||
CreateTime string
|
||||
NoneExistField string
|
||||
}
|
||||
data := User{
|
||||
Id: 1,
|
||||
Passport: "user_1",
|
||||
Password: "pass_1",
|
||||
Nickname: "name_1",
|
||||
CreateTime: "2020-10-10 12:00:01",
|
||||
}
|
||||
_, err := db.Insert(table, data)
|
||||
t.AssertNE(err, nil)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_DB_InsertIgnore(t *testing.T) {
|
||||
table := createInitTable()
|
||||
defer dropTable(table)
|
||||
|
||||
@ -97,6 +97,7 @@ func Test_Model_Insert(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
// Fix issue: https://github.com/gogf/gf/issues/819
|
||||
func Test_Model_Insert_WithStructAndSliceAttribute(t *testing.T) {
|
||||
table := createTable()
|
||||
defer dropTable(table)
|
||||
@ -123,6 +124,91 @@ func Test_Model_Insert_WithStructAndSliceAttribute(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Model_Insert_KeyFieldNameMapping(t *testing.T) {
|
||||
table := createTable()
|
||||
defer dropTable(table)
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
type User struct {
|
||||
Id int
|
||||
Passport string
|
||||
Password string
|
||||
Nickname string
|
||||
CreateTime string
|
||||
}
|
||||
data := User{
|
||||
Id: 1,
|
||||
Passport: "user_1",
|
||||
Password: "pass_1",
|
||||
Nickname: "name_1",
|
||||
CreateTime: "2020-10-10 12:00:01",
|
||||
}
|
||||
_, err := db.Model(table).Data(data).Insert()
|
||||
t.Assert(err, nil)
|
||||
|
||||
one, err := db.Model(table).FindOne(1)
|
||||
t.Assert(err, nil)
|
||||
t.Assert(one["passport"], data.Passport)
|
||||
t.Assert(one["create_time"], data.CreateTime)
|
||||
t.Assert(one["nickname"], data.Nickname)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Model_Update_KeyFieldNameMapping(t *testing.T) {
|
||||
table := createInitTable()
|
||||
defer dropTable(table)
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
type User struct {
|
||||
Id int
|
||||
Passport string
|
||||
Password string
|
||||
Nickname string
|
||||
CreateTime string
|
||||
}
|
||||
data := User{
|
||||
Id: 1,
|
||||
Passport: "user_10",
|
||||
Password: "pass_10",
|
||||
Nickname: "name_10",
|
||||
CreateTime: "2020-10-10 12:00:01",
|
||||
}
|
||||
_, err := db.Model(table).Data(data).WherePri(1).Update()
|
||||
t.Assert(err, nil)
|
||||
|
||||
one, err := db.Model(table).FindOne(1)
|
||||
t.Assert(err, nil)
|
||||
t.Assert(one["passport"], data.Passport)
|
||||
t.Assert(one["create_time"], data.CreateTime)
|
||||
t.Assert(one["nickname"], data.Nickname)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Model_Insert_KeyFieldNameMapping_Error(t *testing.T) {
|
||||
table := createTable()
|
||||
defer dropTable(table)
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
type User struct {
|
||||
Id int
|
||||
Passport string
|
||||
Password string
|
||||
Nickname string
|
||||
CreateTime string
|
||||
NoneExistFiled string
|
||||
}
|
||||
data := User{
|
||||
Id: 1,
|
||||
Passport: "user_1",
|
||||
Password: "pass_1",
|
||||
Nickname: "name_1",
|
||||
CreateTime: "2020-10-10 12:00:01",
|
||||
}
|
||||
_, err := db.Model(table).Data(data).Insert()
|
||||
t.AssertNE(err, nil)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Model_Insert_Time(t *testing.T) {
|
||||
table := createTable()
|
||||
defer dropTable(table)
|
||||
|
||||
@ -151,7 +151,7 @@ CREATE TABLE %s (
|
||||
}
|
||||
|
||||
func Test_SoftUpdateTime(t *testing.T) {
|
||||
table := "time_test_table"
|
||||
table := "time_test_table_" + gtime.TimestampNanoStr()
|
||||
if _, err := db.Exec(fmt.Sprintf(`
|
||||
CREATE TABLE %s (
|
||||
id int(11) NOT NULL,
|
||||
|
||||
Reference in New Issue
Block a user