-
Notifications
You must be signed in to change notification settings - Fork 3.8k
/
Copy pathmap.go
267 lines (235 loc) · 7.63 KB
/
map.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
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
package collections
import (
"context"
"fmt"
"cosmossdk.io/collections/codec"
"cosmossdk.io/core/store"
)
// Map represents the basic collections object.
// It is used to map arbitrary keys to arbitrary
// objects.
type Map[K, V any] struct {
kc codec.KeyCodec[K]
vc codec.ValueCodec[V]
// store accessor
sa func(context.Context) store.KVStore
prefix []byte
name string
}
// NewMap returns a Map given a StoreKey, a Prefix, human-readable name and the relative value and key encoders.
// Name and prefix must be unique within the schema and name must match the format specified by NameRegex, or
// else this method will panic.
func NewMap[K, V any](
schemaBuilder *SchemaBuilder,
prefix Prefix,
name string,
keyCodec codec.KeyCodec[K],
valueCodec codec.ValueCodec[V],
) Map[K, V] {
m := Map[K, V]{
kc: keyCodec,
vc: valueCodec,
sa: schemaBuilder.schema.storeAccessor,
prefix: prefix.Bytes(),
name: name,
}
schemaBuilder.addCollection(collectionImpl[K, V]{m})
return m
}
func (m Map[K, V]) GetName() string {
return m.name
}
func (m Map[K, V]) GetPrefix() []byte {
return m.prefix
}
// Set maps the provided value to the provided key in the store.
// Errors with ErrEncoding if key or value encoding fails.
func (m Map[K, V]) Set(ctx context.Context, key K, value V) error {
bytesKey, err := EncodeKeyWithPrefix(m.prefix, m.kc, key)
if err != nil {
return err
}
valueBytes, err := m.vc.Encode(value)
if err != nil {
return fmt.Errorf("%w: value encode: %s", ErrEncoding, err) // TODO: use multi err wrapping in go1.20: https://github.com/golang/go/issues/53435
}
kvStore := m.sa(ctx)
return kvStore.Set(bytesKey, valueBytes)
}
// Get returns the value associated with the provided key,
// errors with ErrNotFound if the key does not exist, or
// with ErrEncoding if the key or value decoding fails.
func (m Map[K, V]) Get(ctx context.Context, key K) (v V, err error) {
bytesKey, err := EncodeKeyWithPrefix(m.prefix, m.kc, key)
if err != nil {
return v, err
}
kvStore := m.sa(ctx)
valueBytes, err := kvStore.Get(bytesKey)
if err != nil {
return v, err
}
if valueBytes == nil {
return v, fmt.Errorf("%w: key '%s' of type %s", ErrNotFound, m.kc.Stringify(key), m.vc.ValueType())
}
v, err = m.vc.Decode(valueBytes)
if err != nil {
return v, fmt.Errorf("%w: value decode: %s", ErrEncoding, err) // TODO: use multi err wrapping in go1.20: https://github.com/golang/go/issues/53435
}
return v, nil
}
// Has reports whether the key is present in storage or not.
// Errors with ErrEncoding if key encoding fails.
func (m Map[K, V]) Has(ctx context.Context, key K) (bool, error) {
bytesKey, err := EncodeKeyWithPrefix(m.prefix, m.kc, key)
if err != nil {
return false, err
}
kvStore := m.sa(ctx)
return kvStore.Has(bytesKey)
}
// Remove removes the key from the storage.
// Errors with ErrEncoding if key encoding fails.
// If the key does not exist then this is a no-op.
func (m Map[K, V]) Remove(ctx context.Context, key K) error {
bytesKey, err := EncodeKeyWithPrefix(m.prefix, m.kc, key)
if err != nil {
return err
}
kvStore := m.sa(ctx)
return kvStore.Delete(bytesKey)
}
// Iterate provides an Iterator over K and V. It accepts a Ranger interface.
// A nil ranger equals to iterate over all the keys in ascending order.
func (m Map[K, V]) Iterate(ctx context.Context, ranger Ranger[K]) (Iterator[K, V], error) {
return iteratorFromRanger(ctx, m, ranger)
}
// Walk iterates over the Map with the provided range, calls the provided
// walk function with the decoded key and value. If the callback function
// returns true then the walking is stopped.
// A nil ranger equals to walking over the entire key and value set.
func (m Map[K, V]) Walk(ctx context.Context, ranger Ranger[K], walkFunc func(key K, value V) (stop bool, err error)) error {
iter, err := m.Iterate(ctx, ranger)
if err != nil {
return err
}
defer iter.Close()
for ; iter.Valid(); iter.Next() {
kv, err := iter.KeyValue()
if err != nil {
return err
}
stop, err := walkFunc(kv.Key, kv.Value)
if err != nil {
return err
}
if stop {
return nil
}
}
return nil
}
// Clear clears the collection contained within the provided key range.
// A nil ranger equals to clearing the whole collection. In case the collection
// is empty no error will be returned.
// NOTE: this API needs to be used with care, considering that as of today
// cosmos-sdk stores the deletion records to be committed in a memory cache,
// clearing a lot of data might make the node go OOM.
func (m Map[K, V]) Clear(ctx context.Context, ranger Ranger[K]) error {
startBytes, endBytes, _, err := parseRangeInstruction(m.prefix, m.kc, ranger)
if err != nil {
return err
}
return deleteDomain(m.sa(ctx), startBytes, endBytes)
}
const clearBatchSize = 10000
// deleteDomain deletes the domain of an iterator, the key difference
// is that it uses batches to clear the store meaning that it will read
// the keys within the domain close the iterator and then delete them.
func deleteDomain(s store.KVStore, start, end []byte) error {
for {
iter, err := s.Iterator(start, end)
if err != nil {
return err
}
keys := make([][]byte, 0, clearBatchSize)
for ; iter.Valid() && len(keys) < clearBatchSize; iter.Next() {
keys = append(keys, iter.Key())
}
// we close the iterator here instead of deferring
err = iter.Close()
if err != nil {
return err
}
for _, key := range keys {
err = s.Delete(key)
if err != nil {
return err
}
}
// If we've retrieved less than the batchSize, we're done.
if len(keys) < clearBatchSize {
break
}
}
return nil
}
// IterateRaw iterates over the collection. The iteration range is untyped, it uses raw
// bytes. The resulting Iterator is typed.
// A nil start iterates from the first key contained in the collection.
// A nil end iterates up to the last key contained in the collection.
// A nil start and a nil end iterates over every key contained in the collection.
// TODO(tip): simplify after https://github.com/cosmos/cosmos-sdk/pull/14310 is merged
func (m Map[K, V]) IterateRaw(ctx context.Context, start, end []byte, order Order) (Iterator[K, V], error) {
prefixedStart := append(m.prefix, start...)
var prefixedEnd []byte
if end == nil {
prefixedEnd = nextBytesPrefixKey(m.prefix)
} else {
prefixedEnd = append(m.prefix, end...)
}
s := m.sa(ctx)
var (
storeIter store.Iterator
err error
)
switch order {
case OrderAscending:
storeIter, err = s.Iterator(prefixedStart, prefixedEnd)
case OrderDescending:
storeIter, err = s.ReverseIterator(prefixedStart, prefixedEnd)
default:
return Iterator[K, V]{}, errOrder
}
if err != nil {
return Iterator[K, V]{}, err
}
if !storeIter.Valid() {
return Iterator[K, V]{}, ErrInvalidIterator
}
return Iterator[K, V]{
kc: m.kc,
vc: m.vc,
iter: storeIter,
prefixLength: len(m.prefix),
}, nil
}
// KeyCodec returns the Map's KeyCodec.
func (m Map[K, V]) KeyCodec() codec.KeyCodec[K] { return m.kc }
// ValueCodec returns the Map's ValueCodec.
func (m Map[K, V]) ValueCodec() codec.ValueCodec[V] { return m.vc }
// EncodeKeyWithPrefix returns how the collection would store the key in storage given
// prefix, key codec and the concrete key.
func EncodeKeyWithPrefix[K any](prefix []byte, kc codec.KeyCodec[K], key K) ([]byte, error) {
prefixLen := len(prefix)
// preallocate buffer
keyBytes := make([]byte, prefixLen+kc.Size(key))
// put prefix
copy(keyBytes, prefix)
// put key
_, err := kc.Encode(keyBytes[prefixLen:], key)
if err != nil {
return nil, fmt.Errorf("%w: key encode: %s", ErrEncoding, err) // TODO: use multi err wrapping in go1.20: https://github.com/golang/go/issues/53435
}
return keyBytes, nil
}