diff --git a/memory/lru/cache.go b/memory/lru/cache.go index cf525fd..15c92d4 100644 --- a/memory/lru/cache.go +++ b/memory/lru/cache.go @@ -21,6 +21,8 @@ import ( "sync" "time" + "github.com/ecodeclub/ekit/set" + "github.com/ecodeclub/ekit/list" "github.com/ecodeclub/ecache" @@ -138,7 +140,7 @@ func (c *Cache) LPush(ctx context.Context, key string, val ...any) (int64, error result.Val, ok = c.client.Get(key) if !ok { l := &list.ConcurrentList[ecache.Value]{ - List: list.NewLinkedListOf[ecache.Value](c.anySliceToValueSlice(val)), + List: list.NewLinkedListOf[ecache.Value](c.anySliceToValueSlice(val...)), } c.client.Add(key, l) return int64(l.Len()), nil @@ -188,12 +190,56 @@ func (c *Cache) LPop(ctx context.Context, key string) (val ecache.Value) { } func (c *Cache) SAdd(ctx context.Context, key string, members ...any) (int64, error) { - // TODO - return 0, nil + c.lock.Lock() + defer c.lock.Unlock() + + var ( + ok bool + result = ecache.Value{} + ) + result.Val, ok = c.client.Get(key) + if !ok { + result.Val = set.NewMapSet[any](8) + } + + s, ok := result.Val.(set.Set[any]) + if !ok { + return 0, errors.New("当前key已存在不是set类型") + } + + for _, value := range members { + s.Add(value) + } + c.client.Add(key, s) + + return int64(len(s.Keys())), nil } func (c *Cache) SRem(ctx context.Context, key string, members ...any) (val ecache.Value) { - // TODO + c.lock.Lock() + defer c.lock.Unlock() + + result, ok := c.client.Get(key) + if !ok { + val.Err = errs.ErrKeyNotExist + return + } + + s, ok := result.(set.Set[any]) + if !ok { + val.Err = errors.New("当前key已存在不是set类型") + return + } + + var rems = make([]any, 0, cap(members)) + for _, member := range members { + if s.Exist(member) { + rems = append(rems, member) + s.Delete(member) + } + } + + val.Val = rems return } diff --git a/memory/lru/cache_test.go b/memory/lru/cache_test.go index da8511c..3e43d36 100644 --- a/memory/lru/cache_test.go +++ b/memory/lru/cache_test.go @@ -17,9 +17,12 @@ package lru import ( "context" "errors" + "reflect" "testing" "time" + "github.com/ecodeclub/ekit/set" + "github.com/ecodeclub/ecache" "github.com/ecodeclub/ecache/internal/errs" "github.com/ecodeclub/ekit/list" @@ -359,7 +362,7 @@ func TestCache_LPush(t *testing.T) { after func(t *testing.T) key string - val string + val []any wantVal int64 wantErr error }{ @@ -370,9 +373,19 @@ func TestCache_LPush(t *testing.T) { assert.Equal(t, true, lru.Remove("test")) }, key: "test", - val: "hello ecache", + val: []any{"hello ecache"}, wantVal: 1, }, + { + name: "lpush multiple value", + before: func(t *testing.T) {}, + after: func(t *testing.T) { + assert.Equal(t, true, lru.Remove("test")) + }, + key: "test", + val: []any{"hello ecache", "hello world"}, + wantVal: 2, + }, { name: "lpush value exists", before: func(t *testing.T) { @@ -387,7 +400,7 @@ func TestCache_LPush(t *testing.T) { assert.Equal(t, true, lru.Remove("test")) }, key: "test", - val: "hello world", + val: []any{"hello world"}, wantVal: 2, }, { @@ -399,7 +412,7 @@ func TestCache_LPush(t *testing.T) { assert.Equal(t, true, lru.Remove("test")) }, key: "test", - val: "hello ecache", + val: []any{"hello ecache"}, wantErr: errors.New("当前key不是list类型"), }, } @@ -411,7 +424,7 @@ func TestCache_LPush(t *testing.T) { c := NewCache(lru) tc.before(t) - length, err := c.LPush(ctx, tc.key, tc.val) + length, err := c.LPush(ctx, tc.key, tc.val...) assert.Equal(t, tc.wantVal, length) assert.Equal(t, tc.wantErr, err) tc.after(t) @@ -516,6 +529,171 @@ func TestCache_LPop(t *testing.T) { } } +func TestCache_SAdd(t *testing.T) { + evictCounter := 0 + onEvicted := func(key string, value any) { + evictCounter++ + } + lru, err := simplelru.NewLRU[string, any](5, onEvicted) + assert.NoError(t, err) + + testCase := []struct { + name string + before func(t *testing.T) + after func(t *testing.T) + + key string + val []any + wantVal int64 + wantErr error + }{ + { + name: "sadd value", + before: func(t *testing.T) {}, + after: func(t *testing.T) { + assert.Equal(t, true, lru.Remove("test")) + }, + key: "test", + val: []any{"hello ecache", "hello world"}, + wantVal: 2, + }, + { + name: "sadd value exist", + before: func(t *testing.T) { + s := set.NewMapSet[any](8) + s.Add("hello world") + + assert.Equal(t, false, lru.Add("test", s)) + }, + after: func(t *testing.T) { + assert.Equal(t, true, lru.Remove("test")) + }, + key: "test", + val: []any{"hello ecache"}, + wantVal: 2, + }, + { + name: "sadd value type err", + before: func(t *testing.T) { + assert.Equal(t, false, lru.Add("test", "string")) + }, + after: func(t *testing.T) { + assert.Equal(t, true, lru.Remove("test")) + }, + key: "test", + val: []any{"hello"}, + wantErr: errors.New("当前key已存在不是set类型"), + }, + } + + for _, tc := range testCase { + t.Run(tc.name, func(t *testing.T) { + ctx, cancelFunc := context.WithTimeout(context.Background(), time.Second*5) + defer cancelFunc() + c := NewCache(lru) + + tc.before(t) + val, err := c.SAdd(ctx, tc.key, tc.val...) + assert.Equal(t, tc.wantVal, val) + assert.Equal(t, tc.wantErr, err) + tc.after(t) + }) + } +} + +func TestCache_SRem(t *testing.T) { + evictCounter := 0 + onEvicted := func(key string, value any) { + evictCounter++ + } + lru, err := simplelru.NewLRU[string, any](5, onEvicted) + assert.NoError(t, err) + + testCase := []struct { + name string + before func(t *testing.T) + after func(t *testing.T) + + key string + val []any + wantVal []any + wantErr error + }{ + { + name: "srem value", + before: func(t *testing.T) { + s := set.NewMapSet[any](8) + + s.Add("hello world") + s.Add("hello ecache") + + assert.Equal(t, false, lru.Add("test", s)) + }, + after: func(t *testing.T) { + assert.Equal(t, true, lru.Remove("test")) + }, + key: "test", + val: []any{"hello world"}, + wantVal: []any{"hello world"}, + }, + { + name: "srem value ignore", + before: func(t *testing.T) { + s := set.NewMapSet[any](8) + s.Add("hello world") + + assert.Equal(t, false, lru.Add("test", s)) + }, + after: func(t *testing.T) { + assert.Equal(t, true, lru.Remove("test")) + }, + key: "test", + val: []any{"hello ecache"}, + wantVal: []any{}, + }, + { + name: "srem value nil", + before: func(t *testing.T) {}, + after: func(t *testing.T) {}, + key: "test", + val: []any{"hello world"}, + wantErr: errs.ErrKeyNotExist, + }, + { + name: "srem value type error", + before: func(t *testing.T) { + assert.Equal(t, false, lru.Add("test", int64(1))) + }, + after: func(t *testing.T) { + assert.Equal(t, true, lru.Remove("test")) + }, + key: "test", + val: []any{"hello world"}, + wantErr: errors.New("当前key已存在不是set类型"), + }, + } + + for _, tc := range testCase { + t.Run(tc.name, func(t *testing.T) { + ctx, cancelFunc := context.WithTimeout(context.Background(), time.Second*5) + defer cancelFunc() + c := NewCache(lru) + + tc.before(t) + val := c.SRem(ctx, tc.key, tc.val...) + defer tc.after(t) + if val.Err != nil { + assert.Equal(t, tc.wantErr, val.Err) + return + } + + result, ok := val.Val.([]any) + assert.Equal(t, true, ok) + assert.Equal(t, true, reflect.DeepEqual(tc.wantVal, result)) + }) + } +} + func TestCache_IncrBy(t *testing.T) { evictCounter := 0 onEvicted := func(key string, value any) { @@ -524,7 +702,7 @@ func TestCache_IncrBy(t *testing.T) { lru, err := simplelru.NewLRU[string, any](5, onEvicted) assert.NoError(t, err) - testCache := []struct { + testCase := []struct { name string before func(t *testing.T) after func(t *testing.T) @@ -570,7 +748,7 @@ func TestCache_IncrBy(t *testing.T) { }, } - for _, tc := range testCache { + for _, tc := range testCase { t.Run(tc.name, func(t *testing.T) { ctx, cancelFunc := context.WithTimeout(context.Background(), time.Second*5) defer cancelFunc() @@ -593,7 +771,7 @@ func TestCache_DecrBy(t *testing.T) { lru, err := simplelru.NewLRU[string, any](5, onEvicted) assert.NoError(t, err) - testCache := []struct { + testCase := []struct { name string before func(t *testing.T) after func(t *testing.T) @@ -639,7 +817,7 @@ func TestCache_DecrBy(t *testing.T) { }, } - for _, tc := range testCache { + for _, tc := range testCase { t.Run(tc.name, func(t *testing.T) { ctx, cancelFunc := context.WithTimeout(context.Background(), time.Second*5) defer cancelFunc() @@ -662,7 +840,7 @@ func TestCache_IncrByFloat(t *testing.T) { lru, err := simplelru.NewLRU[string, any](5, onEvicted) assert.NoError(t, err) - testCache := []struct { + testCase := []struct { name string before func(t *testing.T) after func(t *testing.T) @@ -708,7 +886,7 @@ func TestCache_IncrByFloat(t *testing.T) { }, } - for _, tc := range testCache { + for _, tc := range testCase { t.Run(tc.name, func(t *testing.T) { ctx, cancelFunc := context.WithTimeout(context.Background(), time.Second*5) defer cancelFunc()