From dd5cd31ef506730857a20fa4f013bea2abcc015a Mon Sep 17 00:00:00 2001 From: John Date: Sat, 17 Oct 2020 18:16:13 +0800 Subject: [PATCH] add automatic data key to field name mapping feature for package gdb --- database/gdb/gdb.go | 2 +- ...gdb_structure.go => gdb_core_structure.go} | 53 ++++++++++-- database/gdb/gdb_model_insert.go | 12 ++- database/gdb/gdb_model_update.go | 6 +- database/gdb/gdb_model_utility.go | 26 +++--- database/gdb/gdb_z_mysql_method_test.go | 86 +++++++++++++++++++ database/gdb/gdb_z_mysql_model_test.go | 86 +++++++++++++++++++ database/gdb/gdb_z_mysql_soft_time_test.go | 2 +- 8 files changed, 250 insertions(+), 23 deletions(-) rename database/gdb/{gdb_structure.go => gdb_core_structure.go} (67%) diff --git a/database/gdb/gdb.go b/database/gdb/gdb.go index 089a201f4..972dfcfda 100644 --- a/database/gdb/gdb.go +++ b/database/gdb/gdb.go @@ -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) } diff --git a/database/gdb/gdb_structure.go b/database/gdb/gdb_core_structure.go similarity index 67% rename from database/gdb/gdb_structure.go rename to database/gdb/gdb_core_structure.go index a879e270d..900a7ea8c 100644 --- a/database/gdb/gdb_structure.go +++ b/database/gdb/gdb_core_structure.go @@ -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 +//} diff --git a/database/gdb/gdb_model_insert.go b/database/gdb/gdb_model_insert.go index 95f797d66..702e2760a 100644 --- a/database/gdb/gdb_model_insert.go +++ b/database/gdb/gdb_model_insert.go @@ -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, ) } diff --git a/database/gdb/gdb_model_update.go b/database/gdb/gdb_model_update.go index 24e835d8a..b3f8b1b04 100644 --- a/database/gdb/gdb_model_update.go +++ b/database/gdb/gdb_model_update.go @@ -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)..., ) diff --git a/database/gdb/gdb_model_utility.go b/database/gdb/gdb_model_utility.go index 9b93c74c1..c825671d2 100644 --- a/database/gdb/gdb_model_utility.go +++ b/database/gdb/gdb_model_utility.go @@ -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 attribute. diff --git a/database/gdb/gdb_z_mysql_method_test.go b/database/gdb/gdb_z_mysql_method_test.go index f94fe1496..a74190b37 100644 --- a/database/gdb/gdb_z_mysql_method_test.go +++ b/database/gdb/gdb_z_mysql_method_test.go @@ -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) diff --git a/database/gdb/gdb_z_mysql_model_test.go b/database/gdb/gdb_z_mysql_model_test.go index ed9aae410..50c1e2f2a 100644 --- a/database/gdb/gdb_z_mysql_model_test.go +++ b/database/gdb/gdb_z_mysql_model_test.go @@ -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) diff --git a/database/gdb/gdb_z_mysql_soft_time_test.go b/database/gdb/gdb_z_mysql_soft_time_test.go index 4d86b4246..4430ed4e0 100644 --- a/database/gdb/gdb_z_mysql_soft_time_test.go +++ b/database/gdb/gdb_z_mysql_soft_time_test.go @@ -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,