github.com/gogf/gf/v2@v2.7.4/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/gogf/gf.
     6  
     7  package gdb
     8  
     9  import (
    10  	"context"
    11  	"time"
    12  
    13  	"github.com/gogf/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  		core      = m.db.GetCore()
    73  	)
    74  	defer func() {
    75  		if cacheItem != nil {
    76  			if internalData := core.getInternalColumnFromCtx(ctx); internalData != nil {
    77  				if cacheItem.FirstResultColumn != "" {
    78  					internalData.FirstResultColumn = cacheItem.FirstResultColumn
    79  				}
    80  			}
    81  		}
    82  	}()
    83  	if v, _ := cacheObj.Get(ctx, cacheKey); !v.IsNil() {
    84  		if err = v.Scan(&cacheItem); err != nil {
    85  			return nil, err
    86  		}
    87  		return cacheItem.Result, nil
    88  	}
    89  	return
    90  }
    91  
    92  func (m *Model) saveSelectResultToCache(
    93  	ctx context.Context, queryType queryType, result Result, sql string, args ...interface{},
    94  ) (err error) {
    95  	if !m.cacheEnabled || m.tx != nil {
    96  		return
    97  	}
    98  	var (
    99  		cacheKey = m.makeSelectCacheKey(sql, args...)
   100  		cacheObj = m.db.GetCache()
   101  	)
   102  	if m.cacheOption.Duration < 0 {
   103  		if _, errCache := cacheObj.Remove(ctx, cacheKey); errCache != nil {
   104  			intlog.Errorf(ctx, `%+v`, errCache)
   105  		}
   106  		return
   107  	}
   108  	// Special handler for Value/Count operations result.
   109  	if len(result) > 0 {
   110  		var core = m.db.GetCore()
   111  		switch queryType {
   112  		case queryTypeValue, queryTypeCount:
   113  			if internalData := core.getInternalColumnFromCtx(ctx); internalData != nil {
   114  				if result[0][internalData.FirstResultColumn].IsEmpty() {
   115  					result = nil
   116  				}
   117  			}
   118  		}
   119  	}
   120  
   121  	// In case of Cache Penetration.
   122  	if result.IsEmpty() {
   123  		if m.cacheOption.Force {
   124  			result = Result{}
   125  		} else {
   126  			result = nil
   127  		}
   128  	}
   129  	var (
   130  		core      = m.db.GetCore()
   131  		cacheItem = &selectCacheItem{
   132  			Result: result,
   133  		}
   134  	)
   135  	if internalData := core.getInternalColumnFromCtx(ctx); internalData != nil {
   136  		cacheItem.FirstResultColumn = internalData.FirstResultColumn
   137  	}
   138  	if errCache := cacheObj.Set(ctx, cacheKey, cacheItem, m.cacheOption.Duration); errCache != nil {
   139  		intlog.Errorf(ctx, `%+v`, errCache)
   140  	}
   141  	return
   142  }
   143  
   144  func (m *Model) makeSelectCacheKey(sql string, args ...interface{}) string {
   145  	var (
   146  		table      = m.db.GetCore().guessPrimaryTableName(m.tables)
   147  		group      = m.db.GetGroup()
   148  		schema     = m.db.GetSchema()
   149  		customName = m.cacheOption.Name
   150  	)
   151  	return genSelectCacheKey(
   152  		table,
   153  		group,
   154  		schema,
   155  		customName,
   156  		sql,
   157  		args...,
   158  	)
   159  }