@@ -113,6 +113,174 @@ func TestOverflow(t *testing.T) {
113
113
}
114
114
}
115
115
116
+ func TestFillAndCollapse (t * testing.T ) {
117
+ ctx := context .Background ()
118
+ cs := cbor .NewCborStore (newMockBlocks ())
119
+ root := NewNode (cs , UseTreeBitWidth (8 ), UseHashFunction (identityHash ))
120
+ val := randValue ()
121
+
122
+ // start with a single node and a single full bucket
123
+ if err := root .Set (ctx , "AAAAAA11" , val ); err != nil {
124
+ t .Fatal (err )
125
+ }
126
+ if err := root .Set (ctx , "AAAAAA12" , val ); err != nil {
127
+ t .Fatal (err )
128
+ }
129
+ if err := root .Set (ctx , "AAAAAA21" , val ); err != nil {
130
+ t .Fatal (err )
131
+ }
132
+
133
+ st := stats (root )
134
+ fmt .Println (st )
135
+ printHamt (root )
136
+ if st .totalNodes != 1 || st .totalKvs != 3 || st .counts [3 ] != 1 {
137
+ t .Fatal ("Should be 1 node with 1 bucket" )
138
+ }
139
+
140
+ baseCid , err := cs .Put (ctx , root )
141
+ if err != nil {
142
+ t .Fatal (err )
143
+ }
144
+
145
+ // add a 4th colliding entry that forces a chain of new nodes to accommodate
146
+ // in a new node where there aren't collisions (7th byte)
147
+ if err := root .Set (ctx , "AAAAAA22" , val ); err != nil {
148
+ t .Fatal (err )
149
+ }
150
+
151
+ st = stats (root )
152
+ fmt .Println (st )
153
+ printHamt (root )
154
+ if st .totalNodes != 7 || st .totalKvs != 4 || st .counts [2 ] != 2 {
155
+ t .Fatal ("Should be 7 nodes with 4 buckets" )
156
+ }
157
+
158
+ // remove and we should be back to the same structure as before
159
+ if err := root .Delete (ctx , "AAAAAA22" ); err != nil {
160
+ t .Fatal (err )
161
+ }
162
+
163
+ st = stats (root )
164
+ fmt .Println (st )
165
+ printHamt (root )
166
+ if st .totalNodes != 1 || st .totalKvs != 3 || st .counts [3 ] != 1 {
167
+ t .Fatal ("Should be 1 node with 1 bucket" )
168
+ }
169
+
170
+ c , err := cs .Put (ctx , root )
171
+ if err != nil {
172
+ t .Fatal (err )
173
+ }
174
+ if ! c .Equals (baseCid ) {
175
+ t .Fatal ("CID mismatch on mutation" )
176
+ }
177
+
178
+ // insert elements that collide at the 4th position so push the tree down by
179
+ // 3 nodes
180
+ if err := root .Set (ctx , "AAA11AA" , val ); err != nil {
181
+ t .Fatal (err )
182
+ }
183
+ if err := root .Set (ctx , "AAA12AA" , val ); err != nil {
184
+ t .Fatal (err )
185
+ }
186
+ if err := root .Set (ctx , "AAA13AA" , val ); err != nil {
187
+ t .Fatal (err )
188
+ }
189
+ st = stats (root )
190
+ fmt .Println (st )
191
+ printHamt (root )
192
+ if st .totalNodes != 4 || st .totalKvs != 6 || st .counts [3 ] != 2 {
193
+ t .Fatal ("Should be 4 nodes with 2 buckets of 3" )
194
+ }
195
+
196
+ midCid , err := cs .Put (ctx , root )
197
+ if err != nil {
198
+ t .Fatal (err )
199
+ }
200
+
201
+ // insert an overflow node that pushes the previous 4 into a separate node
202
+ if err := root .Set (ctx , "AAA14AA" , val ); err != nil {
203
+ t .Fatal (err )
204
+ }
205
+
206
+ st = stats (root )
207
+ fmt .Println (st )
208
+ printHamt (root )
209
+ if st .totalNodes != 5 || st .totalKvs != 7 || st .counts [1 ] != 4 || st .counts [3 ] != 1 {
210
+ t .Fatal ("Should be 4 node with 2 buckets" )
211
+ }
212
+
213
+ // put the colliding 4th back in that will push down to full height
214
+ if err := root .Set (ctx , "AAAAAA22" , val ); err != nil {
215
+ t .Fatal (err )
216
+ }
217
+
218
+ st = stats (root )
219
+ fmt .Println (st )
220
+ printHamt (root )
221
+ if st .totalNodes != 8 || st .totalKvs != 8 || st .counts [1 ] != 4 || st .counts [2 ] != 2 {
222
+ t .Fatal ("Should be 7 nodes with 5 buckets" )
223
+ }
224
+
225
+ // rewind back one step
226
+ if err := root .Delete (ctx , "AAAAAA22" ); err != nil {
227
+ t .Fatal (err )
228
+ }
229
+
230
+ st = stats (root )
231
+ fmt .Println (st )
232
+ printHamt (root )
233
+ if st .totalNodes != 5 || st .totalKvs != 7 || st .counts [1 ] != 4 || st .counts [3 ] != 1 {
234
+ t .Fatal ("Should be 4 node with 2 buckets" )
235
+ }
236
+
237
+ // rewind another step
238
+ if err := root .Delete (ctx , "AAA14AA" ); err != nil {
239
+ t .Fatal (err )
240
+ }
241
+ st = stats (root )
242
+ fmt .Println (st )
243
+ printHamt (root )
244
+ if st .totalNodes != 4 || st .totalKvs != 6 || st .counts [3 ] != 2 {
245
+ t .Fatal ("Should be 4 nodes with 2 buckets of 3" )
246
+ }
247
+
248
+ c , err = cs .Put (ctx , root )
249
+ if err != nil {
250
+ t .Fatal (err )
251
+ }
252
+ if ! c .Equals (midCid ) {
253
+ t .Fatal ("CID mismatch on mutation" )
254
+ }
255
+
256
+ // remove the 3 colliding node so we should be back to the initial state
257
+ if err := root .Delete (ctx , "AAA11AA" ); err != nil {
258
+ t .Fatal (err )
259
+ }
260
+ if err := root .Delete (ctx , "AAA12AA" ); err != nil {
261
+ t .Fatal (err )
262
+ }
263
+ if err := root .Delete (ctx , "AAA13AA" ); err != nil {
264
+ t .Fatal (err )
265
+ }
266
+
267
+ st = stats (root )
268
+ fmt .Println (st )
269
+ printHamt (root )
270
+ if st .totalNodes != 1 || st .totalKvs != 3 || st .counts [3 ] != 1 {
271
+ t .Fatal ("Should be 1 node with 1 bucket" )
272
+ }
273
+
274
+ // should have the same CID as original
275
+ c , err = cs .Put (ctx , root )
276
+ if err != nil {
277
+ t .Fatal (err )
278
+ }
279
+ if ! c .Equals (baseCid ) {
280
+ t .Fatal ("CID mismatch on mutation" )
281
+ }
282
+ }
283
+
116
284
func addAndRemoveKeys (t * testing.T , keys []string , extraKeys []string , options ... Option ) {
117
285
ctx := context .Background ()
118
286
vals := make (map [string ][]byte )
@@ -158,6 +326,8 @@ func addAndRemoveKeys(t *testing.T, keys []string, extraKeys []string, options .
158
326
}
159
327
}
160
328
329
+ printHamt (begn )
330
+
161
331
// create second hamt by adding and deleting the extra keys
162
332
for i := 0 ; i < len (extraKeys ); i ++ {
163
333
begn .Set (ctx , extraKeys [i ], randValue ())
@@ -188,6 +358,37 @@ func addAndRemoveKeys(t *testing.T, keys []string, extraKeys []string, options .
188
358
}
189
359
}
190
360
361
+ func printHamt (hamt * Node ) {
362
+ ctx := context .Background ()
363
+
364
+ var printNode func (n * Node , depth int )
365
+
366
+ printNode = func (n * Node , depth int ) {
367
+ c , err := n .store .Put (ctx , n )
368
+ if err != nil {
369
+ panic (err )
370
+ }
371
+ fmt .Printf ("%s‣ %v:\n " , strings .Repeat (" " , depth ), c )
372
+ for _ , p := range n .Pointers {
373
+ if p .isShard () {
374
+ child , err := p .loadChild (ctx , n .store , n .bitWidth , n .hash )
375
+ if err != nil {
376
+ panic (err )
377
+ }
378
+ printNode (child , depth + 1 )
379
+ } else {
380
+ var keys []string
381
+ for _ , pt := range p .KVs {
382
+ keys = append (keys , string (pt .Key ))
383
+ }
384
+ fmt .Printf ("%s⇶ [ %s ]\n " , strings .Repeat (" " , depth + 1 ), strings .Join (keys , ", " ))
385
+ }
386
+ }
387
+ }
388
+
389
+ printNode (hamt , 0 )
390
+ }
391
+
191
392
func dotGraphRec (n * Node , name * int ) {
192
393
cur := * name
193
394
for _ , p := range n .Pointers {
@@ -216,6 +417,10 @@ type hamtStats struct {
216
417
counts map [int ]int
217
418
}
218
419
420
+ func (hs hamtStats ) String () string {
421
+ return fmt .Sprintf ("nodes=%d, kvs=%d, counts=%v" , hs .totalNodes , hs .totalKvs , hs .counts )
422
+ }
423
+
219
424
func stats (n * Node ) * hamtStats {
220
425
st := & hamtStats {counts : make (map [int ]int )}
221
426
statsrec (n , st )
0 commit comments