diff --git a/bucket.go b/bucket.go index f69e500..3c09015 100644 --- a/bucket.go +++ b/bucket.go @@ -54,13 +54,13 @@ func (b *bucket) delete(key string) *Item { // the item from the map. I'm pretty sure this is 100% fine, but it is unique. // (We do this so that the write to the channel is under the read lock and not the // write lock) -func (b *bucket) deletePrefix(prefix string, deletables chan *Item) int { +func (b *bucket) deleteFunc(matches func(key string, item interface{}) bool, deletables chan *Item) int { lookup := b.lookup items := make([]*Item, 0, len(lookup)/10) b.RLock() for key, item := range lookup { - if strings.HasPrefix(key, prefix) { + if matches(key, item) { deletables <- item items = append(items, item) } @@ -80,6 +80,12 @@ func (b *bucket) deletePrefix(prefix string, deletables chan *Item) int { return len(items) } +func (b *bucket) deletePrefix(prefix string, deletables chan *Item) int { + return b.deleteFunc(func(key string, item interface{}) bool { + return strings.HasPrefix(key, prefix) + }, deletables) +} + func (b *bucket) clear() { b.Lock() b.lookup = make(map[string]*Item) diff --git a/cache.go b/cache.go index 1190b57..e9d1924 100644 --- a/cache.go +++ b/cache.go @@ -63,6 +63,15 @@ func (c *Cache) DeletePrefix(prefix string) int { return count } +// Deletes all items that the matches func evaluates to true. +func (c *Cache) DeleteFunc(matches func(key string, item interface{}) bool) int { + count := 0 + for _, b := range c.buckets { + count += b.deleteFunc(matches, c.deletables) + } + return count +} + // Get an item from the cache. Returns nil if the item wasn't found. // This can return an expired item. Use item.Expired() to see if the item // is expired and item.TTL() to see how long until the item expires (which diff --git a/cache_test.go b/cache_test.go index 5db2264..e128240 100644 --- a/cache_test.go +++ b/cache_test.go @@ -51,6 +51,35 @@ func (_ CacheTests) DeletesAPrefix() { Expect(cache.ItemCount()).To.Equal(2) } +func (_ CacheTests) DeletesAFunc() { + cache := New(Configure()) + Expect(cache.ItemCount()).To.Equal(0) + + cache.Set("a", 1, time.Minute) + cache.Set("b", 2, time.Minute) + cache.Set("c", 3, time.Minute) + cache.Set("d", 4, time.Minute) + cache.Set("e", 5, time.Minute) + cache.Set("f", 6, time.Minute) + Expect(cache.ItemCount()).To.Equal(6) + + Expect(cache.DeleteFunc(func(key string, item interface{}) bool { + return false + })).To.Equal(0) + Expect(cache.ItemCount()).To.Equal(6) + + Expect(cache.DeleteFunc(func(key string, item interface{}) bool { + return item.(*Item).Value().(int) < 4 + })).To.Equal(3) + Expect(cache.ItemCount()).To.Equal(3) + + Expect(cache.DeleteFunc(func(key string, item interface{}) bool { + return key == "d" + })).To.Equal(1) + Expect(cache.ItemCount()).To.Equal(2) + +} + func (_ CacheTests) OnDeleteCallbackCalled() { onDeleteFnCalled := false onDeleteFn := func(item *Item) { diff --git a/layeredbucket.go b/layeredbucket.go index 552c877..a0514e0 100644 --- a/layeredbucket.go +++ b/layeredbucket.go @@ -71,6 +71,16 @@ func (b *layeredBucket) deletePrefix(primary, prefix string, deletables chan *It return bucket.deletePrefix(prefix, deletables) } +func (b *layeredBucket) deleteFunc(primary string, matches func(key string, item interface{}) bool, deletables chan *Item) int { + b.RLock() + bucket, exists := b.buckets[primary] + b.RUnlock() + if exists == false { + return 0 + } + return bucket.deleteFunc(matches, deletables) +} + func (b *layeredBucket) deleteAll(primary string, deletables chan *Item) bool { b.RLock() bucket, exists := b.buckets[primary] diff --git a/layeredcache.go b/layeredcache.go index 850af25..6786880 100644 --- a/layeredcache.go +++ b/layeredcache.go @@ -154,6 +154,11 @@ func (c *LayeredCache) DeletePrefix(primary, prefix string) int { return c.bucket(primary).deletePrefix(primary, prefix, c.deletables) } +// Deletes all items that share the same primary key and where the matches func evaluates to true. +func (c *LayeredCache) DeleteFunc(primary string, matches func(key string, item interface{}) bool) int { + return c.bucket(primary).deleteFunc(primary, matches, c.deletables) +} + //this isn't thread safe. It's meant to be called from non-concurrent tests func (c *LayeredCache) Clear() { for _, bucket := range c.buckets { diff --git a/layeredcache_test.go b/layeredcache_test.go index 11e3562..d0edeb2 100644 --- a/layeredcache_test.go +++ b/layeredcache_test.go @@ -95,6 +95,35 @@ func (_ *LayeredCacheTests) DeletesAPrefix() { Expect(cache.ItemCount()).To.Equal(3) } +func (_ *LayeredCacheTests) DeletesAFunc() { + cache := newLayered() + Expect(cache.ItemCount()).To.Equal(0) + + cache.Set("spice", "a", 1, time.Minute) + cache.Set("leto", "b", 2, time.Minute) + cache.Set("spice", "c", 3, time.Minute) + cache.Set("spice", "d", 4, time.Minute) + cache.Set("spice", "e", 5, time.Minute) + cache.Set("spice", "f", 6, time.Minute) + Expect(cache.ItemCount()).To.Equal(6) + + Expect(cache.DeleteFunc("spice", func(key string, item interface{}) bool { + return false + })).To.Equal(0) + Expect(cache.ItemCount()).To.Equal(6) + + Expect(cache.DeleteFunc("spice", func(key string, item interface{}) bool { + return item.(*Item).Value().(int) < 4 + })).To.Equal(2) + Expect(cache.ItemCount()).To.Equal(4) + + Expect(cache.DeleteFunc("spice", func(key string, item interface{}) bool { + return key == "d" + })).To.Equal(1) + Expect(cache.ItemCount()).To.Equal(3) + +} + func (_ *LayeredCacheTests) OnDeleteCallbackCalled() { onDeleteFnCalled := false