diff --git a/.example/database/gdb/mysql/gdb_complecated.go b/.example/database/gdb/mysql/gdb_complecated.go new file mode 100644 index 000000000..0f8a9906f --- /dev/null +++ b/.example/database/gdb/mysql/gdb_complecated.go @@ -0,0 +1,24 @@ +package main + +import ( + "github.com/gogf/gf/frame/g" +) + +func main() { + // error! + r, err := g.DB().Table("user").Where(g.Map{ + "or": g.Map{ + "nickname": "jim", + "create_time > ": "2019-10-01", + }, + "and": g.Map{ + "nickname": "tom", + "create_time > ": "2019-10-01", + }, + }).All() + if err != nil { + panic(err) + } + g.Dump(r) + +} diff --git a/.example/frame/mvc/app/model/defaults/user.go b/.example/frame/mvc/app/model/defaults/user.go new file mode 100644 index 000000000..347f9991a --- /dev/null +++ b/.example/frame/mvc/app/model/defaults/user.go @@ -0,0 +1,60 @@ +// This is auto-generated by gf cli tool. You may not really want to edit it. + +package defaults + +import ( + "database/sql" + "github.com/gogf/gf/database/gdb" + "github.com/gogf/gf/frame/g" +) + +import ( + "github.com/gogf/gf/os/gtime" +) + +// User is the golang structure for table user. +type User struct { + Id int `orm:"id,primary" json:"id"` + Passport string `orm:"passport" json:"passport"` + Password string `orm:"password" json:"password"` + Nickname string `orm:"nickname,unique" json:"nickname"` + CreateTime *gtime.Time `orm:"create_time" json:"create_time"` +} + +var ( + // TableUser is the table name of user. + TableUser = "user" + // ModelUser is the model object of user. + ModelUser = g.DB("default").Table(TableUser).Safe() +) + +// Inserts does "INSERT...INTO..." statement for inserting current object into table. +func (r *User) Insert() (result sql.Result, err error) { + return ModelUser.Data(r).Insert() +} + +// Replace does "REPLACE...INTO..." statement for inserting current object into table. +// If there's already another same record in the table (it checks using primary key or unique index), +// it deletes it and insert this one. +func (r *User) Replace() (result sql.Result, err error) { + return ModelUser.Data(r).Replace() +} + +// Save does "INSERT...INTO..." statement for inserting/updating current object into table. +// It updates the record if there's already another same record in the table +// (it checks using primary key or unique index). +func (r *User) Save() (result sql.Result, err error) { + return ModelUser.Data(r).Save() +} + +// Update does "UPDATE...WHERE..." statement for updating current object from table. +// It updates the record if there's already another same record in the table +// (it checks using primary key or unique index). +func (r *User) Update() (result sql.Result, err error) { + return ModelUser.Data(r).Where(gdb.GetWhereConditionOfStruct(r)).Update() +} + +// Delete does "DELETE FROM...WHERE..." statement for deleting current object from table. +func (r *User) Delete() (result sql.Result, err error) { + return ModelUser.Where(gdb.GetWhereConditionOfStruct(r)).Delete() +} \ No newline at end of file diff --git a/.example/frame/mvc/config.toml b/.example/frame/mvc/config.toml index 9d1acf5f0..0713b840b 100644 --- a/.example/frame/mvc/config.toml +++ b/.example/frame/mvc/config.toml @@ -1,4 +1,11 @@ viewpath = "/home/www/templates" + +# MySQL数据库配置 +[database] + debug = true + link = "mysql:root:12345678@tcp(127.0.0.1:3306)/test" + + [redis] disk = "127.0.0.1:6379,0" cache = "127.0.0.1:6379,1" \ No newline at end of file diff --git a/.example/frame/mvc/main.go b/.example/frame/mvc/main.go index d4a71f99c..d02b76620 100644 --- a/.example/frame/mvc/main.go +++ b/.example/frame/mvc/main.go @@ -1,15 +1,13 @@ package main import ( - _ "github.com/gogf/gf/.example/frame/mvc/controller/demo" - _ "github.com/gogf/gf/.example/frame/mvc/controller/stats" - "github.com/gogf/gf/frame/g" + "fmt" + "github.com/gogf/gf/.example/frame/mvc/app/model/defaults" + "github.com/gogf/gf/database/gdb" ) func main() { - - //g.Server().SetDumpRouteMap(false) - g.Server().SetPort(8199) - g.Server().Run() - + u := defaults.User{Id: 1, Nickname: "test"} + fmt.Println(gdb.GetWhereConditionOfStruct(&u)) + fmt.Println(u.Replace()) } diff --git a/.example/frame/mvc/model/test/Initialization.go b/.example/frame/mvc/model/test/Initialization.go deleted file mode 100644 index c884b2b67..000000000 --- a/.example/frame/mvc/model/test/Initialization.go +++ /dev/null @@ -1,8 +0,0 @@ -package test - -import "github.com/gogf/gf/database/gdb" - -var ( - // ConfigGroup is the configuration group name for this model. - ConfigGroup = gdb.DEFAULT_GROUP_NAME -) diff --git a/.example/frame/mvc/model/test/User.go b/.example/frame/mvc/model/test/User.go deleted file mode 100644 index 3ac36c955..000000000 --- a/.example/frame/mvc/model/test/User.go +++ /dev/null @@ -1,92 +0,0 @@ -package test - -import ( - "database/sql" - - "github.com/gogf/gf/frame/g" - - "github.com/gogf/gf/database/gdb" - "github.com/gogf/gf/os/gtime" -) - -// User is the golang structure for table user. -type User struct { - Id int `orm:"id,primary" json:"id"` - Passport string `orm:"passport" json:"passport"` - Password string `orm:"password" json:"password"` - NickName string `orm:"nickname" json:"nick_name"` - CreateTime *gtime.Time `orm:"create_time" json:"create_time"` -} - -// UserModel is the model of convenient operations for table user. -type UserModel struct { - *gdb.Model - TableName string -} - -var ( - // UserTableName is the table name of user. - UserTableName = "user" -) - -// ModelUser creates and returns a new model object for table user. -func ModelUser() *UserModel { - return &UserModel{ - g.DB(ConfigGroup).Table(UserTableName).Safe(), - UserTableName, - } -} - -// Inserts does "INSERT...INTO..." statement for inserting current object into table. -func (r *User) Insert() (result sql.Result, err error) { - return ModelUser().Data(r).Insert() -} - -// Replace does "REPLACE...INTO..." statement for inserting current object into table. -// If there's already another same record in the table (it checks using primary key or unique index), -// it deletes it and insert this one. -func (r *User) Replace() (result sql.Result, err error) { - return ModelUser().Data(r).Replace() -} - -// Save does "INSERT...INTO..." statement for inserting/updating current object into table. -// It updates the record if there's already another same record in the table -// (it checks using primary key or unique index). -func (r *User) Save() (result sql.Result, err error) { - return ModelUser().Data(r).Save() -} - -// Update does "UPDATE...WHERE..." statement for updating current object from table. -// It updates the record if there's already another same record in the table -// (it checks using primary key or unique index). -func (r *User) Update() (result sql.Result, err error) { - return ModelUser().Data(r).Where(gdb.GetWhereConditionOfStruct(r)).Update() -} - -// Delete does "DELETE FROM...WHERE..." statement for deleting current object from table. -func (r *User) Delete() (result sql.Result, err error) { - return ModelUser().Where(gdb.GetWhereConditionOfStruct(r)).Delete() -} - -// Select overwrite the Select method from gdb.Model for model -// as retuning all objects with specified structure. -func (m *UserModel) Select() ([]*User, error) { - array := ([]*User)(nil) - if err := m.Scan(&array); err != nil { - return nil, err - } - return array, nil -} - -// First does the same logistics as One method from gdb.Model for model -// as retuning first/one object with specified structure. -func (m *UserModel) First() (*User, error) { - list, err := m.Select() - if err != nil { - return nil, err - } - if len(list) > 0 { - return list[0], nil - } - return nil, nil -} diff --git a/.example/other/test.go b/.example/other/test.go index 7951c4c05..43affb7d8 100644 --- a/.example/other/test.go +++ b/.example/other/test.go @@ -1,12 +1,10 @@ package main import ( - "encoding/json" "fmt" - "github.com/gogf/gf/util/gconv" ) func main() { - b, _ := json.Marshal([]interface{}{1, 2, 3, 4, 5, 123.456, "a"}) - fmt.Println(gconv.String(b)) + fmt.Println('\f') + fmt.Println(0xA0) } diff --git a/container/garray/garray_normal_any.go b/container/garray/garray_normal_any.go index 6e71e3e1a..818e25b9c 100644 --- a/container/garray/garray_normal_any.go +++ b/container/garray/garray_normal_any.go @@ -370,6 +370,11 @@ func (a *Array) Slice() []interface{} { } } +// Interfaces returns current array as []interface{}. +func (a *Array) Interfaces() []interface{} { + return a.Slice() +} + // Clone returns a new array, which is a copy of current array. func (a *Array) Clone() (newArray *Array) { a.mu.RLock() diff --git a/container/garray/garray_normal_int.go b/container/garray/garray_normal_int.go index eab5ca5b7..196195de6 100644 --- a/container/garray/garray_normal_int.go +++ b/container/garray/garray_normal_int.go @@ -376,6 +376,17 @@ func (a *IntArray) Slice() []int { return array } +// Interfaces returns current array as []interface{}. +func (a *IntArray) Interfaces() []interface{} { + a.mu.RLock() + defer a.mu.RUnlock() + array := make([]interface{}, len(a.array)) + for k, v := range a.array { + array[k] = v + } + return array +} + // Clone returns a new array, which is a copy of current array. func (a *IntArray) Clone() (newArray *IntArray) { a.mu.RLock() diff --git a/container/garray/garray_normal_str.go b/container/garray/garray_normal_str.go index 209e3e266..6d421d142 100644 --- a/container/garray/garray_normal_str.go +++ b/container/garray/garray_normal_str.go @@ -378,6 +378,17 @@ func (a *StrArray) Slice() []string { return array } +// Interfaces returns current array as []interface{}. +func (a *StrArray) Interfaces() []interface{} { + a.mu.RLock() + defer a.mu.RUnlock() + array := make([]interface{}, len(a.array)) + for k, v := range a.array { + array[k] = v + } + return array +} + // Clone returns a new array, which is a copy of current array. func (a *StrArray) Clone() (newArray *StrArray) { a.mu.RLock() diff --git a/container/garray/garray_sorted_any.go b/container/garray/garray_sorted_any.go index d03fbd638..67409b6ca 100644 --- a/container/garray/garray_sorted_any.go +++ b/container/garray/garray_sorted_any.go @@ -342,6 +342,11 @@ func (a *SortedArray) Slice() []interface{} { return array } +// Interfaces returns current array as []interface{}. +func (a *SortedArray) Interfaces() []interface{} { + return a.Slice() +} + // Contains checks whether a value exists in the array. func (a *SortedArray) Contains(value interface{}) bool { return a.Search(value) != -1 diff --git a/container/garray/garray_sorted_int.go b/container/garray/garray_sorted_int.go index 81ae0ee55..b90bf2734 100644 --- a/container/garray/garray_sorted_int.go +++ b/container/garray/garray_sorted_int.go @@ -328,6 +328,17 @@ func (a *SortedIntArray) Slice() []int { return array } +// Interfaces returns current array as []interface{}. +func (a *SortedIntArray) Interfaces() []interface{} { + a.mu.RLock() + defer a.mu.RUnlock() + array := make([]interface{}, len(a.array)) + for k, v := range a.array { + array[k] = v + } + return array +} + // Contains checks whether a value exists in the array. func (a *SortedIntArray) Contains(value int) bool { return a.Search(value) != -1 diff --git a/container/garray/garray_sorted_str.go b/container/garray/garray_sorted_str.go index cc61b1c0e..bdf754aad 100644 --- a/container/garray/garray_sorted_str.go +++ b/container/garray/garray_sorted_str.go @@ -329,6 +329,17 @@ func (a *SortedStrArray) Slice() []string { return array } +// Interfaces returns current array as []interface{}. +func (a *SortedStrArray) Interfaces() []interface{} { + a.mu.RLock() + defer a.mu.RUnlock() + array := make([]interface{}, len(a.array)) + for k, v := range a.array { + array[k] = v + } + return array +} + // Contains checks whether a value exists in the array. func (a *SortedStrArray) Contains(value string) bool { return a.Search(value) != -1 diff --git a/container/garray/garray_z_unit_normal_any_array_test.go b/container/garray/garray_z_unit_normal_any_array_test.go index fb709e1c7..8546d8d54 100644 --- a/container/garray/garray_z_unit_normal_any_array_test.go +++ b/container/garray/garray_z_unit_normal_any_array_test.go @@ -26,6 +26,7 @@ func Test_Array_Basic(t *testing.T) { array2 := garray.NewArrayFrom(expect) array3 := garray.NewArrayFrom([]interface{}{}) gtest.Assert(array.Slice(), expect) + gtest.Assert(array.Interfaces(), expect) array.Set(0, 100) gtest.Assert(array.Get(0), 100) gtest.Assert(array.Get(1), 1) diff --git a/container/garray/garray_z_unit_normal_int_array_test.go b/container/garray/garray_z_unit_normal_int_array_test.go index 9bbea3b65..3feeb61ce 100644 --- a/container/garray/garray_z_unit_normal_int_array_test.go +++ b/container/garray/garray_z_unit_normal_int_array_test.go @@ -27,6 +27,7 @@ func Test_IntArray_Basic(t *testing.T) { array := garray.NewIntArrayFrom(expect) array2 := garray.NewIntArrayFrom(expect2) gtest.Assert(array.Slice(), expect) + gtest.Assert(array.Interfaces(), expect) array.Set(0, 100) gtest.Assert(array.Get(0), 100) gtest.Assert(array.Get(1), 1) diff --git a/container/garray/garray_z_unit_normal_str_array_test.go b/container/garray/garray_z_unit_normal_str_array_test.go index c5274f264..19db6e1b0 100644 --- a/container/garray/garray_z_unit_normal_str_array_test.go +++ b/container/garray/garray_z_unit_normal_str_array_test.go @@ -27,6 +27,7 @@ func Test_StrArray_Basic(t *testing.T) { array2 := garray.NewStrArrayFrom(expect, true) array3 := garray.NewStrArrayFrom([]string{}) gtest.Assert(array.Slice(), expect) + gtest.Assert(array.Interfaces(), expect) array.Set(0, "100") gtest.Assert(array.Get(0), 100) gtest.Assert(array.Get(1), 1) diff --git a/container/garray/garray_z_unit_sorted_any_array_test.go b/container/garray/garray_z_unit_sorted_any_array_test.go index fd2e4493b..521ceabf9 100644 --- a/container/garray/garray_z_unit_sorted_any_array_test.go +++ b/container/garray/garray_z_unit_sorted_any_array_test.go @@ -561,6 +561,7 @@ func TestSortedArray_Json(t *testing.T) { err := json.Unmarshal(b2, &a3) gtest.Assert(err, nil) gtest.Assert(a3.Slice(), s1) + gtest.Assert(a3.Interfaces(), s1) }) gtest.Case(t, func() { diff --git a/container/garray/garray_z_unit_sorted_int_array_test.go b/container/garray/garray_z_unit_sorted_int_array_test.go index 05ab0001b..f97fd9dd3 100644 --- a/container/garray/garray_z_unit_sorted_int_array_test.go +++ b/container/garray/garray_z_unit_sorted_int_array_test.go @@ -26,6 +26,7 @@ func TestNewSortedIntArrayFrom(t *testing.T) { array1 := garray.NewSortedIntArrayFrom(a1, true) gtest.Assert(array1.Join("."), "0.1.2.3.4.5.6") gtest.Assert(array1.Slice(), a1) + gtest.Assert(array1.Interfaces(), a1) }) } diff --git a/container/garray/garray_z_unit_sorted_str_array_test.go b/container/garray/garray_z_unit_sorted_str_array_test.go index 72fad5cde..00797ad73 100644 --- a/container/garray/garray_z_unit_sorted_str_array_test.go +++ b/container/garray/garray_z_unit_sorted_str_array_test.go @@ -448,11 +448,13 @@ func TestSortedStrArray_Json(t *testing.T) { a2 := garray.NewSortedStrArray() err1 = json.Unmarshal(b2, &a2) gtest.Assert(a2.Slice(), s2) + gtest.Assert(a2.Interfaces(), s2) var a3 garray.SortedStrArray err := json.Unmarshal(b2, &a3) gtest.Assert(err, nil) gtest.Assert(a3.Slice(), s1) + gtest.Assert(a3.Interfaces(), s1) }) gtest.Case(t, func() { diff --git a/database/gdb/gdb.go b/database/gdb/gdb.go index 5b8da1c25..39b9e3218 100644 --- a/database/gdb/gdb.go +++ b/database/gdb/gdb.go @@ -99,9 +99,8 @@ type DB interface { getChars() (charLeft string, charRight string) getDebug() bool quoteWord(s string) string - setSchema(sqlDb *sql.DB, schema string) error + doSetSchema(sqlDb *sql.DB, schema string) error filterFields(table string, data map[string]interface{}) map[string]interface{} - formatWhere(where interface{}, args []interface{}) (newWhere string, newArgs []interface{}) convertValue(fieldValue []byte, fieldType string) interface{} rowsToResult(rows *sql.Rows) (Result, error) handleSqlBeforeExec(sql string) string @@ -131,11 +130,12 @@ type dbBase struct { // 执行的SQL对象 type Sql struct { - Sql string // SQL语句(可能带有预处理占位符) - Args []interface{} // 预处理参数值列表 - Error error // 执行结果(nil为成功) - Start int64 // 执行开始时间(毫秒) - End int64 // 执行结束时间(毫秒) + Sql string // SQL语句(可能带有预处理占位符) + Args []interface{} // 预处理参数值列表 + Format string // 格式化后的SQL语句(仅供参考) + Error error // 执行结果(nil为成功) + Start int64 // 执行开始时间(毫秒) + End int64 // 执行结束时间(毫秒) } // 表字段结构信息 @@ -359,7 +359,7 @@ func (bs *dbBase) getSqlDb(master bool) (sqlDb *sql.DB, err error) { } // 是否手动选择数据库 if v := bs.schema.Val(); v != "" { - if e := bs.db.setSchema(sqlDb, v); e != nil { + if e := bs.db.doSetSchema(sqlDb, v); e != nil { err = e } } diff --git a/database/gdb/gdb_base.go b/database/gdb/gdb_base.go index 63eba096f..d66a23122 100644 --- a/database/gdb/gdb_base.go +++ b/database/gdb/gdb_base.go @@ -8,11 +8,9 @@ package gdb import ( - "bytes" "database/sql" "errors" "fmt" - "github.com/gogf/gf/container/gmap" "reflect" "regexp" "strings" @@ -69,18 +67,19 @@ func (bs *dbBase) PrintQueriedSqls() { sqlSlice := bs.GetQueriedSqls() for k, v := range sqlSlice { fmt.Println(len(sqlSlice)-k, ":") - fmt.Println(" Sql :", v.Sql) - fmt.Println(" Args :", v.Args) - fmt.Println(" Error:", v.Error) - fmt.Println(" Start:", gtime.NewFromTimeStamp(v.Start).Format("Y-m-d H:i:s.u")) - fmt.Println(" End :", gtime.NewFromTimeStamp(v.End).Format("Y-m-d H:i:s.u")) - fmt.Println(" Cost :", v.End-v.Start, "ms") + fmt.Println(" Sql :", v.Sql) + fmt.Println(" Args :", v.Args) + fmt.Println(" Format :", v.Format) + fmt.Println(" Error :", v.Error) + fmt.Println(" Start :", gtime.NewFromTimeStamp(v.Start).Format("Y-m-d H:i:s.u")) + fmt.Println(" End :", gtime.NewFromTimeStamp(v.End).Format("Y-m-d H:i:s.u")) + fmt.Println(" Cost :", v.End-v.Start, "ms") } } // 打印SQL对象(仅在debug=true时有效) func (bs *dbBase) printSql(v *Sql) { - s := fmt.Sprintf("[%d ms] %s", v.End-v.Start, bindArgsToQuery(v.Sql, v.Args)) + s := fmt.Sprintf("[%d ms] %s", v.End-v.Start, v.Format) if v.Error != nil { s += "\nError: " + v.Error.Error() bs.logger.StackWithFilter(gPATH_FILTER_KEY).Error(s) @@ -107,11 +106,12 @@ func (bs *dbBase) doQuery(link dbLink, query string, args ...interface{}) (rows rows, err = link.Query(query, args...) mTime2 := gtime.Millisecond() s := &Sql{ - Sql: query, - Args: args, - Error: err, - Start: mTime1, - End: mTime2, + Sql: query, + Args: args, + Format: bindArgsToQuery(query, args), + Error: err, + Start: mTime1, + End: mTime2, } bs.sqls.Put(s) bs.printSql(s) @@ -144,11 +144,12 @@ func (bs *dbBase) doExec(link dbLink, query string, args ...interface{}) (result result, err = link.Exec(query, args...) mTime2 := gtime.Millisecond() s := &Sql{ - Sql: query, - Args: args, - Error: err, - Start: mTime1, - End: mTime2, + Sql: query, + Args: args, + Format: bindArgsToQuery(query, args), + Error: err, + Start: mTime1, + End: mTime2, } bs.sqls.Put(s) bs.printSql(s) @@ -355,7 +356,7 @@ func (bs *dbBase) doInsert(link dbLink, table string, data interface{}, option i case reflect.Slice, reflect.Array: return bs.db.doBatchInsert(link, table, data, option, batch...) case reflect.Map, reflect.Struct: - dataMap = structToMap(data) + dataMap = varToMapDeep(data) default: return result, errors.New(fmt.Sprint("unsupported data type:", kind)) } @@ -435,10 +436,10 @@ func (bs *dbBase) doBatchInsert(link dbLink, table string, list interface{}, opt case reflect.Slice, reflect.Array: listMap = make(List, rv.Len()) for i := 0; i < rv.Len(); i++ { - listMap[i] = structToMap(rv.Index(i).Interface()) + listMap[i] = varToMapDeep(rv.Index(i).Interface()) } case reflect.Map, reflect.Struct: - listMap = List{structToMap(list)} + listMap = List{varToMapDeep(list)} default: return result, errors.New(fmt.Sprint("unsupported list type:", kind)) } @@ -522,7 +523,7 @@ func (bs *dbBase) doBatchInsert(link dbLink, table string, list interface{}, opt // CURD操作:数据更新,统一采用sql预处理。 // data参数支持string/map/struct/*struct类型。 func (bs *dbBase) Update(table string, data interface{}, condition interface{}, args ...interface{}) (sql.Result, error) { - newWhere, newArgs := bs.db.formatWhere(condition, args) + newWhere, newArgs := formatWhere(bs.db, condition, args, false) if newWhere != "" { newWhere = " WHERE " + newWhere } @@ -545,7 +546,7 @@ func (bs *dbBase) doUpdate(link dbLink, table string, data interface{}, conditio switch kind { case reflect.Map, reflect.Struct: var fields []string - for k, v := range structToMap(data) { + for k, v := range varToMapDeep(data) { fields = append(fields, bs.db.quoteWord(k)+"=?") params = append(params, convertParam(v)) } @@ -570,7 +571,7 @@ func (bs *dbBase) doUpdate(link dbLink, table string, data interface{}, conditio // CURD操作:删除数据 func (bs *dbBase) Delete(table string, condition interface{}, args ...interface{}) (result sql.Result, err error) { - newWhere, newArgs := bs.db.formatWhere(condition, args) + newWhere, newArgs := formatWhere(bs.db, condition, args, false) if newWhere != "" { newWhere = " WHERE " + newWhere } @@ -642,109 +643,6 @@ func (bs *dbBase) rowsToResult(rows *sql.Rows) (Result, error) { return records, nil } -// 格式化Where查询条件。 -func (bs *dbBase) formatWhere(where interface{}, args []interface{}) (newWhere string, newArgs []interface{}) { - buffer := bytes.NewBuffer(nil) - rv := reflect.ValueOf(where) - kind := rv.Kind() - if kind == reflect.Ptr { - rv = rv.Elem() - kind = rv.Kind() - } - switch kind { - case reflect.Map: - for key, value := range structToMap(where) { - newArgs = bs.formatWhereKeyValue(buffer, newArgs, key, value) - } - - case reflect.Struct: - // ListMap and TreeMap are ordered map, - // which are index-friendly for where conditions. - switch m := where.(type) { - case *gmap.ListMap: - m.Iterator(func(key, value interface{}) bool { - newArgs = bs.formatWhereKeyValue(buffer, newArgs, gconv.String(key), value) - return true - }) - case *gmap.TreeMap: - m.Iterator(func(key, value interface{}) bool { - newArgs = bs.formatWhereKeyValue(buffer, newArgs, gconv.String(key), value) - return true - }) - default: - for key, value := range structToMap(where) { - newArgs = bs.formatWhereKeyValue(buffer, newArgs, key, value) - } - } - - default: - buffer.WriteString(gconv.String(where)) - } - - if buffer.Len() == 0 { - return "", args - } - newArgs = append(newArgs, args...) - newWhere = buffer.String() - if len(newArgs) > 0 { - // It supports formats like: Where/And/Or("uid", 1) , Where/And/Or("uid>=", 1) - if gstr.Pos(newWhere, "?") == -1 { - if lastOperatorReg.MatchString(newWhere) { - newWhere += "?" - } else if wordReg.MatchString(newWhere) { - newWhere += "=?" - } - } - } - return handlerSliceArguments(newWhere, newArgs) -} - -// formatWhereKeyValue handles each key-value pair of the param map. -func (bs *dbBase) formatWhereKeyValue(buffer *bytes.Buffer, newArgs []interface{}, key string, value interface{}) []interface{} { - key = bs.db.quoteWord(key) - if buffer.Len() > 0 { - buffer.WriteString(" AND ") - } - // 支持slice键值/属性,如果只有一个?占位符号,那么作为IN查询,否则打散作为多个查询参数 - rv := reflect.ValueOf(value) - switch rv.Kind() { - case reflect.Slice, reflect.Array: - count := gstr.Count(key, "?") - if count == 0 { - buffer.WriteString(key + " IN(?)") - newArgs = append(newArgs, value) - } else if count != rv.Len() { - buffer.WriteString(key) - newArgs = append(newArgs, value) - } else { - buffer.WriteString(key) - // 如果键名/属性名称中带有多个?占位符号,那么将参数打散 - newArgs = append(newArgs, gconv.Interfaces(value)...) - } - default: - if value == nil { - buffer.WriteString(key) - } else { - // 支持key带操作符号,注意like也算是操作符号 - key = gstr.Trim(key) - if gstr.Pos(key, "?") == -1 { - like := " like" - if len(key) > len(like) && gstr.Equal(key[len(key)-len(like):], like) { - buffer.WriteString(key + " ?") - } else if lastOperatorReg.MatchString(key) { - buffer.WriteString(key + " ?") - } else { - buffer.WriteString(key + "=?") - } - } else { - buffer.WriteString(key) - } - newArgs = append(newArgs, value) - } - } - return newArgs -} - // 使用关键字操作符转义给定字符串。 // 如果给定的字符串不为单词,那么不转义,直接返回该字符串。 func (bs *dbBase) quoteWord(s string) string { @@ -756,7 +654,7 @@ func (bs *dbBase) quoteWord(s string) string { } // 动态切换数据库 -func (bs *dbBase) setSchema(sqlDb *sql.DB, schema string) error { +func (bs *dbBase) doSetSchema(sqlDb *sql.DB, schema string) error { _, err := sqlDb.Exec("USE " + schema) return err } diff --git a/database/gdb/gdb_func.go b/database/gdb/gdb_func.go index 76a52b6d7..b362244ae 100644 --- a/database/gdb/gdb_func.go +++ b/database/gdb/gdb_func.go @@ -7,9 +7,11 @@ package gdb import ( + "bytes" "database/sql" "errors" "fmt" + "github.com/gogf/gf/internal/empty" "reflect" "strings" "time" @@ -21,11 +23,21 @@ import ( "github.com/gogf/gf/util/gconv" ) -// Type assert api for String(). +// Type assert api for String. type apiString interface { String() string } +// Type assert api for Iterator. +type apiIterator interface { + Iterator(f func(key, value interface{}) bool) +} + +// Type assert api for Interfaces. +type apiInterfaces interface { + Interfaces() []interface{} +} + const ( ORM_TAG_FOR_STRUCT = "orm" ORM_TAG_FOR_UNIQUE = "unique" @@ -63,6 +75,173 @@ func formatQuery(query string, args []interface{}) (newQuery string, newArgs []i return handlerSliceArguments(query, args) } +// 格式化Where查询条件。 +// TODO []interface{} type support for parameter does not completed yet. +func formatWhere(db DB, where interface{}, args []interface{}, omitEmpty bool) (newWhere string, newArgs []interface{}) { + buffer := bytes.NewBuffer(nil) + rv := reflect.ValueOf(where) + kind := rv.Kind() + if kind == reflect.Ptr { + rv = rv.Elem() + kind = rv.Kind() + } + switch kind { + case reflect.Array, reflect.Slice: + newArgs = formatWhereInterfaces(db, gconv.Interfaces(where), buffer, newArgs) + + case reflect.Map: + for key, value := range varToMapDeep(where) { + if omitEmpty && empty.IsEmpty(value) { + continue + } + newArgs = formatWhereKeyValue(db, buffer, newArgs, key, value) + } + + case reflect.Struct: + // If struct implements apiIterator interface, + // it then uses its Iterate function to iterates its key-value pairs. + // For example, ListMap and TreeMap are ordered map, + // which implement apiIterator interface and are index-friendly for where conditions. + if iterator, ok := where.(apiIterator); ok { + iterator.Iterator(func(key, value interface{}) bool { + if omitEmpty && empty.IsEmpty(value) { + return true + } + newArgs = formatWhereKeyValue(db, buffer, newArgs, gconv.String(key), value) + return true + }) + break + } + // TODO garray support. + for key, value := range varToMapDeep(where) { + if omitEmpty && empty.IsEmpty(value) { + continue + } + newArgs = formatWhereKeyValue(db, buffer, newArgs, key, value) + } + + default: + buffer.WriteString(gconv.String(where)) + } + + if buffer.Len() == 0 { + return "", args + } + newArgs = append(newArgs, args...) + newWhere = buffer.String() + if len(newArgs) > 0 { + // It supports formats like: Where/And/Or("uid", 1) , Where/And/Or("uid>=", 1) + if gstr.Pos(newWhere, "?") == -1 { + if lastOperatorReg.MatchString(newWhere) { + newWhere += "?" + } else if wordReg.MatchString(newWhere) { + newWhere += "=?" + } + } + } + return handlerSliceArguments(newWhere, newArgs) +} + +// formatWhereInterfaces formats as []interface{}. +// TODO []interface{} type support for parameter does not completed yet. +func formatWhereInterfaces(db DB, where []interface{}, buffer *bytes.Buffer, newArgs []interface{}) []interface{} { + var str string + var array []interface{} + var holderCount int + for i := 0; i < len(where); { + if holderCount > 0 { + array = gconv.Interfaces(where[i]) + newArgs = append(newArgs, array...) + holderCount -= len(array) + } else { + str = gconv.String(where[i]) + holderCount = gstr.Count(str, "?") + buffer.WriteString(str) + } + } + return newArgs +} + +// formatWhereKeyValue handles each key-value pair of the parameter map. +func formatWhereKeyValue(db DB, buffer *bytes.Buffer, newArgs []interface{}, key string, value interface{}) []interface{} { + key = db.quoteWord(key) + if buffer.Len() > 0 { + buffer.WriteString(" AND ") + } + // 支持slice键值/属性,如果只有一个?占位符号,那么作为IN查询,否则打散作为多个查询参数 + rv := reflect.ValueOf(value) + switch rv.Kind() { + case reflect.Slice, reflect.Array: + count := gstr.Count(key, "?") + if count == 0 { + buffer.WriteString(key + " IN(?)") + newArgs = append(newArgs, value) + } else if count != rv.Len() { + buffer.WriteString(key) + newArgs = append(newArgs, value) + } else { + buffer.WriteString(key) + // 如果键名/属性名称中带有多个?占位符号,那么将参数打散 + newArgs = append(newArgs, gconv.Interfaces(value)...) + } + default: + if value == nil { + buffer.WriteString(key) + } else { + // 支持key带操作符号,注意like也算是操作符号 + key = gstr.Trim(key) + if gstr.Pos(key, "?") == -1 { + like := " like" + if len(key) > len(like) && gstr.Equal(key[len(key)-len(like):], like) { + buffer.WriteString(key + " ?") + } else if lastOperatorReg.MatchString(key) { + buffer.WriteString(key + " ?") + } else { + buffer.WriteString(key + "=?") + } + } else { + buffer.WriteString(key) + } + newArgs = append(newArgs, value) + } + } + return newArgs +} + +// 将对象转换为map,如果对象带有继承对象,那么执行递归转换。 +// 该方法用于将变量传递给数据库执行之前。 +func varToMapDeep(obj interface{}) map[string]interface{} { + data := gconv.Map(obj, ORM_TAG_FOR_STRUCT) + for key, value := range data { + rv := reflect.ValueOf(value) + kind := rv.Kind() + if kind == reflect.Ptr { + rv = rv.Elem() + kind = rv.Kind() + } + switch kind { + case reflect.Struct: + // 底层数据库引擎支持 time.Time/*time.Time 类型 + if _, ok := value.(time.Time); ok { + continue + } + if _, ok := value.(*time.Time); ok { + continue + } + // 如果执行String方法,那么执行字符串转换 + if s, ok := value.(apiString); ok { + data[key] = s.String() + continue + } + delete(data, key) + for k, v := range varToMapDeep(value) { + data[k] = v + } + } + } + return data +} + // 处理预处理占位符与slice类型的参数。 // 需要注意的是, // 如果是链式操作,在条件参数中也会调用该方法处理查询参数, @@ -164,40 +343,6 @@ func getInsertOperationByOption(option int) string { return operator } -// 将对象转换为map,如果对象带有继承对象,那么执行递归转换。 -// 该方法用于将变量传递给数据库执行之前。 -func structToMap(obj interface{}) map[string]interface{} { - data := gconv.Map(obj, ORM_TAG_FOR_STRUCT) - for key, value := range data { - rv := reflect.ValueOf(value) - kind := rv.Kind() - if kind == reflect.Ptr { - rv = rv.Elem() - kind = rv.Kind() - } - switch kind { - case reflect.Struct: - // 底层数据库引擎支持 time.Time/*time.Time 类型 - if _, ok := value.(time.Time); ok { - continue - } - if _, ok := value.(*time.Time); ok { - continue - } - // 如果执行String方法,那么执行字符串转换 - if s, ok := value.(apiString); ok { - data[key] = s.String() - continue - } - delete(data, key) - for k, v := range structToMap(value) { - data[k] = v - } - } - } - return data -} - // 将参数绑定到SQL语句中,仅用于调试打印。 func bindArgsToQuery(query string, args []interface{}) string { index := -1 @@ -210,6 +355,9 @@ func bindArgsToQuery(query string, args []interface{}) string { rv := reflect.ValueOf(args[index]) kind := rv.Kind() if kind == reflect.Ptr { + if rv.IsNil() || !rv.IsValid() { + return "null" + } rv = rv.Elem() kind = rv.Kind() } diff --git a/database/gdb/gdb_model.go b/database/gdb/gdb_model.go index f16208404..6ae7360cd 100644 --- a/database/gdb/gdb_model.go +++ b/database/gdb/gdb_model.go @@ -24,33 +24,44 @@ import ( // 数据库链式操作模型对象 type Model struct { - db DB // 数据库操作对象 - tx *TX // 数据库事务对象 - linkType int // 连接对象类型(用于主从集群时开发者自定义操作对象) - tablesInit string // 初始化Model时的表名称(可以是多个) - tables string // 数据库操作表 - fields string // 操作字段 - where string // 操作条件 - whereArgs []interface{} // 操作条件参数 - groupBy string // 分组语句 - orderBy string // 排序语句 - start int // 分页开始 - limit int // 分页条数 - option int // 操作选项 - offset int // 查询偏移量(OFFSET语法) - data interface{} // 操作数据(注意仅支持Map/List/string类型) - batch int // 批量操作条数 - filter bool // 是否按照表字段过滤data参数 - cacheEnabled bool // 当前SQL操作是否开启查询缓存功能 - cacheExpire time.Duration // 查询缓存时间 - cacheName string // 查询缓存名称 - safe bool // 当前模型是否安全模式(默认非安全表示链式操作直接修改当前模型属性;否则每一次链式操作都是返回新的模型对象) + db DB // 数据库操作对象 + tx *TX // 数据库事务对象 + linkType int // 连接对象类型(用于主从集群时开发者自定义操作对象) + tablesInit string // 初始化Model时的表名称(可以是多个) + tables string // 数据库操作表 + fields string // 操作字段 + where string // 操作条件 + whereArgs []interface{} // 操作条件参数 + whereHolder []*whereHolder // 操作条件预处理 + groupBy string // 分组语句 + orderBy string // 排序语句 + start int // 分页开始 + limit int // 分页条数 + option int // 操作选项 + offset int // 查询偏移量(OFFSET语法) + data interface{} // 操作数据(注意仅支持Map/List/string类型) + batch int // 批量操作条数 + filter bool // 是否按照表字段过滤data参数 + cacheEnabled bool // 当前SQL操作是否开启查询缓存功能 + cacheExpire time.Duration // 查询缓存时间 + cacheName string // 查询缓存名称 + safe bool // 当前模型是否安全模式(默认非安全表示链式操作直接修改当前模型属性;否则每一次链式操作都是返回新的模型对象) +} + +// whereHolder is the holder for where condition preparing. +type whereHolder struct { + operator int // Operator for this holder. + where interface{} // Where parameter. + args []interface{} // Arguments for where parameter. } const ( - gLINK_TYPE_MASTER = 1 - gLINK_TYPE_SLAVE = 2 - OPTION_OMITEMPTY = 1 << iota + gLINK_TYPE_MASTER = 1 + gLINK_TYPE_SLAVE = 2 + gWHERE_HOLDER_WHERE = 1 + gWHERE_HOLDER_AND = 2 + gWHERE_HOLDER_OR = 3 + OPTION_OMITEMPTY = 1 << iota OPTION_ALLOWEMPTY ) @@ -179,6 +190,11 @@ func (md *Model) Option(option int) *Model { return model } +// 链式操作,设置 OPTION_OMITEMPTY 常用选项 +func (md *Model) OptionOmitEmpty() *Model { + return md.Option(OPTION_OMITEMPTY) +} + // 链式操作,过滤字段 func (md *Model) Filter() *Model { model := md.getModel() @@ -186,42 +202,46 @@ func (md *Model) Filter() *Model { return model } -// 链式操作,condition,支持string & gdb.Map. +// 链式操作,condition,支持string/map/gmap/struct/*struct. // 注意,多个Where调用时,会自动转换为And条件调用。 func (md *Model) Where(where interface{}, args ...interface{}) *Model { model := md.getModel() - if model.where != "" { - return md.And(where, args...) + if model.whereHolder == nil { + model.whereHolder = make([]*whereHolder, 0) } - newWhere, newArgs := md.db.formatWhere(where, args) - model.where = newWhere - model.whereArgs = newArgs + model.whereHolder = append(model.whereHolder, &whereHolder{ + operator: gWHERE_HOLDER_WHERE, + where: where, + args: args, + }) return model } // 链式操作,添加AND条件到Where中 func (md *Model) And(where interface{}, args ...interface{}) *Model { model := md.getModel() - newWhere, newArgs := md.db.formatWhere(where, args) - if len(model.where) > 0 && model.where[0] == '(' { - model.where = fmt.Sprintf(`%s AND (%s)`, model.where, newWhere) - } else { - model.where = fmt.Sprintf(`(%s) AND (%s)`, model.where, newWhere) + if model.whereHolder == nil { + model.whereHolder = make([]*whereHolder, 0) } - model.whereArgs = append(model.whereArgs, newArgs...) + model.whereHolder = append(model.whereHolder, &whereHolder{ + operator: gWHERE_HOLDER_AND, + where: where, + args: args, + }) return model } // 链式操作,添加OR条件到Where中 func (md *Model) Or(where interface{}, args ...interface{}) *Model { model := md.getModel() - newWhere, newArgs := md.db.formatWhere(where, args) - if len(model.where) > 0 && model.where[0] == '(' { - model.where = fmt.Sprintf(`%s OR (%s)`, model.where, newWhere) - } else { - model.where = fmt.Sprintf(`(%s) OR (%s)`, model.where, newWhere) + if model.whereHolder == nil { + model.whereHolder = make([]*whereHolder, 0) } - model.whereArgs = append(model.whereArgs, newArgs...) + model.whereHolder = append(model.whereHolder, &whereHolder{ + operator: gWHERE_HOLDER_OR, + where: where, + args: args, + }) return model } @@ -334,11 +354,11 @@ func (md *Model) Data(data ...interface{}) *Model { case reflect.Slice, reflect.Array: list := make(List, rv.Len()) for i := 0; i < rv.Len(); i++ { - list[i] = structToMap(rv.Index(i).Interface()) + list[i] = varToMapDeep(rv.Index(i).Interface()) } model.data = list case reflect.Map, reflect.Struct: - model.data = structToMap(data[0]) + model.data = varToMapDeep(data[0]) default: model.data = data[0] } @@ -367,11 +387,13 @@ func (md *Model) doFilterDataMapForInsertOrUpdate(data Map, allowOmitEmpty bool) if md.filter { data = md.db.filterFields(md.tables, data) } + // Remove key-value pairs of which the value is empty. if allowOmitEmpty && md.option&OPTION_OMITEMPTY > 0 { m := gmap.NewStrAnyMapFrom(data) m.FilterEmpty() data = m.Map() } + // Keep specified fields. if len(md.fields) > 0 && md.fields != "*" { set := gset.NewStrSet() for _, v := range gstr.SplitAndTrimSpace(md.fields, ",") { @@ -671,6 +693,44 @@ func (md *Model) checkAndRemoveCache() { // 格式化当前输入参数,返回SQL条件语句(不带参数) func (md *Model) getConditionSql() string { + if len(md.whereHolder) > 0 { + for _, v := range md.whereHolder { + switch v.operator { + case gWHERE_HOLDER_WHERE: + if md.where == "" { + newWhere, newArgs := formatWhere(md.db, v.where, v.args, md.option&OPTION_OMITEMPTY > 0) + if len(newWhere) > 0 { + md.where = newWhere + md.whereArgs = newArgs + } + continue + } + fallthrough + + case gWHERE_HOLDER_AND: + newWhere, newArgs := formatWhere(md.db, v.where, v.args, md.option&OPTION_OMITEMPTY > 0) + if len(newWhere) > 0 { + if md.where[0] == '(' { + md.where = fmt.Sprintf(`%s AND (%s)`, md.where, newWhere) + } else { + md.where = fmt.Sprintf(`(%s) AND (%s)`, md.where, newWhere) + } + md.whereArgs = append(md.whereArgs, newArgs...) + } + + case gWHERE_HOLDER_OR: + newWhere, newArgs := formatWhere(md.db, v.where, v.args, md.option&OPTION_OMITEMPTY > 0) + if len(newWhere) > 0 { + if md.where[0] == '(' { + md.where = fmt.Sprintf(`%s OR (%s)`, md.where, newWhere) + } else { + md.where = fmt.Sprintf(`(%s) OR (%s)`, md.where, newWhere) + } + md.whereArgs = append(md.whereArgs, newArgs...) + } + } + } + } s := "" if md.where != "" { s += " WHERE " + md.where diff --git a/database/gdb/gdb_oracle.go b/database/gdb/gdb_oracle.go index e44bc0d75..b828d492d 100644 --- a/database/gdb/gdb_oracle.go +++ b/database/gdb/gdb_oracle.go @@ -218,7 +218,7 @@ func (db *dbOracle) doInsert(link dbLink, table string, data interface{}, option case reflect.Map: fallthrough case reflect.Struct: - dataMap = structToMap(data) + dataMap = varToMapDeep(data) default: return result, errors.New(fmt.Sprint("unsupported data type:", kind)) } @@ -330,12 +330,12 @@ func (db *dbOracle) doBatchInsert(link dbLink, table string, list interface{}, o case reflect.Array: listMap = make(List, rv.Len()) for i := 0; i < rv.Len(); i++ { - listMap[i] = structToMap(rv.Index(i).Interface()) + listMap[i] = varToMapDeep(rv.Index(i).Interface()) } case reflect.Map: fallthrough case reflect.Struct: - listMap = List{Map(structToMap(list))} + listMap = List{Map(varToMapDeep(list))} default: return result, errors.New(fmt.Sprint("unsupported list type:", kind)) } diff --git a/database/gdb/gdb_transaction.go b/database/gdb/gdb_transaction.go index d6506c5d0..e105832cd 100644 --- a/database/gdb/gdb_transaction.go +++ b/database/gdb/gdb_transaction.go @@ -165,7 +165,7 @@ func (tx *TX) BatchSave(table string, list interface{}, batch ...int) (sql.Resul // CURD操作:数据更新,统一采用sql预处理, // data参数支持字符串或者关联数组类型,内部会自行做判断处理. func (tx *TX) Update(table string, data interface{}, condition interface{}, args ...interface{}) (sql.Result, error) { - newWhere, newArgs := tx.db.formatWhere(condition, args) + newWhere, newArgs := formatWhere(tx.db, condition, args, false) if newWhere != "" { newWhere = " WHERE " + newWhere } @@ -179,7 +179,7 @@ func (tx *TX) doUpdate(table string, data interface{}, condition string, args .. // CURD操作:删除数据 func (tx *TX) Delete(table string, condition interface{}, args ...interface{}) (sql.Result, error) { - newWhere, newArgs := tx.db.formatWhere(condition, args) + newWhere, newArgs := formatWhere(tx.db, condition, args, false) if newWhere != "" { newWhere = " WHERE " + newWhere } diff --git a/database/gdb/gdb_unit_z_mysql_model_test.go b/database/gdb/gdb_unit_z_mysql_model_test.go index bc6e88a5b..a9ebb2822 100644 --- a/database/gdb/gdb_unit_z_mysql_model_test.go +++ b/database/gdb/gdb_unit_z_mysql_model_test.go @@ -1052,15 +1052,15 @@ func Test_Model_Option_Map(t *testing.T) { _, err = db.Table(table).Option(gdb.OPTION_OMITEMPTY).Data(g.Map{"nickname": ""}).Where("id", 2).Update() gtest.AssertNE(err, nil) - r, err = db.Table(table).Option(gdb.OPTION_OMITEMPTY).Data(g.Map{"nickname": "", "password": "123"}).Where("id", 3).Update() + r, err = db.Table(table).OptionOmitEmpty().Data(g.Map{"nickname": "", "password": "123"}).Where("id", 3).Update() gtest.Assert(err, nil) n, _ = r.RowsAffected() gtest.Assert(n, 1) - _, err = db.Table(table).Option(gdb.OPTION_OMITEMPTY).Fields("nickname").Data(g.Map{"nickname": "", "password": "123"}).Where("id", 4).Update() + _, err = db.Table(table).OptionOmitEmpty().Fields("nickname").Data(g.Map{"nickname": "", "password": "123"}).Where("id", 4).Update() gtest.AssertNE(err, nil) - r, err = db.Table(table).Option(gdb.OPTION_OMITEMPTY). + r, err = db.Table(table).OptionOmitEmpty(). Fields("password").Data(g.Map{ "nickname": "", "passport": "123", @@ -1080,75 +1080,95 @@ func Test_Model_Option_Map(t *testing.T) { func Test_Model_Option_List(t *testing.T) { gtest.Case(t, func() { - gtest.Case(t, func() { - table := createTable() - defer dropTable(table) - r, err := db.Table(table).Fields("id, password").Data(g.List{ - g.Map{ - "id": 1, - "passport": "1", - "password": "1", - "nickname": "1", - }, - g.Map{ - "id": 2, - "passport": "2", - "password": "2", - "nickname": "2", - }, - }).Save() - gtest.Assert(err, nil) - n, _ := r.RowsAffected() - gtest.Assert(n, 2) - list, err := db.Table(table).OrderBy("id asc").All() - gtest.Assert(err, nil) - gtest.Assert(len(list), 2) - gtest.Assert(list[0]["id"].String(), "1") - gtest.Assert(list[0]["nickname"].String(), "") - gtest.Assert(list[0]["passport"].String(), "") - gtest.Assert(list[0]["password"].String(), "1") + table := createTable() + defer dropTable(table) + r, err := db.Table(table).Fields("id, password").Data(g.List{ + g.Map{ + "id": 1, + "passport": "1", + "password": "1", + "nickname": "1", + }, + g.Map{ + "id": 2, + "passport": "2", + "password": "2", + "nickname": "2", + }, + }).Save() + gtest.Assert(err, nil) + n, _ := r.RowsAffected() + gtest.Assert(n, 2) + list, err := db.Table(table).OrderBy("id asc").All() + gtest.Assert(err, nil) + gtest.Assert(len(list), 2) + gtest.Assert(list[0]["id"].String(), "1") + gtest.Assert(list[0]["nickname"].String(), "") + gtest.Assert(list[0]["passport"].String(), "") + gtest.Assert(list[0]["password"].String(), "1") - gtest.Assert(list[1]["id"].String(), "2") - gtest.Assert(list[1]["nickname"].String(), "") - gtest.Assert(list[1]["passport"].String(), "") - gtest.Assert(list[1]["password"].String(), "2") - }) + gtest.Assert(list[1]["id"].String(), "2") + gtest.Assert(list[1]["nickname"].String(), "") + gtest.Assert(list[1]["passport"].String(), "") + gtest.Assert(list[1]["password"].String(), "2") }) gtest.Case(t, func() { - gtest.Case(t, func() { - table := createTable() - defer dropTable(table) - r, err := db.Table(table).Option(gdb.OPTION_OMITEMPTY).Fields("id, password").Data(g.List{ - g.Map{ - "id": 1, - "passport": "1", - "password": 0, - "nickname": "1", - }, - g.Map{ - "id": 2, - "passport": "2", - "password": "2", - "nickname": "2", - }, - }).Save() - gtest.Assert(err, nil) - n, _ := r.RowsAffected() - gtest.Assert(n, 2) - list, err := db.Table(table).OrderBy("id asc").All() - g.Dump(list) - gtest.Assert(err, nil) - gtest.Assert(len(list), 2) - gtest.Assert(list[0]["id"].String(), "1") - gtest.Assert(list[0]["nickname"].String(), "") - gtest.Assert(list[0]["passport"].String(), "") - gtest.Assert(list[0]["password"].String(), "0") + table := createTable() + defer dropTable(table) + r, err := db.Table(table).OptionOmitEmpty().Fields("id, password").Data(g.List{ + g.Map{ + "id": 1, + "passport": "1", + "password": 0, + "nickname": "1", + }, + g.Map{ + "id": 2, + "passport": "2", + "password": "2", + "nickname": "2", + }, + }).Save() + gtest.Assert(err, nil) + n, _ := r.RowsAffected() + gtest.Assert(n, 2) + list, err := db.Table(table).OrderBy("id asc").All() + g.Dump(list) + gtest.Assert(err, nil) + gtest.Assert(len(list), 2) + gtest.Assert(list[0]["id"].String(), "1") + gtest.Assert(list[0]["nickname"].String(), "") + gtest.Assert(list[0]["passport"].String(), "") + gtest.Assert(list[0]["password"].String(), "0") + + gtest.Assert(list[1]["id"].String(), "2") + gtest.Assert(list[1]["nickname"].String(), "") + gtest.Assert(list[1]["passport"].String(), "") + gtest.Assert(list[1]["password"].String(), "2") - gtest.Assert(list[1]["id"].String(), "2") - gtest.Assert(list[1]["nickname"].String(), "") - gtest.Assert(list[1]["passport"].String(), "") - gtest.Assert(list[1]["password"].String(), "2") - }) + }) +} + +func Test_Model_Option_Where(t *testing.T) { + gtest.Case(t, func() { + table := createInitTable() + defer dropTable(table) + r, err := db.Table(table).OptionOmitEmpty().Data("nickname", 1).Where(g.Map{"id": 0, "passport": ""}).Update() + gtest.Assert(err, nil) + n, _ := r.RowsAffected() + gtest.Assert(n, INIT_DATA_SIZE) + }) + gtest.Case(t, func() { + table := createInitTable() + defer dropTable(table) + r, err := db.Table(table).OptionOmitEmpty().Data("nickname", 1).Where(g.Map{"id": 1, "passport": ""}).Update() + gtest.Assert(err, nil) + n, _ := r.RowsAffected() + gtest.Assert(n, 1) + + v, err := db.Table(table).Where("id", 1).Fields("nickname").Value() + gtest.Assert(err, nil) + gtest.Assert(v.String(), "1") }) } diff --git a/os/gtime/gtime_time.go b/os/gtime/gtime_time.go index 3187a2d29..5571db3f7 100644 --- a/os/gtime/gtime_time.go +++ b/os/gtime/gtime_time.go @@ -100,6 +100,9 @@ func (t *Time) Millisecond() int64 { // String returns current time object as string. func (t *Time) String() string { + if t == nil { + return "" + } return t.Format("Y-m-d H:i:s") } @@ -200,6 +203,10 @@ func (t *Time) MarshalJSON() ([]byte, error) { // UnmarshalJSON implements the interface UnmarshalJSON for json.Unmarshal. func (t *Time) UnmarshalJSON(b []byte) error { + if len(b) == 0 { + t.Time = time.Time{} + return nil + } newTime, err := StrToTime(string(bytes.Trim(b, `"`))) if err != nil { return err diff --git a/text/gstr/gstr.go b/text/gstr/gstr.go index 621cb7552..3f9363f81 100644 --- a/text/gstr/gstr.go +++ b/text/gstr/gstr.go @@ -443,13 +443,14 @@ func Split(str, delimiter string) []string { } // SplitAndTrim splits string by a string to an array, -// and calls Trim to every element of this array. -func SplitAndTrim(str, delimiter, cut string) []string { +// and calls Trim to every element of this array. It ignores the elements +// which are empty after Trim. +func SplitAndTrim(str, delimiter string, characterMask ...string) []string { array := strings.Split(str, delimiter) for k, v := range array { - v = strings.Trim(v, cut) + v = Trim(v, characterMask...) if v != "" { - array[k] = strings.Trim(v, cut) + array[k] = v } } return array @@ -457,6 +458,7 @@ func SplitAndTrim(str, delimiter, cut string) []string { // SplitAndTrimSpace splits string by a string to an array, // and calls TrimSpace to every element of this array. +// Deprecated. func SplitAndTrimSpace(str, delimiter string) []string { array := strings.Split(str, delimiter) for k, v := range array { diff --git a/text/gstr/gstr_trim.go b/text/gstr/gstr_trim.go index e7549d910..3f63dd748 100644 --- a/text/gstr/gstr_trim.go +++ b/text/gstr/gstr_trim.go @@ -6,29 +6,52 @@ package gstr -import "strings" +import ( + "strings" +) + +var ( + // defaultTrimChars are the characters which are stripped by Trim* functions in default. + defaultTrimChars = string([]byte{ + '\t', // Tab. + '\v', // Vertical tab. + '\n', // New line (line feed). + '\r', // Carriage return. + '\f', // New page. + ' ', // Ordinary space. + 0x00, // NUL-byte. + 0x85, // Delete. + 0xA0, // Non-breaking space. + }) +) // Trim strips whitespace (or other characters) from the beginning and end of a string. +// The optional parameter specifies the additional stripped characters. func Trim(str string, characterMask ...string) string { - if len(characterMask) > 0 { - return strings.Trim(str, characterMask[0]) + if len(characterMask) == 0 { + return strings.Trim(str, defaultTrimChars) } else { - return strings.TrimSpace(str) + return strings.Trim(str, defaultTrimChars+characterMask[0]) } } +// TrimStr strips all of the given string from the beginning and end of a string. +// Note that it does not strips the whitespaces of its beginning or end. +func TrimStr(str string, cut string) string { + return TrimLeftStr(TrimRightStr(str, cut), cut) +} + // TrimLeft strips whitespace (or other characters) from the beginning of a string. func TrimLeft(str string, characterMask ...string) string { - mask := "" if len(characterMask) == 0 { - mask = string([]byte{'\t', '\n', '\v', '\f', '\r', ' ', 0x85, 0xA0}) + return strings.TrimLeft(str, defaultTrimChars) } else { - mask = characterMask[0] + return strings.TrimLeft(str, defaultTrimChars+characterMask[0]) } - return strings.TrimLeft(str, mask) } // TrimLeftStr strips all of the given string from the beginning of a string. +// Note that it does not strips the whitespaces of its beginning. func TrimLeftStr(str string, cut string) string { for str[0:len(cut)] == cut { str = str[len(cut):] @@ -38,19 +61,19 @@ func TrimLeftStr(str string, cut string) string { // TrimRight strips whitespace (or other characters) from the end of a string. func TrimRight(str string, characterMask ...string) string { - mask := "" if len(characterMask) == 0 { - mask = string([]byte{'\t', '\n', '\v', '\f', '\r', ' ', 0x85, 0xA0}) + return strings.TrimRight(str, defaultTrimChars) } else { - mask = characterMask[0] + return strings.TrimRight(str, defaultTrimChars+characterMask[0]) } - return strings.TrimRight(str, mask) } // TrimRightStr strips all of the given string from the end of a string. +// Note that it does not strips the whitespaces of its end. func TrimRightStr(str string, cut string) string { + var length int for { - length := len(str) + length = len(str) if str[length-len(cut):length] == cut { str = str[:length-len(cut)] } else { diff --git a/text/gstr/gstr_z_unit_trim_test.go b/text/gstr/gstr_z_unit_trim_test.go index 0ad6ba7c0..cca73e236 100644 --- a/text/gstr/gstr_z_unit_trim_test.go +++ b/text/gstr/gstr_z_unit_trim_test.go @@ -22,6 +22,15 @@ func Test_Trim(t *testing.T) { }) } +func Test_TrimStr(t *testing.T) { + gtest.Case(t, func() { + gtest.Assert(gstr.TrimStr("gogo我爱gogo", "go"), "我爱") + }) + gtest.Case(t, func() { + gtest.Assert(gstr.TrimStr("啊我爱中国人啊", "啊"), "我爱中国人") + }) +} + func Test_TrimRight(t *testing.T) { gtest.Case(t, func() { gtest.Assert(gstr.TrimRight(" 123456\n "), " 123456") @@ -33,6 +42,9 @@ func Test_TrimRightStr(t *testing.T) { gtest.Case(t, func() { gtest.Assert(gstr.TrimRightStr("gogo我爱gogo", "go"), "gogo我爱") }) + gtest.Case(t, func() { + gtest.Assert(gstr.TrimRightStr("我爱中国人", "人"), "我爱中国") + }) } func Test_TrimLeft(t *testing.T) { @@ -46,4 +58,7 @@ func Test_TrimLeftStr(t *testing.T) { gtest.Case(t, func() { gtest.Assert(gstr.TrimLeftStr("gogo我爱gogo", "go"), "我爱gogo") }) + gtest.Case(t, func() { + gtest.Assert(gstr.TrimLeftStr("我爱中国人", "我爱"), "中国人") + }) } diff --git a/util/gconv/gconv_slice.go b/util/gconv/gconv_slice.go index 1eb61e52f..4157e90b9 100644 --- a/util/gconv/gconv_slice.go +++ b/util/gconv/gconv_slice.go @@ -67,71 +67,86 @@ func Ints(i interface{}) []int { if r, ok := i.([]int); ok { return r } else { - array := make([]int, 0) + var array []int switch value := i.(type) { case []string: - for _, v := range value { - array = append(array, Int(v)) + array = make([]int, len(value)) + for k, v := range value { + array[k] = Int(v) } case []int8: - for _, v := range value { - array = append(array, int(v)) + array = make([]int, len(value)) + for k, v := range value { + array[k] = int(v) } case []int16: - for _, v := range value { - array = append(array, int(v)) + array = make([]int, len(value)) + for k, v := range value { + array[k] = int(v) } case []int32: - for _, v := range value { - array = append(array, int(v)) + array = make([]int, len(value)) + for k, v := range value { + array[k] = int(v) } case []int64: - for _, v := range value { - array = append(array, int(v)) + array = make([]int, len(value)) + for k, v := range value { + array[k] = int(v) } case []uint: - for _, v := range value { - array = append(array, int(v)) + array = make([]int, len(value)) + for k, v := range value { + array[k] = int(v) } case []uint8: - for _, v := range value { - array = append(array, int(v)) + array = make([]int, len(value)) + for k, v := range value { + array[k] = int(v) } case []uint16: - for _, v := range value { - array = append(array, int(v)) + array = make([]int, len(value)) + for k, v := range value { + array[k] = int(v) } case []uint32: - for _, v := range value { - array = append(array, int(v)) + array = make([]int, len(value)) + for k, v := range value { + array[k] = int(v) } case []uint64: - for _, v := range value { - array = append(array, int(v)) + array = make([]int, len(value)) + for k, v := range value { + array[k] = int(v) } case []bool: - for _, v := range value { + array = make([]int, len(value)) + for k, v := range value { if v { - array = append(array, 1) + array[k] = 1 } else { - array = append(array, 0) + array[k] = 0 } } case []float32: - for _, v := range value { - array = append(array, Int(v)) + array = make([]int, len(value)) + for k, v := range value { + array[k] = Int(v) } case []float64: - for _, v := range value { - array = append(array, Int(v)) + array = make([]int, len(value)) + for k, v := range value { + array[k] = Int(v) } case []interface{}: - for _, v := range value { - array = append(array, Int(v)) + array = make([]int, len(value)) + for k, v := range value { + array[k] = Int(v) } case [][]byte: - for _, v := range value { - array = append(array, Int(v)) + array = make([]int, len(value)) + for k, v := range value { + array[k] = Int(v) } default: return []int{Int(i)} @@ -148,67 +163,81 @@ func Uints(i interface{}) []uint { if r, ok := i.([]uint); ok { return r } else { - array := make([]uint, 0) + var array []uint switch value := i.(type) { case []string: - for _, v := range value { - array = append(array, Uint(v)) + array = make([]uint, len(value)) + for k, v := range value { + array[k] = Uint(v) } case []int8: - for _, v := range value { - array = append(array, uint(v)) + array = make([]uint, len(value)) + for k, v := range value { + array[k] = uint(v) } case []int16: - for _, v := range value { - array = append(array, uint(v)) + array = make([]uint, len(value)) + for k, v := range value { + array[k] = uint(v) } case []int32: - for _, v := range value { - array = append(array, uint(v)) + array = make([]uint, len(value)) + for k, v := range value { + array[k] = uint(v) } case []int64: - for _, v := range value { - array = append(array, uint(v)) + array = make([]uint, len(value)) + for k, v := range value { + array[k] = uint(v) } case []uint8: - for _, v := range value { - array = append(array, uint(v)) + array = make([]uint, len(value)) + for k, v := range value { + array[k] = uint(v) } case []uint16: - for _, v := range value { - array = append(array, uint(v)) + array = make([]uint, len(value)) + for k, v := range value { + array[k] = uint(v) } case []uint32: - for _, v := range value { - array = append(array, uint(v)) + array = make([]uint, len(value)) + for k, v := range value { + array[k] = uint(v) } case []uint64: - for _, v := range value { - array = append(array, uint(v)) + array = make([]uint, len(value)) + for k, v := range value { + array[k] = uint(v) } case []bool: - for _, v := range value { + array = make([]uint, len(value)) + for k, v := range value { if v { - array = append(array, 1) + array[k] = 1 } else { - array = append(array, 0) + array[k] = 0 } } case []float32: - for _, v := range value { - array = append(array, Uint(v)) + array = make([]uint, len(value)) + for k, v := range value { + array[k] = Uint(v) } case []float64: - for _, v := range value { - array = append(array, Uint(v)) + array = make([]uint, len(value)) + for k, v := range value { + array[k] = Uint(v) } case []interface{}: - for _, v := range value { - array = append(array, Uint(v)) + array = make([]uint, len(value)) + for k, v := range value { + array[k] = Uint(v) } case [][]byte: - for _, v := range value { - array = append(array, Uint(v)) + array = make([]uint, len(value)) + for k, v := range value { + array[k] = Uint(v) } default: return []uint{Uint(i)} @@ -225,67 +254,82 @@ func Strings(i interface{}) []string { if r, ok := i.([]string); ok { return r } else { - array := make([]string, 0) + var array []string switch value := i.(type) { case []int: - for _, v := range value { - array = append(array, String(v)) + array = make([]string, len(value)) + for k, v := range value { + array[k] = String(v) } case []int8: - for _, v := range value { - array = append(array, String(v)) + array = make([]string, len(value)) + for k, v := range value { + array[k] = String(v) } case []int16: - for _, v := range value { - array = append(array, String(v)) + array = make([]string, len(value)) + for k, v := range value { + array[k] = String(v) } case []int32: - for _, v := range value { - array = append(array, String(v)) + array = make([]string, len(value)) + for k, v := range value { + array[k] = String(v) } case []int64: - for _, v := range value { - array = append(array, String(v)) + array = make([]string, len(value)) + for k, v := range value { + array[k] = String(v) } case []uint: - for _, v := range value { - array = append(array, String(v)) + array = make([]string, len(value)) + for k, v := range value { + array[k] = String(v) } case []uint8: - for _, v := range value { - array = append(array, String(v)) + array = make([]string, len(value)) + for k, v := range value { + array[k] = String(v) } case []uint16: - for _, v := range value { - array = append(array, String(v)) + array = make([]string, len(value)) + for k, v := range value { + array[k] = String(v) } case []uint32: - for _, v := range value { - array = append(array, String(v)) + array = make([]string, len(value)) + for k, v := range value { + array[k] = String(v) } case []uint64: - for _, v := range value { - array = append(array, String(v)) + array = make([]string, len(value)) + for k, v := range value { + array[k] = String(v) } case []bool: - for _, v := range value { - array = append(array, String(v)) + array = make([]string, len(value)) + for k, v := range value { + array[k] = String(v) } case []float32: - for _, v := range value { - array = append(array, String(v)) + array = make([]string, len(value)) + for k, v := range value { + array[k] = String(v) } case []float64: - for _, v := range value { - array = append(array, String(v)) + array = make([]string, len(value)) + for k, v := range value { + array[k] = String(v) } case []interface{}: - for _, v := range value { - array = append(array, String(v)) + array = make([]string, len(value)) + for k, v := range value { + array[k] = String(v) } case [][]byte: - for _, v := range value { - array = append(array, String(v)) + array = make([]string, len(value)) + for k, v := range value { + array[k] = String(v) } default: return []string{String(i)} @@ -302,63 +346,76 @@ func Floats(i interface{}) []float64 { if r, ok := i.([]float64); ok { return r } else { - array := make([]float64, 0) + var array []float64 switch value := i.(type) { case []string: - for _, v := range value { - array = append(array, Float64(v)) + array = make([]float64, len(value)) + for k, v := range value { + array[k] = Float64(v) } case []int: - for _, v := range value { - array = append(array, Float64(v)) + array = make([]float64, len(value)) + for k, v := range value { + array[k] = Float64(v) } case []int8: - for _, v := range value { - array = append(array, Float64(v)) + array = make([]float64, len(value)) + for k, v := range value { + array[k] = Float64(v) } case []int16: - for _, v := range value { - array = append(array, Float64(v)) + array = make([]float64, len(value)) + for k, v := range value { + array[k] = Float64(v) } case []int32: - for _, v := range value { - array = append(array, Float64(v)) + array = make([]float64, len(value)) + for k, v := range value { + array[k] = Float64(v) } case []int64: - for _, v := range value { - array = append(array, Float64(v)) + array = make([]float64, len(value)) + for k, v := range value { + array[k] = Float64(v) } case []uint: for _, v := range value { array = append(array, Float64(v)) } case []uint8: - for _, v := range value { - array = append(array, Float64(v)) + array = make([]float64, len(value)) + for k, v := range value { + array[k] = Float64(v) } case []uint16: - for _, v := range value { - array = append(array, Float64(v)) + array = make([]float64, len(value)) + for k, v := range value { + array[k] = Float64(v) } case []uint32: - for _, v := range value { - array = append(array, Float64(v)) + array = make([]float64, len(value)) + for k, v := range value { + array[k] = Float64(v) } case []uint64: - for _, v := range value { - array = append(array, Float64(v)) + array = make([]float64, len(value)) + for k, v := range value { + array[k] = Float64(v) } case []bool: - for _, v := range value { - array = append(array, Float64(v)) + array = make([]float64, len(value)) + for k, v := range value { + array[k] = Float64(v) } case []float32: - for _, v := range value { - array = append(array, Float64(v)) + array = make([]float64, len(value)) + for k, v := range value { + array[k] = Float64(v) } case []interface{}: - for _, v := range value { - array = append(array, Float64(v)) + array = make([]float64, len(value)) + for k, v := range value { + array[k] = Float64(v) } default: return []float64{Float64(i)} @@ -367,6 +424,11 @@ func Floats(i interface{}) []float64 { } } +// Type assert api for Interfaces. +type apiInterfaces interface { + Interfaces() []interface{} +} + // Interfaces converts to []interface{}. func Interfaces(i interface{}) []interface{} { if i == nil { @@ -374,64 +436,79 @@ func Interfaces(i interface{}) []interface{} { } if r, ok := i.([]interface{}); ok { return r + } else if r, ok := i.(apiInterfaces); ok { + return r.Interfaces() } else { - array := make([]interface{}, 0) + var array []interface{} switch value := i.(type) { case []string: - for _, v := range value { - array = append(array, v) + array = make([]interface{}, len(value)) + for k, v := range value { + array[k] = v } case []int: - for _, v := range value { - array = append(array, v) + array = make([]interface{}, len(value)) + for k, v := range value { + array[k] = v } case []int8: - for _, v := range value { - array = append(array, v) + array = make([]interface{}, len(value)) + for k, v := range value { + array[k] = v } case []int16: - for _, v := range value { - array = append(array, v) + array = make([]interface{}, len(value)) + for k, v := range value { + array[k] = v } case []int32: - for _, v := range value { - array = append(array, v) + array = make([]interface{}, len(value)) + for k, v := range value { + array[k] = v } case []int64: - for _, v := range value { - array = append(array, v) + array = make([]interface{}, len(value)) + for k, v := range value { + array[k] = v } case []uint: - for _, v := range value { - array = append(array, v) + array = make([]interface{}, len(value)) + for k, v := range value { + array[k] = v } case []uint8: - for _, v := range value { - array = append(array, v) + array = make([]interface{}, len(value)) + for k, v := range value { + array[k] = v } case []uint16: - for _, v := range value { - array = append(array, v) + array = make([]interface{}, len(value)) + for k, v := range value { + array[k] = v } case []uint32: for _, v := range value { array = append(array, v) } case []uint64: - for _, v := range value { - array = append(array, v) + array = make([]interface{}, len(value)) + for k, v := range value { + array[k] = v } case []bool: - for _, v := range value { - array = append(array, v) + array = make([]interface{}, len(value)) + for k, v := range value { + array[k] = v } case []float32: - for _, v := range value { - array = append(array, v) + array = make([]interface{}, len(value)) + for k, v := range value { + array[k] = v } case []float64: - for _, v := range value { - array = append(array, v) + array = make([]interface{}, len(value)) + for k, v := range value { + array[k] = v } default: // Finally we use reflection. @@ -443,17 +520,19 @@ func Interfaces(i interface{}) []interface{} { } switch kind { case reflect.Slice, reflect.Array: + array = make([]interface{}, rv.Len()) for i := 0; i < rv.Len(); i++ { - array = append(array, rv.Index(i).Interface()) + array[i] = rv.Index(i).Interface() } case reflect.Struct: rt := rv.Type() + array = make([]interface{}, rv.NumField()) for i := 0; i < rv.NumField(); i++ { // Only public attributes. if !utilstr.IsLetterUpper(rt.Field(i).Name[0]) { continue } - array = append(array, rv.Field(i).Interface()) + array[i] = rv.Field(i).Interface() } default: return []interface{}{i}