Skip to content

Commit fe240a6

Browse files
committed
improved filters and sorting
1 parent 5810d87 commit fe240a6

File tree

9 files changed

+107
-157
lines changed

9 files changed

+107
-157
lines changed

backend/backend.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ type IBackend[T IBackendConstrain] interface {
2727
// Remove deletes the item with the given key from the cache.
2828
Remove(ctx context.Context, keys ...string) error
2929
// List the items in the cache that meet the specified criteria.
30-
List(ctx context.Context, filters ...IFilter) ([]*types.Item, error)
30+
List(ctx context.Context, filters ...IFilter) (items []*types.Item, err error)
3131
// Clear removes all items from the cache.
3232
Clear(ctx context.Context) error
3333
}

backend/filters.go

Lines changed: 75 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -1,81 +1,48 @@
11
package backend
22

33
import (
4+
"fmt"
45
"sort"
56

67
"github.com/hyp3rd/hypercache/types"
78
)
89

10+
// itemSorter is a custom sorter for the items
11+
type itemSorter struct {
12+
items []*types.Item
13+
less func(i, j *types.Item) bool
14+
}
15+
16+
func (s *itemSorter) Len() int { return len(s.items) }
17+
func (s *itemSorter) Swap(i, j int) { s.items[i], s.items[j] = s.items[j], s.items[i] }
18+
func (s *itemSorter) Less(i, j int) bool { return s.less(s.items[i], s.items[j]) }
19+
920
// IFilter is a backend agnostic interface for a filter that can be applied to a list of items.
1021
type IFilter interface {
11-
ApplyFilter(backendType string, items []*types.Item) []*types.Item
22+
ApplyFilter(backendType string, items []*types.Item) ([]*types.Item, error)
1223
}
1324

1425
// sortByFilter is a filter that sorts the items by a given field.
1526
type sortByFilter struct {
1627
field string
1728
}
1829

19-
// ApplyFilter applies the sort filter to the given list of items.
20-
func (f sortByFilter) ApplyFilter(backendType string, items []*types.Item) []*types.Item {
21-
var sorter sort.Interface
22-
switch f.field {
23-
case types.SortByKey.String():
24-
sorter = &itemSorterByKey{items: items}
25-
case types.SortByLastAccess.String():
26-
sorter = &itemSorterByLastAccess{items: items}
27-
case types.SortByAccessCount.String():
28-
sorter = &itemSorterByAccessCount{items: items}
29-
case types.SortByExpiration.String():
30-
sorter = &itemSorterByExpiration{items: items}
31-
default:
32-
return items
33-
}
34-
sort.Sort(sorter)
35-
return items
36-
}
37-
3830
// SortOrderFilter is a filter that sorts the items by a given field.
3931
type SortOrderFilter struct {
4032
ascending bool
4133
}
4234

43-
// ApplyFilter applies the sort order filter to the given list of items.
44-
func (f SortOrderFilter) ApplyFilter(backendType string, items []*types.Item) []*types.Item {
45-
if !f.ascending {
46-
sort.Slice(items, func(i, j int) bool {
47-
return items[j].Key > items[i].Key
48-
})
49-
} else {
50-
sort.Slice(items, func(i, j int) bool {
51-
return items[i].Key < items[j].Key
52-
})
53-
}
54-
return items
55-
}
56-
5735
// filterFunc is a filter that filters the items by a given field's value.
5836
type filterFunc struct {
5937
fn func(item *types.Item) bool
6038
}
6139

62-
// ApplyFilter applies the filter function to the given list of items.
63-
func (f filterFunc) ApplyFilter(backendType string, items []*types.Item) []*types.Item {
64-
filteredItems := make([]*types.Item, 0)
65-
for _, item := range items {
66-
if f.fn(item) {
67-
filteredItems = append(filteredItems, item)
68-
}
69-
}
70-
return filteredItems
71-
}
72-
7340
// WithSortBy returns a filter that sorts the items by a given field.
7441
func WithSortBy(field string) IFilter {
7542
return sortByFilter{field: field}
7643
}
7744

