Skip to content

Commit

Permalink
Merge pull request #13 from alejandro-carstens/feature/add-add-method
Browse files Browse the repository at this point in the history
adding add method
  • Loading branch information
alejandro-carstens authored May 2, 2022
2 parents ee87e5b + 8b39428 commit 622d843
Show file tree
Hide file tree
Showing 9 changed files with 181 additions and 3 deletions.
3 changes: 3 additions & 0 deletions cache.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,9 @@ type (
GetString(key string) (string, error)
// Put puts a value in the given store for a predetermined amount of time in seconds
Put(key string, value interface{}, duration time.Duration) error
// Add an item to the cache only if an item doesn't already exist for the given key, or if the existing item has
// expired. If the record was successfully added true will be returned else false will be returned
Add(key string, value interface{}, duration time.Duration) (bool, error)
// Increment increments an integer counter by a given value
Increment(key string, value int64) (int64, error)
// Decrement decrements an integer counter by a given value
Expand Down
15 changes: 15 additions & 0 deletions local_store.go
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,21 @@ func (s *LocalStore) Put(key string, value interface{}, duration time.Duration)
return nil
}

// Add an item to the cache only if an item doesn't already exist for the given key, or if the existing item has
// expired. If the record was successfully added true will be returned else false will be returned
func (s *LocalStore) Add(key string, value interface{}, duration time.Duration) (bool, error) {
if isNumeric(value) {
return s.c.Add(s.k(key), value, duration) == nil, nil
}

val, err := encode(value)
if err != nil {
return false, err
}

return s.c.Add(s.k(key), val, duration) == nil, nil
}

