diff --git a/cacheitemset.go b/cacheitemset.go new file mode 100644 index 0000000..37ad772 --- /dev/null +++ b/cacheitemset.go @@ -0,0 +1,111 @@ +package cache2go + +import ( + "sync" + "time" +) + +type Set struct { + sync.RWMutex + len int + members map[interface{}]int +} + +func NewSet() *Set { + set := &Set{ + len: 0, + members: make(map[interface{}]int), + } + return set +} + +func (set *Set) Len() int { + set.RLock() + defer set.RUnlock() + return set.len +} + +func (set *Set) SetAdd(member interface{}) error { + set.Lock() + defer set.Unlock() + set.members[member] = 0 + set.len++ + return nil +} + +func (set *Set) SetHas(member interface{}) bool { + set.RLock() + defer set.RUnlock() + + _, ok := set.members[member] + return ok +} + +func (set *Set) SetRemove(member interface{}) error { + set.Lock() + defer set.Unlock() + + if _, ok := set.members[member]; ok { + delete(set.members, member) + set.len-- + } + return nil +} + +// add member to table +func (table *CacheTable) SAdd(key interface{}, lifeSpan time.Duration, member interface{}) *CacheItem { + table.Lock() + item, ok := table.items[key] + if !ok { + set := NewSet() + set.SetAdd(member) + hashItem := NewCacheItem(key, lifeSpan, set) + table.addInternal(hashItem) + return hashItem + } + table.Unlock() + + item.Lock() + if set, ok := item.Data().(*Set); ok { + set.SetAdd(member) + } + item.Unlock() + + return item +} + +// is member +func (table *CacheTable) SIsMember(key interface{}, member interface{}) bool { + table.RLock() + defer table.RUnlock() + + setItem, ok := table.items[key] + if !ok { + return false + } + + set, ok := setItem.Data().(*Set) + if !ok { + return false + } + + return set.SetHas(member) +} + +// delete member +func (table *CacheTable) SDelete(key interface{}, member interface{}) error { + table.Lock() + defer table.Unlock() + + setItem, ok := table.items[key] + if !ok { + return ErrKeyNotFound + } + + set, ok := setItem.Data().(*Set) + if !ok { + return ErrKeyTypeNotSet + } + + return set.SetRemove(member) +} diff --git a/cacheitemset_test.go b/cacheitemset_test.go new file mode 100644 index 0000000..8e978b3 --- /dev/null +++ b/cacheitemset_test.go @@ -0,0 +1,26 @@ +package cache2go + +import ( + "testing" + "time" +) + +func TestCacheTable_SAdd_SIsMember_SDelete(t *testing.T) { + table := Cache("newset") + for i := 1; i <= 10; i++ { + table.SAdd("newset", 0*time.Second, i) + } + for i := 1; i <= 10; i++ { + if has := table.SIsMember("newset", i); !has { + t.Error("SAdd i is", i, "err is", has) + } + } + for i := 1; i <= 10; i++ { + table.SDelete("newset", i) + } + for i := 1; i <= 10; i++ { + if has := table.SIsMember("newset", i); has { + t.Error("SDelete i is", i, "err is", has) + } + } +} diff --git a/cachetablehash.go b/cachetablehash.go new file mode 100644 index 0000000..426786f --- /dev/null +++ b/cachetablehash.go @@ -0,0 +1,69 @@ +/** +cacheItem struct + data map[interface{}]interface{} + +So data is hash, cacheItem is hashItem +*/ +package cache2go + +import "time" + +func (table *CacheTable) HAdd(key interface{}, lifeSpan time.Duration, hkey interface{}, hvalue interface{}) *CacheItem { + table.Lock() + item, ok := table.items[key] + if !ok { + hash := make(map[interface{}]interface{}) + hash[hkey] = hvalue + hashItem := NewCacheItem(key, lifeSpan, hash) + table.addInternal(hashItem) + return hashItem + } + table.Unlock() + + item.Lock() + if hash, ok := item.Data().(map[interface{}]interface{}); ok { + hash[hkey] = hvalue + } + item.Unlock() + + return item + +} + +func (table *CacheTable) HValue(key interface{}, hkey interface{}) (interface{}, error) { + table.RLock() + hashItem, ok := table.items[key] + table.RUnlock() + if !ok { + return nil, ErrKeyNotFound + } + + hvalue, hok := hashItem.Data().(map[interface{}]interface{})[hkey] + if !hok { + return nil, ErrKeyNotFound + } + + hashItem.KeepAlive() + return hvalue, nil +} + +func (table *CacheTable) HDelete(key interface{}, hkey interface{}) error { + table.RLock() + defer table.RUnlock() + + hashItem, ok := table.items[key] + if !ok { + return ErrKeyNotFound + } + + hashItem.Lock() + defer hashItem.Unlock() + + hash, ok := hashItem.Data().(map[interface{}]interface{}) + if !ok { + return ErrKeyTypeNotHash + } + + delete(hash, hkey) + return nil +} diff --git a/cachetablehash_test.go b/cachetablehash_test.go new file mode 100644 index 0000000..d839258 --- /dev/null +++ b/cachetablehash_test.go @@ -0,0 +1,47 @@ +package cache2go + +import ( + "testing" + "time" +) + +func TestCacheTable_HAdd(t *testing.T) { + cache := Cache("htable") + var hs1 int8 = 100 + cache.HAdd("hstudents", 0*time.Second, "s1", hs1) + hv, err := cache.HValue("hstudents", "s1") + if err != nil || hv == nil || hv.(int8) != hs1 { + t.Error("Error retrieving non expiring data from cache", err) + } + + var ht1 int8 = 99 + cache.HAdd("hteacher", 1*time.Second, "t1", ht1) + time.Sleep(2 * time.Second) + htv, err := cache.HValue("hteacher", "t1") + if err == nil || htv != nil { + t.Error("Error retrieving non expiring data from cache", err) + } +} + +func TestCacheTable_HDelete(t *testing.T) { + var hv interface{} + var err error + var hs1 int8 = 100 + + cache := Cache("htable") + cache.HAdd("hstudents", 0*time.Second, "s1", hs1) + hv, err = cache.HValue("hstudents", "s1") + if err != nil || hv == nil || hv.(int8) != hs1 { + t.Error("Error retrieving non expiring data from cache", err) + } + + err = cache.HDelete("hstudents", "s1") + if err != nil { + t.Error("Error delete hash key fail", err) + } + + hv, err = cache.HValue("hstudents", "s1") + if err == nil || hv != nil { + t.Error("Error retrieving non expiring data from cache", err) + } +} diff --git a/cachetablelist.go b/cachetablelist.go new file mode 100644 index 0000000..2278a0c --- /dev/null +++ b/cachetablelist.go @@ -0,0 +1,105 @@ +/** +cacheItem struct + data *container/list.List + +So data is *list.List,cacheItem is listItem +*/ +package cache2go + +import ( + "container/list" + "time" +) + +func (table *CacheTable) LPush(key interface{}, lifeSpan time.Duration, value interface{}) error { + return table.push(true, key, lifeSpan, value) +} + +func (table *CacheTable) LPop(key interface{}) (interface{}, error) { + return table.pop(true, key) +} + +func (table *CacheTable) RPush(key interface{}, lifeSpan time.Duration, value interface{}) error { + return table.push(false, key, lifeSpan, value) +} + +func (table *CacheTable) RPop(key interface{}) (interface{}, error) { + return table.pop(false, key) +} + +func (table *CacheTable) ListLength(key interface{}) (int, error) { + table.RLock() + defer table.RUnlock() + + listItem, ok := table.items[key] + if !ok { + return 0, ErrKeyNotFound + } + + listItem.RLock() + defer listItem.RUnlock() + + list, ok := listItem.Data().(*list.List) + if !ok { + return 0, ErrKeyTypeNotList + } + return list.Len(), nil +} + +// push value into list +// fromLeft true:lpush Or lPop false:rpush,rPop +func (table *CacheTable) push(fromLeft bool, key interface{}, lifeSpan time.Duration, value interface{}) error { + table.Lock() + listItem, ok := table.items[key] + if !ok { + newList := list.New() + if fromLeft { + newList.PushFront(value) + } else { + newList.PushBack(value) + } + newListItem := NewCacheItem(key, lifeSpan, newList) + table.addInternal(newListItem) + return nil + } + table.Unlock() + + listItem.Lock() + defer listItem.Unlock() + listObj, ok := listItem.Data().(*list.List) + if !ok { + return ErrKeyTypeNotList + } + if fromLeft { + listObj.PushFront(value) + } else { + listObj.PushBack(value) + } + return nil +} + +func (table *CacheTable) pop(fromLeft bool, key interface{}) (interface{}, error) { + table.RLock() + listItem, ok := table.items[key] + table.RUnlock() + if !ok { + return nil, ErrKeyNotFound + } + + listItem.RLock() + listObj, ok := listItem.Data().(*list.List) + listItem.RUnlock() + if !ok { + return nil, ErrKeyTypeNotList + } + + var popElement *list.Element + if fromLeft { + popElement = listObj.Front() + } else { + popElement = listObj.Back() + } + + listObj.Remove(popElement) + return popElement.Value, nil +} diff --git a/cachetablelist_test.go b/cachetablelist_test.go new file mode 100644 index 0000000..9558f67 --- /dev/null +++ b/cachetablelist_test.go @@ -0,0 +1,49 @@ +package cache2go + +import ( + "testing" + "time" +) + +func TestCacheTable_LPush_LPop_RPush_RPop_ListLength(t *testing.T) { + table := Cache("newlist") + var err error + var j interface{} + for i := 1; i <= 10; i++ { + err = table.LPush("newlist", 0*time.Second, i) + if err != nil { + t.Error("lpush error", err) + } + } + if length, err := table.ListLength("newlist"); length != 10 || err != nil { + t.Error("length error", err) + } + + for i := 10; i >= 1; i-- { + j, err = table.LPop("newlist") + jInt, ok := j.(int) + if err != nil || i != jInt || !ok { + t.Error("lpop error", err) + } + } + + if length, err := table.ListLength("newlist"); length != 0 || err != nil { + t.Error("length error", err) + } + + for i := 1; i <= 10; i++ { + err = table.RPush("newlist", 0*time.Second, i) + if err != nil { + t.Error("lpush error", err) + } + } + + for i := 10; i >= 1; i-- { + j, err = table.RPop("newlist") + jInt, ok := j.(int) + if err != nil || i != jInt || !ok { + t.Error("lpop error", err) + } + } + +} diff --git a/errors.go b/errors.go index b2b422a..cc06e2a 100644 --- a/errors.go +++ b/errors.go @@ -17,4 +17,10 @@ var ( // ErrKeyNotFoundOrLoadable gets returned when a specific key couldn't be // found and loading via the data-loader callback also failed ErrKeyNotFoundOrLoadable = errors.New("Key not found and could not be loaded into cache") + // ErrKeyTypeNotHash gets returned when cacheitem data is not map type + ErrKeyTypeNotHash = errors.New("Key type is not hash") + // ErrKeyTypeNotList gets returned when cacheitem data is not list type + ErrKeyTypeNotList = errors.New("Key type is not list") + // ErrKeyTypeNotSet gets returned when cacheitem data is not set type + ErrKeyTypeNotSet = errors.New("Key type is not set") )