Skip to content

Commit

Permalink
improved filters and sorting
Browse files Browse the repository at this point in the history
  • Loading branch information
Francesco Cosentino committed Apr 4, 2023
1 parent 5810d87 commit fe240a6
Show file tree
Hide file tree
Showing 9 changed files with 107 additions and 157 deletions.
2 changes: 1 addition & 1 deletion backend/backend.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ type IBackend[T IBackendConstrain] interface {
// Remove deletes the item with the given key from the cache.
Remove(ctx context.Context, keys ...string) error
// List the items in the cache that meet the specified criteria.
List(ctx context.Context, filters ...IFilter) ([]*types.Item, error)
List(ctx context.Context, filters ...IFilter) (items []*types.Item, err error)
// Clear removes all items from the cache.
Clear(ctx context.Context) error
}
121 changes: 75 additions & 46 deletions backend/filters.go
Original file line number Diff line number Diff line change
@@ -1,81 +1,48 @@
package backend

import (
"fmt"
"sort"

"github.com/hyp3rd/hypercache/types"
)

// itemSorter is a custom sorter for the items
type itemSorter struct {
items []*types.Item
less func(i, j *types.Item) bool
}

func (s *itemSorter) Len() int { return len(s.items) }
func (s *itemSorter) Swap(i, j int) { s.items[i], s.items[j] = s.items[j], s.items[i] }
func (s *itemSorter) Less(i, j int) bool { return s.less(s.items[i], s.items[j]) }

// IFilter is a backend agnostic interface for a filter that can be applied to a list of items.
type IFilter interface {
ApplyFilter(backendType string, items []*types.Item) []*types.Item
ApplyFilter(backendType string, items []*types.Item) ([]*types.Item, error)
}

// sortByFilter is a filter that sorts the items by a given field.
type sortByFilter struct {
field string
}

// ApplyFilter applies the sort filter to the given list of items.
func (f sortByFilter) ApplyFilter(backendType string, items []*types.Item) []*types.Item {
var sorter sort.Interface
switch f.field {
case types.SortByKey.String():
sorter = &itemSorterByKey{items: items}
case types.SortByLastAccess.String():
sorter = &itemSorterByLastAccess{items: items}
case types.SortByAccessCount.String():
sorter = &itemSorterByAccessCount{items: items}
case types.SortByExpiration.String():
sorter = &itemSorterByExpiration{items: items}
default:
return items
}
sort.Sort(sorter)
return items
}

// SortOrderFilter is a filter that sorts the items by a given field.
type SortOrderFilter struct {
ascending bool
}

// ApplyFilter applies the sort order filter to the given list of items.
func (f SortOrderFilter) ApplyFilter(backendType string, items []*types.Item) []*types.Item {
if !f.ascending {
sort.Slice(items, func(i, j int) bool {
return items[j].Key > items[i].Key
})
} else {
sort.Slice(items, func(i, j int) bool {
return items[i].Key < items[j].Key
})
}
return items
}

// filterFunc is a filter that filters the items by a given field's value.
type filterFunc struct {
fn func(item *types.Item) bool
}

// ApplyFilter applies the filter function to the given list of items.
func (f filterFunc) ApplyFilter(backendType string, items []*types.Item) []*types.Item {
filteredItems := make([]*types.Item, 0)
for _, item := range items {
if f.fn(item) {
filteredItems = append(filteredItems, item)
}
}
return filteredItems
}

// WithSortBy returns a filter that sorts the items by a given field.
func WithSortBy(field string) IFilter {
return sortByFilter{field: field}
}

