chore: bump internal to main (#23681)

This commit is contained in:
Alex | Interchain Labs
2025-02-12 14:13:20 -05:00
committed by GitHub
parent dfccd9c27e
commit 4f9882b552
65 changed files with 338 additions and 17726 deletions
@@ -1,505 +0,0 @@
// Code generated by protoc-gen-go-pulsar. DO NOT EDIT.
package modulev1alpha1
import (
_ "cosmossdk.io/api/cosmos/app/v1alpha1"
fmt "fmt"
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 (
md_Module protoreflect.MessageDescriptor
)
func init() {
file_cosmos_orm_module_v1alpha1_module_proto_init()
md_Module = File_cosmos_orm_module_v1alpha1_module_proto.Messages().ByName("Module")
}
var _ protoreflect.Message = (*fastReflection_Module)(nil)
type fastReflection_Module Module
func (x *Module) ProtoReflect() protoreflect.Message {
return (*fastReflection_Module)(x)
}
func (x *Module) slowProtoReflect() protoreflect.Message {
mi := &file_cosmos_orm_module_v1alpha1_module_proto_msgTypes[0]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
var _fastReflection_Module_messageType fastReflection_Module_messageType
var _ protoreflect.MessageType = fastReflection_Module_messageType{}
type fastReflection_Module_messageType struct{}
func (x fastReflection_Module_messageType) Zero() protoreflect.Message {
return (*fastReflection_Module)(nil)
}
func (x fastReflection_Module_messageType) New() protoreflect.Message {
return new(fastReflection_Module)
}
func (x fastReflection_Module_messageType) Descriptor() protoreflect.MessageDescriptor {
return md_Module
}
// Descriptor returns message descriptor, which contains only the protobuf
// type information for the message.
func (x *fastReflection_Module) Descriptor() protoreflect.MessageDescriptor {
return md_Module
}
// Type returns the message type, which encapsulates both Go and protobuf
// type information. If the Go type information is not needed,
// it is recommended that the message descriptor be used instead.
func (x *fastReflection_Module) Type() protoreflect.MessageType {
return _fastReflection_Module_messageType
}
// New returns a newly allocated and mutable empty message.
func (x *fastReflection_Module) New() protoreflect.Message {
return new(fastReflection_Module)
}
// Interface unwraps the message reflection interface and
// returns the underlying ProtoMessage interface.
func (x *fastReflection_Module) Interface() protoreflect.ProtoMessage {
return (*Module)(x)
}
// Range iterates over every populated field in an undefined order,
// calling f for each field descriptor and value encountered.
// Range returns immediately if f returns false.
// While iterating, mutating operations may only be performed
// on the current field descriptor.
func (x *fastReflection_Module) Range(f func(protoreflect.FieldDescriptor, protoreflect.Value) bool) {
}
// Has reports whether a field is populated.
//
// Some fields have the property of nullability where it is possible to
// distinguish between the default value of a field and whether the field
// was explicitly populated with the default value. Singular message fields,
// member fields of a oneof, and proto2 scalar fields are nullable. Such
// fields are populated only if explicitly set.
//
// In other cases (aside from the nullable cases above),
// a proto3 scalar field is populated if it contains a non-zero value, and
// a repeated field is populated if it is non-empty.
func (x *fastReflection_Module) Has(fd protoreflect.FieldDescriptor) bool {
switch fd.FullName() {
default:
if fd.IsExtension() {
panic(fmt.Errorf("proto3 declared messages do not support extensions: cosmos.orm.module.v1alpha1.Module"))
}
panic(fmt.Errorf("message cosmos.orm.module.v1alpha1.Module does not contain field %s", fd.FullName()))
}
}
// Clear clears the field such that a subsequent Has call reports false.
//
// Clearing an extension field clears both the extension type and value
// associated with the given field number.
//
// Clear is a mutating operation and unsafe for concurrent use.
func (x *fastReflection_Module) Clear(fd protoreflect.FieldDescriptor) {
switch fd.FullName() {
default:
if fd.IsExtension() {
panic(fmt.Errorf("proto3 declared messages do not support extensions: cosmos.orm.module.v1alpha1.Module"))
}
panic(fmt.Errorf("message cosmos.orm.module.v1alpha1.Module does not contain field %s", fd.FullName()))
}
}
// Get retrieves the value for a field.
//
// For unpopulated scalars, it returns the default value, where
// the default value of a bytes scalar is guaranteed to be a copy.
// For unpopulated composite types, it returns an empty, read-only view
// of the value; to obtain a mutable reference, use Mutable.
func (x *fastReflection_Module) Get(descriptor protoreflect.FieldDescriptor) protoreflect.Value {
switch descriptor.FullName() {
default:
if descriptor.IsExtension() {
panic(fmt.Errorf("proto3 declared messages do not support extensions: cosmos.orm.module.v1alpha1.Module"))
}
panic(fmt.Errorf("message cosmos.orm.module.v1alpha1.Module does not contain field %s", descriptor.FullName()))
}
}
// Set stores the value for a field.
//
// For a field belonging to a oneof, it implicitly clears any other field
// that may be currently set within the same oneof.
// For extension fields, it implicitly stores the provided ExtensionType.
// When setting a composite type, it is unspecified whether the stored value
// aliases the source's memory in any way. If the composite value is an
// empty, read-only value, then it panics.
//
// Set is a mutating operation and unsafe for concurrent use.
func (x *fastReflection_Module) Set(fd protoreflect.FieldDescriptor, value protoreflect.Value) {
switch fd.FullName() {
default:
if fd.IsExtension() {
panic(fmt.Errorf("proto3 declared messages do not support extensions: cosmos.orm.module.v1alpha1.Module"))
}
panic(fmt.Errorf("message cosmos.orm.module.v1alpha1.Module does not contain field %s", fd.FullName()))
}
}
// Mutable returns a mutable reference to a composite type.
//
// If the field is unpopulated, it may allocate a composite value.
// For a field belonging to a oneof, it implicitly clears any other field
// that may be currently set within the same oneof.
// For extension fields, it implicitly stores the provided ExtensionType
// if not already stored.
// It panics if the field does not contain a composite type.
//
// Mutable is a mutating operation and unsafe for concurrent use.
func (x *fastReflection_Module) Mutable(fd protoreflect.FieldDescriptor) protoreflect.Value {
switch fd.FullName() {
default:
if fd.IsExtension() {
panic(fmt.Errorf("proto3 declared messages do not support extensions: cosmos.orm.module.v1alpha1.Module"))
}
panic(fmt.Errorf("message cosmos.orm.module.v1alpha1.Module does not contain field %s", fd.FullName()))
}
}
// NewField returns a new value that is assignable to the field
// for the given descriptor. For scalars, this returns the default value.
// For lists, maps, and messages, this returns a new, empty, mutable value.
func (x *fastReflection_Module) NewField(fd protoreflect.FieldDescriptor) protoreflect.Value {
switch fd.FullName() {
default:
if fd.IsExtension() {
panic(fmt.Errorf("proto3 declared messages do not support extensions: cosmos.orm.module.v1alpha1.Module"))
}
panic(fmt.Errorf("message cosmos.orm.module.v1alpha1.Module does not contain field %s", fd.FullName()))
}
}
// WhichOneof reports which field within the oneof is populated,
// returning nil if none are populated.
// It panics if the oneof descriptor does not belong to this message.
func (x *fastReflection_Module) WhichOneof(d protoreflect.OneofDescriptor) protoreflect.FieldDescriptor {
switch d.FullName() {
default:
panic(fmt.Errorf("%s is not a oneof field in cosmos.orm.module.v1alpha1.Module", d.FullName()))
}
panic("unreachable")
}
// GetUnknown retrieves the entire list of unknown fields.
// The caller may only mutate the contents of the RawFields
// if the mutated bytes are stored back into the message with SetUnknown.
func (x *fastReflection_Module) GetUnknown() protoreflect.RawFields {
return x.unknownFields
}
// SetUnknown stores an entire list of unknown fields.
// The raw fields must be syntactically valid according to the wire format.
// An implementation may panic if this is not the case.
// Once stored, the caller must not mutate the content of the RawFields.
// An empty RawFields may be passed to clear the fields.
//
// SetUnknown is a mutating operation and unsafe for concurrent use.
func (x *fastReflection_Module) SetUnknown(fields protoreflect.RawFields) {
x.unknownFields = fields
}
// IsValid reports whether the message is valid.
//
// An invalid message is an empty, read-only value.
//
// An invalid message often corresponds to a nil pointer of the concrete
// message type, but the details are implementation dependent.
// Validity is not part of the protobuf data model, and may not
// be preserved in marshaling or other operations.
func (x *fastReflection_Module) IsValid() bool {
return x != nil
}
// ProtoMethods returns optional fastReflectionFeature-path implementations of various operations.
// This method may return nil.
//
// The returned methods type is identical to
// "google.golang.org/protobuf/runtime/protoiface".Methods.
// Consult the protoiface package documentation for details.
func (x *fastReflection_Module) ProtoMethods() *protoiface.Methods {
size := func(input protoiface.SizeInput) protoiface.SizeOutput {
x := input.Message.Interface().(*Module)
if x == nil {
return protoiface.SizeOutput{
NoUnkeyedLiterals: input.NoUnkeyedLiterals,
Size: 0,
}
}
options := runtime.SizeInputToOptions(input)
_ = options
var n int
var l int
_ = l
if x.unknownFields != nil {
n += len(x.unknownFields)
}
return protoiface.SizeOutput{
NoUnkeyedLiterals: input.NoUnkeyedLiterals,
Size: n,
}
}
marshal := func(input protoiface.MarshalInput) (protoiface.MarshalOutput, error) {
x := input.Message.Interface().(*Module)
if x == nil {
return protoiface.MarshalOutput{
NoUnkeyedLiterals: input.NoUnkeyedLiterals,
Buf: input.Buf,
}, nil
}
options := runtime.MarshalInputToOptions(input)
_ = options
size := options.Size(x)
dAtA := make([]byte, size)
i := len(dAtA)
_ = i
var l int
_ = l
if x.unknownFields != nil {
i -= len(x.unknownFields)
copy(dAtA[i:], x.unknownFields)
}
if input.Buf != nil {
input.Buf = append(input.Buf, dAtA...)
} else {
input.Buf = dAtA
}
return protoiface.MarshalOutput{
NoUnkeyedLiterals: input.NoUnkeyedLiterals,
Buf: input.Buf,
}, nil
}
unmarshal := func(input protoiface.UnmarshalInput) (protoiface.UnmarshalOutput, error) {
x := input.Message.Interface().(*Module)
if x == nil {
return protoiface.UnmarshalOutput{
NoUnkeyedLiterals: input.NoUnkeyedLiterals,
Flags: input.Flags,
}, nil
}
options := runtime.UnmarshalInputToOptions(input)
_ = options
dAtA := input.Buf
l := len(dAtA)
iNdEx := 0
for iNdEx < l {
preIndex := iNdEx
var wire uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, runtime.ErrIntOverflow
}
if iNdEx >= l {
return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
wire |= uint64(b&0x7F) << shift
if b < 0x80 {
break
}
}
fieldNum := int32(wire >> 3)
wireType := int(wire & 0x7)
if wireType == 4 {
return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, fmt.Errorf("proto: Module: wiretype end group for non-group")
}
if fieldNum <= 0 {
return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, fmt.Errorf("proto: Module: illegal tag %d (wire type %d)", fieldNum, wire)
}
switch fieldNum {
default:
iNdEx = preIndex
skippy, err := runtime.Skip(dAtA[iNdEx:])
if err != nil {
return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, err
}
if (skippy < 0) || (iNdEx+skippy) < 0 {
return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, runtime.ErrInvalidLength
}
if (iNdEx + skippy) > l {
return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, io.ErrUnexpectedEOF
}
if !options.DiscardUnknown {
x.unknownFields = append(x.unknownFields, dAtA[iNdEx:iNdEx+skippy]...)
}
iNdEx += skippy
}
}
if iNdEx > l {
return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, io.ErrUnexpectedEOF
}
return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, nil
}
return &protoiface.Methods{
NoUnkeyedLiterals: struct{}{},
Flags: protoiface.SupportMarshalDeterministic | protoiface.SupportUnmarshalDiscardUnknown,
Size: size,
Marshal: marshal,
Unmarshal: unmarshal,
Merge: nil,
CheckInitialized: nil,
}
}
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.27.0
// protoc (unknown)
// source: cosmos/orm/module/v1alpha1/module.proto
const (
// Verify that this generated code is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
// Verify that runtime/protoimpl is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
)
// Module defines the ORM module which adds providers to the app container for
// ORM ModuleDB's and in the future will automatically register query
// services for modules that use the ORM.
type Module struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
}
func (x *Module) Reset() {
*x = Module{}
if protoimpl.UnsafeEnabled {
mi := &file_cosmos_orm_module_v1alpha1_module_proto_msgTypes[0]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *Module) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*Module) ProtoMessage() {}
// Deprecated: Use Module.ProtoReflect.Descriptor instead.
func (*Module) Descriptor() ([]byte, []int) {
return file_cosmos_orm_module_v1alpha1_module_proto_rawDescGZIP(), []int{0}
}
var File_cosmos_orm_module_v1alpha1_module_proto protoreflect.FileDescriptor
var file_cosmos_orm_module_v1alpha1_module_proto_rawDesc = []byte{
0x0a, 0x27, 0x63, 0x6f, 0x73, 0x6d, 0x6f, 0x73, 0x2f, 0x6f, 0x72, 0x6d, 0x2f, 0x6d, 0x6f, 0x64,
0x75, 0x6c, 0x65, 0x2f, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2f, 0x6d, 0x6f, 0x64,
0x75, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x1a, 0x63, 0x6f, 0x73, 0x6d, 0x6f,
0x73, 0x2e, 0x6f, 0x72, 0x6d, 0x2e, 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x61,
0x6c, 0x70, 0x68, 0x61, 0x31, 0x1a, 0x20, 0x63, 0x6f, 0x73, 0x6d, 0x6f, 0x73, 0x2f, 0x61, 0x70,
0x70, 0x2f, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2f, 0x6d, 0x6f, 0x64, 0x75, 0x6c,
0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x22, 0x0a, 0x06, 0x4d, 0x6f, 0x64, 0x75, 0x6c,
0x65, 0x3a, 0x18, 0xba, 0xc0, 0x96, 0xda, 0x01, 0x12, 0x0a, 0x10, 0x63, 0x6f, 0x73, 0x6d, 0x6f,
0x73, 0x73, 0x64, 0x6b, 0x2e, 0x69, 0x6f, 0x2f, 0x6f, 0x72, 0x6d, 0x42, 0xf4, 0x01, 0x0a, 0x1e,
0x63, 0x6f, 0x6d, 0x2e, 0x63, 0x6f, 0x73, 0x6d, 0x6f, 0x73, 0x2e, 0x6f, 0x72, 0x6d, 0x2e, 0x6d,
0x6f, 0x64, 0x75, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x42, 0x0b,
0x4d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x50, 0x01, 0x5a, 0x3a, 0x63,
0x6f, 0x73, 0x6d, 0x6f, 0x73, 0x73, 0x64, 0x6b, 0x2e, 0x69, 0x6f, 0x2f, 0x61, 0x70, 0x69, 0x2f,
0x63, 0x6f, 0x73, 0x6d, 0x6f, 0x73, 0x2f, 0x6f, 0x72, 0x6d, 0x2f, 0x6d, 0x6f, 0x64, 0x75, 0x6c,
0x65, 0x2f, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x3b, 0x6d, 0x6f, 0x64, 0x75, 0x6c,
0x65, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0xa2, 0x02, 0x03, 0x43, 0x4f, 0x4d, 0xaa,
0x02, 0x1a, 0x43, 0x6f, 0x73, 0x6d, 0x6f, 0x73, 0x2e, 0x4f, 0x72, 0x6d, 0x2e, 0x4d, 0x6f, 0x64,
0x75, 0x6c, 0x65, 0x2e, 0x56, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0xca, 0x02, 0x1a, 0x43,
0x6f, 0x73, 0x6d, 0x6f, 0x73, 0x5c, 0x4f, 0x72, 0x6d, 0x5c, 0x4d, 0x6f, 0x64, 0x75, 0x6c, 0x65,
0x5c, 0x56, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0xe2, 0x02, 0x26, 0x43, 0x6f, 0x73, 0x6d,
0x6f, 0x73, 0x5c, 0x4f, 0x72, 0x6d, 0x5c, 0x4d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x5c, 0x56, 0x31,
0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x5c, 0x47, 0x50, 0x42, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61,
0x74, 0x61, 0xea, 0x02, 0x1d, 0x43, 0x6f, 0x73, 0x6d, 0x6f, 0x73, 0x3a, 0x3a, 0x4f, 0x72, 0x6d,
0x3a, 0x3a, 0x4d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x3a, 0x3a, 0x56, 0x31, 0x61, 0x6c, 0x70, 0x68,
0x61, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
}
var (
file_cosmos_orm_module_v1alpha1_module_proto_rawDescOnce sync.Once
file_cosmos_orm_module_v1alpha1_module_proto_rawDescData = file_cosmos_orm_module_v1alpha1_module_proto_rawDesc
)
func file_cosmos_orm_module_v1alpha1_module_proto_rawDescGZIP() []byte {
file_cosmos_orm_module_v1alpha1_module_proto_rawDescOnce.Do(func() {
file_cosmos_orm_module_v1alpha1_module_proto_rawDescData = protoimpl.X.CompressGZIP(file_cosmos_orm_module_v1alpha1_module_proto_rawDescData)
})
return file_cosmos_orm_module_v1alpha1_module_proto_rawDescData
}
var file_cosmos_orm_module_v1alpha1_module_proto_msgTypes = make([]protoimpl.MessageInfo, 1)
var file_cosmos_orm_module_v1alpha1_module_proto_goTypes = []interface{}{
(*Module)(nil), // 0: cosmos.orm.module.v1alpha1.Module
}
var file_cosmos_orm_module_v1alpha1_module_proto_depIdxs = []int32{
0, // [0:0] is the sub-list for method output_type
0, // [0:0] is the sub-list for method input_type
0, // [0:0] is the sub-list for extension type_name
0, // [0:0] is the sub-list for extension extendee
0, // [0:0] is the sub-list for field type_name
}
func init() { file_cosmos_orm_module_v1alpha1_module_proto_init() }
func file_cosmos_orm_module_v1alpha1_module_proto_init() {
if File_cosmos_orm_module_v1alpha1_module_proto != nil {
return
}
if !protoimpl.UnsafeEnabled {
file_cosmos_orm_module_v1alpha1_module_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*Module); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
}
type x struct{}
out := protoimpl.TypeBuilder{
File: protoimpl.DescBuilder{
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: file_cosmos_orm_module_v1alpha1_module_proto_rawDesc,
NumEnums: 0,
NumMessages: 1,
NumExtensions: 0,
NumServices: 0,
},
GoTypes: file_cosmos_orm_module_v1alpha1_module_proto_goTypes,
DependencyIndexes: file_cosmos_orm_module_v1alpha1_module_proto_depIdxs,
MessageInfos: file_cosmos_orm_module_v1alpha1_module_proto_msgTypes,
}.Build()
File_cosmos_orm_module_v1alpha1_module_proto = out.File
file_cosmos_orm_module_v1alpha1_module_proto_rawDesc = nil
file_cosmos_orm_module_v1alpha1_module_proto_goTypes = nil
file_cosmos_orm_module_v1alpha1_module_proto_depIdxs = nil
}
File diff suppressed because it is too large Load Diff
@@ -1,150 +0,0 @@
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
// versions:
// - protoc-gen-go-grpc v1.3.0
// - protoc (unknown)
// source: cosmos/orm/query/v1alpha1/query.proto
package queryv1alpha1
import (
context "context"
grpc "google.golang.org/grpc"
codes "google.golang.org/grpc/codes"
status "google.golang.org/grpc/status"
)
// This is a compile-time assertion to ensure that this generated file
// is compatible with the grpc package it is being compiled against.
// Requires gRPC-Go v1.32.0 or later.
const _ = grpc.SupportPackageIsVersion7
const (
Query_Get_FullMethodName = "/cosmos.orm.query.v1alpha1.Query/Get"
Query_List_FullMethodName = "/cosmos.orm.query.v1alpha1.Query/List"
)
// QueryClient is the client API for Query service.
//
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
type QueryClient interface {
// Get queries an ORM table against an unique index.
Get(ctx context.Context, in *GetRequest, opts ...grpc.CallOption) (*GetResponse, error)
// List queries an ORM table against an index.
List(ctx context.Context, in *ListRequest, opts ...grpc.CallOption) (*ListResponse, error)
}
type queryClient struct {
cc grpc.ClientConnInterface
}
func NewQueryClient(cc grpc.ClientConnInterface) QueryClient {
return &queryClient{cc}
}
func (c *queryClient) Get(ctx context.Context, in *GetRequest, opts ...grpc.CallOption) (*GetResponse, error) {
out := new(GetResponse)
err := c.cc.Invoke(ctx, Query_Get_FullMethodName, in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *queryClient) List(ctx context.Context, in *ListRequest, opts ...grpc.CallOption) (*ListResponse, error) {
out := new(ListResponse)
err := c.cc.Invoke(ctx, Query_List_FullMethodName, in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
// QueryServer is the server API for Query service.
// All implementations must embed UnimplementedQueryServer
// for forward compatibility
type QueryServer interface {
// Get queries an ORM table against an unique index.
Get(context.Context, *GetRequest) (*GetResponse, error)
// List queries an ORM table against an index.
List(context.Context, *ListRequest) (*ListResponse, error)
mustEmbedUnimplementedQueryServer()
}
// UnimplementedQueryServer must be embedded to have forward compatible implementations.
type UnimplementedQueryServer struct {
}
func (UnimplementedQueryServer) Get(context.Context, *GetRequest) (*GetResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method Get not implemented")
}
func (UnimplementedQueryServer) List(context.Context, *ListRequest) (*ListResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method List not implemented")
}
func (UnimplementedQueryServer) mustEmbedUnimplementedQueryServer() {}
// UnsafeQueryServer may be embedded to opt out of forward compatibility for this service.
// Use of this interface is not recommended, as added methods to QueryServer will
// result in compilation errors.
type UnsafeQueryServer interface {
mustEmbedUnimplementedQueryServer()
}
func RegisterQueryServer(s grpc.ServiceRegistrar, srv QueryServer) {
s.RegisterService(&Query_ServiceDesc, srv)
}
func _Query_Get_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(GetRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(QueryServer).Get(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: Query_Get_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(QueryServer).Get(ctx, req.(*GetRequest))
}
return interceptor(ctx, in, info, handler)
}
func _Query_List_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(ListRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(QueryServer).List(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: Query_List_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(QueryServer).List(ctx, req.(*ListRequest))
}
return interceptor(ctx, in, info, handler)
}
// Query_ServiceDesc is the grpc.ServiceDesc for Query service.
// It's only intended for direct use with grpc.RegisterService,
// and not to be introspected or modified (even as a copy)
var Query_ServiceDesc = grpc.ServiceDesc{
ServiceName: "cosmos.orm.query.v1alpha1.Query",
HandlerType: (*QueryServer)(nil),
Methods: []grpc.MethodDesc{
{
MethodName: "Get",
Handler: _Query_Get_Handler,
},
{
MethodName: "List",
Handler: _Query_List_Handler,
},
},
Streams: []grpc.StreamDesc{},
Metadata: "cosmos/orm/query/v1alpha1/query.proto",
}
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
-1
View File
@@ -21,7 +21,6 @@ docs/docs/tooling/03-hubl.md
docs/docs/core/17-autocli.md
docs/docs/packages/01-depinject.md
docs/docs/packages/02-collections.md
docs/docs/packages/03-orm.md
docs/user/run-node/04-rosetta.md
docs/build/migrations/02-upgrading.md
docs/learn/advanced/17-autocli.md
-1
View File
@@ -21,7 +21,6 @@ For more information on SDK tooling, see the [Tooling](https://docs.cosmos.netwo
## State Management
* [Collections](./02-collections.md) - State management library
* [ORM](./03-orm.md) - State management library
## Automation
+1 -1
View File
@@ -47,7 +47,7 @@ via Protobuf. This means that modules may use Protobuf encoding, but the types m
implement `ProtoMarshaler`. If modules wish to avoid implementing this interface
for their types, this is autogenerated via [buf](https://buf.build/)
If modules use [Collections](../../build/packages/02-collections.md) or [ORM](../../build/packages/03-orm.md), encoding and decoding are handled, marshal and unmarshal should not be handled manually unless for specific cases identified by the developer.
If modules use [Collections](../../build/packages/02-collections.md), encoding and decoding are handled, marshal and unmarshal should not be handled manually unless for specific cases identified by the developer.
### Gogoproto
-1
View File
@@ -6,7 +6,6 @@ rm -rf docs/build/tooling/02-confix.md
rm -rf docs/build/tooling/03-hubl.md
rm -rf docs/build/packages/01-depinject.md
rm -rf docs/build/packages/02-collections.md
rm -rf docs/build/packages/03-orm.md
rm -rf docs/learn/advaced-concepts/17-autocli.md
rm -rf docs/user/run-node/04-rosetta.md
rm -rf docs/build/architecture
-1
View File
@@ -32,7 +32,6 @@ wget -O docs/user/run-node/04-rosetta.md https://raw.githubusercontent.com/cosmo
cp ../client/v2/README.md ./docs/learn/advanced/17-autocli.md
cp ../depinject/README.md ./docs/build/packages/01-depinject.md
cp ../collections/README.md ./docs/build/packages/02-collections.md
cp ../orm/README.md ./docs/build/packages/03-orm.md
## Add architecture documentation
cp -r ./architecture ./docs/build
+1 -1
View File
@@ -1,2 +1,2 @@
// Package conv provides internal functions for convertions and data manipulation
// Package conv provides internal functions for conversions and data manipulation
package conv
+2 -9
View File
@@ -1,20 +1,13 @@
package conv
import (
"reflect"
"unsafe"
)
// UnsafeStrToBytes uses unsafe to convert string into byte array. Returned bytes
// must not be altered after this function is called as it will cause a segmentation fault.
func UnsafeStrToBytes(s string) []byte {
var buf []byte
sHdr := (*reflect.StringHeader)(unsafe.Pointer(&s))
bufHdr := (*reflect.SliceHeader)(unsafe.Pointer(&buf))
bufHdr.Data = sHdr.Data
bufHdr.Cap = sHdr.Len
bufHdr.Len = sHdr.Len
return buf
return unsafe.Slice(unsafe.StringData(s), len(s)) // ref https://github.com/golang/go/issues/53003#issuecomment-1140276077
}
// UnsafeBytesToStr is meant to make a zero allocation conversion
@@ -22,5 +15,5 @@ func UnsafeStrToBytes(s string) []byte {
// to be used generally, but for a specific pattern to delete keys
// from a map.
func UnsafeBytesToStr(b []byte) string {
return *(*string)(unsafe.Pointer(&b))
return unsafe.String(unsafe.SliceData(b), len(b))
}
+50
View File
@@ -0,0 +1,50 @@
package testutil
import (
"strings"
"testing"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
)
// ResetArgs resets arguments for the command. It is desired to be a helpful function for the cmd.SetArgs
// in the case of calling multiple times in a unit test, as cmd.SetArgs doesn't
// reset the flag value as expected.
//
// **Warning**: this is only compatible with following flag types:
// 1. the implementations of pflag.Value
// 2. the built-in implementations of pflag.SliceValue
// 3. the custom implementations of pflag.SliceValue that are split by comma ","
//
// see https://github.com/spf13/cobra/issues/2079#issuecomment-1870115781 for more detail info
func ResetArgs(t *testing.T, cmd *cobra.Command) {
t.Helper()
// if flags haven't been parsed yet, there is no need to reset the args
if !cmd.Flags().Parsed() {
return
}
// If flags have already been parsed, we should reset the values of flags that haven't been set
cmd.Flags().Visit(func(pf *pflag.Flag) {
// if the flag hasn't been changed, there is no need to reset the args
if !pf.Changed {
return
}
// handle pflag.SliceValue
if v, ok := pf.Value.(pflag.SliceValue); ok {
defVal := strings.Trim(pf.DefValue, "[]")
defSliceVal := make([]string, 0)
if defVal != "" {
defSliceVal = strings.Split(defVal, ",")
}
if err := v.Replace(defSliceVal); err != nil {
t.Errorf("error resetting argument <%s> with default value <%+v>: %v", pf.Name, defSliceVal, err)
}
return
}
// handle pflag.Value
if err := pf.Value.Set(pf.DefValue); err != nil {
t.Errorf("error resetting argument <%s> with default value <%s>: %v", pf.Name, pf.DefValue, err)
}
})
}
+207
View File
@@ -0,0 +1,207 @@
package testutil_test
import (
"errors"
"fmt"
"testing"
"github.com/spf13/cobra"
"github.com/stretchr/testify/require"
"github.com/cosmos/cosmos-sdk/internal/testutil"
)
// TestSetArgsWithOriginalMethod is used to illustrate cobra.Command.SetArgs won't reset args as expected
func TestSetArgsWithOriginalMethod(t *testing.T) {
getCMD := func() *cobra.Command {
cmd := &cobra.Command{
Use: "testcmd",
RunE: func(cmd *cobra.Command, args []string) error {
a, _ := cmd.Flags().GetBool("a")
b, _ := cmd.Flags().GetBool("b")
c, _ := cmd.Flags().GetBool("c")
switch {
case a && b, a && c, b && c:
return errors.New("a,b,c only one could be true")
}
return nil
},
}
f := cmd.Flags()
f.BoolP("a", "a", false, "a,b,c only one could be true")
f.BoolP("b", "b", false, "a,b,c only one could be true")
f.Bool("c", false, "a,b,c only one could be true")
return cmd
}
cmd := getCMD()
cmd.SetArgs([]string{
"testcmd",
"--a=true",
})
require.NoError(t, cmd.Execute())
// This call to cmd.SetArgs is expected to set only the 'b' flag. However, due to the bug, the 'a' flag remains set from the previous call to cmd.SetArgs, leading to an error.
cmd.SetArgs([]string{
"testcmd",
"--b=true",
})
require.True(t, cmd.Flags().Changed("a"))
require.Error(t, cmd.Execute())
// This call to cmd.SetArgs is expected to set only the 'c' flag. However, the 'a' and 'b' flags remain set from the previous calls, causing an unexpected error.
cmd.SetArgs([]string{
"testcmd",
"--c=true",
})
require.Error(t, cmd.Execute())
// To work around the bug, we must explicitly reset the 'a' and 'b' flags to false, even though we only want to set the 'c' flag to true.
cmd.SetArgs([]string{
"testcmd",
"--a=false",
"--b=false",
"--c=true",
})
require.NoError(t, cmd.Execute())
}
func TestSetArgsWithWrappedMethod(t *testing.T) {
var (
mockFlagWithCommaD = testutil.MockFlagsWithComma{Ary: []string{"g;m", "g;n"}}
mockFlagWithCommaE testutil.MockFlagsWithComma
)
var (
mockFlagWithSemicolonF = testutil.MockFlagsWithSemicolon{Ary: []string{"g,m", "g,n"}}
mockFlagWithSemicolonG testutil.MockFlagsWithSemicolon
)
getCMD := func() *cobra.Command {
cmd := &cobra.Command{
Use: "testcmd",
RunE: func(cmd *cobra.Command, args []string) error {
return nil
},
}
f := cmd.Flags()
f.BoolP("a", "a", false, "check built-in pflag.Value")
f.IntSlice("b", []int{1, 2}, "check built-in pflag.SliceValue with default value")
f.IntSliceP("c", "c", nil, "check built pflag.SliceValue with nil default value")
f.Var(&mockFlagWithCommaD, "d", "check custom implementation of pflag.SliceValue with splitting by comma and default value")
f.VarP(&mockFlagWithCommaE, "e", "e", "check custom implementation of pflag.SliceValue with splitting by comma and nil default value")
f.Var(&mockFlagWithSemicolonF, "f", "check custom implementation of pflag.SliceValue with splitting by semicolon and default value")
f.VarP(&mockFlagWithSemicolonG, "g", "g", "check custom implementation of pflag.SliceValue with splitting by semicolon and nil default value")
return cmd
}
cmd := getCMD()
checkFlagsValue := func(cmd *cobra.Command, notDefaultFlags map[string]string) bool {
require.NoError(t, cmd.Execute())
for _, k := range []string{"a", "b", "c", "d", "e", "f", "g"} {
curVal := cmd.Flag(k).Value
curDefVal := cmd.Flag(k).DefValue
if v, ok := notDefaultFlags[k]; ok {
require.NotEqual(t, curVal.String(), curDefVal, fmt.Sprintf("flag: %s, cmp_to: %v", k, curVal))
require.Equal(t, curVal.String(), v, fmt.Sprintf("flag: %s, cmp_to: %v", k, curVal))
} else {
require.Equal(t, curVal.String(), curDefVal, fmt.Sprintf("flag: %s, cmp_to: %v", k, curVal))
}
}
return true
}
testCases := []struct {
name string
steps []struct {
args []string
expectNotDefaultFlags map[string]string
}
}{
{
name: "no args",
steps: []struct {
args []string
expectNotDefaultFlags map[string]string
}{
{
args: nil,
expectNotDefaultFlags: nil,
},
},
},
{
name: "built-in implementation of pflag.Value",
steps: []struct {
args []string
expectNotDefaultFlags map[string]string
}{
{
args: []string{"--a=true"},
expectNotDefaultFlags: map[string]string{"a": "true"},
},
},
},
{
name: "built-in implementation of pflag.SliceValue",
steps: []struct {
args []string
expectNotDefaultFlags map[string]string
}{
{
args: []string{"--b=3,4"},
expectNotDefaultFlags: map[string]string{"b": "[3,4]"},
},
{
args: []string{"--c=3,4"},
expectNotDefaultFlags: map[string]string{"c": "[3,4]"},
},
},
},
{
name: "custom implementation of pflag.SliceValue with comma",
steps: []struct {
args []string
expectNotDefaultFlags map[string]string
}{
{
args: []string{"--d=g;n,g;m"},
expectNotDefaultFlags: map[string]string{"d": "g;n,g;m"},
},
{
args: []string{"--e=g;n,g;m"},
expectNotDefaultFlags: map[string]string{"e": "g;n,g;m"},
},
},
},
{
// custom implementation of pflag.SliceValue with splitting by semicolon is not compatible with testutil.SetArgs.
// So `f` is changed to "g;m;g;n" (split to ["g", "m;g", "n"], and then join with ";"), not default value "g,m;g,n"
name: "custom implementation of pflag.SliceValue with semicolon",
steps: []struct {
args []string
expectNotDefaultFlags map[string]string
}{
{
args: []string{"--f=g,n;g,m"},
expectNotDefaultFlags: map[string]string{"f": "g,n;g,m"},
},
{
args: []string{"--g=g,n;g,m"},
expectNotDefaultFlags: map[string]string{"f": "g;m;g;n", "g": "g,n;g,m"},
},
},
},
}
for _, testCase := range testCases {
t.Run(testCase.name, func(t *testing.T) {
for _, step := range testCase.steps {
testutil.ResetArgs(t, cmd)
args := append([]string{"testcmd"}, step.args...)
cmd.SetArgs(args)
checkFlagsValue(cmd, step.expectNotDefaultFlags)
}
})
}
}
+77
View File
@@ -0,0 +1,77 @@
package testutil
import "strings"
type MockFlagsWithComma struct {
Ary []string
changed bool
}
func (m *MockFlagsWithComma) String() string {
return strings.Join(m.Ary, ",")
}
func (m *MockFlagsWithComma) Set(value string) error {
if m.changed {
m.Ary = append(m.Ary, strings.Split(value, ",")...)
} else {
m.Ary = strings.Split(value, ",")
m.changed = true
}
return nil
}
func (m *MockFlagsWithComma) Type() string {
return "mock_flags"
}
func (m *MockFlagsWithComma) Replace(value []string) error {
m.Ary = value
return nil
}
func (m *MockFlagsWithComma) Append(value string) error {
m.Ary = append(m.Ary, value)
return nil
}
func (m *MockFlagsWithComma) GetSlice() []string {
return m.Ary
}
type MockFlagsWithSemicolon struct {
Ary []string
changed bool
}
func (m *MockFlagsWithSemicolon) String() string {
return strings.Join(m.Ary, ";")
}
func (m *MockFlagsWithSemicolon) Set(value string) error {
if m.changed {
m.Ary = append(m.Ary, strings.Split(value, ";")...)
} else {
m.Ary = strings.Split(value, ";")
m.changed = true
}
return nil
}
func (m *MockFlagsWithSemicolon) Type() string {
return "mock_flags"
}
func (m *MockFlagsWithSemicolon) Replace(value []string) error {
m.Ary = value
return nil
}
func (m *MockFlagsWithSemicolon) Append(value string) error {
m.Ary = append(m.Ary, value)
return nil
}
func (m *MockFlagsWithSemicolon) GetSlice() []string {
return m.Ary
}
-55
View File
@@ -1,55 +0,0 @@
<!--
Guiding Principles:
Changelogs are for humans, not machines.
There should be an entry for every single version.
The same types of changes should be grouped.
Versions and sections should be linkable.
The latest version comes first.
The release date of each version is displayed.
Mention whether you follow Semantic Versioning.
Usage:
Change log entries are to be added to the Unreleased section under the
appropriate stanza (see below). Each entry should ideally include a tag and
the Github issue reference in the following format:
* (<tag>) \#<issue-number> message
The issue numbers will later be link-ified during the release process so you do
not have to worry about including a link manually, but you can if you wish.
Types of changes (Stanzas):
"Features" for new features.
"Improvements" for changes in existing functionality.
"Deprecated" for soon-to-be removed features.
"Bug Fixes" for any bug fixes.
"Client Breaking" for breaking Protobuf, gRPC and REST routes used by end-users.
"CLI Breaking" for breaking CLI commands.
"API Breaking" for breaking exported APIs used by developers building on SDK.
Ref: https://keepachangelog.com/en/1.0.0/
-->
# Changelog
## [Unreleased]
### Feature
* [#15320](https://github.com/cosmos/cosmos-sdk/pull/15320) Add current sequence getter (`LastInsertedSequence`) for auto increment tables.
### API Breaking Changes
* [#15870](https://github.com/cosmos/cosmos-sdk/pull/15870) Rename the orm package to `cosmossdk.io/orm`.
* [#14822](https://github.com/cosmos/cosmos-sdk/pull/14822) Migrate to cosmossdk.io/core genesis API.
### State-machine Breaking Changes
* [#12273](https://github.com/cosmos/cosmos-sdk/pull/12273) The timestamp key encoding was reworked to properly handle nil values. Existing users will need to manually migrate their data to the new encoding before upgrading.
* [#15138](https://github.com/cosmos/cosmos-sdk/pull/15138) The duration key encoding was reworked to properly handle nil values. Existing users will need to manually migrate their data to the new encoding before upgrading.
### Bug Fixes
* [#16023](https://github.com/cosmos/cosmos-sdk/pull/16023) Fix bugs introduced by lack of CI tests in [#15138](https://github.com/cosmos/cosmos-sdk/pull/15138) and [#15813](https://github.com/cosmos/cosmos-sdk/pull/15813). This changes the duration encoding in [#15138](https://github.com/cosmos/cosmos-sdk/pull/15138) to correctly order values with negative nanos.
-329
View File
@@ -1,329 +0,0 @@
# ORM
The Cosmos SDK ORM is a state management library that provides a rich, but opinionated set of tools for managing a
module's state. It provides support for:
* type safe management of state
* multipart keys
* secondary indexes
* unique indexes
* easy prefix and range queries
* automatic genesis import/export
* automatic query services for clients, including support for light client proofs (still in development)
* indexing state data in external databases (still in development)
## Design and Philosophy
The ORM's data model is inspired by the relational data model found in SQL databases. The core abstraction is a table
with a primary key and optional secondary indexes.
Because the Cosmos SDK uses protobuf as its encoding layer, ORM tables are defined directly in .proto files using
protobuf options. Each table is defined by a single protobuf `message` type and a schema of multiple tables is
represented by a single .proto file.
Table structure is specified in the same file where messages are defined in order to make it easy to focus on better
design of the state layer. Because blockchain state layout is part of the public API for clients (TODO: link to docs on
light client proofs), it is important to think about the state layout as being part of the public API of a module.
Changing the state layout actually breaks clients, so it is ideal to think through it carefully up front and to aim for
a design that will eliminate or minimize breaking changes down the road. Also, good design of state enables building
more performant and sophisticated applications. Providing users with a set of tools inspired by relational databases
which have a long history of database design best practices and allowing schema to be specified declaratively in a
single place are design choices the ORM makes to enable better design and more durable APIs.
Also, by only supporting the table abstraction as opposed to key-value pair maps, it is easy to add to new
columns/fields to any data structure without causing a breaking change and the data structures can easily be indexed in
any off-the-shelf SQL database for more sophisticated queries.
The encoding of fields in keys is designed to support ordered iteration for all protobuf primitive field types
except for `bytes` as well as the well-known types `google.protobuf.Timestamp` and `google.protobuf.Duration`. Encodings
are optimized for storage space when it makes sense (see the documentation in `cosmos/orm/v1/orm.proto` for more details)
and table rows do not use extra storage space to store key fields in the value.
We recommend that users of the ORM attempt to follow database design best practices such as
[normalization](https://en.wikipedia.org/wiki/Database_normalization) (at least 1NF).
For instance, defining `repeated` fields in a table is considered an anti-pattern because breaks first normal form (1NF).
Although we support `repeated` fields in tables, they cannot be used as key fields for this reason. This may seem
restrictive but years of best practice (and also experience in the SDK) have shown that following this pattern
leads to easier to maintain schemas.
To illustrate the motivation for these principles with an example from the SDK, historically balances were stored
as a mapping from account -> map of denom to amount. This did not scale well because an account with 100 token balances
needed to be encoded/decoded every time a single coin balance changed. Now balances are stored as account,denom -> amount
as in the example above. With the ORM's data model, if we wanted to add a new field to `Balance` such as
`unlocked_balance` (if vesting accounts were redesigned in this way), it would be easy to add it to this table without
requiring a data migration. Because of the ORM's optimizations, the account and denom are only stored in the key part
of storage and not in the value leading to both a flexible data model and efficient usage of storage.
## Defining Tables
To define a table:
1) create a .proto file to describe the module's state (naming it `state.proto` is recommended for consistency),
and import "cosmos/orm/v1/orm.proto", ex:
```protobuf
syntax = "proto3";
package bank_example;
import "cosmos/orm/v1/orm.proto";
```
2) define a `message` for the table, ex:
```protobuf
message Balance {
bytes account = 1;
string denom = 2;
uint64 balance = 3;
}
```
3) add the `cosmos.orm.v1.table` option to the table and give the table an `id` unique within this .proto file:
```protobuf
message Balance {
option (cosmos.orm.v1.table) = {
id: 1
};
bytes account = 1;
string denom = 2;
uint64 balance = 3;
}
```
4) define the primary key field or fields, as a comma-separated list of the fields from the message which should make
up the primary key:
```protobuf
message Balance {
option (cosmos.orm.v1.table) = {
id: 1
primary_key: { fields: "account,denom" }
};
bytes account = 1;
string denom = 2;
uint64 balance = 3;
}
```
5) add any desired secondary indexes by specifying an `id` unique within the table and a comma-separate list of the
index fields:
```protobuf
message Balance {
option (cosmos.orm.v1.table) = {
id: 1;
primary_key: { fields: "account,denom" }
index: { id: 1 fields: "denom" } // this allows querying for the accounts which own a denom
};
bytes account = 1;
string denom = 2;
uint64 amount = 3;
}
```
### Auto-incrementing Primary Keys
A common pattern in SDK modules and in database design is to define tables with a single integer `id` field with an
automatically generated primary key. In the ORM we can do this by setting the `auto_increment` option to `true` on the
primary key, ex:
```protobuf
message Account {
option (cosmos.orm.v1.table) = {
id: 2;
primary_key: { fields: "id", auto_increment: true }
};
uint64 id = 1;
bytes address = 2;
}
```
### Unique Indexes
A unique index can be added by setting the `unique` option to `true` on an index, ex:
```protobuf
message Account {
option (cosmos.orm.v1.table) = {
id: 2;
primary_key: { fields: "id", auto_increment: true }
index: {id: 1, fields: "address", unique: true}
};
uint64 id = 1;
bytes address = 2;
}
```
### Singletons
The ORM also supports a special type of table with only one row called a `singleton`. This can be used for storing
module parameters. Singletons only need to define a unique `id` and that cannot conflict with the id of other
tables or singletons in the same .proto file. Ex:
```protobuf
message Params {
option (cosmos.orm.v1.singleton) = {
id: 3;
};
google.protobuf.Duration voting_period = 1;
uint64 min_threshold = 2;
}
```
## Running Codegen
NOTE: the ORM will only work with protobuf code that implements the [google.golang.org/protobuf](https://pkg.go.dev/google.golang.org/protobuf)
API. That means it will not work with code generated using gogo-proto.
To install the ORM's code generator, run:
```shell
go install cosmossdk.io/orm/cmd/protoc-gen-go-cosmos-orm@latest
```
The recommended way to run the code generator is to use [buf build](https://docs.buf.build/build/usage).
This is an example `buf.gen.yaml` that runs `protoc-gen-go`, `protoc-gen-go-grpc` and `protoc-gen-go-cosmos-orm`
using buf managed mode:
```yaml
version: v1
managed:
enabled: true
go_package_prefix:
default: foo.bar/api # the go package prefix of your package
override:
buf.build/cosmos/cosmos-sdk: cosmossdk.io/api # required to import the Cosmos SDK api module
plugins:
- name: go
out: .
opt: paths=source_relative
- name: go-grpc
out: .
opt: paths=source_relative
- name: go-cosmos-orm
out: .
opt: paths=source_relative
```
## Using the ORM in a module
### Initialization
To use the ORM in a module, first create a `ModuleSchemaDescriptor`. This tells the ORM which .proto files have defined
an ORM schema and assigns them all a unique non-zero id. Ex:
```go
var MyModuleSchema = &ormv1alpha1.ModuleSchemaDescriptor{
SchemaFile: []*ormv1alpha1.ModuleSchemaDescriptor_FileEntry{
{
Id: 1,
ProtoFileName: mymodule.File_my_module_state_proto.Path(),
},
},
}
```
In the ORM generated code for a file named `state.proto`, there should be an interface `StateStore` that got generated
with a constructor `NewStateStore` that takes a parameter of type `ormdb.ModuleDB`. Add a reference to `StateStore`
to your module's keeper struct. Ex:
```go
type Keeper struct {
db StateStore
}
```
Then instantiate the `StateStore` instance via an `ormdb.ModuleDB` that is instantiated from the `SchemaDescriptor`
above and one or more store services from `cosmossdk.io/core/store`. Ex:
```go
func NewKeeper(storeService store.KVStoreService) (*Keeper, error) {
modDb, err := ormdb.NewModuleDB(MyModuleSchema, ormdb.ModuleDBOptions{KVStoreService: storeService})
if err != nil {
return nil, err
}
db, err := NewStateStore(modDb)
if err != nil {
return nil, err
}
return Keeper{db: db}, nil
}
```
### Using the generated code
The generated code for the ORM contains methods for inserting, updating, deleting and querying table entries.
For each table in a .proto file, there is a type-safe table interface implemented in generated code. For instance,
for a table named `Balance` there should be a `BalanceTable` interface that looks like this:
```go
type BalanceTable interface {
Insert(ctx context.Context, balance *Balance) error
Update(ctx context.Context, balance *Balance) error
Save(ctx context.Context, balance *Balance) error
Delete(ctx context.Context, balance *Balance) error
Has(ctx context.Context, acocunt []byte, denom string) (found bool, err error)
// Get returns nil and an error which responds true to ormerrors.IsNotFound() if the record was not found.
Get(ctx context.Context, acocunt []byte, denom string) (*Balance, error)
List(ctx context.Context, prefixKey BalanceIndexKey, opts ...ormlist.Option) (BalanceIterator, error)
ListRange(ctx context.Context, from, to BalanceIndexKey, opts ...ormlist.Option) (BalanceIterator, error)
DeleteBy(ctx context.Context, prefixKey BalanceIndexKey) error
DeleteRange(ctx context.Context, from, to BalanceIndexKey) error
doNotImplement()
}
```
This `BalanceTable` should be accessible from the `StateStore` interface (assuming our file is named `state.proto`)
via a `BalanceTable()` accessor method. If all the above example tables/singletons were in the same `state.proto`,
then `StateStore` would get generated like this:
```go
type BankStore interface {
BalanceTable() BalanceTable
AccountTable() AccountTable
ParamsTable() ParamsTable
doNotImplement()
}
```
So to work with the `BalanceTable` in a keeper method we could use code like this:
```go
func (k keeper) AddBalance(ctx context.Context, acct []byte, denom string, amount uint64) error {
balance, err := k.db.BalanceTable().Get(ctx, acct, denom)
if err != nil && !ormerrors.IsNotFound(err) {
return err
}
if balance == nil {
balance = &Balance{
Account: acct,
Denom: denom,
Amount: amount,
}
} else {
balance.Amount = balance.Amount + amount
}
return k.db.BalanceTable().Save(ctx, balance)
}
```
`List` methods take `IndexKey` parameters. For instance, `BalanceTable.List` takes `BalanceIndexKey`. `BalanceIndexKey`
let's represent index keys for the different indexes (primary and secondary) on the `Balance` table. The primary key
in the `Balance` table gets a struct `BalanceAccountDenomIndexKey` and the first index gets an index key `BalanceDenomIndexKey`.
If we wanted to list all the denoms and amounts that an account holds, we would use `BalanceAccountDenomIndexKey`
with a `List` query just on the account prefix. Ex:
```go
it, err := keeper.db.BalanceTable().List(ctx, BalanceAccountDenomIndexKey{}.WithAccount(acct))
```
-54
View File
@@ -1,54 +0,0 @@
package encodeutil
import (
"bytes"
"encoding/binary"
"io"
"reflect"
"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++ {
// we catch the case of proto messages here and call ProtoReflect.
// this allows us to use imported messages, such as timestamppb.Timestamp
// in iterators.
value := values[i]
if v, ok := value.(protoreflect.ProtoMessage); ok {
if !reflect.ValueOf(value).IsNil() {
value = v.ProtoReflect()
} else {
value = nil
}
}
res[i] = protoreflect.ValueOf(value)
}
return res
}
-60
View File
@@ -1,60 +0,0 @@
package ormfield
import (
io "io"
"google.golang.org/protobuf/reflect/protoreflect"
)
// BoolCodec encodes a bool value as a single byte 0 or 1.
type BoolCodec struct{}
func (b BoolCodec) Decode(r Reader) (protoreflect.Value, error) {
x, err := r.ReadByte()
return protoreflect.ValueOfBool(x != 0), err
}
var (
zeroBz = []byte{0}
oneBz = []byte{1}
)
func (b BoolCodec) Encode(value protoreflect.Value, w io.Writer) error {
var err error
if !value.IsValid() || !value.Bool() {
_, err = w.Write(zeroBz)
} else {
_, err = w.Write(oneBz)
}
return err
}
func (b BoolCodec) Compare(v1, v2 protoreflect.Value) int {
var b1, b2 bool
if v1.IsValid() {
b1 = v1.Bool()
}
if v2.IsValid() {
b2 = v2.Bool()
}
switch {
case b1 == b2:
return 0
case b1:
return -1
default:
return 1
}
}
func (b BoolCodec) IsOrdered() bool {
return false
}
func (b BoolCodec) FixedBufferSize() int {
return 1
}
func (b BoolCodec) ComputeBufferSize(protoreflect.Value) (int, error) {
return b.FixedBufferSize(), nil
}
-171
View File
@@ -1,171 +0,0 @@
package ormfield_test
import (
"bytes"
"fmt"
"testing"
"google.golang.org/protobuf/reflect/protoreflect"
"gotest.tools/v3/assert"
"pgregory.net/rapid"
"cosmossdk.io/orm/encoding/ormfield"
"cosmossdk.io/orm/internal/testutil"
"cosmossdk.io/orm/types/ormerrors"
)
func TestCodec(t *testing.T) {
for _, ks := range testutil.TestFieldSpecs {
testCodec(t, ks)
}
}
func testCodec(t *testing.T, spec testutil.TestFieldSpec) {
t.Run(fmt.Sprintf("%s %v", spec.FieldName, false), func(t *testing.T) {
testCodecNT(t, spec.FieldName, spec.Gen, false)
})
t.Run(fmt.Sprintf("%s %v", spec.FieldName, true), func(t *testing.T) {
testCodecNT(t, spec.FieldName, spec.Gen, true)
})
}
func testCodecNT(t *testing.T, fname protoreflect.Name, generator *rapid.Generator[any], nonTerminal bool) {
cdc, err := testutil.MakeTestCodec(fname, nonTerminal)
assert.NilError(t, err)
rapid.Check(t, func(t *rapid.T) {
x := protoreflect.ValueOf(generator.Draw(t, string(fname)))
bz1 := checkEncodeDecodeSize(t, x, cdc)
if cdc.IsOrdered() {
y := protoreflect.ValueOf(generator.Draw(t, fmt.Sprintf("%s 2", fname)))
bz2 := checkEncodeDecodeSize(t, y, cdc)
assert.Equal(t, cdc.Compare(x, y), bytes.Compare(bz1, bz2))
}
})
}
func checkEncodeDecodeSize(t *rapid.T, x protoreflect.Value, cdc ormfield.Codec) []byte {
buf := &bytes.Buffer{}
err := cdc.Encode(x, buf)
assert.NilError(t, err)
bz := buf.Bytes()
size, err := cdc.ComputeBufferSize(x)
assert.NilError(t, err)
assert.Assert(t, size >= len(bz))
fixedSize := cdc.FixedBufferSize()
if fixedSize > 0 {
assert.Equal(t, fixedSize, size)
}
y, err := cdc.Decode(bytes.NewReader(bz))
assert.NilError(t, err)
assert.Equal(t, 0, cdc.Compare(x, y))
return bz
}
func TestUnsupportedFields(t *testing.T) {
_, err := ormfield.GetCodec(nil, false)
assert.ErrorContains(t, err, ormerrors.InvalidKeyField.Error())
_, err = ormfield.GetCodec(testutil.GetTestField("repeated"), false)
assert.ErrorContains(t, err, ormerrors.InvalidKeyField.Error())
_, err = ormfield.GetCodec(testutil.GetTestField("map"), false)
assert.ErrorContains(t, err, ormerrors.InvalidKeyField.Error())
_, err = ormfield.GetCodec(testutil.GetTestField("msg"), false)
assert.ErrorContains(t, err, ormerrors.InvalidKeyField.Error())
_, err = ormfield.GetCodec(testutil.GetTestField("oneof"), false)
assert.ErrorContains(t, err, ormerrors.InvalidKeyField.Error())
}
func TestCompactUInt32(t *testing.T) {
var lastBz []byte
testEncodeDecode := func(x uint32, expectedLen int) {
bz := ormfield.EncodeCompactUint32(x)
assert.Equal(t, expectedLen, len(bz))
y, err := ormfield.DecodeCompactUint32(bytes.NewReader(bz))
assert.NilError(t, err)
assert.Equal(t, x, y)
assert.Assert(t, bytes.Compare(lastBz, bz) < 0)
lastBz = bz
}
testEncodeDecode(64, 2)
testEncodeDecode(16383, 2)
testEncodeDecode(16384, 3)
testEncodeDecode(4194303, 3)
testEncodeDecode(4194304, 4)
testEncodeDecode(1073741823, 4)
testEncodeDecode(1073741824, 5)
// randomized tests
rapid.Check(t, func(t *rapid.T) {
x := rapid.Uint32().Draw(t, "x")
y := rapid.Uint32().Draw(t, "y")
bx := ormfield.EncodeCompactUint32(x)
by := ormfield.EncodeCompactUint32(y)
cmp := bytes.Compare(bx, by)
switch {
case x < y:
assert.Equal(t, -1, cmp)
case x == y:
assert.Equal(t, 0, cmp)
default:
assert.Equal(t, 1, cmp)
}
x2, err := ormfield.DecodeCompactUint32(bytes.NewReader(bx))
assert.NilError(t, err)
assert.Equal(t, x, x2)
y2, err := ormfield.DecodeCompactUint32(bytes.NewReader(by))
assert.NilError(t, err)
assert.Equal(t, y, y2)
})
}
func TestCompactUInt64(t *testing.T) {
var lastBz []byte
testEncodeDecode := func(x uint64, expectedLen int) {
bz := ormfield.EncodeCompactUint64(x)
assert.Equal(t, expectedLen, len(bz))
y, err := ormfield.DecodeCompactUint64(bytes.NewReader(bz))
assert.NilError(t, err)
assert.Equal(t, x, y)
assert.Assert(t, bytes.Compare(lastBz, bz) < 0)
lastBz = bz
}
testEncodeDecode(64, 2)
testEncodeDecode(16383, 2)
testEncodeDecode(16384, 4)
testEncodeDecode(4194303, 4)
testEncodeDecode(4194304, 4)
testEncodeDecode(1073741823, 4)
testEncodeDecode(1073741824, 6)
testEncodeDecode(70368744177663, 6)
testEncodeDecode(70368744177664, 9)
// randomized tests
rapid.Check(t, func(t *rapid.T) {
x := rapid.Uint64().Draw(t, "x")
y := rapid.Uint64().Draw(t, "y")
bx := ormfield.EncodeCompactUint64(x)
by := ormfield.EncodeCompactUint64(y)
cmp := bytes.Compare(bx, by)
switch {
case x < y:
assert.Equal(t, -1, cmp)
case x == y:
assert.Equal(t, 0, cmp)
default:
assert.Equal(t, 1, cmp)
}
x2, err := ormfield.DecodeCompactUint64(bytes.NewReader(bx))
assert.NilError(t, err)
assert.Equal(t, x, x2)
y2, err := ormfield.DecodeCompactUint64(bytes.NewReader(by))
assert.NilError(t, err)
assert.Equal(t, y, y2)
})
}
-193
View File
@@ -1,193 +0,0 @@
package ormfield
import (
"fmt"
io "io"
"google.golang.org/protobuf/reflect/protoreflect"
)
const (
DurationSecondsMin int64 = -315576000000
DurationSecondsMax int64 = 315576000000
DurationNanosMin = -999999999
DurationNanosMax = 999999999
)
// DurationCodec encodes google.protobuf.Duration values with the following
// encoding:
// - nil is encoded as []byte{0xFF}
// - seconds (which can range from -315,576,000,000 to +315,576,000,000) is encoded as 5 fixed bytes
// - nanos (which can range from 0 to 999,999,999 or -999,999,999 to 0 if seconds is negative) is encoded as:
// - []byte{0x0} for zero nanos
// - 4 fixed bytes with the bit mask 0xC0 applied to the first byte, with negative nanos scaled so that -999,999,999
// is encoded as 1 and -1 is encoded as 999,999,999
//
// When iterating over timestamp indexes, nil values will always be ordered last.
//
// Values for seconds and nanos outside the ranges specified by google.protobuf.Duration will be rejected.
type DurationCodec struct{}
func (d DurationCodec) Encode(value protoreflect.Value, w io.Writer) error {
// nil case
if !value.IsValid() {
_, err := w.Write(timestampDurationNilBz)
return err
}
seconds, nanos := getDurationSecondsAndNanos(value)
secondsInt := seconds.Int()
if secondsInt < DurationSecondsMin || secondsInt > DurationSecondsMax {
return fmt.Errorf("duration seconds is out of range %d, must be between %d and %d", secondsInt, DurationSecondsMin, DurationSecondsMax)
}
negative := secondsInt < 0
// we subtract the min duration value to make sure secondsInt is always non-negative and starts at 0.
secondsInt -= DurationSecondsMin
err := encodeSeconds(secondsInt, w)
if err != nil {
return err
}
nanosInt := nanos.Int()
if nanosInt == 0 {
_, err = w.Write(timestampZeroNanosBz)
return err
}
if negative {
if nanosInt < DurationNanosMin || nanosInt > 0 {
return fmt.Errorf("negative duration nanos is out of range %d, must be between %d and %d", nanosInt, DurationNanosMin, 0)
}
nanosInt = DurationNanosMax + nanosInt + 1
} else if nanosInt < 0 || nanosInt > DurationNanosMax {
return fmt.Errorf("duration nanos is out of range %d, must be between %d and %d", nanosInt, 0, DurationNanosMax)
}
return encodeNanos(nanosInt, w)
}
func (d DurationCodec) Decode(r Reader) (protoreflect.Value, error) {
isNil, seconds, err := decodeSeconds(r)
if isNil || err != nil {
return protoreflect.Value{}, err
}
// we add the min duration value to get back the original value
seconds += DurationSecondsMin
negative := seconds < 0
msg := durationMsgType.New()
msg.Set(durationSecondsField, protoreflect.ValueOfInt64(seconds))
nanos, err := decodeNanos(r)
if err != nil {
return protoreflect.Value{}, err
}
if nanos == 0 {
return protoreflect.ValueOfMessage(msg), nil
}
if negative {
nanos = nanos - DurationNanosMax - 1
}
msg.Set(durationNanosField, protoreflect.ValueOfInt32(nanos))
return protoreflect.ValueOfMessage(msg), nil
}
func (d DurationCodec) Compare(v1, v2 protoreflect.Value) int {
if !v1.IsValid() {
if !v2.IsValid() {
return 0
}
return 1
}
if !v2.IsValid() {
return -1
}
s1, n1 := getDurationSecondsAndNanos(v1)
s2, n2 := getDurationSecondsAndNanos(v2)
c := compareInt(s1, s2)
if c != 0 {
return c
}
return compareInt(n1, n2)
}
func (d DurationCodec) IsOrdered() bool {
return true
}
func (d DurationCodec) FixedBufferSize() int {
return timestampDurationBufferSize
}
func (d DurationCodec) ComputeBufferSize(protoreflect.Value) (int, error) {
return timestampDurationBufferSize, nil
}
var (
durationSecondsField = durationMsgType.Descriptor().Fields().ByName("seconds")
durationNanosField = durationMsgType.Descriptor().Fields().ByName("nanos")
)
func getDurationSecondsAndNanos(value protoreflect.Value) (protoreflect.Value, protoreflect.Value) {
msg := value.Message()
return msg.Get(durationSecondsField), msg.Get(durationNanosField)
}
// DurationV0Codec encodes a google.protobuf.Duration value as 12 bytes using
// Int64Codec for seconds followed by Int32Codec for nanos. This allows for
// sorted iteration.
type DurationV0Codec struct{}
func (d DurationV0Codec) Decode(r Reader) (protoreflect.Value, error) {
seconds, err := int64Codec.Decode(r)
if err != nil {
return protoreflect.Value{}, err
}
nanos, err := int32Codec.Decode(r)
if err != nil {
return protoreflect.Value{}, err
}
msg := durationMsgType.New()
msg.Set(durationSecondsField, seconds)
msg.Set(durationNanosField, nanos)
return protoreflect.ValueOfMessage(msg), nil
}
func (d DurationV0Codec) Encode(value protoreflect.Value, w io.Writer) error {
seconds, nanos := getDurationSecondsAndNanos(value)
err := int64Codec.Encode(seconds, w)
if err != nil {
return err
}
return int32Codec.Encode(nanos, w)
}
func (d DurationV0Codec) Compare(v1, v2 protoreflect.Value) int {
s1, n1 := getDurationSecondsAndNanos(v1)
s2, n2 := getDurationSecondsAndNanos(v2)
c := compareInt(s1, s2)
if c != 0 {
return c
}
return compareInt(n1, n2)
}
func (d DurationV0Codec) IsOrdered() bool {
return true
}
func (d DurationV0Codec) FixedBufferSize() int {
return 12
}
func (d DurationV0Codec) ComputeBufferSize(protoreflect.Value) (int, error) {
return d.FixedBufferSize(), nil
}
-281
View File
@@ -1,281 +0,0 @@
package ormfield_test
import (
"bytes"
"testing"
"time"
"google.golang.org/protobuf/reflect/protoreflect"
"google.golang.org/protobuf/types/known/durationpb"
"gotest.tools/v3/assert"
"cosmossdk.io/orm/encoding/ormfield"
)
func TestDurationNil(t *testing.T) {
t.Parallel()
cdc := ormfield.DurationCodec{}
buf := &bytes.Buffer{}
assert.NilError(t, cdc.Encode(protoreflect.Value{}, buf))
assert.Equal(t, 1, len(buf.Bytes()))
val, err := cdc.Decode(buf)
assert.NilError(t, err)
assert.Assert(t, !val.IsValid())
}
func TestDuration(t *testing.T) {
t.Parallel()
cdc := ormfield.DurationCodec{}
tt := []struct {
name string
seconds int64
nanos int32
wantLen int
}{
{
"no nanos",
100,
0,
6,
},
{
"with nanos",
3,
879468295,
9,
},
{
"min seconds, -1 nanos",
-315576000000,
-1,
9,
},
{
"min value",
-315576000000,
-999999999,
9,
},
{
"max value",
315576000000,
999999999,
9,
},
{
"max seconds, 1 nanos",
315576000000,
1,
9,
},
}
for _, tc := range tt {
t.Run(tc.name, func(t *testing.T) {
durPb := &durationpb.Duration{
Seconds: tc.seconds,
Nanos: tc.nanos,
}
val := protoreflect.ValueOfMessage(durPb.ProtoReflect())
buf := &bytes.Buffer{}
assert.NilError(t, cdc.Encode(val, buf))
assert.Equal(t, tc.wantLen, len(buf.Bytes()))
val2, err := cdc.Decode(buf)
assert.NilError(t, err)
assert.Equal(t, 0, cdc.Compare(val, val2))
})
}
}
func TestDurationOutOfRange(t *testing.T) {
t.Parallel()
cdc := ormfield.DurationCodec{}
tt := []struct {
name string
dur *durationpb.Duration
expectErr string
}{
{
name: "seconds too small",
dur: &durationpb.Duration{
Seconds: -315576000001,
Nanos: 0,
},
expectErr: "seconds is out of range",
},
{
name: "seconds too big",
dur: &durationpb.Duration{
Seconds: 315576000001,
Nanos: 0,
},
expectErr: "seconds is out of range",
},
{
name: "positive seconds negative nanos",
dur: &durationpb.Duration{
Seconds: 0,
Nanos: -1,
},
expectErr: "nanos is out of range",
},
{
name: "positive seconds nanos too big",
dur: &durationpb.Duration{
Seconds: 0,
Nanos: 1000000000,
},
expectErr: "nanos is out of range",
},
{
name: "negative seconds positive nanos",
dur: &durationpb.Duration{
Seconds: -1,
Nanos: 1,
},
expectErr: "negative duration nanos is out of range",
},
{
name: "negative seconds nanos too small",
dur: &durationpb.Duration{
Seconds: -1,
Nanos: -1000000000,
},
expectErr: "negative duration nanos is out of range",
},
}
for _, tc := range tt {
tc := tc
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
val := protoreflect.ValueOfMessage(tc.dur.ProtoReflect())
buf := &bytes.Buffer{}
err := cdc.Encode(val, buf)
assert.ErrorContains(t, err, tc.expectErr)
})
}
}
func TestDurationCompare(t *testing.T) {
t.Parallel()
cdc := ormfield.DurationCodec{}
tt := []struct {
name string
dur1 *durationpb.Duration
dur2 *durationpb.Duration
want int
}{
{
name: "equal",
dur1: &durationpb.Duration{
Seconds: 1,
Nanos: 1,
},
dur2: &durationpb.Duration{
Seconds: 1,
Nanos: 1,
},
want: 0,
},
{
name: "seconds equal, dur1 nanos less than dur2 nanos",
dur1: &durationpb.Duration{
Seconds: 1,
Nanos: 1,
},
dur2: &durationpb.Duration{
Seconds: 1,
Nanos: 2,
},
want: -1,
},
{
name: "seconds equal, dur1 nanos greater than dur2 nanos",
dur1: &durationpb.Duration{
Seconds: 1,
Nanos: 2,
},
dur2: &durationpb.Duration{
Seconds: 1,
Nanos: 1,
},
want: 1,
},
{
name: "seconds less than",
dur1: &durationpb.Duration{
Seconds: 1,
Nanos: 1,
},
dur2: &durationpb.Duration{
Seconds: 2,
Nanos: 1,
},
want: -1,
},
{
name: "seconds greater than",
dur1: &durationpb.Duration{
Seconds: 2,
Nanos: 1,
},
dur2: &durationpb.Duration{
Seconds: 1,
Nanos: 1,
},
want: 1,
},
{
name: "negative seconds equal, dur1 nanos less than dur2 nanos",
dur1: &durationpb.Duration{
Seconds: -1,
Nanos: -2,
},
dur2: &durationpb.Duration{
Seconds: -1,
Nanos: -1,
},
want: -1,
},
}
for _, tc := range tt {
tc := tc
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
val1 := protoreflect.ValueOfMessage(tc.dur1.ProtoReflect())
val2 := protoreflect.ValueOfMessage(tc.dur2.ProtoReflect())
got := cdc.Compare(val1, val2)
assert.Equal(t, tc.want, got, "Compare(%v, %v)", tc.dur1, tc.dur2)
bz1 := encodeValue(t, cdc, val1)
bz2 := encodeValue(t, cdc, val2)
assert.Equal(t, tc.want, bytes.Compare(bz1, bz2), "bytes.Compare(%v, %v)", bz1, bz2)
})
}
t.Run("nanos", func(t *testing.T) {
t.Parallel()
dur, err := time.ParseDuration("3879468295ns")
assert.NilError(t, err)
durPb := durationpb.New(dur)
val := protoreflect.ValueOfMessage(durPb.ProtoReflect())
buf := &bytes.Buffer{}
assert.NilError(t, cdc.Encode(val, buf))
assert.Equal(t, 9, len(buf.Bytes()))
val2, err := cdc.Decode(buf)
assert.NilError(t, err)
assert.Equal(t, 0, cdc.Compare(val, val2))
})
}
func encodeValue(t *testing.T, cdc ormfield.Codec, val protoreflect.Value) []byte {
buf := &bytes.Buffer{}
assert.NilError(t, cdc.Encode(val, buf))
return buf.Bytes()
}
-57
View File
@@ -1,57 +0,0 @@
package ormfield
import (
"encoding/binary"
io "io"
"google.golang.org/protobuf/reflect/protoreflect"
)
// EnumCodec encodes enum values as varints.
type EnumCodec struct{}
func (e EnumCodec) Decode(r Reader) (protoreflect.Value, error) {
x, err := binary.ReadVarint(r)
return protoreflect.ValueOfEnum(protoreflect.EnumNumber(x)), err
}
func (e EnumCodec) Encode(value protoreflect.Value, w io.Writer) error {
var x protoreflect.EnumNumber
if value.IsValid() {
x = value.Enum()
}
buf := make([]byte, binary.MaxVarintLen32)
n := binary.PutVarint(buf, int64(x))
_, err := w.Write(buf[:n])
return err
}
func (e EnumCodec) Compare(v1, v2 protoreflect.Value) int {
var x, y protoreflect.EnumNumber
if v1.IsValid() {
x = v1.Enum()
}
if v2.IsValid() {
y = v2.Enum()
}
switch {
case x == y:
return 0
case x < y:
return -1
default:
return 1
}
}
func (e EnumCodec) IsOrdered() bool {
return false
}
func (e EnumCodec) FixedBufferSize() int {
return binary.MaxVarintLen32
}
func (e EnumCodec) ComputeBufferSize(protoreflect.Value) (int, error) {
return e.FixedBufferSize(), nil
}
-52
View File
@@ -1,52 +0,0 @@
package ormfield
import (
"encoding/binary"
io "io"
"google.golang.org/protobuf/reflect/protoreflect"
)
// Int32Codec encodes 32-bit integers as big-endian unsigned 32-bit integers
// by adding the maximum value of int32 (2147583647) + 1 before encoding so
// that these values can be used for ordered iteration.
type Int32Codec struct{}
var int32Codec = Int32Codec{}
const (
int32Max = 2147483647
int32Offset = int32Max + 1
)
func (i Int32Codec) Decode(r Reader) (protoreflect.Value, error) {
var x uint32
err := binary.Read(r, binary.BigEndian, &x)
y := int64(x) - int32Offset
return protoreflect.ValueOfInt32(int32(y)), err
}
func (i Int32Codec) Encode(value protoreflect.Value, w io.Writer) error {
var x int64
if value.IsValid() {
x = value.Int()
}
x += int32Offset
return binary.Write(w, binary.BigEndian, uint32(x))
}
func (i Int32Codec) Compare(v1, v2 protoreflect.Value) int {
return compareInt(v1, v2)
}
func (i Int32Codec) IsOrdered() bool {
return true
}
func (i Int32Codec) FixedBufferSize() int {
return 4
}
func (i Int32Codec) ComputeBufferSize(protoreflect.Value) (int, error) {
return i.FixedBufferSize(), nil
}
-78
View File
@@ -1,78 +0,0 @@
package ormfield
import (
"encoding/binary"
io "io"
"google.golang.org/protobuf/reflect/protoreflect"
)
// Int64Codec encodes 64-bit integers as big-endian unsigned 64-bit integers
// by adding the maximum value of int32 (9223372036854775807) + 1 before encoding so
// that these values can be used for ordered iteration.
type Int64Codec struct{}
var int64Codec = Int64Codec{}
const int64Max = 9223372036854775807
func (i Int64Codec) Decode(r Reader) (protoreflect.Value, error) {
var x uint64
err := binary.Read(r, binary.BigEndian, &x)
if x >= int64Max {
x = x - int64Max - 1
return protoreflect.ValueOfInt64(int64(x)), err
}
y := int64(x) - int64Max - 1
return protoreflect.ValueOfInt64(y), err
}
func (i Int64Codec) Encode(value protoreflect.Value, w io.Writer) error {
var x int64
if value.IsValid() {
x = value.Int()
}
if x >= -1 {
y := uint64(x) + int64Max + 1
return binary.Write(w, binary.BigEndian, y)
}
x += int64Max
x++
return binary.Write(w, binary.BigEndian, uint64(x))
}
func (i Int64Codec) Compare(v1, v2 protoreflect.Value) int {
return compareInt(v1, v2)
}
func (i Int64Codec) IsOrdered() bool {
return true
}
func (i Int64Codec) FixedBufferSize() int {
return 8
}
func (i Int64Codec) ComputeBufferSize(protoreflect.Value) (int, error) {
return i.FixedBufferSize(), nil
}
func compareInt(v1, v2 protoreflect.Value) int {
var x, y int64
if v1.IsValid() {
x = v1.Int()
}
if v2.IsValid() {
y = v2.Int()
}
switch {
case x == y:
return 0
case x < y:
return -1
default:
return 1
}
}
-109
View File
@@ -1,109 +0,0 @@
package ormfield
import (
"fmt"
"io"
"strings"
"google.golang.org/protobuf/reflect/protoreflect"
)
// StringCodec encodes strings as raw bytes.
type StringCodec struct{}
func (s StringCodec) FixedBufferSize() int {
return -1
}
func (s StringCodec) ComputeBufferSize(value protoreflect.Value) (int, error) {
if !value.IsValid() {
return 0, nil
}
return len(value.String()), nil
}
func (s StringCodec) IsOrdered() bool {
return true
}
func (s StringCodec) Compare(v1, v2 protoreflect.Value) int {
return compareStrings(v1, v2)
}
func (s StringCodec) Decode(r Reader) (protoreflect.Value, error) {
bz, err := io.ReadAll(r)
return protoreflect.ValueOfString(string(bz)), err
}
func (s StringCodec) Encode(value protoreflect.Value, w io.Writer) error {
var x string
if value.IsValid() {
x = value.String()
}
_, err := w.Write([]byte(x))
return err
}
// NonTerminalStringCodec encodes strings as null-terminated raw bytes. Null
// values within strings will produce an error.
type NonTerminalStringCodec struct{}
func (s NonTerminalStringCodec) FixedBufferSize() int {
return -1
}
func (s NonTerminalStringCodec) ComputeBufferSize(value protoreflect.Value) (int, error) {
return len(value.String()) + 1, nil
}
func (s NonTerminalStringCodec) IsOrdered() bool {
return true
}
func (s NonTerminalStringCodec) Compare(v1, v2 protoreflect.Value) int {
return compareStrings(v1, v2)
}
func (s NonTerminalStringCodec) Decode(r Reader) (protoreflect.Value, error) {
var bz []byte
for {
b, err := r.ReadByte()
if b == 0 || err == io.EOF {
return protoreflect.ValueOfString(string(bz)), err
}
bz = append(bz, b)
}
}
func (s NonTerminalStringCodec) Encode(value protoreflect.Value, w io.Writer) error {
var str string
if value.IsValid() {
str = value.String()
}
bz := []byte(str)
for _, b := range bz {
if b == 0 {
return fmt.Errorf("illegal null terminator found in index string: %s", str)
}
}
_, err := w.Write([]byte(str))
if err != nil {
return err
}
_, err = w.Write(nullTerminator)
return err
}
var nullTerminator = []byte{0}
func compareStrings(v1, v2 protoreflect.Value) int {
var x, y string
if v1.IsValid() {
x = v1.String()
}
if v2.IsValid() {
y = v2.String()
}
return strings.Compare(x, y)
}
-266
View File
@@ -1,266 +0,0 @@
package ormfield
import (
"fmt"
"io"
"google.golang.org/protobuf/reflect/protoreflect"
)
// TimestampCodec encodes google.protobuf.Timestamp values with the following
// encoding:
// - nil is encoded as []byte{0xFF}
// - seconds (which can range from 0001-01-01T00:00:00Z to 9999-12-31T23:59:59Z) is encoded as 5 fixed bytes
// - nanos (which can range from 0 to 999,999,999) is encoded as:
// - []byte{0x0} for zero nanos
// - 4 fixed bytes with the bit mask 0xC0 applied to the first byte
//
// When iterating over timestamp indexes, nil values will always be ordered last.
//
// Values for seconds and nanos outside the ranges specified by google.protobuf.Timestamp will be rejected.
type TimestampCodec struct{}
const (
timestampDurationNilValue = 0xFF
timestampDurationZeroNanosValue = 0x0
timestampDurationBufferSize = 9
TimestampSecondsMin int64 = -62135596800
TimestampSecondsMax int64 = 253402300799
TimestampNanosMax = 999999999
)
var (
timestampDurationNilBz = []byte{timestampDurationNilValue}
timestampZeroNanosBz = []byte{timestampDurationZeroNanosValue}
)
func (t TimestampCodec) Encode(value protoreflect.Value, w io.Writer) error {
// nil case
if !value.IsValid() {
_, err := w.Write(timestampDurationNilBz)
return err
}
seconds, nanos := getTimestampSecondsAndNanos(value)
secondsInt := seconds.Int()
if secondsInt < TimestampSecondsMin || secondsInt > TimestampSecondsMax {
return fmt.Errorf("timestamp seconds is out of range %d, must be between %d and %d", secondsInt, TimestampSecondsMin, TimestampSecondsMax)
}
secondsInt -= TimestampSecondsMin
err := encodeSeconds(secondsInt, w)
if err != nil {
return err
}
nanosInt := nanos.Int()
if nanosInt == 0 {
_, err = w.Write(timestampZeroNanosBz)
return err
}
if nanosInt < 0 || nanosInt > TimestampNanosMax {
return fmt.Errorf("timestamp nanos is out of range %d, must be between %d and %d", secondsInt, 0, TimestampNanosMax)
}
return encodeNanos(nanosInt, w)
}
func encodeSeconds(secondsInt int64, w io.Writer) error {
var secondsBz [5]byte
// write the seconds buffer from the end to the front
for i := 4; i >= 0; i-- {
secondsBz[i] = byte(secondsInt)
secondsInt >>= 8
}
_, err := w.Write(secondsBz[:])
return err
}
func encodeNanos(nanosInt int64, w io.Writer) error {
var nanosBz [4]byte
for i := 3; i >= 0; i-- {
nanosBz[i] = byte(nanosInt)
nanosInt >>= 8
}
nanosBz[0] |= 0xC0
_, err := w.Write(nanosBz[:])
return err
}
func (t TimestampCodec) Decode(r Reader) (protoreflect.Value, error) {
isNil, seconds, err := decodeSeconds(r)
if isNil || err != nil {
return protoreflect.Value{}, err
}
seconds += TimestampSecondsMin
msg := timestampMsgType.New()
msg.Set(timestampSecondsField, protoreflect.ValueOfInt64(seconds))
nanos, err := decodeNanos(r)
if err != nil {
return protoreflect.Value{}, err
}
if nanos == 0 {
return protoreflect.ValueOfMessage(msg), nil
}
msg.Set(timestampNanosField, protoreflect.ValueOfInt32(nanos))
return protoreflect.ValueOfMessage(msg), nil
}
func decodeSeconds(r Reader) (isNil bool, seconds int64, err error) {
b0, err := r.ReadByte()
if err != nil {
return false, 0, err
}
if b0 == timestampDurationNilValue {
return true, 0, nil
}
var secondsBz [4]byte
n, err := r.Read(secondsBz[:])
if err != nil {
return false, 0, err
}
if n < 4 {
return false, 0, io.EOF
}
seconds = int64(b0)
for i := 0; i < 4; i++ {
seconds <<= 8
seconds |= int64(secondsBz[i])
}
return false, seconds, nil
}
func decodeNanos(r Reader) (int32, error) {
b0, err := r.ReadByte()
if err != nil {
return 0, err
}
if b0 == timestampDurationZeroNanosValue {
return 0, nil
}
var nanosBz [3]byte
n, err := r.Read(nanosBz[:])
if err != nil {
return 0, err
}
if n < 3 {
return 0, io.EOF
}
nanos := int32(b0) & 0x3F // clear first two bits
for i := 0; i < 3; i++ {
nanos <<= 8
nanos |= int32(nanosBz[i])
}
return nanos, nil
}
func (t TimestampCodec) Compare(v1, v2 protoreflect.Value) int {
if !v1.IsValid() {
if !v2.IsValid() {
return 0
}
return 1
}
if !v2.IsValid() {
return -1
}
s1, n1 := getTimestampSecondsAndNanos(v1)
s2, n2 := getTimestampSecondsAndNanos(v2)
c := compareInt(s1, s2)
if c != 0 {
return c
}
return compareInt(n1, n2)
}
func (t TimestampCodec) IsOrdered() bool {
return true
}
func (t TimestampCodec) FixedBufferSize() int {
return timestampDurationBufferSize
}
func (t TimestampCodec) ComputeBufferSize(protoreflect.Value) (int, error) {
return timestampDurationBufferSize, nil
}
// TimestampV0Codec encodes a google.protobuf.Timestamp value as 12 bytes using
// Int64Codec for seconds followed by Int32Codec for nanos. This type does not
// encode nil values correctly, but is retained in order to allow users of the
// previous encoding to successfully migrate from this encoding to the new encoding
// specified by TimestampCodec.
type TimestampV0Codec struct{}
var (
timestampSecondsField = timestampMsgType.Descriptor().Fields().ByName("seconds")
timestampNanosField = timestampMsgType.Descriptor().Fields().ByName("nanos")
)
func getTimestampSecondsAndNanos(value protoreflect.Value) (protoreflect.Value, protoreflect.Value) {
msg := value.Message()
return msg.Get(timestampSecondsField), msg.Get(timestampNanosField)
}
func (t TimestampV0Codec) Decode(r Reader) (protoreflect.Value, error) {
seconds, err := int64Codec.Decode(r)
if err != nil {
return protoreflect.Value{}, err
}
nanos, err := int32Codec.Decode(r)
if err != nil {
return protoreflect.Value{}, err
}
msg := timestampMsgType.New()
msg.Set(timestampSecondsField, seconds)
msg.Set(timestampNanosField, nanos)
return protoreflect.ValueOfMessage(msg), nil
}
func (t TimestampV0Codec) Encode(value protoreflect.Value, w io.Writer) error {
seconds, nanos := getTimestampSecondsAndNanos(value)
err := int64Codec.Encode(seconds, w)
if err != nil {
return err
}
return int32Codec.Encode(nanos, w)
}
func (t TimestampV0Codec) Compare(v1, v2 protoreflect.Value) int {
s1, n1 := getTimestampSecondsAndNanos(v1)
s2, n2 := getTimestampSecondsAndNanos(v2)
c := compareInt(s1, s2)
if c != 0 {
return c
}
return compareInt(n1, n2)
}
func (t TimestampV0Codec) IsOrdered() bool {
return true
}
func (t TimestampV0Codec) FixedBufferSize() int {
return 12
}
func (t TimestampV0Codec) ComputeBufferSize(protoreflect.Value) (int, error) {
return t.FixedBufferSize(), nil
}
-126
View File
@@ -1,126 +0,0 @@
package ormfield_test
import (
"bytes"
"testing"
"time"
"google.golang.org/protobuf/reflect/protoreflect"
"google.golang.org/protobuf/types/known/timestamppb"
"gotest.tools/v3/assert"
"cosmossdk.io/orm/encoding/ormfield"
)
func TestTimestamp(t *testing.T) {
t.Parallel()
cdc := ormfield.TimestampCodec{}
t.Run("nil value", func(t *testing.T) {
t.Parallel()
buf := &bytes.Buffer{}
assert.NilError(t, cdc.Encode(protoreflect.Value{}, buf))
assert.Equal(t, 1, len(buf.Bytes()))
val, err := cdc.Decode(buf)
assert.NilError(t, err)
assert.Assert(t, !val.IsValid())
})
t.Run("no nanos", func(t *testing.T) {
t.Parallel()
ts := timestamppb.New(time.Date(2022, 1, 1, 12, 30, 15, 0, time.UTC))
val := protoreflect.ValueOfMessage(ts.ProtoReflect())
buf := &bytes.Buffer{}
assert.NilError(t, cdc.Encode(val, buf))
assert.Equal(t, 6, len(buf.Bytes()))
val2, err := cdc.Decode(buf)
assert.NilError(t, err)
assert.Equal(t, 0, cdc.Compare(val, val2))
})
t.Run("nanos", func(t *testing.T) {
t.Parallel()
ts := timestamppb.New(time.Date(2022, 1, 1, 12, 30, 15, 235809753, time.UTC))
val := protoreflect.ValueOfMessage(ts.ProtoReflect())
buf := &bytes.Buffer{}
assert.NilError(t, cdc.Encode(val, buf))
assert.Equal(t, 9, len(buf.Bytes()))
val2, err := cdc.Decode(buf)
assert.NilError(t, err)
assert.Equal(t, 0, cdc.Compare(val, val2))
})
t.Run("min value", func(t *testing.T) {
t.Parallel()
ts := timestamppb.New(time.Date(1, 1, 1, 0, 0, 0, 0, time.UTC))
val := protoreflect.ValueOfMessage(ts.ProtoReflect())
buf := &bytes.Buffer{}
assert.NilError(t, cdc.Encode(val, buf))
assert.Equal(t, 6, len(buf.Bytes()))
assert.Assert(t, bytes.Equal(buf.Bytes(), []byte{0, 0, 0, 0, 0, 0})) // the minimum value should be all zeros
val2, err := cdc.Decode(buf)
assert.NilError(t, err)
assert.Equal(t, 0, cdc.Compare(val, val2))
})
t.Run("max value", func(t *testing.T) {
t.Parallel()
ts := timestamppb.New(time.Date(9999, 12, 31, 23, 59, 59, 999999999, time.UTC))
val := protoreflect.ValueOfMessage(ts.ProtoReflect())
buf := &bytes.Buffer{}
assert.NilError(t, cdc.Encode(val, buf))
assert.Equal(t, 9, len(buf.Bytes()))
val2, err := cdc.Decode(buf)
assert.NilError(t, err)
assert.Equal(t, 0, cdc.Compare(val, val2))
})
}
func TestTimestampOutOfRange(t *testing.T) {
t.Parallel()
cdc := ormfield.TimestampCodec{}
tt := []struct {
name string
ts *timestamppb.Timestamp
expectErr string
}{
{
name: "before min",
ts: timestamppb.New(time.Date(0, 1, 1, 0, 0, 0, 0, time.UTC)),
expectErr: "timestamp seconds is out of range",
},
{
name: "after max",
ts: timestamppb.New(time.Date(10000, 1, 1, 0, 0, 0, 0, time.UTC)),
expectErr: "timestamp seconds is out of range",
},
{
name: "nanos too small",
ts: &timestamppb.Timestamp{
Seconds: 0,
Nanos: -1,
},
expectErr: "timestamp nanos is out of range",
},
{
name: "nanos too big",
ts: &timestamppb.Timestamp{
Seconds: 0,
Nanos: 1000000000,
},
expectErr: "timestamp nanos is out of range",
},
}
for _, tc := range tt {
tc := tc
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
val := protoreflect.ValueOfMessage(tc.ts.ProtoReflect())
buf := &bytes.Buffer{}
err := cdc.Encode(val, buf)
assert.ErrorContains(t, err, tc.expectErr)
})
}
}
-188
View File
@@ -1,188 +0,0 @@
package ormfield
import (
"encoding/binary"
"fmt"
"io"
"google.golang.org/protobuf/reflect/protoreflect"
)
// FixedUint32Codec encodes uint32 values as 4-byte big-endian integers.
type FixedUint32Codec struct{}
func (u FixedUint32Codec) FixedBufferSize() int {
return 4
}
func (u FixedUint32Codec) ComputeBufferSize(protoreflect.Value) (int, error) {
return u.FixedBufferSize(), nil
}
func (u FixedUint32Codec) IsOrdered() bool {
return true
}
func (u FixedUint32Codec) Compare(v1, v2 protoreflect.Value) int {
return compareUint(v1, v2)
}
func (u FixedUint32Codec) Decode(r Reader) (protoreflect.Value, error) {
var x uint32
err := binary.Read(r, binary.BigEndian, &x)
return protoreflect.ValueOfUint32(x), err
}
func (u FixedUint32Codec) Encode(value protoreflect.Value, w io.Writer) error {
var x uint64
if value.IsValid() {
x = value.Uint()
}
return binary.Write(w, binary.BigEndian, uint32(x))
}
// CompactUint32Codec encodes uint32 values using EncodeCompactUint32.
type CompactUint32Codec struct{}
func (c CompactUint32Codec) Decode(r Reader) (protoreflect.Value, error) {
x, err := DecodeCompactUint32(r)
return protoreflect.ValueOfUint32(x), err
}
func (c CompactUint32Codec) Encode(value protoreflect.Value, w io.Writer) error {
var x uint64
if value.IsValid() {
x = value.Uint()
}
_, err := w.Write(EncodeCompactUint32(uint32(x)))
return err
}
func (c CompactUint32Codec) Compare(v1, v2 protoreflect.Value) int {
return compareUint(v1, v2)
}
func (c CompactUint32Codec) IsOrdered() bool {
return true
}
func (c CompactUint32Codec) FixedBufferSize() int {
return 5
}
func (c CompactUint32Codec) ComputeBufferSize(protoreflect.Value) (int, error) {
return c.FixedBufferSize(), nil
}
// EncodeCompactUint32 encodes uint32 values in 2,3,4 or 5 bytes.
// Unlike regular varints, this encoding is
// suitable for ordered prefix scans. The length of the output + 2 is encoded
// in the first 2 bits of the first byte and the remaining bits encoded with
// big-endian ordering.
// Values less than 2^14 fill fit in 2 bytes, values less than 2^22 will
// fit in 3, and values less than 2^30 will fit in 4.
func EncodeCompactUint32(x uint32) []byte {
switch {
case x < 16384: // 2^14
buf := make([]byte, 2)
buf[0] = byte(x >> 8)
buf[1] = byte(x)
return buf
case x < 4194304: // 2^22
buf := make([]byte, 3)
buf[0] = 0x40
buf[0] |= byte(x >> 16)
buf[1] = byte(x >> 8)
buf[2] = byte(x)
return buf
case x < 1073741824: // 2^30
buf := make([]byte, 4)
buf[0] = 0x80
buf[0] |= byte(x >> 24)
buf[1] = byte(x >> 16)
buf[2] = byte(x >> 8)
buf[3] = byte(x)
return buf
default:
buf := make([]byte, 5)
buf[0] = 0xC0
buf[0] |= byte(x >> 26)
buf[1] = byte(x >> 18)
buf[2] = byte(x >> 10)
buf[3] = byte(x >> 2)
buf[4] = byte(x) & 0x3
return buf
}
}
// DecodeCompactUint32 decodes a uint32 encoded with EncodeCompactU32.
func DecodeCompactUint32(reader io.Reader) (uint32, error) {
var buf [5]byte
n, err := reader.Read(buf[:1])
if err != nil {
return 0, err
}
if n < 1 {
return 0, io.ErrUnexpectedEOF
}
switch buf[0] >> 6 {
case 0:
n, err := reader.Read(buf[1:2])
if err != nil {
return 0, err
}
if n < 1 {
return 0, io.ErrUnexpectedEOF
}
x := uint32(buf[0]) << 8
x |= uint32(buf[1])
return x, nil
case 1:
n, err := reader.Read(buf[1:3])
if err != nil {
return 0, err
}
if n < 2 {
return 0, io.ErrUnexpectedEOF
}
x := (uint32(buf[0]) & 0x3F) << 16
x |= uint32(buf[1]) << 8
x |= uint32(buf[2])
return x, nil
case 2:
n, err := reader.Read(buf[1:4])
if err != nil {
return 0, err
}
if n < 3 {
return 0, io.ErrUnexpectedEOF
}
x := (uint32(buf[0]) & 0x3F) << 24
x |= uint32(buf[1]) << 16
x |= uint32(buf[2]) << 8
x |= uint32(buf[3])
return x, nil
case 3:
n, err := reader.Read(buf[1:5])
if err != nil {
return 0, err
}
if n < 4 {
return 0, io.ErrUnexpectedEOF
}
x := (uint32(buf[0]) & 0x3F) << 26
x |= uint32(buf[1]) << 18
x |= uint32(buf[2]) << 10
x |= uint32(buf[3]) << 2
x |= uint32(buf[4])
return x, nil
default:
return 0, fmt.Errorf("unexpected case")
}
}
-218
View File
@@ -1,218 +0,0 @@
package ormfield
import (
"encoding/binary"
"fmt"
"io"
"google.golang.org/protobuf/reflect/protoreflect"
)
// FixedUint64Codec encodes uint64 values as 8-byte big-endian integers.
type FixedUint64Codec struct{}
func (u FixedUint64Codec) FixedBufferSize() int {
return 8
}
func (u FixedUint64Codec) ComputeBufferSize(protoreflect.Value) (int, error) {
return u.FixedBufferSize(), nil
}
func (u FixedUint64Codec) IsOrdered() bool {
return true
}
func (u FixedUint64Codec) Compare(v1, v2 protoreflect.Value) int {
return compareUint(v1, v2)
}
func (u FixedUint64Codec) Decode(r Reader) (protoreflect.Value, error) {
var x uint64
err := binary.Read(r, binary.BigEndian, &x)
return protoreflect.ValueOfUint64(x), err
}
func (u FixedUint64Codec) Encode(value protoreflect.Value, w io.Writer) error {
var x uint64
if value.IsValid() {
x = value.Uint()
}
return binary.Write(w, binary.BigEndian, x)
}
func compareUint(v1, v2 protoreflect.Value) int {
var x, y uint64
if v1.IsValid() {
x = v1.Uint()
}
if v2.IsValid() {
y = v2.Uint()
}
switch {
case x == y:
return 0
case x < y:
return -1
default:
return 1
}
}
// CompactUint64Codec encodes uint64 values using EncodeCompactUint64.
type CompactUint64Codec struct{}
func (c CompactUint64Codec) Decode(r Reader) (protoreflect.Value, error) {
x, err := DecodeCompactUint64(r)
return protoreflect.ValueOfUint64(x), err
}
func (c CompactUint64Codec) Encode(value protoreflect.Value, w io.Writer) error {
var x uint64
if value.IsValid() {
x = value.Uint()
}
_, err := w.Write(EncodeCompactUint64(x))
return err
}
func (c CompactUint64Codec) Compare(v1, v2 protoreflect.Value) int {
return compareUint(v1, v2)
}
func (c CompactUint64Codec) IsOrdered() bool {
return true
}
func (c CompactUint64Codec) FixedBufferSize() int {
return 9
}
func (c CompactUint64Codec) ComputeBufferSize(protoreflect.Value) (int, error) {
return c.FixedBufferSize(), nil
}
// EncodeCompactUint64 encodes uint64 values in 2,4,6 or 9 bytes.
// Unlike regular varints, this encoding is
// suitable for ordered prefix scans. The first two bits of the first byte
// indicate the length of the buffer - 00 for 2, 01 for 4, 10 for 6 and
// 11 for 9. The remaining bits are encoded with big-endian ordering.
// Values less than 2^14 fill fit in 2 bytes, values less than 2^30 will
// fit in 4, and values less than 2^46 will fit in 6.
func EncodeCompactUint64(x uint64) []byte {
switch {
case x < 16384: // 2^14
buf := make([]byte, 2)
buf[0] = byte(x >> 8)
buf[1] = byte(x)
return buf
case x < 1073741824: // 2^30
buf := make([]byte, 4)
buf[0] = 0x40
buf[0] |= byte(x >> 24)
buf[1] = byte(x >> 16)
buf[2] = byte(x >> 8)
buf[3] = byte(x)
return buf
case x < 70368744177664: // 2^46
buf := make([]byte, 6)
buf[0] = 0x80
buf[0] |= byte(x >> 40)
buf[1] = byte(x >> 32)
buf[2] = byte(x >> 24)
buf[3] = byte(x >> 16)
buf[4] = byte(x >> 8)
buf[5] = byte(x)
return buf
default:
buf := make([]byte, 9)
buf[0] = 0xC0
buf[0] |= byte(x >> 58)
buf[1] = byte(x >> 50)
buf[2] = byte(x >> 42)
buf[3] = byte(x >> 34)
buf[4] = byte(x >> 26)
buf[5] = byte(x >> 18)
buf[6] = byte(x >> 10)
buf[7] = byte(x >> 2)
buf[8] = byte(x) & 0x3
return buf
}
}
func DecodeCompactUint64(reader io.Reader) (uint64, error) {
var buf [9]byte
n, err := reader.Read(buf[:1])
if err != nil {
return 0, err
}
if n < 1 {
return 0, io.ErrUnexpectedEOF
}
switch buf[0] >> 6 {
case 0:
n, err := reader.Read(buf[1:2])
if err != nil {
return 0, err
}
if n < 1 {
return 0, io.ErrUnexpectedEOF
}
x := uint64(buf[0]) << 8
x |= uint64(buf[1])
return x, nil
case 1:
n, err := reader.Read(buf[1:4])
if err != nil {
return 0, err
}
if n < 3 {
return 0, io.ErrUnexpectedEOF
}
x := (uint64(buf[0]) & 0x3F) << 24
x |= uint64(buf[1]) << 16
x |= uint64(buf[2]) << 8
x |= uint64(buf[3])
return x, nil
case 2:
n, err := reader.Read(buf[1:6])
if err != nil {
return 0, err
}
if n < 5 {
return 0, io.ErrUnexpectedEOF
}
x := (uint64(buf[0]) & 0x3F) << 40
x |= uint64(buf[1]) << 32
x |= uint64(buf[2]) << 24
x |= uint64(buf[3]) << 16
x |= uint64(buf[4]) << 8
x |= uint64(buf[5])
return x, nil
case 3:
n, err := reader.Read(buf[1:9])
if err != nil {
return 0, err
}
if n < 8 {
return 0, io.ErrUnexpectedEOF
}
x := (uint64(buf[0]) & 0x3F) << 58
x |= uint64(buf[1]) << 50
x |= uint64(buf[2]) << 42
x |= uint64(buf[3]) << 34
x |= uint64(buf[4]) << 26
x |= uint64(buf[5]) << 18
x |= uint64(buf[6]) << 10
x |= uint64(buf[7]) << 2
x |= uint64(buf[8])
return x, nil
default:
return 0, fmt.Errorf("unexpected case")
}
}
-123
View File
@@ -1,123 +0,0 @@
package ormkv
import (
"bytes"
"io"
"google.golang.org/protobuf/reflect/protoreflect"
"cosmossdk.io/orm/types/ormerrors"
)
// IndexKeyCodec is the codec for (non-unique) index keys.
type IndexKeyCodec struct {
*KeyCodec
pkFieldOrder []int
}
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, 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))
for i, f := range indexFields {
indexFieldMap[f] = i
keyFields = append(keyFields, f)
}
numIndexFields := len(indexFields)
numPrimaryKeyFields := len(primaryKeyFields)
pkFieldOrder := make([]int, numPrimaryKeyFields)
k := 0
for j, f := range primaryKeyFields {
if i, ok := indexFieldMap[f]; ok {
pkFieldOrder[j] = i
continue
}
keyFields = append(keyFields, f)
pkFieldOrder[j] = numIndexFields + k
k++
}
cdc, err := NewKeyCodec(prefix, messageType, keyFields)
if err != nil {
return nil, err
}
return &IndexKeyCodec{
KeyCodec: cdc,
pkFieldOrder: pkFieldOrder,
}, nil
}
func (cdc IndexKeyCodec) DecodeIndexKey(k, _ []byte) (indexFields, primaryKey []protoreflect.Value, err error) {
values, err := cdc.DecodeKey(bytes.NewReader(k))
// got prefix key
if err == io.EOF {
return values, nil, nil
} else if err != nil {
return nil, nil, err
}
// got prefix key
if len(values) < len(cdc.fieldCodecs) {
return values, nil, nil
}
numPkFields := len(cdc.pkFieldOrder)
pkValues := make([]protoreflect.Value, numPkFields)
for i := 0; i < numPkFields; i++ {
pkValues[i] = values[cdc.pkFieldOrder[i]]
}
return values, pkValues, nil
}
func (cdc IndexKeyCodec) DecodeEntry(k, v []byte) (Entry, error) {
idxValues, pk, err := cdc.DecodeIndexKey(k, v)
if err != nil {
return nil, err
}
return &IndexKeyEntry{
TableName: cdc.messageType.Descriptor().FullName(),
Fields: cdc.fieldNames,
IndexValues: idxValues,
PrimaryKey: pk,
}, nil
}
func (cdc IndexKeyCodec) EncodeEntry(entry Entry) (k, v []byte, err error) {
indexEntry, ok := entry.(*IndexKeyEntry)
if !ok {
return nil, nil, ormerrors.BadDecodeEntry
}
if indexEntry.TableName != cdc.messageType.Descriptor().FullName() {
return nil, nil, ormerrors.BadDecodeEntry
}
bz, err := cdc.KeyCodec.EncodeKey(indexEntry.IndexValues)
if err != nil {
return nil, nil, err
}
return bz, []byte{}, nil
}
func (cdc IndexKeyCodec) EncodeKVFromMessage(message protoreflect.Message) (k, v []byte, err error) {
_, k, err = cdc.EncodeKeyFromMessage(message)
return k, []byte{}, err
}
-307
View File
@@ -1,307 +0,0 @@
package ormkv
import (
"bytes"
"io"
"google.golang.org/protobuf/reflect/protoreflect"
"cosmossdk.io/orm/encoding/encodeutil"
"cosmossdk.io/orm/encoding/ormfield"
"cosmossdk.io/orm/types/ormerrors"
)
type KeyCodec struct {
fixedSize int
variableSizers []struct {
cdc ormfield.Codec
i int
}
prefix []byte
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, messageType protoreflect.MessageType, fieldNames []protoreflect.Name) (*KeyCodec, error) {
n := len(fieldNames)
fieldCodecs := make([]ormfield.Codec, n)
fieldDescriptors := make([]protoreflect.FieldDescriptor, n)
var variableSizers []struct {
cdc ormfield.Codec
i int
}
fixedSize := 0
messageFields := messageType.Descriptor().Fields()
for i := 0; i < n; i++ {
nonTerminal := i != n-1
field := messageFields.ByName(fieldNames[i])
if field == nil {
return nil, ormerrors.FieldNotFound.Wrapf("field %s on %s", fieldNames[i], messageType.Descriptor().FullName())
}
cdc, err := ormfield.GetCodec(field, nonTerminal)
if err != nil {
return nil, err
}
if x := cdc.FixedBufferSize(); x > 0 {
fixedSize += x
} else {
variableSizers = append(variableSizers, struct {
cdc ormfield.Codec
i int
}{cdc, i})
}
fieldCodecs[i] = cdc
fieldDescriptors[i] = field
}
return &KeyCodec{
fieldCodecs: fieldCodecs,
fieldDescriptors: fieldDescriptors,
fieldNames: fieldNames,
prefix: prefix,
fixedSize: fixedSize,
variableSizers: variableSizers,
messageType: messageType,
}, nil
}
// 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) EncodeKey(values []protoreflect.Value) ([]byte, error) {
sz, err := cdc.ComputeKeyBufferSize(values)
if err != nil {
return nil, err
}
w := bytes.NewBuffer(make([]byte, 0, sz+len(cdc.prefix)))
if _, err = w.Write(cdc.prefix); err != nil {
return nil, err
}
n := len(values)
if n > len(cdc.fieldCodecs) {
return nil, ormerrors.IndexOutOfBounds.Wrapf("cannot encode %d values into %d fields", n, len(cdc.fieldCodecs))
}
for i := 0; i < n; i++ {
if err = cdc.fieldCodecs[i].Encode(values[i], w); err != nil {
return nil, err
}
}
return w.Bytes(), nil
}
// 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 {
if message.Has(f) {
res[i] = message.Get(f)
}
}
return res
}
// 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) DecodeKey(r *bytes.Reader) ([]protoreflect.Value, error) {
if err := encodeutil.SkipPrefix(r, cdc.prefix); err != nil {
return nil, err
}
n := len(cdc.fieldCodecs)
values := make([]protoreflect.Value, 0, n)
for i := 0; i < n; i++ {
value, err := cdc.fieldCodecs[i].Decode(r)
if err == io.EOF {
return values, err
} else if err != nil {
return nil, err
}
values = append(values, value)
}
return values, nil
}
// 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
}
// IsFullyOrdered returns true if all fields are also ordered.
func (cdc *KeyCodec) IsFullyOrdered() bool {
for _, p := range cdc.fieldCodecs {
if !p.IsOrdered() {
return false
}
}
return true
}
// 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) CompareKeys(values1, values2 []protoreflect.Value) int {
j := len(values1)
k := len(values2)
n := j
if k < j {
n = k
}
if n > len(cdc.fieldCodecs) {
panic("array is too long")
}
var cmp int
for i := 0; i < n; i++ {
cmp = cdc.fieldCodecs[i].Compare(values1[i], values2[i])
// any non-equal parts determine our ordering
if cmp != 0 {
return cmp
}
}
// values are equal but arrays of different length
switch {
case j == k:
return 0
case j < k:
return -1
default:
return 1
}
}
// ComputeKeyBufferSize computes the required buffer size for the provided values
// which can represent a full or prefix key.
func (cdc KeyCodec) ComputeKeyBufferSize(values []protoreflect.Value) (int, error) {
size := cdc.fixedSize
n := len(values)
for _, sz := range cdc.variableSizers {
// handle prefix key encoding case where don't need all the sizers
if sz.i >= n {
return size, nil
}
x, err := sz.cdc.ComputeBufferSize(values[sz.i])
if err != nil {
return 0, err
}
size += x
}
return size, nil
}
// 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) SetKeyValues(message protoreflect.Message, values []protoreflect.Value) {
for i, f := range cdc.fieldDescriptors {
value := values[i]
if value.IsValid() {
message.Set(f, value)
}
}
}
// CheckValidRangeIterationKeys checks if the start and end key prefixes are valid
// for range iteration meaning that for each non-equal field in the prefixes
// those field types support ordered iteration. If start or end is longer than
// the other, the omitted values will function as the minimum and maximum
// values of that type respectively.
func (cdc KeyCodec) CheckValidRangeIterationKeys(start, end []protoreflect.Value) error {
lenStart := len(start)
shortest := lenStart
longest := lenStart
lenEnd := len(end)
if lenEnd < shortest {
shortest = lenEnd
} else {
longest = lenEnd
}
if longest > len(cdc.fieldCodecs) {
return ormerrors.IndexOutOfBounds
}
i := 0
var cmp int
for ; i < shortest; i++ {
fieldCdc := cdc.fieldCodecs[i]
x := start[i]
y := end[i]
cmp = fieldCdc.Compare(x, y)
if cmp > 0 {
return ormerrors.InvalidRangeIterationKeys.Wrapf(
"start must be before end for field %s",
cdc.fieldDescriptors[i].FullName(),
)
} else if !fieldCdc.IsOrdered() && cmp != 0 {
descriptor := cdc.fieldDescriptors[i]
return ormerrors.InvalidRangeIterationKeys.Wrapf(
"field %s of kind %s doesn't support ordered range iteration",
descriptor.FullName(),
descriptor.Kind(),
)
} else if cmp < 0 {
break
}
}
// the last prefix value must not be equal if the key lengths are the same
if lenStart == lenEnd {
if cmp == 0 {
return ormerrors.InvalidRangeIterationKeys
}
} else {
// check any remaining values in start or end
for j := i; j < longest; j++ {
if !cdc.fieldCodecs[j].IsOrdered() {
return ormerrors.InvalidRangeIterationKeys.Wrapf(
"field %s of kind %s doesn't support ordered range iteration",
cdc.fieldDescriptors[j].FullName(),
cdc.fieldDescriptors[j].Kind(),
)
}
}
}
return nil
}
// GetFieldDescriptors returns the field descriptors for this codec.
func (cdc *KeyCodec) GetFieldDescriptors() []protoreflect.FieldDescriptor {
return cdc.fieldDescriptors
}
// GetFieldNames returns the field names for this codec.
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
}
-140
View File
@@ -1,140 +0,0 @@
package ormkv
import (
"bytes"
"io"
"google.golang.org/protobuf/proto"
"google.golang.org/protobuf/reflect/protoreflect"
"cosmossdk.io/orm/types/ormerrors"
)
// PrimaryKeyCodec is the codec for primary keys.
type PrimaryKeyCodec struct {
*KeyCodec
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, fieldNames)
if err != nil {
return nil, err
}
return &PrimaryKeyCodec{
KeyCodec: keyCodec,
unmarshalOptions: unmarshalOptions,
}, nil
}
var _ IndexCodec = PrimaryKeyCodec{}
func (p PrimaryKeyCodec) DecodeIndexKey(k, _ []byte) (indexFields, primaryKey []protoreflect.Value, err error) {
indexFields, err = p.DecodeKey(bytes.NewReader(k))
// got prefix key
if err == io.EOF {
return indexFields, nil, nil
} else if err != nil {
return nil, nil, err
}
if len(indexFields) == len(p.fieldCodecs) {
// for primary keys the index fields are the primary key
// but only if we don't have a prefix key
primaryKey = indexFields
}
return indexFields, primaryKey, nil
}
func (p PrimaryKeyCodec) DecodeEntry(k, v []byte) (Entry, error) {
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.messageType.New().Interface()
err = p.Unmarshal(values, v, msg)
return &PrimaryKeyEntry{
TableName: p.messageType.Descriptor().FullName(),
Key: values,
Value: msg,
}, err
}
func (p PrimaryKeyCodec) EncodeEntry(entry Entry) (k, v []byte, err error) {
pkEntry, ok := entry.(*PrimaryKeyEntry)
if !ok {
return nil, nil, ormerrors.BadDecodeEntry.Wrapf("expected %T, got %T", &PrimaryKeyEntry{}, entry)
}
if pkEntry.TableName != p.messageType.Descriptor().FullName() {
return nil, nil, ormerrors.BadDecodeEntry.Wrapf(
"wrong table name, got %s, expected %s",
pkEntry.TableName,
p.messageType.Descriptor().FullName(),
)
}
k, err = p.KeyCodec.EncodeKey(pkEntry.Key)
if err != nil {
return nil, nil, err
}
v, err = p.marshal(pkEntry.Key, pkEntry.Value)
return k, v, err
}
func (p PrimaryKeyCodec) marshal(key []protoreflect.Value, message proto.Message) (v []byte, err error) {
// first clear the priamry key values because these are already stored in
// the key so we don't need to store them again in the value
p.ClearValues(message.ProtoReflect())
v, err = proto.MarshalOptions{Deterministic: true}.Marshal(message)
if err != nil {
return nil, err
}
// set the primary key values again returning the message to its original state
p.SetKeyValues(message.ProtoReflect(), key)
return v, nil
}
func (p *PrimaryKeyCodec) ClearValues(message protoreflect.Message) {
for _, f := range p.fieldDescriptors {
message.Clear(f)
}
}
func (p *PrimaryKeyCodec) Unmarshal(key []protoreflect.Value, value []byte, message proto.Message) error {
err := p.unmarshalOptions.Unmarshal(value, message)
if err != nil {
return err
}
// rehydrate primary 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.EncodeKeyFromMessage(message)
if err != nil {
return nil, nil, err
}
v, err = p.marshal(ks, message.Interface())
return k, v, err
}
-209
View File
@@ -1,209 +0,0 @@
package ormkv
import (
"bytes"
"io"
"google.golang.org/protobuf/reflect/protoreflect"
"cosmossdk.io/orm/types/ormerrors"
)
// 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++
}
}
// if there is nothing in the value we have a trivial unique index
// which shouldn't actually be a unique index at all
if len(valueFields) == 0 {
return nil, ormerrors.InvalidTableDefinition.Wrapf("unique index %s on table %s introduces no new uniqueness constraint not already in the primary key and should not be marked as unique",
indexFields, messageType.Descriptor().FullName())
}
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 (u UniqueKeyCodec) extractPrimaryKey(keyValues, valueValues []protoreflect.Value) []protoreflect.Value {
numPkFields := len(u.pkFieldOrder)
pkValues := make([]protoreflect.Value, numPkFields)
for i := 0; i < numPkFields; i++ {
fo := u.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 if u.keyCodec.fieldCodecs[fieldOrder.i].Compare(value, indexEntry.IndexValues[fieldOrder.i]) != 0 {
// does not go in values, but we need to verify that the value in index values matches the primary key value
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
}
-92
View File
@@ -1,92 +0,0 @@
package ormkv_test
import (
"bytes"
"fmt"
"testing"
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
"gotest.tools/v3/assert"
"pgregory.net/rapid"
"cosmossdk.io/orm/encoding/ormkv"
"cosmossdk.io/orm/internal/testpb"
"cosmossdk.io/orm/internal/testutil"
"cosmossdk.io/orm/types/ormerrors"
)
func TestUniqueKeyCodec(t *testing.T) {
rapid.Check(t, func(t *rapid.T) {
keyCodec := testutil.TestKeyCodecGen(1, 5).Draw(t, "keyCodec")
pkCodec := testutil.TestKeyCodecGen(1, 5).Draw(t, "primaryKeyCodec")
// check if we have a trivial unique index where all of the fields
// in the primary key are in the unique key, we should expect an
// error in this case
isInPk := map[protoreflect.Name]bool{}
for _, spec := range pkCodec.KeySpecs {
isInPk[spec.FieldName] = true
}
numPkFields := 0
for _, spec := range keyCodec.KeySpecs {
if isInPk[spec.FieldName] {
numPkFields++
}
}
isTrivialUniqueKey := numPkFields == len(pkCodec.KeySpecs)
messageType := (&testpb.ExampleTable{}).ProtoReflect().Type()
uniqueKeyCdc, err := ormkv.NewUniqueKeyCodec(
keyCodec.Codec.Prefix(),
messageType,
keyCodec.Codec.GetFieldNames(),
pkCodec.Codec.GetFieldNames(),
)
if isTrivialUniqueKey {
assert.ErrorContains(t, err, "no new uniqueness constraint")
return
}
assert.NilError(t, err)
for i := 0; i < 100; i++ {
a := testutil.GenA.Draw(t, fmt.Sprintf("a%d", i))
key := keyCodec.Codec.GetKeyValues(a.ProtoReflect())
pk := pkCodec.Codec.GetKeyValues(a.ProtoReflect())
uniq1 := &ormkv.IndexKeyEntry{
TableName: messageType.Descriptor().FullName(),
Fields: keyCodec.Codec.GetFieldNames(),
IsUnique: true,
IndexValues: key,
PrimaryKey: pk,
}
k, v, err := uniqueKeyCdc.EncodeEntry(uniq1)
assert.NilError(t, err)
k2, v2, err := uniqueKeyCdc.EncodeKVFromMessage(a.ProtoReflect())
assert.NilError(t, err)
assert.Assert(t, bytes.Equal(k, k2))
assert.Assert(t, bytes.Equal(v, v2))
entry2, err := uniqueKeyCdc.DecodeEntry(k, v)
assert.NilError(t, err)
uniq2 := entry2.(*ormkv.IndexKeyEntry)
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, 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.CompareKeys(key, idxFields))
assert.Equal(t, 0, pkCodec.Codec.CompareKeys(pk, pk2))
}
})
}
func TestTrivialUnique(t *testing.T) {
_, err := ormkv.NewUniqueKeyCodec(nil, (&testpb.ExampleTable{}).ProtoReflect().Type(),
[]protoreflect.Name{"u32", "str"}, []protoreflect.Name{"str", "u32"})
assert.ErrorIs(t, err, ormerrors.InvalidTableDefinition)
}
-70
View File
@@ -1,70 +0,0 @@
module cosmossdk.io/orm
go 1.20
require (
cosmossdk.io/api v0.7.5
cosmossdk.io/core v0.11.0
cosmossdk.io/depinject v1.0.0-alpha.4
cosmossdk.io/errors v1.0.0-beta.7
github.com/cosmos/cosmos-db v1.0.2
github.com/cosmos/cosmos-proto v1.0.0-beta.5
github.com/golang/mock v1.6.0
github.com/google/go-cmp v0.6.0
github.com/iancoleman/strcase v0.2.0
github.com/regen-network/gocuke v0.6.2
github.com/stretchr/testify v1.9.0
golang.org/x/exp v0.0.0-20240222234643-814bf88cf225
google.golang.org/grpc v1.62.1
google.golang.org/protobuf v1.33.0
gotest.tools/v3 v3.5.1
pgregory.net/rapid v1.1.0
)
require (
github.com/DataDog/zstd v1.5.5 // indirect
github.com/alecthomas/participle/v2 v2.0.0-alpha7 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/cespare/xxhash/v2 v2.2.0 // indirect
github.com/cockroachdb/apd/v3 v3.1.0 // indirect
github.com/cockroachdb/errors v1.11.1 // indirect
github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b // indirect
github.com/cockroachdb/pebble v1.1.0 // indirect
github.com/cockroachdb/redact v1.1.5 // indirect
github.com/cockroachdb/tokenbucket v0.0.0-20230807174530-cc333fc44b06 // indirect
github.com/cosmos/gogoproto v1.7.0 // indirect
github.com/cucumber/common/gherkin/go/v22 v22.0.0 // indirect
github.com/cucumber/common/messages/go/v17 v17.1.1 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/fsnotify/fsnotify v1.6.0 // indirect
github.com/getsentry/sentry-go v0.27.0 // indirect
github.com/gofrs/uuid v4.2.0+incompatible // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang/protobuf v1.5.4 // indirect
github.com/golang/snappy v0.0.4 // indirect
github.com/google/btree v1.1.2 // indirect
github.com/klauspost/compress v1.17.7 // indirect
github.com/kr/pretty v0.3.1 // indirect
github.com/kr/text v0.2.0 // indirect
github.com/lib/pq v1.10.7 // indirect
github.com/linxGnu/grocksdb v1.8.12 // indirect
github.com/onsi/gomega v1.20.0 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/prometheus/client_golang v1.18.0 // indirect
github.com/prometheus/client_model v0.6.0 // indirect
github.com/prometheus/common v0.47.0 // indirect
github.com/prometheus/procfs v0.12.0 // indirect
github.com/rogpeppe/go-internal v1.12.0 // indirect
github.com/spf13/cast v1.6.0 // indirect
github.com/syndtr/goleveldb v1.0.1-0.20220721030215-126854af5e6d // indirect
golang.org/x/net v0.20.0 // indirect
golang.org/x/sys v0.17.0 // indirect
golang.org/x/text v0.14.0 // indirect
google.golang.org/genproto v0.0.0-20240123012728-ef4313101c80 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20240123012728-ef4313101c80 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20240123012728-ef4313101c80 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
sigs.k8s.io/yaml v1.3.0 // indirect
)
-255
View File
@@ -1,255 +0,0 @@
cosmossdk.io/api v0.7.5 h1:eMPTReoNmGUm8DeiQL9DyM8sYDjEhWzL1+nLbI9DqtQ=
cosmossdk.io/api v0.7.5/go.mod h1:IcxpYS5fMemZGqyYtErK7OqvdM0C8kdW3dq8Q/XIG38=
cosmossdk.io/core v0.11.0 h1:vtIafqUi+1ZNAE/oxLOQQ7Oek2n4S48SWLG8h/+wdbo=
cosmossdk.io/core v0.11.0/go.mod h1:LaTtayWBSoacF5xNzoF8tmLhehqlA9z1SWiPuNC6X1w=
cosmossdk.io/depinject v1.0.0-alpha.4 h1:PLNp8ZYAMPTUKyG9IK2hsbciDWqna2z1Wsl98okJopc=
cosmossdk.io/depinject v1.0.0-alpha.4/go.mod h1:HeDk7IkR5ckZ3lMGs/o91AVUc7E596vMaOmslGFM3yU=
cosmossdk.io/errors v1.0.0-beta.7 h1:gypHW76pTQGVnHKo6QBkb4yFOJjC+sUGRc5Al3Odj1w=
cosmossdk.io/errors v1.0.0-beta.7/go.mod h1:mz6FQMJRku4bY7aqS/Gwfcmr/ue91roMEKAmDUDpBfE=
github.com/DataDog/zstd v1.5.5 h1:oWf5W7GtOLgp6bciQYDmhHHjdhYkALu6S/5Ni9ZgSvQ=
github.com/DataDog/zstd v1.5.5/go.mod h1:g4AWEaM3yOg3HYfnJ3YIawPnVdXJh9QME85blwSAmyw=
github.com/alecthomas/participle/v2 v2.0.0-alpha7 h1:cK4vjj0VSgb3lN1nuKA5F7dw+1s1pWBe5bx7nNCnN+c=
github.com/alecthomas/participle/v2 v2.0.0-alpha7/go.mod h1:NumScqsC42o9x+dGj8/YqsIfhrIQjFEOFovxotbBirA=
github.com/alecthomas/repr v0.0.0-20181024024818-d37bc2a10ba1 h1:GDQdwm/gAcJcLAKQQZGOJ4knlw+7rfEQQcmwTbt4p5E=
github.com/alecthomas/repr v0.0.0-20181024024818-d37bc2a10ba1/go.mod h1:xTS7Pm1pD1mvyM075QCDSRqH6qRLXylzS24ZTpRiSzQ=
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
github.com/cockroachdb/apd/v3 v3.1.0 h1:MK3Ow7LH0W8zkd5GMKA1PvS9qG3bWFI95WaVNfyZJ/w=
github.com/cockroachdb/apd/v3 v3.1.0/go.mod h1:6qgPBMXjATAdD/VefbRP9NoSLKjbB4LCoA7gN4LpHs4=
github.com/cockroachdb/datadriven v1.0.3-0.20230413201302-be42291fc80f h1:otljaYPt5hWxV3MUfO5dFPFiOXg9CyG5/kCfayTqsJ4=
github.com/cockroachdb/errors v1.11.1 h1:xSEW75zKaKCWzR3OfxXUxgrk/NtT4G1MiOv5lWZazG8=
github.com/cockroachdb/errors v1.11.1/go.mod h1:8MUxA3Gi6b25tYlFEBGLf+D8aISL+M4MIpiWMSNRfxw=
github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b h1:r6VH0faHjZeQy818SGhaone5OnYfxFR/+AzdY3sf5aE=
github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b/go.mod h1:Vz9DsVWQQhf3vs21MhPMZpMGSht7O/2vFW2xusFUVOs=
github.com/cockroachdb/pebble v1.1.0 h1:pcFh8CdCIt2kmEpK0OIatq67Ln9uGDYY3d5XnE0LJG4=
github.com/cockroachdb/pebble v1.1.0/go.mod h1:sEHm5NOXxyiAoKWhoFxT8xMgd/f3RA6qUqQ1BXKrh2E=
github.com/cockroachdb/redact v1.1.5 h1:u1PMllDkdFfPWaNGMyLD1+so+aq3uUItthCFqzwPJ30=
github.com/cockroachdb/redact v1.1.5/go.mod h1:BVNblN9mBWFyMyqK1k3AAiSxhvhfK2oOZZ2lK+dpvRg=
github.com/cockroachdb/tokenbucket v0.0.0-20230807174530-cc333fc44b06 h1:zuQyyAKVxetITBuuhv3BI9cMrmStnpT18zmgmTxunpo=
github.com/cockroachdb/tokenbucket v0.0.0-20230807174530-cc333fc44b06/go.mod h1:7nc4anLGjupUW/PeY5qiNYsdNXj7zopG+eqsS7To5IQ=
github.com/cosmos/cosmos-db v1.0.2 h1:hwMjozuY1OlJs/uh6vddqnk9j7VamLv+0DBlbEXbAKs=
github.com/cosmos/cosmos-db v1.0.2/go.mod h1:Z8IXcFJ9PqKK6BIsVOB3QXtkKoqUOp1vRvPT39kOXEA=
github.com/cosmos/cosmos-proto v1.0.0-beta.5 h1:eNcayDLpip+zVLRLYafhzLvQlSmyab+RC5W7ZfmxJLA=
github.com/cosmos/cosmos-proto v1.0.0-beta.5/go.mod h1:hQGLpiIUloJBMdQMMWb/4wRApmI9hjHH05nefC0Ojec=
github.com/cosmos/gogoproto v1.7.0 h1:79USr0oyXAbxg3rspGh/m4SWNyoz/GLaAh0QlCe2fro=
github.com/cosmos/gogoproto v1.7.0/go.mod h1:yWChEv5IUEYURQasfyBW5ffkMHR/90hiHgbNgrtp4j0=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/cucumber/common/gherkin/go/v22 v22.0.0 h1:4K8NqptbvdOrjL9DEea6HFjSpbdT9+Q5kgLpmmsHYl0=
github.com/cucumber/common/gherkin/go/v22 v22.0.0/go.mod h1:3mJT10B2GGn3MvVPd3FwR7m2u4tLhSRhWUqJU4KN4Fg=
github.com/cucumber/common/messages/go/v17 v17.1.1 h1:RNqopvIFyLWnKv0LfATh34SWBhXeoFTJnSrgm9cT/Ts=
github.com/cucumber/common/messages/go/v17 v17.1.1/go.mod h1:bpGxb57tDE385Rb2EohgUadLkAbhoC4IyCFi89u/JQI=
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/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU=
github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY=
github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw=
github.com/getsentry/sentry-go v0.27.0 h1:Pv98CIbtB3LkMWmXi4Joa5OOcwbmnX88sF5qbK3r3Ps=
github.com/getsentry/sentry-go v0.27.0/go.mod h1:lc76E2QywIyW8WuBnwl8Lc4bkmQH4+w1gwTf25trprY=
github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA=
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
github.com/gofrs/uuid v4.2.0+incompatible h1:yyYWMnhkhrKwwr8gAOcOCYxOOscHgDS9yZgBrnJfGa0=
github.com/gofrs/uuid v4.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc=
github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
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.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/google/btree v1.1.2 h1:xf4v41cLI2Z6FxbKm+8Bu+m8ifhj15JuZ9sa0jZCMUU=
github.com/google/btree v1.1.2/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4=
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.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/iancoleman/strcase v0.2.0 h1:05I4QRnGpI0m37iZQRuskXh+w77mr6Z41lwQzuHLwW0=
github.com/iancoleman/strcase v0.2.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho=
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
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/klauspost/compress v1.17.7 h1:ehO88t2UGzQK66LMdE8tibEd1ErmzZjNEqWkjLAKQQg=
github.com/klauspost/compress v1.17.7/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/lib/pq v1.10.7 h1:p7ZhMD+KsSRozJr34udlUrhboJwWAgCg34+/ZZNvZZw=
github.com/lib/pq v1.10.7/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/linxGnu/grocksdb v1.8.12 h1:1/pCztQUOa3BX/1gR3jSZDoaKFpeHFvQ1XrqZpSvZVo=
github.com/linxGnu/grocksdb v1.8.12/go.mod h1:xZCIb5Muw+nhbDK4Y5UJuOrin5MceOuiXkVUR7vp4WY=
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
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.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0=
github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE=
github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU=
github.com/onsi/ginkgo/v2 v2.1.3/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c=
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY=
github.com/onsi/gomega v1.19.0/go.mod h1:LY+I3pBVzYsTBU1AnDwOSxaYi9WoWiqgwooUqq9yPro=
github.com/onsi/gomega v1.20.0 h1:8W0cWlwFkflGPLltQvLRB7ZVD5HuP6ng320w2IS245Q=
github.com/onsi/gomega v1.20.0/go.mod h1:DtrZpjmvpn2mPm4YWQa0/ALMDj9v4YxLgojwPeREyVo=
github.com/pingcap/errors v0.11.4 h1:lFuQV/oaUMGcD2tqt+01ROSmJs75VG1ToEOkZIZ4nE4=
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
github.com/pkg/errors v0.8.0/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_golang v1.18.0 h1:HzFfmkOzH5Q8L8G+kSJKUx5dtG87sewO+FoDDqP5Tbk=
github.com/prometheus/client_golang v1.18.0/go.mod h1:T+GXkCk5wSJyOqMIzVgvvjFDlkOQntgjkJWKrN5txjA=
github.com/prometheus/client_model v0.6.0 h1:k1v3CzpSRUTrKMppY35TLwPvxHqBu0bYgxZzqGIgaos=
github.com/prometheus/client_model v0.6.0/go.mod h1:NTQHnmxFpouOD0DpvP4XujX3CdOAGQPoaGhyTchlyt8=
github.com/prometheus/common v0.47.0 h1:p5Cz0FNHo7SnWOmWmoRozVcjEp0bIVU8cV7OShpjL1k=
github.com/prometheus/common v0.47.0/go.mod h1:0/KsvlIEfPQCQ5I2iNSAWKPZziNCvRs5EC6ILDTlAPc=
github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo=
github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo=
github.com/regen-network/gocuke v0.6.2 h1:pHviZ0kKAq2U2hN2q3smKNxct6hS0mGByFMHGnWA97M=
github.com/regen-network/gocuke v0.6.2/go.mod h1:zYaqIHZobHyd0xOrHGPQjbhGJsuZ1oElx150u2o1xuk=
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=
github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
github.com/spf13/cast v1.6.0 h1:GEiTHELF+vaR5dhz3VqZfFSzZjYbgeKDpBxQVS4GYJ0=
github.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
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/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/syndtr/goleveldb v1.0.1-0.20220721030215-126854af5e6d h1:vfofYNRScrDdvS342BElfbETmL1Aiz3i2t0zfRj16Hs=
github.com/syndtr/goleveldb v1.0.1-0.20220721030215-126854af5e6d/go.mod h1:RRCYJbIwD5jmqPI9XoAFR0OcDxqUctll6zUj/+B4S48=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
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-20240222234643-814bf88cf225 h1:LfspQV/FYTatPTr/3HzIcmiUFH7PGP+OQ6mgDYo3yuQ=
golang.org/x/exp v0.0.0-20240222234643-814bf88cf225/go.mod h1:CxmFvTBINI24O/j8iY7H1xHzx2i4OsyguNBmN/uPtqc=
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/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
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-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
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-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/net v0.0.0-20220607020251-c690dde0001d/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.20.0 h1:aCL9BSgETF1k+blQaYUBx9hJ9LOGP3gAVemcZlf1Kpo=
golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/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/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/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-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-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/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-20210112080510-489259a85091/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-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y=
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
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-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
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/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20220517211312-f3a8303e98df/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8=
google.golang.org/genproto v0.0.0-20240123012728-ef4313101c80 h1:KAeGQVN3M9nD0/bQXnr/ClcEMJ968gUXJQ9pwfSynuQ=
google.golang.org/genproto v0.0.0-20240123012728-ef4313101c80/go.mod h1:cc8bqMqtv9gMOr0zHg2Vzff5ULhhL2IXP4sbcn32Dro=
google.golang.org/genproto/googleapis/api v0.0.0-20240123012728-ef4313101c80 h1:Lj5rbfG876hIAYFjqiJnPHfhXbv+nzTWfm04Fg/XSVU=
google.golang.org/genproto/googleapis/api v0.0.0-20240123012728-ef4313101c80/go.mod h1:4jWUdICTdgc3Ibxmr8nAJiiLHwQBY0UI0XZcEMaFKaA=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240123012728-ef4313101c80 h1:AjyfHzEPEFp/NpvfN5g+KDla3EMojjhRVZc1i7cj+oM=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240123012728-ef4313101c80/go.mod h1:PAREbraiVEVGVdTZsVWjSbbTtSyGbAgIIvni8a8CD5s=
google.golang.org/grpc v1.62.1 h1:B4n+nfKzOICUXMgyrNd19h/I9oH0L1pizfk1d4zSgTk=
google.golang.org/grpc v1.62.1/go.mod h1:IWTG0VlJLCh1SkC58F7np9ka9mx/WNkjl4PGJaiq+QE=
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.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
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.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI=
google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
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.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/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gotest.tools/v3 v3.5.1 h1:EENdUnS3pdur5nybKYIh2Vfgc8IUNBjxDPSjtiJcOzU=
gotest.tools/v3 v3.5.1/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU=
pgregory.net/rapid v1.1.0 h1:CMa0sjHSru3puNx+J0MIAuiiEV4N0qj8/cMWGBBCsjw=
pgregory.net/rapid v1.1.0/go.mod h1:PY5XlDGj0+V1FCq0o192FdRhpKHGTRIWBgqjDBTrq04=
sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo=
sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8=
-179
View File
@@ -1,179 +0,0 @@
// nolint:unused // ignore unused code linting
package codegen
import (
"path/filepath"
"strings"
"github.com/cosmos/cosmos-proto/generator"
"github.com/iancoleman/strcase"
"google.golang.org/protobuf/compiler/protogen"
"google.golang.org/protobuf/proto"
ormv1 "cosmossdk.io/api/cosmos/orm/v1"
)
type fileGen struct {
*generator.GeneratedFile
file *protogen.File
}
func (f fileGen) gen() error {
f.P("// Code generated by protoc-gen-go-cosmos-orm. DO NOT EDIT.")
f.P()
f.P("package ", f.file.GoPackageName)
stores := make([]*protogen.Message, 0)
for _, msg := range f.file.Messages {
tableDesc := proto.GetExtension(msg.Desc.Options(), ormv1.E_Table).(*ormv1.TableDescriptor)
if tableDesc != nil {
tableGen, err := newTableGen(f, msg, tableDesc)
if err != nil {
return err
}
tableGen.gen()
}
singletonDesc := proto.GetExtension(msg.Desc.Options(), ormv1.E_Singleton).(*ormv1.SingletonDescriptor)
if singletonDesc != nil {
// do some singleton magic
singletonGen, err := newSingletonGen(f, msg, singletonDesc)
if err != nil {
return err
}
singletonGen.gen()
}
if tableDesc != nil || singletonDesc != nil { // message is one of the tables,
stores = append(stores, msg)
}
}
f.genStoreInterface(stores)
f.genStoreStruct(stores)
f.genStoreMethods(stores)
f.genStoreInterfaceGuard()
f.genStoreConstructor(stores)
return nil
}
func (f fileGen) genStoreInterface(stores []*protogen.Message) {
f.P("type ", f.storeInterfaceName(), " interface {")
for _, store := range stores {
name := f.messageTableInterfaceName(store)
f.P(name, "()", name)
}
f.P()
f.P("doNotImplement()")
f.P("}")
f.P()
}
func (f fileGen) genStoreStruct(stores []*protogen.Message) {
// struct
f.P("type ", f.storeStructName(), " struct {")
for _, message := range stores {
f.P(f.param(message.GoIdent.GoName), " ", f.messageTableInterfaceName(message))
}
f.P("}")
}
func (f fileGen) storeAccessorName() string {
return f.storeInterfaceName()
}
func (f fileGen) storeInterfaceName() string {
return strcase.ToCamel(f.fileShortName()) + "Store"
}
func (f fileGen) storeStructName() string {
return strcase.ToLowerCamel(f.fileShortName()) + "Store"
}
func (f fileGen) fileShortName() string {
return fileShortName(f.file)
}
func fileShortName(file *protogen.File) string {
filename := file.Proto.GetName()
shortName := filepath.Base(filename)
i := strings.Index(shortName, ".")
if i > 0 {
return shortName[:i]
}
return strcase.ToCamel(shortName)
}
func (f fileGen) messageTableInterfaceName(m *protogen.Message) string {
return m.GoIdent.GoName + "Table"
}
func (f fileGen) messageReaderInterfaceName(m *protogen.Message) string {
return m.GoIdent.GoName + "Reader"
}
func (f fileGen) messageTableVar(m *protogen.Message) string {
return f.param(m.GoIdent.GoName + "Table")
}
func (f fileGen) param(name string) string {
return strcase.ToLowerCamel(name)
}
func (f fileGen) messageTableReceiverName(m *protogen.Message) string {
return f.param(f.messageTableInterfaceName(m))
}
func (f fileGen) messageConstructorName(m *protogen.Message) string {
return "New" + f.messageTableInterfaceName(m)
}
func (f fileGen) genStoreMethods(stores []*protogen.Message) {
// getters
for _, msg := range stores {
name := f.messageTableInterfaceName(msg)
f.P("func(x ", f.storeStructName(), ") ", name, "() ", name, "{")
f.P("return x.", f.param(msg.GoIdent.GoName))
f.P("}")
f.P()
}
f.P("func(", f.storeStructName(), ") doNotImplement() {}")
f.P()
}
func (f fileGen) genStoreInterfaceGuard() {
f.P("var _ ", f.storeInterfaceName(), " = ", f.storeStructName(), "{}")
}
func (f fileGen) genStoreConstructor(stores []*protogen.Message) {
f.P("func New", f.storeInterfaceName(), "(db ", ormTablePkg.Ident("Schema"), ") (", f.storeInterfaceName(), ", error) {")
for _, store := range stores {
f.P(f.messageTableReceiverName(store), ", err := ", f.messageConstructorName(store), "(db)")
f.P("if err != nil {")
f.P("return nil, err")
f.P("}")
f.P()
}
f.P("return ", f.storeStructName(), "{")
for _, store := range stores {
f.P(f.messageTableReceiverName(store), ",")
}
f.P("}, nil")
f.P("}")
}
func fieldsToCamelCase(fields string) string {
splitFields := strings.Split(fields, ",")
camelFields := make([]string, len(splitFields))
for i, field := range splitFields {
camelFields[i] = strcase.ToCamel(field)
}
return strings.Join(camelFields, "")
}
func fieldsToSnakeCase(fields string) string {
splitFields := strings.Split(fields, ",")
camelFields := make([]string, len(splitFields))
for i, field := range splitFields {
camelFields[i] = strcase.ToSnake(field)
}
return strings.Join(camelFields, "_")
}
-138
View File
@@ -1,138 +0,0 @@
// nolint:unused // ignore unused code linting
package codegen
import (
"fmt"
"strings"
"github.com/iancoleman/strcase"
"google.golang.org/protobuf/reflect/protoreflect"
)
func (t tableGen) genIndexKeys() {
// interface that all keys must adhere to
t.P("type ", t.indexKeyInterfaceName(), " interface {")
t.P("id() uint32")
t.P("values() []interface{}")
t.P(t.param(t.indexKeyInterfaceName()), "()")
t.P("}")
t.P()
// start with primary key..
t.P("// primary key starting index..")
t.genIndex(t.table.PrimaryKey.Fields, 0, true)
for _, idx := range t.table.Index {
t.genIndex(idx.Fields, idx.Id, false)
}
}
func (t tableGen) genIterator() {
t.P("type ", t.iteratorName(), " struct {")
t.P(ormTablePkg.Ident("Iterator"))
t.P("}")
t.P()
t.genValueFunc()
t.P()
}
func (t tableGen) genValueFunc() {
varName := t.param(t.msg.GoIdent.GoName)
t.P("func (i ", t.iteratorName(), ") Value() (*", t.QualifiedGoIdent(t.msg.GoIdent), ", error) {")
t.P("var ", varName, " ", t.QualifiedGoIdent(t.msg.GoIdent))
t.P("err := i.UnmarshalMessage(&", varName, ")")
t.P("return &", varName, ", err")
t.P("}")
}
func (t tableGen) genIndexMethods(idxKeyName string) {
receiverFunc := fmt.Sprintf("func (x %s) ", idxKeyName)
t.P(receiverFunc, "id() uint32 { return ", t.table.Id, " /* primary key */ }")
t.P(receiverFunc, "values() []interface{} { return x.vs }")
t.P(receiverFunc, t.param(t.indexKeyInterfaceName()), "() {}")
t.P()
}
func (t tableGen) genIndexInterfaceGuard(idxKeyName string) {
t.P("var _ ", t.indexKeyInterfaceName(), " = ", idxKeyName, "{}")
t.P()
}
func (t tableGen) indexKeyInterfaceName() string {
return t.msg.GoIdent.GoName + "IndexKey"
}
func (t tableGen) genIndexKey(idxKeyName string) {
t.P("type ", idxKeyName, " struct {")
t.P("vs []interface{}")
t.P("}")
t.P()
}
func (t tableGen) indexKeyParts(names []protoreflect.Name) string {
cnames := make([]string, len(names))
for i, name := range names {
cnames[i] = strcase.ToCamel(string(name))
}
return strings.Join(cnames, "")
}
func (t tableGen) indexKeyName(names []protoreflect.Name) string {
cnames := make([]string, len(names))
for i, name := range names {
cnames[i] = strcase.ToCamel(string(name))
}
joinedNames := strings.Join(cnames, "")
return t.msg.GoIdent.GoName + joinedNames + "IndexKey"
}
func (t tableGen) indexStructName(fields []string) string {
names := make([]string, len(fields))
for i, field := range fields {
names[i] = strcase.ToCamel(field)
}
joinedNames := strings.Join(names, "")
return t.msg.GoIdent.GoName + joinedNames + "IndexKey"
}
func (t tableGen) genIndex(fields string, id uint32, isPrimaryKey bool) {
fieldsSlc := strings.Split(fields, ",")
idxKeyName := t.indexStructName(fieldsSlc)
if isPrimaryKey {
t.P("type ", t.msg.GoIdent.GoName, "PrimaryKey = ", idxKeyName)
t.P()
}
t.P("type ", idxKeyName, " struct {")
t.P("vs []interface{}")
t.P("}")
t.genIndexInterfaceMethods(id, idxKeyName)
for i := 1; i < len(fieldsSlc)+1; i++ {
t.genWithMethods(idxKeyName, fieldsSlc[:i])
}
}
func (t tableGen) genIndexInterfaceMethods(id uint32, indexStructName string) {
funPrefix := fmt.Sprintf("func (x %s) ", indexStructName)
t.P(funPrefix, "id() uint32 {return ", id, "}")
t.P(funPrefix, "values() []interface{} {return x.vs}")
t.P(funPrefix, t.param(t.indexKeyInterfaceName()), "() {}")
t.P()
}
func (t tableGen) genWithMethods(indexStructName string, parts []string) {
funcPrefix := fmt.Sprintf("func (this %s) ", indexStructName)
camelParts := make([]string, len(parts))
for i, part := range parts {
camelParts[i] = strcase.ToCamel(part)
}
funcName := "With" + strings.Join(camelParts, "")
t.P(funcPrefix, funcName, "(", t.fieldArgsFromStringSlice(parts), ") ", indexStructName, "{")
t.P("this.vs = []interface{}{", strings.Join(parts, ","), "}")
t.P("return this")
t.P("}")
t.P()
}
-320
View File
@@ -1,320 +0,0 @@
package codegen
import (
"bytes"
"fmt"
"os"
"github.com/iancoleman/strcase"
"golang.org/x/exp/maps"
"golang.org/x/exp/slices"
"google.golang.org/protobuf/compiler/protogen"
"google.golang.org/protobuf/proto"
"google.golang.org/protobuf/reflect/protoreflect"
ormv1 "cosmossdk.io/api/cosmos/orm/v1"
"cosmossdk.io/orm/internal/fieldnames"
)
type queryProtoGen struct {
*protogen.File
imports map[string]bool
svc *writer
msgs *writer
outFile *os.File
}
func (g queryProtoGen) gen() error {
g.imports[g.Desc.Path()] = true
g.svc.F("// %sService queries the state of the tables specified by %s.", g.queryServiceName(), g.Desc.Path())
g.svc.F("service %sService {", g.queryServiceName())
g.svc.Indent()
for _, msg := range g.Messages {
tableDesc := proto.GetExtension(msg.Desc.Options(), ormv1.E_Table).(*ormv1.TableDescriptor)
if tableDesc != nil {
err := g.genTableRPCMethods(msg, tableDesc)
if err != nil {
return err
}
}
singletonDesc := proto.GetExtension(msg.Desc.Options(), ormv1.E_Singleton).(*ormv1.SingletonDescriptor)
if singletonDesc != nil {
err := g.genSingletonRPCMethods(msg)
if err != nil {
return err
}
}
}
g.svc.Dedent()
g.svc.F("}")
g.svc.F("")
outBuf := newWriter()
outBuf.F("// Code generated by protoc-gen-go-cosmos-orm-proto. DO NOT EDIT.")
outBuf.F(`syntax = "proto3";`)
outBuf.F("package %s;", g.Desc.Package())
outBuf.F("")
imports := maps.Keys(g.imports)
slices.Sort(imports)
for _, i := range imports {
outBuf.F(`import "%s";`, i)
}
outBuf.F("")
_, err := outBuf.Write(g.svc.Bytes())
if err != nil {
return err
}
_, err = outBuf.Write(g.msgs.Bytes())
if err != nil {
return err
}
_, err = g.outFile.Write(outBuf.Bytes())
if err != nil {
return err
}
return g.outFile.Close()
}
func (g queryProtoGen) genTableRPCMethods(msg *protogen.Message, desc *ormv1.TableDescriptor) error {
name := msg.Desc.Name()
g.svc.F("// Get queries the %s table by its primary key.", name)
g.svc.F("rpc Get%s(Get%sRequest) returns (Get%sResponse) {}", name, name, name) // TODO grpc gateway
g.startRequestType("Get%sRequest", name)
g.msgs.Indent()
primaryKeyFields := fieldnames.CommaSeparatedFieldNames(desc.PrimaryKey.Fields)
fields := msg.Desc.Fields()
for i, fieldName := range primaryKeyFields.Names() {
field := fields.ByName(fieldName)
if field == nil {
return fmt.Errorf("can't find primary key field %s", fieldName)
}
g.msgs.F("// %s specifies the value of the %s field in the primary key.", fieldName, fieldName)
g.msgs.F("%s %s = %d;", g.fieldType(field), fieldName, i+1)
}
g.msgs.Dedent()
g.msgs.F("}")
g.msgs.F("")
g.startResponseType("Get%sResponse", name)
g.msgs.Indent()
g.msgs.F("// value is the response value.")
g.msgs.F("%s value = 1;", name)
g.msgs.Dedent()
g.msgs.F("}")
g.msgs.F("")
for _, idx := range desc.Index {
if !idx.Unique {
continue
}
fieldsCamel := fieldsToCamelCase(idx.Fields)
methodName := fmt.Sprintf("Get%sBy%s", name, fieldsCamel)
g.svc.F("// %s queries the %s table by its %s index", methodName, name, fieldsCamel)
g.svc.F("rpc %s(%sRequest) returns (%sResponse) {}", methodName, methodName, methodName) // TODO grpc gateway
g.startRequestType("%sRequest", methodName)
g.msgs.Indent()
fieldNames := fieldnames.CommaSeparatedFieldNames(idx.Fields)
for i, fieldName := range fieldNames.Names() {
field := fields.ByName(fieldName)
if field == nil {
return fmt.Errorf("can't find unique index field %s", fieldName)
}
g.msgs.F("%s %s = %d;", g.fieldType(field), fieldName, i+1)
}
g.msgs.Dedent()
g.msgs.F("}")
g.msgs.F("")
g.startResponseType("%sResponse", methodName)
g.msgs.Indent()
g.msgs.F("%s value = 1;", name)
g.msgs.Dedent()
g.msgs.F("}")
g.msgs.F("")
}
g.imports["cosmos/base/query/v1beta1/pagination.proto"] = true
g.svc.F("// List%s queries the %s table using prefix and range queries against defined indexes.", name, name)
g.svc.F("rpc List%s(List%sRequest) returns (List%sResponse) {}", name, name, name) // TODO grpc gateway
g.startRequestType("List%sRequest", name)
g.msgs.Indent()
g.msgs.F("// IndexKey specifies the value of an index key to use in prefix and range queries.")
g.msgs.F("message IndexKey {")
g.msgs.Indent()
indexFields := []string{desc.PrimaryKey.Fields}
// the primary key has field number 1
fieldNums := []uint32{1}
for _, index := range desc.Index {
indexFields = append(indexFields, index.Fields)
// index field numbers are their id + 1
fieldNums = append(fieldNums, index.Id+1)
}
g.msgs.F("// key specifies the index key value.")
g.msgs.F("oneof key {")
g.msgs.Indent()
for i, fields := range indexFields {
fieldName := fieldsToSnakeCase(fields)
typeName := fieldsToCamelCase(fields)
g.msgs.F("// %s specifies the value of the %s index key to use in the query.", fieldName, typeName)
g.msgs.F("%s %s = %d;", typeName, fieldName, fieldNums[i])
}
g.msgs.Dedent()
g.msgs.F("}")
for _, fieldNames := range indexFields {
g.msgs.F("")
g.msgs.F("message %s {", fieldsToCamelCase(fieldNames))
g.msgs.Indent()
for i, fieldName := range fieldnames.CommaSeparatedFieldNames(fieldNames).Names() {
g.msgs.F("// %s is the value of the %s field in the index.", fieldName, fieldName)
g.msgs.F("// It can be omitted to query for all valid values of that field in this segment of the index.")
g.msgs.F("optional %s %s = %d;", g.fieldType(fields.ByName(fieldName)), fieldName, i+1)
}
g.msgs.Dedent()
g.msgs.F("}")
}
g.msgs.Dedent()
g.msgs.F("}")
g.msgs.F("")
g.msgs.F("// query specifies the type of query - either a prefix or range query.")
g.msgs.F("oneof query {")
g.msgs.Indent()
g.msgs.F("// prefix_query specifies the index key value to use for the prefix query.")
g.msgs.F("IndexKey prefix_query = 1;")
g.msgs.F("// range_query specifies the index key from/to values to use for the range query.")
g.msgs.F("RangeQuery range_query = 2;")
g.msgs.Dedent()
g.msgs.F("}")
g.msgs.F("// pagination specifies optional pagination parameters.")
g.msgs.F("cosmos.base.query.v1beta1.PageRequest pagination = 3;")
g.msgs.F("")
g.msgs.F("// RangeQuery specifies the from/to index keys for a range query.")
g.msgs.F("message RangeQuery {")
g.msgs.Indent()
g.msgs.F("// from is the index key to use for the start of the range query.")
g.msgs.F("// To query from the start of an index, specify an index key for that index with empty values.")
g.msgs.F("IndexKey from = 1;")
g.msgs.F("// to is the index key to use for the end of the range query.")
g.msgs.F("// The index key type MUST be the same as the index key type used for from.")
g.msgs.F("// To query from to the end of an index it can be omitted.")
g.msgs.F("IndexKey to = 2;")
g.msgs.Dedent()
g.msgs.F("}")
g.msgs.Dedent()
g.msgs.F("}")
g.msgs.F("")
g.startResponseType("List%sResponse", name)
g.msgs.Indent()
g.msgs.F("// values are the results of the query.")
g.msgs.F("repeated %s values = 1;", name)
g.msgs.F("// pagination is the pagination response.")
g.msgs.F("cosmos.base.query.v1beta1.PageResponse pagination = 2;")
g.msgs.Dedent()
g.msgs.F("}")
g.msgs.F("")
return nil
}
func (g queryProtoGen) genSingletonRPCMethods(msg *protogen.Message) error {
name := msg.Desc.Name()
g.svc.F("// Get%s queries the %s singleton.", name, name)
g.svc.F("rpc Get%s(Get%sRequest) returns (Get%sResponse) {}", name, name, name) // TODO grpc gateway
g.startRequestType("Get%sRequest", name)
g.msgs.F("}")
g.msgs.F("")
g.startRequestType("Get%sResponse", name)
g.msgs.Indent()
g.msgs.F("%s value = 1;", name)
g.msgs.Dedent()
g.msgs.F("}")
g.msgs.F("")
return nil
}
func (g queryProtoGen) startRequestType(format string, args ...any) {
g.startRequestResponseType("request", format, args...)
}
func (g queryProtoGen) startResponseType(format string, args ...any) {
g.startRequestResponseType("response", format, args...)
}
func (g queryProtoGen) startRequestResponseType(typ, format string, args ...any) {
msgTypeName := fmt.Sprintf(format, args...)
g.msgs.F("// %s is the %s/%s %s type.", msgTypeName, g.queryServiceName(), msgTypeName, typ)
g.msgs.F("message %s {", msgTypeName)
}
func (g queryProtoGen) queryServiceName() string {
return fmt.Sprintf("%sQuery", strcase.ToCamel(fileShortName(g.File)))
}
func (g queryProtoGen) fieldType(descriptor protoreflect.FieldDescriptor) string {
if descriptor.Kind() == protoreflect.MessageKind {
message := descriptor.Message()
g.imports[message.ParentFile().Path()] = true
return string(message.FullName())
}
return descriptor.Kind().String()
}
type writer struct {
*bytes.Buffer
indent int
indentStr string
}
func newWriter() *writer {
return &writer{
Buffer: &bytes.Buffer{},
}
}
func (w *writer) F(format string, args ...interface{}) {
_, err := w.Write([]byte(w.indentStr))
if err != nil {
panic(err)
}
_, err = fmt.Fprintf(w, format, args...)
if err != nil {
panic(err)
}
_, err = fmt.Fprintln(w)
if err != nil {
panic(err)
}
}
func (w *writer) Indent() {
w.indent++
w.updateIndent()
}
func (w *writer) updateIndent() {
w.indentStr = ""
for i := 0; i < w.indent; i++ {
w.indentStr += " "
}
}
func (w *writer) Dedent() {
w.indent--
w.updateIndent()
}
-40
View File
@@ -1,40 +0,0 @@
package listinternal
import (
"fmt"
"google.golang.org/protobuf/proto"
)
// Options is the internal list options struct.
type Options struct {
Reverse, CountTotal bool
Offset, Limit, DefaultLimit uint64
Cursor []byte
Filter func(proto.Message) bool
}
func (o Options) Validate() error {
if len(o.Cursor) != 0 {
if o.Offset > 0 {
return fmt.Errorf("can only specify one of cursor or offset")
}
}
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)
}
}
-38
View File
@@ -1,38 +0,0 @@
package testkv
import (
"bytes"
"gotest.tools/v3/assert"
"cosmossdk.io/orm/model/ormtable"
"cosmossdk.io/orm/types/kv"
)
func AssertBackendsEqual(t assert.TestingT, b1, b2 ormtable.Backend) {
it1, err := b1.CommitmentStoreReader().Iterator(nil, nil)
assert.NilError(t, err)
it2, err := b2.CommitmentStoreReader().Iterator(nil, nil)
assert.NilError(t, err)
AssertIteratorsEqual(t, it1, it2)
it1, err = b1.IndexStoreReader().Iterator(nil, nil)
assert.NilError(t, err)
it2, err = b2.IndexStoreReader().Iterator(nil, nil)
assert.NilError(t, err)
AssertIteratorsEqual(t, it1, it2)
}
func AssertIteratorsEqual(t assert.TestingT, it1, it2 kv.Iterator) {
for it1.Valid() {
assert.Assert(t, it2.Valid())
assert.Assert(t, bytes.Equal(it1.Key(), it2.Key()))
assert.Assert(t, bytes.Equal(it1.Value(), it2.Value()))
it1.Next()
it2.Next()
}
}
-329
View File
@@ -1,329 +0,0 @@
package testkv
import (
"context"
"fmt"
"google.golang.org/protobuf/proto"
"cosmossdk.io/orm/encoding/ormkv"
"cosmossdk.io/orm/internal/stablejson"
"cosmossdk.io/orm/model/ormtable"
"cosmossdk.io/orm/types/kv"
)
// 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 {
hooks := debugHooks{
debugger: debugger,
validateHooks: backend.ValidateHooks(),
writeHooks: backend.WriteHooks(),
}
return ormtable.NewBackend(ormtable.BackendOptions{
CommitmentStore: NewDebugStore(backend.CommitmentStore(), debugger, "commit"),
IndexStore: NewDebugStore(backend.IndexStore(), debugger, "index"),
ValidateHooks: hooks,
WriteHooks: 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, 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
validateHooks ormtable.ValidateHooks
writeHooks ormtable.WriteHooks
}
func (d debugHooks) ValidateInsert(context context.Context, message proto.Message) error {
jsonBz, err := stablejson.Marshal(message)
if err != nil {
return err
}
d.debugger.Log(fmt.Sprintf(
"ORM BEFORE INSERT %s %s",
message.ProtoReflect().Descriptor().FullName(),
jsonBz,
))
if d.validateHooks != nil {
return d.validateHooks.ValidateInsert(context, message)
}
return nil
}
func (d debugHooks) ValidateUpdate(ctx context.Context, existing, new proto.Message) error {
existingJSON, err := stablejson.Marshal(existing)
if err != nil {
return err
}
newJSON, err := stablejson.Marshal(new)
if err != nil {
return err
}
d.debugger.Log(fmt.Sprintf(
"ORM BEFORE UPDATE %s %s -> %s",
existing.ProtoReflect().Descriptor().FullName(),
existingJSON,
newJSON,
))
if d.validateHooks != nil {
return d.validateHooks.ValidateUpdate(ctx, existing, new)
}
return nil
}
func (d debugHooks) ValidateDelete(ctx context.Context, message proto.Message) error {
jsonBz, err := stablejson.Marshal(message)
if err != nil {
return err
}
d.debugger.Log(fmt.Sprintf(
"ORM BEFORE DELETE %s %s",
message.ProtoReflect().Descriptor().FullName(),
jsonBz,
))
if d.validateHooks != nil {
return d.validateHooks.ValidateDelete(ctx, message)
}
return nil
}
func (d debugHooks) OnInsert(ctx context.Context, message proto.Message) {
jsonBz, err := stablejson.Marshal(message)
if err != nil {
panic(err)
}
d.debugger.Log(fmt.Sprintf(
"ORM AFTER INSERT %s %s",
message.ProtoReflect().Descriptor().FullName(),
jsonBz,
))
if d.writeHooks != nil {
d.writeHooks.OnInsert(ctx, message)
}
}
func (d debugHooks) OnUpdate(ctx context.Context, existing, new proto.Message) {
existingJSON, err := stablejson.Marshal(existing)
if err != nil {
panic(err)
}
newJSON, err := stablejson.Marshal(new)
if err != nil {
panic(err)
}
d.debugger.Log(fmt.Sprintf(
"ORM AFTER UPDATE %s %s -> %s",
existing.ProtoReflect().Descriptor().FullName(),
existingJSON,
newJSON,
))
if d.writeHooks != nil {
d.writeHooks.OnUpdate(ctx, existing, new)
}
}
func (d debugHooks) OnDelete(ctx context.Context, message proto.Message) {
jsonBz, err := stablejson.Marshal(message)
if err != nil {
panic(err)
}
d.debugger.Log(fmt.Sprintf(
"ORM AFTER DELETE %s %s",
message.ProtoReflect().Descriptor().FullName(),
jsonBz,
))
if d.writeHooks != nil {
d.writeHooks.OnDelete(ctx, message)
}
}
-18
View File
@@ -1,18 +0,0 @@
package testkv
import (
"testing"
dbm "github.com/cosmos/cosmos-db"
"gotest.tools/v3/assert"
"cosmossdk.io/orm/model/ormtable"
)
func NewGoLevelDBBackend(t testing.TB) ormtable.Backend {
db, err := dbm.NewGoLevelDB("test", t.TempDir(), nil)
assert.NilError(t, err)
return ormtable.NewBackend(ormtable.BackendOptions{
CommitmentStore: db,
})
}
-27
View File
@@ -1,27 +0,0 @@
package testkv
import (
dbm "github.com/cosmos/cosmos-db"
"cosmossdk.io/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
})
}
-151
View File
@@ -1,151 +0,0 @@
package ormdb
import (
"context"
"fmt"
"sort"
"golang.org/x/exp/maps"
"google.golang.org/protobuf/reflect/protoreflect"
"cosmossdk.io/core/appmodule"
"cosmossdk.io/errors"
"cosmossdk.io/orm/types/ormerrors"
)
type appModuleGenesisWrapper struct {
moduleDB
}
func (m appModuleGenesisWrapper) IsOnePerModuleType() {}
func (m appModuleGenesisWrapper) IsAppModule() {}
func (m appModuleGenesisWrapper) DefaultGenesis(target appmodule.GenesisTarget) error {
tableNames := maps.Keys(m.tablesByName)
sort.Slice(tableNames, func(i, j int) bool {
ti, tj := tableNames[i], tableNames[j]
return ti.Name() < tj.Name()
})
for _, name := range tableNames {
table := m.tablesByName[name]
w, err := target(string(name))
if err != nil {
return err
}
_, err = w.Write(table.DefaultJSON())
if err != nil {
return err
}
err = w.Close()
if err != nil {
return err
}
}
return nil
}
func (m appModuleGenesisWrapper) ValidateGenesis(source appmodule.GenesisSource) error {
errMap := map[protoreflect.FullName]error{}
names := maps.Keys(m.tablesByName)
sort.Slice(names, func(i, j int) bool {
ti, tj := names[i], names[j]
return ti.Name() < tj.Name()
})
for _, name := range names {
r, err := source(string(name))
if err != nil {
return err
}
if r == nil {
continue
}
table := m.tablesByName[name]
err = table.ValidateJSON(r)
if err != nil {
errMap[name] = err
}
err = r.Close()
if err != nil {
return err
}
}
if len(errMap) != 0 {
var allErrors string
for name, err := range errMap {
allErrors += fmt.Sprintf("Error in JSON for table %s: %v\n", name, err)
}
return ormerrors.JSONValidationError.Wrap(allErrors)
}
return nil
}
func (m appModuleGenesisWrapper) InitGenesis(ctx context.Context, source appmodule.GenesisSource) error {
var names []string
for name := range m.tablesByName {
names = append(names, string(name))
}
sort.Strings(names)
for _, name := range names {
fullName := protoreflect.FullName(name)
table := m.tablesByName[fullName]
r, err := source(string(fullName))
if err != nil {
return errors.Wrapf(err, "table %s", fullName)
}
if r == nil {
continue
}
err = table.ImportJSON(ctx, r)
if err != nil {
return errors.Wrapf(err, "table %s", fullName)
}
err = r.Close()
if err != nil {
return errors.Wrapf(err, "table %s", fullName)
}
}
return nil
}
func (m appModuleGenesisWrapper) ExportGenesis(ctx context.Context, sink appmodule.GenesisTarget) error {
// Ensure that we export the tables in a deterministic order.
tableNames := maps.Keys(m.tablesByName)
sort.Slice(tableNames, func(i, j int) bool {
ti, tj := tableNames[i], tableNames[j]
return ti.Name() < tj.Name()
})
for _, name := range tableNames {
w, err := sink(string(name))
if err != nil {
return err
}
table := m.tablesByName[name]
err = table.ExportJSON(ctx, w)
if err != nil {
return err
}
err = w.Close()
if err != nil {
return err
}
}
return nil
}
-219
View File
@@ -1,219 +0,0 @@
package ormdb
import (
"bytes"
"context"
"encoding/binary"
"fmt"
"math"
"google.golang.org/protobuf/proto"
"google.golang.org/protobuf/reflect/protodesc"
"google.golang.org/protobuf/reflect/protoreflect"
"google.golang.org/protobuf/reflect/protoregistry"
ormv1alpha1 "cosmossdk.io/api/cosmos/orm/v1alpha1"
"cosmossdk.io/core/appmodule"
"cosmossdk.io/core/store"
"cosmossdk.io/orm/encoding/encodeutil"
"cosmossdk.io/orm/encoding/ormkv"
"cosmossdk.io/orm/model/ormtable"
"cosmossdk.io/orm/types/ormerrors"
)
// ModuleDB defines the ORM database type to be used by modules.
type ModuleDB interface {
ormtable.Schema
// GenesisHandler returns an implementation of appmodule.HasGenesis
// to be embedded in or called from app module implementations.
// Ex:
// type AppModule struct {
// appmodule.HasGenesis
// }
//
// func NewKeeper(db ModuleDB) *Keeper {
// return &Keeper{genesisHandler: db.GenesisHandler()}
// }
//
// func NewAppModule(keeper keeper.Keeper) AppModule {
// return AppModule{HasGenesis: keeper.GenesisHandler()}
// }
GenesisHandler() appmodule.HasGenesis
private()
}
type moduleDB struct {
prefix []byte
filesByID map[uint32]*fileDescriptorDB
tablesByName map[protoreflect.FullName]ormtable.Table
}
// ModuleDBOptions are options for constructing a ModuleDB.
type ModuleDBOptions struct {
// TypeResolver is an optional type resolver to be used when unmarshaling
// protobuf messages. If it is nil, protoregistry.GlobalTypes will be used.
TypeResolver ormtable.TypeResolver
// FileResolver is an optional file resolver that can be used to retrieve
// pinned file descriptors that may be different from those available at
// runtime. The file descriptor versions returned by this resolver will be
// used instead of the ones provided at runtime by the ModuleSchema.
FileResolver protodesc.Resolver
// 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
// KVStoreService is the storage service to use for the DB if default KV-store storage is used.
KVStoreService store.KVStoreService
// KVStoreService is the storage service to use for the DB if memory storage is used.
MemoryStoreService store.MemoryStoreService
// KVStoreService is the storage service to use for the DB if transient storage is used.
TransientStoreService store.TransientStoreService
}
// NewModuleDB constructs a ModuleDB instance from the provided schema and options.
func NewModuleDB(schema *ormv1alpha1.ModuleSchemaDescriptor, options ModuleDBOptions) (ModuleDB, error) {
prefix := schema.Prefix
db := &moduleDB{
prefix: prefix,
filesByID: map[uint32]*fileDescriptorDB{},
tablesByName: map[protoreflect.FullName]ormtable.Table{},
}
fileResolver := options.FileResolver
if fileResolver == nil {
fileResolver = protoregistry.GlobalFiles
}
for _, entry := range schema.SchemaFile {
var backendResolver ormtable.BackendResolver
switch entry.StorageType {
case ormv1alpha1.StorageType_STORAGE_TYPE_DEFAULT_UNSPECIFIED:
service := options.KVStoreService
if service != nil {
// for testing purposes, the ORM allows KVStoreService to be omitted
// and a default test backend can be used
backendResolver = func(ctx context.Context) (ormtable.ReadBackend, error) {
kvStore := service.OpenKVStore(ctx)
return ormtable.NewBackend(ormtable.BackendOptions{
CommitmentStore: kvStore,
IndexStore: kvStore,
}), nil
}
}
case ormv1alpha1.StorageType_STORAGE_TYPE_MEMORY:
service := options.MemoryStoreService
if service == nil {
return nil, fmt.Errorf("missing MemoryStoreService")
}
backendResolver = func(ctx context.Context) (ormtable.ReadBackend, error) {
kvStore := service.OpenMemoryStore(ctx)
return ormtable.NewBackend(ormtable.BackendOptions{
CommitmentStore: kvStore,
IndexStore: kvStore,
}), nil
}
case ormv1alpha1.StorageType_STORAGE_TYPE_TRANSIENT:
service := options.TransientStoreService
if service == nil {
return nil, fmt.Errorf("missing TransientStoreService")
}
backendResolver = func(ctx context.Context) (ormtable.ReadBackend, error) {
kvStore := service.OpenTransientStore(ctx)
return ormtable.NewBackend(ormtable.BackendOptions{
CommitmentStore: kvStore,
IndexStore: kvStore,
}), nil
}
default:
return nil, fmt.Errorf("unsupported storage type %s", entry.StorageType)
}
id := entry.Id
fileDescriptor, err := fileResolver.FindFileByPath(entry.ProtoFileName)
if err != nil {
return nil, err
}
if id == 0 {
return nil, ormerrors.InvalidFileDescriptorID.Wrapf("for %s", fileDescriptor.Path())
}
opts := fileDescriptorDBOptions{
ID: id,
Prefix: prefix,
TypeResolver: options.TypeResolver,
JSONValidator: options.JSONValidator,
BackendResolver: backendResolver,
}
fdSchema, err := newFileDescriptorDB(fileDescriptor, opts)
if err != nil {
return nil, err
}
db.filesByID[id] = fdSchema
for name, table := range fdSchema.tablesByName {
if _, ok := db.tablesByName[name]; ok {
return nil, ormerrors.UnexpectedError.Wrapf("duplicate table %s", name)
}
db.tablesByName[name] = table
}
}
return db, nil
}
func (m moduleDB) DecodeEntry(k, v []byte) (ormkv.Entry, error) {
r := bytes.NewReader(k)
err := encodeutil.SkipPrefix(r, m.prefix)
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)
}
fileSchema, ok := m.filesByID[uint32(id)]
if !ok {
return nil, ormerrors.UnexpectedDecodePrefix.Wrapf("can't find FileDescriptor schema with id %d", id)
}
return fileSchema.DecodeEntry(k, v)
}
func (m moduleDB) EncodeEntry(entry ormkv.Entry) (k, v []byte, err error) {
tableName := entry.GetTableName()
table, ok := m.tablesByName[tableName]
if !ok {
return nil, nil, ormerrors.BadDecodeEntry.Wrapf("can't find table %s", tableName)
}
return table.EncodeEntry(entry)
}
func (m moduleDB) GetTable(message proto.Message) ormtable.Table {
return m.tablesByName[message.ProtoReflect().Descriptor().FullName()]
}
func (m moduleDB) GenesisHandler() appmodule.HasGenesis {
return appModuleGenesisWrapper{m}
}
func (moduleDB) private() {}
-415
View File
@@ -1,415 +0,0 @@
package ormdb_test
import (
"bytes"
"context"
"encoding/json"
"fmt"
"strings"
"testing"
dbm "github.com/cosmos/cosmos-db"
"github.com/golang/mock/gomock"
"gotest.tools/v3/assert"
"gotest.tools/v3/golden"
appv1alpha1 "cosmossdk.io/api/cosmos/app/v1alpha1"
ormmodulev1alpha1 "cosmossdk.io/api/cosmos/orm/module/v1alpha1"
ormv1alpha1 "cosmossdk.io/api/cosmos/orm/v1alpha1"
"cosmossdk.io/core/appconfig"
"cosmossdk.io/core/appmodule"
"cosmossdk.io/core/genesis"
"cosmossdk.io/core/store"
"cosmossdk.io/depinject"
_ "cosmossdk.io/orm" // required for ORM module registration
"cosmossdk.io/orm/internal/testkv"
"cosmossdk.io/orm/internal/testpb"
"cosmossdk.io/orm/model/ormdb"
"cosmossdk.io/orm/model/ormtable"
"cosmossdk.io/orm/testing/ormmocks"
"cosmossdk.io/orm/testing/ormtest"
"cosmossdk.io/orm/types/ormerrors"
)
// These tests use a simulated bank keeper. Addresses and balances use
// string and uint64 types respectively for simplicity.
func init() {
// this registers the test module with the module registry
appmodule.Register(&testpb.Module{},
appmodule.Provide(NewKeeper),
)
}
var TestBankSchema = &ormv1alpha1.ModuleSchemaDescriptor{
SchemaFile: []*ormv1alpha1.ModuleSchemaDescriptor_FileEntry{
{
Id: 1,
ProtoFileName: testpb.File_testpb_bank_proto.Path(),
},
},
}
type keeper struct {
store testpb.BankStore
}
func NewKeeper(db ormdb.ModuleDB) (Keeper, error) {
bankStore, err := testpb.NewBankStore(db)
return keeper{bankStore}, err
}
type Keeper interface {
Send(ctx context.Context, from, to, denom string, amount uint64) error
Mint(ctx context.Context, acct, denom string, amount uint64) error
Burn(ctx context.Context, acct, denom string, amount uint64) error
Balance(ctx context.Context, acct, denom string) (uint64, error)
Supply(ctx context.Context, denom string) (uint64, error)
}
func (k keeper) Send(ctx context.Context, from, to, denom string, amount uint64) error {
err := k.safeSubBalance(ctx, from, denom, amount)
if err != nil {
return err
}
return k.addBalance(ctx, to, denom, amount)
}
func (k keeper) Mint(ctx context.Context, acct, denom string, amount uint64) error {
supply, err := k.store.SupplyTable().Get(ctx, denom)
if err != nil && !ormerrors.IsNotFound(err) {
return err
}
if supply == nil {
supply = &testpb.Supply{Denom: denom, Amount: amount}
} else {
supply.Amount += amount
}
err = k.store.SupplyTable().Save(ctx, supply)
if err != nil {
return err
}
return k.addBalance(ctx, acct, denom, amount)
}
func (k keeper) Burn(ctx context.Context, acct, denom string, amount uint64) error {
supplyStore := k.store.SupplyTable()
supply, err := supplyStore.Get(ctx, denom)
if err != nil {
return err
}
if amount > supply.Amount {
return fmt.Errorf("insufficient supply")
}
supply.Amount -= amount
if supply.Amount == 0 {
err = supplyStore.Delete(ctx, supply)
} else {
err = supplyStore.Save(ctx, supply)
}
if err != nil {
return err
}
return k.safeSubBalance(ctx, acct, denom, amount)
}
func (k keeper) Balance(ctx context.Context, acct, denom string) (uint64, error) {
balance, err := k.store.BalanceTable().Get(ctx, acct, denom)
if err != nil {
if ormerrors.IsNotFound(err) {
return 0, nil
}
return 0, err
}
return balance.Amount, err
}
func (k keeper) Supply(ctx context.Context, denom string) (uint64, error) {
supply, err := k.store.SupplyTable().Get(ctx, denom)
if supply == nil {
if ormerrors.IsNotFound(err) {
return 0, nil
}
return 0, err
}
return supply.Amount, err
}
func (k keeper) addBalance(ctx context.Context, acct, denom string, amount uint64) error {
balance, err := k.store.BalanceTable().Get(ctx, acct, denom)
if err != nil && !ormerrors.IsNotFound(err) {
return err
}
if balance == nil {
balance = &testpb.Balance{
Address: acct,
Denom: denom,
Amount: amount,
}
} else {
balance.Amount += amount
}
return k.store.BalanceTable().Save(ctx, balance)
}
func (k keeper) safeSubBalance(ctx context.Context, acct, denom string, amount uint64) error {
balanceStore := k.store.BalanceTable()
balance, err := balanceStore.Get(ctx, acct, denom)
if err != nil {
return err
}
if amount > balance.Amount {
return fmt.Errorf("insufficient funds")
}
balance.Amount -= amount
if balance.Amount == 0 {
return balanceStore.Delete(ctx, balance)
}
return balanceStore.Save(ctx, balance)
}
func TestModuleDB(t *testing.T) {
// create db & debug context
db, err := ormdb.NewModuleDB(TestBankSchema, ormdb.ModuleDBOptions{})
assert.NilError(t, err)
debugBuf := &strings.Builder{}
backend := ormtest.NewMemoryBackend()
ctx := ormtable.WrapContextDefault(testkv.NewDebugBackend(
backend,
&testkv.EntryCodecDebugger{
EntryCodec: db,
Print: func(s string) { debugBuf.WriteString(s + "\n") },
},
))
// create keeper
k, err := NewKeeper(db)
assert.NilError(t, err)
runSimpleBankTests(t, k, ctx)
// check debug output
golden.Assert(t, debugBuf.String(), "bank_scenario.golden")
// check decode & encode
it, err := backend.CommitmentStore().Iterator(nil, nil)
assert.NilError(t, err)
for it.Valid() {
entry, err := db.DecodeEntry(it.Key(), it.Value())
assert.NilError(t, err)
k, v, err := db.EncodeEntry(entry)
assert.NilError(t, err)
assert.Assert(t, bytes.Equal(k, it.Key()))
assert.Assert(t, bytes.Equal(v, it.Value()))
it.Next()
}
// check JSON
target := genesis.RawJSONTarget{}
assert.NilError(t, db.GenesisHandler().DefaultGenesis(target.Target()))
rawJSON, err := target.JSON()
assert.NilError(t, err)
golden.Assert(t, string(rawJSON), "default_json.golden")
target = genesis.RawJSONTarget{}
assert.NilError(t, db.GenesisHandler().ExportGenesis(ctx, target.Target()))
rawJSON, err = target.JSON()
assert.NilError(t, err)
goodJSON := `{
"testpb.Supply": []
}`
source, err := genesis.SourceFromRawJSON(json.RawMessage(goodJSON))
assert.NilError(t, err)
assert.NilError(t, db.GenesisHandler().ValidateGenesis(source))
assert.NilError(t, db.GenesisHandler().InitGenesis(ormtable.WrapContextDefault(ormtest.NewMemoryBackend()), source))
badJSON := `{
"testpb.Balance": 5,
"testpb.Supply": {}
}
`
source, err = genesis.SourceFromRawJSON(json.RawMessage(badJSON))
assert.NilError(t, err)
assert.ErrorIs(t, db.GenesisHandler().ValidateGenesis(source), ormerrors.JSONValidationError)
backend2 := ormtest.NewMemoryBackend()
ctx2 := ormtable.WrapContextDefault(backend2)
source, err = genesis.SourceFromRawJSON(rawJSON)
assert.NilError(t, err)
assert.NilError(t, db.GenesisHandler().ValidateGenesis(source))
assert.NilError(t, db.GenesisHandler().InitGenesis(ctx2, source))
testkv.AssertBackendsEqual(t, backend, backend2)
}
func runSimpleBankTests(t *testing.T, k Keeper, ctx context.Context) { // nolint:revive // test function
// mint coins
denom := "foo"
acct1 := "bob"
err := k.Mint(ctx, acct1, denom, 100)
assert.NilError(t, err)
bal, err := k.Balance(ctx, acct1, denom)
assert.NilError(t, err)
assert.Equal(t, uint64(100), bal)
supply, err := k.Supply(ctx, denom)
assert.NilError(t, err)
assert.Equal(t, uint64(100), supply)
// send coins
acct2 := "sally"
err = k.Send(ctx, acct1, acct2, denom, 30)
assert.NilError(t, err)
bal, err = k.Balance(ctx, acct1, denom)
assert.NilError(t, err)
assert.Equal(t, uint64(70), bal)
bal, err = k.Balance(ctx, acct2, denom)
assert.NilError(t, err)
assert.Equal(t, uint64(30), bal)
// burn coins
err = k.Burn(ctx, acct2, denom, 3)
assert.NilError(t, err)
bal, err = k.Balance(ctx, acct2, denom)
assert.NilError(t, err)
assert.Equal(t, uint64(27), bal)
supply, err = k.Supply(ctx, denom)
assert.NilError(t, err)
assert.Equal(t, uint64(97), supply)
}
func TestHooks(t *testing.T) {
ctrl := gomock.NewController(t)
db, err := ormdb.NewModuleDB(TestBankSchema, ormdb.ModuleDBOptions{})
assert.NilError(t, err)
validateHooks := ormmocks.NewMockValidateHooks(ctrl)
writeHooks := ormmocks.NewMockWriteHooks(ctrl)
ctx := ormtable.WrapContextDefault(ormtest.NewMemoryBackend().
WithValidateHooks(validateHooks).
WithWriteHooks(writeHooks))
k, err := NewKeeper(db)
assert.NilError(t, err)
denom := "foo"
acct1 := "bob"
acct2 := "sally"
validateHooks.EXPECT().ValidateInsert(gomock.Any(), ormmocks.Eq(&testpb.Balance{Address: acct1, Denom: denom, Amount: 10}))
validateHooks.EXPECT().ValidateInsert(gomock.Any(), ormmocks.Eq(&testpb.Supply{Denom: denom, Amount: 10}))
writeHooks.EXPECT().OnInsert(gomock.Any(), ormmocks.Eq(&testpb.Balance{Address: acct1, Denom: denom, Amount: 10}))
writeHooks.EXPECT().OnInsert(gomock.Any(), ormmocks.Eq(&testpb.Supply{Denom: denom, Amount: 10}))
assert.NilError(t, k.Mint(ctx, acct1, denom, 10))
validateHooks.EXPECT().ValidateUpdate(
gomock.Any(),
ormmocks.Eq(&testpb.Balance{Address: acct1, Denom: denom, Amount: 10}),
ormmocks.Eq(&testpb.Balance{Address: acct1, Denom: denom, Amount: 5}),
)
validateHooks.EXPECT().ValidateInsert(
gomock.Any(),
ormmocks.Eq(&testpb.Balance{Address: acct2, Denom: denom, Amount: 5}),
)
writeHooks.EXPECT().OnUpdate(
gomock.Any(),
ormmocks.Eq(&testpb.Balance{Address: acct1, Denom: denom, Amount: 10}),
ormmocks.Eq(&testpb.Balance{Address: acct1, Denom: denom, Amount: 5}),
)
writeHooks.EXPECT().OnInsert(
gomock.Any(),
ormmocks.Eq(&testpb.Balance{Address: acct2, Denom: denom, Amount: 5}),
)
assert.NilError(t, k.Send(ctx, acct1, acct2, denom, 5))
validateHooks.EXPECT().ValidateUpdate(
gomock.Any(),
ormmocks.Eq(&testpb.Supply{Denom: denom, Amount: 10}),
ormmocks.Eq(&testpb.Supply{Denom: denom, Amount: 5}),
)
validateHooks.EXPECT().ValidateDelete(
gomock.Any(),
ormmocks.Eq(&testpb.Balance{Address: acct1, Denom: denom, Amount: 5}),
)
writeHooks.EXPECT().OnUpdate(
gomock.Any(),
ormmocks.Eq(&testpb.Supply{Denom: denom, Amount: 10}),
ormmocks.Eq(&testpb.Supply{Denom: denom, Amount: 5}),
)
writeHooks.EXPECT().OnDelete(
gomock.Any(),
ormmocks.Eq(&testpb.Balance{Address: acct1, Denom: denom, Amount: 5}),
)
assert.NilError(t, k.Burn(ctx, acct1, denom, 5))
}
type testStoreService struct {
db dbm.DB
}
func (t testStoreService) OpenKVStore(context.Context) store.KVStore {
return t.db
}
func (t testStoreService) OpenMemoryStore(context.Context) store.KVStore {
return t.db
}
func TestGetBackendResolver(t *testing.T) {
_, err := ormdb.NewModuleDB(&ormv1alpha1.ModuleSchemaDescriptor{
SchemaFile: []*ormv1alpha1.ModuleSchemaDescriptor_FileEntry{
{
Id: 1,
ProtoFileName: testpb.File_testpb_bank_proto.Path(),
StorageType: ormv1alpha1.StorageType_STORAGE_TYPE_MEMORY,
},
},
}, ormdb.ModuleDBOptions{})
assert.ErrorContains(t, err, "missing MemoryStoreService")
_, err = ormdb.NewModuleDB(&ormv1alpha1.ModuleSchemaDescriptor{
SchemaFile: []*ormv1alpha1.ModuleSchemaDescriptor_FileEntry{
{
Id: 1,
ProtoFileName: testpb.File_testpb_bank_proto.Path(),
StorageType: ormv1alpha1.StorageType_STORAGE_TYPE_MEMORY,
},
},
}, ormdb.ModuleDBOptions{
MemoryStoreService: testStoreService{db: dbm.NewMemDB()},
})
assert.NilError(t, err)
}
func ProvideTestRuntime() store.KVStoreService {
return testStoreService{db: dbm.NewMemDB()}
}
func TestAppConfigModule(t *testing.T) {
appCfg := appconfig.Compose(&appv1alpha1.Config{
Modules: []*appv1alpha1.ModuleConfig{
{Name: "bank", Config: appconfig.WrapAny(&testpb.Module{})},
{Name: "orm", Config: appconfig.WrapAny(&ormmodulev1alpha1.Module{})},
},
})
var k Keeper
err := depinject.Inject(depinject.Configs(
appCfg, depinject.Provide(ProvideTestRuntime),
), &k)
assert.NilError(t, err)
runSimpleBankTests(t, k, context.Background())
}
-106
View File
@@ -1,106 +0,0 @@
package ormtable_test
import (
"bytes"
"context"
"os"
"strings"
"testing"
"gotest.tools/v3/assert"
"gotest.tools/v3/golden"
"cosmossdk.io/orm/internal/testkv"
"cosmossdk.io/orm/internal/testpb"
"cosmossdk.io/orm/model/ormtable"
)
func TestAutoIncrementScenario(t *testing.T) {
table, err := ormtable.Build(ormtable.Options{
MessageType: (&testpb.ExampleAutoIncrementTable{}).ProtoReflect().Type(),
})
assert.NilError(t, err)
autoTable, ok := table.(ormtable.AutoIncrementTable)
assert.Assert(t, ok)
// first run tests with a split index-commitment store
runAutoIncrementScenario(t, autoTable, 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, autoTable, ormtable.WrapContextDefault(store))
golden.Assert(t, debugBuf.String(), "test_auto_inc.golden")
checkEncodeDecodeEntries(t, table, store.IndexStoreReader())
}
func runAutoIncrementScenario(t *testing.T, table ormtable.AutoIncrementTable, ctx context.Context) { //nolint:revive // ignore linting on testing function signature
store, err := testpb.NewExampleAutoIncrementTableTable(table)
assert.NilError(t, err)
err = store.Save(ctx, &testpb.ExampleAutoIncrementTable{Id: 5})
assert.ErrorContains(t, err, "not found")
ex1 := &testpb.ExampleAutoIncrementTable{X: "foo", Y: 5}
assert.NilError(t, store.Save(ctx, ex1))
assert.Equal(t, uint64(1), ex1.Id)
curSeq, err := table.LastInsertedSequence(ctx)
assert.NilError(t, err)
assert.Equal(t, curSeq, uint64(1))
ex2 := &testpb.ExampleAutoIncrementTable{X: "bar", Y: 10}
newID, err := table.InsertReturningPKey(ctx, ex2)
assert.NilError(t, err)
assert.Equal(t, uint64(2), ex2.Id)
assert.Equal(t, newID, ex2.Id)
curSeq, err = table.LastInsertedSequence(ctx)
assert.NilError(t, err)
assert.Equal(t, curSeq, uint64(2))
buf := &bytes.Buffer{}
assert.NilError(t, table.ExportJSON(ctx, 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)
// test edge case where we have deleted all entities but we're still exporting the sequence number
assert.NilError(t, table.Delete(ctx, ex1))
assert.NilError(t, table.Delete(ctx, ex2))
buf = &bytes.Buffer{}
assert.NilError(t, table.ExportJSON(ctx, buf))
assert.NilError(t, table.ValidateJSON(bytes.NewReader(buf.Bytes())))
golden.Assert(t, buf.String(), "trivial_auto_inc_export.golden")
store3 := ormtable.WrapContextDefault(testkv.NewSplitMemBackend())
assert.NilError(t, table.ImportJSON(store3, bytes.NewReader(buf.Bytes())))
ex1.Id = 0
assert.NilError(t, table.Insert(store3, ex1))
assert.Equal(t, uint64(3), ex1.Id) // should equal 3 because the sequence number 2 should have been imported from JSON
curSeq, err = table.LastInsertedSequence(store3)
assert.NilError(t, err)
assert.Equal(t, curSeq, uint64(3))
}
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 auto increment primary key")
f, err = os.Open("testdata/bad_auto_inc2.json")
assert.NilError(t, err)
assert.ErrorContains(t, table.ImportJSON(store, f), "invalid auto increment primary key")
}
-193
View File
@@ -1,193 +0,0 @@
package ormtable
import (
"context"
"fmt"
"cosmossdk.io/orm/types/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
// ValidateHooks returns a ValidateHooks instance or nil.
ValidateHooks() ValidateHooks
// WithValidateHooks returns a copy of this backend with the provided validate hooks.
WithValidateHooks(ValidateHooks) Backend
// WriteHooks returns a WriteHooks instance of nil.
WriteHooks() WriteHooks
// WithWriteHooks returns a copy of this backend with the provided write hooks.
WithWriteHooks(WriteHooks) Backend
}
// 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
validateHooks ValidateHooks
writeHooks WriteHooks
}
func (c backend) ValidateHooks() ValidateHooks {
return c.validateHooks
}
func (c backend) WithValidateHooks(hooks ValidateHooks) Backend {
c.validateHooks = hooks
return c
}
func (c backend) WriteHooks() WriteHooks {
return c.writeHooks
}
func (c backend) WithWriteHooks(hooks WriteHooks) Backend {
c.writeHooks = hooks
return c
}
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
}
// 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
// ValidateHooks are optional hooks into ORM insert, update and delete operations.
ValidateHooks ValidateHooks
WriteHooks WriteHooks
}
// 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,
validateHooks: options.ValidateHooks,
writeHooks: options.WriteHooks,
}
}
// BackendResolver resolves a backend from the context or returns an error.
// Callers should type cast the returned ReadBackend to Backend to test whether
// the backend is writable.
type BackendResolver func(context.Context) (ReadBackend, error)
// 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) (ReadBackend, error) {
value := ctx.Value(defaultContextKey)
if value == nil {
return nil, fmt.Errorf("can't resolve backend")
}
backend, ok := value.(ReadBackend)
if !ok {
return nil, fmt.Errorf("expected value of type %T, instead got %T", backend, value)
}
return backend, nil
}
-129
View File
@@ -1,129 +0,0 @@
package ormtable
import (
"cosmossdk.io/orm/types/kv"
)
type batchIndexCommitmentWriter struct {
Backend
commitmentWriter *batchStoreWriter
indexWriter *batchStoreWriter
}
func newBatchIndexCommitmentWriter(store Backend) *batchIndexCommitmentWriter {
return &batchIndexCommitmentWriter{
Backend: store,
commitmentWriter: &batchStoreWriter{
ReadonlyStore: store.CommitmentStoreReader(),
curBuf: make([]*batchWriterEntry, 0, capacity),
},
indexWriter: &batchStoreWriter{
ReadonlyStore: store.IndexStoreReader(),
curBuf: make([]*batchWriterEntry, 0, capacity),
},
}
}
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)
if err != nil {
return err
}
err = flushWrites(w.Backend.IndexStore(), w.indexWriter)
if err != nil {
return err
}
// clear writes
w.Close()
return err
}
func flushWrites(store kv.Store, writer *batchStoreWriter) error {
for _, buf := range writer.prevBufs {
err := flushBuf(store, buf)
if err != nil {
return err
}
}
return flushBuf(store, writer.curBuf)
}
func flushBuf(store kv.Store, writes []*batchWriterEntry) error {
for _, write := range writes {
switch {
case write.hookCall != nil:
write.hookCall()
case !write.delete:
err := store.Set(write.key, write.value)
if err != nil {
return err
}
default:
err := store.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.prevBufs = nil
w.commitmentWriter.curBuf = nil
w.indexWriter.prevBufs = nil
w.indexWriter.curBuf = nil
}
type batchWriterEntry struct {
key, value []byte
delete bool
hookCall func()
}
type batchStoreWriter struct {
kv.ReadonlyStore
prevBufs [][]*batchWriterEntry
curBuf []*batchWriterEntry
}
const capacity = 16
func (b *batchStoreWriter) Set(key, value []byte) error {
b.append(&batchWriterEntry{key: key, value: value})
return nil
}
func (b *batchStoreWriter) Delete(key []byte) error {
b.append(&batchWriterEntry{key: key, delete: true})
return nil
}
func (w *batchIndexCommitmentWriter) enqueueHook(f func()) {
w.indexWriter.append(&batchWriterEntry{hookCall: f})
}
func (b *batchStoreWriter) append(entry *batchWriterEntry) {
if len(b.curBuf) == capacity {
b.prevBufs = append(b.prevBufs, b.curBuf)
b.curBuf = make([]*batchWriterEntry, 0, capacity)
}
b.curBuf = append(b.curBuf, entry)
}
var _ Backend = &batchIndexCommitmentWriter{}
-313
View File
@@ -1,313 +0,0 @@
package ormtable_test
import (
"context"
"fmt"
"testing"
dbm "github.com/cosmos/cosmos-db"
"google.golang.org/protobuf/proto"
"gotest.tools/v3/assert"
"cosmossdk.io/orm/internal/testkv"
"cosmossdk.io/orm/internal/testpb"
"cosmossdk.io/orm/model/ormtable"
"cosmossdk.io/orm/testing/ormtest"
"cosmossdk.io/orm/types/kv"
)
func initBalanceTable(t testing.TB) testpb.BalanceTable {
table, err := ormtable.Build(ormtable.Options{
MessageType: (&testpb.Balance{}).ProtoReflect().Type(),
})
assert.NilError(t, err)
balanceTable, err := testpb.NewBalanceTable(table)
assert.NilError(t, err)
return balanceTable
}
func BenchmarkMemory(b *testing.B) {
bench(b, func(tb testing.TB) ormtable.Backend {
return ormtest.NewMemoryBackend()
})
}
func BenchmarkLevelDB(b *testing.B) {
bench(b, testkv.NewGoLevelDBBackend)
}
func bench(b *testing.B, newBackend func(testing.TB) ormtable.Backend) {
b.Run("insert", func(b *testing.B) {
b.StopTimer()
ctx := ormtable.WrapContextDefault(newBackend(b))
b.StartTimer()
benchInsert(b, ctx)
})
b.Run("update", func(b *testing.B) {
b.StopTimer()
ctx := ormtable.WrapContextDefault(newBackend(b))
benchInsert(b, ctx)
b.StartTimer()
benchUpdate(b, ctx)
})
b.Run("get", func(b *testing.B) {
b.StopTimer()
ctx := ormtable.WrapContextDefault(newBackend(b))
benchInsert(b, ctx)
b.StartTimer()
benchGet(b, ctx)
})
b.Run("delete", func(b *testing.B) {
b.StopTimer()
ctx := ormtable.WrapContextDefault(newBackend(b))
benchInsert(b, ctx)
b.StartTimer()
benchDelete(b, ctx)
})
}
func benchInsert(b *testing.B, ctx context.Context) { //nolint:revive // ignore for benchmark
balanceTable := initBalanceTable(b)
for i := 0; i < b.N; i++ {
assert.NilError(b, balanceTable.Insert(ctx, &testpb.Balance{
Address: fmt.Sprintf("acct%d", i),
Denom: "bar",
Amount: 10,
}))
}
}
func benchUpdate(b *testing.B, ctx context.Context) { //nolint:revive // ignore for benchmark
balanceTable := initBalanceTable(b)
for i := 0; i < b.N; i++ {
assert.NilError(b, balanceTable.Update(ctx, &testpb.Balance{
Address: fmt.Sprintf("acct%d", i),
Denom: "bar",
Amount: 11,
}))
}
}
func benchGet(b *testing.B, ctx context.Context) { //nolint:revive // ignore for benchmark
balanceTable := initBalanceTable(b)
for i := 0; i < b.N; i++ {
balance, err := balanceTable.Get(ctx, fmt.Sprintf("acct%d", i), "bar")
assert.NilError(b, err)
assert.Equal(b, uint64(10), balance.Amount)
}
}
func benchDelete(b *testing.B, ctx context.Context) { //nolint:revive // ignore for benchmark
balanceTable := initBalanceTable(b)
for i := 0; i < b.N; i++ {
assert.NilError(b, balanceTable.Delete(ctx, &testpb.Balance{
Address: fmt.Sprintf("acct%d", i),
Denom: "bar",
}))
}
}
//
// Manually written versions of insert, update, delete and get for testpb.Balance
//
const (
addressDenomPrefix byte = iota
denomAddressPrefix
)
func insertBalance(store kv.Store, balance *testpb.Balance) error {
denom := balance.Denom
balance.Denom = ""
addr := balance.Address
balance.Address = ""
addressDenomKey := []byte{addressDenomPrefix}
addressDenomKey = append(addressDenomKey, []byte(addr)...)
addressDenomKey = append(addressDenomKey, 0x0)
addressDenomKey = append(addressDenomKey, []byte(denom)...)
has, err := store.Has(addressDenomKey)
if err != nil {
return err
}
if has {
return fmt.Errorf("already exists")
}
bz, err := proto.Marshal(balance)
if err != nil {
return err
}
balance.Denom = denom
balance.Address = addr
err = store.Set(addressDenomKey, bz)
if err != nil {
return err
}
// set denom address index
denomAddressKey := []byte{denomAddressPrefix}
denomAddressKey = append(denomAddressKey, []byte(balance.Denom)...)
denomAddressKey = append(denomAddressKey, 0x0)
denomAddressKey = append(denomAddressKey, []byte(balance.Address)...)
err = store.Set(denomAddressKey, []byte{})
if err != nil {
return err
}
return nil
}
func updateBalance(store kv.Store, balance *testpb.Balance) error {
denom := balance.Denom
balance.Denom = ""
addr := balance.Address
balance.Address = ""
bz, err := proto.Marshal(balance)
if err != nil {
return err
}
balance.Denom = denom
balance.Address = addr
addressDenomKey := []byte{addressDenomPrefix}
addressDenomKey = append(addressDenomKey, []byte(addr)...)
addressDenomKey = append(addressDenomKey, 0x0)
addressDenomKey = append(addressDenomKey, []byte(denom)...)
return store.Set(addressDenomKey, bz)
}
func deleteBalance(store kv.Store, balance *testpb.Balance) error {
denom := balance.Denom
addr := balance.Address
addressDenomKey := []byte{addressDenomPrefix}
addressDenomKey = append(addressDenomKey, []byte(addr)...)
addressDenomKey = append(addressDenomKey, 0x0)
addressDenomKey = append(addressDenomKey, []byte(denom)...)
err := store.Delete(addressDenomKey)
if err != nil {
return err
}
denomAddressKey := []byte{denomAddressPrefix}
denomAddressKey = append(denomAddressKey, []byte(balance.Denom)...)
denomAddressKey = append(denomAddressKey, 0x0)
denomAddressKey = append(denomAddressKey, []byte(balance.Address)...)
return store.Delete(denomAddressKey)
}
func getBalance(store kv.Store, address, denom string) (*testpb.Balance, error) {
addressDenomKey := []byte{addressDenomPrefix}
addressDenomKey = append(addressDenomKey, []byte(address)...)
addressDenomKey = append(addressDenomKey, 0x0)
addressDenomKey = append(addressDenomKey, []byte(denom)...)
bz, err := store.Get(addressDenomKey)
if err != nil {
return nil, err
}
if bz == nil {
return nil, fmt.Errorf("not found")
}
balance := testpb.Balance{}
err = proto.Unmarshal(bz, &balance)
if err != nil {
return nil, err
}
balance.Address = address
balance.Denom = denom
return &balance, nil
}
func BenchmarkManualInsertMemory(b *testing.B) {
benchManual(b, func() (dbm.DB, error) {
return dbm.NewMemDB(), nil
})
}
func BenchmarkManualInsertLevelDB(b *testing.B) {
benchManual(b, func() (dbm.DB, error) {
return dbm.NewGoLevelDB("test", b.TempDir(), nil)
})
}
func benchManual(b *testing.B, newStore func() (dbm.DB, error)) {
b.Run("insert", func(b *testing.B) {
b.StopTimer()
store, err := newStore()
assert.NilError(b, err)
b.StartTimer()
benchManualInsert(b, store)
})
b.Run("update", func(b *testing.B) {
b.StopTimer()
store, err := newStore()
assert.NilError(b, err)
benchManualInsert(b, store)
b.StartTimer()
benchManualUpdate(b, store)
})
b.Run("get", func(b *testing.B) {
b.StopTimer()
store, err := newStore()
assert.NilError(b, err)
benchManualInsert(b, store)
b.StartTimer()
benchManualGet(b, store)
})
b.Run("delete", func(b *testing.B) {
b.StopTimer()
store, err := newStore()
assert.NilError(b, err)
benchManualInsert(b, store)
b.StartTimer()
benchManualDelete(b, store)
})
}
func benchManualInsert(b *testing.B, store kv.Store) {
for i := 0; i < b.N; i++ {
assert.NilError(b, insertBalance(store, &testpb.Balance{
Address: fmt.Sprintf("acct%d", i),
Denom: "bar",
Amount: 10,
}))
}
}
func benchManualUpdate(b *testing.B, store kv.Store) {
for i := 0; i < b.N; i++ {
assert.NilError(b, updateBalance(store, &testpb.Balance{
Address: fmt.Sprintf("acct%d", i),
Denom: "bar",
Amount: 11,
}))
}
}
func benchManualDelete(b *testing.B, store kv.Store) {
for i := 0; i < b.N; i++ {
assert.NilError(b, deleteBalance(store, &testpb.Balance{
Address: fmt.Sprintf("acct%d", i),
Denom: "bar",
}))
}
}
func benchManualGet(b *testing.B, store kv.Store) {
for i := 0; i < b.N; i++ {
balance, err := getBalance(store, fmt.Sprintf("acct%d", i), "bar")
assert.NilError(b, err)
assert.Equal(b, uint64(10), balance.Amount)
}
}
-40
View File
@@ -1,40 +0,0 @@
package ormtable
import (
"context"
"google.golang.org/protobuf/proto"
)
// ValidateHooks defines an interface for a table hooks which can intercept
// insert, update and delete operations and possibly return an error.
type ValidateHooks interface {
// ValidateInsert is called before the message is inserted.
// If error is not nil the insertion will fail.
ValidateInsert(context.Context, proto.Message) error
// ValidateUpdate is called before the existing message is updated with the new one.
// If error is not nil the update will fail.
ValidateUpdate(ctx context.Context, existing, new proto.Message) error
// ValidateDelete is called before the message is deleted.
// If error is not nil the deletion will fail.
ValidateDelete(context.Context, proto.Message) error
}
// WriteHooks defines an interface for listening to insertions, updates and
// deletes after they are written to the store. This can be used for indexing
// state in another database. Indexers should make sure they coordinate with
// transactions at live at the next level above the ORM as they write hooks
// may be called but the enclosing transaction may still fail. The context
// is provided in each method to help coordinate this.
type WriteHooks interface {
// OnInsert is called after an message is inserted into the store.
OnInsert(context.Context, proto.Message)
// OnUpdate is called after the entity is updated in the store.
OnUpdate(ctx context.Context, existing, new proto.Message)
// OnDelete is called after the entity is deleted from the store.
OnDelete(context.Context, proto.Message)
}
-264
View File
@@ -1,264 +0,0 @@
package ormtable
import (
"google.golang.org/protobuf/proto"
"google.golang.org/protobuf/reflect/protoreflect"
queryv1beta1 "cosmossdk.io/api/cosmos/base/query/v1beta1"
"cosmossdk.io/orm/encoding/encodeutil"
"cosmossdk.io/orm/encoding/ormkv"
"cosmossdk.io/orm/internal/listinternal"
"cosmossdk.io/orm/model/ormlist"
"cosmossdk.io/orm/types/kv"
)
// Iterator defines the interface for iterating over indexes.
//
// WARNING: it is generally unsafe to mutate a table while iterating over it.
// Instead you should do reads and writes separately, or use a helper
// function like DeleteBy which does this efficiently.
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
// PageResponse returns a non-nil page response after Next() returns false
// if pagination was requested in list options.
PageResponse() *queryv1beta1.PageResponse
// 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 prefixIterator(iteratorStore kv.ReadonlyStore, backend ReadBackend, index concreteIndex, codec *ormkv.KeyCodec, prefix []interface{}, opts []listinternal.Option) (Iterator, error) {
options := &listinternal.Options{}
listinternal.ApplyOptions(options, opts)
if err := options.Validate(); err != nil {
return nil, err
}
var prefixBz []byte
prefixBz, err := codec.EncodeKey(encodeutil.ValuesOf(prefix...))
if err != nil {
return nil, err
}
var res Iterator
if !options.Reverse {
var start []byte
if len(options.Cursor) != 0 {
// must start right after cursor
start = append(options.Cursor, 0x0)
} else {
start = prefixBz
}
end := prefixEndBytes(prefixBz)
it, err := iteratorStore.Iterator(start, end)
if err != nil {
return nil, err
}
res = &indexIterator{
index: index,
store: backend,
iterator: it,
started: false,
}
} else {
var end []byte
if len(options.Cursor) != 0 {
// end bytes is already exclusive by default
end = options.Cursor
} else {
end = prefixEndBytes(prefixBz)
}
it, err := iteratorStore.ReverseIterator(prefixBz, end)
if err != nil {
return nil, err
}
res = &indexIterator{
index: index,
store: backend,
iterator: it,
started: false,
}
}
return applyCommonIteratorOptions(res, options)
}
func rangeIterator(iteratorStore kv.ReadonlyStore, reader ReadBackend, index concreteIndex, codec *ormkv.KeyCodec, start, end []interface{}, opts []listinternal.Option) (Iterator, error) {
options := &listinternal.Options{}
listinternal.ApplyOptions(options, opts)
if err := options.Validate(); err != nil {
return nil, err
}
startValues := encodeutil.ValuesOf(start...)
endValues := encodeutil.ValuesOf(end...)
err := codec.CheckValidRangeIterationKeys(startValues, endValues)
if err != nil {
return nil, err
}
startBz, err := codec.EncodeKey(startValues)
if err != nil {
return nil, err
}
endBz, err := codec.EncodeKey(endValues)
if err != nil {
return nil, err
}
// 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
fullEndKey := len(codec.GetFieldNames()) == len(end)
var res Iterator
if !options.Reverse {
if len(options.Cursor) != 0 {
startBz = append(options.Cursor, 0)
}
if fullEndKey {
endBz = inclusiveEndBytes(endBz)
} else {
endBz = prefixEndBytes(endBz)
}
it, err := iteratorStore.Iterator(startBz, endBz)
if err != nil {
return nil, err
}
res = &indexIterator{
index: index,
store: reader,
iterator: it,
started: false,
}
} else {
if len(options.Cursor) != 0 {
endBz = options.Cursor
} else {
if fullEndKey {
endBz = inclusiveEndBytes(endBz)
} else {
endBz = prefixEndBytes(endBz)
}
}
it, err := iteratorStore.ReverseIterator(startBz, endBz)
if err != nil {
return nil, err
}
res = &indexIterator{
index: index,
store: reader,
iterator: it,
started: false,
}
}
return applyCommonIteratorOptions(res, options)
}
func applyCommonIteratorOptions(iterator Iterator, options *listinternal.Options) (Iterator, error) {
if options.Filter != nil {
iterator = &filterIterator{Iterator: iterator, filter: options.Filter}
}
if options.CountTotal || options.Limit != 0 || options.Offset != 0 || options.DefaultLimit != 0 {
iterator = paginate(iterator, options)
}
return iterator, nil
}
type indexIterator struct {
index concreteIndex
store ReadBackend
iterator kv.Iterator
indexValues []protoreflect.Value
primaryKey []protoreflect.Value
value []byte
started bool
}
func (i *indexIterator) PageResponse() *queryv1beta1.PageResponse {
return nil
}
func (i *indexIterator) Next() bool {
if !i.started {
i.started = true
} else {
i.iterator.Next()
i.indexValues = nil
}
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{}
-761
View File
@@ -1,761 +0,0 @@
package ormtable_test
import (
"bytes"
"context"
"fmt"
"sort"
"strings"
"testing"
dbm "github.com/cosmos/cosmos-db"
"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 "cosmossdk.io/api/cosmos/base/query/v1beta1"
sdkerrors "cosmossdk.io/errors"
"cosmossdk.io/orm/encoding/ormkv"
"cosmossdk.io/orm/internal/testkv"
"cosmossdk.io/orm/internal/testpb"
"cosmossdk.io/orm/internal/testutil"
"cosmossdk.io/orm/model/ormlist"
"cosmossdk.io/orm/model/ormtable"
"cosmossdk.io/orm/types/kv"
"cosmossdk.io/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())
}
// isolated test for bug - https://github.com/cosmos/cosmos-sdk/issues/11431
func TestPaginationLimitCountTotal(t *testing.T) {
table, err := ormtable.Build(ormtable.Options{
MessageType: (&testpb.ExampleTable{}).ProtoReflect().Type(),
})
assert.NilError(t, err)
backend := testkv.NewSplitMemBackend()
ctx := ormtable.WrapContextDefault(backend)
store, err := testpb.NewExampleTableTable(table)
assert.NilError(t, err)
assert.NilError(t, store.Insert(ctx, &testpb.ExampleTable{U32: 4, I64: 2, Str: "co"}))
assert.NilError(t, store.Insert(ctx, &testpb.ExampleTable{U32: 5, I64: 2, Str: "sm"}))
assert.NilError(t, store.Insert(ctx, &testpb.ExampleTable{U32: 6, I64: 2, Str: "os"}))
it, err := store.List(ctx, &testpb.ExampleTablePrimaryKey{}, ormlist.Paginate(&queryv1beta1.PageRequest{Limit: 3, CountTotal: true}))
assert.NilError(t, err)
assert.Check(t, it.Next())
it, err = store.List(ctx, &testpb.ExampleTablePrimaryKey{}, ormlist.Paginate(&queryv1beta1.PageRequest{Limit: 4, CountTotal: true}))
assert.NilError(t, err)
assert.Check(t, it.Next())
it, err = store.List(ctx, &testpb.ExampleTablePrimaryKey{}, ormlist.Paginate(&queryv1beta1.PageRequest{Limit: 1, CountTotal: true}))
assert.NilError(t, err)
for it.Next() {
}
pr := it.PageResponse()
assert.Check(t, pr != nil)
assert.Equal(t, uint64(3), pr.Total)
}
// 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.NilError(t, err)
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)
store, err := testpb.NewExampleTableTable(table)
assert.NilError(t, err)
// 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 = store.Insert(ctx, data[0])
assert.NilError(t, err)
// trivial prefix query has one record
it, err := store.List(ctx, testpb.ExampleTablePrimaryKey{})
assert.NilError(t, err)
assertIteratorItems(it, 0)
// insert one record
err = store.Insert(ctx, data[1])
assert.NilError(t, err)
// trivial prefix query has two records
it, err = store.List(ctx, testpb.ExampleTablePrimaryKey{})
assert.NilError(t, err)
assertIteratorItems(it, 0, 1)
// insert the other records
assert.NilError(t, err)
for i := 2; i < len(data); i++ {
err = store.Insert(ctx, data[i])
assert.NilError(t, err)
}
// let's do a prefix query on the primary key
it, err = store.List(ctx, testpb.ExampleTablePrimaryKey{}.WithU32(8))
assert.NilError(t, err)
assertIteratorItems(it, 7, 8, 9)
// let's try a reverse prefix query
it, err = store.List(ctx, testpb.ExampleTablePrimaryKey{}.WithU32(4), ormlist.Reverse())
assert.NilError(t, err)
defer it.Close()
assertIteratorItems(it, 2, 1, 0)
// let's try a range query
it, err = store.ListRange(ctx,
testpb.ExampleTablePrimaryKey{}.WithU32I64(4, -1),
testpb.ExampleTablePrimaryKey{}.WithU32(7),
)
assert.NilError(t, err)
defer it.Close()
assertIteratorItems(it, 2, 3, 4, 5, 6)
// and another range query
it, err = store.ListRange(ctx,
testpb.ExampleTablePrimaryKey{}.WithU32I64(5, -3),
testpb.ExampleTablePrimaryKey{}.WithU32I64Str(8, 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 = store.ListRange(ctx,
testpb.ExampleTableStrU32IndexKey{}.WithStr("abc"),
testpb.ExampleTableStrU32IndexKey{}.WithStr("abd"),
ormlist.Reverse(),
)
assert.NilError(t, err)
assertIteratorItems(it, 9, 3, 1, 8, 7, 2, 0)
// another prefix query forwards
it, err = store.List(ctx,
testpb.ExampleTableStrU32IndexKey{}.WithStrU32("abe", 7),
)
assert.NilError(t, err)
assertIteratorItems(it, 5, 6)
// and backwards
it, err = store.List(ctx,
testpb.ExampleTableStrU32IndexKey{}.WithStrU32("abc", 4),
ormlist.Reverse(),
)
assert.NilError(t, err)
assertIteratorItems(it, 2, 0)
// try filtering
it, err = store.List(ctx, testpb.ExampleTablePrimaryKey{}, ormlist.Filter(func(message proto.Message) bool {
ex := message.(*testpb.ExampleTable)
return ex.U64 != 10
}))
assert.NilError(t, err)
assertIteratorItems(it, 0, 1, 2, 3, 4, 6, 7, 8)
// try a cursor
it, err = store.List(ctx, testpb.ExampleTablePrimaryKey{})
assert.NilError(t, err)
assert.Assert(t, it.Next())
assert.Assert(t, it.Next())
it, err = store.List(ctx, testpb.ExampleTablePrimaryKey{}, ormlist.Cursor(it.Cursor()))
assert.NilError(t, err)
assertIteratorItems(it, 2, 3, 4, 5, 6, 7, 8, 9)
// try an unique index
found, err := store.HasByU64Str(ctx, 12, "abc")
assert.NilError(t, err)
assert.Assert(t, found)
a, err := store.GetByU64Str(ctx, 12, "abc")
assert.NilError(t, err)
assert.DeepEqual(t, data[8], a, protocmp.Transform())
// let's try paginating some stuff
it, err = store.List(ctx, testpb.ExampleTablePrimaryKey{}, ormlist.Paginate(&queryv1beta1.PageRequest{
Limit: 4,
CountTotal: true,
}))
assert.NilError(t, err)
assertIteratorItems(it, 0, 1, 2, 3)
res := it.PageResponse()
assert.Assert(t, res != nil)
assert.Equal(t, uint64(10), res.Total)
assert.Assert(t, res.NextKey != nil)
// let's use a default limit
it, err = store.List(ctx, testpb.ExampleTablePrimaryKey{},
ormlist.DefaultLimit(4),
ormlist.Paginate(&queryv1beta1.PageRequest{
CountTotal: true,
}))
assert.NilError(t, err)
assertIteratorItems(it, 0, 1, 2, 3)
res = it.PageResponse()
assert.Assert(t, res != nil)
assert.Equal(t, uint64(10), res.Total)
assert.Assert(t, res.NextKey != nil)
// read another page
it, err = store.List(ctx, testpb.ExampleTablePrimaryKey{}, ormlist.Paginate(&queryv1beta1.PageRequest{
Key: res.NextKey,
Limit: 4,
}))
assert.NilError(t, err)
assertIteratorItems(it, 4, 5, 6, 7)
res = it.PageResponse()
assert.Assert(t, res != nil)
assert.Assert(t, res.NextKey != nil)
// and the last page
it, err = store.List(ctx, testpb.ExampleTablePrimaryKey{}, ormlist.Paginate(&queryv1beta1.PageRequest{
Key: res.NextKey,
Limit: 4,
}))
assert.NilError(t, err)
assertIteratorItems(it, 8, 9)
res = it.PageResponse()
assert.Assert(t, res != nil)
assert.Assert(t, res.NextKey == nil)
// let's go backwards
it, err = store.List(ctx, testpb.ExampleTablePrimaryKey{}, ormlist.Paginate(&queryv1beta1.PageRequest{
Limit: 2,
CountTotal: true,
Reverse: true,
}))
assert.NilError(t, err)
assertIteratorItems(it, 9, 8)
res = it.PageResponse()
assert.Assert(t, res != nil)
assert.Assert(t, res.NextKey != nil)
assert.Equal(t, uint64(10), res.Total)
// a bit more
it, err = store.List(ctx, testpb.ExampleTablePrimaryKey{}, ormlist.Paginate(&queryv1beta1.PageRequest{
Key: res.NextKey,
Limit: 2,
Reverse: true,
}))
assert.NilError(t, err)
assertIteratorItems(it, 7, 6)
res = it.PageResponse()
assert.Assert(t, res != nil)
assert.Assert(t, res.NextKey != nil)
// range query
it, err = store.ListRange(ctx,
testpb.ExampleTablePrimaryKey{}.WithU32I64Str(4, -1, "abc"),
testpb.ExampleTablePrimaryKey{}.WithU32I64Str(7, -2, "abe"),
ormlist.Paginate(&queryv1beta1.PageRequest{
Limit: 10,
}))
assert.NilError(t, err)
assertIteratorItems(it, 2, 3, 4, 5)
res = it.PageResponse()
assert.Assert(t, res != nil)
assert.Assert(t, res.NextKey == nil)
// let's try an offset
it, err = store.List(ctx, testpb.ExampleTablePrimaryKey{}, ormlist.Paginate(&queryv1beta1.PageRequest{
Limit: 2,
CountTotal: true,
Offset: 3,
}))
assert.NilError(t, err)
assertIteratorItems(it, 3, 4)
res = it.PageResponse()
assert.Assert(t, res != nil)
assert.Assert(t, res.NextKey != nil)
assert.Equal(t, uint64(10), res.Total)
// and reverse
it, err = store.List(ctx, testpb.ExampleTablePrimaryKey{}, ormlist.Paginate(&queryv1beta1.PageRequest{
Limit: 3,
CountTotal: true,
Offset: 5,
Reverse: true,
}))
assert.NilError(t, err)
assertIteratorItems(it, 4, 3, 2)
res = it.PageResponse()
assert.Assert(t, res != nil)
assert.Assert(t, res.NextKey != nil)
assert.Equal(t, uint64(10), res.Total)
// now an offset that's slightly too big
it, err = store.List(ctx, testpb.ExampleTablePrimaryKey{}, ormlist.Paginate(&queryv1beta1.PageRequest{
Limit: 1,
CountTotal: true,
Offset: 10,
}))
assert.NilError(t, err)
assert.Assert(t, !it.Next())
res = it.PageResponse()
assert.Assert(t, res != nil)
assert.Assert(t, res.NextKey == nil)
assert.Equal(t, uint64(10), res.Total)
// now let's update some things
for i := 0; i < 5; i++ {
data[i].U64 *= 2
data[i].Bz = []byte(data[i].Str)
err = store.Update(ctx, data[i])
assert.NilError(t, err)
}
it, err = store.List(ctx, testpb.ExampleTablePrimaryKey{})
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 = store.Save(ctx, data[10])
assert.NilError(t, err)
a, err = store.Get(ctx, 9, 0, "")
assert.NilError(t, err)
assert.Assert(t, a != nil)
assert.DeepEqual(t, data[10], a, protocmp.Transform())
// and update it
data[10].B = true
assert.NilError(t, table.Save(ctx, data[10]))
a, err = store.Get(ctx, 9, 0, "")
assert.NilError(t, err)
assert.Assert(t, a != nil)
assert.DeepEqual(t, data[10], a, protocmp.Transform())
// and iterate
it, err = store.List(ctx, testpb.ExampleTablePrimaryKey{})
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
err = store.DeleteBy(ctx, testpb.ExampleTableU32I64StrIndexKey{}.WithU32I64Str(7, -2, "abe"))
assert.NilError(t, err)
// it should be gone
found, err = store.Has(ctx, 7, -2, "abe")
assert.NilError(t, err)
assert.Assert(t, !found)
// and missing from the iterator
it, err = store.List(ctx, testpb.ExampleTablePrimaryKey{})
assert.NilError(t, err)
assertIteratorItems(it, 0, 1, 2, 3, 4, 6, 7, 8, 9, 10)
// let's do a batch delete
// first iterate over the items we'll delete to check that iterator
it, err = store.List(ctx, testpb.ExampleTableStrU32IndexKey{}.WithStr("abd"))
assert.NilError(t, err)
assertIteratorItems(it, 1, 3, 9)
// now delete them
assert.NilError(t, store.DeleteBy(ctx, testpb.ExampleTableStrU32IndexKey{}.WithStr("abd")))
it, err = store.List(ctx, testpb.ExampleTablePrimaryKey{})
assert.NilError(t, err)
assertIteratorItems(it, 0, 2, 4, 6, 7, 8, 10)
// Let's do a range delete
assert.NilError(t, store.DeleteRange(ctx,
testpb.ExampleTableStrU32IndexKey{}.WithStrU32("abc", 8),
testpb.ExampleTableStrU32IndexKey{}.WithStrU32("abe", 5),
))
it, err = store.List(ctx, testpb.ExampleTablePrimaryKey{})
assert.NilError(t, err)
assertIteratorItems(it, 0, 2, 6, 10)
// Let's delete something directly
assert.NilError(t, store.Delete(ctx, data[0]))
it, err = store.List(ctx, testpb.ExampleTablePrimaryKey{})
assert.NilError(t, err)
assertIteratorItems(it, 2, 6, 10)
}
func TestRandomTableData(t *testing.T) {
testTable(t, TableDataGen(testutil.GenA, 100).Example())
}
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.List(model.context, nil)
assert.NilError(t, err)
checkIteratorAgainstSlice(t, it, model.data)
it, err = model.index.List(model.context, nil, 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")
j := rapid.IntRange(i+1, len(model.data)-1).Draw(t, "j")
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.ListRange(model.context, startVals, endVals)
assert.NilError(t, err)
checkIteratorAgainstSlice(t, it, model.data[i:j+1])
it, err = model.index.ListRange(model.context, startVals, 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.List(model.context, nil)
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[T proto.Message](elemGen *rapid.Generator[T], n int) *rapid.Generator[*TableData] {
return rapid.Custom(func(t *rapid.T) *TableData {
prefix := rapid.SliceOfN(rapid.Byte(), 0, 5).Draw(t, "prefix")
message := elemGen.Draw(t, "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))
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) {
m.data[i], m.data[j] = m.data[j], m.data[i]
}
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()
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) { //nolint:revive // ignore long function name
it, err := table.List(ctx, nil)
assert.NilError(t, err)
it2, err := table.List(ctx2, nil)
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
}
func TestReadonly(t *testing.T) {
table, err := ormtable.Build(ormtable.Options{
MessageType: (&testpb.ExampleTable{}).ProtoReflect().Type(),
})
assert.NilError(t, err)
readBackend := ormtable.NewReadBackend(ormtable.ReadBackendOptions{
CommitmentStoreReader: dbm.NewMemDB(),
IndexStoreReader: dbm.NewMemDB(),
})
ctx := ormtable.WrapContextDefault(readBackend)
assert.ErrorIs(t, ormerrors.ReadOnly, table.Insert(ctx, &testpb.ExampleTable{}))
}
func TestInsertReturningFieldName(t *testing.T) {
table, err := ormtable.Build(ormtable.Options{
MessageType: (&testpb.ExampleAutoIncFieldName{}).ProtoReflect().Type(),
})
assert.NilError(t, err)
backend := testkv.NewSplitMemBackend()
ctx := ormtable.WrapContextDefault(backend)
store, err := testpb.NewExampleAutoIncFieldNameTable(table)
assert.NilError(t, err)
foo, err := store.InsertReturningFoo(ctx, &testpb.ExampleAutoIncFieldName{
Bar: 45,
})
assert.NilError(t, err)
assert.Equal(t, uint64(1), foo)
}
-68
View File
@@ -1,68 +0,0 @@
package orm
import (
"fmt"
"google.golang.org/protobuf/proto"
"google.golang.org/protobuf/reflect/protodesc"
"google.golang.org/protobuf/reflect/protoregistry"
appv1alpha1 "cosmossdk.io/api/cosmos/app/v1alpha1"
modulev1alpha1 "cosmossdk.io/api/cosmos/orm/module/v1alpha1"
ormv1alpha1 "cosmossdk.io/api/cosmos/orm/v1alpha1"
"cosmossdk.io/core/appmodule"
"cosmossdk.io/core/store"
"cosmossdk.io/depinject"
"cosmossdk.io/orm/model/ormdb"
"cosmossdk.io/orm/model/ormtable"
)
func init() {
appmodule.Register(&modulev1alpha1.Module{},
appmodule.Provide(ProvideModuleDB),
)
}
// ModuleDBInputs are the inputs to ProvideModuleDB. NOTE: this is intended to be used by depinject.
type ModuleDBInputs struct {
depinject.In
AppConfig *appv1alpha1.Config
KVStoreService store.KVStoreService
MemoryStoreService store.MemoryStoreService `optional:"true"`
TransientStoreService store.TransientStoreService `optional:"true"`
TypeResolver ormtable.TypeResolver `optional:"true"`
FileResolver protodesc.Resolver `optional:"true"`
}
// ProvideModuleDB provides an ORM ModuleDB scoped to a module. NOTE: this is intended to be used by depinject.
func ProvideModuleDB(moduleKey depinject.ModuleKey, inputs ModuleDBInputs) (ormdb.ModuleDB, error) {
for _, module := range inputs.AppConfig.Modules {
if module.Name == moduleKey.Name() {
typeResolver := inputs.TypeResolver
if typeResolver == nil {
typeResolver = protoregistry.GlobalTypes
}
modTyp, err := typeResolver.FindMessageByURL(module.Config.TypeUrl)
if err != nil {
return nil, err
}
modSchema := proto.GetExtension(modTyp.Descriptor().Options(), ormv1alpha1.E_ModuleSchema).(*ormv1alpha1.ModuleSchemaDescriptor)
if modSchema == nil {
return nil, fmt.Errorf("no schema for module %s", moduleKey.Name())
}
return ormdb.NewModuleDB(modSchema, ormdb.ModuleDBOptions{
TypeResolver: inputs.TypeResolver,
FileResolver: inputs.FileResolver,
KVStoreService: inputs.KVStoreService,
MemoryStoreService: inputs.MemoryStoreService,
TransientStoreService: inputs.TransientStoreService,
})
}
}
return nil, fmt.Errorf("unable to find config for module %s", moduleKey.Name())
}
-137
View File
@@ -1,137 +0,0 @@
// Code generated by MockGen. DO NOT EDIT.
// Source: orm/model/ormtable/hooks.go
// Package ormmocks is a generated GoMock package.
package ormmocks
import (
context "context"
reflect "reflect"
gomock "github.com/golang/mock/gomock"
proto "google.golang.org/protobuf/proto"
)
// MockValidateHooks is a mock of ValidateHooks interface.
type MockValidateHooks struct {
ctrl *gomock.Controller
recorder *MockValidateHooksMockRecorder
}
// MockValidateHooksMockRecorder is the mock recorder for MockValidateHooks.
type MockValidateHooksMockRecorder struct {
mock *MockValidateHooks
}
// NewMockValidateHooks creates a new mock instance.
func NewMockValidateHooks(ctrl *gomock.Controller) *MockValidateHooks {
mock := &MockValidateHooks{ctrl: ctrl}
mock.recorder = &MockValidateHooksMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use.
func (m *MockValidateHooks) EXPECT() *MockValidateHooksMockRecorder {
return m.recorder
}
// ValidateDelete mocks base method.
func (m *MockValidateHooks) ValidateDelete(arg0 context.Context, arg1 proto.Message) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "ValidateDelete", arg0, arg1)
ret0, _ := ret[0].(error)
return ret0
}
// ValidateDelete indicates an expected call of ValidateDelete.
func (mr *MockValidateHooksMockRecorder) ValidateDelete(arg0, arg1 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ValidateDelete", reflect.TypeOf((*MockValidateHooks)(nil).ValidateDelete), arg0, arg1)
}
// ValidateInsert mocks base method.
func (m *MockValidateHooks) ValidateInsert(arg0 context.Context, arg1 proto.Message) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "ValidateInsert", arg0, arg1)
ret0, _ := ret[0].(error)
return ret0
}
// ValidateInsert indicates an expected call of ValidateInsert.
func (mr *MockValidateHooksMockRecorder) ValidateInsert(arg0, arg1 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ValidateInsert", reflect.TypeOf((*MockValidateHooks)(nil).ValidateInsert), arg0, arg1)
}
// ValidateUpdate mocks base method.
func (m *MockValidateHooks) ValidateUpdate(ctx context.Context, existing, new proto.Message) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "ValidateUpdate", ctx, existing, new)
ret0, _ := ret[0].(error)
return ret0
}
// ValidateUpdate indicates an expected call of ValidateUpdate.
func (mr *MockValidateHooksMockRecorder) ValidateUpdate(ctx, existing, new interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ValidateUpdate", reflect.TypeOf((*MockValidateHooks)(nil).ValidateUpdate), ctx, existing, new)
}
// MockWriteHooks is a mock of WriteHooks interface.
type MockWriteHooks struct {
ctrl *gomock.Controller
recorder *MockWriteHooksMockRecorder
}
// MockWriteHooksMockRecorder is the mock recorder for MockWriteHooks.
type MockWriteHooksMockRecorder struct {
mock *MockWriteHooks
}
// NewMockWriteHooks creates a new mock instance.
func NewMockWriteHooks(ctrl *gomock.Controller) *MockWriteHooks {
mock := &MockWriteHooks{ctrl: ctrl}
mock.recorder = &MockWriteHooksMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use.
func (m *MockWriteHooks) EXPECT() *MockWriteHooksMockRecorder {
return m.recorder
}
// OnDelete mocks base method.
func (m *MockWriteHooks) OnDelete(arg0 context.Context, arg1 proto.Message) {
m.ctrl.T.Helper()
m.ctrl.Call(m, "OnDelete", arg0, arg1)
}
// OnDelete indicates an expected call of OnDelete.
func (mr *MockWriteHooksMockRecorder) OnDelete(arg0, arg1 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "OnDelete", reflect.TypeOf((*MockWriteHooks)(nil).OnDelete), arg0, arg1)
}
// OnInsert mocks base method.
func (m *MockWriteHooks) OnInsert(arg0 context.Context, arg1 proto.Message) {
m.ctrl.T.Helper()
m.ctrl.Call(m, "OnInsert", arg0, arg1)
}
// OnInsert indicates an expected call of OnInsert.
func (mr *MockWriteHooksMockRecorder) OnInsert(arg0, arg1 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "OnInsert", reflect.TypeOf((*MockWriteHooks)(nil).OnInsert), arg0, arg1)
}
// OnUpdate mocks base method.
func (m *MockWriteHooks) OnUpdate(ctx context.Context, existing, new proto.Message) {
m.ctrl.T.Helper()
m.ctrl.Call(m, "OnUpdate", ctx, existing, new)
}
// OnUpdate indicates an expected call of OnUpdate.
func (mr *MockWriteHooksMockRecorder) OnUpdate(ctx, existing, new interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "OnUpdate", reflect.TypeOf((*MockWriteHooks)(nil).OnUpdate), ctx, existing, new)
}
-29
View File
@@ -1,29 +0,0 @@
package ormmocks
import (
"github.com/golang/mock/gomock"
"github.com/google/go-cmp/cmp"
"google.golang.org/protobuf/proto"
"google.golang.org/protobuf/testing/protocmp"
)
// Code adapted from MIT-licensed https://github.com/budougumi0617/cmpmock/blob/master/diffmatcher.go
// Eq returns a gomock.Matcher which uses go-cmp to compare protobuf messages.
func Eq(message proto.Message) gomock.Matcher {
return &protoEq{message: message}
}
type protoEq struct {
message interface{}
diff string
}
func (p protoEq) Matches(x interface{}) bool {
p.diff = cmp.Diff(x, p.message, protocmp.Transform())
return len(p.diff) == 0
}
func (p protoEq) String() string {
return p.diff
}
-50
View File
@@ -1,50 +0,0 @@
// 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/cosmos/cosmos-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/cosmos/cosmos-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
}
-49
View File
@@ -1,49 +0,0 @@
package ormerrors
import (
"google.golang.org/grpc/codes"
"cosmossdk.io/errors"
)
var codespace = "orm"
// IsNotFound returns true if the error indicates that the record was not found.
func IsNotFound(err error) bool {
return errors.IsOf(err, NotFound)
}
// nolint: revive // avoid break API
var (
InvalidTableId = errors.New(codespace, 1, "invalid or missing table or single id, need a non-zero value")
MissingPrimaryKey = errors.New(codespace, 2, "table is missing primary key")
InvalidKeyFieldsDefinition = errors.New(codespace, 3, "invalid field definition for key")
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 value >= 0 and < 32768")
DuplicateIndexId = errors.New(codespace, 8, "duplicate index id")
PrimaryKeyConstraintViolation = errors.New(codespace, 9, "object with primary key already exists")
PrimaryKeyInvalidOnUpdate = errors.New(codespace, 11, "can't update object with missing or invalid primary key")
AutoIncrementKeyAlreadySet = errors.New(codespace, 12, "can't create with auto-increment primary key already set")
CantFindIndex = errors.New(codespace, 13, "can't find index")
UnexpectedDecodePrefix = errors.New(codespace, 14, "unexpected prefix while trying to decode an entry")
UnsupportedOperation = errors.New(codespace, 16, "unsupported operation")
BadDecodeEntry = errors.New(codespace, 17, "bad decode entry")
IndexOutOfBounds = errors.New(codespace, 18, "index out of bounds")
InvalidListOptions = errors.New(codespace, 19, "invalid list options")
InvalidKeyField = errors.New(codespace, 20, "invalid key field")
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.RegisterWithGRPCCode(codespace, 24, codes.FailedPrecondition, "unique key violation")
InvalidTableDefinition = errors.New(codespace, 25, "invalid table definition")
InvalidFileDescriptorID = errors.New(codespace, 26, "invalid file descriptor ID")
TableNotFound = errors.New(codespace, 27, "table not found")
JSONValidationError = errors.New(codespace, 28, "invalid JSON")
NotFound = errors.RegisterWithGRPCCode(codespace, 29, codes.NotFound, "not found")
ReadOnly = errors.New(codespace, 30, "database is read-only")
AlreadyExists = errors.RegisterWithGRPCCode(codespace, 31, codes.AlreadyExists, "already exists")
ConstraintViolation = errors.RegisterWithGRPCCode(codespace, 32, codes.FailedPrecondition, "failed precondition")
NoTableDescriptor = errors.New(codespace, 33, "no table descriptor found")
)
-3
View File
@@ -15,6 +15,3 @@ plugins:
- name: go-grpc
out: ../api
opt: paths=source_relative
- name: go-cosmos-orm
out: ../api
opt: paths=source_relative
@@ -1,14 +0,0 @@
syntax = "proto3";
package cosmos.orm.module.v1alpha1;
import "cosmos/app/v1alpha1/module.proto";
// Module defines the ORM module which adds providers to the app container for
// ORM ModuleDB's and in the future will automatically register query
// services for modules that use the ORM.
message Module {
option (cosmos.app.v1alpha1.module) = {
go_import: "cosmossdk.io/orm"
};
}
-131
View File
@@ -1,131 +0,0 @@
syntax = "proto3";
package cosmos.orm.query.v1alpha1;
import "google/protobuf/timestamp.proto";
import "google/protobuf/duration.proto";
import "google/protobuf/any.proto";
import "cosmos/base/query/v1beta1/pagination.proto";
// Query is a generic gRPC service for querying ORM data.
service Query {
// Get queries an ORM table against an unique index.
rpc Get(GetRequest) returns (GetResponse);
// List queries an ORM table against an index.
rpc List(ListRequest) returns (ListResponse);
}
// GetRequest is the Query/Get request type.
message GetRequest {
// message_name is the fully-qualified message name of the ORM table being queried.
string message_name = 1;
// index is the index fields expression used in orm definitions. If it
// is empty, the table's primary key is assumed. If it is non-empty, it must
// refer to an unique index.
string index = 2;
// values are the values of the fields corresponding to the requested index.
// There must be as many values provided as there are fields in the index and
// these values must correspond to the index field types.
repeated IndexValue values = 3;
}
// GetResponse is the Query/Get response type.
message GetResponse {
// result is the result of the get query. If no value is found, the gRPC
// status code NOT_FOUND will be returned.
google.protobuf.Any result = 1;
}
// ListRequest is the Query/List request type.
message ListRequest {
// message_name is the fully-qualified message name of the ORM table being queried.
string message_name = 1;
// index is the index fields expression used in orm definitions. If it
// is empty, the table's primary key is assumed.
string index = 2;
// query is the query expression corresponding to the provided index. If
// neither prefix nor range is specified, the query will list all the fields
// in the index.
oneof query {
// prefix defines a prefix query.
Prefix prefix = 3;
// range defines a range query.
Range range = 4;
}
// pagination is the pagination request.
cosmos.base.query.v1beta1.PageRequest pagination = 5;
// Prefix specifies the arguments to a prefix query.
message Prefix {
// values specifies the index values for the prefix query.
// It is valid to special a partial prefix with fewer values than
// the number of fields in the index.
repeated IndexValue values = 1;
}
// Range specifies the arguments to a range query.
message Range {
// start specifies the starting index values for the range query.
// It is valid to provide fewer values than the number of fields in the
// index.
repeated IndexValue start = 1;
// end specifies the inclusive ending index values for the range query.
// It is valid to provide fewer values than the number of fields in the
// index.
repeated IndexValue end = 2;
}
}
// ListResponse is the Query/List response type.
message ListResponse {
// results are the results of the query.
repeated google.protobuf.Any results = 1;
// pagination is the pagination response.
cosmos.base.query.v1beta1.PageResponse pagination = 5;
}
// IndexValue represents the value of a field in an ORM index expression.
message IndexValue {
// value specifies the index value
oneof value {
// uint specifies a value for an uint32, fixed32, uint64, or fixed64
// index field.
uint64 uint = 1;
// int64 specifies a value for an int32, sfixed32, int64, or sfixed64
// index field.
int64 int = 2;
// str specifies a value for a string index field.
string str = 3;
// bytes specifies a value for a bytes index field.
bytes bytes = 4;
// enum specifies a value for an enum index field.
string enum = 5;
// bool specifies a value for a bool index field.
bool bool = 6;
// timestamp specifies a value for a timestamp index field.
google.protobuf.Timestamp timestamp = 7;
// duration specifies a value for a duration index field.
google.protobuf.Duration duration = 8;
}
}
-107
View File
@@ -1,107 +0,0 @@
syntax = "proto3";
package cosmos.orm.v1;
import "google/protobuf/descriptor.proto";
extend google.protobuf.MessageOptions {
// table specifies that this message will be used as an ORM table. It cannot
// be used together with the singleton option.
TableDescriptor table = 104503790;
// singleton specifies that this message will be used as an ORM singleton. It cannot
// be used together with the table option.
SingletonDescriptor singleton = 104503791;
}
// TableDescriptor describes an ORM table.
message TableDescriptor {
// primary_key defines the primary key for the table.
PrimaryKeyDescriptor primary_key = 1;
// index defines one or more secondary indexes.
repeated SecondaryIndexDescriptor index = 2;
// id is a non-zero integer ID that must be unique within the
// tables and singletons in this file. It may be deprecated in the future when this
// can be auto-generated.
uint32 id = 3;
}
// PrimaryKeyDescriptor describes a table primary key.
message PrimaryKeyDescriptor {
// fields is a comma-separated list of fields in the primary key. Spaces are
// not allowed. Supported field types, their encodings, and any applicable constraints
// are described below.
// - uint32 are encoded as 2,3,4 or 5 bytes using a compact encoding that
// is suitable for sorted iteration (not varint encoding). This type is
// well-suited for small integers.
// - uint64 are encoded as 2,4,6 or 9 bytes using a compact encoding that
// is suitable for sorted iteration (not varint encoding). This type is
// well-suited for small integers such as auto-incrementing sequences.
// - fixed32, fixed64 are encoded as big-endian fixed width bytes and support
// sorted iteration. These types are well-suited for encoding fixed with
// decimals as integers.
// - string's are encoded as raw bytes in terminal key segments and null-terminated
// in non-terminal segments. Null characters are thus forbidden in strings.
// string fields support sorted iteration.
// - bytes are encoded as raw bytes in terminal segments and length-prefixed
// with a 32-bit unsigned varint in non-terminal segments.
// - int32, sint32, int64, sint64, sfixed32, sfixed64 are encoded as fixed width bytes with
// an encoding that enables sorted iteration.
// - google.protobuf.Timestamp is encoded such that values with only seconds occupy 6 bytes,
// values including nanos occupy 9 bytes, and nil values occupy 1 byte. When iterating, nil
// values will always be ordered last. Seconds and nanos values must conform to the officially
// specified ranges of 0001-01-01T00:00:00Z to 9999-12-31T23:59:59Z and 0 to 999,999,999 respectively.
// - google.protobuf.Duration is encoded as 12 bytes using an encoding that enables sorted iteration.
// - enum fields are encoded using varint encoding and do not support sorted
// iteration.
// - bool fields are encoded as a single byte 0 or 1.
//
// All other fields types are unsupported in keys including repeated and
// oneof fields.
//
// Primary keys are prefixed by the varint encoded table id and the byte 0x0
// plus any additional prefix specified by the schema.
string fields = 1;
// auto_increment specifies that the primary key is generated by an
// auto-incrementing integer. If this is set to true fields must only
// contain one field of that is of type uint64.
bool auto_increment = 2;
}
// PrimaryKeyDescriptor describes a table secondary index.
message SecondaryIndexDescriptor {
// fields is a comma-separated list of fields in the index. The supported
// field types are the same as those for PrimaryKeyDescriptor.fields.
// Index keys are prefixed by the varint encoded table id and the varint
// encoded index id plus any additional prefix specified by the schema.
//
// In addition the field segments, non-unique index keys are suffixed with
// any additional primary key fields not present in the index fields so that the
// primary key can be reconstructed. Unique indexes instead of being suffixed
// store the remaining primary key fields in the value..
string fields = 1;
// id is a non-zero integer ID that must be unique within the indexes for this
// 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.
bool unique = 3;
}
// TableDescriptor describes an ORM singleton table which has at most one instance.
message SingletonDescriptor {
// id is a non-zero integer ID that must be unique within the
// tables and singletons in this file. It may be deprecated in the future when this
// can be auto-generated.
uint32 id = 1;
}
-58
View File
@@ -1,58 +0,0 @@
syntax = "proto3";
package cosmos.orm.v1alpha1;
import "google/protobuf/descriptor.proto";
extend google.protobuf.MessageOptions {
// module_schema is used to define the ORM schema for an app module.
// All module config messages that use module_schema must also declare
// themselves as app module config messages using the cosmos.app.v1.is_module
// option.
ModuleSchemaDescriptor module_schema = 104503792;
}
// ModuleSchemaDescriptor describe's a module's ORM schema.
message ModuleSchemaDescriptor {
repeated FileEntry schema_file = 1;
// FileEntry describes an ORM file used in a module.
message FileEntry {
// id is a prefix that will be varint encoded and prepended to all the
// table keys specified in the file's tables.
uint32 id = 1;
// proto_file_name is the name of a file .proto in that contains
// table definitions. The .proto file must be in a package that the
// module has referenced using cosmos.app.v1.ModuleDescriptor.use_package.
string proto_file_name = 2;
// storage_type optionally indicates the type of storage this file's
// tables should used. If it is left unspecified, the default KV-storage
// of the app will be used.
StorageType storage_type = 3;
}
// prefix is an optional prefix that precedes all keys in this module's
// store.
bytes prefix = 2;
}
// StorageType
enum StorageType {
// STORAGE_TYPE_DEFAULT_UNSPECIFIED indicates the persistent storage where all
// data is stored in the regular Merkle-tree backed KV-store.
STORAGE_TYPE_DEFAULT_UNSPECIFIED = 0;
// STORAGE_TYPE_MEMORY indicates in-memory storage that will be
// reloaded every time an app restarts. Tables with this type of storage
// will by default be ignored when importing and exporting a module's
// state from JSON.
STORAGE_TYPE_MEMORY = 1;
// STORAGE_TYPE_TRANSIENT indicates transient storage that is reset
// at the end of every block. Tables with this type of storage
// will by default be ignored when importing and exporting a module's
// state from JSON.
STORAGE_TYPE_TRANSIENT = 2;
}