// Forever puts a value in the given store until it is forgotten/evicted
func (s *LocalStore) Forever(key string, value interface{}) error {
return s.Put(key, value, -1)
Expand Down
7 changes: 6 additions & 1 deletion lua_scripts.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
package gocache

const redisLuaReleaseLockScript = `
const (
redisLuaReleaseLockScript = `
if redis.call("get",KEYS[1]) == ARGV[1] then
return redis.call("del",KEYS[1])
else
return 0
end
`
redisLuaAddScript = `
return redis.call('exists',KEYS[1])<1 and redis.call('setex',KEYS[1],ARGV[2],ARGV[1])
`
)
16 changes: 16 additions & 0 deletions memcache_store.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,22 @@ func (s *MemcacheStore) Put(key string, value interface{}, duration time.Duratio
return s.client.Set(item)
}

// Add an item to the cache only if an item doesn't already exist for the given key, or if the existing item has
// expired. If the record was successfully added true will be returned else false will be returned
func (s *MemcacheStore) Add(key string, value interface{}, duration time.Duration) (bool, error) {
item, err := s.item(key, value, duration)
if err != nil {
return false, err
}
if err = s.client.Add(item); errors.Is(err, memcache.ErrNotStored) {
return false, nil
} else if err != nil {
return false, err
}

return true, nil
}

// Forever puts a value in the given store until it is forgotten/evicted
func (s *MemcacheStore) Forever(key string, value interface{}) error {
return s.Put(key, value, 0)
Expand Down
31 changes: 30 additions & 1 deletion redis_store.go
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
package gocache

import (
"errors"
"time"

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

const deleteLimit = 1000
const (
deleteLimit = 1000
redisOk = "OK"
)

var _ Cache = &RedisStore{}

Expand Down Expand Up @@ -128,6 +132,31 @@ func (s *RedisStore) Put(key string, value interface{}, duration time.Duration)
return s.client.Set(s.k(key), val, duration).Err()
}

// Add an item to the cache only if an item doesn't already exist for the given key, or if the existing item has
// expired. If the record was successfully added true will be returned else false will be returned
func (s *RedisStore) Add(key string, value interface{}, duration time.Duration) (bool, error) {
if isNumeric(value) {
res, err := s.client.Eval(redisLuaAddScript, []string{s.k(key)}, value, duration.Seconds()).String()
if err != nil && !errors.Is(err, redis.Nil) {
return false, err
}

return res == redisOk, nil
}

val, err := encode(value)
if err != nil {
return false, err
}

res, err := s.client.Eval(redisLuaAddScript, []string{s.k(key)}, val, duration.Seconds()).String()
if err != nil && !errors.Is(err, redis.Nil) {
return false, err
}

return res == redisOk, nil
}

// Forever puts a value in the given store until it is forgotten/evicted
func (s *RedisStore) Forever(key string, value interface{}) error {
if isNumeric(value) {
Expand Down
10 changes: 10 additions & 0 deletions redis_tagged_cache.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,16 @@ func (tc *redisTaggedCache) Put(key string, value interface{}, duration time.Dur
return tc.taggedCache.Put(key, value, duration)
}

// Add an item to the cache only if an item doesn't already exist for the given key, or if the existing item has
// expired. If the record was successfully added true will be returned else false will be returned
func (tc *redisTaggedCache) Add(key string, value interface{}, duration time.Duration) (bool, error) {
if err := tc.pushKeys(key, referenceKeyStandard); err != nil {
return false, err
}

return tc.taggedCache.Add(key, value, duration)
}

// PutMany implementation of the TaggedCache interface
func (tc *redisTaggedCache) PutMany(entries ...Entry) error {
for i, entry := range entries {
Expand Down
44 changes: 44 additions & 0 deletions store_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -447,6 +447,50 @@ func TestExists(t *testing.T) {
}
}

func TestAdd(t *testing.T) {
for _, d := range drivers {
t.Run(d.string(), func(t *testing.T) {
var (
cache = createStore(t, d)
res, err = cache.Add("key", 2, time.Second)
)
require.NoError(t, err)
require.True(t, res)

res, err = cache.Add("key", 2, time.Second)
require.NoError(t, err)
require.False(t, res)

i, err := cache.GetInt("key")
require.NoError(t, err)
require.Equal(t, 2, i)

res, err = cache.Forget("key")
require.NoError(t, err)
require.True(t, res)

res, err = cache.Add("key", 2, time.Second)
require.NoError(t, err)
require.True(t, res)

res, err = cache.Add("other_key", "whatever", time.Second)
require.NoError(t, err)
require.True(t, res)

res, err = cache.Add("other_key", "whatever", time.Second)
require.NoError(t, err)
require.False(t, res)

v, err := cache.GetString("other_key")
require.NoError(t, err)
require.Equal(t, "whatever", v)

_, err = cache.Flush()
require.NoError(t, err)
})
}
}

func createStore(t *testing.T, d driver) Cache {
t.Helper()

Expand Down
11 changes: 11 additions & 0 deletions tagged_cache.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,17 @@ func (tc *taggedCache) Put(key string, value interface{}, duration time.Duration
return tc.store.Put(tagKey, value, duration)
}

// Add an item to the cache only if an item doesn't already exist for the given key, or if the existing item has
// expired. If the record was successfully added true will be returned else false will be returned
func (tc *taggedCache) Add(key string, value interface{}, duration time.Duration) (bool, error) {
tagKey, err := tc.taggedItemKey(key)
if err != nil {
return false, err
}

return tc.store.Add(tagKey, value, duration)
}

// Increment increments an integer counter by a given value
func (tc *taggedCache) Increment(key string, value int64) (int64, error) {
tagKey, err := tc.taggedItemKey(key)
Expand Down
47 changes: 46 additions & 1 deletion tagged_cache_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -478,7 +478,52 @@ func TestTagExists(t *testing.T) {
require.NoError(t, err)
require.False(t, exists)

_, err = cache.Tags(ts).Flush()
_, err = cache.Flush()
require.NoError(t, err)
})
}
}

func TestTagAdd(t *testing.T) {
for _, d := range drivers {
t.Run(d.string(), func(t *testing.T) {
var (
cache = createStore(t, d)
ts = tag()
res, err = cache.Tags(ts).Add("key", 2, time.Second)
)
require.NoError(t, err)
require.True(t, res)

res, err = cache.Tags(ts).Add("key", 2, time.Second)
require.NoError(t, err)
require.False(t, res)

i, err := cache.Tags(ts).GetInt("key")
require.NoError(t, err)
require.Equal(t, 2, i)

res, err = cache.Tags(ts).Flush()
require.NoError(t, err)
require.True(t, res)

res, err = cache.Tags(ts).Add("key", 2, time.Second)
require.NoError(t, err)
require.True(t, res)

res, err = cache.Tags(ts).Add("other_key", "whatever", time.Second)
require.NoError(t, err)
require.True(t, res)

res, err = cache.Tags(ts).Add("other_key", "whatever", time.Second)
require.NoError(t, err)
require.False(t, res)

v, err := cache.Tags(ts).GetString("other_key")
require.NoError(t, err)
require.Equal(t, "whatever", v)

_, err = cache.Flush()
require.NoError(t, err)
})
}
Expand Down

0 comments on commit 622d843

Please sign in to comment.