github.com/wangyougui/gf/v2@v2.6.5/database/gdb/gdb_model_cache.go (about) 1 // Copyright GoFrame Author(https://goframe.org). All Rights Reserved. 2 // 3 // This Source Code Form is subject to the terms of the MIT License. 4 // If a copy of the MIT was not distributed with this file, 5 // You can obtain one at https://github.com/wangyougui/gf. 6 7 package gdb 8 9 import ( 10 "context" 11 "time" 12 13 "github.com/wangyougui/gf/v2/internal/intlog" 14 ) 15 16 // CacheOption is options for model cache control in query. 17 type CacheOption struct { 18 // Duration is the TTL for the cache. 19 // If the parameter `Duration` < 0, which means it clear the cache with given `Name`. 20 // If the parameter `Duration` = 0, which means it never expires. 21 // If the parameter `Duration` > 0, which means it expires after `Duration`. 22 Duration time.Duration 23 24 // Name is an optional unique name for the cache. 25 // The Name is used to bind a name to the cache, which means you can later control the cache 26 // like changing the `duration` or clearing the cache with specified Name. 27 Name string 28 29 // Force caches the query result whatever the result is nil or not. 30 // It is used to avoid Cache Penetration. 31 Force bool 32 } 33 34 // selectCacheItem is the cache item for SELECT statement result. 35 type selectCacheItem struct { 36 Result Result // Sql result of SELECT statement. 37 FirstResultColumn string // The first column name of result, for Value/Count functions. 38 } 39 40 // Cache sets the cache feature for the model. It caches the result of the sql, which means 41 // if there's another same sql request, it just reads and returns the result from cache, it 42 // but not committed and executed into the database. 43 // 44 // Note that, the cache feature is disabled if the model is performing select statement 45 // on a transaction. 46 func (m *Model) Cache(option CacheOption) *Model { 47 model := m.getModel() 48 model.cacheOption = option 49 model.cacheEnabled = true 50 return model 51 } 52 53 // checkAndRemoveSelectCache checks and removes the cache in insert/update/delete statement if 54 // cache feature is enabled. 55 func (m *Model) checkAndRemoveSelectCache(ctx context.Context) { 56 if m.cacheEnabled && m.cacheOption.Duration < 0 && len(m.cacheOption.Name) > 0 { 57 var cacheKey = m.makeSelectCacheKey("") 58 if _, err := m.db.GetCache().Remove(ctx, cacheKey); err != nil { 59 intlog.Errorf(ctx, `%+v`, err) 60 } 61 } 62 } 63 64 func (m *Model) getSelectResultFromCache(ctx context.Context, sql string, args ...interface{}) (result Result, err error) { 65 if !m.cacheEnabled || m.tx != nil { 66 return 67 } 68 var ( 69 cacheItem *selectCacheItem 70 cacheKey = m.makeSelectCacheKey(sql, args...) 71 cacheObj = m.db.GetCache() 72 ) 73 defer func() { 74 if cacheItem != nil { 75 if internalData := m.db.GetCore().GetInternalCtxDataFromCtx(ctx); internalData != nil { 76 if cacheItem.FirstResultColumn != "" { 77 internalData.FirstResultColumn = cacheItem.FirstResultColumn 78 } 79 } 80 } 81 }() 82 if v, _ := cacheObj.Get(ctx, cacheKey); !v.IsNil() { 83 if err = v.Scan(&cacheItem); err != nil { 84 return nil, err 85 } 86 return cacheItem.Result, nil 87 } 88 return 89 } 90 91 func (m *Model) saveSelectResultToCache( 92 ctx context.Context, queryType queryType, result Result, sql string, args ...interface{}, 93 ) (err error) { 94 if !m.cacheEnabled || m.tx != nil { 95 return 96 } 97 var ( 98 cacheKey = m.makeSelectCacheKey(sql, args...) 99 cacheObj = m.db.GetCache() 100 ) 101 if m.cacheOption.Duration < 0 { 102 if _, errCache := cacheObj.Remove(ctx, cacheKey); errCache != nil { 103 intlog.Errorf(ctx, `%+v`, errCache) 104 } 105 return 106 } 107 // Special handler for Value/Count operations result. 108 if len(result) > 0 { 109 switch queryType { 110 case queryTypeValue, queryTypeCount: 111 if internalData := m.db.GetCore().GetInternalCtxDataFromCtx(ctx); internalData != nil { 112 if result[0][internalData.FirstResultColumn].IsEmpty() { 113 result = nil 114 } 115 } 116 } 117 } 118 119 // In case of Cache Penetration. 120 if result.IsEmpty() { 121 if m.cacheOption.Force { 122 result = Result{} 123 } else { 124 result = nil 125 } 126 } 127 var cacheItem = &selectCacheItem{ 128 Result: result, 129 } 130 if internalData := m.db.GetCore().GetInternalCtxDataFromCtx(ctx); internalData != nil { 131 cacheItem.FirstResultColumn = internalData.FirstResultColumn 132 } 133 if errCache := cacheObj.Set(ctx, cacheKey, cacheItem, m.cacheOption.Duration); errCache != nil { 134 intlog.Errorf(ctx, `%+v`, errCache) 135 } 136 return 137 } 138 139 func (m *Model) makeSelectCacheKey(sql string, args ...interface{}) string { 140 return m.db.GetCore().makeSelectCacheKey( 141 m.cacheOption.Name, 142 m.db.GetSchema(), 143 m.db.GetCore().guessPrimaryTableName(m.tables), 144 sql, 145 args..., 146 ) 147 }