diff --git a/g/database/gdb/gdb.go b/g/database/gdb/gdb.go index adb1a6735..f3afee6c9 100644 --- a/g/database/gdb/gdb.go +++ b/g/database/gdb/gdb.go @@ -95,6 +95,7 @@ type DB interface { getCache() *gcache.Cache getChars() (charLeft string, charRight string) getDebug() bool + setSchema(sqlDb *sql.DB, schema string) error filterFields(table string, data map[string]interface{}) map[string]interface{} convertValue(fieldValue interface{}, fieldType string) interface{} getTableFields(table string) (map[string]string, error) @@ -339,10 +340,12 @@ func (bs *dbBase) getSqlDb(master bool) (sqlDb *sql.DB, err error) { sqlDb = v.(*sql.DB) } // 是否开启调试模式 - bs.SetDebug(node.Debug) + bs.db.SetDebug(node.Debug) // 是否手动选择数据库 if v := bs.schema.Val(); v != "" { - sqlDb.Exec("USE " + v) + if e := bs.db.setSchema(sqlDb, v); e != nil { + err = e + } } return } diff --git a/g/database/gdb/gdb_base.go b/g/database/gdb/gdb_base.go index dfc17d577..0d33a3449 100644 --- a/g/database/gdb/gdb_base.go +++ b/g/database/gdb/gdb_base.go @@ -79,6 +79,7 @@ func (bs *dbBase) Query(query string, args ...interface{}) (rows *sql.Rows, err // 数据库sql查询操作,主要执行查询 func (bs *dbBase) doQuery(link dbLink, query string, args ...interface{}) (rows *sql.Rows, err error) { + query, args = formatQuery(query, args) query = bs.db.handleSqlBeforeExec(query) if bs.db.getDebug() { mTime1 := gtime.Millisecond() @@ -115,6 +116,7 @@ func (bs *dbBase) Exec(query string, args ...interface{}) (result sql.Result, er // 执行一条sql,并返回执行情况,主要用于非查询操作 func (bs *dbBase) doExec(link dbLink, query string, args ...interface{}) (result sql.Result, err error) { + query, args = formatQuery(query, args) query = bs.db.handleSqlBeforeExec(query) if bs.db.getDebug() { mTime1 := gtime.Millisecond() @@ -179,28 +181,28 @@ func (bs *dbBase) GetOne(query string, args ...interface{}) (Record, error) { } // 数据库查询,查询单条记录,自动映射数据到给定的struct对象中 -func (bs *dbBase) GetStruct(objPointer interface{}, query string, args ...interface{}) error { +func (bs *dbBase) GetStruct(pointer interface{}, query string, args ...interface{}) error { one, err := bs.GetOne(query, args...) if err != nil { return err } - return one.ToStruct(objPointer) + return one.ToStruct(pointer) } // 数据库查询,查询多条记录,并自动转换为指定的slice对象, 如: []struct/[]*struct。 -func (bs *dbBase) GetStructs(objPointerSlice interface{}, query string, args ...interface{}) error { +func (bs *dbBase) GetStructs(pointer interface{}, query string, args ...interface{}) error { all, err := bs.GetAll(query, args...) if err != nil { return err } - return all.ToStructs(objPointerSlice) + return all.ToStructs(pointer) } // 将结果转换为指定的struct/*struct/[]struct/[]*struct, // 参数应该为指针类型,否则返回失败。 // 该方法自动识别参数类型,调用Struct/Structs方法。 -func (bs *dbBase) GetScan(objPointer interface{}, query string, args ...interface{}) error { - t := reflect.TypeOf(objPointer) +func (bs *dbBase) GetScan(pointer interface{}, query string, args ...interface{}) error { + t := reflect.TypeOf(pointer) k := t.Kind() if k != reflect.Ptr { return fmt.Errorf("params should be type of pointer, but got: %v", k) @@ -208,9 +210,9 @@ func (bs *dbBase) GetScan(objPointer interface{}, query string, args ...interfac k = t.Elem().Kind() switch k { case reflect.Array, reflect.Slice: - return bs.db.GetStructs(objPointer, query, args...) + return bs.db.GetStructs(pointer, query, args...) case reflect.Struct: - return bs.db.GetStruct(objPointer, query, args...) + return bs.db.GetStruct(pointer, query, args...) } return fmt.Errorf("element type should be type of struct/slice, unsupported: %v", k) } @@ -613,3 +615,9 @@ func (bs *dbBase) rowsToResult(rows *sql.Rows) (Result, error) { } return records, nil } + +// 动态切换数据库 +func (bs *dbBase) setSchema(sqlDb *sql.DB, schema string) error { + _, err := sqlDb.Exec("USE " + schema) + return err +} diff --git a/g/database/gdb/gdb_func.go b/g/database/gdb/gdb_func.go index 090694155..1dd68c58a 100644 --- a/g/database/gdb/gdb_func.go +++ b/g/database/gdb/gdb_func.go @@ -27,7 +27,49 @@ type apiString interface { String() string } -// 格式化Where查询条件 +// 格式化SQL语句。 +// 1. 支持参数只传一个slice; +// 2. 支持占位符号数量自动扩展; +func formatQuery(query string, args []interface{}) (newQuery string, newArgs []interface{}) { + newQuery = query + // 查询条件参数处理,主要处理slice参数类型 + if len(args) > 0 { + for index, arg := range args { + rv := reflect.ValueOf(arg) + kind := rv.Kind() + if kind == reflect.Ptr { + rv = rv.Elem() + kind = rv.Kind() + } + switch kind { + // '?'占位符支持slice类型, 这里会将slice参数拆散,并更新原有占位符'?'为多个'?',使用','符号连接。 + case reflect.Slice, reflect.Array: + for i := 0; i < rv.Len(); i++ { + newArgs = append(newArgs, rv.Index(i).Interface()) + } + // 如果参数直接传递slice,并且占位符数量与slice长度相等, + // 那么不用替换扩展占位符数量,直接使用该slice作为查询参数 + if len(args) == 1 && gstr.Count(newQuery, "?") == rv.Len() { + break + } + // counter用于匹配该参数的位置(与index对应) + counter := 0 + newQuery, _ = gregex.ReplaceStringFunc(`\?`, newQuery, func(s string) string { + counter++ + if counter == index+1 { + return "?" + strings.Repeat(",?", rv.Len()-1) + } + return s + }) + default: + newArgs = append(newArgs, arg) + } + } + } + return +} + +// 格式化Where查询条件。 func formatWhere(where interface{}, args []interface{}) (newWhere string, newArgs []interface{}) { // 条件字符串处理 buffer := bytes.NewBuffer(nil) @@ -38,7 +80,6 @@ func formatWhere(where interface{}, args []interface{}) (newWhere string, newArg rv = rv.Elem() kind = rv.Kind() } - tmpArgs := []interface{}(nil) switch kind { // map/struct类型 case reflect.Map: @@ -57,19 +98,20 @@ func formatWhere(where interface{}, args []interface{}) (newWhere string, newArg count := gstr.Count(key, "?") if count == 0 { buffer.WriteString(key + " IN(?)") - tmpArgs = append(tmpArgs, value) + newArgs = append(newArgs, value) } else if count != rv.Len() { buffer.WriteString(key) - tmpArgs = append(tmpArgs, value) + newArgs = append(newArgs, value) } else { buffer.WriteString(key) // 如果键名/属性名称中带有多个?占位符号,那么将参数打散 - tmpArgs = append(tmpArgs, gconv.Interfaces(value)...) + newArgs = append(newArgs, gconv.Interfaces(value)...) } default: if value == nil { buffer.WriteString(key) } else { + // 支持key带操作符号 if gstr.Pos(key, "?") == -1 { if gstr.Pos(key, "<") == -1 && gstr.Pos(key, ">") == -1 && gstr.Pos(key, "=") == -1 { buffer.WriteString(key + "=?") @@ -79,7 +121,7 @@ func formatWhere(where interface{}, args []interface{}) (newWhere string, newArg } else { buffer.WriteString(key) } - tmpArgs = append(tmpArgs, value) + newArgs = append(newArgs, value) } } } @@ -91,45 +133,16 @@ func formatWhere(where interface{}, args []interface{}) (newWhere string, newArg if buffer.Len() == 0 { return "", args } + newArgs = append(newArgs, args...) newWhere = buffer.String() - tmpArgs = append(tmpArgs, args...) // 查询条件参数处理,主要处理slice参数类型 - if len(tmpArgs) > 0 { - for index, arg := range tmpArgs { - rv := reflect.ValueOf(arg) - kind := rv.Kind() - if kind == reflect.Ptr { - rv = rv.Elem() - kind = rv.Kind() - } - switch kind { - // '?'占位符支持slice类型, - // 这里会将slice参数拆散,并更新原有占位符'?'为多个'?',使用','符号连接。 - case reflect.Slice: - fallthrough - case reflect.Array: - for i := 0; i < rv.Len(); i++ { - newArgs = append(newArgs, rv.Index(i).Interface()) - } - // counter用于匹配该参数的位置(与index对应) - counter := 0 - newWhere, _ = gregex.ReplaceStringFunc(`\?`, newWhere, func(s string) string { - counter++ - if counter == index+1 { - return "?" + strings.Repeat(",?", rv.Len()-1) - } - return s - }) - default: - // 支持例如 Where/And/Or("uid", 1) 这种格式 - if gstr.Pos(newWhere, "?") == -1 { - if gstr.Pos(newWhere, "<") == -1 && gstr.Pos(newWhere, ">") == -1 && gstr.Pos(newWhere, "=") == -1 { - newWhere += "=?" - } else { - newWhere += "?" - } - } - newArgs = append(newArgs, arg) + if len(newArgs) > 0 { + // 支持例如 Where/And/Or("uid", 1) 这种格式 + if gstr.Pos(newWhere, "?") == -1 { + if gstr.Pos(newWhere, "<") == -1 && gstr.Pos(newWhere, ">") == -1 && gstr.Pos(newWhere, "=") == -1 { + newWhere += "=?" + } else { + newWhere += "?" } } } diff --git a/g/database/gdb/gdb_model.go b/g/database/gdb/gdb_model.go index ef61c8041..f2ab4d437 100644 --- a/g/database/gdb/gdb_model.go +++ b/g/database/gdb/gdb_model.go @@ -30,6 +30,7 @@ type Model struct { orderBy string // 排序语句 start int // 分页开始 limit int // 分页条数 + offset int // 查询偏移量(OFFSET语法) data interface{} // 操作数据(注意仅支持Map/List/string类型) batch int // 批量操作条数 filter bool // 是否按照表字段过滤data参数 @@ -47,6 +48,7 @@ func (bs *dbBase) Table(tables string) *Model { tables: tables, fields: "*", start: -1, + offset: -1, safe: false, } } @@ -214,6 +216,14 @@ func (md *Model) Limit(limit ...int) *Model { return model } +// 链式操作,OFFSET语法(部分数据库支持)。 +// 注意:可以使用Limit方法调用替换该方法特性,底层不同数据库将会自动替换LIMIT语法为OFFSET语法。 +func (md *Model) Offset(offset int) *Model { + model := md.getModel() + model.offset = offset + return model +} + // 链式操作,翻页,注意分页页码从1开始,而Limit方法从0开始。 func (md *Model) ForPage(page, limit int) *Model { model := md.getModel() @@ -605,11 +615,13 @@ func (md *Model) getConditionSql() string { } if md.limit != 0 { if md.start >= 0 { - s += fmt.Sprintf(" LIMIT %d, %d", md.start, md.limit) + s += fmt.Sprintf(" LIMIT %d,%d", md.start, md.limit) } else { s += fmt.Sprintf(" LIMIT %d", md.limit) } - + } + if md.offset >= 0 { + s += fmt.Sprintf(" OFFSET %d", md.offset) } return s } diff --git a/g/database/gdb/gdb_pgsql.go b/g/database/gdb/gdb_pgsql.go index 60d921b1a..44a1c72e2 100644 --- a/g/database/gdb/gdb_pgsql.go +++ b/g/database/gdb/gdb_pgsql.go @@ -9,12 +9,16 @@ package gdb import ( "database/sql" "fmt" - "regexp" + + "github.com/gogf/gf/g/text/gregex" ) // PostgreSQL的适配. +// // 使用时需要import: +// // _ "github.com/gogf/gf/third/github.com/lib/pq" +// // @todo 需要完善replace和save的操作覆盖 // 数据库链接对象 @@ -37,6 +41,12 @@ func (db *dbPgsql) Open(config *ConfigNode) (*sql.DB, error) { } } +// 动态切换数据库 +func (db *dbPgsql) setSchema(sqlDb *sql.DB, schema string) error { + _, err := sqlDb.Exec("SET search_path TO " + schema) + return err +} + // 获得关键字操作符 func (db *dbPgsql) getChars() (charLeft string, charRight string) { return "\"", "\"" @@ -44,11 +54,12 @@ func (db *dbPgsql) getChars() (charLeft string, charRight string) { // 在执行sql之前对sql进行进一步处理 func (db *dbPgsql) handleSqlBeforeExec(query string) string { - reg := regexp.MustCompile("\\?") index := 0 - str := reg.ReplaceAllStringFunc(query, func(s string) string { + query, _ = gregex.ReplaceStringFunc("\\?", query, func(s string) string { index++ return fmt.Sprintf("$%d", index) }) - return str + // 分页语法替换 + query, _ = gregex.ReplaceString(` LIMIT (\d+),\s*(\d+)`, ` LIMIT $1 OFFSET $2`, query) + return query } diff --git a/g/database/gdb/gdb_sqlite.go b/g/database/gdb/gdb_sqlite.go index f368cd548..708243231 100644 --- a/g/database/gdb/gdb_sqlite.go +++ b/g/database/gdb/gdb_sqlite.go @@ -41,7 +41,7 @@ func (db *dbSqlite) getChars() (charLeft string, charRight string) { return "`", "`" } -// 在执行sql之前对sql进行进一步处理 +// 在执行sql之前对sql进行进一步处理。 // @todo 需要增加对Save方法的支持,可使用正则来实现替换, // @todo 将ON DUPLICATE KEY UPDATE触发器修改为两条SQL语句(INSERT OR IGNORE & UPDATE) func (db *dbSqlite) handleSqlBeforeExec(query string) string { diff --git a/g/database/gdb/gdb_unit_method_test.go b/g/database/gdb/gdb_unit_method_test.go index 886277a24..21f72c1b7 100644 --- a/g/database/gdb/gdb_unit_method_test.go +++ b/g/database/gdb/gdb_unit_method_test.go @@ -7,11 +7,12 @@ package gdb_test import ( + "testing" + "time" + "github.com/gogf/gf/g" "github.com/gogf/gf/g/os/gtime" "github.com/gogf/gf/g/test/gtest" - "testing" - "time" ) func TestDbBase_Ping(t *testing.T) { @@ -24,12 +25,17 @@ func TestDbBase_Ping(t *testing.T) { } func TestDbBase_Query(t *testing.T) { - if _, err := db.Query("SELECT ?", 1); err != nil { - gtest.Fatal(err) - } - if _, err := db.Query("ERROR"); err == nil { - gtest.Fatal("FAIL") - } + gtest.Case(t, func() { + _, err := db.Query("SELECT ?", 1) + gtest.Assert(err, nil) + _, err = db.Query("SELECT ?+?", 1, 2) + gtest.Assert(err, nil) + _, err = db.Query("SELECT ?+?", g.Slice{1, 2}) + gtest.Assert(err, nil) + _, err = db.Query("ERROR") + gtest.AssertNE(err, nil) + }) + } func TestDbBase_Exec(t *testing.T) { @@ -290,11 +296,44 @@ func TestDbBase_Update(t *testing.T) { } func TestDbBase_GetAll(t *testing.T) { - if result, err := db.GetAll("SELECT * FROM user WHERE id=?", 1); err != nil { - gtest.Fatal(err) - } else { + gtest.Case(t, func() { + result, err := db.GetAll("SELECT * FROM user WHERE id=?", 1) + gtest.Assert(err, nil) gtest.Assert(len(result), 1) - } + gtest.Assert(result[0]["id"].Int(), 1) + }) + gtest.Case(t, func() { + result, err := db.GetAll("SELECT * FROM user WHERE id in(?)", g.Slice{1, 2, 3}) + gtest.Assert(err, nil) + gtest.Assert(len(result), 3) + gtest.Assert(result[0]["id"].Int(), 1) + gtest.Assert(result[1]["id"].Int(), 2) + gtest.Assert(result[2]["id"].Int(), 3) + }) + gtest.Case(t, func() { + result, err := db.GetAll("SELECT * FROM user WHERE id in(?,?,?)", g.Slice{1, 2, 3}) + gtest.Assert(err, nil) + gtest.Assert(len(result), 3) + gtest.Assert(result[0]["id"].Int(), 1) + gtest.Assert(result[1]["id"].Int(), 2) + gtest.Assert(result[2]["id"].Int(), 3) + }) + gtest.Case(t, func() { + result, err := db.GetAll("SELECT * FROM user WHERE id in(?,?,?)", g.Slice{1, 2, 3}...) + gtest.Assert(err, nil) + gtest.Assert(len(result), 3) + gtest.Assert(result[0]["id"].Int(), 1) + gtest.Assert(result[1]["id"].Int(), 2) + gtest.Assert(result[2]["id"].Int(), 3) + }) + gtest.Case(t, func() { + result, err := db.GetAll("SELECT * FROM user WHERE id>=? AND id <=?", g.Slice{1, 3}) + gtest.Assert(err, nil) + gtest.Assert(len(result), 3) + gtest.Assert(result[0]["id"].Int(), 1) + gtest.Assert(result[1]["id"].Int(), 2) + gtest.Assert(result[2]["id"].Int(), 3) + }) } func TestDbBase_GetOne(t *testing.T) { diff --git a/g/database/gdb/gdb_unit_model_test.go b/g/database/gdb/gdb_unit_model_test.go index 2971ad3bd..e815de0bd 100644 --- a/g/database/gdb/gdb_unit_model_test.go +++ b/g/database/gdb/gdb_unit_model_test.go @@ -682,7 +682,14 @@ func TestModel_Where(t *testing.T) { gtest.Assert(result["id"].Int(), 3) }) gtest.Case(t, func() { - result, err := db.Table("user").Where("passport like ? and nickname like ?", g.Slice{"t3", "T3"}...).One() + result, err := db.Table("user").Where("id=? AND nickname=?", g.Slice{3, "T3"}).One() + if err != nil { + gtest.Fatal(err) + } + gtest.Assert(result["id"].Int(), 3) + }) + gtest.Case(t, func() { + result, err := db.Table("user").Where("passport like ? and nickname like ?", g.Slice{"t3", "T3"}).One() if err != nil { gtest.Fatal(err) } diff --git a/g/database/gdb/gdb_unit_transaction_test.go b/g/database/gdb/gdb_unit_transaction_test.go index 835004241..5ddedabd6 100644 --- a/g/database/gdb/gdb_unit_transaction_test.go +++ b/g/database/gdb/gdb_unit_transaction_test.go @@ -7,10 +7,11 @@ package gdb_test import ( + "testing" + "github.com/gogf/gf/g" "github.com/gogf/gf/g/os/gtime" "github.com/gogf/gf/g/test/gtest" - "testing" ) func TestTX_Query(t *testing.T) { @@ -23,6 +24,16 @@ func TestTX_Query(t *testing.T) { } else { rows.Close() } + if rows, err := tx.Query("SELECT ?+?", 1, 2); err != nil { + gtest.Fatal(err) + } else { + rows.Close() + } + if rows, err := tx.Query("SELECT ?+?", g.Slice{1, 2}); err != nil { + gtest.Fatal(err) + } else { + rows.Close() + } if _, err := tx.Query("ERROR"); err == nil { gtest.Fatal("FAIL") } @@ -39,6 +50,12 @@ func TestTX_Exec(t *testing.T) { if _, err := tx.Exec("SELECT ?", 1); err != nil { gtest.Fatal(err) } + if _, err := tx.Exec("SELECT ?+?", 1, 2); err != nil { + gtest.Fatal(err) + } + if _, err := tx.Exec("SELECT ?+?", g.Slice{1, 2}); err != nil { + gtest.Fatal(err) + } if _, err := tx.Exec("ERROR"); err == nil { gtest.Fatal("FAIL") } diff --git a/g/os/gcache/gcache.go b/g/os/gcache/gcache.go index 6e3fa7e6e..8815b4da7 100644 --- a/g/os/gcache/gcache.go +++ b/g/os/gcache/gcache.go @@ -12,21 +12,21 @@ var cache = New() // Set sets cache with - pair, which is expired after milliseconds. // If <=0 means it does not expire. -func Set(key interface{}, value interface{}, expire int) { - cache.Set(key, value, expire) +func Set(key interface{}, value interface{}, duration interface{}) { + cache.Set(key, value, duration) } // SetIfNotExist sets cache with - pair if does not exist in the cache, // which is expired after milliseconds. // If <=0 means it does not expire. -func SetIfNotExist(key interface{}, value interface{}, expire int) bool { - return cache.SetIfNotExist(key, value, expire) +func SetIfNotExist(key interface{}, value interface{}, duration interface{}) bool { + return cache.SetIfNotExist(key, value, duration) } // Sets batch sets cache with key-value pairs by , which is expired after milliseconds. // If <=0 means it does not expire. -func Sets(data map[interface{}]interface{}, expire int) { - cache.Sets(data, expire) +func Sets(data map[interface{}]interface{}, duration interface{}) { + cache.Sets(data, duration) } // Get returns the value of . @@ -39,8 +39,8 @@ func Get(key interface{}) interface{} { // or sets - pair and returns if does not exist in the cache. // The key-value pair expires after milliseconds. // If <=0 means it does not expire. -func GetOrSet(key interface{}, value interface{}, expire int) interface{} { - return cache.GetOrSet(key, value, expire) +func GetOrSet(key interface{}, value interface{}, duration interface{}) interface{} { + return cache.GetOrSet(key, value, duration) } // GetOrSetFunc returns the value of , @@ -48,8 +48,8 @@ func GetOrSet(key interface{}, value interface{}, expire int) interface{} { // if does not exist in the cache. // The key-value pair expires after milliseconds. // If <=0 means it does not expire. -func GetOrSetFunc(key interface{}, f func() interface{}, expire int) interface{} { - return cache.GetOrSetFunc(key, f, expire) +func GetOrSetFunc(key interface{}, f func() interface{}, duration interface{}) interface{} { + return cache.GetOrSetFunc(key, f, duration) } // GetOrSetFuncLock returns the value of , @@ -59,8 +59,8 @@ func GetOrSetFunc(key interface{}, f func() interface{}, expire int) interface{} // If <=0 means it does not expire. // // Note that the function is executed within writing mutex lock. -func GetOrSetFuncLock(key interface{}, f func() interface{}, expire int) interface{} { - return cache.GetOrSetFuncLock(key, f, expire) +func GetOrSetFuncLock(key interface{}, f func() interface{}, duration interface{}) interface{} { + return cache.GetOrSetFuncLock(key, f, duration) } // Contains returns true if exists in the cache, or else returns false. diff --git a/g/os/gcache/gcache_mem_cache.go b/g/os/gcache/gcache_mem_cache.go index edb648c10..976d2c5a1 100644 --- a/g/os/gcache/gcache_mem_cache.go +++ b/g/os/gcache/gcache_mem_cache.go @@ -9,6 +9,7 @@ package gcache import ( "math" "sync" + "time" "github.com/gogf/gf/g/container/glist" "github.com/gogf/gf/g/container/gset" @@ -103,9 +104,21 @@ func (c *memCache) getOrNewExpireSet(expire int64) (expireSet *gset.Set) { return } +// getMilliExpire converts parameter to int type in milliseconds. +// +// Note that there's some performance cost in type assertion here, but it's valuable. +func (c *memCache) getMilliExpire(duration interface{}) int { + if d, ok := duration.(time.Duration); ok { + return int(d.Nanoseconds() / 1000000) + } else { + return duration.(int) + } +} + // Set sets cache with - pair, which is expired after milliseconds. // If <=0 means it does not expire. -func (c *memCache) Set(key interface{}, value interface{}, expire int) { +func (c *memCache) Set(key interface{}, value interface{}, duration interface{}) { + expire := c.getMilliExpire(duration) expireTime := c.getInternalExpire(expire) c.dataMu.Lock() c.data[key] = memCacheItem{v: value, e: expireTime} @@ -119,7 +132,8 @@ func (c *memCache) Set(key interface{}, value interface{}, expire int) { // // It doubly checks the whether exists in the cache using mutex writing lock // before setting it to the cache. -func (c *memCache) doSetWithLockCheck(key interface{}, value interface{}, expire int) interface{} { +func (c *memCache) doSetWithLockCheck(key interface{}, value interface{}, duration interface{}) interface{} { + expire := c.getMilliExpire(duration) expireTimestamp := c.getInternalExpire(expire) c.dataMu.Lock() defer c.dataMu.Unlock() @@ -149,7 +163,8 @@ func (c *memCache) getInternalExpire(expire int) int64 { // SetIfNotExist sets cache with - pair if does not exist in the cache, // which is expired after milliseconds. // If <=0 means it does not expire. -func (c *memCache) SetIfNotExist(key interface{}, value interface{}, expire int) bool { +func (c *memCache) SetIfNotExist(key interface{}, value interface{}, duration interface{}) bool { + expire := c.getMilliExpire(duration) if !c.Contains(key) { c.doSetWithLockCheck(key, value, expire) return true @@ -159,7 +174,8 @@ func (c *memCache) SetIfNotExist(key interface{}, value interface{}, expire int) // Sets batch sets cache with key-value pairs by , which is expired after milliseconds. // If <=0 means it does not expire. -func (c *memCache) Sets(data map[interface{}]interface{}, expire int) { +func (c *memCache) Sets(data map[interface{}]interface{}, duration interface{}) { + expire := c.getMilliExpire(duration) expireTime := c.getInternalExpire(expire) for k, v := range data { c.dataMu.Lock() @@ -189,9 +205,9 @@ func (c *memCache) Get(key interface{}) interface{} { // or sets - pair and returns if does not exist in the cache. // The key-value pair expires after milliseconds. // If <=0 means it does not expire. -func (c *memCache) GetOrSet(key interface{}, value interface{}, expire int) interface{} { +func (c *memCache) GetOrSet(key interface{}, value interface{}, duration interface{}) interface{} { if v := c.Get(key); v == nil { - return c.doSetWithLockCheck(key, value, expire) + return c.doSetWithLockCheck(key, value, duration) } else { return v } @@ -202,9 +218,9 @@ func (c *memCache) GetOrSet(key interface{}, value interface{}, expire int) inte // if does not exist in the cache. // The key-value pair expires after milliseconds. // If <=0 means it does not expire. -func (c *memCache) GetOrSetFunc(key interface{}, f func() interface{}, expire int) interface{} { +func (c *memCache) GetOrSetFunc(key interface{}, f func() interface{}, duration interface{}) interface{} { if v := c.Get(key); v == nil { - return c.doSetWithLockCheck(key, f(), expire) + return c.doSetWithLockCheck(key, f(), duration) } else { return v } @@ -217,9 +233,9 @@ func (c *memCache) GetOrSetFunc(key interface{}, f func() interface{}, expire in // If <=0 means it does not expire. // // Note that the function is executed within writing mutex lock. -func (c *memCache) GetOrSetFuncLock(key interface{}, f func() interface{}, expire int) interface{} { +func (c *memCache) GetOrSetFuncLock(key interface{}, f func() interface{}, duration interface{}) interface{} { if v := c.Get(key); v == nil { - return c.doSetWithLockCheck(key, f, expire) + return c.doSetWithLockCheck(key, f, duration) } else { return v } diff --git a/g/os/gcache/gcache_z_unit_1_test.go b/g/os/gcache/gcache_z_unit_1_test.go index 0cd6ea07c..3c89dad8f 100644 --- a/g/os/gcache/gcache_z_unit_1_test.go +++ b/g/os/gcache/gcache_z_unit_1_test.go @@ -9,13 +9,14 @@ package gcache_test import ( + "testing" + "time" + "github.com/gogf/gf/g" "github.com/gogf/gf/g/container/gset" "github.com/gogf/gf/g/os/gcache" "github.com/gogf/gf/g/os/grpool" "github.com/gogf/gf/g/test/gtest" - "testing" - "time" ) //clear 用于清除全局缓存,因gcache api 暂未暴露 Clear 方法 @@ -49,6 +50,14 @@ func TestCache_Set_Expire(t *testing.T) { gtest.Assert(cache.Size(), 0) cache.Close() }) + + gtest.Case(t, func() { + cache := gcache.New() + cache.Set(1, 11, 100*time.Millisecond) + gtest.Assert(cache.Get(1), 11) + time.Sleep(200 * time.Millisecond) + gtest.Assert(cache.Get(1), nil) + }) } func TestCache_Keys_Values(t *testing.T) { @@ -205,7 +214,7 @@ func TestCache_SetConcurrency(t *testing.T) { }() select { case <-time.After(2 * time.Second): - t.Log("first part end") + //t.Log("first part end") } go func() { @@ -217,7 +226,7 @@ func TestCache_SetConcurrency(t *testing.T) { }() select { case <-time.After(2 * time.Second): - t.Log("second part end") + //t.Log("second part end") } }) } diff --git a/g/os/gfcache/gfcache.go b/g/os/gfcache/gfcache.go index 9cea2062b..d32bb1f18 100644 --- a/g/os/gfcache/gfcache.go +++ b/g/os/gfcache/gfcache.go @@ -8,6 +8,8 @@ package gfcache import ( + "time" + "github.com/gogf/gf/g/internal/cmdenv" "github.com/gogf/gf/g/os/gcache" "github.com/gogf/gf/g/os/gfile" @@ -27,18 +29,18 @@ var ( // GetContents returns string content of given file by from cache. // If there's no content in the cache, it will read it from disk file specified by . // The parameter specifies the caching time for this file content in seconds. -func GetContents(path string, expire ...int) string { - return string(GetBinContents(path, expire...)) +func GetContents(path string, duration ...interface{}) string { + return string(GetBinContents(path, duration...)) } // GetBinContents returns []byte content of given file by from cache. // If there's no content in the cache, it will read it from disk file specified by . // The parameter specifies the caching time for this file content in seconds. -func GetBinContents(path string, expire ...int) []byte { +func GetBinContents(path string, duration ...interface{}) []byte { k := cacheKey(path) e := cacheExpire - if len(expire) > 0 { - e = expire[0] + if len(duration) > 0 { + e = getSecondExpire(duration[0]) } r := gcache.GetOrSetFuncLock(k, func() interface{} { b := gfile.GetBinContents(path) @@ -58,7 +60,18 @@ func GetBinContents(path string, expire ...int) []byte { return nil } -// 生成缓存键名 +// getSecondExpire converts parameter to int type in seconds. +// +// Note that there's some performance cost in type assertion here, but it's valuable. +func getSecondExpire(duration interface{}) int { + if d, ok := duration.(time.Duration); ok { + return int(d.Nanoseconds() / 1000000000) + } else { + return duration.(int) + } +} + +// cacheKey produces the cache key for gcache. func cacheKey(path string) string { return "gf.gfcache:" + path } diff --git a/geg/database/gdb/mysql/gdb_args_slice.go b/geg/database/gdb/mysql/gdb_args_slice.go index 28e3193c8..9c7c568ad 100644 --- a/geg/database/gdb/mysql/gdb_args_slice.go +++ b/geg/database/gdb/mysql/gdb_args_slice.go @@ -6,6 +6,9 @@ import ( func main() { db := g.DB() + + db.Table("user").Where("nickname like ? and passport like ?", g.Slice{"T3", "t3"}).OrderBy("id asc").All() + conditions := g.Map{ "nickname like ?": "%T%", "id between ? and ?": g.Slice{1, 3}, diff --git a/geg/other/test.go b/geg/other/test.go index 381a581b9..5bd1eae9c 100644 --- a/geg/other/test.go +++ b/geg/other/test.go @@ -1,19 +1,13 @@ package main import ( - "encoding/base64" "fmt" - "github.com/gogf/gf/g/encoding/gbase64" + + "github.com/gogf/gf/g/text/gregex" ) func main() { - data := "HwHsGhXMaGc===" - datab, err := gbase64.Decode([]byte(data)) - fmt.Println(err) - fmt.Println(datab) - fmt.Println(string(datab)) - - s, e := base64.StdEncoding.DecodeString(data) - fmt.Println(e) - fmt.Println(string(s)) + query := "SELECT * FROM user where status=1 LIMIT 10, 100" + query, _ = gregex.ReplaceString(` LIMIT (\d+),\s*(\d+)`, ` LIMIT $1 OFFSET $2`, query) + fmt.Println(query) }