feat: Add Table-Store (aka ORM) package - `AutoUInt64Table` and `PrimaryKeyTable` (#10415)

<!--
The default pull request template is for types feat, fix, or refactor.
For other templates, add one of the following parameters to the url:
- template=docs.md
- template=other.md
-->

## Description

ref: #9237, #9156

This PR is a follow-up of #9751.
It introduces 2 new public table types: `AutoUInt64Table` and `PrimaryKeyTable` based on the parent `table` struct introduced by #9751.

Upcoming work will include:
- multi-key secondary indexes
- iterator and pagination
- import/export genesis

---

### Author Checklist

*All items are required. Please add a note to the item if the item is not applicable and
please add links to any relevant follow up issues.*

I have...

- [x] included the correct [type prefix](https://github.com/commitizen/conventional-commit-types/blob/v3.0.0/index.json) in the PR title
- [ ] added `!` to the type prefix if API or client breaking change
- [x] targeted the correct branch (see [PR Targeting](https://github.com/cosmos/cosmos-sdk/blob/master/CONTRIBUTING.md#pr-targeting))
- [x] provided a link to the relevant issue or specification
- [ ] followed the guidelines for [building modules](https://github.com/cosmos/cosmos-sdk/blob/master/docs/building-modules)
- [x] included the necessary unit and integration [tests](https://github.com/cosmos/cosmos-sdk/blob/master/CONTRIBUTING.md#testing)
- [ ] added a changelog entry to `CHANGELOG.md`
- [x] included comments for [documenting Go code](https://blog.golang.org/godoc)
- [x] updated the relevant documentation or specification
- [x] reviewed "Files changed" and left comments if necessary
- [ ] confirmed all CI checks have passed

### Reviewers Checklist

*All items are required. Please add a note if the item is not applicable and please add
your handle next to the items reviewed if you only reviewed selected items.*

I have...

- [ ] confirmed the correct [type prefix](https://github.com/commitizen/conventional-commit-types/blob/v3.0.0/index.json) in the PR title
- [ ] confirmed `!` in the type prefix if API or client breaking change
- [ ] confirmed all author checklist items have been addressed 
- [ ] reviewed state machine logic
- [ ] reviewed API design and naming
- [ ] reviewed documentation is accurate
- [ ] reviewed tests and test coverage
- [ ] manually tested (if applicable)
This commit is contained in:
Marie Gauthier 2021-10-27 14:49:37 +02:00 committed by GitHub
parent 33737f4d8a
commit d8196920aa
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 1089 additions and 68 deletions

2
go.sum
View File

@ -163,8 +163,6 @@ github.com/cloudflare/cloudflare-go v0.10.2-0.20190916151808-a80f83b9add9/go.mod
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8=
github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI=
github.com/coinbase/rosetta-sdk-go v0.6.10 h1:rgHD/nHjxLh0lMEdfGDqpTtlvtSBwULqrrZ2qPdNaCM=
github.com/coinbase/rosetta-sdk-go v0.6.10/go.mod h1:J/JFMsfcePrjJZkwQFLh+hJErkAmdm9Iyy3D5Y0LfXo=
github.com/coinbase/rosetta-sdk-go v0.7.0 h1:lmTO/JEpCvZgpbkOITL95rA80CPKb5CtMzLaqF2mCNg=
github.com/coinbase/rosetta-sdk-go v0.7.0/go.mod h1:7nD3oBPIiHqhRprqvMgPoGxe/nyq3yftRmpsy29coWE=
github.com/confio/ics23/go v0.0.0-20200817220745-f173e6211efb/go.mod h1:E45NqnlpxGnpfTWL/xauN7MRwEE28T4Dd4uraToOaKg=

View File

@ -6,6 +6,10 @@ var (
ErrTest = errors.Register("table_testdata", 2, "test")
)
func (g TableModel) PrimaryKeyFields() []interface{} {
return []interface{}{g.Id}
}
func (g TableModel) ValidateBasic() error {
if g.Name == "" {
return errors.Wrap(ErrTest, "name")

View File

@ -323,8 +323,10 @@ func (m *BadMultiSignature) GetMaliciousField() []byte {
}
type TableModel struct {
Id uint64 `protobuf:"varint,1,opt,name=id,proto3" json:"id,omitempty"`
Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"`
Id uint64 `protobuf:"varint,1,opt,name=id,proto3" json:"id,omitempty"`
Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"`
Number uint64 `protobuf:"varint,3,opt,name=number,proto3" json:"number,omitempty"`
Metadata []byte `protobuf:"bytes,4,opt,name=metadata,proto3" json:"metadata,omitempty"`
}
func (m *TableModel) Reset() { *m = TableModel{} }
@ -374,6 +376,20 @@ func (m *TableModel) GetName() string {
return ""
}
func (m *TableModel) GetNumber() uint64 {
if m != nil {
return m.Number
}
return 0
}
func (m *TableModel) GetMetadata() []byte {
if m != nil {
return m.Metadata
}
return nil
}
func init() {
proto.RegisterType((*Dog)(nil), "testdata.Dog")
proto.RegisterType((*Cat)(nil), "testdata.Cat")
@ -387,32 +403,34 @@ func init() {
func init() { proto.RegisterFile("testdata.proto", fileDescriptor_40c4782d007dfce9) }
var fileDescriptor_40c4782d007dfce9 = []byte{
// 393 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x8c, 0x92, 0xb1, 0x8e, 0xda, 0x40,
0x14, 0x45, 0x19, 0x0c, 0x24, 0xbc, 0x58, 0x46, 0x19, 0x51, 0x38, 0x14, 0x0e, 0x72, 0x13, 0x8a,
0x60, 0x47, 0x41, 0x69, 0xe8, 0x80, 0x28, 0xa1, 0xa1, 0x71, 0x52, 0xa5, 0x41, 0x63, 0x3c, 0xd8,
0x23, 0xc6, 0x9e, 0x88, 0x19, 0x47, 0x90, 0xaf, 0xd8, 0x5f, 0xd8, 0xbf, 0xd9, 0x92, 0x72, 0xcb,
0x15, 0xfc, 0xc8, 0xca, 0x63, 0xbc, 0x50, 0x6c, 0x41, 0xe5, 0x7b, 0xef, 0xf3, 0x3d, 0x7a, 0x96,
0x1f, 0x58, 0x8a, 0x4a, 0x15, 0x11, 0x45, 0xbc, 0xbf, 0x5b, 0xa1, 0x04, 0x7e, 0x5b, 0xf9, 0x5e,
0x37, 0x16, 0xb1, 0xd0, 0xa1, 0x5f, 0xa8, 0x72, 0xde, 0xfb, 0x10, 0x0b, 0x11, 0x73, 0xea, 0x6b,
0x17, 0xe6, 0x6b, 0x9f, 0x64, 0xfb, 0x72, 0xe4, 0x0e, 0xc1, 0xf8, 0x2e, 0x62, 0x8c, 0xa1, 0x21,
0xd9, 0x7f, 0x6a, 0xa3, 0x3e, 0x1a, 0xb4, 0x03, 0xad, 0x8b, 0x2c, 0x23, 0x29, 0xb5, 0xeb, 0x65,
0x56, 0x68, 0xf7, 0x1b, 0x18, 0x33, 0xa2, 0xb0, 0x0d, 0x6f, 0x52, 0x91, 0xb1, 0x0d, 0xdd, 0x9e,
0x1b, 0x95, 0xc5, 0x5d, 0x68, 0x72, 0xf6, 0x8f, 0x4a, 0xdd, 0x6a, 0x06, 0xa5, 0x71, 0x7f, 0x42,
0x7b, 0x4e, 0xe4, 0x24, 0x63, 0x29, 0xe1, 0xf8, 0x33, 0xb4, 0x88, 0x56, 0xba, 0xfb, 0xee, 0x6b,
0xd7, 0x2b, 0xd7, 0xf3, 0xaa, 0xf5, 0xbc, 0x49, 0xb6, 0x0f, 0xce, 0xef, 0x60, 0x13, 0xd0, 0x4e,
0xc3, 0x8c, 0x00, 0xed, 0xdc, 0x19, 0x98, 0x73, 0x22, 0x2f, 0xac, 0x11, 0x40, 0x42, 0xe4, 0xf2,
0x06, 0x5e, 0x3b, 0xa9, 0x4a, 0xee, 0x02, 0x3a, 0x25, 0xe4, 0xc2, 0x19, 0x83, 0x55, 0x70, 0x6e,
0x64, 0x99, 0xc9, 0x55, 0xd7, 0x0d, 0xe1, 0xfd, 0x94, 0x44, 0x8b, 0x9c, 0x2b, 0xf6, 0x8b, 0xc5,
0x19, 0x51, 0xf9, 0x96, 0x62, 0x07, 0x40, 0x56, 0x46, 0xda, 0xa8, 0x6f, 0x0c, 0xcc, 0xe0, 0x2a,
0xc1, 0x9f, 0xa0, 0x93, 0x12, 0xce, 0x56, 0x4c, 0xe4, 0x72, 0xb9, 0x66, 0x94, 0x47, 0x76, 0xb3,
0x8f, 0x06, 0x66, 0x60, 0xbd, 0xc4, 0x3f, 0x8a, 0x74, 0xdc, 0x38, 0xdc, 0x7f, 0x44, 0xee, 0x17,
0x80, 0xdf, 0x24, 0xe4, 0x74, 0x21, 0x22, 0xca, 0xb1, 0x05, 0x75, 0x16, 0xe9, 0x0d, 0x1b, 0x41,
0x9d, 0x45, 0xaf, 0xfd, 0xa9, 0xe9, 0xfc, 0xe1, 0xe8, 0xa0, 0xc3, 0xd1, 0x41, 0x4f, 0x47, 0x07,
0xdd, 0x9d, 0x9c, 0xda, 0xe1, 0xe4, 0xd4, 0x1e, 0x4f, 0x4e, 0xed, 0x8f, 0x17, 0x33, 0x95, 0xe4,
0xa1, 0xb7, 0x12, 0xa9, 0xbf, 0x12, 0x32, 0x15, 0xf2, 0xfc, 0x18, 0xca, 0x68, 0xe3, 0x17, 0xa7,
0x94, 0x2b, 0xc6, 0xfd, 0xea, 0xa6, 0xc2, 0x96, 0xfe, 0xf6, 0xd1, 0x73, 0x00, 0x00, 0x00, 0xff,
0xff, 0x7a, 0x9a, 0x17, 0x6f, 0x76, 0x02, 0x00, 0x00,
// 419 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x8c, 0x52, 0xb1, 0x6e, 0xdb, 0x30,
0x10, 0x35, 0x2d, 0xd9, 0x8d, 0xaf, 0x82, 0x82, 0x12, 0x46, 0xa1, 0x7a, 0x50, 0x0d, 0x2d, 0xf5,
0xd0, 0x48, 0x40, 0x83, 0x2e, 0xd9, 0x92, 0x14, 0xad, 0x17, 0x2f, 0x6a, 0xa7, 0x2e, 0x01, 0x65,
0x32, 0x12, 0x11, 0x52, 0x2c, 0x44, 0xaa, 0x48, 0xfa, 0x15, 0xfd, 0x85, 0xfe, 0x4d, 0x47, 0x8f,
0x1d, 0x0b, 0xfb, 0x47, 0x0a, 0x51, 0x92, 0xed, 0xa1, 0x83, 0x27, 0xbe, 0xf7, 0xee, 0xde, 0xe3,
0x81, 0x3c, 0xf0, 0x0d, 0xd3, 0x86, 0x12, 0x43, 0xe2, 0x6f, 0x95, 0x32, 0x0a, 0x9f, 0xf5, 0x7c,
0x36, 0xcd, 0x55, 0xae, 0xac, 0x98, 0x34, 0xa8, 0xad, 0xcf, 0x5e, 0xe5, 0x4a, 0xe5, 0x82, 0x25,
0x96, 0x65, 0xf5, 0x7d, 0x42, 0xca, 0xa7, 0xb6, 0x14, 0x5d, 0x80, 0xf3, 0x41, 0xe5, 0x18, 0x83,
0xab, 0xf9, 0x0f, 0x16, 0xa0, 0x39, 0x5a, 0x4c, 0x52, 0x8b, 0x1b, 0xad, 0x24, 0x92, 0x05, 0xc3,
0x56, 0x6b, 0x70, 0xf4, 0x1e, 0x9c, 0x5b, 0x62, 0x70, 0x00, 0xcf, 0xa4, 0x2a, 0xf9, 0x03, 0xab,
0x3a, 0x47, 0x4f, 0xf1, 0x14, 0x46, 0x82, 0x7f, 0x67, 0xda, 0xba, 0x46, 0x69, 0x4b, 0xa2, 0x4f,
0x30, 0x59, 0x12, 0x7d, 0x5d, 0x72, 0x49, 0x04, 0x7e, 0x0b, 0x63, 0x62, 0x91, 0xf5, 0x3e, 0x7f,
0x37, 0x8d, 0xdb, 0xf1, 0xe2, 0x7e, 0xbc, 0xf8, 0xba, 0x7c, 0x4a, 0xbb, 0x1e, 0xec, 0x01, 0x7a,
0xb4, 0x61, 0x4e, 0x8a, 0x1e, 0xa3, 0x5b, 0xf0, 0x96, 0x44, 0x1f, 0xb2, 0x2e, 0x01, 0x0a, 0xa2,
0xef, 0x4e, 0xc8, 0x9b, 0x14, 0xbd, 0x29, 0x5a, 0xc1, 0x79, 0x1b, 0x72, 0xc8, 0xb9, 0x02, 0xbf,
0xc9, 0x39, 0x31, 0xcb, 0x2b, 0x8e, 0xbc, 0x51, 0x06, 0x2f, 0x6e, 0x08, 0x5d, 0xd5, 0xc2, 0xf0,
0xcf, 0x3c, 0x2f, 0x89, 0xa9, 0x2b, 0x86, 0x43, 0x00, 0xdd, 0x13, 0x1d, 0xa0, 0xb9, 0xb3, 0xf0,
0xd2, 0x23, 0x05, 0xbf, 0x81, 0x73, 0x49, 0x04, 0x5f, 0x73, 0x55, 0xeb, 0xbb, 0x7b, 0xce, 0x04,
0x0d, 0x46, 0x73, 0xb4, 0xf0, 0x52, 0x7f, 0x2f, 0x7f, 0x6c, 0xd4, 0x2b, 0x77, 0xf3, 0xeb, 0x35,
0x8a, 0x28, 0xc0, 0x17, 0x92, 0x09, 0xb6, 0x52, 0x94, 0x09, 0xec, 0xc3, 0x90, 0x53, 0x3b, 0xa1,
0x9b, 0x0e, 0x39, 0xfd, 0xdf, 0x4f, 0xe1, 0x97, 0x30, 0x2e, 0x6b, 0x99, 0xb1, 0x2a, 0x70, 0x6c,
0x5f, 0xc7, 0xf0, 0x0c, 0xce, 0x24, 0x33, 0xa4, 0xd9, 0x96, 0xc0, 0xb5, 0x37, 0xee, 0xf9, 0xcd,
0xf2, 0xf7, 0x36, 0x44, 0x9b, 0x6d, 0x88, 0xfe, 0x6e, 0x43, 0xf4, 0x73, 0x17, 0x0e, 0x36, 0xbb,
0x70, 0xf0, 0x67, 0x17, 0x0e, 0xbe, 0xc6, 0x39, 0x37, 0x45, 0x9d, 0xc5, 0x6b, 0x25, 0x93, 0xb5,
0xd2, 0x52, 0xe9, 0xee, 0xb8, 0xd0, 0xf4, 0x21, 0x69, 0xd6, 0xaf, 0x36, 0x5c, 0x24, 0xfd, 0x1e,
0x66, 0x63, 0xfb, 0x5e, 0x97, 0xff, 0x02, 0x00, 0x00, 0xff, 0xff, 0xb5, 0x8f, 0xef, 0x9c, 0xaa,
0x02, 0x00, 0x00,
}
func (m *Dog) Marshal() (dAtA []byte, err error) {
@ -660,6 +678,18 @@ func (m *TableModel) MarshalToSizedBuffer(dAtA []byte) (int, error) {
_ = i
var l int
_ = l
if len(m.Metadata) > 0 {
i -= len(m.Metadata)
copy(dAtA[i:], m.Metadata)
i = encodeVarintTestdata(dAtA, i, uint64(len(m.Metadata)))
i--
dAtA[i] = 0x22
}
if m.Number != 0 {
i = encodeVarintTestdata(dAtA, i, uint64(m.Number))
i--
dAtA[i] = 0x18
}
if len(m.Name) > 0 {
i -= len(m.Name)
copy(dAtA[i:], m.Name)
@ -796,6 +826,13 @@ func (m *TableModel) Size() (n int) {
if l > 0 {
n += 1 + l + sovTestdata(uint64(l))
}
if m.Number != 0 {
n += 1 + sovTestdata(uint64(m.Number))
}
l = len(m.Metadata)
if l > 0 {
n += 1 + l + sovTestdata(uint64(l))
}
return n
}
@ -1494,6 +1531,59 @@ func (m *TableModel) Unmarshal(dAtA []byte) error {
}
m.Name = string(dAtA[iNdEx:postIndex])
iNdEx = postIndex
case 3:
if wireType != 0 {
return fmt.Errorf("proto: wrong wireType = %d for field Number", wireType)
}
m.Number = 0
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowTestdata
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
m.Number |= uint64(b&0x7F) << shift
if b < 0x80 {
break
}
}
case 4:
if wireType != 2 {
return fmt.Errorf("proto: wrong wireType = %d for field Metadata", wireType)
}
var byteLen int
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowTestdata
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
byteLen |= int(b&0x7F) << shift
if b < 0x80 {
break
}
}
if byteLen < 0 {
return ErrInvalidLengthTestdata
}
postIndex := iNdEx + byteLen
if postIndex < 0 {
return ErrInvalidLengthTestdata
}
if postIndex > l {
return io.ErrUnexpectedEOF
}
m.Metadata = append(m.Metadata[:0], dAtA[iNdEx:postIndex]...)
if m.Metadata == nil {
m.Metadata = []byte{}
}
iNdEx = postIndex
default:
iNdEx = preIndex
skippy, err := skipTestdata(dAtA[iNdEx:])

View File

@ -39,4 +39,6 @@ message BadMultiSignature {
message TableModel {
uint64 id = 1;
string name = 2;
uint64 number = 3;
bytes metadata = 4;
}

View File

@ -170,7 +170,7 @@ var (
ErrORMEmptyModel = Register(ormCodespace, 45, "invalid argument")
// ErrORMKeyMaxLength defines an error when a key exceeds max length
ErrORMKeyMaxLength = Register(ormCodespace, 46, "index key exceeds max length")
ErrORMKeyMaxLength = Register(ormCodespace, 46, "key exceeds max length")
// ErrORMEmptyKey defines an error for an empty key
ErrORMEmptyKey = Register(ormCodespace, 47, "cannot use empty key")

View File

@ -17,7 +17,7 @@ require github.com/tendermint/tm-db v0.6.4
require (
github.com/DataDog/zstd v1.4.5 // indirect
github.com/armon/go-metrics v0.3.9 // indirect
github.com/armon/go-metrics v0.3.10 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/btcsuite/btcd v0.22.0-beta // indirect
github.com/cespare/xxhash v1.1.0 // indirect
@ -27,12 +27,13 @@ require (
github.com/cosmos/iavl v0.17.1 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/dgraph-io/badger/v2 v2.2007.2 // indirect
github.com/dgraph-io/ristretto v0.0.3 // indirect
github.com/dgraph-io/ristretto v0.1.0 // indirect
github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13 // indirect
github.com/dustin/go-humanize v1.0.0 // indirect
github.com/fsnotify/fsnotify v1.5.1 // indirect
github.com/go-kit/kit v0.10.0 // indirect
github.com/go-logfmt/logfmt v0.5.0 // indirect
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b // indirect
github.com/golang/protobuf v1.5.2 // indirect
github.com/golang/snappy v0.0.3 // indirect
github.com/google/btree v1.0.0 // indirect
@ -44,6 +45,7 @@ require (
github.com/hashicorp/hcl v1.0.0 // indirect
github.com/inconshreveable/mousetrap v1.0.0 // indirect
github.com/jmhodges/levigo v1.0.0 // indirect
github.com/lazyledger/smt v0.2.1-0.20210709230900-03ea40719554 // indirect
github.com/libp2p/go-buffer-pool v0.0.2 // indirect
github.com/magiconair/properties v1.8.5 // indirect
github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect
@ -56,7 +58,7 @@ require (
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/prometheus/client_golang v1.11.0 // indirect
github.com/prometheus/client_model v0.2.0 // indirect
github.com/prometheus/common v0.31.1 // indirect
github.com/prometheus/common v0.32.1 // indirect
github.com/prometheus/procfs v0.6.0 // indirect
github.com/sasha-s/go-deadlock v0.2.1-0.20190427202633-1595213edefa // indirect
github.com/spf13/afero v1.6.0 // indirect
@ -69,7 +71,7 @@ require (
github.com/syndtr/goleveldb v1.0.1-0.20200815110645-5c35d600f0ca // indirect
github.com/tecbot/gorocksdb v0.0.0-20191217155057-f0fad39f321c // indirect
github.com/tendermint/go-amino v0.16.0 // indirect
github.com/tendermint/tendermint v0.34.13 // indirect
github.com/tendermint/tendermint v0.34.14 // indirect
go.etcd.io/bbolt v1.3.5 // indirect
golang.org/x/crypto v0.0.0-20210817164053-32db794688a5 // indirect
golang.org/x/net v0.0.0-20210903162142-ad29c8ab022f // indirect
@ -87,3 +89,5 @@ replace google.golang.org/grpc => google.golang.org/grpc v1.33.2
replace github.com/gogo/protobuf => github.com/regen-network/protobuf v1.3.3-alpha.regen.1
replace github.com/cosmos/cosmos-sdk => ../../
replace github.com/cosmos/cosmos-sdk/db => ../../db

View File

@ -101,8 +101,8 @@ github.com/aristanetworks/goarista v0.0.0-20170210015632-ea17b1a17847/go.mod h1:
github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
github.com/armon/go-metrics v0.3.9 h1:O2sNqxBdvq8Eq5xmzljcYzAORli6RWCvEym4cJf9m18=
github.com/armon/go-metrics v0.3.9/go.mod h1:4O98XIr/9W0sxpJ8UaYkvjk10Iff7SnFrb4QAOwNTFc=
github.com/armon/go-metrics v0.3.10 h1:FR+drcQStOe+32sYyJYyZ7FIdgoGGBnwLl+flodp8Uo=
github.com/armon/go-metrics v0.3.10/go.mod h1:4O98XIr/9W0sxpJ8UaYkvjk10Iff7SnFrb4QAOwNTFc=
github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
github.com/aryann/difflib v0.0.0-20170710044230-e206f873d14a/go.mod h1:DAHtR1m6lCRdSC2Tm3DSWRPvIPr6xNKyeHdqDQSQT+A=
@ -162,8 +162,8 @@ github.com/cockroachdb/apd/v2 v2.0.2 h1:weh8u7Cneje73dDh+2tEVLUvyBc89iwepWCD8b80
github.com/cockroachdb/apd/v2 v2.0.2/go.mod h1:DDxRlzC2lo3/vSlmSoS7JkqbbrARPuFOGr0B9pvN3Gw=
github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8=
github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI=
github.com/coinbase/rosetta-sdk-go v0.6.10 h1:rgHD/nHjxLh0lMEdfGDqpTtlvtSBwULqrrZ2qPdNaCM=
github.com/coinbase/rosetta-sdk-go v0.6.10/go.mod h1:J/JFMsfcePrjJZkwQFLh+hJErkAmdm9Iyy3D5Y0LfXo=
github.com/coinbase/rosetta-sdk-go v0.7.0 h1:lmTO/JEpCvZgpbkOITL95rA80CPKb5CtMzLaqF2mCNg=
github.com/coinbase/rosetta-sdk-go v0.7.0/go.mod h1:7nD3oBPIiHqhRprqvMgPoGxe/nyq3yftRmpsy29coWE=
github.com/confio/ics23/go v0.0.0-20200817220745-f173e6211efb/go.mod h1:E45NqnlpxGnpfTWL/xauN7MRwEE28T4Dd4uraToOaKg=
github.com/confio/ics23/go v0.6.3/go.mod h1:E45NqnlpxGnpfTWL/xauN7MRwEE28T4Dd4uraToOaKg=
github.com/confio/ics23/go v0.6.6 h1:pkOy18YxxJ/r0XFDCnrl4Bjv6h4LkBSpLS6F38mrKL8=
@ -217,9 +217,11 @@ github.com/desertbit/timer v0.0.0-20180107155436-c41aec40b27f/go.mod h1:xH/i4TFM
github.com/dgraph-io/badger/v2 v2.2007.1/go.mod h1:26P/7fbL4kUZVEVKLAKXkBXKOydDmM2p1e+NhhnBCAE=
github.com/dgraph-io/badger/v2 v2.2007.2 h1:EjjK0KqwaFMlPin1ajhP943VPENHJdEz1KLIegjaI3k=
github.com/dgraph-io/badger/v2 v2.2007.2/go.mod h1:26P/7fbL4kUZVEVKLAKXkBXKOydDmM2p1e+NhhnBCAE=
github.com/dgraph-io/badger/v3 v3.2103.1/go.mod h1:dULbq6ehJ5K0cGW/1TQ9iSfUk0gbSiToDWmWmTsJ53E=
github.com/dgraph-io/ristretto v0.0.3-0.20200630154024-f66de99634de/go.mod h1:KPxhHT9ZxKefz+PCeOGsrHpl1qZ7i70dGTu2u+Ahh6E=
github.com/dgraph-io/ristretto v0.0.3 h1:jh22xisGBjrEVnRZ1DVTpBVQm0Xndu8sMl0CWDzSIBI=
github.com/dgraph-io/ristretto v0.0.3/go.mod h1:KPxhHT9ZxKefz+PCeOGsrHpl1qZ7i70dGTu2u+Ahh6E=
github.com/dgraph-io/ristretto v0.1.0 h1:Jv3CGQHp9OjuMBSne1485aDpUkTKEcUqF+jm/LuerPI=
github.com/dgraph-io/ristretto v0.1.0/go.mod h1:fux0lOrBhrVCJd3lcTHsIJhq1T2rokOu6v9Vcb3Q9ug=
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw=
github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13 h1:fAjc9m62+UWV/WAFKLNi6ZS0675eEUC9y3AlwSbQu1Y=
@ -300,6 +302,7 @@ github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2/go.mod h1:bBOAhwG1umN6
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/gogo/gateway v1.1.0 h1:u0SuhL9+Il+UbjM9VIE3ntfRujKbvVpFvNB4HbjeVQ0=
github.com/gogo/gateway v1.1.0/go.mod h1:S7rR8FRQyG3QFESeSv4l2WnsyzlCLG0CzBbUUo/mbic=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
@ -342,6 +345,7 @@ github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEW
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/btree v1.0.0 h1:0udJVsspx3VBr5FwtLhQQtuAsVc79tTq0ocGIPAU6qo=
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/flatbuffers v1.12.0/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8=
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=
@ -480,7 +484,7 @@ github.com/jackpal/go-nat-pmp v1.0.2-0.20160603034137-1fa385a6f458/go.mod h1:QPH
github.com/jedisct1/go-minisign v0.0.0-20190909160543-45766022959e/go.mod h1:G1CVv03EnqU1wYL2dFwXxW2An0az9JTl/ZsqXQeBlkU=
github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
github.com/jhump/protoreflect v1.10.0/go.mod h1:7GcYQDdMU/O/BBrl/cX6PNHpXh6cenjd8pneu5yW7Tg=
github.com/jhump/protoreflect v1.10.1/go.mod h1:7GcYQDdMU/O/BBrl/cX6PNHpXh6cenjd8pneu5yW7Tg=
github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
github.com/jmhodges/levigo v1.0.0 h1:q5EC36kV79HWeTBWsod3mG11EgStG3qArTKcvlksN1U=
github.com/jmhodges/levigo v1.0.0/go.mod h1:Q6Qx+uH3RAqyK4rFQroq9RL7mdkABMcfhEI+nNuzMJQ=
@ -506,8 +510,9 @@ github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4=
github.com/klauspost/compress v1.10.3/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
github.com/klauspost/compress v1.11.7 h1:0hzRabrMN4tSTvMfnL3SCv1ZGeAP23ynzodBgaHeMeg=
github.com/klauspost/compress v1.11.7/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
github.com/klauspost/compress v1.12.3 h1:G5AfA94pHPysR56qqrkO2pxEexdDzrpFJ6yt/VqWxVU=
github.com/klauspost/compress v1.12.3/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg=
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/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
@ -520,6 +525,8 @@ 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/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
github.com/lazyledger/smt v0.2.1-0.20210709230900-03ea40719554 h1:nDOkLO7klmnEw1s4AyKt1Arvpgyh33uj1JmkYlJaDsk=
github.com/lazyledger/smt v0.2.1-0.20210709230900-03ea40719554/go.mod h1:9+Pb2/tg1PvEgW7aFx4bFhDE4bvbI03zuJ8kb7nJ9Jc=
github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
github.com/lib/pq v1.2.0 h1:LXpIM/LZ5xGFhOpXAQUIMM1HdyqzVYM13zNdjCEEcA0=
github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
@ -689,8 +696,8 @@ github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB8
github.com/prometheus/common v0.14.0/go.mod h1:U+gB1OBLb1lF3O42bTCL+FK18tX9Oar16Clt/msog/s=
github.com/prometheus/common v0.15.0/go.mod h1:U+gB1OBLb1lF3O42bTCL+FK18tX9Oar16Clt/msog/s=
github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc=
github.com/prometheus/common v0.31.1 h1:d18hG4PkHnNAKNMOmFuXFaiY8Us0nird/2m60uS1AMs=
github.com/prometheus/common v0.31.1/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls=
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.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
@ -809,8 +816,9 @@ github.com/tendermint/go-amino v0.16.0/go.mod h1:TQU0M1i/ImAo+tYpZi73AU3V/dKeCoM
github.com/tendermint/tendermint v0.34.0-rc4/go.mod h1:yotsojf2C1QBOw4dZrTcxbyxmPUrT4hNuOQWX9XUwB4=
github.com/tendermint/tendermint v0.34.0-rc6/go.mod h1:ugzyZO5foutZImv0Iyx/gOFCX6mjJTgbLHTwi17VDVg=
github.com/tendermint/tendermint v0.34.0/go.mod h1:Aj3PIipBFSNO21r+Lq3TtzQ+uKESxkbA3yo/INM4QwQ=
github.com/tendermint/tendermint v0.34.13 h1:fu+tsHudbOr5PvepjH0q47Jae59hQAvn3IqAHv2EbC8=
github.com/tendermint/tendermint v0.34.13/go.mod h1:6RVVRBqwtKhA+H59APKumO+B7Nye4QXSFc6+TYxAxCI=
github.com/tendermint/tendermint v0.34.14 h1:GCXmlS8Bqd2Ix3TQCpwYLUNHe+Y+QyJsm5YE+S/FkPo=
github.com/tendermint/tendermint v0.34.14/go.mod h1:FrwVm3TvsVicI9Z7FlucHV6Znfd5KBc/Lpp69cCwtk0=
github.com/tendermint/tm-db v0.6.2/go.mod h1:GYtQ67SUvATOcoY8/+x6ylk8Qo02BQyLrAs+yAcLvGI=
github.com/tendermint/tm-db v0.6.3/go.mod h1:lfA1dL9/Y/Y8wwyPp2NMLyn5P5Ptr/gvDFNWtrCWSf8=
github.com/tendermint/tm-db v0.6.4 h1:3N2jlnYQkXNQclQwd/eKV/NzlqPlfK21cpRRIx80XXQ=

View File

@ -0,0 +1,75 @@
package orm
import (
"github.com/cosmos/cosmos-sdk/codec"
sdk "github.com/cosmos/cosmos-sdk/types"
)
var _ Indexable = &AutoUInt64Table{}
// AutoUInt64Table is the table type with an auto incrementing ID.
type AutoUInt64Table struct {
*table
seq Sequence
}
// NewAutoUInt64Table creates a new AutoUInt64Table.
func NewAutoUInt64Table(prefixData [2]byte, prefixSeq byte, model codec.ProtoMarshaler, cdc codec.Codec) (*AutoUInt64Table, error) {
table, err := newTable(prefixData, model, cdc)
if err != nil {
return nil, err
}
return &AutoUInt64Table{
table: table,
seq: NewSequence(prefixSeq),
}, nil
}
// Create a new persistent object with an auto generated uint64 primary key. The
// key is returned.
//
// Create iterates through the registered callbacks that may add secondary index
// keys.
func (a AutoUInt64Table) Create(store sdk.KVStore, obj codec.ProtoMarshaler) (uint64, error) {
autoIncID := a.seq.NextVal(store)
err := a.table.Create(store, EncodeSequence(autoIncID), obj)
if err != nil {
return 0, err
}
return autoIncID, nil
}
// Update 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.
//
// Update iterates through the registered callbacks that may add or remove
// secondary index keys.
func (a AutoUInt64Table) Update(store sdk.KVStore, rowID uint64, newValue codec.ProtoMarshaler) error {
return a.table.Update(store, 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(store sdk.KVStore, rowID uint64) error {
return a.table.Delete(store, EncodeSequence(rowID))
}
// Has checks if a rowID exists.
func (a AutoUInt64Table) Has(store sdk.KVStore, rowID uint64) bool {
return a.table.Has(store, 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(store sdk.KVStore, rowID uint64, dest codec.ProtoMarshaler) (RowID, error) {
rawRowID := EncodeSequence(rowID)
if err := a.table.GetOne(store, rawRowID, dest); err != nil {
return nil, err
}
return rawRowID, nil
}

View File

@ -0,0 +1,35 @@
package orm
import (
"github.com/cosmos/cosmos-sdk/codec"
"github.com/cosmos/cosmos-sdk/testutil/testdata"
)
type TestKeeper struct {
autoUInt64Table *AutoUInt64Table
primaryKeyTable *PrimaryKeyTable
}
var (
AutoUInt64TableTablePrefix [2]byte = [2]byte{0x0}
PrimaryKeyTablePrefix [2]byte = [2]byte{0x1}
AutoUInt64TableSeqPrefix byte = 0x2
)
func NewTestKeeper(cdc codec.Codec) TestKeeper {
k := TestKeeper{}
autoUInt64Table, err := NewAutoUInt64Table(AutoUInt64TableTablePrefix, AutoUInt64TableSeqPrefix, &testdata.TableModel{}, cdc)
if err != nil {
panic(err.Error())
}
k.autoUInt64Table = autoUInt64Table
primaryKeyTable, err := NewPrimaryKeyTable(PrimaryKeyTablePrefix, &testdata.TableModel{}, cdc)
if err != nil {
panic(err.Error())
}
k.primaryKeyTable = primaryKeyTable
return k
}

View File

@ -0,0 +1,18 @@
package orm
import (
"pgregory.net/rapid"
"github.com/cosmos/cosmos-sdk/testutil/testdata"
)
// genTableModel generates a new table model. At the moment it doesn't
// generate empty strings for Name.
var genTableModel = rapid.Custom(func(t *rapid.T) *testdata.TableModel {
return &testdata.TableModel{
Id: rapid.Uint64().Draw(t, "id").(uint64),
Name: rapid.StringN(1, 100, 150).Draw(t, "name").(string),
Number: rapid.Uint64().Draw(t, "number ").(uint64),
Metadata: []byte(rapid.StringN(1, 100, 150).Draw(t, "metadata").(string)),
}
})

View File

@ -0,0 +1,75 @@
package orm
import (
"fmt"
"github.com/cosmos/cosmos-sdk/types/errors"
)
// MaxBytesLen is the maximum allowed length for a key part of type []byte
const MaxBytesLen = 255
// buildKeyFromParts encodes and concatenates primary key and index parts.
// They can be []byte, string, and integer types. The function will return
// an error if there is a part of any other type.
// Key parts, except the last part, follow these rules:
// - []byte is encoded with a single byte length prefix
// - strings are null-terminated
// - integers are encoded using 8 byte big endian.
func buildKeyFromParts(parts []interface{}) ([]byte, error) {
bytesSlice := make([][]byte, len(parts))
totalLen := 0
var err error
for i, part := range parts {
bytesSlice[i], err = keyPartBytes(part, len(parts) > 1 && i == len(parts)-1)
if err != nil {
return nil, err
}
totalLen += len(bytesSlice[i])
}
key := make([]byte, 0, totalLen)
for _, bs := range bytesSlice {
key = append(key, bs...)
}
return key, nil
}
func keyPartBytes(part interface{}, last bool) ([]byte, error) {
switch v := part.(type) {
case []byte:
if last || len(v) == 0 {
return v, nil
}
return AddLengthPrefix(v), nil
case string:
if last || len(v) == 0 {
return []byte(v), nil
}
return NullTerminatedBytes(v), nil
case uint64:
return EncodeSequence(v), nil
default:
return nil, fmt.Errorf("type %T not allowed as key part", v)
}
}
// AddLengthPrefix prefixes the byte array with its length as 8 bytes. The function will panic
// if the bytes length is bigger than 255.
func AddLengthPrefix(bytes []byte) []byte {
byteLen := len(bytes)
if byteLen > MaxBytesLen {
panic(errors.Wrap(errors.ErrORMKeyMaxLength, "Cannot create key part with an []byte of length greater than 255 bytes. Try again with a smaller []byte."))
}
prefixedBytes := make([]byte, 1+len(bytes))
copy(prefixedBytes, []byte{uint8(byteLen)})
copy(prefixedBytes[1:], bytes)
return prefixedBytes
}
// NullTerminatedBytes converts string to byte array and null terminate it
func NullTerminatedBytes(s string) []byte {
bytes := make([]byte, len(s)+1)
copy(bytes, s)
return bytes
}

View File

@ -0,0 +1,46 @@
package orm
import (
"testing"
"github.com/stretchr/testify/require"
)
func TestAddLengthPrefix(t *testing.T) {
tcs := []struct {
name string
in []byte
expected []byte
}{
{"empty", []byte{}, []byte{0}},
{"nil", nil, []byte{0}},
{"some data", []byte{0, 1, 100, 200}, []byte{4, 0, 1, 100, 200}},
}
for _, tc := range tcs {
t.Run(tc.name, func(t *testing.T) {
out := AddLengthPrefix(tc.in)
require.Equal(t, tc.expected, out)
})
}
require.Panics(t, func() {
AddLengthPrefix(make([]byte, 256))
})
}
func TestNullTerminatedBytes(t *testing.T) {
tcs := []struct {
name string
in string
expected []byte
}{
{"empty", "", []byte{0}},
{"some data", "abc", []byte{0x61, 0x62, 0x63, 0}},
}
for _, tc := range tcs {
t.Run(tc.name, func(t *testing.T) {
out := NullTerminatedBytes(tc.in)
require.Equal(t, tc.expected, out)
})
}
}

View File

@ -0,0 +1,207 @@
package orm
import (
"encoding/binary"
"fmt"
"testing"
"github.com/cosmos/cosmos-sdk/codec"
"github.com/cosmos/cosmos-sdk/codec/types"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/types/errors"
"github.com/stretchr/testify/require"
"github.com/cosmos/cosmos-sdk/testutil/testdata"
)
func TestKeeperEndToEndWithAutoUInt64Table(t *testing.T) {
interfaceRegistry := types.NewInterfaceRegistry()
cdc := codec.NewProtoCodec(interfaceRegistry)
ctx := NewMockContext()
store := ctx.KVStore(sdk.NewKVStoreKey("test"))
k := NewTestKeeper(cdc)
tm := testdata.TableModel{
Id: 1,
Name: "name",
Number: 123,
Metadata: []byte("metadata"),
}
// when stored
rowID, err := k.autoUInt64Table.Create(store, &tm)
require.NoError(t, err)
// then we should find it
exists := k.autoUInt64Table.Has(store, rowID)
require.True(t, exists)
// and load it
var loaded testdata.TableModel
binKey, err := k.autoUInt64Table.GetOne(store, rowID, &loaded)
require.NoError(t, err)
require.Equal(t, rowID, binary.BigEndian.Uint64(binKey))
require.Equal(t, tm, loaded)
// when updated
tm.Name = "new name"
err = k.autoUInt64Table.Update(store, rowID, &tm)
require.NoError(t, err)
binKey, err = k.autoUInt64Table.GetOne(store, rowID, &loaded)
require.NoError(t, err)
require.Equal(t, rowID, binary.BigEndian.Uint64(binKey))
require.Equal(t, tm, loaded)
// when deleted
err = k.autoUInt64Table.Delete(store, rowID)
require.NoError(t, err)
exists = k.autoUInt64Table.Has(store, rowID)
require.False(t, exists)
}
func TestKeeperEndToEndWithPrimaryKeyTable(t *testing.T) {
interfaceRegistry := types.NewInterfaceRegistry()
cdc := codec.NewProtoCodec(interfaceRegistry)
ctx := NewMockContext()
store := ctx.KVStore(sdk.NewKVStoreKey("test"))
k := NewTestKeeper(cdc)
tm := testdata.TableModel{
Id: 1,
Name: "name",
Number: 123,
Metadata: []byte("metadata"),
}
// when stored
err := k.primaryKeyTable.Create(store, &tm)
require.NoError(t, err)
// then we should find it by primary key
primaryKey := PrimaryKey(&tm)
exists := k.primaryKeyTable.Has(store, primaryKey)
require.True(t, exists)
// and load it by primary key
var loaded testdata.TableModel
err = k.primaryKeyTable.GetOne(store, primaryKey, &loaded)
require.NoError(t, err)
// then values should match expectations
require.Equal(t, tm, loaded)
// and when we create another entry with the same primary key
err = k.primaryKeyTable.Create(store, &tm)
// then it should fail as the primary key must be unique
require.True(t, errors.ErrORMUniqueConstraint.Is(err), err)
// and when entity updated with new primary key
updatedMember := &testdata.TableModel{
Id: 2,
Name: tm.Name,
Number: tm.Number,
Metadata: tm.Metadata,
}
// then it should fail as the primary key is immutable
err = k.primaryKeyTable.Update(store, updatedMember)
require.Error(t, err)
// and when entity updated with non primary key attribute modified
updatedMember = &testdata.TableModel{
Id: 1,
Name: "new name",
Number: tm.Number,
Metadata: tm.Metadata,
}
// then it should not fail
err = k.primaryKeyTable.Update(store, updatedMember)
require.NoError(t, err)
// and when entity deleted
err = k.primaryKeyTable.Delete(store, &tm)
require.NoError(t, err)
exists = k.primaryKeyTable.Has(store, primaryKey)
require.False(t, exists)
}
func TestGasCostsPrimaryKeyTable(t *testing.T) {
interfaceRegistry := types.NewInterfaceRegistry()
cdc := codec.NewProtoCodec(interfaceRegistry)
ctx := NewMockContext()
store := ctx.KVStore(sdk.NewKVStoreKey("test"))
k := NewTestKeeper(cdc)
tm := testdata.TableModel{
Id: 1,
Name: "name",
Number: 123,
Metadata: []byte("metadata"),
}
rowID, err := k.autoUInt64Table.Create(store, &tm)
require.NoError(t, err)
require.Equal(t, uint64(1), rowID)
gCtx := NewGasCountingMockContext()
err = k.primaryKeyTable.Create(gCtx.KVStore(store), &tm)
require.NoError(t, err)
t.Logf("gas consumed on create: %d", gCtx.GasConsumed())
// get by primary key
gCtx.ResetGasMeter()
var loaded testdata.TableModel
err = k.primaryKeyTable.GetOne(gCtx.KVStore(store), PrimaryKey(&tm), &loaded)
require.NoError(t, err)
t.Logf("gas consumed on get by primary key: %d", gCtx.GasConsumed())
// delete
gCtx.ResetGasMeter()
err = k.primaryKeyTable.Delete(gCtx.KVStore(store), &tm)
require.NoError(t, err)
t.Logf("gas consumed on delete by primary key: %d", gCtx.GasConsumed())
// with 3 elements
var tms []testdata.TableModel
for i := 1; i < 4; i++ {
gCtx.ResetGasMeter()
tm := testdata.TableModel{
Id: uint64(i),
Name: fmt.Sprintf("name%d", i),
Number: 123,
Metadata: []byte("metadata"),
}
err = k.primaryKeyTable.Create(gCtx.KVStore(store), &tm)
require.NoError(t, err)
t.Logf("%d: gas consumed on create: %d", i, gCtx.GasConsumed())
tms = append(tms, tm)
}
for i := 1; i < 4; i++ {
gCtx.ResetGasMeter()
tm := testdata.TableModel{
Id: uint64(i),
Name: fmt.Sprintf("name%d", i),
Number: 123,
Metadata: []byte("metadata"),
}
err = k.primaryKeyTable.GetOne(gCtx.KVStore(store), PrimaryKey(&tm), &loaded)
require.NoError(t, err)
t.Logf("%d: gas consumed on get by primary key: %d", i, gCtx.GasConsumed())
}
// delete
for i, m := range tms {
gCtx.ResetGasMeter()
err = k.primaryKeyTable.Delete(gCtx.KVStore(store), &m)
require.NoError(t, err)
t.Logf("%d: gas consumed on delete: %d", i, gCtx.GasConsumed())
}
}

View File

@ -0,0 +1,113 @@
package orm
import (
"github.com/cosmos/cosmos-sdk/codec"
sdk "github.com/cosmos/cosmos-sdk/types"
)
var _ Indexable = &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.
type PrimaryKeyTable struct {
*table
}
// NewPrimaryKeyTable creates a new PrimaryKeyTable.
func NewPrimaryKeyTable(prefixData [2]byte, model PrimaryKeyed, cdc codec.Codec) (*PrimaryKeyTable, error) {
table, err := newTable(prefixData, model, cdc)
if err != nil {
return nil, err
}
return &PrimaryKeyTable{
table: table,
}, nil
}
// PrimaryKeyed defines an object type that is aware of its immutable primary key.
type PrimaryKeyed interface {
// PrimaryKeyFields returns the fields of the object that will make up
// the primary key. The PrimaryKey function will encode and concatenate
// the fields to build the primary key.
//
// PrimaryKey parts can be []byte, string, and integer types. []byte is
// encoded with a length prefix, strings are null-terminated, and
// integers are encoded using 8 byte big endian.
//
// IMPORTANT: []byte parts are encoded with a single byte length prefix,
// so cannot be longer than 255 bytes.
PrimaryKeyFields() []interface{}
codec.ProtoMarshaler
}
// PrimaryKey returns the immutable and serialized primary key of this object.
// The primary key has to be unique within it's domain so that not two with same
// value can exist in the same table. This means PrimaryKeyFields() has to
// return a unique value for each object.
func PrimaryKey(obj PrimaryKeyed) []byte {
fields := obj.PrimaryKeyFields()
key, err := buildKeyFromParts(fields)
if err != nil {
panic(err)
}
return key
}
// Create persists the given object under their primary key. It checks if the
// key already exists and may return an `ErrUniqueConstraint`.
//
// Create iterates through the registered callbacks that may add secondary
// index keys.
func (a PrimaryKeyTable) Create(store sdk.KVStore, obj PrimaryKeyed) error {
rowID := PrimaryKey(obj)
return a.table.Create(store, rowID, obj)
}
// Update updates the given object under the primary 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.
//
// Update iterates through the registered callbacks that may add or remove
// secondary index keys.
func (a PrimaryKeyTable) Update(store sdk.KVStore, newValue PrimaryKeyed) error {
return a.table.Update(store, PrimaryKey(newValue), newValue)
}
// Set persists the given object under the rowID key. It does not check if the
// key already exists and overwrites the value if it does.
//
// Set iterates through the registered callbacks that may add secondary index
// keys.
func (a PrimaryKeyTable) Set(store sdk.KVStore, newValue PrimaryKeyed) error {
return a.table.Set(store, PrimaryKey(newValue), newValue)
}
// Delete removes the object. It expects the primary key to exists already and
// fails with a `ErrNotFound` otherwise. Any caller must therefore make sure
// that this contract is fulfilled.
//
// Delete iterates through the registered callbacks that remove secondary index
// keys.
func (a PrimaryKeyTable) Delete(store sdk.KVStore, obj PrimaryKeyed) error {
return a.table.Delete(store, PrimaryKey(obj))
}
// Has checks if a key exists. Always returns false on nil or empty key.
func (a PrimaryKeyTable) Has(store sdk.KVStore, primaryKey RowID) bool {
return a.table.Has(store, primaryKey)
}
// Contains returns true when an object with same type and primary key is persisted in this table.
func (a PrimaryKeyTable) Contains(store sdk.KVStore, obj PrimaryKeyed) bool {
if err := assertCorrectType(a.table.model, obj); err != nil {
return false
}
return a.table.Has(store, PrimaryKey(obj))
}
// GetOne loads the object persisted for the given primary Key into the dest parameter.
// If none exists `ErrNotFound` is returned instead. Parameters must not be nil.
func (a PrimaryKeyTable) GetOne(store sdk.KVStore, primKey RowID, dest codec.ProtoMarshaler) error {
return a.table.GetOne(store, primKey, dest)
}

View File

@ -0,0 +1,188 @@
package orm
import (
"testing"
"github.com/cosmos/cosmos-sdk/codec"
"github.com/cosmos/cosmos-sdk/codec/types"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/stretchr/testify/require"
"pgregory.net/rapid"
"github.com/cosmos/cosmos-sdk/testutil/testdata"
)
func TestPrimaryKeyTable(t *testing.T) {
rapid.Check(t, rapid.Run(&primaryKeyMachine{}))
}
// primaryKeyMachine is a state machine model of the PrimaryKeyTable. The state
// is modelled as a map of strings to TableModels.
type primaryKeyMachine struct {
store sdk.KVStore
table *PrimaryKeyTable
state map[string]*testdata.TableModel
}
// stateKeys gets all the keys in the model map
func (m *primaryKeyMachine) stateKeys() []string {
keys := make([]string, len(m.state))
i := 0
for k := range m.state {
keys[i] = k
i++
}
return keys
}
// Generate a TableModel that has a 50% chance of being a part of the existing
// state
func (m *primaryKeyMachine) genTableModel() *rapid.Generator {
genStateTableModel := rapid.Custom(func(t *rapid.T) *testdata.TableModel {
pk := rapid.SampledFrom(m.stateKeys()).Draw(t, "key").(string)
return m.state[pk]
})
if len(m.stateKeys()) == 0 {
return genTableModel
} else {
return rapid.OneOf(genTableModel, genStateTableModel)
}
}
// Init creates a new instance of the state machine model by building the real
// table and making the empty model map
func (m *primaryKeyMachine) Init(t *rapid.T) {
// Create context
ctx := NewMockContext()
m.store = ctx.KVStore(sdk.NewKVStoreKey("test"))
// Create primary key table
interfaceRegistry := types.NewInterfaceRegistry()
cdc := codec.NewProtoCodec(interfaceRegistry)
table, err := NewPrimaryKeyTable(
[2]byte{0x1},
&testdata.TableModel{},
cdc,
)
require.NoError(t, err)
m.table = table
// Create model state
m.state = make(map[string]*testdata.TableModel)
}
// Check that the real values match the state values.
func (m *primaryKeyMachine) Check(t *rapid.T) {
for i := range m.state {
has := m.table.Has(m.store, []byte(i))
require.Equal(t, true, has)
}
}
// Create is one of the model commands. It adds an object to the table, creating
// an error if it already exists.
func (m *primaryKeyMachine) Create(t *rapid.T) {
g := genTableModel.Draw(t, "g").(*testdata.TableModel)
pk := string(PrimaryKey(g))
t.Logf("pk: %v", pk)
t.Logf("m.state: %v", m.state)
err := m.table.Create(m.store, g)
if m.state[pk] != nil {
require.Error(t, err)
} else {
require.NoError(t, err)
m.state[pk] = g
}
}
// Update is one of the model commands. It updates the value at a given primary
// key and fails if that primary key doesn't already exist in the table.
func (m *primaryKeyMachine) Update(t *rapid.T) {
tm := m.genTableModel().Draw(t, "tm").(*testdata.TableModel)
newName := rapid.StringN(1, 100, 150).Draw(t, "newName").(string)
tm.Name = newName
// Perform the real Update
err := m.table.Update(m.store, tm)
if m.state[string(PrimaryKey(tm))] == nil {
// If there's no value in the model, we expect an error
require.Error(t, err)
} else {
// If we have a value in the model, expect no error
require.NoError(t, err)
// Update the model with the new value
m.state[string(PrimaryKey(tm))] = tm
}
}
// Set is one of the model commands. It sets the value at a key in the table
// whether it exists or not.
func (m *primaryKeyMachine) Set(t *rapid.T) {
g := genTableModel.Draw(t, "g").(*testdata.TableModel)
pk := string(PrimaryKey(g))
err := m.table.Set(m.store, g)
require.NoError(t, err)
m.state[pk] = g
}
// Delete is one of the model commands. It removes the object with the given
// primary key from the table and returns an error if that primary key doesn't
// already exist in the table.
func (m *primaryKeyMachine) Delete(t *rapid.T) {
tm := m.genTableModel().Draw(t, "tm").(*testdata.TableModel)
// Perform the real Delete
err := m.table.Delete(m.store, tm)
if m.state[string(PrimaryKey(tm))] == nil {
// If there's no value in the model, we expect an error
require.Error(t, err)
} else {
// If we have a value in the model, expect no error
require.NoError(t, err)
// Delete the value from the model
delete(m.state, string(PrimaryKey(tm)))
}
}
// Has is one of the model commands. It checks whether a key already exists in
// the table.
func (m *primaryKeyMachine) Has(t *rapid.T) {
pk := PrimaryKey(m.genTableModel().Draw(t, "g").(*testdata.TableModel))
realHas := m.table.Has(m.store, pk)
modelHas := m.state[string(pk)] != nil
require.Equal(t, realHas, modelHas)
}
// GetOne is one of the model commands. It fetches an object from the table by
// its primary key and returns an error if that primary key isn't in the table.
func (m *primaryKeyMachine) GetOne(t *rapid.T) {
pk := PrimaryKey(m.genTableModel().Draw(t, "tm").(*testdata.TableModel))
var tm testdata.TableModel
err := m.table.GetOne(m.store, pk, &tm)
t.Logf("tm: %v", tm)
if m.state[string(pk)] == nil {
require.Error(t, err)
} else {
require.NoError(t, err)
require.Equal(t, *m.state[string(pk)], tm)
}
}

View File

@ -0,0 +1,67 @@
package orm
import (
"testing"
"github.com/cosmos/cosmos-sdk/codec"
"github.com/cosmos/cosmos-sdk/codec/types"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/cosmos/cosmos-sdk/testutil/testdata"
)
func TestContains(t *testing.T) {
interfaceRegistry := types.NewInterfaceRegistry()
cdc := codec.NewProtoCodec(interfaceRegistry)
ctx := NewMockContext()
store := ctx.KVStore(sdk.NewKVStoreKey("test"))
tb, err := NewPrimaryKeyTable([2]byte{0x1}, &testdata.TableModel{}, cdc)
require.NoError(t, err)
obj := testdata.TableModel{
Id: 1,
Name: "Some name",
}
err = tb.Create(store, &obj)
require.NoError(t, err)
specs := map[string]struct {
src PrimaryKeyed
exp bool
}{
"same object": {src: &obj, exp: true},
"clone": {
src: &testdata.TableModel{
Id: 1,
Name: "Some name",
},
exp: true,
},
"different primary key": {
src: &testdata.TableModel{
Id: 2,
Name: "Some name",
},
exp: false,
},
"different type, same key": {
src: mockPrimaryKeyed{&obj},
exp: false,
},
}
for msg, spec := range specs {
t.Run(msg, func(t *testing.T) {
got := tb.Contains(store, spec.src)
assert.Equal(t, spec.exp, got)
})
}
}
type mockPrimaryKeyed struct {
*testdata.TableModel
}

View File

@ -0,0 +1,38 @@
# Table
A table can be built given a `codec.ProtoMarshaler` model type, a prefix to access the underlying prefix store used to store table data as well as a `Codec` for marshalling/unmarshalling.
+++ https://github.com/cosmos/cosmos-sdk/blob/9f78f16ae75cc42fc5fe636bde18a453ba74831f/x/group/internal/orm/table.go#L24-L30
In the prefix store, entities should be stored by an unique identifier called `RowID` which can be based either on an `uint64` auto-increment counter, string or dynamic size bytes.
Regular CRUD operations can be performed on a table, these methods take a `sdk.KVStore` as parameter to get the table prefix store.
The `table` struct does not:
- enforce uniqueness of the `RowID`
- enforce prefix uniqueness of keys, i.e. not allowing one key to be a prefix
of another
- 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.
## AutoUInt64Table
`AutoUInt64Table` is a table type with an auto incrementing `uint64` ID.
+++ https://github.com/cosmos/cosmos-sdk/blob/9f78f16ae75cc42fc5fe636bde18a453ba74831f/x/group/internal/orm/auto_uint64.go#L11-L14
It's based on the `Sequence` struct which is a persistent unique key generator based on a counter encoded using 8 byte big endian.
## PrimaryKeyTable
`PrimaryKeyTable` provides simpler object style orm methods where are persisted and loaded with a reference to their unique primary key.
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
`PrimaryKeyFields()` method returns the list of key parts for a given object.
The primary key parts can be []byte, string, and `uint64` types.
Key parts, except the last part, follow these rules:
- []byte is encoded with a single byte length prefix
- strings are null-terminated
- `uint64` are encoded using 8 byte big endian.

View File

@ -1,26 +1,9 @@
## Abstract
# Abstract
The orm package provides a framework for creating relational database tables with primary and secondary keys.
### Tables
## Contents
```go
type table struct {
model reflect.Type
prefix [2]byte
afterSet []AfterSetInterceptor
afterDelete []AfterDeleteInterceptor
cdc codec.Codec
}
```
A table can be built given a `codec.ProtoMarshaler` model type, a prefix to access the underlying prefix store used to store table data as well as a `Codec` for marshalling/unmarshalling.
In the prefix store, entities should be stored by an unique identifier called `RowID` which can be based either on an `uint64` auto-increment counter, string or dynamic size bytes.
Regular CRUD operations can be performed on a table, these methods take a `sdk.KVStore` as parameter to get the table prefix store.
The `table` struct does not:
- enforce uniqueness of the `RowID`
- enforce prefix uniqueness of keys, i.e. not allowing one key to be a prefix
of another
- 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.
1. **[Table](01_table.md)**
- [AutoUInt64Table](01_table.md#autouint64table)
- [PrimaryKeyTable](01_table.md#primarykeytable)

View File

@ -1,7 +1,10 @@
package orm
import (
"fmt"
"github.com/cosmos/cosmos-sdk/store"
"github.com/cosmos/cosmos-sdk/store/gaskv"
"github.com/cosmos/cosmos-sdk/store/types"
storetypes "github.com/cosmos/cosmos-sdk/store/types"
sdk "github.com/cosmos/cosmos-sdk/types"
@ -31,3 +34,70 @@ func (m MockContext) KVStore(key storetypes.StoreKey) sdk.KVStore {
}
return m.store.GetCommitKVStore(key)
}
type debuggingGasMeter struct {
g types.GasMeter
}
func (d debuggingGasMeter) GasConsumed() types.Gas {
return d.g.GasConsumed()
}
func (d debuggingGasMeter) GasRemaining() types.Gas {
return d.g.GasRemaining()
}
func (d debuggingGasMeter) GasConsumedToLimit() types.Gas {
return d.g.GasConsumedToLimit()
}
func (d debuggingGasMeter) RefundGas(amount uint64, descriptor string) {
d.g.RefundGas(amount, descriptor)
}
func (d debuggingGasMeter) Limit() types.Gas {
return d.g.Limit()
}
func (d debuggingGasMeter) ConsumeGas(amount types.Gas, descriptor string) {
fmt.Printf("++ Consuming gas: %q :%d\n", descriptor, amount)
d.g.ConsumeGas(amount, descriptor)
}
func (d debuggingGasMeter) IsPastLimit() bool {
return d.g.IsPastLimit()
}
func (d debuggingGasMeter) IsOutOfGas() bool {
return d.g.IsOutOfGas()
}
func (d debuggingGasMeter) String() string {
return d.g.String()
}
type GasCountingMockContext struct {
GasMeter sdk.GasMeter
}
func NewGasCountingMockContext() *GasCountingMockContext {
return &GasCountingMockContext{
GasMeter: &debuggingGasMeter{sdk.NewInfiniteGasMeter()},
}
}
func (g GasCountingMockContext) KVStore(store sdk.KVStore) sdk.KVStore {
return gaskv.NewStore(store, g.GasMeter, types.KVGasConfig())
}
func (g GasCountingMockContext) GasConsumed() types.Gas {
return g.GasMeter.GasConsumed()
}
func (g GasCountingMockContext) GasRemaining() types.Gas {
return g.GasMeter.GasRemaining()
}
func (g *GasCountingMockContext) ResetGasMeter() {
g.GasMeter = sdk.NewInfiniteGasMeter()
}

View File

@ -32,7 +32,7 @@ type Iterator interface {
// LoadNext loads the next value in the sequence into the pointer passed as dest and returns the key. If there
// are no more items the ErrORMIteratorDone error is returned
// The key is the rowID.
LoadNext(dest codec.ProtoMarshaler) (RowID, error)
LoadNext(store sdk.KVStore, dest codec.ProtoMarshaler) (RowID, error)
// Close releases the iterator and should be called at the end of iteration
io.Closer
}