## 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)
205 lines
5.1 KiB
Go
205 lines
5.1 KiB
Go
package ormkv
|
|
|
|
import (
|
|
"bytes"
|
|
"io"
|
|
|
|
"github.com/cosmos/cosmos-sdk/orm/types/ormerrors"
|
|
|
|
"google.golang.org/protobuf/reflect/protoreflect"
|
|
)
|
|
|
|
// UniqueKeyCodec is the codec for unique indexes.
|
|
type UniqueKeyCodec struct {
|
|
pkFieldOrder []struct {
|
|
inKey bool
|
|
i int
|
|
}
|
|
keyCodec *KeyCodec
|
|
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, 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
|
|
}
|
|
|
|
haveFields := map[protoreflect.Name]int{}
|
|
for i, descriptor := range keyCodec.fieldDescriptors {
|
|
haveFields[descriptor.Name()] = i
|
|
}
|
|
|
|
var valueFields []protoreflect.Name
|
|
var pkFieldOrder []struct {
|
|
inKey bool
|
|
i int
|
|
}
|
|
k := 0
|
|
for _, field := range primaryKeyFields {
|
|
if j, ok := haveFields[field]; ok {
|
|
pkFieldOrder = append(pkFieldOrder, struct {
|
|
inKey bool
|
|
i int
|
|
}{inKey: true, i: j})
|
|
} else {
|
|
valueFields = append(valueFields, field)
|
|
pkFieldOrder = append(pkFieldOrder, struct {
|
|
inKey bool
|
|
i int
|
|
}{inKey: false, i: k})
|
|
k++
|
|
}
|
|
}
|
|
|
|
valueCodec, err := NewKeyCodec(nil, messageType, valueFields)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &UniqueKeyCodec{
|
|
pkFieldOrder: pkFieldOrder,
|
|
keyCodec: keyCodec,
|
|
valueCodec: valueCodec,
|
|
}, nil
|
|
}
|
|
|
|
func (u UniqueKeyCodec) DecodeIndexKey(k, v []byte) (indexFields, primaryKey []protoreflect.Value, err error) {
|
|
ks, err := u.keyCodec.DecodeKey(bytes.NewReader(k))
|
|
|
|
// got prefix key
|
|
if err == io.EOF {
|
|
return ks, nil, err
|
|
} else if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
|
|
// got prefix key
|
|
if len(ks) < len(u.keyCodec.fieldCodecs) {
|
|
return ks, nil, err
|
|
}
|
|
|
|
vs, err := u.valueCodec.DecodeKey(bytes.NewReader(v))
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
|
|
pk := u.extractPrimaryKey(ks, vs)
|
|
return ks, pk, nil
|
|
}
|
|
|
|
func (cdc UniqueKeyCodec) extractPrimaryKey(keyValues, valueValues []protoreflect.Value) []protoreflect.Value {
|
|
numPkFields := len(cdc.pkFieldOrder)
|
|
pkValues := make([]protoreflect.Value, numPkFields)
|
|
|
|
for i := 0; i < numPkFields; i++ {
|
|
fo := cdc.pkFieldOrder[i]
|
|
if fo.inKey {
|
|
pkValues[i] = keyValues[fo.i]
|
|
} else {
|
|
pkValues[i] = valueValues[fo.i]
|
|
}
|
|
}
|
|
|
|
return pkValues
|
|
}
|
|
|
|
func (u UniqueKeyCodec) DecodeEntry(k, v []byte) (Entry, error) {
|
|
idxVals, pk, err := u.DecodeIndexKey(k, v)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &IndexKeyEntry{
|
|
TableName: u.MessageType().Descriptor().FullName(),
|
|
Fields: u.keyCodec.fieldNames,
|
|
IsUnique: true,
|
|
IndexValues: idxVals,
|
|
PrimaryKey: pk,
|
|
}, err
|
|
}
|
|
|
|
func (u UniqueKeyCodec) EncodeEntry(entry Entry) (k, v []byte, err error) {
|
|
indexEntry, ok := entry.(*IndexKeyEntry)
|
|
if !ok {
|
|
return nil, nil, ormerrors.BadDecodeEntry
|
|
}
|
|
k, err = u.keyCodec.EncodeKey(indexEntry.IndexValues)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
|
|
n := len(indexEntry.PrimaryKey)
|
|
if n != len(u.pkFieldOrder) {
|
|
return nil, nil, ormerrors.BadDecodeEntry.Wrapf("wrong primary key length")
|
|
}
|
|
|
|
var values []protoreflect.Value
|
|
for i := 0; i < n; i++ {
|
|
value := indexEntry.PrimaryKey[i]
|
|
fieldOrder := u.pkFieldOrder[i]
|
|
if !fieldOrder.inKey {
|
|
// goes in values because it is not present in the index key otherwise
|
|
values = append(values, value)
|
|
} else {
|
|
// does not go in values, but we need to verify that the value in index values matches the primary key value
|
|
if u.keyCodec.fieldCodecs[fieldOrder.i].Compare(value, indexEntry.IndexValues[fieldOrder.i]) != 0 {
|
|
return nil, nil, ormerrors.BadDecodeEntry.Wrapf("value in primary key does not match corresponding value in index key")
|
|
}
|
|
}
|
|
}
|
|
|
|
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.EncodeKeyFromMessage(message)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
|
|
_, 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
|
|
}
|