From 6c81e0ec3e9f821ba3e8dfd6c31861ecb51555b9 Mon Sep 17 00:00:00 2001 From: MD Aleem <72057206+aleem1314@users.noreply.github.com> Date: Tue, 23 Mar 2021 17:28:20 +0800 Subject: [PATCH] add reverse iteration to pagination (#8875) * WIP * add tests * add tests * fix lint * fix pagination * add proto message doc * fix filtered_pagination * fix test * cleanup * add reverse flag to pagination * changelog * Update client/flags/flags.go * Update CHANGELOG.md Co-authored-by: Alessio Treglia Co-authored-by: Federico Kunze <31522760+fedekunze@users.noreply.github.com> (cherry picked from commit a78f777ea8527438cb90b80f7aab65c356523c4d) --- client/flags/flags.go | 2 + client/utils.go | 2 + .../base/query/v1beta1/pagination.proto | 3 + types/query/filtered_pagination.go | 5 +- types/query/filtered_pagination_test.go | 82 +++++++++++- types/query/pagination.go | 23 +++- types/query/pagination.pb.go | 79 ++++++++--- types/query/pagination_test.go | 124 ++++++++++++++++++ 8 files changed, 297 insertions(+), 23 deletions(-) diff --git a/client/flags/flags.go b/client/flags/flags.go index af774bcf1ee0..e162673418c1 100644 --- a/client/flags/flags.go +++ b/client/flags/flags.go @@ -70,6 +70,7 @@ const ( FlagCountTotal = "count-total" FlagTimeoutHeight = "timeout-height" FlagKeyAlgorithm = "algo" + FlagReverse = "reverse" // Tendermint logging flags FlagLogLevel = "log_level" @@ -124,6 +125,7 @@ func AddPaginationFlagsToCmd(cmd *cobra.Command, query string) { cmd.Flags().Uint64(FlagOffset, 0, fmt.Sprintf("pagination offset of %s to query for", query)) cmd.Flags().Uint64(FlagLimit, 100, fmt.Sprintf("pagination limit of %s to query for", query)) cmd.Flags().Bool(FlagCountTotal, false, fmt.Sprintf("count total number of records in %s to query for", query)) + cmd.Flags().Bool(FlagReverse, false, "results are sorted in descending order") } // GasSetting encapsulates the possible values passed through the --gas flag. diff --git a/client/utils.go b/client/utils.go index b833d4c1f0c5..944471bc90bd 100644 --- a/client/utils.go +++ b/client/utils.go @@ -52,6 +52,7 @@ func ReadPageRequest(flagSet *pflag.FlagSet) (*query.PageRequest, error) { limit, _ := flagSet.GetUint64(flags.FlagLimit) countTotal, _ := flagSet.GetBool(flags.FlagCountTotal) page, _ := flagSet.GetUint64(flags.FlagPage) + reverse, _ := flagSet.GetBool(flags.FlagReverse) if page > 1 && offset > 0 { return nil, sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "page and offset cannot be used together") @@ -66,6 +67,7 @@ func ReadPageRequest(flagSet *pflag.FlagSet) (*query.PageRequest, error) { Offset: offset, Limit: limit, CountTotal: countTotal, + Reverse: reverse, }, nil } diff --git a/proto/cosmos/base/query/v1beta1/pagination.proto b/proto/cosmos/base/query/v1beta1/pagination.proto index 2a8cbccedd87..7ee60f18d517 100644 --- a/proto/cosmos/base/query/v1beta1/pagination.proto +++ b/proto/cosmos/base/query/v1beta1/pagination.proto @@ -30,6 +30,9 @@ message PageRequest { // count_total is only respected when offset is used. It is ignored when key // is set. bool count_total = 4; + + // reverse is set to true indicates that, results to be returned in the descending order. + bool reverse = 5; } // PageResponse is to be embedded in gRPC response messages where the diff --git a/types/query/filtered_pagination.go b/types/query/filtered_pagination.go index 0ab29a4acded..2856d513f225 100644 --- a/types/query/filtered_pagination.go +++ b/types/query/filtered_pagination.go @@ -29,6 +29,7 @@ func FilteredPaginate( key := pageRequest.Key limit := pageRequest.Limit countTotal := pageRequest.CountTotal + reverse := pageRequest.Reverse if offset > 0 && key != nil { return nil, fmt.Errorf("invalid request, either offset or key is expected, got both") @@ -42,7 +43,7 @@ func FilteredPaginate( } if len(key) != 0 { - iterator := prefixStore.Iterator(key, nil) + iterator := getIterator(prefixStore, key, reverse) defer iterator.Close() var numHits uint64 @@ -73,7 +74,7 @@ func FilteredPaginate( }, nil } - iterator := prefixStore.Iterator(nil, nil) + iterator := getIterator(prefixStore, nil, reverse) defer iterator.Close() end := offset + limit diff --git a/types/query/filtered_pagination_test.go b/types/query/filtered_pagination_test.go index 7ce08bc9d77b..537d146f8b19 100644 --- a/types/query/filtered_pagination_test.go +++ b/types/query/filtered_pagination_test.go @@ -2,7 +2,6 @@ package query_test import ( "fmt" - "github.com/cosmos/cosmos-sdk/codec" "github.com/cosmos/cosmos-sdk/store/prefix" sdk "github.com/cosmos/cosmos-sdk/types" @@ -87,6 +86,87 @@ func (s *paginationTestSuite) TestFilteredPaginations() { s.Require().LessOrEqual(len(balances), 2) } +func (s *paginationTestSuite) TestReverseFilteredPaginations() { + app, ctx, appCodec := setupTest() + + var balances sdk.Coins + for i := 0; i < numBalances; i++ { + denom := fmt.Sprintf("foo%ddenom", i) + balances = append(balances, sdk.NewInt64Coin(denom, 100)) + } + + for i := 0; i < 10; i++ { + denom := fmt.Sprintf("test%ddenom", i) + balances = append(balances, sdk.NewInt64Coin(denom, 250)) + } + + balances = balances.Sort() + addr1 := sdk.AccAddress([]byte("addr1")) + acc1 := app.AccountKeeper.NewAccountWithAddress(ctx, addr1) + app.AccountKeeper.SetAccount(ctx, acc1) + s.Require().NoError(app.BankKeeper.SetBalances(ctx, addr1, balances)) + store := ctx.KVStore(app.GetKey(authtypes.StoreKey)) + + // verify pagination with limit > total values + pageReq := &query.PageRequest{Key: nil, Limit: 5, CountTotal: true, Reverse: true} + balns, res, err := execFilterPaginate(store, pageReq, appCodec) + s.Require().NoError(err) + s.Require().NotNil(res) + s.Require().Equal(5, len(balns)) + + s.T().Log("verify empty request") + balns, res, err = execFilterPaginate(store, nil, appCodec) + s.Require().NoError(err) + s.Require().NotNil(res) + s.Require().Equal(10, len(balns)) + s.Require().Equal(uint64(10), res.Total) + s.Require().Nil(res.NextKey) + + s.T().Log("verify default limit") + pageReq = &query.PageRequest{Reverse: true} + balns, res, err = execFilterPaginate(store, pageReq, appCodec) + s.Require().NoError(err) + s.Require().NotNil(res) + s.Require().Equal(10, len(balns)) + s.Require().Equal(uint64(10), res.Total) + + s.T().Log("verify nextKey is returned if there are more results") + pageReq = &query.PageRequest{Limit: 2, CountTotal: true, Reverse: true} + balns, res, err = execFilterPaginate(store, pageReq, appCodec) + s.Require().NoError(err) + s.Require().NotNil(res) + s.Require().Equal(2, len(balns)) + s.Require().NotNil(res.NextKey) + s.Require().Equal(string(res.NextKey), fmt.Sprintf("test7denom")) + s.Require().Equal(uint64(10), res.Total) + + s.T().Log("verify both key and offset can't be given") + pageReq = &query.PageRequest{Key: res.NextKey, Limit: 1, Offset: 2, Reverse: true} + _, _, err = execFilterPaginate(store, pageReq, appCodec) + s.Require().Error(err) + + s.T().Log("use nextKey for query and reverse true") + pageReq = &query.PageRequest{Key: res.NextKey, Limit: 2, Reverse: true} + balns, res, err = execFilterPaginate(store, pageReq, appCodec) + s.Require().NoError(err) + s.Require().NotNil(res) + s.Require().Equal(2, len(balns)) + s.Require().NotNil(res.NextKey) + s.Require().Equal(string(res.NextKey), fmt.Sprintf("test5denom")) + + s.T().Log("verify last page records, nextKey for query and reverse true") + pageReq = &query.PageRequest{Key: res.NextKey, Reverse: true} + balns, res, err = execFilterPaginate(store, pageReq, appCodec) + s.Require().NoError(err) + s.Require().NotNil(res) + s.Require().Equal(6, len(balns)) + s.Require().Nil(res.NextKey) + + s.T().Log("verify Reverse pagination returns valid result") + s.Require().Equal(balances[235:241].String(), balns.Sort().String()) + +} + func ExampleFilteredPaginate() { app, ctx, appCodec := setupTest() diff --git a/types/query/pagination.go b/types/query/pagination.go index 9a0999fe61d6..868571f861cf 100644 --- a/types/query/pagination.go +++ b/types/query/pagination.go @@ -7,6 +7,7 @@ import ( "google.golang.org/grpc/status" "github.com/cosmos/cosmos-sdk/store/types" + db "github.com/tendermint/tm-db" ) // DefaultLimit is the default `limit` for queries @@ -54,6 +55,7 @@ func Paginate( key := pageRequest.Key limit := pageRequest.Limit countTotal := pageRequest.CountTotal + reverse := pageRequest.Reverse if offset > 0 && key != nil { return nil, fmt.Errorf("invalid request, either offset or key is expected, got both") @@ -67,13 +69,14 @@ func Paginate( } if len(key) != 0 { - iterator := prefixStore.Iterator(key, nil) + iterator := getIterator(prefixStore, key, reverse) defer iterator.Close() var count uint64 var nextKey []byte for ; iterator.Valid(); iterator.Next() { + if count == limit { nextKey = iterator.Key() break @@ -94,7 +97,7 @@ func Paginate( }, nil } - iterator := prefixStore.Iterator(nil, nil) + iterator := getIterator(prefixStore, nil, reverse) defer iterator.Close() end := offset + limit @@ -132,3 +135,19 @@ func Paginate( return res, nil } + +func getIterator(prefixStore types.KVStore, start []byte, reverse bool) db.Iterator { + if reverse { + var end []byte + if start != nil { + itr := prefixStore.Iterator(start, nil) + defer itr.Close() + if itr.Valid() { + itr.Next() + end = itr.Key() + } + } + return prefixStore.ReverseIterator(nil, end) + } + return prefixStore.Iterator(start, nil) +} diff --git a/types/query/pagination.pb.go b/types/query/pagination.pb.go index b27db91746a5..cf1be5b980a8 100644 --- a/types/query/pagination.pb.go +++ b/types/query/pagination.pb.go @@ -46,6 +46,8 @@ type PageRequest struct { // count_total is only respected when offset is used. It is ignored when key // is set. CountTotal bool `protobuf:"varint,4,opt,name=count_total,json=countTotal,proto3" json:"count_total,omitempty"` + // reverse is set to true indicates that, results to be returned in the descending order. + Reverse bool `protobuf:"varint,5,opt,name=reverse,proto3" json:"reverse,omitempty"` } func (m *PageRequest) Reset() { *m = PageRequest{} } @@ -109,6 +111,13 @@ func (m *PageRequest) GetCountTotal() bool { return false } +func (m *PageRequest) GetReverse() bool { + if m != nil { + return m.Reverse + } + return false +} + // PageResponse is to be embedded in gRPC response messages where the // corresponding request message has used PageRequest. // @@ -182,24 +191,25 @@ func init() { } var fileDescriptor_53d6d609fe6828af = []byte{ - // 266 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x44, 0x90, 0xc1, 0x4a, 0xc3, 0x40, - 0x14, 0x45, 0x33, 0xb6, 0xd6, 0x32, 0xed, 0x42, 0x06, 0x91, 0x74, 0x33, 0x86, 0xae, 0x82, 0x60, - 0x86, 0xe2, 0x07, 0x08, 0xdd, 0xba, 0x91, 0xe0, 0xca, 0x4d, 0x99, 0xc4, 0xd7, 0x18, 0xda, 0xcc, - 0x4b, 0x3b, 0x2f, 0x62, 0xfe, 0xc2, 0xcf, 0x72, 0xd9, 0xa5, 0x4b, 0x49, 0x7e, 0x44, 0x92, 0x09, - 0x74, 0x35, 0x73, 0x2f, 0x87, 0x77, 0xe0, 0xf2, 0xfb, 0x14, 0x6d, 0x81, 0x56, 0x25, 0xda, 0x82, - 0x3a, 0x54, 0x70, 0xac, 0xd5, 0xe7, 0x2a, 0x01, 0xd2, 0x2b, 0x55, 0xea, 0x2c, 0x37, 0x9a, 0x72, - 0x34, 0x51, 0x79, 0x44, 0x42, 0xb1, 0x70, 0x6c, 0xd4, 0xb1, 0x51, 0xcf, 0x46, 0x03, 0xbb, 0x34, - 0x7c, 0xf6, 0xa2, 0x33, 0x88, 0xe1, 0x50, 0x81, 0x25, 0x71, 0xcd, 0x47, 0x3b, 0xa8, 0x7d, 0x16, - 0xb0, 0x70, 0x1e, 0x77, 0x5f, 0x71, 0xcb, 0x27, 0xb8, 0xdd, 0x5a, 0x20, 0xff, 0x22, 0x60, 0xe1, - 0x38, 0x1e, 0x92, 0xb8, 0xe1, 0x97, 0xfb, 0xbc, 0xc8, 0xc9, 0x1f, 0xf5, 0xb5, 0x0b, 0xe2, 0x8e, - 0xcf, 0x52, 0xac, 0x0c, 0x6d, 0x08, 0x49, 0xef, 0xfd, 0x71, 0xc0, 0xc2, 0x69, 0xcc, 0xfb, 0xea, - 0xb5, 0x6b, 0x96, 0x4f, 0x7c, 0xee, 0x7c, 0xb6, 0x44, 0x63, 0x41, 0x2c, 0xf8, 0xd4, 0xc0, 0x17, - 0x6d, 0xce, 0xd6, 0xab, 0x2e, 0x3f, 0x43, 0xdd, 0x19, 0xdc, 0x15, 0x27, 0x76, 0x61, 0xbd, 0xfe, - 0x69, 0x24, 0x3b, 0x35, 0x92, 0xfd, 0x35, 0x92, 0x7d, 0xb7, 0xd2, 0x3b, 0xb5, 0xd2, 0xfb, 0x6d, - 0xa5, 0xf7, 0x16, 0x66, 0x39, 0x7d, 0x54, 0x49, 0x94, 0x62, 0xa1, 0x86, 0x71, 0xdc, 0xf3, 0x60, - 0xdf, 0x77, 0x8a, 0xea, 0x12, 0xac, 0x1b, 0x2a, 0x99, 0xf4, 0xb3, 0x3c, 0xfe, 0x07, 0x00, 0x00, - 0xff, 0xff, 0xb5, 0x65, 0x82, 0x18, 0x44, 0x01, 0x00, 0x00, + // 282 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x44, 0x90, 0xbd, 0x4e, 0xc3, 0x30, + 0x14, 0x85, 0x6b, 0xfa, 0x2b, 0xb7, 0x03, 0xb2, 0x10, 0x72, 0x17, 0x13, 0x75, 0x8a, 0x90, 0x88, + 0x55, 0xf1, 0x00, 0x48, 0x5d, 0x59, 0x50, 0xc4, 0xc4, 0x52, 0x39, 0xe1, 0x36, 0x44, 0x6d, 0xe2, + 0x34, 0xbe, 0xa9, 0xc8, 0x1b, 0x30, 0xf2, 0x58, 0x8c, 0x1d, 0x19, 0x51, 0xf2, 0x22, 0x28, 0x76, + 0x10, 0x93, 0xfd, 0x9d, 0x7b, 0x74, 0xef, 0xd1, 0xa1, 0xb7, 0xb1, 0x36, 0x99, 0x36, 0x32, 0x52, + 0x06, 0xe4, 0xb1, 0x82, 0xb2, 0x96, 0xa7, 0x75, 0x04, 0xa8, 0xd6, 0xb2, 0x50, 0x49, 0x9a, 0x2b, + 0x4c, 0x75, 0x1e, 0x14, 0xa5, 0x46, 0xcd, 0x96, 0xce, 0x1b, 0x74, 0xde, 0xc0, 0x7a, 0x83, 0xde, + 0xbb, 0xfa, 0x20, 0x74, 0xfe, 0xa4, 0x12, 0x08, 0xe1, 0x58, 0x81, 0x41, 0x76, 0x49, 0x87, 0x7b, + 0xa8, 0x39, 0xf1, 0x88, 0xbf, 0x08, 0xbb, 0x2f, 0xbb, 0xa6, 0x13, 0xbd, 0xdb, 0x19, 0x40, 0x7e, + 0xe1, 0x11, 0x7f, 0x14, 0xf6, 0xc4, 0xae, 0xe8, 0xf8, 0x90, 0x66, 0x29, 0xf2, 0xa1, 0x95, 0x1d, + 0xb0, 0x1b, 0x3a, 0x8f, 0x75, 0x95, 0xe3, 0x16, 0x35, 0xaa, 0x03, 0x1f, 0x79, 0xc4, 0x9f, 0x85, + 0xd4, 0x4a, 0xcf, 0x9d, 0xc2, 0x38, 0x9d, 0x96, 0x70, 0x82, 0xd2, 0x00, 0x1f, 0xdb, 0xe1, 0x1f, + 0xae, 0x1e, 0xe8, 0xc2, 0x25, 0x31, 0x85, 0xce, 0x0d, 0xb0, 0x25, 0x9d, 0xe5, 0xf0, 0x8e, 0xdb, + 0xff, 0x3c, 0xd3, 0x8e, 0x1f, 0xa1, 0xee, 0x6e, 0xbb, 0xfd, 0x2e, 0x92, 0x83, 0xcd, 0xe6, 0xab, + 0x11, 0xe4, 0xdc, 0x08, 0xf2, 0xd3, 0x08, 0xf2, 0xd9, 0x8a, 0xc1, 0xb9, 0x15, 0x83, 0xef, 0x56, + 0x0c, 0x5e, 0xfc, 0x24, 0xc5, 0xb7, 0x2a, 0x0a, 0x62, 0x9d, 0xc9, 0xbe, 0x37, 0xf7, 0xdc, 0x99, + 0xd7, 0xbd, 0xc4, 0xba, 0x00, 0xe3, 0x3a, 0x8c, 0x26, 0xb6, 0xb1, 0xfb, 0xdf, 0x00, 0x00, 0x00, + 0xff, 0xff, 0x3d, 0x43, 0x85, 0xf7, 0x5f, 0x01, 0x00, 0x00, } func (m *PageRequest) Marshal() (dAtA []byte, err error) { @@ -222,6 +232,16 @@ func (m *PageRequest) MarshalToSizedBuffer(dAtA []byte) (int, error) { _ = i var l int _ = l + if m.Reverse { + i-- + if m.Reverse { + dAtA[i] = 1 + } else { + dAtA[i] = 0 + } + i-- + dAtA[i] = 0x28 + } if m.CountTotal { i-- if m.CountTotal { @@ -317,6 +337,9 @@ func (m *PageRequest) Size() (n int) { if m.CountTotal { n += 2 } + if m.Reverse { + n += 2 + } return n } @@ -463,6 +486,26 @@ func (m *PageRequest) Unmarshal(dAtA []byte) error { } } m.CountTotal = bool(v != 0) + case 5: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Reverse", wireType) + } + var v int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowPagination + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + v |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + m.Reverse = bool(v != 0) default: iNdEx = preIndex skippy, err := skipPagination(dAtA[iNdEx:]) diff --git a/types/query/pagination_test.go b/types/query/pagination_test.go index b7277cecadd3..b8bd19cffa35 100644 --- a/types/query/pagination_test.go +++ b/types/query/pagination_test.go @@ -170,6 +170,130 @@ func (s *paginationTestSuite) TestPagination() { s.Require().Nil(res.Pagination.NextKey) } +func (s *paginationTestSuite) TestReversePagination() { + app, ctx, _ := setupTest() + queryHelper := baseapp.NewQueryServerTestHelper(ctx, app.InterfaceRegistry()) + types.RegisterQueryServer(queryHelper, app.BankKeeper) + queryClient := types.NewQueryClient(queryHelper) + + var balances sdk.Coins + + for i := 0; i < numBalances; i++ { + denom := fmt.Sprintf("foo%ddenom", i) + balances = append(balances, sdk.NewInt64Coin(denom, 100)) + } + + balances = balances.Sort() + addr1 := sdk.AccAddress(secp256k1.GenPrivKey().PubKey().Address()) + acc1 := app.AccountKeeper.NewAccountWithAddress(ctx, addr1) + app.AccountKeeper.SetAccount(ctx, acc1) + s.Require().NoError(app.BankKeeper.SetBalances(ctx, addr1, balances)) + + s.T().Log("verify paginate with custom limit and countTotal, Reverse false") + pageReq := &query.PageRequest{Limit: 2, CountTotal: true, Reverse: true, Key: nil} + request := types.NewQueryAllBalancesRequest(addr1, pageReq) + res1, err := queryClient.AllBalances(gocontext.Background(), request) + s.Require().NoError(err) + s.Require().Equal(res1.Balances.Len(), 2) + s.Require().NotNil(res1.Pagination.NextKey) + + s.T().Log("verify paginate with custom limit and countTotal, Reverse false") + pageReq = &query.PageRequest{Limit: 150} + request = types.NewQueryAllBalancesRequest(addr1, pageReq) + res1, err = queryClient.AllBalances(gocontext.Background(), request) + s.Require().NoError(err) + s.Require().Equal(res1.Balances.Len(), 150) + s.Require().NotNil(res1.Pagination.NextKey) + s.Require().Equal(res1.Pagination.Total, uint64(0)) + + s.T().Log("verify paginate with custom limit, key and Reverse true") + pageReq = &query.PageRequest{Limit: defaultLimit, Reverse: true} + request = types.NewQueryAllBalancesRequest(addr1, pageReq) + res, err := queryClient.AllBalances(gocontext.Background(), request) + s.Require().NoError(err) + s.Require().Equal(res.Balances.Len(), defaultLimit) + s.Require().NotNil(res.Pagination.NextKey) + s.Require().Equal(res.Pagination.Total, uint64(0)) + + s.T().Log("verify paginate with custom limit, key and Reverse true") + pageReq = &query.PageRequest{Offset: 100, Limit: defaultLimit, Reverse: true} + request = types.NewQueryAllBalancesRequest(addr1, pageReq) + res, err = queryClient.AllBalances(gocontext.Background(), request) + s.Require().NoError(err) + s.Require().Equal(res.Balances.Len(), defaultLimit) + s.Require().NotNil(res.Pagination.NextKey) + s.Require().Equal(res.Pagination.Total, uint64(0)) + + s.T().Log("verify paginate for last page, Reverse true") + pageReq = &query.PageRequest{Offset: 200, Limit: defaultLimit, Reverse: true} + request = types.NewQueryAllBalancesRequest(addr1, pageReq) + res, err = queryClient.AllBalances(gocontext.Background(), request) + s.Require().NoError(err) + s.Require().Equal(res.Balances.Len(), lastPageRecords) + s.Require().Nil(res.Pagination.NextKey) + s.Require().Equal(res.Pagination.Total, uint64(0)) + + s.T().Log("verify page request with limit > defaultLimit, returns less or equal to `limit` records") + pageReq = &query.PageRequest{Limit: overLimit, Reverse: true} + request = types.NewQueryAllBalancesRequest(addr1, pageReq) + res, err = queryClient.AllBalances(gocontext.Background(), request) + s.Require().NoError(err) + s.Require().Equal(res.Pagination.Total, uint64(0)) + s.Require().NotNil(res.Pagination.NextKey) + s.Require().LessOrEqual(res.Balances.Len(), overLimit) + + s.T().Log("verify paginate with custom limit, key, countTotal false and Reverse true") + pageReq = &query.PageRequest{Key: res1.Pagination.NextKey, Limit: 50, Reverse: true} + request = types.NewQueryAllBalancesRequest(addr1, pageReq) + res, err = queryClient.AllBalances(gocontext.Background(), request) + s.Require().NoError(err) + s.Require().Equal(res.Balances.Len(), 50) + s.Require().NotNil(res.Pagination.NextKey) + s.Require().Equal(res.Pagination.Total, uint64(0)) + + s.T().Log("verify Reverse pagination returns valid result") + s.Require().Equal(balances[101:151].String(), res.Balances.Sort().String()) + + s.T().Log("verify paginate with custom limit, key, countTotal false and Reverse true") + pageReq = &query.PageRequest{Key: res.Pagination.NextKey, Limit: 50, Reverse: true} + request = types.NewQueryAllBalancesRequest(addr1, pageReq) + res, err = queryClient.AllBalances(gocontext.Background(), request) + s.Require().NoError(err) + s.Require().Equal(res.Balances.Len(), 50) + s.Require().NotNil(res.Pagination.NextKey) + s.Require().Equal(res.Pagination.Total, uint64(0)) + + s.T().Log("verify Reverse pagination returns valid result") + s.Require().Equal(balances[51:101].String(), res.Balances.Sort().String()) + + s.T().Log("verify paginate for last page Reverse true") + pageReq = &query.PageRequest{Key: res.Pagination.NextKey, Limit: defaultLimit, Reverse: true} + request = types.NewQueryAllBalancesRequest(addr1, pageReq) + res, err = queryClient.AllBalances(gocontext.Background(), request) + s.Require().NoError(err) + s.Require().Equal(res.Balances.Len(), 51) + s.Require().Nil(res.Pagination.NextKey) + s.Require().Equal(res.Pagination.Total, uint64(0)) + + s.T().Log("verify Reverse pagination returns valid result") + s.Require().Equal(balances[0:51].String(), res.Balances.Sort().String()) + + s.T().Log("verify paginate with offset and key - error") + pageReq = &query.PageRequest{Key: res1.Pagination.NextKey, Offset: 100, Limit: defaultLimit, CountTotal: false} + request = types.NewQueryAllBalancesRequest(addr1, pageReq) + res, err = queryClient.AllBalances(gocontext.Background(), request) + s.Require().Error(err) + s.Require().Equal("rpc error: code = InvalidArgument desc = paginate: invalid request, either offset or key is expected, got both", err.Error()) + + s.T().Log("verify paginate with offset greater than total results") + pageReq = &query.PageRequest{Offset: 300, Limit: defaultLimit, CountTotal: false, Reverse: true} + request = types.NewQueryAllBalancesRequest(addr1, pageReq) + res, err = queryClient.AllBalances(gocontext.Background(), request) + s.Require().NoError(err) + s.Require().LessOrEqual(res.Balances.Len(), 0) + s.Require().Nil(res.Pagination.NextKey) +} + func ExamplePaginate() { app, ctx, _ := setupTest()