cosmos-sdk/state/kvcache.go

148 lines
3.2 KiB
Go

package state
// MemKVCache is designed to wrap MemKVStore as a cache
type MemKVCache struct {
store SimpleDB
cache *MemKVStore
}
var _ SimpleDB = (*MemKVCache)(nil)
// NewMemKVCache wraps a cache around MemKVStore
//
// You probably don't want to use directly, but rather
// via MemKVCache.Checkpoint()
func NewMemKVCache(store SimpleDB) *MemKVCache {
if store == nil {
panic("wtf")
}
return &MemKVCache{
store: store,
cache: NewMemKVStore(),
}
}
// Set sets a key, fulfills KVStore interface
func (c *MemKVCache) Set(key []byte, value []byte) {
c.cache.Set(key, value)
}
// Get gets a key, fulfills KVStore interface
func (c *MemKVCache) Get(key []byte) (value []byte) {
value, ok := c.cache.m[string(key)]
if !ok {
value = c.store.Get(key)
c.cache.Set(key, value)
}
return value
}
// Has checks existence of a key, fulfills KVStore interface
func (c *MemKVCache) Has(key []byte) bool {
value := c.Get(key)
return value != nil
}
// Remove uses nil value as a flag to delete... not ideal but good enough
// for testing
func (c *MemKVCache) Remove(key []byte) (value []byte) {
value = c.Get(key)
c.cache.Set(key, nil)
return value
}
// List is also inefficiently implemented...
func (c *MemKVCache) List(start, end []byte, limit int) []Model {
orig := c.store.List(start, end, 0)
cached := c.cache.List(start, end, 0)
keys := c.combineLists(orig, cached)
// apply limit (too late)
if limit > 0 && len(keys) > 0 {
if limit > len(keys) {
limit = len(keys)
}
keys = keys[:limit]
}
return keys
}
func (c *MemKVCache) combineLists(orig, cache []Model) []Model {
store := NewMemKVStore()
for _, m := range orig {
store.Set(m.Key, m.Value)
}
for _, m := range cache {
if m.Value == nil {
store.Remove([]byte(m.Key))
} else {
store.Set([]byte(m.Key), m.Value)
}
}
return store.List(nil, nil, 0)
}
// First is done with List, but could be much more efficient
func (c *MemKVCache) First(start, end []byte) Model {
data := c.List(start, end, 0)
if len(data) == 0 {
return Model{}
}
return data[0]
}
// Last is done with List, but could be much more efficient
func (c *MemKVCache) Last(start, end []byte) Model {
data := c.List(start, end, 0)
if len(data) == 0 {
return Model{}
}
return data[len(data)-1]
}
// Checkpoint returns the same state, but where writes
// are buffered and don't affect the parent
func (c *MemKVCache) Checkpoint() SimpleDB {
return NewMemKVCache(c)
}
// Commit will take all changes from the checkpoint and write
// them to the parent.
// Returns an error if this is not a child of this one
func (c *MemKVCache) Commit(sub SimpleDB) error {
cache, ok := sub.(*MemKVCache)
if !ok {
return ErrNotASubTransaction()
}
// see if it points to us
ref, ok := cache.store.(*MemKVCache)
if !ok || ref != c {
return ErrNotASubTransaction()
}
// apply the cached data to us
cache.applyCache()
return nil
}
// applyCache will apply all the cache methods to the underlying store
func (c *MemKVCache) applyCache() {
for _, k := range c.cache.keysInRange(nil, nil) {
v := c.cache.m[k]
if v == nil {
c.store.Remove([]byte(k))
} else {
c.store.Set([]byte(k), v)
}
}
}
// Discard will remove reference to this
func (c *MemKVCache) Discard() {
c.cache = NewMemKVStore()
}