chore: bump internal to main (#23681)
This commit is contained in:
@@ -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
@@ -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
|
||||
|
||||
Vendored
-1
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,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
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
@@ -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))
|
||||
```
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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)
|
||||
})
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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: ×tamppb.Timestamp{
|
||||
Seconds: 0,
|
||||
Nanos: -1,
|
||||
},
|
||||
expectErr: "timestamp nanos is out of range",
|
||||
},
|
||||
|
||||
{
|
||||
name: "nanos too big",
|
||||
ts: ×tamppb.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)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -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")
|
||||
}
|
||||
}
|
||||
@@ -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")
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
@@ -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
@@ -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=
|
||||
@@ -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, "_")
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
})
|
||||
}
|
||||
@@ -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
|
||||
})
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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() {}
|
||||
@@ -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())
|
||||
}
|
||||
@@ -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")
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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{}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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{}
|
||||
@@ -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
@@ -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())
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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")
|
||||
)
|
||||
@@ -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"
|
||||
};
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
Reference in New Issue
Block a user