Skip to content

Commit

Permalink
feat(orm)!: tweak API to allow pagination in generated code (#11079)
Browse files Browse the repository at this point in the history
* WIP on fixing issues and expanding tests with codegen

* fixes

* fix tests

* feat(orm)!: pagination codegen and API cleanup

* WIP on refactoring list/paginate

* fixes

* fixes

* tests pass

* codegen

* codegen

* Update orm/model/ormtable/index.go

Co-authored-by: Marko <[email protected]>

* codegen

* Update orm/model/ormtable/filter.go

Co-authored-by: Tyler <[email protected]>

* revert

Co-authored-by: Marko <[email protected]>
Co-authored-by: Tyler <[email protected]>
  • Loading branch information
3 people authored Feb 2, 2022
1 parent 894969b commit 5126ec3
Show file tree
Hide file tree
Showing 20 changed files with 483 additions and 470 deletions.
10 changes: 6 additions & 4 deletions api/cosmos/base/query/v1beta1/pagination.pulsar.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 6 additions & 4 deletions api/cosmos/tx/signing/v1beta1/signing.pulsar.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 1 addition & 2 deletions orm/internal/codegen/codegen.go
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down
6 changes: 2 additions & 4 deletions orm/internal/codegen/table.go
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
17 changes: 9 additions & 8 deletions orm/internal/listinternal/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}

Expand Down
12 changes: 4 additions & 8 deletions orm/internal/testpb/bank.cosmos_orm.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

12 changes: 4 additions & 8 deletions orm/internal/testpb/test_schema.cosmos_orm.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

79 changes: 30 additions & 49 deletions orm/model/ormlist/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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 {
Expand All @@ -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
28 changes: 28 additions & 0 deletions orm/model/ormtable/filter.go
Original file line number Diff line number Diff line change
@@ -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
}
23 changes: 21 additions & 2 deletions orm/model/ormtable/index.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
13 changes: 11 additions & 2 deletions orm/model/ormtable/index_impl.go
Original file line number Diff line number Diff line change
Expand Up @@ -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{}
Expand Down
Loading

0 comments on commit 5126ec3

Please sign in to comment.