-
Notifications
You must be signed in to change notification settings - Fork 37
Expand file tree
/
Copy pathstore_decoder.go
More file actions
259 lines (242 loc) · 7.17 KB
/
Copy pathstore_decoder.go
File metadata and controls
259 lines (242 loc) · 7.17 KB
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
package mmdbwriter
import (
"fmt"
"math/big"
"slices"
"strings"
"github.com/oschwald/maxminddb-golang/v2/mmdbdata"
"github.com/maxmind/mmdbwriter/v2/mmdbtype"
)
// storeDecoder implements mmdbdata.Unmarshaler by interning directly into a
// valueStore. It never constructs an intermediate map or slice graph. The
// offset cache owns one reference per decoded MMDB offset until close runs.
type storeDecoder struct {
store *valueStore
cache map[uint]valueRef
result valueRef
// pairScratch pools the per-map working slices. Maps nest, so each
// decodeMap call takes a slice and returns it when done.
pairScratch [][]decodedPair
}
// decodedPair carries one interned key and value of a map being decoded.
type decodedPair struct {
key string
keyRef valueRef
valueRef valueRef
}
func (d *storeDecoder) takePairScratch() []decodedPair {
if n := len(d.pairScratch); n != 0 {
pairs := d.pairScratch[n-1]
d.pairScratch = d.pairScratch[:n-1]
return pairs[:0]
}
return nil
}
func (d *storeDecoder) putPairScratch(pairs []decodedPair) {
// Entries reference decoded keys. Clear the full backing array so pooling
// does not pin them.
pairs = pairs[:cap(pairs)]
clear(pairs)
d.pairScratch = append(d.pairScratch, pairs)
}
func newStoreDecoder(store *valueStore) *storeDecoder {
return &storeDecoder{store: store, cache: map[uint]valueRef{}}
}
func (d *storeDecoder) UnmarshalMaxMindDB(decoder *mmdbdata.Decoder) error {
ref, err := d.decodeRef(decoder)
if err != nil {
return err
}
// Release a result the caller never took, so a repeated Decode does not
// leak its reference.
d.store.release(d.result)
d.result = ref
return nil
}
// takeResult transfers ownership of the most recently decoded top-level ref.
func (d *storeDecoder) takeResult() valueRef {
ref := d.result
d.result = nilValueRef
return ref
}
func (d *storeDecoder) close() {
if d.result != nilValueRef {
d.store.release(d.result)
d.result = nilValueRef
}
for _, ref := range d.cache {
d.store.release(ref)
}
clear(d.cache)
}
func (d *storeDecoder) decodeRef(decoder *mmdbdata.Decoder) (valueRef, error) {
offset := decoder.Offset()
if ref, ok := d.cache[offset]; ok {
d.store.retain(ref)
return ref, nil
}
kind, err := decoder.PeekKind()
if err != nil {
return nilValueRef, fmt.Errorf("peeking kind: %w", err)
}
var ref valueRef
switch kind {
case mmdbdata.KindMap:
ref, err = d.decodeMap(decoder)
case mmdbdata.KindSlice:
ref, err = d.decodeSlice(decoder)
case mmdbdata.KindString:
var value string
value, err = decoder.ReadString()
if err == nil {
ref, err = d.store.internString(mmdbtype.String(value))
}
case mmdbdata.KindFloat64:
var value float64
value, err = decoder.ReadFloat64()
if err == nil {
ref, err = d.store.internUncached(mmdbtype.Float64(value))
}
case mmdbdata.KindBytes:
var value []byte
value, err = decoder.ReadBytes()
if err == nil {
ref, err = d.store.internUncached(mmdbtype.Bytes(value))
}
case mmdbdata.KindUint16:
var value uint16
value, err = decoder.ReadUint16()
if err == nil {
ref, err = d.store.internUncached(mmdbtype.Uint16(value))
}
case mmdbdata.KindUint32:
var value uint32
value, err = decoder.ReadUint32()
if err == nil {
ref, err = d.store.internUncached(mmdbtype.Uint32(value))
}
case mmdbdata.KindInt32:
var value int32
value, err = decoder.ReadInt32()
if err == nil {
ref, err = d.store.internUncached(mmdbtype.Int32(value))
}
case mmdbdata.KindUint64:
var value uint64
value, err = decoder.ReadUint64()
if err == nil {
ref, err = d.store.internUncached(mmdbtype.Uint64(value))
}
case mmdbdata.KindUint128:
var hi, lo uint64
hi, lo, err = decoder.ReadUint128()
if err == nil {
integer := new(big.Int).SetUint64(hi)
integer.Lsh(integer, 64)
integer.Add(integer, new(big.Int).SetUint64(lo))
value := mmdbtype.Uint128(*integer)
ref, err = d.store.internUncached(&value)
}
case mmdbdata.KindBool:
var value bool
value, err = decoder.ReadBool()
if err == nil {
ref, err = d.store.internUncached(mmdbtype.Bool(value))
}
case mmdbdata.KindFloat32:
var value float32
value, err = decoder.ReadFloat32()
if err == nil {
ref, err = d.store.internUncached(mmdbtype.Float32(value))
}
default:
return nilValueRef, fmt.Errorf(
"unsupported data type %v at offset %d", kind, offset)
}
if err != nil {
return nilValueRef, fmt.Errorf("decoding %v at offset %d: %w", kind, offset, err)
}
// The returned ref and the cache each own one reference.
d.store.retain(ref)
d.cache[offset] = ref
return ref, nil
}
func (d *storeDecoder) decodeMap(decoder *mmdbdata.Decoder) (valueRef, error) {
iterator, _, err := decoder.ReadMap()
if err != nil {
return nilValueRef, fmt.Errorf("reading map: %w", err)
}
pairs := d.takePairScratch()
defer func() { d.putPairScratch(pairs) }()
release := func() {
for _, pair := range pairs {
d.store.release(pair.keyRef)
d.store.release(pair.valueRef)
}
}
for key, iteratorErr := range iterator {
if iteratorErr != nil {
release()
return nilValueRef, fmt.Errorf("reading map entry: %w", iteratorErr)
}
keyRef, keyErr := d.store.internString(mmdbtype.String(key))
if keyErr != nil {
release()
return nilValueRef, fmt.Errorf("interning map key %q: %w", key, keyErr)
}
childRef, valueErr := d.decodeRef(decoder)
if valueErr != nil {
d.store.release(keyRef)
release()
return nilValueRef, fmt.Errorf(
"decoding value for map key %q: %w", key, valueErr)
}
pairs = append(pairs, decodedPair{key: string(key), keyRef: keyRef, valueRef: childRef})
}
// The sort must stay byte-order identical to internMap's, or loaded and
// inserted maps stop deduplicating against each other.
slices.SortFunc(pairs, func(left, right decodedPair) int {
return strings.Compare(left.key, right.key)
})
for index := 1; index < len(pairs); index++ {
if pairs[index].key == pairs[index-1].key {
key := pairs[index].key
release()
return nilValueRef, fmt.Errorf("map has duplicate key %q", key)
}
}
children := d.store.takeChildScratch()
defer func() { d.store.putChildScratch(children) }()
for _, pair := range pairs {
children = append(children, pair.keyRef, pair.valueRef)
}
return d.store.internOwnedChildren(valueKindMap, children)
}
func (d *storeDecoder) decodeSlice(decoder *mmdbdata.Decoder) (valueRef, error) {
iterator, _, err := decoder.ReadSlice()
if err != nil {
return nilValueRef, fmt.Errorf("reading slice: %w", err)
}
// The declared size comes from the source database header, so grow into
// the pooled slice instead of trusting the header for one allocation.
children := d.store.takeChildScratch()
defer func() { d.store.putChildScratch(children) }()
for iteratorErr := range iterator {
if iteratorErr != nil {
for _, child := range children {
d.store.release(child)
}
return nilValueRef, fmt.Errorf("reading slice element: %w", iteratorErr)
}
ref, valueErr := d.decodeRef(decoder)
if valueErr != nil {
for _, child := range children {
d.store.release(child)
}
return nilValueRef, fmt.Errorf(
"decoding slice index %d: %w", len(children), valueErr)
}
children = append(children, ref)
}
return d.store.internOwnedChildren(valueKindSlice, children)
}