78-
// WithSortOrderAsc returns a filter that determins whether to sort ascending or not.
45+
// WithSortOrderAsc returns a filter that determines whether to sort ascending or not.
7946
func WithSortOrderAsc(ascending bool) SortOrderFilter {
8047
return SortOrderFilter{ascending: ascending}
8148
}
@@ -84,3 +51,65 @@ func WithSortOrderAsc(ascending bool) SortOrderFilter {
8451
func WithFilterFunc(fn func(item *types.Item) bool) IFilter {
8552
return filterFunc{fn: fn}
8653
}
54+
55+
// ApplyFilter applies the sort by filter to the given list of items.
56+
func (f sortByFilter) ApplyFilter(backendType string, items []*types.Item) ([]*types.Item, error) {
57+
var sorter *itemSorter
58+
59+
switch f.field {
60+
case types.SortByKey.String():
61+
sorter = &itemSorter{
62+
items: items,
63+
less: func(i, j *types.Item) bool {
64+
return i.Key < j.Key
65+
},
66+
}
67+
case types.SortByLastAccess.String():
68+
sorter = &itemSorter{
69+
items: items,
70+
less: func(i, j *types.Item) bool {
71+
return i.LastAccess.UnixNano() < j.LastAccess.UnixNano()
72+
},
73+
}
74+
case types.SortByAccessCount.String():
75+
sorter = &itemSorter{
76+
items: items,
77+
less: func(i, j *types.Item) bool {
78+
return i.AccessCount < j.AccessCount
79+
},
80+
}
81+
case types.SortByExpiration.String():
82+
sorter = &itemSorter{
83+
items: items,
84+
less: func(i, j *types.Item) bool {
85+
return i.Expiration < j.Expiration
86+
},
87+
}
88+
default:
89+
return nil, fmt.Errorf("invalid sort field: %s", f.field)
90+
}
91+
92+
sort.Sort(sorter)
93+
return items, nil
94+
}
95+
96+
// ApplyFilter applies the sort order filter to the given list of items.
97+
func (f SortOrderFilter) ApplyFilter(backendType string, items []*types.Item) ([]*types.Item, error) {
98+
if !f.ascending {
99+
for i, j := 0, len(items)-1; i < j; i, j = i+1, j-1 {
100+
items[i], items[j] = items[j], items[i]
101+
}
102+
}
103+
return items, nil
104+
}
105+
106+
// ApplyFilter applies the filter function to the given list of items.
107+
func (f filterFunc) ApplyFilter(backendType string, items []*types.Item) ([]*types.Item, error) {
108+
filteredItems := make([]*types.Item, 0)
109+
for _, item := range items {
110+
if f.fn(item) {
111+
filteredItems = append(filteredItems, item)
112+
}
113+
}
114+
return filteredItems, nil
115+
}

backend/inmemory.go

Lines changed: 5 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@ type InMemory struct {
1414
items datastructure.ConcurrentMap // map to store the items in the cache
1515
capacity int // capacity of the cache, limits the number of items that can be stored in the cache
1616
sync.RWMutex // mutex to protect the cache from concurrent access
17-
// SortFilters // filters applied when listing the items in the cache
1817
}
1918

2019
// NewInMemory creates a new in-memory cache with the given options.
@@ -76,12 +75,12 @@ func (cacheBackend *InMemory) Set(item *types.Item) error {
7675
}
7776

7877
// List returns a list of all items in the cache filtered and ordered by the given options
79-
func (cacheBackend *InMemory) List(ctx context.Context, filters ...IFilter) ([]*types.Item, error) {
78+
func (cacheBackend *InMemory) List(ctx context.Context, filters ...IFilter) (items []*types.Item, err error) {
8079
// Apply the filters
8180
cacheBackend.RLock()
8281
defer cacheBackend.RUnlock()
8382

84-
items := make([]*types.Item, 0, cacheBackend.items.Count())
83+
items = make([]*types.Item, 0, cacheBackend.items.Count())
8584

8685
for item := range cacheBackend.items.IterBuffered() {
8786
copy := item
@@ -90,18 +89,13 @@ func (cacheBackend *InMemory) List(ctx context.Context, filters ...IFilter) ([]*
9089

9190
// Apply the filters
9291
if len(filters) > 0 {
93-
wg := sync.WaitGroup{}
94-
wg.Add(len(filters))
9592
for _, filter := range filters {
96-
go func(filter IFilter) {
97-
defer wg.Done()
98-
items = filter.ApplyFilter("in-memory", items)
99-
}(filter)
93+
items, err = filter.ApplyFilter("in-memory", items)
10094
}
101-
wg.Wait()
95+
10296
}
10397

104-
return items, nil
98+
return items, err
10599
}
106100

107101
// Remove removes items with the given key from the cacheBackend. If an item is not found, it does nothing.

backend/redis.go

Lines changed: 4 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ package backend
22

33
import (
44
"context"
5-
"sync"
65

76
"github.com/hyp3rd/hypercache/errors"
87
"github.com/hyp3rd/hypercache/libs/serializer"
@@ -16,7 +15,6 @@ type Redis struct {
1615
capacity int // capacity of the cache, limits the number of items that can be stored in the cache
1716
keysSetName string // keysSetName is the name of the set that holds the keys of the items in the cache
1817
Serializer serializer.ISerializer // Serializer is the serializer used to serialize the items before storing them in the cache
19-
// SortFilters // SortFilters holds the filters applied when listing the items in the cache
2018
}
2119

2220
// NewRedis creates a new redis cache with the given options.
@@ -143,7 +141,7 @@ func (cacheBackend *Redis) Set(item *types.Item) error {
143141
}
144142

145143
// List returns a list of all the items in the cacheBackend that match the given filter options.
146-
func (cacheBackend *Redis) List(ctx context.Context, filters ...IFilter) ([]*types.Item, error) {
144+
func (cacheBackend *Redis) List(ctx context.Context, filters ...IFilter) (items []*types.Item, err error) {
147145
// Get the set of keys held in the cacheBackend with the given `keysSetName`
148146
keys, err := cacheBackend.rdb.SMembers(ctx, cacheBackend.keysSetName).Result()
149147
if err != nil {
@@ -163,7 +161,7 @@ func (cacheBackend *Redis) List(ctx context.Context, filters ...IFilter) ([]*typ
163161
}
164162

165163
// Create a slice to hold the items
166-
items := make([]*types.Item, 0, len(keys))
164+
items = make([]*types.Item, 0, len(keys))
167165

168166
// Deserialize the items and add them to the slice of items to return
169167
for _, cmd := range cmds {
@@ -179,18 +177,12 @@ func (cacheBackend *Redis) List(ctx context.Context, filters ...IFilter) ([]*typ
179177

180178
// Apply the filters
181179
if len(filters) > 0 {
182-
wg := sync.WaitGroup{}
183-
wg.Add(len(filters))
184180
for _, filter := range filters {
185-
go func(filter IFilter) {
186-
defer wg.Done()
187-
items = filter.ApplyFilter("redis", items)
188-
}(filter)
181+
items, err = filter.ApplyFilter("redis", items)
189182
}
190-
wg.Wait()
191183
}
192184

193-
return items, nil
185+
return items, err
194186
}
195187

196188
// Remove removes an item from the cache with the given key

backend/sorting.go

Lines changed: 0 additions & 53 deletions
This file was deleted.

examples/eviction/eviction.go

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -60,20 +60,16 @@ func executeExample(evictionInterval time.Duration) {
6060
log.Println("capacity after adding 15 items", cache.Capacity())
6161

6262
log.Println("listing all items in the cache")
63-
items, err := cache.List(context.TODO())
63+
64+
// Apply filters
65+
sortByFilter := backend.WithSortBy(types.SortByKey.String())
66+
items, err := cache.List(context.TODO(), sortByFilter)
6467
if err != nil {
6568
fmt.Println(err)
6669
return
6770
}
6871

69-
// Apply filters
70-
sortByFilter := backend.WithSortBy(types.SortByKey.String())
71-
sortOrderFilter := backend.WithSortOrderAsc(true)
72-
73-
filteredItems := sortByFilter.ApplyFilter("in-memory", items)
74-
sortedItems := sortOrderFilter.ApplyFilter("in-memory", filteredItems)
75-
76-
for _, item := range sortedItems {
72+
for _, item := range items {
7773
fmt.Println(item.Key, item.Value)
7874
}
7975

examples/list/list.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,13 +43,14 @@ func main() {
4343
}
4444

4545
sortByFilter := backend.WithSortBy(types.SortByExpiration.String())
46-
sortOrderFilter := backend.WithSortOrderAsc(true)
46+
47+
sortOrder := backend.WithSortOrderAsc(true)
4748

4849
// Create a filterFuncFilter with the defined filter function
4950
filter := backend.WithFilterFunc(itemsFilterFunc)
5051

5152
// Retrieve the list of items from the cache
52-
items, err := hyperCache.List(context.TODO(), sortByFilter, sortOrderFilter, filter)
53+
items, err := hyperCache.List(context.TODO(), sortByFilter, sortOrder, filter)
5354
if err != nil {
5455
fmt.Println(err)
5556
return

0 commit comments

Comments
 (0)