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.
- package/dist/core-buffered-file.d.ts +41 -0
- package/dist/core-file.d.ts +18 -0
- package/dist/core-memory.d.ts +36 -0
- package/dist/core.d.ts +23 -0
- package/dist/database.d.ts +244 -0
- package/dist/exceptions.d.ts +51 -0
- package/dist/hasher.d.ts +9 -0
- package/dist/index.d.ts +26 -0
- package/dist/index.js +429 -266
- package/dist/read-array-list.d.ts +13 -0
- package/dist/read-counted-hash-map.d.ts +7 -0
- package/dist/read-counted-hash-set.d.ts +7 -0
- package/dist/read-cursor.d.ts +57 -0
- package/dist/read-hash-map.d.ts +27 -0
- package/dist/read-hash-set.d.ts +18 -0
- package/dist/read-linked-array-list.d.ts +13 -0
- package/dist/slot-pointer.d.ts +7 -0
- package/dist/slot.d.ts +15 -0
- package/{src/slotted.ts → dist/slotted.d.ts} +1 -2
- package/dist/tag.d.ts +17 -0
- package/dist/write-array-list.d.ts +16 -0
- package/dist/write-counted-hash-map.d.ts +7 -0
- package/dist/write-counted-hash-set.d.ts +7 -0
- package/dist/write-cursor.d.ts +36 -0
- package/dist/write-hash-map.d.ts +25 -0
- package/dist/write-hash-set.d.ts +19 -0
- package/dist/write-linked-array-list.d.ts +19 -0
- package/dist/writeable-data.d.ts +20 -0
- package/package.json +12 -1
- package/.claude/settings.local.json +0 -9
- package/bun.lock +0 -24
- package/bunfig.toml +0 -1
- package/example/README.md +0 -46
- package/example/dump.ts +0 -201
- package/src/core-buffered-file.ts +0 -226
- package/src/core-file.ts +0 -137
- package/src/core-memory.ts +0 -179
- package/src/core.ts +0 -25
- package/src/database.ts +0 -2232
- package/src/exceptions.ts +0 -31
- package/src/hasher.ts +0 -52
- package/src/index.ts +0 -110
- package/src/read-array-list.ts +0 -45
- package/src/read-counted-hash-map.ts +0 -28
- package/src/read-counted-hash-set.ts +0 -28
- package/src/read-cursor.ts +0 -546
- package/src/read-hash-map.ts +0 -117
- package/src/read-hash-set.ts +0 -70
- package/src/read-linked-array-list.ts +0 -45
- package/src/slot-pointer.ts +0 -15
- package/src/slot.ts +0 -51
- package/src/tag.ts +0 -23
- package/src/write-array-list.ts +0 -65
- package/src/write-counted-hash-map.ts +0 -31
- package/src/write-counted-hash-set.ts +0 -31
- package/src/write-cursor.ts +0 -166
- package/src/write-hash-map.ts +0 -129
- package/src/write-hash-set.ts +0 -86
- package/src/write-linked-array-list.ts +0 -80
- package/src/writeable-data.ts +0 -67
- package/tests/database.test.ts +0 -2519
- package/tests/fixtures/test.db +0 -0
- package/tsconfig.json +0 -17
package/tests/database.test.ts
DELETED
|
@@ -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
|
-
}
|