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 }