Merge branch 'feature_1.8.0' of https://github.com/gogf/gf into feature_1.8.0

This commit is contained in:
john
2019-07-09 13:27:34 +08:00
15 changed files with 262 additions and 117 deletions

View File

@ -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
}

View File

@ -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
}

View File

@ -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 += "?"
}
}
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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 {

View File

@ -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) {

View File

@ -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)
}

View File

@ -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")
}

View File

@ -12,21 +12,21 @@ var cache = New()
// Set sets cache with <key>-<value> pair, which is expired after <expire> milliseconds.
// If <expire> <=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 <key>-<value> pair if <key> does not exist in the cache,
// which is expired after <expire> milliseconds.
// If <expire> <=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 <data>, which is expired after <expire> milliseconds.
// If <expire> <=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 <key>.
@ -39,8 +39,8 @@ func Get(key interface{}) interface{} {
// or sets <key>-<value> pair and returns <value> if <key> does not exist in the cache.
// The key-value pair expires after <expire> milliseconds.
// If <expire> <=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 <key>,
@ -48,8 +48,8 @@ func GetOrSet(key interface{}, value interface{}, expire int) interface{} {
// if <key> does not exist in the cache.
// The key-value pair expires after <expire> milliseconds.
// If <expire> <=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 <key>,
@ -59,8 +59,8 @@ func GetOrSetFunc(key interface{}, f func() interface{}, expire int) interface{}
// If <expire> <=0 means it does not expire.
//
// Note that the function <f> 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 <key> exists in the cache, or else returns false.

View File

@ -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 <duration> 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 <key>-<value> pair, which is expired after <expire> milliseconds.
// If <expire> <=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 <key> 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 <key>-<value> pair if <key> does not exist in the cache,
// which is expired after <expire> milliseconds.
// If <expire> <=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 <data>, which is expired after <expire> milliseconds.
// If <expire> <=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 <key>-<value> pair and returns <value> if <key> does not exist in the cache.
// The key-value pair expires after <expire> milliseconds.
// If <expire> <=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 <key> does not exist in the cache.
// The key-value pair expires after <expire> milliseconds.
// If <expire> <=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 <expire> <=0 means it does not expire.
//
// Note that the function <f> 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
}

View File

@ -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")
}
})
}

View File

@ -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 <path> from cache.
// If there's no content in the cache, it will read it from disk file specified by <path>.
// The parameter <expire> 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 <path> from cache.
// If there's no content in the cache, it will read it from disk file specified by <path>.
// The parameter <expire> 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 <duration> 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
}

View File

@ -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},

View File

@ -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)
}