cache_redis.go 5.15 KiB
Newer Older
Provides cache access and manipulation methods for redis server
Implements cacher interface.

Official docs -
1. redis client - https://github.com/go-redis/redis
2. redis server - https://redis.io/


Note - corelab.mkcl.org/MKCLOS/coredevelopmentplatform/corepkgv2/loggermdl must be initialized before use

*/

import (
	"encoding/json"
	"time"

	"corelab.mkcl.org/MKCLOS/coredevelopmentplatform/corepkgv2/loggermdl"

	"github.com/go-redis/redis"
)

const (
	noExp time.Duration = 0

	// NotOK refers to a unsuccessfull operations
	NotOK int64 = 0
	// OK refers to a successfull operations
	OK int64 = 1
// RedisCache represents a Redis client with provided configuration. Do not change configuration at runtime.
type RedisCache struct {
	cli       *redis.Client  // represents redis client
	opt       *redis.Options //
	keyStr    string         // "<Prefix>:"
	addPrefix bool           //
	connected bool           // will be enabled if redis client connects to server

	Addr       string        // redis server address, default "127.0.0.1:6379"
	DB         int           // redis DB on provided server, default 0
	Password   string        //
	Expiration time.Duration // this duration will be used for Set() method
	Prefix     string        // this will be used for storing keys for provided project
// Setup initializes redis cache for application. Must be called only once.
func (rc *RedisCache) Setup(addr, password, prefix string, db int, exp time.Duration) {
	if rc == nil {
		rc = new(RedisCache)
	}

	rc.Addr = addr
	rc.Password = password
	rc.DB = db
	rc.Expiration = exp
	rc.Prefix = prefix
	opt := redis.Options{
		Addr:     addr,
		Password: password,
		DB:       db,
	}

	rc.opt = &opt
	rc.cli = redis.NewClient(&opt)

	if _, err := rc.cli.Ping().Result(); err != nil {
		// exit if connection to redis server fails
		loggermdl.LogError("connection to redis server failed: ", err)
		log.Fatal("connection to redis server failed: ", err)
	if prefix != "" {
		rc.keyStr = contcat(rc.Prefix, keySplitter)
		rc.addPrefix = true
	}
}

// Set marshalls provided value and stores against provided key. Errors will be logged to initialized logger.
func (rc *RedisCache) Set(key string, val interface{}) {
	ba, err := marshalWithTypeCheck(val)
	if err != nil {
		loggermdl.LogError("error setting key ", key, " error: ", err)
		return
	}

	rc.cli.Set(rc.key(key), ba, rc.Expiration)
}

// SetWithExpiration marshalls provided value and stores against provided key for given duration. Errors will be logged to initialized logger.
func (rc *RedisCache) SetWithExpiration(key string, val interface{}, exp time.Duration) {
	ba, err := marshalWithTypeCheck(val)
	if err != nil {
		loggermdl.LogError("error setting key ", key, " error: ", err)
		return
	}

	rc.cli.Set(rc.key(key), ba, exp)
}

// SetNoExpiration marshalls provided value and stores against provided key.
// Errors will be logged to initialized logger.
func (rc *RedisCache) SetNoExpiration(key string, val interface{}) {
	ba, err := marshalWithTypeCheck(val)
	if err != nil {
		loggermdl.LogError("error setting key ", key, " error: ", err)
		return
	}

	rc.cli.Set(rc.key(key), ba, noExp)
}

// Get returns data against provided key. The final result is parsed with gjson. Returns false if not present.
func (rc *RedisCache) Get(key string) (interface{}, bool) {
	// exists, err := rc.cli.Exists(key).Result()
	// if err != nil {
	// 	loggermdl.LogError("error checking key ", key, " error: ", err)
	// 	return nil, false
	// }

	// if exists == NotOK {
	// 	return nil, false
	// }

	// Get returns error if key is not present.
	val, err := rc.cli.Get(rc.key(key)).Result()
		loggermdl.LogError("error getting key", key, "from redis cache with error:", err)
}

// Delete -
func (rc *RedisCache) Delete(key string) {
	rc.cli.Del(rc.key(key)).Result()
}

// GetItemsCount -
func (rc *RedisCache) GetItemsCount() int {
	pattern := "*"
	keys, err := rc.cli.Keys(pattern).Result()
	if err != nil {
		loggermdl.LogError("error getting item count for ", pattern, " error: ", err)
		return 0
	}
	return len(keys)
}

func (rc *RedisCache) flushDB() (string, error) {
	return rc.cli.FlushDB().Result()
}
Akshay Bharambe's avatar
Akshay Bharambe committed

// Purge deletes for current redis db
func (rc *RedisCache) Purge() {
	_, err := rc.flushDB()
	if err != nil {
		loggermdl.LogError("error purging redis cache for db ", rc.Addr, "/", rc.DB, " error: ", err)
	}
}

func marshal(v interface{}) ([]byte, error) {
	return json.Marshal(v)
}
// marshalWithTypeCheck checks type before marshsal. Save allocations and time significantly if the existing data is string or []byte
func marshalWithTypeCheck(v interface{}) ([]byte, error) {
	switch d := v.(type) {
	default:
		return json.Marshal(v)
	case string:
		return []byte(d), nil
	case []byte:
		return d, nil
	}
}

func contcat(s ...string) string {
	sb := strings.Builder{}
	for i := range s {
		sb.WriteString(s[i])
	}

	return sb.String()
}

func (rc *RedisCache) key(key string) string {
	// prepare in format "<Prefix>:<key>"
	if rc.addPrefix {
		return contcat(rc.keyStr, key)
	}
	return key
}

func (rc *RedisCache) Type() int {
	return TypeRedisCache
}