// WithSortOrderAsc returns a filter that determins whether to sort ascending or not.
// WithSortOrderAsc returns a filter that determines whether to sort ascending or not.
func WithSortOrderAsc(ascending bool) SortOrderFilter {
return SortOrderFilter{ascending: ascending}
}
Expand All @@ -84,3 +51,65 @@ func WithSortOrderAsc(ascending bool) SortOrderFilter {
func WithFilterFunc(fn func(item *types.Item) bool) IFilter {
return filterFunc{fn: fn}
}

// ApplyFilter applies the sort by filter to the given list of items.
func (f sortByFilter) ApplyFilter(backendType string, items []*types.Item) ([]*types.Item, error) {
var sorter *itemSorter

switch f.field {
case types.SortByKey.String():
sorter = &itemSorter{
items: items,
less: func(i, j *types.Item) bool {
return i.Key < j.Key
},
}
case types.SortByLastAccess.String():
sorter = &itemSorter{
items: items,
less: func(i, j *types.Item) bool {
return i.LastAccess.UnixNano() < j.LastAccess.UnixNano()
},
}
case types.SortByAccessCount.String():
sorter = &itemSorter{
items: items,
less: func(i, j *types.Item) bool {
return i.AccessCount < j.AccessCount
},
}
case types.SortByExpiration.String():
sorter = &itemSorter{
items: items,
less: func(i, j *types.Item) bool {
return i.Expiration < j.Expiration
},
}
default:
return nil, fmt.Errorf("invalid sort field: %s", f.field)
}

sort.Sort(sorter)
return items, nil
}

// ApplyFilter applies the sort order filter to the given list of items.
func (f SortOrderFilter) ApplyFilter(backendType string, items []*types.Item) ([]*types.Item, error) {
if !f.ascending {
for i, j := 0, len(items)-1; i < j; i, j = i+1, j-1 {
items[i], items[j] = items[j], items[i]
}
}
return items, nil
}

// ApplyFilter applies the filter function to the given list of items.
func (f filterFunc) ApplyFilter(backendType string, items []*types.Item) ([]*types.Item, error) {
filteredItems := make([]*types.Item, 0)
for _, item := range items {
if f.fn(item) {
filteredItems = append(filteredItems, item)
}
}
return filteredItems, nil
}
16 changes: 5 additions & 11 deletions backend/inmemory.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ type InMemory struct {
items datastructure.ConcurrentMap // map to store the items in the cache
capacity int // capacity of the cache, limits the number of items that can be stored in the cache
sync.RWMutex // mutex to protect the cache from concurrent access
// SortFilters // filters applied when listing the items in the cache
}

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

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

items := make([]*types.Item, 0, cacheBackend.items.Count())
items = make([]*types.Item, 0, cacheBackend.items.Count())

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

// Apply the filters
if len(filters) > 0 {
wg := sync.WaitGroup{}
wg.Add(len(filters))
for _, filter := range filters {
go func(filter IFilter) {
defer wg.Done()
items = filter.ApplyFilter("in-memory", items)
}(filter)
items, err = filter.ApplyFilter("in-memory", items)
}
wg.Wait()

}

return items, nil
return items, err
}

// Remove removes items with the given key from the cacheBackend. If an item is not found, it does nothing.
Expand Down
16 changes: 4 additions & 12 deletions backend/redis.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package backend

import (
"context"
"sync"

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

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

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

// Create a slice to hold the items
items := make([]*types.Item, 0, len(keys))
items = make([]*types.Item, 0, len(keys))

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

// Apply the filters
if len(filters) > 0 {
wg := sync.WaitGroup{}
wg.Add(len(filters))
for _, filter := range filters {
go func(filter IFilter) {
defer wg.Done()
items = filter.ApplyFilter("redis", items)
}(filter)
items, err = filter.ApplyFilter("redis", items)
}
wg.Wait()
}

return items, nil
return items, err
}

// Remove removes an item from the cache with the given key
Expand Down
53 changes: 0 additions & 53 deletions backend/sorting.go

This file was deleted.

14 changes: 5 additions & 9 deletions examples/eviction/eviction.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,20 +60,16 @@ func executeExample(evictionInterval time.Duration) {
log.Println("capacity after adding 15 items", cache.Capacity())

log.Println("listing all items in the cache")
items, err := cache.List(context.TODO())

// Apply filters
sortByFilter := backend.WithSortBy(types.SortByKey.String())
items, err := cache.List(context.TODO(), sortByFilter)
if err != nil {
fmt.Println(err)
return
}

// Apply filters
sortByFilter := backend.WithSortBy(types.SortByKey.String())
sortOrderFilter := backend.WithSortOrderAsc(true)

filteredItems := sortByFilter.ApplyFilter("in-memory", items)
sortedItems := sortOrderFilter.ApplyFilter("in-memory", filteredItems)

for _, item := range sortedItems {
for _, item := range items {
fmt.Println(item.Key, item.Value)
}

Expand Down
5 changes: 3 additions & 2 deletions examples/list/list.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,13 +43,14 @@ func main() {
}

sortByFilter := backend.WithSortBy(types.SortByExpiration.String())
sortOrderFilter := backend.WithSortOrderAsc(true)

sortOrder := backend.WithSortOrderAsc(true)

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

// Retrieve the list of items from the cache
items, err := hyperCache.List(context.TODO(), sortByFilter, sortOrderFilter, filter)
items, err := hyperCache.List(context.TODO(), sortByFilter, sortOrder, filter)
if err != nil {
fmt.Println(err)
return
Expand Down
Loading

0 comments on commit fe240a6

Please sign in to comment.