xitdb 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (63) hide show
  1. package/dist/core-buffered-file.d.ts +41 -0
  2. package/dist/core-file.d.ts +18 -0
  3. package/dist/core-memory.d.ts +36 -0
  4. package/dist/core.d.ts +23 -0
  5. package/dist/database.d.ts +244 -0
  6. package/dist/exceptions.d.ts +51 -0
  7. package/dist/hasher.d.ts +9 -0
  8. package/dist/index.d.ts +26 -0
  9. package/dist/index.js +429 -266
  10. package/dist/read-array-list.d.ts +13 -0
  11. package/dist/read-counted-hash-map.d.ts +7 -0
  12. package/dist/read-counted-hash-set.d.ts +7 -0
  13. package/dist/read-cursor.d.ts +57 -0
  14. package/dist/read-hash-map.d.ts +27 -0
  15. package/dist/read-hash-set.d.ts +18 -0
  16. package/dist/read-linked-array-list.d.ts +13 -0
  17. package/dist/slot-pointer.d.ts +7 -0
  18. package/dist/slot.d.ts +15 -0
  19. package/{src/slotted.ts → dist/slotted.d.ts} +1 -2
  20. package/dist/tag.d.ts +17 -0
  21. package/dist/write-array-list.d.ts +16 -0
  22. package/dist/write-counted-hash-map.d.ts +7 -0
  23. package/dist/write-counted-hash-set.d.ts +7 -0
  24. package/dist/write-cursor.d.ts +36 -0
  25. package/dist/write-hash-map.d.ts +25 -0
  26. package/dist/write-hash-set.d.ts +19 -0
  27. package/dist/write-linked-array-list.d.ts +19 -0
  28. package/dist/writeable-data.d.ts +20 -0
  29. package/package.json +12 -1
  30. package/.claude/settings.local.json +0 -9
  31. package/bun.lock +0 -24
  32. package/bunfig.toml +0 -1
  33. package/example/README.md +0 -46
  34. package/example/dump.ts +0 -201
  35. package/src/core-buffered-file.ts +0 -226
  36. package/src/core-file.ts +0 -137
  37. package/src/core-memory.ts +0 -179
  38. package/src/core.ts +0 -25
  39. package/src/database.ts +0 -2232
  40. package/src/exceptions.ts +0 -31
  41. package/src/hasher.ts +0 -52
  42. package/src/index.ts +0 -110
  43. package/src/read-array-list.ts +0 -45
  44. package/src/read-counted-hash-map.ts +0 -28
  45. package/src/read-counted-hash-set.ts +0 -28
  46. package/src/read-cursor.ts +0 -546
  47. package/src/read-hash-map.ts +0 -117
  48. package/src/read-hash-set.ts +0 -70
  49. package/src/read-linked-array-list.ts +0 -45
  50. package/src/slot-pointer.ts +0 -15
  51. package/src/slot.ts +0 -51
  52. package/src/tag.ts +0 -23
  53. package/src/write-array-list.ts +0 -65
  54. package/src/write-counted-hash-map.ts +0 -31
  55. package/src/write-counted-hash-set.ts +0 -31
  56. package/src/write-cursor.ts +0 -166
  57. package/src/write-hash-map.ts +0 -129
  58. package/src/write-hash-set.ts +0 -86
  59. package/src/write-linked-array-list.ts +0 -80
  60. package/src/writeable-data.ts +0 -67
  61. package/tests/database.test.ts +0 -2519
  62. package/tests/fixtures/test.db +0 -0
  63. package/tsconfig.json +0 -17
