feat(orm): add ORM Table and Indexes (#10670)

## Description

Closes: #10729 

Includes:
* table, auto-increment table, and singleton `Table` implementations
* primary key, index and unique index `Index` implementations
* store wrappers based on tm-db but that could be retargeted to the new ADR 040 db which separate index and commitment stores, with a debug wrapper
* streaming JSON import and export
* full logical decoding (and encoding)



---

### 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:
Aaron Craelius 2022-01-20 11:22:06 -05:00 committed by GitHub
parent 033e5f3076
commit 531bf50845
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
55 changed files with 6435 additions and 847 deletions

View File

@ -2,13 +2,14 @@ package groupv1beta1
import (
fmt "fmt"
io "io"
reflect "reflect"
sync "sync"
runtime "github.com/cosmos/cosmos-proto/runtime"
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
protoiface "google.golang.org/protobuf/runtime/protoiface"
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
io "io"
reflect "reflect"
sync "sync"
)
var _ protoreflect.List = (*_GenesisState_2_list)(nil)

View File

@ -0,0 +1,42 @@
package encodeutil
import (
"bytes"
"encoding/binary"
"io"
"google.golang.org/protobuf/reflect/protoreflect"
)
// SkipPrefix skips the provided prefix in the reader or returns an error.
// This is used for efficient logical decoding of keys.
func SkipPrefix(r *bytes.Reader, prefix []byte) error {
n := len(prefix)
if n > 0 {
// we skip checking the prefix for performance reasons because we assume
// that it was checked by the caller
_, err := r.Seek(int64(n), io.SeekCurrent)
return err
}
return nil
}
// AppendVarUInt32 creates a new key prefix, by encoding and appending a
// var-uint32 to the provided prefix.
func AppendVarUInt32(prefix []byte, x uint32) []byte {
prefixLen := len(prefix)
res := make([]byte, prefixLen+binary.MaxVarintLen32)
copy(res, prefix)
n := binary.PutUvarint(res[prefixLen:], uint64(x))
return res[:prefixLen+n]
}
// ValuesOf takes the arguments and converts them to protoreflect.Value's.
func ValuesOf(values ...interface{}) []protoreflect.Value {
n := len(values)
res := make([]protoreflect.Value, n)
for i := 0; i < n; i++ {
res[i] = protoreflect.ValueOf(values[i])
}
return res
}

View File

@ -19,10 +19,30 @@ type EntryCodec interface {
type IndexCodec interface {
EntryCodec
// MessageType returns the message type this index codec applies to.
MessageType() protoreflect.MessageType
// GetFieldNames returns the field names in the key of this index.
GetFieldNames() []protoreflect.Name
// DecodeIndexKey decodes a kv-pair into index-fields and primary-key field
// values. These fields may or may not overlap depending on the index.
DecodeIndexKey(k, v []byte) (indexFields, primaryKey []protoreflect.Value, err error)
// EncodeKVFromMessage encodes a kv-pair for the index from a message.
EncodeKVFromMessage(message protoreflect.Message) (k, v []byte, err error)
// CompareKeys compares the provided values which must correspond to the
// fields in this key. Prefix keys of different lengths are supported but the
// function will panic if either array is too long. A negative value is returned
// if values1 is less than values2, 0 is returned if the two arrays are equal,
// and a positive value is returned if values2 is greater.
CompareKeys(key1, key2 []protoreflect.Value) int
// EncodeKeyFromMessage encodes the key part of this index and returns both
// index values and encoded key.
EncodeKeyFromMessage(message protoreflect.Message) (keyValues []protoreflect.Value, key []byte, err error)
// IsFullyOrdered returns true if all fields in the key are also ordered.
IsFullyOrdered() bool
}

View File

@ -42,17 +42,11 @@ func (p *PrimaryKeyEntry) GetTableName() protoreflect.FullName {
}
func (p *PrimaryKeyEntry) String() string {
msg := p.Value
msgStr := "_"
if msg != nil {
msgBz, err := protojson.Marshal(msg)
if err == nil {
msgStr = string(msgBz)
} else {
msgStr = fmt.Sprintf("ERR:%v", err)
}
if p.Value == nil {
return fmt.Sprintf("PK %s %s -> _", p.TableName, fmtValues(p.Key))
} else {
return fmt.Sprintf("PK %s %s -> %s", p.TableName, fmtValues(p.Key), p.Value)
}
return fmt.Sprintf("PK:%s/%s:%s", p.TableName, fmtValues(p.Key), msgStr)
}
func fmtValues(values []protoreflect.Value) string {
@ -109,7 +103,7 @@ func (i *IndexKeyEntry) GetTableName() protoreflect.FullName {
func (i *IndexKeyEntry) doNotImplement() {}
func (i *IndexKeyEntry) string() string {
return fmt.Sprintf("%s/%s:%s:%s", i.TableName, fmtFields(i.Fields), fmtValues(i.IndexValues), fmtValues(i.PrimaryKey))
return fmt.Sprintf("%s %s : %s -> %s", i.TableName, fmtFields(i.Fields), fmtValues(i.IndexValues), fmtValues(i.PrimaryKey))
}
func fmtFields(fields []protoreflect.Name) string {
@ -122,10 +116,10 @@ func fmtFields(fields []protoreflect.Name) string {
func (i *IndexKeyEntry) String() string {
if i.IsUnique {
return fmt.Sprintf("UNIQ:%s", i.string())
return fmt.Sprintf("UNIQ %s", i.string())
} else {
return fmt.Sprintf("IDX:%s", i.string())
return fmt.Sprintf("IDX %s", i.string())
}
}
@ -146,7 +140,7 @@ func (s *SeqEntry) GetTableName() protoreflect.FullName {
func (s *SeqEntry) doNotImplement() {}
func (s *SeqEntry) String() string {
return fmt.Sprintf("SEQ:%s:%d", s.TableName, s.Value)
return fmt.Sprintf("SEQ %s %d", s.TableName, s.Value)
}
var _, _, _ Entry = &PrimaryKeyEntry{}, &IndexKeyEntry{}, &SeqEntry{}

View File

@ -4,33 +4,31 @@ import (
"testing"
"google.golang.org/protobuf/reflect/protoreflect"
"github.com/cosmos/cosmos-sdk/orm/encoding/ormkv"
"gotest.tools/v3/assert"
"github.com/cosmos/cosmos-sdk/orm/encoding/encodeutil"
"github.com/cosmos/cosmos-sdk/orm/encoding/ormkv"
"github.com/cosmos/cosmos-sdk/orm/internal/testpb"
"github.com/cosmos/cosmos-sdk/orm/internal/testutil"
)
var aFullName = (&testpb.A{}).ProtoReflect().Descriptor().FullName()
var aFullName = (&testpb.ExampleTable{}).ProtoReflect().Descriptor().FullName()
func TestPrimaryKeyEntry(t *testing.T) {
entry := &ormkv.PrimaryKeyEntry{
TableName: aFullName,
Key: testutil.ValuesOf(uint32(1), "abc"),
Value: &testpb.A{I32: -1},
Key: encodeutil.ValuesOf(uint32(1), "abc"),
Value: &testpb.ExampleTable{I32: -1},
}
assert.Equal(t, `PK:testpb.A/1/"abc":{"i32":-1}`, entry.String())
assert.Equal(t, `PK testpb.ExampleTable 1/"abc" -> i32:-1`, entry.String())
assert.Equal(t, aFullName, entry.GetTableName())
// prefix key
entry = &ormkv.PrimaryKeyEntry{
TableName: aFullName,
Key: testutil.ValuesOf(uint32(1), "abc"),
Key: encodeutil.ValuesOf(uint32(1), "abc"),
Value: nil,
}
assert.Equal(t, `PK:testpb.A/1/"abc":_`, entry.String())
assert.Equal(t, `PK testpb.ExampleTable 1/"abc" -> _`, entry.String())
assert.Equal(t, aFullName, entry.GetTableName())
}
@ -39,20 +37,20 @@ func TestIndexKeyEntry(t *testing.T) {
TableName: aFullName,
Fields: []protoreflect.Name{"u32", "i32", "str"},
IsUnique: false,
IndexValues: testutil.ValuesOf(uint32(10), int32(-1), "abc"),
PrimaryKey: testutil.ValuesOf("abc", int32(-1)),
IndexValues: encodeutil.ValuesOf(uint32(10), int32(-1), "abc"),
PrimaryKey: encodeutil.ValuesOf("abc", int32(-1)),
}
assert.Equal(t, `IDX:testpb.A/u32/i32/str:10/-1/"abc":"abc"/-1`, entry.String())
assert.Equal(t, `IDX testpb.ExampleTable u32/i32/str : 10/-1/"abc" -> "abc"/-1`, entry.String())
assert.Equal(t, aFullName, entry.GetTableName())
entry = &ormkv.IndexKeyEntry{
TableName: aFullName,
Fields: []protoreflect.Name{"u32"},
IsUnique: true,
IndexValues: testutil.ValuesOf(uint32(10)),
PrimaryKey: testutil.ValuesOf("abc", int32(-1)),
IndexValues: encodeutil.ValuesOf(uint32(10)),
PrimaryKey: encodeutil.ValuesOf("abc", int32(-1)),
}
assert.Equal(t, `UNIQ:testpb.A/u32:10:"abc"/-1`, entry.String())
assert.Equal(t, `UNIQ testpb.ExampleTable u32 : 10 -> "abc"/-1`, entry.String())
assert.Equal(t, aFullName, entry.GetTableName())
// prefix key
@ -60,9 +58,9 @@ func TestIndexKeyEntry(t *testing.T) {
TableName: aFullName,
Fields: []protoreflect.Name{"u32", "i32", "str"},
IsUnique: false,
IndexValues: testutil.ValuesOf(uint32(10), int32(-1)),
IndexValues: encodeutil.ValuesOf(uint32(10), int32(-1)),
}
assert.Equal(t, `IDX:testpb.A/u32/i32/str:10/-1:_`, entry.String())
assert.Equal(t, `IDX testpb.ExampleTable u32/i32/str : 10/-1 -> _`, entry.String())
assert.Equal(t, aFullName, entry.GetTableName())
// prefix key
@ -70,8 +68,8 @@ func TestIndexKeyEntry(t *testing.T) {
TableName: aFullName,
Fields: []protoreflect.Name{"str", "i32"},
IsUnique: true,
IndexValues: testutil.ValuesOf("abc", int32(1)),
IndexValues: encodeutil.ValuesOf("abc", int32(1)),
}
assert.Equal(t, `UNIQ:testpb.A/str/i32:"abc"/1:_`, entry.String())
assert.Equal(t, `UNIQ testpb.ExampleTable str/i32 : "abc"/1 -> _`, entry.String())
assert.Equal(t, aFullName, entry.GetTableName())
}

View File

@ -12,7 +12,6 @@ import (
// IndexKeyCodec is the codec for (non-unique) index keys.
type IndexKeyCodec struct {
*KeyCodec
tableName protoreflect.FullName
pkFieldOrder []int
}
@ -20,7 +19,15 @@ var _ IndexCodec = &IndexKeyCodec{}
// NewIndexKeyCodec creates a new IndexKeyCodec with an optional prefix for the
// provided message descriptor, index and primary key fields.
func NewIndexKeyCodec(prefix []byte, messageDescriptor protoreflect.MessageDescriptor, indexFields, primaryKeyFields []protoreflect.Name) (*IndexKeyCodec, error) {
func NewIndexKeyCodec(prefix []byte, messageType protoreflect.MessageType, indexFields, primaryKeyFields []protoreflect.Name) (*IndexKeyCodec, error) {
if len(indexFields) == 0 {
return nil, ormerrors.InvalidTableDefinition.Wrapf("index fields are empty")
}
if len(primaryKeyFields) == 0 {
return nil, ormerrors.InvalidTableDefinition.Wrapf("primary key fields are empty")
}
indexFieldMap := map[protoreflect.Name]int{}
keyFields := make([]protoreflect.Name, 0, len(indexFields)+len(primaryKeyFields))
@ -43,7 +50,7 @@ func NewIndexKeyCodec(prefix []byte, messageDescriptor protoreflect.MessageDescr
k++
}
cdc, err := NewKeyCodec(prefix, messageDescriptor, keyFields)
cdc, err := NewKeyCodec(prefix, messageType, keyFields)
if err != nil {
return nil, err
}
@ -51,13 +58,12 @@ func NewIndexKeyCodec(prefix []byte, messageDescriptor protoreflect.MessageDescr
return &IndexKeyCodec{
KeyCodec: cdc,
pkFieldOrder: pkFieldOrder,
tableName: messageDescriptor.FullName(),
}, nil
}
func (cdc IndexKeyCodec) DecodeIndexKey(k, _ []byte) (indexFields, primaryKey []protoreflect.Value, err error) {
values, err := cdc.Decode(bytes.NewReader(k))
values, err := cdc.DecodeKey(bytes.NewReader(k))
// got prefix key
if err == io.EOF {
return values, nil, nil
@ -87,7 +93,7 @@ func (cdc IndexKeyCodec) DecodeEntry(k, v []byte) (Entry, error) {
}
return &IndexKeyEntry{
TableName: cdc.tableName,
TableName: cdc.messageType.Descriptor().FullName(),
Fields: cdc.fieldNames,
IndexValues: idxValues,
PrimaryKey: pk,
@ -100,21 +106,19 @@ func (cdc IndexKeyCodec) EncodeEntry(entry Entry) (k, v []byte, err error) {
return nil, nil, ormerrors.BadDecodeEntry
}
if indexEntry.TableName != cdc.tableName {
if indexEntry.TableName != cdc.messageType.Descriptor().FullName() {
return nil, nil, ormerrors.BadDecodeEntry
}
bz, err := cdc.KeyCodec.Encode(indexEntry.IndexValues)
bz, err := cdc.KeyCodec.EncodeKey(indexEntry.IndexValues)
if err != nil {
return nil, nil, err
}
return bz, sentinel, nil
return bz, []byte{}, nil
}
var sentinel = []byte{0}
func (cdc IndexKeyCodec) EncodeKVFromMessage(message protoreflect.Message) (k, v []byte, err error) {
_, k, err = cdc.EncodeFromMessage(message)
return k, sentinel, err
_, k, err = cdc.EncodeKeyFromMessage(message)
return k, []byte{}, err
}

View File

@ -18,20 +18,20 @@ func TestIndexKeyCodec(t *testing.T) {
idxPartCdc := testutil.TestKeyCodecGen(1, 5).Draw(t, "idxPartCdc").(testutil.TestKeyCodec)
pkCodec := testutil.TestKeyCodecGen(1, 5).Draw(t, "pkCdc").(testutil.TestKeyCodec)
prefix := rapid.SliceOfN(rapid.Byte(), 0, 5).Draw(t, "prefix").([]byte)
desc := (&testpb.A{}).ProtoReflect().Descriptor()
messageType := (&testpb.ExampleTable{}).ProtoReflect().Type()
indexKeyCdc, err := ormkv.NewIndexKeyCodec(
prefix,
desc,
messageType,
idxPartCdc.Codec.GetFieldNames(),
pkCodec.Codec.GetFieldNames(),
)
assert.NilError(t, err)
for i := 0; i < 100; i++ {
a := testutil.GenA.Draw(t, fmt.Sprintf("a%d", i)).(*testpb.A)
key := indexKeyCdc.GetValues(a.ProtoReflect())
pk := pkCodec.Codec.GetValues(a.ProtoReflect())
a := testutil.GenA.Draw(t, fmt.Sprintf("a%d", i)).(*testpb.ExampleTable)
key := indexKeyCdc.GetKeyValues(a.ProtoReflect())
pk := pkCodec.Codec.GetKeyValues(a.ProtoReflect())
idx1 := &ormkv.IndexKeyEntry{
TableName: desc.FullName(),
TableName: messageType.Descriptor().FullName(),
Fields: indexKeyCdc.GetFieldNames(),
IsUnique: false,
IndexValues: key,
@ -48,16 +48,16 @@ func TestIndexKeyCodec(t *testing.T) {
entry2, err := indexKeyCdc.DecodeEntry(k, v)
assert.NilError(t, err)
idx2 := entry2.(*ormkv.IndexKeyEntry)
assert.Equal(t, 0, indexKeyCdc.CompareValues(idx1.IndexValues, idx2.IndexValues))
assert.Equal(t, 0, pkCodec.Codec.CompareValues(idx1.PrimaryKey, idx2.PrimaryKey))
assert.Equal(t, 0, indexKeyCdc.CompareKeys(idx1.IndexValues, idx2.IndexValues))
assert.Equal(t, 0, pkCodec.Codec.CompareKeys(idx1.PrimaryKey, idx2.PrimaryKey))
assert.Equal(t, false, idx2.IsUnique)
assert.Equal(t, desc.FullName(), idx2.TableName)
assert.Equal(t, messageType.Descriptor().FullName(), idx2.TableName)
assert.DeepEqual(t, idx1.Fields, idx2.Fields)
idxFields, pk2, err := indexKeyCdc.DecodeIndexKey(k, v)
assert.NilError(t, err)
assert.Equal(t, 0, indexKeyCdc.CompareValues(key, idxFields))
assert.Equal(t, 0, pkCodec.Codec.CompareValues(pk, pk2))
assert.Equal(t, 0, indexKeyCdc.CompareKeys(key, idxFields))
assert.Equal(t, 0, pkCodec.Codec.CompareKeys(pk, pk2))
}
})
}

View File

@ -8,6 +8,7 @@ import (
"google.golang.org/protobuf/reflect/protoreflect"
"github.com/cosmos/cosmos-sdk/orm/encoding/encodeutil"
"github.com/cosmos/cosmos-sdk/orm/encoding/ormfield"
)
@ -22,11 +23,12 @@ type KeyCodec struct {
fieldDescriptors []protoreflect.FieldDescriptor
fieldNames []protoreflect.Name
fieldCodecs []ormfield.Codec
messageType protoreflect.MessageType
}
// NewKeyCodec returns a new KeyCodec with an optional prefix for the provided
// message descriptor and fields.
func NewKeyCodec(prefix []byte, messageDescriptor protoreflect.MessageDescriptor, fieldNames []protoreflect.Name) (*KeyCodec, error) {
func NewKeyCodec(prefix []byte, messageType protoreflect.MessageType, fieldNames []protoreflect.Name) (*KeyCodec, error) {
n := len(fieldNames)
fieldCodecs := make([]ormfield.Codec, n)
fieldDescriptors := make([]protoreflect.FieldDescriptor, n)
@ -35,7 +37,7 @@ func NewKeyCodec(prefix []byte, messageDescriptor protoreflect.MessageDescriptor
i int
}
fixedSize := 0
messageFields := messageDescriptor.Fields()
messageFields := messageType.Descriptor().Fields()
for i := 0; i < n; i++ {
nonTerminal := i != n-1
@ -63,15 +65,16 @@ func NewKeyCodec(prefix []byte, messageDescriptor protoreflect.MessageDescriptor
prefix: prefix,
fixedSize: fixedSize,
variableSizers: variableSizers,
messageType: messageType,
}, nil
}
// Encode encodes the values assuming that they correspond to the fields
// EncodeKey encodes the values assuming that they correspond to the fields
// specified for the key. If the array of values is shorter than the
// number of fields in the key, a partial "prefix" key will be encoded
// which can be used for constructing a prefix iterator.
func (cdc *KeyCodec) Encode(values []protoreflect.Value) ([]byte, error) {
sz, err := cdc.ComputeBufferSize(values)
func (cdc *KeyCodec) EncodeKey(values []protoreflect.Value) ([]byte, error) {
sz, err := cdc.ComputeKeyBufferSize(values)
if err != nil {
return nil, err
}
@ -94,8 +97,8 @@ func (cdc *KeyCodec) Encode(values []protoreflect.Value) ([]byte, error) {
return w.Bytes(), nil
}
// GetValues extracts the values specified by the key fields from the message.
func (cdc *KeyCodec) GetValues(message protoreflect.Message) []protoreflect.Value {
// GetKeyValues extracts the values specified by the key fields from the message.
func (cdc *KeyCodec) GetKeyValues(message protoreflect.Message) []protoreflect.Value {
res := make([]protoreflect.Value, len(cdc.fieldDescriptors))
for i, f := range cdc.fieldDescriptors {
res[i] = message.Get(f)
@ -103,11 +106,11 @@ func (cdc *KeyCodec) GetValues(message protoreflect.Message) []protoreflect.Valu
return res
}
// Decode decodes the values in the key specified by the reader. If the
// DecodeKey decodes the values in the key specified by the reader. If the
// provided key is a prefix key, the values that could be decoded will
// be returned with io.EOF as the error.
func (cdc *KeyCodec) Decode(r *bytes.Reader) ([]protoreflect.Value, error) {
if err := skipPrefix(r, cdc.prefix); err != nil {
func (cdc *KeyCodec) DecodeKey(r *bytes.Reader) ([]protoreflect.Value, error) {
if err := encodeutil.SkipPrefix(r, cdc.prefix); err != nil {
return nil, err
}
@ -125,10 +128,10 @@ func (cdc *KeyCodec) Decode(r *bytes.Reader) ([]protoreflect.Value, error) {
return values, nil
}
// EncodeFromMessage combines GetValues and Encode.
func (cdc *KeyCodec) EncodeFromMessage(message protoreflect.Message) ([]protoreflect.Value, []byte, error) {
values := cdc.GetValues(message)
bz, err := cdc.Encode(values)
// EncodeKeyFromMessage combines GetKeyValues and EncodeKey.
func (cdc *KeyCodec) EncodeKeyFromMessage(message protoreflect.Message) ([]protoreflect.Value, []byte, error) {
values := cdc.GetKeyValues(message)
bz, err := cdc.EncodeKey(values)
return values, bz, err
}
@ -142,12 +145,12 @@ func (cdc *KeyCodec) IsFullyOrdered() bool {
return true
}
// CompareValues compares the provided values which must correspond to the
// CompareKeys compares the provided values which must correspond to the
// fields in this key. Prefix keys of different lengths are supported but the
// function will panic if either array is too long. A negative value is returned
// if values1 is less than values2, 0 is returned if the two arrays are equal,
// and a positive value is returned if values2 is greater.
func (cdc *KeyCodec) CompareValues(values1, values2 []protoreflect.Value) int {
func (cdc *KeyCodec) CompareKeys(values1, values2 []protoreflect.Value) int {
j := len(values1)
k := len(values2)
n := j
@ -178,9 +181,9 @@ func (cdc *KeyCodec) CompareValues(values1, values2 []protoreflect.Value) int {
}
}
// ComputeBufferSize computes the required buffer size for the provided values
// ComputeKeyBufferSize computes the required buffer size for the provided values
// which can represent a full or prefix key.
func (cdc KeyCodec) ComputeBufferSize(values []protoreflect.Value) (int, error) {
func (cdc KeyCodec) ComputeKeyBufferSize(values []protoreflect.Value) (int, error) {
size := cdc.fixedSize
n := len(values)
for _, sz := range cdc.variableSizers {
@ -198,10 +201,10 @@ func (cdc KeyCodec) ComputeBufferSize(values []protoreflect.Value) (int, error)
return size, nil
}
// SetValues sets the provided values on the message which must correspond
// SetKeyValues sets the provided values on the message which must correspond
// exactly to the field descriptors for this key. Prefix keys aren't
// supported.
func (cdc *KeyCodec) SetValues(message protoreflect.Message, values []protoreflect.Value) {
func (cdc *KeyCodec) SetKeyValues(message protoreflect.Message, values []protoreflect.Value) {
for i, f := range cdc.fieldDescriptors {
message.Set(f, values[i])
}
@ -284,6 +287,13 @@ func (cdc *KeyCodec) GetFieldNames() []protoreflect.Name {
return cdc.fieldNames
}
// Prefix returns the prefix applied to keys in this codec before any field
// values are encoded.
func (cdc *KeyCodec) Prefix() []byte {
return cdc.prefix
}
// MessageType returns the message type of fields in this key.
func (cdc *KeyCodec) MessageType() protoreflect.MessageType {
return cdc.messageType
}

View File

@ -5,14 +5,13 @@ import (
"io"
"testing"
"github.com/cosmos/cosmos-sdk/orm/internal/testpb"
"github.com/cosmos/cosmos-sdk/orm/encoding/ormkv"
"google.golang.org/protobuf/reflect/protoreflect"
"gotest.tools/v3/assert"
"pgregory.net/rapid"
"github.com/cosmos/cosmos-sdk/orm/encoding/encodeutil"
"github.com/cosmos/cosmos-sdk/orm/encoding/ormkv"
"github.com/cosmos/cosmos-sdk/orm/internal/testpb"
"github.com/cosmos/cosmos-sdk/orm/internal/testutil"
)
@ -29,24 +28,24 @@ func TestKeyCodec(t *testing.T) {
keyValues2 := key.Draw(t, "values2")
bz2 := assertEncDecKey(t, key, keyValues2)
// bytes comparison should equal comparison of values
assert.Equal(t, key.Codec.CompareValues(keyValues, keyValues2), bytes.Compare(bz1, bz2))
assert.Equal(t, key.Codec.CompareKeys(keyValues, keyValues2), bytes.Compare(bz1, bz2))
}
}
})
}
func assertEncDecKey(t *rapid.T, key testutil.TestKeyCodec, keyValues []protoreflect.Value) []byte {
bz, err := key.Codec.Encode(keyValues)
bz, err := key.Codec.EncodeKey(keyValues)
assert.NilError(t, err)
keyValues2, err := key.Codec.Decode(bytes.NewReader(bz))
keyValues2, err := key.Codec.DecodeKey(bytes.NewReader(bz))
assert.NilError(t, err)
assert.Equal(t, 0, key.Codec.CompareValues(keyValues, keyValues2))
assert.Equal(t, 0, key.Codec.CompareKeys(keyValues, keyValues2))
return bz
}
func TestCompareValues(t *testing.T) {
cdc, err := ormkv.NewKeyCodec(nil,
(&testpb.A{}).ProtoReflect().Descriptor(),
(&testpb.ExampleTable{}).ProtoReflect().Type(),
[]protoreflect.Name{"u32", "str", "i32"})
assert.NilError(t, err)
@ -59,113 +58,113 @@ func TestCompareValues(t *testing.T) {
}{
{
"eq",
testutil.ValuesOf(uint32(0), "abc", int32(-3)),
testutil.ValuesOf(uint32(0), "abc", int32(-3)),
encodeutil.ValuesOf(uint32(0), "abc", int32(-3)),
encodeutil.ValuesOf(uint32(0), "abc", int32(-3)),
0,
false,
},
{
"eq prefix 0",
testutil.ValuesOf(),
testutil.ValuesOf(),
encodeutil.ValuesOf(),
encodeutil.ValuesOf(),
0,
false,
},
{
"eq prefix 1",
testutil.ValuesOf(uint32(0)),
testutil.ValuesOf(uint32(0)),
encodeutil.ValuesOf(uint32(0)),
encodeutil.ValuesOf(uint32(0)),
0,
false,
},
{
"eq prefix 2",
testutil.ValuesOf(uint32(0), "abc"),
testutil.ValuesOf(uint32(0), "abc"),
encodeutil.ValuesOf(uint32(0), "abc"),
encodeutil.ValuesOf(uint32(0), "abc"),
0,
false,
},
{
"lt1",
testutil.ValuesOf(uint32(0), "abc", int32(-3)),
testutil.ValuesOf(uint32(1), "abc", int32(-3)),
encodeutil.ValuesOf(uint32(0), "abc", int32(-3)),
encodeutil.ValuesOf(uint32(1), "abc", int32(-3)),
-1,
true,
},
{
"lt2",
testutil.ValuesOf(uint32(1), "abb", int32(-3)),
testutil.ValuesOf(uint32(1), "abc", int32(-3)),
encodeutil.ValuesOf(uint32(1), "abb", int32(-3)),
encodeutil.ValuesOf(uint32(1), "abc", int32(-3)),
-1,
true,
},
{
"lt3",
testutil.ValuesOf(uint32(1), "abb", int32(-4)),
testutil.ValuesOf(uint32(1), "abb", int32(-3)),
encodeutil.ValuesOf(uint32(1), "abb", int32(-4)),
encodeutil.ValuesOf(uint32(1), "abb", int32(-3)),
-1,
true,
},
{
"less prefix 0",
testutil.ValuesOf(),
testutil.ValuesOf(uint32(1), "abb", int32(-4)),
encodeutil.ValuesOf(),
encodeutil.ValuesOf(uint32(1), "abb", int32(-4)),
-1,
true,
},
{
"less prefix 1",
testutil.ValuesOf(uint32(1)),
testutil.ValuesOf(uint32(1), "abb", int32(-4)),
encodeutil.ValuesOf(uint32(1)),
encodeutil.ValuesOf(uint32(1), "abb", int32(-4)),
-1,
true,
},
{
"less prefix 2",
testutil.ValuesOf(uint32(1), "abb"),
testutil.ValuesOf(uint32(1), "abb", int32(-4)),
encodeutil.ValuesOf(uint32(1), "abb"),
encodeutil.ValuesOf(uint32(1), "abb", int32(-4)),
-1,
true,
},
{
"gt1",
testutil.ValuesOf(uint32(2), "abb", int32(-4)),
testutil.ValuesOf(uint32(1), "abb", int32(-4)),
encodeutil.ValuesOf(uint32(2), "abb", int32(-4)),
encodeutil.ValuesOf(uint32(1), "abb", int32(-4)),
1,
false,
},
{
"gt2",
testutil.ValuesOf(uint32(2), "abc", int32(-4)),
testutil.ValuesOf(uint32(2), "abb", int32(-4)),
encodeutil.ValuesOf(uint32(2), "abc", int32(-4)),
encodeutil.ValuesOf(uint32(2), "abb", int32(-4)),
1,
false,
},
{
"gt3",
testutil.ValuesOf(uint32(2), "abc", int32(1)),
testutil.ValuesOf(uint32(2), "abc", int32(-3)),
encodeutil.ValuesOf(uint32(2), "abc", int32(1)),
encodeutil.ValuesOf(uint32(2), "abc", int32(-3)),
1,
false,
},
{
"gt prefix 0",
testutil.ValuesOf(uint32(2), "abc", int32(-3)),
testutil.ValuesOf(),
encodeutil.ValuesOf(uint32(2), "abc", int32(-3)),
encodeutil.ValuesOf(),
1,
true,
},
{
"gt prefix 1",
testutil.ValuesOf(uint32(2), "abc", int32(-3)),
testutil.ValuesOf(uint32(2)),
encodeutil.ValuesOf(uint32(2), "abc", int32(-3)),
encodeutil.ValuesOf(uint32(2)),
1,
true,
},
{
"gt prefix 2",
testutil.ValuesOf(uint32(2), "abc", int32(-3)),
testutil.ValuesOf(uint32(2), "abc"),
encodeutil.ValuesOf(uint32(2), "abc", int32(-3)),
encodeutil.ValuesOf(uint32(2), "abc"),
1,
true,
},
@ -174,7 +173,7 @@ func TestCompareValues(t *testing.T) {
t.Run(test.name, func(t *testing.T) {
assert.Equal(
t, test.expect,
cdc.CompareValues(test.values1, test.values2),
cdc.CompareKeys(test.values1, test.values2),
)
// CheckValidRangeIterationKeys should give comparable results
err := cdc.CheckValidRangeIterationKeys(test.values1, test.values2)
@ -189,7 +188,7 @@ func TestCompareValues(t *testing.T) {
func TestDecodePrefixKey(t *testing.T) {
cdc, err := ormkv.NewKeyCodec(nil,
(&testpb.A{}).ProtoReflect().Descriptor(),
(&testpb.ExampleTable{}).ProtoReflect().Type(),
[]protoreflect.Name{"u32", "str", "bz", "i32"})
assert.NilError(t, err)
@ -199,23 +198,23 @@ func TestDecodePrefixKey(t *testing.T) {
}{
{
"1",
testutil.ValuesOf(uint32(5), "abc"),
encodeutil.ValuesOf(uint32(5), "abc"),
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
bz, err := cdc.Encode(test.values)
bz, err := cdc.EncodeKey(test.values)
assert.NilError(t, err)
values, err := cdc.Decode(bytes.NewReader(bz))
values, err := cdc.DecodeKey(bytes.NewReader(bz))
assert.ErrorType(t, err, io.EOF)
assert.Equal(t, 0, cdc.CompareValues(test.values, values))
assert.Equal(t, 0, cdc.CompareKeys(test.values, values))
})
}
}
func TestValidRangeIterationKeys(t *testing.T) {
cdc, err := ormkv.NewKeyCodec(nil,
(&testpb.A{}).ProtoReflect().Descriptor(),
(&testpb.ExampleTable{}).ProtoReflect().Type(),
[]protoreflect.Name{"u32", "str", "bz", "i32"})
assert.NilError(t, err)
@ -227,62 +226,62 @@ func TestValidRangeIterationKeys(t *testing.T) {
}{
{
"1 eq",
testutil.ValuesOf(uint32(0)),
testutil.ValuesOf(uint32(0)),
encodeutil.ValuesOf(uint32(0)),
encodeutil.ValuesOf(uint32(0)),
true,
},
{
"1 lt",
testutil.ValuesOf(uint32(0)),
testutil.ValuesOf(uint32(1)),
encodeutil.ValuesOf(uint32(0)),
encodeutil.ValuesOf(uint32(1)),
false,
},
{
"1 gt",
testutil.ValuesOf(uint32(1)),
testutil.ValuesOf(uint32(0)),
encodeutil.ValuesOf(uint32(1)),
encodeutil.ValuesOf(uint32(0)),
true,
},
{
"1,2 lt",
testutil.ValuesOf(uint32(0)),
testutil.ValuesOf(uint32(0), "abc"),
encodeutil.ValuesOf(uint32(0)),
encodeutil.ValuesOf(uint32(0), "abc"),
false,
},
{
"1,2 gt",
testutil.ValuesOf(uint32(0), "abc"),
testutil.ValuesOf(uint32(0)),
encodeutil.ValuesOf(uint32(0), "abc"),
encodeutil.ValuesOf(uint32(0)),
false,
},
{
"1,2,3",
testutil.ValuesOf(uint32(0)),
testutil.ValuesOf(uint32(0), "abc", []byte{1, 2}),
encodeutil.ValuesOf(uint32(0)),
encodeutil.ValuesOf(uint32(0), "abc", []byte{1, 2}),
true,
},
{
"1,2,3,4 lt",
testutil.ValuesOf(uint32(0), "abc", []byte{1, 2}, int32(-1)),
testutil.ValuesOf(uint32(0), "abc", []byte{1, 2}, int32(1)),
encodeutil.ValuesOf(uint32(0), "abc", []byte{1, 2}, int32(-1)),
encodeutil.ValuesOf(uint32(0), "abc", []byte{1, 2}, int32(1)),
false,
},
{
"too long",
testutil.ValuesOf(uint32(0), "abc", []byte{1, 2}, int32(-1)),
testutil.ValuesOf(uint32(0), "abc", []byte{1, 2}, int32(1), int32(1)),
encodeutil.ValuesOf(uint32(0), "abc", []byte{1, 2}, int32(-1)),
encodeutil.ValuesOf(uint32(0), "abc", []byte{1, 2}, int32(1), int32(1)),
true,
},
{
"1,2,3,4 eq",
testutil.ValuesOf(uint32(0), "abc", []byte{1, 2}, int32(1)),
testutil.ValuesOf(uint32(0), "abc", []byte{1, 2}, int32(1)),
encodeutil.ValuesOf(uint32(0), "abc", []byte{1, 2}, int32(1)),
encodeutil.ValuesOf(uint32(0), "abc", []byte{1, 2}, int32(1)),
true,
},
{
"1,2,3,4 bz err",
testutil.ValuesOf(uint32(0), "abc", []byte{1, 2}, int32(-1)),
testutil.ValuesOf(uint32(0), "abc", []byte{1, 2, 3}, int32(1)),
encodeutil.ValuesOf(uint32(0), "abc", []byte{1, 2}, int32(-1)),
encodeutil.ValuesOf(uint32(0), "abc", []byte{1, 2, 3}, int32(1)),
true,
},
}
@ -300,19 +299,19 @@ func TestValidRangeIterationKeys(t *testing.T) {
func TestGetSet(t *testing.T) {
cdc, err := ormkv.NewKeyCodec(nil,
(&testpb.A{}).ProtoReflect().Descriptor(),
(&testpb.ExampleTable{}).ProtoReflect().Type(),
[]protoreflect.Name{"u32", "str", "i32"})
assert.NilError(t, err)
var a testpb.A
values := testutil.ValuesOf(uint32(4), "abc", int32(1))
cdc.SetValues(a.ProtoReflect(), values)
values2 := cdc.GetValues(a.ProtoReflect())
assert.Equal(t, 0, cdc.CompareValues(values, values2))
bz, err := cdc.Encode(values)
var a testpb.ExampleTable
values := encodeutil.ValuesOf(uint32(4), "abc", int32(1))
cdc.SetKeyValues(a.ProtoReflect(), values)
values2 := cdc.GetKeyValues(a.ProtoReflect())
assert.Equal(t, 0, cdc.CompareKeys(values, values2))
bz, err := cdc.EncodeKey(values)
assert.NilError(t, err)
values3, bz2, err := cdc.EncodeFromMessage(a.ProtoReflect())
values3, bz2, err := cdc.EncodeKeyFromMessage(a.ProtoReflect())
assert.NilError(t, err)
assert.Equal(t, 0, cdc.CompareValues(values, values3))
assert.Equal(t, 0, cdc.CompareKeys(values, values3))
assert.Assert(t, bytes.Equal(bz, bz2))
}

View File

@ -14,21 +14,21 @@ import (
// PrimaryKeyCodec is the codec for primary keys.
type PrimaryKeyCodec struct {
*KeyCodec
msgType protoreflect.MessageType
unmarshalOptions proto.UnmarshalOptions
}
var _ IndexCodec = &PrimaryKeyCodec{}
// NewPrimaryKeyCodec creates a new PrimaryKeyCodec for the provided msg and
// fields, with an optional prefix and unmarshal options.
func NewPrimaryKeyCodec(prefix []byte, msgType protoreflect.MessageType, fieldNames []protoreflect.Name, unmarshalOptions proto.UnmarshalOptions) (*PrimaryKeyCodec, error) {
keyCodec, err := NewKeyCodec(prefix, msgType.Descriptor(), fieldNames)
keyCodec, err := NewKeyCodec(prefix, msgType, fieldNames)
if err != nil {
return nil, err
}
return &PrimaryKeyCodec{
KeyCodec: keyCodec,
msgType: msgType,
unmarshalOptions: unmarshalOptions,
}, nil
}
@ -36,7 +36,7 @@ func NewPrimaryKeyCodec(prefix []byte, msgType protoreflect.MessageType, fieldNa
var _ IndexCodec = PrimaryKeyCodec{}
func (p PrimaryKeyCodec) DecodeIndexKey(k, _ []byte) (indexFields, primaryKey []protoreflect.Value, err error) {
indexFields, err = p.Decode(bytes.NewReader(k))
indexFields, err = p.DecodeKey(bytes.NewReader(k))
// got prefix key
if err == io.EOF {
@ -55,16 +55,21 @@ func (p PrimaryKeyCodec) DecodeIndexKey(k, _ []byte) (indexFields, primaryKey []
}
func (p PrimaryKeyCodec) DecodeEntry(k, v []byte) (Entry, error) {
values, err := p.Decode(bytes.NewReader(k))
if err != nil {
values, err := p.DecodeKey(bytes.NewReader(k))
if err == io.EOF {
return &PrimaryKeyEntry{
TableName: p.messageType.Descriptor().FullName(),
Key: values,
}, nil
} else if err != nil {
return nil, err
}
msg := p.msgType.New().Interface()
msg := p.messageType.New().Interface()
err = p.Unmarshal(values, v, msg)
return &PrimaryKeyEntry{
TableName: p.msgType.Descriptor().FullName(),
TableName: p.messageType.Descriptor().FullName(),
Key: values,
Value: msg,
}, err
@ -76,15 +81,15 @@ func (p PrimaryKeyCodec) EncodeEntry(entry Entry) (k, v []byte, err error) {
return nil, nil, ormerrors.BadDecodeEntry.Wrapf("expected %T, got %T", &PrimaryKeyEntry{}, entry)
}
if pkEntry.TableName != p.msgType.Descriptor().FullName() {
if pkEntry.TableName != p.messageType.Descriptor().FullName() {
return nil, nil, ormerrors.BadDecodeEntry.Wrapf(
"wrong table name, got %s, expected %s",
pkEntry.TableName,
p.msgType.Descriptor().FullName(),
p.messageType.Descriptor().FullName(),
)
}
k, err = p.KeyCodec.Encode(pkEntry.Key)
k, err = p.KeyCodec.EncodeKey(pkEntry.Key)
if err != nil {
return nil, nil, err
}
@ -104,7 +109,7 @@ func (p PrimaryKeyCodec) marshal(key []protoreflect.Value, message proto.Message
}
// set the primary key values again returning the message to its original state
p.SetValues(message.ProtoReflect(), key)
p.SetKeyValues(message.ProtoReflect(), key)
return v, nil
}
@ -122,12 +127,12 @@ func (p *PrimaryKeyCodec) Unmarshal(key []protoreflect.Value, value []byte, mess
}
// rehydrate primary key
p.SetValues(message.ProtoReflect(), key)
p.SetKeyValues(message.ProtoReflect(), key)
return nil
}
func (p PrimaryKeyCodec) EncodeKVFromMessage(message protoreflect.Message) (k, v []byte, err error) {
ks, k, err := p.KeyCodec.EncodeFromMessage(message)
ks, k, err := p.KeyCodec.EncodeKeyFromMessage(message)
if err != nil {
return nil, nil, err
}

View File

@ -20,14 +20,14 @@ func TestPrimaryKeyCodec(t *testing.T) {
keyCodec := testutil.TestKeyCodecGen(0, 5).Draw(t, "keyCodec").(testutil.TestKeyCodec)
pkCodec, err := ormkv.NewPrimaryKeyCodec(
keyCodec.Codec.Prefix(),
(&testpb.A{}).ProtoReflect().Type(),
(&testpb.ExampleTable{}).ProtoReflect().Type(),
keyCodec.Codec.GetFieldNames(),
proto.UnmarshalOptions{},
)
assert.NilError(t, err)
for i := 0; i < 100; i++ {
a := testutil.GenA.Draw(t, fmt.Sprintf("a%d", i)).(*testpb.A)
key := keyCodec.Codec.GetValues(a.ProtoReflect())
a := testutil.GenA.Draw(t, fmt.Sprintf("a%d", i)).(*testpb.ExampleTable)
key := keyCodec.Codec.GetKeyValues(a.ProtoReflect())
pk1 := &ormkv.PrimaryKeyEntry{
TableName: aFullName,
Key: key,
@ -44,16 +44,16 @@ func TestPrimaryKeyCodec(t *testing.T) {
entry2, err := pkCodec.DecodeEntry(k, v)
assert.NilError(t, err)
pk2 := entry2.(*ormkv.PrimaryKeyEntry)
assert.Equal(t, 0, pkCodec.CompareValues(pk1.Key, pk2.Key))
assert.Equal(t, 0, pkCodec.CompareKeys(pk1.Key, pk2.Key))
assert.DeepEqual(t, pk1.Value, pk2.Value, protocmp.Transform())
idxFields, pk3, err := pkCodec.DecodeIndexKey(k, v)
assert.NilError(t, err)
assert.Equal(t, 0, pkCodec.CompareValues(pk1.Key, pk3))
assert.Equal(t, 0, pkCodec.CompareValues(pk1.Key, idxFields))
assert.Equal(t, 0, pkCodec.CompareKeys(pk1.Key, pk3))
assert.Equal(t, 0, pkCodec.CompareKeys(pk1.Key, idxFields))
pkCodec.ClearValues(a.ProtoReflect())
pkCodec.SetValues(a.ProtoReflect(), pk1.Key)
pkCodec.SetKeyValues(a.ProtoReflect(), pk1.Key)
assert.DeepEqual(t, a, pk2.Value, protocmp.Transform())
}
})

View File

@ -11,13 +11,13 @@ import (
// SeqCodec is the codec for auto-incrementing uint64 primary key sequences.
type SeqCodec struct {
tableName protoreflect.FullName
prefix []byte
messageType protoreflect.FullName
prefix []byte
}
// NewSeqCodec creates a new SeqCodec.
func NewSeqCodec(tableName protoreflect.FullName, prefix []byte) *SeqCodec {
return &SeqCodec{tableName: tableName, prefix: prefix}
func NewSeqCodec(messageType protoreflect.MessageType, prefix []byte) *SeqCodec {
return &SeqCodec{messageType: messageType.Descriptor().FullName(), prefix: prefix}
}
var _ EntryCodec = &SeqCodec{}
@ -33,7 +33,7 @@ func (s SeqCodec) DecodeEntry(k, v []byte) (Entry, error) {
}
return &SeqEntry{
TableName: s.tableName,
TableName: s.messageType,
Value: x,
}, nil
}
@ -44,7 +44,7 @@ func (s SeqCodec) EncodeEntry(entry Entry) (k, v []byte, err error) {
return nil, nil, ormerrors.BadDecodeEntry
}
if seqEntry.TableName != s.tableName {
if seqEntry.TableName != s.messageType {
return nil, nil, ormerrors.BadDecodeEntry
}

View File

@ -15,8 +15,9 @@ import (
func TestSeqCodec(t *testing.T) {
rapid.Check(t, func(t *rapid.T) {
prefix := rapid.SliceOfN(rapid.Byte(), 0, 5).Draw(t, "prefix").([]byte)
tableName := (&testpb.A{}).ProtoReflect().Descriptor().FullName()
cdc := ormkv.NewSeqCodec(tableName, prefix)
typ := (&testpb.ExampleTable{}).ProtoReflect().Type()
tableName := typ.Descriptor().FullName()
cdc := ormkv.NewSeqCodec(typ, prefix)
seq, err := cdc.DecodeValue(nil)
assert.NilError(t, err)

View File

@ -11,7 +11,6 @@ import (
// UniqueKeyCodec is the codec for unique indexes.
type UniqueKeyCodec struct {
tableName protoreflect.FullName
pkFieldOrder []struct {
inKey bool
i int
@ -20,10 +19,20 @@ type UniqueKeyCodec struct {
valueCodec *KeyCodec
}
var _ IndexCodec = &UniqueKeyCodec{}
// NewUniqueKeyCodec creates a new UniqueKeyCodec with an optional prefix for the
// provided message descriptor, index and primary key fields.
func NewUniqueKeyCodec(prefix []byte, messageDescriptor protoreflect.MessageDescriptor, indexFields, primaryKeyFields []protoreflect.Name) (*UniqueKeyCodec, error) {
keyCodec, err := NewKeyCodec(prefix, messageDescriptor, indexFields)
func NewUniqueKeyCodec(prefix []byte, messageType protoreflect.MessageType, indexFields, primaryKeyFields []protoreflect.Name) (*UniqueKeyCodec, error) {
if len(indexFields) == 0 {
return nil, ormerrors.InvalidTableDefinition.Wrapf("index fields are empty")
}
if len(primaryKeyFields) == 0 {
return nil, ormerrors.InvalidTableDefinition.Wrapf("primary key fields are empty")
}
keyCodec, err := NewKeyCodec(prefix, messageType, indexFields)
if err != nil {
return nil, err
}
@ -55,23 +64,20 @@ func NewUniqueKeyCodec(prefix []byte, messageDescriptor protoreflect.MessageDesc
}
}
valueCodec, err := NewKeyCodec(nil, messageDescriptor, valueFields)
valueCodec, err := NewKeyCodec(nil, messageType, valueFields)
if err != nil {
return nil, err
}
return &UniqueKeyCodec{
tableName: messageDescriptor.FullName(),
pkFieldOrder: pkFieldOrder,
keyCodec: keyCodec,
valueCodec: valueCodec,
}, nil
}
var _ IndexCodec = &UniqueKeyCodec{}
func (u UniqueKeyCodec) DecodeIndexKey(k, v []byte) (indexFields, primaryKey []protoreflect.Value, err error) {
ks, err := u.keyCodec.Decode(bytes.NewReader(k))
ks, err := u.keyCodec.DecodeKey(bytes.NewReader(k))
// got prefix key
if err == io.EOF {
@ -85,7 +91,7 @@ func (u UniqueKeyCodec) DecodeIndexKey(k, v []byte) (indexFields, primaryKey []p
return ks, nil, err
}
vs, err := u.valueCodec.Decode(bytes.NewReader(v))
vs, err := u.valueCodec.DecodeKey(bytes.NewReader(v))
if err != nil {
return nil, nil, err
}
@ -117,7 +123,7 @@ func (u UniqueKeyCodec) DecodeEntry(k, v []byte) (Entry, error) {
}
return &IndexKeyEntry{
TableName: u.tableName,
TableName: u.MessageType().Descriptor().FullName(),
Fields: u.keyCodec.fieldNames,
IsUnique: true,
IndexValues: idxVals,
@ -130,7 +136,7 @@ func (u UniqueKeyCodec) EncodeEntry(entry Entry) (k, v []byte, err error) {
if !ok {
return nil, nil, ormerrors.BadDecodeEntry
}
k, err = u.keyCodec.Encode(indexEntry.IndexValues)
k, err = u.keyCodec.EncodeKey(indexEntry.IndexValues)
if err != nil {
return nil, nil, err
}
@ -155,16 +161,44 @@ func (u UniqueKeyCodec) EncodeEntry(entry Entry) (k, v []byte, err error) {
}
}
v, err = u.valueCodec.Encode(values)
v, err = u.valueCodec.EncodeKey(values)
return k, v, err
}
func (u UniqueKeyCodec) EncodeKVFromMessage(message protoreflect.Message) (k, v []byte, err error) {
_, k, err = u.keyCodec.EncodeFromMessage(message)
_, k, err = u.keyCodec.EncodeKeyFromMessage(message)
if err != nil {
return nil, nil, err
}
_, v, err = u.valueCodec.EncodeFromMessage(message)
_, v, err = u.valueCodec.EncodeKeyFromMessage(message)
return k, v, err
}
func (u UniqueKeyCodec) GetFieldNames() []protoreflect.Name {
return u.keyCodec.GetFieldNames()
}
func (u UniqueKeyCodec) GetKeyCodec() *KeyCodec {
return u.keyCodec
}
func (u UniqueKeyCodec) GetValueCodec() *KeyCodec {
return u.valueCodec
}
func (u UniqueKeyCodec) CompareKeys(key1, key2 []protoreflect.Value) int {
return u.keyCodec.CompareKeys(key1, key2)
}
func (u UniqueKeyCodec) EncodeKeyFromMessage(message protoreflect.Message) (keyValues []protoreflect.Value, key []byte, err error) {
return u.keyCodec.EncodeKeyFromMessage(message)
}
func (u UniqueKeyCodec) IsFullyOrdered() bool {
return u.keyCodec.IsFullyOrdered()
}
func (u UniqueKeyCodec) MessageType() protoreflect.MessageType {
return u.keyCodec.messageType
}

View File

@ -17,20 +17,20 @@ func TestUniqueKeyCodec(t *testing.T) {
rapid.Check(t, func(t *rapid.T) {
keyCodec := testutil.TestKeyCodecGen(1, 5).Draw(t, "keyCodec").(testutil.TestKeyCodec)
pkCodec := testutil.TestKeyCodecGen(1, 5).Draw(t, "primaryKeyCodec").(testutil.TestKeyCodec)
desc := (&testpb.A{}).ProtoReflect().Descriptor()
messageType := (&testpb.ExampleTable{}).ProtoReflect().Type()
uniqueKeyCdc, err := ormkv.NewUniqueKeyCodec(
keyCodec.Codec.Prefix(),
desc,
messageType,
keyCodec.Codec.GetFieldNames(),
pkCodec.Codec.GetFieldNames(),
)
assert.NilError(t, err)
for i := 0; i < 100; i++ {
a := testutil.GenA.Draw(t, fmt.Sprintf("a%d", i)).(*testpb.A)
key := keyCodec.Codec.GetValues(a.ProtoReflect())
pk := pkCodec.Codec.GetValues(a.ProtoReflect())
a := testutil.GenA.Draw(t, fmt.Sprintf("a%d", i)).(*testpb.ExampleTable)
key := keyCodec.Codec.GetKeyValues(a.ProtoReflect())
pk := pkCodec.Codec.GetKeyValues(a.ProtoReflect())
uniq1 := &ormkv.IndexKeyEntry{
TableName: desc.FullName(),
TableName: messageType.Descriptor().FullName(),
Fields: keyCodec.Codec.GetFieldNames(),
IsUnique: true,
IndexValues: key,
@ -47,16 +47,16 @@ func TestUniqueKeyCodec(t *testing.T) {
entry2, err := uniqueKeyCdc.DecodeEntry(k, v)
assert.NilError(t, err)
uniq2 := entry2.(*ormkv.IndexKeyEntry)
assert.Equal(t, 0, keyCodec.Codec.CompareValues(uniq1.IndexValues, uniq2.IndexValues))
assert.Equal(t, 0, pkCodec.Codec.CompareValues(uniq1.PrimaryKey, uniq2.PrimaryKey))
assert.Equal(t, 0, keyCodec.Codec.CompareKeys(uniq1.IndexValues, uniq2.IndexValues))
assert.Equal(t, 0, pkCodec.Codec.CompareKeys(uniq1.PrimaryKey, uniq2.PrimaryKey))
assert.Equal(t, true, uniq2.IsUnique)
assert.Equal(t, desc.FullName(), uniq2.TableName)
assert.Equal(t, messageType.Descriptor().FullName(), uniq2.TableName)
assert.DeepEqual(t, uniq1.Fields, uniq2.Fields)
idxFields, pk2, err := uniqueKeyCdc.DecodeIndexKey(k, v)
assert.NilError(t, err)
assert.Equal(t, 0, keyCodec.Codec.CompareValues(key, idxFields))
assert.Equal(t, 0, pkCodec.Codec.CompareValues(pk, pk2))
assert.Equal(t, 0, keyCodec.Codec.CompareKeys(key, idxFields))
assert.Equal(t, 0, pkCodec.Codec.CompareKeys(pk, pk2))
}
})
}

View File

@ -1,17 +0,0 @@
package ormkv
import (
"bytes"
"io"
)
func skipPrefix(r *bytes.Reader, prefix []byte) error {
n := len(prefix)
if n > 0 {
// we skip checking the prefix for performance reasons because we assume
// that it was checked by the caller
_, err := r.Seek(int64(n), io.SeekCurrent)
return err
}
return nil
}

View File

@ -4,18 +4,37 @@ go 1.17
require (
github.com/cosmos/cosmos-proto v1.0.0-alpha6
github.com/cosmos/cosmos-sdk/api v0.1.0-alpha1
github.com/cosmos/cosmos-sdk/api v0.1.0-alpha2
github.com/cosmos/cosmos-sdk/errors v1.0.0-beta.2
github.com/tendermint/tm-db v0.6.6
google.golang.org/protobuf v1.27.1
gotest.tools/v3 v3.1.0
pgregory.net/rapid v0.4.7
)
require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/DataDog/zstd v1.4.5 // indirect
github.com/cespare/xxhash v1.1.0 // indirect
github.com/dgraph-io/badger/v2 v2.2007.2 // indirect
github.com/dgraph-io/ristretto v0.0.3 // indirect
github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13 // indirect
github.com/dustin/go-humanize v1.0.0 // indirect
github.com/facebookgo/ensure v0.0.0-20160127193407-b4ab57deab51 // indirect
github.com/facebookgo/stack v0.0.0-20160209184415-751773369052 // indirect
github.com/facebookgo/subset v0.0.0-20150612182917-8dac2c3c4870 // indirect
github.com/golang/protobuf v1.5.2 // indirect
github.com/golang/snappy v0.0.3-0.20201103224600-674baa8c7fc3 // indirect
github.com/google/btree v1.0.0 // indirect
github.com/google/go-cmp v0.5.5 // indirect
github.com/jmhodges/levigo v1.0.0 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/syndtr/goleveldb v1.0.1-0.20200815110645-5c35d600f0ca // indirect
github.com/tecbot/gorocksdb v0.0.0-20191217155057-f0fad39f321c // indirect
go.etcd.io/bbolt v1.3.6 // indirect
golang.org/x/net v0.0.0-20210903162142-ad29c8ab022f // indirect
golang.org/x/sys v0.0.0-20210903071746-97244b99971b // indirect
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
)
replace github.com/gogo/protobuf => github.com/regen-network/protobuf v1.3.3-alpha.regen.1

View File

@ -1,56 +1,283 @@
github.com/cosmos/cosmos-proto v1.0.0-alpha1/go.mod h1:msdDWOvfStHLG+Z2y2SJ0dcqimZ2vc8M1MPnZ4jOF7U=
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/DataDog/zstd v1.4.1/go.mod h1:1jcaCB/ufaK+sKp1NBhlGmpz41jOoPQ35bpF36t7BBo=
github.com/DataDog/zstd v1.4.5 h1:EndNeuB0l9syBZhut0wns3gV1hL8zX8LIu6ZiVHWLIQ=
github.com/DataDog/zstd v1.4.5/go.mod h1:1jcaCB/ufaK+sKp1NBhlGmpz41jOoPQ35bpF36t7BBo=
github.com/OneOfOne/xxhash v1.2.2 h1:KMrpdQIwFcEqXDklaen+P1axHaj9BSKzvpUUfnHldSE=
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko=
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI=
github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk=
github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
github.com/cosmos/cosmos-proto v1.0.0-alpha4/go.mod h1:msdDWOvfStHLG+Z2y2SJ0dcqimZ2vc8M1MPnZ4jOF7U=
github.com/cosmos/cosmos-proto v1.0.0-alpha6 h1:N2BvV2AyzGAXCJnvlw1pMzEQ+76tj5FDBrkYQYIDCdU=
github.com/cosmos/cosmos-proto v1.0.0-alpha6/go.mod h1:msdDWOvfStHLG+Z2y2SJ0dcqimZ2vc8M1MPnZ4jOF7U=
github.com/cosmos/cosmos-sdk/api v0.1.0-alpha1 h1:inilHdPSVUHHp8R+uW2MNPXkC3AFmTySE1kHWwc8XjM=
github.com/cosmos/cosmos-sdk/api v0.1.0-alpha1/go.mod h1:cJoD3zcka5CZsRloy0auZiVDQtlXAfZSUxLyJMVJ/uk=
github.com/cosmos/cosmos-sdk/api v0.1.0-alpha2 h1:47aK2mZ8oh3wyr5Q4OiZxyrMkQZRW67Ah/HfC8dW8hs=
github.com/cosmos/cosmos-sdk/api v0.1.0-alpha2/go.mod h1:xWm3hne2f6upv80eIS+fJnnUaed/R2EJno1It4Zb9aw=
github.com/cosmos/cosmos-sdk/errors v1.0.0-beta.2 h1:bBglNlra8ZHb4dmbEE8V85ihLA+DkriSm7tcx6x/JWo=
github.com/cosmos/cosmos-sdk/errors v1.0.0-beta.2/go.mod h1:Gi7pzVRnvZ1N16JAXpLADzng0ePoE7YeEHaULSFB2Ts=
github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/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/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/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=
github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw=
github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo=
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ=
github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/facebookgo/ensure v0.0.0-20160127193407-b4ab57deab51 h1:0JZ+dUmQeA8IIVUMzysrX4/AKuQwWhV2dYQuPZdvdSQ=
github.com/facebookgo/ensure v0.0.0-20160127193407-b4ab57deab51/go.mod h1:Yg+htXGokKKdzcwhuNDwVvN+uBxDGXJ7G/VN1d8fa64=
github.com/facebookgo/stack v0.0.0-20160209184415-751773369052 h1:JWuenKqqX8nojtoVVWjGfOF9635RETekkoH6Cc9SX0A=
github.com/facebookgo/stack v0.0.0-20160209184415-751773369052/go.mod h1:UbMTZqLaRiH3MsBH8va0n7s1pQYcu3uTb8G4tygF4Zg=
github.com/facebookgo/subset v0.0.0-20150612182917-8dac2c3c4870 h1:E2s37DuLxFhQDg5gKsWoLBOB0n+ZW8s599zru8FJ2/Y=
github.com/facebookgo/subset v0.0.0-20150612182917-8dac2c3c4870/go.mod h1:5tD+neXqOorC30/tWg0LCSkrqj/AR6gu8yY8/fpw1q0=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/golang/snappy v0.0.3-0.20201103224600-674baa8c7fc3 h1:ur2rms48b3Ep1dxh7aUV2FZEQ8jEVO2F6ILKx8ofkAg=
github.com/golang/snappy v0.0.3-0.20201103224600-674baa8c7fc3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/google/btree v1.0.0 h1:0udJVsspx3VBr5FwtLhQQtuAsVc79tTq0ocGIPAU6qo=
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/jmhodges/levigo v1.0.0 h1:q5EC36kV79HWeTBWsod3mG11EgStG3qArTKcvlksN1U=
github.com/jmhodges/levigo v1.0.0/go.mod h1:Q6Qx+uH3RAqyK4rFQroq9RL7mdkABMcfhEI+nNuzMJQ=
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78=
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
github.com/onsi/ginkgo v1.14.0 h1:2mOpI4JVVPBN+WQRa0WKH2eXR+Ey+uK4n7Zj0aYpIQA=
github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY=
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
github.com/onsi/gomega v1.10.1 h1:o0+MgICZLuZ7xjH7Vx6zS/zcu93/BEp1VwkIW1mEXCE=
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/regen-network/protobuf v1.3.3-alpha.regen.1/go.mod h1:2DjTFR1HhMQhiWC5sZ4OhQ3+NtdbZ6oBDKQwq5Ou+FI=
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI=
github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU=
github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/syndtr/goleveldb v1.0.1-0.20200815110645-5c35d600f0ca h1:Ld/zXl5t4+D69SiV4JoN7kkfvJdOWlPpfxrzxpLMoUk=
github.com/syndtr/goleveldb v1.0.1-0.20200815110645-5c35d600f0ca/go.mod h1:u2MKkTVTVJWe5D1rCvame8WqhBd88EuIwODJZ1VHCPM=
github.com/tecbot/gorocksdb v0.0.0-20191217155057-f0fad39f321c h1:g+WoO5jjkqGAzHWCjJB1zZfXPIAaDpzXIEJ0eS6B5Ok=
github.com/tecbot/gorocksdb v0.0.0-20191217155057-f0fad39f321c/go.mod h1:ahpPrc7HpcfEWDQRZEmnXMzHY03mLDYMCxeDzy46i+8=
github.com/tendermint/tm-db v0.6.6 h1:EzhaOfR0bdKyATqcd5PNeyeq8r+V4bRPHBfyFdD9kGM=
github.com/tendermint/tm-db v0.6.6/go.mod h1:wP8d49A85B7/erz/r4YbKssKw6ylsO/hKtFk7E1aWZI=
github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
go.etcd.io/bbolt v1.3.6 h1:/ecaJf0sk1l4l6V4awd65v2C3ILy7MSj+s/x1ADCIMU=
go.etcd.io/bbolt v1.3.6/go.mod h1:qXsaaIqmgQH0T+OPdb99Bf+PKfBBQVAdyD6TY9G8XM4=
go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI=
golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
golang.org/x/net v0.0.0-20210903162142-ad29c8ab022f h1:w6wWR0H+nyVpbSAQbzVEIACVyr/h8l/BEkY6Sokc7Eg=
golang.org/x/net v0.0.0-20210903162142-ad29c8ab022f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200814200057-3d37ad5750ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200923182605-d9f96fdee20d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210903071746-97244b99971b h1:3Dq0eVHn0uaQJmPO+/aYPI/fRMqdrVDbu7MQcku54gg=
golang.org/x/sys v0.0.0-20210903071746-97244b99971b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20200324203455-a04cca1dde73/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
google.golang.org/genproto v0.0.0-20211223182754-3ac035c7e7cb/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60=
google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0=
google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34=
google.golang.org/grpc v1.42.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU=
google.golang.org/grpc v1.43.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.27.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.27.1 h1:SnqbnDw1V7RiZcXPx5MEeqPv2s79L9i7BJUlG/+RurQ=
google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4/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=
gotest.tools/v3 v3.1.0 h1:rVV8Tcg/8jHUkPUorwjaMTtemIMVXfIPKiOqnhEhakk=
gotest.tools/v3 v3.1.0/go.mod h1:fHy7eyTmJFO5bQbUsEGQ1v4m2J3Jz9eWL54TP2/ZuYQ=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
pgregory.net/rapid v0.4.7 h1:MTNRktPuv5FNqOO151TM9mDTa+XHcX6ypYeISDVD14g=
pgregory.net/rapid v0.4.7/go.mod h1:UYpPVyjFHzYBGHIxLFoupi8vwk6rXNzRY9OMvVxFIOU=

View File

@ -0,0 +1,40 @@
package listinternal
import (
"fmt"
"google.golang.org/protobuf/reflect/protoreflect"
)
// Options is the internal list options struct.
type Options struct {
Start, End, Prefix []protoreflect.Value
Reverse bool
Cursor []byte
}
func (o Options) Validate() error {
if o.Start != nil || o.End != nil {
if o.Prefix != nil {
return fmt.Errorf("can either use Start/End or Prefix, not both")
}
}
return nil
}
type Option interface {
apply(*Options)
}
type FuncOption func(*Options)
func (f FuncOption) apply(options *Options) {
f(options)
}
func ApplyOptions(opts *Options, funcOpts []Option) {
for _, opt := range funcOpts {
opt.apply(opts)
}
}

View File

@ -0,0 +1,247 @@
package testkv
import (
"fmt"
"google.golang.org/protobuf/proto"
"github.com/cosmos/cosmos-sdk/orm/encoding/ormkv"
"github.com/cosmos/cosmos-sdk/orm/model/kv"
"github.com/cosmos/cosmos-sdk/orm/model/ormtable"
)
// Debugger is an interface that handles debug info from the debug store wrapper.
type Debugger interface {
// Log logs a single log message.
Log(string)
// Decode decodes a key-value entry into a debug string.
Decode(key, value []byte) string
}
// NewDebugBackend wraps both stores from a Backend with a debugger.
func NewDebugBackend(backend ormtable.Backend, debugger Debugger) ormtable.Backend {
return ormtable.NewBackend(ormtable.BackendOptions{
CommitmentStore: NewDebugStore(backend.CommitmentStore(), debugger, "commit"),
IndexStore: NewDebugStore(backend.IndexStore(), debugger, "index"),
Hooks: debugHooks{debugger: debugger, hooks: backend.Hooks()},
})
}
type debugStore struct {
store kv.Store
debugger Debugger
storeName string
}
// NewDebugStore wraps the store with the debugger instance returning a debug store wrapper.
func NewDebugStore(store kv.Store, debugger Debugger, storeName string) kv.Store {
return &debugStore{store: store, debugger: debugger, storeName: storeName}
}
func (t debugStore) Get(key []byte) ([]byte, error) {
val, err := t.store.Get(key)
if err != nil {
if t.debugger != nil {
t.debugger.Log(fmt.Sprintf("ERR on GET %s: %v", t.debugger.Decode(key, nil), err))
}
return nil, err
}
if t.debugger != nil {
t.debugger.Log(fmt.Sprintf("GET %x %x", key, val))
t.debugger.Log(fmt.Sprintf(" %s", t.debugger.Decode(key, val)))
}
return val, nil
}
func (t debugStore) Has(key []byte) (bool, error) {
has, err := t.store.Has(key)
if err != nil {
if t.debugger != nil {
t.debugger.Log(fmt.Sprintf("ERR on HAS %s: %v", t.debugger.Decode(key, nil), err))
}
return has, err
}
if t.debugger != nil {
t.debugger.Log(fmt.Sprintf("HAS %x", key))
t.debugger.Log(fmt.Sprintf(" %s", t.debugger.Decode(key, nil)))
}
return has, nil
}
func (t debugStore) Iterator(start, end []byte) (kv.Iterator, error) {
if t.debugger != nil {
t.debugger.Log(fmt.Sprintf("ITERATOR %x -> %x", start, end))
}
it, err := t.store.Iterator(start, end)
if err != nil {
return nil, err
}
return &debugIterator{
iterator: it,
storeName: t.storeName,
debugger: t.debugger,
}, nil
}
func (t debugStore) ReverseIterator(start, end []byte) (kv.Iterator, error) {
if t.debugger != nil {
t.debugger.Log(fmt.Sprintf("ITERATOR %x <- %x", start, end))
}
it, err := t.store.ReverseIterator(start, end)
if err != nil {
return nil, err
}
return &debugIterator{
iterator: it,
storeName: t.storeName,
debugger: t.debugger,
}, nil
}
func (t debugStore) Set(key, value []byte) error {
if t.debugger != nil {
t.debugger.Log(fmt.Sprintf("SET %x %x", key, value))
t.debugger.Log(fmt.Sprintf(" %s", t.debugger.Decode(key, value)))
}
err := t.store.Set(key, value)
if err != nil {
if t.debugger != nil {
t.debugger.Log(fmt.Sprintf("ERR on SET %s: %v", t.debugger.Decode(key, value), err))
}
return err
}
return nil
}
func (t debugStore) Delete(key []byte) error {
if t.debugger != nil {
t.debugger.Log(fmt.Sprintf("DEL %x", key))
t.debugger.Log(fmt.Sprintf("DEL %s", t.debugger.Decode(key, nil)))
}
err := t.store.Delete(key)
if err != nil {
if t.debugger != nil {
t.debugger.Log(fmt.Sprintf("ERR on SET %s: %v", t.debugger.Decode(key, nil), err))
}
return err
}
return nil
}
var _ kv.Store = &debugStore{}
type debugIterator struct {
iterator kv.Iterator
storeName string
debugger Debugger
}
func (d debugIterator) Domain() (start []byte, end []byte) {
start, end = d.iterator.Domain()
d.debugger.Log(fmt.Sprintf(" DOMAIN %x -> %x", start, end))
return start, end
}
func (d debugIterator) Valid() bool {
valid := d.iterator.Valid()
d.debugger.Log(fmt.Sprintf(" VALID %t", valid))
return valid
}
func (d debugIterator) Next() {
d.debugger.Log(" NEXT")
d.iterator.Next()
}
func (d debugIterator) Key() (key []byte) {
key = d.iterator.Key()
value := d.iterator.Value()
d.debugger.Log(fmt.Sprintf(" KEY %x %x", key, value))
d.debugger.Log(fmt.Sprintf(" %s", d.debugger.Decode(key, value)))
return key
}
func (d debugIterator) Value() (value []byte) {
return d.iterator.Value()
}
func (d debugIterator) Error() error {
err := d.iterator.Error()
d.debugger.Log(fmt.Sprintf(" ERR %+v", err))
return err
}
func (d debugIterator) Close() error {
d.debugger.Log(" CLOSE")
return d.iterator.Close()
}
var _ kv.Iterator = &debugIterator{}
// EntryCodecDebugger is a Debugger instance that uses an EntryCodec and Print
// function for debugging.
type EntryCodecDebugger struct {
EntryCodec ormkv.EntryCodec
Print func(string)
}
func (d *EntryCodecDebugger) Log(s string) {
if d.Print != nil {
d.Print(s)
} else {
fmt.Println(s)
}
}
func (d *EntryCodecDebugger) Decode(key, value []byte) string {
entry, err := d.EntryCodec.DecodeEntry(key, value)
if err != nil {
return fmt.Sprintf("ERR:%v", err)
}
return entry.String()
}
type debugHooks struct {
debugger Debugger
hooks ormtable.Hooks
}
func (d debugHooks) OnInsert(message proto.Message) error {
d.debugger.Log(fmt.Sprintf(
"ORM INSERT %s %s",
message.ProtoReflect().Descriptor().FullName(),
message,
))
if d.hooks != nil {
return d.hooks.OnInsert(message)
}
return nil
}
func (d debugHooks) OnUpdate(existing, new proto.Message) error {
d.debugger.Log(fmt.Sprintf(
"ORM UPDATE %s %s -> %s",
existing.ProtoReflect().Descriptor().FullName(),
existing,
new,
))
if d.hooks != nil {
return d.hooks.OnUpdate(existing, new)
}
return nil
}
func (d debugHooks) OnDelete(message proto.Message) error {
d.debugger.Log(fmt.Sprintf(
"ORM DELETE %s %s",
message.ProtoReflect().Descriptor().FullName(),
message,
))
if d.hooks != nil {
return d.hooks.OnDelete(message)
}
return nil
}

View File

@ -0,0 +1,27 @@
package testkv
import (
dbm "github.com/tendermint/tm-db"
"github.com/cosmos/cosmos-sdk/orm/model/ormtable"
)
// NewSplitMemBackend returns a Backend instance
// which uses two separate memory stores to simulate behavior when there
// are really two separate backing stores.
func NewSplitMemBackend() ormtable.Backend {
return ormtable.NewBackend(ormtable.BackendOptions{
CommitmentStore: dbm.NewMemDB(),
IndexStore: dbm.NewMemDB(),
})
}
// NewSharedMemBackend returns a Backend instance
// which uses a single backing memory store to simulate legacy scenarios
// where only a single KV-store is available to modules.
func NewSharedMemBackend() ormtable.Backend {
return ormtable.NewBackend(ormtable.BackendOptions{
CommitmentStore: dbm.NewMemDB(),
// commit store is automatically used as the index store
})
}

View File

@ -8,15 +8,16 @@ import "cosmos/orm/v1alpha1/orm.proto";
option go_package = "github.com/cosmos/cosmos-sdk/orm/internal/testpb";
message A {
message ExampleTable {
option (cosmos.orm.v1alpha1.table) = {
id: 1;
primary_key: {
fields: "u32,u64,str"
fields: "u32,i64,str"
}
index:{
id: 1;
fields:"u64,str"
unique: true
}
index:{
id: 2;
@ -49,10 +50,15 @@ message A {
// Invalid key fields:
repeated uint32 repeated = 17;
map<string, uint32> map = 18;
B msg = 19;
ExampleMessage msg = 19;
oneof sum {
uint32 oneof = 20;
}
message ExampleMessage {
string foo = 1;
int32 bar = 2;
}
}
enum Enum {
@ -63,20 +69,28 @@ enum Enum {
ENUM_NEG_THREE = -3;
}
message B {
option (cosmos.orm.v1alpha1.singleton) = {id: 2};
string x = 1;
}
message C {
message ExampleAutoIncrementTable {
option (cosmos.orm.v1alpha1.table) = {
id: 3
primary_key:{
fields:"id"
auto_increment: true
}
index:{
id: 1
fields:"x"
unique: true
}
};
uint64 id = 1;
string x = 2;
int32 y = 3;
}
message ExampleSingleton {
option (cosmos.orm.v1alpha1.singleton) = {id: 2};
string foo = 1;
int32 bar = 2;
}

File diff suppressed because it is too large Load Diff

View File

@ -4,8 +4,9 @@ import (
"fmt"
"strings"
"google.golang.org/protobuf/reflect/protoreflect"
"google.golang.org/protobuf/types/known/durationpb"
"google.golang.org/protobuf/reflect/protoreflect"
"google.golang.org/protobuf/types/known/timestamppb"
"pgregory.net/rapid"
@ -14,7 +15,7 @@ import (
"github.com/cosmos/cosmos-sdk/orm/internal/testpb"
)
// TestFieldSpec defines a test field against the testpb.A message.
// TestFieldSpec defines a test field against the testpb.ExampleTable message.
type TestFieldSpec struct {
FieldName protoreflect.Name
Gen *rapid.Generator
@ -78,19 +79,23 @@ var TestFieldSpecs = []TestFieldSpec{
},
{
"ts",
rapid.ArrayOf(2, rapid.Int64()).Map(func(xs [2]int64) protoreflect.Message {
rapid.Custom(func(t *rapid.T) protoreflect.Message {
seconds := rapid.Int64Range(-9999999999, 9999999999).Draw(t, "seconds").(int64)
nanos := rapid.Int32Range(0, 999999999).Draw(t, "nanos").(int32)
return (&timestamppb.Timestamp{
Seconds: xs[0],
Nanos: int32(xs[1]),
Seconds: seconds,
Nanos: nanos,
}).ProtoReflect()
}),
},
{
"dur",
rapid.ArrayOf(2, rapid.Int64()).Map(func(xs [2]int64) protoreflect.Message {
rapid.Custom(func(t *rapid.T) protoreflect.Message {
seconds := rapid.Int64Range(0, 315576000000).Draw(t, "seconds").(int64)
nanos := rapid.Int32Range(0, 999999999).Draw(t, "nanos").(int32)
return (&durationpb.Duration{
Seconds: xs[0],
Nanos: int32(xs[1]),
Seconds: seconds,
Nanos: nanos,
}).ProtoReflect()
}),
},
@ -111,7 +116,7 @@ func MakeTestCodec(fname protoreflect.Name, nonTerminal bool) (ormfield.Codec, e
}
func GetTestField(fname protoreflect.Name) protoreflect.FieldDescriptor {
a := &testpb.A{}
a := &testpb.ExampleTable{}
return a.ProtoReflect().Descriptor().Fields().ByName(fname)
}
@ -147,8 +152,8 @@ func TestKeyCodecGen(minLen, maxLen int) *rapid.Generator {
prefix := rapid.SliceOfN(rapid.Byte(), 0, 5).Draw(t, "prefix").([]byte)
desc := (&testpb.A{}).ProtoReflect().Descriptor()
cdc, err := ormkv.NewKeyCodec(prefix, desc, fields)
msgType := (&testpb.ExampleTable{}).ProtoReflect().Type()
cdc, err := ormkv.NewKeyCodec(prefix, msgType, fields)
if err != nil {
panic(err)
}
@ -169,8 +174,8 @@ func (k TestKeyCodec) Draw(t *rapid.T, id string) []protoreflect.Value {
return keyValues
}
var GenA = rapid.Custom(func(t *rapid.T) *testpb.A {
a := &testpb.A{}
var GenA = rapid.Custom(func(t *rapid.T) *testpb.ExampleTable {
a := &testpb.ExampleTable{}
ref := a.ProtoReflect()
for _, spec := range TestFieldSpecs {
field := GetTestField(spec.FieldName)
@ -179,12 +184,3 @@ var GenA = rapid.Custom(func(t *rapid.T) *testpb.A {
}
return a
})
func ValuesOf(values ...interface{}) []protoreflect.Value {
n := len(values)
res := make([]protoreflect.Value, n)
for i := 0; i < n; i++ {
res[i] = protoreflect.ValueOf(values[i])
}
return res
}

3
orm/model/doc.go Normal file
View File

@ -0,0 +1,3 @@
// Package model contains packages which define ORM data "model" types
// such as tables, indexes, and schemas.
package model

50
orm/model/kv/store.go Normal file
View File

@ -0,0 +1,50 @@
// Package kvstore defines the abstract interfaces which ORM tables and indexes
// use for reading and writing data against a KV-store backend.
package kv
import (
dbm "github.com/tendermint/tm-db"
)
// ReadonlyStore is an interface for readonly access to a kv-store.
type ReadonlyStore interface {
// Get fetches the value of the given key, or nil if it does not exist.
// CONTRACT: key, value readonly []byte
Get(key []byte) ([]byte, error)
// Has checks if a key exists.
// CONTRACT: key, value readonly []byte
Has(key []byte) (bool, error)
// Iterator returns an iterator over a domain of keys, in ascending order. The caller must call
// Close when done. End is exclusive, and start must be less than end. A nil start iterates
// from the first key, and a nil end iterates to the last key (inclusive). Empty keys are not
// valid.
// CONTRACT: No writes may happen within a domain while an iterator exists over it.
// CONTRACT: start, end readonly []byte
Iterator(start, end []byte) (Iterator, error)
// ReverseIterator returns an iterator over a domain of keys, in descending order. The caller
// must call Close when done. End is exclusive, and start must be less than end. A nil end
// iterates from the last key (inclusive), and a nil start iterates to the first key (inclusive).
// Empty keys are not valid.
// CONTRACT: No writes may happen within a domain while an iterator exists over it.
// CONTRACT: start, end readonly []byte
ReverseIterator(start, end []byte) (Iterator, error)
}
// Iterator aliases github.com/tendermint/tm-db.Iterator.
type Iterator = dbm.Iterator
// Store is an interface for writing to a kv-store.
type Store interface {
ReadonlyStore
// Set sets the value for the given key, replacing it if it already exists.
// CONTRACT: key, value readonly []byte
Set(key, value []byte) error
// Delete deletes the key, or does nothing if the key does not exist.
// CONTRACT: key readonly []byte
Delete(key []byte) error
}

View File

@ -0,0 +1,77 @@
// Package ormlist defines options for listing items from ORM indexes.
package ormlist
import (
"github.com/cosmos/cosmos-sdk/orm/encoding/encodeutil"
"github.com/cosmos/cosmos-sdk/orm/internal/listinternal"
)
// Option represents a list option.
type Option = listinternal.Option
// Start defines the values to use to start range iteration. It cannot be
// combined with Prefix.
//
// Values must correspond in type to the index's fields and the number of values
// provided cannot exceed the number of fields in the index, although fewer
// values can be provided.
//
// Range iteration can only be done for start and end values which are
// well-ordered, meaning that any unordered components must be equal. Ex.
// the bytes type is considered unordered, so a range iterator is created
// over an index with a bytes field, both start and end must have the same
// value for bytes.
func Start(values ...interface{}) Option {
return listinternal.FuncOption(func(options *listinternal.Options) {
options.Start = encodeutil.ValuesOf(values...)
})
}
// End defines the values to use to end range iteration. It cannot be
// combined with Prefix.
//
// Values must correspond in type to the index's fields and the number of values
// provided cannot exceed the number of fields in the index, although fewer
// values can be provided.
//
// Range iteration can only be done for start and end values which are
// well-ordered, meaning that any unordered components must be equal. Ex.
// the bytes type is considered unordered, so a range iterator is created
// over an index with a bytes field, both start and end must have the same
// value for bytes.
func End(values ...interface{}) Option {
return listinternal.FuncOption(func(options *listinternal.Options) {
options.End = encodeutil.ValuesOf(values...)
})
}
// Prefix defines values to use for prefix iteration. It cannot be used
// together with Start or End.
//
// Values must correspond in type to the index's fields and the number of values
// provided cannot exceed the number of fields in the index, although fewer
// values can be provided.
func Prefix(values ...interface{}) Option {
return listinternal.FuncOption(func(options *listinternal.Options) {
options.Prefix = encodeutil.ValuesOf(values...)
})
}
// Reverse reverses the direction of iteration. If Reverse is
// provided twice, iteration will happen in the forward direction.
func Reverse() Option {
return listinternal.FuncOption(func(options *listinternal.Options) {
options.Reverse = !options.Reverse
})
}
// Cursor specifies a cursor after which to restart iteration. Cursor values
// are returned by iterators and in pagination results.
func Cursor(cursor CursorT) Option {
return listinternal.FuncOption(func(options *listinternal.Options) {
options.Cursor = cursor
})
}
// CursorT defines a cursor type.
type CursorT []byte

View File

@ -0,0 +1,218 @@
package ormtable
import (
"context"
"encoding/json"
"fmt"
"io"
"google.golang.org/protobuf/proto"
"google.golang.org/protobuf/reflect/protoreflect"
"github.com/cosmos/cosmos-sdk/orm/encoding/ormkv"
"github.com/cosmos/cosmos-sdk/orm/model/kv"
"github.com/cosmos/cosmos-sdk/orm/types/ormerrors"
)
// autoIncrementTable is a Table implementation for tables with an
// auto-incrementing uint64 primary key.
type autoIncrementTable struct {
*tableImpl
autoIncField protoreflect.FieldDescriptor
seqCodec *ormkv.SeqCodec
}
func (t autoIncrementTable) Save(ctx context.Context, message proto.Message) error {
backend, err := t.getBackend(ctx)
if err != nil {
return err
}
return t.save(backend, message, saveModeDefault)
}
func (t autoIncrementTable) Insert(ctx context.Context, message proto.Message) error {
backend, err := t.getBackend(ctx)
if err != nil {
return err
}
return t.save(backend, message, saveModeInsert)
}
func (t autoIncrementTable) Update(ctx context.Context, message proto.Message) error {
backend, err := t.getBackend(ctx)
if err != nil {
return err
}
return t.save(backend, message, saveModeUpdate)
}
func (t *autoIncrementTable) save(backend Backend, message proto.Message, mode saveMode) error {
messageRef := message.ProtoReflect()
val := messageRef.Get(t.autoIncField).Uint()
writer := newBatchIndexCommitmentWriter(backend)
defer writer.Close()
if val == 0 {
if mode == saveModeUpdate {
return ormerrors.PrimaryKeyInvalidOnUpdate
}
mode = saveModeInsert
key, err := t.nextSeqValue(writer.IndexStore())
if err != nil {
return err
}
messageRef.Set(t.autoIncField, protoreflect.ValueOfUint64(key))
} else {
if mode == saveModeInsert {
return ormerrors.AutoIncrementKeyAlreadySet
}
mode = saveModeUpdate
}
return t.tableImpl.doSave(writer, message, mode)
}
func (t *autoIncrementTable) curSeqValue(kv kv.ReadonlyStore) (uint64, error) {
bz, err := kv.Get(t.seqCodec.Prefix())
if err != nil {
return 0, err
}
return t.seqCodec.DecodeValue(bz)
}
func (t *autoIncrementTable) nextSeqValue(kv kv.Store) (uint64, error) {
seq, err := t.curSeqValue(kv)
if err != nil {
return 0, err
}
seq++
return seq, t.setSeqValue(kv, seq)
}
func (t *autoIncrementTable) setSeqValue(kv kv.Store, seq uint64) error {
return kv.Set(t.seqCodec.Prefix(), t.seqCodec.EncodeValue(seq))
}
func (t autoIncrementTable) EncodeEntry(entry ormkv.Entry) (k, v []byte, err error) {
if _, ok := entry.(*ormkv.SeqEntry); ok {
return t.seqCodec.EncodeEntry(entry)
}
return t.tableImpl.EncodeEntry(entry)
}
func (t autoIncrementTable) ValidateJSON(reader io.Reader) error {
return t.decodeAutoIncJson(nil, reader, func(message proto.Message, maxID uint64) error {
messageRef := message.ProtoReflect()
id := messageRef.Get(t.autoIncField).Uint()
if id > maxID {
return fmt.Errorf("invalid ID %d, expected a value <= %d", id, maxID)
}
if t.customJSONValidator != nil {
return t.customJSONValidator(message)
} else {
return DefaultJSONValidator(message)
}
})
}
func (t autoIncrementTable) ImportJSON(ctx context.Context, reader io.Reader) error {
backend, err := t.getBackend(ctx)
if err != nil {
return err
}
return t.decodeAutoIncJson(backend, reader, func(message proto.Message, maxID uint64) error {
messageRef := message.ProtoReflect()
id := messageRef.Get(t.autoIncField).Uint()
if id == 0 {
// we don't have an ID in the JSON, so we call Save to insert and
// generate one
return t.save(backend, message, saveModeInsert)
} else {
if id > maxID {
return fmt.Errorf("invalid ID %d, expected a value <= %d", id, maxID)
}
// we do have an ID and calling Save will fail because it expects
// either no ID or SAVE_MODE_UPDATE. So instead we drop one level
// down and insert using tableImpl which doesn't know about
// auto-incrementing IDs
return t.tableImpl.save(backend, message, saveModeInsert)
}
})
}
func (t autoIncrementTable) decodeAutoIncJson(backend Backend, reader io.Reader, onMsg func(message proto.Message, maxID uint64) error) error {
decoder, err := t.startDecodeJson(reader)
if err != nil {
return err
}
var seq uint64
return t.doDecodeJson(decoder,
func(message json.RawMessage) bool {
err = json.Unmarshal(message, &seq)
if err == nil {
// writer is nil during validation
if backend != nil {
writer := newBatchIndexCommitmentWriter(backend)
defer writer.Close()
err = t.setSeqValue(writer.IndexStore(), seq)
if err != nil {
panic(err)
}
err = writer.Write()
if err != nil {
panic(err)
}
}
return true
}
return false
},
func(message proto.Message) error {
return onMsg(message, seq)
})
}
func (t autoIncrementTable) ExportJSON(ctx context.Context, writer io.Writer) error {
backend, err := t.getBackend(ctx)
if err != nil {
return err
}
_, err = writer.Write([]byte("["))
if err != nil {
return err
}
seq, err := t.curSeqValue(backend.IndexStoreReader())
if err != nil {
return err
}
bz, err := json.Marshal(seq)
if err != nil {
return err
}
_, err = writer.Write(bz)
if err != nil {
return err
}
_, err = writer.Write([]byte(",\n"))
if err != nil {
return err
}
return t.doExportJSON(ctx, writer)
}

View File

@ -0,0 +1,74 @@
package ormtable_test
import (
"bytes"
"context"
"os"
"strings"
"testing"
"gotest.tools/v3/assert"
"gotest.tools/v3/golden"
"github.com/cosmos/cosmos-sdk/orm/internal/testkv"
"github.com/cosmos/cosmos-sdk/orm/internal/testpb"
"github.com/cosmos/cosmos-sdk/orm/model/ormtable"
)
func TestAutoIncrementScenario(t *testing.T) {
table, err := ormtable.Build(ormtable.Options{
MessageType: (&testpb.ExampleAutoIncrementTable{}).ProtoReflect().Type(),
})
assert.NilError(t, err)
// first run tests with a split index-commitment store
runAutoIncrementScenario(t, table, ormtable.WrapContextDefault(testkv.NewSplitMemBackend()))
// now run with shared store and debugging
debugBuf := &strings.Builder{}
store := testkv.NewDebugBackend(
testkv.NewSharedMemBackend(),
&testkv.EntryCodecDebugger{
EntryCodec: table,
Print: func(s string) { debugBuf.WriteString(s + "\n") },
},
)
runAutoIncrementScenario(t, table, ormtable.WrapContextDefault(store))
golden.Assert(t, debugBuf.String(), "test_auto_inc.golden")
checkEncodeDecodeEntries(t, table, store.IndexStoreReader())
}
func runAutoIncrementScenario(t *testing.T, table ormtable.Table, context context.Context) {
err := table.Save(context, &testpb.ExampleAutoIncrementTable{Id: 5})
assert.ErrorContains(t, err, "update")
ex1 := &testpb.ExampleAutoIncrementTable{X: "foo", Y: 5}
assert.NilError(t, table.Save(context, ex1))
assert.Equal(t, uint64(1), ex1.Id)
buf := &bytes.Buffer{}
assert.NilError(t, table.ExportJSON(context, buf))
golden.Assert(t, string(buf.Bytes()), "auto_inc_json.golden")
assert.NilError(t, table.ValidateJSON(bytes.NewReader(buf.Bytes())))
store2 := ormtable.WrapContextDefault(testkv.NewSplitMemBackend())
assert.NilError(t, table.ImportJSON(store2, bytes.NewReader(buf.Bytes())))
assertTablesEqual(t, table, context, store2)
}
func TestBadJSON(t *testing.T) {
table, err := ormtable.Build(ormtable.Options{
MessageType: (&testpb.ExampleAutoIncrementTable{}).ProtoReflect().Type(),
})
assert.NilError(t, err)
store := ormtable.WrapContextDefault(testkv.NewSplitMemBackend())
f, err := os.Open("testdata/bad_auto_inc.json")
assert.NilError(t, err)
assert.ErrorContains(t, table.ImportJSON(store, f), "invalid ID")
f, err = os.Open("testdata/bad_auto_inc2.json")
assert.NilError(t, err)
assert.ErrorContains(t, table.ImportJSON(store, f), "invalid ID")
}

View File

@ -0,0 +1,156 @@
package ormtable
import (
"context"
"github.com/cosmos/cosmos-sdk/orm/model/kv"
)
// ReadBackend defines the type used for read-only ORM operations.
type ReadBackend interface {
// CommitmentStoreReader returns the reader for the commitment store.
CommitmentStoreReader() kv.ReadonlyStore
// IndexStoreReader returns the reader for the index store.
IndexStoreReader() kv.ReadonlyStore
private()
}
// Backend defines the type used for read-write ORM operations.
// Unlike ReadBackend, write access to the underlying kv-store
// is hidden so that this can be fully encapsulated by the ORM.
type Backend interface {
ReadBackend
// CommitmentStore returns the merklized commitment store.
CommitmentStore() kv.Store
// IndexStore returns the index store if a separate one exists,
// otherwise it the commitment store.
IndexStore() kv.Store
// Hooks returns a Hooks instance or nil.
Hooks() Hooks
}
// ReadBackendOptions defines options for creating a ReadBackend.
// Read context can optionally define two stores - a commitment store
// that is backed by a merkle tree and an index store that isn't.
// If the index store is not defined, the commitment store will be
// used for all operations.
type ReadBackendOptions struct {
// CommitmentStoreReader is a reader for the commitment store.
CommitmentStoreReader kv.ReadonlyStore
// IndexStoreReader is an optional reader for the index store.
// If it is nil the CommitmentStoreReader will be used.
IndexStoreReader kv.ReadonlyStore
}
type readBackend struct {
commitmentReader kv.ReadonlyStore
indexReader kv.ReadonlyStore
}
func (r readBackend) CommitmentStoreReader() kv.ReadonlyStore {
return r.commitmentReader
}
func (r readBackend) IndexStoreReader() kv.ReadonlyStore {
return r.indexReader
}
func (readBackend) private() {}
// NewReadBackend creates a new ReadBackend.
func NewReadBackend(options ReadBackendOptions) ReadBackend {
indexReader := options.IndexStoreReader
if indexReader == nil {
indexReader = options.CommitmentStoreReader
}
return &readBackend{
commitmentReader: options.CommitmentStoreReader,
indexReader: indexReader,
}
}
type backend struct {
commitmentStore kv.Store
indexStore kv.Store
hooks Hooks
}
func (backend) private() {}
func (c backend) CommitmentStoreReader() kv.ReadonlyStore {
return c.commitmentStore
}
func (c backend) IndexStoreReader() kv.ReadonlyStore {
return c.indexStore
}
func (c backend) CommitmentStore() kv.Store {
return c.commitmentStore
}
func (c backend) IndexStore() kv.Store {
return c.indexStore
}
func (c backend) Hooks() Hooks {
return c.hooks
}
// BackendOptions defines options for creating a Backend.
// Context can optionally define two stores - a commitment store
// that is backed by a merkle tree and an index store that isn't.
// If the index store is not defined, the commitment store will be
// used for all operations.
type BackendOptions struct {
// CommitmentStore is the commitment store.
CommitmentStore kv.Store
// IndexStore is the optional index store.
// If it is nil the CommitmentStore will be used.
IndexStore kv.Store
// Hooks are optional hooks into ORM insert, update and delete operations.
Hooks Hooks
}
// NewBackend creates a new Backend.
func NewBackend(options BackendOptions) Backend {
indexStore := options.IndexStore
if indexStore == nil {
indexStore = options.CommitmentStore
}
return &backend{
commitmentStore: options.CommitmentStore,
indexStore: indexStore,
hooks: options.Hooks,
}
}
// WrapContextDefault performs the default wrapping of a backend in a context.
// This should be used primarily for testing purposes and production code
// should use some other framework specific wrapping (for instance using
// "store keys").
func WrapContextDefault(backend ReadBackend) context.Context {
return context.WithValue(context.Background(), defaultContextKey, backend)
}
type contextKeyType string
var defaultContextKey = contextKeyType("backend")
func getBackendDefault(ctx context.Context) (Backend, error) {
return ctx.Value(defaultContextKey).(Backend), nil
}
func getReadBackendDefault(ctx context.Context) (ReadBackend, error) {
return ctx.Value(defaultContextKey).(ReadBackend), nil
}

View File

@ -0,0 +1,96 @@
package ormtable
import "github.com/cosmos/cosmos-sdk/orm/model/kv"
type batchIndexCommitmentWriter struct {
Backend
commitmentWriter *batchStoreWriter
indexWriter *batchStoreWriter
}
func newBatchIndexCommitmentWriter(store Backend) *batchIndexCommitmentWriter {
return &batchIndexCommitmentWriter{
Backend: store,
// optimal array capacities are estimated here:
commitmentWriter: &batchStoreWriter{
ReadonlyStore: store.CommitmentStoreReader(),
writes: make([]batchWriterEntry, 0, 2),
},
indexWriter: &batchStoreWriter{
ReadonlyStore: store.IndexStoreReader(),
writes: make([]batchWriterEntry, 0, 16),
},
}
}
func (w *batchIndexCommitmentWriter) CommitmentStore() kv.Store {
return w.commitmentWriter
}
func (w *batchIndexCommitmentWriter) IndexStore() kv.Store {
return w.indexWriter
}
// Write flushes any pending writes.
func (w *batchIndexCommitmentWriter) Write() error {
err := flushWrites(w.Backend.CommitmentStore(), w.commitmentWriter.writes)
if err != nil {
return err
}
err = flushWrites(w.Backend.IndexStore(), w.indexWriter.writes)
if err != nil {
return err
}
// clear writes
w.Close()
return err
}
func flushWrites(writer kv.Store, writes []batchWriterEntry) error {
for _, write := range writes {
if !write.delete {
err := writer.Set(write.key, write.value)
if err != nil {
return err
}
} else {
err := writer.Delete(write.key)
if err != nil {
return err
}
}
}
return nil
}
// Close discards any pending writes and should generally be called using
// a defer statement.
func (w *batchIndexCommitmentWriter) Close() {
w.commitmentWriter.writes = nil
w.indexWriter.writes = nil
}
type batchWriterEntry struct {
key, value []byte
delete bool
}
type batchStoreWriter struct {
kv.ReadonlyStore
writes []batchWriterEntry
}
func (b *batchStoreWriter) Set(key, value []byte) error {
b.writes = append(b.writes, batchWriterEntry{key: key, value: value})
return nil
}
func (b *batchStoreWriter) Delete(key []byte) error {
b.writes = append(b.writes, batchWriterEntry{key: key, delete: true})
return nil
}
var _ Backend = &batchIndexCommitmentWriter{}

296
orm/model/ormtable/build.go Normal file
View File

@ -0,0 +1,296 @@
package ormtable
import (
"context"
"fmt"
"github.com/cosmos/cosmos-sdk/orm/encoding/encodeutil"
"google.golang.org/protobuf/reflect/protoregistry"
"google.golang.org/protobuf/proto"
"google.golang.org/protobuf/reflect/protoreflect"
ormv1alpha1 "github.com/cosmos/cosmos-sdk/api/cosmos/orm/v1alpha1"
"github.com/cosmos/cosmos-sdk/orm/encoding/ormkv"
"github.com/cosmos/cosmos-sdk/orm/types/ormerrors"
)
const (
primaryKeyId uint32 = 0
indexIdLimit uint32 = 32768
seqId = indexIdLimit
)
// Options are options for building a Table.
type Options struct {
// Prefix is an optional prefix used to build the table's prefix.
Prefix []byte
// MessageType is the protobuf message type of the table.
MessageType protoreflect.MessageType
// TableDescriptor is an optional table descriptor to be explicitly used
// with the table. Generally this should be nil and the table descriptor
// should be pulled from the table message option. TableDescriptor
// cannot be used together with SingletonDescriptor.
TableDescriptor *ormv1alpha1.TableDescriptor
// SingletonDescriptor is an optional singleton descriptor to be explicitly used.
// Generally this should be nil and the table descriptor
// should be pulled from the singleton message option. SingletonDescriptor
// cannot be used together with TableDescriptor.
SingletonDescriptor *ormv1alpha1.SingletonDescriptor
// TypeResolver is an optional type resolver to be used when unmarshaling
// protobuf messages.
TypeResolver TypeResolver
// JSONValidator is an optional validator that can be used for validating
// messaging when using ValidateJSON. If it is nil, DefaultJSONValidator
// will be used
JSONValidator func(proto.Message) error
// GetBackend is an optional function which retrieves a Backend from the context.
// If it is nil, the default behavior will be to attempt to retrieve a
// backend using the method that WrapContextDefault uses. This method
// can be used to imlement things like "store keys" which would allow a
// table to only be used with a specific backend and to hide direct
// access to the backend other than through the table interface.
GetBackend func(context.Context) (Backend, error)
// GetReadBackend is an optional function which retrieves a ReadBackend from the context.
// If it is nil, the default behavior will be to attempt to retrieve a
// backend using the method that WrapContextDefault uses.
GetReadBackend func(context.Context) (ReadBackend, error)
}
// TypeResolver is an interface that can be used for the protoreflect.UnmarshalOptions.Resolver option.
type TypeResolver interface {
protoregistry.MessageTypeResolver
protoregistry.ExtensionTypeResolver
}
// Build builds a Table instance from the provided Options.
func Build(options Options) (Table, error) {
messageDescriptor := options.MessageType.Descriptor()
getReadBackend := options.GetReadBackend
if getReadBackend == nil {
getReadBackend = getReadBackendDefault
}
getBackend := options.GetBackend
if getBackend == nil {
getBackend = getBackendDefault
}
table := &tableImpl{
primaryKeyIndex: &primaryKeyIndex{
indexers: []indexer{},
getBackend: getBackend,
getReadBackend: getReadBackend,
},
indexes: []Index{},
indexesByFields: map[fieldNames]concreteIndex{},
uniqueIndexesByFields: map[fieldNames]UniqueIndex{},
entryCodecsById: map[uint32]ormkv.EntryCodec{},
typeResolver: options.TypeResolver,
customJSONValidator: options.JSONValidator,
}
pkIndex := table.primaryKeyIndex
tableDesc := options.TableDescriptor
if tableDesc == nil {
tableDesc = proto.GetExtension(messageDescriptor.Options(), ormv1alpha1.E_Table).(*ormv1alpha1.TableDescriptor)
}
singletonDesc := options.SingletonDescriptor
if singletonDesc == nil {
singletonDesc = proto.GetExtension(messageDescriptor.Options(), ormv1alpha1.E_Singleton).(*ormv1alpha1.SingletonDescriptor)
}
switch {
case tableDesc != nil:
if singletonDesc != nil {
return nil, ormerrors.InvalidTableDefinition.Wrapf("message %s cannot be declared as both a table and a singleton", messageDescriptor.FullName())
}
case singletonDesc != nil:
if singletonDesc.Id == 0 {
return nil, ormerrors.InvalidTableId.Wrapf("%s", messageDescriptor.FullName())
}
prefix := encodeutil.AppendVarUInt32(options.Prefix, singletonDesc.Id)
pkCodec, err := ormkv.NewPrimaryKeyCodec(
prefix,
options.MessageType,
nil,
proto.UnmarshalOptions{Resolver: options.TypeResolver},
)
if err != nil {
return nil, err
}
pkIndex.PrimaryKeyCodec = pkCodec
table.tablePrefix = prefix
table.tableId = singletonDesc.Id
return &singleton{table}, nil
default:
return nil, ormerrors.InvalidTableDefinition.Wrapf("missing table descriptor for %s", messageDescriptor.FullName())
}
tableId := tableDesc.Id
if tableId == 0 {
return nil, ormerrors.InvalidTableId.Wrapf("table %s", messageDescriptor.FullName())
}
prefix := options.Prefix
prefix = encodeutil.AppendVarUInt32(prefix, tableId)
table.tablePrefix = prefix
table.tableId = tableId
if tableDesc.PrimaryKey == nil {
return nil, ormerrors.MissingPrimaryKey.Wrap(string(messageDescriptor.FullName()))
}
pkFields := commaSeparatedFieldNames(tableDesc.PrimaryKey.Fields)
table.primaryKeyIndex.fields = pkFields
pkFieldNames := pkFields.Names()
if len(pkFieldNames) == 0 {
return nil, ormerrors.InvalidTableDefinition.Wrapf("empty primary key fields for %s", messageDescriptor.FullName())
}
pkPrefix := encodeutil.AppendVarUInt32(prefix, primaryKeyId)
pkCodec, err := ormkv.NewPrimaryKeyCodec(
pkPrefix,
options.MessageType,
pkFieldNames,
proto.UnmarshalOptions{Resolver: options.TypeResolver},
)
if err != nil {
return nil, err
}
pkIndex.PrimaryKeyCodec = pkCodec
table.indexesByFields[pkFields] = pkIndex
table.uniqueIndexesByFields[pkFields] = pkIndex
table.entryCodecsById[primaryKeyId] = pkIndex
table.indexes = append(table.indexes, pkIndex)
for _, idxDesc := range tableDesc.Index {
id := idxDesc.Id
if id == 0 || id >= indexIdLimit {
return nil, ormerrors.InvalidIndexId.Wrapf("index on table %s with fields %s, invalid id %d", messageDescriptor.FullName(), idxDesc.Fields, id)
}
if _, ok := table.entryCodecsById[id]; ok {
return nil, ormerrors.DuplicateIndexId.Wrapf("id %d on table %s", id, messageDescriptor.FullName())
}
idxFields := commaSeparatedFieldNames(idxDesc.Fields)
idxPrefix := encodeutil.AppendVarUInt32(prefix, id)
var index concreteIndex
// altNames contains all the alternative "names" of this index
altNames := map[fieldNames]bool{idxFields: true}
if idxDesc.Unique && isNonTrivialUniqueKey(idxFields.Names(), pkFieldNames) {
uniqCdc, err := ormkv.NewUniqueKeyCodec(
idxPrefix,
options.MessageType,
idxFields.Names(),
pkFieldNames,
)
if err != nil {
return nil, err
}
uniqIdx := &uniqueKeyIndex{
UniqueKeyCodec: uniqCdc,
fields: idxFields,
primaryKey: pkIndex,
getReadBackend: getReadBackend,
}
table.uniqueIndexesByFields[idxFields] = uniqIdx
index = uniqIdx
} else {
idxCdc, err := ormkv.NewIndexKeyCodec(
idxPrefix,
options.MessageType,
idxFields.Names(),
pkFieldNames,
)
if err != nil {
return nil, err
}
index = &indexKeyIndex{
IndexKeyCodec: idxCdc,
fields: idxFields,
primaryKey: pkIndex,
getReadBackend: getReadBackend,
}
// non-unique indexes can sometimes be named by several sub-lists of
// fields and we need to handle all of them. For example consider,
// a primary key for fields "a,b,c" and an index on field "c". Because the
// rest of the primary key gets appended to the index key, the index for "c"
// is actually stored as "c,a,b". So this index can be referred to
// by the fields "c", "c,a", or "c,a,b".
allFields := index.GetFieldNames()
allFieldNames := fieldsFromNames(allFields)
altNames[allFieldNames] = true
for i := 1; i < len(allFields); i++ {
altName := fieldsFromNames(allFields[:i])
if altNames[altName] {
continue
}
// we check by generating a codec for each sub-list of fields,
// then we see if the full list of fields matches.
altIdxCdc, err := ormkv.NewIndexKeyCodec(
idxPrefix,
options.MessageType,
allFields[:i],
pkFieldNames,
)
if err != nil {
return nil, err
}
if fieldsFromNames(altIdxCdc.GetFieldNames()) == allFieldNames {
altNames[altName] = true
}
}
}
for name := range altNames {
if _, ok := table.indexesByFields[name]; ok {
return nil, fmt.Errorf("duplicate index for fields %s", name)
}
table.indexesByFields[name] = index
}
table.entryCodecsById[id] = index
table.indexes = append(table.indexes, index)
table.indexers = append(table.indexers, index.(indexer))
}
if tableDesc.PrimaryKey.AutoIncrement {
autoIncField := pkCodec.GetFieldDescriptors()[0]
if len(pkFieldNames) != 1 && autoIncField.Kind() != protoreflect.Uint64Kind {
return nil, ormerrors.InvalidAutoIncrementKey.Wrapf("field %s", autoIncField.FullName())
}
seqPrefix := encodeutil.AppendVarUInt32(prefix, seqId)
seqCodec := ormkv.NewSeqCodec(options.MessageType, seqPrefix)
table.entryCodecsById[seqId] = seqCodec
return &autoIncrementTable{
tableImpl: table,
autoIncField: autoIncField,
seqCodec: seqCodec,
}, nil
}
return table, nil
}

View File

@ -0,0 +1,3 @@
// Package ormtable defines the interfaces and implementations of tables and
// indexes.
package ormtable

View File

@ -0,0 +1,55 @@
package ormtable
import (
"strings"
"google.golang.org/protobuf/reflect/protoreflect"
)
// fieldNames abstractly represents a list of fields with a comparable type which
// can be used as a map key. It is used primarily to lookup indexes.
type fieldNames struct {
fields string
}
// commaSeparatedFieldNames creates a fieldNames instance from a list of comma-separated
// fields.
func commaSeparatedFieldNames(fields string) fieldNames {
// normalize cases where there are spaces
if strings.IndexByte(fields, ' ') >= 0 {
parts := strings.Split(fields, ",")
for i, part := range parts {
parts[i] = strings.TrimSpace(part)
}
fields = strings.Join(parts, ",")
}
return fieldNames{fields: fields}
}
// fieldsFromNames creates a fieldNames instance from an array of field
// names.
func fieldsFromNames(fnames []protoreflect.Name) fieldNames {
var names []string
for _, name := range fnames {
names = append(names, string(name))
}
return fieldNames{fields: strings.Join(names, ",")}
}
// Names returns the array of names this fieldNames instance represents.
func (f fieldNames) Names() []protoreflect.Name {
if f.fields == "" {
return nil
}
fields := strings.Split(f.fields, ",")
names := make([]protoreflect.Name, len(fields))
for i, field := range fields {
names[i] = protoreflect.Name(field)
}
return names
}
func (f fieldNames) String() string {
return f.fields
}

View File

@ -0,0 +1,43 @@
package ormtable
import (
"testing"
"google.golang.org/protobuf/reflect/protoreflect"
"gotest.tools/v3/assert"
)
func TestFieldNames(t *testing.T) {
names := []protoreflect.Name{"a", "b", "c"}
abc := "a,b,c"
f := commaSeparatedFieldNames(abc)
assert.Equal(t, fieldNames{abc}, f)
assert.DeepEqual(t, names, f.Names())
assert.Equal(t, abc, f.String())
f = commaSeparatedFieldNames("a, b ,c")
assert.Equal(t, fieldNames{abc}, f)
assert.DeepEqual(t, names, f.Names())
assert.Equal(t, abc, f.String())
// empty okay
f = commaSeparatedFieldNames("")
assert.Equal(t, fieldNames{""}, f)
assert.Equal(t, 0, len(f.Names()))
assert.Equal(t, "", f.String())
f = fieldsFromNames(names)
assert.Equal(t, fieldNames{abc}, f)
assert.DeepEqual(t, names, f.Names())
assert.Equal(t, abc, f.String())
// empty okay
f = fieldsFromNames([]protoreflect.Name{})
assert.Equal(t, fieldNames{""}, f)
f = fieldsFromNames(nil)
assert.Equal(t, fieldNames{""}, f)
assert.Equal(t, 0, len(f.Names()))
assert.Equal(t, "", f.String())
}

View File

@ -0,0 +1,21 @@
package ormtable
import "google.golang.org/protobuf/proto"
// Hooks defines an interface for a table hooks which can intercept
// insert, update and delete operations. Table.Save and Table.Delete methods will
// do a type assertion on kvstore.IndexCommitmentStore and if the Hooks
// interface is defined call the appropriate hooks.
type Hooks interface {
// OnInsert is called before the message is inserted.
// If error is not nil the operation will fail.
OnInsert(proto.Message) error
// OnUpdate is called before the existing message is updated with the new one.
// If error is not nil the operation will fail.
OnUpdate(existing, new proto.Message) error
// OnDelete is called before the message is deleted.
// If error is not nil the operation will fail.
OnDelete(proto.Message) error
}

View File

@ -0,0 +1,57 @@
package ormtable
import (
"context"
"google.golang.org/protobuf/proto"
"google.golang.org/protobuf/reflect/protoreflect"
"github.com/cosmos/cosmos-sdk/orm/encoding/ormkv"
"github.com/cosmos/cosmos-sdk/orm/model/kv"
"github.com/cosmos/cosmos-sdk/orm/model/ormlist"
)
// Index defines an index on a table. Index instances
// are stateless, with all state existing only in the store passed
// to index methods.
type Index interface {
// Iterator returns an iterator for this index with the provided list options.
Iterator(ctx context.Context, options ...ormlist.Option) (Iterator, error)
// MessageType returns the protobuf message type of the index.
MessageType() protoreflect.MessageType
// Fields returns the canonical field names of the index.
Fields() string
doNotImplement()
}
// concreteIndex is used internally by table implementations.
type concreteIndex interface {
Index
ormkv.IndexCodec
readValueFromIndexKey(context ReadBackend, primaryKey []protoreflect.Value, value []byte, message proto.Message) error
}
// UniqueIndex defines an unique index on a table.
type UniqueIndex interface {
Index
// Has returns true if the key values are present in the store for this index.
Has(context context.Context, keyValues ...interface{}) (found bool, err error)
// Get retrieves the message if one exists for the provided key values.
Get(context context.Context, message proto.Message, keyValues ...interface{}) (found bool, err error)
// DeleteByKey deletes the message if one exists in for the provided key values.
DeleteByKey(context context.Context, keyValues ...interface{}) error
}
type indexer interface {
onInsert(store kv.Store, message protoreflect.Message) error
onUpdate(store kv.Store, new, existing protoreflect.Message) error
onDelete(store kv.Store, message protoreflect.Message) error
}

View File

@ -0,0 +1,93 @@
package ormtable
import (
"context"
"github.com/cosmos/cosmos-sdk/orm/model/kv"
"github.com/cosmos/cosmos-sdk/orm/model/ormlist"
"github.com/cosmos/cosmos-sdk/orm/types/ormerrors"
"google.golang.org/protobuf/proto"
"google.golang.org/protobuf/reflect/protoreflect"
"github.com/cosmos/cosmos-sdk/orm/encoding/ormkv"
)
// indexKeyIndex implements Index for a regular IndexKey.
type indexKeyIndex struct {
*ormkv.IndexKeyCodec
fields fieldNames
primaryKey *primaryKeyIndex
getReadBackend func(context.Context) (ReadBackend, error)
}
func (i indexKeyIndex) Iterator(ctx context.Context, options ...ormlist.Option) (Iterator, error) {
backend, err := i.getReadBackend(ctx)
if err != nil {
return nil, err
}
return iterator(backend, backend.IndexStoreReader(), i, i.KeyCodec, options)
}
var _ indexer = &indexKeyIndex{}
var _ Index = &indexKeyIndex{}
func (i indexKeyIndex) doNotImplement() {}
func (i indexKeyIndex) onInsert(store kv.Store, message protoreflect.Message) error {
k, v, err := i.EncodeKVFromMessage(message)
if err != nil {
return err
}
return store.Set(k, v)
}
func (i indexKeyIndex) onUpdate(store kv.Store, new, existing protoreflect.Message) error {
newValues := i.GetKeyValues(new)
existingValues := i.GetKeyValues(existing)
if i.CompareKeys(newValues, existingValues) == 0 {
return nil
}
existingKey, err := i.EncodeKey(existingValues)
if err != nil {
return err
}
err = store.Delete(existingKey)
if err != nil {
return err
}
newKey, err := i.EncodeKey(newValues)
if err != nil {
return err
}
return store.Set(newKey, []byte{})
}
func (i indexKeyIndex) onDelete(store kv.Store, message protoreflect.Message) error {
_, key, err := i.EncodeKeyFromMessage(message)
if err != nil {
return err
}
return store.Delete(key)
}
func (i indexKeyIndex) readValueFromIndexKey(backend ReadBackend, primaryKey []protoreflect.Value, _ []byte, message proto.Message) error {
found, err := i.primaryKey.get(backend, message, primaryKey)
if err != nil {
return err
}
if !found {
return ormerrors.UnexpectedError.Wrapf("can't find primary key")
}
return nil
}
func (p indexKeyIndex) Fields() string {
return p.fields.String()
}

View File

@ -0,0 +1,239 @@
package ormtable
import (
"google.golang.org/protobuf/proto"
"google.golang.org/protobuf/reflect/protoreflect"
"github.com/cosmos/cosmos-sdk/orm/encoding/ormkv"
"github.com/cosmos/cosmos-sdk/orm/internal/listinternal"
"github.com/cosmos/cosmos-sdk/orm/model/kv"
"github.com/cosmos/cosmos-sdk/orm/model/ormlist"
)
// Iterator defines the interface for iterating over indexes.
type Iterator interface {
// Next advances the iterator and returns true if a valid entry is found.
// Next must be called before starting iteration.
Next() bool
// Keys returns the current index key and primary key values that the
// iterator points to.
Keys() (indexKey, primaryKey []protoreflect.Value, err error)
// UnmarshalMessage unmarshals the entry the iterator currently points to
// the provided proto.Message.
UnmarshalMessage(proto.Message) error
// GetMessage retrieves the proto.Message that the iterator currently points
// to.
GetMessage() (proto.Message, error)
// Cursor returns the cursor referencing the current iteration position
// and can be used to restart iteration right after this position.
Cursor() ormlist.CursorT
// Close closes the iterator and must always be called when done using
// the iterator. The defer keyword should generally be used for this.
Close()
doNotImplement()
}
func iterator(
backend ReadBackend,
reader kv.ReadonlyStore,
index concreteIndex,
codec *ormkv.KeyCodec,
options []listinternal.Option,
) (Iterator, error) {
opts := &listinternal.Options{}
listinternal.ApplyOptions(opts, options)
if err := opts.Validate(); err != nil {
return nil, err
}
if opts.Start != nil || opts.End != nil {
err := codec.CheckValidRangeIterationKeys(opts.Start, opts.End)
if err != nil {
return nil, err
}
startBz, err := codec.EncodeKey(opts.Start)
if err != nil {
return nil, err
}
endBz, err := codec.EncodeKey(opts.End)
if err != nil {
return nil, err
}
fullEndKey := len(codec.GetFieldNames()) == len(opts.End)
return rangeIterator(reader, backend, index, startBz, endBz, fullEndKey, opts)
} else {
prefixBz, err := codec.EncodeKey(opts.Prefix)
if err != nil {
return nil, err
}
return prefixIterator(reader, backend, index, prefixBz, opts)
}
}
func prefixIterator(iteratorStore kv.ReadonlyStore, backend ReadBackend, index concreteIndex, prefix []byte, options *listinternal.Options) (Iterator, error) {
if !options.Reverse {
var start []byte
if len(options.Cursor) != 0 {
// must start right after cursor
start = append(options.Cursor, 0x0)
} else {
start = prefix
}
end := prefixEndBytes(prefix)
it, err := iteratorStore.Iterator(start, end)
if err != nil {
return nil, err
}
return &indexIterator{
index: index,
store: backend,
iterator: it,
started: false,
}, nil
} else {
var end []byte
if len(options.Cursor) != 0 {
// end bytes is already exclusive by default
end = options.Cursor
} else {
end = prefixEndBytes(prefix)
}
it, err := iteratorStore.ReverseIterator(prefix, end)
if err != nil {
return nil, err
}
return &indexIterator{
index: index,
store: backend,
iterator: it,
started: false,
}, nil
}
}
// NOTE: fullEndKey indicates whether the end key contained all the fields of the key,
// if it did then we need to use inclusive end bytes, otherwise we prefix the end bytes
func rangeIterator(iteratorStore kv.ReadonlyStore, reader ReadBackend, index concreteIndex, start, end []byte, fullEndKey bool, options *listinternal.Options) (Iterator, error) {
if !options.Reverse {
if len(options.Cursor) != 0 {
start = append(options.Cursor, 0)
}
if fullEndKey {
end = inclusiveEndBytes(end)
} else {
end = prefixEndBytes(end)
}
it, err := iteratorStore.Iterator(start, end)
if err != nil {
return nil, err
}
return &indexIterator{
index: index,
store: reader,
iterator: it,
started: false,
}, nil
} else {
if len(options.Cursor) != 0 {
end = options.Cursor
} else {
if fullEndKey {
end = inclusiveEndBytes(end)
} else {
end = prefixEndBytes(end)
}
}
it, err := iteratorStore.ReverseIterator(start, end)
if err != nil {
return nil, err
}
return &indexIterator{
index: index,
store: reader,
iterator: it,
started: false,
}, nil
}
}
type indexIterator struct {
index concreteIndex
store ReadBackend
iterator kv.Iterator
indexValues []protoreflect.Value
primaryKey []protoreflect.Value
value []byte
started bool
}
func (i *indexIterator) Next() bool {
if !i.started {
i.started = true
} else {
i.iterator.Next()
}
return i.iterator.Valid()
}
func (i *indexIterator) Keys() (indexKey, primaryKey []protoreflect.Value, err error) {
if i.indexValues != nil {
return i.indexValues, i.primaryKey, nil
}
i.value = i.iterator.Value()
i.indexValues, i.primaryKey, err = i.index.DecodeIndexKey(i.iterator.Key(), i.value)
if err != nil {
return nil, nil, err
}
return i.indexValues, i.primaryKey, nil
}
func (i indexIterator) UnmarshalMessage(message proto.Message) error {
_, pk, err := i.Keys()
if err != nil {
return err
}
return i.index.readValueFromIndexKey(i.store, pk, i.value, message)
}
func (i *indexIterator) GetMessage() (proto.Message, error) {
msg := i.index.MessageType().New().Interface()
err := i.UnmarshalMessage(msg)
return msg, err
}
func (i indexIterator) Cursor() ormlist.CursorT {
return i.iterator.Key()
}
func (i indexIterator) Close() {
err := i.iterator.Close()
if err != nil {
panic(err)
}
}
func (indexIterator) doNotImplement() {
}
var _ Iterator = &indexIterator{}

View File

@ -0,0 +1,124 @@
package ormtable
import (
"context"
"fmt"
"google.golang.org/protobuf/proto"
queryv1beta1 "github.com/cosmos/cosmos-sdk/api/cosmos/base/query/v1beta1"
"github.com/cosmos/cosmos-sdk/orm/model/ormlist"
)
// PaginationRequest is a request to the Paginate function and extends the
// options in query.PageRequest.
type PaginationRequest struct {
*queryv1beta1.PageRequest
// Filter is an optional filter function that can be used to filter
// the results in the underlying iterator and should return true to include
// an item in the result.
Filter func(message proto.Message) bool
}
// PaginationResponse is a response from the Paginate function and extends the
// options in query.PageResponse.
type PaginationResponse struct {
*queryv1beta1.PageResponse
// HaveMore indicates whether there are more pages.
HaveMore bool
// Cursors returns a cursor for each item and can be used to implement
// GraphQL connection edges.
Cursors []ormlist.CursorT
}
// Paginate retrieves a "page" of data from the provided index and context.
func Paginate(
index Index,
ctx context.Context,
request *PaginationRequest,
onItem func(proto.Message),
options ...ormlist.Option,
) (*PaginationResponse, error) {
offset := int(request.Offset)
if len(request.Key) != 0 {
if offset > 0 {
return nil, fmt.Errorf("can only specify one of cursor or offset")
}
options = append(options, ormlist.Cursor(request.Key))
}
if request.Reverse {
options = append(options, ormlist.Reverse())
}
it, err := index.Iterator(ctx, options...)
if err != nil {
return nil, err
}
defer it.Close()
limit := int(request.Limit)
if limit == 0 {
return nil, fmt.Errorf("limit not specified")
}
i := 0
if offset != 0 {
for ; i < offset; i++ {
if !it.Next() {
return &PaginationResponse{
PageResponse: &queryv1beta1.PageResponse{Total: uint64(i)},
}, nil
}
}
}
haveMore := false
cursors := make([]ormlist.CursorT, 0, limit)
done := limit + offset
for it.Next() {
if i == done {
haveMore = true
if request.CountTotal {
for {
i++
if !it.Next() {
break
}
}
}
break
}
message, err := it.GetMessage()
if err != nil {
return nil, err
}
if request.Filter != nil && !request.Filter(message) {
continue
}
i++
cursors = append(cursors, it.Cursor())
onItem(message)
}
pageRes := &queryv1beta1.PageResponse{}
if request.CountTotal {
pageRes.Total = uint64(i)
}
n := len(cursors)
if n != 0 {
pageRes.NextKey = cursors[n-1]
}
return &PaginationResponse{
PageResponse: pageRes,
HaveMore: haveMore,
Cursors: cursors,
}, nil
}

View File

@ -0,0 +1,142 @@
package ormtable
import (
"context"
"github.com/cosmos/cosmos-sdk/orm/model/ormlist"
"github.com/cosmos/cosmos-sdk/orm/encoding/encodeutil"
"google.golang.org/protobuf/proto"
"google.golang.org/protobuf/reflect/protoreflect"
"github.com/cosmos/cosmos-sdk/orm/encoding/ormkv"
)
// primaryKeyIndex defines an UniqueIndex for the primary key.
type primaryKeyIndex struct {
*ormkv.PrimaryKeyCodec
fields fieldNames
indexers []indexer
getBackend func(context.Context) (Backend, error)
getReadBackend func(context.Context) (ReadBackend, error)
}
func (p primaryKeyIndex) Iterator(ctx context.Context, options ...ormlist.Option) (Iterator, error) {
backend, err := p.getReadBackend(ctx)
if err != nil {
return nil, err
}
return iterator(backend, backend.CommitmentStoreReader(), p, p.KeyCodec, options)
}
func (p primaryKeyIndex) doNotImplement() {}
func (p primaryKeyIndex) Has(context context.Context, key ...interface{}) (found bool, err error) {
ctx, err := p.getReadBackend(context)
if err != nil {
return false, err
}
keyBz, err := p.EncodeKey(encodeutil.ValuesOf(key...))
if err != nil {
return false, err
}
return ctx.CommitmentStoreReader().Has(keyBz)
}
func (p primaryKeyIndex) Get(ctx context.Context, message proto.Message, values ...interface{}) (found bool, err error) {
backend, err := p.getReadBackend(ctx)
if err != nil {
return false, err
}
return p.get(backend, message, encodeutil.ValuesOf(values...))
}
func (t primaryKeyIndex) DeleteByKey(ctx context.Context, primaryKeyValues ...interface{}) error {
return t.doDeleteByKey(ctx, encodeutil.ValuesOf(primaryKeyValues...))
}
func (t primaryKeyIndex) doDeleteByKey(ctx context.Context, primaryKeyValues []protoreflect.Value) error {
backend, err := t.getBackend(ctx)
if err != nil {
return err
}
pk, err := t.EncodeKey(primaryKeyValues)
if err != nil {
return err
}
msg := t.MessageType().New().Interface()
found, err := t.getByKeyBytes(backend, pk, primaryKeyValues, msg)
if err != nil {
return err
}
if !found {
return nil
}
if hooks := backend.Hooks(); hooks != nil {
err = hooks.OnDelete(msg)
if err != nil {
return err
}
}
// delete object
writer := newBatchIndexCommitmentWriter(backend)
defer writer.Close()
err = writer.CommitmentStore().Delete(pk)
if err != nil {
return err
}
// clear indexes
mref := msg.ProtoReflect()
indexStoreWriter := writer.IndexStore()
for _, idx := range t.indexers {
err := idx.onDelete(indexStoreWriter, mref)
if err != nil {
return err
}
}
return writer.Write()
}
func (p primaryKeyIndex) get(backend ReadBackend, message proto.Message, values []protoreflect.Value) (found bool, err error) {
key, err := p.EncodeKey(values)
if err != nil {
return false, err
}
return p.getByKeyBytes(backend, key, values, message)
}
func (p primaryKeyIndex) getByKeyBytes(store ReadBackend, key []byte, keyValues []protoreflect.Value, message proto.Message) (found bool, err error) {
bz, err := store.CommitmentStoreReader().Get(key)
if err != nil {
return false, err
}
if bz == nil {
return false, nil
}
return true, p.Unmarshal(keyValues, bz, message)
}
func (p primaryKeyIndex) readValueFromIndexKey(_ ReadBackend, primaryKey []protoreflect.Value, value []byte, message proto.Message) error {
return p.Unmarshal(primaryKey, value, message)
}
func (p primaryKeyIndex) Fields() string {
return p.fields.String()
}
var _ UniqueIndex = &primaryKeyIndex{}

View File

@ -0,0 +1,93 @@
package ormtable
import (
"context"
"encoding/json"
"io"
"google.golang.org/protobuf/encoding/protojson"
)
// singleton implements a Table instance for singletons.
type singleton struct {
*tableImpl
}
func (t singleton) DefaultJSON() json.RawMessage {
msg := t.MessageType().New().Interface()
bz, err := t.jsonMarshalOptions().Marshal(msg)
if err != nil {
return json.RawMessage("{}")
}
return bz
}
func (t singleton) ValidateJSON(reader io.Reader) error {
bz, err := io.ReadAll(reader)
if err != nil {
return err
}
msg := t.MessageType().New().Interface()
err = protojson.Unmarshal(bz, msg)
if err != nil {
return err
}
if t.customJSONValidator != nil {
return t.customJSONValidator(msg)
} else {
return DefaultJSONValidator(msg)
}
}
func (t singleton) ImportJSON(ctx context.Context, reader io.Reader) error {
backend, err := t.getBackend(ctx)
if err != nil {
return err
}
bz, err := io.ReadAll(reader)
if err != nil {
return err
}
msg := t.MessageType().New().Interface()
err = protojson.Unmarshal(bz, msg)
if err != nil {
return err
}
return t.save(backend, msg, saveModeDefault)
}
func (t singleton) ExportJSON(ctx context.Context, writer io.Writer) error {
msg := t.MessageType().New().Interface()
found, err := t.Get(ctx, msg)
if err != nil {
return err
}
var bz []byte
if !found {
bz = t.DefaultJSON()
} else {
bz, err = t.jsonMarshalOptions().Marshal(msg)
if err != nil {
return err
}
}
_, err = writer.Write(bz)
return err
}
func (t singleton) jsonMarshalOptions() protojson.MarshalOptions {
return protojson.MarshalOptions{
Multiline: true,
Indent: "",
UseProtoNames: true,
EmitUnpopulated: true,
Resolver: t.typeResolver,
}
}

View File

@ -0,0 +1,52 @@
package ormtable_test
import (
"bytes"
"testing"
"github.com/cosmos/cosmos-sdk/orm/model/ormtable"
"google.golang.org/protobuf/testing/protocmp"
"gotest.tools/v3/assert"
"github.com/cosmos/cosmos-sdk/orm/internal/testkv"
"github.com/cosmos/cosmos-sdk/orm/internal/testpb"
)
func TestSingleton(t *testing.T) {
val := &testpb.ExampleSingleton{}
singleton, err := ormtable.Build(ormtable.Options{
MessageType: val.ProtoReflect().Type(),
})
assert.NilError(t, err)
store := ormtable.WrapContextDefault(testkv.NewSplitMemBackend())
found, err := singleton.Has(store)
assert.NilError(t, err)
assert.Assert(t, !found)
assert.NilError(t, singleton.Save(store, val))
found, err = singleton.Has(store)
assert.NilError(t, err)
assert.Assert(t, found)
val.Foo = "abc"
val.Bar = 3
assert.NilError(t, singleton.Save(store, val))
var val2 testpb.ExampleSingleton
found, err = singleton.Get(store, &val2)
assert.NilError(t, err)
assert.DeepEqual(t, val, &val2, protocmp.Transform())
buf := &bytes.Buffer{}
assert.NilError(t, singleton.ExportJSON(store, buf))
assert.NilError(t, singleton.ValidateJSON(bytes.NewReader(buf.Bytes())))
store2 := ormtable.WrapContextDefault(testkv.NewSplitMemBackend())
assert.NilError(t, singleton.ImportJSON(store2, bytes.NewReader(buf.Bytes())))
var val3 testpb.ExampleSingleton
found, err = singleton.Get(store, &val3)
assert.NilError(t, err)
assert.DeepEqual(t, val, &val3, protocmp.Transform())
}

113
orm/model/ormtable/table.go Normal file
View File

@ -0,0 +1,113 @@
package ormtable
import (
"context"
"encoding/json"
"io"
"google.golang.org/protobuf/proto"
"github.com/cosmos/cosmos-sdk/orm/encoding/ormkv"
)
// View defines a read-only table.
//
// It exists as a separate interacted to support future scenarios where
// tables may be "supported" virtually to provide compatibility between
// systems, for instance to enable backwards compatibility when a major
// migration needs to be performed.
type View interface {
UniqueIndex
// GetIndex returns the index referenced by the provided fields if
// one exists or nil. Note that some concrete indexes can be retrieved by
// multiple lists of fields.
GetIndex(fields string) Index
// GetUniqueIndex returns the unique index referenced by the provided fields if
// one exists or nil. Note that some concrete indexes can be retrieved by
// multiple lists of fields.
GetUniqueIndex(fields string) UniqueIndex
// Indexes returns all the concrete indexes for the table.
Indexes() []Index
}
// Table is an abstract interface around a concrete table. Table instances
// are stateless, with all state existing only in the store passed
// to table and index methods.
type Table interface {
View
ormkv.EntryCodec
// Save saves the provided entry in the store either inserting it or
// updating it if needed.
//
// If store implement the Hooks interface, the appropriate OnInsert or
// OnUpdate hook method will be called.
//
// Save attempts to be atomic with respect to the underlying store,
// meaning that either the full save operation is written or the store is
// left unchanged, unless there is an error with the underlying store.
Save(context context.Context, message proto.Message) error
// Insert inserts the provided entry in the store and fails if there is
// an unique key violation. See Save for more details on behavior.
Insert(context context.Context, message proto.Message) error
// Update updates the provided entry in the store and fails if an entry
// with a matching primary key does not exist. See Save for more details
// on behavior.
Update(context context.Context, message proto.Message) error
// Delete deletes the entry with the provided primary key from the store.
//
// If store implement the Hooks interface, the OnDelete hook method will
// be called.
//
// Delete attempts to be atomic with respect to the underlying store,
// meaning that either the full save operation is written or the store is
// left unchanged, unless there is an error with the underlying store.
Delete(context context.Context, message proto.Message) error
// DefaultJSON returns default JSON that can be used as a template for
// genesis files.
//
// For regular tables this an empty JSON array, but for singletons an
// empty instance of the singleton is marshaled.
DefaultJSON() json.RawMessage
// ValidateJSON validates JSON streamed from the reader.
ValidateJSON(io.Reader) error
// ImportJSON imports JSON into the store, streaming one entry at a time.
// Each table should be import from a separate JSON file to enable proper
// streaming.
//
// Regular tables should be stored as an array of objects with each object
// corresponding to a single record in the table.
//
// Auto-incrementing tables
// can optionally have the last sequence value as the first element in the
// array. If the last sequence value is provided, then each value of the
// primary key in the file must be <= this last sequence value or omitted
// entirely. If no last sequence value is provided, no entries should
// contain the primary key as this will be auto-assigned.
//
// Singletons should define a single object and not an array.
//
// ImportJSON is not atomic with respect to the underlying store, meaning
// that in the case of an error, some records may already have been
// imported. It is assumed that ImportJSON is called in the context of some
// larger transaction isolation.
ImportJSON(context.Context, io.Reader) error
// ExportJSON exports JSON in the format accepted by ImportJSON.
// Auto-incrementing tables will export the last sequence number as the
// first element in the JSON array.
ExportJSON(context.Context, io.Writer) error
// ID is the ID of this table within the schema of its FileDescriptor.
ID() uint32
}

View File

@ -0,0 +1,375 @@
package ormtable
import (
"bytes"
"context"
"encoding/binary"
"encoding/json"
"io"
"math"
"google.golang.org/protobuf/encoding/protojson"
"google.golang.org/protobuf/proto"
"github.com/cosmos/cosmos-sdk/orm/encoding/encodeutil"
"github.com/cosmos/cosmos-sdk/orm/encoding/ormkv"
"github.com/cosmos/cosmos-sdk/orm/types/ormerrors"
)
// tableImpl implements Table.
type tableImpl struct {
*primaryKeyIndex
indexes []Index
indexesByFields map[fieldNames]concreteIndex
uniqueIndexesByFields map[fieldNames]UniqueIndex
entryCodecsById map[uint32]ormkv.EntryCodec
tablePrefix []byte
tableId uint32
typeResolver TypeResolver
customJSONValidator func(message proto.Message) error
}
func (t tableImpl) Save(ctx context.Context, message proto.Message) error {
backend, err := t.getBackend(ctx)
if err != nil {
return err
}
return t.save(backend, message, saveModeDefault)
}
func (t tableImpl) Insert(ctx context.Context, message proto.Message) error {
backend, err := t.getBackend(ctx)
if err != nil {
return err
}
return t.save(backend, message, saveModeInsert)
}
func (t tableImpl) Update(ctx context.Context, message proto.Message) error {
backend, err := t.getBackend(ctx)
if err != nil {
return err
}
return t.save(backend, message, saveModeUpdate)
}
func (t tableImpl) save(backend Backend, message proto.Message, mode saveMode) error {
writer := newBatchIndexCommitmentWriter(backend)
defer writer.Close()
return t.doSave(writer, message, mode)
}
func (t tableImpl) doSave(writer *batchIndexCommitmentWriter, message proto.Message, mode saveMode) error {
mref := message.ProtoReflect()
pkValues, pk, err := t.EncodeKeyFromMessage(mref)
if err != nil {
return err
}
existing := mref.New().Interface()
haveExisting, err := t.getByKeyBytes(writer, pk, pkValues, existing)
if err != nil {
return err
}
if haveExisting {
if mode == saveModeInsert {
return ormerrors.PrimaryKeyConstraintViolation.Wrapf("%q:%+v", mref.Descriptor().FullName(), pkValues)
}
if hooks := writer.Hooks(); hooks != nil {
err = hooks.OnUpdate(existing, message)
if err != nil {
return err
}
}
} else {
if mode == saveModeUpdate {
return ormerrors.NotFoundOnUpdate.Wrapf("%q", mref.Descriptor().FullName())
}
if hooks := writer.Hooks(); hooks != nil {
err = hooks.OnInsert(message)
if err != nil {
return err
}
}
}
// temporarily clear primary key
t.ClearValues(mref)
// store object
bz, err := proto.MarshalOptions{Deterministic: true}.Marshal(message)
err = writer.CommitmentStore().Set(pk, bz)
if err != nil {
return err
}
// set primary key again
t.SetKeyValues(mref, pkValues)
// set indexes
indexStoreWriter := writer.IndexStore()
if !haveExisting {
for _, idx := range t.indexers {
err = idx.onInsert(indexStoreWriter, mref)
if err != nil {
return err
}
}
} else {
existingMref := existing.ProtoReflect()
for _, idx := range t.indexers {
err = idx.onUpdate(indexStoreWriter, mref, existingMref)
if err != nil {
return err
}
}
}
return writer.Write()
}
func (t tableImpl) Delete(context context.Context, message proto.Message) error {
pk := t.PrimaryKeyCodec.GetKeyValues(message.ProtoReflect())
return t.DeleteByKey(context, pk)
}
func (t tableImpl) GetIndex(fields string) Index {
return t.indexesByFields[commaSeparatedFieldNames(fields)]
}
func (t tableImpl) GetUniqueIndex(fields string) UniqueIndex {
return t.uniqueIndexesByFields[commaSeparatedFieldNames(fields)]
}
func (t tableImpl) Indexes() []Index {
return t.indexes
}
func (t tableImpl) DefaultJSON() json.RawMessage {
return json.RawMessage("[]")
}
func (t tableImpl) decodeJson(reader io.Reader, onMsg func(message proto.Message) error) error {
decoder, err := t.startDecodeJson(reader)
if err != nil {
return err
}
return t.doDecodeJson(decoder, nil, onMsg)
}
func (t tableImpl) startDecodeJson(reader io.Reader) (*json.Decoder, error) {
decoder := json.NewDecoder(reader)
token, err := decoder.Token()
if err != nil {
return nil, err
}
if token != json.Delim('[') {
return nil, ormerrors.JSONImportError.Wrapf("expected [ got %s", token)
}
return decoder, nil
}
// onFirst is called on the first RawMessage and used for auto-increment tables
// to decode the sequence in which case it should return true.
// onMsg is called on every decoded message
func (t tableImpl) doDecodeJson(decoder *json.Decoder, onFirst func(message json.RawMessage) bool, onMsg func(message proto.Message) error) error {
unmarshalOptions := protojson.UnmarshalOptions{Resolver: t.typeResolver}
first := true
for decoder.More() {
var rawJson json.RawMessage
err := decoder.Decode(&rawJson)
if err != nil {
return ormerrors.JSONImportError.Wrapf("%s", err)
}
if first {
first = false
if onFirst != nil {
if onFirst(rawJson) {
// if onFirst handled this, skip decoding into a proto message
continue
}
}
}
msg := t.MessageType().New().Interface()
err = unmarshalOptions.Unmarshal(rawJson, msg)
if err != nil {
return err
}
err = onMsg(msg)
if err != nil {
return err
}
}
token, err := decoder.Token()
if err != nil {
return err
}
if token != json.Delim(']') {
return ormerrors.JSONImportError.Wrapf("expected ] got %s", token)
}
return nil
}
// DefaultJSONValidator is the default validator used when calling
// Table.ValidateJSON(). It will call methods with the signature `ValidateBasic() error`
// and/or `Validate() error` to validate the message.
func DefaultJSONValidator(message proto.Message) error {
if v, ok := message.(interface{ ValidateBasic() error }); ok {
err := v.ValidateBasic()
if err != nil {
return err
}
}
if v, ok := message.(interface{ Validate() error }); ok {
err := v.Validate()
if err != nil {
return err
}
}
return nil
}
func (t tableImpl) ValidateJSON(reader io.Reader) error {
return t.decodeJson(reader, func(message proto.Message) error {
if t.customJSONValidator != nil {
return t.customJSONValidator(message)
} else {
return DefaultJSONValidator(message)
}
})
}
func (t tableImpl) ImportJSON(ctx context.Context, reader io.Reader) error {
backend, err := t.getBackend(ctx)
if err != nil {
return err
}
return t.decodeJson(reader, func(message proto.Message) error {
return t.save(backend, message, saveModeDefault)
})
}
func (t tableImpl) ExportJSON(context context.Context, writer io.Writer) error {
_, err := writer.Write([]byte("["))
if err != nil {
return err
}
return t.doExportJSON(context, writer)
}
func (t tableImpl) doExportJSON(ctx context.Context, writer io.Writer) error {
marshalOptions := protojson.MarshalOptions{
UseProtoNames: true,
Resolver: t.typeResolver,
}
var err error
it, _ := t.Iterator(ctx)
start := true
for {
found := it.Next()
if !found {
_, err = writer.Write([]byte("]"))
return err
} else if !start {
_, err = writer.Write([]byte(",\n"))
if err != nil {
return err
}
}
start = false
msg := t.MessageType().New().Interface()
err = it.UnmarshalMessage(msg)
if err != nil {
return err
}
bz, err := marshalOptions.Marshal(msg)
if err != nil {
return err
}
_, err = writer.Write(bz)
if err != nil {
return err
}
}
}
func (t tableImpl) DecodeEntry(k, v []byte) (ormkv.Entry, error) {
r := bytes.NewReader(k)
err := encodeutil.SkipPrefix(r, t.tablePrefix)
if err != nil {
return nil, err
}
id, err := binary.ReadUvarint(r)
if err != nil {
return nil, err
}
if id > math.MaxUint32 {
return nil, ormerrors.UnexpectedDecodePrefix.Wrapf("uint32 varint id out of range %d", id)
}
idx, ok := t.entryCodecsById[uint32(id)]
if !ok {
return nil, ormerrors.UnexpectedDecodePrefix.Wrapf("can't find field with id %d", id)
}
return idx.DecodeEntry(k, v)
}
func (t tableImpl) EncodeEntry(entry ormkv.Entry) (k, v []byte, err error) {
switch entry := entry.(type) {
case *ormkv.PrimaryKeyEntry:
return t.PrimaryKeyCodec.EncodeEntry(entry)
case *ormkv.IndexKeyEntry:
idx, ok := t.indexesByFields[fieldsFromNames(entry.Fields)]
if !ok {
return nil, nil, ormerrors.BadDecodeEntry.Wrapf("can't find index with fields %s", entry.Fields)
}
return idx.EncodeEntry(entry)
default:
return nil, nil, ormerrors.BadDecodeEntry.Wrapf("%s", entry)
}
}
func (t tableImpl) ID() uint32 {
return t.tableId
}
var _ Table = &tableImpl{}
type saveMode int
const (
saveModeDefault saveMode = iota
saveModeInsert
saveModeUpdate
)

View File

@ -0,0 +1,692 @@
package ormtable_test
import (
"bytes"
"context"
"fmt"
"sort"
"strings"
"testing"
"google.golang.org/protobuf/proto"
"google.golang.org/protobuf/reflect/protoreflect"
"google.golang.org/protobuf/testing/protocmp"
"gotest.tools/v3/assert"
"gotest.tools/v3/golden"
"pgregory.net/rapid"
queryv1beta1 "github.com/cosmos/cosmos-sdk/api/cosmos/base/query/v1beta1"
sdkerrors "github.com/cosmos/cosmos-sdk/errors"
"github.com/cosmos/cosmos-sdk/orm/encoding/ormkv"
"github.com/cosmos/cosmos-sdk/orm/internal/testkv"
"github.com/cosmos/cosmos-sdk/orm/internal/testpb"
"github.com/cosmos/cosmos-sdk/orm/internal/testutil"
"github.com/cosmos/cosmos-sdk/orm/model/kv"
"github.com/cosmos/cosmos-sdk/orm/model/ormlist"
"github.com/cosmos/cosmos-sdk/orm/model/ormtable"
"github.com/cosmos/cosmos-sdk/orm/types/ormerrors"
)
func TestScenario(t *testing.T) {
table, err := ormtable.Build(ormtable.Options{
MessageType: (&testpb.ExampleTable{}).ProtoReflect().Type(),
})
assert.NilError(t, err)
// first run tests with a split index-commitment store
runTestScenario(t, table, testkv.NewSplitMemBackend())
// now run tests with a shared index-commitment store
// we're going to wrap this test in a debug store and save the decoded debug
// messages, these will be checked against a golden file at the end of the
// test. the golden file can be used for fine-grained debugging of kv-store
// layout
debugBuf := &strings.Builder{}
store := testkv.NewDebugBackend(
testkv.NewSharedMemBackend(),
&testkv.EntryCodecDebugger{
EntryCodec: table,
Print: func(s string) { debugBuf.WriteString(s + "\n") },
},
)
runTestScenario(t, table, store)
// we're going to store debug data in a golden file to make sure that
// logical decoding works successfully
// run `go test pkgname -test.update-golden` to update the golden file
// see https://pkg.go.dev/gotest.tools/v3/golden for docs
golden.Assert(t, debugBuf.String(), "test_scenario.golden")
checkEncodeDecodeEntries(t, table, store.IndexStoreReader())
}
// check that the ormkv.Entry's decode and encode to the same bytes
func checkEncodeDecodeEntries(t *testing.T, table ormtable.Table, store kv.ReadonlyStore) {
it, err := store.Iterator(nil, nil)
assert.NilError(t, err)
for it.Valid() {
key := it.Key()
value := it.Value()
entry, err := table.DecodeEntry(key, value)
assert.NilError(t, err)
k, v, err := table.EncodeEntry(entry)
assert.Assert(t, bytes.Equal(key, k), "%x %x %s", key, k, entry)
assert.Assert(t, bytes.Equal(value, v), "%x %x %s", value, v, entry)
it.Next()
}
}
func runTestScenario(t *testing.T, table ormtable.Table, backend ormtable.Backend) {
ctx := ormtable.WrapContextDefault(backend)
// let's create 10 data items we'll use later and give them indexes
data := []*testpb.ExampleTable{
{U32: 4, I64: -2, Str: "abc", U64: 7}, // 0
{U32: 4, I64: -2, Str: "abd", U64: 7}, // 1
{U32: 4, I64: -1, Str: "abc", U64: 8}, // 2
{U32: 5, I64: -2, Str: "abd", U64: 8}, // 3
{U32: 5, I64: -2, Str: "abe", U64: 9}, // 4
{U32: 7, I64: -2, Str: "abe", U64: 10}, // 5
{U32: 7, I64: -1, Str: "abe", U64: 11}, // 6
{U32: 8, I64: -4, Str: "abc", U64: 11}, // 7
{U32: 8, I64: 1, Str: "abc", U64: 12}, // 8
{U32: 8, I64: 1, Str: "abd", U64: 10}, // 9
}
// let's make a function to match what's in our iterator with what we
// expect using indexes in the data array above
assertIteratorItems := func(it ormtable.Iterator, xs ...int) {
for _, i := range xs {
assert.Assert(t, it.Next())
msg, err := it.GetMessage()
assert.NilError(t, err)
//t.Logf("data[%d] %v == %v", i, data[i], msg)
assert.DeepEqual(t, data[i], msg, protocmp.Transform())
}
// make sure the iterator is done
assert.Assert(t, !it.Next())
}
// insert one record
err := table.Insert(ctx, data[0])
// trivial prefix query has one record
it, err := table.Iterator(ctx)
assert.NilError(t, err)
assertIteratorItems(it, 0)
// insert one record
err = table.Insert(ctx, data[1])
// trivial prefix query has two records
it, err = table.Iterator(ctx)
assert.NilError(t, err)
assertIteratorItems(it, 0, 1)
// insert the other records
assert.NilError(t, err)
for i := 2; i < len(data); i++ {
err = table.Insert(ctx, data[i])
assert.NilError(t, err)
}
// let's do a prefix query on the primary key
it, err = table.Iterator(ctx, ormlist.Prefix(uint32(8)))
assert.NilError(t, err)
assertIteratorItems(it, 7, 8, 9)
// let's try a reverse prefix query
it, err = table.Iterator(ctx, ormlist.Prefix(uint32(4)), ormlist.Reverse())
assert.NilError(t, err)
defer it.Close()
assertIteratorItems(it, 2, 1, 0)
// let's try a range query
it, err = table.Iterator(ctx,
ormlist.Start(uint32(4), int64(-1)),
ormlist.End(uint32(7)),
)
assert.NilError(t, err)
defer it.Close()
assertIteratorItems(it, 2, 3, 4, 5, 6)
// and another range query
it, err = table.Iterator(ctx,
ormlist.Start(uint32(5), int64(-3)),
ormlist.End(uint32(8), int64(1), "abc"),
)
assert.NilError(t, err)
defer it.Close()
assertIteratorItems(it, 3, 4, 5, 6, 7, 8)
// now a reverse range query on a different index
strU32Index := table.GetIndex("str,u32")
assert.Assert(t, strU32Index != nil)
it, err = strU32Index.Iterator(ctx,
ormlist.Start("abc"),
ormlist.End("abd"),
ormlist.Reverse(),
)
assertIteratorItems(it, 9, 3, 1, 8, 7, 2, 0)
// another prefix query forwards
it, err = strU32Index.Iterator(ctx, ormlist.Prefix("abe", uint32(7)))
assertIteratorItems(it, 5, 6)
// and backwards
it, err = strU32Index.Iterator(ctx, ormlist.Prefix("abc", uint32(4)), ormlist.Reverse())
assertIteratorItems(it, 2, 0)
// try an unique index
u64StrIndex := table.GetUniqueIndex("u64,str")
assert.Assert(t, u64StrIndex != nil)
found, err := u64StrIndex.Has(ctx, uint64(12), "abc")
assert.NilError(t, err)
assert.Assert(t, found)
var a testpb.ExampleTable
found, err = u64StrIndex.Get(ctx, &a, uint64(12), "abc")
assert.NilError(t, err)
assert.Assert(t, found)
assert.DeepEqual(t, data[8], &a, protocmp.Transform())
// let's try paginating some stuff
// first create a function to test what we got from pagination
assertGotItems := func(items []proto.Message, xs ...int) {
n := len(xs)
assert.Equal(t, n, len(items))
for i := 0; i < n; i++ {
j := xs[i]
//t.Logf("data[%d] %v == %v", j, data[j], items[i])
assert.DeepEqual(t, data[j], items[i], protocmp.Transform())
}
}
// now do some pagination
var items []proto.Message
onItem := func(item proto.Message) {
items = append(items, item)
}
res, err := ormtable.Paginate(table, ctx, &ormtable.PaginationRequest{
PageRequest: &queryv1beta1.PageRequest{
Limit: 4,
CountTotal: true,
}}, onItem)
assert.NilError(t, err)
assert.Assert(t, res != nil)
assert.Equal(t, uint64(10), res.Total)
assert.Assert(t, res.NextKey != nil)
assert.Assert(t, res.HaveMore)
assert.Equal(t, 4, len(res.Cursors))
assertGotItems(items, 0, 1, 2, 3)
// read another page
items = nil
res, err = ormtable.Paginate(table, ctx, &ormtable.PaginationRequest{
PageRequest: &queryv1beta1.PageRequest{
Key: res.NextKey,
Limit: 4,
}}, onItem)
assert.NilError(t, err)
assert.Assert(t, res != nil)
assert.Assert(t, res.NextKey != nil)
assert.Assert(t, res.HaveMore)
assert.Equal(t, 4, len(res.Cursors))
assertGotItems(items, 4, 5, 6, 7)
// and the last page
items = nil
res, err = ormtable.Paginate(table, ctx, &ormtable.PaginationRequest{
PageRequest: &queryv1beta1.PageRequest{
Key: res.NextKey,
Limit: 4,
}}, onItem)
assert.NilError(t, err)
assert.Assert(t, res != nil)
assert.Assert(t, res.NextKey != nil)
assert.Assert(t, !res.HaveMore)
assert.Equal(t, 2, len(res.Cursors))
assertGotItems(items, 8, 9)
// let's go backwards
items = nil
res, err = ormtable.Paginate(table, ctx, &ormtable.PaginationRequest{
PageRequest: &queryv1beta1.PageRequest{
Limit: 2,
CountTotal: true,
Reverse: true,
}}, onItem)
assert.NilError(t, err)
assert.Assert(t, res != nil)
assert.Assert(t, res.NextKey != nil)
assert.Equal(t, uint64(10), res.Total)
assert.Assert(t, res.HaveMore)
assert.Equal(t, 2, len(res.Cursors))
assertGotItems(items, 9, 8)
// a bit more
items = nil
res, err = ormtable.Paginate(table, ctx, &ormtable.PaginationRequest{
PageRequest: &queryv1beta1.PageRequest{
Key: res.NextKey,
Limit: 2,
Reverse: true,
}}, onItem)
assert.NilError(t, err)
assert.Assert(t, res != nil)
assert.Assert(t, res.NextKey != nil)
assert.Assert(t, res.HaveMore)
assert.Equal(t, 2, len(res.Cursors))
assertGotItems(items, 7, 6)
// range query
items = nil
res, err = ormtable.Paginate(table, ctx,
&ormtable.PaginationRequest{
PageRequest: &queryv1beta1.PageRequest{
Limit: 10,
},
},
onItem,
ormlist.Start(uint32(4), int64(-1), "abc"),
ormlist.End(uint32(7), int64(-2), "abe"),
)
assert.NilError(t, err)
assert.Assert(t, res != nil)
assert.Assert(t, !res.HaveMore)
assert.Equal(t, 4, len(res.Cursors))
assertGotItems(items, 2, 3, 4, 5)
// let's try an offset
items = nil
res, err = ormtable.Paginate(table, ctx, &ormtable.PaginationRequest{
PageRequest: &queryv1beta1.PageRequest{
Limit: 2,
CountTotal: true,
Offset: 3,
}}, onItem)
assert.NilError(t, err)
assert.Assert(t, res != nil)
assert.Assert(t, res.NextKey != nil)
assert.Equal(t, uint64(10), res.Total)
assert.Assert(t, res.HaveMore)
assert.Equal(t, 2, len(res.Cursors))
assertGotItems(items, 3, 4)
// and reverse
items = nil
res, err = ormtable.Paginate(table, ctx, &ormtable.PaginationRequest{
PageRequest: &queryv1beta1.PageRequest{
Limit: 3,
CountTotal: true,
Offset: 5,
Reverse: true,
}}, onItem)
assert.NilError(t, err)
assert.Assert(t, res != nil)
assert.Assert(t, res.NextKey != nil)
assert.Equal(t, uint64(10), res.Total)
assert.Assert(t, res.HaveMore)
assert.Equal(t, 3, len(res.Cursors))
assertGotItems(items, 4, 3, 2)
// now an offset that's slightly too big
items = nil
res, err = ormtable.Paginate(table, ctx, &ormtable.PaginationRequest{
PageRequest: &queryv1beta1.PageRequest{
Limit: 1,
CountTotal: true,
Offset: 10,
}}, onItem)
assert.NilError(t, err)
assert.Equal(t, 0, len(items))
assert.Assert(t, !res.HaveMore)
assert.Equal(t, uint64(10), res.Total)
// another offset that's too big
items = nil
res, err = ormtable.Paginate(table, ctx, &ormtable.PaginationRequest{
PageRequest: &queryv1beta1.PageRequest{
Limit: 1,
CountTotal: true,
Offset: 14,
}}, onItem)
assert.NilError(t, err)
assert.Equal(t, 0, len(items))
assert.Assert(t, !res.HaveMore)
assert.Equal(t, uint64(10), res.Total)
// now let's update some things
for i := 0; i < 5; i++ {
data[i].U64 = data[i].U64 * 2
data[i].Bz = []byte(data[i].Str)
err = table.Update(ctx, data[i])
assert.NilError(t, err)
}
it, err = table.Iterator(ctx)
assert.NilError(t, err)
// we should still get everything in the same order
assertIteratorItems(it, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9)
// let's use SAVE_MODE_DEFAULT and add something
data = append(data, &testpb.ExampleTable{U32: 9})
err = table.Save(ctx, data[10])
assert.NilError(t, err)
found, err = table.Get(ctx, &a, uint32(9), int64(0), "")
assert.NilError(t, err)
assert.Assert(t, found)
assert.DeepEqual(t, data[10], &a, protocmp.Transform())
// and update it
data[10].B = true
assert.NilError(t, table.Save(ctx, data[10]))
found, err = table.Get(ctx, &a, uint32(9), int64(0), "")
assert.NilError(t, err)
assert.Assert(t, found)
assert.DeepEqual(t, data[10], &a, protocmp.Transform())
// and iterate
it, err = table.Iterator(ctx)
assert.NilError(t, err)
assertIteratorItems(it, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
// let's export and import JSON and use a read-only backend
buf := &bytes.Buffer{}
readBackend := ormtable.NewReadBackend(ormtable.ReadBackendOptions{
CommitmentStoreReader: backend.CommitmentStoreReader(),
IndexStoreReader: backend.IndexStoreReader(),
})
assert.NilError(t, table.ExportJSON(ormtable.WrapContextDefault(readBackend), buf))
assert.NilError(t, table.ValidateJSON(bytes.NewReader(buf.Bytes())))
store2 := ormtable.WrapContextDefault(testkv.NewSplitMemBackend())
assert.NilError(t, table.ImportJSON(store2, bytes.NewReader(buf.Bytes())))
assertTablesEqual(t, table, ctx, store2)
// let's delete item 5
key5 := []interface{}{uint32(7), int64(-2), "abe"}
err = table.DeleteByKey(ctx, key5...)
assert.NilError(t, err)
// it should be gone
found, err = table.Has(ctx, key5...)
assert.NilError(t, err)
assert.Assert(t, !found)
// and missing from the iterator
it, err = table.Iterator(ctx)
assert.NilError(t, err)
assertIteratorItems(it, 0, 1, 2, 3, 4, 6, 7, 8, 9, 10)
}
func TestRandomTableData(t *testing.T) {
testTable(t, TableDataGen(testutil.GenA, 100).Example().(*TableData))
}
func testTable(t *testing.T, tableData *TableData) {
for _, index := range tableData.table.Indexes() {
indexModel := &IndexModel{
TableData: tableData,
index: index.(TestIndex),
}
sort.Sort(indexModel)
if _, ok := index.(ormtable.UniqueIndex); ok {
testUniqueIndex(t, indexModel)
}
testIndex(t, indexModel)
}
}
func testUniqueIndex(t *testing.T, model *IndexModel) {
index := model.index.(ormtable.UniqueIndex)
t.Logf("testing unique index %T %s", index, index.Fields())
for i := 0; i < len(model.data); i++ {
x := model.data[i]
ks, _, err := index.(ormkv.IndexCodec).EncodeKeyFromMessage(x.ProtoReflect())
assert.NilError(t, err)
values := protoValuesToInterfaces(ks)
found, err := index.Has(model.context, values...)
assert.NilError(t, err)
assert.Assert(t, found)
msg := model.table.MessageType().New().Interface()
found, err = index.Get(model.context, msg, values...)
assert.NilError(t, err)
assert.Assert(t, found)
assert.DeepEqual(t, x, msg, protocmp.Transform())
}
}
func testIndex(t *testing.T, model *IndexModel) {
index := model.index
if index.IsFullyOrdered() {
t.Logf("testing index %T %s", index, index.Fields())
it, err := model.index.Iterator(model.context)
assert.NilError(t, err)
checkIteratorAgainstSlice(t, it, model.data)
it, err = model.index.Iterator(model.context, ormlist.Reverse())
assert.NilError(t, err)
checkIteratorAgainstSlice(t, it, reverseData(model.data))
rapid.Check(t, func(t *rapid.T) {
i := rapid.IntRange(0, len(model.data)-2).Draw(t, "i").(int)
j := rapid.IntRange(i+1, len(model.data)-1).Draw(t, "j").(int)
start, _, err := model.index.(ormkv.IndexCodec).EncodeKeyFromMessage(model.data[i].ProtoReflect())
assert.NilError(t, err)
end, _, err := model.index.(ormkv.IndexCodec).EncodeKeyFromMessage(model.data[j].ProtoReflect())
assert.NilError(t, err)
startVals := protoValuesToInterfaces(start)
endVals := protoValuesToInterfaces(end)
it, err = model.index.Iterator(model.context, ormlist.Start(startVals...), ormlist.End(endVals...))
assert.NilError(t, err)
checkIteratorAgainstSlice(t, it, model.data[i:j+1])
it, err = model.index.Iterator(model.context, ormlist.Start(startVals...), ormlist.End(endVals...), ormlist.Reverse())
assert.NilError(t, err)
checkIteratorAgainstSlice(t, it, reverseData(model.data[i:j+1]))
})
} else {
t.Logf("testing unordered index %T %s", index, index.Fields())
// get all the data
it, err := model.index.Iterator(model.context)
assert.NilError(t, err)
var data2 []proto.Message
for it.Next() {
msg, err := it.GetMessage()
assert.NilError(t, err)
data2 = append(data2, msg)
}
assert.Equal(t, len(model.data), len(data2))
// sort it
model2 := &IndexModel{
TableData: &TableData{
table: model.table,
data: data2,
context: model.context,
},
index: model.index,
}
sort.Sort(model2)
// compare
for i := 0; i < len(data2); i++ {
assert.DeepEqual(t, model.data[i], data2[i], protocmp.Transform())
}
}
}
func reverseData(data []proto.Message) []proto.Message {
n := len(data)
reverse := make([]proto.Message, n)
for i := 0; i < n; i++ {
reverse[n-i-1] = data[i]
}
return reverse
}
func checkIteratorAgainstSlice(t assert.TestingT, iterator ormtable.Iterator, data []proto.Message) {
i := 0
for iterator.Next() {
if i >= len(data) {
for iterator.Next() {
i++
}
t.Log(fmt.Sprintf("too many elements in iterator, len(data) = %d, i = %d", len(data), i))
t.FailNow()
}
msg, err := iterator.GetMessage()
assert.NilError(t, err)
assert.DeepEqual(t, data[i], msg, protocmp.Transform())
i++
}
}
func TableDataGen(elemGen *rapid.Generator, n int) *rapid.Generator {
return rapid.Custom(func(t *rapid.T) *TableData {
prefix := rapid.SliceOfN(rapid.Byte(), 0, 5).Draw(t, "prefix").([]byte)
message := elemGen.Draw(t, "message").(proto.Message)
table, err := ormtable.Build(ormtable.Options{
Prefix: prefix,
MessageType: message.ProtoReflect().Type(),
})
if err != nil {
panic(err)
}
data := make([]proto.Message, n)
store := ormtable.WrapContextDefault(testkv.NewSplitMemBackend())
for i := 0; i < n; {
message = elemGen.Draw(t, fmt.Sprintf("message[%d]", i)).(proto.Message)
err := table.Insert(store, message)
if sdkerrors.IsOf(err, ormerrors.PrimaryKeyConstraintViolation, ormerrors.UniqueKeyViolation) {
continue
} else if err != nil {
panic(err)
}
data[i] = message
i++
}
return &TableData{
data: data,
table: table,
context: store,
}
})
}
type TableData struct {
table ormtable.Table
data []proto.Message
context context.Context
}
type IndexModel struct {
*TableData
index TestIndex
}
// TestIndex exposes methods that all index implementations expose publicly
// but on private structs because they are intended only to be used for testing.
type TestIndex interface {
ormtable.Index
// CompareKeys the two keys against the underlying IndexCodec, returning a
// negative value if key1 is less than key2, 0 if they are equal, and a
// positive value otherwise.
CompareKeys(key1, key2 []protoreflect.Value) int
// IsFullyOrdered returns true if all of the fields in the index are
// considered "well-ordered" in terms of sorted iteration.
IsFullyOrdered() bool
}
func (m *IndexModel) Len() int {
return len(m.data)
}
func (m *IndexModel) Less(i, j int) bool {
is, _, err := m.index.(ormkv.IndexCodec).EncodeKeyFromMessage(m.data[i].ProtoReflect())
if err != nil {
panic(err)
}
js, _, err := m.index.(ormkv.IndexCodec).EncodeKeyFromMessage(m.data[j].ProtoReflect())
if err != nil {
panic(err)
}
return m.index.CompareKeys(is, js) < 0
}
func (m *IndexModel) Swap(i, j int) {
x := m.data[i]
m.data[i] = m.data[j]
m.data[j] = x
}
var _ sort.Interface = &IndexModel{}
func TestJSONExportImport(t *testing.T) {
table, err := ormtable.Build(ormtable.Options{
MessageType: (&testpb.ExampleTable{}).ProtoReflect().Type(),
})
assert.NilError(t, err)
store := ormtable.WrapContextDefault(testkv.NewSplitMemBackend())
for i := 0; i < 100; {
x := testutil.GenA.Example().(proto.Message)
err = table.Insert(store, x)
if sdkerrors.IsOf(err, ormerrors.PrimaryKeyConstraintViolation, ormerrors.UniqueKeyViolation) {
continue
} else {
assert.NilError(t, err)
}
i++
}
buf := &bytes.Buffer{}
assert.NilError(t, table.ExportJSON(store, buf))
assert.NilError(t, table.ValidateJSON(bytes.NewReader(buf.Bytes())))
store2 := ormtable.WrapContextDefault(testkv.NewSplitMemBackend())
assert.NilError(t, table.ImportJSON(store2, bytes.NewReader(buf.Bytes())))
assertTablesEqual(t, table, store, store2)
}
func assertTablesEqual(t assert.TestingT, table ormtable.Table, ctx, ctx2 context.Context) {
it, err := table.Iterator(ctx)
assert.NilError(t, err)
it2, err := table.Iterator(ctx2)
assert.NilError(t, err)
for {
have := it.Next()
have2 := it2.Next()
assert.Equal(t, have, have2)
if !have {
break
}
msg1, err := it.GetMessage()
assert.NilError(t, err)
msg2, err := it.GetMessage()
assert.NilError(t, err)
assert.DeepEqual(t, msg1, msg2, protocmp.Transform())
}
}
func protoValuesToInterfaces(ks []protoreflect.Value) []interface{} {
values := make([]interface{}, len(ks))
for i := 0; i < len(ks); i++ {
values[i] = ks[i].Interface()
}
return values
}

View File

@ -0,0 +1,2 @@
[1,
{"id":"1","x":"foo","y":5}]

View File

@ -0,0 +1,2 @@
[1,
{"id":"2","x":"foo","y":5}]

View File

@ -0,0 +1 @@
[{"id":"1","x":"foo","y":5}]

View File

@ -0,0 +1,31 @@
GET 03000000000000000005
PK testpb.ExampleAutoIncrementTable 5 -> id:5
GET 03808002
SEQ testpb.ExampleAutoIncrementTable 0
GET 03000000000000000001
PK testpb.ExampleAutoIncrementTable 1 -> id:1
ORM INSERT testpb.ExampleAutoIncrementTable id:1 x:"foo" y:5
HAS 0301666f6f
ERR:EOF
SET 03000000000000000001 1203666f6f1805
PK testpb.ExampleAutoIncrementTable 1 -> id:1 x:"foo" y:5
SET 03808002 01
SEQ testpb.ExampleAutoIncrementTable 1
SET 0301666f6f 0000000000000001
UNIQ testpb.ExampleAutoIncrementTable x : "foo" -> 1
GET 03808002 01
SEQ testpb.ExampleAutoIncrementTable 1
ITERATOR 0300 -> 0301
VALID true
KEY 03000000000000000001 1203666f6f1805
PK testpb.ExampleAutoIncrementTable 1 -> id:1 x:"foo" y:5
NEXT
VALID false
ITERATOR 0300 -> 0301
VALID true
KEY 03000000000000000001 1203666f6f1805
PK testpb.ExampleAutoIncrementTable 1 -> id:1 x:"foo" y:5
KEY 03000000000000000001 1203666f6f1805
PK testpb.ExampleAutoIncrementTable 1 -> id:1 x:"foo" y:5
NEXT
VALID false

View File

@ -0,0 +1,923 @@
GET 0100000000047ffffffffffffffe616263
PK testpb.ExampleTable 4/-2/"abc" -> u32:4 str:"abc" i64:-2
ORM INSERT testpb.ExampleTable u32:4 u64:7 str:"abc" i64:-2
HAS 01010000000000000007616263
ERR:EOF
SET 0100000000047ffffffffffffffe616263 1007
PK testpb.ExampleTable 4/-2/"abc" -> u32:4 u64:7 str:"abc" i64:-2
SET 01010000000000000007616263 000000047ffffffffffffffe
UNIQ testpb.ExampleTable u64/str : 7/"abc" -> 4/-2/"abc"
SET 010261626300000000047ffffffffffffffe
IDX testpb.ExampleTable str/u32/i64 : "abc"/4/-2 -> 4/-2/"abc"
SET 01030061626300000000047ffffffffffffffe
IDX testpb.ExampleTable bz/str/u32/i64 : ""/"abc"/4/-2 -> 4/-2/"abc"
ITERATOR 0100 -> 0101
VALID true
KEY 0100000000047ffffffffffffffe616263 1007
PK testpb.ExampleTable 4/-2/"abc" -> u32:4 u64:7 str:"abc" i64:-2
NEXT
VALID false
GET 0100000000047ffffffffffffffe616264
PK testpb.ExampleTable 4/-2/"abd" -> u32:4 str:"abd" i64:-2
ORM INSERT testpb.ExampleTable u32:4 u64:7 str:"abd" i64:-2
HAS 01010000000000000007616264
ERR:EOF
SET 0100000000047ffffffffffffffe616264 1007
PK testpb.ExampleTable 4/-2/"abd" -> u32:4 u64:7 str:"abd" i64:-2
SET 01010000000000000007616264 000000047ffffffffffffffe
UNIQ testpb.ExampleTable u64/str : 7/"abd" -> 4/-2/"abd"
SET 010261626400000000047ffffffffffffffe
IDX testpb.ExampleTable str/u32/i64 : "abd"/4/-2 -> 4/-2/"abd"
SET 01030061626400000000047ffffffffffffffe
IDX testpb.ExampleTable bz/str/u32/i64 : ""/"abd"/4/-2 -> 4/-2/"abd"
ITERATOR 0100 -> 0101
VALID true
KEY 0100000000047ffffffffffffffe616263 1007
PK testpb.ExampleTable 4/-2/"abc" -> u32:4 u64:7 str:"abc" i64:-2
NEXT
VALID true
KEY 0100000000047ffffffffffffffe616264 1007
PK testpb.ExampleTable 4/-2/"abd" -> u32:4 u64:7 str:"abd" i64:-2
NEXT
VALID false
GET 0100000000047fffffffffffffff616263
PK testpb.ExampleTable 4/-1/"abc" -> u32:4 str:"abc" i64:-1
ORM INSERT testpb.ExampleTable u32:4 u64:8 str:"abc" i64:-1
HAS 01010000000000000008616263
ERR:EOF
SET 0100000000047fffffffffffffff616263 1008
PK testpb.ExampleTable 4/-1/"abc" -> u32:4 u64:8 str:"abc" i64:-1
SET 01010000000000000008616263 000000047fffffffffffffff
UNIQ testpb.ExampleTable u64/str : 8/"abc" -> 4/-1/"abc"
SET 010261626300000000047fffffffffffffff
IDX testpb.ExampleTable str/u32/i64 : "abc"/4/-1 -> 4/-1/"abc"
SET 01030061626300000000047fffffffffffffff
IDX testpb.ExampleTable bz/str/u32/i64 : ""/"abc"/4/-1 -> 4/-1/"abc"
GET 0100000000057ffffffffffffffe616264
PK testpb.ExampleTable 5/-2/"abd" -> u32:5 str:"abd" i64:-2
ORM INSERT testpb.ExampleTable u32:5 u64:8 str:"abd" i64:-2
HAS 01010000000000000008616264
ERR:EOF
SET 0100000000057ffffffffffffffe616264 1008
PK testpb.ExampleTable 5/-2/"abd" -> u32:5 u64:8 str:"abd" i64:-2
SET 01010000000000000008616264 000000057ffffffffffffffe
UNIQ testpb.ExampleTable u64/str : 8/"abd" -> 5/-2/"abd"
SET 010261626400000000057ffffffffffffffe
IDX testpb.ExampleTable str/u32/i64 : "abd"/5/-2 -> 5/-2/"abd"
SET 01030061626400000000057ffffffffffffffe
IDX testpb.ExampleTable bz/str/u32/i64 : ""/"abd"/5/-2 -> 5/-2/"abd"
GET 0100000000057ffffffffffffffe616265
PK testpb.ExampleTable 5/-2/"abe" -> u32:5 str:"abe" i64:-2
ORM INSERT testpb.ExampleTable u32:5 u64:9 str:"abe" i64:-2
HAS 01010000000000000009616265
ERR:EOF
SET 0100000000057ffffffffffffffe616265 1009
PK testpb.ExampleTable 5/-2/"abe" -> u32:5 u64:9 str:"abe" i64:-2
SET 01010000000000000009616265 000000057ffffffffffffffe
UNIQ testpb.ExampleTable u64/str : 9/"abe" -> 5/-2/"abe"
SET 010261626500000000057ffffffffffffffe
IDX testpb.ExampleTable str/u32/i64 : "abe"/5/-2 -> 5/-2/"abe"
SET 01030061626500000000057ffffffffffffffe
IDX testpb.ExampleTable bz/str/u32/i64 : ""/"abe"/5/-2 -> 5/-2/"abe"
GET 0100000000077ffffffffffffffe616265
PK testpb.ExampleTable 7/-2/"abe" -> u32:7 str:"abe" i64:-2
ORM INSERT testpb.ExampleTable u32:7 u64:10 str:"abe" i64:-2
HAS 0101000000000000000a616265
ERR:EOF
SET 0100000000077ffffffffffffffe616265 100a
PK testpb.ExampleTable 7/-2/"abe" -> u32:7 u64:10 str:"abe" i64:-2
SET 0101000000000000000a616265 000000077ffffffffffffffe
UNIQ testpb.ExampleTable u64/str : 10/"abe" -> 7/-2/"abe"
SET 010261626500000000077ffffffffffffffe
IDX testpb.ExampleTable str/u32/i64 : "abe"/7/-2 -> 7/-2/"abe"
SET 01030061626500000000077ffffffffffffffe
IDX testpb.ExampleTable bz/str/u32/i64 : ""/"abe"/7/-2 -> 7/-2/"abe"
GET 0100000000077fffffffffffffff616265
PK testpb.ExampleTable 7/-1/"abe" -> u32:7 str:"abe" i64:-1
ORM INSERT testpb.ExampleTable u32:7 u64:11 str:"abe" i64:-1
HAS 0101000000000000000b616265
ERR:EOF
SET 0100000000077fffffffffffffff616265 100b
PK testpb.ExampleTable 7/-1/"abe" -> u32:7 u64:11 str:"abe" i64:-1
SET 0101000000000000000b616265 000000077fffffffffffffff
UNIQ testpb.ExampleTable u64/str : 11/"abe" -> 7/-1/"abe"
SET 010261626500000000077fffffffffffffff
IDX testpb.ExampleTable str/u32/i64 : "abe"/7/-1 -> 7/-1/"abe"
SET 01030061626500000000077fffffffffffffff
IDX testpb.ExampleTable bz/str/u32/i64 : ""/"abe"/7/-1 -> 7/-1/"abe"
GET 0100000000087ffffffffffffffc616263
PK testpb.ExampleTable 8/-4/"abc" -> u32:8 str:"abc" i64:-4
ORM INSERT testpb.ExampleTable u32:8 u64:11 str:"abc" i64:-4
HAS 0101000000000000000b616263
ERR:EOF
SET 0100000000087ffffffffffffffc616263 100b
PK testpb.ExampleTable 8/-4/"abc" -> u32:8 u64:11 str:"abc" i64:-4
SET 0101000000000000000b616263 000000087ffffffffffffffc
UNIQ testpb.ExampleTable u64/str : 11/"abc" -> 8/-4/"abc"
SET 010261626300000000087ffffffffffffffc
IDX testpb.ExampleTable str/u32/i64 : "abc"/8/-4 -> 8/-4/"abc"
SET 01030061626300000000087ffffffffffffffc
IDX testpb.ExampleTable bz/str/u32/i64 : ""/"abc"/8/-4 -> 8/-4/"abc"
GET 0100000000088000000000000001616263
PK testpb.ExampleTable 8/1/"abc" -> u32:8 str:"abc" i64:1
ORM INSERT testpb.ExampleTable u32:8 u64:12 str:"abc" i64:1
HAS 0101000000000000000c616263
ERR:EOF
SET 0100000000088000000000000001616263 100c
PK testpb.ExampleTable 8/1/"abc" -> u32:8 u64:12 str:"abc" i64:1
SET 0101000000000000000c616263 000000088000000000000001
UNIQ testpb.ExampleTable u64/str : 12/"abc" -> 8/1/"abc"
SET 010261626300000000088000000000000001
IDX testpb.ExampleTable str/u32/i64 : "abc"/8/1 -> 8/1/"abc"
SET 01030061626300000000088000000000000001
IDX testpb.ExampleTable bz/str/u32/i64 : ""/"abc"/8/1 -> 8/1/"abc"
GET 0100000000088000000000000001616264
PK testpb.ExampleTable 8/1/"abd" -> u32:8 str:"abd" i64:1
ORM INSERT testpb.ExampleTable u32:8 u64:10 str:"abd" i64:1
HAS 0101000000000000000a616264
ERR:EOF
SET 0100000000088000000000000001616264 100a
PK testpb.ExampleTable 8/1/"abd" -> u32:8 u64:10 str:"abd" i64:1
SET 0101000000000000000a616264 000000088000000000000001
UNIQ testpb.ExampleTable u64/str : 10/"abd" -> 8/1/"abd"
SET 010261626400000000088000000000000001
IDX testpb.ExampleTable str/u32/i64 : "abd"/8/1 -> 8/1/"abd"
SET 01030061626400000000088000000000000001
IDX testpb.ExampleTable bz/str/u32/i64 : ""/"abd"/8/1 -> 8/1/"abd"
ITERATOR 010000000008 -> 010000000009
VALID true
KEY 0100000000087ffffffffffffffc616263 100b
PK testpb.ExampleTable 8/-4/"abc" -> u32:8 u64:11 str:"abc" i64:-4
NEXT
VALID true
KEY 0100000000088000000000000001616263 100c
PK testpb.ExampleTable 8/1/"abc" -> u32:8 u64:12 str:"abc" i64:1
NEXT
VALID true
KEY 0100000000088000000000000001616264 100a
PK testpb.ExampleTable 8/1/"abd" -> u32:8 u64:10 str:"abd" i64:1
NEXT
VALID false
ITERATOR 010000000004 <- 010000000005
VALID true
KEY 0100000000047fffffffffffffff616263 1008
PK testpb.ExampleTable 4/-1/"abc" -> u32:4 u64:8 str:"abc" i64:-1
NEXT
VALID true
KEY 0100000000047ffffffffffffffe616264 1007
PK testpb.ExampleTable 4/-2/"abd" -> u32:4 u64:7 str:"abd" i64:-2
NEXT
VALID true
KEY 0100000000047ffffffffffffffe616263 1007
PK testpb.ExampleTable 4/-2/"abc" -> u32:4 u64:7 str:"abc" i64:-2
NEXT
VALID false
ITERATOR 0100000000047fffffffffffffff -> 010000000008
VALID true
KEY 0100000000047fffffffffffffff616263 1008
PK testpb.ExampleTable 4/-1/"abc" -> u32:4 u64:8 str:"abc" i64:-1
NEXT
VALID true
KEY 0100000000057ffffffffffffffe616264 1008
PK testpb.ExampleTable 5/-2/"abd" -> u32:5 u64:8 str:"abd" i64:-2
NEXT
VALID true
KEY 0100000000057ffffffffffffffe616265 1009
PK testpb.ExampleTable 5/-2/"abe" -> u32:5 u64:9 str:"abe" i64:-2
NEXT
VALID true
KEY 0100000000077ffffffffffffffe616265 100a
PK testpb.ExampleTable 7/-2/"abe" -> u32:7 u64:10 str:"abe" i64:-2
NEXT
VALID true
KEY 0100000000077fffffffffffffff616265 100b
PK testpb.ExampleTable 7/-1/"abe" -> u32:7 u64:11 str:"abe" i64:-1
NEXT
VALID false
ITERATOR 0100000000057ffffffffffffffd -> 010000000008800000000000000161626300
VALID true
KEY 0100000000057ffffffffffffffe616264 1008
PK testpb.ExampleTable 5/-2/"abd" -> u32:5 u64:8 str:"abd" i64:-2
NEXT
VALID true
KEY 0100000000057ffffffffffffffe616265 1009
PK testpb.ExampleTable 5/-2/"abe" -> u32:5 u64:9 str:"abe" i64:-2
NEXT
VALID true
KEY 0100000000077ffffffffffffffe616265 100a
PK testpb.ExampleTable 7/-2/"abe" -> u32:7 u64:10 str:"abe" i64:-2
NEXT
VALID true
KEY 0100000000077fffffffffffffff616265 100b
PK testpb.ExampleTable 7/-1/"abe" -> u32:7 u64:11 str:"abe" i64:-1
NEXT
VALID true
KEY 0100000000087ffffffffffffffc616263 100b
PK testpb.ExampleTable 8/-4/"abc" -> u32:8 u64:11 str:"abc" i64:-4
NEXT
VALID true
KEY 0100000000088000000000000001616263 100c
PK testpb.ExampleTable 8/1/"abc" -> u32:8 u64:12 str:"abc" i64:1
NEXT
VALID false
ITERATOR 010261626300 <- 010261626401
VALID true
KEY 010261626400000000088000000000000001
IDX testpb.ExampleTable str/u32/i64 : "abd"/8/1 -> 8/1/"abd"
GET 0100000000088000000000000001616264 100a
PK testpb.ExampleTable 8/1/"abd" -> u32:8 u64:10 str:"abd" i64:1
NEXT
VALID true
KEY 010261626400000000057ffffffffffffffe
IDX testpb.ExampleTable str/u32/i64 : "abd"/5/-2 -> 5/-2/"abd"
GET 0100000000057ffffffffffffffe616264 1008
PK testpb.ExampleTable 5/-2/"abd" -> u32:5 u64:8 str:"abd" i64:-2
NEXT
VALID true
KEY 010261626400000000047ffffffffffffffe
IDX testpb.ExampleTable str/u32/i64 : "abd"/4/-2 -> 4/-2/"abd"
GET 0100000000047ffffffffffffffe616264 1007
PK testpb.ExampleTable 4/-2/"abd" -> u32:4 u64:7 str:"abd" i64:-2
NEXT
VALID true
KEY 010261626300000000088000000000000001
IDX testpb.ExampleTable str/u32/i64 : "abc"/8/1 -> 8/1/"abc"
GET 0100000000088000000000000001616263 100c
PK testpb.ExampleTable 8/1/"abc" -> u32:8 u64:12 str:"abc" i64:1
NEXT
VALID true
KEY 010261626300000000087ffffffffffffffc
IDX testpb.ExampleTable str/u32/i64 : "abc"/8/-4 -> 8/-4/"abc"
GET 0100000000087ffffffffffffffc616263 100b
PK testpb.ExampleTable 8/-4/"abc" -> u32:8 u64:11 str:"abc" i64:-4
NEXT
VALID true
KEY 010261626300000000047fffffffffffffff
IDX testpb.ExampleTable str/u32/i64 : "abc"/4/-1 -> 4/-1/"abc"
GET 0100000000047fffffffffffffff616263 1008
PK testpb.ExampleTable 4/-1/"abc" -> u32:4 u64:8 str:"abc" i64:-1
NEXT
VALID true
KEY 010261626300000000047ffffffffffffffe
IDX testpb.ExampleTable str/u32/i64 : "abc"/4/-2 -> 4/-2/"abc"
GET 0100000000047ffffffffffffffe616263 1007
PK testpb.ExampleTable 4/-2/"abc" -> u32:4 u64:7 str:"abc" i64:-2
NEXT
VALID false
ITERATOR 01026162650000000007 -> 01026162650000000008
VALID true
KEY 010261626500000000077ffffffffffffffe
IDX testpb.ExampleTable str/u32/i64 : "abe"/7/-2 -> 7/-2/"abe"
GET 0100000000077ffffffffffffffe616265 100a
PK testpb.ExampleTable 7/-2/"abe" -> u32:7 u64:10 str:"abe" i64:-2
NEXT
VALID true
KEY 010261626500000000077fffffffffffffff
IDX testpb.ExampleTable str/u32/i64 : "abe"/7/-1 -> 7/-1/"abe"
GET 0100000000077fffffffffffffff616265 100b
PK testpb.ExampleTable 7/-1/"abe" -> u32:7 u64:11 str:"abe" i64:-1
NEXT
VALID false
ITERATOR 01026162630000000004 <- 01026162630000000005
VALID true
KEY 010261626300000000047fffffffffffffff
IDX testpb.ExampleTable str/u32/i64 : "abc"/4/-1 -> 4/-1/"abc"
GET 0100000000047fffffffffffffff616263 1008
PK testpb.ExampleTable 4/-1/"abc" -> u32:4 u64:8 str:"abc" i64:-1
NEXT
VALID true
KEY 010261626300000000047ffffffffffffffe
IDX testpb.ExampleTable str/u32/i64 : "abc"/4/-2 -> 4/-2/"abc"
GET 0100000000047ffffffffffffffe616263 1007
PK testpb.ExampleTable 4/-2/"abc" -> u32:4 u64:7 str:"abc" i64:-2
NEXT
VALID false
HAS 0101000000000000000c616263
ERR:EOF
GET 0101000000000000000c616263 000000088000000000000001
UNIQ testpb.ExampleTable u64/str : 12/"abc" -> 8/1/"abc"
GET 0100000000088000000000000001616263 100c
PK testpb.ExampleTable 8/1/"abc" -> u32:8 u64:12 str:"abc" i64:1
ITERATOR 0100 -> 0101
VALID true
KEY 0100000000047ffffffffffffffe616263 1007
PK testpb.ExampleTable 4/-2/"abc" -> u32:4 u64:7 str:"abc" i64:-2
KEY 0100000000047ffffffffffffffe616263 1007
PK testpb.ExampleTable 4/-2/"abc" -> u32:4 u64:7 str:"abc" i64:-2
NEXT
VALID true
KEY 0100000000047ffffffffffffffe616264 1007
PK testpb.ExampleTable 4/-2/"abd" -> u32:4 u64:7 str:"abd" i64:-2
KEY 0100000000047ffffffffffffffe616264 1007
PK testpb.ExampleTable 4/-2/"abd" -> u32:4 u64:7 str:"abd" i64:-2
NEXT
VALID true
KEY 0100000000047fffffffffffffff616263 1008
PK testpb.ExampleTable 4/-1/"abc" -> u32:4 u64:8 str:"abc" i64:-1
KEY 0100000000047fffffffffffffff616263 1008
PK testpb.ExampleTable 4/-1/"abc" -> u32:4 u64:8 str:"abc" i64:-1
NEXT
VALID true
KEY 0100000000057ffffffffffffffe616264 1008
PK testpb.ExampleTable 5/-2/"abd" -> u32:5 u64:8 str:"abd" i64:-2
KEY 0100000000057ffffffffffffffe616264 1008
PK testpb.ExampleTable 5/-2/"abd" -> u32:5 u64:8 str:"abd" i64:-2
NEXT
VALID true
NEXT
VALID true
NEXT
VALID true
NEXT
VALID true
NEXT
VALID true
NEXT
VALID true
NEXT
VALID false
CLOSE
ITERATOR 0100000000057ffffffffffffffe61626400 -> 0101
VALID true
KEY 0100000000057ffffffffffffffe616265 1009
PK testpb.ExampleTable 5/-2/"abe" -> u32:5 u64:9 str:"abe" i64:-2
KEY 0100000000057ffffffffffffffe616265 1009
PK testpb.ExampleTable 5/-2/"abe" -> u32:5 u64:9 str:"abe" i64:-2
NEXT
VALID true
KEY 0100000000077ffffffffffffffe616265 100a
PK testpb.ExampleTable 7/-2/"abe" -> u32:7 u64:10 str:"abe" i64:-2
KEY 0100000000077ffffffffffffffe616265 100a
PK testpb.ExampleTable 7/-2/"abe" -> u32:7 u64:10 str:"abe" i64:-2
NEXT
VALID true
KEY 0100000000077fffffffffffffff616265 100b
PK testpb.ExampleTable 7/-1/"abe" -> u32:7 u64:11 str:"abe" i64:-1
KEY 0100000000077fffffffffffffff616265 100b
PK testpb.ExampleTable 7/-1/"abe" -> u32:7 u64:11 str:"abe" i64:-1
NEXT
VALID true
KEY 0100000000087ffffffffffffffc616263 100b
PK testpb.ExampleTable 8/-4/"abc" -> u32:8 u64:11 str:"abc" i64:-4
KEY 0100000000087ffffffffffffffc616263 100b
PK testpb.ExampleTable 8/-4/"abc" -> u32:8 u64:11 str:"abc" i64:-4
NEXT
VALID true
CLOSE
ITERATOR 0100000000087ffffffffffffffc61626300 -> 0101
VALID true
KEY 0100000000088000000000000001616263 100c
PK testpb.ExampleTable 8/1/"abc" -> u32:8 u64:12 str:"abc" i64:1
KEY 0100000000088000000000000001616263 100c
PK testpb.ExampleTable 8/1/"abc" -> u32:8 u64:12 str:"abc" i64:1
NEXT
VALID true
KEY 0100000000088000000000000001616264 100a
PK testpb.ExampleTable 8/1/"abd" -> u32:8 u64:10 str:"abd" i64:1
KEY 0100000000088000000000000001616264 100a
PK testpb.ExampleTable 8/1/"abd" -> u32:8 u64:10 str:"abd" i64:1
NEXT
VALID false
CLOSE
ITERATOR 0100 <- 0101
VALID true
KEY 0100000000088000000000000001616264 100a
PK testpb.ExampleTable 8/1/"abd" -> u32:8 u64:10 str:"abd" i64:1
KEY 0100000000088000000000000001616264 100a
PK testpb.ExampleTable 8/1/"abd" -> u32:8 u64:10 str:"abd" i64:1
NEXT
VALID true
KEY 0100000000088000000000000001616263 100c
PK testpb.ExampleTable 8/1/"abc" -> u32:8 u64:12 str:"abc" i64:1
KEY 0100000000088000000000000001616263 100c
PK testpb.ExampleTable 8/1/"abc" -> u32:8 u64:12 str:"abc" i64:1
NEXT
VALID true
NEXT
VALID true
NEXT
VALID true
NEXT
VALID true
NEXT
VALID true
NEXT
VALID true
NEXT
VALID true
NEXT
VALID true
NEXT
VALID false
CLOSE
ITERATOR 0100 <- 0100000000088000000000000001616263
VALID true
KEY 0100000000087ffffffffffffffc616263 100b
PK testpb.ExampleTable 8/-4/"abc" -> u32:8 u64:11 str:"abc" i64:-4
KEY 0100000000087ffffffffffffffc616263 100b
PK testpb.ExampleTable 8/-4/"abc" -> u32:8 u64:11 str:"abc" i64:-4
NEXT
VALID true
KEY 0100000000077fffffffffffffff616265 100b
PK testpb.ExampleTable 7/-1/"abe" -> u32:7 u64:11 str:"abe" i64:-1
KEY 0100000000077fffffffffffffff616265 100b
PK testpb.ExampleTable 7/-1/"abe" -> u32:7 u64:11 str:"abe" i64:-1
NEXT
VALID true
CLOSE
ITERATOR 0100000000047fffffffffffffff616263 -> 0100000000077ffffffffffffffe61626500
VALID true
KEY 0100000000047fffffffffffffff616263 1008
PK testpb.ExampleTable 4/-1/"abc" -> u32:4 u64:8 str:"abc" i64:-1
KEY 0100000000047fffffffffffffff616263 1008
PK testpb.ExampleTable 4/-1/"abc" -> u32:4 u64:8 str:"abc" i64:-1
NEXT
VALID true
KEY 0100000000057ffffffffffffffe616264 1008
PK testpb.ExampleTable 5/-2/"abd" -> u32:5 u64:8 str:"abd" i64:-2
KEY 0100000000057ffffffffffffffe616264 1008
PK testpb.ExampleTable 5/-2/"abd" -> u32:5 u64:8 str:"abd" i64:-2
NEXT
VALID true
KEY 0100000000057ffffffffffffffe616265 1009
PK testpb.ExampleTable 5/-2/"abe" -> u32:5 u64:9 str:"abe" i64:-2
KEY 0100000000057ffffffffffffffe616265 1009
PK testpb.ExampleTable 5/-2/"abe" -> u32:5 u64:9 str:"abe" i64:-2
NEXT
VALID true
KEY 0100000000077ffffffffffffffe616265 100a
PK testpb.ExampleTable 7/-2/"abe" -> u32:7 u64:10 str:"abe" i64:-2
KEY 0100000000077ffffffffffffffe616265 100a
PK testpb.ExampleTable 7/-2/"abe" -> u32:7 u64:10 str:"abe" i64:-2
NEXT
VALID false
CLOSE
ITERATOR 0100 -> 0101
VALID true
NEXT
VALID true
NEXT
VALID true
NEXT
VALID true
KEY 0100000000057ffffffffffffffe616264 1008
PK testpb.ExampleTable 5/-2/"abd" -> u32:5 u64:8 str:"abd" i64:-2
KEY 0100000000057ffffffffffffffe616264 1008
PK testpb.ExampleTable 5/-2/"abd" -> u32:5 u64:8 str:"abd" i64:-2
NEXT
VALID true
KEY 0100000000057ffffffffffffffe616265 1009
PK testpb.ExampleTable 5/-2/"abe" -> u32:5 u64:9 str:"abe" i64:-2
KEY 0100000000057ffffffffffffffe616265 1009
PK testpb.ExampleTable 5/-2/"abe" -> u32:5 u64:9 str:"abe" i64:-2
NEXT
VALID true
NEXT
VALID true
NEXT
VALID true
NEXT
VALID true
NEXT
VALID true
NEXT
VALID false
CLOSE
ITERATOR 0100 <- 0101
VALID true
NEXT
VALID true
NEXT
VALID true
NEXT
VALID true
NEXT
VALID true
NEXT
VALID true
KEY 0100000000057ffffffffffffffe616265 1009
PK testpb.ExampleTable 5/-2/"abe" -> u32:5 u64:9 str:"abe" i64:-2
KEY 0100000000057ffffffffffffffe616265 1009
PK testpb.ExampleTable 5/-2/"abe" -> u32:5 u64:9 str:"abe" i64:-2
NEXT
VALID true
KEY 0100000000057ffffffffffffffe616264 1008
PK testpb.ExampleTable 5/-2/"abd" -> u32:5 u64:8 str:"abd" i64:-2
KEY 0100000000057ffffffffffffffe616264 1008
PK testpb.ExampleTable 5/-2/"abd" -> u32:5 u64:8 str:"abd" i64:-2
NEXT
VALID true
KEY 0100000000047fffffffffffffff616263 1008
PK testpb.ExampleTable 4/-1/"abc" -> u32:4 u64:8 str:"abc" i64:-1
KEY 0100000000047fffffffffffffff616263 1008
PK testpb.ExampleTable 4/-1/"abc" -> u32:4 u64:8 str:"abc" i64:-1
NEXT
VALID true
NEXT
VALID true
NEXT
VALID false
CLOSE
ITERATOR 0100 -> 0101
VALID true
NEXT
VALID true
NEXT
VALID true
NEXT
VALID true
NEXT
VALID true
NEXT
VALID true
NEXT
VALID true
NEXT
VALID true
NEXT
VALID true
NEXT
VALID true
NEXT
VALID false
CLOSE
ITERATOR 0100 -> 0101
VALID true
NEXT
VALID true
NEXT
VALID true
NEXT
VALID true
NEXT
VALID true
NEXT
VALID true
NEXT
VALID true
NEXT
VALID true
NEXT
VALID true
NEXT
VALID true
NEXT
VALID false
CLOSE
GET 0100000000047ffffffffffffffe616263 1007
PK testpb.ExampleTable 4/-2/"abc" -> u32:4 u64:7 str:"abc" i64:-2
ORM UPDATE testpb.ExampleTable u32:4 u64:7 str:"abc" i64:-2 -> u32:4 u64:14 str:"abc" bz:"abc" i64:-2
HAS 0101000000000000000e616263
ERR:EOF
SET 0100000000047ffffffffffffffe616263 100e2203616263
PK testpb.ExampleTable 4/-2/"abc" -> u32:4 u64:14 str:"abc" bz:"abc" i64:-2
DEL 01010000000000000007616263
DEL ERR:EOF
SET 0101000000000000000e616263 000000047ffffffffffffffe
UNIQ testpb.ExampleTable u64/str : 14/"abc" -> 4/-2/"abc"
DEL 01030061626300000000047ffffffffffffffe
DEL IDX testpb.ExampleTable bz/str/u32/i64 : ""/"abc"/4/-2 -> 4/-2/"abc"
SET 01030361626361626300000000047ffffffffffffffe
IDX testpb.ExampleTable bz/str/u32/i64 : "YWJj"/"abc"/4/-2 -> 4/-2/"abc"
GET 0100000000047ffffffffffffffe616264 1007
PK testpb.ExampleTable 4/-2/"abd" -> u32:4 u64:7 str:"abd" i64:-2
ORM UPDATE testpb.ExampleTable u32:4 u64:7 str:"abd" i64:-2 -> u32:4 u64:14 str:"abd" bz:"abd" i64:-2
HAS 0101000000000000000e616264
ERR:EOF
SET 0100000000047ffffffffffffffe616264 100e2203616264
PK testpb.ExampleTable 4/-2/"abd" -> u32:4 u64:14 str:"abd" bz:"abd" i64:-2
DEL 01010000000000000007616264
DEL ERR:EOF
SET 0101000000000000000e616264 000000047ffffffffffffffe
UNIQ testpb.ExampleTable u64/str : 14/"abd" -> 4/-2/"abd"
DEL 01030061626400000000047ffffffffffffffe
DEL IDX testpb.ExampleTable bz/str/u32/i64 : ""/"abd"/4/-2 -> 4/-2/"abd"
SET 01030361626461626400000000047ffffffffffffffe
IDX testpb.ExampleTable bz/str/u32/i64 : "YWJk"/"abd"/4/-2 -> 4/-2/"abd"
GET 0100000000047fffffffffffffff616263 1008
PK testpb.ExampleTable 4/-1/"abc" -> u32:4 u64:8 str:"abc" i64:-1
ORM UPDATE testpb.ExampleTable u32:4 u64:8 str:"abc" i64:-1 -> u32:4 u64:16 str:"abc" bz:"abc" i64:-1
HAS 01010000000000000010616263
ERR:EOF
SET 0100000000047fffffffffffffff616263 10102203616263
PK testpb.ExampleTable 4/-1/"abc" -> u32:4 u64:16 str:"abc" bz:"abc" i64:-1
DEL 01010000000000000008616263
DEL ERR:EOF
SET 01010000000000000010616263 000000047fffffffffffffff
UNIQ testpb.ExampleTable u64/str : 16/"abc" -> 4/-1/"abc"
DEL 01030061626300000000047fffffffffffffff
DEL IDX testpb.ExampleTable bz/str/u32/i64 : ""/"abc"/4/-1 -> 4/-1/"abc"
SET 01030361626361626300000000047fffffffffffffff
IDX testpb.ExampleTable bz/str/u32/i64 : "YWJj"/"abc"/4/-1 -> 4/-1/"abc"
GET 0100000000057ffffffffffffffe616264 1008
PK testpb.ExampleTable 5/-2/"abd" -> u32:5 u64:8 str:"abd" i64:-2
ORM UPDATE testpb.ExampleTable u32:5 u64:8 str:"abd" i64:-2 -> u32:5 u64:16 str:"abd" bz:"abd" i64:-2
HAS 01010000000000000010616264
ERR:EOF
SET 0100000000057ffffffffffffffe616264 10102203616264
PK testpb.ExampleTable 5/-2/"abd" -> u32:5 u64:16 str:"abd" bz:"abd" i64:-2
DEL 01010000000000000008616264
DEL ERR:EOF
SET 01010000000000000010616264 000000057ffffffffffffffe
UNIQ testpb.ExampleTable u64/str : 16/"abd" -> 5/-2/"abd"
DEL 01030061626400000000057ffffffffffffffe
DEL IDX testpb.ExampleTable bz/str/u32/i64 : ""/"abd"/5/-2 -> 5/-2/"abd"
SET 01030361626461626400000000057ffffffffffffffe
IDX testpb.ExampleTable bz/str/u32/i64 : "YWJk"/"abd"/5/-2 -> 5/-2/"abd"
GET 0100000000057ffffffffffffffe616265 1009
PK testpb.ExampleTable 5/-2/"abe" -> u32:5 u64:9 str:"abe" i64:-2
ORM UPDATE testpb.ExampleTable u32:5 u64:9 str:"abe" i64:-2 -> u32:5 u64:18 str:"abe" bz:"abe" i64:-2
HAS 01010000000000000012616265
ERR:EOF
SET 0100000000057ffffffffffffffe616265 10122203616265
PK testpb.ExampleTable 5/-2/"abe" -> u32:5 u64:18 str:"abe" bz:"abe" i64:-2
DEL 01010000000000000009616265
DEL ERR:EOF
SET 01010000000000000012616265 000000057ffffffffffffffe
UNIQ testpb.ExampleTable u64/str : 18/"abe" -> 5/-2/"abe"
DEL 01030061626500000000057ffffffffffffffe
DEL IDX testpb.ExampleTable bz/str/u32/i64 : ""/"abe"/5/-2 -> 5/-2/"abe"
SET 01030361626561626500000000057ffffffffffffffe
IDX testpb.ExampleTable bz/str/u32/i64 : "YWJl"/"abe"/5/-2 -> 5/-2/"abe"
ITERATOR 0100 -> 0101
VALID true
KEY 0100000000047ffffffffffffffe616263 100e2203616263
PK testpb.ExampleTable 4/-2/"abc" -> u32:4 u64:14 str:"abc" bz:"abc" i64:-2
NEXT
VALID true
KEY 0100000000047ffffffffffffffe616264 100e2203616264
PK testpb.ExampleTable 4/-2/"abd" -> u32:4 u64:14 str:"abd" bz:"abd" i64:-2
NEXT
VALID true
KEY 0100000000047fffffffffffffff616263 10102203616263
PK testpb.ExampleTable 4/-1/"abc" -> u32:4 u64:16 str:"abc" bz:"abc" i64:-1
NEXT
VALID true
KEY 0100000000057ffffffffffffffe616264 10102203616264
PK testpb.ExampleTable 5/-2/"abd" -> u32:5 u64:16 str:"abd" bz:"abd" i64:-2
NEXT
VALID true
KEY 0100000000057ffffffffffffffe616265 10122203616265
PK testpb.ExampleTable 5/-2/"abe" -> u32:5 u64:18 str:"abe" bz:"abe" i64:-2
NEXT
VALID true
KEY 0100000000077ffffffffffffffe616265 100a
PK testpb.ExampleTable 7/-2/"abe" -> u32:7 u64:10 str:"abe" i64:-2
NEXT
VALID true
KEY 0100000000077fffffffffffffff616265 100b
PK testpb.ExampleTable 7/-1/"abe" -> u32:7 u64:11 str:"abe" i64:-1
NEXT
VALID true
KEY 0100000000087ffffffffffffffc616263 100b
PK testpb.ExampleTable 8/-4/"abc" -> u32:8 u64:11 str:"abc" i64:-4
NEXT
VALID true
KEY 0100000000088000000000000001616263 100c
PK testpb.ExampleTable 8/1/"abc" -> u32:8 u64:12 str:"abc" i64:1
NEXT
VALID true
KEY 0100000000088000000000000001616264 100a
PK testpb.ExampleTable 8/1/"abd" -> u32:8 u64:10 str:"abd" i64:1
NEXT
VALID false
GET 0100000000098000000000000000
PK testpb.ExampleTable 9/0/"" -> u32:9
ORM INSERT testpb.ExampleTable u32:9
HAS 01010000000000000000
ERR:EOF
SET 0100000000098000000000000000
PK testpb.ExampleTable 9/0/"" -> u32:9
SET 01010000000000000000 000000098000000000000000
UNIQ testpb.ExampleTable u64/str : 0/"" -> 9/0/""
SET 010200000000098000000000000000
IDX testpb.ExampleTable str/u32/i64 : ""/9/0 -> 9/0/""
SET 01030000000000098000000000000000
IDX testpb.ExampleTable bz/str/u32/i64 : ""/""/9/0 -> 9/0/""
GET 0100000000098000000000000000
PK testpb.ExampleTable 9/0/"" -> u32:9
GET 0100000000098000000000000000
PK testpb.ExampleTable 9/0/"" -> u32:9
ORM UPDATE testpb.ExampleTable u32:9 -> u32:9 b:true
SET 0100000000098000000000000000 7801
PK testpb.ExampleTable 9/0/"" -> u32:9 b:true
GET 0100000000098000000000000000 7801
PK testpb.ExampleTable 9/0/"" -> u32:9 b:true
ITERATOR 0100 -> 0101
VALID true
KEY 0100000000047ffffffffffffffe616263 100e2203616263
PK testpb.ExampleTable 4/-2/"abc" -> u32:4 u64:14 str:"abc" bz:"abc" i64:-2
NEXT
VALID true
KEY 0100000000047ffffffffffffffe616264 100e2203616264
PK testpb.ExampleTable 4/-2/"abd" -> u32:4 u64:14 str:"abd" bz:"abd" i64:-2
NEXT
VALID true
KEY 0100000000047fffffffffffffff616263 10102203616263
PK testpb.ExampleTable 4/-1/"abc" -> u32:4 u64:16 str:"abc" bz:"abc" i64:-1
NEXT
VALID true
KEY 0100000000057ffffffffffffffe616264 10102203616264
PK testpb.ExampleTable 5/-2/"abd" -> u32:5 u64:16 str:"abd" bz:"abd" i64:-2
NEXT
VALID true
KEY 0100000000057ffffffffffffffe616265 10122203616265
PK testpb.ExampleTable 5/-2/"abe" -> u32:5 u64:18 str:"abe" bz:"abe" i64:-2
NEXT
VALID true
KEY 0100000000077ffffffffffffffe616265 100a
PK testpb.ExampleTable 7/-2/"abe" -> u32:7 u64:10 str:"abe" i64:-2
NEXT
VALID true
KEY 0100000000077fffffffffffffff616265 100b
PK testpb.ExampleTable 7/-1/"abe" -> u32:7 u64:11 str:"abe" i64:-1
NEXT
VALID true
KEY 0100000000087ffffffffffffffc616263 100b
PK testpb.ExampleTable 8/-4/"abc" -> u32:8 u64:11 str:"abc" i64:-4
NEXT
VALID true
KEY 0100000000088000000000000001616263 100c
PK testpb.ExampleTable 8/1/"abc" -> u32:8 u64:12 str:"abc" i64:1
NEXT
VALID true
KEY 0100000000088000000000000001616264 100a
PK testpb.ExampleTable 8/1/"abd" -> u32:8 u64:10 str:"abd" i64:1
NEXT
VALID true
KEY 0100000000098000000000000000 7801
PK testpb.ExampleTable 9/0/"" -> u32:9 b:true
NEXT
VALID false
ITERATOR 0100 -> 0101
VALID true
KEY 0100000000047ffffffffffffffe616263 100e2203616263
PK testpb.ExampleTable 4/-2/"abc" -> u32:4 u64:14 str:"abc" bz:"abc" i64:-2
NEXT
VALID true
KEY 0100000000047ffffffffffffffe616264 100e2203616264
PK testpb.ExampleTable 4/-2/"abd" -> u32:4 u64:14 str:"abd" bz:"abd" i64:-2
NEXT
VALID true
KEY 0100000000047fffffffffffffff616263 10102203616263
PK testpb.ExampleTable 4/-1/"abc" -> u32:4 u64:16 str:"abc" bz:"abc" i64:-1
NEXT
VALID true
KEY 0100000000057ffffffffffffffe616264 10102203616264
PK testpb.ExampleTable 5/-2/"abd" -> u32:5 u64:16 str:"abd" bz:"abd" i64:-2
NEXT
VALID true
KEY 0100000000057ffffffffffffffe616265 10122203616265
PK testpb.ExampleTable 5/-2/"abe" -> u32:5 u64:18 str:"abe" bz:"abe" i64:-2
NEXT
VALID true
KEY 0100000000077ffffffffffffffe616265 100a
PK testpb.ExampleTable 7/-2/"abe" -> u32:7 u64:10 str:"abe" i64:-2
NEXT
VALID true
KEY 0100000000077fffffffffffffff616265 100b
PK testpb.ExampleTable 7/-1/"abe" -> u32:7 u64:11 str:"abe" i64:-1
NEXT
VALID true
KEY 0100000000087ffffffffffffffc616263 100b
PK testpb.ExampleTable 8/-4/"abc" -> u32:8 u64:11 str:"abc" i64:-4
NEXT
VALID true
KEY 0100000000088000000000000001616263 100c
PK testpb.ExampleTable 8/1/"abc" -> u32:8 u64:12 str:"abc" i64:1
NEXT
VALID true
KEY 0100000000088000000000000001616264 100a
PK testpb.ExampleTable 8/1/"abd" -> u32:8 u64:10 str:"abd" i64:1
NEXT
VALID true
KEY 0100000000098000000000000000 7801
PK testpb.ExampleTable 9/0/"" -> u32:9 b:true
NEXT
VALID false
ITERATOR 0100 -> 0101
VALID true
KEY 0100000000047ffffffffffffffe616263 100e2203616263
PK testpb.ExampleTable 4/-2/"abc" -> u32:4 u64:14 str:"abc" bz:"abc" i64:-2
KEY 0100000000047ffffffffffffffe616263 100e2203616263
PK testpb.ExampleTable 4/-2/"abc" -> u32:4 u64:14 str:"abc" bz:"abc" i64:-2
NEXT
VALID true
KEY 0100000000047ffffffffffffffe616264 100e2203616264
PK testpb.ExampleTable 4/-2/"abd" -> u32:4 u64:14 str:"abd" bz:"abd" i64:-2
KEY 0100000000047ffffffffffffffe616264 100e2203616264
PK testpb.ExampleTable 4/-2/"abd" -> u32:4 u64:14 str:"abd" bz:"abd" i64:-2
NEXT
VALID true
KEY 0100000000047fffffffffffffff616263 10102203616263
PK testpb.ExampleTable 4/-1/"abc" -> u32:4 u64:16 str:"abc" bz:"abc" i64:-1
KEY 0100000000047fffffffffffffff616263 10102203616263
PK testpb.ExampleTable 4/-1/"abc" -> u32:4 u64:16 str:"abc" bz:"abc" i64:-1
NEXT
VALID true
KEY 0100000000057ffffffffffffffe616264 10102203616264
PK testpb.ExampleTable 5/-2/"abd" -> u32:5 u64:16 str:"abd" bz:"abd" i64:-2
KEY 0100000000057ffffffffffffffe616264 10102203616264
PK testpb.ExampleTable 5/-2/"abd" -> u32:5 u64:16 str:"abd" bz:"abd" i64:-2
NEXT
VALID true
KEY 0100000000057ffffffffffffffe616265 10122203616265
PK testpb.ExampleTable 5/-2/"abe" -> u32:5 u64:18 str:"abe" bz:"abe" i64:-2
KEY 0100000000057ffffffffffffffe616265 10122203616265
PK testpb.ExampleTable 5/-2/"abe" -> u32:5 u64:18 str:"abe" bz:"abe" i64:-2
NEXT
VALID true
KEY 0100000000077ffffffffffffffe616265 100a
PK testpb.ExampleTable 7/-2/"abe" -> u32:7 u64:10 str:"abe" i64:-2
KEY 0100000000077ffffffffffffffe616265 100a
PK testpb.ExampleTable 7/-2/"abe" -> u32:7 u64:10 str:"abe" i64:-2
NEXT
VALID true
KEY 0100000000077fffffffffffffff616265 100b
PK testpb.ExampleTable 7/-1/"abe" -> u32:7 u64:11 str:"abe" i64:-1
KEY 0100000000077fffffffffffffff616265 100b
PK testpb.ExampleTable 7/-1/"abe" -> u32:7 u64:11 str:"abe" i64:-1
NEXT
VALID true
KEY 0100000000087ffffffffffffffc616263 100b
PK testpb.ExampleTable 8/-4/"abc" -> u32:8 u64:11 str:"abc" i64:-4
KEY 0100000000087ffffffffffffffc616263 100b
PK testpb.ExampleTable 8/-4/"abc" -> u32:8 u64:11 str:"abc" i64:-4
NEXT
VALID true
KEY 0100000000088000000000000001616263 100c
PK testpb.ExampleTable 8/1/"abc" -> u32:8 u64:12 str:"abc" i64:1
KEY 0100000000088000000000000001616263 100c
PK testpb.ExampleTable 8/1/"abc" -> u32:8 u64:12 str:"abc" i64:1
NEXT
VALID true
KEY 0100000000088000000000000001616264 100a
PK testpb.ExampleTable 8/1/"abd" -> u32:8 u64:10 str:"abd" i64:1
KEY 0100000000088000000000000001616264 100a
PK testpb.ExampleTable 8/1/"abd" -> u32:8 u64:10 str:"abd" i64:1
NEXT
VALID true
KEY 0100000000098000000000000000 7801
PK testpb.ExampleTable 9/0/"" -> u32:9 b:true
KEY 0100000000098000000000000000 7801
PK testpb.ExampleTable 9/0/"" -> u32:9 b:true
NEXT
VALID false
GET 0100000000077ffffffffffffffe616265 100a
PK testpb.ExampleTable 7/-2/"abe" -> u32:7 u64:10 str:"abe" i64:-2
ORM DELETE testpb.ExampleTable u32:7 u64:10 str:"abe" i64:-2
DEL 0100000000077ffffffffffffffe616265
DEL PK testpb.ExampleTable 7/-2/"abe" -> u32:7 str:"abe" i64:-2
DEL 0101000000000000000a616265
DEL ERR:EOF
DEL 010261626500000000077ffffffffffffffe
DEL IDX testpb.ExampleTable str/u32/i64 : "abe"/7/-2 -> 7/-2/"abe"
DEL 01030061626500000000077ffffffffffffffe
DEL IDX testpb.ExampleTable bz/str/u32/i64 : ""/"abe"/7/-2 -> 7/-2/"abe"
HAS 0100000000077ffffffffffffffe616265
PK testpb.ExampleTable 7/-2/"abe" -> u32:7 str:"abe" i64:-2
ITERATOR 0100 -> 0101
VALID true
KEY 0100000000047ffffffffffffffe616263 100e2203616263
PK testpb.ExampleTable 4/-2/"abc" -> u32:4 u64:14 str:"abc" bz:"abc" i64:-2
NEXT
VALID true
KEY 0100000000047ffffffffffffffe616264 100e2203616264
PK testpb.ExampleTable 4/-2/"abd" -> u32:4 u64:14 str:"abd" bz:"abd" i64:-2
NEXT
VALID true
KEY 0100000000047fffffffffffffff616263 10102203616263
PK testpb.ExampleTable 4/-1/"abc" -> u32:4 u64:16 str:"abc" bz:"abc" i64:-1
NEXT
VALID true
KEY 0100000000057ffffffffffffffe616264 10102203616264
PK testpb.ExampleTable 5/-2/"abd" -> u32:5 u64:16 str:"abd" bz:"abd" i64:-2
NEXT
VALID true
KEY 0100000000057ffffffffffffffe616265 10122203616265
PK testpb.ExampleTable 5/-2/"abe" -> u32:5 u64:18 str:"abe" bz:"abe" i64:-2
NEXT
VALID true
KEY 0100000000077fffffffffffffff616265 100b
PK testpb.ExampleTable 7/-1/"abe" -> u32:7 u64:11 str:"abe" i64:-1
NEXT
VALID true
KEY 0100000000087ffffffffffffffc616263 100b
PK testpb.ExampleTable 8/-4/"abc" -> u32:8 u64:11 str:"abc" i64:-4
NEXT
VALID true
KEY 0100000000088000000000000001616263 100c
PK testpb.ExampleTable 8/1/"abc" -> u32:8 u64:12 str:"abc" i64:1
NEXT
VALID true
KEY 0100000000088000000000000001616264 100a
PK testpb.ExampleTable 8/1/"abd" -> u32:8 u64:10 str:"abd" i64:1
NEXT
VALID true
KEY 0100000000098000000000000000 7801
PK testpb.ExampleTable 9/0/"" -> u32:9 b:true
NEXT
VALID false
CLOSE
CLOSE
CLOSE

View File

@ -0,0 +1,211 @@
package ormtable
import (
"context"
"github.com/cosmos/cosmos-sdk/orm/model/kv"
"github.com/cosmos/cosmos-sdk/orm/model/ormlist"
"github.com/cosmos/cosmos-sdk/orm/encoding/encodeutil"
"google.golang.org/protobuf/proto"
"google.golang.org/protobuf/reflect/protoreflect"
"github.com/cosmos/cosmos-sdk/orm/encoding/ormkv"
"github.com/cosmos/cosmos-sdk/orm/types/ormerrors"
)
type uniqueKeyIndex struct {
*ormkv.UniqueKeyCodec
fields fieldNames
primaryKey *primaryKeyIndex
getReadBackend func(context.Context) (ReadBackend, error)
}
func (u uniqueKeyIndex) Iterator(ctx context.Context, options ...ormlist.Option) (Iterator, error) {
backend, err := u.getReadBackend(ctx)
if err != nil {
return nil, err
}
return iterator(backend, backend.IndexStoreReader(), u, u.GetKeyCodec(), options)
}
func (u uniqueKeyIndex) doNotImplement() {}
func (u uniqueKeyIndex) Has(ctx context.Context, values ...interface{}) (found bool, err error) {
backend, err := u.getReadBackend(ctx)
if err != nil {
return false, err
}
key, err := u.GetKeyCodec().EncodeKey(encodeutil.ValuesOf(values...))
if err != nil {
return false, err
}
return backend.IndexStoreReader().Has(key)
}
func (u uniqueKeyIndex) Get(ctx context.Context, message proto.Message, keyValues ...interface{}) (found bool, err error) {
backend, err := u.getReadBackend(ctx)
if err != nil {
return false, err
}
key, err := u.GetKeyCodec().EncodeKey(encodeutil.ValuesOf(keyValues...))
if err != nil {
return false, err
}
value, err := backend.IndexStoreReader().Get(key)
if err != nil {
return false, err
}
// for unique keys, value can be empty and the entry still exists
if value == nil {
return false, nil
}
_, pk, err := u.DecodeIndexKey(key, value)
if err != nil {
return true, err
}
return u.primaryKey.get(backend, message, pk)
}
func (u uniqueKeyIndex) DeleteByKey(ctx context.Context, keyValues ...interface{}) error {
backend, err := u.getReadBackend(ctx)
if err != nil {
return err
}
key, err := u.GetKeyCodec().EncodeKey(encodeutil.ValuesOf(keyValues...))
if err != nil {
return err
}
value, err := backend.IndexStoreReader().Get(key)
if err != nil {
return err
}
// for unique keys, value can be empty and the entry still exists
if value == nil {
return nil
}
_, pk, err := u.DecodeIndexKey(key, value)
if err != nil {
return err
}
return u.primaryKey.doDeleteByKey(ctx, pk)
}
func (u uniqueKeyIndex) onInsert(store kv.Store, message protoreflect.Message) error {
k, v, err := u.EncodeKVFromMessage(message)
if err != nil {
return err
}
has, err := store.Has(k)
if err != nil {
return err
}
if has {
return ormerrors.UniqueKeyViolation
}
return store.Set(k, v)
}
func (u uniqueKeyIndex) onUpdate(store kv.Store, new, existing protoreflect.Message) error {
keyCodec := u.GetKeyCodec()
newValues := keyCodec.GetKeyValues(new)
existingValues := keyCodec.GetKeyValues(existing)
if keyCodec.CompareKeys(newValues, existingValues) == 0 {
return nil
}
newKey, err := keyCodec.EncodeKey(newValues)
if err != nil {
return err
}
has, err := store.Has(newKey)
if err != nil {
return err
}
if has {
return ormerrors.UniqueKeyViolation
}
existingKey, err := keyCodec.EncodeKey(existingValues)
if err != nil {
return err
}
err = store.Delete(existingKey)
if err != nil {
return err
}
_, value, err := u.GetValueCodec().EncodeKeyFromMessage(new)
if err != nil {
return err
}
return store.Set(newKey, value)
}
func (u uniqueKeyIndex) onDelete(store kv.Store, message protoreflect.Message) error {
_, key, err := u.GetKeyCodec().EncodeKeyFromMessage(message)
if err != nil {
return err
}
return store.Delete(key)
}
func (u uniqueKeyIndex) readValueFromIndexKey(store ReadBackend, primaryKey []protoreflect.Value, _ []byte, message proto.Message) error {
found, err := u.primaryKey.get(store, message, primaryKey)
if err != nil {
return err
}
if !found {
return ormerrors.UnexpectedError.Wrapf("can't find primary key")
}
return nil
}
func (p uniqueKeyIndex) Fields() string {
return p.fields.String()
}
var _ indexer = &uniqueKeyIndex{}
var _ UniqueIndex = &uniqueKeyIndex{}
// isNonTrivialUniqueKey checks if unique key fields are non-trivial, meaning that they
// don't contain the full primary key. If they contain the full primary key, then
// we can just use a regular index because there is no new unique constraint.
func isNonTrivialUniqueKey(fields []protoreflect.Name, primaryKeyFields []protoreflect.Name) bool {
have := map[protoreflect.Name]bool{}
for _, field := range fields {
have[field] = true
}
for _, field := range primaryKeyFields {
if !have[field] {
return true
}
}
return false
}

View File

@ -0,0 +1,35 @@
package ormtable
// prefixEndBytes returns the []byte that would end a
// range query for all []byte with a certain prefix
// Deals with last byte of prefix being FF without overflowing
func prefixEndBytes(prefix []byte) []byte {
if len(prefix) == 0 {
return nil
}
end := make([]byte, len(prefix))
copy(end, prefix)
for {
if end[len(end)-1] != byte(255) {
end[len(end)-1]++
break
}
end = end[:len(end)-1]
if len(end) == 0 {
end = nil
break
}
}
return end
}
// inclusiveEndBytes returns the []byte that would end a
// range query such that the input would be included
func inclusiveEndBytes(inclusiveBytes []byte) []byte {
return append(inclusiveBytes, byte(0x00))
}

View File

@ -11,7 +11,7 @@ var (
DuplicateKeyField = errors.New(codespace, 4, "duplicate field in key")
FieldNotFound = errors.New(codespace, 5, "field not found")
InvalidAutoIncrementKey = errors.New(codespace, 6, "an auto-increment primary key must specify a single uint64 field")
InvalidIndexId = errors.New(codespace, 7, "invalid or missing index id, need a non-zero value")
InvalidIndexId = errors.New(codespace, 7, "invalid or missing index id, need a value >= 0 and < 32768")
DuplicateIndexId = errors.New(codespace, 8, "duplicate index id")
PrimaryKeyConstraintViolation = errors.New(codespace, 9, "object with primary key already exists")
NotFoundOnUpdate = errors.New(codespace, 10, "can't update object which doesn't exist")
@ -28,4 +28,6 @@ var (
UnexpectedError = errors.New(codespace, 21, "unexpected error")
InvalidRangeIterationKeys = errors.New(codespace, 22, "invalid range iteration keys")
JSONImportError = errors.New(codespace, 23, "json import error")
UniqueKeyViolation = errors.New(codespace, 24, "unique key violation")
InvalidTableDefinition = errors.New(codespace, 25, "invalid table definition")
)

View File

@ -88,7 +88,8 @@ message SecondaryIndexDescriptor {
string fields = 1;
// id is a non-zero integer ID that must be unique within the indexes for this
// table. It may be deprecated in the future when this can be auto-generated.
// table and less than 32768. It may be deprecated in the future when this can
// be auto-generated.
uint32 id = 2;
// unique specifies that this an unique index.