diff --git a/api/cosmos/base/query/v1beta1/pagination.pulsar.go b/api/cosmos/base/query/v1beta1/pagination.pulsar.go index a4e6df7d7115..1f9eca212585 100644 --- a/api/cosmos/base/query/v1beta1/pagination.pulsar.go +++ b/api/cosmos/base/query/v1beta1/pagination.pulsar.go @@ -3,13 +3,14 @@ package queryv1beta1 import ( fmt "fmt" + io "io" + reflect "reflect" + sync "sync" + runtime "github.com/cosmos/cosmos-proto/runtime" protoreflect "google.golang.org/protobuf/reflect/protoreflect" protoiface "google.golang.org/protobuf/runtime/protoiface" protoimpl "google.golang.org/protobuf/runtime/protoimpl" - io "io" - reflect "reflect" - sync "sync" ) var ( @@ -1223,7 +1224,8 @@ type PageResponse struct { unknownFields protoimpl.UnknownFields // next_key is the key to be passed to PageRequest.key to - // query the next page most efficiently + // query the next page most efficiently. It will be empty if + // there are no more results. NextKey []byte `protobuf:"bytes,1,opt,name=next_key,json=nextKey,proto3" json:"next_key,omitempty"` // total is total number of results available if PageRequest.count_total // was set, its value is undefined otherwise diff --git a/api/cosmos/tx/signing/v1beta1/signing.pulsar.go b/api/cosmos/tx/signing/v1beta1/signing.pulsar.go index 63aad962ed0c..d59ff2ba3b4c 100644 --- a/api/cosmos/tx/signing/v1beta1/signing.pulsar.go +++ b/api/cosmos/tx/signing/v1beta1/signing.pulsar.go @@ -3,15 +3,17 @@ package signingv1beta1 import ( fmt "fmt" + io "io" + reflect "reflect" + sync "sync" + runtime "github.com/cosmos/cosmos-proto/runtime" - v1beta1 "github.com/cosmos/cosmos-sdk/api/cosmos/crypto/multisig/v1beta1" protoreflect "google.golang.org/protobuf/reflect/protoreflect" protoiface "google.golang.org/protobuf/runtime/protoiface" protoimpl "google.golang.org/protobuf/runtime/protoimpl" anypb "google.golang.org/protobuf/types/known/anypb" - io "io" - reflect "reflect" - sync "sync" + + v1beta1 "github.com/cosmos/cosmos-sdk/api/cosmos/crypto/multisig/v1beta1" ) var _ protoreflect.List = (*_SignatureDescriptors_1_list)(nil) diff --git a/orm/internal/codegen/codegen.go b/orm/internal/codegen/codegen.go index 1d52cec2e803..32060091d13f 100644 --- a/orm/internal/codegen/codegen.go +++ b/orm/internal/codegen/codegen.go @@ -13,8 +13,7 @@ import ( ) const ( - contextPkg = protogen.GoImportPath("context") - + contextPkg = protogen.GoImportPath("context") ormListPkg = protogen.GoImportPath("github.com/cosmos/cosmos-sdk/orm/model/ormlist") ormdbPkg = protogen.GoImportPath("github.com/cosmos/cosmos-sdk/orm/model/ormdb") ormErrPkg = protogen.GoImportPath("github.com/cosmos/cosmos-sdk/orm/types/ormerrors") diff --git a/orm/internal/codegen/table.go b/orm/internal/codegen/table.go index 93ffd20e63f4..b5472538c7bd 100644 --- a/orm/internal/codegen/table.go +++ b/orm/internal/codegen/table.go @@ -231,16 +231,14 @@ func (t tableGen) genStoreImpl() { // List t.P(receiver, "List(ctx ", contextPkg.Ident("Context"), ", prefixKey ", t.indexKeyInterfaceName(), ", opts ...", ormListPkg.Ident("Option"), ") (", t.iteratorName(), ", error) {") - t.P("opts = append(opts, ", ormListPkg.Ident("Prefix"), "(prefixKey.values()...))") - t.P("it, err := ", receiverVar, ".table.GetIndexByID(prefixKey.id()).Iterator(ctx, opts...)") + t.P("it, err := ", receiverVar, ".table.GetIndexByID(prefixKey.id()).List(ctx, prefixKey.values(), opts...)") t.P("return ", t.iteratorName(), "{it}, err") t.P("}") t.P() // ListRange t.P(receiver, "ListRange(ctx ", contextPkg.Ident("Context"), ", from, to ", t.indexKeyInterfaceName(), ", opts ...", ormListPkg.Ident("Option"), ") (", t.iteratorName(), ", error) {") - t.P("opts = append(opts, ", ormListPkg.Ident("Start"), "(from.values()...), ", ormListPkg.Ident("End"), "(to.values()...))") - t.P("it, err := ", receiverVar, ".table.GetIndexByID(from.id()).Iterator(ctx, opts...)") + t.P("it, err := ", receiverVar, ".table.GetIndexByID(from.id()).ListRange(ctx, from.values(), to.values(), opts...)") t.P("return ", t.iteratorName(), "{it}, err") t.P("}") t.P() diff --git a/orm/internal/listinternal/options.go b/orm/internal/listinternal/options.go index 6db3af3cfdaf..4bd65a4f2f9f 100644 --- a/orm/internal/listinternal/options.go +++ b/orm/internal/listinternal/options.go @@ -3,23 +3,24 @@ package listinternal import ( "fmt" - "google.golang.org/protobuf/reflect/protoreflect" + "google.golang.org/protobuf/proto" ) // Options is the internal list options struct. type Options struct { - Start, End, Prefix []protoreflect.Value - Reverse bool - Cursor []byte + Reverse bool + Cursor []byte + Filter func(proto.Message) bool + Offset, Limit uint64 + CountTotal bool } func (o Options) Validate() error { - if o.Start != nil || o.End != nil { - if o.Prefix != nil { - return fmt.Errorf("can either use Start/End or Prefix, not both") + if len(o.Cursor) != 0 { + if o.Offset > 0 { + return fmt.Errorf("can only specify one of cursor or offset") } } - return nil } diff --git a/orm/internal/testpb/bank.cosmos_orm.go b/orm/internal/testpb/bank.cosmos_orm.go index e8eafbb42d5d..f48041ff1d83 100644 --- a/orm/internal/testpb/bank.cosmos_orm.go +++ b/orm/internal/testpb/bank.cosmos_orm.go @@ -108,14 +108,12 @@ func (this balanceStore) Get(ctx context.Context, address string, denom string) } func (this balanceStore) List(ctx context.Context, prefixKey BalanceIndexKey, opts ...ormlist.Option) (BalanceIterator, error) { - opts = append(opts, ormlist.Prefix(prefixKey.values()...)) - it, err := this.table.GetIndexByID(prefixKey.id()).Iterator(ctx, opts...) + it, err := this.table.GetIndexByID(prefixKey.id()).List(ctx, prefixKey.values(), opts...) return BalanceIterator{it}, err } func (this balanceStore) ListRange(ctx context.Context, from, to BalanceIndexKey, opts ...ormlist.Option) (BalanceIterator, error) { - opts = append(opts, ormlist.Start(from.values()...), ormlist.End(to.values()...)) - it, err := this.table.GetIndexByID(from.id()).Iterator(ctx, opts...) + it, err := this.table.GetIndexByID(from.id()).ListRange(ctx, from.values(), to.values(), opts...) return BalanceIterator{it}, err } @@ -210,14 +208,12 @@ func (this supplyStore) Get(ctx context.Context, denom string) (*Supply, error) } func (this supplyStore) List(ctx context.Context, prefixKey SupplyIndexKey, opts ...ormlist.Option) (SupplyIterator, error) { - opts = append(opts, ormlist.Prefix(prefixKey.values()...)) - it, err := this.table.GetIndexByID(prefixKey.id()).Iterator(ctx, opts...) + it, err := this.table.GetIndexByID(prefixKey.id()).List(ctx, prefixKey.values(), opts...) return SupplyIterator{it}, err } func (this supplyStore) ListRange(ctx context.Context, from, to SupplyIndexKey, opts ...ormlist.Option) (SupplyIterator, error) { - opts = append(opts, ormlist.Start(from.values()...), ormlist.End(to.values()...)) - it, err := this.table.GetIndexByID(from.id()).Iterator(ctx, opts...) + it, err := this.table.GetIndexByID(from.id()).ListRange(ctx, from.values(), to.values(), opts...) return SupplyIterator{it}, err } diff --git a/orm/internal/testpb/test_schema.cosmos_orm.go b/orm/internal/testpb/test_schema.cosmos_orm.go index 14eb4f09a0ac..6b45f8aec39e 100644 --- a/orm/internal/testpb/test_schema.cosmos_orm.go +++ b/orm/internal/testpb/test_schema.cosmos_orm.go @@ -175,14 +175,12 @@ func (this exampleTableStore) GetByU64Str(ctx context.Context, u64 uint64, str s } func (this exampleTableStore) List(ctx context.Context, prefixKey ExampleTableIndexKey, opts ...ormlist.Option) (ExampleTableIterator, error) { - opts = append(opts, ormlist.Prefix(prefixKey.values()...)) - it, err := this.table.GetIndexByID(prefixKey.id()).Iterator(ctx, opts...) + it, err := this.table.GetIndexByID(prefixKey.id()).List(ctx, prefixKey.values(), opts...) return ExampleTableIterator{it}, err } func (this exampleTableStore) ListRange(ctx context.Context, from, to ExampleTableIndexKey, opts ...ormlist.Option) (ExampleTableIterator, error) { - opts = append(opts, ormlist.Start(from.values()...), ormlist.End(to.values()...)) - it, err := this.table.GetIndexByID(from.id()).Iterator(ctx, opts...) + it, err := this.table.GetIndexByID(from.id()).ListRange(ctx, from.values(), to.values(), opts...) return ExampleTableIterator{it}, err } @@ -314,14 +312,12 @@ func (this exampleAutoIncrementTableStore) GetByX(ctx context.Context, x string) } func (this exampleAutoIncrementTableStore) List(ctx context.Context, prefixKey ExampleAutoIncrementTableIndexKey, opts ...ormlist.Option) (ExampleAutoIncrementTableIterator, error) { - opts = append(opts, ormlist.Prefix(prefixKey.values()...)) - it, err := this.table.GetIndexByID(prefixKey.id()).Iterator(ctx, opts...) + it, err := this.table.GetIndexByID(prefixKey.id()).List(ctx, prefixKey.values(), opts...) return ExampleAutoIncrementTableIterator{it}, err } func (this exampleAutoIncrementTableStore) ListRange(ctx context.Context, from, to ExampleAutoIncrementTableIndexKey, opts ...ormlist.Option) (ExampleAutoIncrementTableIterator, error) { - opts = append(opts, ormlist.Start(from.values()...), ormlist.End(to.values()...)) - it, err := this.table.GetIndexByID(from.id()).Iterator(ctx, opts...) + it, err := this.table.GetIndexByID(from.id()).ListRange(ctx, from.values(), to.values(), opts...) return ExampleAutoIncrementTableIterator{it}, err } diff --git a/orm/model/ormlist/options.go b/orm/model/ormlist/options.go index c917921e8f1e..8a6007c8c629 100644 --- a/orm/model/ormlist/options.go +++ b/orm/model/ormlist/options.go @@ -2,61 +2,16 @@ package ormlist import ( - "github.com/cosmos/cosmos-sdk/orm/encoding/encodeutil" + "google.golang.org/protobuf/proto" + + queryv1beta1 "github.com/cosmos/cosmos-sdk/api/cosmos/base/query/v1beta1" + "github.com/cosmos/cosmos-sdk/orm/internal/listinternal" ) // Option represents a list option. type Option = listinternal.Option -// Start defines the values to use to start range iteration. It cannot be -// combined with Prefix. -// -// Values must correspond in type to the index's fields and the number of values -// provided cannot exceed the number of fields in the index, although fewer -// values can be provided. -// -// Range iteration can only be done for start and end values which are -// well-ordered, meaning that any unordered components must be equal. Ex. -// the bytes type is considered unordered, so a range iterator is created -// over an index with a bytes field, both start and end must have the same -// value for bytes. -func Start(values ...interface{}) Option { - return listinternal.FuncOption(func(options *listinternal.Options) { - options.Start = encodeutil.ValuesOf(values...) - }) -} - -// End defines the values to use to end range iteration. It cannot be -// combined with Prefix. -// -// Values must correspond in type to the index's fields and the number of values -// provided cannot exceed the number of fields in the index, although fewer -// values can be provided. -// -// Range iteration can only be done for start and end values which are -// well-ordered, meaning that any unordered components must be equal. Ex. -// the bytes type is considered unordered, so a range iterator is created -// over an index with a bytes field, both start and end must have the same -// value for bytes. -func End(values ...interface{}) Option { - return listinternal.FuncOption(func(options *listinternal.Options) { - options.End = encodeutil.ValuesOf(values...) - }) -} - -// Prefix defines values to use for prefix iteration. It cannot be used -// together with Start or End. -// -// Values must correspond in type to the index's fields and the number of values -// provided cannot exceed the number of fields in the index, although fewer -// values can be provided. -func Prefix(values ...interface{}) Option { - return listinternal.FuncOption(func(options *listinternal.Options) { - options.Prefix = encodeutil.ValuesOf(values...) - }) -} - // Reverse reverses the direction of iteration. If Reverse is // provided twice, iteration will happen in the forward direction. func Reverse() Option { @@ -65,6 +20,14 @@ func Reverse() Option { }) } +// Filter returns an option which applies a filter function to each item +// and skips over it when the filter function returns false. +func Filter(filterFn func(message proto.Message) bool) Option { + return listinternal.FuncOption(func(options *listinternal.Options) { + options.Filter = filterFn + }) +} + // Cursor specifies a cursor after which to restart iteration. Cursor values // are returned by iterators and in pagination results. func Cursor(cursor CursorT) Option { @@ -73,5 +36,23 @@ func Cursor(cursor CursorT) Option { }) } +// Paginate paginates iterator output based on the provided page request. +// The Iterator.PageRequest value on the returned iterator will be non-nil +// after Iterator.Next() returns false when this option is provided. +// Care should be taken when using Paginate together with Reverse and/or Cursor +// and generally this should be avoided. +func Paginate(pageRequest *queryv1beta1.PageRequest) Option { + return listinternal.FuncOption(func(options *listinternal.Options) { + if pageRequest.Reverse { + options.Reverse = !options.Reverse + } + + options.Cursor = pageRequest.Key + options.Offset = pageRequest.Offset + options.Limit = pageRequest.Limit + options.CountTotal = pageRequest.CountTotal + }) +} + // CursorT defines a cursor type. type CursorT []byte diff --git a/orm/model/ormtable/filter.go b/orm/model/ormtable/filter.go new file mode 100644 index 000000000000..a7df4ff62bb7 --- /dev/null +++ b/orm/model/ormtable/filter.go @@ -0,0 +1,28 @@ +package ormtable + +import "google.golang.org/protobuf/proto" + +type filterIterator struct { + Iterator + filter func(proto.Message) bool + msg proto.Message +} + +func (f *filterIterator) Next() bool { + for f.Iterator.Next() { + msg, err := f.Iterator.GetMessage() + if err != nil { + return false + } + + if f.filter(msg) { + f.msg = msg + return true + } + } + return false +} + +func (f filterIterator) GetMessage() (proto.Message, error) { + return f.msg, nil +} diff --git a/orm/model/ormtable/index.go b/orm/model/ormtable/index.go index a859606524e7..23918c63012c 100644 --- a/orm/model/ormtable/index.go +++ b/orm/model/ormtable/index.go @@ -16,8 +16,27 @@ import ( // to index methods. type Index interface { - // Iterator returns an iterator for this index with the provided list options. - Iterator(ctx context.Context, options ...ormlist.Option) (Iterator, error) + // List does iteration over the index with the provided prefix key and options. + // Prefix key values must correspond in type to the index's fields and the + // number of values provided cannot exceed the number of fields in the index, + // although fewer values can be provided. + List(ctx context.Context, prefixKey []interface{}, options ...ormlist.Option) (Iterator, error) + + // ListRange does range iteration over the index with the provided from and to + // values and options. + // + // From and to values must correspond in type to the index's fields and the number of values + // provided cannot exceed the number of fields in the index, although fewer + // values can be provided. + // + // Range iteration can only be done for from and to values which are + // well-ordered, meaning that any unordered components must be equal. Ex. + // the bytes type is considered unordered, so a range iterator is created + // over an index with a bytes field, both start and end must have the same + // value for bytes. + // + // Range iteration is inclusive at both ends. + ListRange(ctx context.Context, from, to []interface{}, options ...ormlist.Option) (Iterator, error) // MessageType returns the protobuf message type of the index. MessageType() protoreflect.MessageType diff --git a/orm/model/ormtable/index_impl.go b/orm/model/ormtable/index_impl.go index 4db9b3caa9b5..a2a892d86af7 100644 --- a/orm/model/ormtable/index_impl.go +++ b/orm/model/ormtable/index_impl.go @@ -24,13 +24,22 @@ type indexKeyIndex struct { getReadBackend func(context.Context) (ReadBackend, error) } -func (i indexKeyIndex) Iterator(ctx context.Context, options ...ormlist.Option) (Iterator, error) { +func (i indexKeyIndex) List(ctx context.Context, prefixKey []interface{}, options ...ormlist.Option) (Iterator, error) { backend, err := i.getReadBackend(ctx) if err != nil { return nil, err } - return iterator(backend, backend.IndexStoreReader(), i, i.KeyCodec, options) + return prefixIterator(backend.IndexStoreReader(), backend, i, i.KeyCodec, prefixKey, options) +} + +func (i indexKeyIndex) ListRange(ctx context.Context, from, to []interface{}, options ...ormlist.Option) (Iterator, error) { + backend, err := i.getReadBackend(ctx) + if err != nil { + return nil, err + } + + return rangeIterator(backend.IndexStoreReader(), backend, i, i.KeyCodec, from, to, options) } var _ indexer = &indexKeyIndex{} diff --git a/orm/model/ormtable/iterator.go b/orm/model/ormtable/iterator.go index cefbc227630f..b8135cd671a6 100644 --- a/orm/model/ormtable/iterator.go +++ b/orm/model/ormtable/iterator.go @@ -4,6 +4,10 @@ import ( "google.golang.org/protobuf/proto" "google.golang.org/protobuf/reflect/protoreflect" + "github.com/cosmos/cosmos-sdk/orm/encoding/encodeutil" + + queryv1beta1 "github.com/cosmos/cosmos-sdk/api/cosmos/base/query/v1beta1" + "github.com/cosmos/cosmos-sdk/orm/encoding/ormkv" "github.com/cosmos/cosmos-sdk/orm/internal/listinternal" "github.com/cosmos/cosmos-sdk/orm/model/kv" @@ -33,6 +37,10 @@ type Iterator interface { // 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() @@ -40,136 +48,149 @@ type Iterator interface { doNotImplement() } -func iterator( - backend ReadBackend, - reader kv.ReadonlyStore, - index concreteIndex, - codec *ormkv.KeyCodec, - options []listinternal.Option, -) (Iterator, error) { - opts := &listinternal.Options{} - listinternal.ApplyOptions(opts, options) - if err := opts.Validate(); err != nil { +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 } - if opts.Start != nil || opts.End != nil { - err := codec.CheckValidRangeIterationKeys(opts.Start, opts.End) - if err != nil { - return nil, err - } - - startBz, err := codec.EncodeKey(opts.Start) - if err != nil { - return nil, err - } - - endBz, err := codec.EncodeKey(opts.End) - if err != nil { - return nil, err - } - - fullEndKey := len(codec.GetFieldNames()) == len(opts.End) - - return rangeIterator(reader, backend, index, startBz, endBz, fullEndKey, opts) - } else { - prefixBz, err := codec.EncodeKey(opts.Prefix) - if err != nil { - return nil, err - } - - return prefixIterator(reader, backend, index, prefixBz, opts) + var prefixBz []byte + prefixBz, err := codec.EncodeKey(encodeutil.ValuesOf(prefix...)) + if err != nil { + return nil, err } -} -func prefixIterator(iteratorStore kv.ReadonlyStore, backend ReadBackend, index concreteIndex, prefix []byte, options *listinternal.Options) (Iterator, error) { + 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 = prefix + start = prefixBz } - end := prefixEndBytes(prefix) + end := prefixEndBytes(prefixBz) it, err := iteratorStore.Iterator(start, end) if err != nil { return nil, err } - return &indexIterator{ + res = &indexIterator{ index: index, store: backend, iterator: it, started: false, - }, nil + } } else { var end []byte if len(options.Cursor) != 0 { // end bytes is already exclusive by default end = options.Cursor } else { - end = prefixEndBytes(prefix) + end = prefixEndBytes(prefixBz) } - it, err := iteratorStore.ReverseIterator(prefix, end) + it, err := iteratorStore.ReverseIterator(prefixBz, end) if err != nil { return nil, err } - return &indexIterator{ + res = &indexIterator{ index: index, store: backend, iterator: it, started: false, - }, nil + } } + + return applyCommonIteratorOptions(res, options) } -// NOTE: fullEndKey indicates whether the end key contained all the fields of the key, -// if it did then we need to use inclusive end bytes, otherwise we prefix the end bytes -func rangeIterator(iteratorStore kv.ReadonlyStore, reader ReadBackend, index concreteIndex, start, end []byte, fullEndKey bool, options *listinternal.Options) (Iterator, error) { +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 { - start = append(options.Cursor, 0) + startBz = append(options.Cursor, 0) } if fullEndKey { - end = inclusiveEndBytes(end) + endBz = inclusiveEndBytes(endBz) } else { - end = prefixEndBytes(end) + endBz = prefixEndBytes(endBz) } - it, err := iteratorStore.Iterator(start, end) + it, err := iteratorStore.Iterator(startBz, endBz) if err != nil { return nil, err } - return &indexIterator{ + res = &indexIterator{ index: index, store: reader, iterator: it, started: false, - }, nil + } } else { if len(options.Cursor) != 0 { - end = options.Cursor + endBz = options.Cursor } else { if fullEndKey { - end = inclusiveEndBytes(end) + endBz = inclusiveEndBytes(endBz) } else { - end = prefixEndBytes(end) + endBz = prefixEndBytes(endBz) } } - it, err := iteratorStore.ReverseIterator(start, end) + it, err := iteratorStore.ReverseIterator(startBz, endBz) if err != nil { return nil, err } - return &indexIterator{ + res = &indexIterator{ index: index, store: reader, iterator: it, started: false, - }, nil + } + } + + 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 { + iterator = paginate(iterator, options) + } + + return iterator, nil } type indexIterator struct { @@ -183,6 +204,10 @@ type indexIterator struct { started bool } +func (i *indexIterator) PageResponse() *queryv1beta1.PageResponse { + return nil +} + func (i *indexIterator) Next() bool { if !i.started { i.started = true @@ -232,8 +257,6 @@ func (i indexIterator) Close() { } } -func (indexIterator) doNotImplement() { - -} +func (indexIterator) doNotImplement() {} var _ Iterator = &indexIterator{} diff --git a/orm/model/ormtable/paginate.go b/orm/model/ormtable/paginate.go index d3e29d2d2f62..b56a911eb5c9 100644 --- a/orm/model/ormtable/paginate.go +++ b/orm/model/ormtable/paginate.go @@ -1,124 +1,85 @@ package ormtable import ( - "context" - "fmt" + "math" - "google.golang.org/protobuf/proto" + "github.com/cosmos/cosmos-sdk/orm/internal/listinternal" queryv1beta1 "github.com/cosmos/cosmos-sdk/api/cosmos/base/query/v1beta1" - "github.com/cosmos/cosmos-sdk/orm/model/ormlist" ) -// PaginationRequest is a request to the Paginate function and extends the -// options in query.PageRequest. -type PaginationRequest struct { - *queryv1beta1.PageRequest +func paginate(it Iterator, options *listinternal.Options) Iterator { + offset := int(options.Offset) + limit := int(options.Limit) - // Filter is an optional filter function that can be used to filter - // the results in the underlying iterator and should return true to include - // an item in the result. - Filter func(message proto.Message) bool -} - -// PaginationResponse is a response from the Paginate function and extends the -// options in query.PageResponse. -type PaginationResponse struct { - *queryv1beta1.PageResponse - - // HaveMore indicates whether there are more pages. - HaveMore bool - - // Cursors returns a cursor for each item and can be used to implement - // GraphQL connection edges. - Cursors []ormlist.CursorT -} - -// Paginate retrieves a "page" of data from the provided index and context. -func Paginate( - index Index, - ctx context.Context, - request *PaginationRequest, - onItem func(proto.Message), - options ...ormlist.Option, -) (*PaginationResponse, error) { - offset := int(request.Offset) - if len(request.Key) != 0 { - if offset > 0 { - return nil, fmt.Errorf("can only specify one of cursor or offset") + i := 0 + if offset != 0 { + for ; i < offset; i++ { + if !it.Next() { + return &paginationIterator{ + Iterator: it, + pageRes: &queryv1beta1.PageResponse{Total: uint64(i)}, + } + } } - - options = append(options, ormlist.Cursor(request.Key)) } - if request.Reverse { - options = append(options, ormlist.Reverse()) + var done int + if limit != 0 { + done = limit + offset + } else { + done = math.MaxInt } - it, err := index.Iterator(ctx, options...) - if err != nil { - return nil, err + return &paginationIterator{ + Iterator: it, + pageRes: nil, + countTotal: options.CountTotal, + i: i, + done: done, } - defer it.Close() +} - limit := int(request.Limit) - if limit == 0 { - return nil, fmt.Errorf("limit not specified") - } +type paginationIterator struct { + Iterator + pageRes *queryv1beta1.PageResponse + countTotal bool + i int + done int +} - i := 0 - if offset != 0 { - for ; i < offset; i++ { - if !it.Next() { - return &PaginationResponse{ - PageResponse: &queryv1beta1.PageResponse{Total: uint64(i)}, - }, nil - } +func (it *paginationIterator) Next() bool { + if it.i >= it.done { + it.pageRes = &queryv1beta1.PageResponse{} + cursor := it.Cursor() + if it.Iterator.Next() { + it.pageRes.NextKey = cursor + it.i++ } - } - - haveMore := false - cursors := make([]ormlist.CursorT, 0, limit) - done := limit + offset - for it.Next() { - if i == done { - haveMore = true - if request.CountTotal { - for { - i++ - if !it.Next() { - break - } + if it.countTotal { + for { + if !it.Iterator.Next() { + it.pageRes.Total = uint64(it.i) + return false } + it.i++ } - break - } - - message, err := it.GetMessage() - if err != nil { - return nil, err } + return false + } - if request.Filter != nil && !request.Filter(message) { - continue + ok := it.Iterator.Next() + if ok { + it.i++ + return true + } else { + it.pageRes = &queryv1beta1.PageResponse{ + Total: uint64(it.i), } - - i++ - cursors = append(cursors, it.Cursor()) - onItem(message) + return false } +} - pageRes := &queryv1beta1.PageResponse{} - if request.CountTotal { - pageRes.Total = uint64(i) - } - n := len(cursors) - if n != 0 { - pageRes.NextKey = cursors[n-1] - } - return &PaginationResponse{ - PageResponse: pageRes, - HaveMore: haveMore, - Cursors: cursors, - }, nil +func (it paginationIterator) PageResponse() *queryv1beta1.PageResponse { + return it.pageRes } diff --git a/orm/model/ormtable/primary_key.go b/orm/model/ormtable/primary_key.go index eb68b1e269c8..65c921a8e41d 100644 --- a/orm/model/ormtable/primary_key.go +++ b/orm/model/ormtable/primary_key.go @@ -24,13 +24,22 @@ type primaryKeyIndex struct { getReadBackend func(context.Context) (ReadBackend, error) } -func (p primaryKeyIndex) Iterator(ctx context.Context, options ...ormlist.Option) (Iterator, error) { +func (p primaryKeyIndex) List(ctx context.Context, prefixKey []interface{}, options ...ormlist.Option) (Iterator, error) { backend, err := p.getReadBackend(ctx) if err != nil { return nil, err } - return iterator(backend, backend.CommitmentStoreReader(), p, p.KeyCodec, options) + return prefixIterator(backend.CommitmentStoreReader(), backend, p, p.KeyCodec, prefixKey, options) +} + +func (p primaryKeyIndex) ListRange(ctx context.Context, from, to []interface{}, options ...ormlist.Option) (Iterator, error) { + backend, err := p.getReadBackend(ctx) + if err != nil { + return nil, err + } + + return rangeIterator(backend.CommitmentStoreReader(), backend, p, p.KeyCodec, from, to, options) } func (p primaryKeyIndex) doNotImplement() {} @@ -71,23 +80,23 @@ func (p primaryKeyIndex) get(backend ReadBackend, message proto.Message, values return p.getByKeyBytes(backend, key, values, message) } -func (t primaryKeyIndex) DeleteByKey(ctx context.Context, primaryKeyValues ...interface{}) error { - return t.doDeleteByKey(ctx, encodeutil.ValuesOf(primaryKeyValues...)) +func (p primaryKeyIndex) DeleteByKey(ctx context.Context, primaryKeyValues ...interface{}) error { + return p.doDeleteByKey(ctx, encodeutil.ValuesOf(primaryKeyValues...)) } -func (t primaryKeyIndex) doDeleteByKey(ctx context.Context, primaryKeyValues []protoreflect.Value) error { - backend, err := t.getBackend(ctx) +func (p primaryKeyIndex) doDeleteByKey(ctx context.Context, primaryKeyValues []protoreflect.Value) error { + backend, err := p.getBackend(ctx) if err != nil { return err } - pk, err := t.EncodeKey(primaryKeyValues) + pk, err := p.EncodeKey(primaryKeyValues) if err != nil { return err } - msg := t.MessageType().New().Interface() - found, err := t.getByKeyBytes(backend, pk, primaryKeyValues, msg) + msg := p.MessageType().New().Interface() + found, err := p.getByKeyBytes(backend, pk, primaryKeyValues, msg) if err != nil { return err } @@ -114,7 +123,7 @@ func (t primaryKeyIndex) doDeleteByKey(ctx context.Context, primaryKeyValues []p // clear indexes mref := msg.ProtoReflect() indexStoreWriter := writer.IndexStore() - for _, idx := range t.indexers { + for _, idx := range p.indexers { err := idx.onDelete(indexStoreWriter, mref) if err != nil { return err diff --git a/orm/model/ormtable/table_impl.go b/orm/model/ormtable/table_impl.go index 0ccda1eb9053..0026cc4dedf4 100644 --- a/orm/model/ormtable/table_impl.go +++ b/orm/model/ormtable/table_impl.go @@ -303,7 +303,7 @@ func (t tableImpl) doExportJSON(ctx context.Context, writer io.Writer) error { } var err error - it, _ := t.Iterator(ctx) + it, _ := t.List(ctx, nil) start := true for { found := it.Next() diff --git a/orm/model/ormtable/table_test.go b/orm/model/ormtable/table_test.go index 02813a1b7ded..775e040fc6ea 100644 --- a/orm/model/ormtable/table_test.go +++ b/orm/model/ormtable/table_test.go @@ -183,6 +183,23 @@ func runTestScenario(t *testing.T, table ormtable.Table, backend ormtable.Backen ) 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) @@ -192,170 +209,115 @@ func runTestScenario(t *testing.T, table ormtable.Table, backend ormtable.Backen assert.DeepEqual(t, data[8], a, protocmp.Transform()) // let's try paginating some stuff - - // first create a function to test what we got from pagination - assertGotItems := func(items []proto.Message, xs ...int) { - n := len(xs) - assert.Equal(t, n, len(items)) - for i := 0; i < n; i++ { - j := xs[i] - //t.Logf("data[%d] %v == %v", j, data[j], items[i]) - assert.DeepEqual(t, data[j], items[i], protocmp.Transform()) - } - } - - // now do some pagination - var items []proto.Message - onItem := func(item proto.Message) { - items = append(items, item) - } - res, err := ormtable.Paginate(table, ctx, &ormtable.PaginationRequest{ - PageRequest: &queryv1beta1.PageRequest{ - Limit: 4, - CountTotal: true, - }}, onItem) + 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) - assert.Assert(t, res.HaveMore) - assert.Equal(t, 4, len(res.Cursors)) - assertGotItems(items, 0, 1, 2, 3) // read another page - items = nil - res, err = ormtable.Paginate(table, ctx, &ormtable.PaginationRequest{ - PageRequest: &queryv1beta1.PageRequest{ - Key: res.NextKey, - Limit: 4, - }}, onItem) + 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) - assert.Assert(t, res.HaveMore) - assert.Equal(t, 4, len(res.Cursors)) - assertGotItems(items, 4, 5, 6, 7) // and the last page - items = nil - res, err = ormtable.Paginate(table, ctx, &ormtable.PaginationRequest{ - PageRequest: &queryv1beta1.PageRequest{ - Key: res.NextKey, - Limit: 4, - }}, onItem) + 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) - assert.Assert(t, !res.HaveMore) - assert.Equal(t, 2, len(res.Cursors)) - assertGotItems(items, 8, 9) + assert.Assert(t, res.NextKey == nil) // let's go backwards - items = nil - res, err = ormtable.Paginate(table, ctx, &ormtable.PaginationRequest{ - PageRequest: &queryv1beta1.PageRequest{ - Limit: 2, - CountTotal: true, - Reverse: true, - }}, onItem) - assert.NilError(t, err) + 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) - assert.Assert(t, res.HaveMore) - assert.Equal(t, 2, len(res.Cursors)) - assertGotItems(items, 9, 8) // a bit more - items = nil - res, err = ormtable.Paginate(table, ctx, &ormtable.PaginationRequest{ - PageRequest: &queryv1beta1.PageRequest{ - Key: res.NextKey, - Limit: 2, - Reverse: true, - }}, onItem) - assert.NilError(t, err) + 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) - assert.Assert(t, res.HaveMore) - assert.Equal(t, 2, len(res.Cursors)) - assertGotItems(items, 7, 6) // range query - items = nil - res, err = ormtable.Paginate(table, ctx, - &ormtable.PaginationRequest{ - PageRequest: &queryv1beta1.PageRequest{ - Limit: 10, - }, - }, - onItem, - ormlist.Start(uint32(4), int64(-1), "abc"), - ormlist.End(uint32(7), int64(-2), "abe"), - ) - assert.NilError(t, err) + 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.HaveMore) - assert.Equal(t, 4, len(res.Cursors)) - assertGotItems(items, 2, 3, 4, 5) + assert.Assert(t, res.NextKey == nil) // let's try an offset - items = nil - res, err = ormtable.Paginate(table, ctx, &ormtable.PaginationRequest{ - PageRequest: &queryv1beta1.PageRequest{ - Limit: 2, - CountTotal: true, - Offset: 3, - }}, onItem) - assert.NilError(t, err) + 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) - assert.Assert(t, res.HaveMore) - assert.Equal(t, 2, len(res.Cursors)) - assertGotItems(items, 3, 4) // and reverse - items = nil - res, err = ormtable.Paginate(table, ctx, &ormtable.PaginationRequest{ - PageRequest: &queryv1beta1.PageRequest{ - Limit: 3, - CountTotal: true, - Offset: 5, - Reverse: true, - }}, onItem) - assert.NilError(t, err) + 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) - assert.Assert(t, res.HaveMore) - assert.Equal(t, 3, len(res.Cursors)) - assertGotItems(items, 4, 3, 2) // now an offset that's slightly too big - items = nil - res, err = ormtable.Paginate(table, ctx, &ormtable.PaginationRequest{ - PageRequest: &queryv1beta1.PageRequest{ - Limit: 1, - CountTotal: true, - Offset: 10, - }}, onItem) - assert.NilError(t, err) - assert.Equal(t, 0, len(items)) - assert.Assert(t, !res.HaveMore) - assert.Equal(t, uint64(10), res.Total) - - // another offset that's too big - items = nil - res, err = ormtable.Paginate(table, ctx, &ormtable.PaginationRequest{ - PageRequest: &queryv1beta1.PageRequest{ - Limit: 1, - CountTotal: true, - Offset: 14, - }}, onItem) - assert.NilError(t, err) - assert.Equal(t, 0, len(items)) - assert.Assert(t, !res.HaveMore) + 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 @@ -462,11 +424,11 @@ func testIndex(t *testing.T, model *IndexModel) { if index.IsFullyOrdered() { t.Logf("testing index %T %s", index, index.Fields()) - it, err := model.index.Iterator(model.context) + it, err := model.index.List(model.context, nil) assert.NilError(t, err) checkIteratorAgainstSlice(t, it, model.data) - it, err = model.index.Iterator(model.context, ormlist.Reverse()) + it, err = model.index.List(model.context, nil, ormlist.Reverse()) assert.NilError(t, err) checkIteratorAgainstSlice(t, it, reverseData(model.data)) @@ -482,11 +444,11 @@ func testIndex(t *testing.T, model *IndexModel) { startVals := protoValuesToInterfaces(start) endVals := protoValuesToInterfaces(end) - it, err = model.index.Iterator(model.context, ormlist.Start(startVals...), ormlist.End(endVals...)) + 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.Iterator(model.context, ormlist.Start(startVals...), ormlist.End(endVals...), ormlist.Reverse()) + it, err = model.index.ListRange(model.context, startVals, endVals, ormlist.Reverse()) assert.NilError(t, err) checkIteratorAgainstSlice(t, it, reverseData(model.data[i:j+1])) }) @@ -494,7 +456,7 @@ func testIndex(t *testing.T, model *IndexModel) { t.Logf("testing unordered index %T %s", index, index.Fields()) // get all the data - it, err := model.index.Iterator(model.context) + it, err := model.index.List(model.context, nil) assert.NilError(t, err) var data2 []proto.Message for it.Next() { @@ -664,9 +626,9 @@ func TestJSONExportImport(t *testing.T) { } func assertTablesEqual(t assert.TestingT, table ormtable.Table, ctx, ctx2 context.Context) { - it, err := table.Iterator(ctx) + it, err := table.List(ctx, nil) assert.NilError(t, err) - it2, err := table.Iterator(ctx2) + it2, err := table.List(ctx2, nil) assert.NilError(t, err) for { diff --git a/orm/model/ormtable/testdata/test_scenario.golden b/orm/model/ormtable/testdata/test_scenario.golden index 55ccb6f9b891..1bb1e271231b 100644 --- a/orm/model/ormtable/testdata/test_scenario.golden +++ b/orm/model/ormtable/testdata/test_scenario.golden @@ -292,6 +292,88 @@ GET 0100000000047ffffffffffffffe616263 1007 PK testpb.ExampleTable 4/-2/abc -> {"u32":4,"u64":7,"str":"abc","i64":-2} NEXT VALID false +ITERATOR 0100 -> 0101 + VALID true + KEY 0100000000047ffffffffffffffe616263 1007 + PK testpb.ExampleTable 4/-2/abc -> {"u32":4,"u64":7,"str":"abc","i64":-2} + NEXT + VALID true + KEY 0100000000047ffffffffffffffe616264 1007 + PK testpb.ExampleTable 4/-2/abd -> {"u32":4,"u64":7,"str":"abd","i64":-2} + NEXT + VALID true + KEY 0100000000047fffffffffffffff616263 1008 + PK testpb.ExampleTable 4/-1/abc -> {"u32":4,"u64":8,"str":"abc","i64":-1} + NEXT + VALID true + KEY 0100000000057ffffffffffffffe616264 1008 + PK testpb.ExampleTable 5/-2/abd -> {"u32":5,"u64":8,"str":"abd","i64":-2} + NEXT + VALID true + KEY 0100000000057ffffffffffffffe616265 1009 + PK testpb.ExampleTable 5/-2/abe -> {"u32":5,"u64":9,"str":"abe","i64":-2} + NEXT + VALID true + KEY 0100000000077ffffffffffffffe616265 100a + PK testpb.ExampleTable 7/-2/abe -> {"u32":7,"u64":10,"str":"abe","i64":-2} + NEXT + VALID true + KEY 0100000000077fffffffffffffff616265 100b + PK testpb.ExampleTable 7/-1/abe -> {"u32":7,"u64":11,"str":"abe","i64":-1} + NEXT + VALID true + KEY 0100000000087ffffffffffffffc616263 100b + PK testpb.ExampleTable 8/-4/abc -> {"u32":8,"u64":11,"str":"abc","i64":-4} + NEXT + VALID true + KEY 0100000000088000000000000001616263 100c + PK testpb.ExampleTable 8/1/abc -> {"u32":8,"u64":12,"str":"abc","i64":1} + NEXT + VALID true + KEY 0100000000088000000000000001616264 100a + PK testpb.ExampleTable 8/1/abd -> {"u32":8,"u64":10,"str":"abd","i64":1} + NEXT + VALID false +ITERATOR 0100 -> 0101 + VALID true + NEXT + VALID true + KEY 0100000000047ffffffffffffffe616264 1007 + PK testpb.ExampleTable 4/-2/abd -> {"u32":4,"u64":7,"str":"abd","i64":-2} +ITERATOR 0100000000047ffffffffffffffe61626400 -> 0101 + VALID true + KEY 0100000000047fffffffffffffff616263 1008 + PK testpb.ExampleTable 4/-1/abc -> {"u32":4,"u64":8,"str":"abc","i64":-1} + NEXT + VALID true + KEY 0100000000057ffffffffffffffe616264 1008 + PK testpb.ExampleTable 5/-2/abd -> {"u32":5,"u64":8,"str":"abd","i64":-2} + NEXT + VALID true + KEY 0100000000057ffffffffffffffe616265 1009 + PK testpb.ExampleTable 5/-2/abe -> {"u32":5,"u64":9,"str":"abe","i64":-2} + NEXT + VALID true + KEY 0100000000077ffffffffffffffe616265 100a + PK testpb.ExampleTable 7/-2/abe -> {"u32":7,"u64":10,"str":"abe","i64":-2} + NEXT + VALID true + KEY 0100000000077fffffffffffffff616265 100b + PK testpb.ExampleTable 7/-1/abe -> {"u32":7,"u64":11,"str":"abe","i64":-1} + NEXT + VALID true + KEY 0100000000087ffffffffffffffc616263 100b + PK testpb.ExampleTable 8/-4/abc -> {"u32":8,"u64":11,"str":"abc","i64":-4} + NEXT + VALID true + KEY 0100000000088000000000000001616263 100c + PK testpb.ExampleTable 8/1/abc -> {"u32":8,"u64":12,"str":"abc","i64":1} + NEXT + VALID true + KEY 0100000000088000000000000001616264 100a + PK testpb.ExampleTable 8/1/abd -> {"u32":8,"u64":10,"str":"abd","i64":1} + NEXT + VALID false HAS 0101000000000000000c616263 ERR:EOF GET 0101000000000000000c616263 000000088000000000000001 @@ -300,20 +382,14 @@ GET 0100000000088000000000000001616263 100c PK testpb.ExampleTable 8/1/abc -> {"u32":8,"u64":12,"str":"abc","i64":1} ITERATOR 0100 -> 0101 VALID true - KEY 0100000000047ffffffffffffffe616263 1007 - PK testpb.ExampleTable 4/-2/abc -> {"u32":4,"u64":7,"str":"abc","i64":-2} KEY 0100000000047ffffffffffffffe616263 1007 PK testpb.ExampleTable 4/-2/abc -> {"u32":4,"u64":7,"str":"abc","i64":-2} NEXT VALID true - KEY 0100000000047ffffffffffffffe616264 1007 - PK testpb.ExampleTable 4/-2/abd -> {"u32":4,"u64":7,"str":"abd","i64":-2} KEY 0100000000047ffffffffffffffe616264 1007 PK testpb.ExampleTable 4/-2/abd -> {"u32":4,"u64":7,"str":"abd","i64":-2} NEXT VALID true - KEY 0100000000047fffffffffffffff616263 1008 - PK testpb.ExampleTable 4/-1/abc -> {"u32":4,"u64":8,"str":"abc","i64":-1} KEY 0100000000047fffffffffffffff616263 1008 PK testpb.ExampleTable 4/-1/abc -> {"u32":4,"u64":8,"str":"abc","i64":-1} NEXT @@ -336,23 +412,16 @@ ITERATOR 0100 -> 0101 VALID true NEXT VALID false - CLOSE ITERATOR 0100000000057ffffffffffffffe61626400 -> 0101 VALID true - KEY 0100000000057ffffffffffffffe616265 1009 - PK testpb.ExampleTable 5/-2/abe -> {"u32":5,"u64":9,"str":"abe","i64":-2} KEY 0100000000057ffffffffffffffe616265 1009 PK testpb.ExampleTable 5/-2/abe -> {"u32":5,"u64":9,"str":"abe","i64":-2} NEXT VALID true - KEY 0100000000077ffffffffffffffe616265 100a - PK testpb.ExampleTable 7/-2/abe -> {"u32":7,"u64":10,"str":"abe","i64":-2} KEY 0100000000077ffffffffffffffe616265 100a PK testpb.ExampleTable 7/-2/abe -> {"u32":7,"u64":10,"str":"abe","i64":-2} NEXT VALID true - KEY 0100000000077fffffffffffffff616265 100b - PK testpb.ExampleTable 7/-1/abe -> {"u32":7,"u64":11,"str":"abe","i64":-1} KEY 0100000000077fffffffffffffff616265 100b PK testpb.ExampleTable 7/-1/abe -> {"u32":7,"u64":11,"str":"abe","i64":-1} NEXT @@ -363,26 +432,18 @@ ITERATOR 0100000000057ffffffffffffffe61626400 -> 0101 PK testpb.ExampleTable 8/-4/abc -> {"u32":8,"u64":11,"str":"abc","i64":-4} NEXT VALID true - CLOSE ITERATOR 0100000000087ffffffffffffffc61626300 -> 0101 VALID true - KEY 0100000000088000000000000001616263 100c - PK testpb.ExampleTable 8/1/abc -> {"u32":8,"u64":12,"str":"abc","i64":1} KEY 0100000000088000000000000001616263 100c PK testpb.ExampleTable 8/1/abc -> {"u32":8,"u64":12,"str":"abc","i64":1} NEXT VALID true - KEY 0100000000088000000000000001616264 100a - PK testpb.ExampleTable 8/1/abd -> {"u32":8,"u64":10,"str":"abd","i64":1} KEY 0100000000088000000000000001616264 100a PK testpb.ExampleTable 8/1/abd -> {"u32":8,"u64":10,"str":"abd","i64":1} NEXT VALID false - CLOSE ITERATOR 0100 <- 0101 VALID true - KEY 0100000000088000000000000001616264 100a - PK testpb.ExampleTable 8/1/abd -> {"u32":8,"u64":10,"str":"abd","i64":1} KEY 0100000000088000000000000001616264 100a PK testpb.ExampleTable 8/1/abd -> {"u32":8,"u64":10,"str":"abd","i64":1} NEXT @@ -409,11 +470,8 @@ ITERATOR 0100 <- 0101 VALID true NEXT VALID false - CLOSE ITERATOR 0100 <- 0100000000088000000000000001616263 VALID true - KEY 0100000000087ffffffffffffffc616263 100b - PK testpb.ExampleTable 8/-4/abc -> {"u32":8,"u64":11,"str":"abc","i64":-4} KEY 0100000000087ffffffffffffffc616263 100b PK testpb.ExampleTable 8/-4/abc -> {"u32":8,"u64":11,"str":"abc","i64":-4} NEXT @@ -424,34 +482,24 @@ ITERATOR 0100 <- 0100000000088000000000000001616263 PK testpb.ExampleTable 7/-1/abe -> {"u32":7,"u64":11,"str":"abe","i64":-1} NEXT VALID true - CLOSE ITERATOR 0100000000047fffffffffffffff616263 -> 0100000000077ffffffffffffffe61626500 VALID true - KEY 0100000000047fffffffffffffff616263 1008 - PK testpb.ExampleTable 4/-1/abc -> {"u32":4,"u64":8,"str":"abc","i64":-1} KEY 0100000000047fffffffffffffff616263 1008 PK testpb.ExampleTable 4/-1/abc -> {"u32":4,"u64":8,"str":"abc","i64":-1} NEXT VALID true - KEY 0100000000057ffffffffffffffe616264 1008 - PK testpb.ExampleTable 5/-2/abd -> {"u32":5,"u64":8,"str":"abd","i64":-2} KEY 0100000000057ffffffffffffffe616264 1008 PK testpb.ExampleTable 5/-2/abd -> {"u32":5,"u64":8,"str":"abd","i64":-2} NEXT VALID true - KEY 0100000000057ffffffffffffffe616265 1009 - PK testpb.ExampleTable 5/-2/abe -> {"u32":5,"u64":9,"str":"abe","i64":-2} KEY 0100000000057ffffffffffffffe616265 1009 PK testpb.ExampleTable 5/-2/abe -> {"u32":5,"u64":9,"str":"abe","i64":-2} NEXT VALID true - KEY 0100000000077ffffffffffffffe616265 100a - PK testpb.ExampleTable 7/-2/abe -> {"u32":7,"u64":10,"str":"abe","i64":-2} KEY 0100000000077ffffffffffffffe616265 100a PK testpb.ExampleTable 7/-2/abe -> {"u32":7,"u64":10,"str":"abe","i64":-2} NEXT VALID false - CLOSE ITERATOR 0100 -> 0101 VALID true NEXT @@ -460,8 +508,6 @@ ITERATOR 0100 -> 0101 VALID true NEXT VALID true - KEY 0100000000057ffffffffffffffe616264 1008 - PK testpb.ExampleTable 5/-2/abd -> {"u32":5,"u64":8,"str":"abd","i64":-2} KEY 0100000000057ffffffffffffffe616264 1008 PK testpb.ExampleTable 5/-2/abd -> {"u32":5,"u64":8,"str":"abd","i64":-2} NEXT @@ -482,7 +528,6 @@ ITERATOR 0100 -> 0101 VALID true NEXT VALID false - CLOSE ITERATOR 0100 <- 0101 VALID true NEXT @@ -495,14 +540,10 @@ ITERATOR 0100 <- 0101 VALID true NEXT VALID true - KEY 0100000000057ffffffffffffffe616265 1009 - PK testpb.ExampleTable 5/-2/abe -> {"u32":5,"u64":9,"str":"abe","i64":-2} KEY 0100000000057ffffffffffffffe616265 1009 PK testpb.ExampleTable 5/-2/abe -> {"u32":5,"u64":9,"str":"abe","i64":-2} NEXT VALID true - KEY 0100000000057ffffffffffffffe616264 1008 - PK testpb.ExampleTable 5/-2/abd -> {"u32":5,"u64":8,"str":"abd","i64":-2} KEY 0100000000057ffffffffffffffe616264 1008 PK testpb.ExampleTable 5/-2/abd -> {"u32":5,"u64":8,"str":"abd","i64":-2} NEXT @@ -517,30 +558,6 @@ ITERATOR 0100 <- 0101 VALID true NEXT VALID false - CLOSE -ITERATOR 0100 -> 0101 - VALID true - NEXT - VALID true - NEXT - VALID true - NEXT - VALID true - NEXT - VALID true - NEXT - VALID true - NEXT - VALID true - NEXT - VALID true - NEXT - VALID true - NEXT - VALID true - NEXT - VALID false - CLOSE ITERATOR 0100 -> 0101 VALID true NEXT @@ -563,7 +580,6 @@ ITERATOR 0100 -> 0101 VALID true NEXT VALID false - CLOSE GET 0100000000047ffffffffffffffe616263 1007 PK testpb.ExampleTable 4/-2/abc -> {"u32":4,"u64":7,"str":"abc","i64":-2} ORM UPDATE testpb.ExampleTable {"u32":4,"u64":7,"str":"abc","i64":-2} -> {"u32":4,"u64":14,"str":"abc","bz":"abc","i64":-2} diff --git a/orm/model/ormtable/unique.go b/orm/model/ormtable/unique.go index 3437f6a6a106..a3c671a16e99 100644 --- a/orm/model/ormtable/unique.go +++ b/orm/model/ormtable/unique.go @@ -24,13 +24,22 @@ type uniqueKeyIndex struct { getReadBackend func(context.Context) (ReadBackend, error) } -func (u uniqueKeyIndex) Iterator(ctx context.Context, options ...ormlist.Option) (Iterator, error) { +func (u uniqueKeyIndex) List(ctx context.Context, prefixKey []interface{}, options ...ormlist.Option) (Iterator, error) { backend, err := u.getReadBackend(ctx) if err != nil { return nil, err } - return iterator(backend, backend.IndexStoreReader(), u, u.GetKeyCodec(), options) + return prefixIterator(backend.IndexStoreReader(), backend, u, u.GetKeyCodec(), prefixKey, options) +} + +func (u uniqueKeyIndex) ListRange(ctx context.Context, from, to []interface{}, options ...ormlist.Option) (Iterator, error) { + backend, err := u.getReadBackend(ctx) + if err != nil { + return nil, err + } + + return rangeIterator(backend.IndexStoreReader(), backend, u, u.GetKeyCodec(), from, to, options) } func (u uniqueKeyIndex) doNotImplement() {} @@ -187,8 +196,8 @@ func (u uniqueKeyIndex) readValueFromIndexKey(store ReadBackend, primaryKey []pr return nil } -func (p uniqueKeyIndex) Fields() string { - return p.fields.String() +func (u uniqueKeyIndex) Fields() string { + return u.fields.String() } var _ indexer = &uniqueKeyIndex{} diff --git a/proto/cosmos/base/query/v1beta1/pagination.proto b/proto/cosmos/base/query/v1beta1/pagination.proto index cd5eb066d399..0a368144abaf 100644 --- a/proto/cosmos/base/query/v1beta1/pagination.proto +++ b/proto/cosmos/base/query/v1beta1/pagination.proto @@ -46,7 +46,8 @@ message PageRequest { // } message PageResponse { // next_key is the key to be passed to PageRequest.key to - // query the next page most efficiently + // query the next page most efficiently. It will be empty if + // there are no more results. bytes next_key = 1; // total is total number of results available if PageRequest.count_total diff --git a/types/query/pagination.pb.go b/types/query/pagination.pb.go index c631ecfb1eaf..3e7cc5486220 100644 --- a/types/query/pagination.pb.go +++ b/types/query/pagination.pb.go @@ -129,7 +129,8 @@ func (m *PageRequest) GetReverse() bool { // } type PageResponse struct { // next_key is the key to be passed to PageRequest.key to - // query the next page most efficiently + // query the next page most efficiently. It will be empty if + // there are no more results. NextKey []byte `protobuf:"bytes,1,opt,name=next_key,json=nextKey,proto3" json:"next_key,omitempty"` // total is total number of results available if PageRequest.count_total // was set, its value is undefined otherwise