From fa66bf5d9d0bfee1ff6b8606fe2ae60c1be82f08 Mon Sep 17 00:00:00 2001 From: John Date: Sun, 26 Apr 2020 21:31:55 +0800 Subject: [PATCH] improve cache feature of package gdb.Model --- database/gdb/gdb_func.go | 16 +++--- database/gdb/gdb_model_cache.go | 21 +++++--- database/gdb/gdb_model_condition.go | 29 +---------- database/gdb/gdb_model_select.go | 29 ++++++++++- database/gdb/gdb_model_time.go | 24 ++++----- database/gdb/gdb_model_utility.go | 7 --- database/gdb/gdb_unit_z_mysql_model_test.go | 57 +++++++++++++++++++++ 7 files changed, 121 insertions(+), 62 deletions(-) diff --git a/database/gdb/gdb_func.go b/database/gdb/gdb_func.go index f18f3951f..4354c2a52 100644 --- a/database/gdb/gdb_func.go +++ b/database/gdb/gdb_func.go @@ -206,15 +206,16 @@ func GetPrimaryKey(pointer interface{}) string { // GetPrimaryKeyCondition returns a new where condition by primary field name. // The optional parameter 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 parameter directly if there's the is empty. +// Note that it returns the given parameter directly if the is empty +// or length of > 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 . break default: diff --git a/database/gdb/gdb_model_cache.go b/database/gdb/gdb_model_cache.go index 016c0b945..549f0c7b7 100644 --- a/database/gdb/gdb_model_cache.go +++ b/database/gdb/gdb_model_cache.go @@ -18,19 +18,26 @@ import ( // If the parameter = 0, which means it never expires. // If the parameter > 0, which means it expires after . // -// The optional parameter is used to bind a name to the cache, which means you can later -// control the cache like changing the or clearing the cache with specified . +// The optional parameter is used to bind a name to the cache, which means you can +// later control the cache like changing the or clearing the cache with specified +// . // -// 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) + } +} diff --git a/database/gdb/gdb_model_condition.go b/database/gdb/gdb_model_condition.go index 09e2679a9..71845ba12 100644 --- a/database/gdb/gdb_model_condition.go +++ b/database/gdb/gdb_model_condition.go @@ -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 // 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 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 -} diff --git a/database/gdb/gdb_model_select.go b/database/gdb/gdb_model_select.go index 3f0f626d8..3e8ef3038 100644 --- a/database/gdb/gdb_model_select.go +++ b/database/gdb/gdb_model_select.go @@ -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 +} diff --git a/database/gdb/gdb_model_time.go b/database/gdb/gdb_model_time.go index 8953b312c..0b59b6aa6 100644 --- a/database/gdb/gdb_model_time.go +++ b/database/gdb/gdb_model_time.go @@ -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. diff --git a/database/gdb/gdb_model_utility.go b/database/gdb/gdb_model_utility.go index cf41a44dd..93fe47566 100644 --- a/database/gdb/gdb_model_utility.go +++ b/database/gdb/gdb_model_utility.go @@ -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 . // diff --git a/database/gdb/gdb_unit_z_mysql_model_test.go b/database/gdb/gdb_unit_z_mysql_model_test.go index 3e8f3ac7d..f37a9fd61 100644 --- a/database/gdb/gdb_unit_z_mysql_model_test.go +++ b/database/gdb/gdb_unit_z_mysql_model_test.go @@ -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) {