chore: set collections to main (#23696)
This commit is contained in:
parent
3120df7ff7
commit
256e3e143b
@ -31,16 +31,59 @@ Ref: https://keepachangelog.com/en/1.0.0/
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
## [v1.1.0](https://github.com/cosmos/cosmos-sdk/releases/tag/collections%2Fv1.1.0)
|
||||
|
||||
### Improvements
|
||||
|
||||
* [#23515](https://github.com/cosmos/cosmos-sdk/pull/23515) Bring in `collections/protocodec` go module as package within `collections` module.
|
||||
|
||||
## [v1.0.0](https://github.com/cosmos/cosmos-sdk/releases/tag/collections%2Fv1.0.0)
|
||||
|
||||
### Features
|
||||
|
||||
* [#22641](https://github.com/cosmos/cosmos-sdk/pull/22641) Add reverse iterator support for `Triple`.
|
||||
* [#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.
|
||||
* [#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.
|
||||
* [#19343](https://github.com/cosmos/cosmos-sdk/pull/19343) Simplify IndexedMap creation by allowing to infer indexes through reflection.
|
||||
* [#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` variations to `KeyCodec` and `ValueCodec` to allow for better indexing of `collections` types.
|
||||
* [#22544](https://github.com/cosmos/cosmos-sdk/pull/22544) Schema's `ModuleCodec` will now also return Enum descriptors to be registered with the indexer.
|
||||
|
||||
## [v0.4.0](https://github.com/cosmos/cosmos-sdk/releases/tag/collections%2Fv0.4.0)
|
||||
|
||||
### Features
|
||||
|
||||
* [#17024](https://github.com/cosmos/cosmos-sdk/pull/17024) Introduces `Triple`, a composite key with three keys.
|
||||
|
||||
### API Breaking
|
||||
|
||||
* [#17290](https://github.com/cosmos/cosmos-sdk/pull/17290) Collections iteration methods (Iterate, Walk) will not error when the collection is empty.
|
||||
|
||||
### Improvements
|
||||
|
||||
* [#17021](https://github.com/cosmos/cosmos-sdk/pull/17021) Make collections implement the `appmodule.HasGenesis` interface.
|
||||
|
||||
## [v0.3.0](https://github.com/cosmos/cosmos-sdk/releases/tag/collections%2Fv0.3.0)
|
||||
|
||||
### Features
|
||||
|
||||
* [#16074](https://github.com/cosmos/cosmos-sdk/pull/16607) Introduces `Clear` method for `Map` and `KeySet`
|
||||
* [#16773](https://github.com/cosmos/cosmos-sdk/pull/16773)
|
||||
* Adds `AltValueCodec` which provides a way to decode a value in two ways.
|
||||
* Adds the possibility to specify an alternative way to decode the values of `KeySet`, `indexes.Multi`, `indexes.ReversePair`.
|
||||
|
||||
## [v0.2.0](https://github.com/cosmos/cosmos-sdk/releases/tag/collections%2Fv0.2.0)
|
||||
|
||||
### Features
|
||||
|
||||
* [#16074](https://github.com/cosmos/cosmos-sdk/pull/16074) – Makes the generic Collection interface public, still highly unstable.
|
||||
* [#16074](https://github.com/cosmos/cosmos-sdk/pull/16074) Makes the generic Collection interface public, still highly unstable.
|
||||
|
||||
### API Breaking
|
||||
|
||||
* [#16127](https://github.com/cosmos/cosmos-sdk/pull/16127) – In the `Walk` method the call back function being passed is allowed to error.
|
||||
* [#16127](https://github.com/cosmos/cosmos-sdk/pull/16127) In the `Walk` method the call back function being passed is allowed to error.
|
||||
|
||||
## [v0.1.0](https://github.com/cosmos/cosmos-sdk/releases/tag/collections%2Fv0.1.0)
|
||||
|
||||
Collections `v0.1.0` is released! Check out the [docs](https://docs.cosmos.network/main/packages/collections) to know how to use the APIs.
|
||||
Collections `v0.1.0` is released! Check out the [docs](https://docs.cosmos.network/main/build/packages/collections) to know how to use the APIs.
|
||||
|
||||
@ -23,11 +23,12 @@ go get cosmossdk.io/collections
|
||||
## Core types
|
||||
|
||||
Collections offers 5 different APIs to work with state, which will be explored in the next sections, these APIs are:
|
||||
- ``Map``: to work with typed arbitrary KV pairings.
|
||||
- ``KeySet``: to work with just typed keys
|
||||
- ``Item``: to work with just one typed value
|
||||
- ``Sequence``: which is a monotonically increasing number.
|
||||
- ``IndexedMap``: which combines ``Map`` and `KeySet` to provide a `Map` with indexing capabilities.
|
||||
|
||||
* ``Map``: to work with typed arbitrary KV pairings.
|
||||
* ``KeySet``: to work with just typed keys
|
||||
* ``Item``: to work with just one typed value
|
||||
* ``Sequence``: which is a monotonically increasing number.
|
||||
* ``IndexedMap``: which combines ``Map`` and `KeySet` to provide a `Map` with indexing capabilities.
|
||||
|
||||
## Preliminary components
|
||||
|
||||
@ -82,15 +83,17 @@ The second argument passed to our ``KeySet`` is a `collections.Prefix`, a prefix
|
||||
where all the state of a specific collection will be saved.
|
||||
|
||||
Since a module can have multiple collections, the following is expected:
|
||||
- module params will become a `collections.Item`
|
||||
- the `AllowList` is a `collections.KeySet`
|
||||
|
||||
* module params will become a `collections.Item`
|
||||
* the `AllowList` is a `collections.KeySet`
|
||||
|
||||
We don't want a collection to write over the state of the other collection so we pass it a prefix, which defines a storage
|
||||
partition owned by the collection.
|
||||
|
||||
If you already built modules, the prefix translates to the items you were creating in your ``types/keys.go`` file, example: https://github.com/cosmos/cosmos-sdk/blob/main/x/feegrant/key.go#L27
|
||||
If you already built modules, the prefix translates to the items you were creating in your ``types/keys.go`` file, example: https://github.com/cosmos/cosmos-sdk/blob/v0.52.0-rc.1/x/feegrant/key.go#L16~L22
|
||||
|
||||
your old:
|
||||
|
||||
```go
|
||||
var (
|
||||
// FeeAllowanceKeyPrefix is the set of the kvstore for fee allowance data
|
||||
@ -102,7 +105,9 @@ var (
|
||||
FeeAllowanceQueueKeyPrefix = []byte{0x01}
|
||||
)
|
||||
```
|
||||
|
||||
becomes:
|
||||
|
||||
```go
|
||||
var (
|
||||
// FeeAllowanceKeyPrefix is the set of the kvstore for fee allowance data
|
||||
@ -130,6 +135,7 @@ prefix2 := collections.NewPrefix("prefix") // THIS IS BAD!
|
||||
prefix1 := collections.NewPrefix("a")
|
||||
prefix2 := collections.NewPrefix("aa") // prefix2 starts with the same as prefix1: BAD!!!
|
||||
```
|
||||
|
||||
### Human-Readable Name
|
||||
|
||||
The third parameter we pass to a collection is a string, which is a human-readable name.
|
||||
@ -157,7 +163,7 @@ You might need to implement them only if you're migrating to collections and the
|
||||
|
||||
Let's explore an example:
|
||||
|
||||
````go
|
||||
```go
|
||||
package collections
|
||||
|
||||
import (
|
||||
@ -180,14 +186,14 @@ func NewKeeper(storeKey *storetypes.KVStoreKey) Keeper {
|
||||
IDs: collections.NewMap(sb, IDsPrefix, "ids", collections.StringKey, collections.Uint64Value),
|
||||
}
|
||||
}
|
||||
````
|
||||
```
|
||||
|
||||
We're now instantiating a map where the key is string and the value is `uint64`.
|
||||
We already know the first three arguments of the ``NewMap`` function.
|
||||
|
||||
The fourth parameter is our `KeyCodec`, we know that the ``Map`` has `string` as key so we pass it a `KeyCodec` that handles strings as keys.
|
||||
|
||||
The fifth parameter is our `ValueCodec`, we know that the `Map` as a `uint64` as value so we pass it a `ValueCodec` that handles uint64.
|
||||
The fifth parameter is our `ValueCodec`, we know that the `Map` has a `uint64` as value so we pass it a `ValueCodec` that handles uint64.
|
||||
|
||||
Collections already comes with all the required implementations for golang primitive types.
|
||||
|
||||
@ -390,6 +396,7 @@ func (k Keeper) RemoveValidator(ctx sdk.Context, validator sdk.ValAddress) error
|
||||
return nil
|
||||
}
|
||||
```
|
||||
|
||||
The first difference we notice is that `KeySet` needs use to specify only one type parameter: the key (`sdk.ValAddress` in this case).
|
||||
The second difference we notice is that `KeySet` in its `NewKeySet` function does not require
|
||||
us to specify a `ValueCodec` but only a `KeyCodec`. This is because a `KeySet` only saves keys and not values.
|
||||
@ -431,7 +438,7 @@ import (
|
||||
storetypes "cosmossdk.io/store/types"
|
||||
"github.com/cosmos/cosmos-sdk/codec"
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types"
|
||||
stakingtypes "cosmossdk.io/x/staking/types"
|
||||
)
|
||||
|
||||
var ParamsPrefix = collections.NewPrefix(0)
|
||||
@ -580,7 +587,7 @@ Let's analyse each method in the example and how it makes use of the `Iterate` a
|
||||
In `GetAllAccounts` we pass to our `Iterate` a nil `Ranger`. This means that the returned `Iterator` will include
|
||||
all the existing keys within the collection.
|
||||
|
||||
Then we use some the `Values` method from the returned `Iterator` API to collect all the values into a slice.
|
||||
Then we use the `Values` method from the returned `Iterator` API to collect all the values into a slice.
|
||||
|
||||
`Iterator` offers other methods such as `Keys()` to collect only the keys and not the values and `KeyValues` to collect
|
||||
all the keys and values.
|
||||
@ -595,9 +602,9 @@ we instruct it to report us results in reverse order through `Descending`
|
||||
Then we pass the range instruction to `Iterate` and get an `Iterator`, which will contain only the results
|
||||
we specified in the range.
|
||||
|
||||
Then we use again th `Values` method of the `Iterator` to collect all the results.
|
||||
Then we use again the `Values` method of the `Iterator` to collect all the results.
|
||||
|
||||
`collections.Range` also offers a `Prefix` API which is not appliable to all keys types,
|
||||
`collections.Range` also offers a `Prefix` API which is not applicable to all keys types,
|
||||
for example uint64 cannot be prefix because it is of constant size, but a `string` key
|
||||
can be prefixed.
|
||||
|
||||
@ -670,7 +677,7 @@ func NewKeeper(storeKey *storetypes.KVStoreKey) Keeper {
|
||||
Balances: collections.NewMap(
|
||||
sb, BalancesPrefix, "balances",
|
||||
collections.PairKeyCodec(sdk.AccAddressKey, collections.StringKey),
|
||||
math.IntValue,
|
||||
sdk.IntValue,
|
||||
),
|
||||
}
|
||||
}
|
||||
@ -679,9 +686,10 @@ func NewKeeper(storeKey *storetypes.KVStoreKey) Keeper {
|
||||
#### The Map Key definition
|
||||
|
||||
First of all we can see that in order to define a composite key of two elements we use the `collections.Pair` type:
|
||||
````go
|
||||
|
||||
```go
|
||||
collections.Map[collections.Pair[sdk.AccAddress, string], math.Int]
|
||||
````
|
||||
```
|
||||
|
||||
`collections.Pair` defines a key composed of two other keys, in our case the first part is `sdk.AccAddress`, the second
|
||||
part is `string`.
|
||||
@ -698,7 +706,7 @@ encode the second part of the key.
|
||||
|
||||
Let's expand on the example we used before:
|
||||
|
||||
````go
|
||||
```go
|
||||
var BalancesPrefix = collections.NewPrefix(1)
|
||||
|
||||
type Keeper struct {
|
||||
@ -712,7 +720,7 @@ func NewKeeper(storeKey *storetypes.KVStoreKey) Keeper {
|
||||
Balances: collections.NewMap(
|
||||
sb, BalancesPrefix, "balances",
|
||||
collections.PairKeyCodec(sdk.AccAddressKey, collections.StringKey),
|
||||
math.IntValue,
|
||||
sdk.IntValue,
|
||||
),
|
||||
}
|
||||
}
|
||||
@ -758,7 +766,7 @@ func (k Keeper) GetAllAddressBalancesBetween(ctx sdk.Context, address sdk.AccAdd
|
||||
}
|
||||
...
|
||||
}
|
||||
````
|
||||
```
|
||||
|
||||
#### SetBalance
|
||||
|
||||
@ -769,7 +777,7 @@ We use the `collections.Join` function to generate the composite key.
|
||||
`collections.Pair` contains the two keys we have joined, it also exposes two methods: `K1` to fetch the 1st part of the
|
||||
key and `K2` to fetch the second part.
|
||||
|
||||
As always, we use the `collections.Map.Set` method to map the composite key to our value (`math.Int`in this case)
|
||||
As always, we use the `collections.Map.Set` method to map the composite key to our value (`math.Int` in this case)
|
||||
|
||||
#### GetBalance
|
||||
|
||||
@ -788,7 +796,7 @@ in `Pair` keys iterations.
|
||||
```
|
||||
|
||||
As we can see here we're passing the type parameters of the `collections.Pair` because golang type inference
|
||||
with respect to generics is not as permissive as other languages, so we need to explitly say what are the types of the pair key.
|
||||
with respect to generics is not as permissive as other languages, so we need to explicitly say what are the types of the pair key.
|
||||
|
||||
#### GetAllAddressesBalancesBetween
|
||||
|
||||
@ -811,7 +819,7 @@ type BaseAccount struct {
|
||||
```
|
||||
|
||||
First of all, when we save our accounts in state we map them using a primary key `sdk.AccAddress`.
|
||||
If it were to be a `collections.Map` it would be `collections.Map[sdk.AccAddres, authtypes.BaseAccount]`.
|
||||
If it were to be a `collections.Map` it would be `collections.Map[sdk.AccAddress, authtypes.BaseAccount]`.
|
||||
|
||||
Then we also want to be able to get an account not only by its `sdk.AccAddress`, but also by its `AccountNumber`.
|
||||
|
||||
@ -829,10 +837,6 @@ type AccountsIndexes struct {
|
||||
Number *indexes.Unique[uint64, sdk.AccAddress, authtypes.BaseAccount]
|
||||
}
|
||||
|
||||
func (a AccountsIndexes) IndexesList() []collections.Index[sdk.AccAddress, authtypes.BaseAccount] {
|
||||
return []collections.Index[sdk.AccAddress, authtypes.BaseAccount]{a.Number}
|
||||
}
|
||||
|
||||
func NewAccountIndexes(sb *collections.SchemaBuilder) AccountsIndexes {
|
||||
return AccountsIndexes{
|
||||
Number: indexes.NewUnique(
|
||||
@ -856,18 +860,26 @@ Then we can see in our `AccountIndexes` struct the `Number` field is defined as:
|
||||
```
|
||||
|
||||
Where the first type parameter is `uint64`, which is the field type of our index.
|
||||
The second type parameter is the primary key `sdk.AccAddress`
|
||||
The second type parameter is the primary key `sdk.AccAddress`.
|
||||
And the third type parameter is the actual object we're storing `authtypes.BaseAccount`.
|
||||
|
||||
Then we implement a function called `IndexesList` on our `AccountIndexes` struct, this will be used
|
||||
by the `IndexedMap` to keep the underlying map in sync with the indexes, in our case `Number`.
|
||||
This function just needs to return the slice of indexes contained in the struct.
|
||||
|
||||
Then we create a `NewAccountIndexes` function that instantiates and returns the `AccountsIndexes` struct.
|
||||
|
||||
The function takes a `SchemaBuilder`. Then we instantiate our `indexes.Unique`, let's analyse the arguments we pass to
|
||||
`indexes.NewUnique`.
|
||||
|
||||
#### NOTE: indexes list
|
||||
|
||||
The `AccountsIndexes` struct contains the indexes, the `NewIndexedMap` function will infer the indexes form that struct
|
||||
using reflection, this happens only at init and is not computationally expensive. In case you want to explicitly declare
|
||||
indexes: implement the `Indexes` interface in the `AccountsIndexes` struct:
|
||||
|
||||
```go
|
||||
func (a AccountsIndexes) IndexesList() []collections.Index[sdk.AccAddress, authtypes.BaseAccount] {
|
||||
return []collections.Index[sdk.AccAddress, authtypes.BaseAccount]{a.Number}
|
||||
}
|
||||
```
|
||||
|
||||
#### Instantiating a `indexes.Unique`
|
||||
|
||||
The first three arguments, we already know them, they are: `SchemaBuilder`, `Prefix` which is our index prefix (the partition
|
||||
@ -1117,3 +1129,82 @@ func (k Keeper) GetAccount(ctx sdk.context, addr sdk.AccAddress) (sdk.AccountI,
|
||||
return k.Accounts.Get(ctx, addr)
|
||||
}
|
||||
```
|
||||
|
||||
## Triple key
|
||||
|
||||
The `collections.Triple` is a special type of key composed of three keys, it's identical to `collections.Pair`.
|
||||
|
||||
Let's see an example.
|
||||
|
||||
```go
|
||||
package example
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"cosmossdk.io/collections"
|
||||
storetypes "cosmossdk.io/store/types"
|
||||
"github.com/cosmos/cosmos-sdk/codec"
|
||||
)
|
||||
|
||||
type AccAddress = string
|
||||
type ValAddress = string
|
||||
|
||||
type Keeper struct {
|
||||
// let's simulate we have redelegations which are stored as a triple key composed of
|
||||
// the delegator, the source validator and the destination validator.
|
||||
Redelegations collections.KeySet[collections.Triple[AccAddress, ValAddress, ValAddress]]
|
||||
}
|
||||
|
||||
func NewKeeper(storeKey *storetypes.KVStoreKey) Keeper {
|
||||
sb := collections.NewSchemaBuilder(sdk.OpenKVStore(storeKey))
|
||||
return Keeper{
|
||||
Redelegations: collections.NewKeySet(sb, collections.NewPrefix(0), "redelegations", collections.TripleKeyCodec(collections.StringKey, collections.StringKey, collections.StringKey)
|
||||
}
|
||||
}
|
||||
|
||||
// RedelegationsByDelegator iterates over all the redelegations of a given delegator and calls onResult providing
|
||||
// each redelegation from source validator towards the destination validator.
|
||||
func (k Keeper) RedelegationsByDelegator(ctx context.Context, delegator AccAddress, onResult func(src, dst ValAddress) (stop bool, err error)) error {
|
||||
rng := collections.NewPrefixedTripleRange[AccAddress, ValAddress, ValAddress](delegator)
|
||||
return k.Redelegations.Walk(ctx, rng, func(key collections.Triple[AccAddress, ValAddress, ValAddress]) (stop bool, err error) {
|
||||
return onResult(key.K2(), key.K3())
|
||||
})
|
||||
}
|
||||
|
||||
// RedelegationsByDelegatorAndValidator iterates over all the redelegations of a given delegator and its source validator and calls onResult for each
|
||||
// destination validator.
|
||||
func (k Keeper) RedelegationsByDelegatorAndValidator(ctx context.Context, delegator AccAddress, validator ValAddress, onResult func(dst ValAddress) (stop bool, err error)) error {
|
||||
rng := collections.NewSuperPrefixedTripleRange[AccAddress, ValAddress, ValAddress](delegator, validator)
|
||||
return k.Redelegations.Walk(ctx, rng, func(key collections.Triple[AccAddress, ValAddress, ValAddress]) (stop bool, err error) {
|
||||
return onResult(key.K3())
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
## Advanced Usages
|
||||
|
||||
### Alternative Value Codec
|
||||
|
||||
The `codec.AltValueCodec` allows a collection to decode values using a different codec than the one used to encode them.
|
||||
Basically it enables to decode two different byte representations of the same concrete value.
|
||||
It can be used to lazily migrate values from one bytes representation to another, as long as the new representation is
|
||||
not able to decode the old one.
|
||||
|
||||
A concrete example can be found in `x/bank` where the balance was initially stored as `Coin` and then migrated to `Int`.
|
||||
|
||||
```go
|
||||
|
||||
var BankBalanceValueCodec = codec.NewAltValueCodec(sdk.IntValue, func(b []byte) (sdk.Int, error) {
|
||||
coin := sdk.Coin{}
|
||||
err := coin.Unmarshal(b)
|
||||
if err != nil {
|
||||
return sdk.Int{}, err
|
||||
}
|
||||
return coin.Amount, nil
|
||||
})
|
||||
```
|
||||
|
||||
The above example shows how to create an `AltValueCodec` that can decode both `sdk.Int` and `sdk.Coin` values. The provided
|
||||
decoder function will be used as a fallback in case the default decoder fails. When the value will be encoded back into state
|
||||
it will use the default encoder. This allows to lazily migrate values to a new bytes representation.
|
||||
|
||||
47
collections/codec/alternative_value.go
Normal file
47
collections/codec/alternative_value.go
Normal file
@ -0,0 +1,47 @@
|
||||
package codec
|
||||
|
||||
// NewAltValueCodec returns a new AltValueCodec. canonicalValueCodec is the codec that you want the value
|
||||
// to be encoded and decoded as, alternativeDecoder is a function that will attempt to decode the value
|
||||
// in case the canonicalValueCodec fails to decode it.
|
||||
func NewAltValueCodec[V any](canonicalValueCodec ValueCodec[V], alternativeDecoder func([]byte) (V, error)) ValueCodec[V] {
|
||||
return AltValueCodec[V]{
|
||||
canonicalValueCodec: canonicalValueCodec,
|
||||
alternativeDecoder: alternativeDecoder,
|
||||
}
|
||||
}
|
||||
|
||||
// AltValueCodec is a codec that can decode a value from state in an alternative format.
|
||||
// This is useful for migrating data from one format to another. For example, in x/bank
|
||||
// balances were initially encoded as sdk.Coin, now they are encoded as math.Int.
|
||||
// The AltValueCodec will be trying to decode the value as math.Int, and if that fails,
|
||||
// it will attempt to decode it as sdk.Coin.
|
||||
// NOTE: if the canonical format can also decode the alternative format, then this codec
|
||||
// will produce undefined and undesirable behavior.
|
||||
type AltValueCodec[V any] struct {
|
||||
canonicalValueCodec ValueCodec[V]
|
||||
alternativeDecoder func([]byte) (V, error)
|
||||
}
|
||||
|
||||
// Decode will attempt to decode the value from state using the canonical value codec.
|
||||
// If it fails to decode, it will attempt to decode the value using the alternative decoder.
|
||||
func (a AltValueCodec[V]) Decode(b []byte) (V, error) {
|
||||
v, err := a.canonicalValueCodec.Decode(b)
|
||||
if err != nil {
|
||||
return a.alternativeDecoder(b)
|
||||
}
|
||||
return v, nil
|
||||
}
|
||||
|
||||
// Below there is the implementation of ValueCodec relying on the canonical value codec.
|
||||
|
||||
func (a AltValueCodec[V]) Encode(value V) ([]byte, error) { return a.canonicalValueCodec.Encode(value) }
|
||||
|
||||
func (a AltValueCodec[V]) EncodeJSON(value V) ([]byte, error) {
|
||||
return a.canonicalValueCodec.EncodeJSON(value)
|
||||
}
|
||||
|
||||
func (a AltValueCodec[V]) DecodeJSON(b []byte) (V, error) { return a.canonicalValueCodec.DecodeJSON(b) }
|
||||
|
||||
func (a AltValueCodec[V]) Stringify(value V) string { return a.canonicalValueCodec.Stringify(value) }
|
||||
|
||||
func (a AltValueCodec[V]) ValueType() string { return a.canonicalValueCodec.ValueType() }
|
||||
53
collections/codec/alternative_value_test.go
Normal file
53
collections/codec/alternative_value_test.go
Normal file
@ -0,0 +1,53 @@
|
||||
package codec_test
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"cosmossdk.io/collections/codec"
|
||||
"cosmossdk.io/collections/colltest"
|
||||
)
|
||||
|
||||
type altValue struct {
|
||||
Value uint64 `json:"value"`
|
||||
}
|
||||
|
||||
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]())
|
||||
alternative := func(v []byte) (uint64, error) {
|
||||
var alt altValue
|
||||
err := json.Unmarshal(v, &alt)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return alt.Value, nil
|
||||
}
|
||||
|
||||
cdc := codec.NewAltValueCodec(canonical, alternative)
|
||||
|
||||
t.Run("decodes alternative value", func(t *testing.T) {
|
||||
expected := uint64(100)
|
||||
alternativeEncodedBytes, err := json.Marshal(altValue{Value: expected})
|
||||
require.NoError(t, err)
|
||||
got, err := cdc.Decode(alternativeEncodedBytes)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, expected, got)
|
||||
})
|
||||
|
||||
t.Run("decodes canonical value", func(t *testing.T) {
|
||||
expected := uint64(100)
|
||||
canonicalEncodedBytes, err := cdc.Encode(expected)
|
||||
require.NoError(t, err)
|
||||
got, err := cdc.Decode(canonicalEncodedBytes)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, expected, got)
|
||||
})
|
||||
|
||||
t.Run("conformance", func(t *testing.T) {
|
||||
colltest.TestValueCodec(t, cdc, uint64(100))
|
||||
})
|
||||
}
|
||||
@ -6,7 +6,7 @@ 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{}
|
||||
|
||||
@ -33,7 +33,7 @@ func (b boolKey[T]) Decode(buffer []byte) (int, T, error) {
|
||||
}
|
||||
}
|
||||
|
||||
func (b boolKey[T]) Size(key T) int { return 1 }
|
||||
func (b boolKey[T]) Size(_ T) int { return 1 }
|
||||
|
||||
func (b boolKey[T]) EncodeJSON(value T) ([]byte, error) {
|
||||
return json.Marshal(value)
|
||||
@ -64,3 +64,7 @@ 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) KeyCodec[T] {
|
||||
return NamedKeyCodec[T]{KeyCodec: b, Name: name}
|
||||
}
|
||||
|
||||
@ -10,7 +10,7 @@ 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{}
|
||||
|
||||
@ -77,3 +77,7 @@ 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) KeyCodec[T] {
|
||||
return NamedKeyCodec[T]{KeyCodec: b, Name: name}
|
||||
}
|
||||
|
||||
@ -125,7 +125,9 @@ 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 {
|
||||
@ -167,3 +169,7 @@ 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) ValueCodec[K] {
|
||||
return NamedValueCodec[K]{ValueCodec: k, Name: name}
|
||||
}
|
||||
|
||||
@ -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)
|
||||
|
||||
102
collections/codec/indexing.go
Normal file
102
collections/codec/indexing.go
Normal file
@ -0,0 +1,102 @@
|
||||
package codec
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"cosmossdk.io/schema"
|
||||
)
|
||||
|
||||
// HasSchemaCodec is an interface that all codec's should implement in order
|
||||
// to properly support indexing. It is not required by KeyCodec or ValueCodec
|
||||
// in order to preserve backwards compatibility, but a future version of collections
|
||||
// may make it required and all codec's should aim to implement it. If it is not
|
||||
// implemented, fallback defaults will be used for indexing that may be sub-optimal.
|
||||
//
|
||||
// Implementations of HasSchemaCodec should test that they are conformant using
|
||||
// schema.ValidateObjectKey or schema.ValidateObjectValue depending on whether
|
||||
// the codec is a KeyCodec or ValueCodec respectively.
|
||||
type HasSchemaCodec[T any] interface {
|
||||
// SchemaCodec returns the schema codec for the collections codec.
|
||||
SchemaCodec() (SchemaCodec[T], error)
|
||||
}
|
||||
|
||||
// SchemaCodec is a codec that supports converting collection codec values to and
|
||||
// from schema codec values.
|
||||
type SchemaCodec[T any] struct {
|
||||
// Fields are the schema fields that the codec represents. If this is empty,
|
||||
// it will be assumed that this codec represents no value (such as an item key
|
||||
// or key set value).
|
||||
Fields []schema.Field
|
||||
|
||||
// ToSchemaType converts a codec value of type T to a value corresponding to
|
||||
// a schema object key or value (depending on whether this is a key or value
|
||||
// codec). The returned value should pass validation with schema.ValidateObjectKey
|
||||
// or schema.ValidateObjectValue with the fields specified in Fields.
|
||||
// If this function is nil, it will be assumed that T already represents a
|
||||
// value that conforms to a schema value without any further conversion.
|
||||
ToSchemaType func(T) (any, error)
|
||||
|
||||
// FromSchemaType converts a schema object key or value to T.
|
||||
// If this function is nil, it will be assumed that T already represents a
|
||||
// value that conforms to a schema value without any further conversion.
|
||||
FromSchemaType func(any) (T, error)
|
||||
}
|
||||
|
||||
// KeySchemaCodec gets the schema codec for the provided KeyCodec either
|
||||
// by casting to HasSchemaCodec or returning a fallback codec.
|
||||
func KeySchemaCodec[K any](cdc KeyCodec[K]) (SchemaCodec[K], error) {
|
||||
if indexable, ok := cdc.(HasSchemaCodec[K]); ok {
|
||||
return indexable.SchemaCodec()
|
||||
} else {
|
||||
return FallbackSchemaCodec[K](), nil
|
||||
}
|
||||
}
|
||||
|
||||
// ValueSchemaCodec gets the schema codec for the provided ValueCodec either
|
||||
// by casting to HasSchemaCodec or returning a fallback codec.
|
||||
func ValueSchemaCodec[V any](cdc ValueCodec[V]) (SchemaCodec[V], error) {
|
||||
if indexable, ok := cdc.(HasSchemaCodec[V]); ok {
|
||||
return indexable.SchemaCodec()
|
||||
} else {
|
||||
return FallbackSchemaCodec[V](), nil
|
||||
}
|
||||
}
|
||||
|
||||
// FallbackSchemaCodec returns a fallback schema codec for T when one isn't explicitly
|
||||
// specified with HasSchemaCodec. It maps all simple types directly to schema kinds
|
||||
// and converts everything else to JSON String.
|
||||
func FallbackSchemaCodec[T any]() SchemaCodec[T] {
|
||||
var t T
|
||||
kind := schema.KindForGoValue(t)
|
||||
if err := kind.Validate(); err == nil {
|
||||
return SchemaCodec[T]{
|
||||
Fields: []schema.Field{{
|
||||
// we don't set any name so that this can be set to a good default by the caller
|
||||
Name: "",
|
||||
Kind: kind,
|
||||
}},
|
||||
// these can be nil because T maps directly to a schema value for this kind
|
||||
ToSchemaType: nil,
|
||||
FromSchemaType: nil,
|
||||
}
|
||||
} else {
|
||||
// we default to encoding everything to JSON String
|
||||
return SchemaCodec[T]{
|
||||
Fields: []schema.Field{{Kind: schema.StringKind}},
|
||||
ToSchemaType: func(t T) (any, error) {
|
||||
bz, err := json.Marshal(t)
|
||||
return string(json.RawMessage(bz)), err
|
||||
},
|
||||
FromSchemaType: func(a any) (T, error) {
|
||||
var t T
|
||||
sz, ok := a.(string)
|
||||
if !ok {
|
||||
return t, fmt.Errorf("expected string, got %T", a)
|
||||
}
|
||||
err := json.Unmarshal([]byte(sz), &t)
|
||||
return t, err
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -7,7 +7,7 @@ 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{}
|
||||
|
||||
@ -64,7 +64,11 @@ func (i int64Key[T]) SizeNonTerminal(_ T) int {
|
||||
return 8
|
||||
}
|
||||
|
||||
func NewInt32Key[T ~int32]() KeyCodec[T] {
|
||||
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]{}
|
||||
}
|
||||
|
||||
@ -121,3 +125,7 @@ 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) KeyCodec[T] {
|
||||
return NamedKeyCodec[T]{KeyCodec: i, Name: name}
|
||||
}
|
||||
|
||||
63
collections/codec/naming.go
Normal file
63
collections/codec/naming.go
Normal file
@ -0,0 +1,63 @@
|
||||
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) KeyCodec[T]
|
||||
}
|
||||
|
||||
// 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) 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 {
|
||||
return SchemaCodec[T]{}, err
|
||||
}
|
||||
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 {
|
||||
return SchemaCodec[T]{}, err
|
||||
}
|
||||
return withName(cdc, n.Name)
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
@ -6,7 +6,7 @@ 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.
|
||||
@ -66,3 +66,7 @@ func (stringKey[T]) Stringify(key T) string {
|
||||
func (stringKey[T]) KeyType() string {
|
||||
return "string"
|
||||
}
|
||||
|
||||
func (s stringKey[T]) WithName(name string) KeyCodec[T] {
|
||||
return NamedKeyCodec[T]{KeyCodec: s, Name: name}
|
||||
}
|
||||
|
||||
@ -7,7 +7,7 @@ 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{}
|
||||
|
||||
@ -55,7 +55,11 @@ func (uint64Key[T]) KeyType() string {
|
||||
return "uint64"
|
||||
}
|
||||
|
||||
func NewUint32Key[T ~uint32]() KeyCodec[T] { return uint32Key[T]{} }
|
||||
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{}
|
||||
|
||||
@ -95,7 +99,11 @@ 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) KeyCodec[T] {
|
||||
return NamedKeyCodec[T]{KeyCodec: u, Name: name}
|
||||
}
|
||||
|
||||
func NewUint16Key[T ~uint16]() NameableKeyCodec[T] { return uint16Key[T]{} }
|
||||
|
||||
type uint16Key[T ~uint16] struct{}
|
||||
|
||||
@ -135,6 +143,10 @@ 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) KeyCodec[T] {
|
||||
return NamedKeyCodec[T]{KeyCodec: u, Name: name}
|
||||
}
|
||||
|
||||
func uintEncodeJSON(value uint64) ([]byte, error) {
|
||||
str := `"` + strconv.FormatUint(value, 10) + `"`
|
||||
return []byte(str), nil
|
||||
|
||||
@ -3,10 +3,11 @@ package collections
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
io "io"
|
||||
"io"
|
||||
"math"
|
||||
|
||||
"cosmossdk.io/collections/codec"
|
||||
"cosmossdk.io/schema"
|
||||
)
|
||||
|
||||
var (
|
||||
@ -29,7 +30,7 @@ var (
|
||||
Uint64Key = codec.NewUint64Key[uint64]()
|
||||
// Int32Key can be used to encode int32 keys. Encoding retains ordering by toggling the MSB.
|
||||
Int32Key = codec.NewInt32Key[int32]()
|
||||
// Int64Key can be used to encode int64. Encoding retains ordering by toggling the MSB.
|
||||
// Int64Key can be used to encode int64 keys. Encoding retains ordering by toggling the MSB.
|
||||
Int64Key = codec.NewInt64Key[int64]()
|
||||
// StringKey can be used to encode string keys. The encoding just converts the string
|
||||
// to bytes.
|
||||
@ -90,6 +91,22 @@ type Collection interface {
|
||||
ValueCodec() codec.UntypedValueCodec
|
||||
|
||||
genesisHandler
|
||||
|
||||
// collectionSchemaCodec returns the schema codec for this collection.
|
||||
schemaCodec() (*collectionSchemaCodec, error)
|
||||
|
||||
// isSecondaryIndex indicates that this collection represents a secondary index
|
||||
// in the schema and should be excluded from the module's user facing schema.
|
||||
isSecondaryIndex() bool
|
||||
}
|
||||
|
||||
// collectionSchemaCodec maps a collection to a schema object type and provides
|
||||
// decoders and encoders to and from schema values and raw kv-store bytes.
|
||||
type collectionSchemaCodec struct {
|
||||
coll Collection
|
||||
objectType schema.StateObjectType
|
||||
keyDecoder func([]byte) (any, error)
|
||||
valueDecoder func([]byte) (any, error)
|
||||
}
|
||||
|
||||
// Prefix defines a segregation bytes namespace for specific collections objects.
|
||||
@ -157,3 +174,5 @@ func (c collectionImpl[K, V]) exportGenesis(ctx context.Context, w io.Writer) er
|
||||
}
|
||||
|
||||
func (c collectionImpl[K, V]) defaultGenesis(w io.Writer) error { return c.m.defaultGenesis(w) }
|
||||
|
||||
func (c collectionImpl[K, V]) isSecondaryIndex() bool { return c.m.isSecondaryIndex }
|
||||
|
||||
@ -5,49 +5,16 @@ import (
|
||||
"math"
|
||||
"testing"
|
||||
|
||||
db "github.com/cosmos/cosmos-db"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"cosmossdk.io/core/store"
|
||||
"cosmossdk.io/core/testing"
|
||||
)
|
||||
|
||||
type testStore struct {
|
||||
db db.DB
|
||||
}
|
||||
|
||||
func (t testStore) OpenKVStore(ctx context.Context) store.KVStore {
|
||||
return t
|
||||
}
|
||||
|
||||
func (t testStore) Get(key []byte) ([]byte, error) {
|
||||
return t.db.Get(key)
|
||||
}
|
||||
|
||||
func (t testStore) Has(key []byte) (bool, error) {
|
||||
return t.db.Has(key)
|
||||
}
|
||||
|
||||
func (t testStore) Set(key, value []byte) error {
|
||||
return t.db.Set(key, value)
|
||||
}
|
||||
|
||||
func (t testStore) Delete(key []byte) error {
|
||||
return t.db.Delete(key)
|
||||
}
|
||||
|
||||
func (t testStore) Iterator(start, end []byte) (store.Iterator, error) {
|
||||
return t.db.Iterator(start, end)
|
||||
}
|
||||
|
||||
func (t testStore) ReverseIterator(start, end []byte) (store.Iterator, error) {
|
||||
return t.db.ReverseIterator(start, end)
|
||||
}
|
||||
|
||||
var _ store.KVStore = testStore{}
|
||||
|
||||
func deps() (store.KVStoreService, context.Context) {
|
||||
kv := db.NewMemDB()
|
||||
return &testStore{kv}, context.Background()
|
||||
ctx := coretesting.Context()
|
||||
kv := coretesting.KVStoreService(ctx, "test")
|
||||
return kv, ctx
|
||||
}
|
||||
|
||||
func TestPrefix(t *testing.T) {
|
||||
|
||||
@ -40,6 +40,11 @@ func TestKeyCodec[T any](t *testing.T, keyCodec codec.KeyCodec[T], key T) {
|
||||
decoded, err := keyCodec.DecodeJSON(keyJSON)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, key, decoded, "json encoding and decoding did not produce the same results")
|
||||
|
||||
// check type
|
||||
require.NotEmpty(t, keyCodec.KeyType())
|
||||
// check string
|
||||
_ = keyCodec.Stringify(key)
|
||||
}
|
||||
|
||||
// TestValueCodec asserts the correct behavior of a ValueCodec over the type T.
|
||||
@ -119,7 +124,7 @@ func (m mockValueCodec[T]) Decode(b []byte) (t T, err error) {
|
||||
|
||||
typ, exists := m.seenTypes[wrappedValue.TypeName]
|
||||
if !exists {
|
||||
return t, fmt.Errorf("uknown type %s, you're dealing with interfaces... in order to make the interface types known for the MockValueCodec, you need to first encode them", wrappedValue.TypeName)
|
||||
return t, fmt.Errorf("unknown type %s, you're dealing with interfaces... in order to make the interface types known for the MockValueCodec, you need to first encode them", wrappedValue.TypeName)
|
||||
}
|
||||
|
||||
newT := reflect.New(typ).Interface()
|
||||
|
||||
@ -32,7 +32,7 @@ func (m Map[K, V]) importGenesis(ctx context.Context, reader io.Reader) error {
|
||||
}
|
||||
|
||||
func (m Map[K, V]) exportGenesis(ctx context.Context, writer io.Writer) error {
|
||||
_, err := writer.Write([]byte("["))
|
||||
_, err := io.WriteString(writer, "[")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -48,7 +48,7 @@ func (m Map[K, V]) exportGenesis(ctx context.Context, writer io.Writer) error {
|
||||
// add a comma before encoding the object
|
||||
// for all objects besides the first one.
|
||||
if !first {
|
||||
_, err = writer.Write([]byte(","))
|
||||
_, err = io.WriteString(writer, ",")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -91,7 +91,7 @@ func (m Map[K, V]) exportGenesis(ctx context.Context, writer io.Writer) error {
|
||||
}
|
||||
}
|
||||
|
||||
_, err = writer.Write([]byte("]"))
|
||||
_, err = io.WriteString(writer, "]")
|
||||
return err
|
||||
}
|
||||
|
||||
@ -148,6 +148,6 @@ func (m Map[K, V]) doDecodeJSON(reader io.Reader, onEntry func(key K, value V) e
|
||||
}
|
||||
|
||||
func (m Map[K, V]) defaultGenesis(writer io.Writer) error {
|
||||
_, err := writer.Write([]byte(`[]`))
|
||||
_, err := io.WriteString(writer, `[]`)
|
||||
return err
|
||||
}
|
||||
|
||||
@ -91,6 +91,7 @@ type testFixture struct {
|
||||
}
|
||||
|
||||
func initFixture(t *testing.T) *testFixture {
|
||||
t.Helper()
|
||||
sk, ctx := deps()
|
||||
schemaBuilder := NewSchemaBuilder(sk)
|
||||
m := NewMap(schemaBuilder, NewPrefix(1), "map", StringKey, Uint64Value)
|
||||
@ -110,6 +111,7 @@ func initFixture(t *testing.T) *testFixture {
|
||||
}
|
||||
|
||||
func createTestGenesisSource(t *testing.T) appmodule.GenesisSource {
|
||||
t.Helper()
|
||||
expectedOrder := []string{"item", "key_set", "map", "sequence"}
|
||||
currentIndex := 0
|
||||
return func(field string) (io.ReadCloser, error) {
|
||||
@ -149,6 +151,7 @@ func (b *bufCloser) Close() error {
|
||||
}
|
||||
|
||||
func newBufCloser(t *testing.T, str string) *bufCloser {
|
||||
t.Helper()
|
||||
b := &bufCloser{
|
||||
Buffer: bytes.NewBufferString(str),
|
||||
closed: false,
|
||||
|
||||
@ -1,53 +1,52 @@
|
||||
module cosmossdk.io/collections
|
||||
|
||||
go 1.23
|
||||
go 1.23.2
|
||||
|
||||
require (
|
||||
cosmossdk.io/core v0.11.0
|
||||
github.com/cosmos/cosmos-db v1.0.2
|
||||
github.com/stretchr/testify v1.9.0
|
||||
cosmossdk.io/core v1.0.0
|
||||
cosmossdk.io/core/testing v0.0.2
|
||||
cosmossdk.io/schema v1.0.0
|
||||
github.com/cosmos/cosmos-db v1.1.1
|
||||
github.com/cosmos/gogoproto v1.7.0
|
||||
github.com/google/go-cmp v0.6.0
|
||||
github.com/stretchr/testify v1.10.0
|
||||
github.com/tidwall/btree v1.7.0
|
||||
google.golang.org/protobuf v1.36.4
|
||||
pgregory.net/rapid v1.1.0
|
||||
)
|
||||
|
||||
require (
|
||||
cosmossdk.io/api v0.7.5 // indirect
|
||||
cosmossdk.io/depinject v1.0.0-alpha.4 // indirect
|
||||
github.com/DataDog/zstd v1.5.5 // indirect
|
||||
github.com/DataDog/zstd v1.4.5 // indirect
|
||||
github.com/beorn7/perks v1.0.1 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.2.0 // indirect
|
||||
github.com/cockroachdb/errors v1.11.1 // indirect
|
||||
github.com/cockroachdb/errors v1.11.3 // indirect
|
||||
github.com/cockroachdb/fifo v0.0.0-20240606204812-0bbfbd93a7ce // indirect
|
||||
github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b // indirect
|
||||
github.com/cockroachdb/pebble v1.1.0 // indirect
|
||||
github.com/cockroachdb/pebble v1.1.2 // indirect
|
||||
github.com/cockroachdb/redact v1.1.5 // indirect
|
||||
github.com/cockroachdb/tokenbucket v0.0.0-20230807174530-cc333fc44b06 // indirect
|
||||
github.com/cosmos/cosmos-proto v1.0.0-beta.5 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/fsnotify/fsnotify v1.6.0 // indirect
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
|
||||
github.com/getsentry/sentry-go v0.27.0 // indirect
|
||||
github.com/gogo/protobuf v1.3.2 // indirect
|
||||
github.com/golang/protobuf v1.5.4 // indirect
|
||||
github.com/golang/snappy v0.0.4 // indirect
|
||||
github.com/google/btree v1.1.2 // indirect
|
||||
github.com/klauspost/compress v1.17.7 // indirect
|
||||
github.com/google/btree v1.1.3 // indirect
|
||||
github.com/klauspost/compress v1.16.0 // indirect
|
||||
github.com/kr/pretty v0.3.1 // indirect
|
||||
github.com/kr/text v0.2.0 // indirect
|
||||
github.com/linxGnu/grocksdb v1.8.12 // indirect
|
||||
github.com/onsi/gomega v1.20.0 // indirect
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/prometheus/client_golang v1.18.0 // indirect
|
||||
github.com/prometheus/client_model v0.6.0 // indirect
|
||||
github.com/prometheus/common v0.47.0 // indirect
|
||||
github.com/prometheus/procfs v0.12.0 // indirect
|
||||
github.com/rogpeppe/go-internal v1.12.0 // indirect
|
||||
github.com/spf13/cast v1.6.0 // indirect
|
||||
github.com/syndtr/goleveldb v1.0.1-0.20220721030215-126854af5e6d // indirect
|
||||
golang.org/x/exp v0.0.0-20240222234643-814bf88cf225 // indirect
|
||||
golang.org/x/net v0.20.0 // indirect
|
||||
golang.org/x/sys v0.17.0 // indirect
|
||||
golang.org/x/text v0.14.0 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20231009173412-8bfb1ae86b6c // indirect
|
||||
google.golang.org/grpc v1.58.3 // indirect
|
||||
google.golang.org/protobuf v1.33.0 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
|
||||
github.com/prometheus/client_golang v1.12.0 // indirect
|
||||
github.com/prometheus/client_model v0.2.1-0.20210607210712-147c58e9608a // indirect
|
||||
github.com/prometheus/common v0.32.1 // indirect
|
||||
github.com/prometheus/procfs v0.7.3 // indirect
|
||||
github.com/rogpeppe/go-internal v1.9.0 // indirect
|
||||
github.com/spf13/cast v1.7.1 // indirect
|
||||
github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 // indirect
|
||||
golang.org/x/exp v0.0.0-20230626212559-97b1e661b5df // indirect
|
||||
golang.org/x/sys v0.29.0 // indirect
|
||||
golang.org/x/text v0.21.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
|
||||
@ -1,218 +1,582 @@
|
||||
cosmossdk.io/api v0.7.5 h1:eMPTReoNmGUm8DeiQL9DyM8sYDjEhWzL1+nLbI9DqtQ=
|
||||
cosmossdk.io/api v0.7.5/go.mod h1:IcxpYS5fMemZGqyYtErK7OqvdM0C8kdW3dq8Q/XIG38=
|
||||
cosmossdk.io/core v0.11.0 h1:vtIafqUi+1ZNAE/oxLOQQ7Oek2n4S48SWLG8h/+wdbo=
|
||||
cosmossdk.io/core v0.11.0/go.mod h1:LaTtayWBSoacF5xNzoF8tmLhehqlA9z1SWiPuNC6X1w=
|
||||
cosmossdk.io/depinject v1.0.0-alpha.4 h1:PLNp8ZYAMPTUKyG9IK2hsbciDWqna2z1Wsl98okJopc=
|
||||
cosmossdk.io/depinject v1.0.0-alpha.4/go.mod h1:HeDk7IkR5ckZ3lMGs/o91AVUc7E596vMaOmslGFM3yU=
|
||||
github.com/DataDog/zstd v1.5.5 h1:oWf5W7GtOLgp6bciQYDmhHHjdhYkALu6S/5Ni9ZgSvQ=
|
||||
github.com/DataDog/zstd v1.5.5/go.mod h1:g4AWEaM3yOg3HYfnJ3YIawPnVdXJh9QME85blwSAmyw=
|
||||
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
|
||||
cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
|
||||
cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
|
||||
cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
|
||||
cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
|
||||
cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To=
|
||||
cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4=
|
||||
cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M=
|
||||
cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc=
|
||||
cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk=
|
||||
cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs=
|
||||
cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc=
|
||||
cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY=
|
||||
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
|
||||
cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
|
||||
cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=
|
||||
cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg=
|
||||
cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc=
|
||||
cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ=
|
||||
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
|
||||
cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
|
||||
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
|
||||
cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=
|
||||
cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA=
|
||||
cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU=
|
||||
cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
|
||||
cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=
|
||||
cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
|
||||
cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
|
||||
cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
|
||||
cosmossdk.io/core v1.0.0 h1:e7XBbISOytLBOXMVwpRPixThXqEkeLGlg8no/qpgS8U=
|
||||
cosmossdk.io/core v1.0.0/go.mod h1:mKIp3RkoEmtqdEdFHxHwWAULRe+79gfdOvmArrLDbDc=
|
||||
cosmossdk.io/core/testing v0.0.2 h1:TXAmsnHa1C5lLiVN1z+rnkiyNS6sNwcGJCb6OouaNv8=
|
||||
cosmossdk.io/core/testing v0.0.2/go.mod h1:6Y3QaoNXsgeHiV4+9oYF/IqMFqCK5cJClonw/OXxHlY=
|
||||
cosmossdk.io/schema v1.0.0 h1:/diH4XJjpV1JQwuIozwr+A4uFuuwanFdnw2kKeiXwwQ=
|
||||
cosmossdk.io/schema v1.0.0/go.mod h1:RDAhxIeNB4bYqAlF4NBJwRrgtnciMcyyg0DOKnhNZQQ=
|
||||
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
||||
github.com/DataDog/zstd v1.4.5 h1:EndNeuB0l9syBZhut0wns3gV1hL8zX8LIu6ZiVHWLIQ=
|
||||
github.com/DataDog/zstd v1.4.5/go.mod h1:1jcaCB/ufaK+sKp1NBhlGmpz41jOoPQ35bpF36t7BBo=
|
||||
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
||||
github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
||||
github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho=
|
||||
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
|
||||
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
|
||||
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
||||
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
|
||||
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
||||
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
|
||||
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
|
||||
github.com/cockroachdb/datadriven v1.0.3-0.20230413201302-be42291fc80f h1:otljaYPt5hWxV3MUfO5dFPFiOXg9CyG5/kCfayTqsJ4=
|
||||
github.com/cockroachdb/datadriven v1.0.3-0.20230413201302-be42291fc80f/go.mod h1:a9RdTaap04u637JoCzcUoIcDmvwSUtcUFtT/C3kJlTU=
|
||||
github.com/cockroachdb/errors v1.11.1 h1:xSEW75zKaKCWzR3OfxXUxgrk/NtT4G1MiOv5lWZazG8=
|
||||
github.com/cockroachdb/errors v1.11.1/go.mod h1:8MUxA3Gi6b25tYlFEBGLf+D8aISL+M4MIpiWMSNRfxw=
|
||||
github.com/cockroachdb/errors v1.11.3 h1:5bA+k2Y6r+oz/6Z/RFlNeVCesGARKuC6YymtcDrbC/I=
|
||||
github.com/cockroachdb/errors v1.11.3/go.mod h1:m4UIW4CDjx+R5cybPsNrRbreomiFqt8o1h1wUVazSd8=
|
||||
github.com/cockroachdb/fifo v0.0.0-20240606204812-0bbfbd93a7ce h1:giXvy4KSc/6g/esnpM7Geqxka4WSqI1SZc7sMJFd3y4=
|
||||
github.com/cockroachdb/fifo v0.0.0-20240606204812-0bbfbd93a7ce/go.mod h1:9/y3cnZ5GKakj/H4y9r9GTjCvAFta7KLgSHPJJYc52M=
|
||||
github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b h1:r6VH0faHjZeQy818SGhaone5OnYfxFR/+AzdY3sf5aE=
|
||||
github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b/go.mod h1:Vz9DsVWQQhf3vs21MhPMZpMGSht7O/2vFW2xusFUVOs=
|
||||
github.com/cockroachdb/pebble v1.1.0 h1:pcFh8CdCIt2kmEpK0OIatq67Ln9uGDYY3d5XnE0LJG4=
|
||||
github.com/cockroachdb/pebble v1.1.0/go.mod h1:sEHm5NOXxyiAoKWhoFxT8xMgd/f3RA6qUqQ1BXKrh2E=
|
||||
github.com/cockroachdb/pebble v1.1.2 h1:CUh2IPtR4swHlEj48Rhfzw6l/d0qA31fItcIszQVIsA=
|
||||
github.com/cockroachdb/pebble v1.1.2/go.mod h1:4exszw1r40423ZsmkG/09AFEG83I0uDgfujJdbL6kYU=
|
||||
github.com/cockroachdb/redact v1.1.5 h1:u1PMllDkdFfPWaNGMyLD1+so+aq3uUItthCFqzwPJ30=
|
||||
github.com/cockroachdb/redact v1.1.5/go.mod h1:BVNblN9mBWFyMyqK1k3AAiSxhvhfK2oOZZ2lK+dpvRg=
|
||||
github.com/cockroachdb/tokenbucket v0.0.0-20230807174530-cc333fc44b06 h1:zuQyyAKVxetITBuuhv3BI9cMrmStnpT18zmgmTxunpo=
|
||||
github.com/cockroachdb/tokenbucket v0.0.0-20230807174530-cc333fc44b06/go.mod h1:7nc4anLGjupUW/PeY5qiNYsdNXj7zopG+eqsS7To5IQ=
|
||||
github.com/cosmos/cosmos-db v1.0.2 h1:hwMjozuY1OlJs/uh6vddqnk9j7VamLv+0DBlbEXbAKs=
|
||||
github.com/cosmos/cosmos-db v1.0.2/go.mod h1:Z8IXcFJ9PqKK6BIsVOB3QXtkKoqUOp1vRvPT39kOXEA=
|
||||
github.com/cosmos/cosmos-proto v1.0.0-beta.5 h1:eNcayDLpip+zVLRLYafhzLvQlSmyab+RC5W7ZfmxJLA=
|
||||
github.com/cosmos/cosmos-proto v1.0.0-beta.5/go.mod h1:hQGLpiIUloJBMdQMMWb/4wRApmI9hjHH05nefC0Ojec=
|
||||
github.com/cosmos/cosmos-db v1.1.1 h1:FezFSU37AlBC8S98NlSagL76oqBRWq/prTPvFcEJNCM=
|
||||
github.com/cosmos/cosmos-db v1.1.1/go.mod h1:AghjcIPqdhSLP/2Z0yha5xPH3nLnskz81pBx3tcVSAw=
|
||||
github.com/cosmos/gogoproto v1.7.0 h1:79USr0oyXAbxg3rspGh/m4SWNyoz/GLaAh0QlCe2fro=
|
||||
github.com/cosmos/gogoproto v1.7.0/go.mod h1:yWChEv5IUEYURQasfyBW5ffkMHR/90hiHgbNgrtp4j0=
|
||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
|
||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
|
||||
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
|
||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
|
||||
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
|
||||
github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU=
|
||||
github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY=
|
||||
github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw=
|
||||
github.com/getsentry/sentry-go v0.27.0 h1:Pv98CIbtB3LkMWmXi4Joa5OOcwbmnX88sF5qbK3r3Ps=
|
||||
github.com/getsentry/sentry-go v0.27.0/go.mod h1:lc76E2QywIyW8WuBnwl8Lc4bkmQH4+w1gwTf25trprY=
|
||||
github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA=
|
||||
github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og=
|
||||
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
|
||||
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
|
||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||
github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY=
|
||||
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
|
||||
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
|
||||
github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
|
||||
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
|
||||
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
|
||||
github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
|
||||
github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
|
||||
github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
|
||||
github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
|
||||
github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
|
||||
github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=
|
||||
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
|
||||
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
|
||||
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
|
||||
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
|
||||
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
|
||||
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
|
||||
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
|
||||
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
|
||||
github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
|
||||
github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||
github.com/google/btree v1.1.2 h1:xf4v41cLI2Z6FxbKm+8Bu+m8ifhj15JuZ9sa0jZCMUU=
|
||||
github.com/google/btree v1.1.2/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4=
|
||||
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||
github.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg=
|
||||
github.com/google/btree v1.1.3/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4=
|
||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
|
||||
github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
|
||||
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
||||
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
||||
github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||
github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||
github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||
github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||
github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
||||
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
|
||||
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
|
||||
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
||||
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
||||
github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=
|
||||
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
||||
github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||
github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
|
||||
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
|
||||
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
|
||||
github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
|
||||
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
|
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
github.com/klauspost/compress v1.17.7 h1:ehO88t2UGzQK66LMdE8tibEd1ErmzZjNEqWkjLAKQQg=
|
||||
github.com/klauspost/compress v1.17.7/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
|
||||
github.com/klauspost/compress v1.16.0 h1:iULayQNOReoYUe+1qtKOqw9CwJv3aNQu8ivo7lw1HU4=
|
||||
github.com/klauspost/compress v1.16.0/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/linxGnu/grocksdb v1.8.12 h1:1/pCztQUOa3BX/1gR3jSZDoaKFpeHFvQ1XrqZpSvZVo=
|
||||
github.com/linxGnu/grocksdb v1.8.12/go.mod h1:xZCIb5Muw+nhbDK4Y5UJuOrin5MceOuiXkVUR7vp4WY=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 h1:I0XW9+e1XWDxdcEniV4rQAIOPUGDq67JSCiRCgGCZLI=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
||||
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
||||
github.com/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78=
|
||||
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
|
||||
github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
|
||||
github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
|
||||
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
|
||||
github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0=
|
||||
github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE=
|
||||
github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU=
|
||||
github.com/onsi/ginkgo/v2 v2.1.3/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c=
|
||||
github.com/onsi/ginkgo v1.14.0 h1:2mOpI4JVVPBN+WQRa0WKH2eXR+Ey+uK4n7Zj0aYpIQA=
|
||||
github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY=
|
||||
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
|
||||
github.com/onsi/gomega v1.10.1 h1:o0+MgICZLuZ7xjH7Vx6zS/zcu93/BEp1VwkIW1mEXCE=
|
||||
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
|
||||
github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY=
|
||||
github.com/onsi/gomega v1.19.0/go.mod h1:LY+I3pBVzYsTBU1AnDwOSxaYi9WoWiqgwooUqq9yPro=
|
||||
github.com/onsi/gomega v1.20.0 h1:8W0cWlwFkflGPLltQvLRB7ZVD5HuP6ng320w2IS245Q=
|
||||
github.com/onsi/gomega v1.20.0/go.mod h1:DtrZpjmvpn2mPm4YWQa0/ALMDj9v4YxLgojwPeREyVo=
|
||||
github.com/pingcap/errors v0.11.4 h1:lFuQV/oaUMGcD2tqt+01ROSmJs75VG1ToEOkZIZ4nE4=
|
||||
github.com/pingcap/errors v0.11.4/go.mod h1:Oi8TUi2kEtXXLMJk9l1cGmz20kV3TaQ0usTwv5KuLY8=
|
||||
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
|
||||
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/prometheus/client_golang v1.18.0 h1:HzFfmkOzH5Q8L8G+kSJKUx5dtG87sewO+FoDDqP5Tbk=
|
||||
github.com/prometheus/client_golang v1.18.0/go.mod h1:T+GXkCk5wSJyOqMIzVgvvjFDlkOQntgjkJWKrN5txjA=
|
||||
github.com/prometheus/client_model v0.6.0 h1:k1v3CzpSRUTrKMppY35TLwPvxHqBu0bYgxZzqGIgaos=
|
||||
github.com/prometheus/client_model v0.6.0/go.mod h1:NTQHnmxFpouOD0DpvP4XujX3CdOAGQPoaGhyTchlyt8=
|
||||
github.com/prometheus/common v0.47.0 h1:p5Cz0FNHo7SnWOmWmoRozVcjEp0bIVU8cV7OShpjL1k=
|
||||
github.com/prometheus/common v0.47.0/go.mod h1:0/KsvlIEfPQCQ5I2iNSAWKPZziNCvRs5EC6ILDTlAPc=
|
||||
github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo=
|
||||
github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo=
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
|
||||
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
|
||||
github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M=
|
||||
github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0=
|
||||
github.com/prometheus/client_golang v1.12.0 h1:C+UIj/QWtmqY13Arb8kwMt5j34/0Z2iKamrJ+ryC0Gg=
|
||||
github.com/prometheus/client_golang v1.12.0/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY=
|
||||
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
||||
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/prometheus/client_model v0.2.1-0.20210607210712-147c58e9608a h1:CmF68hwI0XsOQ5UwlBopMi2Ow4Pbg32akc4KIVCOm+Y=
|
||||
github.com/prometheus/client_model v0.2.1-0.20210607210712-147c58e9608a/go.mod h1:LDGWKZIo7rky3hgvBe+caln+Dr3dPggB5dvjtD7w9+w=
|
||||
github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
|
||||
github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo=
|
||||
github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc=
|
||||
github.com/prometheus/common v0.32.1 h1:hWIdL3N2HoUx3B8j3YN9mWor0qhY/NlEKZEaXxuIRh4=
|
||||
github.com/prometheus/common v0.32.1/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls=
|
||||
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
||||
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
|
||||
github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
|
||||
github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
|
||||
github.com/prometheus/procfs v0.7.3 h1:4jVXhlkAyzOScmCkXBTOLRLTz8EeU+eyjrwB/EPq0VU=
|
||||
github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
|
||||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
|
||||
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
|
||||
github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=
|
||||
github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
|
||||
github.com/spf13/cast v1.6.0 h1:GEiTHELF+vaR5dhz3VqZfFSzZjYbgeKDpBxQVS4GYJ0=
|
||||
github.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
|
||||
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
||||
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
|
||||
github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
|
||||
github.com/spf13/cast v1.7.1 h1:cuNEagBQEHWN1FnbGEjCXL2szYEXqfJPbP2HNUaca9Y=
|
||||
github.com/spf13/cast v1.7.1/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||
github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals=
|
||||
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/syndtr/goleveldb v1.0.1-0.20220721030215-126854af5e6d h1:vfofYNRScrDdvS342BElfbETmL1Aiz3i2t0zfRj16Hs=
|
||||
github.com/syndtr/goleveldb v1.0.1-0.20220721030215-126854af5e6d/go.mod h1:RRCYJbIwD5jmqPI9XoAFR0OcDxqUctll6zUj/+B4S48=
|
||||
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 h1:epCh84lMvA70Z7CTTCmYQn2CKbY8j86K7/FAIr141uY=
|
||||
github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7/go.mod h1:q4W45IWZaF22tdD+VEXcAWRA037jwmWEB5VWYORlTpc=
|
||||
github.com/tidwall/btree v1.7.0 h1:L1fkJH/AuEh5zBnnBbmTwQ5Lt+bRJ5A8EWecslvo9iI=
|
||||
github.com/tidwall/btree v1.7.0/go.mod h1:twD9XRA5jj9VUQGELzDO4HPQTNJsoWWfYEL+EUQ2cKY=
|
||||
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
|
||||
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
|
||||
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||
go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/exp v0.0.0-20240222234643-814bf88cf225 h1:LfspQV/FYTatPTr/3HzIcmiUFH7PGP+OQ6mgDYo3yuQ=
|
||||
golang.org/x/exp v0.0.0-20240222234643-814bf88cf225/go.mod h1:CxmFvTBINI24O/j8iY7H1xHzx2i4OsyguNBmN/uPtqc=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
||||
golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
|
||||
golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
|
||||
golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
|
||||
golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
|
||||
golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
|
||||
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
|
||||
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
|
||||
golang.org/x/exp v0.0.0-20230626212559-97b1e661b5df h1:UA2aFVmmsIlefxMk29Dp2juaUSth8Pyn3Tq5Y5mJGME=
|
||||
golang.org/x/exp v0.0.0-20230626212559-97b1e661b5df/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc=
|
||||
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
||||
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=
|
||||
golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
||||
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
||||
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
|
||||
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
|
||||
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
|
||||
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
|
||||
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||
golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
|
||||
golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
|
||||
golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||
golang.org/x/net v0.0.0-20220607020251-c690dde0001d/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.20.0 h1:aCL9BSgETF1k+blQaYUBx9hJ9LOGP3gAVemcZlf1Kpo=
|
||||
golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY=
|
||||
golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0=
|
||||
golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E=
|
||||
golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
|
||||
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ=
|
||||
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200814200057-3d37ad5750ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y=
|
||||
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU=
|
||||
golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
|
||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
|
||||
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
|
||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
||||
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
|
||||
golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
|
||||
golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=
|
||||
golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
||||
golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
||||
golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
||||
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20220517211312-f3a8303e98df/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20231009173412-8bfb1ae86b6c h1:jHkCUWkseRf+W+edG5hMzr/Uh1xkDREY4caybAq4dpY=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20231009173412-8bfb1ae86b6c/go.mod h1:4cYg8o5yUbm77w8ZX00LhMVNl/YVBFJRYWDc0uYWMs0=
|
||||
google.golang.org/grpc v1.58.3 h1:BjnpXut1btbtgN/6sp+brB2Kbm2LjNXnidYujAVbSoQ=
|
||||
google.golang.org/grpc v1.58.3/go.mod h1:tgX3ZQDlNJGU96V6yHh1T/JeoBQ2TXdr43YbYSsCJk0=
|
||||
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
|
||||
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
|
||||
google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
|
||||
google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
|
||||
google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
|
||||
google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
|
||||
google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
|
||||
google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
||||
google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
||||
google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
||||
google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
||||
google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
||||
google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
|
||||
google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
|
||||
google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM=
|
||||
google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc=
|
||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
|
||||
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
||||
google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
||||
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
||||
google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
|
||||
google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA=
|
||||
google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U=
|
||||
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
|
||||
google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA=
|
||||
google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
|
||||
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
|
||||
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
||||
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
|
||||
google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||
google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||
google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60=
|
||||
google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
|
||||
google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
|
||||
google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
|
||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
||||
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
|
||||
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
|
||||
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
|
||||
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
|
||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||
google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI=
|
||||
google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
|
||||
google.golang.org/protobuf v1.36.4 h1:6A3ZDJHn/eNqc1i+IdefRzy/9PokBTPvcqMySR7NNIM=
|
||||
google.golang.org/protobuf v1.36.4/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
|
||||
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
||||
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gotest.tools/v3 v3.5.1 h1:EENdUnS3pdur5nybKYIh2Vfgc8IUNBjxDPSjtiJcOzU=
|
||||
gotest.tools/v3 v3.5.1/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU=
|
||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
|
||||
honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
|
||||
honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
|
||||
pgregory.net/rapid v1.1.0 h1:CMa0sjHSru3puNx+J0MIAuiiEV4N0qj8/cMWGBBCsjw=
|
||||
pgregory.net/rapid v1.1.0/go.mod h1:PY5XlDGj0+V1FCq0o192FdRhpKHGTRIWBgqjDBTrq04=
|
||||
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
|
||||
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
|
||||
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
|
||||
|
||||
@ -2,6 +2,9 @@ package collections
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"reflect"
|
||||
|
||||
"cosmossdk.io/collections/codec"
|
||||
)
|
||||
@ -32,17 +35,77 @@ type Index[PrimaryKey, Value any] interface {
|
||||
// Internally IndexedMap can be seen as a partitioned collection, one partition
|
||||
// is a Map[PrimaryKey, Value], that maintains the object, the second
|
||||
// are the Indexes.
|
||||
type IndexedMap[PrimaryKey, Value any, Idx Indexes[PrimaryKey, Value]] struct {
|
||||
Indexes Idx
|
||||
m Map[PrimaryKey, Value]
|
||||
type IndexedMap[PrimaryKey, Value, Idx any] struct {
|
||||
Indexes Idx
|
||||
computedIndexes []Index[PrimaryKey, Value]
|
||||
m Map[PrimaryKey, Value]
|
||||
}
|
||||
|
||||
// NewIndexedMapSafe behaves like NewIndexedMap but returns errors.
|
||||
func NewIndexedMapSafe[K, V, I any](
|
||||
schema *SchemaBuilder,
|
||||
prefix Prefix,
|
||||
name string,
|
||||
pkCodec codec.KeyCodec[K],
|
||||
valueCodec codec.ValueCodec[V],
|
||||
indexes I,
|
||||
) (im *IndexedMap[K, V, I], err error) {
|
||||
var indexesList []Index[K, V]
|
||||
indexesImpl, ok := any(indexes).(Indexes[K, V])
|
||||
if ok {
|
||||
indexesList = indexesImpl.IndexesList()
|
||||
} else {
|
||||
// if does not implement Indexes, then we try to infer using reflection
|
||||
indexesList, err = tryInferIndexes[I, K, V](indexes)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to infer indexes using reflection, consider implementing Indexes interface: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
return &IndexedMap[K, V, I]{
|
||||
computedIndexes: indexesList,
|
||||
Indexes: indexes,
|
||||
m: NewMap(schema, prefix, name, pkCodec, valueCodec),
|
||||
}, nil
|
||||
}
|
||||
|
||||
var (
|
||||
// testing sentinel errors
|
||||
errNotStruct = errors.New("wanted struct or pointer to a struct")
|
||||
errNotIndex = errors.New("field is not an index implementation")
|
||||
)
|
||||
|
||||
func tryInferIndexes[I, K, V any](indexes I) ([]Index[K, V], error) {
|
||||
typ := reflect.TypeOf(indexes)
|
||||
v := reflect.ValueOf(indexes)
|
||||
// check if struct or pointer to a struct
|
||||
if typ.Kind() != reflect.Struct && (typ.Kind() != reflect.Pointer || typ.Elem().Kind() != reflect.Struct) {
|
||||
return nil, fmt.Errorf("%w: type %v", errNotStruct, typ)
|
||||
}
|
||||
// dereference
|
||||
if typ.Kind() == reflect.Pointer {
|
||||
v = v.Elem()
|
||||
}
|
||||
indexesImpl := make([]Index[K, V], v.NumField())
|
||||
for i := 0; i < v.NumField(); i++ {
|
||||
field := v.Field(i)
|
||||
index, ok := field.Interface().(Index[K, V])
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("%w: field number %d", errNotIndex, i)
|
||||
}
|
||||
indexesImpl[i] = index
|
||||
}
|
||||
return indexesImpl, nil
|
||||
}
|
||||
|
||||
// NewIndexedMap instantiates a new IndexedMap. Accepts a SchemaBuilder, a Prefix,
|
||||
// a humanized name that defines the name of the collection, the primary key codec
|
||||
// which is basically what IndexedMap uses to encode the primary key to bytes,
|
||||
// the value codec which is what the IndexedMap uses to encode the value.
|
||||
// Then it expects the initialized indexes.
|
||||
func NewIndexedMap[PrimaryKey, Value any, Idx Indexes[PrimaryKey, Value]](
|
||||
// Then it expects the initialized indexes. Reflection is used to infer the
|
||||
// indexes, Indexes can optionally be implemented to be explicit. Panics
|
||||
// on failure to create indexes. If you want an erroring API use NewIndexedMapSafe.
|
||||
func NewIndexedMap[PrimaryKey, Value, Idx any](
|
||||
schema *SchemaBuilder,
|
||||
prefix Prefix,
|
||||
name string,
|
||||
@ -50,10 +113,11 @@ func NewIndexedMap[PrimaryKey, Value any, Idx Indexes[PrimaryKey, Value]](
|
||||
valueCodec codec.ValueCodec[Value],
|
||||
indexes Idx,
|
||||
) *IndexedMap[PrimaryKey, Value, Idx] {
|
||||
return &IndexedMap[PrimaryKey, Value, Idx]{
|
||||
Indexes: indexes,
|
||||
m: NewMap(schema, prefix, name, pkCodec, valueCodec),
|
||||
im, err := NewIndexedMapSafe(schema, prefix, name, pkCodec, valueCodec, indexes)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return im
|
||||
}
|
||||
|
||||
// Get gets the object given its primary key.
|
||||
@ -111,7 +175,7 @@ func (m *IndexedMap[PrimaryKey, Value, Idx]) ValueCodec() codec.ValueCodec[Value
|
||||
}
|
||||
|
||||
func (m *IndexedMap[PrimaryKey, Value, Idx]) ref(ctx context.Context, pk PrimaryKey, value Value) error {
|
||||
for _, index := range m.Indexes.IndexesList() {
|
||||
for _, index := range m.computedIndexes {
|
||||
err := index.Reference(ctx, pk, value, cachedGet[PrimaryKey, Value](ctx, m, pk))
|
||||
if err != nil {
|
||||
return err
|
||||
@ -121,7 +185,7 @@ func (m *IndexedMap[PrimaryKey, Value, Idx]) ref(ctx context.Context, pk Primary
|
||||
}
|
||||
|
||||
func (m *IndexedMap[PrimaryKey, Value, Idx]) unref(ctx context.Context, pk PrimaryKey) error {
|
||||
for _, index := range m.Indexes.IndexesList() {
|
||||
for _, index := range m.computedIndexes {
|
||||
err := index.Unreference(ctx, pk, cachedGet[PrimaryKey, Value](ctx, m, pk))
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
30
collections/indexed_map_internal_test.go
Normal file
30
collections/indexed_map_internal_test.go
Normal file
@ -0,0 +1,30 @@
|
||||
package collections
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestTryInferIndex(t *testing.T) {
|
||||
invalidIdx := 5
|
||||
|
||||
t.Run("not a pointer to struct", func(t *testing.T) {
|
||||
_, err := tryInferIndexes[*int, string, string](&invalidIdx)
|
||||
require.ErrorIs(t, err, errNotStruct)
|
||||
})
|
||||
|
||||
t.Run("not a struct", func(t *testing.T) {
|
||||
_, err := tryInferIndexes[int, string, string](invalidIdx)
|
||||
require.ErrorIs(t, err, errNotStruct)
|
||||
})
|
||||
|
||||
t.Run("not an index field", func(t *testing.T) {
|
||||
type invalidIndex struct {
|
||||
A int
|
||||
}
|
||||
|
||||
_, err := tryInferIndexes[invalidIndex, string, string](invalidIndex{})
|
||||
require.ErrorIs(t, err, errNotIndex)
|
||||
})
|
||||
}
|
||||
@ -8,6 +8,7 @@ import (
|
||||
"cosmossdk.io/collections"
|
||||
"cosmossdk.io/collections/colltest"
|
||||
"cosmossdk.io/collections/indexes"
|
||||
"cosmossdk.io/core/testing"
|
||||
)
|
||||
|
||||
type company struct {
|
||||
@ -44,7 +45,9 @@ func newTestIndexedMap(schema *collections.SchemaBuilder) *collections.IndexedMa
|
||||
}
|
||||
|
||||
func TestIndexedMap(t *testing.T) {
|
||||
sk, ctx := colltest.MockStore()
|
||||
ctx := coretesting.Context()
|
||||
sk := coretesting.KVStoreService(ctx, "test")
|
||||
|
||||
schema := collections.NewSchemaBuilder(sk)
|
||||
|
||||
im := newTestIndexedMap(schema)
|
||||
@ -104,3 +107,27 @@ func TestIndexedMap(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, company{"milan", 4}, v)
|
||||
}
|
||||
|
||||
type inferIndex struct {
|
||||
City *indexes.Multi[string, string, company]
|
||||
Vat *indexes.Unique[uint64, string, company]
|
||||
}
|
||||
|
||||
func newInferIndex(schema *collections.SchemaBuilder) *inferIndex {
|
||||
return &inferIndex{
|
||||
City: indexes.NewMulti(schema, collections.NewPrefix(1), "companies_by_city", collections.StringKey, collections.StringKey, func(pk string, value company) (string, error) {
|
||||
return value.City, nil
|
||||
}),
|
||||
Vat: indexes.NewUnique(schema, collections.NewPrefix(2), "companies_by_vat", collections.Uint64Key, collections.StringKey, func(pk string, value company) (uint64, error) {
|
||||
return value.Vat, nil
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
func TestIndexedMapInfer(t *testing.T) {
|
||||
sk := coretesting.KVStoreService(coretesting.Context(), "test")
|
||||
schema := collections.NewSchemaBuilder(sk)
|
||||
|
||||
_, err := collections.NewIndexedMapSafe(schema, collections.NewPrefix(0), "im", collections.StringKey, colltest.MockValueCodec[company](), newInferIndex(schema))
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
@ -3,50 +3,14 @@ package indexes
|
||||
import (
|
||||
"context"
|
||||
|
||||
db "github.com/cosmos/cosmos-db"
|
||||
|
||||
"cosmossdk.io/core/store"
|
||||
"cosmossdk.io/core/testing"
|
||||
)
|
||||
|
||||
// TODO remove this when we add testStore to core/store.
|
||||
|
||||
type testStore struct {
|
||||
db db.DB
|
||||
}
|
||||
|
||||
func (t testStore) OpenKVStore(ctx context.Context) store.KVStore {
|
||||
return t
|
||||
}
|
||||
|
||||
func (t testStore) Get(key []byte) ([]byte, error) {
|
||||
return t.db.Get(key)
|
||||
}
|
||||
|
||||
func (t testStore) Has(key []byte) (bool, error) {
|
||||
return t.db.Has(key)
|
||||
}
|
||||
|
||||
func (t testStore) Set(key, value []byte) error {
|
||||
return t.db.Set(key, value)
|
||||
}
|
||||
|
||||
func (t testStore) Delete(key []byte) error {
|
||||
return t.db.Delete(key)
|
||||
}
|
||||
|
||||
func (t testStore) Iterator(start, end []byte) (store.Iterator, error) {
|
||||
return t.db.Iterator(start, end)
|
||||
}
|
||||
|
||||
func (t testStore) ReverseIterator(start, end []byte) (store.Iterator, error) {
|
||||
return t.db.ReverseIterator(start, end)
|
||||
}
|
||||
|
||||
var _ store.KVStore = testStore{}
|
||||
|
||||
func deps() (store.KVStoreService, context.Context) {
|
||||
kv := db.NewMemDB()
|
||||
return &testStore{kv}, context.Background()
|
||||
ctx := coretesting.Context()
|
||||
kv := coretesting.KVStoreService(ctx, "test")
|
||||
return kv, ctx
|
||||
}
|
||||
|
||||
type company struct {
|
||||
|
||||
@ -8,6 +8,21 @@ import (
|
||||
"cosmossdk.io/collections/codec"
|
||||
)
|
||||
|
||||
type multiOptions struct {
|
||||
uncheckedValue bool
|
||||
}
|
||||
|
||||
// WithMultiUncheckedValue is an option that can be passed to NewMulti to
|
||||
// ignore index values different from '[]byte{}' and continue with the operation.
|
||||
// This should be used only to behave nicely in case you have used values different
|
||||
// from '[]byte{}' in your storage before migrating to collections. Refer to
|
||||
// WithKeySetUncheckedValue for more information.
|
||||
func WithMultiUncheckedValue() func(*multiOptions) {
|
||||
return func(o *multiOptions) {
|
||||
o.uncheckedValue = true
|
||||
}
|
||||
}
|
||||
|
||||
// Multi defines the most common index. It can be used to create a reference between
|
||||
// a field of value and its primary key. Multiple primary keys can be mapped to the same
|
||||
// reference key as the index does not enforce uniqueness constraints.
|
||||
@ -27,10 +42,35 @@ func NewMulti[ReferenceKey, PrimaryKey, Value any](
|
||||
refCodec codec.KeyCodec[ReferenceKey],
|
||||
pkCodec codec.KeyCodec[PrimaryKey],
|
||||
getRefKeyFunc func(pk PrimaryKey, value Value) (ReferenceKey, error),
|
||||
options ...func(*multiOptions),
|
||||
) *Multi[ReferenceKey, PrimaryKey, Value] {
|
||||
o := new(multiOptions)
|
||||
for _, opt := range options {
|
||||
opt(o)
|
||||
}
|
||||
if o.uncheckedValue {
|
||||
return &Multi[ReferenceKey, PrimaryKey, Value]{
|
||||
getRefKey: getRefKeyFunc,
|
||||
refKeys: collections.NewKeySet(
|
||||
schema,
|
||||
prefix,
|
||||
name,
|
||||
collections.PairKeyCodec(refCodec, pkCodec),
|
||||
collections.WithKeySetUncheckedValue(),
|
||||
collections.WithKeySetSecondaryIndex(),
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
return &Multi[ReferenceKey, PrimaryKey, Value]{
|
||||
getRefKey: getRefKeyFunc,
|
||||
refKeys: collections.NewKeySet(schema, prefix, name, collections.PairKeyCodec(refCodec, pkCodec)),
|
||||
refKeys: collections.NewKeySet(
|
||||
schema,
|
||||
prefix,
|
||||
name,
|
||||
collections.PairKeyCodec(refCodec, pkCodec),
|
||||
collections.WithKeySetSecondaryIndex(),
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -16,7 +16,7 @@ func TestMultiIndex(t *testing.T) {
|
||||
return value.City, nil
|
||||
})
|
||||
|
||||
// we crete two reference keys for primary key 1 and 2 associated with "milan"
|
||||
// we create two reference keys for primary key 1 and 2 associated with "milan"
|
||||
require.NoError(t, mi.Reference(ctx, 1, company{City: "milan"}, func() (company, error) { return company{}, collections.ErrNotFound }))
|
||||
require.NoError(t, mi.Reference(ctx, 2, company{City: "milan"}, func() (company, error) { return company{}, collections.ErrNotFound }))
|
||||
|
||||
@ -61,3 +61,52 @@ func TestMultiIndex(t *testing.T) {
|
||||
require.False(t, iter.Valid())
|
||||
require.NoError(t, iter.Close())
|
||||
}
|
||||
|
||||
func TestMultiUnchecked(t *testing.T) {
|
||||
sk, ctx := deps()
|
||||
schema := collections.NewSchemaBuilder(sk)
|
||||
|
||||
uncheckedMi := NewMulti(schema, collections.NewPrefix("prefix"), "multi_index", collections.StringKey, collections.Uint64Key, func(_ uint64, value company) (string, error) {
|
||||
return value.City, nil
|
||||
}, WithMultiUncheckedValue())
|
||||
|
||||
mi := NewMulti(schema, collections.NewPrefix("prefix"), "multi_index", collections.StringKey, collections.Uint64Key, func(_ uint64, value company) (string, error) {
|
||||
return value.City, nil
|
||||
})
|
||||
|
||||
rawKey, err := collections.EncodeKeyWithPrefix(
|
||||
collections.NewPrefix("prefix"),
|
||||
uncheckedMi.KeyCodec(),
|
||||
collections.Join("milan", uint64(2)))
|
||||
require.NoError(t, err)
|
||||
|
||||
// set value to be something different from []byte{}
|
||||
require.NoError(t, sk.OpenKVStore(ctx).Set(rawKey, []byte("something")))
|
||||
|
||||
// normal multi index will fail.
|
||||
err = mi.Walk(ctx, nil, func(indexingKey string, indexedKey uint64) (stop bool, err error) {
|
||||
return true, err
|
||||
})
|
||||
require.ErrorIs(t, err, collections.ErrEncoding)
|
||||
|
||||
// unchecked multi index will not fail.
|
||||
err = uncheckedMi.Walk(ctx, nil, func(indexingKey string, indexedKey uint64) (stop bool, err error) {
|
||||
require.Equal(t, "milan", indexingKey)
|
||||
require.Equal(t, uint64(2), indexedKey)
|
||||
return true, err
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
// unchecked multi will also reset the value
|
||||
err = mi.Reference(ctx, 2, company{City: "milan"}, func() (company, error) {
|
||||
return company{
|
||||
City: "milan",
|
||||
}, nil
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
// value reset to []byte{}
|
||||
rawValue, err := sk.OpenKVStore(ctx).Get(rawKey)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, []byte{}, rawValue)
|
||||
}
|
||||
|
||||
@ -7,6 +7,21 @@ import (
|
||||
"cosmossdk.io/collections/codec"
|
||||
)
|
||||
|
||||
type reversePairOptions struct {
|
||||
uncheckedValue bool
|
||||
}
|
||||
|
||||
// WithReversePairUncheckedValue is an option that can be passed to NewReversePair to
|
||||
// ignore index values different from '[]byte{}' and continue with the operation.
|
||||
// This should be used only if you are migrating to collections and have used a different
|
||||
// placeholder value in your storage index keys.
|
||||
// Refer to WithKeySetUncheckedValue for more information.
|
||||
func WithReversePairUncheckedValue() func(*reversePairOptions) {
|
||||
return func(o *reversePairOptions) {
|
||||
o.uncheckedValue = true
|
||||
}
|
||||
}
|
||||
|
||||
// ReversePair is an index that is used with collections.Pair keys. It indexes objects by their second part of the key.
|
||||
// When the value is being indexed by collections.IndexedMap then ReversePair will create a relationship between
|
||||
// the second part of the primary key and the first part.
|
||||
@ -31,10 +46,34 @@ func NewReversePair[Value, K1, K2 any](
|
||||
prefix collections.Prefix,
|
||||
name string,
|
||||
pairCodec codec.KeyCodec[collections.Pair[K1, K2]],
|
||||
options ...func(*reversePairOptions),
|
||||
) *ReversePair[K1, K2, Value] {
|
||||
pkc := pairCodec.(pairKeyCodec[K1, K2])
|
||||
o := new(reversePairOptions)
|
||||
for _, option := range options {
|
||||
option(o)
|
||||
}
|
||||
if o.uncheckedValue {
|
||||
return &ReversePair[K1, K2, Value]{
|
||||
refKeys: collections.NewKeySet(
|
||||
sb,
|
||||
prefix,
|
||||
name,
|
||||
collections.PairKeyCodec(pkc.KeyCodec2(), pkc.KeyCodec1()),
|
||||
collections.WithKeySetUncheckedValue(),
|
||||
collections.WithKeySetSecondaryIndex(),
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
mi := &ReversePair[K1, K2, Value]{
|
||||
refKeys: collections.NewKeySet(sb, prefix, name, collections.PairKeyCodec(pkc.KeyCodec2(), pkc.KeyCodec1())),
|
||||
refKeys: collections.NewKeySet(
|
||||
sb,
|
||||
prefix,
|
||||
name,
|
||||
collections.PairKeyCodec(pkc.KeyCodec2(), pkc.KeyCodec1()),
|
||||
collections.WithKeySetSecondaryIndex(),
|
||||
),
|
||||
}
|
||||
|
||||
return mi
|
||||
|
||||
@ -64,6 +64,45 @@ func TestReversePair(t *testing.T) {
|
||||
// assert if we remove address1 atom balance, we can no longer find it in the index
|
||||
err = indexedMap.Remove(ctx, collections.Join("address1", "atom"))
|
||||
require.NoError(t, err)
|
||||
_, err = indexedMap.Indexes.Denom.MatchExact(ctx, "atom")
|
||||
require.ErrorIs(t, collections.ErrInvalidIterator, err)
|
||||
iter, err = indexedMap.Indexes.Denom.MatchExact(ctx, "atom")
|
||||
require.NoError(t, err)
|
||||
defer iter.Close()
|
||||
pks, err = iter.PrimaryKeys()
|
||||
require.NoError(t, err)
|
||||
require.Empty(t, pks)
|
||||
}
|
||||
|
||||
func TestUncheckedReversePair(t *testing.T) {
|
||||
sk, ctx := deps()
|
||||
sb := collections.NewSchemaBuilder(sk)
|
||||
prefix := collections.NewPrefix("prefix")
|
||||
keyCodec := collections.PairKeyCodec(collections.StringKey, collections.StringKey)
|
||||
|
||||
uncheckedRp := NewReversePair[Amount](sb, prefix, "denom_index", keyCodec, WithReversePairUncheckedValue())
|
||||
rp := NewReversePair[Amount](sb, prefix, "denom_index", keyCodec)
|
||||
|
||||
rawKey, err := collections.EncodeKeyWithPrefix(prefix, uncheckedRp.KeyCodec(), collections.Join("atom", "address1"))
|
||||
require.NoError(t, err)
|
||||
|
||||
require.NoError(t, sk.OpenKVStore(ctx).Set(rawKey, []byte("i should not be here")))
|
||||
|
||||
// normal reverse pair fails
|
||||
err = rp.Walk(ctx, nil, func(denom, address string) (bool, error) {
|
||||
return false, nil
|
||||
})
|
||||
require.ErrorIs(t, err, collections.ErrEncoding)
|
||||
|
||||
// unchecked reverse pair succeeds
|
||||
err = uncheckedRp.Walk(ctx, nil, func(indexingKey, indexedKey string) (stop bool, err error) {
|
||||
require.Equal(t, "atom", indexingKey)
|
||||
return true, nil
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
// unchecked reverse pair lazily updates
|
||||
err = uncheckedRp.Reference(ctx, collections.Join("address1", "atom"), 0, nil)
|
||||
require.NoError(t, err)
|
||||
rawValue, err := sk.OpenKVStore(ctx).Get(rawKey)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, []byte{}, rawValue)
|
||||
}
|
||||
|
||||
258
collections/indexing.go
Normal file
258
collections/indexing.go
Normal file
@ -0,0 +1,258 @@
|
||||
package collections
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
"github.com/cosmos/gogoproto/proto"
|
||||
"github.com/tidwall/btree"
|
||||
"google.golang.org/protobuf/reflect/protoreflect"
|
||||
|
||||
"cosmossdk.io/collections/codec"
|
||||
"cosmossdk.io/schema"
|
||||
)
|
||||
|
||||
// IndexingOptions are indexing options for the collections schema.
|
||||
type IndexingOptions struct {
|
||||
// RetainDeletionsFor is the list of collections to retain deletions for.
|
||||
RetainDeletionsFor []string
|
||||
}
|
||||
|
||||
// ModuleCodec returns the ModuleCodec for this schema for the provided options.
|
||||
func (s Schema) ModuleCodec(opts IndexingOptions) (schema.ModuleCodec, error) {
|
||||
decoder := moduleDecoder{
|
||||
collectionLookup: &btree.Map[string, *collectionSchemaCodec]{},
|
||||
}
|
||||
|
||||
retainDeletions := make(map[string]bool)
|
||||
for _, collName := range opts.RetainDeletionsFor {
|
||||
retainDeletions[collName] = true
|
||||
}
|
||||
|
||||
var types []schema.Type
|
||||
for _, collName := range s.collectionsOrdered {
|
||||
coll := s.collectionsByName[collName]
|
||||
|
||||
// skip secondary indexes
|
||||
if coll.isSecondaryIndex() {
|
||||
continue
|
||||
}
|
||||
|
||||
cdc, err := coll.schemaCodec()
|
||||
if err != nil {
|
||||
return schema.ModuleCodec{}, err
|
||||
}
|
||||
|
||||
if retainDeletions[coll.GetName()] {
|
||||
cdc.objectType.RetainDeletions = true
|
||||
}
|
||||
|
||||
// this part below is a bit hacky, it will try to convert to a proto.Message
|
||||
// in order to get any enum types inside of it.
|
||||
emptyVal, err := coll.ValueCodec().Decode([]byte{})
|
||||
if err == nil {
|
||||
// convert to proto.Message
|
||||
pt, err := toProtoMessage(emptyVal)
|
||||
if err == nil {
|
||||
msgName := proto.MessageName(pt)
|
||||
desc, err := proto.HybridResolver.FindDescriptorByName(protoreflect.FullName(msgName))
|
||||
if err != nil {
|
||||
return schema.ModuleCodec{}, fmt.Errorf("could not find descriptor for %s: %w", msgName, err)
|
||||
}
|
||||
msgDesc := desc.(protoreflect.MessageDescriptor)
|
||||
|
||||
// go through enum descriptors and add them to types
|
||||
for i := 0; i < msgDesc.Fields().Len(); i++ {
|
||||
field := msgDesc.Fields().Get(i)
|
||||
enum := field.Enum()
|
||||
if enum == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
enumType := schema.EnumType{
|
||||
Name: strings.ReplaceAll(string(enum.FullName()), ".", "_"), // make it compatible with schema
|
||||
}
|
||||
for j := 0; j < enum.Values().Len(); j++ {
|
||||
val := enum.Values().Get(j)
|
||||
enumType.Values = append(enumType.Values, schema.EnumValueDefinition{
|
||||
Name: string(val.Name()),
|
||||
Value: int32(val.Number()),
|
||||
})
|
||||
}
|
||||
types = append(types, enumType)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
types = append(types, cdc.objectType)
|
||||
decoder.collectionLookup.Set(string(coll.GetPrefix()), cdc)
|
||||
}
|
||||
|
||||
modSchema, err := schema.CompileModuleSchema(types...)
|
||||
if err != nil {
|
||||
return schema.ModuleCodec{}, err
|
||||
}
|
||||
|
||||
return schema.ModuleCodec{
|
||||
Schema: modSchema,
|
||||
KVDecoder: decoder.decodeKV,
|
||||
}, nil
|
||||
}
|
||||
|
||||
type moduleDecoder struct {
|
||||
// collectionLookup lets us efficiently look the correct collection based on raw key bytes
|
||||
collectionLookup *btree.Map[string, *collectionSchemaCodec]
|
||||
}
|
||||
|
||||
func (m moduleDecoder) decodeKV(update schema.KVPairUpdate) ([]schema.StateObjectUpdate, error) {
|
||||
key := update.Key
|
||||
ks := string(key)
|
||||
var cd *collectionSchemaCodec
|
||||
// we look for the collection whose prefix is less than this key
|
||||
m.collectionLookup.Descend(ks, func(prefix string, cur *collectionSchemaCodec) bool {
|
||||
bytesPrefix := cur.coll.GetPrefix()
|
||||
if bytes.HasPrefix(key, bytesPrefix) {
|
||||
cd = cur
|
||||
return true
|
||||
}
|
||||
return false
|
||||
})
|
||||
if cd == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
return cd.decodeKVPair(update)
|
||||
}
|
||||
|
||||
func (c collectionSchemaCodec) decodeKVPair(update schema.KVPairUpdate) ([]schema.StateObjectUpdate, error) {
|
||||
// strip prefix
|
||||
key := update.Key
|
||||
key = key[len(c.coll.GetPrefix()):]
|
||||
|
||||
k, err := c.keyDecoder(key)
|
||||
if err != nil {
|
||||
return []schema.StateObjectUpdate{
|
||||
{TypeName: c.coll.GetName()},
|
||||
}, err
|
||||
}
|
||||
|
||||
if update.Remove {
|
||||
return []schema.StateObjectUpdate{
|
||||
{TypeName: c.coll.GetName(), Key: k, Delete: true},
|
||||
}, nil
|
||||
}
|
||||
|
||||
v, err := c.valueDecoder(update.Value)
|
||||
if err != nil {
|
||||
return []schema.StateObjectUpdate{
|
||||
{TypeName: c.coll.GetName(), Key: k},
|
||||
}, err
|
||||
}
|
||||
|
||||
return []schema.StateObjectUpdate{
|
||||
{TypeName: c.coll.GetName(), Key: k, Value: v},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (c collectionImpl[K, V]) schemaCodec() (*collectionSchemaCodec, error) {
|
||||
res := &collectionSchemaCodec{
|
||||
coll: c,
|
||||
}
|
||||
res.objectType.Name = c.GetName()
|
||||
|
||||
keyDecoder, err := codec.KeySchemaCodec(c.m.kc)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res.objectType.KeyFields = keyDecoder.Fields
|
||||
res.keyDecoder = func(i []byte) (any, error) {
|
||||
_, x, err := c.m.kc.Decode(i)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if keyDecoder.ToSchemaType == nil {
|
||||
return x, nil
|
||||
}
|
||||
return keyDecoder.ToSchemaType(x)
|
||||
}
|
||||
ensureFieldNames(c.m.kc, "key", res.objectType.KeyFields)
|
||||
|
||||
valueDecoder, err := codec.ValueSchemaCodec(c.m.vc)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res.objectType.ValueFields = valueDecoder.Fields
|
||||
res.valueDecoder = func(i []byte) (any, error) {
|
||||
x, err := c.m.vc.Decode(i)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if valueDecoder.ToSchemaType == nil {
|
||||
return x, nil
|
||||
}
|
||||
|
||||
return valueDecoder.ToSchemaType(x)
|
||||
}
|
||||
ensureFieldNames(c.m.vc, "value", res.objectType.ValueFields)
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// ensureFieldNames makes sure that all fields have valid names - either the
|
||||
// names were specified by user or they get filled
|
||||
func ensureFieldNames(x any, defaultName string, cols []schema.Field) {
|
||||
var names []string = nil
|
||||
if hasName, ok := x.(interface{ Name() string }); ok {
|
||||
name := hasName.Name()
|
||||
if name != "" {
|
||||
names = strings.Split(hasName.Name(), ",")
|
||||
}
|
||||
}
|
||||
for i, col := range cols {
|
||||
if names != nil && i < len(names) {
|
||||
col.Name = names[i]
|
||||
} else if col.Name == "" {
|
||||
if i == 0 && len(cols) == 1 {
|
||||
col.Name = defaultName
|
||||
} else {
|
||||
col.Name = fmt.Sprintf("%s%d", defaultName, i+1)
|
||||
}
|
||||
}
|
||||
cols[i] = col
|
||||
}
|
||||
}
|
||||
|
||||
// toProtoMessage is a helper to convert a value to a proto.Message.
|
||||
func toProtoMessage(value interface{}) (proto.Message, error) {
|
||||
if value == nil {
|
||||
return nil, fmt.Errorf("value is nil")
|
||||
}
|
||||
|
||||
// Check if the value already implements proto.Message
|
||||
if msg, ok := value.(proto.Message); ok {
|
||||
return msg, nil
|
||||
}
|
||||
|
||||
// Use reflection to handle non-pointer values
|
||||
v := reflect.ValueOf(value)
|
||||
if v.Kind() == reflect.Ptr {
|
||||
// Already a pointer, but doesn't implement proto.Message
|
||||
return nil, fmt.Errorf("value is a pointer but does not implement proto.Message")
|
||||
}
|
||||
|
||||
// If not a pointer, create a pointer to the value dynamically
|
||||
ptr := reflect.New(v.Type())
|
||||
ptr.Elem().Set(v)
|
||||
|
||||
// Assert if the pointer implements proto.Message
|
||||
msg, ok := ptr.Interface().(proto.Message)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("value does not implement proto.Message")
|
||||
}
|
||||
|
||||
return msg, nil
|
||||
}
|
||||
@ -1,6 +1,7 @@
|
||||
package collections
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
@ -129,39 +130,51 @@ func (r *Range[K]) RangeValues() (start, end *RangeKey[K], order Order, err erro
|
||||
return r.start, r.end, r.order, nil
|
||||
}
|
||||
|
||||
// iteratorFromRanger generates an Iterator instance, with the proper prefixing and ranging.
|
||||
// a nil Ranger can be seen as an ascending iteration over all the possible keys.
|
||||
func iteratorFromRanger[K, V any](ctx context.Context, m Map[K, V], r Ranger[K]) (iter Iterator[K, V], err error) {
|
||||
// parseRangeInstruction converts a Ranger into start bytes, end bytes and order of a store iteration.
|
||||
func parseRangeInstruction[K any](prefix []byte, keyCodec codec.KeyCodec[K], r Ranger[K]) ([]byte, []byte, Order, error) {
|
||||
var (
|
||||
start *RangeKey[K]
|
||||
end *RangeKey[K]
|
||||
order = OrderAscending
|
||||
err error
|
||||
)
|
||||
|
||||
if r != nil {
|
||||
start, end, order, err = r.RangeValues()
|
||||
if err != nil {
|
||||
return iter, err
|
||||
return nil, nil, 0, err
|
||||
}
|
||||
}
|
||||
|
||||
startBytes := m.prefix
|
||||
startBytes := prefix
|
||||
if start != nil {
|
||||
startBytes, err = encodeRangeBound(m.prefix, m.kc, start)
|
||||
startBytes, err = encodeRangeBound(prefix, keyCodec, start)
|
||||
if err != nil {
|
||||
return iter, err
|
||||
return nil, nil, 0, err
|
||||
}
|
||||
}
|
||||
var endBytes []byte
|
||||
if end != nil {
|
||||
endBytes, err = encodeRangeBound(m.prefix, m.kc, end)
|
||||
endBytes, err = encodeRangeBound(prefix, keyCodec, end)
|
||||
if err != nil {
|
||||
return iter, err
|
||||
return nil, nil, 0, err
|
||||
}
|
||||
} else {
|
||||
endBytes = nextBytesPrefixKey(m.prefix)
|
||||
endBytes = nextBytesPrefixKey(prefix)
|
||||
}
|
||||
if bytes.Compare(startBytes, endBytes) == 1 {
|
||||
return nil, nil, 0, ErrInvalidIterator
|
||||
}
|
||||
return startBytes, endBytes, order, nil
|
||||
}
|
||||
|
||||
// iteratorFromRanger generates an Iterator instance, with the proper prefixing and ranging.
|
||||
// a nil Ranger can be seen as an ascending iteration over all the possible keys.
|
||||
func iteratorFromRanger[K, V any](ctx context.Context, m Map[K, V], r Ranger[K]) (iter Iterator[K, V], err error) {
|
||||
startBytes, endBytes, order, err := parseRangeInstruction(m.prefix, m.kc, r)
|
||||
if err != nil {
|
||||
return Iterator[K, V]{}, err
|
||||
}
|
||||
return newIterator(ctx, startBytes, endBytes, order, m)
|
||||
}
|
||||
|
||||
@ -182,9 +195,6 @@ func newIterator[K, V any](ctx context.Context, start, end []byte, order Order,
|
||||
if err != nil {
|
||||
return Iterator[K, V]{}, err
|
||||
}
|
||||
if !iter.Valid() {
|
||||
return Iterator[K, V]{}, ErrInvalidIterator
|
||||
}
|
||||
|
||||
return Iterator[K, V]{
|
||||
kc: m.kc,
|
||||
@ -194,7 +204,7 @@ func newIterator[K, V any](ctx context.Context, start, end []byte, order Order,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Iterator defines a generic wrapper around an storetypes.Iterator.
|
||||
// Iterator defines a generic wrapper around a storetypes.Iterator.
|
||||
// This iterator provides automatic key and value encoding,
|
||||
// it assumes all the keys and values contained within the storetypes.Iterator
|
||||
// range are the same.
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
package collections
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
@ -10,11 +11,13 @@ import (
|
||||
func TestIteratorBasic(t *testing.T) {
|
||||
sk, ctx := deps()
|
||||
// safety check to ensure that iteration does not cross prefix boundaries
|
||||
sk.OpenKVStore(ctx).Set([]byte{0, 0}, []byte("before prefix"))
|
||||
sk.OpenKVStore(ctx).Set([]byte{2, 1}, []byte("after prefix"))
|
||||
err := sk.OpenKVStore(ctx).Set([]byte{0, 0}, []byte("before prefix"))
|
||||
require.NoError(t, err)
|
||||
err = sk.OpenKVStore(ctx).Set([]byte{2, 1}, []byte("after prefix"))
|
||||
require.NoError(t, err)
|
||||
schemaBuilder := NewSchemaBuilder(sk)
|
||||
m := NewMap(schemaBuilder, NewPrefix(1), "m", StringKey, Uint64Value)
|
||||
_, err := schemaBuilder.Build()
|
||||
_, err = schemaBuilder.Build()
|
||||
require.NoError(t, err)
|
||||
|
||||
for i := uint64(1); i <= 2; i++ {
|
||||
@ -186,7 +189,7 @@ func TestWalk(t *testing.T) {
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
sentinelErr := fmt.Errorf("sentinel error")
|
||||
sentinelErr := errors.New("sentinel error")
|
||||
err = m.Walk(ctx, nil, func(key, value uint64) (stop bool, err error) {
|
||||
require.LessOrEqual(t, key, uint64(3)) // asserts that after the number three we stop
|
||||
if key == 3 {
|
||||
|
||||
58
collections/json.go
Normal file
58
collections/json.go
Normal file
@ -0,0 +1,58 @@
|
||||
package collections
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"cosmossdk.io/collections/codec"
|
||||
)
|
||||
|
||||
func NewJSONValueCodec[T any]() codec.ValueCodec[T] {
|
||||
return jsonValue[T]{
|
||||
typeName: fmt.Sprintf("%T", new(T)),
|
||||
}
|
||||
}
|
||||
|
||||
type jsonValue[T any] struct {
|
||||
typeName string
|
||||
}
|
||||
|
||||
// Decode implements codec.ValueCodec.
|
||||
func (jsonValue[T]) Decode(b []byte) (T, error) {
|
||||
var t T
|
||||
if err := json.Unmarshal(b, &t); err != nil {
|
||||
return t, err
|
||||
}
|
||||
|
||||
return t, nil
|
||||
}
|
||||
|
||||
// DecodeJSON implements codec.ValueCodec.
|
||||
func (jsonValue[T]) DecodeJSON(b []byte) (T, error) {
|
||||
var t T
|
||||
if err := json.Unmarshal(b, &t); err != nil {
|
||||
return t, err
|
||||
}
|
||||
|
||||
return t, nil
|
||||
}
|
||||
|
||||
// Encode implements codec.ValueCodec.
|
||||
func (jsonValue[T]) Encode(value T) ([]byte, error) {
|
||||
return json.Marshal(value)
|
||||
}
|
||||
|
||||
// EncodeJSON implements codec.ValueCodec.
|
||||
func (jsonValue[T]) EncodeJSON(value T) ([]byte, error) {
|
||||
return json.Marshal(value)
|
||||
}
|
||||
|
||||
// Stringify implements codec.ValueCodec.
|
||||
func (jsonValue[T]) Stringify(value T) string {
|
||||
return fmt.Sprintf("%v", value)
|
||||
}
|
||||
|
||||
// ValueType implements codec.ValueCodec.
|
||||
func (jv jsonValue[T]) ValueType() string {
|
||||
return fmt.Sprintf("json(%s)", jv.typeName)
|
||||
}
|
||||
@ -8,14 +8,53 @@ import (
|
||||
"cosmossdk.io/collections/codec"
|
||||
)
|
||||
|
||||
// WithKeySetUncheckedValue changes the behavior of the KeySet when it encounters
|
||||
// a value different from '[]byte{}', by default the KeySet errors when this happens.
|
||||
// This option allows to ignore the value and continue with the operation, in turn
|
||||
// the value will be cleared out and set to '[]byte{}'.
|
||||
// You should never use this option if you're creating a new state object from scratch.
|
||||
// This should be used only to behave nicely in case you have used values different
|
||||
// from '[]byte{}' in your storage before migrating to collections.
|
||||
func WithKeySetUncheckedValue() func(opt *keySetOptions) {
|
||||
return func(opt *keySetOptions) {
|
||||
opt.uncheckedValue = true
|
||||
}
|
||||
}
|
||||
|
||||
// WithKeySetSecondaryIndex changes the behavior of the KeySet to be a secondary index.
|
||||
func WithKeySetSecondaryIndex() func(opt *keySetOptions) {
|
||||
return func(opt *keySetOptions) {
|
||||
opt.isSecondaryIndex = true
|
||||
}
|
||||
}
|
||||
|
||||
type keySetOptions struct {
|
||||
uncheckedValue bool
|
||||
isSecondaryIndex bool
|
||||
}
|
||||
|
||||
// KeySet builds on top of a Map and represents a collection retaining only a set
|
||||
// of keys and no value. It can be used, for example, in an allow list.
|
||||
type KeySet[K any] Map[K, NoValue]
|
||||
|
||||
// NewKeySet returns a KeySet given a Schema, Prefix a human name for the collection
|
||||
// and a KeyCodec for the key K.
|
||||
func NewKeySet[K any](schema *SchemaBuilder, prefix Prefix, name string, keyCodec codec.KeyCodec[K]) KeySet[K] {
|
||||
return (KeySet[K])(NewMap(schema, prefix, name, keyCodec, noValueCodec))
|
||||
func NewKeySet[K any](
|
||||
schema *SchemaBuilder,
|
||||
prefix Prefix,
|
||||
name string,
|
||||
keyCodec codec.KeyCodec[K],
|
||||
options ...func(opt *keySetOptions),
|
||||
) KeySet[K] {
|
||||
o := new(keySetOptions)
|
||||
for _, opt := range options {
|
||||
opt(o)
|
||||
}
|
||||
vc := noValueCodec
|
||||
if o.uncheckedValue {
|
||||
vc = codec.NewAltValueCodec(vc, func(_ []byte) (NoValue, error) { return NoValue{}, nil })
|
||||
}
|
||||
return (KeySet[K])(NewMap(schema, prefix, name, keyCodec, vc, withMapSecondaryIndex(o.isSecondaryIndex)))
|
||||
}
|
||||
|
||||
// Set adds the key to the KeySet. Errors on encoding problems.
|
||||
@ -57,6 +96,12 @@ func (k KeySet[K]) Walk(ctx context.Context, ranger Ranger[K], walkFunc func(key
|
||||
return (Map[K, NoValue])(k).Walk(ctx, ranger, func(key K, value NoValue) (bool, error) { return walkFunc(key) })
|
||||
}
|
||||
|
||||
// Clear clears the KeySet using the provided Ranger. Refer to Map.Clear for
|
||||
// behavioral documentation.
|
||||
func (k KeySet[K]) Clear(ctx context.Context, ranger Ranger[K]) error {
|
||||
return (Map[K, NoValue])(k).Clear(ctx, ranger)
|
||||
}
|
||||
|
||||
func (k KeySet[K]) KeyCodec() codec.KeyCodec[K] { return (Map[K, NoValue])(k).KeyCodec() }
|
||||
func (k KeySet[K]) ValueCodec() codec.ValueCodec[NoValue] { return (Map[K, NoValue])(k).ValueCodec() }
|
||||
|
||||
@ -73,6 +118,7 @@ var noValueCodec codec.ValueCodec[NoValue] = NoValue{}
|
||||
|
||||
const noValueValueType = "no_value"
|
||||
|
||||
// NoValue is a type that can be used to represent a non-existing value.
|
||||
type NoValue struct{}
|
||||
|
||||
func (n NoValue) EncodeJSON(_ NoValue) ([]byte, error) {
|
||||
@ -92,7 +138,7 @@ func (NoValue) Encode(_ NoValue) ([]byte, error) {
|
||||
|
||||
func (NoValue) Decode(b []byte) (NoValue, error) {
|
||||
if !bytes.Equal(b, []byte{}) {
|
||||
return NoValue{}, fmt.Errorf("%w: invalid value, wanted an empty non-nil byte slice", ErrEncoding)
|
||||
return NoValue{}, fmt.Errorf("%w: invalid value, wanted an empty non-nil byte slice, got: %x", ErrEncoding, b)
|
||||
}
|
||||
return NoValue{}, nil
|
||||
}
|
||||
|
||||
@ -67,3 +67,34 @@ func Test_noValue(t *testing.T) {
|
||||
_, err = noValueCodec.Decode([]byte("bad"))
|
||||
require.ErrorIs(t, err, ErrEncoding)
|
||||
}
|
||||
|
||||
func TestUncheckedKeySet(t *testing.T) {
|
||||
sk, ctx := deps()
|
||||
schema := NewSchemaBuilder(sk)
|
||||
uncheckedKs := NewKeySet(schema, NewPrefix("keyset"), "keyset", StringKey, WithKeySetUncheckedValue())
|
||||
ks := NewKeySet(schema, NewPrefix("keyset"), "keyset", StringKey)
|
||||
// we set a NoValue unfriendly value.
|
||||
require.NoError(t, sk.OpenKVStore(ctx).Set([]byte("keyset1"), []byte("A")))
|
||||
require.NoError(t, sk.OpenKVStore(ctx).Set([]byte("keyset2"), []byte("B")))
|
||||
|
||||
// the standard KeySet errors here, because it doesn't like the fact that the value is []byte("A")
|
||||
// and not []byte{}.
|
||||
err := ks.Walk(ctx, nil, func(key string) (stop bool, err error) {
|
||||
return true, nil
|
||||
})
|
||||
require.ErrorIs(t, err, ErrEncoding)
|
||||
|
||||
// the unchecked KeySet doesn't care about the value, so it works.
|
||||
err = uncheckedKs.Walk(ctx, nil, func(key string) (stop bool, err error) {
|
||||
require.Equal(t, "1", key)
|
||||
return true, nil
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
// now we set it again
|
||||
require.NoError(t, uncheckedKs.Set(ctx, "1"))
|
||||
// and we will see that the value which was []byte("A") has been cleared to be []byte{}
|
||||
raw, err := sk.OpenKVStore(ctx).Get([]byte("keyset1"))
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, []byte{}, raw)
|
||||
}
|
||||
|
||||
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 }
|
||||
42
collections/lookup_map_test.go
Normal file
42
collections/lookup_map_test.go
Normal file
@ -0,0 +1,42 @@
|
||||
package collections_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"cosmossdk.io/collections"
|
||||
"cosmossdk.io/core/testing"
|
||||
)
|
||||
|
||||
func TestLookupMap(t *testing.T) {
|
||||
ctx := coretesting.Context()
|
||||
sk := coretesting.KVStoreService(ctx, "test")
|
||||
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)
|
||||
}
|
||||
@ -1,6 +1,7 @@
|
||||
package collections
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
@ -19,6 +20,22 @@ type Map[K, V any] struct {
|
||||
sa func(context.Context) store.KVStore
|
||||
prefix []byte
|
||||
name string
|
||||
|
||||
// isSecondaryIndex indicates that this map represents a secondary index
|
||||
// on another collection and that it should be skipped when generating
|
||||
// a user facing schema
|
||||
isSecondaryIndex bool
|
||||
}
|
||||
|
||||
// withMapSecondaryIndex changes the behavior of the Map to be a secondary index.
|
||||
func withMapSecondaryIndex(isSecondaryIndex bool) func(opt *mapOptions) {
|
||||
return func(opt *mapOptions) {
|
||||
opt.isSecondaryIndex = isSecondaryIndex
|
||||
}
|
||||
}
|
||||
|
||||
type mapOptions struct {
|
||||
isSecondaryIndex bool
|
||||
}
|
||||
|
||||
// NewMap returns a Map given a StoreKey, a Prefix, human-readable name and the relative value and key encoders.
|
||||
@ -30,13 +47,19 @@ func NewMap[K, V any](
|
||||
name string,
|
||||
keyCodec codec.KeyCodec[K],
|
||||
valueCodec codec.ValueCodec[V],
|
||||
options ...func(opt *mapOptions),
|
||||
) Map[K, V] {
|
||||
o := new(mapOptions)
|
||||
for _, opt := range options {
|
||||
opt(o)
|
||||
}
|
||||
m := Map[K, V]{
|
||||
kc: keyCodec,
|
||||
vc: valueCodec,
|
||||
sa: schemaBuilder.schema.storeAccessor,
|
||||
prefix: prefix.Bytes(),
|
||||
name: name,
|
||||
kc: keyCodec,
|
||||
vc: valueCodec,
|
||||
sa: schemaBuilder.schema.storeAccessor,
|
||||
prefix: prefix.Bytes(),
|
||||
name: name,
|
||||
isSecondaryIndex: o.isSecondaryIndex,
|
||||
}
|
||||
schemaBuilder.addCollection(collectionImpl[K, V]{m})
|
||||
return m
|
||||
@ -60,12 +83,11 @@ 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)
|
||||
kvStore.Set(bytesKey, valueBytes)
|
||||
return nil
|
||||
return kvStore.Set(bytesKey, valueBytes)
|
||||
}
|
||||
|
||||
// Get returns the value associated with the provided key,
|
||||
@ -88,7 +110,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
|
||||
}
|
||||
@ -149,6 +171,58 @@ func (m Map[K, V]) Walk(ctx context.Context, ranger Ranger[K], walkFunc func(key
|
||||
return nil
|
||||
}
|
||||
|
||||
// Clear clears the collection contained within the provided key range.
|
||||
// A nil ranger equals to clearing the whole collection.
|
||||
// NOTE: this API needs to be used with care, considering that as of today
|
||||
// cosmos-sdk stores the deletion records to be committed in a memory cache,
|
||||
// clearing a lot of data might make the node go OOM.
|
||||
func (m Map[K, V]) Clear(ctx context.Context, ranger Ranger[K]) error {
|
||||
startBytes, endBytes, _, err := parseRangeInstruction(m.prefix, m.kc, ranger)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return deleteDomain(m.sa(ctx), startBytes, endBytes)
|
||||
}
|
||||
|
||||
const clearBatchSize = 10000
|
||||
|
||||
// deleteDomain deletes the domain of an iterator, the key difference
|
||||
// is that it uses batches to clear the store meaning that it will read
|
||||
// the keys within the domain close the iterator and then delete them.
|
||||
func deleteDomain(s store.KVStore, start, end []byte) error {
|
||||
for {
|
||||
iter, err := s.Iterator(start, end)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
keys := make([][]byte, 0, clearBatchSize)
|
||||
for ; iter.Valid() && len(keys) < clearBatchSize; iter.Next() {
|
||||
keys = append(keys, iter.Key())
|
||||
}
|
||||
|
||||
// we close the iterator here instead of deferring
|
||||
err = iter.Close()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, key := range keys {
|
||||
err = s.Delete(key)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// If we've retrieved less than the batchSize, we're done.
|
||||
if len(keys) < clearBatchSize {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// IterateRaw iterates over the collection. The iteration range is untyped, it uses raw
|
||||
// bytes. The resulting Iterator is typed.
|
||||
// A nil start iterates from the first key contained in the collection.
|
||||
@ -164,6 +238,10 @@ func (m Map[K, V]) IterateRaw(ctx context.Context, start, end []byte, order Orde
|
||||
prefixedEnd = append(m.prefix, end...)
|
||||
}
|
||||
|
||||
if bytes.Compare(prefixedStart, prefixedEnd) == 1 {
|
||||
return Iterator[K, V]{}, ErrInvalidIterator
|
||||
}
|
||||
|
||||
s := m.sa(ctx)
|
||||
var (
|
||||
storeIter store.Iterator
|
||||
@ -181,9 +259,6 @@ func (m Map[K, V]) IterateRaw(ctx context.Context, start, end []byte, order Orde
|
||||
return Iterator[K, V]{}, err
|
||||
}
|
||||
|
||||
if !storeIter.Valid() {
|
||||
return Iterator[K, V]{}, ErrInvalidIterator
|
||||
}
|
||||
return Iterator[K, V]{
|
||||
kc: m.kc,
|
||||
vc: m.vc,
|
||||
@ -209,7 +284,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
|
||||
}
|
||||
|
||||
@ -1,6 +1,8 @@
|
||||
package collections
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
@ -36,6 +38,42 @@ func TestMap(t *testing.T) {
|
||||
require.False(t, has)
|
||||
}
|
||||
|
||||
func TestMap_Clear(t *testing.T) {
|
||||
makeTest := func() (context.Context, Map[uint64, uint64]) {
|
||||
sk, ctx := deps()
|
||||
m := NewMap(NewSchemaBuilder(sk), NewPrefix(0), "test", Uint64Key, Uint64Value)
|
||||
for i := uint64(0); i < clearBatchSize*2; i++ {
|
||||
require.NoError(t, m.Set(ctx, i, i))
|
||||
}
|
||||
return ctx, m
|
||||
}
|
||||
|
||||
t.Run("nil ranger", func(t *testing.T) {
|
||||
ctx, m := makeTest()
|
||||
err := m.Clear(ctx, nil)
|
||||
require.NoError(t, err)
|
||||
err = m.Walk(ctx, nil, func(key, value uint64) (bool, error) {
|
||||
return false, errors.New("should never be called")
|
||||
})
|
||||
require.NoError(t, err)
|
||||
})
|
||||
|
||||
t.Run("custom ranger", func(t *testing.T) {
|
||||
ctx, m := makeTest()
|
||||
// delete from 0 to 100
|
||||
err := m.Clear(ctx, new(Range[uint64]).StartInclusive(0).EndInclusive(100))
|
||||
require.NoError(t, err)
|
||||
|
||||
iter, err := m.Iterate(ctx, nil)
|
||||
require.NoError(t, err)
|
||||
keys, err := iter.Keys()
|
||||
require.NoError(t, err)
|
||||
require.Len(t, keys, clearBatchSize*2-101)
|
||||
require.Equal(t, keys[0], uint64(101))
|
||||
require.Equal(t, keys[len(keys)-1], uint64(clearBatchSize*2-1))
|
||||
})
|
||||
}
|
||||
|
||||
func TestMap_IterateRaw(t *testing.T) {
|
||||
sk, ctx := deps()
|
||||
// safety check to ensure prefix boundaries are not crossed
|
||||
@ -69,6 +107,15 @@ func TestMap_IterateRaw(t *testing.T) {
|
||||
keys, err = iter.Keys()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, []uint64{2, 1, 0}, keys)
|
||||
|
||||
// test invalid iter
|
||||
_, err = m.IterateRaw(ctx, []byte{0x2, 0x0}, []byte{0x0, 0x0}, OrderAscending)
|
||||
require.ErrorIs(t, err, ErrInvalidIterator)
|
||||
|
||||
// test on empty collection iterating does not error
|
||||
require.NoError(t, m.Clear(ctx, nil))
|
||||
_, err = m.IterateRaw(ctx, nil, nil, OrderAscending)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
func Test_encodeKey(t *testing.T) {
|
||||
|
||||
59
collections/naming_test.go
Normal file
59
collections/naming_test.go
Normal file
@ -0,0 +1,59 @@
|
||||
package collections
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"cosmossdk.io/collections/codec"
|
||||
)
|
||||
|
||||
func TestNaming(t *testing.T) {
|
||||
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")
|
||||
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]) {
|
||||
t.Helper()
|
||||
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]) {
|
||||
t.Helper()
|
||||
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) {
|
||||
t.Helper()
|
||||
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)
|
||||
}
|
||||
}
|
||||
@ -6,6 +6,7 @@ import (
|
||||
"strings"
|
||||
|
||||
"cosmossdk.io/collections/codec"
|
||||
"cosmossdk.io/schema"
|
||||
)
|
||||
|
||||
// Pair defines a key composed of two keys.
|
||||
@ -54,9 +55,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.
|
||||
// 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,
|
||||
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 +230,94 @@ 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)
|
||||
}
|
||||
|
||||
func (p pairKeyCodec[K1, K2]) SchemaCodec() (codec.SchemaCodec[Pair[K1, K2]], error) {
|
||||
field1, err := getNamedKeyField(p.keyCodec1, p.key1Name)
|
||||
if err != nil {
|
||||
return codec.SchemaCodec[Pair[K1, K2]]{}, fmt.Errorf("error getting key1 field: %w", err)
|
||||
}
|
||||
|
||||
field2, err := getNamedKeyField(p.keyCodec2, p.key2Name)
|
||||
if err != nil {
|
||||
return codec.SchemaCodec[Pair[K1, K2]]{}, fmt.Errorf("error getting key2 field: %w", err)
|
||||
}
|
||||
|
||||
codec1, err := codec.KeySchemaCodec(p.keyCodec1)
|
||||
if err != nil {
|
||||
return codec.SchemaCodec[Pair[K1, K2]]{}, fmt.Errorf("error getting key1 schema codec: %w", err)
|
||||
}
|
||||
|
||||
codec2, err := codec.KeySchemaCodec(p.keyCodec2)
|
||||
if err != nil {
|
||||
return codec.SchemaCodec[Pair[K1, K2]]{}, fmt.Errorf("error getting key2 schema codec: %w", err)
|
||||
}
|
||||
|
||||
return codec.SchemaCodec[Pair[K1, K2]]{
|
||||
Fields: []schema.Field{field1, field2},
|
||||
ToSchemaType: func(pair Pair[K1, K2]) (any, error) {
|
||||
k1, err := toKeySchemaType(codec1, pair.K1())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
k2, err := toKeySchemaType(codec2, pair.K2())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return []interface{}{k1, k2}, nil
|
||||
},
|
||||
FromSchemaType: func(a any) (Pair[K1, K2], error) {
|
||||
aSlice, ok := a.([]interface{})
|
||||
if !ok || len(aSlice) != 2 {
|
||||
return Pair[K1, K2]{}, fmt.Errorf("expected slice of length 2, got %T", a)
|
||||
}
|
||||
k1, err := fromKeySchemaType(codec1, aSlice[0])
|
||||
if err != nil {
|
||||
return Pair[K1, K2]{}, err
|
||||
}
|
||||
k2, err := fromKeySchemaType(codec2, aSlice[1])
|
||||
if err != nil {
|
||||
return Pair[K1, K2]{}, err
|
||||
}
|
||||
return Join(k1, k2), nil
|
||||
},
|
||||
}, 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
|
||||
}
|
||||
|
||||
func toKeySchemaType[T any](cdc codec.SchemaCodec[T], key T) (any, error) {
|
||||
if cdc.ToSchemaType != nil {
|
||||
return cdc.ToSchemaType(key)
|
||||
}
|
||||
return key, nil
|
||||
}
|
||||
|
||||
func fromKeySchemaType[T any](cdc codec.SchemaCodec[T], key any) (T, error) {
|
||||
if cdc.FromSchemaType != nil {
|
||||
return cdc.FromSchemaType(key)
|
||||
}
|
||||
tKey, ok := key.(T)
|
||||
if !ok {
|
||||
var zero T
|
||||
return zero, fmt.Errorf("expected type %T, got %T", zero, key)
|
||||
}
|
||||
return tKey, 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] {
|
||||
|
||||
137
collections/protocodec/collections.go
Normal file
137
collections/protocodec/collections.go
Normal file
@ -0,0 +1,137 @@
|
||||
package protocodec
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/cosmos/gogoproto/proto"
|
||||
gogotypes "github.com/cosmos/gogoproto/types"
|
||||
"google.golang.org/protobuf/encoding/protojson"
|
||||
protov2 "google.golang.org/protobuf/proto"
|
||||
|
||||
"cosmossdk.io/collections"
|
||||
collcodec "cosmossdk.io/collections/codec"
|
||||
corecodec "cosmossdk.io/core/codec"
|
||||
)
|
||||
|
||||
// BoolValue implements a ValueCodec that saves the bool value
|
||||
// as if it was a prototypes.BoolValue. Required for backwards
|
||||
// compatibility of state.
|
||||
var BoolValue collcodec.ValueCodec[bool] = boolValue{}
|
||||
|
||||
type boolValue struct{}
|
||||
|
||||
func (boolValue) Encode(value bool) ([]byte, error) {
|
||||
return (&gogotypes.BoolValue{Value: value}).Marshal()
|
||||
}
|
||||
|
||||
func (boolValue) Decode(b []byte) (bool, error) {
|
||||
v := new(gogotypes.BoolValue)
|
||||
err := v.Unmarshal(b)
|
||||
return v.Value, err
|
||||
}
|
||||
|
||||
func (boolValue) EncodeJSON(value bool) ([]byte, error) {
|
||||
return collections.BoolValue.EncodeJSON(value)
|
||||
}
|
||||
|
||||
func (boolValue) DecodeJSON(b []byte) (bool, error) {
|
||||
return collections.BoolValue.DecodeJSON(b)
|
||||
}
|
||||
|
||||
func (boolValue) Stringify(value bool) string {
|
||||
return collections.BoolValue.Stringify(value)
|
||||
}
|
||||
|
||||
func (boolValue) ValueType() string {
|
||||
return "protobuf/bool"
|
||||
}
|
||||
|
||||
type protoMessage[T any] interface {
|
||||
*T
|
||||
proto.Message
|
||||
}
|
||||
|
||||
// CollValue inits a collections.ValueCodec for a generic gogo protobuf message.
|
||||
func CollValue[T any, PT protoMessage[T]](cdc interface {
|
||||
Marshal(proto.Message) ([]byte, error)
|
||||
Unmarshal([]byte, proto.Message) error
|
||||
},
|
||||
) collcodec.ValueCodec[T] {
|
||||
return &collValue[T, PT]{cdc.(corecodec.Codec), proto.MessageName(PT(new(T)))}
|
||||
}
|
||||
|
||||
type collValue[T any, PT protoMessage[T]] struct {
|
||||
cdc corecodec.Codec
|
||||
messageName string
|
||||
}
|
||||
|
||||
func (c collValue[T, PT]) Encode(value T) ([]byte, error) {
|
||||
return c.cdc.Marshal(PT(&value))
|
||||
}
|
||||
|
||||
func (c collValue[T, PT]) Decode(b []byte) (value T, err error) {
|
||||
err = c.cdc.Unmarshal(b, PT(&value))
|
||||
return value, err
|
||||
}
|
||||
|
||||
func (c collValue[T, PT]) EncodeJSON(value T) ([]byte, error) {
|
||||
return c.cdc.MarshalJSON(PT(&value))
|
||||
}
|
||||
|
||||
func (c collValue[T, PT]) DecodeJSON(b []byte) (value T, err error) {
|
||||
err = c.cdc.UnmarshalJSON(b, PT(&value))
|
||||
return
|
||||
}
|
||||
|
||||
func (c collValue[T, PT]) Stringify(value T) string {
|
||||
return PT(&value).String()
|
||||
}
|
||||
|
||||
func (c collValue[T, PT]) ValueType() string {
|
||||
return "github.com/cosmos/gogoproto/" + c.messageName
|
||||
}
|
||||
|
||||
type protoMessageV2[T any] interface {
|
||||
*T
|
||||
protov2.Message
|
||||
}
|
||||
|
||||
// CollValueV2 is used for protobuf values of the newest google.golang.org/protobuf API.
|
||||
func CollValueV2[T any, PT protoMessageV2[T]]() collcodec.ValueCodec[PT] {
|
||||
return &collValue2[T, PT]{
|
||||
messageName: string(PT(new(T)).ProtoReflect().Descriptor().FullName()),
|
||||
}
|
||||
}
|
||||
|
||||
type collValue2[T any, PT protoMessageV2[T]] struct {
|
||||
messageName string
|
||||
}
|
||||
|
||||
func (c collValue2[T, PT]) Encode(value PT) ([]byte, error) {
|
||||
protov2MarshalOpts := protov2.MarshalOptions{Deterministic: true}
|
||||
return protov2MarshalOpts.Marshal(value)
|
||||
}
|
||||
|
||||
func (c collValue2[T, PT]) Decode(b []byte) (PT, error) {
|
||||
var value T
|
||||
err := protov2.Unmarshal(b, PT(&value))
|
||||
return &value, err
|
||||
}
|
||||
|
||||
func (c collValue2[T, PT]) EncodeJSON(value PT) ([]byte, error) {
|
||||
return protojson.Marshal(value)
|
||||
}
|
||||
|
||||
func (c collValue2[T, PT]) DecodeJSON(b []byte) (PT, error) {
|
||||
var value T
|
||||
err := protojson.Unmarshal(b, PT(&value))
|
||||
return &value, err
|
||||
}
|
||||
|
||||
func (c collValue2[T, PT]) Stringify(value PT) string {
|
||||
return fmt.Sprintf("%v", value)
|
||||
}
|
||||
|
||||
func (c collValue2[T, PT]) ValueType() string {
|
||||
return "google.golang.org/protobuf/" + c.messageName
|
||||
}
|
||||
55
collections/protocodec/collections_test.go
Normal file
55
collections/protocodec/collections_test.go
Normal file
@ -0,0 +1,55 @@
|
||||
package protocodec_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
gogotypes "github.com/cosmos/gogoproto/types"
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/stretchr/testify/require"
|
||||
"google.golang.org/protobuf/testing/protocmp"
|
||||
"google.golang.org/protobuf/types/known/wrapperspb"
|
||||
|
||||
"cosmossdk.io/collections/colltest"
|
||||
codec "cosmossdk.io/collections/protocodec"
|
||||
)
|
||||
|
||||
func TestCollectionsCorrectness(t *testing.T) {
|
||||
t.Run("CollValueV2", func(t *testing.T) {
|
||||
// NOTE: we cannot use colltest.TestValueCodec because protov2 has different
|
||||
// compare semantics than protov1. We need to use protocmp.Transform() alongside
|
||||
// cmp to ensure equality.
|
||||
encoder := codec.CollValueV2[wrapperspb.UInt64Value]()
|
||||
value := &wrapperspb.UInt64Value{Value: 500}
|
||||
encodedValue, err := encoder.Encode(value)
|
||||
require.NoError(t, err)
|
||||
decodedValue, err := encoder.Decode(encodedValue)
|
||||
require.NoError(t, err)
|
||||
require.True(t, cmp.Equal(value, decodedValue, protocmp.Transform()), "encoding and decoding produces different values")
|
||||
|
||||
encodedJSONValue, err := encoder.EncodeJSON(value)
|
||||
require.NoError(t, err)
|
||||
decodedJSONValue, err := encoder.DecodeJSON(encodedJSONValue)
|
||||
require.NoError(t, err)
|
||||
require.True(t, cmp.Equal(value, decodedJSONValue, protocmp.Transform()), "encoding and decoding produces different values")
|
||||
require.NotEmpty(t, encoder.ValueType())
|
||||
|
||||
_ = encoder.Stringify(value)
|
||||
})
|
||||
|
||||
t.Run("BoolValue", func(t *testing.T) {
|
||||
colltest.TestValueCodec(t, codec.BoolValue, true)
|
||||
colltest.TestValueCodec(t, codec.BoolValue, false)
|
||||
|
||||
// asserts produced bytes are equal
|
||||
valueAssert := func(b bool) {
|
||||
wantBytes, err := (&gogotypes.BoolValue{Value: b}).Marshal()
|
||||
require.NoError(t, err)
|
||||
gotBytes, err := codec.BoolValue.Encode(b)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, wantBytes, gotBytes)
|
||||
}
|
||||
|
||||
valueAssert(true)
|
||||
valueAssert(false)
|
||||
})
|
||||
}
|
||||
435
collections/quad.go
Normal file
435
collections/quad.go
Normal file
@ -0,0 +1,435 @@
|
||||
package collections
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"cosmossdk.io/collections/codec"
|
||||
"cosmossdk.io/schema"
|
||||
)
|
||||
|
||||
// Quad defines a multipart key composed of four keys.
|
||||
type Quad[K1, K2, K3, K4 any] struct {
|
||||
k1 *K1
|
||||
k2 *K2
|
||||
k3 *K3
|
||||
k4 *K4
|
||||
}
|
||||
|
||||
// Join4 instantiates a new Quad instance composed of the four provided keys, in order.
|
||||
func Join4[K1, K2, K3, K4 any](k1 K1, k2 K2, k3 K3, k4 K4) Quad[K1, K2, K3, K4] {
|
||||
return Quad[K1, K2, K3, K4]{&k1, &k2, &k3, &k4}
|
||||
}
|
||||
|
||||
// K1 returns the first part of the key. If nil, the zero value is returned.
|
||||
func (t Quad[K1, K2, K3, K4]) K1() (x K1) {
|
||||
if t.k1 != nil {
|
||||
return *t.k1
|
||||
}
|
||||
return x
|
||||
}
|
||||
|
||||
// K2 returns the second part of the key. If nil, the zero value is returned.
|
||||
func (t Quad[K1, K2, K3, K4]) K2() (x K2) {
|
||||
if t.k2 != nil {
|
||||
return *t.k2
|
||||
}
|
||||
return x
|
||||
}
|
||||
|
||||
// K3 returns the third part of the key. If nil, the zero value is returned.
|
||||
func (t Quad[K1, K2, K3, K4]) K3() (x K3) {
|
||||
if t.k3 != nil {
|
||||
return *t.k3
|
||||
}
|
||||
return x
|
||||
}
|
||||
|
||||
// K4 returns the fourth part of the key. If nil, the zero value is returned.
|
||||
func (t Quad[K1, K2, K3, K4]) K4() (x K4) {
|
||||
if t.k4 != nil {
|
||||
return *t.k4
|
||||
}
|
||||
return x
|
||||
}
|
||||
|
||||
// QuadPrefix creates a new Quad instance composed only of the first part of the key.
|
||||
func QuadPrefix[K1, K2, K3, K4 any](k1 K1) Quad[K1, K2, K3, K4] {
|
||||
return Quad[K1, K2, K3, K4]{k1: &k1}
|
||||
}
|
||||
|
||||
// QuadSuperPrefix creates a new Quad instance composed only of the first two parts of the key.
|
||||
func QuadSuperPrefix[K1, K2, K3, K4 any](k1 K1, k2 K2) Quad[K1, K2, K3, K4] {
|
||||
return Quad[K1, K2, K3, K4]{k1: &k1, k2: &k2}
|
||||
}
|
||||
|
||||
// QuadSuperPrefix3 creates a new Quad instance composed only of the first three parts of the key.
|
||||
func QuadSuperPrefix3[K1, K2, K3, K4 any](k1 K1, k2 K2, k3 K3) Quad[K1, K2, K3, K4] {
|
||||
return Quad[K1, K2, K3, K4]{k1: &k1, k2: &k2, k3: &k3}
|
||||
}
|
||||
|
||||
// QuadKeyCodec instantiates a new KeyCodec instance that can encode the Quad, given
|
||||
// the KeyCodecs of the four parts of the key, in order.
|
||||
func QuadKeyCodec[K1, K2, K3, K4 any](keyCodec1 codec.KeyCodec[K1], keyCodec2 codec.KeyCodec[K2], keyCodec3 codec.KeyCodec[K3], keyCodec4 codec.KeyCodec[K4]) codec.KeyCodec[Quad[K1, K2, K3, K4]] {
|
||||
return quadKeyCodec[K1, K2, K3, K4]{
|
||||
keyCodec1: keyCodec1,
|
||||
keyCodec2: keyCodec2,
|
||||
keyCodec3: keyCodec3,
|
||||
keyCodec4: keyCodec4,
|
||||
}
|
||||
}
|
||||
|
||||
// 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 {
|
||||
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
|
||||
|
||||
// EncodeJSON encodes Quads to json
|
||||
func (t quadKeyCodec[K1, K2, K3, K4]) EncodeJSON(value Quad[K1, K2, K3, K4]) ([]byte, error) {
|
||||
json1, err := t.keyCodec1.EncodeJSON(*value.k1)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
json2, err := t.keyCodec2.EncodeJSON(*value.k2)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
json3, err := t.keyCodec3.EncodeJSON(*value.k3)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
json4, err := t.keyCodec4.EncodeJSON(*value.k4)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return json.Marshal(jsonQuadKey{json1, json2, json3, json4})
|
||||
}
|
||||
|
||||
// DecodeJSON decodes json to Quads
|
||||
func (t quadKeyCodec[K1, K2, K3, K4]) DecodeJSON(b []byte) (Quad[K1, K2, K3, K4], error) {
|
||||
var jsonKey jsonQuadKey
|
||||
err := json.Unmarshal(b, &jsonKey)
|
||||
if err != nil {
|
||||
return Quad[K1, K2, K3, K4]{}, err
|
||||
}
|
||||
|
||||
key1, err := t.keyCodec1.DecodeJSON(jsonKey[0])
|
||||
if err != nil {
|
||||
return Quad[K1, K2, K3, K4]{}, err
|
||||
}
|
||||
|
||||
key2, err := t.keyCodec2.DecodeJSON(jsonKey[1])
|
||||
if err != nil {
|
||||
return Quad[K1, K2, K3, K4]{}, err
|
||||
}
|
||||
|
||||
key3, err := t.keyCodec3.DecodeJSON(jsonKey[2])
|
||||
if err != nil {
|
||||
return Quad[K1, K2, K3, K4]{}, err
|
||||
}
|
||||
|
||||
key4, err := t.keyCodec4.DecodeJSON(jsonKey[3])
|
||||
if err != nil {
|
||||
return Quad[K1, K2, K3, K4]{}, err
|
||||
}
|
||||
|
||||
return Join4(key1, key2, key3, key4), nil
|
||||
}
|
||||
|
||||
// Stringify converts Quads to string
|
||||
func (t quadKeyCodec[K1, K2, K3, K4]) Stringify(key Quad[K1, K2, K3, K4]) string {
|
||||
b := new(strings.Builder)
|
||||
b.WriteByte('(')
|
||||
if key.k1 != nil {
|
||||
b.WriteByte('"')
|
||||
b.WriteString(t.keyCodec1.Stringify(*key.k1))
|
||||
b.WriteByte('"')
|
||||
} else {
|
||||
b.WriteString("<nil>")
|
||||
}
|
||||
|
||||
b.WriteString(", ")
|
||||
if key.k2 != nil {
|
||||
b.WriteByte('"')
|
||||
b.WriteString(t.keyCodec2.Stringify(*key.k2))
|
||||
b.WriteByte('"')
|
||||
} else {
|
||||
b.WriteString("<nil>")
|
||||
}
|
||||
|
||||
b.WriteString(", ")
|
||||
if key.k3 != nil {
|
||||
b.WriteByte('"')
|
||||
b.WriteString(t.keyCodec3.Stringify(*key.k3))
|
||||
b.WriteByte('"')
|
||||
} else {
|
||||
b.WriteString("<nil>")
|
||||
}
|
||||
|
||||
b.WriteString(", ")
|
||||
if key.k4 != nil {
|
||||
b.WriteByte('"')
|
||||
b.WriteString(t.keyCodec4.Stringify(*key.k4))
|
||||
b.WriteByte('"')
|
||||
} else {
|
||||
b.WriteString("<nil>")
|
||||
}
|
||||
|
||||
b.WriteByte(')')
|
||||
return b.String()
|
||||
}
|
||||
|
||||
func (t quadKeyCodec[K1, K2, K3, K4]) KeyType() string {
|
||||
return fmt.Sprintf("Quad[%s,%s,%s,%s]", t.keyCodec1.KeyType(), t.keyCodec2.KeyType(), t.keyCodec3.KeyType(), t.keyCodec4.KeyType())
|
||||
}
|
||||
|
||||
func (t quadKeyCodec[K1, K2, K3, K4]) Encode(buffer []byte, key Quad[K1, K2, K3, K4]) (int, error) {
|
||||
writtenTotal := 0
|
||||
if key.k1 != nil {
|
||||
written, err := t.keyCodec1.EncodeNonTerminal(buffer, *key.k1)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
writtenTotal += written
|
||||
}
|
||||
if key.k2 != nil {
|
||||
written, err := t.keyCodec2.EncodeNonTerminal(buffer[writtenTotal:], *key.k2)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
writtenTotal += written
|
||||
}
|
||||
if key.k3 != nil {
|
||||
written, err := t.keyCodec3.EncodeNonTerminal(buffer[writtenTotal:], *key.k3)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
writtenTotal += written
|
||||
}
|
||||
if key.k4 != nil {
|
||||
written, err := t.keyCodec4.Encode(buffer[writtenTotal:], *key.k4)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
writtenTotal += written
|
||||
}
|
||||
return writtenTotal, nil
|
||||
}
|
||||
|
||||
func (t quadKeyCodec[K1, K2, K3, K4]) Decode(buffer []byte) (int, Quad[K1, K2, K3, K4], error) {
|
||||
readTotal := 0
|
||||
read, key1, err := t.keyCodec1.DecodeNonTerminal(buffer)
|
||||
if err != nil {
|
||||
return 0, Quad[K1, K2, K3, K4]{}, err
|
||||
}
|
||||
readTotal += read
|
||||
read, key2, err := t.keyCodec2.DecodeNonTerminal(buffer[readTotal:])
|
||||
if err != nil {
|
||||
return 0, Quad[K1, K2, K3, K4]{}, err
|
||||
}
|
||||
readTotal += read
|
||||
read, key3, err := t.keyCodec3.DecodeNonTerminal(buffer[readTotal:])
|
||||
if err != nil {
|
||||
return 0, Quad[K1, K2, K3, K4]{}, err
|
||||
}
|
||||
readTotal += read
|
||||
read, key4, err := t.keyCodec4.Decode(buffer[readTotal:])
|
||||
if err != nil {
|
||||
return 0, Quad[K1, K2, K3, K4]{}, err
|
||||
}
|
||||
readTotal += read
|
||||
return readTotal, Join4(key1, key2, key3, key4), nil
|
||||
}
|
||||
|
||||
func (t quadKeyCodec[K1, K2, K3, K4]) Size(key Quad[K1, K2, K3, K4]) int {
|
||||
size := 0
|
||||
if key.k1 != nil {
|
||||
size += t.keyCodec1.SizeNonTerminal(*key.k1)
|
||||
}
|
||||
if key.k2 != nil {
|
||||
size += t.keyCodec2.SizeNonTerminal(*key.k2)
|
||||
}
|
||||
if key.k3 != nil {
|
||||
size += t.keyCodec3.SizeNonTerminal(*key.k3)
|
||||
}
|
||||
if key.k4 != nil {
|
||||
size += t.keyCodec4.Size(*key.k4)
|
||||
}
|
||||
return size
|
||||
}
|
||||
|
||||
func (t quadKeyCodec[K1, K2, K3, K4]) EncodeNonTerminal(buffer []byte, key Quad[K1, K2, K3, K4]) (int, error) {
|
||||
writtenTotal := 0
|
||||
if key.k1 != nil {
|
||||
written, err := t.keyCodec1.EncodeNonTerminal(buffer, *key.k1)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
writtenTotal += written
|
||||
}
|
||||
if key.k2 != nil {
|
||||
written, err := t.keyCodec2.EncodeNonTerminal(buffer[writtenTotal:], *key.k2)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
writtenTotal += written
|
||||
}
|
||||
if key.k3 != nil {
|
||||
written, err := t.keyCodec3.EncodeNonTerminal(buffer[writtenTotal:], *key.k3)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
writtenTotal += written
|
||||
}
|
||||
if key.k4 != nil {
|
||||
written, err := t.keyCodec4.EncodeNonTerminal(buffer[writtenTotal:], *key.k4)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
writtenTotal += written
|
||||
}
|
||||
return writtenTotal, nil
|
||||
}
|
||||
|
||||
func (t quadKeyCodec[K1, K2, K3, K4]) DecodeNonTerminal(buffer []byte) (int, Quad[K1, K2, K3, K4], error) {
|
||||
readTotal := 0
|
||||
read, key1, err := t.keyCodec1.DecodeNonTerminal(buffer)
|
||||
if err != nil {
|
||||
return 0, Quad[K1, K2, K3, K4]{}, err
|
||||
}
|
||||
readTotal += read
|
||||
read, key2, err := t.keyCodec2.DecodeNonTerminal(buffer[readTotal:])
|
||||
if err != nil {
|
||||
return 0, Quad[K1, K2, K3, K4]{}, err
|
||||
}
|
||||
readTotal += read
|
||||
read, key3, err := t.keyCodec3.DecodeNonTerminal(buffer[readTotal:])
|
||||
if err != nil {
|
||||
return 0, Quad[K1, K2, K3, K4]{}, err
|
||||
}
|
||||
readTotal += read
|
||||
read, key4, err := t.keyCodec4.DecodeNonTerminal(buffer[readTotal:])
|
||||
if err != nil {
|
||||
return 0, Quad[K1, K2, K3, K4]{}, err
|
||||
}
|
||||
readTotal += read
|
||||
return readTotal, Join4(key1, key2, key3, key4), nil
|
||||
}
|
||||
|
||||
func (t quadKeyCodec[K1, K2, K3, K4]) SizeNonTerminal(key Quad[K1, K2, K3, K4]) int {
|
||||
size := 0
|
||||
if key.k1 != nil {
|
||||
size += t.keyCodec1.SizeNonTerminal(*key.k1)
|
||||
}
|
||||
if key.k2 != nil {
|
||||
size += t.keyCodec2.SizeNonTerminal(*key.k2)
|
||||
}
|
||||
if key.k3 != nil {
|
||||
size += t.keyCodec3.SizeNonTerminal(*key.k3)
|
||||
}
|
||||
if key.k4 != nil {
|
||||
size += t.keyCodec4.SizeNonTerminal(*key.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},
|
||||
ToSchemaType: func(q Quad[K1, K2, K3, K4]) (any, error) {
|
||||
return []interface{}{q.K1(), q.K2(), q.K3(), q.K4()}, nil
|
||||
},
|
||||
FromSchemaType: func(a any) (Quad[K1, K2, K3, K4], error) {
|
||||
aSlice, ok := a.([]interface{})
|
||||
if !ok || len(aSlice) != 4 {
|
||||
return Quad[K1, K2, K3, K4]{}, fmt.Errorf("expected slice of length 4, got %T", a)
|
||||
}
|
||||
return Join4(aSlice[0].(K1), aSlice[1].(K2), aSlice[2].(K3), aSlice[3].(K4)), nil
|
||||
},
|
||||
}, 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]] {
|
||||
key := QuadPrefix[K1, K2, K3, K4](k1)
|
||||
return &Range[Quad[K1, K2, K3, K4]]{
|
||||
end: RangeKeyPrefixEnd(key),
|
||||
}
|
||||
}
|
||||
|
||||
// NewPrefixedQuadRange provides a Range for all keys prefixed with the given
|
||||
// first part of the Quad key.
|
||||
func NewPrefixedQuadRange[K1, K2, K3, K4 any](k1 K1) Ranger[Quad[K1, K2, K3, K4]] {
|
||||
key := QuadPrefix[K1, K2, K3, K4](k1)
|
||||
return &Range[Quad[K1, K2, K3, K4]]{
|
||||
start: RangeKeyExact(key),
|
||||
end: RangeKeyPrefixEnd(key),
|
||||
}
|
||||
}
|
||||
|
||||
// NewSuperPrefixedQuadRange provides a Range for all keys prefixed with the given
|
||||
// first and second parts of the Quad key.
|
||||
func NewSuperPrefixedQuadRange[K1, K2, K3, K4 any](k1 K1, k2 K2) Ranger[Quad[K1, K2, K3, K4]] {
|
||||
key := QuadSuperPrefix[K1, K2, K3, K4](k1, k2)
|
||||
return &Range[Quad[K1, K2, K3, K4]]{
|
||||
start: RangeKeyExact(key),
|
||||
end: RangeKeyPrefixEnd(key),
|
||||
}
|
||||
}
|
||||
|
||||
// NewSuperPrefixedQuadRange3 provides a Range for all keys prefixed with the given
|
||||
// first, second and third parts of the Quad key.
|
||||
func NewSuperPrefixedQuadRange3[K1, K2, K3, K4 any](k1 K1, k2 K2, k3 K3) Ranger[Quad[K1, K2, K3, K4]] {
|
||||
key := QuadSuperPrefix3[K1, K2, K3, K4](k1, k2, k3)
|
||||
return &Range[Quad[K1, K2, K3, K4]]{
|
||||
start: RangeKeyExact(key),
|
||||
end: RangeKeyPrefixEnd(key),
|
||||
}
|
||||
}
|
||||
61
collections/quad_test.go
Normal file
61
collections/quad_test.go
Normal file
@ -0,0 +1,61 @@
|
||||
package collections_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"cosmossdk.io/collections"
|
||||
"cosmossdk.io/collections/colltest"
|
||||
coretesting "cosmossdk.io/core/testing"
|
||||
)
|
||||
|
||||
func TestQuad(t *testing.T) {
|
||||
kc := collections.QuadKeyCodec(collections.Uint64Key, collections.StringKey, collections.BytesKey, collections.BoolKey)
|
||||
|
||||
t.Run("conformance", func(t *testing.T) {
|
||||
colltest.TestKeyCodec(t, kc, collections.Join4(uint64(1), "2", []byte("3"), true))
|
||||
})
|
||||
}
|
||||
|
||||
func TestQuadRange(t *testing.T) {
|
||||
ctx := coretesting.Context()
|
||||
sk := coretesting.KVStoreService(ctx, "test")
|
||||
schema := collections.NewSchemaBuilder(sk)
|
||||
// this is a key composed of 4 parts: uint64, string, []byte, bool
|
||||
kc := collections.QuadKeyCodec(collections.Uint64Key, collections.StringKey, collections.BytesKey, collections.BoolKey)
|
||||
|
||||
keySet := collections.NewKeySet(schema, collections.NewPrefix(0), "Quad", kc)
|
||||
|
||||
keys := []collections.Quad[uint64, string, []byte, bool]{
|
||||
collections.Join4(uint64(1), "A", []byte("1"), true),
|
||||
collections.Join4(uint64(1), "A", []byte("2"), true),
|
||||
collections.Join4(uint64(1), "B", []byte("3"), false),
|
||||
collections.Join4(uint64(2), "B", []byte("4"), false),
|
||||
}
|
||||
|
||||
for _, k := range keys {
|
||||
require.NoError(t, keySet.Set(ctx, k))
|
||||
}
|
||||
|
||||
// we prefix over (1) we expect 3 results
|
||||
iter, err := keySet.Iterate(ctx, collections.NewPrefixedQuadRange[uint64, string, []byte, bool](uint64(1)))
|
||||
require.NoError(t, err)
|
||||
gotKeys, err := iter.Keys()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, keys[:3], gotKeys)
|
||||
|
||||
// we super prefix over Join(1, "A") we expect 2 results
|
||||
iter, err = keySet.Iterate(ctx, collections.NewSuperPrefixedQuadRange[uint64, string, []byte, bool](1, "A"))
|
||||
require.NoError(t, err)
|
||||
gotKeys, err = iter.Keys()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, keys[:2], gotKeys)
|
||||
|
||||
// we super prefix 3 over Join(1, "A", []byte("1")) we expect 1 result
|
||||
iter, err = keySet.Iterate(ctx, collections.NewSuperPrefixedQuadRange3[uint64, string, []byte, bool](1, "A", []byte("1")))
|
||||
require.NoError(t, err)
|
||||
gotKeys, err = iter.Keys()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, keys[:1], gotKeys)
|
||||
}
|
||||
@ -161,6 +161,12 @@ func NewSchemaFromAccessor(accessor func(context.Context) store.KVStore) Schema
|
||||
}
|
||||
}
|
||||
|
||||
// IsOnePerModuleType implements the depinject.OnePerModuleType interface.
|
||||
func (s Schema) IsOnePerModuleType() {}
|
||||
|
||||
// IsAppModule implements the appmodule.AppModule interface.
|
||||
func (s Schema) IsAppModule() {}
|
||||
|
||||
// DefaultGenesis implements the appmodule.HasGenesis.DefaultGenesis method.
|
||||
func (s Schema) DefaultGenesis(target appmodule.GenesisTarget) error {
|
||||
for _, name := range s.collectionsOrdered {
|
||||
|
||||
@ -32,7 +32,7 @@ func (s Sequence) Peek(ctx context.Context) (uint64, error) {
|
||||
}
|
||||
}
|
||||
|
||||
// Next returns the next sequence number, and sets the next expected sequence.
|
||||
// Next returns the current sequence number, and sets the next expected sequence.
|
||||
// Errors on encoding issues.
|
||||
func (s Sequence) Next(ctx context.Context) (uint64, error) {
|
||||
seq, err := s.Peek(ctx)
|
||||
|
||||
15
collections/sonar-project.properties
Normal file
15
collections/sonar-project.properties
Normal file
@ -0,0 +1,15 @@
|
||||
sonar.projectKey=cosmos-sdk-collections
|
||||
sonar.organization=cosmos
|
||||
|
||||
sonar.projectName=Cosmos SDK - Collections
|
||||
sonar.project.monorepo.enabled=true
|
||||
|
||||
sonar.sources=.
|
||||
sonar.exclusions=**/*_test.go,**/*.pb.go,**/*.pulsar.go,**/*.pb.gateway.go
|
||||
sonar.tests=.
|
||||
sonar.test.inclusions=**/*_test.go
|
||||
sonar.go.coverage.reportPaths=coverage.out
|
||||
|
||||
sonar.sourceEncoding=UTF-8
|
||||
sonar.scm.provider=git
|
||||
sonar.scm.forceReloadAll=true
|
||||
387
collections/triple.go
Normal file
387
collections/triple.go
Normal file
@ -0,0 +1,387 @@
|
||||
package collections
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"cosmossdk.io/collections/codec"
|
||||
"cosmossdk.io/schema"
|
||||
)
|
||||
|
||||
// Triple defines a multipart key composed of three keys.
|
||||
type Triple[K1, K2, K3 any] struct {
|
||||
k1 *K1
|
||||
k2 *K2
|
||||
k3 *K3
|
||||
}
|
||||
|
||||
// Join3 instantiates a new Triple instance composed of the three provided keys, in order.
|
||||
func Join3[K1, K2, K3 any](k1 K1, k2 K2, k3 K3) Triple[K1, K2, K3] {
|
||||
return Triple[K1, K2, K3]{&k1, &k2, &k3}
|
||||
}
|
||||
|
||||
// K1 returns the first part of the key. If nil, the zero value is returned.
|
||||
func (t Triple[K1, K2, K3]) K1() (x K1) {
|
||||
if t.k1 != nil {
|
||||
return *t.k1
|
||||
}
|
||||
return x
|
||||
}
|
||||
|
||||
// K2 returns the second part of the key. If nil, the zero value is returned.
|
||||
func (t Triple[K1, K2, K3]) K2() (x K2) {
|
||||
if t.k2 != nil {
|
||||
return *t.k2
|
||||
}
|
||||
return x
|
||||
}
|
||||
|
||||
// K3 returns the third part of the key. If nil, the zero value is returned.
|
||||
func (t Triple[K1, K2, K3]) K3() (x K3) {
|
||||
if t.k3 != nil {
|
||||
return *t.k3
|
||||
}
|
||||
return x
|
||||
}
|
||||
|
||||
// TriplePrefix creates a new Triple instance composed only of the first part of the key.
|
||||
func TriplePrefix[K1, K2, K3 any](k1 K1) Triple[K1, K2, K3] {
|
||||
return Triple[K1, K2, K3]{k1: &k1}
|
||||
}
|
||||
|
||||
// TripleSuperPrefix creates a new Triple instance composed only of the first two parts of the key.
|
||||
func TripleSuperPrefix[K1, K2, K3 any](k1 K1, k2 K2) Triple[K1, K2, K3] {
|
||||
return Triple[K1, K2, K3]{k1: &k1, k2: &k2}
|
||||
}
|
||||
|
||||
// TripleKeyCodec instantiates a new KeyCodec instance that can encode the Triple, given
|
||||
// the KeyCodecs of the three parts of the key, in order.
|
||||
func TripleKeyCodec[K1, K2, K3 any](keyCodec1 codec.KeyCodec[K1], keyCodec2 codec.KeyCodec[K2], keyCodec3 codec.KeyCodec[K3]) codec.KeyCodec[Triple[K1, K2, K3]] {
|
||||
return tripleKeyCodec[K1, K2, K3]{
|
||||
keyCodec1: keyCodec1,
|
||||
keyCodec2: keyCodec2,
|
||||
keyCodec3: keyCodec3,
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
key1Name, key2Name, key3Name string
|
||||
keyCodec1 codec.KeyCodec[K1]
|
||||
keyCodec2 codec.KeyCodec[K2]
|
||||
keyCodec3 codec.KeyCodec[K3]
|
||||
}
|
||||
|
||||
type jsonTripleKey [3]json.RawMessage
|
||||
|
||||
// EncodeJSON convert triple keys to json
|
||||
func (t tripleKeyCodec[K1, K2, K3]) EncodeJSON(value Triple[K1, K2, K3]) ([]byte, error) {
|
||||
json1, err := t.keyCodec1.EncodeJSON(*value.k1)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
json2, err := t.keyCodec2.EncodeJSON(*value.k2)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
json3, err := t.keyCodec3.EncodeJSON(*value.k3)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return json.Marshal(jsonTripleKey{json1, json2, json3})
|
||||
}
|
||||
|
||||
// DecodeJSON convert json to triple keys
|
||||
func (t tripleKeyCodec[K1, K2, K3]) DecodeJSON(b []byte) (Triple[K1, K2, K3], error) {
|
||||
var jsonKey jsonTripleKey
|
||||
err := json.Unmarshal(b, &jsonKey)
|
||||
if err != nil {
|
||||
return Triple[K1, K2, K3]{}, err
|
||||
}
|
||||
|
||||
key1, err := t.keyCodec1.DecodeJSON(jsonKey[0])
|
||||
if err != nil {
|
||||
return Triple[K1, K2, K3]{}, err
|
||||
}
|
||||
|
||||
key2, err := t.keyCodec2.DecodeJSON(jsonKey[1])
|
||||
if err != nil {
|
||||
return Triple[K1, K2, K3]{}, err
|
||||
}
|
||||
|
||||
key3, err := t.keyCodec3.DecodeJSON(jsonKey[2])
|
||||
if err != nil {
|
||||
return Triple[K1, K2, K3]{}, err
|
||||
}
|
||||
|
||||
return Join3(key1, key2, key3), nil
|
||||
}
|
||||
|
||||
// Stringify convert triple keys to string
|
||||
func (t tripleKeyCodec[K1, K2, K3]) Stringify(key Triple[K1, K2, K3]) string {
|
||||
b := new(strings.Builder)
|
||||
b.WriteByte('(')
|
||||
if key.k1 != nil {
|
||||
b.WriteByte('"')
|
||||
b.WriteString(t.keyCodec1.Stringify(*key.k1))
|
||||
b.WriteByte('"')
|
||||
} else {
|
||||
b.WriteString("<nil>")
|
||||
}
|
||||
|
||||
b.WriteString(", ")
|
||||
if key.k2 != nil {
|
||||
b.WriteByte('"')
|
||||
b.WriteString(t.keyCodec2.Stringify(*key.k2))
|
||||
b.WriteByte('"')
|
||||
} else {
|
||||
b.WriteString("<nil>")
|
||||
}
|
||||
|
||||
b.WriteString(", ")
|
||||
if key.k3 != nil {
|
||||
b.WriteByte('"')
|
||||
b.WriteString(t.keyCodec3.Stringify(*key.k3))
|
||||
b.WriteByte('"')
|
||||
} else {
|
||||
b.WriteString("<nil>")
|
||||
}
|
||||
|
||||
b.WriteByte(')')
|
||||
return b.String()
|
||||
}
|
||||
|
||||
func (t tripleKeyCodec[K1, K2, K3]) KeyType() string {
|
||||
return fmt.Sprintf("Triple[%s,%s,%s]", t.keyCodec1.KeyType(), t.keyCodec2.KeyType(), t.keyCodec3.KeyType())
|
||||
}
|
||||
|
||||
func (t tripleKeyCodec[K1, K2, K3]) Encode(buffer []byte, key Triple[K1, K2, K3]) (int, error) {
|
||||
writtenTotal := 0
|
||||
if key.k1 != nil {
|
||||
written, err := t.keyCodec1.EncodeNonTerminal(buffer, *key.k1)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
writtenTotal += written
|
||||
}
|
||||
if key.k2 != nil {
|
||||
written, err := t.keyCodec2.EncodeNonTerminal(buffer[writtenTotal:], *key.k2)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
writtenTotal += written
|
||||
}
|
||||
if key.k3 != nil {
|
||||
written, err := t.keyCodec3.Encode(buffer[writtenTotal:], *key.k3)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
writtenTotal += written
|
||||
}
|
||||
return writtenTotal, nil
|
||||
}
|
||||
|
||||
func (t tripleKeyCodec[K1, K2, K3]) Decode(buffer []byte) (int, Triple[K1, K2, K3], error) {
|
||||
readTotal := 0
|
||||
read, key1, err := t.keyCodec1.DecodeNonTerminal(buffer)
|
||||
if err != nil {
|
||||
return 0, Triple[K1, K2, K3]{}, err
|
||||
}
|
||||
readTotal += read
|
||||
read, key2, err := t.keyCodec2.DecodeNonTerminal(buffer[readTotal:])
|
||||
if err != nil {
|
||||
return 0, Triple[K1, K2, K3]{}, err
|
||||
}
|
||||
readTotal += read
|
||||
read, key3, err := t.keyCodec3.Decode(buffer[readTotal:])
|
||||
if err != nil {
|
||||
return 0, Triple[K1, K2, K3]{}, err
|
||||
}
|
||||
readTotal += read
|
||||
return readTotal, Join3(key1, key2, key3), nil
|
||||
}
|
||||
|
||||
func (t tripleKeyCodec[K1, K2, K3]) Size(key Triple[K1, K2, K3]) int {
|
||||
size := 0
|
||||
if key.k1 != nil {
|
||||
size += t.keyCodec1.SizeNonTerminal(*key.k1)
|
||||
}
|
||||
if key.k2 != nil {
|
||||
size += t.keyCodec2.SizeNonTerminal(*key.k2)
|
||||
}
|
||||
if key.k3 != nil {
|
||||
size += t.keyCodec3.Size(*key.k3)
|
||||
}
|
||||
return size
|
||||
}
|
||||
|
||||
func (t tripleKeyCodec[K1, K2, K3]) EncodeNonTerminal(buffer []byte, key Triple[K1, K2, K3]) (int, error) {
|
||||
writtenTotal := 0
|
||||
if key.k1 != nil {
|
||||
written, err := t.keyCodec1.EncodeNonTerminal(buffer, *key.k1)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
writtenTotal += written
|
||||
}
|
||||
if key.k2 != nil {
|
||||
written, err := t.keyCodec2.EncodeNonTerminal(buffer[writtenTotal:], *key.k2)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
writtenTotal += written
|
||||
}
|
||||
if key.k3 != nil {
|
||||
written, err := t.keyCodec3.EncodeNonTerminal(buffer[writtenTotal:], *key.k3)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
writtenTotal += written
|
||||
}
|
||||
return writtenTotal, nil
|
||||
}
|
||||
|
||||
func (t tripleKeyCodec[K1, K2, K3]) DecodeNonTerminal(buffer []byte) (int, Triple[K1, K2, K3], error) {
|
||||
readTotal := 0
|
||||
read, key1, err := t.keyCodec1.DecodeNonTerminal(buffer)
|
||||
if err != nil {
|
||||
return 0, Triple[K1, K2, K3]{}, err
|
||||
}
|
||||
readTotal += read
|
||||
read, key2, err := t.keyCodec2.DecodeNonTerminal(buffer[readTotal:])
|
||||
if err != nil {
|
||||
return 0, Triple[K1, K2, K3]{}, err
|
||||
}
|
||||
readTotal += read
|
||||
read, key3, err := t.keyCodec3.DecodeNonTerminal(buffer[readTotal:])
|
||||
if err != nil {
|
||||
return 0, Triple[K1, K2, K3]{}, err
|
||||
}
|
||||
readTotal += read
|
||||
return readTotal, Join3(key1, key2, key3), nil
|
||||
}
|
||||
|
||||
func (t tripleKeyCodec[K1, K2, K3]) SizeNonTerminal(key Triple[K1, K2, K3]) int {
|
||||
size := 0
|
||||
if key.k1 != nil {
|
||||
size += t.keyCodec1.SizeNonTerminal(*key.k1)
|
||||
}
|
||||
if key.k2 != nil {
|
||||
size += t.keyCodec2.SizeNonTerminal(*key.k2)
|
||||
}
|
||||
if key.k3 != nil {
|
||||
size += t.keyCodec3.SizeNonTerminal(*key.k3)
|
||||
}
|
||||
return size
|
||||
}
|
||||
|
||||
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) {
|
||||
field1, err := getNamedKeyField(t.keyCodec1, t.key1Name)
|
||||
if err != nil {
|
||||
return codec.SchemaCodec[Triple[K1, K2, K3]]{}, fmt.Errorf("error getting key1 field: %w", err)
|
||||
}
|
||||
|
||||
field2, err := getNamedKeyField(t.keyCodec2, t.key2Name)
|
||||
if err != nil {
|
||||
return codec.SchemaCodec[Triple[K1, K2, K3]]{}, fmt.Errorf("error getting key2 field: %w", err)
|
||||
}
|
||||
|
||||
field3, err := getNamedKeyField(t.keyCodec3, t.key3Name)
|
||||
if err != nil {
|
||||
return codec.SchemaCodec[Triple[K1, K2, K3]]{}, fmt.Errorf("error getting key3 field: %w", err)
|
||||
}
|
||||
|
||||
return codec.SchemaCodec[Triple[K1, K2, K3]]{
|
||||
Fields: []schema.Field{field1, field2, field3},
|
||||
ToSchemaType: func(t Triple[K1, K2, K3]) (any, error) {
|
||||
return []interface{}{t.K1(), t.K2(), t.K3()}, nil
|
||||
},
|
||||
FromSchemaType: func(a any) (Triple[K1, K2, K3], error) {
|
||||
aSlice, ok := a.([]interface{})
|
||||
if !ok || len(aSlice) != 3 {
|
||||
return Triple[K1, K2, K3]{}, fmt.Errorf("expected slice of length 3, got %T", a)
|
||||
}
|
||||
return Join3(aSlice[0].(K1), aSlice[1].(K2), aSlice[2].(K3)), nil
|
||||
},
|
||||
}, 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]] {
|
||||
key := TriplePrefix[K1, K2, K3](k1)
|
||||
return &Range[Triple[K1, K2, K3]]{
|
||||
end: RangeKeyPrefixEnd(key),
|
||||
}
|
||||
}
|
||||
|
||||
// NewPrefixedTripleRange provides a Range for all keys prefixed with the given
|
||||
// first part of the Triple key.
|
||||
func NewPrefixedTripleRange[K1, K2, K3 any](k1 K1) Ranger[Triple[K1, K2, K3]] {
|
||||
key := TriplePrefix[K1, K2, K3](k1)
|
||||
return &Range[Triple[K1, K2, K3]]{
|
||||
start: RangeKeyExact(key),
|
||||
end: RangeKeyPrefixEnd(key),
|
||||
}
|
||||
}
|
||||
|
||||
// NewSuperPrefixedTripleRange provides a Range for all keys prefixed with the given
|
||||
// first and second parts of the Triple key.
|
||||
func NewSuperPrefixedTripleRange[K1, K2, K3 any](k1 K1, k2 K2) Ranger[Triple[K1, K2, K3]] {
|
||||
key := TripleSuperPrefix[K1, K2, K3](k1, k2)
|
||||
return &Range[Triple[K1, K2, K3]]{
|
||||
start: RangeKeyExact(key),
|
||||
end: RangeKeyPrefixEnd(key),
|
||||
}
|
||||
}
|
||||
|
||||
// NewPrefixUntilTripleRangeReversed defines a collection query which ranges until the provided Pair prefix
|
||||
// in reverse order.
|
||||
// Unstable: this API might change in the future.
|
||||
func NewPrefixUntilTripleRangeReversed[K1, K2, K3 any](k1 K1) Ranger[Triple[K1, K2, K3]] {
|
||||
key := TriplePrefix[K1, K2, K3](k1)
|
||||
return &Range[Triple[K1, K2, K3]]{
|
||||
end: RangeKeyPrefixEnd(key),
|
||||
order: OrderDescending,
|
||||
}
|
||||
}
|
||||
|
||||
// NewPrefixedTripleRangeReversed provides a Range for all keys prefixed with the given
|
||||
// first part of the Triple key in reverse order.
|
||||
func NewPrefixedTripleRangeReversed[K1, K2, K3 any](k1 K1) Ranger[Triple[K1, K2, K3]] {
|
||||
key := TriplePrefix[K1, K2, K3](k1)
|
||||
return &Range[Triple[K1, K2, K3]]{
|
||||
start: RangeKeyExact(key),
|
||||
end: RangeKeyPrefixEnd(key),
|
||||
order: OrderDescending,
|
||||
}
|
||||
}
|
||||
|
||||
// NewSuperPrefixedTripleRangeReversed provides a Range for all keys prefixed with the given
|
||||
// first and second parts of the Triple key in reverse order.
|
||||
func NewSuperPrefixedTripleRangeReversed[K1, K2, K3 any](k1 K1, k2 K2) Ranger[Triple[K1, K2, K3]] {
|
||||
key := TripleSuperPrefix[K1, K2, K3](k1, k2)
|
||||
return &Range[Triple[K1, K2, K3]]{
|
||||
start: RangeKeyExact(key),
|
||||
end: RangeKeyPrefixEnd(key),
|
||||
order: OrderDescending,
|
||||
}
|
||||
}
|
||||
74
collections/triple_test.go
Normal file
74
collections/triple_test.go
Normal file
@ -0,0 +1,74 @@
|
||||
package collections_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"cosmossdk.io/collections"
|
||||
"cosmossdk.io/collections/colltest"
|
||||
coretesting "cosmossdk.io/core/testing"
|
||||
)
|
||||
|
||||
func TestTriple(t *testing.T) {
|
||||
kc := collections.TripleKeyCodec(collections.Uint64Key, collections.StringKey, collections.BytesKey)
|
||||
|
||||
t.Run("conformance", func(t *testing.T) {
|
||||
colltest.TestKeyCodec(t, kc, collections.Join3(uint64(1), "2", []byte("3")))
|
||||
})
|
||||
}
|
||||
|
||||
func TestTripleRange(t *testing.T) {
|
||||
ctx := coretesting.Context()
|
||||
sk := coretesting.KVStoreService(ctx, "test")
|
||||
schema := collections.NewSchemaBuilder(sk)
|
||||
// this is a key composed of 3 parts: uint64, string, []byte
|
||||
kc := collections.TripleKeyCodec(collections.Uint64Key, collections.StringKey, collections.BytesKey)
|
||||
|
||||
keySet := collections.NewKeySet(schema, collections.NewPrefix(0), "triple", kc)
|
||||
|
||||
keys := []collections.Triple[uint64, string, []byte]{
|
||||
collections.Join3(uint64(1), "A", []byte("1")),
|
||||
collections.Join3(uint64(1), "A", []byte("2")),
|
||||
collections.Join3(uint64(1), "B", []byte("3")),
|
||||
collections.Join3(uint64(2), "B", []byte("4")),
|
||||
}
|
||||
|
||||
for _, k := range keys {
|
||||
require.NoError(t, keySet.Set(ctx, k))
|
||||
}
|
||||
|
||||
// we prefix over (1) we expect 3 results
|
||||
iter, err := keySet.Iterate(ctx, collections.NewPrefixedTripleRange[uint64, string, []byte](uint64(1)))
|
||||
require.NoError(t, err)
|
||||
gotKeys, err := iter.Keys()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, keys[:3], gotKeys)
|
||||
|
||||
// we prefix over (1) with "reverse" enabled, we expect 3 results in reverse order
|
||||
iter, err = keySet.Iterate(ctx, collections.NewPrefixedTripleRangeReversed[uint64, string, []byte](uint64(1)))
|
||||
require.NoError(t, err)
|
||||
gotKeys, err = iter.Keys()
|
||||
require.NoError(t, err)
|
||||
require.Len(t, gotKeys, 3)
|
||||
for i := range gotKeys {
|
||||
require.Equal(t, gotKeys[i], keys[len(gotKeys)-i-1])
|
||||
}
|
||||
|
||||
// we super prefix over Join(1, "A") we expect 2 results
|
||||
iter, err = keySet.Iterate(ctx, collections.NewSuperPrefixedTripleRange[uint64, string, []byte](1, "A"))
|
||||
require.NoError(t, err)
|
||||
gotKeys, err = iter.Keys()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, keys[:2], gotKeys)
|
||||
|
||||
// we prefix over Join(1, "A") with "reverse" enabled, we expect 2 results in reverse order
|
||||
iter, err = keySet.Iterate(ctx, collections.NewSuperPrefixedTripleRangeReversed[uint64, string, []byte](1, "A"))
|
||||
require.NoError(t, err)
|
||||
gotKeys, err = iter.Keys()
|
||||
require.NoError(t, err)
|
||||
require.Len(t, gotKeys, 2)
|
||||
for i := range gotKeys {
|
||||
require.Equal(t, gotKeys[i], keys[len(gotKeys)-i-1])
|
||||
}
|
||||
}
|
||||
141
collections/vec.go
Normal file
141
collections/vec.go
Normal file
@ -0,0 +1,141 @@
|
||||
package collections
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"cosmossdk.io/collections/codec"
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrEmptyVec is returned when trying to pop an element from an empty Vec.
|
||||
ErrEmptyVec = errors.New("vec is empty")
|
||||
// ErrOutOfBounds is returned when trying to do an operation on an index that is out of bounds.
|
||||
ErrOutOfBounds = errors.New("vec index is out of bounds")
|
||||
)
|
||||
|
||||
const (
|
||||
VecElementsNameSuffix = "_elements"
|
||||
VecLengthNameSuffix = "_length"
|
||||
VecElementsPrefixSuffix = 0x0
|
||||
VecLengthPrefixSuffix = 0x1
|
||||
)
|
||||
|
||||
// NewVec creates a new Vec instance. Since Vec relies on two collections, one for the length
|
||||
// and the other for the elements, it will register two state objects on the schema builder.
|
||||
// The first is the length which is an item, whose prefix is the provided prefix with a suffix
|
||||
// which equals to VecLengthPrefixSuffix, the name is also suffixed with VecLengthNameSuffix.
|
||||
// The second is the elements which is a map, whose prefix is the provided prefix with a suffix
|
||||
// which equals to VecElementsPrefixSuffix, the name is also suffixed with VecElementsNameSuffix.
|
||||
func NewVec[T any](sb *SchemaBuilder, prefix Prefix, name string, vc codec.ValueCodec[T]) Vec[T] {
|
||||
return Vec[T]{
|
||||
length: NewItem(sb, append(prefix, VecLengthPrefixSuffix), name+VecLengthNameSuffix, Uint64Value),
|
||||
elements: NewMap(sb, append(prefix, VecElementsPrefixSuffix), name+VecElementsNameSuffix, Uint64Key, vc),
|
||||
}
|
||||
}
|
||||
|
||||
// Vec works like a slice sitting on top of a KVStore.
|
||||
// It relies on two collections, one for the length which is an Item[uint64],
|
||||
// the other for the elements which is a Map[uint64, T].
|
||||
type Vec[T any] struct {
|
||||
length Item[uint64]
|
||||
elements Map[uint64, T]
|
||||
}
|
||||
|
||||
// Push adds an element to the end of the Vec.
|
||||
func (v Vec[T]) Push(ctx context.Context, elem T) error {
|
||||
length, err := v.length.Get(ctx)
|
||||
if err != nil && !errors.Is(err, ErrNotFound) {
|
||||
return err
|
||||
}
|
||||
err = v.elements.Set(ctx, length, elem)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = v.length.Set(ctx, length+1)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Pop removes an element from the end of the Vec and returns it. Fails
|
||||
// if the Vec is empty.
|
||||
func (v Vec[T]) Pop(ctx context.Context) (elem T, err error) {
|
||||
length, err := v.length.Get(ctx)
|
||||
if err != nil && !errors.Is(err, ErrNotFound) {
|
||||
return elem, err
|
||||
}
|
||||
if length == 0 {
|
||||
return elem, ErrEmptyVec
|
||||
}
|
||||
length -= 1
|
||||
elem, err = v.elements.Get(ctx, length)
|
||||
if err != nil {
|
||||
return elem, err
|
||||
}
|
||||
err = v.elements.Remove(ctx, length)
|
||||
if err != nil {
|
||||
return elem, err
|
||||
}
|
||||
err = v.length.Set(ctx, length)
|
||||
if err != nil {
|
||||
return elem, err
|
||||
}
|
||||
return elem, nil
|
||||
}
|
||||
|
||||
// Replace replaces an element at a given index. Fails if the index is out of bounds.
|
||||
func (v Vec[T]) Replace(ctx context.Context, index uint64, elem T) error {
|
||||
length, err := v.length.Get(ctx)
|
||||
if err != nil && !errors.Is(err, ErrNotFound) {
|
||||
return err
|
||||
}
|
||||
if index >= length {
|
||||
return fmt.Errorf("%w: length %d", ErrOutOfBounds, length)
|
||||
}
|
||||
return v.elements.Set(ctx, index, elem)
|
||||
}
|
||||
|
||||
// Get returns an element at a given index. Returns ErrOutOfBounds
|
||||
// if the index is out of bounds.
|
||||
func (v Vec[T]) Get(ctx context.Context, index uint64) (elem T, err error) {
|
||||
elem, err = v.elements.Get(ctx, index)
|
||||
switch {
|
||||
case err == nil:
|
||||
return elem, nil
|
||||
case errors.Is(err, ErrNotFound):
|
||||
return elem, fmt.Errorf("%w: index %d", ErrOutOfBounds, index)
|
||||
default:
|
||||
return elem, err
|
||||
}
|
||||
}
|
||||
|
||||
// Len returns the length of the Vec.
|
||||
func (v Vec[T]) Len(ctx context.Context) (uint64, error) {
|
||||
length, err := v.length.Get(ctx)
|
||||
switch {
|
||||
// no error, return length as the vec is populated
|
||||
case err == nil:
|
||||
return length, nil
|
||||
// not found, return 0 as the vec is empty
|
||||
case errors.Is(err, ErrNotFound):
|
||||
return 0, nil
|
||||
// something else happened
|
||||
default:
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
|
||||
// Iterate iterates over the Vec. It returns an Iterator whose key is the index
|
||||
// and the value is the element at that index.
|
||||
func (v Vec[T]) Iterate(ctx context.Context, rng Ranger[uint64]) (Iterator[uint64, T], error) {
|
||||
return v.elements.Iterate(ctx, rng)
|
||||
}
|
||||
|
||||
// Walk walks over the Vec. It calls the walkFn for each element in the Vec,
|
||||
// where the key is the index and the value is the element at that index.
|
||||
func (v Vec[T]) Walk(ctx context.Context, rng Ranger[uint64], walkFn func(index uint64, elem T) (stop bool, err error)) error {
|
||||
return v.elements.Walk(ctx, rng, walkFn)
|
||||
}
|
||||
63
collections/vec_test.go
Normal file
63
collections/vec_test.go
Normal file
@ -0,0 +1,63 @@
|
||||
package collections
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestVec(t *testing.T) {
|
||||
sk, ctx := deps()
|
||||
schemaBuilder := NewSchemaBuilder(sk)
|
||||
vec := NewVec(schemaBuilder, NewPrefix(0), "vec", StringValue)
|
||||
_, err := schemaBuilder.Build()
|
||||
require.NoError(t, err)
|
||||
|
||||
// length when empty
|
||||
length, err := vec.Len(ctx)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, uint64(0), length)
|
||||
|
||||
// pop when empty should error with an empty vec error
|
||||
_, err = vec.Pop(ctx)
|
||||
require.ErrorIs(t, err, ErrEmptyVec)
|
||||
|
||||
// replace when out of bounds should error with an out of bounds error
|
||||
err = vec.Replace(ctx, 0, "foo")
|
||||
require.ErrorIs(t, err, ErrOutOfBounds)
|
||||
|
||||
// get out of bounds should error with an out of bounds error
|
||||
_, err = vec.Get(ctx, 0)
|
||||
require.ErrorIs(t, err, ErrOutOfBounds)
|
||||
|
||||
// push
|
||||
err = vec.Push(ctx, "foo")
|
||||
require.NoError(t, err)
|
||||
|
||||
// push more
|
||||
err = vec.Push(ctx, "bar")
|
||||
require.NoError(t, err)
|
||||
|
||||
// check length
|
||||
length, err = vec.Len(ctx)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, uint64(2), length)
|
||||
|
||||
// get
|
||||
v, err := vec.Get(ctx, 0)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "foo", v)
|
||||
|
||||
// replace
|
||||
err = vec.Replace(ctx, 0, "bar")
|
||||
require.NoError(t, err)
|
||||
|
||||
v, err = vec.Get(ctx, 0)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "bar", v)
|
||||
|
||||
// pop
|
||||
v, err = vec.Pop(ctx)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "bar", v)
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user