diff --git a/bucket.go b/bucket.go index e9e9a77..7d91aab 100644 --- a/bucket.go +++ b/bucket.go @@ -35,6 +35,22 @@ func (b *bucket[T]) get(key string) *Item[T] { return b.lookup[key] } +func (b *bucket[T]) setnx(key string, value T, duration time.Duration, track bool) *Item[T] { + b.Lock() + defer b.Unlock() + + item := b.lookup[key] + if item != nil { + return item + } + + expires := time.Now().Add(duration).UnixNano() + item = newItem(key, value, expires, track) + b.lookup[key] = item + + return item +} + func (b *bucket[T]) set(key string, value T, duration time.Duration, track bool) (*Item[T], *Item[T]) { expires := time.Now().Add(duration).UnixNano() item := newItem(key, value, expires, track) diff --git a/cache.go b/cache.go index 0124caa..24ccc3b 100644 --- a/cache.go +++ b/cache.go @@ -146,6 +146,11 @@ func (c *Cache[T]) Set(key string, value T, duration time.Duration) { c.set(key, value, duration, false) } +// Setnx set the value in the cache for the specified duration if not exists +func (c *Cache[T]) Setnx(key string, value T, duration time.Duration) { + c.setnx(key, value, duration, false) +} + // Replace the value if it exists, does not set if it doesn't. // Returns true if the item existed an was replaced, false otherwise. // Replace does not reset item's TTL @@ -200,6 +205,10 @@ func (c *Cache[T]) set(key string, value T, duration time.Duration, track bool) return item } +func (c *Cache[T]) setnx(key string, value T, duration time.Duration, track bool) *Item[T] { + return c.bucket(key).setnx(key, value, duration, track) +} + func (c *Cache[T]) bucket(key string) *bucket[T] { h := fnv.New32a() h.Write([]byte(key)) diff --git a/cache_test.go b/cache_test.go index bb89015..860fe6d 100644 --- a/cache_test.go +++ b/cache_test.go @@ -12,6 +12,27 @@ import ( "github.com/karlseguin/ccache/v3/assert" ) +func Test_Setnx(t *testing.T) { + cache := New(Configure[string]()) + defer cache.Stop() + assert.Equal(t, cache.ItemCount(), 0) + + cache.Set("spice", "flow", time.Minute) + assert.Equal(t, cache.ItemCount(), 1) + + // set if exists + cache.Setnx("spice", "worm", time.Minute) + assert.Equal(t, cache.ItemCount(), 1) + assert.Equal(t, cache.Get("spice").Value(), "flow") + + // set if not exists + cache.Delete("spice") + cache.Setnx("spice", "worm", time.Minute) + assert.Equal(t, cache.Get("spice").Value(), "worm") + + assert.Equal(t, cache.ItemCount(), 1) +} + func Test_CacheDeletesAValue(t *testing.T) { cache := New(Configure[string]()) defer cache.Stop() diff --git a/readme.md b/readme.md index 101927a..e020ccb 100644 --- a/readme.md +++ b/readme.md @@ -122,6 +122,14 @@ cache.Replace("user:4", user) `Replace` returns true if the item existed (and thus was replaced). In the case where the key was not in the cache, the value *is not* inserted and false is returned. +### Setnx + +Set the value if not exists. setnx will first check whether kv exists. If it does not exist, set kv in cache. this operation is atomic. + +```go +cache.Set("user:4", user, time.Minute * 10) +``` + ### GetDropped You can get the number of keys evicted due to memory pressure by calling `GetDropped`: