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  }