mirror of
https://github.com/grafana/grafana.git
synced 2025-07-29 05:22:32 +08:00

* Initial refactoring work for plugins kvstore * Replace implementations for keystore and angularstore * Cleanup * add interface check * lint * fix storeKeyGetter not being called in namespacedstore set * Fix tests * Comments * Add tests * Fix invalid cap in ListKeys when store is empty * Update docstrings * Add setLastUpdatedOnDelete * Renamed DefaultStoreKeyGetterFunc, add TestDefaultStoreKeyGetter * Sort imports * PR review: removed last_updated key * PR review: Removed setLastUpdatedOnDelete * Re-added relevant tests * PR review: Removed SingleKeyStore * PR review: Removed custom marshaling support * Renamed marshaler.go to marshal.go * PR review: removed unused interfaces * PR review: Moved marshal into namespacedstore.go * PR review: removed storekeygetter * Removed unused file cachekvstore.go * Renamed NamespacedStore to CacheKvStore * removed todo
143 lines
4.4 KiB
Go
143 lines
4.4 KiB
Go
package cachekvstore
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"time"
|
|
|
|
"github.com/grafana/grafana/pkg/infra/kvstore"
|
|
)
|
|
|
|
// keyLastUpdated is the key used to store the last updated time.
|
|
const keyLastUpdated = "last_updated"
|
|
|
|
// CacheKvStore is a Store that stores data in a *kvstore.NamespacedKVStore.
|
|
// It also stores a last updated time, which is unique for all the keys and is updated on each call to `Set`,
|
|
// and can be used to determine if the data is stale.
|
|
type CacheKvStore struct {
|
|
// kv is the underlying KV store.
|
|
kv *kvstore.NamespacedKVStore
|
|
|
|
// keyPrefix is the prefix to use for all the keys.
|
|
keyPrefix string
|
|
}
|
|
|
|
// NewCacheKvStoreWithPrefix creates a new CacheKvStore using the provided underlying KVStore, namespace and prefix.
|
|
func NewCacheKvStoreWithPrefix(kv kvstore.KVStore, namespace, prefix string) *CacheKvStore {
|
|
return &CacheKvStore{
|
|
kv: kvstore.WithNamespace(kv, 0, namespace),
|
|
keyPrefix: prefix,
|
|
}
|
|
}
|
|
|
|
// NewCacheKvStore creates a new CacheKvStore using the provided underlying KVStore and namespace.
|
|
func NewCacheKvStore(kv kvstore.KVStore, namespace string) *CacheKvStore {
|
|
return NewCacheKvStoreWithPrefix(kv, namespace, "")
|
|
}
|
|
|
|
// storeKey returns the key to use in the underlying store for the given key.
|
|
func (s *CacheKvStore) storeKey(k string) string {
|
|
return s.keyPrefix + k
|
|
}
|
|
|
|
// Get returns the value for the given key.
|
|
// If no value is present, the second argument is false and the returned error is nil.
|
|
func (s *CacheKvStore) Get(ctx context.Context, key string) (string, bool, error) {
|
|
return s.kv.Get(ctx, s.storeKey(key))
|
|
}
|
|
|
|
// Set sets the value for the given key and updates the last updated time.
|
|
// It uses the marshal method to marshal the value before storing it.
|
|
// This means that the value to store can implement the Marshaler interface to control how it is stored.
|
|
func (s *CacheKvStore) Set(ctx context.Context, key string, value any) error {
|
|
valueToStore, err := marshal(value)
|
|
if err != nil {
|
|
return fmt.Errorf("marshal: %w", err)
|
|
}
|
|
|
|
if err := s.kv.Set(ctx, s.storeKey(key), valueToStore); err != nil {
|
|
return fmt.Errorf("kv set: %w", err)
|
|
}
|
|
if err := s.SetLastUpdated(ctx); err != nil {
|
|
return fmt.Errorf("set last updated: %w", err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// GetLastUpdated returns the last updated time.
|
|
// If the last updated time is not set, it returns a zero time.
|
|
func (s *CacheKvStore) GetLastUpdated(ctx context.Context) (time.Time, error) {
|
|
v, ok, err := s.kv.Get(ctx, keyLastUpdated)
|
|
if err != nil {
|
|
return time.Time{}, fmt.Errorf("kv get: %w", err)
|
|
}
|
|
if !ok {
|
|
return time.Time{}, nil
|
|
}
|
|
t, err := time.Parse(time.RFC3339, v)
|
|
if err != nil {
|
|
// Ignore decode errors, so we can change the format in future versions
|
|
// and keep backwards/forwards compatibility
|
|
return time.Time{}, nil
|
|
}
|
|
return t, nil
|
|
}
|
|
|
|
// SetLastUpdated sets the last updated time to the current time.
|
|
// The last updated time is shared between all the keys for this store.
|
|
func (s *CacheKvStore) SetLastUpdated(ctx context.Context) error {
|
|
return s.kv.Set(ctx, keyLastUpdated, time.Now().Format(time.RFC3339))
|
|
}
|
|
|
|
// Delete deletes the value for the given key and it also updates the last updated time.
|
|
func (s *CacheKvStore) Delete(ctx context.Context, key string) error {
|
|
if err := s.kv.Del(ctx, s.storeKey(key)); err != nil {
|
|
return fmt.Errorf("kv del: %w", err)
|
|
}
|
|
if err := s.SetLastUpdated(ctx); err != nil {
|
|
return fmt.Errorf("set last updated: %w", err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// ListKeys returns all the keys in the store.
|
|
func (s *CacheKvStore) ListKeys(ctx context.Context) ([]string, error) {
|
|
keys, err := s.kv.Keys(ctx, s.storeKey(""))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if len(keys) == 0 {
|
|
return nil, nil
|
|
}
|
|
res := make([]string, 0, len(keys)-1)
|
|
for _, key := range keys {
|
|
// Filter out last updated time
|
|
if key.Key == keyLastUpdated {
|
|
continue
|
|
}
|
|
res = append(res, key.Key)
|
|
}
|
|
return res, nil
|
|
}
|
|
|
|
// marshal marshals the provided value to a string to store it in the kv store.
|
|
// The provided value can be of a type implementing fmt.Stringer, a string or []byte.
|
|
// If the value is none of those, it is marshaled to JSON.
|
|
func marshal(value any) (string, error) {
|
|
switch value := value.(type) {
|
|
case fmt.Stringer:
|
|
return value.String(), nil
|
|
case string:
|
|
return value, nil
|
|
case []byte:
|
|
return string(value), nil
|
|
default:
|
|
b, err := json.Marshal(value)
|
|
if err != nil {
|
|
return "", fmt.Errorf("json marshal: %w", err)
|
|
}
|
|
return string(b), nil
|
|
}
|
|
}
|