From 3fa68249eb727d35fd2eea57fd30241fb53d951b Mon Sep 17 00:00:00 2001 From: Joon Date: Thu, 21 Jun 2018 14:33:36 -0700 Subject: [PATCH] Merge PR #1254: Add prefixstore and lib refactor * Merge pull request #1254: add prefixstore and lib refactor add prefixstore refactor lib fix test fix linter in progress in progress add test for prefixstore add KVStoreGetter, PrefixStoreGetter move PrefixStoreGetter to types/ add tests KVStore.Prefix(string) -> KVStore.Prefix([]byte) fix mock apply requests pass lint, add test apply requests * Remove unnecessarily 'valid' boolean --- server/mock/store.go | 4 + store/cachekvstore.go | 5 + store/dbstoreadapter.go | 5 + store/gaskvstore.go | 5 + store/iavlstore.go | 5 + store/prefixstore.go | 102 ++++++++++++++ store/prefixstore_test.go | 109 +++++++++++++++ types/lib/linear.go | 250 +++++++++++++++++++++++++++++++++ types/lib/linear_test.go | 157 +++++++++++++++++++++ types/lib/mapper.go | 286 -------------------------------------- types/lib/mapper_test.go | 110 --------------- types/store.go | 31 +++++ 12 files changed, 673 insertions(+), 396 deletions(-) create mode 100644 store/prefixstore.go create mode 100644 store/prefixstore_test.go create mode 100644 types/lib/linear.go create mode 100644 types/lib/linear_test.go delete mode 100644 types/lib/mapper.go delete mode 100644 types/lib/mapper_test.go diff --git a/server/mock/store.go b/server/mock/store.go index 7f62234ea..f00c4dd8d 100644 --- a/server/mock/store.go +++ b/server/mock/store.go @@ -95,6 +95,10 @@ func (kv kvStore) Delete(key []byte) { delete(kv.store, string(key)) } +func (kv kvStore) Prefix(prefix []byte) sdk.KVStore { + panic("not implemented") +} + func (kv kvStore) Iterator(start, end []byte) sdk.Iterator { panic("not implemented") } diff --git a/store/cachekvstore.go b/store/cachekvstore.go index 109bbfc75..aeed6ee5f 100644 --- a/store/cachekvstore.go +++ b/store/cachekvstore.go @@ -80,6 +80,11 @@ func (ci *cacheKVStore) Delete(key []byte) { ci.setCacheValue(key, nil, true, true) } +// Implements KVStore +func (ci *cacheKVStore) Prefix(prefix []byte) KVStore { + return prefixStore{ci, prefix} +} + // Implements CacheKVStore. func (ci *cacheKVStore) Write() { ci.mtx.Lock() diff --git a/store/dbstoreadapter.go b/store/dbstoreadapter.go index 58c9e1b29..59369af56 100644 --- a/store/dbstoreadapter.go +++ b/store/dbstoreadapter.go @@ -19,5 +19,10 @@ func (dsa dbStoreAdapter) CacheWrap() CacheWrap { return NewCacheKVStore(dsa) } +// Implements KVStore +func (dsa dbStoreAdapter) Prefix(prefix []byte) KVStore { + return prefixStore{dsa, prefix} +} + // dbm.DB implements KVStore so we can CacheKVStore it. var _ KVStore = dbStoreAdapter{dbm.DB(nil)} diff --git a/store/gaskvstore.go b/store/gaskvstore.go index db65921da..6dc699dfb 100644 --- a/store/gaskvstore.go +++ b/store/gaskvstore.go @@ -65,6 +65,11 @@ func (gi *gasKVStore) Delete(key []byte) { gi.parent.Delete(key) } +// Implements KVStore +func (gi *gasKVStore) Prefix(prefix []byte) KVStore { + return prefixStore{gi, prefix} +} + // Implements KVStore. func (gi *gasKVStore) Iterator(start, end []byte) sdk.Iterator { return gi.iterator(start, end, true) diff --git a/store/iavlstore.go b/store/iavlstore.go index 2b7914c4e..3b4e77ee0 100644 --- a/store/iavlstore.go +++ b/store/iavlstore.go @@ -115,6 +115,11 @@ func (st *iavlStore) Delete(key []byte) { st.tree.Remove(key) } +// Implements KVStore +func (st *iavlStore) Prefix(prefix []byte) KVStore { + return prefixStore{st, prefix} +} + // Implements KVStore. func (st *iavlStore) Iterator(start, end []byte) Iterator { return newIAVLIterator(st.tree.Tree(), start, end, true) diff --git a/store/prefixstore.go b/store/prefixstore.go new file mode 100644 index 000000000..8c0624f35 --- /dev/null +++ b/store/prefixstore.go @@ -0,0 +1,102 @@ +package store + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" +) + +type prefixStore struct { + store KVStore + prefix []byte +} + +// Implements Store +func (s prefixStore) GetStoreType() StoreType { + return sdk.StoreTypePrefix +} + +// Implements CacheWrap +func (s prefixStore) CacheWrap() CacheWrap { + return NewCacheKVStore(s) +} + +// Implements KVStore +func (s prefixStore) Get(key []byte) []byte { + return s.store.Get(append(s.prefix, key...)) +} + +// Implements KVStore +func (s prefixStore) Has(key []byte) bool { + return s.store.Has(append(s.prefix, key...)) +} + +// Implements KVStore +func (s prefixStore) Set(key, value []byte) { + s.store.Set(append(s.prefix, key...), value) +} + +// Implements KVStore +func (s prefixStore) Delete(key []byte) { + s.store.Delete(append(s.prefix, key...)) +} + +// Implements KVStore +func (s prefixStore) Prefix(prefix []byte) KVStore { + return prefixStore{s, prefix} +} + +// Implements KVStore +func (s prefixStore) Iterator(start, end []byte) Iterator { + return prefixIterator{ + prefix: s.prefix, + iter: s.store.Iterator(start, end), + } +} + +// Implements KVStore +func (s prefixStore) ReverseIterator(start, end []byte) Iterator { + return prefixIterator{ + prefix: s.prefix, + iter: s.store.ReverseIterator(start, end), + } +} + +type prefixIterator struct { + prefix []byte + + iter Iterator +} + +// Implements Iterator +func (iter prefixIterator) Domain() (start []byte, end []byte) { + start, end = iter.iter.Domain() + start = start[len(iter.prefix):] + end = end[len(iter.prefix):] + return +} + +// Implements Iterator +func (iter prefixIterator) Valid() bool { + return iter.iter.Valid() +} + +// Implements Iterator +func (iter prefixIterator) Next() { + iter.iter.Next() +} + +// Implements Iterator +func (iter prefixIterator) Key() (key []byte) { + key = iter.iter.Key() + key = key[len(iter.prefix):] + return +} + +// Implements Iterator +func (iter prefixIterator) Value() []byte { + return iter.iter.Value() +} + +// Implements Iterator +func (iter prefixIterator) Close() { + iter.iter.Close() +} diff --git a/store/prefixstore_test.go b/store/prefixstore_test.go new file mode 100644 index 000000000..d41d12055 --- /dev/null +++ b/store/prefixstore_test.go @@ -0,0 +1,109 @@ +package store + +import ( + "math/rand" + "testing" + + "github.com/stretchr/testify/assert" + + "github.com/tendermint/iavl" + dbm "github.com/tendermint/tmlibs/db" + + sdk "github.com/cosmos/cosmos-sdk/types" +) + +type kvpair struct { + key []byte + value []byte +} + +func setRandomKVPairs(t *testing.T, store KVStore) []kvpair { + kvps := make([]kvpair, 20) + + for i := 0; i < 20; i++ { + kvps[i].key = make([]byte, 32) + rand.Read(kvps[i].key) + kvps[i].value = make([]byte, 32) + rand.Read(kvps[i].value) + + store.Set(kvps[i].key, kvps[i].value) + } + + return kvps +} + +func testPrefixStore(t *testing.T, baseStore KVStore, prefix []byte) { + prefixStore := baseStore.Prefix(prefix) + + kvps := setRandomKVPairs(t, prefixStore) + + buf := make([]byte, 32) + for i := 0; i < 20; i++ { + rand.Read(buf) + assert.False(t, prefixStore.Has(buf)) + assert.Nil(t, prefixStore.Get(buf)) + assert.False(t, baseStore.Has(append(prefix, buf...))) + assert.Nil(t, baseStore.Get(append(prefix, buf...))) + } + + for i := 0; i < 20; i++ { + key := kvps[i].key + assert.True(t, prefixStore.Has(key)) + assert.Equal(t, kvps[i].value, prefixStore.Get(key)) + assert.True(t, baseStore.Has(append(prefix, key...))) + assert.Equal(t, kvps[i].value, baseStore.Get(append(prefix, key...))) + + prefixStore.Delete(key) + assert.False(t, prefixStore.Has(key)) + assert.Nil(t, prefixStore.Get(key)) + assert.False(t, baseStore.Has(append(prefix, buf...))) + assert.Nil(t, baseStore.Get(append(prefix, buf...))) + } + +} + +func TestIAVLStorePrefix(t *testing.T) { + db := dbm.NewMemDB() + tree := iavl.NewVersionedTree(db, cacheSize) + iavlStore := newIAVLStore(tree, numHistory) + + testPrefixStore(t, iavlStore, []byte("test")) +} + +func TestCacheKVStorePrefix(t *testing.T) { + cacheStore := newCacheKVStore() + + testPrefixStore(t, cacheStore, []byte("test")) +} + +func TestGasKVStorePrefix(t *testing.T) { + meter := sdk.NewGasMeter(100000000) + mem := dbStoreAdapter{dbm.NewMemDB()} + gasStore := NewGasKVStore(meter, mem) + + testPrefixStore(t, gasStore, []byte("test")) +} + +func TestPrefixStoreIterate(t *testing.T) { + db := dbm.NewMemDB() + baseStore := dbStoreAdapter{db} + prefix := []byte("test") + prefixStore := baseStore.Prefix(prefix) + + setRandomKVPairs(t, prefixStore) + + bIter := sdk.KVStorePrefixIterator(baseStore, prefix) + pIter := sdk.KVStorePrefixIterator(prefixStore, nil) + + for bIter.Valid() && pIter.Valid() { + assert.Equal(t, bIter.Key(), append(prefix, pIter.Key()...)) + assert.Equal(t, bIter.Value(), pIter.Value()) + + bIter.Next() + pIter.Next() + } + + assert.Equal(t, bIter.Valid(), pIter.Valid()) + bIter.Close() + pIter.Close() +} diff --git a/types/lib/linear.go b/types/lib/linear.go new file mode 100644 index 000000000..75cb719a5 --- /dev/null +++ b/types/lib/linear.go @@ -0,0 +1,250 @@ +package lib + +import ( + "fmt" + "strconv" + + sdk "github.com/cosmos/cosmos-sdk/types" + wire "github.com/cosmos/cosmos-sdk/wire" +) + +// Linear defines a primitive mapper type +type Linear struct { + cdc *wire.Codec + store sdk.KVStore + keys *LinearKeys +} + +// LinearKeys defines keysions for the key bytes +type LinearKeys struct { + LengthKey []byte + ElemKey []byte + TopKey []byte +} + +// Should never be modified +var cachedDefaultLinearKeys = DefaultLinearKeys() + +// DefaultLinearKeys returns the default setting of LinearOption +func DefaultLinearKeys() *LinearKeys { + keys := LinearKeys{ + LengthKey: []byte{0x00}, + ElemKey: []byte{0x01}, + TopKey: []byte{0x02}, + } + return &keys +} + +// NewLinear constructs new Linear +func NewLinear(cdc *wire.Codec, store sdk.KVStore, keys *LinearKeys) Linear { + if keys == nil { + keys = cachedDefaultLinearKeys + } + if keys.LengthKey == nil || keys.ElemKey == nil || keys.TopKey == nil { + panic("Invalid LinearKeys") + } + return Linear{ + cdc: cdc, + store: store, + keys: keys, + } +} + +// List is a Linear interface that provides list-like functions +// It panics when the element type cannot be (un/)marshalled by the codec +type List interface { + + // Len() returns the length of the list + // The length is only increased by Push() and not decreased + // List dosen't check if an index is in bounds + // The user should check Len() before doing any actions + Len() uint64 + + // Get() returns the element by its index + Get(uint64, interface{}) error + + // Set() stores the element to the given position + // Setting element out of range will break length counting + // Use Push() instead of Set() to append a new element + Set(uint64, interface{}) + + // Delete() deletes the element in the given position + // Other elements' indices are preserved after deletion + // Panics when the index is out of range + Delete(uint64) + + // Push() inserts the element to the end of the list + // It will increase the length when it is called + Push(interface{}) + + // Iterate*() is used to iterate over all existing elements in the list + // Return true in the continuation to break + // The second element of the continuation will indicate the position of the element + // Using it with Get() will return the same one with the provided element + + // CONTRACT: No writes may happen within a domain while iterating over it. + Iterate(interface{}, func(uint64) bool) +} + +// NewList constructs new List +func NewList(cdc *wire.Codec, store sdk.KVStore, keys *LinearKeys) List { + return NewLinear(cdc, store, keys) +} + +// Key for the length of the list +func (m Linear) LengthKey() []byte { + return m.keys.LengthKey +} + +// Key for the elements of the list +func (m Linear) ElemKey(index uint64) []byte { + return append(m.keys.ElemKey, []byte(fmt.Sprintf("%020d", index))...) +} + +// Len implements List +func (m Linear) Len() (res uint64) { + bz := m.store.Get(m.LengthKey()) + if bz == nil { + return 0 + } + m.cdc.MustUnmarshalBinary(bz, &res) + return +} + +// Get implements List +func (m Linear) Get(index uint64, ptr interface{}) error { + bz := m.store.Get(m.ElemKey(index)) + return m.cdc.UnmarshalBinary(bz, ptr) +} + +// Set implements List +func (m Linear) Set(index uint64, value interface{}) { + bz := m.cdc.MustMarshalBinary(value) + m.store.Set(m.ElemKey(index), bz) +} + +// Delete implements List +func (m Linear) Delete(index uint64) { + m.store.Delete(m.ElemKey(index)) +} + +// Push implements List +func (m Linear) Push(value interface{}) { + length := m.Len() + m.Set(length, value) + m.store.Set(m.LengthKey(), m.cdc.MustMarshalBinary(length+1)) +} + +// IterateRead implements List +func (m Linear) Iterate(ptr interface{}, fn func(uint64) bool) { + iter := sdk.KVStorePrefixIterator(m.store, []byte{0x01}) + for ; iter.Valid(); iter.Next() { + v := iter.Value() + m.cdc.MustUnmarshalBinary(v, ptr) + k := iter.Key() + s := string(k[len(k)-20:]) + index, err := strconv.ParseUint(s, 10, 64) + if err != nil { + panic(err) + } + if fn(index) { + break + } + } + + iter.Close() +} + +// Queue is a Linear interface that provides queue-like functions +// It panics when the element type cannot be (un/)marshalled by the codec +type Queue interface { + // Push() inserts the elements to the rear of the queue + Push(interface{}) + + // Popping/Peeking on an empty queue will cause panic + // The user should check IsEmpty() before doing any actions + + // Peek() returns the element at the front of the queue without removing it + Peek(interface{}) error + + // Pop() returns the element at the front of the queue and removes it + Pop() + + // IsEmpty() checks if the queue is empty + IsEmpty() bool + + // Flush() removes elements it processed + // Return true in the continuation to break + // The interface{} is unmarshalled before the continuation is called + // Starts from the top(head) of the queue + // CONTRACT: Pop() or Push() should not be performed while flushing + Flush(interface{}, func() bool) +} + +// NewQueue constructs new Queue +func NewQueue(cdc *wire.Codec, store sdk.KVStore, keys *LinearKeys) Queue { + return NewLinear(cdc, store, keys) +} + +// Key for the top element position in the queue +func (m Linear) TopKey() []byte { + return m.keys.TopKey +} + +func (m Linear) getTop() (res uint64) { + bz := m.store.Get(m.TopKey()) + if bz == nil { + return 0 + } + + m.cdc.MustUnmarshalBinary(bz, &res) + return +} + +func (m Linear) setTop(top uint64) { + bz := m.cdc.MustMarshalBinary(top) + m.store.Set(m.TopKey(), bz) +} + +// Peek implements Queue +func (m Linear) Peek(ptr interface{}) error { + top := m.getTop() + return m.Get(top, ptr) +} + +// Pop implements Queue +func (m Linear) Pop() { + top := m.getTop() + m.Delete(top) + m.setTop(top + 1) +} + +// IsEmpty implements Queue +func (m Linear) IsEmpty() bool { + top := m.getTop() + length := m.Len() + return top >= length +} + +// Flush implements Queue +func (m Linear) Flush(ptr interface{}, fn func() bool) { + top := m.getTop() + length := m.Len() + + var i uint64 + for i = top; i < length; i++ { + m.Get(i, ptr) + m.Delete(i) + if fn() { + break + } + } + m.setTop(i) +} + +func subspace(prefix []byte) (start, end []byte) { + end = make([]byte, len(prefix)) + copy(end, prefix) + end[len(end)-1]++ + return prefix, end +} diff --git a/types/lib/linear_test.go b/types/lib/linear_test.go new file mode 100644 index 000000000..db65f374f --- /dev/null +++ b/types/lib/linear_test.go @@ -0,0 +1,157 @@ +package lib + +import ( + "fmt" + "testing" + + "github.com/stretchr/testify/assert" + + dbm "github.com/tendermint/tmlibs/db" + "github.com/tendermint/tmlibs/log" + + abci "github.com/tendermint/abci/types" + + "github.com/cosmos/cosmos-sdk/store" + sdk "github.com/cosmos/cosmos-sdk/types" + wire "github.com/cosmos/cosmos-sdk/wire" +) + +type S struct { + I uint64 + B bool +} + +func defaultComponents(key sdk.StoreKey) (sdk.Context, *wire.Codec) { + db := dbm.NewMemDB() + cms := store.NewCommitMultiStore(db) + cms.MountStoreWithDB(key, sdk.StoreTypeIAVL, db) + cms.LoadLatestVersion() + ctx := sdk.NewContext(cms, abci.Header{}, false, nil, log.NewNopLogger()) + cdc := wire.NewCodec() + return ctx, cdc +} + +func TestList(t *testing.T) { + key := sdk.NewKVStoreKey("test") + ctx, cdc := defaultComponents(key) + store := ctx.KVStore(key) + lm := NewList(cdc, store, nil) + + val := S{1, true} + var res S + + lm.Push(val) + assert.Equal(t, uint64(1), lm.Len()) + lm.Get(uint64(0), &res) + assert.Equal(t, val, res) + + val = S{2, false} + lm.Set(uint64(0), val) + lm.Get(uint64(0), &res) + assert.Equal(t, val, res) + + val = S{100, false} + lm.Push(val) + assert.Equal(t, uint64(2), lm.Len()) + lm.Get(uint64(1), &res) + assert.Equal(t, val, res) + + lm.Delete(uint64(1)) + assert.Equal(t, uint64(2), lm.Len()) + + lm.Iterate(&res, func(index uint64) (brk bool) { + var temp S + lm.Get(index, &temp) + assert.Equal(t, temp, res) + + assert.True(t, index != 1) + return + }) + + lm.Iterate(&res, func(index uint64) (brk bool) { + lm.Set(index, S{res.I + 1, !res.B}) + return + }) + + lm.Get(uint64(0), &res) + assert.Equal(t, S{3, true}, res) +} + +func TestQueue(t *testing.T) { + key := sdk.NewKVStoreKey("test") + ctx, cdc := defaultComponents(key) + store := ctx.KVStore(key) + + qm := NewQueue(cdc, store, nil) + + val := S{1, true} + var res S + + qm.Push(val) + qm.Peek(&res) + assert.Equal(t, val, res) + + qm.Pop() + empty := qm.IsEmpty() + + assert.True(t, empty) + assert.NotNil(t, qm.Peek(&res)) + + qm.Push(S{1, true}) + qm.Push(S{2, true}) + qm.Push(S{3, true}) + qm.Flush(&res, func() (brk bool) { + if res.I == 3 { + brk = true + } + return + }) + + assert.False(t, qm.IsEmpty()) + + qm.Pop() + assert.True(t, qm.IsEmpty()) +} + +func TestOptions(t *testing.T) { + key := sdk.NewKVStoreKey("test") + ctx, cdc := defaultComponents(key) + store := ctx.KVStore(key) + + keys := &LinearKeys{ + LengthKey: []byte{0xDE, 0xAD}, + ElemKey: []byte{0xBE, 0xEF}, + TopKey: []byte{0x12, 0x34}, + } + linear := NewLinear(cdc, store, keys) + + for i := 0; i < 10; i++ { + linear.Push(i) + } + + var len uint64 + var top uint64 + var expected int + var actual int + + // Checking keys.LengthKey + err := cdc.UnmarshalBinary(store.Get(keys.LengthKey), &len) + assert.Nil(t, err) + assert.Equal(t, len, linear.Len()) + + // Checking keys.ElemKey + for i := 0; i < 10; i++ { + linear.Get(uint64(i), &expected) + bz := store.Get(append(keys.ElemKey, []byte(fmt.Sprintf("%020d", i))...)) + err = cdc.UnmarshalBinary(bz, &actual) + assert.Nil(t, err) + assert.Equal(t, expected, actual) + } + + linear.Pop() + + err = cdc.UnmarshalBinary(store.Get(keys.TopKey), &top) + assert.Nil(t, err) + assert.Equal(t, top, linear.getTop()) + +} diff --git a/types/lib/mapper.go b/types/lib/mapper.go deleted file mode 100644 index ca9020077..000000000 --- a/types/lib/mapper.go +++ /dev/null @@ -1,286 +0,0 @@ -package lib - -import ( - "fmt" - "strconv" - - sdk "github.com/cosmos/cosmos-sdk/types" - wire "github.com/cosmos/cosmos-sdk/wire" -) - -// Mapper defines a primitive mapper type -type Mapper struct { - key sdk.StoreKey - cdc *wire.Codec - prefix string -} - -// ListMapper is a Mapper interface that provides list-like functions -// It panics when the element type cannot be (un/)marshalled by the codec -type ListMapper interface { - - // Len() returns the length of the list - // The length is only increased by Push() and not decreased - // ListMapper dosen't check if an index is in bounds - // The user should check Len() before doing any actions - Len(sdk.Context) uint64 - - // Get() returns the element by its index - Get(sdk.Context, uint64, interface{}) error - - // Set() stores the element to the given position - // Setting element out of range will break length counting - // Use Push() instead of Set() to append a new element - Set(sdk.Context, uint64, interface{}) - - // Delete() deletes the element in the given position - // Other elements' indices are preserved after deletion - // Panics when the index is out of range - Delete(sdk.Context, uint64) - - // Push() inserts the element to the end of the list - // It will increase the length when it is called - Push(sdk.Context, interface{}) - - // Iterate*() is used to iterate over all existing elements in the list - // Return true in the continuation to break - // The second element of the continuation will indicate the position of the element - // Using it with Get() will return the same one with the provided element - - // CONTRACT: No writes may happen within a domain while iterating over it. - IterateRead(sdk.Context, interface{}, func(sdk.Context, uint64) bool) - - // IterateWrite() is safe to write over the domain - IterateWrite(sdk.Context, interface{}, func(sdk.Context, uint64) bool) - - // Key for the length of the list - LengthKey() []byte - - // Key for getting elements - ElemKey(uint64) []byte -} - -// NewListMapper constructs new ListMapper -func NewListMapper(cdc *wire.Codec, key sdk.StoreKey, prefix string) ListMapper { - return Mapper{ - key: key, - cdc: cdc, - prefix: prefix, - } -} - -// Len implements ListMapper -func (m Mapper) Len(ctx sdk.Context) uint64 { - store := ctx.KVStore(m.key) - bz := store.Get(m.LengthKey()) - if bz == nil { - zero, err := m.cdc.MarshalBinary(0) - if err != nil { - panic(err) - } - store.Set(m.LengthKey(), zero) - return 0 - } - var res uint64 - if err := m.cdc.UnmarshalBinary(bz, &res); err != nil { - panic(err) - } - return res -} - -// Get implements ListMapper -func (m Mapper) Get(ctx sdk.Context, index uint64, ptr interface{}) error { - store := ctx.KVStore(m.key) - bz := store.Get(m.ElemKey(index)) - return m.cdc.UnmarshalBinary(bz, ptr) -} - -// Set implements ListMapper -func (m Mapper) Set(ctx sdk.Context, index uint64, value interface{}) { - store := ctx.KVStore(m.key) - bz, err := m.cdc.MarshalBinary(value) - if err != nil { - panic(err) - } - store.Set(m.ElemKey(index), bz) -} - -// Delete implements ListMapper -func (m Mapper) Delete(ctx sdk.Context, index uint64) { - store := ctx.KVStore(m.key) - store.Delete(m.ElemKey(index)) -} - -// Push implements ListMapper -func (m Mapper) Push(ctx sdk.Context, value interface{}) { - length := m.Len(ctx) - m.Set(ctx, length, value) - - store := ctx.KVStore(m.key) - store.Set(m.LengthKey(), marshalUint64(m.cdc, length+1)) -} - -// IterateRead implements ListMapper -func (m Mapper) IterateRead(ctx sdk.Context, ptr interface{}, fn func(sdk.Context, uint64) bool) { - store := ctx.KVStore(m.key) - start, end := subspace([]byte(fmt.Sprintf("%s/elem/", m.prefix))) - iter := store.Iterator(start, end) - for ; iter.Valid(); iter.Next() { - v := iter.Value() - if err := m.cdc.UnmarshalBinary(v, ptr); err != nil { - panic(err) - } - s := string(iter.Key()[len(m.prefix)+6:]) - index, err := strconv.ParseUint(s, 10, 64) - if err != nil { - panic(err) - } - if fn(ctx, index) { - break - } - } - - iter.Close() -} - -// IterateWrite implements ListMapper -func (m Mapper) IterateWrite(ctx sdk.Context, ptr interface{}, fn func(sdk.Context, uint64) bool) { - length := m.Len(ctx) - - for i := uint64(0); i < length; i++ { - if err := m.Get(ctx, i, ptr); err != nil { - continue - } - if fn(ctx, i) { - break - } - } -} - -// LengthKey implements ListMapper -func (m Mapper) LengthKey() []byte { - return []byte(fmt.Sprintf("%s/length", m.prefix)) -} - -// ElemKey implements ListMapper -func (m Mapper) ElemKey(i uint64) []byte { - return []byte(fmt.Sprintf("%s/elem/%020d", m.prefix, i)) -} - -// QueueMapper is a Mapper interface that provides queue-like functions -// It panics when the element type cannot be (un/)marshalled by the codec -type QueueMapper interface { - // Push() inserts the elements to the rear of the queue - Push(sdk.Context, interface{}) - - // Popping/Peeking on an empty queue will cause panic - // The user should check IsEmpty() before doing any actions - - // Peek() returns the element at the front of the queue without removing it - Peek(sdk.Context, interface{}) error - - // Pop() returns the element at the front of the queue and removes it - Pop(sdk.Context) - - // IsEmpty() checks if the queue is empty - IsEmpty(sdk.Context) bool - - // Flush() removes elements it processed - // Return true in the continuation to break - // The interface{} is unmarshalled before the continuation is called - // Starts from the top(head) of the queue - // CONTRACT: Pop() or Push() should not be performed while flushing - Flush(sdk.Context, interface{}, func(sdk.Context) bool) - - // Key for the index of top element - TopKey() []byte -} - -// NewQueueMapper constructs new QueueMapper -func NewQueueMapper(cdc *wire.Codec, key sdk.StoreKey, prefix string) QueueMapper { - return Mapper{ - key: key, - cdc: cdc, - prefix: prefix, - } -} - -func (m Mapper) getTop(store sdk.KVStore) (res uint64) { - bz := store.Get(m.TopKey()) - if bz == nil { - store.Set(m.TopKey(), marshalUint64(m.cdc, 0)) - return 0 - } - - if err := m.cdc.UnmarshalBinary(bz, &res); err != nil { - panic(err) - } - - return -} - -func (m Mapper) setTop(store sdk.KVStore, top uint64) { - bz := marshalUint64(m.cdc, top) - store.Set(m.TopKey(), bz) -} - -// Peek implements QueueMapper -func (m Mapper) Peek(ctx sdk.Context, ptr interface{}) error { - store := ctx.KVStore(m.key) - top := m.getTop(store) - return m.Get(ctx, top, ptr) -} - -// Pop implements QueueMapper -func (m Mapper) Pop(ctx sdk.Context) { - store := ctx.KVStore(m.key) - top := m.getTop(store) - m.Delete(ctx, top) - m.setTop(store, top+1) -} - -// IsEmpty implements QueueMapper -func (m Mapper) IsEmpty(ctx sdk.Context) bool { - store := ctx.KVStore(m.key) - top := m.getTop(store) - length := m.Len(ctx) - return top >= length -} - -// Flush implements QueueMapper -func (m Mapper) Flush(ctx sdk.Context, ptr interface{}, fn func(sdk.Context) bool) { - store := ctx.KVStore(m.key) - top := m.getTop(store) - length := m.Len(ctx) - - var i uint64 - for i = top; i < length; i++ { - m.Get(ctx, i, ptr) - m.Delete(ctx, i) - if fn(ctx) { - break - } - } - - m.setTop(store, i) -} - -// TopKey implements QueueMapper -func (m Mapper) TopKey() []byte { - return []byte(fmt.Sprintf("%s/top", m.prefix)) -} - -func marshalUint64(cdc *wire.Codec, i uint64) []byte { - bz, err := cdc.MarshalBinary(i) - if err != nil { - panic(err) - } - return bz -} - -func subspace(prefix []byte) (start, end []byte) { - end = make([]byte, len(prefix)) - copy(end, prefix) - end[len(end)-1]++ - return prefix, end -} diff --git a/types/lib/mapper_test.go b/types/lib/mapper_test.go deleted file mode 100644 index e1759b06a..000000000 --- a/types/lib/mapper_test.go +++ /dev/null @@ -1,110 +0,0 @@ -package lib - -import ( - "testing" - - "github.com/stretchr/testify/assert" - - dbm "github.com/tendermint/tmlibs/db" - "github.com/tendermint/tmlibs/log" - - abci "github.com/tendermint/abci/types" - - "github.com/cosmos/cosmos-sdk/store" - sdk "github.com/cosmos/cosmos-sdk/types" - wire "github.com/cosmos/cosmos-sdk/wire" -) - -type S struct { - I uint64 - B bool -} - -func defaultComponents(key sdk.StoreKey) (sdk.Context, *wire.Codec) { - db := dbm.NewMemDB() - cms := store.NewCommitMultiStore(db) - cms.MountStoreWithDB(key, sdk.StoreTypeIAVL, db) - cms.LoadLatestVersion() - ctx := sdk.NewContext(cms, abci.Header{}, false, nil, log.NewNopLogger()) - cdc := wire.NewCodec() - return ctx, cdc -} - -func TestListMapper(t *testing.T) { - key := sdk.NewKVStoreKey("list") - ctx, cdc := defaultComponents(key) - lm := NewListMapper(cdc, key, "data") - - val := S{1, true} - var res S - - lm.Push(ctx, val) - assert.Equal(t, uint64(1), lm.Len(ctx)) - lm.Get(ctx, uint64(0), &res) - assert.Equal(t, val, res) - - val = S{2, false} - lm.Set(ctx, uint64(0), val) - lm.Get(ctx, uint64(0), &res) - assert.Equal(t, val, res) - - val = S{100, false} - lm.Push(ctx, val) - assert.Equal(t, uint64(2), lm.Len(ctx)) - lm.Get(ctx, uint64(1), &res) - assert.Equal(t, val, res) - - lm.Delete(ctx, uint64(1)) - assert.Equal(t, uint64(2), lm.Len(ctx)) - - lm.IterateRead(ctx, &res, func(ctx sdk.Context, index uint64) (brk bool) { - var temp S - lm.Get(ctx, index, &temp) - assert.Equal(t, temp, res) - - assert.True(t, index != 1) - return - }) - - lm.IterateWrite(ctx, &res, func(ctx sdk.Context, index uint64) (brk bool) { - lm.Set(ctx, index, S{res.I + 1, !res.B}) - return - }) - - lm.Get(ctx, uint64(0), &res) - assert.Equal(t, S{3, true}, res) -} - -func TestQueueMapper(t *testing.T) { - key := sdk.NewKVStoreKey("queue") - ctx, cdc := defaultComponents(key) - qm := NewQueueMapper(cdc, key, "data") - - val := S{1, true} - var res S - - qm.Push(ctx, val) - qm.Peek(ctx, &res) - assert.Equal(t, val, res) - - qm.Pop(ctx) - empty := qm.IsEmpty(ctx) - - assert.True(t, empty) - assert.NotNil(t, qm.Peek(ctx, &res)) - - qm.Push(ctx, S{1, true}) - qm.Push(ctx, S{2, true}) - qm.Push(ctx, S{3, true}) - qm.Flush(ctx, &res, func(ctx sdk.Context) (brk bool) { - if res.I == 3 { - brk = true - } - return - }) - - assert.False(t, qm.IsEmpty(ctx)) - - qm.Pop(ctx) - assert.True(t, qm.IsEmpty(ctx)) -} diff --git a/types/store.go b/types/store.go index d0a8df07a..899b57015 100644 --- a/types/store.go +++ b/types/store.go @@ -103,6 +103,9 @@ type KVStore interface { // Delete deletes the key. Panics on nil key. Delete(key []byte) + // Prefix applied keys with the argument + Prefix(prefix []byte) KVStore + // Iterator over a domain of keys in ascending order. End is exclusive. // Start must be less than end, or the Iterator is invalid. // Iterator must be closed by caller. @@ -152,6 +155,11 @@ type CommitKVStore interface { KVStore } +// Wrapper for StoreKeys to get KVStores +type KVStoreGetter interface { + KVStore(Context) KVStore +} + //---------------------------------------- // CacheWrap @@ -204,6 +212,7 @@ const ( StoreTypeMulti StoreType = iota StoreTypeDB StoreTypeIAVL + StoreTypePrefix ) //---------------------------------------- @@ -237,6 +246,11 @@ func (key *KVStoreKey) String() string { return fmt.Sprintf("KVStoreKey{%p, %s}", key, key.name) } +// Implements KVStoreGetter +func (key *KVStoreKey) KVStore(ctx Context) KVStore { + return ctx.KVStore(key) +} + // PrefixEndBytes returns the []byte that would end a // range query for all []byte with a certain prefix // Deals with last byte of prefix being FF without overflowing @@ -263,7 +277,24 @@ func PrefixEndBytes(prefix []byte) []byte { return end } +// Getter struct for prefixed stores +type PrefixStoreGetter struct { + key StoreKey + prefix []byte +} + +func NewPrefixStoreGetter(key StoreKey, prefix []byte) PrefixStoreGetter { + return PrefixStoreGetter{key, prefix} +} + +// Implements sdk.KVStoreGetter +func (getter PrefixStoreGetter) KVStore(ctx Context) KVStore { + return ctx.KVStore(getter.key).Prefix(getter.prefix) +} + //---------------------------------------- // key-value result for iterator queries type KVPair cmn.KVPair + +//----------------------------------------