xitdb 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (63) hide show
  1. package/dist/core-buffered-file.d.ts +41 -0
  2. package/dist/core-file.d.ts +18 -0
  3. package/dist/core-memory.d.ts +36 -0
  4. package/dist/core.d.ts +23 -0
  5. package/dist/database.d.ts +244 -0
  6. package/dist/exceptions.d.ts +51 -0
  7. package/dist/hasher.d.ts +9 -0
  8. package/dist/index.d.ts +26 -0
  9. package/dist/index.js +429 -266
  10. package/dist/read-array-list.d.ts +13 -0
  11. package/dist/read-counted-hash-map.d.ts +7 -0
  12. package/dist/read-counted-hash-set.d.ts +7 -0
  13. package/dist/read-cursor.d.ts +57 -0
  14. package/dist/read-hash-map.d.ts +27 -0
  15. package/dist/read-hash-set.d.ts +18 -0
  16. package/dist/read-linked-array-list.d.ts +13 -0
  17. package/dist/slot-pointer.d.ts +7 -0
  18. package/dist/slot.d.ts +15 -0
  19. package/{src/slotted.ts → dist/slotted.d.ts} +1 -2
  20. package/dist/tag.d.ts +17 -0
  21. package/dist/write-array-list.d.ts +16 -0
  22. package/dist/write-counted-hash-map.d.ts +7 -0
  23. package/dist/write-counted-hash-set.d.ts +7 -0
  24. package/dist/write-cursor.d.ts +36 -0
  25. package/dist/write-hash-map.d.ts +25 -0
  26. package/dist/write-hash-set.d.ts +19 -0
  27. package/dist/write-linked-array-list.d.ts +19 -0
  28. package/dist/writeable-data.d.ts +20 -0
  29. package/package.json +12 -1
  30. package/.claude/settings.local.json +0 -9
  31. package/bun.lock +0 -24
  32. package/bunfig.toml +0 -1
  33. package/example/README.md +0 -46
  34. package/example/dump.ts +0 -201
  35. package/src/core-buffered-file.ts +0 -226
  36. package/src/core-file.ts +0 -137
  37. package/src/core-memory.ts +0 -179
  38. package/src/core.ts +0 -25
  39. package/src/database.ts +0 -2232
  40. package/src/exceptions.ts +0 -31
  41. package/src/hasher.ts +0 -52
  42. package/src/index.ts +0 -110
  43. package/src/read-array-list.ts +0 -45
  44. package/src/read-counted-hash-map.ts +0 -28
  45. package/src/read-counted-hash-set.ts +0 -28
  46. package/src/read-cursor.ts +0 -546
  47. package/src/read-hash-map.ts +0 -117
  48. package/src/read-hash-set.ts +0 -70
  49. package/src/read-linked-array-list.ts +0 -45
  50. package/src/slot-pointer.ts +0 -15
  51. package/src/slot.ts +0 -51
  52. package/src/tag.ts +0 -23
  53. package/src/write-array-list.ts +0 -65
  54. package/src/write-counted-hash-map.ts +0 -31
  55. package/src/write-counted-hash-set.ts +0 -31
  56. package/src/write-cursor.ts +0 -166
  57. package/src/write-hash-map.ts +0 -129
  58. package/src/write-hash-set.ts +0 -86
  59. package/src/write-linked-array-list.ts +0 -80
  60. package/src/writeable-data.ts +0 -67
  61. package/tests/database.test.ts +0 -2519
  62. package/tests/fixtures/test.db +0 -0
  63. package/tsconfig.json +0 -17
