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

Likhita/add table store #1

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
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)
})
}
}
52 changes: 52 additions & 0 deletions orm/example_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package orm_test

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

type GroupKeeper struct {
key sdk.StoreKey
groupTable orm.AutoUInt64Table
groupByAdminIndex orm.Index
groupMemberTable orm.PrimaryKeyTable
groupMemberByGroupIndex orm.Index
groupMemberByMemberIndex orm.Index
}

var (
GroupTablePrefix byte = 0x0
GroupTableSeqPrefix byte = 0x1
GroupByAdminIndexPrefix byte = 0x2
GroupMemberTablePrefix byte = 0x3
GroupMemberTableSeqPrefix byte = 0x4
GroupMemberTableIndexPrefix byte = 0x5
GroupMemberByGroupIndexPrefix byte = 0x6
GroupMemberByMemberIndexPrefix byte = 0x7
)

func NewGroupKeeper(storeKey sdk.StoreKey, cdc codec.Codec) GroupKeeper {
k := GroupKeeper{key: storeKey}

groupTableBuilder := orm.NewAutoUInt64TableBuilder(GroupTablePrefix, GroupTableSeqPrefix, storeKey, &testdata.GroupInfo{}, cdc)
// note: quite easy to mess with Index prefixes when managed outside. no fail fast on duplicates
k.groupByAdminIndex = orm.NewIndex(groupTableBuilder, GroupByAdminIndexPrefix, func(val interface{}) ([]orm.RowID, error) {
return []orm.RowID{[]byte(val.(*testdata.GroupInfo).Admin)}, nil
})
k.groupTable = groupTableBuilder.Build()

groupMemberTableBuilder := orm.NewPrimaryKeyTableBuilder(GroupMemberTablePrefix, storeKey, &testdata.GroupMember{}, orm.Max255DynamicLengthIndexKeyCodec{}, cdc)

k.groupMemberByGroupIndex = orm.NewIndex(groupMemberTableBuilder, GroupMemberByGroupIndexPrefix, func(val interface{}) ([]orm.RowID, error) {
group := val.(*testdata.GroupMember).Group
return []orm.RowID{[]byte(group)}, nil
})
k.groupMemberByMemberIndex = orm.NewIndex(groupMemberTableBuilder, GroupMemberByMemberIndexPrefix, func(val interface{}) ([]orm.RowID, error) {
return []orm.RowID{[]byte(val.(*testdata.GroupMember).Member)}, nil
})
k.groupMemberTable = groupMemberTableBuilder.Build()

return k
}
Loading