Documentation ¶
Index ¶
- Constants
- Variables
- func ParsePagination(pageReq *PageRequest) (page, limit int, err error)
- type PageRequest
- func (*PageRequest) Descriptor() ([]byte, []int)
- func (m *PageRequest) GetCountTotal() bool
- func (m *PageRequest) GetKey() []byte
- func (m *PageRequest) GetLimit() uint64
- func (m *PageRequest) GetOffset() uint64
- func (m *PageRequest) GetReverse() bool
- func (m *PageRequest) Marshal() (dAtA []byte, err error)
- func (m *PageRequest) MarshalTo(dAtA []byte) (int, error)
- func (m *PageRequest) MarshalToSizedBuffer(dAtA []byte) (int, error)
- func (*PageRequest) ProtoMessage()
- func (m *PageRequest) Reset()
- func (m *PageRequest) Size() (n int)
- func (m *PageRequest) String() string
- func (m *PageRequest) Unmarshal(dAtA []byte) error
- func (m *PageRequest) XXX_DiscardUnknown()
- func (m *PageRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error)
- func (m *PageRequest) XXX_Merge(src proto.Message)
- func (m *PageRequest) XXX_Size() int
- func (m *PageRequest) XXX_Unmarshal(b []byte) error
- type PageResponse
- func (*PageResponse) Descriptor() ([]byte, []int)
- func (m *PageResponse) GetNextKey() []byte
- func (m *PageResponse) GetTotal() uint64
- func (m *PageResponse) Marshal() (dAtA []byte, err error)
- func (m *PageResponse) MarshalTo(dAtA []byte) (int, error)
- func (m *PageResponse) MarshalToSizedBuffer(dAtA []byte) (int, error)
- func (*PageResponse) ProtoMessage()
- func (m *PageResponse) Reset()
- func (m *PageResponse) Size() (n int)
- func (m *PageResponse) String() string
- func (m *PageResponse) Unmarshal(dAtA []byte) error
- func (m *PageResponse) XXX_DiscardUnknown()
- func (m *PageResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error)
- func (m *PageResponse) XXX_Merge(src proto.Message)
- func (m *PageResponse) XXX_Size() int
- func (m *PageResponse) XXX_Unmarshal(b []byte) error
Examples ¶
Constants ¶
const DefaultLimit = 100
DefaultLimit is the default `limit` for queries if the `limit` is not supplied, paginate will use `DefaultLimit`
const MaxLimit = math.MaxUint64
MaxLimit is the maximum limit the paginate function can handle which equals the maximum value that can be stored in uint64
Variables ¶
Functions ¶
func ParsePagination ¶
func ParsePagination(pageReq *PageRequest) (page, limit int, err error)
ParsePagination validate PageRequest and returns page number & limit.
Types ¶
type PageRequest ¶
type PageRequest struct { // key is a value returned in PageResponse.next_key to begin // querying the next page most efficiently. Only one of offset or key // should be set. Key []byte `protobuf:"bytes,1,opt,name=key,proto3" json:"key,omitempty"` // offset is a numeric offset that can be used when key is unavailable. // It is less efficient than using key. Only one of offset or key should // be set. Offset uint64 `protobuf:"varint,2,opt,name=offset,proto3" json:"offset,omitempty"` // limit is the total number of results to be returned in the result page. // If left empty it will default to a value to be set by each app. Limit uint64 `protobuf:"varint,3,opt,name=limit,proto3" json:"limit,omitempty"` // count_total is set to true to indicate that the result set should include // a count of the total number of items available for pagination in UIs. // 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"` }
PageRequest is to be embedded in gRPC request messages for efficient pagination. Ex:
message SomeRequest { Foo some_parameter = 1; PageRequest pagination = 2; }
func (*PageRequest) Descriptor ¶
func (*PageRequest) Descriptor() ([]byte, []int)
func (*PageRequest) GetCountTotal ¶
func (m *PageRequest) GetCountTotal() bool
func (*PageRequest) GetKey ¶
func (m *PageRequest) GetKey() []byte
func (*PageRequest) GetLimit ¶
func (m *PageRequest) GetLimit() uint64
func (*PageRequest) GetOffset ¶
func (m *PageRequest) GetOffset() uint64
func (*PageRequest) GetReverse ¶
func (m *PageRequest) GetReverse() bool
func (*PageRequest) Marshal ¶
func (m *PageRequest) Marshal() (dAtA []byte, err error)
func (*PageRequest) MarshalToSizedBuffer ¶
func (m *PageRequest) MarshalToSizedBuffer(dAtA []byte) (int, error)
func (*PageRequest) ProtoMessage ¶
func (*PageRequest) ProtoMessage()
func (*PageRequest) Reset ¶
func (m *PageRequest) Reset()
func (*PageRequest) Size ¶
func (m *PageRequest) Size() (n int)
func (*PageRequest) String ¶
func (m *PageRequest) String() string
func (*PageRequest) Unmarshal ¶
func (m *PageRequest) Unmarshal(dAtA []byte) error
func (*PageRequest) XXX_DiscardUnknown ¶
func (m *PageRequest) XXX_DiscardUnknown()
func (*PageRequest) XXX_Marshal ¶
func (m *PageRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error)
func (*PageRequest) XXX_Merge ¶
func (m *PageRequest) XXX_Merge(src proto.Message)
func (*PageRequest) XXX_Size ¶
func (m *PageRequest) XXX_Size() int
func (*PageRequest) XXX_Unmarshal ¶
func (m *PageRequest) XXX_Unmarshal(b []byte) error
type PageResponse ¶
type PageResponse struct { // next_key is the key to be passed to PageRequest.key to // query the next page most efficiently 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 Total uint64 `protobuf:"varint,2,opt,name=total,proto3" json:"total,omitempty"` }
PageResponse is to be embedded in gRPC response messages where the corresponding request message has used PageRequest.
message SomeResponse { repeated Bar results = 1; PageResponse page = 2; }
func FilteredPaginate ¶
func FilteredPaginate( prefixStore types.KVStore, pageRequest *PageRequest, onResult func(key []byte, value []byte, accumulate bool) (bool, error), ) (*PageResponse, error)
FilteredPaginate does pagination of all the results in the PrefixStore based on the provided PageRequest. onResult should be used to do actual unmarshaling and filter the results. If key is provided, the pagination uses the optimized querying. If offset is used, the pagination uses lazy filtering i.e., searches through all the records. The accumulate parameter represents if the response is valid based on the offset given. It will be false for the results (filtered) < offset and true for `offset > accumulate <= end`. When accumulate is set to true the current result should be appended to the result set returned to the client.
Example ¶
package main import ( "fmt" "github.com/soominhyunwoo/chain-sdk/codec" "github.com/soominhyunwoo/chain-sdk/simapp" "github.com/soominhyunwoo/chain-sdk/store/prefix" sdk "github.com/soominhyunwoo/chain-sdk/types" "github.com/soominhyunwoo/chain-sdk/types/address" "github.com/soominhyunwoo/chain-sdk/types/query" "github.com/soominhyunwoo/chain-sdk/x/bank/types" ) var addr1 = sdk.AccAddress([]byte("addr1")) func (s *paginationTestSuite) TestFilteredPaginations() { 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 < 4; 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(simapp.FundAccount(app, ctx, addr1, balances)) store := ctx.KVStore(app.GetKey(types.StoreKey)) // verify pagination with limit > total values pageReq := &query.PageRequest{Key: nil, Limit: 5, CountTotal: true} balances, res, err := execFilterPaginate(store, pageReq, appCodec) s.Require().NoError(err) s.Require().NotNil(res) s.Require().Equal(4, len(balances)) s.T().Log("verify empty request") balances, res, err = execFilterPaginate(store, nil, appCodec) s.Require().NoError(err) s.Require().NotNil(res) s.Require().Equal(4, len(balances)) s.Require().Equal(uint64(4), res.Total) s.Require().Nil(res.NextKey) s.T().Log("verify nextKey is returned if there are more results") pageReq = &query.PageRequest{Key: nil, Limit: 2, CountTotal: true} balances, res, err = execFilterPaginate(store, pageReq, appCodec) s.Require().NoError(err) s.Require().NotNil(res) s.Require().Equal(2, len(balances)) s.Require().NotNil(res.NextKey) s.Require().Equal(string(res.NextKey), fmt.Sprintf("test2denom")) s.Require().Equal(uint64(4), res.Total) s.T().Log("verify both key and offset can't be given") pageReq = &query.PageRequest{Key: res.NextKey, Limit: 1, Offset: 2, CountTotal: true} _, _, err = execFilterPaginate(store, pageReq, appCodec) s.Require().Error(err) s.T().Log("use nextKey for query") pageReq = &query.PageRequest{Key: res.NextKey, Limit: 2, CountTotal: true} balances, res, err = execFilterPaginate(store, pageReq, appCodec) s.Require().NoError(err) s.Require().NotNil(res) s.Require().Equal(2, len(balances)) s.Require().Nil(res.NextKey) s.T().Log("verify default limit") pageReq = &query.PageRequest{Key: nil, Limit: 0} balances, res, err = execFilterPaginate(store, pageReq, appCodec) s.Require().NoError(err) s.Require().NotNil(res) s.Require().Equal(4, len(balances)) s.Require().Equal(uint64(4), res.Total) s.T().Log("verify with offset") pageReq = &query.PageRequest{Offset: 2, Limit: 2} balances, res, err = execFilterPaginate(store, pageReq, appCodec) s.Require().NoError(err) s.Require().NotNil(res) 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(simapp.FundAccount(app, ctx, addr1, balances)) store := ctx.KVStore(app.GetKey(types.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 main() { 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 < 5; 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) err := simapp.FundAccount(app, ctx, addr1, balances) if err != nil { // should return no error fmt.Println(err) } pageReq := &query.PageRequest{Key: nil, Limit: 1, CountTotal: true} store := ctx.KVStore(app.GetKey(types.StoreKey)) balancesStore := prefix.NewStore(store, types.BalancesPrefix) accountStore := prefix.NewStore(balancesStore, address.MustLengthPrefix(addr1)) var balResult sdk.Coins pageRes, err := query.FilteredPaginate(accountStore, pageReq, func(key []byte, value []byte, accumulate bool) (bool, error) { var bal sdk.Coin err := appCodec.Unmarshal(value, &bal) if err != nil { return false, err } // filter balances with amount greater than 100 if bal.Amount.Int64() > int64(100) { if accumulate { balResult = append(balResult, bal) } return true, nil } return false, nil }) if err != nil { // should return no error fmt.Println(err) } fmt.Println(&types.QueryAllBalancesResponse{Balances: balResult, Pagination: pageRes}) } func execFilterPaginate(store sdk.KVStore, pageReq *query.PageRequest, appCodec codec.Codec) (balances sdk.Coins, res *query.PageResponse, err error) { balancesStore := prefix.NewStore(store, types.BalancesPrefix) accountStore := prefix.NewStore(balancesStore, address.MustLengthPrefix(addr1)) var balResult sdk.Coins res, err = query.FilteredPaginate(accountStore, pageReq, func(key []byte, value []byte, accumulate bool) (bool, error) { var bal sdk.Coin err := appCodec.Unmarshal(value, &bal) if err != nil { return false, err } // filter balances with amount greater than 100 if bal.Amount.Int64() > int64(100) { if accumulate { balResult = append(balResult, bal) } return true, nil } return false, nil }) return balResult, res, err }
Output: balances:<denom:"test0denom" amount:"250" > pagination:<next_key:"test1denom" total:5 >
func Paginate ¶
func Paginate( prefixStore types.KVStore, pageRequest *PageRequest, onResult func(key []byte, value []byte) error, ) (*PageResponse, error)
Paginate does pagination of all the results in the PrefixStore based on the provided PageRequest. onResult should be used to do actual unmarshaling.
Example ¶
package main import ( "fmt" tmproto "github.com/soominhyunwoo/tendermint/proto/tendermint/types" dbm "github.com/soominhyunwoo/tm-db" "github.com/soominhyunwoo/chain-sdk/codec" "github.com/soominhyunwoo/chain-sdk/simapp" "github.com/soominhyunwoo/chain-sdk/store" "github.com/soominhyunwoo/chain-sdk/store/prefix" sdk "github.com/soominhyunwoo/chain-sdk/types" "github.com/soominhyunwoo/chain-sdk/types/address" "github.com/soominhyunwoo/chain-sdk/types/query" "github.com/soominhyunwoo/chain-sdk/x/bank/types" ) func main() { app, ctx, _ := setupTest() var balances sdk.Coins for i := 0; i < 2; i++ { denom := fmt.Sprintf("foo%ddenom", i) balances = append(balances, sdk.NewInt64Coin(denom, 100)) } balances = balances.Sort() addr1 := sdk.AccAddress([]byte("addr1")) acc1 := app.AccountKeeper.NewAccountWithAddress(ctx, addr1) app.AccountKeeper.SetAccount(ctx, acc1) err := simapp.FundAccount(app, ctx, addr1, balances) if err != nil { // should return no error fmt.Println(err) } // Paginate example pageReq := &query.PageRequest{Key: nil, Limit: 1, CountTotal: true} request := types.NewQueryAllBalancesRequest(addr1, pageReq) balResult := sdk.NewCoins() authStore := ctx.KVStore(app.GetKey(types.StoreKey)) balancesStore := prefix.NewStore(authStore, types.BalancesPrefix) accountStore := prefix.NewStore(balancesStore, address.MustLengthPrefix(addr1)) pageRes, err := query.Paginate(accountStore, request.Pagination, func(key []byte, value []byte) error { var tempRes sdk.Coin err := app.AppCodec().Unmarshal(value, &tempRes) if err != nil { return err } balResult = append(balResult, tempRes) return nil }) if err != nil { // should return no error fmt.Println(err) } fmt.Println(&types.QueryAllBalancesResponse{Balances: balResult, Pagination: pageRes}) } func setupTest() (*simapp.SimApp, sdk.Context, codec.Codec) { app := simapp.Setup(false) ctx := app.BaseApp.NewContext(false, tmproto.Header{Height: 1}) appCodec := app.AppCodec() db := dbm.NewMemDB() ms := store.NewCommitMultiStore(db) ms.LoadLatestVersion() return app, ctx, appCodec }
Output: balances:<denom:"foo0denom" amount:"100" > pagination:<next_key:"foo1denom" total:2 >
func (*PageResponse) Descriptor ¶
func (*PageResponse) Descriptor() ([]byte, []int)
func (*PageResponse) GetNextKey ¶
func (m *PageResponse) GetNextKey() []byte
func (*PageResponse) GetTotal ¶
func (m *PageResponse) GetTotal() uint64
func (*PageResponse) Marshal ¶
func (m *PageResponse) Marshal() (dAtA []byte, err error)
func (*PageResponse) MarshalToSizedBuffer ¶
func (m *PageResponse) MarshalToSizedBuffer(dAtA []byte) (int, error)
func (*PageResponse) ProtoMessage ¶
func (*PageResponse) ProtoMessage()
func (*PageResponse) Reset ¶
func (m *PageResponse) Reset()
func (*PageResponse) Size ¶
func (m *PageResponse) Size() (n int)
func (*PageResponse) String ¶
func (m *PageResponse) String() string
func (*PageResponse) Unmarshal ¶
func (m *PageResponse) Unmarshal(dAtA []byte) error
func (*PageResponse) XXX_DiscardUnknown ¶
func (m *PageResponse) XXX_DiscardUnknown()
func (*PageResponse) XXX_Marshal ¶
func (m *PageResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error)
func (*PageResponse) XXX_Merge ¶
func (m *PageResponse) XXX_Merge(src proto.Message)
func (*PageResponse) XXX_Size ¶
func (m *PageResponse) XXX_Size() int
func (*PageResponse) XXX_Unmarshal ¶
func (m *PageResponse) XXX_Unmarshal(b []byte) error