From 37ccc098dbd48314482f9de022ebc75064d4b45f Mon Sep 17 00:00:00 2001 From: Aaron Craelius Date: Tue, 4 Jun 2024 12:04:01 -0400 Subject: [PATCH 1/9] feat(collections): add optional key and naming methods --- collections/codec/alternative_value_test.go | 2 +- collections/codec/bool.go | 15 ++++++- collections/codec/bytes.go | 15 ++++++- collections/codec/codec.go | 16 +++++++- collections/codec/codec_test.go | 2 +- collections/codec/int.go | 30 ++++++++++++-- collections/codec/naming.go | 35 ++++++++++++++++ collections/codec/string.go | 15 ++++++- collections/codec/uint.go | 45 ++++++++++++++++++--- collections/collections.go | 16 ++++---- collections/pair.go | 21 +++++++++- collections/triple.go | 22 ++++++++-- 12 files changed, 201 insertions(+), 33 deletions(-) create mode 100644 collections/codec/naming.go diff --git a/collections/codec/alternative_value_test.go b/collections/codec/alternative_value_test.go index 358395427b90..871d95d653ce 100644 --- a/collections/codec/alternative_value_test.go +++ b/collections/codec/alternative_value_test.go @@ -17,7 +17,7 @@ type altValue struct { func TestAltValueCodec(t *testing.T) { // we assume we want to migrate the value from json(altValue) to just be // the raw value uint64. - canonical := codec.KeyToValueCodec(codec.NewUint64Key[uint64]()) + canonical := codec.KeyToValueCodec(codec.KeyCodec[uint64](codec.NewUint64Key[uint64]())) alternative := func(v []byte) (uint64, error) { var alt altValue err := json.Unmarshal(v, &alt) diff --git a/collections/codec/bool.go b/collections/codec/bool.go index 827af36c0715..f5a4616ca00b 100644 --- a/collections/codec/bool.go +++ b/collections/codec/bool.go @@ -6,9 +6,11 @@ import ( "strconv" ) -func NewBoolKey[T ~bool]() KeyCodec[T] { return boolKey[T]{} } +func NewBoolKey[T ~bool]() NameableKeyCodec[T] { return boolKey[T]{} } -type boolKey[T ~bool] struct{} +type boolKey[T ~bool] struct { + name string +} func (b boolKey[T]) Encode(buffer []byte, key T) (int, error) { if key { @@ -64,3 +66,12 @@ func (b boolKey[T]) DecodeNonTerminal(buffer []byte) (int, T, error) { func (b boolKey[T]) SizeNonTerminal(key T) int { return b.Size(key) } + +func (b boolKey[T]) WithName(name string) NamedKeyCodec[T] { + b.name = name + return b +} + +func (b boolKey[T]) Name() string { + return b.name +} diff --git a/collections/codec/bytes.go b/collections/codec/bytes.go index 28334795e365..87b42046bcb0 100644 --- a/collections/codec/bytes.go +++ b/collections/codec/bytes.go @@ -10,9 +10,11 @@ import ( // using the BytesKey KeyCodec. const MaxBytesKeyNonTerminalSize = math.MaxUint8 -func NewBytesKey[T ~[]byte]() KeyCodec[T] { return bytesKey[T]{} } +func NewBytesKey[T ~[]byte]() NameableKeyCodec[T] { return bytesKey[T]{} } -type bytesKey[T ~[]byte] struct{} +type bytesKey[T ~[]byte] struct { + name string +} func (b bytesKey[T]) Encode(buffer []byte, key T) (int, error) { return copy(buffer, key), nil @@ -77,3 +79,12 @@ func (bytesKey[T]) DecodeNonTerminal(buffer []byte) (int, T, error) { func (bytesKey[T]) SizeNonTerminal(key T) int { return len(key) + 1 } + +func (b bytesKey[T]) WithName(name string) NamedKeyCodec[T] { + b.name = name + return b +} + +func (b bytesKey[T]) Name() string { + return b.name +} diff --git a/collections/codec/codec.go b/collections/codec/codec.go index 2988c9f52425..72cc00fc5db7 100644 --- a/collections/codec/codec.go +++ b/collections/codec/codec.go @@ -125,11 +125,14 @@ type UntypedValueCodec struct { } // KeyToValueCodec converts a KeyCodec into a ValueCodec. -func KeyToValueCodec[K any](keyCodec KeyCodec[K]) ValueCodec[K] { return keyToValueCodec[K]{keyCodec} } +func KeyToValueCodec[K any](keyCodec KeyCodec[K]) NameableValueCodec[K] { + return keyToValueCodec[K]{kc: keyCodec} +} // keyToValueCodec is a ValueCodec that wraps a KeyCodec to make it behave like a ValueCodec. type keyToValueCodec[K any] struct { - kc KeyCodec[K] + name string + kc KeyCodec[K] } func (k keyToValueCodec[K]) EncodeJSON(value K) ([]byte, error) { @@ -167,3 +170,12 @@ func (k keyToValueCodec[K]) Stringify(value K) string { func (k keyToValueCodec[K]) ValueType() string { return k.kc.KeyType() } + +func (k keyToValueCodec[K]) WithName(name string) NamedValueCodec[K] { + k.name = name + return k +} + +func (k keyToValueCodec[K]) Name() string { + return k.name +} diff --git a/collections/codec/codec_test.go b/collections/codec/codec_test.go index 16fd10553f41..65eb0704ee2d 100644 --- a/collections/codec/codec_test.go +++ b/collections/codec/codec_test.go @@ -7,7 +7,7 @@ import ( ) func TestUntypedValueCodec(t *testing.T) { - vc := NewUntypedValueCodec(KeyToValueCodec(NewStringKeyCodec[string]())) + vc := NewUntypedValueCodec(ValueCodec[string](KeyToValueCodec(KeyCodec[string](NewStringKeyCodec[string]())))) t.Run("encode/decode", func(t *testing.T) { _, err := vc.Encode(0) diff --git a/collections/codec/int.go b/collections/codec/int.go index 1300efde4df0..5ee3e6fd6d6e 100644 --- a/collections/codec/int.go +++ b/collections/codec/int.go @@ -7,9 +7,11 @@ import ( "strconv" ) -func NewInt64Key[T ~int64]() KeyCodec[T] { return int64Key[T]{} } +func NewInt64Key[T ~int64]() NameableKeyCodec[T] { return int64Key[T]{} } -type int64Key[T ~int64] struct{} +type int64Key[T ~int64] struct { + name string +} func (i int64Key[T]) Encode(buffer []byte, key T) (int, error) { binary.BigEndian.PutUint64(buffer, (uint64)(key)) @@ -64,11 +66,22 @@ func (i int64Key[T]) SizeNonTerminal(_ T) int { return 8 } -func NewInt32Key[T ~int32]() KeyCodec[T] { +func (i int64Key[T]) WithName(name string) NamedKeyCodec[T] { + i.name = name + return i +} + +func (i int64Key[T]) Name() string { + return i.name +} + +func NewInt32Key[T ~int32]() NameableKeyCodec[T] { return int32Key[T]{} } -type int32Key[T ~int32] struct{} +type int32Key[T ~int32] struct { + name string +} func (i int32Key[T]) Encode(buffer []byte, key T) (int, error) { binary.BigEndian.PutUint32(buffer, (uint32)(key)) @@ -121,3 +134,12 @@ func (i int32Key[T]) DecodeNonTerminal(buffer []byte) (int, T, error) { func (i int32Key[T]) SizeNonTerminal(_ T) int { return 4 } + +func (i int32Key[T]) WithName(name string) NamedKeyCodec[T] { + i.name = name + return i +} + +func (i int32Key[T]) Name() string { + return i.name +} diff --git a/collections/codec/naming.go b/collections/codec/naming.go new file mode 100644 index 000000000000..b00a8eeaf17d --- /dev/null +++ b/collections/codec/naming.go @@ -0,0 +1,35 @@ +package codec + +// NameableKeyCodec is a KeyCodec that can be named. +type NameableKeyCodec[T any] interface { + KeyCodec[T] + + // WithName returns the KeyCodec with the provided name. + WithName(name string) NamedKeyCodec[T] +} + +// NamedKeyCodec is a KeyCodec that has a name. +type NamedKeyCodec[T any] interface { + KeyCodec[T] + + // Name returns the name of key in the schema if one is defined or the empty string. + // Multipart keys should separate names with commas, i.e. "name1,name2". + Name() string +} + +// NameableValueCodec is a ValueCodec that can be named. +type NameableValueCodec[T any] interface { + ValueCodec[T] + + // WithName returns the ValueCodec with the provided name. + WithName(name string) NamedValueCodec[T] +} + +// NamedValueCodec is a ValueCodec that has a name. +type NamedValueCodec[T any] interface { + ValueCodec[T] + + // Name returns the name of key in the schema if one is defined or the empty string. + // Multipart keys should separate names with commas, i.e. "name1,name2". + Name() string +} diff --git a/collections/codec/string.go b/collections/codec/string.go index 3189b8bc9cbf..3c9d53b1c42b 100644 --- a/collections/codec/string.go +++ b/collections/codec/string.go @@ -6,14 +6,16 @@ import ( "fmt" ) -func NewStringKeyCodec[T ~string]() KeyCodec[T] { return stringKey[T]{} } +func NewStringKeyCodec[T ~string]() NameableKeyCodec[T] { return stringKey[T]{} } const ( // StringDelimiter defines the delimiter of a string key when used in non-terminal encodings. StringDelimiter uint8 = 0x0 ) -type stringKey[T ~string] struct{} +type stringKey[T ~string] struct { + name string +} func (stringKey[T]) Encode(buffer []byte, key T) (int, error) { return copy(buffer, key), nil @@ -66,3 +68,12 @@ func (stringKey[T]) Stringify(key T) string { func (stringKey[T]) KeyType() string { return "string" } + +func (s stringKey[T]) WithName(name string) NamedKeyCodec[T] { + s.name = name + return s +} + +func (s stringKey[T]) Name() string { + return s.name +} diff --git a/collections/codec/uint.go b/collections/codec/uint.go index 658235d385ad..8586aeb1b6ff 100644 --- a/collections/codec/uint.go +++ b/collections/codec/uint.go @@ -7,9 +7,11 @@ import ( "strconv" ) -func NewUint64Key[T ~uint64]() KeyCodec[T] { return uint64Key[T]{} } +func NewUint64Key[T ~uint64]() NameableKeyCodec[T] { return uint64Key[T]{} } -type uint64Key[T ~uint64] struct{} +type uint64Key[T ~uint64] struct { + name string +} func (uint64Key[T]) Encode(buffer []byte, key T) (int, error) { binary.BigEndian.PutUint64(buffer, (uint64)(key)) @@ -55,9 +57,20 @@ func (uint64Key[T]) KeyType() string { return "uint64" } -func NewUint32Key[T ~uint32]() KeyCodec[T] { return uint32Key[T]{} } +func (u uint64Key[T]) WithName(name string) NamedKeyCodec[T] { + u.name = name + return u +} + +func (u uint64Key[T]) Name() string { + return u.name +} + +func NewUint32Key[T ~uint32]() NameableKeyCodec[T] { return uint32Key[T]{} } -type uint32Key[T ~uint32] struct{} +type uint32Key[T ~uint32] struct { + name string +} func (uint32Key[T]) Encode(buffer []byte, key T) (int, error) { binary.BigEndian.PutUint32(buffer, (uint32)(key)) @@ -95,9 +108,20 @@ func (u uint32Key[T]) DecodeNonTerminal(buffer []byte) (int, T, error) { return func (uint32Key[T]) SizeNonTerminal(_ T) int { return 4 } -func NewUint16Key[T ~uint16]() KeyCodec[T] { return uint16Key[T]{} } +func (u uint32Key[T]) WithName(name string) NamedKeyCodec[T] { + u.name = name + return u +} -type uint16Key[T ~uint16] struct{} +func (u uint32Key[T]) Name() string { + return u.name +} + +func NewUint16Key[T ~uint16]() NameableKeyCodec[T] { return uint16Key[T]{} } + +type uint16Key[T ~uint16] struct { + name string +} func (uint16Key[T]) Encode(buffer []byte, key T) (int, error) { binary.BigEndian.PutUint16(buffer, (uint16)(key)) @@ -135,6 +159,15 @@ func (u uint16Key[T]) DecodeNonTerminal(buffer []byte) (int, T, error) { return func (u uint16Key[T]) SizeNonTerminal(key T) int { return u.Size(key) } +func (u uint16Key[T]) WithName(name string) NamedKeyCodec[T] { + u.name = name + return u +} + +func (u uint16Key[T]) Name() string { + return u.name +} + func uintEncodeJSON(value uint64) ([]byte, error) { str := `"` + strconv.FormatUint(value, 10) + `"` return []byte(str), nil diff --git a/collections/collections.go b/collections/collections.go index 9de3bbc38226..46a2f87c03c2 100644 --- a/collections/collections.go +++ b/collections/collections.go @@ -57,21 +57,21 @@ var ( var ( // BoolValue implements a ValueCodec for bool. - BoolValue = codec.KeyToValueCodec(BoolKey) + BoolValue = codec.KeyToValueCodec(codec.KeyCodec[bool](BoolKey)) // Uint16Value implements a ValueCodec for uint16. - Uint16Value = codec.KeyToValueCodec(Uint16Key) + Uint16Value = codec.KeyToValueCodec(codec.KeyCodec[uint16](Uint16Key)) // Uint32Value implements a ValueCodec for uint32. - Uint32Value = codec.KeyToValueCodec(Uint32Key) + Uint32Value = codec.KeyToValueCodec(codec.KeyCodec[uint32](Uint32Key)) // Uint64Value implements a ValueCodec for uint64. - Uint64Value = codec.KeyToValueCodec(Uint64Key) + Uint64Value = codec.KeyToValueCodec(codec.KeyCodec[uint64](Uint64Key)) // Int32Value implements a ValueCodec for int32. - Int32Value = codec.KeyToValueCodec(Int32Key) + Int32Value = codec.KeyToValueCodec(codec.KeyCodec[int32](Int32Key)) // Int64Value implements a ValueCodec for int64. - Int64Value = codec.KeyToValueCodec(Int64Key) + Int64Value = codec.KeyToValueCodec(codec.KeyCodec[int64](Int64Key)) // StringValue implements a ValueCodec for string. - StringValue = codec.KeyToValueCodec(StringKey) + StringValue = codec.KeyToValueCodec(codec.KeyCodec[string](StringKey)) // BytesValue implements a ValueCodec for bytes. - BytesValue = codec.KeyToValueCodec(BytesKey) + BytesValue = codec.KeyToValueCodec(codec.KeyCodec[[]byte](BytesKey)) ) // Collection is the interface that all collections implement. It will eventually diff --git a/collections/pair.go b/collections/pair.go index f12aaac1b576..59596d39a2cd 100644 --- a/collections/pair.go +++ b/collections/pair.go @@ -54,9 +54,22 @@ func PairKeyCodec[K1, K2 any](keyCodec1 codec.KeyCodec[K1], keyCodec2 codec.KeyC } } +// NamedPairKeyCodec instantiates a new KeyCodec instance that can encode the Pair, given the KeyCodec of the +// first part of the key and the KeyCodec of the second part of the key, with names assigned to each part +// which will only be used for indexing and informational purposes. +func NamedPairKeyCodec[K1, K2 any](key1Name string, keyCodec1 codec.KeyCodec[K1], key2Name string, keyCodec2 codec.KeyCodec[K2]) codec.KeyCodec[Pair[K1, K2]] { + return pairKeyCodec[K1, K2]{ + key1Name: key1Name, + key2Name: key2Name, + keyCodec1: keyCodec1, + keyCodec2: keyCodec2, + } +} + type pairKeyCodec[K1, K2 any] struct { - keyCodec1 codec.KeyCodec[K1] - keyCodec2 codec.KeyCodec[K2] + key1Name, key2Name string + keyCodec1 codec.KeyCodec[K1] + keyCodec2 codec.KeyCodec[K2] } func (p pairKeyCodec[K1, K2]) KeyCodec1() codec.KeyCodec[K1] { return p.keyCodec1 } @@ -216,6 +229,10 @@ func (p pairKeyCodec[K1, K2]) DecodeJSON(b []byte) (Pair[K1, K2], error) { return Join(k1, k2), nil } +func (p pairKeyCodec[K1, K2]) Name() string { + return fmt.Sprintf("%s,%s", p.key1Name, p.key2Name) +} + // NewPrefixUntilPairRange defines a collection query which ranges until the provided Pair prefix. // Unstable: this API might change in the future. func NewPrefixUntilPairRange[K1, K2 any](prefix K1) *PairRange[K1, K2] { diff --git a/collections/triple.go b/collections/triple.go index 9733d9984099..0e15151e29eb 100644 --- a/collections/triple.go +++ b/collections/triple.go @@ -64,10 +64,22 @@ func TripleKeyCodec[K1, K2, K3 any](keyCodec1 codec.KeyCodec[K1], keyCodec2 code } } +func NamedTripleKeyCodec[K1, K2, K3 any](key1Name string, keyCodec1 codec.KeyCodec[K1], key2Name string, keyCodec2 codec.KeyCodec[K2], key3Name string, keyCodec3 codec.KeyCodec[K3]) codec.KeyCodec[Triple[K1, K2, K3]] { + return tripleKeyCodec[K1, K2, K3]{ + key1Name: key1Name, + key2Name: key2Name, + key3Name: key3Name, + keyCodec1: keyCodec1, + keyCodec2: keyCodec2, + keyCodec3: keyCodec3, + } +} + type tripleKeyCodec[K1, K2, K3 any] struct { - keyCodec1 codec.KeyCodec[K1] - keyCodec2 codec.KeyCodec[K2] - keyCodec3 codec.KeyCodec[K3] + key1Name, key2Name, key3Name string + keyCodec1 codec.KeyCodec[K1] + keyCodec2 codec.KeyCodec[K2] + keyCodec3 codec.KeyCodec[K3] } type jsonTripleKey [3]json.RawMessage @@ -273,6 +285,10 @@ func (t tripleKeyCodec[K1, K2, K3]) SizeNonTerminal(key Triple[K1, K2, K3]) int return size } +func (t tripleKeyCodec[K1, K2, K3]) Name() string { + return fmt.Sprintf("%s,%s,%s", t.key1Name, t.key2Name, t.key3Name) +} + // NewPrefixUntilTripleRange defines a collection query which ranges until the provided Pair prefix. // Unstable: this API might change in the future. func NewPrefixUntilTripleRange[K1, K2, K3 any](k1 K1) Ranger[Triple[K1, K2, K3]] { From 6311f4f71ce74901b32b15d23c0d49c772fb015a Mon Sep 17 00:00:00 2001 From: Aaron Craelius Date: Tue, 4 Jun 2024 12:08:30 -0400 Subject: [PATCH 2/9] add tests --- collections/naming_test.go | 30 ++++++++++++++++++++++++++++++ collections/pair.go | 2 +- collections/triple.go | 2 +- 3 files changed, 32 insertions(+), 2 deletions(-) create mode 100644 collections/naming_test.go diff --git a/collections/naming_test.go b/collections/naming_test.go new file mode 100644 index 000000000000..48d05d8d7670 --- /dev/null +++ b/collections/naming_test.go @@ -0,0 +1,30 @@ +package collections + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestNaming(t *testing.T) { + require.Equal(t, "u16", Uint16Key.WithName("u16").Name()) + require.Equal(t, "u32", Uint32Key.WithName("u32").Name()) + require.Equal(t, "u64", Uint64Key.WithName("u64").Name()) + require.Equal(t, "i32", Int32Key.WithName("i32").Name()) + require.Equal(t, "i64", Int64Key.WithName("i64").Name()) + require.Equal(t, "str", StringKey.WithName("str").Name()) + require.Equal(t, "bytes", BytesKey.WithName("bytes").Name()) + require.Equal(t, "bool", BoolKey.WithName("bool").Name()) + + require.Equal(t, "vu16", Uint16Value.WithName("vu16").Name()) + require.Equal(t, "vu32", Uint32Value.WithName("vu32").Name()) + require.Equal(t, "vu64", Uint64Value.WithName("vu64").Name()) + require.Equal(t, "vi32", Int32Value.WithName("vi32").Name()) + require.Equal(t, "vi64", Int64Value.WithName("vi64").Name()) + require.Equal(t, "vstr", StringValue.WithName("vstr").Name()) + require.Equal(t, "vbytes", BytesValue.WithName("vbytes").Name()) + require.Equal(t, "vbool", BoolValue.WithName("vbool").Name()) + + require.Equal(t, "abc,def", NamedPairKeyCodec[bool, string]("abc", BoolKey, "def", StringKey).Name()) + require.Equal(t, "abc,def,ghi", NamedTripleKeyCodec[bool, string, int32]("abc", BoolKey, "def", StringKey, "ghi", Int32Key).Name()) +} diff --git a/collections/pair.go b/collections/pair.go index 59596d39a2cd..a3c87c2f4a35 100644 --- a/collections/pair.go +++ b/collections/pair.go @@ -57,7 +57,7 @@ func PairKeyCodec[K1, K2 any](keyCodec1 codec.KeyCodec[K1], keyCodec2 codec.KeyC // NamedPairKeyCodec instantiates a new KeyCodec instance that can encode the Pair, given the KeyCodec of the // first part of the key and the KeyCodec of the second part of the key, with names assigned to each part // which will only be used for indexing and informational purposes. -func NamedPairKeyCodec[K1, K2 any](key1Name string, keyCodec1 codec.KeyCodec[K1], key2Name string, keyCodec2 codec.KeyCodec[K2]) codec.KeyCodec[Pair[K1, K2]] { +func NamedPairKeyCodec[K1, K2 any](key1Name string, keyCodec1 codec.KeyCodec[K1], key2Name string, keyCodec2 codec.KeyCodec[K2]) codec.NamedKeyCodec[Pair[K1, K2]] { return pairKeyCodec[K1, K2]{ key1Name: key1Name, key2Name: key2Name, diff --git a/collections/triple.go b/collections/triple.go index 0e15151e29eb..ddd7728e7a07 100644 --- a/collections/triple.go +++ b/collections/triple.go @@ -64,7 +64,7 @@ func TripleKeyCodec[K1, K2, K3 any](keyCodec1 codec.KeyCodec[K1], keyCodec2 code } } -func NamedTripleKeyCodec[K1, K2, K3 any](key1Name string, keyCodec1 codec.KeyCodec[K1], key2Name string, keyCodec2 codec.KeyCodec[K2], key3Name string, keyCodec3 codec.KeyCodec[K3]) codec.KeyCodec[Triple[K1, K2, K3]] { +func NamedTripleKeyCodec[K1, K2, K3 any](key1Name string, keyCodec1 codec.KeyCodec[K1], key2Name string, keyCodec2 codec.KeyCodec[K2], key3Name string, keyCodec3 codec.KeyCodec[K3]) codec.NamedKeyCodec[Triple[K1, K2, K3]] { return tripleKeyCodec[K1, K2, K3]{ key1Name: key1Name, key2Name: key2Name, From e337c846abd0ace61e5e444aad8adf005dbf8179 Mon Sep 17 00:00:00 2001 From: Aaron Craelius Date: Tue, 4 Jun 2024 12:10:47 -0400 Subject: [PATCH 3/9] update CHANGELOG.md --- collections/CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/collections/CHANGELOG.md b/collections/CHANGELOG.md index fda6ca900b30..a5ca78dca06b 100644 --- a/collections/CHANGELOG.md +++ b/collections/CHANGELOG.md @@ -37,6 +37,7 @@ Ref: https://keepachangelog.com/en/1.0.0/ * [#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. * [#19861](https://github.com/cosmos/cosmos-sdk/pull/19861) Add `NewJSONValueCodec` value codec as an alternative for `codec.CollValue` from the SDK for non protobuf types. +* [#20538](https://github.com/cosmos/cosmos-sdk/pull/20538) Add `Nameable` and `Named` variations to `KeyCodec` and `ValueCodec` to allow for better indexing of collections types. ## [v0.4.0](https://github.com/cosmos/cosmos-sdk/releases/tag/collections%2Fv0.4.0) From 7c7ba4216fc88ba9294ecc89d093bae43105124f Mon Sep 17 00:00:00 2001 From: Aaron Craelius Date: Tue, 17 Sep 2024 11:19:07 -0400 Subject: [PATCH 4/9] update naming to reflect current schema --- collections/codec/bool.go | 13 ++------ collections/codec/bytes.go | 13 ++------ collections/codec/codec.go | 12 ++----- collections/codec/int.go | 26 ++++----------- collections/codec/naming.go | 50 +++++++++++++++++++--------- collections/codec/string.go | 13 ++------ collections/codec/uint.go | 39 +++++----------------- collections/naming_test.go | 65 +++++++++++++++++++++++++------------ collections/pair.go | 34 +++++++++++++++++-- collections/triple.go | 40 ++++++++++++++++++++++- 10 files changed, 176 insertions(+), 129 deletions(-) diff --git a/collections/codec/bool.go b/collections/codec/bool.go index f5a4616ca00b..8278def71185 100644 --- a/collections/codec/bool.go +++ b/collections/codec/bool.go @@ -8,9 +8,7 @@ import ( func NewBoolKey[T ~bool]() NameableKeyCodec[T] { return boolKey[T]{} } -type boolKey[T ~bool] struct { - name string -} +type boolKey[T ~bool] struct{} func (b boolKey[T]) Encode(buffer []byte, key T) (int, error) { if key { @@ -67,11 +65,6 @@ func (b boolKey[T]) SizeNonTerminal(key T) int { return b.Size(key) } -func (b boolKey[T]) WithName(name string) NamedKeyCodec[T] { - b.name = name - return b -} - -func (b boolKey[T]) Name() string { - return b.name +func (b boolKey[T]) WithName(name string) KeyCodec[T] { + return NamedKeyCodec[T]{KeyCodec: b, Name: name} } diff --git a/collections/codec/bytes.go b/collections/codec/bytes.go index 87b42046bcb0..0fb056782c05 100644 --- a/collections/codec/bytes.go +++ b/collections/codec/bytes.go @@ -12,9 +12,7 @@ const MaxBytesKeyNonTerminalSize = math.MaxUint8 func NewBytesKey[T ~[]byte]() NameableKeyCodec[T] { return bytesKey[T]{} } -type bytesKey[T ~[]byte] struct { - name string -} +type bytesKey[T ~[]byte] struct{} func (b bytesKey[T]) Encode(buffer []byte, key T) (int, error) { return copy(buffer, key), nil @@ -80,11 +78,6 @@ func (bytesKey[T]) SizeNonTerminal(key T) int { return len(key) + 1 } -func (b bytesKey[T]) WithName(name string) NamedKeyCodec[T] { - b.name = name - return b -} - -func (b bytesKey[T]) Name() string { - return b.name +func (b bytesKey[T]) WithName(name string) KeyCodec[T] { + return NamedKeyCodec[T]{KeyCodec: b, Name: name} } diff --git a/collections/codec/codec.go b/collections/codec/codec.go index 72cc00fc5db7..b8f5cf994613 100644 --- a/collections/codec/codec.go +++ b/collections/codec/codec.go @@ -131,8 +131,7 @@ func KeyToValueCodec[K any](keyCodec KeyCodec[K]) NameableValueCodec[K] { // keyToValueCodec is a ValueCodec that wraps a KeyCodec to make it behave like a ValueCodec. type keyToValueCodec[K any] struct { - name string - kc KeyCodec[K] + kc KeyCodec[K] } func (k keyToValueCodec[K]) EncodeJSON(value K) ([]byte, error) { @@ -171,11 +170,6 @@ func (k keyToValueCodec[K]) ValueType() string { return k.kc.KeyType() } -func (k keyToValueCodec[K]) WithName(name string) NamedValueCodec[K] { - k.name = name - return k -} - -func (k keyToValueCodec[K]) Name() string { - return k.name +func (k keyToValueCodec[K]) WithName(name string) ValueCodec[K] { + return NamedValueCodec[K]{ValueCodec: k, Name: name} } diff --git a/collections/codec/int.go b/collections/codec/int.go index 5ee3e6fd6d6e..9fb2a824f2fd 100644 --- a/collections/codec/int.go +++ b/collections/codec/int.go @@ -9,9 +9,7 @@ import ( func NewInt64Key[T ~int64]() NameableKeyCodec[T] { return int64Key[T]{} } -type int64Key[T ~int64] struct { - name string -} +type int64Key[T ~int64] struct{} func (i int64Key[T]) Encode(buffer []byte, key T) (int, error) { binary.BigEndian.PutUint64(buffer, (uint64)(key)) @@ -66,22 +64,15 @@ func (i int64Key[T]) SizeNonTerminal(_ T) int { return 8 } -func (i int64Key[T]) WithName(name string) NamedKeyCodec[T] { - i.name = name - return i -} - -func (i int64Key[T]) Name() string { - return i.name +func (i int64Key[T]) WithName(name string) KeyCodec[T] { + return NamedKeyCodec[T]{KeyCodec: i, Name: name} } func NewInt32Key[T ~int32]() NameableKeyCodec[T] { return int32Key[T]{} } -type int32Key[T ~int32] struct { - name string -} +type int32Key[T ~int32] struct{} func (i int32Key[T]) Encode(buffer []byte, key T) (int, error) { binary.BigEndian.PutUint32(buffer, (uint32)(key)) @@ -135,11 +126,6 @@ func (i int32Key[T]) SizeNonTerminal(_ T) int { return 4 } -func (i int32Key[T]) WithName(name string) NamedKeyCodec[T] { - i.name = name - return i -} - -func (i int32Key[T]) Name() string { - return i.name +func (i int32Key[T]) WithName(name string) KeyCodec[T] { + return NamedKeyCodec[T]{KeyCodec: i, Name: name} } diff --git a/collections/codec/naming.go b/collections/codec/naming.go index b00a8eeaf17d..31f94a0d01b7 100644 --- a/collections/codec/naming.go +++ b/collections/codec/naming.go @@ -1,20 +1,13 @@ package codec +import "fmt" + // NameableKeyCodec is a KeyCodec that can be named. type NameableKeyCodec[T any] interface { KeyCodec[T] // WithName returns the KeyCodec with the provided name. - WithName(name string) NamedKeyCodec[T] -} - -// NamedKeyCodec is a KeyCodec that has a name. -type NamedKeyCodec[T any] interface { - KeyCodec[T] - - // Name returns the name of key in the schema if one is defined or the empty string. - // Multipart keys should separate names with commas, i.e. "name1,name2". - Name() string + WithName(name string) KeyCodec[T] } // NameableValueCodec is a ValueCodec that can be named. @@ -22,14 +15,39 @@ type NameableValueCodec[T any] interface { ValueCodec[T] // WithName returns the ValueCodec with the provided name. - WithName(name string) NamedValueCodec[T] + WithName(name string) ValueCodec[T] } -// NamedValueCodec is a ValueCodec that has a name. -type NamedValueCodec[T any] interface { +type NamedKeyCodec[T any] struct { + KeyCodec[T] + Name string +} + +func (n NamedKeyCodec[T]) SchemaCodec() (SchemaCodec[T], error) { + cdc, err := KeySchemaCodec[T](n.KeyCodec) + if err != nil { + return SchemaCodec[T]{}, err + } + return withName(cdc, n.Name) +} + +type NamedValueCodec[T any] struct { ValueCodec[T] + Name string +} + +func (n NamedValueCodec[T]) SchemaCodec() (SchemaCodec[T], error) { + cdc, err := ValueSchemaCodec[T](n.ValueCodec) + if err != nil { + return SchemaCodec[T]{}, err + } + return withName(cdc, n.Name) +} - // Name returns the name of key in the schema if one is defined or the empty string. - // Multipart keys should separate names with commas, i.e. "name1,name2". - Name() string +func withName[T any](cdc SchemaCodec[T], name string) (SchemaCodec[T], error) { + if len(cdc.Fields) != 1 { + return SchemaCodec[T]{}, fmt.Errorf("expected exactly one field to be named, got %d", len(cdc.Fields)) + } + cdc.Fields[0].Name = name + return cdc, nil } diff --git a/collections/codec/string.go b/collections/codec/string.go index 3c9d53b1c42b..7b3cdf0faa1e 100644 --- a/collections/codec/string.go +++ b/collections/codec/string.go @@ -13,9 +13,7 @@ const ( StringDelimiter uint8 = 0x0 ) -type stringKey[T ~string] struct { - name string -} +type stringKey[T ~string] struct{} func (stringKey[T]) Encode(buffer []byte, key T) (int, error) { return copy(buffer, key), nil @@ -69,11 +67,6 @@ func (stringKey[T]) KeyType() string { return "string" } -func (s stringKey[T]) WithName(name string) NamedKeyCodec[T] { - s.name = name - return s -} - -func (s stringKey[T]) Name() string { - return s.name +func (s stringKey[T]) WithName(name string) KeyCodec[T] { + return NamedKeyCodec[T]{KeyCodec: s, Name: name} } diff --git a/collections/codec/uint.go b/collections/codec/uint.go index 8586aeb1b6ff..83efa2ec6453 100644 --- a/collections/codec/uint.go +++ b/collections/codec/uint.go @@ -9,9 +9,7 @@ import ( func NewUint64Key[T ~uint64]() NameableKeyCodec[T] { return uint64Key[T]{} } -type uint64Key[T ~uint64] struct { - name string -} +type uint64Key[T ~uint64] struct{} func (uint64Key[T]) Encode(buffer []byte, key T) (int, error) { binary.BigEndian.PutUint64(buffer, (uint64)(key)) @@ -57,20 +55,13 @@ func (uint64Key[T]) KeyType() string { return "uint64" } -func (u uint64Key[T]) WithName(name string) NamedKeyCodec[T] { - u.name = name - return u -} - -func (u uint64Key[T]) Name() string { - return u.name +func (u uint64Key[T]) WithName(name string) KeyCodec[T] { + return NamedKeyCodec[T]{KeyCodec: u, Name: name} } func NewUint32Key[T ~uint32]() NameableKeyCodec[T] { return uint32Key[T]{} } -type uint32Key[T ~uint32] struct { - name string -} +type uint32Key[T ~uint32] struct{} func (uint32Key[T]) Encode(buffer []byte, key T) (int, error) { binary.BigEndian.PutUint32(buffer, (uint32)(key)) @@ -108,20 +99,13 @@ func (u uint32Key[T]) DecodeNonTerminal(buffer []byte) (int, T, error) { return func (uint32Key[T]) SizeNonTerminal(_ T) int { return 4 } -func (u uint32Key[T]) WithName(name string) NamedKeyCodec[T] { - u.name = name - return u -} - -func (u uint32Key[T]) Name() string { - return u.name +func (u uint32Key[T]) WithName(name string) KeyCodec[T] { + return NamedKeyCodec[T]{KeyCodec: u, Name: name} } func NewUint16Key[T ~uint16]() NameableKeyCodec[T] { return uint16Key[T]{} } -type uint16Key[T ~uint16] struct { - name string -} +type uint16Key[T ~uint16] struct{} func (uint16Key[T]) Encode(buffer []byte, key T) (int, error) { binary.BigEndian.PutUint16(buffer, (uint16)(key)) @@ -159,13 +143,8 @@ func (u uint16Key[T]) DecodeNonTerminal(buffer []byte) (int, T, error) { return func (u uint16Key[T]) SizeNonTerminal(key T) int { return u.Size(key) } -func (u uint16Key[T]) WithName(name string) NamedKeyCodec[T] { - u.name = name - return u -} - -func (u uint16Key[T]) Name() string { - return u.name +func (u uint16Key[T]) WithName(name string) KeyCodec[T] { + return NamedKeyCodec[T]{KeyCodec: u, Name: name} } func uintEncodeJSON(value uint64) ([]byte, error) { diff --git a/collections/naming_test.go b/collections/naming_test.go index 48d05d8d7670..2f2b1b68e55d 100644 --- a/collections/naming_test.go +++ b/collections/naming_test.go @@ -4,27 +4,52 @@ import ( "testing" "github.com/stretchr/testify/require" + + "cosmossdk.io/collections/codec" ) func TestNaming(t *testing.T) { - require.Equal(t, "u16", Uint16Key.WithName("u16").Name()) - require.Equal(t, "u32", Uint32Key.WithName("u32").Name()) - require.Equal(t, "u64", Uint64Key.WithName("u64").Name()) - require.Equal(t, "i32", Int32Key.WithName("i32").Name()) - require.Equal(t, "i64", Int64Key.WithName("i64").Name()) - require.Equal(t, "str", StringKey.WithName("str").Name()) - require.Equal(t, "bytes", BytesKey.WithName("bytes").Name()) - require.Equal(t, "bool", BoolKey.WithName("bool").Name()) - - require.Equal(t, "vu16", Uint16Value.WithName("vu16").Name()) - require.Equal(t, "vu32", Uint32Value.WithName("vu32").Name()) - require.Equal(t, "vu64", Uint64Value.WithName("vu64").Name()) - require.Equal(t, "vi32", Int32Value.WithName("vi32").Name()) - require.Equal(t, "vi64", Int64Value.WithName("vi64").Name()) - require.Equal(t, "vstr", StringValue.WithName("vstr").Name()) - require.Equal(t, "vbytes", BytesValue.WithName("vbytes").Name()) - require.Equal(t, "vbool", BoolValue.WithName("vbool").Name()) - - require.Equal(t, "abc,def", NamedPairKeyCodec[bool, string]("abc", BoolKey, "def", StringKey).Name()) - require.Equal(t, "abc,def,ghi", NamedTripleKeyCodec[bool, string, int32]("abc", BoolKey, "def", StringKey, "ghi", Int32Key).Name()) + expectKeyCodecName(t, "u16", Uint16Key.WithName("u16")) + expectKeyCodecName(t, "u32", Uint32Key.WithName("u32")) + expectKeyCodecName(t, "u64", Uint64Key.WithName("u64")) + expectKeyCodecName(t, "i32", Int32Key.WithName("i32")) + expectKeyCodecName(t, "i64", Int64Key.WithName("i64")) + expectKeyCodecName(t, "str", StringKey.WithName("str")) + expectKeyCodecName(t, "bytes", BytesKey.WithName("bytes")) + expectKeyCodecName(t, "bool", BoolKey.WithName("bool")) + + expectValueCodecName(t, "vu16", Uint16Value.WithName("vu16")) + expectValueCodecName(t, "vu32", Uint32Value.WithName("vu32")) + expectValueCodecName(t, "vu64", Uint64Value.WithName("vu64")) + expectValueCodecName(t, "vi32", Int32Value.WithName("vi32")) + expectValueCodecName(t, "vi64", Int64Value.WithName("vi64")) + expectValueCodecName(t, "vstr", StringValue.WithName("vstr")) + expectValueCodecName(t, "vbytes", BytesValue.WithName("vbytes")) + expectValueCodecName(t, "vbool", BoolValue.WithName("vbool")) + + expectKeyCodecnames(t, NamedPairKeyCodec[bool, string]("abc", BoolKey, "def", StringKey), "abc", "def") + expectKeyCodecnames(t, NamedTripleKeyCodec[bool, string, int32]("abc", BoolKey, "def", StringKey, "ghi", Int32Key), "abc", "def", "ghi") +} + +func expectKeyCodecName[T any](t *testing.T, name string, cdc codec.KeyCodec[T]) { + schema, err := codec.KeySchemaCodec(cdc) + require.NoError(t, err) + require.Equal(t, 1, len(schema.Fields)) + require.Equal(t, name, schema.Fields[0].Name) +} + +func expectValueCodecName[T any](t *testing.T, name string, cdc codec.ValueCodec[T]) { + schema, err := codec.ValueSchemaCodec(cdc) + require.NoError(t, err) + require.Equal(t, 1, len(schema.Fields)) + require.Equal(t, name, schema.Fields[0].Name) +} + +func expectKeyCodecnames[T any](t *testing.T, cdc codec.KeyCodec[T], names ...string) { + schema, err := codec.KeySchemaCodec(cdc) + require.NoError(t, err) + require.Equal(t, len(names), len(schema.Fields)) + for i, name := range names { + require.Equal(t, name, schema.Fields[i].Name) + } } diff --git a/collections/pair.go b/collections/pair.go index a3c87c2f4a35..e15f8ba2950e 100644 --- a/collections/pair.go +++ b/collections/pair.go @@ -5,6 +5,8 @@ import ( "fmt" "strings" + "cosmossdk.io/schema" + "cosmossdk.io/collections/codec" ) @@ -55,9 +57,9 @@ func PairKeyCodec[K1, K2 any](keyCodec1 codec.KeyCodec[K1], keyCodec2 codec.KeyC } // NamedPairKeyCodec instantiates a new KeyCodec instance that can encode the Pair, given the KeyCodec of the -// first part of the key and the KeyCodec of the second part of the key, with names assigned to each part -// which will only be used for indexing and informational purposes. -func NamedPairKeyCodec[K1, K2 any](key1Name string, keyCodec1 codec.KeyCodec[K1], key2Name string, keyCodec2 codec.KeyCodec[K2]) codec.NamedKeyCodec[Pair[K1, K2]] { +// first part of the key and the KeyCodec of the second part of the key. +// It also provides names for the keys which are used for indexing purposes. +func NamedPairKeyCodec[K1, K2 any](key1Name string, keyCodec1 codec.KeyCodec[K1], key2Name string, keyCodec2 codec.KeyCodec[K2]) codec.KeyCodec[Pair[K1, K2]] { return pairKeyCodec[K1, K2]{ key1Name: key1Name, key2Name: key2Name, @@ -233,6 +235,32 @@ func (p pairKeyCodec[K1, K2]) Name() string { return fmt.Sprintf("%s,%s", p.key1Name, p.key2Name) } +func (p pairKeyCodec[K1, K2]) SchemaCodec() (codec.SchemaCodec[Pair[K1, K2]], error) { + key1Schema, err := codec.KeySchemaCodec(p.keyCodec1) + if err != nil { + return codec.SchemaCodec[Pair[K1, K2]]{}, err + } + if len(key1Schema.Fields) != 1 { + return codec.SchemaCodec[Pair[K1, K2]]{}, fmt.Errorf("key1 schema has more than one field") + } + field1 := key1Schema.Fields[0] + field1.Name = p.key1Name + + key2Schema, err := codec.KeySchemaCodec(p.keyCodec2) + if err != nil { + return codec.SchemaCodec[Pair[K1, K2]]{}, err + } + if len(key2Schema.Fields) != 1 { + return codec.SchemaCodec[Pair[K1, K2]]{}, fmt.Errorf("key2 schema has more than one field") + } + field2 := key2Schema.Fields[0] + field2.Name = p.key2Name + + return codec.SchemaCodec[Pair[K1, K2]]{ + Fields: []schema.Field{field1, field2}, + }, nil +} + // NewPrefixUntilPairRange defines a collection query which ranges until the provided Pair prefix. // Unstable: this API might change in the future. func NewPrefixUntilPairRange[K1, K2 any](prefix K1) *PairRange[K1, K2] { diff --git a/collections/triple.go b/collections/triple.go index ddd7728e7a07..c85dcd1e1cdc 100644 --- a/collections/triple.go +++ b/collections/triple.go @@ -5,6 +5,8 @@ import ( "fmt" "strings" + "cosmossdk.io/schema" + "cosmossdk.io/collections/codec" ) @@ -64,7 +66,7 @@ func TripleKeyCodec[K1, K2, K3 any](keyCodec1 codec.KeyCodec[K1], keyCodec2 code } } -func NamedTripleKeyCodec[K1, K2, K3 any](key1Name string, keyCodec1 codec.KeyCodec[K1], key2Name string, keyCodec2 codec.KeyCodec[K2], key3Name string, keyCodec3 codec.KeyCodec[K3]) codec.NamedKeyCodec[Triple[K1, K2, K3]] { +func NamedTripleKeyCodec[K1, K2, K3 any](key1Name string, keyCodec1 codec.KeyCodec[K1], key2Name string, keyCodec2 codec.KeyCodec[K2], key3Name string, keyCodec3 codec.KeyCodec[K3]) codec.KeyCodec[Triple[K1, K2, K3]] { return tripleKeyCodec[K1, K2, K3]{ key1Name: key1Name, key2Name: key2Name, @@ -289,6 +291,42 @@ func (t tripleKeyCodec[K1, K2, K3]) Name() string { return fmt.Sprintf("%s,%s,%s", t.key1Name, t.key2Name, t.key3Name) } +func (t tripleKeyCodec[K1, K2, K3]) SchemaCodec() (codec.SchemaCodec[Triple[K1, K2, K3]], error) { + key1Schema, err := codec.KeySchemaCodec(t.keyCodec1) + if err != nil { + return codec.SchemaCodec[Triple[K1, K2, K3]]{}, err + } + if len(key1Schema.Fields) != 1 { + return codec.SchemaCodec[Triple[K1, K2, K3]]{}, fmt.Errorf("expected 1 field in key1 schema, got: %d", len(key1Schema.Fields)) + } + field1 := key1Schema.Fields[0] + field1.Name = t.key1Name + + key2Schema, err := codec.KeySchemaCodec(t.keyCodec2) + if err != nil { + return codec.SchemaCodec[Triple[K1, K2, K3]]{}, err + } + if len(key2Schema.Fields) != 1 { + return codec.SchemaCodec[Triple[K1, K2, K3]]{}, fmt.Errorf("expected 1 field in key2 schema, got: %d", len(key2Schema.Fields)) + } + field2 := key2Schema.Fields[0] + field2.Name = t.key2Name + + key3Schema, err := codec.KeySchemaCodec(t.keyCodec3) + if err != nil { + return codec.SchemaCodec[Triple[K1, K2, K3]]{}, err + } + if len(key3Schema.Fields) != 1 { + return codec.SchemaCodec[Triple[K1, K2, K3]]{}, fmt.Errorf("expected 1 field in key3 schema, got: %d", len(key3Schema.Fields)) + } + + field3 := key3Schema.Fields[0] + field3.Name = t.key3Name + return codec.SchemaCodec[Triple[K1, K2, K3]]{ + Fields: []schema.Field{field1, field2, field3}, + }, nil +} + // NewPrefixUntilTripleRange defines a collection query which ranges until the provided Pair prefix. // Unstable: this API might change in the future. func NewPrefixUntilTripleRange[K1, K2, K3 any](k1 K1) Ranger[Triple[K1, K2, K3]] { From 6c4eaf7501251c638336667f51b5959f04188f22 Mon Sep 17 00:00:00 2001 From: Aaron Craelius Date: Tue, 17 Sep 2024 11:21:12 -0400 Subject: [PATCH 5/9] remove unnecessary cast --- collections/codec/alternative_value_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/collections/codec/alternative_value_test.go b/collections/codec/alternative_value_test.go index 871d95d653ce..358395427b90 100644 --- a/collections/codec/alternative_value_test.go +++ b/collections/codec/alternative_value_test.go @@ -17,7 +17,7 @@ type altValue struct { func TestAltValueCodec(t *testing.T) { // we assume we want to migrate the value from json(altValue) to just be // the raw value uint64. - canonical := codec.KeyToValueCodec(codec.KeyCodec[uint64](codec.NewUint64Key[uint64]())) + canonical := codec.KeyToValueCodec(codec.NewUint64Key[uint64]()) alternative := func(v []byte) (uint64, error) { var alt altValue err := json.Unmarshal(v, &alt) From ba73fb12b7929c998b33c96029b2094eab34047f Mon Sep 17 00:00:00 2001 From: Aaron Craelius Date: Tue, 17 Sep 2024 11:22:56 -0400 Subject: [PATCH 6/9] docs --- collections/codec/naming.go | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/collections/codec/naming.go b/collections/codec/naming.go index 31f94a0d01b7..ffaeeae58db3 100644 --- a/collections/codec/naming.go +++ b/collections/codec/naming.go @@ -18,11 +18,16 @@ type NameableValueCodec[T any] interface { WithName(name string) ValueCodec[T] } +// NamedKeyCodec wraps a KeyCodec with a name. +// The underlying key codec MUST have exactly one field in its schema. type NamedKeyCodec[T any] struct { KeyCodec[T] + + // Name is the name of the KeyCodec in the schema. Name string } +// SchemaCodec returns the schema codec for the named key codec. func (n NamedKeyCodec[T]) SchemaCodec() (SchemaCodec[T], error) { cdc, err := KeySchemaCodec[T](n.KeyCodec) if err != nil { @@ -31,11 +36,16 @@ func (n NamedKeyCodec[T]) SchemaCodec() (SchemaCodec[T], error) { return withName(cdc, n.Name) } +// NamedValueCodec wraps a ValueCodec with a name. +// The underlying value codec MUST have exactly one field in its schema. type NamedValueCodec[T any] struct { ValueCodec[T] + + // Name is the name of the ValueCodec in the schema. Name string } +// SchemaCodec returns the schema codec for the named value codec. func (n NamedValueCodec[T]) SchemaCodec() (SchemaCodec[T], error) { cdc, err := ValueSchemaCodec[T](n.ValueCodec) if err != nil { From 064709afb8a3c39298e09019e14b87df1daac2bd Mon Sep 17 00:00:00 2001 From: Aaron Craelius Date: Tue, 17 Sep 2024 11:24:05 -0400 Subject: [PATCH 7/9] docs --- collections/collections.go | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/collections/collections.go b/collections/collections.go index 582378394db7..57219c4e6767 100644 --- a/collections/collections.go +++ b/collections/collections.go @@ -6,8 +6,9 @@ import ( "io" "math" - "cosmossdk.io/collections/codec" "cosmossdk.io/schema" + + "cosmossdk.io/collections/codec" ) var ( @@ -58,21 +59,21 @@ var ( var ( // BoolValue implements a ValueCodec for bool. - BoolValue = codec.KeyToValueCodec(codec.KeyCodec[bool](BoolKey)) + BoolValue = codec.KeyToValueCodec(BoolKey) // Uint16Value implements a ValueCodec for uint16. - Uint16Value = codec.KeyToValueCodec(codec.KeyCodec[uint16](Uint16Key)) + Uint16Value = codec.KeyToValueCodec(Uint16Key) // Uint32Value implements a ValueCodec for uint32. - Uint32Value = codec.KeyToValueCodec(codec.KeyCodec[uint32](Uint32Key)) + Uint32Value = codec.KeyToValueCodec(Uint32Key) // Uint64Value implements a ValueCodec for uint64. - Uint64Value = codec.KeyToValueCodec(codec.KeyCodec[uint64](Uint64Key)) + Uint64Value = codec.KeyToValueCodec(Uint64Key) // Int32Value implements a ValueCodec for int32. - Int32Value = codec.KeyToValueCodec(codec.KeyCodec[int32](Int32Key)) + Int32Value = codec.KeyToValueCodec(Int32Key) // Int64Value implements a ValueCodec for int64. - Int64Value = codec.KeyToValueCodec(codec.KeyCodec[int64](Int64Key)) + Int64Value = codec.KeyToValueCodec(Int64Key) // StringValue implements a ValueCodec for string. - StringValue = codec.KeyToValueCodec(codec.KeyCodec[string](StringKey)) + StringValue = codec.KeyToValueCodec(StringKey) // BytesValue implements a ValueCodec for bytes. - BytesValue = codec.KeyToValueCodec(codec.KeyCodec[[]byte](BytesKey)) + BytesValue = codec.KeyToValueCodec(BytesKey) ) // Collection is the interface that all collections implement. It will eventually From f2f750cf64d5be8667ce9978c33822fc00f096b7 Mon Sep 17 00:00:00 2001 From: Aaron Craelius Date: Tue, 17 Sep 2024 11:34:28 -0400 Subject: [PATCH 8/9] add quad naming --- collections/naming_test.go | 7 ++--- collections/pair.go | 31 ++++++++++++---------- collections/quad.go | 53 +++++++++++++++++++++++++++++++++++--- collections/triple.go | 27 +++++-------------- 4 files changed, 76 insertions(+), 42 deletions(-) diff --git a/collections/naming_test.go b/collections/naming_test.go index 2f2b1b68e55d..1dc271391433 100644 --- a/collections/naming_test.go +++ b/collections/naming_test.go @@ -27,8 +27,9 @@ func TestNaming(t *testing.T) { expectValueCodecName(t, "vbytes", BytesValue.WithName("vbytes")) expectValueCodecName(t, "vbool", BoolValue.WithName("vbool")) - expectKeyCodecnames(t, NamedPairKeyCodec[bool, string]("abc", BoolKey, "def", StringKey), "abc", "def") - expectKeyCodecnames(t, NamedTripleKeyCodec[bool, string, int32]("abc", BoolKey, "def", StringKey, "ghi", Int32Key), "abc", "def", "ghi") + expectKeyCodecNames(t, NamedPairKeyCodec[bool, string]("abc", BoolKey, "def", StringKey), "abc", "def") + expectKeyCodecNames(t, NamedTripleKeyCodec[bool, string, int32]("abc", BoolKey, "def", StringKey, "ghi", Int32Key), "abc", "def", "ghi") + expectKeyCodecNames(t, NamedQuadKeyCodec[bool, string, int32, uint64]("abc", BoolKey, "def", StringKey, "ghi", Int32Key, "jkl", Uint64Key), "abc", "def", "ghi", "jkl") } func expectKeyCodecName[T any](t *testing.T, name string, cdc codec.KeyCodec[T]) { @@ -45,7 +46,7 @@ func expectValueCodecName[T any](t *testing.T, name string, cdc codec.ValueCodec require.Equal(t, name, schema.Fields[0].Name) } -func expectKeyCodecnames[T any](t *testing.T, cdc codec.KeyCodec[T], names ...string) { +func expectKeyCodecNames[T any](t *testing.T, cdc codec.KeyCodec[T], names ...string) { schema, err := codec.KeySchemaCodec(cdc) require.NoError(t, err) require.Equal(t, len(names), len(schema.Fields)) diff --git a/collections/pair.go b/collections/pair.go index e15f8ba2950e..ef0738b8b870 100644 --- a/collections/pair.go +++ b/collections/pair.go @@ -236,31 +236,34 @@ func (p pairKeyCodec[K1, K2]) Name() string { } func (p pairKeyCodec[K1, K2]) SchemaCodec() (codec.SchemaCodec[Pair[K1, K2]], error) { - key1Schema, err := codec.KeySchemaCodec(p.keyCodec1) + field1, err := getNamedKeyField(p.keyCodec1, p.key1Name) if err != nil { - return codec.SchemaCodec[Pair[K1, K2]]{}, err + return codec.SchemaCodec[Pair[K1, K2]]{}, fmt.Errorf("error getting key1 field: %w", err) } - if len(key1Schema.Fields) != 1 { - return codec.SchemaCodec[Pair[K1, K2]]{}, fmt.Errorf("key1 schema has more than one field") - } - field1 := key1Schema.Fields[0] - field1.Name = p.key1Name - key2Schema, err := codec.KeySchemaCodec(p.keyCodec2) + field2, err := getNamedKeyField(p.keyCodec2, p.key2Name) if err != nil { - return codec.SchemaCodec[Pair[K1, K2]]{}, err - } - if len(key2Schema.Fields) != 1 { - return codec.SchemaCodec[Pair[K1, K2]]{}, fmt.Errorf("key2 schema has more than one field") + return codec.SchemaCodec[Pair[K1, K2]]{}, fmt.Errorf("error getting key2 field: %w", err) } - field2 := key2Schema.Fields[0] - field2.Name = p.key2Name return codec.SchemaCodec[Pair[K1, K2]]{ Fields: []schema.Field{field1, field2}, }, nil } +func getNamedKeyField[T any](keyCdc codec.KeyCodec[T], name string) (schema.Field, error) { + keySchema, err := codec.KeySchemaCodec(keyCdc) + if err != nil { + return schema.Field{}, err + } + if len(keySchema.Fields) != 1 { + return schema.Field{}, fmt.Errorf("key schema in composite key has more than one field, got %v", keySchema.Fields) + } + field := keySchema.Fields[0] + field.Name = name + return field, nil +} + // NewPrefixUntilPairRange defines a collection query which ranges until the provided Pair prefix. // Unstable: this API might change in the future. func NewPrefixUntilPairRange[K1, K2 any](prefix K1) *PairRange[K1, K2] { diff --git a/collections/quad.go b/collections/quad.go index bd2844728360..1baeaac47aec 100644 --- a/collections/quad.go +++ b/collections/quad.go @@ -5,6 +5,8 @@ import ( "fmt" "strings" + "cosmossdk.io/schema" + "cosmossdk.io/collections/codec" ) @@ -79,11 +81,28 @@ func QuadKeyCodec[K1, K2, K3, K4 any](keyCodec1 codec.KeyCodec[K1], keyCodec2 co } } +// NamedQuadKeyCodec instantiates a new KeyCodec instance that can encode the Quad, given +// the KeyCodecs of the four parts of the key, in order. +// The provided names are used to identify the parts of the key in the schema for indexing. +func NamedQuadKeyCodec[K1, K2, K3, K4 any](key1Name string, keyCodec1 codec.KeyCodec[K1], key2Name string, keyCodec2 codec.KeyCodec[K2], key3Name string, keyCodec3 codec.KeyCodec[K3], key4Name string, keyCodec4 codec.KeyCodec[K4]) codec.KeyCodec[Quad[K1, K2, K3, K4]] { + return quadKeyCodec[K1, K2, K3, K4]{ + name1: key1Name, + keyCodec1: keyCodec1, + name2: key2Name, + keyCodec2: keyCodec2, + name3: key3Name, + keyCodec3: keyCodec3, + name4: key4Name, + keyCodec4: keyCodec4, + } +} + type quadKeyCodec[K1, K2, K3, K4 any] struct { - keyCodec1 codec.KeyCodec[K1] - keyCodec2 codec.KeyCodec[K2] - keyCodec3 codec.KeyCodec[K3] - keyCodec4 codec.KeyCodec[K4] + name1, name2, name3, name4 string + keyCodec1 codec.KeyCodec[K1] + keyCodec2 codec.KeyCodec[K2] + keyCodec3 codec.KeyCodec[K3] + keyCodec4 codec.KeyCodec[K4] } type jsonQuadKey [4]json.RawMessage @@ -338,6 +357,32 @@ func (t quadKeyCodec[K1, K2, K3, K4]) SizeNonTerminal(key Quad[K1, K2, K3, K4]) return size } +func (t quadKeyCodec[K1, K2, K3, K4]) SchemaCodec() (codec.SchemaCodec[Quad[K1, K2, K3, K4]], error) { + field1, err := getNamedKeyField(t.keyCodec1, t.name1) + if err != nil { + return codec.SchemaCodec[Quad[K1, K2, K3, K4]]{}, fmt.Errorf("error getting key1 field: %w", err) + } + + field2, err := getNamedKeyField(t.keyCodec2, t.name2) + if err != nil { + return codec.SchemaCodec[Quad[K1, K2, K3, K4]]{}, fmt.Errorf("error getting key2 field: %w", err) + } + + field3, err := getNamedKeyField(t.keyCodec3, t.name3) + if err != nil { + return codec.SchemaCodec[Quad[K1, K2, K3, K4]]{}, fmt.Errorf("error getting key3 field: %w", err) + } + + field4, err := getNamedKeyField(t.keyCodec4, t.name4) + if err != nil { + return codec.SchemaCodec[Quad[K1, K2, K3, K4]]{}, fmt.Errorf("error getting key4 field: %w", err) + } + + return codec.SchemaCodec[Quad[K1, K2, K3, K4]]{ + Fields: []schema.Field{field1, field2, field3, field4}, + }, nil +} + // NewPrefixUntilQuadRange defines a collection query which ranges until the provided Quad prefix. // Unstable: this API might change in the future. func NewPrefixUntilQuadRange[K1, K2, K3, K4 any](k1 K1) Ranger[Quad[K1, K2, K3, K4]] { diff --git a/collections/triple.go b/collections/triple.go index c85dcd1e1cdc..e5121602c21a 100644 --- a/collections/triple.go +++ b/collections/triple.go @@ -292,36 +292,21 @@ func (t tripleKeyCodec[K1, K2, K3]) Name() string { } func (t tripleKeyCodec[K1, K2, K3]) SchemaCodec() (codec.SchemaCodec[Triple[K1, K2, K3]], error) { - key1Schema, err := codec.KeySchemaCodec(t.keyCodec1) + field1, err := getNamedKeyField(t.keyCodec1, t.key1Name) if err != nil { - return codec.SchemaCodec[Triple[K1, K2, K3]]{}, err + return codec.SchemaCodec[Triple[K1, K2, K3]]{}, fmt.Errorf("error getting key1 field: %w", err) } - if len(key1Schema.Fields) != 1 { - return codec.SchemaCodec[Triple[K1, K2, K3]]{}, fmt.Errorf("expected 1 field in key1 schema, got: %d", len(key1Schema.Fields)) - } - field1 := key1Schema.Fields[0] - field1.Name = t.key1Name - key2Schema, err := codec.KeySchemaCodec(t.keyCodec2) + field2, err := getNamedKeyField(t.keyCodec2, t.key2Name) if err != nil { - return codec.SchemaCodec[Triple[K1, K2, K3]]{}, err - } - if len(key2Schema.Fields) != 1 { - return codec.SchemaCodec[Triple[K1, K2, K3]]{}, fmt.Errorf("expected 1 field in key2 schema, got: %d", len(key2Schema.Fields)) + return codec.SchemaCodec[Triple[K1, K2, K3]]{}, fmt.Errorf("error getting key2 field: %w", err) } - field2 := key2Schema.Fields[0] - field2.Name = t.key2Name - key3Schema, err := codec.KeySchemaCodec(t.keyCodec3) + field3, err := getNamedKeyField(t.keyCodec3, t.key3Name) if err != nil { - return codec.SchemaCodec[Triple[K1, K2, K3]]{}, err - } - if len(key3Schema.Fields) != 1 { - return codec.SchemaCodec[Triple[K1, K2, K3]]{}, fmt.Errorf("expected 1 field in key3 schema, got: %d", len(key3Schema.Fields)) + return codec.SchemaCodec[Triple[K1, K2, K3]]{}, fmt.Errorf("error getting key3 field: %w", err) } - field3 := key3Schema.Fields[0] - field3.Name = t.key3Name return codec.SchemaCodec[Triple[K1, K2, K3]]{ Fields: []schema.Field{field1, field2, field3}, }, nil From 88a7fc27c620036478c21b613b4a6862d01822e8 Mon Sep 17 00:00:00 2001 From: Aaron Craelius Date: Tue, 17 Sep 2024 14:06:41 -0400 Subject: [PATCH 9/9] update CHANGELOG --- collections/CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/collections/CHANGELOG.md b/collections/CHANGELOG.md index 6431b0b6b8a6..d0da0dcfa533 100644 --- a/collections/CHANGELOG.md +++ b/collections/CHANGELOG.md @@ -39,7 +39,7 @@ Ref: https://keepachangelog.com/en/1.0.0/ * [#19861](https://github.com/cosmos/cosmos-sdk/pull/19861) Add `NewJSONValueCodec` value codec as an alternative for `codec.CollValue` from the SDK for non protobuf types. * [#21090](https://github.com/cosmos/cosmos-sdk/pull/21090) Introduces `Quad`, a composite key with four keys. * [#20704](https://github.com/cosmos/cosmos-sdk/pull/20704) Add `ModuleCodec` method to `Schema` and `HasSchemaCodec` interface in order to support `cosmossdk.io/schema` compatible indexing. -* [#20538](https://github.com/cosmos/cosmos-sdk/pull/20538) Add `Nameable` and `Named` variations to `KeyCodec` and `ValueCodec` to allow for better indexing of collections types. +* [#20538](https://github.com/cosmos/cosmos-sdk/pull/20538) Add `Nameable` variations to `KeyCodec` and `ValueCodec` to allow for better indexing of `collections` types. ## [v0.4.0](https://github.com/cosmos/cosmos-sdk/releases/tag/collections%2Fv0.4.0)