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 Table-Store (aka ORM) package - Genesis #10481

Merged
merged 88 commits into from
Nov 16, 2021
Merged
Show file tree
Hide file tree
Changes from 82 commits
Commits
Show all changes
88 commits
Select commit Hold shift + click to select a range
5539498
WIP on adding table/indexable
blushi Jul 8, 2021
10a3bec
Add some tests
blushi Jul 20, 2021
86a5ac6
Add more tests
blushi Jul 22, 2021
580c41b
Add sequence
blushi Jul 22, 2021
d0a77b6
Update testdata
blushi Jul 22, 2021
fc72796
Lint
blushi Jul 22, 2021
71cac77
Merge branch 'master' into marie/9237-table-store-1
blushi Jul 22, 2021
a4a4e09
Add docs
blushi Jul 22, 2021
b6f2d08
Update docs
blushi Jul 22, 2021
ed2a1d2
Use Update instead of Save
blushi Jul 28, 2021
b0a8586
Merge branch 'master' into marie/9237-table-store-1
blushi Jul 28, 2021
f4df020
Add AutoUInt64Table
blushi Jul 30, 2021
f1dd883
WIP on adding tests
blushi Jul 30, 2021
0234149
Fix table tests
blushi Aug 5, 2021
30c765b
Move orm to x/group
blushi Aug 11, 2021
5e71573
Add orm
blushi Aug 11, 2021
63168ed
Merge branch 'master' into marie/9237-table-store-1
blushi Aug 11, 2021
482617d
Merge branch 'master' into marie/9237-table-store-1
blushi Sep 2, 2021
09d832a
Merge branch 'master' into marie/9237-table-store-1
blushi Oct 7, 2021
0151b74
Update orm with latest changes
blushi Oct 8, 2021
4d2486a
Mv orm to x/group/internal
blushi Oct 8, 2021
a368554
Update go.mod and fix tests
blushi Oct 8, 2021
ce21252
Update README
blushi Oct 8, 2021
8956be8
Merge branch 'master' into marie/9237-table-store-1
blushi Oct 8, 2021
560662e
Fix tests
blushi Oct 8, 2021
bb495f7
Use [2]byte for table prefix key
blushi Oct 8, 2021
915d734
Update docs
blushi Oct 8, 2021
0ef7f82
Merge branch 'master' into marie/9237-table-store-1
blushi Oct 8, 2021
e3aa3e8
Rm file
blushi Oct 8, 2021
df0c304
Rm file
blushi Oct 8, 2021
661d449
Revert store/README
blushi Oct 8, 2021
b47264d
Register errors in types/errors
blushi Oct 8, 2021
00b97d8
Fix group err
blushi Oct 8, 2021
247fcbe
Merge branch 'marie/9237-table-store-1' into marie/9237-table-store-2
blushi Oct 8, 2021
61cbb3c
Merge branch 'master' into marie/9237-table-store-1
blushi Oct 14, 2021
5079788
WIP adding key codec
blushi Oct 14, 2021
5b38f67
Add property tests
blushi Oct 15, 2021
55e5ef2
WIP adding orm scenario test
blushi Oct 15, 2021
8311958
Add orm scenario test
blushi Oct 20, 2021
fc4f5d7
Merge branch 'master' into marie/9237-table-store-2
blushi Oct 20, 2021
f1712c8
go mod tidy
blushi Oct 21, 2021
f16d662
WIP on docs
blushi Oct 21, 2021
a8b57d2
Simplify table creation
blushi Oct 21, 2021
7c63cbd
Merge branch 'master' into marie/9237-table-store-1
blushi Oct 21, 2021
9f78f16
Merge branch 'marie/9237-table-store-1' into marie/9237-table-store-2
blushi Oct 21, 2021
8504cd4
Replace code snippets with github links
blushi Oct 21, 2021
851e6e7
Merge branch 'master' into marie/9237-table-store-2
blushi Oct 21, 2021
87fc7b0
Update x/group/internal/orm/table.go
blushi Oct 21, 2021
d2f9a52
Address review comments
blushi Oct 21, 2021
21a5d8b
Merge branch 'marie/9237-table-store-1' into marie/9237-table-store-2
blushi Oct 21, 2021
28f601e
Fix tests
blushi Oct 21, 2021
c1a4e74
Merge branch 'marie/9237-table-store-2' of github.com:cosmos/cosmos-s…
blushi Oct 21, 2021
5aa297e
Merge branch 'master' into marie/9237-table-store-2
blushi Oct 21, 2021
77fd4d1
Small nits
blushi Oct 21, 2021
6594197
wip adding indexer
blushi Oct 27, 2021
a073a39
Merge branch 'master' into marie/9237-table-store-3
blushi Oct 27, 2021
09e7830
Add indexer and iterator tests
blushi Oct 28, 2021
899cd49
Add iterator property tests
blushi Oct 28, 2021
538c19f
Update orm scenario tests
blushi Oct 28, 2021
a49e591
Add prefix scan methods to tables types
blushi Oct 28, 2021
430163e
Merge branch 'master' into marie/9237-table-store-3
blushi Oct 28, 2021
badd821
Add docs
blushi Oct 29, 2021
837bf90
Merge branch 'master' into marie/9237-table-store-3
blushi Oct 29, 2021
44d7e26
Update var name
blushi Oct 29, 2021
21cd674
Update docs
blushi Oct 29, 2021
2f0bfdf
Add Export/Import methods
blushi Oct 29, 2021
ef8800a
Wip adding tests
blushi Oct 29, 2021
0f329d4
Fix naming
blushi Oct 29, 2021
1906dfe
Merge branch 'marie/9237-table-store-3' into marie/9237-table-store-4
blushi Oct 29, 2021
0e19ffa
Add genesis tests
blushi Oct 29, 2021
70c83a5
Add genesis test to orm scenario tests
blushi Oct 29, 2021
8d0c2b0
Update x/group/internal/orm/auto_uint64_test.go
blushi Nov 2, 2021
6f45be8
Update x/group/internal/orm/auto_uint64_test.go
blushi Nov 2, 2021
0dbc7d4
Update prefix scan docs
blushi Nov 2, 2021
3b37ef2
Use const
blushi Nov 2, 2021
6f86782
Merge branch 'master' into marie/9237-table-store-3
blushi Nov 2, 2021
484e511
Merge branch 'marie/9237-table-store-3' into marie/9237-table-store-4
blushi Nov 2, 2021
1cc22c7
Add genesis docs
blushi Nov 2, 2021
ef9a3a4
Move registered errors to x/group/errors
blushi Nov 2, 2021
3bd6bce
Merge branch 'marie/9237-table-store-3' into marie/9237-table-store-4
blushi Nov 2, 2021
7030315
Merge branch 'master' into marie/9237-table-store-4
blushi Nov 10, 2021
22c1521
Fix tests
blushi Nov 10, 2021
178e6a1
Merge branch 'master' into marie/9237-table-store-4
blushi Nov 10, 2021
476ffce
Address review comments
blushi Nov 16, 2021
ab9c214
Merge branch 'master' into marie/9237-table-store-4
blushi Nov 16, 2021
af149b8
go mod tidy
blushi Nov 16, 2021
3eaeb86
Merge branch 'marie/9237-table-store-4' of github.com:cosmos/cosmos-s…
blushi Nov 16, 2021
10dd18b
go mod tidy
blushi Nov 16, 2021
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
30 changes: 29 additions & 1 deletion x/group/internal/orm/auto_uint64.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,13 @@ package orm
import (
"github.com/cosmos/cosmos-sdk/codec"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/types/errors"
)

