-
Notifications
You must be signed in to change notification settings - Fork 3.6k
/
pagination.go
161 lines (130 loc) · 3.42 KB
/
pagination.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
package query
import (
"fmt"
"math"
db "github.com/tendermint/tm-db"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"github.com/cosmos/cosmos-sdk/store/types"
)
// DefaultPage is the default `page` number for queries.
// If the `page` number is not supplied, `DefaultPage` will be used.
const DefaultPage = 1
// DefaultLimit is the default `limit` for queries
// if the `limit` is not supplied, paginate will use `DefaultLimit`
const DefaultLimit = 100
// MaxLimit is the maximum limit the paginate function can handle
// which equals the maximum value that can be stored in uint64
const MaxLimit = math.MaxUint64
// ParsePagination validate PageRequest and returns page number & limit.
func ParsePagination(pageReq *PageRequest) (page, limit int, err error) {
offset := 0
limit = DefaultLimit
if pageReq != nil {
offset = int(pageReq.Offset)
limit = int(pageReq.Limit)
}
if offset < 0 {
return 1, 0, status.Error(codes.InvalidArgument, "offset must greater than 0")
}
if limit < 0 {
return 1, 0, status.Error(codes.InvalidArgument, "limit must greater than 0")
} else if limit == 0 {
limit = DefaultLimit
}
page = offset/limit + 1
return page, limit, nil
}
// Paginate does pagination of all the results in the PrefixStore based on the
// provided PageRequest. onResult should be used to do actual unmarshaling.
func Paginate(
prefixStore types.KVStore,
pageRequest *PageRequest,
onResult func(key []byte, value []byte) error,
) (*PageResponse, error) {
// if the PageRequest is nil, use default PageRequest
if pageRequest == nil {
pageRequest = &PageRequest{}
}
offset := pageRequest.Offset
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")
}
if limit == 0 {
limit = DefaultLimit
// count total results when the limit is zero/not supplied
countTotal = true
}
if len(key) != 0 {
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
}
if iterator.Error() != nil {
return nil, iterator.Error()
}
err := onResult(iterator.Key(), iterator.Value())
if err != nil {
return nil, err
}
count++
}
return &PageResponse{
NextKey: nextKey,
}, nil
}
iterator := getIterator(prefixStore, nil, reverse)
defer iterator.Close()
end := offset + limit
var count uint64
var nextKey []byte
for ; iterator.Valid(); iterator.Next() {
count++
if count <= offset {
continue
}
if count <= end {
err := onResult(iterator.Key(), iterator.Value())
if err != nil {
return nil, err
}
} else if count == end+1 {
nextKey = iterator.Key()
if !countTotal {
break
}
}
if iterator.Error() != nil {
return nil, iterator.Error()
}
}
res := &PageResponse{NextKey: nextKey}
if countTotal {
res.Total = count
}
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)
}