Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add ORM package DO NOT MERGE! #9491

Closed
wants to merge 16 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 54 additions & 0 deletions docs/core/proto-docs.md
Original file line number Diff line number Diff line change
Expand Up @@ -401,6 +401,10 @@

- [Query](#cosmos.mint.v1beta1.Query)

- [cosmos/orm/testdata/codec.proto](#cosmos/orm/testdata/codec.proto)
- [GroupInfo](#cosmos.orm.testdata.GroupInfo)
- [GroupMember](#cosmos.orm.testdata.GroupMember)

- [cosmos/params/v1beta1/params.proto](#cosmos/params/v1beta1/params.proto)
- [ParamChange](#cosmos.params.v1beta1.ParamChange)
- [ParameterChangeProposal](#cosmos.params.v1beta1.ParameterChangeProposal)
Expand Down Expand Up @@ -5720,6 +5724,56 @@ Query provides defines the gRPC querier service.



<a name="cosmos/orm/testdata/codec.proto"></a>
<p align="right"><a href="#top">Top</a></p>

## cosmos/orm/testdata/codec.proto



<a name="cosmos.orm.testdata.GroupInfo"></a>

### GroupInfo
GroupInfo represents the high-level on-chain information for a group.


| Field | Type | Label | Description |
| ----- | ---- | ----- | ----------- |
| `group_id` | [uint64](#uint64) | | |
| `description` | [string](#string) | | |
| `admin` | [bytes](#bytes) | | |






<a name="cosmos.orm.testdata.GroupMember"></a>

### GroupMember
GroupMember represents the relationship between a group and a member.


| Field | Type | Label | Description |
| ----- | ---- | ----- | ----------- |
| `group` | [bytes](#bytes) | | |
| `member` | [bytes](#bytes) | | |
| `weight` | [uint64](#uint64) | | |





<!-- end messages -->

<!-- end enums -->

<!-- end HasExtensions -->

<!-- end services -->



<a name="cosmos/params/v1beta1/params.proto"></a>
<p align="right"><a href="#top">Top</a></p>

Expand Down
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ require (
google.golang.org/protobuf v1.26.0
gopkg.in/ini.v1 v1.61.0 // indirect
gopkg.in/yaml.v2 v2.4.0
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776 // indirect
nhooyr.io/websocket v1.8.6 // indirect
)

Expand Down
3 changes: 2 additions & 1 deletion go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -1147,8 +1147,9 @@ gopkg.in/yaml.v2 v2.2.8/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.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776 h1:tQIYjPdBoyREyB9XMu+nnTclpTYkz2zFM+lzLJFO4gQ=
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw=
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=
Expand Down
130 changes: 130 additions & 0 deletions orm/auto_uint64.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
package orm

import (
"github.com/cosmos/cosmos-sdk/codec"
sdk "github.com/cosmos/cosmos-sdk/types"
)

var _ Indexable = &AutoUInt64TableBuilder{}

// NewAutoUInt64TableBuilder creates a builder to setup a AutoUInt64Table object.
func NewAutoUInt64TableBuilder(prefixData byte, prefixSeq byte, storeKey sdk.StoreKey, model codec.ProtoMarshaler, cdc codec.Codec) *AutoUInt64TableBuilder {
if prefixData == prefixSeq {
panic("prefixData and prefixSeq must be unique")
}

uInt64KeyCodec := FixLengthIndexKeys(EncodedSeqLength)
return &AutoUInt64TableBuilder{
TableBuilder: NewTableBuilder(prefixData, storeKey, model, uInt64KeyCodec, cdc),
seq: NewSequence(storeKey, prefixSeq),
}
}

type AutoUInt64TableBuilder struct {
*TableBuilder
seq Sequence
}

// Build create the AutoUInt64Table object.
func (a AutoUInt64TableBuilder) Build() AutoUInt64Table {
return AutoUInt64Table{
table: a.TableBuilder.Build(),
seq: a.seq,
}
}

var _ SequenceExportable = &AutoUInt64Table{}
var _ TableExportable = &AutoUInt64Table{}

// AutoUInt64Table is the table type which an auto incrementing ID.
type AutoUInt64Table struct {
table Table
seq Sequence
}

// Create a new persistent object with an auto generated uint64 primary key. They key is returned.
// Create iterates though the registered callbacks and may add secondary index keys by them.
func (a AutoUInt64Table) Create(ctx HasKVStore, obj codec.ProtoMarshaler) (uint64, error) {
autoIncID := a.seq.NextVal(ctx)
err := a.table.Create(ctx, EncodeSequence(autoIncID), obj)
if err != nil {
return 0, err
}
return autoIncID, nil
}

// Save updates the given object under the rowID key. It expects the key to exists already
// and fails with an `ErrNotFound` otherwise. Any caller must therefore make sure that this contract
// is fulfilled. Parameters must not be nil.
//
// Save iterates though the registered callbacks and may add or remove secondary index keys by them.
func (a AutoUInt64Table) Save(ctx HasKVStore, rowID uint64, newValue codec.ProtoMarshaler) error {
return a.table.Save(ctx, EncodeSequence(rowID), newValue)
}

// Delete removes the object under the rowID key. It expects the key to exists already
// and fails with a `ErrNotFound` otherwise. Any caller must therefore make sure that this contract
// is fulfilled.
//
// Delete iterates though the registered callbacks and removes secondary index keys by them.
func (a AutoUInt64Table) Delete(ctx HasKVStore, rowID uint64) error {
return a.table.Delete(ctx, EncodeSequence(rowID))
}

// Has checks if a rowID exists.
func (a AutoUInt64Table) Has(ctx HasKVStore, rowID uint64) bool {
return a.table.Has(ctx, EncodeSequence(rowID))
}

// GetOne load the object persisted for the given RowID into the dest parameter.
// If none exists `ErrNotFound` is returned instead. Parameters must not be nil.
func (a AutoUInt64Table) GetOne(ctx HasKVStore, rowID uint64, dest codec.ProtoMarshaler) (RowID, error) {
rawRowID := EncodeSequence(rowID)
if err := a.table.GetOne(ctx, rawRowID, dest); err != nil {
return nil, err
}
return rawRowID, nil
}

// PrefixScan returns an Iterator over a domain of keys in ascending order. End is exclusive.
// Start is an MultiKeyIndex key or prefix. It must be less than end, or the Iterator is invalid and error is returned.
// Iterator must be closed by caller.
// To iterate over entire domain, use PrefixScan(nil, nil)
//
// WARNING: The use of a PrefixScan can be very expensive in terms of Gas. Please make sure you do not expose
// this as an endpoint to the public without further limits.
// Example:
// it, err := idx.PrefixScan(ctx, start, end)
// if err !=nil {
// return err
// }
// const defaultLimit = 20
// it = LimitIterator(it, defaultLimit)
//
// CONTRACT: No writes may happen within a domain while an iterator exists over it.
func (a AutoUInt64Table) PrefixScan(ctx HasKVStore, start, end uint64) (Iterator, error) {
return a.table.PrefixScan(ctx, EncodeSequence(start), EncodeSequence(end))
}

// ReversePrefixScan returns an Iterator over a domain of keys in descending order. End is exclusive.
// Start is an MultiKeyIndex key or prefix. It must be less than end, or the Iterator is invalid and error is returned.
// Iterator must be closed by caller.
// To iterate over entire domain, use PrefixScan(nil, nil)
//
// WARNING: The use of a ReversePrefixScan can be very expensive in terms of Gas. Please make sure you do not expose
// this as an endpoint to the public without further limits. See `LimitIterator`
//
// CONTRACT: No writes may happen within a domain while an iterator exists over it.
func (a AutoUInt64Table) ReversePrefixScan(ctx HasKVStore, start uint64, end uint64) (Iterator, error) {
return a.table.ReversePrefixScan(ctx, EncodeSequence(start), EncodeSequence(end))
}

// Sequence returns the sequence used by this table
func (a AutoUInt64Table) Sequence() Sequence {
return a.seq
}

// Table satisfies the TableExportable interface and must not be used otherwise.
func (a AutoUInt64Table) Table() Table {
return a.table
}
163 changes: 163 additions & 0 deletions orm/auto_uint64_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
package orm_test

import (
"math"
"testing"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"

"github.com/cosmos/cosmos-sdk/codec"
"github.com/cosmos/cosmos-sdk/codec/types"
"github.com/cosmos/cosmos-sdk/orm"
"github.com/cosmos/cosmos-sdk/orm/testdata"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/types/errors"
)

func TestAutoUInt64PrefixScan(t *testing.T) {
interfaceRegistry := types.NewInterfaceRegistry()
cdc := codec.NewProtoCodec(interfaceRegistry)

storeKey := sdk.NewKVStoreKey("test")
const (
testTablePrefix = iota
testTableSeqPrefix
)
tb := orm.NewAutoUInt64TableBuilder(testTablePrefix, testTableSeqPrefix, storeKey, &testdata.GroupInfo{}, cdc).Build()
ctx := orm.NewMockContext()

g1 := testdata.GroupInfo{
Description: "my test 1",
Admin: sdk.AccAddress([]byte("admin-address")),
}
g2 := testdata.GroupInfo{
Description: "my test 2",
Admin: sdk.AccAddress([]byte("admin-address")),
}
g3 := testdata.GroupInfo{
Description: "my test 3",
Admin: sdk.AccAddress([]byte("admin-address")),
}
for _, g := range []testdata.GroupInfo{g1, g2, g3} {
_, err := tb.Create(ctx, &g)
require.NoError(t, err)
}

specs := map[string]struct {
start, end uint64
expResult []testdata.GroupInfo
expRowIDs []orm.RowID
expError *errors.Error
method func(ctx orm.HasKVStore, start uint64, end uint64) (orm.Iterator, error)
}{
"first element": {
start: 1,
end: 2,
method: tb.PrefixScan,
expResult: []testdata.GroupInfo{g1},
expRowIDs: []orm.RowID{orm.EncodeSequence(1)},
},
"first 2 elements": {
start: 1,
end: 3,
method: tb.PrefixScan,
expResult: []testdata.GroupInfo{g1, g2},
expRowIDs: []orm.RowID{orm.EncodeSequence(1), orm.EncodeSequence(2)},
},
"first 3 elements": {
start: 1,
end: 4,
method: tb.PrefixScan,
expResult: []testdata.GroupInfo{g1, g2, g3},
expRowIDs: []orm.RowID{orm.EncodeSequence(1), orm.EncodeSequence(2), orm.EncodeSequence(3)},
},
"search with max end": {
start: 1,
end: math.MaxUint64,
method: tb.PrefixScan,
expResult: []testdata.GroupInfo{g1, g2, g3},
expRowIDs: []orm.RowID{orm.EncodeSequence(1), orm.EncodeSequence(2), orm.EncodeSequence(3)},
},
"2 to end": {
start: 2,
end: 5,
method: tb.PrefixScan,
expResult: []testdata.GroupInfo{g2, g3},
expRowIDs: []orm.RowID{orm.EncodeSequence(2), orm.EncodeSequence(3)},
},
"start before end should fail": {
start: 2,
end: 1,
method: tb.PrefixScan,
expError: orm.ErrArgument,
},
"start equals end should fail": {
start: 1,
end: 1,
method: tb.PrefixScan,
expError: orm.ErrArgument,
},
"reverse first element": {
start: 1,
end: 2,
method: tb.ReversePrefixScan,
expResult: []testdata.GroupInfo{g1},
expRowIDs: []orm.RowID{orm.EncodeSequence(1)},
},
"reverse first 2 elements": {
start: 1,
end: 3,
method: tb.ReversePrefixScan,
expResult: []testdata.GroupInfo{g2, g1},
expRowIDs: []orm.RowID{orm.EncodeSequence(2), orm.EncodeSequence(1)},
},
"reverse first 3 elements": {
start: 1,
end: 4,
method: tb.ReversePrefixScan,
expResult: []testdata.GroupInfo{g3, g2, g1},
expRowIDs: []orm.RowID{orm.EncodeSequence(3), orm.EncodeSequence(2), orm.EncodeSequence(1)},
},
"reverse search with max end": {
start: 1,
end: math.MaxUint64,
method: tb.ReversePrefixScan,
expResult: []testdata.GroupInfo{g3, g2, g1},
expRowIDs: []orm.RowID{orm.EncodeSequence(3), orm.EncodeSequence(2), orm.EncodeSequence(1)},
},
"reverse 2 to end": {
start: 2,
end: 5,
method: tb.ReversePrefixScan,
expResult: []testdata.GroupInfo{g3, g2},
expRowIDs: []orm.RowID{orm.EncodeSequence(3), orm.EncodeSequence(2)},
},
"reverse start before end should fail": {
start: 2,
end: 1,
method: tb.ReversePrefixScan,
expError: orm.ErrArgument,
},
"reverse start equals end should fail": {
start: 1,
end: 1,
method: tb.ReversePrefixScan,
expError: orm.ErrArgument,
},
}
for msg, spec := range specs {
t.Run(msg, func(t *testing.T) {
it, err := spec.method(ctx, spec.start, spec.end)
require.True(t, spec.expError.Is(err), "expected #+v but got #+v", spec.expError, err)
if spec.expError != nil {
return
}
var loaded []testdata.GroupInfo
rowIDs, err := orm.ReadAll(it, &loaded)
require.NoError(t, err)
assert.Equal(t, spec.expResult, loaded)
assert.Equal(t, spec.expRowIDs, rowIDs)
})
}
}
Loading