improve cache feature of package gdb.Model

This commit is contained in:
John
2020-04-26 21:31:55 +08:00
parent f69da3ace1
commit fa66bf5d9d
7 changed files with 121 additions and 62 deletions

View File

@ -206,15 +206,16 @@ func GetPrimaryKey(pointer interface{}) string {
// GetPrimaryKeyCondition returns a new where condition by primary field name.
// The optional parameter <where> is like follows:
// 123
// []int{1, 2, 3}
// "john"
// []string{"john", "smith"}
// g.Map{"id": g.Slice{1,2,3}}
// g.Map{"id": 1, "name": "john"}
// 123 => primary=123
// []int{1, 2, 3} => primary IN(1,2,3)
// "john" => primary='john'
// []string{"john", "smith"} => primary IN('john','smith')
// g.Map{"id": g.Slice{1,2,3}} => id IN(1,2,3)
// g.Map{"id": 1, "name": "john"} => id=1 AND name='john'
// etc.
//
// Note that it returns the given <where> parameter directly if there's the <primary> is empty.
// Note that it returns the given <where> parameter directly if the <primary> is empty
// or length of <where> > 1.
func GetPrimaryKeyCondition(primary string, where ...interface{}) (newWhereCondition []interface{}) {
if len(where) == 0 {
return nil
@ -231,6 +232,7 @@ func GetPrimaryKeyCondition(primary string, where ...interface{}) (newWhereCondi
}
switch kind {
case reflect.Map, reflect.Struct:
// Ignore the parameter <primary>.
break
default:

View File

@ -18,19 +18,26 @@ import (
// If the parameter <duration> = 0, which means it never expires.
// If the parameter <duration> > 0, which means it expires after <duration>.
//
// The optional parameter <name> is used to bind a name to the cache, which means you can later
// control the cache like changing the <duration> or clearing the cache with specified <name>.
// The optional parameter <name> is used to bind a name to the cache, which means you can
// later control the cache like changing the <duration> or clearing the cache with specified
// <name>.
//
// Note that, the cache feature is disabled if the model is operating on a transaction.
// Note that, the cache feature is disabled if the model is performing select statement
// on a transaction.
func (m *Model) Cache(duration time.Duration, name ...string) *Model {
model := m.getModel()
model.cacheDuration = duration
if len(name) > 0 {
model.cacheName = name[0]
}
// It does not support cache on transaction.
if model.tx == nil {
model.cacheEnabled = true
}
model.cacheEnabled = true
return model
}
// checkAndRemoveCache checks and removes the cache in insert/update/delete statement if
// cache feature is enabled.
func (m *Model) checkAndRemoveCache() {
if m.cacheEnabled && m.cacheDuration < 0 && len(m.cacheName) > 0 {
m.db.GetCache().Remove(m.cacheName)
}
}

View File

@ -7,7 +7,6 @@
package gdb
import (
"github.com/gogf/gf/util/gconv"
"strings"
)
@ -49,7 +48,8 @@ func (m *Model) Having(having interface{}, args ...interface{}) *Model {
// WherePri does the same logic as Model.Where except that if the parameter <where>
// is a single condition like int/string/float/slice, it treats the condition as the primary
// key value. That is, if primary key is "id" and given <where> parameter as "123", the
// WherePri function treats it as "id=123", but Model.Where treats it as string "123".
// WherePri function treats the condition as "id=123", but Model.Where treats the condition
// as string "123".
func (m *Model) WherePri(where interface{}, args ...interface{}) *Model {
if len(args) > 0 {
return m.Where(where, args...)
@ -157,28 +157,3 @@ func (m *Model) Page(page, limit int) *Model {
func (m *Model) ForPage(page, limit int) *Model {
return m.Page(page, limit)
}
// getAll does the query from database.
func (m *Model) getAll(sql string, args ...interface{}) (result Result, err error) {
cacheKey := ""
// Retrieve from cache.
if m.cacheEnabled {
cacheKey = m.cacheName
if len(cacheKey) == 0 {
cacheKey = sql + "/" + gconv.String(args)
}
if v := m.db.GetCache().Get(cacheKey); v != nil {
return v.(Result), nil
}
}
result, err = m.db.DoGetAll(m.getLink(false), sql, m.mergeArguments(args)...)
// Cache the result.
if len(cacheKey) > 0 && err == nil {
if m.cacheDuration < 0 {
m.db.GetCache().Remove(cacheKey)
} else {
m.db.GetCache().Set(cacheKey, result, m.cacheDuration)
}
}
return result, err
}

View File

@ -42,7 +42,7 @@ func (m *Model) All(where ...interface{}) (Result, error) {
}
conditionWhere += softDeletingCondition
}
return m.getAll(
return m.doGetAll(
fmt.Sprintf(
"SELECT %s FROM %s%s",
m.db.QuoteString(m.fields),
@ -268,7 +268,7 @@ func (m *Model) Count(where ...interface{}) (int, error) {
if len(m.groupBy) > 0 {
s = fmt.Sprintf("SELECT COUNT(1) FROM (%s) count_alias", s)
}
list, err := m.getAll(s, conditionArgs...)
list, err := m.doGetAll(s, conditionArgs...)
if err != nil {
return 0, err
}
@ -340,3 +340,28 @@ func (m *Model) FindScan(pointer interface{}, where ...interface{}) error {
}
return m.Scan(pointer)
}
// doGetAll does the select statement on the database.
func (m *Model) doGetAll(sql string, args ...interface{}) (result Result, err error) {
cacheKey := ""
// Retrieve from cache.
if m.cacheEnabled && m.tx == nil {
cacheKey = m.cacheName
if len(cacheKey) == 0 {
cacheKey = sql + "/" + gconv.String(args)
}
if v := m.db.GetCache().Get(cacheKey); v != nil {
return v.(Result), nil
}
}
result, err = m.db.DoGetAll(m.getLink(false), sql, m.mergeArguments(args)...)
// Cache the result.
if cacheKey != "" && err == nil {
if m.cacheDuration < 0 {
m.db.GetCache().Remove(cacheKey)
} else {
m.db.GetCache().Set(cacheKey, result, m.cacheDuration)
}
}
return result, err
}

View File

@ -25,39 +25,39 @@ const (
// If there's no field name for storing creating time, it returns an empty string.
// It checks the key with or without cases or chars '-'/'_'/'.'/' '.
func (m *Model) getSoftFieldNameCreate(table ...string) string {
name := ""
tableName := ""
if len(table) > 0 {
name = table[0]
tableName = table[0]
} else {
name = m.getPrimaryTableName()
tableName = m.getPrimaryTableName()
}
return m.getSoftFieldName(name, gSOFT_FIELD_NAME_CREATE)
return m.getSoftFieldName(tableName, gSOFT_FIELD_NAME_CREATE)
}
// getSoftFieldNameUpdate checks and returns the field name for record updating time.
// If there's no field name for storing updating time, it returns an empty string.
// It checks the key with or without cases or chars '-'/'_'/'.'/' '.
func (m *Model) getSoftFieldNameUpdate(table ...string) (field string) {
name := ""
tableName := ""
if len(table) > 0 {
name = table[0]
tableName = table[0]
} else {
name = m.getPrimaryTableName()
tableName = m.getPrimaryTableName()
}
return m.getSoftFieldName(name, gSOFT_FIELD_NAME_UPDATE)
return m.getSoftFieldName(tableName, gSOFT_FIELD_NAME_UPDATE)
}
// getSoftFieldNameDelete checks and returns the field name for record deleting time.
// If there's no field name for storing deleting time, it returns an empty string.
// It checks the key with or without cases or chars '-'/'_'/'.'/' '.
func (m *Model) getSoftFieldNameDelete(table ...string) (field string) {
name := ""
tableName := ""
if len(table) > 0 {
name = table[0]
tableName = table[0]
} else {
name = m.getPrimaryTableName()
tableName = m.getPrimaryTableName()
}
return m.getSoftFieldName(name, gSOFT_FIELD_NAME_DELETE)
return m.getSoftFieldName(tableName, gSOFT_FIELD_NAME_DELETE)
}
// getSoftFieldName retrieves and returns the field name of the table for possible key.

View File

@ -143,13 +143,6 @@ func (m *Model) getPrimaryKey() string {
return ""
}
// checkAndRemoveCache checks and remove the cache if necessary.
func (m *Model) checkAndRemoveCache() {
if m.cacheEnabled && m.cacheDuration < 0 && len(m.cacheName) > 0 {
m.db.GetCache().Remove(m.cacheName)
}
}
// formatCondition formats where arguments of the model and returns a new condition sql and its arguments.
// Note that this function does not change any attribute value of the <m>.
//

View File

@ -2213,6 +2213,63 @@ func Test_Model_Cache(t *testing.T) {
t.Assert(err, nil)
t.Assert(one["passport"], "user_200")
})
// transaction.
gtest.C(t, func(t *gtest.T) {
// make cache for id 3
one, err := db.Table(table).Cache(time.Second, "test3").FindOne(3)
t.Assert(err, nil)
t.Assert(one["passport"], "user_3")
r, err := db.Table(table).Data("passport", "user_300").Cache(time.Second, "test3").WherePri(3).Update()
t.Assert(err, nil)
n, err := r.RowsAffected()
t.Assert(err, nil)
t.Assert(n, 1)
err = db.Transaction(func(tx *gdb.TX) error {
one, err := tx.Table(table).Cache(time.Second, "test3").FindOne(3)
t.Assert(err, nil)
t.Assert(one["passport"], "user_300")
return nil
})
t.Assert(err, nil)
one, err = db.Table(table).Cache(time.Second, "test3").FindOne(3)
t.Assert(err, nil)
t.Assert(one["passport"], "user_3")
})
gtest.C(t, func(t *gtest.T) {
// make cache for id 4
one, err := db.Table(table).Cache(time.Second, "test4").FindOne(4)
t.Assert(err, nil)
t.Assert(one["passport"], "user_4")
r, err := db.Table(table).Data("passport", "user_400").Cache(time.Second, "test3").WherePri(4).Update()
t.Assert(err, nil)
n, err := r.RowsAffected()
t.Assert(err, nil)
t.Assert(n, 1)
err = db.Transaction(func(tx *gdb.TX) error {
// Cache feature disabled.
one, err := tx.Table(table).Cache(time.Second, "test4").FindOne(4)
t.Assert(err, nil)
t.Assert(one["passport"], "user_400")
// Update the cache.
r, err := tx.Table(table).Data("passport", "user_4000").
Cache(-1, "test4").WherePri(4).Update()
t.Assert(err, nil)
n, err := r.RowsAffected()
t.Assert(err, nil)
t.Assert(n, 1)
return nil
})
t.Assert(err, nil)
// Read from db.
one, err = db.Table(table).Cache(time.Second, "test4").FindOne(4)
t.Assert(err, nil)
t.Assert(one["passport"], "user_4000")
})
}
func Test_Model_Having(t *testing.T) {