@@ -1,2519 +0,0 @@
1
- import { expect, test, describe } from 'bun:test';
2
- import {
3
- Database,
4
- Tag,
5
- Hasher,
6
- CoreMemory,
7
- CoreFile,
8
- CoreBufferedFile,
9
- ReadArrayList,
10
- WriteArrayList,
11
- ReadHashMap,
12
- WriteHashMap,
13
- ReadHashSet,
14
- WriteHashSet,
15
- ReadLinkedArrayList,
16
- WriteLinkedArrayList,
17
- ReadCountedHashMap,
18
- WriteCountedHashMap,
19
- ReadCountedHashSet,
20
- WriteCountedHashSet,
21
- Bytes,
22
- Uint,
23
- Int,
24
- Float,
25
- Slot,
26
- SlotPointer,
27
- InvalidTopLevelTypeException,
28
- InvalidDatabaseException,
29
- InvalidVersionException,
30
- KeyNotFoundException,
31
- EndOfStreamException,
32
- ArrayListInit,
33
- ArrayListGet,
34
- ArrayListAppend,
35
- ArrayListSlice,
36
- HashMapInit,
37
- HashMapGet,
38
- HashMapGetValue,
39
- HashMapGetKey,
40
- HashMapRemove,
41
- LinkedArrayListInit,
42
- LinkedArrayListGet,
43
- LinkedArrayListAppend,
44
- LinkedArrayListSlice,
45
- LinkedArrayListConcat,
46
- LinkedArrayListInsert,
47
- LinkedArrayListRemove,
48
- WriteData,
49
- Context,
50
- VERSION,
51
- SLOT_COUNT,
52
- MASK,
53
- type Core,
54
- type WriteableData,
55
- } from '../src';
56
- import { WriteCursor } from '../src/write-cursor';
57
- import { tmpdir } from 'os';
58
- import { join } from 'path';
59
- import { mkdtemp, rm } from 'fs/promises';
60
-
61
- const MAX_READ_BYTES = 1024;
62
-
63
- describe('Database High Level API', () => {
64
- test('high level API with in-memory storage', async () => {
65
- const core = new CoreMemory();
66
- const hasher = new Hasher('SHA-1');
67
- await testHighLevelApi(core, hasher, null);
68
- });
69
-
70
- test('high level API with file storage', async () => {
71
- const tmpDir = await mkdtemp(join(tmpdir(), 'xitdb-'));
72
- const filePath = join(tmpDir, 'test.db');
73
- try {
74
- using core = await CoreFile.create(filePath);
75
- const hasher = new Hasher('SHA-1');
76
- await testHighLevelApi(core, hasher, filePath);
77
- } finally {
78
- await rm(tmpDir, { recursive: true });
79
- }
80
- });
81
-
82
- test('high level API with buffered file storage', async () => {
83
- const tmpDir = await mkdtemp(join(tmpdir(), 'xitdb-'));
84
- const filePath = join(tmpDir, 'test.db');
85
- try {
86
- using core = await CoreBufferedFile.create(filePath);
87
- const hasher = new Hasher('SHA-1');
88
- await testHighLevelApi(core, hasher, filePath);
89
- } finally {
90
- await rm(tmpDir, { recursive: true });
91
- }
92
- });
93
-
94
- test('not using array list at top level - hash map', async () => {
95
- const core = new CoreMemory();
96
- const hasher = new Hasher('SHA-1');
97
- const db = await Database.create(core, hasher);
98
-
99
- const map = await WriteHashMap.create(await db.rootCursor());
100
- await map.putByString('foo', new Bytes('foo'));
101
- await map.putByString('bar', new Bytes('bar'));
102
-
103
- // init inner map
104
- {
105
- const innerMapCursor = await map.putCursorByString('inner-map');
106
- await WriteHashMap.create(innerMapCursor);
107
- }
108
-
109
- // re-init inner map
110
- {
111
- const innerMapCursor = await map.putCursorByString('inner-map');
112
- await WriteHashMap.create(innerMapCursor);
113
- }
114
- });
115
-
116
- test('not using array list at top level - linked array list throws', async () => {
117
- const core = new CoreMemory();
118
- const hasher = new Hasher('SHA-1');
119
- const db = await Database.create(core, hasher);
120
-
121
- await expect(WriteLinkedArrayList.create(await db.rootCursor())).rejects.toThrow(
122
- InvalidTopLevelTypeException
123
- );
124
- });
125
-
126
- test('read database from fixture', async () => {
127
- const filePath = new URL('./fixtures/test.db', import.meta.url).pathname;
128
- using core = await CoreFile.create(filePath);
129
- const hasher = new Hasher('SHA-1');
130
- const db = await Database.create(core, hasher);
131
- const history = new ReadArrayList(await db.rootCursor());
132
-
133
- // First moment
134
- {
135
- const momentCursor = await history.getCursor(0);
136
- expect(momentCursor).not.toBeNull();
137
- const moment = await ReadHashMap.create(momentCursor!);
138
-
139
- const fooCursor = await moment.getCursorByString('foo');
140
- expect(fooCursor).not.toBeNull();
141
- const fooValue = await fooCursor!.readBytes(MAX_READ_BYTES);
142
- expect(new TextDecoder().decode(fooValue)).toBe('foo');
143
-
144
- const fooSlot = await moment.getSlotByString('foo');
145
- expect(fooSlot?.tag).toBe(Tag.SHORT_BYTES);
146
- const barSlot = await moment.getSlotByString('bar');
147
- expect(barSlot?.tag).toBe(Tag.SHORT_BYTES);
148
-
149
- const fruitsCursor = await moment.getCursorByString('fruits');
150
- expect(fruitsCursor).not.toBeNull();
151
- const fruits = new ReadArrayList(fruitsCursor!);
152
- expect(await fruits.count()).toBe(3);
153
-
154
- const appleCursor = await fruits.getCursor(0);
155
- expect(appleCursor).not.toBeNull();
156
- const appleValue = await appleCursor!.readBytes(MAX_READ_BYTES);
157
- expect(new TextDecoder().decode(appleValue)).toBe('apple');
158
-
159
- const peopleCursor = await moment.getCursorByString('people');
160
- expect(peopleCursor).not.toBeNull();
161
- const people = new ReadArrayList(peopleCursor!);
162
- expect(await people.count()).toBe(2);
163
-
164
- const aliceCursor = await people.getCursor(0);
165
- expect(aliceCursor).not.toBeNull();
166
- const alice = await ReadHashMap.create(aliceCursor!);
167
- const aliceAgeCursor = await alice.getCursorByString('age');
168
- expect(aliceAgeCursor).not.toBeNull();
169
- expect(aliceAgeCursor!.readUint()).toBe(25);
170
-
171
- const todosCursor = await moment.getCursorByString('todos');
172
- expect(todosCursor).not.toBeNull();
173
- const todos = new ReadLinkedArrayList(todosCursor!);
174
- expect(await todos.count()).toBe(3);
175
-
176
- const todoCursor = await todos.getCursor(0);
177
- expect(todoCursor).not.toBeNull();
178
- const todoValue = await todoCursor!.readBytes(MAX_READ_BYTES);
179
- expect(new TextDecoder().decode(todoValue)).toBe('Pay the bills');
180
-
181
- // Test iterating over people
182
- const peopleIter = people.iterator();
183
- await peopleIter.init();
184
- while (await peopleIter.hasNext()) {
185
- const personCursor = await peopleIter.next();
186
- expect(personCursor).not.toBeNull();
187
- const person = await ReadHashMap.create(personCursor!);
188
- const personIter = person.iterator();
189
- await personIter.init();
190
- while (await personIter.hasNext()) {
191
- const kvPairCursor = await personIter.next();
192
- expect(kvPairCursor).not.toBeNull();
193
- await kvPairCursor!.readKeyValuePair();
194
- }
195
- }
196
-
197
- // Counted hash map
198
- {
199
- const lettersCountedMapCursor = await moment.getCursorByString('letters-counted-map');
200
- expect(lettersCountedMapCursor).not.toBeNull();
201
- const lettersCountedMap = await ReadCountedHashMap.create(lettersCountedMapCursor!);
202
- expect(await lettersCountedMap.count()).toBe(2);
203
-
204
- const iter = lettersCountedMap.iterator();
205
- await iter.init();
206
- let count = 0;
207
- while (await iter.hasNext()) {
208
- const kvPairCursor = await iter.next();
209
- expect(kvPairCursor).not.toBeNull();
210
- const kvPair = await kvPairCursor!.readKeyValuePair();
211
- await kvPair.keyCursor.readBytes(MAX_READ_BYTES);
212
- count += 1;
213
- }
214
- expect(count).toBe(2);
215
- }
216
-
217
- // Hash set
218
- {
219
- const lettersSetCursor = await moment.getCursorByString('letters-set');
220
- expect(lettersSetCursor).not.toBeNull();
221
- const lettersSet = await ReadHashSet.create(lettersSetCursor!);
222
- expect(await lettersSet.getCursorByString('a')).not.toBeNull();
223
- expect(await lettersSet.getCursorByString('c')).not.toBeNull();
224
-
225
- const iter = lettersSet.iterator();
226
- await iter.init();
227
- let count = 0;
228
- while (await iter.hasNext()) {
229
- const kvPairCursor = await iter.next();
230
- expect(kvPairCursor).not.toBeNull();
231
- const kvPair = await kvPairCursor!.readKeyValuePair();
232
- await kvPair.keyCursor.readBytes(MAX_READ_BYTES);
233
- count += 1;
234
- }
235
- expect(count).toBe(2);
236
- }
237
-
238
- // Counted hash set
239
- {
240
- const lettersCountedSetCursor = await moment.getCursorByString('letters-counted-set');
241
- expect(lettersCountedSetCursor).not.toBeNull();
242
- const lettersCountedSet = await ReadCountedHashSet.create(lettersCountedSetCursor!);
243
- expect(await lettersCountedSet.count()).toBe(2);
244
-
245
- const iter = lettersCountedSet.iterator();
246
- await iter.init();
247
- let count = 0;
248
- while (await iter.hasNext()) {
249
- const kvPairCursor = await iter.next();
250
- expect(kvPairCursor).not.toBeNull();
251
- const kvPair = await kvPairCursor!.readKeyValuePair();
252
- await kvPair.keyCursor.readBytes(MAX_READ_BYTES);
253
- count += 1;
254
- }
255
- expect(count).toBe(2);
256
- }
257
- }
258
-
259
- // Second moment
260
- {
261
- const momentCursor = await history.getCursor(1);
262
- expect(momentCursor).not.toBeNull();
263
- const moment = await ReadHashMap.create(momentCursor!);
264
-
265
- expect(await moment.getCursorByString('bar')).toBeNull();
266
-
267
- const fruitsKeyCursor = await moment.getKeyCursorByString('fruits');
268
- expect(fruitsKeyCursor).not.toBeNull();
269
- const fruitsKeyValue = await fruitsKeyCursor!.readBytes(MAX_READ_BYTES);
270
- expect(new TextDecoder().decode(fruitsKeyValue)).toBe('fruits');
271
-
272
- const fruitsCursor = await moment.getCursorByString('fruits');
273
- expect(fruitsCursor).not.toBeNull();
274
- const fruits = new ReadArrayList(fruitsCursor!);
275
- expect(await fruits.count()).toBe(2);
276
-
277
- const fruitsKVCursor = await moment.getKeyValuePairByString('fruits');
278
- expect(fruitsKVCursor).not.toBeNull();
279
- expect(fruitsKVCursor!.keyCursor.slotPtr.slot.tag).toBe(Tag.SHORT_BYTES);
280
- expect(fruitsKVCursor!.valueCursor.slotPtr.slot.tag).toBe(Tag.ARRAY_LIST);
281
-
282
- const lemonCursor = await fruits.getCursor(0);
283
- expect(lemonCursor).not.toBeNull();
284
- const lemonValue = await lemonCursor!.readBytes(MAX_READ_BYTES);
285
- expect(new TextDecoder().decode(lemonValue)).toBe('lemon');
286
-
287
- const peopleCursor = await moment.getCursorByString('people');
288
- expect(peopleCursor).not.toBeNull();
289
- const people = new ReadArrayList(peopleCursor!);
290
- expect(await people.count()).toBe(2);
291
-
292
- const aliceCursor = await people.getCursor(0);
293
- expect(aliceCursor).not.toBeNull();
294
- const alice = await ReadHashMap.create(aliceCursor!);
295
- const aliceAgeCursor = await alice.getCursorByString('age');
296
- expect(aliceAgeCursor).not.toBeNull();
297
- expect(aliceAgeCursor!.readUint()).toBe(26);
298
-
299
- const todosCursor = await moment.getCursorByString('todos');
300
- expect(todosCursor).not.toBeNull();
301
- const todos = new ReadLinkedArrayList(todosCursor!);
302
- expect(await todos.count()).toBe(1);
303
-
304
- const todoCursor = await todos.getCursor(0);
305
- expect(todoCursor).not.toBeNull();
306
- const todoValue = await todoCursor!.readBytes(MAX_READ_BYTES);
307
- expect(new TextDecoder().decode(todoValue)).toBe('Wash the car');
308
-
309
- const lettersCountedMapCursor = await moment.getCursorByString('letters-counted-map');
310
- expect(lettersCountedMapCursor).not.toBeNull();
311
- const lettersCountedMap = await ReadCountedHashMap.create(lettersCountedMapCursor!);
312
- expect(await lettersCountedMap.count()).toBe(1);
313
-
314
- const lettersSetCursor = await moment.getCursorByString('letters-set');
315
- expect(lettersSetCursor).not.toBeNull();
316
- const lettersSet = await ReadHashSet.create(lettersSetCursor!);
317
- expect(await lettersSet.getCursorByString('a')).not.toBeNull();
318
- expect(await lettersSet.getCursorByString('c')).toBeNull();
319
-
320
- const lettersCountedSetCursor = await moment.getCursorByString('letters-counted-set');
321
- expect(lettersCountedSetCursor).not.toBeNull();
322
- const lettersCountedSet = await ReadCountedHashSet.create(lettersCountedSetCursor!);
323
- expect(await lettersCountedSet.count()).toBe(1);
324
- }
325
- });
326
-
327
- test('low level memory operations', async () => {
328
- const core = new CoreMemory();
329
- const hasher = new Hasher('SHA-1');
330
- const db = await Database.create(core, hasher);
331
-
332
- const map = await WriteHashMap.create(await db.rootCursor());
333
- const textCursor = await map.putCursorByString('text');
334
-
335
- const writer = await textCursor.writer();
336
- await writer.write(new TextEncoder().encode('goodbye, world!'));
337
- writer.seek(9);
338
- await writer.write(new TextEncoder().encode('cruel world!'));
339
- await writer.finish();
340
-
341
- const reader = await textCursor.reader();
342
- const allBytes = new Uint8Array(Number(await textCursor.count()));
343
- await reader.readFully(allBytes);
344
- expect(new TextDecoder().decode(allBytes)).toBe('goodbye, cruel world!');
345
- });
346
- });
347
-
348
- describe('Database Low Level API', () => {
349
- test('low level API with in-memory storage', async () => {
350
- const core = new CoreMemory();
351
- const hasher = new Hasher('SHA-1');
352
- await testLowLevelApi(core, hasher);
353
- });
354
-
355
- test('low level API with file storage', async () => {
356
- const tmpDir = await mkdtemp(join(tmpdir(), 'xitdb-'));
357
- const filePath = join(tmpDir, 'test.db');
358
- try {
359
- using core = await CoreFile.create(filePath);
360
- const hasher = new Hasher('SHA-1');
361
- await testLowLevelApi(core, hasher);
362
- } finally {
363
- await rm(tmpDir, { recursive: true });
364
- }
365
- }, 20000);
366
-
367
- test('low level API with buffered file storage', async () => {
368
- const tmpDir = await mkdtemp(join(tmpdir(), 'xitdb-'));
369
- const filePath = join(tmpDir, 'test.db');
370
- try {
371
- using core = await CoreBufferedFile.create(filePath);
372
- const hasher = new Hasher('SHA-1');
373
- await testLowLevelApi(core, hasher);
374
- } finally {
375
- await rm(tmpDir, { recursive: true });
376
- }
377
- }, 20000);
378
- });
379
-
380
- // Helper function for high level API tests
381
- async function testHighLevelApi(core: Core, hasher: Hasher, filePath: string | null): Promise<void> {
382
- // init the db
383
- await core.setLength(0);
384
- let db = await Database.create(core, hasher);
385
-
386
- // First transaction
387
- {
388
- const history = await WriteArrayList.create(await db.rootCursor());
389
- await history.appendContext(await history.getSlot(-1), async (cursor) => {
390
- const moment = await WriteHashMap.create(cursor);
391
-
392
- await moment.putByString('foo', new Bytes('foo'));
393
- await moment.putByString('bar', new Bytes('bar'));
394
-
395
- const fruitsCursor = await moment.putCursorByString('fruits');
396
- const fruits = await WriteArrayList.create(fruitsCursor);
397
- await fruits.append(new Bytes('apple'));
398
- await fruits.append(new Bytes('pear'));
399
- await fruits.append(new Bytes('grape'));
400
-
401
- const peopleCursor = await moment.putCursorByString('people');
402
- const people = await WriteArrayList.create(peopleCursor);
403
-
404
- const aliceCursor = await people.appendCursor();
405
- const alice = await WriteHashMap.create(aliceCursor);
406
- await alice.putByString('name', new Bytes('Alice'));
407
- await alice.putByString('age', new Uint(25));
408
-
409
- const bobCursor = await people.appendCursor();
410
- const bob = await WriteHashMap.create(bobCursor);
411
- await bob.putByString('name', new Bytes('Bob'));
412
- await bob.putByString('age', new Uint(42));
413
-
414
- const todosCursor = await moment.putCursorByString('todos');
415
- const todos = await WriteLinkedArrayList.create(todosCursor);
416
- await todos.append(new Bytes('Pay the bills'));
417
- await todos.append(new Bytes('Get an oil change'));
418
- await todos.insert(1, new Bytes('Wash the car'));
419
-
420
- // make sure insertCursor works as well
421
- const todoCursor = await todos.insertCursor(1);
422
- await WriteHashMap.create(todoCursor);
423
- await todos.remove(1);
424
-
425
- const lettersCountedMapCursor = await moment.putCursorByString('letters-counted-map');
426
- const lettersCountedMap = await WriteCountedHashMap.create(lettersCountedMapCursor);
427
- await lettersCountedMap.putByString('a', new Uint(1));
428
- await lettersCountedMap.putByString('a', new Uint(2));
429
- await lettersCountedMap.putByString('c', new Uint(2));
430
-
431
- const lettersSetCursor = await moment.putCursorByString('letters-set');
432
- const lettersSet = await WriteHashSet.create(lettersSetCursor);
433
- await lettersSet.putByString('a');
434
- await lettersSet.putByString('a');
435
- await lettersSet.putByString('c');
436
-
437
- const lettersCountedSetCursor = await moment.putCursorByString('letters-counted-set');
438
- const lettersCountedSet = await WriteCountedHashSet.create(lettersCountedSetCursor);
439
- await lettersCountedSet.putByString('a');
440
- await lettersCountedSet.putByString('a');
441
- await lettersCountedSet.putByString('c');
442
-
443
- // big int with format tag
444
- const bigIntBytes = new Uint8Array(32);
445
- bigIntBytes.fill(42); // deterministic bytes
446
- await moment.putByString('big-number', new Bytes(bigIntBytes, new TextEncoder().encode('bi')));
447
-
448
- // long text using writer
449
- const longTextCursor = await moment.putCursorByString('long-text');
450
- const cursorWriter = await longTextCursor.writer();
451
- for (let i = 0; i < 50; i++) {
452
- await cursorWriter.write(new TextEncoder().encode('hello, world\n'));
453
- }
454
- await cursorWriter.finish();
455
- });
456
-
457
- // Verify first transaction
458
- const momentCursor = await history.getCursor(-1);
459
- const moment = await ReadHashMap.create(momentCursor!);
460
-
461
- const fooCursor = await moment.getCursorByString('foo');
462
- const fooValue = await fooCursor!.readBytes(MAX_READ_BYTES);
463
- expect(new TextDecoder().decode(fooValue)).toBe('foo');
464
-
465
- expect((await moment.getSlotByString('foo'))?.tag).toBe(Tag.SHORT_BYTES);
466
- expect((await moment.getSlotByString('bar'))?.tag).toBe(Tag.SHORT_BYTES);
467
-
468
- const fruitsCursor = await moment.getCursorByString('fruits');
469
- const fruits = new ReadArrayList(fruitsCursor!);
470
- expect(await fruits.count()).toBe(3);
471
-
472
- const appleCursor = await fruits.getCursor(0);
473
- const appleValue = await appleCursor!.readBytes(MAX_READ_BYTES);
474
- expect(new TextDecoder().decode(appleValue)).toBe('apple');
475
-
476
- const peopleCursor = await moment.getCursorByString('people');
477
- const people = new ReadArrayList(peopleCursor!);
478
- expect(await people.count()).toBe(2);
479
-
480
- const aliceCursor = await people.getCursor(0);
481
- const alice = await ReadHashMap.create(aliceCursor!);
482
- const aliceAgeCursor = await alice.getCursorByString('age');
483
- expect(aliceAgeCursor!.readUint()).toBe(25);
484
-
485
- const todosCursor = await moment.getCursorByString('todos');
486
- const todos = new ReadLinkedArrayList(todosCursor!);
487
- expect(await todos.count()).toBe(3);
488
-
489
- const todoCursor = await todos.getCursor(0);
490
- const todoValue = await todoCursor!.readBytes(MAX_READ_BYTES);
491
- expect(new TextDecoder().decode(todoValue)).toBe('Pay the bills');
492
-
493
- // iterate over people
494
- const peopleIter = people.iterator();
495
- await peopleIter.init();
496
- while (await peopleIter.hasNext()) {
497
- const personCursor = await peopleIter.next();
498
- const person = await ReadHashMap.create(personCursor!);
499
- const personIter = person.iterator();
500
- await personIter.init();
501
- while (await personIter.hasNext()) {
502
- const kvPairCursor = await personIter.next();
503
- const kvPair = await kvPairCursor!.readKeyValuePair();
504
- await kvPair.keyCursor.readBytes(MAX_READ_BYTES);
505
-
506
- switch (kvPair.valueCursor.slot().tag) {
507
- case Tag.SHORT_BYTES:
508
- case Tag.BYTES:
509
- await kvPair.valueCursor.readBytes(MAX_READ_BYTES);
510
- break;
511
- case Tag.UINT:
512
- kvPair.valueCursor.readUint();
513
- break;
514
- case Tag.INT:
515
- kvPair.valueCursor.readInt();
516
- break;
517
- case Tag.FLOAT:
518
- kvPair.valueCursor.readFloat();
519
- break;
520
- }
521
- }
522
- }
523
-
524
- // iterate over fruits
525
- const fruitsIter = fruits.iterator();
526
- await fruitsIter.init();
527
- while (await fruitsIter.hasNext()) {
528
- await fruitsIter.next();
529
- }
530
-
531
- // Counted hash map
532
- {
533
- const lettersCountedMapCursor = await moment.getCursorByString('letters-counted-map');
534
- const lettersCountedMap = await ReadCountedHashMap.create(lettersCountedMapCursor!);
535
- expect(await lettersCountedMap.count()).toBe(2);
536
-
537
- const iter = lettersCountedMap.iterator();
538
- await iter.init();
539
- let count = 0;
540
- while (await iter.hasNext()) {
541
- const kvPairCursor = await iter.next();
542
- const kvPair = await kvPairCursor!.readKeyValuePair();
543
- await kvPair.keyCursor.readBytes(MAX_READ_BYTES);
544
- count += 1;
545
- }
546
- expect(count).toBe(2);
547
- }
548
-
549
- // Hash set
550
- {
551
- const lettersSetCursor = await moment.getCursorByString('letters-set');
552
- const lettersSet = await ReadHashSet.create(lettersSetCursor!);
553
- expect(await lettersSet.getCursorByString('a')).not.toBeNull();
554
- expect(await lettersSet.getCursorByString('c')).not.toBeNull();
555
-
556
- const iter = lettersSet.iterator();
557
- await iter.init();
558
- let count = 0;
559
- while (await iter.hasNext()) {
560
- const kvPairCursor = await iter.next();
561
- const kvPair = await kvPairCursor!.readKeyValuePair();
562
- await kvPair.keyCursor.readBytes(MAX_READ_BYTES);
563
- count += 1;
564
- }
565
- expect(count).toBe(2);
566
- }
567
-
568
- // Counted hash set
569
- {
570
- const lettersCountedSetCursor = await moment.getCursorByString('letters-counted-set');
571
- const lettersCountedSet = await ReadCountedHashSet.create(lettersCountedSetCursor!);
572
- expect(await lettersCountedSet.count()).toBe(2);
573
-
574
- const iter = lettersCountedSet.iterator();
575
- await iter.init();
576
- let count = 0;
577
- while (await iter.hasNext()) {
578
- const kvPairCursor = await iter.next();
579
- const kvPair = await kvPairCursor!.readKeyValuePair();
580
- await kvPair.keyCursor.readBytes(MAX_READ_BYTES);
581
- count += 1;
582
- }
583
- expect(count).toBe(2);
584
- }
585
-
586
- // big number with format tag
587
- {
588
- const bigNumberCursor = await moment.getCursorByString('big-number');
589
- const bigNumber = await bigNumberCursor!.readBytesObject(MAX_READ_BYTES);
590
- expect(bigNumber.value.length).toBe(32);
591
- expect(bigNumber.value[0]).toBe(42);
592
- expect(new TextDecoder().decode(bigNumber.formatTag!)).toBe('bi');
593
- }
594
-
595
- // long text
596
- {
597
- const longTextCursor = await moment.getCursorByString('long-text');
598
- const cursorReader = await longTextCursor!.reader();
599
- const content = new Uint8Array(Number(await longTextCursor!.count()));
600
- await cursorReader.readFully(content);
601
- const lines = new TextDecoder().decode(content).split('\n').filter(l => l.length > 0);
602
- expect(lines.length).toBe(50);
603
- }
604
- }
605
-
606
- // Second transaction - modify data
607
- {
608
- const history = await WriteArrayList.create(await db.rootCursor());
609
- await history.appendContext(await history.getSlot(-1), async (cursor) => {
610
- const moment = await WriteHashMap.create(cursor);
611
-
612
- expect(await moment.removeByString('bar')).toBe(true);
613
- expect(await moment.removeByString("doesn't exist")).toBe(false);
614
-
615
- const fruitsCursor = await moment.putCursorByString('fruits');
616
- const fruits = await WriteArrayList.create(fruitsCursor);
617
- await fruits.put(0, new Bytes('lemon'));
618
- await fruits.slice(2);
619
-
620
- const peopleCursor = await moment.putCursorByString('people');
621
- const people = await WriteArrayList.create(peopleCursor);
622
- const aliceCursor = await people.putCursor(0);
623
- const alice = await WriteHashMap.create(aliceCursor);
624
- await alice.putByString('age', new Uint(26));
625
-
626
- const todosCursor = await moment.putCursorByString('todos');
627
- const todos = await WriteLinkedArrayList.create(todosCursor);
628
- await todos.concat(todosCursor.slot());
629
- await todos.slice(1, 2);
630
- await todos.remove(1);
631
-
632
- const lettersCountedMapCursor = await moment.putCursorByString('letters-counted-map');
633
- const lettersCountedMap = await WriteCountedHashMap.create(lettersCountedMapCursor);
634
- await lettersCountedMap.removeByString('b');
635
- await lettersCountedMap.removeByString('c');
636
-
637
- const lettersSetCursor = await moment.putCursorByString('letters-set');
638
- const lettersSet = await WriteHashSet.create(lettersSetCursor);
639
- await lettersSet.removeByString('b');
640
- await lettersSet.removeByString('c');
641
-
642
- const lettersCountedSetCursor = await moment.putCursorByString('letters-counted-set');
643
- const lettersCountedSet = await WriteCountedHashSet.create(lettersCountedSetCursor);
644
- await lettersCountedSet.removeByString('b');
645
- await lettersCountedSet.removeByString('c');
646
- });
647
-
648
- // Verify second transaction
649
- const momentCursor = await history.getCursor(-1);
650
- const moment = await ReadHashMap.create(momentCursor!);
651
-
652
- expect(await moment.getCursorByString('bar')).toBeNull();
653
-
654
- const fruitsKeyCursor = await moment.getKeyCursorByString('fruits');
655
- const fruitsKeyValue = await fruitsKeyCursor!.readBytes(MAX_READ_BYTES);
656
- expect(new TextDecoder().decode(fruitsKeyValue)).toBe('fruits');
657
-
658
- const fruitsCursor = await moment.getCursorByString('fruits');
659
- const fruits = new ReadArrayList(fruitsCursor!);
660
- expect(await fruits.count()).toBe(2);
661
-
662
- const fruitsKVCursor = await moment.getKeyValuePairByString('fruits');
663
- expect(fruitsKVCursor!.keyCursor.slotPtr.slot.tag).toBe(Tag.SHORT_BYTES);
664
- expect(fruitsKVCursor!.valueCursor.slotPtr.slot.tag).toBe(Tag.ARRAY_LIST);
665
-
666
- const lemonCursor = await fruits.getCursor(0);
667
- const lemonValue = await lemonCursor!.readBytes(MAX_READ_BYTES);
668
- expect(new TextDecoder().decode(lemonValue)).toBe('lemon');
669
-
670
- const peopleCursor = await moment.getCursorByString('people');
671
- const people = new ReadArrayList(peopleCursor!);
672
- expect(await people.count()).toBe(2);
673
-
674
- const aliceCursor = await people.getCursor(0);
675
- const alice = await ReadHashMap.create(aliceCursor!);
676
- const aliceAgeCursor = await alice.getCursorByString('age');
677
- expect(aliceAgeCursor!.readUint()).toBe(26);
678
-
679
- const todosCursor = await moment.getCursorByString('todos');
680
- const todos = new ReadLinkedArrayList(todosCursor!);
681
- expect(await todos.count()).toBe(1);
682
-
683
- const todoCursor = await todos.getCursor(0);
684
- const todoValue = await todoCursor!.readBytes(MAX_READ_BYTES);
685
- expect(new TextDecoder().decode(todoValue)).toBe('Wash the car');
686
-
687
- const lettersCountedMapCursor = await moment.getCursorByString('letters-counted-map');
688
- const lettersCountedMap = await ReadCountedHashMap.create(lettersCountedMapCursor!);
689
- expect(await lettersCountedMap.count()).toBe(1);
690
-
691
- const lettersSetCursor = await moment.getCursorByString('letters-set');
692
- const lettersSet = await ReadHashSet.create(lettersSetCursor!);
693
- expect(await lettersSet.getCursorByString('a')).not.toBeNull();
694
- expect(await lettersSet.getCursorByString('c')).toBeNull();
695
-
696
- const lettersCountedSetCursor = await moment.getCursorByString('letters-counted-set');
697
- const lettersCountedSet = await ReadCountedHashSet.create(lettersCountedSetCursor!);
698
- expect(await lettersCountedSet.count()).toBe(1);
699
- }
700
-
701
- // The old data hasn't changed
702
- {
703
- const history = await WriteArrayList.create(await db.rootCursor());
704
- const momentCursor = await history.getCursor(0);
705
- const moment = await ReadHashMap.create(momentCursor!);
706
-
707
- const fooCursor = await moment.getCursorByString('foo');
708
- const fooValue = await fooCursor!.readBytes(MAX_READ_BYTES);
709
- expect(new TextDecoder().decode(fooValue)).toBe('foo');
710
-
711
- expect((await moment.getSlotByString('foo'))?.tag).toBe(Tag.SHORT_BYTES);
712
- expect((await moment.getSlotByString('bar'))?.tag).toBe(Tag.SHORT_BYTES);
713
-
714
- const fruitsCursor = await moment.getCursorByString('fruits');
715
- const fruits = new ReadArrayList(fruitsCursor!);
716
- expect(await fruits.count()).toBe(3);
717
-
718
- const appleCursor = await fruits.getCursor(0);
719
- const appleValue = await appleCursor!.readBytes(MAX_READ_BYTES);
720
- expect(new TextDecoder().decode(appleValue)).toBe('apple');
721
-
722
- const peopleCursor = await moment.getCursorByString('people');
723
- const people = new ReadArrayList(peopleCursor!);
724
- expect(await people.count()).toBe(2);
725
-
726
- const aliceCursor = await people.getCursor(0);
727
- const alice = await ReadHashMap.create(aliceCursor!);
728
- const aliceAgeCursor = await alice.getCursorByString('age');
729
- expect(aliceAgeCursor!.readUint()).toBe(25);
730
-
731
- const todosCursor = await moment.getCursorByString('todos');
732
- const todos = new ReadLinkedArrayList(todosCursor!);
733
- expect(await todos.count()).toBe(3);
734
-
735
- const todoCursor = await todos.getCursor(0);
736
- const todoValue = await todoCursor!.readBytes(MAX_READ_BYTES);
737
- expect(new TextDecoder().decode(todoValue)).toBe('Pay the bills');
738
- }
739
-
740
- // Remove the last transaction with slice
741
- {
742
- const history = await WriteArrayList.create(await db.rootCursor());
743
- await history.slice(1);
744
-
745
- const momentCursor = await history.getCursor(-1);
746
- const moment = await ReadHashMap.create(momentCursor!);
747
-
748
- const fooCursor = await moment.getCursorByString('foo');
749
- const fooValue = await fooCursor!.readBytes(MAX_READ_BYTES);
750
- expect(new TextDecoder().decode(fooValue)).toBe('foo');
751
-
752
- expect((await moment.getSlotByString('foo'))?.tag).toBe(Tag.SHORT_BYTES);
753
- expect((await moment.getSlotByString('bar'))?.tag).toBe(Tag.SHORT_BYTES);
754
-
755
- const fruitsCursor = await moment.getCursorByString('fruits');
756
- const fruits = new ReadArrayList(fruitsCursor!);
757
- expect(await fruits.count()).toBe(3);
758
-
759
- const appleCursor = await fruits.getCursor(0);
760
- const appleValue = await appleCursor!.readBytes(MAX_READ_BYTES);
761
- expect(new TextDecoder().decode(appleValue)).toBe('apple');
762
-
763
- const peopleCursor = await moment.getCursorByString('people');
764
- const people = new ReadArrayList(peopleCursor!);
765
- expect(await people.count()).toBe(2);
766
-
767
- const aliceCursor = await people.getCursor(0);
768
- const alice = await ReadHashMap.create(aliceCursor!);
769
- const aliceAgeCursor = await alice.getCursorByString('age');
770
- expect(aliceAgeCursor!.readUint()).toBe(25);
771
-
772
- const todosCursor = await moment.getCursorByString('todos');
773
- const todos = new ReadLinkedArrayList(todosCursor!);
774
- expect(await todos.count()).toBe(3);
775
-
776
- const todoCursor = await todos.getCursor(0);
777
- const todoValue = await todoCursor!.readBytes(MAX_READ_BYTES);
778
- expect(new TextDecoder().decode(todoValue)).toBe('Pay the bills');
779
- }
780
-
781
- // The db size remains the same after writing junk data and then reinitializing the db
782
- {
783
- await core.seek(await core.length());
784
- const sizeBefore = await core.length();
785
-
786
- const writer = core.writer();
787
- await writer.write(new TextEncoder().encode('this is junk data that will be deleted during init'));
788
-
789
- db = await Database.create(core, hasher);
790
-
791
- const sizeAfter = await core.length();
792
- expect(sizeBefore).toBe(sizeAfter);
793
- }
794
-
795
- // Cloning
796
- {
797
- const history = await WriteArrayList.create(await db.rootCursor());
798
- await history.appendContext(await history.getSlot(-1), async (cursor) => {
799
- const moment = await WriteHashMap.create(cursor);
800
-
801
- const fruitsCursor = await moment.getCursorByString('fruits');
802
- const fruits = new ReadArrayList(fruitsCursor!);
803
-
804
- // create a new key called "food" whose initial value is based on the "fruits" list
805
- const foodCursor = await moment.putCursorByString('food');
806
- await foodCursor.write(fruits.slot());
807
-
808
- const food = await WriteArrayList.create(foodCursor);
809
- await food.append(new Bytes('eggs'));
810
- await food.append(new Bytes('rice'));
811
- await food.append(new Bytes('fish'));
812
- });
813
-
814
- const momentCursor = await history.getCursor(-1);
815
- const moment = await ReadHashMap.create(momentCursor!);
816
-
817
- // the food list includes the fruits
818
- const foodCursor = await moment.getCursorByString('food');
819
- const food = new ReadArrayList(foodCursor!);
820
- expect(await food.count()).toBe(6);
821
-
822
- // ...but the fruits list hasn't been changed
823
- const fruitsCursor = await moment.getCursorByString('fruits');
824
- const fruits = new ReadArrayList(fruitsCursor!);
825
- expect(await fruits.count()).toBe(3);
826
- }
827
-
828
- // Accidental mutation when cloning inside a transaction
829
- {
830
- const history = await WriteArrayList.create(await db.rootCursor());
831
- const historyIndex = (await history.count()) - 1;
832
-
833
- await history.appendContext(await history.getSlot(-1), async (cursor) => {
834
- const moment = await WriteHashMap.create(cursor);
835
-
836
- const bigCitiesCursor = await moment.putCursorByString('big-cities');
837
- const bigCities = await WriteArrayList.create(bigCitiesCursor);
838
- await bigCities.append(new Bytes('New York, NY'));
839
- await bigCities.append(new Bytes('Los Angeles, CA'));
840
-
841
- // create a new key called "cities" whose initial value is based on the "big-cities" list
842
- const citiesCursor = await moment.putCursorByString('cities');
843
- await citiesCursor.write(bigCities.slot());
844
-
845
- const cities = await WriteArrayList.create(citiesCursor);
846
- await cities.append(new Bytes('Charleston, SC'));
847
- await cities.append(new Bytes('Louisville, KY'));
848
- });
849
-
850
- const momentCursor = await history.getCursor(-1);
851
- const moment = await ReadHashMap.create(momentCursor!);
852
-
853
- // the cities list contains all four
854
- const citiesCursor = await moment.getCursorByString('cities');
855
- const cities = new ReadArrayList(citiesCursor!);
856
- expect(await cities.count()).toBe(4);
857
-
858
- // ..but so does big-cities! we did not intend to mutate this
859
- const bigCitiesCursor = await moment.getCursorByString('big-cities');
860
- const bigCities = new ReadArrayList(bigCitiesCursor!);
861
- expect(await bigCities.count()).toBe(4);
862
-
863
- // revert that change
864
- await history.append((await history.getSlot(historyIndex))!);
865
- }
866
-
867
- // Preventing accidental mutation with freezing
868
- {
869
- const history = await WriteArrayList.create(await db.rootCursor());
870
- await history.appendContext(await history.getSlot(-1), async (cursor) => {
871
- const moment = await WriteHashMap.create(cursor);
872
-
873
- const bigCitiesCursor = await moment.putCursorByString('big-cities');
874
- const bigCities = await WriteArrayList.create(bigCitiesCursor);
875
- await bigCities.append(new Bytes('New York, NY'));
876
- await bigCities.append(new Bytes('Los Angeles, CA'));
877
-
878
- // freeze here, so big-cities won't be mutated
879
- cursor.db.freeze();
880
-
881
- // create a new key called "cities" whose initial value is based on the "big-cities" list
882
- const citiesCursor = await moment.putCursorByString('cities');
883
- await citiesCursor.write(bigCities.slot());
884
-
885
- const cities = await WriteArrayList.create(citiesCursor);
886
- await cities.append(new Bytes('Charleston, SC'));
887
- await cities.append(new Bytes('Louisville, KY'));
888
- });
889
-
890
- const momentCursor = await history.getCursor(-1);
891
- const moment = await ReadHashMap.create(momentCursor!);
892
-
893
- // the cities list contains all four
894
- const citiesCursor = await moment.getCursorByString('cities');
895
- const cities = new ReadArrayList(citiesCursor!);
896
- expect(await cities.count()).toBe(4);
897
-
898
- // and big-cities only contains the original two
899
- const bigCitiesCursor = await moment.getCursorByString('big-cities');
900
- const bigCities = new ReadArrayList(bigCitiesCursor!);
901
- expect(await bigCities.count()).toBe(2);
902
- }
903
- }
904
-
905
- // Helper function for low level API tests
906
- async function testLowLevelApi(core: Core, hasher: Hasher): Promise<void> {
907
- // open and re-open database
908
- {
909
- // make empty database
910
- await core.setLength(0);
911
- await Database.create(core, hasher);
912
-
913
- // re-open without error
914
- let db = await Database.create(core, hasher);
915
- const writer = db.core.writer();
916
- await db.core.seek(0);
917
- await writer.writeByte('g'.charCodeAt(0));
918
-
919
- // re-open with error
920
- await expect(Database.create(core, hasher)).rejects.toThrow(InvalidDatabaseException);
921
-
922
- // modify the version
923
- await db.core.seek(0);
924
- await writer.writeByte('x'.charCodeAt(0));
925
- await db.core.seek(4);
926
- await writer.writeShort(VERSION + 1);
927
-
928
- // re-open with error
929
- await expect(Database.create(core, hasher)).rejects.toThrow(InvalidVersionException);
930
- }
931
-
932
- // save hash id in header
933
- {
934
- const hashId = Hasher.stringToId('sha1');
935
- const hasherWithHashId = new Hasher('SHA-1', hashId);
936
-
937
- // make empty database
938
- await core.setLength(0);
939
- const db = await Database.create(core, hasherWithHashId);
940
-
941
- // verify hash id was stored
942
- expect(db.hasher.id).toBe(hashId);
943
- expect(Hasher.idToString(db.hasher.id)).toBe('sha1');
944
- }
945
-
946
- // array_list of hash_maps
947
- {
948
- await core.setLength(0);
949
- const db = await Database.create(core, hasher);
950
- const rootCursor = await db.rootCursor();
951
-
952
- // write foo -> bar with a writer
953
- const fooKey = await db.hasher.digest(new TextEncoder().encode('foo'));
954
- await rootCursor.writePath([
955
- new ArrayListInit(),
956
- new ArrayListAppend(),
957
- new WriteData(await rootCursor.readPathSlot([new ArrayListGet(-1)])),
958
- new HashMapInit(false, false),
959
- new HashMapGet(new HashMapGetValue(fooKey)),
960
- new Context(async (cursor) => {
961
- expect(cursor.slot().tag).toBe(Tag.NONE);
962
- const writer = await cursor.writer();
963
- await writer.write(new TextEncoder().encode('bar'));
964
- await writer.finish();
965
- }),
966
- ]);
967
-
968
- // read foo
969
- {
970
- const barCursor = await rootCursor.readPath([
971
- new ArrayListGet(-1),
972
- new HashMapGet(new HashMapGetValue(fooKey)),
973
- ]);
974
- expect(await barCursor!.count()).toBe(3);
975
- const barValue = await barCursor!.readBytes(MAX_READ_BYTES);
976
- expect(new TextDecoder().decode(barValue)).toBe('bar');
977
- }
978
-
979
- // read foo from ctx
980
- await rootCursor.writePath([
981
- new ArrayListInit(),
982
- new ArrayListAppend(),
983
- new WriteData(await rootCursor.readPathSlot([new ArrayListGet(-1)])),
984
- new HashMapInit(false, false),
985
- new HashMapGet(new HashMapGetValue(fooKey)),
986
- new Context(async (cursor) => {
987
- expect(cursor.slot().tag).not.toBe(Tag.NONE);
988
-
989
- const value = await cursor.readBytes(MAX_READ_BYTES);
990
- expect(new TextDecoder().decode(value)).toBe('bar');
991
-
992
- const barReader = await cursor.reader();
993
-
994
- // read into buffer
995
- const barBytes = new Uint8Array(10);
996
- const barSize = await barReader.read(barBytes);
997
- expect(new TextDecoder().decode(barBytes.slice(0, barSize))).toBe('bar');
998
- barReader.seek(0);
999
- expect(await barReader.read(barBytes)).toBe(3);
1000
- expect(new TextDecoder().decode(barBytes.slice(0, 3))).toBe('bar');
1001
-
1002
- // read one char at a time
1003
- {
1004
- const ch = new Uint8Array(1);
1005
- barReader.seek(0);
1006
-
1007
- await barReader.readFully(ch);
1008
- expect(new TextDecoder().decode(ch)).toBe('b');
1009
-
1010
- await barReader.readFully(ch);
1011
- expect(new TextDecoder().decode(ch)).toBe('a');
1012
-
1013
- await barReader.readFully(ch);
1014
- expect(new TextDecoder().decode(ch)).toBe('r');
1015
-
1016
- await expect(barReader.readFully(ch)).rejects.toThrow(EndOfStreamException);
1017
-
1018
- barReader.seek(1);
1019
- expect(String.fromCharCode(await barReader.readByte())).toBe('a');
1020
-
1021
- barReader.seek(0);
1022
- expect(String.fromCharCode(await barReader.readByte())).toBe('b');
1023
- }
1024
- }),
1025
- ]);
1026
-
1027
- // overwrite foo -> baz
1028
- await rootCursor.writePath([
1029
- new ArrayListInit(),
1030
- new ArrayListAppend(),
1031
- new WriteData(await rootCursor.readPathSlot([new ArrayListGet(-1)])),
1032
- new HashMapInit(false, false),
1033
- new HashMapGet(new HashMapGetValue(fooKey)),
1034
- new Context(async (cursor) => {
1035
- expect(cursor.slot().tag).not.toBe(Tag.NONE);
1036
-
1037
- const writer = await cursor.writer();
1038
- await writer.write(new TextEncoder().encode('x'));
1039
- await writer.write(new TextEncoder().encode('x'));
1040
- await writer.write(new TextEncoder().encode('x'));
1041
- writer.seek(0);
1042
- await writer.write(new TextEncoder().encode('b'));
1043
- writer.seek(2);
1044
- await writer.write(new TextEncoder().encode('z'));
1045
- writer.seek(1);
1046
- await writer.write(new TextEncoder().encode('a'));
1047
- await writer.finish();
1048
-
1049
- const value = await cursor.readBytes(MAX_READ_BYTES);
1050
- expect(new TextDecoder().decode(value)).toBe('baz');
1051
- }),
1052
- ]);
1053
-
1054
- // if error in ctx, db doesn't change
1055
- {
1056
- const sizeBefore = await core.length();
1057
-
1058
- try {
1059
- await rootCursor.writePath([
1060
- new ArrayListInit(),
1061
- new ArrayListAppend(),
1062
- new WriteData(await rootCursor.readPathSlot([new ArrayListGet(-1)])),
1063
- new HashMapInit(false, false),
1064
- new HashMapGet(new HashMapGetValue(fooKey)),
1065
- new Context(async (cursor) => {
1066
- const writer = await cursor.writer();
1067
- await writer.write(new TextEncoder().encode("this value won't be visible"));
1068
- await writer.finish();
1069
- throw new Error();
1070
- }),
1071
- ]);
1072
- } catch (e) {}
1073
-
1074
- // read foo
1075
- const valueCursor = await rootCursor.readPath([
1076
- new ArrayListGet(-1),
1077
- new HashMapGet(new HashMapGetValue(fooKey)),
1078
- ]);
1079
- const value = await valueCursor!.readBytes();
1080
- expect(new TextDecoder().decode(value)).toBe('baz');
1081
-
1082
- // verify that the db is properly truncated back to its original size after error
1083
- const sizeAfter = await core.length();
1084
- expect(sizeBefore).toBe(sizeAfter);
1085
- }
1086
-
1087
- // write bar -> longstring
1088
- const barKey = await db.hasher.digest(new TextEncoder().encode('bar'));
1089
- {
1090
- const barCursor = await rootCursor.writePath([
1091
- new ArrayListInit(),
1092
- new ArrayListAppend(),
1093
- new WriteData(await rootCursor.readPathSlot([new ArrayListGet(-1)])),
1094
- new HashMapInit(false, false),
1095
- new HashMapGet(new HashMapGetValue(barKey)),
1096
- ]);
1097
- await barCursor.write(new Bytes('longstring'));
1098
-
1099
- // the slot tag is BYTES because the byte array is > 8 bytes long
1100
- expect(barCursor.slot().tag).toBe(Tag.BYTES);
1101
-
1102
- // writing again returns the same slot
1103
- {
1104
- const nextBarCursor = await rootCursor.writePath([
1105
- new ArrayListInit(),
1106
- new ArrayListAppend(),
1107
- new WriteData(await rootCursor.readPathSlot([new ArrayListGet(-1)])),
1108
- new HashMapInit(false, false),
1109
- new HashMapGet(new HashMapGetValue(barKey)),
1110
- ]);
1111
- await nextBarCursor.writeIfEmpty(new Bytes('longstring'));
1112
- expect(barCursor.slot().value).toBe(nextBarCursor.slot().value);
1113
- }
1114
-
1115
- // writing with write returns a new slot
1116
- {
1117
- const nextBarCursor = await rootCursor.writePath([
1118
- new ArrayListInit(),
1119
- new ArrayListAppend(),
1120
- new WriteData(await rootCursor.readPathSlot([new ArrayListGet(-1)])),
1121
- new HashMapInit(false, false),
1122
- new HashMapGet(new HashMapGetValue(barKey)),
1123
- ]);
1124
- await nextBarCursor.write(new Bytes('longstring'));
1125
- expect(barCursor.slot().value).not.toBe(nextBarCursor.slot().value);
1126
- }
1127
- }
1128
-
1129
- // read bar
1130
- {
1131
- const readBarCursor = await rootCursor.readPath([
1132
- new ArrayListGet(-1),
1133
- new HashMapGet(new HashMapGetValue(barKey)),
1134
- ]);
1135
- const barValue = await readBarCursor!.readBytes(MAX_READ_BYTES);
1136
- expect(new TextDecoder().decode(barValue)).toBe('longstring');
1137
- }
1138
-
1139
- // write bar -> shortstr
1140
- {
1141
- const barCursor = await rootCursor.writePath([
1142
- new ArrayListInit(),
1143
- new ArrayListAppend(),
1144
- new WriteData(await rootCursor.readPathSlot([new ArrayListGet(-1)])),
1145
- new HashMapInit(false, false),
1146
- new HashMapGet(new HashMapGetValue(barKey)),
1147
- ]);
1148
- await barCursor.write(new Bytes('shortstr'));
1149
-
1150
- // the slot tag is SHORT_BYTES because the byte array is <= 8 bytes long
1151
- expect(barCursor.slot().tag).toBe(Tag.SHORT_BYTES);
1152
- expect(await barCursor.count()).toBe(8);
1153
-
1154
- // make sure that SHORT_BYTES can be read with a reader
1155
- const barReader = await barCursor.reader();
1156
- const barValue = new Uint8Array(Number(await barCursor.count()));
1157
- await barReader.readFully(barValue);
1158
- expect(new TextDecoder().decode(barValue)).toBe('shortstr');
1159
- }
1160
-
1161
- // write bytes with a format tag - shortstr
1162
- {
1163
- const barCursor = await rootCursor.writePath([
1164
- new ArrayListInit(),
1165
- new ArrayListAppend(),
1166
- new WriteData(await rootCursor.readPathSlot([new ArrayListGet(-1)])),
1167
- new HashMapInit(false, false),
1168
- new HashMapGet(new HashMapGetValue(barKey)),
1169
- ]);
1170
- await barCursor.write(new Bytes('shortstr', new TextEncoder().encode('st')));
1171
-
1172
- // the slot tag is BYTES because the byte array is > 8 bytes long including the format tag
1173
- expect(barCursor.slot().tag).toBe(Tag.BYTES);
1174
- expect(await barCursor.count()).toBe(8);
1175
-
1176
- // read bar
1177
- const readBarCursor = await rootCursor.readPath([
1178
- new ArrayListGet(-1),
1179
- new HashMapGet(new HashMapGetValue(barKey)),
1180
- ]);
1181
- const barBytes = await readBarCursor!.readBytesObject(MAX_READ_BYTES);
1182
- expect(new TextDecoder().decode(barBytes.value)).toBe('shortstr');
1183
- expect(new TextDecoder().decode(barBytes.formatTag!)).toBe('st');
1184
-
1185
- // make sure that BYTES can be read with a reader
1186
- const barReader = await barCursor.reader();
1187
- const barValue = new Uint8Array(Number(await barCursor.count()));
1188
- await barReader.readFully(barValue);
1189
- expect(new TextDecoder().decode(barValue)).toBe('shortstr');
1190
- }
1191
-
1192
- // write bytes with a format tag - shorts
1193
- {
1194
- const barCursor = await rootCursor.writePath([
1195
- new ArrayListInit(),
1196
- new ArrayListAppend(),
1197
- new WriteData(await rootCursor.readPathSlot([new ArrayListGet(-1)])),
1198
- new HashMapInit(false, false),
1199
- new HashMapGet(new HashMapGetValue(barKey)),
1200
- ]);
1201
- await barCursor.write(new Bytes('shorts', new TextEncoder().encode('st')));
1202
-
1203
- // the slot tag is SHORT_BYTES because the byte array is <= 8 bytes long including the format tag
1204
- expect(barCursor.slot().tag).toBe(Tag.SHORT_BYTES);
1205
- expect(await barCursor.count()).toBe(6);
1206
-
1207
- // read bar
1208
- const readBarCursor = await rootCursor.readPath([
1209
- new ArrayListGet(-1),
1210
- new HashMapGet(new HashMapGetValue(barKey)),
1211
- ]);
1212
- const barBytes = await readBarCursor!.readBytesObject(MAX_READ_BYTES);
1213
- expect(new TextDecoder().decode(barBytes.value)).toBe('shorts');
1214
- expect(new TextDecoder().decode(barBytes.formatTag!)).toBe('st');
1215
-
1216
- // make sure that SHORT_BYTES can be read with a reader
1217
- const barReader = await barCursor.reader();
1218
- const barValue = new Uint8Array(Number(await barCursor.count()));
1219
- await barReader.readFully(barValue);
1220
- expect(new TextDecoder().decode(barValue)).toBe('shorts');
1221
- }
1222
-
1223
- // write bytes with a format tag - short
1224
- {
1225
- const barCursor = await rootCursor.writePath([
1226
- new ArrayListInit(),
1227
- new ArrayListAppend(),
1228
- new WriteData(await rootCursor.readPathSlot([new ArrayListGet(-1)])),
1229
- new HashMapInit(false, false),
1230
- new HashMapGet(new HashMapGetValue(barKey)),
1231
- ]);
1232
- await barCursor.write(new Bytes('short', new TextEncoder().encode('st')));
1233
-
1234
- // the slot tag is SHORT_BYTES because the byte array is <= 8 bytes long including the format tag
1235
- expect(barCursor.slot().tag).toBe(Tag.SHORT_BYTES);
1236
- expect(await barCursor.count()).toBe(5);
1237
-
1238
- // read bar
1239
- const readBarCursor = await rootCursor.readPath([
1240
- new ArrayListGet(-1),
1241
- new HashMapGet(new HashMapGetValue(barKey)),
1242
- ]);
1243
- const barBytes = await readBarCursor!.readBytesObject(MAX_READ_BYTES);
1244
- expect(new TextDecoder().decode(barBytes.value)).toBe('short');
1245
- expect(new TextDecoder().decode(barBytes.formatTag!)).toBe('st');
1246
-
1247
- // make sure that SHORT_BYTES can be read with a reader
1248
- const barReader = await barCursor.reader();
1249
- const barValue = new Uint8Array(Number(await barCursor.count()));
1250
- await barReader.readFully(barValue);
1251
- expect(new TextDecoder().decode(barValue)).toBe('short');
1252
- }
1253
-
1254
- // read foo into buffer
1255
- {
1256
- const barCursor = await rootCursor.readPath([
1257
- new ArrayListGet(-1),
1258
- new HashMapGet(new HashMapGetValue(fooKey)),
1259
- ]);
1260
- const barBufferValue = await barCursor!.readBytes(MAX_READ_BYTES);
1261
- expect(new TextDecoder().decode(barBufferValue)).toBe('baz');
1262
- }
1263
-
1264
- // write bar and get a pointer to it
1265
- const barSlot = (
1266
- await rootCursor.writePath([
1267
- new ArrayListInit(),
1268
- new ArrayListAppend(),
1269
- new WriteData(await rootCursor.readPathSlot([new ArrayListGet(-1)])),
1270
- new HashMapInit(false, false),
1271
- new HashMapGet(new HashMapGetValue(barKey)),
1272
- new WriteData(new Bytes('bar')),
1273
- ])
1274
- ).slot();
1275
-
1276
- // overwrite foo -> bar using the bar pointer
1277
- await rootCursor.writePath([
1278
- new ArrayListInit(),
1279
- new ArrayListAppend(),
1280
- new WriteData(await rootCursor.readPathSlot([new ArrayListGet(-1)])),
1281
- new HashMapInit(false, false),
1282
- new HashMapGet(new HashMapGetValue(fooKey)),
1283
- new WriteData(barSlot),
1284
- ]);
1285
- const barCursor = await rootCursor.readPath([
1286
- new ArrayListGet(-1),
1287
- new HashMapGet(new HashMapGetValue(fooKey)),
1288
- ]);
1289
- const barValue = await barCursor!.readBytes(MAX_READ_BYTES);
1290
- expect(new TextDecoder().decode(barValue)).toBe('bar');
1291
-
1292
- // can still read the old value
1293
- const bazCursor = await rootCursor.readPath([
1294
- new ArrayListGet(-2),
1295
- new HashMapGet(new HashMapGetValue(fooKey)),
1296
- ]);
1297
- const bazValue = await bazCursor!.readBytes(MAX_READ_BYTES);
1298
- expect(new TextDecoder().decode(bazValue)).toBe('baz');
1299
-
1300
- // key not found
1301
- const notFoundKey = await db.hasher.digest(new TextEncoder().encode("this doesn't exist"));
1302
- expect(
1303
- await rootCursor.readPath([new ArrayListGet(-2), new HashMapGet(new HashMapGetValue(notFoundKey))])
1304
- ).toBeNull();
1305
-
1306
- // write key that conflicts with foo the first two bytes
1307
- const smallConflictKey = await db.hasher.digest(new TextEncoder().encode('small conflict'));
1308
- smallConflictKey[smallConflictKey.length - 1] = fooKey[fooKey.length - 1];
1309
- smallConflictKey[smallConflictKey.length - 2] = fooKey[fooKey.length - 2];
1310
- await rootCursor.writePath([
1311
- new ArrayListInit(),
1312
- new ArrayListAppend(),
1313
- new WriteData(await rootCursor.readPathSlot([new ArrayListGet(-1)])),
1314
- new HashMapInit(false, false),
1315
- new HashMapGet(new HashMapGetValue(smallConflictKey)),
1316
- new WriteData(new Bytes('small')),
1317
- ]);
1318
-
1319
- // write key that conflicts with foo the first four bytes
1320
- const conflictKey = await db.hasher.digest(new TextEncoder().encode('conflict'));
1321
- conflictKey[conflictKey.length - 1] = fooKey[fooKey.length - 1];
1322
- conflictKey[conflictKey.length - 2] = fooKey[fooKey.length - 2];
1323
- conflictKey[conflictKey.length - 3] = fooKey[fooKey.length - 3];
1324
- conflictKey[conflictKey.length - 4] = fooKey[fooKey.length - 4];
1325
- await rootCursor.writePath([
1326
- new ArrayListInit(),
1327
- new ArrayListAppend(),
1328
- new WriteData(await rootCursor.readPathSlot([new ArrayListGet(-1)])),
1329
- new HashMapInit(false, false),
1330
- new HashMapGet(new HashMapGetValue(conflictKey)),
1331
- new WriteData(new Bytes('hello')),
1332
- ]);
1333
-
1334
- // read conflicting key
1335
- const helloCursor = await rootCursor.readPath([
1336
- new ArrayListGet(-1),
1337
- new HashMapGet(new HashMapGetValue(conflictKey)),
1338
- ]);
1339
- const helloValue = await helloCursor!.readBytes(MAX_READ_BYTES);
1340
- expect(new TextDecoder().decode(helloValue)).toBe('hello');
1341
-
1342
- // we can still read foo
1343
- const barCursor2 = await rootCursor.readPath([
1344
- new ArrayListGet(-1),
1345
- new HashMapGet(new HashMapGetValue(fooKey)),
1346
- ]);
1347
- const barValue2 = await barCursor2!.readBytes(MAX_READ_BYTES);
1348
- expect(new TextDecoder().decode(barValue2)).toBe('bar');
1349
-
1350
- // overwrite conflicting key
1351
- await rootCursor.writePath([
1352
- new ArrayListInit(),
1353
- new ArrayListAppend(),
1354
- new WriteData(await rootCursor.readPathSlot([new ArrayListGet(-1)])),
1355
- new HashMapInit(false, false),
1356
- new HashMapGet(new HashMapGetValue(conflictKey)),
1357
- new WriteData(new Bytes('goodbye')),
1358
- ]);
1359
- const goodbyeCursor = await rootCursor.readPath([
1360
- new ArrayListGet(-1),
1361
- new HashMapGet(new HashMapGetValue(conflictKey)),
1362
- ]);
1363
- const goodbyeValue = await goodbyeCursor!.readBytes(MAX_READ_BYTES);
1364
- expect(new TextDecoder().decode(goodbyeValue)).toBe('goodbye');
1365
-
1366
- // we can still read the old conflicting key
1367
- const helloCursor2 = await rootCursor.readPath([
1368
- new ArrayListGet(-2),
1369
- new HashMapGet(new HashMapGetValue(conflictKey)),
1370
- ]);
1371
- const helloValue2 = await helloCursor2!.readBytes(MAX_READ_BYTES);
1372
- expect(new TextDecoder().decode(helloValue2)).toBe('hello');
1373
-
1374
- // remove the conflicting keys
1375
- {
1376
- // foo's slot is an INDEX slot due to the conflict
1377
- {
1378
- const mapCursor = await rootCursor.readPath([new ArrayListGet(-1)]);
1379
- expect(mapCursor!.slot().tag).toBe(Tag.HASH_MAP);
1380
-
1381
- const i = Number(BigInt.asUintN(64, bytesToBigInt(fooKey)) & MASK);
1382
- const slotPos = Number(mapCursor!.slot().value) + Slot.LENGTH * i;
1383
- await core.seek(slotPos);
1384
- const reader = core.reader();
1385
- const slotBytes = new Uint8Array(Slot.LENGTH);
1386
- await reader.readFully(slotBytes);
1387
- const slot = Slot.fromBytes(slotBytes);
1388
-
1389
- expect(slot.tag).toBe(Tag.INDEX);
1390
- }
1391
-
1392
- // remove the small conflict key
1393
- await rootCursor.writePath([
1394
- new ArrayListInit(),
1395
- new ArrayListAppend(),
1396
- new WriteData(await rootCursor.readPathSlot([new ArrayListGet(-1)])),
1397
- new HashMapInit(false, false),
1398
- new HashMapRemove(smallConflictKey),
1399
- ]);
1400
-
1401
- // the conflict key still exists in history
1402
- expect(
1403
- await rootCursor.readPath([new ArrayListGet(-2), new HashMapGet(new HashMapGetValue(smallConflictKey))])
1404
- ).not.toBeNull();
1405
-
1406
- // the conflict key doesn't exist in the latest moment
1407
- expect(
1408
- await rootCursor.readPath([new ArrayListGet(-1), new HashMapGet(new HashMapGetValue(smallConflictKey))])
1409
- ).toBeNull();
1410
-
1411
- // the other conflict key still exists
1412
- expect(
1413
- await rootCursor.readPath([new ArrayListGet(-1), new HashMapGet(new HashMapGetValue(conflictKey))])
1414
- ).not.toBeNull();
1415
-
1416
- // foo's slot is still an INDEX slot due to the other conflicting key
1417
- {
1418
- const mapCursor = await rootCursor.readPath([new ArrayListGet(-1)]);
1419
- expect(mapCursor!.slot().tag).toBe(Tag.HASH_MAP);
1420
-
1421
- const i = Number(BigInt.asUintN(64, bytesToBigInt(fooKey)) & MASK);
1422
- const slotPos = Number(mapCursor!.slot().value) + Slot.LENGTH * i;
1423
- await core.seek(slotPos);
1424
- const reader = core.reader();
1425
- const slotBytes = new Uint8Array(Slot.LENGTH);
1426
- await reader.readFully(slotBytes);
1427
- const slot = Slot.fromBytes(slotBytes);
1428
-
1429
- expect(slot.tag).toBe(Tag.INDEX);
1430
- }
1431
-
1432
- // remove the conflict key
1433
- await rootCursor.writePath([
1434
- new ArrayListInit(),
1435
- new ArrayListAppend(),
1436
- new WriteData(await rootCursor.readPathSlot([new ArrayListGet(-1)])),
1437
- new HashMapInit(false, false),
1438
- new HashMapRemove(conflictKey),
1439
- ]);
1440
-
1441
- // the conflict keys don't exist in the latest moment
1442
- expect(
1443
- await rootCursor.readPath([new ArrayListGet(-1), new HashMapGet(new HashMapGetValue(smallConflictKey))])
1444
- ).toBeNull();
1445
- expect(
1446
- await rootCursor.readPath([new ArrayListGet(-1), new HashMapGet(new HashMapGetValue(conflictKey))])
1447
- ).toBeNull();
1448
-
1449
- // foo's slot is now a KV_PAIR slot, because the branch was shortened
1450
- {
1451
- const mapCursor = await rootCursor.readPath([new ArrayListGet(-1)]);
1452
- expect(mapCursor!.slot().tag).toBe(Tag.HASH_MAP);
1453
-
1454
- const i = Number(BigInt.asUintN(64, bytesToBigInt(fooKey)) & MASK);
1455
- const slotPos = Number(mapCursor!.slot().value) + Slot.LENGTH * i;
1456
- await core.seek(slotPos);
1457
- const reader = core.reader();
1458
- const slotBytes = new Uint8Array(Slot.LENGTH);
1459
- await reader.readFully(slotBytes);
1460
- const slot = Slot.fromBytes(slotBytes);
1461
-
1462
- expect(slot.tag).toBe(Tag.KV_PAIR);
1463
- }
1464
- }
1465
-
1466
- // overwrite foo with uint, int, float
1467
- {
1468
- // overwrite foo with a uint
1469
- await rootCursor.writePath([
1470
- new ArrayListInit(),
1471
- new ArrayListAppend(),
1472
- new WriteData(await rootCursor.readPathSlot([new ArrayListGet(-1)])),
1473
- new HashMapInit(false, false),
1474
- new HashMapGet(new HashMapGetValue(fooKey)),
1475
- new WriteData(new Uint(42)),
1476
- ]);
1477
-
1478
- // read foo
1479
- const uintValue = (
1480
- await rootCursor.readPath([new ArrayListGet(-1), new HashMapGet(new HashMapGetValue(fooKey))])
1481
- )!.readUint();
1482
- expect(uintValue).toBe(42);
1483
- }
1484
-
1485
- {
1486
- // overwrite foo with an int
1487
- await rootCursor.writePath([
1488
- new ArrayListInit(),
1489
- new ArrayListAppend(),
1490
- new WriteData(await rootCursor.readPathSlot([new ArrayListGet(-1)])),
1491
- new HashMapInit(false, false),
1492
- new HashMapGet(new HashMapGetValue(fooKey)),
1493
- new WriteData(new Int(-42)),
1494
- ]);
1495
-
1496
- // read foo
1497
- const intValue = (
1498
- await rootCursor.readPath([new ArrayListGet(-1), new HashMapGet(new HashMapGetValue(fooKey))])
1499
- )!.readInt();
1500
- expect(intValue).toBe(-42);
1501
- }
1502
-
1503
- {
1504
- // overwrite foo with a float
1505
- await rootCursor.writePath([
1506
- new ArrayListInit(),
1507
- new ArrayListAppend(),
1508
- new WriteData(await rootCursor.readPathSlot([new ArrayListGet(-1)])),
1509
- new HashMapInit(false, false),
1510
- new HashMapGet(new HashMapGetValue(fooKey)),
1511
- new WriteData(new Float(42.5)),
1512
- ]);
1513
-
1514
- // read foo
1515
- const floatValue = (
1516
- await rootCursor.readPath([new ArrayListGet(-1), new HashMapGet(new HashMapGetValue(fooKey))])
1517
- )!.readFloat();
1518
- expect(floatValue).toBe(42.5);
1519
- }
1520
-
1521
- // remove foo
1522
- await rootCursor.writePath([
1523
- new ArrayListInit(),
1524
- new ArrayListAppend(),
1525
- new WriteData(await rootCursor.readPathSlot([new ArrayListGet(-1)])),
1526
- new HashMapInit(false, false),
1527
- new HashMapRemove(fooKey),
1528
- ]);
1529
-
1530
- // remove key that does not exist
1531
- await expect(
1532
- rootCursor.writePath([
1533
- new ArrayListInit(),
1534
- new ArrayListAppend(),
1535
- new WriteData(await rootCursor.readPathSlot([new ArrayListGet(-1)])),
1536
- new HashMapInit(false, false),
1537
- new HashMapRemove(await db.hasher.digest(new TextEncoder().encode("doesn't exist"))),
1538
- ])
1539
- ).rejects.toThrow(KeyNotFoundException);
1540
-
1541
- // make sure foo doesn't exist anymore
1542
- expect(
1543
- await rootCursor.readPath([new ArrayListGet(-1), new HashMapGet(new HashMapGetValue(fooKey))])
1544
- ).toBeNull();
1545
-
1546
- // non-top-level list
1547
- {
1548
- const fruitsKey = await db.hasher.digest(new TextEncoder().encode('fruits'));
1549
-
1550
- // write apple
1551
- await rootCursor.writePath([
1552
- new ArrayListInit(),
1553
- new ArrayListAppend(),
1554
- new WriteData(await rootCursor.readPathSlot([new ArrayListGet(-1)])),
1555
- new HashMapInit(false, false),
1556
- new HashMapGet(new HashMapGetValue(fruitsKey)),
1557
- new ArrayListInit(),
1558
- new ArrayListAppend(),
1559
- new WriteData(new Bytes('apple')),
1560
- ]);
1561
-
1562
- // read apple
1563
- const appleCursor = await rootCursor.readPath([
1564
- new ArrayListGet(-1),
1565
- new HashMapGet(new HashMapGetValue(fruitsKey)),
1566
- new ArrayListGet(-1),
1567
- ]);
1568
- const appleValue = await appleCursor!.readBytes(MAX_READ_BYTES);
1569
- expect(new TextDecoder().decode(appleValue)).toBe('apple');
1570
-
1571
- // write banana
1572
- await rootCursor.writePath([
1573
- new ArrayListInit(),
1574
- new ArrayListAppend(),
1575
- new WriteData(await rootCursor.readPathSlot([new ArrayListGet(-1)])),
1576
- new HashMapInit(false, false),
1577
- new HashMapGet(new HashMapGetValue(fruitsKey)),
1578
- new ArrayListInit(),
1579
- new ArrayListAppend(),
1580
- new WriteData(new Bytes('banana')),
1581
- ]);
1582
-
1583
- // read banana
1584
- const bananaCursor = await rootCursor.readPath([
1585
- new ArrayListGet(-1),
1586
- new HashMapGet(new HashMapGetValue(fruitsKey)),
1587
- new ArrayListGet(-1),
1588
- ]);
1589
- const bananaValue = await bananaCursor!.readBytes(MAX_READ_BYTES);
1590
- expect(new TextDecoder().decode(bananaValue)).toBe('banana');
1591
-
1592
- // can't read banana in older array_list
1593
- expect(
1594
- await rootCursor.readPath([
1595
- new ArrayListGet(-2),
1596
- new HashMapGet(new HashMapGetValue(fruitsKey)),
1597
- new ArrayListGet(1),
1598
- ])
1599
- ).toBeNull();
1600
-
1601
- // write pear
1602
- await rootCursor.writePath([
1603
- new ArrayListInit(),
1604
- new ArrayListAppend(),
1605
- new WriteData(await rootCursor.readPathSlot([new ArrayListGet(-1)])),
1606
- new HashMapInit(false, false),
1607
- new HashMapGet(new HashMapGetValue(fruitsKey)),
1608
- new ArrayListInit(),
1609
- new ArrayListAppend(),
1610
- new WriteData(new Bytes('pear')),
1611
- ]);
1612
-
1613
- // write grape
1614
- await rootCursor.writePath([
1615
- new ArrayListInit(),
1616
- new ArrayListAppend(),
1617
- new WriteData(await rootCursor.readPathSlot([new ArrayListGet(-1)])),
1618
- new HashMapInit(false, false),
1619
- new HashMapGet(new HashMapGetValue(fruitsKey)),
1620
- new ArrayListInit(),
1621
- new ArrayListAppend(),
1622
- new WriteData(new Bytes('grape')),
1623
- ]);
1624
-
1625
- // read pear
1626
- const pearCursor = await rootCursor.readPath([
1627
- new ArrayListGet(-1),
1628
- new HashMapGet(new HashMapGetValue(fruitsKey)),
1629
- new ArrayListGet(-2),
1630
- ]);
1631
- const pearValue = await pearCursor!.readBytes(MAX_READ_BYTES);
1632
- expect(new TextDecoder().decode(pearValue)).toBe('pear');
1633
-
1634
- // read grape
1635
- const grapeCursor = await rootCursor.readPath([
1636
- new ArrayListGet(-1),
1637
- new HashMapGet(new HashMapGetValue(fruitsKey)),
1638
- new ArrayListGet(-1),
1639
- ]);
1640
- const grapeValue = await grapeCursor!.readBytes(MAX_READ_BYTES);
1641
- expect(new TextDecoder().decode(grapeValue)).toBe('grape');
1642
- }
1643
- }
1644
-
1645
- // append to top-level array_list many times, filling up the array_list until a root overflow occurs
1646
- {
1647
- await core.setLength(0);
1648
- const db = await Database.create(core, hasher);
1649
- const rootCursor = await db.rootCursor();
1650
-
1651
- const watKey = await db.hasher.digest(new TextEncoder().encode('wat'));
1652
-
1653
- for (let i = 0; i < SLOT_COUNT + 1; i++) {
1654
- const value = `wat${i}`;
1655
- await rootCursor.writePath([
1656
- new ArrayListInit(),
1657
- new ArrayListAppend(),
1658
- new WriteData(await rootCursor.readPathSlot([new ArrayListGet(-1)])),
1659
- new HashMapInit(false, false),
1660
- new HashMapGet(new HashMapGetValue(watKey)),
1661
- new WriteData(new Bytes(value)),
1662
- ]);
1663
- }
1664
-
1665
- // verify all values
1666
- for (let i = 0; i < SLOT_COUNT + 1; i++) {
1667
- const value = `wat${i}`;
1668
- const cursor = await rootCursor.readPath([
1669
- new ArrayListGet(i),
1670
- new HashMapGet(new HashMapGetValue(watKey)),
1671
- ]);
1672
- const value2 = new TextDecoder().decode(await cursor!.readBytes(MAX_READ_BYTES));
1673
- expect(value).toBe(value2);
1674
- }
1675
-
1676
- // add more slots to cause a new index block to be created.
1677
- // during that transaction, return an error so the transaction is cancelled,
1678
- // causing truncation to happen. this test ensures that the new index block
1679
- // is NOT truncated.
1680
- for (let i = SLOT_COUNT + 1; i < SLOT_COUNT * 2 + 1; i++) {
1681
- const value = `wat${i}`;
1682
- const index = i;
1683
-
1684
- try {
1685
- await rootCursor.writePath([
1686
- new ArrayListInit(),
1687
- new ArrayListAppend(),
1688
- new WriteData(await rootCursor.readPathSlot([new ArrayListGet(-1)])),
1689
- new HashMapInit(false, false),
1690
- new HashMapGet(new HashMapGetValue(watKey)),
1691
- new WriteData(new Bytes(value)),
1692
- new Context(async () => {
1693
- if (index === 32) {
1694
- throw new Error('intentional error');
1695
- }
1696
- }),
1697
- ]);
1698
- } catch (e) {
1699
- // expected error
1700
- }
1701
- }
1702
-
1703
- // try another append to make sure we still can.
1704
- // if truncation destroyed the index block, this would fail.
1705
- await rootCursor.writePath([
1706
- new ArrayListInit(),
1707
- new ArrayListAppend(),
1708
- new WriteData(await rootCursor.readPathSlot([new ArrayListGet(-1)])),
1709
- new HashMapInit(false, false),
1710
- new HashMapGet(new HashMapGetValue(watKey)),
1711
- new WriteData(new Bytes('wat32')),
1712
- ]);
1713
-
1714
- // slice so it contains exactly SLOT_COUNT, so we have the old root again
1715
- await rootCursor.writePath([new ArrayListInit(), new ArrayListSlice(SLOT_COUNT)]);
1716
-
1717
- // we can iterate over the remaining slots
1718
- for (let i = 0; i < SLOT_COUNT; i++) {
1719
- const value = `wat${i}`;
1720
- const cursor = await rootCursor.readPath([
1721
- new ArrayListGet(i),
1722
- new HashMapGet(new HashMapGetValue(watKey)),
1723
- ]);
1724
- const value2 = new TextDecoder().decode(await cursor!.readBytes(MAX_READ_BYTES));
1725
- expect(value).toBe(value2);
1726
- }
1727
-
1728
- // but we can't get the value that we sliced out of the array list
1729
- expect(await rootCursor.readPath([new ArrayListGet(SLOT_COUNT + 1)])).toBeNull();
1730
- }
1731
-
1732
- // append to inner array_list many times, filling up the array_list until a root overflow occurs
1733
- {
1734
- await core.setLength(0);
1735
- const db = await Database.create(core, hasher);
1736
- const rootCursor = await db.rootCursor();
1737
-
1738
- for (let i = 0; i < SLOT_COUNT + 1; i++) {
1739
- const value = `wat${i}`;
1740
- await rootCursor.writePath([
1741
- new ArrayListInit(),
1742
- new ArrayListAppend(),
1743
- new WriteData(await rootCursor.readPathSlot([new ArrayListGet(-1)])),
1744
- new ArrayListInit(),
1745
- new ArrayListAppend(),
1746
- new WriteData(new Bytes(value)),
1747
- ]);
1748
- }
1749
-
1750
- // verify all values
1751
- for (let i = 0; i < SLOT_COUNT + 1; i++) {
1752
- const value = `wat${i}`;
1753
- const cursor = await rootCursor.readPath([new ArrayListGet(-1), new ArrayListGet(i)]);
1754
- const value2 = new TextDecoder().decode(await cursor!.readBytes(MAX_READ_BYTES));
1755
- expect(value).toBe(value2);
1756
- }
1757
-
1758
- // slice the inner array list so it contains exactly SLOT_COUNT, so we have the old root again
1759
- await rootCursor.writePath([
1760
- new ArrayListInit(),
1761
- new ArrayListGet(-1),
1762
- new ArrayListInit(),
1763
- new ArrayListSlice(SLOT_COUNT),
1764
- ]);
1765
-
1766
- // we can iterate over the remaining slots
1767
- for (let i = 0; i < SLOT_COUNT; i++) {
1768
- const value = `wat${i}`;
1769
- const cursor = await rootCursor.readPath([new ArrayListGet(-1), new ArrayListGet(i)]);
1770
- const value2 = new TextDecoder().decode(await cursor!.readBytes(MAX_READ_BYTES));
1771
- expect(value).toBe(value2);
1772
- }
1773
-
1774
- // but we can't get the value that we sliced out of the array list
1775
- expect(await rootCursor.readPath([new ArrayListGet(-1), new ArrayListGet(SLOT_COUNT + 1)])).toBeNull();
1776
-
1777
- // overwrite the last value with hello
1778
- await rootCursor.writePath([
1779
- new ArrayListInit(),
1780
- new ArrayListAppend(),
1781
- new WriteData(await rootCursor.readPathSlot([new ArrayListGet(-1)])),
1782
- new ArrayListInit(),
1783
- new ArrayListGet(-1),
1784
- new WriteData(new Bytes('hello')),
1785
- ]);
1786
-
1787
- // read last value
1788
- {
1789
- const cursor = await rootCursor.readPath([new ArrayListGet(-1), new ArrayListGet(-1)]);
1790
- const value = new TextDecoder().decode(await cursor!.readBytes(MAX_READ_BYTES));
1791
- expect(value).toBe('hello');
1792
- }
1793
-
1794
- // overwrite the last value with goodbye
1795
- await rootCursor.writePath([
1796
- new ArrayListInit(),
1797
- new ArrayListAppend(),
1798
- new WriteData(await rootCursor.readPathSlot([new ArrayListGet(-1)])),
1799
- new ArrayListInit(),
1800
- new ArrayListGet(-1),
1801
- new WriteData(new Bytes('goodbye')),
1802
- ]);
1803
-
1804
- // read last value
1805
- {
1806
- const cursor = await rootCursor.readPath([new ArrayListGet(-1), new ArrayListGet(-1)]);
1807
- const value = new TextDecoder().decode(await cursor!.readBytes(MAX_READ_BYTES));
1808
- expect(value).toBe('goodbye');
1809
- }
1810
-
1811
- // previous last value is still hello
1812
- {
1813
- const cursor = await rootCursor.readPath([new ArrayListGet(-2), new ArrayListGet(-1)]);
1814
- const value = new TextDecoder().decode(await cursor!.readBytes(MAX_READ_BYTES));
1815
- expect(value).toBe('hello');
1816
- }
1817
- }
1818
-
1819
- // iterate over inner array_list
1820
- {
1821
- await core.setLength(0);
1822
- const db = await Database.create(core, hasher);
1823
- const rootCursor = await db.rootCursor();
1824
-
1825
- // add wats
1826
- for (let i = 0; i < 10; i++) {
1827
- const value = `wat${i}`;
1828
- await rootCursor.writePath([
1829
- new ArrayListInit(),
1830
- new ArrayListAppend(),
1831
- new WriteData(await rootCursor.readPathSlot([new ArrayListGet(-1)])),
1832
- new ArrayListInit(),
1833
- new ArrayListAppend(),
1834
- new WriteData(new Bytes(value)),
1835
- ]);
1836
-
1837
- const cursor = await rootCursor.readPath([new ArrayListGet(-1), new ArrayListGet(-1)]);
1838
- const value2 = new TextDecoder().decode(await cursor!.readBytes(MAX_READ_BYTES));
1839
- expect(value).toBe(value2);
1840
- }
1841
-
1842
- // iterate over array_list
1843
- {
1844
- const innerCursor = await rootCursor.readPath([new ArrayListGet(-1)]);
1845
- const iter = innerCursor!.iterator();
1846
- await iter.init();
1847
- let i = 0;
1848
- while (await iter.hasNext()) {
1849
- const nextCursor = await iter.next();
1850
- const value = `wat${i}`;
1851
- const value2 = new TextDecoder().decode(await nextCursor!.readBytes(MAX_READ_BYTES));
1852
- expect(value).toBe(value2);
1853
- i += 1;
1854
- }
1855
- expect(i).toBe(10);
1856
- }
1857
-
1858
- // set first slot to .none and make sure iteration still works
1859
- {
1860
- await rootCursor.writePath([
1861
- new ArrayListInit(),
1862
- new ArrayListGet(-1),
1863
- new ArrayListInit(),
1864
- new ArrayListGet(0),
1865
- new WriteData(null),
1866
- ]);
1867
- const innerCursor = await rootCursor.readPath([new ArrayListGet(-1)]);
1868
- const iter = innerCursor!.iterator();
1869
- await iter.init();
1870
- let i = 0;
1871
- while (await iter.hasNext()) {
1872
- await iter.next();
1873
- i += 1;
1874
- }
1875
- expect(i).toBe(10);
1876
- }
1877
-
1878
- // get list slot
1879
- const listCursor = await rootCursor.readPath([new ArrayListGet(-1)]);
1880
- expect(await listCursor!.count()).toBe(10);
1881
- }
1882
-
1883
- // iterate over inner hash_map
1884
- {
1885
- await core.setLength(0);
1886
- const db = await Database.create(core, hasher);
1887
- const rootCursor = await db.rootCursor();
1888
-
1889
- // add wats
1890
- for (let i = 0; i < 10; i++) {
1891
- const value = `wat${i}`;
1892
- const watKey = await db.hasher.digest(new TextEncoder().encode(value));
1893
- await rootCursor.writePath([
1894
- new ArrayListInit(),
1895
- new ArrayListAppend(),
1896
- new WriteData(await rootCursor.readPathSlot([new ArrayListGet(-1)])),
1897
- new HashMapInit(false, false),
1898
- new HashMapGet(new HashMapGetValue(watKey)),
1899
- new WriteData(new Bytes(value)),
1900
- ]);
1901
-
1902
- const cursor = await rootCursor.readPath([
1903
- new ArrayListGet(-1),
1904
- new HashMapGet(new HashMapGetValue(watKey)),
1905
- ]);
1906
- const value2 = new TextDecoder().decode(await cursor!.readBytes(MAX_READ_BYTES));
1907
- expect(value).toBe(value2);
1908
- }
1909
-
1910
- // add foo
1911
- const fooKey = await db.hasher.digest(new TextEncoder().encode('foo'));
1912
- await rootCursor.writePath([
1913
- new ArrayListInit(),
1914
- new ArrayListAppend(),
1915
- new WriteData(await rootCursor.readPathSlot([new ArrayListGet(-1)])),
1916
- new HashMapInit(false, false),
1917
- new HashMapGet(new HashMapGetKey(fooKey)),
1918
- new WriteData(new Bytes('foo')),
1919
- ]);
1920
- await rootCursor.writePath([
1921
- new ArrayListInit(),
1922
- new ArrayListAppend(),
1923
- new WriteData(await rootCursor.readPathSlot([new ArrayListGet(-1)])),
1924
- new HashMapInit(false, false),
1925
- new HashMapGet(new HashMapGetValue(fooKey)),
1926
- new WriteData(new Uint(42)),
1927
- ]);
1928
-
1929
- // remove a wat
1930
- await rootCursor.writePath([
1931
- new ArrayListInit(),
1932
- new ArrayListAppend(),
1933
- new WriteData(await rootCursor.readPathSlot([new ArrayListGet(-1)])),
1934
- new HashMapInit(false, false),
1935
- new HashMapRemove(await db.hasher.digest(new TextEncoder().encode('wat0'))),
1936
- ]);
1937
-
1938
- // iterate over hash_map
1939
- {
1940
- const innerCursor = await rootCursor.readPath([new ArrayListGet(-1)]);
1941
- const iter = innerCursor!.iterator();
1942
- await iter.init();
1943
- let i = 0;
1944
- while (await iter.hasNext()) {
1945
- const kvPairCursor = await iter.next();
1946
- const kvPair = await kvPairCursor!.readKeyValuePair();
1947
- if (arraysEqual(kvPair.hash, fooKey)) {
1948
- const key = new TextDecoder().decode(await kvPair.keyCursor.readBytes(MAX_READ_BYTES));
1949
- expect(key).toBe('foo');
1950
- expect(kvPair.valueCursor.slotPtr.slot.value).toBe(42n);
1951
- } else {
1952
- const value = await kvPair.valueCursor.readBytes(MAX_READ_BYTES);
1953
- const hash = await db.hasher.digest(value);
1954
- expect(arraysEqual(kvPair.hash, hash)).toBe(true);
1955
- }
1956
- i += 1;
1957
- }
1958
- expect(i).toBe(10);
1959
- }
1960
-
1961
- // iterate over hash_map with writeable cursor
1962
- {
1963
- const innerCursor = await rootCursor.writePath([
1964
- new ArrayListInit(),
1965
- new ArrayListAppend(),
1966
- new WriteData(await rootCursor.readPathSlot([new ArrayListGet(-1)])),
1967
- ]);
1968
- const iter = (innerCursor as WriteCursor).iterator();
1969
- await iter.init();
1970
- let i = 0;
1971
- while (await iter.hasNext()) {
1972
- const kvPairCursor = await iter.next();
1973
- const kvPair = await kvPairCursor!.readKeyValuePair();
1974
- if (arraysEqual(kvPair.hash, fooKey)) {
1975
- await (kvPair.keyCursor as WriteCursor).write(new Bytes('bar'));
1976
- }
1977
- i += 1;
1978
- }
1979
- expect(i).toBe(10);
1980
- }
1981
- }
1982
-
1983
- {
1984
- // slice linked_array_list
1985
- await testSlice(core, hasher, SLOT_COUNT * 5 + 1, 10, 5);
1986
- await testSlice(core, hasher, SLOT_COUNT * 5 + 1, 0, SLOT_COUNT * 2);
1987
- await testSlice(core, hasher, SLOT_COUNT * 5, SLOT_COUNT * 3, SLOT_COUNT);
1988
- await testSlice(core, hasher, SLOT_COUNT * 5, SLOT_COUNT * 3, SLOT_COUNT * 2);
1989
- await testSlice(core, hasher, SLOT_COUNT * 2, 10, SLOT_COUNT);
1990
- await testSlice(core, hasher, 2, 0, 2);
1991
- await testSlice(core, hasher, 2, 1, 1);
1992
- await testSlice(core, hasher, 1, 0, 0);
1993
-
1994
- // concat linked_array_list
1995
- await testConcat(core, hasher, SLOT_COUNT * 5 + 1, SLOT_COUNT + 1);
1996
- await testConcat(core, hasher, SLOT_COUNT, SLOT_COUNT);
1997
- await testConcat(core, hasher, 1, 1);
1998
- await testConcat(core, hasher, 0, 0);
1999
-
2000
- // insert linked_array_list
2001
- await testInsertAndRemove(core, hasher, 1, 0);
2002
- await testInsertAndRemove(core, hasher, 10, 0);
2003
- await testInsertAndRemove(core, hasher, 10, 5);
2004
- await testInsertAndRemove(core, hasher, 10, 9);
2005
- await testInsertAndRemove(core, hasher, SLOT_COUNT * 5, SLOT_COUNT * 2);
2006
- }
2007
-
2008
- // concat linked_array_list multiple times
2009
- {
2010
- await core.setLength(0);
2011
- const db = await Database.create(core, hasher);
2012
- const rootCursor = await db.rootCursor();
2013
-
2014
- const evenKey = await db.hasher.digest(new TextEncoder().encode('even'));
2015
- const comboKey = await db.hasher.digest(new TextEncoder().encode('combo'));
2016
-
2017
- await rootCursor.writePath([
2018
- new ArrayListInit(),
2019
- new ArrayListAppend(),
2020
- new WriteData(await rootCursor.readPathSlot([new ArrayListGet(-1)])),
2021
- new HashMapInit(false, false),
2022
- new Context(async (cursor) => {
2023
- // create list
2024
- for (let i = 0; i < SLOT_COUNT + 1; i++) {
2025
- const n = i * 2;
2026
- await cursor.writePath([
2027
- new HashMapGet(new HashMapGetValue(evenKey)),
2028
- new LinkedArrayListInit(),
2029
- new LinkedArrayListAppend(),
2030
- new WriteData(new Uint(n)),
2031
- ]);
2032
- }
2033
-
2034
- // get list slot
2035
- const evenListCursor = await cursor.readPath([new HashMapGet(new HashMapGetValue(evenKey))]);
2036
- expect(await evenListCursor!.count()).toBe(SLOT_COUNT + 1);
2037
-
2038
- // check all values in the new slice with an iterator
2039
- {
2040
- const innerCursor = await cursor.readPath([new HashMapGet(new HashMapGetValue(evenKey))]);
2041
- const iter = innerCursor!.iterator();
2042
- await iter.init();
2043
- let i = 0;
2044
- while (await iter.hasNext()) {
2045
- await iter.next();
2046
- i += 1;
2047
- }
2048
- expect(i).toBe(SLOT_COUNT + 1);
2049
- }
2050
-
2051
- // concat the list with itself multiple times.
2052
- // since each list has 17 items, each concat will create a gap, causing a root overflow
2053
- // before a normal array list would've.
2054
- let comboListCursor = await cursor.writePath([
2055
- new HashMapGet(new HashMapGetValue(comboKey)),
2056
- new WriteData(evenListCursor!.slotPtr.slot),
2057
- new LinkedArrayListInit(),
2058
- ]);
2059
- for (let i = 0; i < 16; i++) {
2060
- comboListCursor = await comboListCursor.writePath([
2061
- new LinkedArrayListConcat(evenListCursor!.slotPtr.slot),
2062
- ]);
2063
- }
2064
-
2065
- // append to the new list
2066
- await cursor.writePath([
2067
- new HashMapGet(new HashMapGetValue(comboKey)),
2068
- new LinkedArrayListAppend(),
2069
- new WriteData(new Uint(3)),
2070
- ]);
2071
-
2072
- // read the new value from the list
2073
- expect(
2074
- (await cursor.readPath([new HashMapGet(new HashMapGetValue(comboKey)), new LinkedArrayListGet(-1)]))!.readUint()
2075
- ).toBe(3);
2076
-
2077
- // append more to the new list
2078
- for (let i = 0; i < 500; i++) {
2079
- await cursor.writePath([
2080
- new HashMapGet(new HashMapGetValue(comboKey)),
2081
- new LinkedArrayListAppend(),
2082
- new WriteData(new Uint(1)),
2083
- ]);
2084
- }
2085
- }),
2086
- ]);
2087
- }
2088
-
2089
- // append items to linked_array_list without setting their value
2090
- {
2091
- await core.setLength(0);
2092
- const db = await Database.create(core, hasher);
2093
- const rootCursor = await db.rootCursor();
2094
-
2095
- // appending without setting any value should work
2096
- for (let i = 0; i < 8; i++) {
2097
- await rootCursor.writePath([
2098
- new ArrayListInit(),
2099
- new ArrayListAppend(),
2100
- new WriteData(await rootCursor.readPathSlot([new ArrayListGet(-1)])),
2101
- new LinkedArrayListInit(),
2102
- new LinkedArrayListAppend(),
2103
- ]);
2104
- }
2105
-
2106
- // explicitly writing a null slot should also work
2107
- for (let i = 0; i < 8; i++) {
2108
- await rootCursor.writePath([
2109
- new ArrayListInit(),
2110
- new ArrayListAppend(),
2111
- new WriteData(await rootCursor.readPathSlot([new ArrayListGet(-1)])),
2112
- new LinkedArrayListInit(),
2113
- new LinkedArrayListAppend(),
2114
- new WriteData(null),
2115
- ]);
2116
- }
2117
- }
2118
-
2119
- // insert at beginning of linked_array_list many times
2120
- {
2121
- await core.setLength(0);
2122
- const db = await Database.create(core, hasher);
2123
- const rootCursor = await db.rootCursor();
2124
-
2125
- await rootCursor.writePath([
2126
- new ArrayListInit(),
2127
- new ArrayListAppend(),
2128
- new WriteData(await rootCursor.readPathSlot([new ArrayListGet(-1)])),
2129
- new LinkedArrayListInit(),
2130
- new LinkedArrayListAppend(),
2131
- new WriteData(new Uint(42)),
2132
- ]);
2133
-
2134
- for (let i = 0; i < 1000; i++) {
2135
- await rootCursor.writePath([
2136
- new ArrayListInit(),
2137
- new ArrayListAppend(),
2138
- new WriteData(await rootCursor.readPathSlot([new ArrayListGet(-1)])),
2139
- new LinkedArrayListInit(),
2140
- new LinkedArrayListInsert(0),
2141
- new WriteData(new Uint(i)),
2142
- ]);
2143
- }
2144
- }
2145
-
2146
- // insert at end of linked_array_list many times
2147
- {
2148
- await core.setLength(0);
2149
- const db = await Database.create(core, hasher);
2150
- const rootCursor = await db.rootCursor();
2151
-
2152
- await rootCursor.writePath([
2153
- new ArrayListInit(),
2154
- new ArrayListAppend(),
2155
- new WriteData(await rootCursor.readPathSlot([new ArrayListGet(-1)])),
2156
- new LinkedArrayListInit(),
2157
- new LinkedArrayListAppend(),
2158
- new WriteData(new Uint(42)),
2159
- ]);
2160
-
2161
- for (let i = 0; i < 1000; i++) {
2162
- await rootCursor.writePath([
2163
- new ArrayListInit(),
2164
- new ArrayListAppend(),
2165
- new WriteData(await rootCursor.readPathSlot([new ArrayListGet(-1)])),
2166
- new LinkedArrayListInit(),
2167
- new LinkedArrayListInsert(i),
2168
- new WriteData(new Uint(i)),
2169
- ]);
2170
- }
2171
- }
2172
- }
2173
-
2174
- // Helper function to compare Uint8Arrays
2175
- function arraysEqual(a: Uint8Array, b: Uint8Array): boolean {
2176
- if (a.length !== b.length) return false;
2177
- for (let i = 0; i < a.length; i++) {
2178
- if (a[i] !== b[i]) return false;
2179
- }
2180
- return true;
2181
- }
2182
-
2183
- // Helper function to convert bytes to BigInt
2184
- function bytesToBigInt(bytes: Uint8Array): bigint {
2185
- let result = 0n;
2186
- for (let i = 0; i < bytes.length; i++) {
2187
- result = (result << 8n) | BigInt(bytes[i]);
2188
- }
2189
- return result;
2190
- }
2191
-
2192
- async function testSlice(
2193
- core: Core,
2194
- hasher: Hasher,
2195
- originalSize: number,
2196
- sliceOffset: number,
2197
- sliceSize: number
2198
- ): Promise<void> {
2199
- await core.setLength(0);
2200
- const db = await Database.create(core, hasher);
2201
- const rootCursor = await db.rootCursor();
2202
-
2203
- const evenKey = await db.hasher.digest(new TextEncoder().encode('even'));
2204
- const evenSliceKey = await db.hasher.digest(new TextEncoder().encode('even-slice'));
2205
- const comboKey = await db.hasher.digest(new TextEncoder().encode('combo'));
2206
-
2207
- await rootCursor.writePath([
2208
- new ArrayListInit(),
2209
- new ArrayListAppend(),
2210
- new WriteData(await rootCursor.readPathSlot([new ArrayListGet(-1)])),
2211
- new HashMapInit(false, false),
2212
- new Context(async (cursor) => {
2213
- const values: number[] = [];
2214
-
2215
- // create list
2216
- for (let i = 0; i < originalSize; i++) {
2217
- const n = i * 2;
2218
- values.push(n);
2219
- await cursor.writePath([
2220
- new HashMapGet(new HashMapGetValue(evenKey)),
2221
- new LinkedArrayListInit(),
2222
- new LinkedArrayListAppend(),
2223
- new WriteData(new Uint(n)),
2224
- ]);
2225
- }
2226
-
2227
- // slice list
2228
- const evenListCursor = await cursor.readPath([new HashMapGet(new HashMapGetValue(evenKey))]);
2229
- const evenListSliceCursor = await cursor.writePath([
2230
- new HashMapGet(new HashMapGetValue(evenSliceKey)),
2231
- new WriteData(evenListCursor!.slotPtr.slot),
2232
- new LinkedArrayListInit(),
2233
- new LinkedArrayListSlice(sliceOffset, sliceSize),
2234
- ]);
2235
-
2236
- // check all the values in the new slice
2237
- for (let i = 0; i < sliceSize; i++) {
2238
- const val = values[sliceOffset + i];
2239
- const n = (
2240
- await cursor.readPath([new HashMapGet(new HashMapGetValue(evenSliceKey)), new LinkedArrayListGet(i)])
2241
- )!.readUint();
2242
- expect(val).toBe(n);
2243
- }
2244
-
2245
- // check all values in the new slice with an iterator
2246
- {
2247
- const iter = evenListSliceCursor.iterator();
2248
- await iter.init();
2249
- let i = 0;
2250
- while (await iter.hasNext()) {
2251
- const numCursor = await iter.next();
2252
- expect(values[sliceOffset + i]).toBe(numCursor!.readUint());
2253
- i += 1;
2254
- }
2255
- expect(sliceSize).toBe(i);
2256
- }
2257
-
2258
- // there are no extra items
2259
- expect(
2260
- await cursor.readPath([new HashMapGet(new HashMapGetValue(evenSliceKey)), new LinkedArrayListGet(sliceSize)])
2261
- ).toBeNull();
2262
-
2263
- // concat the slice with itself
2264
- await cursor.writePath([
2265
- new HashMapGet(new HashMapGetValue(comboKey)),
2266
- new WriteData(evenListSliceCursor.slotPtr.slot),
2267
- new LinkedArrayListInit(),
2268
- new LinkedArrayListConcat(evenListSliceCursor.slotPtr.slot),
2269
- ]);
2270
-
2271
- // check all values in the combo list
2272
- const comboValues: number[] = [];
2273
- comboValues.push(...values.slice(sliceOffset, sliceOffset + sliceSize));
2274
- comboValues.push(...values.slice(sliceOffset, sliceOffset + sliceSize));
2275
- for (let i = 0; i < comboValues.length; i++) {
2276
- const n = (
2277
- await cursor.readPath([new HashMapGet(new HashMapGetValue(comboKey)), new LinkedArrayListGet(i)])
2278
- )!.readUint();
2279
- expect(comboValues[i]).toBe(n);
2280
- }
2281
-
2282
- // append to the slice
2283
- await cursor.writePath([
2284
- new HashMapGet(new HashMapGetValue(evenSliceKey)),
2285
- new LinkedArrayListInit(),
2286
- new LinkedArrayListAppend(),
2287
- new WriteData(new Uint(3)),
2288
- ]);
2289
-
2290
- // read the new value from the slice
2291
- expect(
2292
- (await cursor.readPath([new HashMapGet(new HashMapGetValue(evenSliceKey)), new LinkedArrayListGet(-1)]))!
2293
- .readUint()
2294
- ).toBe(3);
2295
- }),
2296
- ]);
2297
- }
2298
-
2299
- async function testConcat(core: Core, hasher: Hasher, listASize: number, listBSize: number): Promise<void> {
2300
- await core.setLength(0);
2301
- const db = await Database.create(core, hasher);
2302
- const rootCursor = await db.rootCursor();
2303
-
2304
- const evenKey = await db.hasher.digest(new TextEncoder().encode('even'));
2305
- const oddKey = await db.hasher.digest(new TextEncoder().encode('odd'));
2306
- const comboKey = await db.hasher.digest(new TextEncoder().encode('combo'));
2307
-
2308
- const values: number[] = [];
2309
-
2310
- await rootCursor.writePath([
2311
- new ArrayListInit(),
2312
- new ArrayListAppend(),
2313
- new WriteData(await rootCursor.readPathSlot([new ArrayListGet(-1)])),
2314
- new HashMapInit(false, false),
2315
- new Context(async (cursor) => {
2316
- // create even list
2317
- await cursor.writePath([new HashMapGet(new HashMapGetValue(evenKey)), new LinkedArrayListInit()]);
2318
- for (let i = 0; i < listASize; i++) {
2319
- const n = i * 2;
2320
- values.push(n);
2321
- await cursor.writePath([
2322
- new HashMapGet(new HashMapGetValue(evenKey)),
2323
- new LinkedArrayListInit(),
2324
- new LinkedArrayListAppend(),
2325
- new WriteData(new Uint(n)),
2326
- ]);
2327
- }
2328
-
2329
- // create odd list
2330
- await cursor.writePath([new HashMapGet(new HashMapGetValue(oddKey)), new LinkedArrayListInit()]);
2331
- for (let i = 0; i < listBSize; i++) {
2332
- const n = i * 2 + 1;
2333
- values.push(n);
2334
- await cursor.writePath([
2335
- new HashMapGet(new HashMapGetValue(oddKey)),
2336
- new LinkedArrayListInit(),
2337
- new LinkedArrayListAppend(),
2338
- new WriteData(new Uint(n)),
2339
- ]);
2340
- }
2341
- }),
2342
- ]);
2343
-
2344
- await rootCursor.writePath([
2345
- new ArrayListInit(),
2346
- new ArrayListAppend(),
2347
- new WriteData(await rootCursor.readPathSlot([new ArrayListGet(-1)])),
2348
- new HashMapInit(false, false),
2349
- new Context(async (cursor) => {
2350
- // get the even list
2351
- const evenListCursor = await cursor.readPath([new HashMapGet(new HashMapGetValue(evenKey))]);
2352
-
2353
- // get the odd list
2354
- const oddListCursor = await cursor.readPath([new HashMapGet(new HashMapGetValue(oddKey))]);
2355
-
2356
- // concat the lists
2357
- const comboListCursor = await cursor.writePath([
2358
- new HashMapGet(new HashMapGetValue(comboKey)),
2359
- new WriteData(evenListCursor!.slotPtr.slot),
2360
- new LinkedArrayListInit(),
2361
- new LinkedArrayListConcat(oddListCursor!.slotPtr.slot),
2362
- ]);
2363
-
2364
- // check all values in the new list
2365
- for (let i = 0; i < values.length; i++) {
2366
- const n = (
2367
- await cursor.readPath([new HashMapGet(new HashMapGetValue(comboKey)), new LinkedArrayListGet(i)])
2368
- )!.readUint();
2369
- expect(values[i]).toBe(n);
2370
- }
2371
-
2372
- // check all values in the new slice with an iterator
2373
- {
2374
- const iter = comboListCursor.iterator();
2375
- await iter.init();
2376
- let i = 0;
2377
- while (await iter.hasNext()) {
2378
- const numCursor = await iter.next();
2379
- expect(values[i]).toBe(numCursor!.readUint());
2380
- i += 1;
2381
- }
2382
- expect((await evenListCursor!.count()) + (await oddListCursor!.count())).toBe(i);
2383
- }
2384
-
2385
- // there are no extra items
2386
- expect(
2387
- await cursor.readPath([new HashMapGet(new HashMapGetValue(comboKey)), new LinkedArrayListGet(values.length)])
2388
- ).toBeNull();
2389
- }),
2390
- ]);
2391
- }
2392
-
2393
- async function testInsertAndRemove(core: Core, hasher: Hasher, originalSize: number, insertIndex: number): Promise<void> {
2394
- await core.setLength(0);
2395
- const db = await Database.create(core, hasher);
2396
- const rootCursor = await db.rootCursor();
2397
-
2398
- const evenKey = await db.hasher.digest(new TextEncoder().encode('even'));
2399
- const evenInsertKey = await db.hasher.digest(new TextEncoder().encode('even-insert'));
2400
- const insertValue = 12345;
2401
-
2402
- await rootCursor.writePath([
2403
- new ArrayListInit(),
2404
- new ArrayListAppend(),
2405
- new WriteData(await rootCursor.readPathSlot([new ArrayListGet(-1)])),
2406
- new HashMapInit(false, false),
2407
- new Context(async (cursor) => {
2408
- const values: number[] = [];
2409
-
2410
- // create list
2411
- for (let i = 0; i < originalSize; i++) {
2412
- if (i === insertIndex) {
2413
- values.push(insertValue);
2414
- }
2415
- const n = i * 2;
2416
- values.push(n);
2417
- await cursor.writePath([
2418
- new HashMapGet(new HashMapGetValue(evenKey)),
2419
- new LinkedArrayListInit(),
2420
- new LinkedArrayListAppend(),
2421
- new WriteData(new Uint(n)),
2422
- ]);
2423
- }
2424
-
2425
- // insert into list
2426
- const evenListCursor = await cursor.readPath([new HashMapGet(new HashMapGetValue(evenKey))]);
2427
- const evenListInsertCursor = await cursor.writePath([
2428
- new HashMapGet(new HashMapGetValue(evenInsertKey)),
2429
- new WriteData(evenListCursor!.slotPtr.slot),
2430
- new LinkedArrayListInit(),
2431
- ]);
2432
- await evenListInsertCursor.writePath([
2433
- new LinkedArrayListInsert(insertIndex),
2434
- new WriteData(new Uint(insertValue)),
2435
- ]);
2436
-
2437
- // check all the values in the new list
2438
- for (let i = 0; i < values.length; i++) {
2439
- const val = values[i];
2440
- const n = (
2441
- await cursor.readPath([new HashMapGet(new HashMapGetValue(evenInsertKey)), new LinkedArrayListGet(i)])
2442
- )!.readUint();
2443
- expect(val).toBe(n);
2444
- }
2445
-
2446
- // check all values in the new list with an iterator
2447
- {
2448
- const iter = evenListInsertCursor.iterator();
2449
- await iter.init();
2450
- let i = 0;
2451
- while (await iter.hasNext()) {
2452
- const numCursor = await iter.next();
2453
- expect(values[i]).toBe(numCursor!.readUint());
2454
- i += 1;
2455
- }
2456
- expect(values.length).toBe(i);
2457
- }
2458
-
2459
- // there are no extra items
2460
- expect(
2461
- await cursor.readPath([
2462
- new HashMapGet(new HashMapGetValue(evenInsertKey)),
2463
- new LinkedArrayListGet(values.length),
2464
- ])
2465
- ).toBeNull();
2466
- }),
2467
- ]);
2468
-
2469
- await rootCursor.writePath([
2470
- new ArrayListInit(),
2471
- new ArrayListAppend(),
2472
- new WriteData(await rootCursor.readPathSlot([new ArrayListGet(-1)])),
2473
- new HashMapInit(false, false),
2474
- new Context(async (cursor) => {
2475
- const values: number[] = [];
2476
-
2477
- for (let i = 0; i < originalSize; i++) {
2478
- const n = i * 2;
2479
- values.push(n);
2480
- }
2481
-
2482
- // remove inserted value from the list
2483
- const evenListInsertCursor = await cursor.writePath([
2484
- new HashMapGet(new HashMapGetValue(evenInsertKey)),
2485
- new LinkedArrayListRemove(insertIndex),
2486
- ]);
2487
-
2488
- // check all the values in the new list
2489
- for (let i = 0; i < values.length; i++) {
2490
- const val = values[i];
2491
- const n = (
2492
- await cursor.readPath([new HashMapGet(new HashMapGetValue(evenInsertKey)), new LinkedArrayListGet(i)])
2493
- )!.readUint();
2494
- expect(val).toBe(n);
2495
- }
2496
-
2497
- // check all values in the new list with an iterator
2498
- {
2499
- const iter = evenListInsertCursor.iterator();
2500
- await iter.init();
2501
- let i = 0;
2502
- while (await iter.hasNext()) {
2503
- const numCursor = await iter.next();
2504
- expect(values[i]).toBe(numCursor!.readUint());
2505
- i += 1;
2506
- }
2507
- expect(values.length).toBe(i);
2508
- }
2509
-
2510
- // there are no extra items
2511
- expect(
2512
- await cursor.readPath([
2513
- new HashMapGet(new HashMapGetValue(evenInsertKey)),
2514
- new LinkedArrayListGet(values.length),
2515
- ])
2516
- ).toBeNull();
2517
- }),
2518
- ]);
2519
- }