package/src/database.ts DELETED
@@ -1,2232 +0,0 @@
1
- import type { Core } from './core';
2
- import { Hasher } from './hasher';
3
- import { Tag, tagValueOf } from './tag';
4
- import { Slot } from './slot';
5
- import { SlotPointer } from './slot-pointer';
6
- import {
7
- InvalidDatabaseException,
8
- InvalidVersionException,
9
- InvalidHashSizeException,
10
- KeyNotFoundException,
11
- WriteNotAllowedException,
12
- UnexpectedTagException,
13
- CursorNotWriteableException,
14
- ExpectedTxStartException,
15
- KeyOffsetExceededException,
16
- PathPartMustBeAtEndException,
17
- InvalidTopLevelTypeException,
18
- ExpectedUnsignedLongException,
19
- NoAvailableSlotsException,
20
- MustSetNewSlotsToFullException,
21
- EmptySlotException,
22
- ExpectedRootNodeException,
23
- UnreachableException,
24
- MaxShiftExceededException,
25
- } from './exceptions';
26
- import { Bytes, Float, Int, Uint, type WriteableData } from './writeable-data';
27
-
28
- export const VERSION = 0;
29
- export const MAGIC_NUMBER = new Uint8Array([0x78, 0x69, 0x74]); // 'xit'
30
- export const BIT_COUNT = 4;
31
- export const SLOT_COUNT = 1 << BIT_COUNT;
32
- export const MASK = BigInt(SLOT_COUNT - 1);
33
- export const INDEX_BLOCK_SIZE = Slot.LENGTH * SLOT_COUNT;
34
- export const LINKED_ARRAY_LIST_SLOT_LENGTH = 8 + Slot.LENGTH;
35
- export const LINKED_ARRAY_LIST_INDEX_BLOCK_SIZE = LINKED_ARRAY_LIST_SLOT_LENGTH * SLOT_COUNT;
36
- export const MAX_BRANCH_LENGTH = 16;
37
-
38
- export enum WriteMode {
39
- READ_ONLY,
40
- READ_WRITE,
41
- }
42
-
43
- // Header
44
- export class Header {
45
- static readonly LENGTH = 12;
46
-
47
- constructor(
48
- public hashId: number,
49
- public hashSize: number,
50
- public version: number,
51
- public tag: Tag,
52
- public magicNumber: Uint8Array
53
- ) {}
54
-
55
- toBytes(): Uint8Array {
56
- const buffer = new ArrayBuffer(Header.LENGTH);
57
- const view = new DataView(buffer);
58
- const arr = new Uint8Array(buffer);
59
- arr.set(this.magicNumber, 0);
60
- view.setUint8(3, this.tag);
61
- view.setInt16(4, this.version, false);
62
- view.setInt16(6, this.hashSize, false);
63
- view.setInt32(8, this.hashId, false);
64
- return arr;
65
- }
66
-
67
- static async read(core: Core): Promise<Header> {
68
- const reader = core.reader();
69
- const magicNumber = new Uint8Array(3);
70
- await reader.readFully(magicNumber);
71
- const tagByte = await reader.readByte();
72
- const tag = tagValueOf(tagByte & 0b0111_1111);
73
- const version = await reader.readShort();
74
- const hashSize = await reader.readShort();
75
- const hashId = await reader.readInt();
76
- return new Header(hashId, hashSize, version, tag, magicNumber);
77
- }
78
-
79
- async write(core: Core): Promise<void> {
80
- const writer = core.writer();
81
- await writer.write(this.toBytes());
82
- }
83
-
84
- validate(): void {
85
- if (!arraysEqual(this.magicNumber, MAGIC_NUMBER)) {
86
- throw new InvalidDatabaseException();
87
- }
88
- if (this.version > VERSION) {
89
- throw new InvalidVersionException();
90
- }
91
- }
92
-
93
- withTag(tag: Tag): Header {
94
- return new Header(this.hashId, this.hashSize, this.version, tag, this.magicNumber);
95
- }
96
- }
97
-
98
- // ArrayListHeader
99
- export class ArrayListHeader {
100
- static readonly LENGTH = 16;
101
-
102
- constructor(public ptr: number, public size: number) {}
103
-
104
- toBytes(): Uint8Array {
105
- const buffer = new ArrayBuffer(ArrayListHeader.LENGTH);
106
- const view = new DataView(buffer);
107
- view.setBigInt64(0, BigInt(this.size), false);
108
- view.setBigInt64(8, BigInt(this.ptr), false);
109
- return new Uint8Array(buffer);
110
- }
111
-
112
- static fromBytes(bytes: Uint8Array): ArrayListHeader {
113
- const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
114
- const size = Number(view.getBigInt64(0, false));
115
- checkLong(size);
116
- const ptr = Number(view.getBigInt64(8, false));
117
- checkLong(ptr);
118
- return new ArrayListHeader(ptr, size);
119
- }
120
-
121
- withPtr(ptr: number): ArrayListHeader {
122
- return new ArrayListHeader(ptr, this.size);
123
- }
124
- }
125
-
126
- // TopLevelArrayListHeader
127
- export class TopLevelArrayListHeader {
128
- static readonly LENGTH = 8 + ArrayListHeader.LENGTH;
129
-
130
- constructor(public fileSize: number, public parent: ArrayListHeader) {}
131
-
132
- toBytes(): Uint8Array {
133
- const buffer = new ArrayBuffer(TopLevelArrayListHeader.LENGTH);
134
- const view = new DataView(buffer);
135
- const arr = new Uint8Array(buffer);
136
- arr.set(this.parent.toBytes(), 0);
137
- view.setBigInt64(ArrayListHeader.LENGTH, BigInt(this.fileSize), false);
138
- return arr;
139
- }
140
- }
141
-
142
- // LinkedArrayListHeader
143
- export class LinkedArrayListHeader {
144
- static readonly LENGTH = 17;
145
-
146
- constructor(public shift: number, public ptr: number, public size: number) {}
147
-
148
- toBytes(): Uint8Array {
149
- const buffer = new ArrayBuffer(LinkedArrayListHeader.LENGTH);
150
- const view = new DataView(buffer);
151
- view.setBigInt64(0, BigInt(this.size), false);
152
- view.setBigInt64(8, BigInt(this.ptr), false);
153
- view.setUint8(16, this.shift & 0b0011_1111);
154
- return new Uint8Array(buffer);
155
- }
156
-
157
- static fromBytes(bytes: Uint8Array): LinkedArrayListHeader {
158
- const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
159
- const size = Number(view.getBigInt64(0, false));
160
- checkLong(size);
161
- const ptr = Number(view.getBigInt64(8, false));
162
- checkLong(ptr);
163
- const shift = view.getUint8(16) & 0b0011_1111;
164
- return new LinkedArrayListHeader(shift, ptr, size);
165
- }
166
-
167
- withPtr(ptr: number): LinkedArrayListHeader {
168
- return new LinkedArrayListHeader(this.shift, ptr, this.size);
169
- }
170
- }
171
-
172
- // KeyValuePair
173
- export class KeyValuePair {
174
- constructor(
175
- public valueSlot: Slot,
176
- public keySlot: Slot,
177
- public hash: Uint8Array
178
- ) {}
179
-
180
- static length(hashSize: number): number {
181
- return hashSize + Slot.LENGTH * 2;
182
- }
183
-
184
- toBytes(): Uint8Array {
185
- const buffer = new Uint8Array(KeyValuePair.length(this.hash.length));
186
- buffer.set(this.hash, 0);
187
- buffer.set(this.keySlot.toBytes(), this.hash.length);
188
- buffer.set(this.valueSlot.toBytes(), this.hash.length + Slot.LENGTH);
189
- return buffer;
190
- }
191
-
192
- static fromBytes(bytes: Uint8Array, hashSize: number): KeyValuePair {
193
- const hash = bytes.slice(0, hashSize);
194
- const keySlotBytes = bytes.slice(hashSize, hashSize + Slot.LENGTH);
195
- const keySlot = Slot.fromBytes(keySlotBytes);
196
- const valueSlotBytes = bytes.slice(hashSize + Slot.LENGTH, hashSize + Slot.LENGTH * 2);
197
- const valueSlot = Slot.fromBytes(valueSlotBytes);
198
- return new KeyValuePair(valueSlot, keySlot, hash);
199
- }
200
- }
201
-
202
- // LinkedArrayListSlot
203
- export class LinkedArrayListSlot {
204
- static readonly LENGTH = 8 + Slot.LENGTH;
205
-
206
- constructor(public size: number, public slot: Slot) {}
207
-
208
- withSize(size: number): LinkedArrayListSlot {
209
- return new LinkedArrayListSlot(size, this.slot);
210
- }
211
-
212
- toBytes(): Uint8Array {
213
- const buffer = new ArrayBuffer(LinkedArrayListSlot.LENGTH);
214
- const view = new DataView(buffer);
215
- const arr = new Uint8Array(buffer);
216
- arr.set(this.slot.toBytes(), 0);
217
- view.setBigInt64(Slot.LENGTH, BigInt(this.size), false);
218
- return arr;
219
- }
220
-
221
- static fromBytes(bytes: Uint8Array): LinkedArrayListSlot {
222
- const slotBytes = bytes.slice(0, Slot.LENGTH);
223
- const slot = Slot.fromBytes(slotBytes);
224
- const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
225
- const size = Number(view.getBigInt64(Slot.LENGTH, false));
226
- checkLong(size);
227
- return new LinkedArrayListSlot(size, slot);
228
- }
229
- }
230
-
231
- // LinkedArrayListSlotPointer
232
- export class LinkedArrayListSlotPointer {
233
- constructor(public slotPtr: SlotPointer, public leafCount: number) {}
234
-
235
- withSlotPointer(slotPtr: SlotPointer): LinkedArrayListSlotPointer {
236
- return new LinkedArrayListSlotPointer(slotPtr, this.leafCount);
237
- }
238
- }
239
-
240
- // LinkedArrayListBlockInfo
241
- export class LinkedArrayListBlockInfo {
242
- constructor(
243
- public block: LinkedArrayListSlot[],
244
- public i: number,
245
- public parentSlot: LinkedArrayListSlot
246
- ) {}
247
- }
248
-
249
- // PathPart types (discriminated union)
250
- export type PathPart =
251
- | ArrayListInit
252
- | ArrayListGet
253
- | ArrayListAppend
254
- | ArrayListSlice
255
- | LinkedArrayListInit
256
- | LinkedArrayListGet
257
- | LinkedArrayListAppend
258
- | LinkedArrayListSlice
259
- | LinkedArrayListConcat
260
- | LinkedArrayListInsert
261
- | LinkedArrayListRemove
262
- | HashMapInit
263
- | HashMapGet
264
- | HashMapRemove
265
- | WriteData
266
- | Context;
267
-
268
- export interface PathPartBase {
269
- readSlotPointer(
270
- db: Database,
271
- isTopLevel: boolean,
272
- writeMode: WriteMode,
273
- path: PathPart[],
274
- pathI: number,
275
- slotPtr: SlotPointer
276
- ): Promise<SlotPointer>;
277
- }
278
-
279
- // HashMapGetTarget types
280
- export type HashMapGetTarget = HashMapGetKVPair | HashMapGetKey | HashMapGetValue;
281
-
282
- export class HashMapGetKVPair {
283
- readonly kind = 'kv_pair';
284
- constructor(public hash: Uint8Array) {}
285
- }
286
-
287
- export class HashMapGetKey {
288
- readonly kind = 'key';
289
- constructor(public hash: Uint8Array) {}
290
- }
291
-
292
- export class HashMapGetValue {
293
- readonly kind = 'value';
294
- constructor(public hash: Uint8Array) {}
295
- }
296
-
297
- // ContextFunction type
298
- export type ContextFunction = (cursor: any) => Promise<void>;
299
-
300
- // PathPart implementations
301
- export class ArrayListInit implements PathPartBase {
302
- readonly kind = 'ArrayListInit';
303
-
304
- async readSlotPointer(
305
- db: Database,
306
- isTopLevel: boolean,
307
- writeMode: WriteMode,
308
- path: PathPart[],
309
- pathI: number,
310
- slotPtr: SlotPointer
311
- ): Promise<SlotPointer> {
312
- if (writeMode === WriteMode.READ_ONLY) throw new WriteNotAllowedException();
313
-
314
- if (isTopLevel) {
315
- const writer = db.core.writer();
316
-
317
- if (db.header.tag === Tag.NONE) {
318
- await db.core.seek(Header.LENGTH);
319
- const arrayListPtr = Header.LENGTH + TopLevelArrayListHeader.LENGTH;
320
- await writer.write(
321
- new TopLevelArrayListHeader(0, new ArrayListHeader(arrayListPtr, 0)).toBytes()
322
- );
323
- await writer.write(new Uint8Array(INDEX_BLOCK_SIZE));
324
-
325
- await db.core.seek(0);
326
- db.header = db.header.withTag(Tag.ARRAY_LIST);
327
- await writer.write(db.header.toBytes());
328
- }
329
-
330
- const nextSlotPtr = slotPtr.withSlot(slotPtr.slot.withTag(Tag.ARRAY_LIST));
331
- return db.readSlotPointer(writeMode, path, pathI + 1, nextSlotPtr);
332
- }
333
-
334
- if (slotPtr.position === null) throw new CursorNotWriteableException();
335
- const position = slotPtr.position;
336
-
337
- switch (slotPtr.slot.tag) {
338
- case Tag.NONE: {
339
- const writer = db.core.writer();
340
- let arrayListStart = await db.core.length();
341
- await db.core.seek(arrayListStart);
342
- const arrayListPtr = arrayListStart + ArrayListHeader.LENGTH;
343
- await writer.write(new ArrayListHeader(arrayListPtr, 0).toBytes());
344
- await writer.write(new Uint8Array(INDEX_BLOCK_SIZE));
345
-
346
- const nextSlotPtr = new SlotPointer(position, new Slot(arrayListStart, Tag.ARRAY_LIST));
347
- await db.core.seek(position);
348
- await writer.write(nextSlotPtr.slot.toBytes());
349
- return db.readSlotPointer(writeMode, path, pathI + 1, nextSlotPtr);
350
- }
351
- case Tag.ARRAY_LIST: {
352
- const reader = db.core.reader();
353
- const writer = db.core.writer();
354
-
355
- let arrayListStart = Number(slotPtr.slot.value);
356
-
357
- if (db.txStart !== null) {
358
- if (arrayListStart < db.txStart) {
359
- await db.core.seek(arrayListStart);
360
- const headerBytes = new Uint8Array(ArrayListHeader.LENGTH);
361
- await reader.readFully(headerBytes);
362
- const header = ArrayListHeader.fromBytes(headerBytes);
363
- await db.core.seek(header.ptr);
364
- const arrayListIndexBlock = new Uint8Array(INDEX_BLOCK_SIZE);
365
- await reader.readFully(arrayListIndexBlock);
366
-
367
- arrayListStart = await db.core.length();
368
- await db.core.seek(arrayListStart);
369
- const nextArrayListPtr = arrayListStart + ArrayListHeader.LENGTH;
370
- const newHeader = header.withPtr(nextArrayListPtr);
371
- await writer.write(newHeader.toBytes());
372
- await writer.write(arrayListIndexBlock);
373
- }
374
- } else if (db.header.tag === Tag.ARRAY_LIST) {
375
- throw new ExpectedTxStartException();
376
- }
377
-
378
- const nextSlotPtr = new SlotPointer(position, new Slot(arrayListStart, Tag.ARRAY_LIST));
379
- await db.core.seek(position);
380
- await writer.write(nextSlotPtr.slot.toBytes());
381
- return db.readSlotPointer(writeMode, path, pathI + 1, nextSlotPtr);
382
- }
383
- default:
384
- throw new UnexpectedTagException();
385
- }
386
- }
387
- }
388
-
389
- export class ArrayListGet implements PathPartBase {
390
- readonly kind = 'ArrayListGet';
391
- constructor(public index: number) {}
392
-
393
- async readSlotPointer(
394
- db: Database,
395
- isTopLevel: boolean,
396
- writeMode: WriteMode,
397
- path: PathPart[],
398
- pathI: number,
399
- slotPtr: SlotPointer
400
- ): Promise<SlotPointer> {
401
- const tag = isTopLevel ? db.header.tag : slotPtr.slot.tag;
402
- switch (tag) {
403
- case Tag.NONE:
404
- throw new KeyNotFoundException();
405
- case Tag.ARRAY_LIST:
406
- break;
407
- default:
408
- throw new UnexpectedTagException();
409
- }
410
-
411
- const nextArrayListStart = Number(slotPtr.slot.value);
412
- let index = this.index;
413
-
414
- await db.core.seek(nextArrayListStart);
415
- const reader = db.core.reader();
416
- const headerBytes = new Uint8Array(ArrayListHeader.LENGTH);
417
- await reader.readFully(headerBytes);
418
- const header = ArrayListHeader.fromBytes(headerBytes);
419
- if (index >= header.size || index < -header.size) {
420
- throw new KeyNotFoundException();
421
- }
422
-
423
- const key = index < 0 ? header.size - Math.abs(index) : index;
424
- const lastKey = header.size - 1;
425
- const shift = lastKey < SLOT_COUNT ? 0 : Math.floor(Math.log(lastKey) / Math.log(SLOT_COUNT));
426
- const finalSlotPtr = await db.readArrayListSlot(header.ptr, key, shift, writeMode, isTopLevel);
427
-
428
- return db.readSlotPointer(writeMode, path, pathI + 1, finalSlotPtr);
429
- }
430
- }
431
-
432
- export class ArrayListAppend implements PathPartBase {
433
- readonly kind = 'ArrayListAppend';
434
-
435
- async readSlotPointer(
436
- db: Database,
437
- isTopLevel: boolean,
438
- writeMode: WriteMode,
439
- path: PathPart[],
440
- pathI: number,
441
- slotPtr: SlotPointer
442
- ): Promise<SlotPointer> {
443
- if (writeMode === WriteMode.READ_ONLY) throw new WriteNotAllowedException();
444
-
445
- const tag = isTopLevel ? db.header.tag : slotPtr.slot.tag;
446
- if (tag !== Tag.ARRAY_LIST) throw new UnexpectedTagException();
447
-
448
- const reader = db.core.reader();
449
- const nextArrayListStart = Number(slotPtr.slot.value);
450
-
451
- await db.core.seek(nextArrayListStart);
452
- const headerBytes = new Uint8Array(ArrayListHeader.LENGTH);
453
- await reader.readFully(headerBytes);
454
- const origHeader = ArrayListHeader.fromBytes(headerBytes);
455
-
456
- const appendResult = await db.readArrayListSlotAppend(origHeader, writeMode, isTopLevel);
457
- const finalSlotPtr = await db.readSlotPointer(writeMode, path, pathI + 1, appendResult.slotPtr);
458
-
459
- const writer = db.core.writer();
460
- if (isTopLevel) {
461
- await db.core.flush();
462
- const fileSize = await db.core.length();
463
- const header = new TopLevelArrayListHeader(fileSize, appendResult.header);
464
- await db.core.seek(nextArrayListStart);
465
- await writer.write(header.toBytes());
466
- } else {
467
- await db.core.seek(nextArrayListStart);
468
- await writer.write(appendResult.header.toBytes());
469
- }
470
-
471
- return finalSlotPtr;
472
- }
473
- }
474
-
475
- export class ArrayListSlice implements PathPartBase {
476
- readonly kind = 'ArrayListSlice';
477
- constructor(public size: number) {}
478
-
479
- async readSlotPointer(
480
- db: Database,
481
- isTopLevel: boolean,
482
- writeMode: WriteMode,
483
- path: PathPart[],
484
- pathI: number,
485
- slotPtr: SlotPointer
486
- ): Promise<SlotPointer> {
487
- if (writeMode === WriteMode.READ_ONLY) throw new WriteNotAllowedException();
488
- if (slotPtr.slot.tag !== Tag.ARRAY_LIST) throw new UnexpectedTagException();
489
-
490
- const reader = db.core.reader();
491
- const nextArrayListStart = Number(slotPtr.slot.value);
492
-
493
- await db.core.seek(nextArrayListStart);
494
- const headerBytes = new Uint8Array(ArrayListHeader.LENGTH);
495
- await reader.readFully(headerBytes);
496
- const origHeader = ArrayListHeader.fromBytes(headerBytes);
497
-
498
- const sliceHeader = await db.readArrayListSlice(origHeader, this.size);
499
- const finalSlotPtr = await db.readSlotPointer(writeMode, path, pathI + 1, slotPtr);
500
-
501
- const writer = db.core.writer();
502
- await db.core.seek(nextArrayListStart);
503
- await writer.write(sliceHeader.toBytes());
504
-
505
- return finalSlotPtr;
506
- }
507
- }
508
-
509
- export class LinkedArrayListInit implements PathPartBase {
510
- readonly kind = 'LinkedArrayListInit';
511
-
512
- async readSlotPointer(
513
- db: Database,
514
- isTopLevel: boolean,
515
- writeMode: WriteMode,
516
- path: PathPart[],
517
- pathI: number,
518
- slotPtr: SlotPointer
519
- ): Promise<SlotPointer> {
520
- if (writeMode === WriteMode.READ_ONLY) throw new WriteNotAllowedException();
521
- if (isTopLevel) throw new InvalidTopLevelTypeException();
522
- if (slotPtr.position === null) throw new CursorNotWriteableException();
523
- const position = slotPtr.position;
524
-
525
- switch (slotPtr.slot.tag) {
526
- case Tag.NONE: {
527
- const writer = db.core.writer();
528
- const arrayListStart = await db.core.length();
529
- await db.core.seek(arrayListStart);
530
- const arrayListPtr = arrayListStart + LinkedArrayListHeader.LENGTH;
531
- await writer.write(new LinkedArrayListHeader(0, arrayListPtr, 0).toBytes());
532
- await writer.write(new Uint8Array(LINKED_ARRAY_LIST_INDEX_BLOCK_SIZE));
533
-
534
- const nextSlotPtr = new SlotPointer(position, new Slot(arrayListStart, Tag.LINKED_ARRAY_LIST));
535
- await db.core.seek(position);
536
- await writer.write(nextSlotPtr.slot.toBytes());
537
- return db.readSlotPointer(writeMode, path, pathI + 1, nextSlotPtr);
538
- }
539
- case Tag.LINKED_ARRAY_LIST: {
540
- const reader = db.core.reader();
541
- const writer = db.core.writer();
542
-
543
- let arrayListStart = Number(slotPtr.slot.value);
544
-
545
- if (db.txStart !== null) {
546
- if (arrayListStart < db.txStart) {
547
- await db.core.seek(arrayListStart);
548
- const headerBytes = new Uint8Array(LinkedArrayListHeader.LENGTH);
549
- await reader.readFully(headerBytes);
550
- const header = LinkedArrayListHeader.fromBytes(headerBytes);
551
- await db.core.seek(header.ptr);
552
- const arrayListIndexBlock = new Uint8Array(LINKED_ARRAY_LIST_INDEX_BLOCK_SIZE);
553
- await reader.readFully(arrayListIndexBlock);
554
-
555
- arrayListStart = await db.core.length();
556
- await db.core.seek(arrayListStart);
557
- const nextArrayListPtr = arrayListStart + LinkedArrayListHeader.LENGTH;
558
- const newHeader = header.withPtr(nextArrayListPtr);
559
- await writer.write(newHeader.toBytes());
560
- await writer.write(arrayListIndexBlock);
561
- }
562
- } else if (db.header.tag === Tag.ARRAY_LIST) {
563
- throw new ExpectedTxStartException();
564
- }
565
-
566
- const nextSlotPtr = new SlotPointer(position, new Slot(arrayListStart, Tag.LINKED_ARRAY_LIST));
567
- await db.core.seek(position);
568
- await writer.write(nextSlotPtr.slot.toBytes());
569
- return db.readSlotPointer(writeMode, path, pathI + 1, nextSlotPtr);
570
- }
571
- default:
572
- throw new UnexpectedTagException();
573
- }
574
- }
575
- }
576
-
577
- export class LinkedArrayListGet implements PathPartBase {
578
- readonly kind = 'LinkedArrayListGet';
579
- constructor(public index: number) {}
580
-
581
- async readSlotPointer(
582
- db: Database,
583
- isTopLevel: boolean,
584
- writeMode: WriteMode,
585
- path: PathPart[],
586
- pathI: number,
587
- slotPtr: SlotPointer
588
- ): Promise<SlotPointer> {
589
- switch (slotPtr.slot.tag) {
590
- case Tag.NONE:
591
- throw new KeyNotFoundException();
592
- case Tag.LINKED_ARRAY_LIST:
593
- break;
594
- default:
595
- throw new UnexpectedTagException();
596
- }
597
-
598
- let index = this.index;
599
-
600
- await db.core.seek(Number(slotPtr.slot.value));
601
- const reader = db.core.reader();
602
- const headerBytes = new Uint8Array(LinkedArrayListHeader.LENGTH);
603
- await reader.readFully(headerBytes);
604
- const header = LinkedArrayListHeader.fromBytes(headerBytes);
605
- if (index >= header.size || index < -header.size) {
606
- throw new KeyNotFoundException();
607
- }
608
-
609
- const key = index < 0 ? header.size - Math.abs(index) : index;
610
- const finalSlotPtr = await db.readLinkedArrayListSlot(header.ptr, key, header.shift, writeMode, isTopLevel);
611
-
612
- return db.readSlotPointer(writeMode, path, pathI + 1, finalSlotPtr.slotPtr);
613
- }
614
- }
615
-
616
- export class LinkedArrayListAppend implements PathPartBase {
617
- readonly kind = 'LinkedArrayListAppend';
618
-
619
- async readSlotPointer(
620
- db: Database,
621
- isTopLevel: boolean,
622
- writeMode: WriteMode,
623
- path: PathPart[],
624
- pathI: number,
625
- slotPtr: SlotPointer
626
- ): Promise<SlotPointer> {
627
- if (writeMode === WriteMode.READ_ONLY) throw new WriteNotAllowedException();
628
- if (slotPtr.slot.tag !== Tag.LINKED_ARRAY_LIST) throw new UnexpectedTagException();
629
-
630
- const reader = db.core.reader();
631
- const nextArrayListStart = Number(slotPtr.slot.value);
632
-
633
- await db.core.seek(nextArrayListStart);
634
- const headerBytes = new Uint8Array(LinkedArrayListHeader.LENGTH);
635
- await reader.readFully(headerBytes);
636
- const origHeader = LinkedArrayListHeader.fromBytes(headerBytes);
637
-
638
- const appendResult = await db.readLinkedArrayListSlotAppend(origHeader, writeMode, isTopLevel);
639
- const finalSlotPtr = await db.readSlotPointer(writeMode, path, pathI + 1, appendResult.slotPtr.slotPtr);
640
-
641
- const writer = db.core.writer();
642
- await db.core.seek(nextArrayListStart);
643
- await writer.write(appendResult.header.toBytes());
644
-
645
- return finalSlotPtr;
646
- }
647
- }
648
-
649
- export class LinkedArrayListSlice implements PathPartBase {
650
- readonly kind = 'LinkedArrayListSlice';
651
- constructor(public offset: number, public size: number) {}
652
-
653
- async readSlotPointer(
654
- db: Database,
655
- isTopLevel: boolean,
656
- writeMode: WriteMode,
657
- path: PathPart[],
658
- pathI: number,
659
- slotPtr: SlotPointer
660
- ): Promise<SlotPointer> {
661
- if (writeMode === WriteMode.READ_ONLY) throw new WriteNotAllowedException();
662
- if (slotPtr.slot.tag !== Tag.LINKED_ARRAY_LIST) throw new UnexpectedTagException();
663
-
664
- const reader = db.core.reader();
665
- const nextArrayListStart = Number(slotPtr.slot.value);
666
-
667
- await db.core.seek(nextArrayListStart);
668
- const headerBytes = new Uint8Array(LinkedArrayListHeader.LENGTH);
669
- await reader.readFully(headerBytes);
670
- const origHeader = LinkedArrayListHeader.fromBytes(headerBytes);
671
-
672
- const sliceHeader = await db.readLinkedArrayListSlice(origHeader, this.offset, this.size);
673
- const finalSlotPtr = await db.readSlotPointer(writeMode, path, pathI + 1, slotPtr);
674
-
675
- const writer = db.core.writer();
676
- await db.core.seek(nextArrayListStart);
677
- await writer.write(sliceHeader.toBytes());
678
-
679
- return finalSlotPtr;
680
- }
681
- }
682
-
683
- export class LinkedArrayListConcat implements PathPartBase {
684
- readonly kind = 'LinkedArrayListConcat';
685
- constructor(public list: Slot) {}
686
-
687
- async readSlotPointer(
688
- db: Database,
689
- isTopLevel: boolean,
690
- writeMode: WriteMode,
691
- path: PathPart[],
692
- pathI: number,
693
- slotPtr: SlotPointer
694
- ): Promise<SlotPointer> {
695
- if (writeMode === WriteMode.READ_ONLY) throw new WriteNotAllowedException();
696
- if (slotPtr.slot.tag !== Tag.LINKED_ARRAY_LIST) throw new UnexpectedTagException();
697
- if (this.list.tag !== Tag.LINKED_ARRAY_LIST) throw new UnexpectedTagException();
698
-
699
- const reader = db.core.reader();
700
- const nextArrayListStart = Number(slotPtr.slot.value);
701
-
702
- await db.core.seek(nextArrayListStart);
703
- const headerBytesA = new Uint8Array(LinkedArrayListHeader.LENGTH);
704
- await reader.readFully(headerBytesA);
705
- const headerA = LinkedArrayListHeader.fromBytes(headerBytesA);
706
- await db.core.seek(Number(this.list.value));
707
- const headerBytesB = new Uint8Array(LinkedArrayListHeader.LENGTH);
708
- await reader.readFully(headerBytesB);
709
- const headerB = LinkedArrayListHeader.fromBytes(headerBytesB);
710
-
711
- const concatHeader = await db.readLinkedArrayListConcat(headerA, headerB);
712
- const finalSlotPtr = await db.readSlotPointer(writeMode, path, pathI + 1, slotPtr);
713
-
714
- const writer = db.core.writer();
715
- await db.core.seek(nextArrayListStart);
716
- await writer.write(concatHeader.toBytes());
717
-
718
- return finalSlotPtr;
719
- }
720
- }
721
-
722
- export class LinkedArrayListInsert implements PathPartBase {
723
- readonly kind = 'LinkedArrayListInsert';
724
- constructor(public index: number) {}
725
-
726
- async readSlotPointer(
727
- db: Database,
728
- isTopLevel: boolean,
729
- writeMode: WriteMode,
730
- path: PathPart[],
731
- pathI: number,
732
- slotPtr: SlotPointer
733
- ): Promise<SlotPointer> {
734
- if (writeMode === WriteMode.READ_ONLY) throw new WriteNotAllowedException();
735
- if (slotPtr.slot.tag !== Tag.LINKED_ARRAY_LIST) throw new UnexpectedTagException();
736
-
737
- const reader = db.core.reader();
738
- const nextArrayListStart = Number(slotPtr.slot.value);
739
-
740
- await db.core.seek(nextArrayListStart);
741
- const headerBytes = new Uint8Array(LinkedArrayListHeader.LENGTH);
742
- await reader.readFully(headerBytes);
743
- const origHeader = LinkedArrayListHeader.fromBytes(headerBytes);
744
-
745
- let index = this.index;
746
- if (index >= origHeader.size || index < -origHeader.size) {
747
- throw new KeyNotFoundException();
748
- }
749
- const key = index < 0 ? origHeader.size - Math.abs(index) : index;
750
-
751
- const headerA = await db.readLinkedArrayListSlice(origHeader, 0, key);
752
- const headerB = await db.readLinkedArrayListSlice(origHeader, key, origHeader.size - key);
753
-
754
- const appendResult = await db.readLinkedArrayListSlotAppend(headerA, writeMode, isTopLevel);
755
- const concatHeader = await db.readLinkedArrayListConcat(appendResult.header, headerB);
756
-
757
- const nextSlotPtr = await db.readLinkedArrayListSlot(concatHeader.ptr, key, concatHeader.shift, WriteMode.READ_ONLY, isTopLevel);
758
- const finalSlotPtr = await db.readSlotPointer(writeMode, path, pathI + 1, nextSlotPtr.slotPtr);
759
-
760
- const writer = db.core.writer();
761
- await db.core.seek(nextArrayListStart);
762
- await writer.write(concatHeader.toBytes());
763
-
764
- return finalSlotPtr;
765
- }
766
- }
767
-
768
- export class LinkedArrayListRemove implements PathPartBase {
769
- readonly kind = 'LinkedArrayListRemove';
770
- constructor(public index: number) {}
771
-
772
- async readSlotPointer(
773
- db: Database,
774
- isTopLevel: boolean,
775
- writeMode: WriteMode,
776
- path: PathPart[],
777
- pathI: number,
778
- slotPtr: SlotPointer
779
- ): Promise<SlotPointer> {
780
- if (writeMode === WriteMode.READ_ONLY) throw new WriteNotAllowedException();
781
- if (slotPtr.slot.tag !== Tag.LINKED_ARRAY_LIST) throw new UnexpectedTagException();
782
-
783
- const reader = db.core.reader();
784
- const nextArrayListStart = Number(slotPtr.slot.value);
785
-
786
- await db.core.seek(nextArrayListStart);
787
- const headerBytes = new Uint8Array(LinkedArrayListHeader.LENGTH);
788
- await reader.readFully(headerBytes);
789
- const origHeader = LinkedArrayListHeader.fromBytes(headerBytes);
790
-
791
- let index = this.index;
792
- if (index >= origHeader.size || index < -origHeader.size) {
793
- throw new KeyNotFoundException();
794
- }
795
- const key = index < 0 ? origHeader.size - Math.abs(index) : index;
796
-
797
- const headerA = await db.readLinkedArrayListSlice(origHeader, 0, key);
798
- const headerB = await db.readLinkedArrayListSlice(origHeader, key + 1, origHeader.size - (key + 1));
799
- const concatHeader = await db.readLinkedArrayListConcat(headerA, headerB);
800
-
801
- const nextSlotPtr = new SlotPointer(concatHeader.ptr, new Slot(nextArrayListStart, Tag.LINKED_ARRAY_LIST));
802
- const finalSlotPtr = await db.readSlotPointer(writeMode, path, pathI + 1, nextSlotPtr);
803
-
804
- const writer = db.core.writer();
805
- await db.core.seek(nextArrayListStart);
806
- await writer.write(concatHeader.toBytes());
807
-
808
- return finalSlotPtr;
809
- }
810
- }
811
-
812
- export class HashMapInit implements PathPartBase {
813
- readonly kind = 'HashMapInit';
814
- constructor(public counted: boolean = false, public set: boolean = false) {}
815
-
816
- async readSlotPointer(
817
- db: Database,
818
- isTopLevel: boolean,
819
- writeMode: WriteMode,
820
- path: PathPart[],
821
- pathI: number,
822
- slotPtr: SlotPointer
823
- ): Promise<SlotPointer> {
824
- if (writeMode === WriteMode.READ_ONLY) throw new WriteNotAllowedException();
825
-
826
- const tag = this.counted
827
- ? (this.set ? Tag.COUNTED_HASH_SET : Tag.COUNTED_HASH_MAP)
828
- : (this.set ? Tag.HASH_SET : Tag.HASH_MAP);
829
-
830
- if (isTopLevel) {
831
- const writer = db.core.writer();
832
-
833
- if (db.header.tag === Tag.NONE) {
834
- await db.core.seek(Header.LENGTH);
835
-
836
- if (this.counted) {
837
- await writer.writeLong(0);
838
- }
839
-
840
- await writer.write(new Uint8Array(INDEX_BLOCK_SIZE));
841
-
842
- await db.core.seek(0);
843
- db.header = db.header.withTag(tag);
844
- await writer.write(db.header.toBytes());
845
- }
846
-
847
- const nextSlotPtr = slotPtr.withSlot(slotPtr.slot.withTag(tag));
848
- return db.readSlotPointer(writeMode, path, pathI + 1, nextSlotPtr);
849
- }
850
-
851
- if (slotPtr.position === null) throw new CursorNotWriteableException();
852
- const position = slotPtr.position;
853
-
854
- switch (slotPtr.slot.tag) {
855
- case Tag.NONE: {
856
- const writer = db.core.writer();
857
- const mapStart = await db.core.length();
858
- await db.core.seek(mapStart);
859
- if (this.counted) {
860
- await writer.writeLong(0);
861
- }
862
- await writer.write(new Uint8Array(INDEX_BLOCK_SIZE));
863
-
864
- const nextSlotPtr = new SlotPointer(position, new Slot(mapStart, tag));
865
- await db.core.seek(position);
866
- await writer.write(nextSlotPtr.slot.toBytes());
867
- return db.readSlotPointer(writeMode, path, pathI + 1, nextSlotPtr);
868
- }
869
- case Tag.HASH_MAP:
870
- case Tag.HASH_SET:
871
- case Tag.COUNTED_HASH_MAP:
872
- case Tag.COUNTED_HASH_SET: {
873
- if (this.counted) {
874
- switch (slotPtr.slot.tag) {
875
- case Tag.COUNTED_HASH_MAP:
876
- case Tag.COUNTED_HASH_SET:
877
- break;
878
- default:
879
- throw new UnexpectedTagException();
880
- }
881
- } else {
882
- switch (slotPtr.slot.tag) {
883
- case Tag.HASH_MAP:
884
- case Tag.HASH_SET:
885
- break;
886
- default:
887
- throw new UnexpectedTagException();
888
- }
889
- }
890
-
891
- const reader = db.core.reader();
892
- const writer = db.core.writer();
893
-
894
- let mapStart = Number(slotPtr.slot.value);
895
-
896
- if (db.txStart !== null) {
897
- if (mapStart < db.txStart) {
898
- await db.core.seek(mapStart);
899
- let mapCountMaybe: number | null = null;
900
- if (this.counted) {
901
- mapCountMaybe = await reader.readLong();
902
- }
903
- const mapIndexBlock = new Uint8Array(INDEX_BLOCK_SIZE);
904
- await reader.readFully(mapIndexBlock);
905
-
906
- mapStart = await db.core.length();
907
- await db.core.seek(mapStart);
908
- if (mapCountMaybe !== null) {
909
- await writer.writeLong(mapCountMaybe);
910
- }
911
- await writer.write(mapIndexBlock);
912
- }
913
- } else if (db.header.tag === Tag.ARRAY_LIST) {
914
- throw new ExpectedTxStartException();
915
- }
916
-
917
- const nextSlotPtr = new SlotPointer(position, new Slot(mapStart, tag));
918
- await db.core.seek(position);
919
- await writer.write(nextSlotPtr.slot.toBytes());
920
- return db.readSlotPointer(writeMode, path, pathI + 1, nextSlotPtr);
921
- }
922
- default:
923
- throw new UnexpectedTagException();
924
- }
925
- }
926
- }
927
-
928
- export class HashMapGet implements PathPartBase {
929
- readonly kind = 'HashMapGet';
930
- constructor(public target: HashMapGetTarget) {}
931
-
932
- async readSlotPointer(
933
- db: Database,
934
- isTopLevel: boolean,
935
- writeMode: WriteMode,
936
- path: PathPart[],
937
- pathI: number,
938
- slotPtr: SlotPointer
939
- ): Promise<SlotPointer> {
940
- let counted = false;
941
- switch (slotPtr.slot.tag) {
942
- case Tag.NONE:
943
- throw new KeyNotFoundException();
944
- case Tag.HASH_MAP:
945
- case Tag.HASH_SET:
946
- break;
947
- case Tag.COUNTED_HASH_MAP:
948
- case Tag.COUNTED_HASH_SET:
949
- counted = true;
950
- break;
951
- default:
952
- throw new UnexpectedTagException();
953
- }
954
-
955
- const indexPos = counted ? Number(slotPtr.slot.value) + 8 : Number(slotPtr.slot.value);
956
- const hash = db.checkHash(this.target);
957
- const res = await db.readMapSlot(indexPos, hash, 0, writeMode, isTopLevel, this.target);
958
-
959
- if (writeMode === WriteMode.READ_WRITE && counted && res.isEmpty) {
960
- const reader = db.core.reader();
961
- const writer = db.core.writer();
962
- await db.core.seek(Number(slotPtr.slot.value));
963
- const mapCount = await reader.readLong();
964
- await db.core.seek(Number(slotPtr.slot.value));
965
- await writer.writeLong(mapCount + 1);
966
- }
967
-
968
- return db.readSlotPointer(writeMode, path, pathI + 1, res.slotPtr);
969
- }
970
- }
971
-
972
- export class HashMapRemove implements PathPartBase {
973
- readonly kind = 'HashMapRemove';
974
- constructor(public hash: Uint8Array) {}
975
-
976
- async readSlotPointer(
977
- db: Database,
978
- isTopLevel: boolean,
979
- writeMode: WriteMode,
980
- path: PathPart[],
981
- pathI: number,
982
- slotPtr: SlotPointer
983
- ): Promise<SlotPointer> {
984
- if (writeMode === WriteMode.READ_ONLY) throw new WriteNotAllowedException();
985
-
986
- let counted = false;
987
- switch (slotPtr.slot.tag) {
988
- case Tag.NONE:
989
- throw new KeyNotFoundException();
990
- case Tag.HASH_MAP:
991
- case Tag.HASH_SET:
992
- break;
993
- case Tag.COUNTED_HASH_MAP:
994
- case Tag.COUNTED_HASH_SET:
995
- counted = true;
996
- break;
997
- default:
998
- throw new UnexpectedTagException();
999
- }
1000
-
1001
- const indexPos = counted ? Number(slotPtr.slot.value) + 8 : Number(slotPtr.slot.value);
1002
- const hash = db.checkHashBytes(this.hash);
1003
-
1004
- let keyFound = true;
1005
- try {
1006
- await db.removeMapSlot(indexPos, hash, 0, isTopLevel);
1007
- } catch (e) {
1008
- if (e instanceof KeyNotFoundException) {
1009
- keyFound = false;
1010
- } else {
1011
- throw e;
1012
- }
1013
- }
1014
-
1015
- if (writeMode === WriteMode.READ_WRITE && counted && keyFound) {
1016
- const reader = db.core.reader();
1017
- const writer = db.core.writer();
1018
- await db.core.seek(Number(slotPtr.slot.value));
1019
- const mapCount = await reader.readLong();
1020
- await db.core.seek(Number(slotPtr.slot.value));
1021
- await writer.writeLong(mapCount - 1);
1022
- }
1023
-
1024
- if (!keyFound) throw new KeyNotFoundException();
1025
-
1026
- return slotPtr;
1027
- }
1028
- }
1029
-
1030
- export class WriteData implements PathPartBase {
1031
- readonly kind = 'WriteData';
1032
- constructor(public data: WriteableData | null) {}
1033
-
1034
- async readSlotPointer(
1035
- db: Database,
1036
- isTopLevel: boolean,
1037
- writeMode: WriteMode,
1038
- path: PathPart[],
1039
- pathI: number,
1040
- slotPtr: SlotPointer
1041
- ): Promise<SlotPointer> {
1042
- if (writeMode === WriteMode.READ_ONLY) throw new WriteNotAllowedException();
1043
- if (slotPtr.position === null) throw new CursorNotWriteableException();
1044
- const position = slotPtr.position;
1045
-
1046
- const writer = db.core.writer();
1047
-
1048
- const data = this.data;
1049
- let slot: Slot;
1050
-
1051
- if (data === null) {
1052
- slot = new Slot();
1053
- } else if (data instanceof Slot) {
1054
- slot = data;
1055
- } else if (data instanceof Uint) {
1056
- if (data.value < 0) {
1057
- throw new Error('Uint must not be negative');
1058
- }
1059
- slot = new Slot(data.value, Tag.UINT);
1060
- } else if (data instanceof Int) {
1061
- slot = new Slot(data.value, Tag.INT);
1062
- } else if (data instanceof Float) {
1063
- const buffer = new ArrayBuffer(8);
1064
- const view = new DataView(buffer);
1065
- view.setFloat64(0, data.value, false);
1066
- const longValue = view.getBigInt64(0, false);
1067
- slot = new Slot(longValue, Tag.FLOAT);
1068
- } else if (data instanceof Bytes) {
1069
- if (data.isShort()) {
1070
- const buffer = new Uint8Array(8);
1071
- buffer.set(data.value, 0);
1072
- if (data.formatTag !== null) {
1073
- buffer.set(data.formatTag, 6);
1074
- }
1075
- const view = new DataView(buffer.buffer);
1076
- // Read 8 bytes big-endian as BigInt for full precision
1077
- const longValue = view.getBigInt64(0, false);
1078
- slot = new Slot(longValue, Tag.SHORT_BYTES, data.formatTag !== null);
1079
- } else {
1080
- // Import WriteCursor dynamically to avoid circular dependency
1081
- const { WriteCursor } = await import('./write-cursor');
1082
- const nextCursor = new WriteCursor(slotPtr, db);
1083
- const cursorWriter = await nextCursor.writer();
1084
- cursorWriter.formatTag = data.formatTag;
1085
- await cursorWriter.write(data.value);
1086
- await cursorWriter.finish();
1087
- slot = cursorWriter.slot;
1088
- }
1089
- } else {
1090
- throw new Error('Unknown data type');
1091
- }
1092
-
1093
- if (slot.tag === Tag.NONE) {
1094
- slot = slot.withFull(true);
1095
- }
1096
-
1097
- await db.core.seek(position);
1098
- await writer.write(slot.toBytes());
1099
-
1100
- const nextSlotPtr = new SlotPointer(slotPtr.position, slot);
1101
- return db.readSlotPointer(writeMode, path, pathI + 1, nextSlotPtr);
1102
- }
1103
- }
1104
-
1105
- export class Context implements PathPartBase {
1106
- readonly kind = 'Context';
1107
- constructor(public fn: ContextFunction) {}
1108
-
1109
- async readSlotPointer(
1110
- db: Database,
1111
- isTopLevel: boolean,
1112
- writeMode: WriteMode,
1113
- path: PathPart[],
1114
- pathI: number,
1115
- slotPtr: SlotPointer
1116
- ): Promise<SlotPointer> {
1117
- if (writeMode === WriteMode.READ_ONLY) throw new WriteNotAllowedException();
1118
- if (pathI !== path.length - 1) throw new PathPartMustBeAtEndException();
1119
-
1120
- const { WriteCursor } = await import('./write-cursor');
1121
- const nextCursor = new WriteCursor(slotPtr, db);
1122
- try {
1123
- await this.fn(nextCursor);
1124
- } catch (e) {
1125
- try {
1126
- await db.truncate();
1127
- } catch (_) {}
1128
- throw e;
1129
- }
1130
- return nextCursor.slotPtr;
1131
- }
1132
- }
1133
-
1134
- // HashMapGetResult
1135
- class HashMapGetResult {
1136
- constructor(public slotPtr: SlotPointer, public isEmpty: boolean) {}
1137
- }
1138
-
1139
- // ArrayListAppendResult
1140
- class ArrayListAppendResult {
1141
- constructor(public header: ArrayListHeader, public slotPtr: SlotPointer) {}
1142
- }
1143
-
1144
- // LinkedArrayListAppendResult
1145
- class LinkedArrayListAppendResult {
1146
- constructor(public header: LinkedArrayListHeader, public slotPtr: LinkedArrayListSlotPointer) {}
1147
- }
1148
-
1149
- // Helper functions
1150
- function arraysEqual(a: Uint8Array, b: Uint8Array): boolean {
1151
- if (a.length !== b.length) return false;
1152
- for (let i = 0; i < a.length; i++) {
1153
- if (a[i] !== b[i]) return false;
1154
- }
1155
- return true;
1156
- }
1157
-
1158
- function checkLong(n: number): number {
1159
- if (n < 0) {
1160
- throw new ExpectedUnsignedLongException();
1161
- }
1162
- return n;
1163
- }
1164
-
1165
- function bigIntShiftRight(value: Uint8Array, bits: number): bigint {
1166
- let result = 0n;
1167
- for (let i = 0; i < value.length; i++) {
1168
- result = (result << 8n) | BigInt(value[i]);
1169
- }
1170
- return result >> BigInt(bits);
1171
- }
1172
-
1173
- // Database class
1174
- export class Database {
1175
- public core: Core;
1176
- public hasher: Hasher;
1177
- public header!: Header;
1178
- public txStart: number | null = null;
1179
-
1180
- private constructor(core: Core, hasher: Hasher) {
1181
- this.core = core;
1182
- this.hasher = hasher;
1183
- }
1184
-
1185
- static async create(core: Core, hasher: Hasher): Promise<Database> {
1186
- const db = new Database(core, hasher);
1187
-
1188
- await core.seek(0);
1189
- if ((await core.length()) === 0) {
1190
- db.header = new Header(hasher.id, hasher.digestLength, VERSION, Tag.NONE, MAGIC_NUMBER);
1191
- await db.header.write(core);
1192
- await core.flush();
1193
- } else {
1194
- db.header = await Header.read(core);
1195
- db.header.validate();
1196
- if (db.header.hashSize !== hasher.digestLength) {
1197
- throw new InvalidHashSizeException();
1198
- }
1199
- await db.truncate();
1200
- }
1201
-
1202
- return db;
1203
- }
1204
-
1205
- async rootCursor(): Promise<any> {
1206
- // Import WriteCursor dynamically to avoid circular dependency
1207
- const { WriteCursor } = await import('./write-cursor');
1208
-
1209
- if (this.header.tag === Tag.NONE) {
1210
- await this.core.seek(0);
1211
- this.header = await Header.read(this.core);
1212
- }
1213
- return new WriteCursor(
1214
- new SlotPointer(null, new Slot(Header.LENGTH, this.header.tag)),
1215
- this
1216
- );
1217
- }
1218
-
1219
- async freeze(): Promise<void> {
1220
- if (this.txStart !== null) {
1221
- this.txStart = await this.core.length();
1222
- } else {
1223
- throw new ExpectedTxStartException();
1224
- }
1225
- }
1226
-
1227
- async truncate(): Promise<void> {
1228
- if (this.header.tag !== Tag.ARRAY_LIST) return;
1229
-
1230
- const rootCursor = await this.rootCursor();
1231
- const listSize = await rootCursor.count();
1232
-
1233
- if (listSize === 0) return;
1234
-
1235
- await this.core.seek(Header.LENGTH + ArrayListHeader.LENGTH);
1236
- const reader = this.core.reader();
1237
- const headerFileSize = await reader.readLong();
1238
-
1239
- if (headerFileSize === 0) return;
1240
-
1241
- const fileSize = await this.core.length();
1242
-
1243
- if (fileSize === headerFileSize) return;
1244
-
1245
- try {
1246
- await this.core.setLength(headerFileSize);
1247
- } catch (_) {}
1248
- }
1249
-
1250
- checkHashBytes(hash: Uint8Array): Uint8Array {
1251
- if (hash.length !== this.header.hashSize) {
1252
- throw new InvalidHashSizeException();
1253
- }
1254
- return hash;
1255
- }
1256
-
1257
- checkHash(target: HashMapGetTarget): Uint8Array {
1258
- return this.checkHashBytes(target.hash);
1259
- }
1260
-
1261
- async readSlotPointer(
1262
- writeMode: WriteMode,
1263
- path: PathPart[],
1264
- pathI: number,
1265
- slotPtr: SlotPointer
1266
- ): Promise<SlotPointer> {
1267
- if (pathI === path.length) {
1268
- if (writeMode === WriteMode.READ_ONLY && slotPtr.slot.tag === Tag.NONE) {
1269
- throw new KeyNotFoundException();
1270
- }
1271
- return slotPtr;
1272
- }
1273
-
1274
- const part = path[pathI];
1275
- const isTopLevel = slotPtr.slot.value === BigInt(Header.LENGTH);
1276
-
1277
- const isTxStart = isTopLevel && this.header.tag === Tag.ARRAY_LIST && this.txStart === null;
1278
- if (isTxStart) {
1279
- this.txStart = await this.core.length();
1280
- }
1281
-
1282
- try {
1283
- return await part.readSlotPointer(this, isTopLevel, writeMode, path, pathI, slotPtr);
1284
- } finally {
1285
- if (isTxStart) {
1286
- this.txStart = null;
1287
- }
1288
- }
1289
- }
1290
-
1291
- // HashMap methods
1292
- async readMapSlot(
1293
- indexPos: number,
1294
- keyHash: Uint8Array,
1295
- keyOffset: number,
1296
- writeMode: WriteMode,
1297
- isTopLevel: boolean,
1298
- target: HashMapGetTarget
1299
- ): Promise<HashMapGetResult> {
1300
- if (keyOffset > (this.header.hashSize * 8) / BIT_COUNT) {
1301
- throw new KeyOffsetExceededException();
1302
- }
1303
-
1304
- const reader = this.core.reader();
1305
- const writer = this.core.writer();
1306
-
1307
- const i = Number(bigIntShiftRight(keyHash, keyOffset * BIT_COUNT) & MASK);
1308
- const slotPos = indexPos + Slot.LENGTH * i;
1309
- await this.core.seek(slotPos);
1310
- const slotBytes = new Uint8Array(Slot.LENGTH);
1311
- await reader.readFully(slotBytes);
1312
- const slot = Slot.fromBytes(slotBytes);
1313
-
1314
- const ptr = Number(slot.value);
1315
-
1316
- switch (slot.tag) {
1317
- case Tag.NONE: {
1318
- switch (writeMode) {
1319
- case WriteMode.READ_ONLY:
1320
- throw new KeyNotFoundException();
1321
- case WriteMode.READ_WRITE: {
1322
- const hashPos = await this.core.length();
1323
- await this.core.seek(hashPos);
1324
- const keySlotPos = hashPos + this.header.hashSize;
1325
- const valueSlotPos = keySlotPos + Slot.LENGTH;
1326
- const kvPair = new KeyValuePair(new Slot(), new Slot(), keyHash);
1327
- await writer.write(kvPair.toBytes());
1328
-
1329
- const nextSlot = new Slot(hashPos, Tag.KV_PAIR);
1330
- await this.core.seek(slotPos);
1331
- await writer.write(nextSlot.toBytes());
1332
-
1333
- let nextSlotPtr: SlotPointer;
1334
- if (target.kind === 'kv_pair') {
1335
- nextSlotPtr = new SlotPointer(slotPos, nextSlot);
1336
- } else if (target.kind === 'key') {
1337
- nextSlotPtr = new SlotPointer(keySlotPos, kvPair.keySlot);
1338
- } else {
1339
- nextSlotPtr = new SlotPointer(valueSlotPos, kvPair.valueSlot);
1340
- }
1341
- return new HashMapGetResult(nextSlotPtr, true);
1342
- }
1343
- default:
1344
- throw new UnreachableException();
1345
- }
1346
- }
1347
- case Tag.INDEX: {
1348
- let nextPtr = ptr;
1349
- if (writeMode === WriteMode.READ_WRITE && !isTopLevel) {
1350
- if (this.txStart !== null) {
1351
- if (nextPtr < this.txStart) {
1352
- await this.core.seek(ptr);
1353
- const indexBlock = new Uint8Array(INDEX_BLOCK_SIZE);
1354
- await reader.readFully(indexBlock);
1355
-
1356
- nextPtr = await this.core.length();
1357
- await this.core.seek(nextPtr);
1358
- await writer.write(indexBlock);
1359
-
1360
- await this.core.seek(slotPos);
1361
- await writer.write(new Slot(nextPtr, Tag.INDEX).toBytes());
1362
- }
1363
- } else if (this.header.tag === Tag.ARRAY_LIST) {
1364
- throw new ExpectedTxStartException();
1365
- }
1366
- }
1367
- return this.readMapSlot(nextPtr, keyHash, keyOffset + 1, writeMode, isTopLevel, target);
1368
- }
1369
- case Tag.KV_PAIR: {
1370
- await this.core.seek(ptr);
1371
- const kvPairBytes = new Uint8Array(KeyValuePair.length(this.header.hashSize));
1372
- await reader.readFully(kvPairBytes);
1373
- const kvPair = KeyValuePair.fromBytes(kvPairBytes, this.header.hashSize);
1374
-
1375
- if (arraysEqual(kvPair.hash, keyHash)) {
1376
- if (writeMode === WriteMode.READ_WRITE && !isTopLevel) {
1377
- if (this.txStart !== null) {
1378
- if (ptr < this.txStart) {
1379
- const hashPos = await this.core.length();
1380
- await this.core.seek(hashPos);
1381
- const keySlotPos = hashPos + this.header.hashSize;
1382
- const valueSlotPos = keySlotPos + Slot.LENGTH;
1383
- await writer.write(kvPair.toBytes());
1384
-
1385
- const nextSlot = new Slot(hashPos, Tag.KV_PAIR);
1386
- await this.core.seek(slotPos);
1387
- await writer.write(nextSlot.toBytes());
1388
-
1389
- let nextSlotPtr: SlotPointer;
1390
- if (target.kind === 'kv_pair') {
1391
- nextSlotPtr = new SlotPointer(slotPos, nextSlot);
1392
- } else if (target.kind === 'key') {
1393
- nextSlotPtr = new SlotPointer(keySlotPos, kvPair.keySlot);
1394
- } else {
1395
- nextSlotPtr = new SlotPointer(valueSlotPos, kvPair.valueSlot);
1396
- }
1397
- return new HashMapGetResult(nextSlotPtr, false);
1398
- }
1399
- } else if (this.header.tag === Tag.ARRAY_LIST) {
1400
- throw new ExpectedTxStartException();
1401
- }
1402
- }
1403
-
1404
- const keySlotPos = ptr + this.header.hashSize;
1405
- const valueSlotPos = keySlotPos + Slot.LENGTH;
1406
- let nextSlotPtr: SlotPointer;
1407
- if (target.kind === 'kv_pair') {
1408
- nextSlotPtr = new SlotPointer(slotPos, slot);
1409
- } else if (target.kind === 'key') {
1410
- nextSlotPtr = new SlotPointer(keySlotPos, kvPair.keySlot);
1411
- } else {
1412
- nextSlotPtr = new SlotPointer(valueSlotPos, kvPair.valueSlot);
1413
- }
1414
- return new HashMapGetResult(nextSlotPtr, false);
1415
- } else {
1416
- switch (writeMode) {
1417
- case WriteMode.READ_ONLY:
1418
- throw new KeyNotFoundException();
1419
- case WriteMode.READ_WRITE: {
1420
- if (keyOffset + 1 >= (this.header.hashSize * 8) / BIT_COUNT) {
1421
- throw new KeyOffsetExceededException();
1422
- }
1423
- const nextI = Number(bigIntShiftRight(kvPair.hash, (keyOffset + 1) * BIT_COUNT) & MASK);
1424
- const nextIndexPos = await this.core.length();
1425
- await this.core.seek(nextIndexPos);
1426
- await writer.write(new Uint8Array(INDEX_BLOCK_SIZE));
1427
- await this.core.seek(nextIndexPos + Slot.LENGTH * nextI);
1428
- await writer.write(slot.toBytes());
1429
- const res = await this.readMapSlot(nextIndexPos, keyHash, keyOffset + 1, writeMode, isTopLevel, target);
1430
- await this.core.seek(slotPos);
1431
- await writer.write(new Slot(nextIndexPos, Tag.INDEX).toBytes());
1432
- return res;
1433
- }
1434
- default:
1435
- throw new UnreachableException();
1436
- }
1437
- }
1438
- }
1439
- default:
1440
- throw new UnexpectedTagException();
1441
- }
1442
- }
1443
-
1444
- async removeMapSlot(
1445
- indexPos: number,
1446
- keyHash: Uint8Array,
1447
- keyOffset: number,
1448
- isTopLevel: boolean
1449
- ): Promise<Slot> {
1450
- if (keyOffset > (this.header.hashSize * 8) / BIT_COUNT) {
1451
- throw new KeyOffsetExceededException();
1452
- }
1453
-
1454
- const reader = this.core.reader();
1455
- const writer = this.core.writer();
1456
-
1457
- const slotBlock: Slot[] = new Array(SLOT_COUNT);
1458
- await this.core.seek(indexPos);
1459
- const indexBlock = new Uint8Array(INDEX_BLOCK_SIZE);
1460
- await reader.readFully(indexBlock);
1461
- for (let i = 0; i < SLOT_COUNT; i++) {
1462
- const slotBytes = indexBlock.slice(i * Slot.LENGTH, (i + 1) * Slot.LENGTH);
1463
- slotBlock[i] = Slot.fromBytes(slotBytes);
1464
- }
1465
-
1466
- const i = Number(bigIntShiftRight(keyHash, keyOffset * BIT_COUNT) & MASK);
1467
- const slotPos = indexPos + Slot.LENGTH * i;
1468
- const slot = slotBlock[i];
1469
-
1470
- let nextSlot: Slot;
1471
- switch (slot.tag) {
1472
- case Tag.NONE:
1473
- throw new KeyNotFoundException();
1474
- case Tag.INDEX:
1475
- nextSlot = await this.removeMapSlot(Number(slot.value), keyHash, keyOffset + 1, isTopLevel);
1476
- break;
1477
- case Tag.KV_PAIR: {
1478
- await this.core.seek(Number(slot.value));
1479
- const kvPairBytes = new Uint8Array(KeyValuePair.length(this.header.hashSize));
1480
- await reader.readFully(kvPairBytes);
1481
- const kvPair = KeyValuePair.fromBytes(kvPairBytes, this.header.hashSize);
1482
- if (arraysEqual(kvPair.hash, keyHash)) {
1483
- nextSlot = new Slot();
1484
- } else {
1485
- throw new KeyNotFoundException();
1486
- }
1487
- break;
1488
- }
1489
- default:
1490
- throw new UnexpectedTagException();
1491
- }
1492
-
1493
- if (keyOffset === 0) {
1494
- await this.core.seek(slotPos);
1495
- await writer.write(nextSlot.toBytes());
1496
- return new Slot(indexPos, Tag.INDEX);
1497
- }
1498
-
1499
- let slotToReturnMaybe: Slot | null = new Slot();
1500
- slotBlock[i] = nextSlot;
1501
- for (const blockSlot of slotBlock) {
1502
- if (blockSlot.tag === Tag.NONE) continue;
1503
-
1504
- if (slotToReturnMaybe !== null) {
1505
- if (slotToReturnMaybe.tag !== Tag.NONE) {
1506
- slotToReturnMaybe = null;
1507
- break;
1508
- }
1509
- }
1510
-
1511
- slotToReturnMaybe = blockSlot;
1512
- }
1513
-
1514
- if (slotToReturnMaybe !== null) {
1515
- switch (slotToReturnMaybe.tag) {
1516
- case Tag.NONE:
1517
- case Tag.KV_PAIR:
1518
- return slotToReturnMaybe;
1519
- default:
1520
- break;
1521
- }
1522
- }
1523
-
1524
- if (!isTopLevel) {
1525
- if (this.txStart !== null) {
1526
- if (indexPos < this.txStart) {
1527
- const nextIndexPos = await this.core.length();
1528
- await this.core.seek(nextIndexPos);
1529
- await writer.write(indexBlock);
1530
- const nextSlotPos = nextIndexPos + Slot.LENGTH * i;
1531
- await this.core.seek(nextSlotPos);
1532
- await writer.write(nextSlot.toBytes());
1533
- return new Slot(nextIndexPos, Tag.INDEX);
1534
- }
1535
- } else if (this.header.tag === Tag.ARRAY_LIST) {
1536
- throw new ExpectedTxStartException();
1537
- }
1538
- }
1539
-
1540
- await this.core.seek(slotPos);
1541
- await writer.write(nextSlot.toBytes());
1542
- return new Slot(indexPos, Tag.INDEX);
1543
- }
1544
-
1545
- // ArrayList methods
1546
- async readArrayListSlotAppend(
1547
- header: ArrayListHeader,
1548
- writeMode: WriteMode,
1549
- isTopLevel: boolean
1550
- ): Promise<ArrayListAppendResult> {
1551
- const writer = this.core.writer();
1552
-
1553
- let indexPos = header.ptr;
1554
- const key = header.size;
1555
-
1556
- const prevShift = key < SLOT_COUNT ? 0 : Math.floor(Math.log(key - 1) / Math.log(SLOT_COUNT));
1557
- const nextShift = key < SLOT_COUNT ? 0 : Math.floor(Math.log(key) / Math.log(SLOT_COUNT));
1558
-
1559
- if (prevShift !== nextShift) {
1560
- const nextIndexPos = await this.core.length();
1561
- await this.core.seek(nextIndexPos);
1562
- await writer.write(new Uint8Array(INDEX_BLOCK_SIZE));
1563
- await this.core.seek(nextIndexPos);
1564
- await writer.write(new Slot(indexPos, Tag.INDEX).toBytes());
1565
- indexPos = nextIndexPos;
1566
- }
1567
-
1568
- const slotPtr = await this.readArrayListSlot(indexPos, key, nextShift, writeMode, isTopLevel);
1569
- return new ArrayListAppendResult(new ArrayListHeader(indexPos, header.size + 1), slotPtr);
1570
- }
1571
-
1572
- async readArrayListSlot(
1573
- indexPos: number,
1574
- key: number,
1575
- shift: number,
1576
- writeMode: WriteMode,
1577
- isTopLevel: boolean
1578
- ): Promise<SlotPointer> {
1579
- if (shift >= MAX_BRANCH_LENGTH) throw new MaxShiftExceededException();
1580
-
1581
- const reader = this.core.reader();
1582
-
1583
- const i = (key >>> (shift * BIT_COUNT)) & (SLOT_COUNT - 1);
1584
- const slotPos = indexPos + Slot.LENGTH * i;
1585
- await this.core.seek(slotPos);
1586
- const slotBytes = new Uint8Array(Slot.LENGTH);
1587
- await reader.readFully(slotBytes);
1588
- const slot = Slot.fromBytes(slotBytes);
1589
-
1590
- if (shift === 0) {
1591
- return new SlotPointer(slotPos, slot);
1592
- }
1593
-
1594
- const ptr = Number(slot.value);
1595
-
1596
- switch (slot.tag) {
1597
- case Tag.NONE: {
1598
- switch (writeMode) {
1599
- case WriteMode.READ_ONLY:
1600
- throw new KeyNotFoundException();
1601
- case WriteMode.READ_WRITE: {
1602
- const writer = this.core.writer();
1603
- const nextIndexPos = await this.core.length();
1604
- await this.core.seek(nextIndexPos);
1605
- await writer.write(new Uint8Array(INDEX_BLOCK_SIZE));
1606
-
1607
- if (isTopLevel) {
1608
- const fileSize = await this.core.length();
1609
- await this.core.seek(Header.LENGTH + ArrayListHeader.LENGTH);
1610
- await writer.writeLong(fileSize);
1611
- }
1612
-
1613
- await this.core.seek(slotPos);
1614
- await writer.write(new Slot(nextIndexPos, Tag.INDEX).toBytes());
1615
- return this.readArrayListSlot(nextIndexPos, key, shift - 1, writeMode, isTopLevel);
1616
- }
1617
- default:
1618
- throw new UnreachableException();
1619
- }
1620
- }
1621
- case Tag.INDEX: {
1622
- let nextPtr = ptr;
1623
- if (writeMode === WriteMode.READ_WRITE && !isTopLevel) {
1624
- if (this.txStart !== null) {
1625
- if (nextPtr < this.txStart) {
1626
- await this.core.seek(ptr);
1627
- const indexBlock = new Uint8Array(INDEX_BLOCK_SIZE);
1628
- await reader.readFully(indexBlock);
1629
-
1630
- const writer = this.core.writer();
1631
- nextPtr = await this.core.length();
1632
- await this.core.seek(nextPtr);
1633
- await writer.write(indexBlock);
1634
-
1635
- await this.core.seek(slotPos);
1636
- await writer.write(new Slot(nextPtr, Tag.INDEX).toBytes());
1637
- }
1638
- } else if (this.header.tag === Tag.ARRAY_LIST) {
1639
- throw new ExpectedTxStartException();
1640
- }
1641
- }
1642
- return this.readArrayListSlot(nextPtr, key, shift - 1, writeMode, isTopLevel);
1643
- }
1644
- default:
1645
- throw new UnexpectedTagException();
1646
- }
1647
- }
1648
-
1649
- async readArrayListSlice(header: ArrayListHeader, size: number): Promise<ArrayListHeader> {
1650
- const reader = this.core.reader();
1651
-
1652
- if (size > header.size || size < 0) {
1653
- throw new KeyNotFoundException();
1654
- }
1655
-
1656
- const prevShift = header.size < SLOT_COUNT + 1 ? 0 : Math.floor(Math.log(header.size - 1) / Math.log(SLOT_COUNT));
1657
- const nextShift = size < SLOT_COUNT + 1 ? 0 : Math.floor(Math.log(size - 1) / Math.log(SLOT_COUNT));
1658
-
1659
- if (prevShift === nextShift) {
1660
- return new ArrayListHeader(header.ptr, size);
1661
- } else {
1662
- let shift = prevShift;
1663
- let indexPos = header.ptr;
1664
- while (shift > nextShift) {
1665
- await this.core.seek(indexPos);
1666
- const slotBytes = new Uint8Array(Slot.LENGTH);
1667
- await reader.readFully(slotBytes);
1668
- const slot = Slot.fromBytes(slotBytes);
1669
- shift -= 1;
1670
- indexPos = Number(slot.value);
1671
- }
1672
- return new ArrayListHeader(indexPos, size);
1673
- }
1674
- }
1675
-
1676
- // LinkedArrayList methods
1677
- async readLinkedArrayListSlotAppend(
1678
- header: LinkedArrayListHeader,
1679
- writeMode: WriteMode,
1680
- isTopLevel: boolean
1681
- ): Promise<LinkedArrayListAppendResult> {
1682
- const writer = this.core.writer();
1683
-
1684
- let ptr = header.ptr;
1685
- const key = header.size;
1686
- let shift = header.shift;
1687
-
1688
- let slotPtr: LinkedArrayListSlotPointer;
1689
- try {
1690
- slotPtr = await this.readLinkedArrayListSlot(ptr, key, shift, writeMode, isTopLevel);
1691
- } catch (e) {
1692
- if (e instanceof NoAvailableSlotsException) {
1693
- const nextPtr = await this.core.length();
1694
- await this.core.seek(nextPtr);
1695
- await writer.write(new Uint8Array(LINKED_ARRAY_LIST_INDEX_BLOCK_SIZE));
1696
- await this.core.seek(nextPtr);
1697
- await writer.write(new LinkedArrayListSlot(header.size, new Slot(ptr, Tag.INDEX, true)).toBytes());
1698
- ptr = nextPtr;
1699
- shift += 1;
1700
- slotPtr = await this.readLinkedArrayListSlot(ptr, key, shift, writeMode, isTopLevel);
1701
- } else {
1702
- throw e;
1703
- }
1704
- }
1705
-
1706
- const newSlot = new Slot(0, Tag.NONE, true);
1707
- slotPtr = slotPtr.withSlotPointer(slotPtr.slotPtr.withSlot(newSlot));
1708
- if (slotPtr.slotPtr.position === null) throw new CursorNotWriteableException();
1709
- const position = slotPtr.slotPtr.position;
1710
- await this.core.seek(position);
1711
- await writer.write(new LinkedArrayListSlot(0, newSlot).toBytes());
1712
- if (header.size < SLOT_COUNT && shift > 0) {
1713
- throw new MustSetNewSlotsToFullException();
1714
- }
1715
-
1716
- return new LinkedArrayListAppendResult(
1717
- new LinkedArrayListHeader(shift, ptr, header.size + 1),
1718
- slotPtr
1719
- );
1720
- }
1721
-
1722
- private static blockLeafCount(block: LinkedArrayListSlot[], shift: number, i: number): number {
1723
- let n = 0;
1724
- if (shift === 0) {
1725
- for (let blockI = 0; blockI < block.length; blockI++) {
1726
- const blockSlot = block[blockI];
1727
- if (!blockSlot.slot.empty() || blockI === i) {
1728
- n += 1;
1729
- }
1730
- }
1731
- } else {
1732
- for (const blockSlot of block) {
1733
- n += blockSlot.size;
1734
- }
1735
- }
1736
- return n;
1737
- }
1738
-
1739
- private static slotLeafCount(slot: LinkedArrayListSlot, shift: number): number {
1740
- if (shift === 0) {
1741
- if (slot.slot.empty()) {
1742
- return 0;
1743
- } else {
1744
- return 1;
1745
- }
1746
- } else {
1747
- return slot.size;
1748
- }
1749
- }
1750
-
1751
- private static keyAndIndexForLinkedArrayList(
1752
- slotBlock: LinkedArrayListSlot[],
1753
- key: number,
1754
- shift: number
1755
- ): { key: number; index: number } | null {
1756
- let nextKey = key;
1757
- let i = 0;
1758
- const maxLeafCount = shift === 0 ? 1 : Math.pow(SLOT_COUNT, shift);
1759
- while (true) {
1760
- const slotLeafCount = Database.slotLeafCount(slotBlock[i], shift);
1761
- if (nextKey === slotLeafCount) {
1762
- if (slotLeafCount === maxLeafCount || slotBlock[i].slot.full) {
1763
- if (i < SLOT_COUNT - 1) {
1764
- nextKey -= slotLeafCount;
1765
- i += 1;
1766
- } else {
1767
- return null;
1768
- }
1769
- }
1770
- break;
1771
- } else if (nextKey < slotLeafCount) {
1772
- break;
1773
- } else if (i < SLOT_COUNT - 1) {
1774
- nextKey -= slotLeafCount;
1775
- i += 1;
1776
- } else {
1777
- return null;
1778
- }
1779
- }
1780
- return { key: nextKey, index: i };
1781
- }
1782
-
1783
- async readLinkedArrayListSlot(
1784
- indexPos: number,
1785
- key: number,
1786
- shift: number,
1787
- writeMode: WriteMode,
1788
- isTopLevel: boolean
1789
- ): Promise<LinkedArrayListSlotPointer> {
1790
- if (shift >= MAX_BRANCH_LENGTH) throw new MaxShiftExceededException();
1791
-
1792
- const reader = this.core.reader();
1793
- const writer = this.core.writer();
1794
-
1795
- const slotBlock: LinkedArrayListSlot[] = new Array(SLOT_COUNT);
1796
- await this.core.seek(indexPos);
1797
- const indexBlock = new Uint8Array(LINKED_ARRAY_LIST_INDEX_BLOCK_SIZE);
1798
- await reader.readFully(indexBlock);
1799
-
1800
- for (let i = 0; i < SLOT_COUNT; i++) {
1801
- const slotBytes = indexBlock.slice(i * LinkedArrayListSlot.LENGTH, (i + 1) * LinkedArrayListSlot.LENGTH);
1802
- slotBlock[i] = LinkedArrayListSlot.fromBytes(slotBytes);
1803
- }
1804
-
1805
- const keyAndIndex = Database.keyAndIndexForLinkedArrayList(slotBlock, key, shift);
1806
- if (keyAndIndex === null) throw new NoAvailableSlotsException();
1807
- const nextKey = keyAndIndex.key;
1808
- const i = keyAndIndex.index;
1809
- const slot = slotBlock[i];
1810
- const slotPos = indexPos + LinkedArrayListSlot.LENGTH * i;
1811
-
1812
- if (shift === 0) {
1813
- const leafCount = Database.blockLeafCount(slotBlock, shift, i);
1814
- return new LinkedArrayListSlotPointer(new SlotPointer(slotPos, slot.slot), leafCount);
1815
- }
1816
-
1817
- const ptr = Number(slot.slot.value);
1818
-
1819
- switch (slot.slot.tag) {
1820
- case Tag.NONE: {
1821
- switch (writeMode) {
1822
- case WriteMode.READ_ONLY:
1823
- throw new KeyNotFoundException();
1824
- case WriteMode.READ_WRITE: {
1825
- const nextIndexPos = await this.core.length();
1826
- await this.core.seek(nextIndexPos);
1827
- await writer.write(new Uint8Array(LINKED_ARRAY_LIST_INDEX_BLOCK_SIZE));
1828
-
1829
- const nextSlotPtr = await this.readLinkedArrayListSlot(nextIndexPos, nextKey, shift - 1, writeMode, isTopLevel);
1830
- slotBlock[i] = slotBlock[i].withSize(nextSlotPtr.leafCount);
1831
- const leafCount = Database.blockLeafCount(slotBlock, shift, i);
1832
- await this.core.seek(slotPos);
1833
- await writer.write(new LinkedArrayListSlot(nextSlotPtr.leafCount, new Slot(nextIndexPos, Tag.INDEX)).toBytes());
1834
- return new LinkedArrayListSlotPointer(nextSlotPtr.slotPtr, leafCount);
1835
- }
1836
- default:
1837
- throw new UnreachableException();
1838
- }
1839
- }
1840
- case Tag.INDEX: {
1841
- let nextPtr = ptr;
1842
- if (writeMode === WriteMode.READ_WRITE && !isTopLevel) {
1843
- if (this.txStart !== null) {
1844
- if (nextPtr < this.txStart) {
1845
- await this.core.seek(ptr);
1846
- const indexBlockCopy = new Uint8Array(LINKED_ARRAY_LIST_INDEX_BLOCK_SIZE);
1847
- await reader.readFully(indexBlockCopy);
1848
-
1849
- nextPtr = await this.core.length();
1850
- await this.core.seek(nextPtr);
1851
- await writer.write(indexBlockCopy);
1852
- }
1853
- } else if (this.header.tag === Tag.ARRAY_LIST) {
1854
- throw new ExpectedTxStartException();
1855
- }
1856
- }
1857
-
1858
- const nextSlotPtr = await this.readLinkedArrayListSlot(nextPtr, nextKey, shift - 1, writeMode, isTopLevel);
1859
-
1860
- slotBlock[i] = slotBlock[i].withSize(nextSlotPtr.leafCount);
1861
- const leafCount = Database.blockLeafCount(slotBlock, shift, i);
1862
-
1863
- if (writeMode === WriteMode.READ_WRITE && !isTopLevel) {
1864
- await this.core.seek(slotPos);
1865
- await writer.write(new LinkedArrayListSlot(nextSlotPtr.leafCount, new Slot(nextPtr, Tag.INDEX)).toBytes());
1866
- }
1867
-
1868
- return new LinkedArrayListSlotPointer(nextSlotPtr.slotPtr, leafCount);
1869
- }
1870
- default:
1871
- throw new UnexpectedTagException();
1872
- }
1873
- }
1874
-
1875
- async readLinkedArrayListBlocks(
1876
- indexPos: number,
1877
- key: number,
1878
- shift: number,
1879
- blocks: LinkedArrayListBlockInfo[]
1880
- ): Promise<void> {
1881
- const reader = this.core.reader();
1882
-
1883
- const slotBlock: LinkedArrayListSlot[] = new Array(SLOT_COUNT);
1884
- await this.core.seek(indexPos);
1885
- const indexBlock = new Uint8Array(LINKED_ARRAY_LIST_INDEX_BLOCK_SIZE);
1886
- await reader.readFully(indexBlock);
1887
-
1888
- for (let i = 0; i < SLOT_COUNT; i++) {
1889
- const slotBytes = indexBlock.slice(i * LinkedArrayListSlot.LENGTH, (i + 1) * LinkedArrayListSlot.LENGTH);
1890
- slotBlock[i] = LinkedArrayListSlot.fromBytes(slotBytes);
1891
- }
1892
-
1893
- const keyAndIndex = Database.keyAndIndexForLinkedArrayList(slotBlock, key, shift);
1894
- if (keyAndIndex === null) throw new NoAvailableSlotsException();
1895
- const nextKey = keyAndIndex.key;
1896
- const i = keyAndIndex.index;
1897
- const leafCount = Database.blockLeafCount(slotBlock, shift, i);
1898
-
1899
- blocks.push(new LinkedArrayListBlockInfo(slotBlock, i, new LinkedArrayListSlot(leafCount, new Slot(indexPos, Tag.INDEX))));
1900
-
1901
- if (shift === 0) {
1902
- return;
1903
- }
1904
-
1905
- const slot = slotBlock[i];
1906
- switch (slot.slot.tag) {
1907
- case Tag.NONE:
1908
- throw new EmptySlotException();
1909
- case Tag.INDEX:
1910
- await this.readLinkedArrayListBlocks(Number(slot.slot.value), nextKey, shift - 1, blocks);
1911
- break;
1912
- default:
1913
- throw new UnexpectedTagException();
1914
- }
1915
- }
1916
-
1917
- private populateArray(arr: LinkedArrayListSlot[]): void {
1918
- for (let i = 0; i < arr.length; i++) {
1919
- arr[i] = new LinkedArrayListSlot(0, new Slot());
1920
- }
1921
- }
1922
-
1923
- async readLinkedArrayListSlice(
1924
- header: LinkedArrayListHeader,
1925
- offset: number,
1926
- size: number
1927
- ): Promise<LinkedArrayListHeader> {
1928
- const writer = this.core.writer();
1929
-
1930
- if (offset + size > header.size) {
1931
- throw new KeyNotFoundException();
1932
- }
1933
-
1934
- const leftBlocks: LinkedArrayListBlockInfo[] = [];
1935
- await this.readLinkedArrayListBlocks(header.ptr, offset, header.shift, leftBlocks);
1936
-
1937
- const rightBlocks: LinkedArrayListBlockInfo[] = [];
1938
- const rightKey = offset + size === 0 ? 0 : offset + size - 1;
1939
- await this.readLinkedArrayListBlocks(header.ptr, rightKey, header.shift, rightBlocks);
1940
-
1941
- const blockCount = leftBlocks.length;
1942
- let nextSlots: (LinkedArrayListSlot | null)[] = [null, null];
1943
- let nextShift = 0;
1944
-
1945
- for (let i = 0; i < blockCount; i++) {
1946
- const isLeafNode = nextSlots[0] === null;
1947
-
1948
- const leftBlock = leftBlocks[blockCount - i - 1];
1949
- const rightBlock = rightBlocks[blockCount - i - 1];
1950
- const origBlockInfos = [leftBlock, rightBlock];
1951
- let nextBlocks: (LinkedArrayListSlot[] | null)[] = [null, null];
1952
-
1953
- if (leftBlock.parentSlot.slot.value === rightBlock.parentSlot.slot.value) {
1954
- let slotI = 0;
1955
- const newRootBlock: LinkedArrayListSlot[] = new Array(SLOT_COUNT);
1956
- this.populateArray(newRootBlock);
1957
-
1958
- if (size > 0) {
1959
- if (nextSlots[0] !== null) {
1960
- newRootBlock[slotI] = nextSlots[0];
1961
- } else {
1962
- newRootBlock[slotI] = leftBlock.block[leftBlock.i];
1963
- }
1964
- slotI += 1;
1965
- }
1966
- if (size > 1) {
1967
- for (let j = leftBlock.i + 1; j < rightBlock.i; j++) {
1968
- const middleSlot = leftBlock.block[j];
1969
- newRootBlock[slotI] = middleSlot;
1970
- slotI += 1;
1971
- }
1972
-
1973
- if (nextSlots[1] !== null) {
1974
- newRootBlock[slotI] = nextSlots[1];
1975
- } else {
1976
- newRootBlock[slotI] = leftBlock.block[rightBlock.i];
1977
- }
1978
- }
1979
- nextBlocks[0] = newRootBlock;
1980
- } else {
1981
- let slotI = 0;
1982
- const newLeftBlock: LinkedArrayListSlot[] = new Array(SLOT_COUNT);
1983
- this.populateArray(newLeftBlock);
1984
-
1985
- if (nextSlots[0] !== null) {
1986
- newLeftBlock[slotI] = nextSlots[0];
1987
- } else {
1988
- newLeftBlock[slotI] = leftBlock.block[leftBlock.i];
1989
- }
1990
- slotI += 1;
1991
- for (let j = leftBlock.i + 1; j < leftBlock.block.length; j++) {
1992
- const nextSlot = leftBlock.block[j];
1993
- newLeftBlock[slotI] = nextSlot;
1994
- slotI += 1;
1995
- }
1996
- nextBlocks[0] = newLeftBlock;
1997
-
1998
- slotI = 0;
1999
- const newRightBlock: LinkedArrayListSlot[] = new Array(SLOT_COUNT);
2000
- this.populateArray(newRightBlock);
2001
- for (let j = 0; j < rightBlock.i; j++) {
2002
- const firstSlot = rightBlock.block[j];
2003
- newRightBlock[slotI] = firstSlot;
2004
- slotI += 1;
2005
- }
2006
- if (nextSlots[1] !== null) {
2007
- newRightBlock[slotI] = nextSlots[1];
2008
- } else {
2009
- newRightBlock[slotI] = rightBlock.block[rightBlock.i];
2010
- }
2011
- nextBlocks[1] = newRightBlock;
2012
-
2013
- nextShift += 1;
2014
- }
2015
-
2016
- nextSlots = [null, null];
2017
-
2018
- await this.core.seek(await this.core.length());
2019
- for (let j = 0; j < 2; j++) {
2020
- const blockMaybe = nextBlocks[j];
2021
- const origBlockInfo = origBlockInfos[j];
2022
-
2023
- if (blockMaybe !== null) {
2024
- let eql = true;
2025
- for (let k = 0; k < blockMaybe.length; k++) {
2026
- const blockSlot = blockMaybe[k];
2027
- const origSlot = origBlockInfo.block[k];
2028
- if (!blockSlot.slot.equals(origSlot.slot)) {
2029
- eql = false;
2030
- break;
2031
- }
2032
- }
2033
-
2034
- if (eql) {
2035
- nextSlots[j] = origBlockInfo.parentSlot;
2036
- } else {
2037
- const nextPtr = await this.core.position();
2038
- let leafCount = 0;
2039
- for (let k = 0; k < blockMaybe.length; k++) {
2040
- const blockSlot = blockMaybe[k];
2041
- await writer.write(blockSlot.toBytes());
2042
- if (isLeafNode) {
2043
- if (!blockSlot.slot.empty()) {
2044
- leafCount += 1;
2045
- }
2046
- } else {
2047
- leafCount += blockSlot.size;
2048
- }
2049
- }
2050
- nextSlots[j] = new LinkedArrayListSlot(
2051
- leafCount,
2052
- j === 0 ? new Slot(nextPtr, Tag.INDEX, true) : new Slot(nextPtr, Tag.INDEX)
2053
- );
2054
- }
2055
- }
2056
- }
2057
-
2058
- if (nextSlots[0] !== null && nextSlots[1] === null) {
2059
- break;
2060
- }
2061
- }
2062
-
2063
- const rootSlot = nextSlots[0];
2064
- if (rootSlot === null) throw new ExpectedRootNodeException();
2065
-
2066
- return new LinkedArrayListHeader(nextShift, Number(rootSlot.slot.value), size);
2067
- }
2068
-
2069
- async readLinkedArrayListConcat(
2070
- headerA: LinkedArrayListHeader,
2071
- headerB: LinkedArrayListHeader
2072
- ): Promise<LinkedArrayListHeader> {
2073
- const writer = this.core.writer();
2074
-
2075
- const blocksA: LinkedArrayListBlockInfo[] = [];
2076
- const keyA = headerA.size === 0 ? 0 : headerA.size - 1;
2077
- await this.readLinkedArrayListBlocks(headerA.ptr, keyA, headerA.shift, blocksA);
2078
-
2079
- const blocksB: LinkedArrayListBlockInfo[] = [];
2080
- await this.readLinkedArrayListBlocks(headerB.ptr, 0, headerB.shift, blocksB);
2081
-
2082
- let nextSlots: (LinkedArrayListSlot | null)[] = [null, null];
2083
- let nextShift = 0;
2084
-
2085
- for (let i = 0; i < Math.max(blocksA.length, blocksB.length); i++) {
2086
- const blockInfos: (LinkedArrayListBlockInfo | null)[] = [
2087
- i < blocksA.length ? blocksA[blocksA.length - 1 - i] : null,
2088
- i < blocksB.length ? blocksB[blocksB.length - 1 - i] : null,
2089
- ];
2090
- let nextBlocks: (LinkedArrayListSlot[] | null)[] = [null, null];
2091
- const isLeafNode = nextSlots[0] === null;
2092
-
2093
- if (!isLeafNode) {
2094
- nextShift += 1;
2095
- }
2096
-
2097
- for (let j = 0; j < 2; j++) {
2098
- const blockInfoMaybe = blockInfos[j];
2099
- if (blockInfoMaybe !== null) {
2100
- const block: LinkedArrayListSlot[] = new Array(SLOT_COUNT);
2101
- this.populateArray(block);
2102
- let targetI = 0;
2103
- for (let sourceI = 0; sourceI < blockInfoMaybe.block.length; sourceI++) {
2104
- const blockSlot = blockInfoMaybe.block[sourceI];
2105
- if (!isLeafNode && blockInfoMaybe.i === sourceI) {
2106
- continue;
2107
- } else if (blockSlot.slot.empty()) {
2108
- break;
2109
- }
2110
- block[targetI] = blockSlot;
2111
- targetI += 1;
2112
- }
2113
-
2114
- if (targetI === 0) {
2115
- continue;
2116
- }
2117
-
2118
- nextBlocks[j] = block;
2119
- }
2120
- }
2121
-
2122
- const slotsToWrite: LinkedArrayListSlot[] = new Array(SLOT_COUNT * 2);
2123
- this.populateArray(slotsToWrite);
2124
- let slotI = 0;
2125
-
2126
- if (nextBlocks[0] !== null) {
2127
- for (const blockSlot of nextBlocks[0]) {
2128
- if (blockSlot.slot.empty()) {
2129
- break;
2130
- }
2131
- slotsToWrite[slotI] = blockSlot;
2132
- slotI += 1;
2133
- }
2134
- }
2135
-
2136
- for (const slotMaybe of nextSlots) {
2137
- if (slotMaybe !== null) {
2138
- slotsToWrite[slotI] = slotMaybe;
2139
- slotI += 1;
2140
- }
2141
- }
2142
-
2143
- if (nextBlocks[1] !== null) {
2144
- for (const blockSlot of nextBlocks[1]) {
2145
- if (blockSlot.slot.empty()) {
2146
- break;
2147
- }
2148
- slotsToWrite[slotI] = blockSlot;
2149
- slotI += 1;
2150
- }
2151
- }
2152
-
2153
- nextSlots = [null, null];
2154
-
2155
- const blocks: LinkedArrayListSlot[][] = [new Array(SLOT_COUNT), new Array(SLOT_COUNT)];
2156
- this.populateArray(blocks[0]);
2157
- this.populateArray(blocks[1]);
2158
-
2159
- if (slotI > SLOT_COUNT) {
2160
- if (headerA.size < headerB.size) {
2161
- for (let j = 0; j < slotI - SLOT_COUNT; j++) {
2162
- blocks[0][j] = slotsToWrite[j];
2163
- }
2164
- for (let j = 0; j < SLOT_COUNT; j++) {
2165
- blocks[1][j] = slotsToWrite[j + (slotI - SLOT_COUNT)];
2166
- }
2167
- } else {
2168
- for (let j = 0; j < SLOT_COUNT; j++) {
2169
- blocks[0][j] = slotsToWrite[j];
2170
- }
2171
- for (let j = 0; j < slotI - SLOT_COUNT; j++) {
2172
- blocks[1][j] = slotsToWrite[j + SLOT_COUNT];
2173
- }
2174
- }
2175
- } else {
2176
- for (let j = 0; j < slotI; j++) {
2177
- blocks[0][j] = slotsToWrite[j];
2178
- }
2179
- }
2180
-
2181
- await this.core.seek(await this.core.length());
2182
- for (let blockI = 0; blockI < blocks.length; blockI++) {
2183
- const block = blocks[blockI];
2184
-
2185
- if (block[0].slot.empty()) {
2186
- break;
2187
- }
2188
-
2189
- const nextPtr = await this.core.position();
2190
- let leafCount = 0;
2191
- for (const blockSlot of block) {
2192
- await writer.write(blockSlot.toBytes());
2193
- if (isLeafNode) {
2194
- if (!blockSlot.slot.empty()) {
2195
- leafCount += 1;
2196
- }
2197
- } else {
2198
- leafCount += blockSlot.size;
2199
- }
2200
- }
2201
-
2202
- nextSlots[blockI] = new LinkedArrayListSlot(leafCount, new Slot(nextPtr, Tag.INDEX, true));
2203
- }
2204
- }
2205
-
2206
- let rootPtr: number;
2207
- if (nextSlots[0] !== null) {
2208
- if (nextSlots[1] !== null) {
2209
- const block: LinkedArrayListSlot[] = new Array(SLOT_COUNT);
2210
- this.populateArray(block);
2211
- block[0] = nextSlots[0];
2212
- block[1] = nextSlots[1];
2213
-
2214
- const newPtr = await this.core.length();
2215
- for (const blockSlot of block) {
2216
- await writer.write(blockSlot.toBytes());
2217
- }
2218
-
2219
- if (nextShift === MAX_BRANCH_LENGTH) throw new MaxShiftExceededException();
2220
- nextShift += 1;
2221
-
2222
- rootPtr = newPtr;
2223
- } else {
2224
- rootPtr = Number(nextSlots[0].slot.value);
2225
- }
2226
- } else {
2227
- rootPtr = headerA.ptr;
2228
- }
2229
-
2230
- return new LinkedArrayListHeader(nextShift, rootPtr, headerA.size + headerB.size);
2231
- }
2232
- }