var _ Indexable = &AutoUInt64Table{}
var (
_ Indexable = &AutoUInt64Table{}
_ TableExportable = &AutoUInt64Table{}
)

// AutoUInt64Table is the table type with an auto incrementing ID.
type AutoUInt64Table struct {
Expand Down Expand Up @@ -106,3 +110,27 @@ func (a AutoUInt64Table) PrefixScan(store sdk.KVStore, start, end uint64) (Itera
func (a AutoUInt64Table) ReversePrefixScan(store sdk.KVStore, start uint64, end uint64) (Iterator, error) {
return a.table.ReversePrefixScan(store, EncodeSequence(start), EncodeSequence(end))
}

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

// Export stores all the values in the table in the passed ModelSlicePtr and
// returns the current value of the associated sequence.
func (a AutoUInt64Table) Export(store sdk.KVStore, dest ModelSlicePtr) (uint64, error) {
_, err := a.table.Export(store, dest)
if err != nil {
return 0, err
}
return a.seq.CurVal(store), nil
}

// Import clears the table and initializes it from the given data interface{}.
// data should be a slice of structs that implement PrimaryKeyed.
func (a AutoUInt64Table) Import(store sdk.KVStore, data interface{}, seqValue uint64) error {
if err := a.seq.InitVal(store, seqValue); err != nil {
return errors.Wrap(err, "sequence")
}
return a.table.Import(store, data, seqValue)
}
19 changes: 19 additions & 0 deletions x/group/internal/orm/genesis.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package orm

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

// TableExportable
type TableExportable interface {
// Export stores all the values in the table in the passed
// ModelSlicePtr. If the table has an associated sequence, then its
// current value is returned, otherwise 0 is returned by default.
Export(sdk.KVStore, ModelSlicePtr) (uint64, error)

// Import clears the table and initializes it from the given data
// interface{}. data should be a slice of structs that implement
// PrimaryKeyed. The seqValue is optional and only
// used with tables that have an associated sequence.
Import(sdk.KVStore, interface{}, uint64) error
}
57 changes: 57 additions & 0 deletions x/group/internal/orm/genesis_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package orm

import (
"testing"

"github.com/cosmos/cosmos-sdk/codec"
"github.com/cosmos/cosmos-sdk/codec/types"
"github.com/cosmos/cosmos-sdk/testutil/testdata"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/stretchr/testify/require"
)

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

table, err := NewAutoUInt64Table(AutoUInt64TablePrefix, AutoUInt64TableSeqPrefix, &testdata.TableModel{}, cdc)
require.NoError(t, err)

ctx := NewMockContext()
store := ctx.KVStore(sdk.NewKVStoreKey("test"))

tms := []*testdata.TableModel{
{
Id: 1,
Name: "my test 1",
Number: 123,
Metadata: []byte("metadata 1"),
},
{
Id: 2,
Name: "my test 2",
Number: 456,
Metadata: []byte("metadata 2"),
},
}

err = table.Import(store, tms, 2)
require.NoError(t, err)

for _, g := range tms {
var loaded testdata.TableModel
_, err := table.GetOne(store, g.Id, &loaded)
require.NoError(t, err)

require.Equal(t, g, &loaded)
}

var exported []*testdata.TableModel
seq, err := table.Export(store, &exported)
require.NoError(t, err)
require.Equal(t, seq, uint64(2))

for i, g := range exported {
require.Equal(t, g, tms[i])
}
}
142 changes: 142 additions & 0 deletions x/group/internal/orm/orm_scenario_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package orm

import (
"bytes"
"encoding/binary"
"fmt"
"testing"
Expand All @@ -15,6 +16,9 @@ import (
"github.com/cosmos/cosmos-sdk/testutil/testdata"
)

// Testing ORM with arbitrary metadata length
const metadataLen = 10

func TestKeeperEndToEndWithAutoUInt64Table(t *testing.T) {
interfaceRegistry := types.NewInterfaceRegistry()
cdc := codec.NewProtoCodec(interfaceRegistry)
Expand Down Expand Up @@ -275,6 +279,144 @@ func TestGasCostsPrimaryKeyTable(t *testing.T) {
}
}

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

ctx := NewMockContext()
store := ctx.KVStore(sdk.NewKVStoreKey("test"))

k := NewTestKeeper(cdc)

testRecordsNum := 10
for i := 1; i <= testRecordsNum; i++ {
tm := testdata.TableModel{
Id: uint64(i),
Name: fmt.Sprintf("my test %d", i),
Metadata: bytes.Repeat([]byte{byte(i)}, metadataLen),
}

rowID, err := k.autoUInt64Table.Create(store, &tm)
require.NoError(t, err)
require.Equal(t, uint64(i), rowID)
}
var tms []*testdata.TableModel
seqVal, err := k.autoUInt64Table.Export(store, &tms)
require.NoError(t, err)

// when a new db seeded
ctx = NewMockContext()
store = ctx.KVStore(sdk.NewKVStoreKey("test"))

err = k.autoUInt64Table.Import(store, tms, seqVal)
require.NoError(t, err)

// then all data is set again
for i := 1; i <= testRecordsNum; i++ {
require.True(t, k.autoUInt64Table.Has(store, uint64(i)))
var loaded testdata.TableModel
rowID, err := k.autoUInt64Table.GetOne(store, uint64(i), &loaded)
require.NoError(t, err)

require.Equal(t, RowID(EncodeSequence(uint64(i))), rowID)
assert.Equal(t, fmt.Sprintf("my test %d", i), loaded.Name)
exp := bytes.Repeat([]byte{byte(i)}, metadataLen)
assert.Equal(t, exp, loaded.Metadata)

// and also the indexes
exists, err := k.autoUInt64TableModelByMetadataIndex.Has(store, exp)
require.NoError(t, err)
require.True(t, exists)

it, err := k.autoUInt64TableModelByMetadataIndex.Get(store, exp)
require.NoError(t, err)
var all []testdata.TableModel
ReadAll(it, &all)
require.Len(t, all, 1)
assert.Equal(t, loaded, all[0])
}
require.Equal(t, uint64(testRecordsNum), k.autoUInt64Table.Sequence().CurVal(store))
}

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

ctx := NewMockContext()
store := ctx.KVStore(sdk.NewKVStoreKey("test"))

k := NewTestKeeper(cdc)

testRecordsNum := 10
testRecords := make([]testdata.TableModel, testRecordsNum)
for i := 1; i <= testRecordsNum; i++ {
tm := testdata.TableModel{
Id: uint64(i),
Name: fmt.Sprintf("my test %d", i),
Number: uint64(i - 1),
Metadata: bytes.Repeat([]byte{byte(i)}, metadataLen),
}

err := k.primaryKeyTable.Create(store, &tm)
require.NoError(t, err)
testRecords[i-1] = tm
}
var tms []*testdata.TableModel
_, err := k.primaryKeyTable.Export(store, &tms)
require.NoError(t, err)

// when a new db seeded
ctx = NewMockContext()
store = ctx.KVStore(sdk.NewKVStoreKey("test"))

err = k.primaryKeyTable.Import(store, tms, 0)
require.NoError(t, err)

// then all data is set again
it, err := k.primaryKeyTable.PrefixScan(store, nil, nil)
require.NoError(t, err)
var loaded []testdata.TableModel
keys, err := ReadAll(it, &loaded)
require.NoError(t, err)
for i := range keys {
assert.Equal(t, PrimaryKey(&testRecords[i]), keys[i].Bytes())
}
assert.Equal(t, testRecords, loaded)

// and first index setup
for _, v := range testRecords {
it, err = k.primaryKeyTableModelByNameIndex.Get(store, v.Name)
require.NoError(t, err)
loaded = nil
keys, err = ReadAll(it, &loaded)
require.NoError(t, err)
assert.Equal(t, []RowID{PrimaryKey(&v)}, keys)
assert.Equal(t, []testdata.TableModel{v}, loaded)
}

// and second index setup
for _, v := range testRecords {
it, err = k.primaryKeyTableModelByNumberIndex.Get(store, v.Number)
require.NoError(t, err)
loaded = nil
keys, err = ReadAll(it, &loaded)
require.NoError(t, err)
assert.Equal(t, []RowID{PrimaryKey(&v)}, keys)
assert.Equal(t, []testdata.TableModel{v}, loaded)
}

// and uint64 index setup
for _, v := range testRecords {
it, err = k.primaryKeyTableModelByMetadataIndex.Get(store, v.Metadata)
require.NoError(t, err)
loaded = nil
keys, err = ReadAll(it, &loaded)
require.NoError(t, err)
assert.Equal(t, []RowID{PrimaryKey(&v)}, keys)
assert.Equal(t, []testdata.TableModel{v}, loaded)
}
}

