feat(collections): Implement LookupMap (#18933)
Co-authored-by: cool-developer <51834436+cool-develope@users.noreply.github.com> Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
This commit is contained in:
parent
14fd0ceda3
commit
639735d41a
@ -33,6 +33,7 @@ Ref: https://keepachangelog.com/en/1.0.0/
|
||||
|
||||
### Features
|
||||
|
||||
* [#18933](https://github.com/cosmos/cosmos-sdk/pull/18933) Add LookupMap implementation. It is basic wrapping of the standard Map methods but is not iterable.
|
||||
* [#17656](https://github.com/cosmos/cosmos-sdk/pull/17656) – Introduces `Vec`, a collection type that allows to represent a growable array on top of a KVStore.
|
||||
|
||||
## [v0.4.0](https://github.com/cosmos/cosmos-sdk/releases/tag/collections%2Fv0.4.0)
|
||||
|
||||
104
collections/lookup_map.go
Normal file
104
collections/lookup_map.go
Normal file
@ -0,0 +1,104 @@
|
||||
package collections
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"cosmossdk.io/collections/codec"
|
||||
)
|
||||
|
||||
// LookupMap represents a map that is not iterable.
|
||||
type LookupMap[K, V any] Map[K, V]
|
||||
|
||||
// NewLookupMap creates a new LookupMap.
|
||||
func NewLookupMap[K, V any](
|
||||
schemaBuilder *SchemaBuilder,
|
||||
prefix Prefix,
|
||||
name string,
|
||||
keyCodec codec.KeyCodec[K],
|
||||
valueCodec codec.ValueCodec[V],
|
||||
) LookupMap[K, V] {
|
||||
m := LookupMap[K, V](NewMap[K, V](schemaBuilder, prefix, name, keyCodec, valueCodec))
|
||||
return m
|
||||
}
|
||||
|
||||
// GetName returns the name of the collection.
|
||||
func (m LookupMap[K, V]) GetName() string {
|
||||
return m.name
|
||||
}
|
||||
|
||||
// GetPrefix returns the prefix of the collection.
|
||||
func (m LookupMap[K, V]) GetPrefix() []byte {
|
||||
return m.prefix
|
||||
}
|
||||
|
||||
// Set maps the provided value to the provided key in the store.
|
||||
// Errors with ErrEncoding if key or value encoding fails.
|
||||
func (m LookupMap[K, V]) Set(ctx context.Context, key K, value V) error {
|
||||
bytesKey, err := EncodeKeyWithPrefix(m.prefix, m.kc, key)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
valueBytes, err := m.vc.Encode(value)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%w: value encode: %w", ErrEncoding, err)
|
||||
}
|
||||
|
||||
kvStore := m.sa(ctx)
|
||||
return kvStore.Set(bytesKey, valueBytes)
|
||||
}
|
||||
|
||||
// Get returns the value associated with the provided key,
|
||||
// errors with ErrNotFound if the key does not exist, or
|
||||
// with ErrEncoding if the key or value decoding fails.
|
||||
func (m LookupMap[K, V]) Get(ctx context.Context, key K) (v V, err error) {
|
||||
bytesKey, err := EncodeKeyWithPrefix(m.prefix, m.kc, key)
|
||||
if err != nil {
|
||||
return v, err
|
||||
}
|
||||
|
||||
kvStore := m.sa(ctx)
|
||||
valueBytes, err := kvStore.Get(bytesKey)
|
||||
if err != nil {
|
||||
return v, err
|
||||
}
|
||||
if valueBytes == nil {
|
||||
return v, fmt.Errorf("%w: key '%s' of type %s", ErrNotFound, m.kc.Stringify(key), m.vc.ValueType())
|
||||
}
|
||||
|
||||
v, err = m.vc.Decode(valueBytes)
|
||||
if err != nil {
|
||||
return v, fmt.Errorf("%w: value decode: %w", ErrEncoding, err)
|
||||
}
|
||||
return v, nil
|
||||
}
|
||||
|
||||
// Has reports whether the key is present in storage or not.
|
||||
// Errors with ErrEncoding if key encoding fails.
|
||||
func (m LookupMap[K, V]) Has(ctx context.Context, key K) (bool, error) {
|
||||
bytesKey, err := EncodeKeyWithPrefix(m.prefix, m.kc, key)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
kvStore := m.sa(ctx)
|
||||
return kvStore.Has(bytesKey)
|
||||
}
|
||||
|
||||
// Remove removes the key from the storage.
|
||||
// Errors with ErrEncoding if key encoding fails.
|
||||
// If the key does not exist then this is a no-op.
|
||||
func (m LookupMap[K, V]) Remove(ctx context.Context, key K) error {
|
||||
bytesKey, err := EncodeKeyWithPrefix(m.prefix, m.kc, key)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
kvStore := m.sa(ctx)
|
||||
return kvStore.Delete(bytesKey)
|
||||
}
|
||||
|
||||
// KeyCodec returns the Map's KeyCodec.
|
||||
func (m LookupMap[K, V]) KeyCodec() codec.KeyCodec[K] { return m.kc }
|
||||
|
||||
// ValueCodec returns the Map's ValueCodec.
|
||||
func (m LookupMap[K, V]) ValueCodec() codec.ValueCodec[V] { return m.vc }
|
||||
40
collections/lookup_map_test.go
Normal file
40
collections/lookup_map_test.go
Normal file
@ -0,0 +1,40 @@
|
||||
package collections_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"cosmossdk.io/collections"
|
||||
"cosmossdk.io/collections/colltest"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestLookupMap(t *testing.T) {
|
||||
sk, ctx := colltest.MockStore()
|
||||
schema := collections.NewSchemaBuilder(sk)
|
||||
|
||||
lm := collections.NewLookupMap(schema, collections.NewPrefix("hi"), "lm", collections.Uint64Key, collections.Uint64Value)
|
||||
_, err := schema.Build()
|
||||
require.NoError(t, err)
|
||||
|
||||
// test not has
|
||||
has, err := lm.Has(ctx, 1)
|
||||
require.NoError(t, err)
|
||||
require.False(t, has)
|
||||
// test get error
|
||||
_, err = lm.Get(ctx, 1)
|
||||
require.ErrorIs(t, err, collections.ErrNotFound)
|
||||
|
||||
// test set/get
|
||||
err = lm.Set(ctx, 1, 100)
|
||||
require.NoError(t, err)
|
||||
v, err := lm.Get(ctx, 1)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, uint64(100), v)
|
||||
|
||||
// test remove
|
||||
err = lm.Remove(ctx, 1)
|
||||
require.NoError(t, err)
|
||||
has, err = lm.Has(ctx, 1)
|
||||
require.NoError(t, err)
|
||||
require.False(t, has)
|
||||
}
|
||||
@ -61,7 +61,7 @@ func (m Map[K, V]) Set(ctx context.Context, key K, value V) error {
|
||||
|
||||
valueBytes, err := m.vc.Encode(value)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%w: value encode: %s", ErrEncoding, err) // TODO: use multi err wrapping in go1.20: https://github.com/golang/go/issues/53435
|
||||
return fmt.Errorf("%w: value encode: %w", ErrEncoding, err)
|
||||
}
|
||||
|
||||
kvStore := m.sa(ctx)
|
||||
@ -88,7 +88,7 @@ func (m Map[K, V]) Get(ctx context.Context, key K) (v V, err error) {
|
||||
|
||||
v, err = m.vc.Decode(valueBytes)
|
||||
if err != nil {
|
||||
return v, fmt.Errorf("%w: value decode: %s", ErrEncoding, err) // TODO: use multi err wrapping in go1.20: https://github.com/golang/go/issues/53435
|
||||
return v, fmt.Errorf("%w: value decode: %w", ErrEncoding, err)
|
||||
}
|
||||
return v, nil
|
||||
}
|
||||
@ -262,7 +262,7 @@ func EncodeKeyWithPrefix[K any](prefix []byte, kc codec.KeyCodec[K], key K) ([]b
|
||||
// put key
|
||||
_, err := kc.Encode(keyBytes[prefixLen:], key)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%w: key encode: %s", ErrEncoding, err) // TODO: use multi err wrapping in go1.20: https://github.com/golang/go/issues/53435
|
||||
return nil, fmt.Errorf("%w: key encode: %w", ErrEncoding, err)
|
||||
}
|
||||
return keyBytes, nil
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user