github.com/safedep/dry@v0.0.0-20241016050132-a15651f0548b/cache/mysql.go (about)

     1  package cache
     2  
     3  import (
     4  	"errors"
     5  	"strconv"
     6  	"time"
     7  
     8  	"github.com/safedep/dry/db"
     9  	"gorm.io/gorm"
    10  )
    11  
    12  const (
    13  	mysqlCacheTableName = "caches"
    14  )
    15  
    16  var (
    17  	mysqlCacheErrExpired      = errors.New("cache entry expired")
    18  	mysqlCacheErrNonExistent  = errors.New("cache key not found")
    19  	mysqlCacheErrBadParams    = errors.New("bad params")
    20  	mysqlCacheErrActiveExists = errors.New("cache entry exists and active")
    21  )
    22  
    23  type mysqlCache struct {
    24  	mysqlAdapter db.SqlDataAdapter
    25  }
    26  
    27  // Cache table
    28  type mysqlCacheEntry struct {
    29  	gorm.Model
    30  
    31  	// Composite index as cache lookup key
    32  	Source string `gorm:"type:varchar(100);not null;uniqueIndex:lookup_idx;priority:1"`
    33  	Type   string `gorm:"type:varchar(100);not null;uniqueIndex:lookup_idx;priority:2"`
    34  	Key    string `gorm:"type:varchar(100);not null;uniqueIndex:lookup_idx;priority:3"`
    35  
    36  	// Cache data
    37  	Data []byte `gorm:"type:mediumblob;not null"`
    38  
    39  	// Cache TTL (Should be now + ttl)
    40  	ExpiresAt time.Time `gorm:"not null"`
    41  }
    42  
    43  // Override table name
    44  func (mysqlCacheEntry) TableName() string {
    45  	return mysqlCacheTableName
    46  }
    47  
    48  type MySqlCacheConfig struct {
    49  	Host, Port, Username, Password, Database string
    50  
    51  	MaxIdleTime        time.Duration
    52  	MaxOpenConnections int
    53  }
    54  
    55  func NewMySqlCache(config MySqlCacheConfig) (Cache, error) {
    56  	if config.MaxIdleTime == 0 {
    57  		config.MaxIdleTime = 60 * time.Second
    58  	}
    59  
    60  	if config.MaxOpenConnections == 0 {
    61  		config.MaxOpenConnections = 10
    62  	}
    63  
    64  	port, err := strconv.ParseInt(config.Port, 0, 16)
    65  	if err != nil {
    66  		port = 3306
    67  	}
    68  
    69  	dbConfig := db.MySqlAdapterConfig{
    70  		Host:     config.Host,
    71  		Port:     int16(port),
    72  		Username: config.Username,
    73  		Password: config.Password,
    74  		Database: config.Database,
    75  	}
    76  
    77  	conn, err := db.NewMySqlAdapter(dbConfig)
    78  	if err != nil {
    79  		return nil, err
    80  	}
    81  
    82  	gDB, _ := conn.GetDB()
    83  	dbConn, err := gDB.DB()
    84  	if err != nil {
    85  		return nil, err
    86  	}
    87  
    88  	dbConn.SetConnMaxIdleTime(config.MaxIdleTime)
    89  	dbConn.SetMaxOpenConns(config.MaxOpenConnections)
    90  
    91  	return &mysqlCache{mysqlAdapter: conn}, nil
    92  }
    93  
    94  func (mcache *mysqlCache) Put(key *CacheKey, data *CacheData, ttl time.Duration) error {
    95  	if (key == nil) || (data == nil) {
    96  		return mysqlCacheErrBadParams
    97  	}
    98  
    99  	entry := mysqlCacheEntry{
   100  		Source:    key.Source,
   101  		Type:      key.Type,
   102  		Key:       key.Id,
   103  		ExpiresAt: time.Now().Add(ttl),
   104  		Data:      []byte(*data),
   105  	}
   106  
   107  	return mcache.createEntry(&entry)
   108  }
   109  
   110  func (mcache *mysqlCache) Get(key *CacheKey) (*CacheData, error) {
   111  	record, err := mcache.findByCacheKey(key)
   112  	if err != nil {
   113  		return nil, err
   114  	}
   115  
   116  	if record.ExpiresAt.Before(time.Now()) {
   117  		_ = mcache.deleteEntry(&record)
   118  		return nil, mysqlCacheErrExpired
   119  	}
   120  
   121  	data := CacheData(record.Data)
   122  	return &data, nil
   123  }
   124  
   125  func (mcache *mysqlCache) findByCacheKey(key *CacheKey) (mysqlCacheEntry, error) {
   126  	var record mysqlCacheEntry
   127  
   128  	db, err := mcache.mysqlAdapter.GetDB()
   129  	if err != nil {
   130  		return record, err
   131  	}
   132  
   133  	// Avoid soft delete flag
   134  	tx := db.Unscoped().Where(&mysqlCacheEntry{
   135  		Source: key.Source,
   136  		Type:   key.Type,
   137  		Key:    key.Id,
   138  	}).First(&record)
   139  
   140  	if tx.Error != nil {
   141  		return record, tx.Error
   142  	}
   143  
   144  	return record, nil
   145  }
   146  
   147  func (mcache *mysqlCache) createEntry(entry *mysqlCacheEntry) error {
   148  	db, err := mcache.mysqlAdapter.GetDB()
   149  	if err != nil {
   150  		return err
   151  	}
   152  
   153  	tx := db.Create(entry)
   154  	return tx.Error
   155  }
   156  
   157  func (mcache *mysqlCache) deleteEntry(entry *mysqlCacheEntry) error {
   158  	db, err := mcache.mysqlAdapter.GetDB()
   159  	if err != nil {
   160  		return err
   161  	}
   162  
   163  	// Force hard delete
   164  	tx := db.Unscoped().Delete(entry)
   165  	return tx.Error
   166  }