func first(t *testing.T, it Iterator) ([]byte, testdata.TableModel) {
var loaded testdata.TableModel
key, err := First(it, &loaded)
Expand Down
16 changes: 15 additions & 1 deletion x/group/internal/orm/primary_key.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,10 @@ import (
sdk "github.com/cosmos/cosmos-sdk/types"
)

var _ Indexable = &PrimaryKeyTable{}
var (
_ Indexable = &PrimaryKeyTable{}
_ TableExportable = &PrimaryKeyTable{}
)

// PrimaryKeyTable provides simpler object style orm methods without passing database RowIDs.
// Entries are persisted and loaded with a reference to their unique primary key.
Expand Down Expand Up @@ -144,3 +147,14 @@ func (a PrimaryKeyTable) PrefixScan(store sdk.KVStore, start, end []byte) (Itera
func (a PrimaryKeyTable) ReversePrefixScan(store sdk.KVStore, start, end []byte) (Iterator, error) {
return a.table.ReversePrefixScan(store, start, end)
}

// Export stores all the values in the table in the passed ModelSlicePtr.
func (a PrimaryKeyTable) Export(store sdk.KVStore, dest ModelSlicePtr) (uint64, error) {
return a.table.Export(store, dest)
}

// Import clears the table and initializes it from the given data interface{}.
// data should be a slice of structs that implement PrimaryKeyed.
func (a PrimaryKeyTable) Import(store sdk.KVStore, data interface{}, seqValue uint64) error {
return a.table.Import(store, data, seqValue)
}
4 changes: 4 additions & 0 deletions x/group/internal/orm/spec/01_table.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ The `table` struct does not:
- optimize Gas usage conditions
The `table` struct is private, so that we only have custom tables built on top of it, that do satisfy these requirements.

`table` provides methods for exporting (using a [`PrefixScan` `Iterator`](03_iterator_pagination.md#iterator)) and importing genesis data. For the import to be successful, objects have to be aware of their primary key by implementing the [`PrimaryKeyed`](#primarykeyed) interface.

## AutoUInt64Table

`AutoUInt64Table` is a table type with an auto incrementing `uint64` ID.
Expand All @@ -27,6 +29,8 @@ It's based on the `Sequence` struct which is a persistent unique key generator b

`PrimaryKeyTable` provides simpler object style orm methods where are persisted and loaded with a reference to their unique primary key.

### PrimaryKeyed

The model provided for creating a `PrimaryKeyTable` should implement the `PrimaryKeyed` interface:

+++ https://github.com/cosmos/cosmos-sdk/blob/9f78f16ae75cc42fc5fe636bde18a453ba74831f/x/group/internal/orm/primary_key.go#L28-L41
Expand Down
52 changes: 51 additions & 1 deletion x/group/internal/orm/table.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,10 @@ import (
"github.com/cosmos/cosmos-sdk/x/group/errors"
)

var _ Indexable = &table{}
var (
_ Indexable = &table{}
_ TableExportable = &table{}
)

// table is the high level object to storage mapper functionality. Persistent
// entities are stored by an unique identifier called `RowID`. The table struct
Expand Down Expand Up @@ -232,6 +235,53 @@ func (a table) ReversePrefixScan(store sdk.KVStore, start, end RowID) (Iterator,
}, nil
}

// Export stores all the values in the table in the passed ModelSlicePtr.
func (a table) Export(store sdk.KVStore, dest ModelSlicePtr) (uint64, error) {
it, err := a.PrefixScan(store, nil, nil)
if err != nil {
return 0, sdkerrors.Wrap(err, "table Export failure when exporting table data")
}
_, err = ReadAll(it, dest)
if err != nil {
return 0, err
}
return 0, nil
}

// Import clears the table and initializes it from the given data interface{}.
// data should be a slice of structs that implement PrimaryKeyed.
func (a table) Import(store sdk.KVStore, data interface{}, _ uint64) error {
// Clear all data
pStore := prefix.NewStore(store, a.prefix[:])
it := pStore.Iterator(nil, nil)
defer it.Close()
for ; it.Valid(); it.Next() {
if err := a.Delete(store, it.Key()); err != nil {
return err
}
}

// Provided data must be a slice
modelSlice := reflect.ValueOf(data)
if modelSlice.Kind() != reflect.Slice {
return sdkerrors.Wrap(errors.ErrORMInvalidArgument, "data must be a slice")
}

// Import values from slice
for i := 0; i < modelSlice.Len(); i++ {
obj, ok := modelSlice.Index(i).Interface().(PrimaryKeyed)
if !ok {
return sdkerrors.Wrapf(errors.ErrORMInvalidArgument, "unsupported type :%s", reflect.TypeOf(data).Elem().Elem())
}
err := a.Create(store, PrimaryKey(obj), obj)
if err != nil {
return err
}
}

return nil
}

// typeSafeIterator is initialized with a type safe RowGetter only.
type typeSafeIterator struct {
store sdk.KVStore
Expand Down