From ae92822ee0a5159a1049fb6a24b20d2860765e2e Mon Sep 17 00:00:00 2001 From: kolaente Date: Fri, 27 Jun 2025 13:05:37 +0200 Subject: [PATCH] feat: add prefix key support to keyvalue store (#1038) feat: add prefix key operations to keyvalue store --- pkg/modules/keyvalue/keyvalue.go | 12 ++++++++++ pkg/modules/keyvalue/memory/memory.go | 30 +++++++++++++++++++++++ pkg/modules/keyvalue/redis/redis.go | 34 +++++++++++++++++++++++++++ 3 files changed, 76 insertions(+) diff --git a/pkg/modules/keyvalue/keyvalue.go b/pkg/modules/keyvalue/keyvalue.go index d0104a19f..4e594b3c5 100644 --- a/pkg/modules/keyvalue/keyvalue.go +++ b/pkg/modules/keyvalue/keyvalue.go @@ -30,6 +30,8 @@ type Storage interface { Del(key string) (err error) IncrBy(key string, update int64) (err error) DecrBy(key string, update int64) (err error) + ListKeys(prefix string) ([]string, error) + DelPrefix(prefix string) error } var store Storage @@ -74,3 +76,13 @@ func IncrBy(key string, update int64) (err error) { func DecrBy(key string, update int64) (err error) { return store.DecrBy(key, update) } + +// ListKeys returns all keys beginning with prefix from the configured backend +func ListKeys(prefix string) ([]string, error) { + return store.ListKeys(prefix) +} + +// DelPrefix deletes all keys with the given prefix in the backend +func DelPrefix(prefix string) error { + return store.DelPrefix(prefix) +} diff --git a/pkg/modules/keyvalue/memory/memory.go b/pkg/modules/keyvalue/memory/memory.go index e4e30206d..20c60497d 100644 --- a/pkg/modules/keyvalue/memory/memory.go +++ b/pkg/modules/keyvalue/memory/memory.go @@ -18,6 +18,7 @@ package memory import ( "reflect" + "strings" "sync" e "code.vikunja.io/api/pkg/modules/keyvalue/error" @@ -125,3 +126,32 @@ func (s *Storage) DecrBy(key string, update int64) (err error) { s.store[key] = val - update return nil } + +// ListKeys returns all keys in the storage which start with the given prefix +func (s *Storage) ListKeys(prefix string) ([]string, error) { + s.mutex.Lock() + defer s.mutex.Unlock() + + keys := make([]string, 0) + for k := range s.store { + if strings.HasPrefix(k, prefix) { + keys = append(keys, k) + } + } + + return keys, nil +} + +// DelPrefix removes all keys which start with the given prefix +func (s *Storage) DelPrefix(prefix string) error { + s.mutex.Lock() + defer s.mutex.Unlock() + + for k := range s.store { + if strings.HasPrefix(k, prefix) { + delete(s.store, k) + } + } + + return nil +} diff --git a/pkg/modules/keyvalue/redis/redis.go b/pkg/modules/keyvalue/redis/redis.go index 9db97048c..b6caae300 100644 --- a/pkg/modules/keyvalue/redis/redis.go +++ b/pkg/modules/keyvalue/redis/redis.go @@ -113,3 +113,37 @@ func (s *Storage) IncrBy(key string, update int64) (err error) { func (s *Storage) DecrBy(key string, update int64) (err error) { return s.client.DecrBy(context.Background(), key, update).Err() } + +// ListKeys returns all keys in redis starting with the given prefix +func (s *Storage) ListKeys(prefix string) ([]string, error) { + ctx := context.Background() + pattern := prefix + "*" + var cursor uint64 + var keys []string + + for { + k, c, err := s.client.Scan(ctx, cursor, pattern, 100).Result() + if err != nil { + return nil, err + } + keys = append(keys, k...) + cursor = c + if cursor == 0 { + break + } + } + + return keys, nil +} + +// DelPrefix removes all keys in redis which start with the given prefix +func (s *Storage) DelPrefix(prefix string) error { + keys, err := s.ListKeys(prefix) + if err != nil { + return err + } + if len(keys) == 0 { + return nil + } + return s.client.Del(context.Background(), keys...).Err() +}