xitdb 0.1.0 → 0.3.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/LICENSE +21 -0
- 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 +14 -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/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
|
-
}
|