Skip to content

Commit e5daf6b

Browse files
feat: add query.GenericFilteredPaginated (#12253)
1 parent f10f5e5 commit e5daf6b

File tree

4 files changed

+197
-79
lines changed

4 files changed

+197
-79
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ Ref: https://keepachangelog.com/en/1.0.0/
4040
### Features
4141

4242
* (cli) [#12028](https://github.com/cosmos/cosmos-sdk/pull/12028) Add the `tendermint key-migrate` to perform Tendermint v0.35 DB key migration.
43+
* (query) [#12253](https://github.com/cosmos/cosmos-sdk/pull/12253) Add `GenericFilteredPaginate` to the `query` package to improve UX.
4344

4445
### Improvements
4546

types/query/filtered_pagination.go

Lines changed: 143 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package query
33
import (
44
"fmt"
55

6+
"github.com/cosmos/cosmos-sdk/codec"
67
"github.com/cosmos/cosmos-sdk/store/types"
78
)
89

@@ -45,8 +46,10 @@ func FilteredPaginate(
4546
iterator := getIterator(prefixStore, key, reverse)
4647
defer iterator.Close()
4748

48-
var numHits uint64
49-
var nextKey []byte
49+
var (
50+
numHits uint64
51+
nextKey []byte
52+
)
5053

5154
for ; iterator.Valid(); iterator.Next() {
5255
if numHits == limit {
@@ -78,8 +81,10 @@ func FilteredPaginate(
7881

7982
end := offset + limit
8083

81-
var numHits uint64
82-
var nextKey []byte
84+
var (
85+
numHits uint64
86+
nextKey []byte
87+
)
8388

8489
for ; iterator.Valid(); iterator.Next() {
8590
if iterator.Error() != nil {
@@ -114,3 +119,137 @@ func FilteredPaginate(
114119

115120
return res, nil
116121
}
122+
123+
// GenericFilteredPaginate does pagination of all the results in the PrefixStore based on the
124+
// provided PageRequest. `onResult` should be used to filter or transform the results.
125+
// `c` is a constructor function that needs to return a new instance of the type T (this is to
126+
// workaround some generic pitfalls in which we can't instantiate a T struct inside the function).
127+
// If key is provided, the pagination uses the optimized querying.
128+
// If offset is used, the pagination uses lazy filtering i.e., searches through all the records.
129+
// The resulting slice (of type F) can be of a different type than the one being iterated through
130+
// (type T), so it's possible to do any necessary transformation inside the onResult function.
131+
func GenericFilteredPaginate[T codec.ProtoMarshaler, F codec.ProtoMarshaler](
132+
cdc codec.BinaryCodec,
133+
prefixStore types.KVStore,
134+
pageRequest *PageRequest,
135+
onResult func(key []byte, value T) (F, error),
136+
constructor func() T,
137+
) ([]F, *PageResponse, error) {
138+
// if the PageRequest is nil, use default PageRequest
139+
if pageRequest == nil {
140+
pageRequest = &PageRequest{}
141+
}
142+
143+
offset := pageRequest.Offset
144+
key := pageRequest.Key
145+
limit := pageRequest.Limit
146+
countTotal := pageRequest.CountTotal
147+
reverse := pageRequest.Reverse
148+
results := []F{}
149+
150+
if offset > 0 && key != nil {
151+
return results, nil, fmt.Errorf("invalid request, either offset or key is expected, got both")
152+
}
153+
154+
if limit == 0 {
155+
limit = DefaultLimit
156+
157+
// count total results when the limit is zero/not supplied
158+
countTotal = true
159+
}
160+
161+
if len(key) != 0 {
162+
iterator := getIterator(prefixStore, key, reverse)
163+
defer iterator.Close()
164+
165+
var (
166+
numHits uint64
167+
nextKey []byte
168+
)
169+
170+
for ; iterator.Valid(); iterator.Next() {
171+
if numHits == limit {
172+
nextKey = iterator.Key()
173+
break
174+
}
175+
176+
if iterator.Error() != nil {
177+
return nil, nil, iterator.Error()
178+
}
179+
180+
protoMsg := constructor()
181+
182+
err := cdc.Unmarshal(iterator.Value(), protoMsg)
183+
if err != nil {
184+
return nil, nil, err
185+
}
186+
187+
val, err := onResult(iterator.Key(), protoMsg)
188+
if err != nil {
189+
return nil, nil, err
190+
}
191+
192+
if val.Size() != 0 {
193+
results = append(results, val)
194+
numHits++
195+
}
196+
}
197+
198+
return results, &PageResponse{
199+
NextKey: nextKey,
200+
}, nil
201+
}
202+
203+
iterator := getIterator(prefixStore, nil, reverse)
204+
defer iterator.Close()
205+
206+
end := offset + limit
207+
208+
var (
209+
numHits uint64
210+
nextKey []byte
211+
)
212+
213+
for ; iterator.Valid(); iterator.Next() {
214+
if iterator.Error() != nil {
215+
return nil, nil, iterator.Error()
216+
}
217+
218+
protoMsg := constructor()
219+
220+
err := cdc.Unmarshal(iterator.Value(), protoMsg)
221+
if err != nil {
222+
return nil, nil, err
223+
}
224+
225+
val, err := onResult(iterator.Key(), protoMsg)
226+
if err != nil {
227+
return nil, nil, err
228+
}
229+
230+
if val.Size() != 0 {
231+
// Previously this was the "accumulate" flag
232+
if numHits >= offset && numHits < end {
233+
results = append(results, val)
234+
}
235+
numHits++
236+
}
237+
238+
if numHits == end+1 {
239+
if nextKey == nil {
240+
nextKey = iterator.Key()
241+
}
242+
243+
if !countTotal {
244+
break
245+
}
246+
}
247+
}
248+
249+
res := &PageResponse{NextKey: nextKey}
250+
if countTotal {
251+
res.Total = numHits
252+
}
253+
254+
return results, res, nil
255+
}

x/authz/keeper/grpc_query.go

Lines changed: 42 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,6 @@ import (
66
"google.golang.org/grpc/codes"
77
"google.golang.org/grpc/status"
88

9-
proto "github.com/gogo/protobuf/proto"
10-
11-
"github.com/cosmos/cosmos-sdk/codec"
129
codectypes "github.com/cosmos/cosmos-sdk/codec/types"
1310
"github.com/cosmos/cosmos-sdk/store/prefix"
1411
sdk "github.com/cosmos/cosmos-sdk/types"
@@ -64,34 +61,22 @@ func (k Keeper) Grants(c context.Context, req *authz.QueryGrantsRequest) (*authz
6461
key := grantStoreKey(grantee, granter, "")
6562
grantsStore := prefix.NewStore(store, key)
6663

67-
var authorizations []*authz.Grant
68-
pageRes, err := query.FilteredPaginate(grantsStore, req.Pagination, func(key []byte, value []byte, accumulate bool) (bool, error) {
69-
auth, err := unmarshalAuthorization(k.cdc, value)
70-
if err != nil {
71-
return false, err
72-
}
73-
64+
authorizations, pageRes, err := query.GenericFilteredPaginate(k.cdc, grantsStore, req.Pagination, func(key []byte, auth *authz.Grant) (*authz.Grant, error) {
7465
auth1, err := auth.GetAuthorization()
7566
if err != nil {
76-
return false, err
67+
return nil, err
7768
}
7869

79-
if accumulate {
80-
msg, ok := auth1.(proto.Message)
81-
if !ok {
82-
return false, status.Errorf(codes.Internal, "can't protomarshal %T", msg)
83-
}
84-
85-
authorizationAny, err := codectypes.NewAnyWithValue(msg)
86-
if err != nil {
87-
return false, status.Errorf(codes.Internal, err.Error())
88-
}
89-
authorizations = append(authorizations, &authz.Grant{
90-
Authorization: authorizationAny,
91-
Expiration: auth.Expiration,
92-
})
70+
authorizationAny, err := codectypes.NewAnyWithValue(auth1)
71+
if err != nil {
72+
return nil, status.Errorf(codes.Internal, err.Error())
9373
}
94-
return true, nil
74+
return &authz.Grant{
75+
Authorization: authorizationAny,
76+
Expiration: auth.Expiration,
77+
}, nil
78+
}, func() *authz.Grant {
79+
return &authz.Grant{}
9580
})
9681
if err != nil {
9782
return nil, err
@@ -118,34 +103,29 @@ func (k Keeper) GranterGrants(c context.Context, req *authz.QueryGranterGrantsRe
118103
store := ctx.KVStore(k.storeKey)
119104
authzStore := prefix.NewStore(store, grantStoreKey(nil, granter, ""))
120105

121-
var grants []*authz.GrantAuthorization
122-
pageRes, err := query.FilteredPaginate(authzStore, req.Pagination, func(key []byte, value []byte, accumulate bool) (bool, error) {
123-
auth, err := unmarshalAuthorization(k.cdc, value)
106+
grants, pageRes, err := query.GenericFilteredPaginate(k.cdc, authzStore, req.Pagination, func(key []byte, auth *authz.Grant) (*authz.GrantAuthorization, error) {
107+
auth1, err := auth.GetAuthorization()
124108
if err != nil {
125-
return false, err
109+
return nil, err
126110
}
127111

128-
auth1, err := auth.GetAuthorization()
112+
any, err := codectypes.NewAnyWithValue(auth1)
129113
if err != nil {
130-
return false, err
114+
return nil, status.Errorf(codes.Internal, err.Error())
131115
}
132116

133-
if accumulate {
134-
any, err := codectypes.NewAnyWithValue(auth1)
135-
if err != nil {
136-
return false, status.Errorf(codes.Internal, err.Error())
137-
}
138-
139-
grantee := firstAddressFromGrantStoreKey(key)
140-
grants = append(grants, &authz.GrantAuthorization{
141-
Granter: granter.String(),
142-
Grantee: grantee.String(),
143-
Authorization: any,
144-
Expiration: auth.Expiration,
145-
})
146-
}
147-
return true, nil
117+
grantee := firstAddressFromGrantStoreKey(key)
118+
return &authz.GrantAuthorization{
119+
Granter: granter.String(),
120+
Grantee: grantee.String(),
121+
Authorization: any,
122+
Expiration: auth.Expiration,
123+
}, nil
124+
125+
}, func() *authz.Grant {
126+
return &authz.Grant{}
148127
})
128+
149129
if err != nil {
150130
return nil, err
151131
}
@@ -170,36 +150,30 @@ func (k Keeper) GranteeGrants(c context.Context, req *authz.QueryGranteeGrantsRe
170150
ctx := sdk.UnwrapSDKContext(c)
171151
store := prefix.NewStore(ctx.KVStore(k.storeKey), GrantKey)
172152

173-
var authorizations []*authz.GrantAuthorization
174-
pageRes, err := query.FilteredPaginate(store, req.Pagination, func(key []byte, value []byte, accumulate bool) (bool, error) {
175-
auth, err := unmarshalAuthorization(k.cdc, value)
153+
authorizations, pageRes, err := query.GenericFilteredPaginate(k.cdc, store, req.Pagination, func(key []byte, auth *authz.Grant) (*authz.GrantAuthorization, error) {
154+
auth1, err := auth.GetAuthorization()
176155
if err != nil {
177-
return false, err
156+
return nil, err
178157
}
179158

180159
granter, g, _ := parseGrantStoreKey(append(GrantKey, key...))
181160
if !g.Equals(grantee) {
182-
return false, nil
161+
return nil, nil
183162
}
184163

185-
auth1, err := auth.GetAuthorization()
164+
authorizationAny, err := codectypes.NewAnyWithValue(auth1)
186165
if err != nil {
187-
return false, err
188-
}
189-
if accumulate {
190-
any, err := codectypes.NewAnyWithValue(auth1)
191-
if err != nil {
192-
return false, status.Errorf(codes.Internal, err.Error())
193-
}
194-
195-
authorizations = append(authorizations, &authz.GrantAuthorization{
196-
Authorization: any,
197-
Expiration: auth.Expiration,
198-
Granter: granter.String(),
199-
Grantee: grantee.String(),
200-
})
166+
return nil, status.Errorf(codes.Internal, err.Error())
201167
}
202-
return true, nil
168+
169+
return &authz.GrantAuthorization{
170+
Authorization: authorizationAny,
171+
Expiration: auth.Expiration,
172+
Granter: granter.String(),
173+
Grantee: grantee.String(),
174+
}, nil
175+
}, func() *authz.Grant {
176+
return &authz.Grant{}
203177
})
204178
if err != nil {
205179
return nil, err
@@ -210,9 +184,3 @@ func (k Keeper) GranteeGrants(c context.Context, req *authz.QueryGranteeGrantsRe
210184
Pagination: pageRes,
211185
}, nil
212186
}
213-
214-
// unmarshal an authorization from a store value
215-
func unmarshalAuthorization(cdc codec.BinaryCodec, value []byte) (v authz.Grant, err error) {
216-
err = cdc.Unmarshal(value, &v)
217-
return v, err
218-
}

x/authz/keeper/grpc_query_test.go

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -198,7 +198,8 @@ func (suite *TestSuite) TestGRPCQueryGranterGrants() {
198198
},
199199
{
200200
"valid case, pagination",
201-
func() {},
201+
func() {
202+
},
202203
false,
203204
authz.QueryGranterGrantsRequest{
204205
Granter: addrs[0].String(),
@@ -253,6 +254,15 @@ func (suite *TestSuite) TestGRPCQueryGranteeGrants() {
253254
},
254255
1,
255256
},
257+
{
258+
"valid case, no authorization found",
259+
func() {},
260+
false,
261+
authz.QueryGranteeGrantsRequest{
262+
Grantee: addrs[2].String(),
263+
},
264+
0,
265+
},
256266
{
257267
"valid case, multiple authorization",
258268
func() {

0 commit comments

Comments
 (0)