xitdb 0.13.0 → 0.14.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/README.md +16 -15
- package/dist/core-buffered-file.d.ts +2 -2
- package/dist/core-buffered-file.js +182 -0
- package/dist/core-file.d.ts +1 -1
- package/dist/core-file.js +105 -0
- package/dist/core-memory.d.ts +1 -1
- package/dist/core-memory.js +152 -0
- package/dist/core.js +1 -0
- package/dist/database.d.ts +174 -49
- package/dist/database.js +3047 -0
- package/dist/exceptions.d.ts +4 -0
- package/dist/exceptions.js +62 -0
- package/dist/hasher.js +49 -0
- package/dist/index.d.ts +30 -26
- package/dist/index.js +35 -3923
- package/dist/read-array-list.d.ts +3 -3
- package/dist/read-array-list.js +35 -0
- package/dist/read-counted-hash-map.d.ts +2 -2
- package/dist/read-counted-hash-map.js +19 -0
- package/dist/read-counted-hash-set.d.ts +2 -2
- package/dist/read-counted-hash-set.js +19 -0
- package/dist/read-cursor.d.ts +11 -5
- package/dist/read-cursor.js +577 -0
- package/dist/read-hash-map.d.ts +4 -4
- package/dist/read-hash-map.js +65 -0
- package/dist/read-hash-set.d.ts +4 -4
- package/dist/read-hash-set.js +47 -0
- package/dist/read-linked-array-list.d.ts +3 -3
- package/dist/read-linked-array-list.js +35 -0
- package/dist/read-sorted-map.d.ts +21 -0
- package/dist/read-sorted-map.js +73 -0
- package/dist/read-sorted-set.d.ts +19 -0
- package/dist/read-sorted-set.js +65 -0
- package/dist/slot-pointer.d.ts +1 -1
- package/dist/slot-pointer.js +11 -0
- package/dist/slot.d.ts +2 -2
- package/dist/slot.js +41 -0
- package/dist/slotted.d.ts +1 -1
- package/dist/slotted.js +1 -0
- package/dist/tag.d.ts +3 -1
- package/dist/tag.js +25 -0
- package/dist/write-array-list.d.ts +4 -4
- package/dist/write-array-list.js +42 -0
- package/dist/write-counted-hash-map.d.ts +2 -2
- package/dist/write-counted-hash-map.js +18 -0
- package/dist/write-counted-hash-set.d.ts +2 -2
- package/dist/write-counted-hash-set.js +18 -0
- package/dist/write-cursor.d.ts +5 -5
- package/dist/write-cursor.js +124 -0
- package/dist/write-hash-map.d.ts +4 -4
- package/dist/write-hash-map.js +90 -0
- package/dist/write-hash-set.d.ts +4 -4
- package/dist/write-hash-set.js +59 -0
- package/dist/write-linked-array-list.d.ts +4 -4
- package/dist/write-linked-array-list.js +52 -0
- package/dist/write-sorted-map.d.ts +12 -0
- package/dist/write-sorted-map.js +37 -0
- package/dist/write-sorted-set.d.ts +10 -0
- package/dist/write-sorted-set.js +29 -0
- package/dist/writeable-data.js +68 -0
- package/package.json +6 -6
package/dist/database.js
ADDED
|
@@ -0,0 +1,3047 @@
|
|
|
1
|
+
import { Hasher } from './hasher.js';
|
|
2
|
+
import { tagValueOf } from './tag.js';
|
|
3
|
+
import { Slot } from './slot.js';
|
|
4
|
+
import { SlotPointer } from './slot-pointer.js';
|
|
5
|
+
import { InvalidDatabaseException, InvalidVersionException, InvalidHashSizeException, KeyNotFoundException, WriteNotAllowedException, UnexpectedTagException, CursorNotWriteableException, ExpectedTxStartException, KeyOffsetExceededException, PathPartMustBeAtEndException, InvalidTopLevelTypeException, ExpectedUnsignedLongException, UnreachableException, MaxShiftExceededException, InvalidBTreeNodeException, InvalidBTreeNodeKindException, } from './exceptions.js';
|
|
6
|
+
import { Bytes, Float, Int, Uint } from './writeable-data.js';
|
|
7
|
+
import { WriteCursor } from './write-cursor.js';
|
|
8
|
+
export const VERSION = 0;
|
|
9
|
+
export const MAGIC_NUMBER = new Uint8Array([0x78, 0x69, 0x74]); // 'xit'
|
|
10
|
+
export const BIT_COUNT = 4;
|
|
11
|
+
export const SLOT_COUNT = 1 << BIT_COUNT;
|
|
12
|
+
export const MASK = BigInt(SLOT_COUNT - 1);
|
|
13
|
+
export const INDEX_BLOCK_SIZE = Slot.LENGTH * SLOT_COUNT;
|
|
14
|
+
export const MAX_BRANCH_LENGTH = 16;
|
|
15
|
+
// b-tree (backs LinkedArrayList): nodes hold up to BTREE_SLOT_COUNT entries
|
|
16
|
+
export const BTREE_SLOT_COUNT = SLOT_COUNT; // max entries per leaf / children per branch
|
|
17
|
+
export const BTREE_SPLIT_COUNT = Math.floor((BTREE_SLOT_COUNT + 1) / 2); // left side of a split
|
|
18
|
+
// on-disk node block: [kind: u8][num: u8] followed by, for a leaf, BTREE_SLOT_COUNT
|
|
19
|
+
// value slots; for a branch, BTREE_SLOT_COUNT child slots then BTREE_SLOT_COUNT u64
|
|
20
|
+
// subtree counts
|
|
21
|
+
export const BTREE_NODE_HEADER_SIZE = 2;
|
|
22
|
+
export const BTREE_LEAF_BLOCK_SIZE = BTREE_NODE_HEADER_SIZE + Slot.LENGTH * BTREE_SLOT_COUNT;
|
|
23
|
+
export const BTREE_BRANCH_BLOCK_SIZE = BTREE_NODE_HEADER_SIZE + (Slot.LENGTH + 8) * BTREE_SLOT_COUNT;
|
|
24
|
+
// sorted_map / sorted_set node block: [kind: u8][num: u8] then, for a leaf,
|
|
25
|
+
// BTREE_SLOT_COUNT .kv_pair slots; for a branch, BTREE_SLOT_COUNT child slots, then
|
|
26
|
+
// BTREE_SLOT_COUNT separator slots, then BTREE_SLOT_COUNT u64 counts
|
|
27
|
+
export const SORTED_LEAF_BLOCK_SIZE = BTREE_NODE_HEADER_SIZE + Slot.LENGTH * BTREE_SLOT_COUNT;
|
|
28
|
+
export const SORTED_BRANCH_BLOCK_SIZE = BTREE_NODE_HEADER_SIZE + (Slot.LENGTH * 2 + 8) * BTREE_SLOT_COUNT;
|
|
29
|
+
export var WriteMode;
|
|
30
|
+
(function (WriteMode) {
|
|
31
|
+
WriteMode[WriteMode["READ_ONLY"] = 0] = "READ_ONLY";
|
|
32
|
+
WriteMode[WriteMode["READ_WRITE"] = 1] = "READ_WRITE";
|
|
33
|
+
})(WriteMode || (WriteMode = {}));
|
|
34
|
+
// Header
|
|
35
|
+
export class Header {
|
|
36
|
+
hashId;
|
|
37
|
+
hashSize;
|
|
38
|
+
version;
|
|
39
|
+
tag;
|
|
40
|
+
magicNumber;
|
|
41
|
+
static LENGTH = 12;
|
|
42
|
+
constructor(hashId, hashSize, version, tag, magicNumber) {
|
|
43
|
+
this.hashId = hashId;
|
|
44
|
+
this.hashSize = hashSize;
|
|
45
|
+
this.version = version;
|
|
46
|
+
this.tag = tag;
|
|
47
|
+
this.magicNumber = magicNumber;
|
|
48
|
+
}
|
|
49
|
+
toBytes() {
|
|
50
|
+
const buffer = new ArrayBuffer(Header.LENGTH);
|
|
51
|
+
const view = new DataView(buffer);
|
|
52
|
+
const arr = new Uint8Array(buffer);
|
|
53
|
+
arr.set(this.magicNumber, 0);
|
|
54
|
+
view.setUint8(3, this.tag);
|
|
55
|
+
view.setInt16(4, this.version, false);
|
|
56
|
+
view.setInt16(6, this.hashSize, false);
|
|
57
|
+
view.setInt32(8, this.hashId, false);
|
|
58
|
+
return arr;
|
|
59
|
+
}
|
|
60
|
+
static read(core) {
|
|
61
|
+
const reader = core.reader();
|
|
62
|
+
const magicNumber = new Uint8Array(3);
|
|
63
|
+
reader.readFully(magicNumber);
|
|
64
|
+
const tagByte = reader.readByte();
|
|
65
|
+
const tag = tagValueOf(tagByte & 0b0111_1111);
|
|
66
|
+
const version = reader.readShort();
|
|
67
|
+
const hashSize = reader.readShort();
|
|
68
|
+
const hashId = reader.readInt();
|
|
69
|
+
return new Header(hashId, hashSize, version, tag, magicNumber);
|
|
70
|
+
}
|
|
71
|
+
write(core) {
|
|
72
|
+
const writer = core.writer();
|
|
73
|
+
writer.write(this.toBytes());
|
|
74
|
+
}
|
|
75
|
+
validate() {
|
|
76
|
+
if (!arraysEqual(this.magicNumber, MAGIC_NUMBER)) {
|
|
77
|
+
throw new InvalidDatabaseException();
|
|
78
|
+
}
|
|
79
|
+
if (this.version > VERSION) {
|
|
80
|
+
throw new InvalidVersionException();
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
withTag(tag) {
|
|
84
|
+
return new Header(this.hashId, this.hashSize, this.version, tag, this.magicNumber);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
// ArrayListHeader
|
|
88
|
+
export class ArrayListHeader {
|
|
89
|
+
ptr;
|
|
90
|
+
size;
|
|
91
|
+
static LENGTH = 16;
|
|
92
|
+
constructor(ptr, size) {
|
|
93
|
+
this.ptr = ptr;
|
|
94
|
+
this.size = size;
|
|
95
|
+
}
|
|
96
|
+
toBytes() {
|
|
97
|
+
const buffer = new ArrayBuffer(ArrayListHeader.LENGTH);
|
|
98
|
+
const view = new DataView(buffer);
|
|
99
|
+
view.setBigInt64(0, BigInt(this.size), false);
|
|
100
|
+
view.setBigInt64(8, BigInt(this.ptr), false);
|
|
101
|
+
return new Uint8Array(buffer);
|
|
102
|
+
}
|
|
103
|
+
static fromBytes(bytes) {
|
|
104
|
+
const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
|
|
105
|
+
const size = Number(view.getBigInt64(0, false));
|
|
106
|
+
checkLong(size);
|
|
107
|
+
const ptr = Number(view.getBigInt64(8, false));
|
|
108
|
+
checkLong(ptr);
|
|
109
|
+
return new ArrayListHeader(ptr, size);
|
|
110
|
+
}
|
|
111
|
+
withPtr(ptr) {
|
|
112
|
+
return new ArrayListHeader(ptr, this.size);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
// TopLevelArrayListHeader
|
|
116
|
+
export class TopLevelArrayListHeader {
|
|
117
|
+
fileSize;
|
|
118
|
+
parent;
|
|
119
|
+
static LENGTH = 8 + ArrayListHeader.LENGTH;
|
|
120
|
+
constructor(fileSize, parent) {
|
|
121
|
+
this.fileSize = fileSize;
|
|
122
|
+
this.parent = parent;
|
|
123
|
+
}
|
|
124
|
+
toBytes() {
|
|
125
|
+
const buffer = new ArrayBuffer(TopLevelArrayListHeader.LENGTH);
|
|
126
|
+
const view = new DataView(buffer);
|
|
127
|
+
const arr = new Uint8Array(buffer);
|
|
128
|
+
arr.set(this.parent.toBytes(), 0);
|
|
129
|
+
view.setBigInt64(ArrayListHeader.LENGTH, BigInt(this.fileSize), false);
|
|
130
|
+
return arr;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
// BTreeHeader: a root pointer plus the element count (backs LinkedArrayList)
|
|
134
|
+
export class BTreeHeader {
|
|
135
|
+
rootPtr;
|
|
136
|
+
size;
|
|
137
|
+
static LENGTH = 16;
|
|
138
|
+
constructor(rootPtr, size) {
|
|
139
|
+
this.rootPtr = rootPtr;
|
|
140
|
+
this.size = size;
|
|
141
|
+
}
|
|
142
|
+
toBytes() {
|
|
143
|
+
const buffer = new ArrayBuffer(BTreeHeader.LENGTH);
|
|
144
|
+
const view = new DataView(buffer);
|
|
145
|
+
view.setBigInt64(0, BigInt(this.size), false);
|
|
146
|
+
view.setBigInt64(8, BigInt(this.rootPtr), false);
|
|
147
|
+
return new Uint8Array(buffer);
|
|
148
|
+
}
|
|
149
|
+
static fromBytes(bytes) {
|
|
150
|
+
const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
|
|
151
|
+
const size = Number(view.getBigInt64(0, false));
|
|
152
|
+
checkLong(size);
|
|
153
|
+
const rootPtr = Number(view.getBigInt64(8, false));
|
|
154
|
+
checkLong(rootPtr);
|
|
155
|
+
return new BTreeHeader(rootPtr, size);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
// KeyValuePair
|
|
159
|
+
export class KeyValuePair {
|
|
160
|
+
valueSlot;
|
|
161
|
+
keySlot;
|
|
162
|
+
hash;
|
|
163
|
+
constructor(valueSlot, keySlot, hash) {
|
|
164
|
+
this.valueSlot = valueSlot;
|
|
165
|
+
this.keySlot = keySlot;
|
|
166
|
+
this.hash = hash;
|
|
167
|
+
}
|
|
168
|
+
static length(hashSize) {
|
|
169
|
+
return hashSize + Slot.LENGTH * 2;
|
|
170
|
+
}
|
|
171
|
+
toBytes() {
|
|
172
|
+
const buffer = new Uint8Array(KeyValuePair.length(this.hash.length));
|
|
173
|
+
buffer.set(this.hash, 0);
|
|
174
|
+
buffer.set(this.keySlot.toBytes(), this.hash.length);
|
|
175
|
+
buffer.set(this.valueSlot.toBytes(), this.hash.length + Slot.LENGTH);
|
|
176
|
+
return buffer;
|
|
177
|
+
}
|
|
178
|
+
static fromBytes(bytes, hashSize) {
|
|
179
|
+
const hash = bytes.slice(0, hashSize);
|
|
180
|
+
const keySlotBytes = bytes.slice(hashSize, hashSize + Slot.LENGTH);
|
|
181
|
+
const keySlot = Slot.fromBytes(keySlotBytes);
|
|
182
|
+
const valueSlotBytes = bytes.slice(hashSize + Slot.LENGTH, hashSize + Slot.LENGTH * 2);
|
|
183
|
+
const valueSlot = Slot.fromBytes(valueSlotBytes);
|
|
184
|
+
return new KeyValuePair(valueSlot, keySlot, hash);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
// sorted-by-position B-tree node. a leaf holds value slots; a branch holds child
|
|
188
|
+
// slots (.index) plus a per-child u64 subtree count.
|
|
189
|
+
export var BTreeNodeKind;
|
|
190
|
+
(function (BTreeNodeKind) {
|
|
191
|
+
BTreeNodeKind[BTreeNodeKind["LEAF"] = 0] = "LEAF";
|
|
192
|
+
BTreeNodeKind[BTreeNodeKind["BRANCH"] = 1] = "BRANCH";
|
|
193
|
+
})(BTreeNodeKind || (BTreeNodeKind = {}));
|
|
194
|
+
export class BTreeNode {
|
|
195
|
+
kind;
|
|
196
|
+
num;
|
|
197
|
+
values = new Array(BTREE_SLOT_COUNT); // leaf
|
|
198
|
+
children = new Array(BTREE_SLOT_COUNT); // branch
|
|
199
|
+
counts = new Array(BTREE_SLOT_COUNT).fill(0); // branch
|
|
200
|
+
constructor(kind, num) {
|
|
201
|
+
this.kind = kind;
|
|
202
|
+
this.num = num;
|
|
203
|
+
for (let i = 0; i < BTREE_SLOT_COUNT; i++) {
|
|
204
|
+
this.values[i] = new Slot();
|
|
205
|
+
this.children[i] = new Slot();
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
subtreeCount() {
|
|
209
|
+
if (this.kind === BTreeNodeKind.LEAF)
|
|
210
|
+
return this.num;
|
|
211
|
+
let total = 0;
|
|
212
|
+
for (let i = 0; i < this.num; i++)
|
|
213
|
+
total += this.counts[i];
|
|
214
|
+
return total;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
// a node pointer plus the element count of its subtree (the right sibling of a split)
|
|
218
|
+
export class BTreeNodeRef {
|
|
219
|
+
nodePtr;
|
|
220
|
+
count;
|
|
221
|
+
constructor(nodePtr, count) {
|
|
222
|
+
this.nodePtr = nodePtr;
|
|
223
|
+
this.count = count;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
export class BTreeInsertResult {
|
|
227
|
+
nodePtr;
|
|
228
|
+
count;
|
|
229
|
+
valuePosition;
|
|
230
|
+
split;
|
|
231
|
+
constructor(nodePtr, count, valuePosition, split) {
|
|
232
|
+
this.nodePtr = nodePtr;
|
|
233
|
+
this.count = count;
|
|
234
|
+
this.valuePosition = valuePosition;
|
|
235
|
+
this.split = split;
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
export class BTreeWriteSlot {
|
|
239
|
+
nodePtr;
|
|
240
|
+
valuePosition;
|
|
241
|
+
slot;
|
|
242
|
+
constructor(nodePtr, valuePosition, slot) {
|
|
243
|
+
this.nodePtr = nodePtr;
|
|
244
|
+
this.valuePosition = valuePosition;
|
|
245
|
+
this.slot = slot;
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
export class BTreeJoinResult {
|
|
249
|
+
nodePtr;
|
|
250
|
+
count;
|
|
251
|
+
split;
|
|
252
|
+
constructor(nodePtr, count, split) {
|
|
253
|
+
this.nodePtr = nodePtr;
|
|
254
|
+
this.count = count;
|
|
255
|
+
this.split = split;
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
export class BTreeSplitResult {
|
|
259
|
+
left;
|
|
260
|
+
right;
|
|
261
|
+
constructor(left, right) {
|
|
262
|
+
this.left = left;
|
|
263
|
+
this.right = right;
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
// sorted_map / sorted_set: a count-augmented B+tree keyed on arbitrary byte strings,
|
|
267
|
+
// ordered lexicographically. reuses the b-tree's capacity constants, persistence model
|
|
268
|
+
// (txStart reuse), KeyValuePair entries, and the BTreeHeader {rootPtr, size} header. a
|
|
269
|
+
// leaf holds .kv_pair entries in ascending key order; a branch holds child slots,
|
|
270
|
+
// separator slots (the smallest key in each child's subtree; separators[0] is an unused
|
|
271
|
+
// sentinel), and per-child subtree counts.
|
|
272
|
+
export class SortedNode {
|
|
273
|
+
kind;
|
|
274
|
+
num;
|
|
275
|
+
entries = new Array(BTREE_SLOT_COUNT); // leaf
|
|
276
|
+
children = new Array(BTREE_SLOT_COUNT); // branch
|
|
277
|
+
separators = new Array(BTREE_SLOT_COUNT); // branch
|
|
278
|
+
counts = new Array(BTREE_SLOT_COUNT).fill(0); // branch
|
|
279
|
+
constructor(kind, num) {
|
|
280
|
+
this.kind = kind;
|
|
281
|
+
this.num = num;
|
|
282
|
+
for (let i = 0; i < BTREE_SLOT_COUNT; i++) {
|
|
283
|
+
this.entries[i] = new Slot();
|
|
284
|
+
this.children[i] = new Slot();
|
|
285
|
+
this.separators[i] = new Slot();
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
subtreeCount() {
|
|
289
|
+
if (this.kind === BTreeNodeKind.LEAF)
|
|
290
|
+
return this.num;
|
|
291
|
+
let total = 0;
|
|
292
|
+
for (let i = 0; i < this.num; i++)
|
|
293
|
+
total += this.counts[i];
|
|
294
|
+
return total;
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
// the new right sibling produced when a node splits
|
|
298
|
+
export class SortedSplit {
|
|
299
|
+
nodePtr;
|
|
300
|
+
count;
|
|
301
|
+
separator;
|
|
302
|
+
constructor(nodePtr, count, separator) {
|
|
303
|
+
this.nodePtr = nodePtr;
|
|
304
|
+
this.count = count;
|
|
305
|
+
this.separator = separator;
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
// insert/replace result: where to write the value, whether a new entry was added (vs
|
|
309
|
+
// replacing), and the new right sibling if this node split
|
|
310
|
+
export class SortedInsertResult {
|
|
311
|
+
nodePtr;
|
|
312
|
+
count;
|
|
313
|
+
valuePosition;
|
|
314
|
+
added;
|
|
315
|
+
split;
|
|
316
|
+
constructor(nodePtr, count, valuePosition, added, split) {
|
|
317
|
+
this.nodePtr = nodePtr;
|
|
318
|
+
this.count = count;
|
|
319
|
+
this.valuePosition = valuePosition;
|
|
320
|
+
this.added = added;
|
|
321
|
+
this.split = split;
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
// remove result threaded back up the descent: the rewritten node and whether the key
|
|
325
|
+
// was found. separators are stable lower-bound boundaries (not exact mins), so
|
|
326
|
+
// deletions never refresh them; an emptied leaf is left in place.
|
|
327
|
+
export class SortedRemoveResult {
|
|
328
|
+
nodePtr;
|
|
329
|
+
found;
|
|
330
|
+
constructor(nodePtr, found) {
|
|
331
|
+
this.nodePtr = nodePtr;
|
|
332
|
+
this.found = found;
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
export class SortedSlot {
|
|
336
|
+
slot;
|
|
337
|
+
position;
|
|
338
|
+
constructor(slot, position) {
|
|
339
|
+
this.slot = slot;
|
|
340
|
+
this.position = position;
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
export class SortedEntry {
|
|
344
|
+
kvSlot;
|
|
345
|
+
keySlot;
|
|
346
|
+
valuePosition;
|
|
347
|
+
constructor(kvSlot, keySlot, valuePosition) {
|
|
348
|
+
this.kvSlot = kvSlot;
|
|
349
|
+
this.keySlot = keySlot;
|
|
350
|
+
this.valuePosition = valuePosition;
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
export class HashMapGetKVPair {
|
|
354
|
+
hash;
|
|
355
|
+
kind = 'kv_pair';
|
|
356
|
+
constructor(hash) {
|
|
357
|
+
this.hash = hash;
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
export class HashMapGetKey {
|
|
361
|
+
hash;
|
|
362
|
+
kind = 'key';
|
|
363
|
+
constructor(hash) {
|
|
364
|
+
this.hash = hash;
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
export class HashMapGetValue {
|
|
368
|
+
hash;
|
|
369
|
+
kind = 'value';
|
|
370
|
+
constructor(hash) {
|
|
371
|
+
this.hash = hash;
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
export class SortedMapGetKVPair {
|
|
375
|
+
key;
|
|
376
|
+
kind = 'kv_pair';
|
|
377
|
+
constructor(key) {
|
|
378
|
+
this.key = key;
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
export class SortedMapGetKey {
|
|
382
|
+
key;
|
|
383
|
+
kind = 'key';
|
|
384
|
+
constructor(key) {
|
|
385
|
+
this.key = key;
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
export class SortedMapGetValue {
|
|
389
|
+
key;
|
|
390
|
+
kind = 'value';
|
|
391
|
+
constructor(key) {
|
|
392
|
+
this.key = key;
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
// PathPart implementations
|
|
396
|
+
export class ArrayListInit {
|
|
397
|
+
kind = 'ArrayListInit';
|
|
398
|
+
readSlotPointer(db, isTopLevel, writeMode, path, pathI, slotPtr) {
|
|
399
|
+
if (writeMode === WriteMode.READ_ONLY)
|
|
400
|
+
throw new WriteNotAllowedException();
|
|
401
|
+
if (isTopLevel) {
|
|
402
|
+
const writer = db.core.writer();
|
|
403
|
+
if (db.header.tag === 0 /* Tag.NONE */) {
|
|
404
|
+
db.core.seek(Header.LENGTH);
|
|
405
|
+
const arrayListPtr = Header.LENGTH + TopLevelArrayListHeader.LENGTH;
|
|
406
|
+
writer.write(new TopLevelArrayListHeader(0, new ArrayListHeader(arrayListPtr, 0)).toBytes());
|
|
407
|
+
writer.write(new Uint8Array(INDEX_BLOCK_SIZE));
|
|
408
|
+
db.core.seek(0);
|
|
409
|
+
db.header = db.header.withTag(2 /* Tag.ARRAY_LIST */);
|
|
410
|
+
writer.write(db.header.toBytes());
|
|
411
|
+
}
|
|
412
|
+
const nextSlotPtr = slotPtr.withSlot(slotPtr.slot.withTag(2 /* Tag.ARRAY_LIST */));
|
|
413
|
+
return db.readSlotPointer(writeMode, path, pathI + 1, nextSlotPtr);
|
|
414
|
+
}
|
|
415
|
+
if (slotPtr.position === null)
|
|
416
|
+
throw new CursorNotWriteableException();
|
|
417
|
+
const position = slotPtr.position;
|
|
418
|
+
switch (slotPtr.slot.tag) {
|
|
419
|
+
case 0 /* Tag.NONE */: {
|
|
420
|
+
const writer = db.core.writer();
|
|
421
|
+
let arrayListStart = db.core.length();
|
|
422
|
+
db.core.seek(arrayListStart);
|
|
423
|
+
const arrayListPtr = arrayListStart + ArrayListHeader.LENGTH;
|
|
424
|
+
writer.write(new ArrayListHeader(arrayListPtr, 0).toBytes());
|
|
425
|
+
writer.write(new Uint8Array(INDEX_BLOCK_SIZE));
|
|
426
|
+
const nextSlotPtr = new SlotPointer(position, new Slot(arrayListStart, 2 /* Tag.ARRAY_LIST */));
|
|
427
|
+
db.core.seek(position);
|
|
428
|
+
writer.write(nextSlotPtr.slot.toBytes());
|
|
429
|
+
return db.readSlotPointer(writeMode, path, pathI + 1, nextSlotPtr);
|
|
430
|
+
}
|
|
431
|
+
case 2 /* Tag.ARRAY_LIST */: {
|
|
432
|
+
const reader = db.core.reader();
|
|
433
|
+
const writer = db.core.writer();
|
|
434
|
+
let arrayListStart = Number(slotPtr.slot.value);
|
|
435
|
+
if (db.txStart !== null) {
|
|
436
|
+
if (arrayListStart < db.txStart) {
|
|
437
|
+
db.core.seek(arrayListStart);
|
|
438
|
+
const headerBytes = new Uint8Array(ArrayListHeader.LENGTH);
|
|
439
|
+
reader.readFully(headerBytes);
|
|
440
|
+
const header = ArrayListHeader.fromBytes(headerBytes);
|
|
441
|
+
db.core.seek(header.ptr);
|
|
442
|
+
const arrayListIndexBlock = new Uint8Array(INDEX_BLOCK_SIZE);
|
|
443
|
+
reader.readFully(arrayListIndexBlock);
|
|
444
|
+
arrayListStart = db.core.length();
|
|
445
|
+
db.core.seek(arrayListStart);
|
|
446
|
+
const nextArrayListPtr = arrayListStart + ArrayListHeader.LENGTH;
|
|
447
|
+
const newHeader = header.withPtr(nextArrayListPtr);
|
|
448
|
+
writer.write(newHeader.toBytes());
|
|
449
|
+
writer.write(arrayListIndexBlock);
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
else if (db.header.tag === 2 /* Tag.ARRAY_LIST */) {
|
|
453
|
+
throw new ExpectedTxStartException();
|
|
454
|
+
}
|
|
455
|
+
const nextSlotPtr = new SlotPointer(position, new Slot(arrayListStart, 2 /* Tag.ARRAY_LIST */));
|
|
456
|
+
db.core.seek(position);
|
|
457
|
+
writer.write(nextSlotPtr.slot.toBytes());
|
|
458
|
+
return db.readSlotPointer(writeMode, path, pathI + 1, nextSlotPtr);
|
|
459
|
+
}
|
|
460
|
+
default:
|
|
461
|
+
throw new UnexpectedTagException();
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
export class ArrayListGet {
|
|
466
|
+
index;
|
|
467
|
+
kind = 'ArrayListGet';
|
|
468
|
+
constructor(index) {
|
|
469
|
+
this.index = index;
|
|
470
|
+
}
|
|
471
|
+
readSlotPointer(db, isTopLevel, writeMode, path, pathI, slotPtr) {
|
|
472
|
+
const tag = isTopLevel ? db.header.tag : slotPtr.slot.tag;
|
|
473
|
+
switch (tag) {
|
|
474
|
+
case 0 /* Tag.NONE */:
|
|
475
|
+
throw new KeyNotFoundException();
|
|
476
|
+
case 2 /* Tag.ARRAY_LIST */:
|
|
477
|
+
break;
|
|
478
|
+
default:
|
|
479
|
+
throw new UnexpectedTagException();
|
|
480
|
+
}
|
|
481
|
+
const nextArrayListStart = Number(slotPtr.slot.value);
|
|
482
|
+
let index = this.index;
|
|
483
|
+
db.core.seek(nextArrayListStart);
|
|
484
|
+
const reader = db.core.reader();
|
|
485
|
+
const headerBytes = new Uint8Array(ArrayListHeader.LENGTH);
|
|
486
|
+
reader.readFully(headerBytes);
|
|
487
|
+
const header = ArrayListHeader.fromBytes(headerBytes);
|
|
488
|
+
if (index >= header.size || index < -header.size) {
|
|
489
|
+
throw new KeyNotFoundException();
|
|
490
|
+
}
|
|
491
|
+
const key = index < 0 ? header.size - Math.abs(index) : index;
|
|
492
|
+
const lastKey = header.size - 1;
|
|
493
|
+
const shift = lastKey < SLOT_COUNT ? 0 : Math.floor(Math.log(lastKey) / Math.log(SLOT_COUNT));
|
|
494
|
+
const finalSlotPtr = db.readArrayListSlot(header.ptr, key, shift, writeMode, isTopLevel);
|
|
495
|
+
return db.readSlotPointer(writeMode, path, pathI + 1, finalSlotPtr);
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
export class ArrayListAppend {
|
|
499
|
+
kind = 'ArrayListAppend';
|
|
500
|
+
readSlotPointer(db, isTopLevel, writeMode, path, pathI, slotPtr) {
|
|
501
|
+
if (writeMode === WriteMode.READ_ONLY)
|
|
502
|
+
throw new WriteNotAllowedException();
|
|
503
|
+
const tag = isTopLevel ? db.header.tag : slotPtr.slot.tag;
|
|
504
|
+
if (tag !== 2 /* Tag.ARRAY_LIST */)
|
|
505
|
+
throw new UnexpectedTagException();
|
|
506
|
+
const reader = db.core.reader();
|
|
507
|
+
const nextArrayListStart = Number(slotPtr.slot.value);
|
|
508
|
+
db.core.seek(nextArrayListStart);
|
|
509
|
+
const headerBytes = new Uint8Array(ArrayListHeader.LENGTH);
|
|
510
|
+
reader.readFully(headerBytes);
|
|
511
|
+
const origHeader = ArrayListHeader.fromBytes(headerBytes);
|
|
512
|
+
const appendResult = db.readArrayListSlotAppend(origHeader, writeMode, isTopLevel);
|
|
513
|
+
const finalSlotPtr = db.readSlotPointer(writeMode, path, pathI + 1, appendResult.slotPtr);
|
|
514
|
+
const writer = db.core.writer();
|
|
515
|
+
if (isTopLevel) {
|
|
516
|
+
db.core.flush();
|
|
517
|
+
const fileSize = db.core.length();
|
|
518
|
+
const header = new TopLevelArrayListHeader(fileSize, appendResult.header);
|
|
519
|
+
db.core.seek(nextArrayListStart);
|
|
520
|
+
writer.write(header.toBytes());
|
|
521
|
+
}
|
|
522
|
+
else {
|
|
523
|
+
db.core.seek(nextArrayListStart);
|
|
524
|
+
writer.write(appendResult.header.toBytes());
|
|
525
|
+
}
|
|
526
|
+
return finalSlotPtr;
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
export class ArrayListSlice {
|
|
530
|
+
size;
|
|
531
|
+
kind = 'ArrayListSlice';
|
|
532
|
+
constructor(size) {
|
|
533
|
+
this.size = size;
|
|
534
|
+
}
|
|
535
|
+
readSlotPointer(db, isTopLevel, writeMode, path, pathI, slotPtr) {
|
|
536
|
+
if (writeMode === WriteMode.READ_ONLY)
|
|
537
|
+
throw new WriteNotAllowedException();
|
|
538
|
+
if (slotPtr.slot.tag !== 2 /* Tag.ARRAY_LIST */)
|
|
539
|
+
throw new UnexpectedTagException();
|
|
540
|
+
const reader = db.core.reader();
|
|
541
|
+
const nextArrayListStart = Number(slotPtr.slot.value);
|
|
542
|
+
db.core.seek(nextArrayListStart);
|
|
543
|
+
const headerBytes = new Uint8Array(ArrayListHeader.LENGTH);
|
|
544
|
+
reader.readFully(headerBytes);
|
|
545
|
+
const origHeader = ArrayListHeader.fromBytes(headerBytes);
|
|
546
|
+
const sliceHeader = db.readArrayListSlice(origHeader, this.size);
|
|
547
|
+
const finalSlotPtr = db.readSlotPointer(writeMode, path, pathI + 1, slotPtr);
|
|
548
|
+
const writer = db.core.writer();
|
|
549
|
+
db.core.seek(nextArrayListStart);
|
|
550
|
+
writer.write(sliceHeader.toBytes());
|
|
551
|
+
return finalSlotPtr;
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
export class LinkedArrayListInit {
|
|
555
|
+
kind = 'LinkedArrayListInit';
|
|
556
|
+
readSlotPointer(db, isTopLevel, writeMode, path, pathI, slotPtr) {
|
|
557
|
+
if (writeMode === WriteMode.READ_ONLY)
|
|
558
|
+
throw new WriteNotAllowedException();
|
|
559
|
+
if (isTopLevel)
|
|
560
|
+
throw new InvalidTopLevelTypeException();
|
|
561
|
+
if (slotPtr.position === null)
|
|
562
|
+
throw new CursorNotWriteableException();
|
|
563
|
+
const position = slotPtr.position;
|
|
564
|
+
const writer = db.core.writer();
|
|
565
|
+
switch (slotPtr.slot.tag) {
|
|
566
|
+
case 0 /* Tag.NONE */: {
|
|
567
|
+
// create an empty tree: a single empty leaf plus a header
|
|
568
|
+
const rootPtr = db.writeBTreeNode(new BTreeNode(BTreeNodeKind.LEAF, 0));
|
|
569
|
+
const headerPtr = db.core.length();
|
|
570
|
+
db.core.seek(headerPtr);
|
|
571
|
+
writer.write(new BTreeHeader(rootPtr, 0).toBytes());
|
|
572
|
+
const nextSlotPtr = new SlotPointer(position, new Slot(headerPtr, 3 /* Tag.LINKED_ARRAY_LIST */));
|
|
573
|
+
db.core.seek(position);
|
|
574
|
+
writer.write(nextSlotPtr.slot.toBytes());
|
|
575
|
+
return db.readSlotPointer(writeMode, path, pathI + 1, nextSlotPtr);
|
|
576
|
+
}
|
|
577
|
+
case 3 /* Tag.LINKED_ARRAY_LIST */: {
|
|
578
|
+
let headerPtr = Number(slotPtr.slot.value);
|
|
579
|
+
// copy the header into this transaction unless it was made in it, so past
|
|
580
|
+
// moments still pointing at the old header are unaffected. b-tree nodes are
|
|
581
|
+
// always appended, so only the header (updated in place by later operations
|
|
582
|
+
// in this tx) needs copying.
|
|
583
|
+
if (db.txStart !== null) {
|
|
584
|
+
if (headerPtr < db.txStart) {
|
|
585
|
+
const reader = db.core.reader();
|
|
586
|
+
db.core.seek(headerPtr);
|
|
587
|
+
const headerBytes = new Uint8Array(BTreeHeader.LENGTH);
|
|
588
|
+
reader.readFully(headerBytes);
|
|
589
|
+
headerPtr = db.core.length();
|
|
590
|
+
db.core.seek(headerPtr);
|
|
591
|
+
writer.write(headerBytes);
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
else if (db.header.tag === 2 /* Tag.ARRAY_LIST */) {
|
|
595
|
+
throw new ExpectedTxStartException();
|
|
596
|
+
}
|
|
597
|
+
const nextSlotPtr = new SlotPointer(position, new Slot(headerPtr, 3 /* Tag.LINKED_ARRAY_LIST */));
|
|
598
|
+
db.core.seek(position);
|
|
599
|
+
writer.write(nextSlotPtr.slot.toBytes());
|
|
600
|
+
return db.readSlotPointer(writeMode, path, pathI + 1, nextSlotPtr);
|
|
601
|
+
}
|
|
602
|
+
default:
|
|
603
|
+
throw new UnexpectedTagException();
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
export class LinkedArrayListGet {
|
|
608
|
+
index;
|
|
609
|
+
kind = 'LinkedArrayListGet';
|
|
610
|
+
constructor(index) {
|
|
611
|
+
this.index = index;
|
|
612
|
+
}
|
|
613
|
+
readSlotPointer(db, isTopLevel, writeMode, path, pathI, slotPtr) {
|
|
614
|
+
switch (slotPtr.slot.tag) {
|
|
615
|
+
case 0 /* Tag.NONE */:
|
|
616
|
+
throw new KeyNotFoundException();
|
|
617
|
+
case 3 /* Tag.LINKED_ARRAY_LIST */:
|
|
618
|
+
break;
|
|
619
|
+
default:
|
|
620
|
+
throw new UnexpectedTagException();
|
|
621
|
+
}
|
|
622
|
+
const index = this.index;
|
|
623
|
+
const headerPtr = Number(slotPtr.slot.value);
|
|
624
|
+
const reader = db.core.reader();
|
|
625
|
+
db.core.seek(headerPtr);
|
|
626
|
+
const headerBytes = new Uint8Array(BTreeHeader.LENGTH);
|
|
627
|
+
reader.readFully(headerBytes);
|
|
628
|
+
const header = BTreeHeader.fromBytes(headerBytes);
|
|
629
|
+
if (index >= header.size || index < -header.size) {
|
|
630
|
+
throw new KeyNotFoundException();
|
|
631
|
+
}
|
|
632
|
+
const rank = index < 0 ? header.size - Math.abs(index) : index;
|
|
633
|
+
if (writeMode === WriteMode.READ_ONLY) {
|
|
634
|
+
const finalSlotPtr = db.readBTreeSlot(header.rootPtr, rank);
|
|
635
|
+
return db.readSlotPointer(writeMode, path, pathI + 1, finalSlotPtr);
|
|
636
|
+
}
|
|
637
|
+
else {
|
|
638
|
+
// path-copy down to the value slot so the write is persistent
|
|
639
|
+
const writeSlot = db.btreeGetForWrite(header.rootPtr, rank);
|
|
640
|
+
const finalSlotPtr = db.readSlotPointer(writeMode, path, pathI + 1, new SlotPointer(writeSlot.valuePosition, writeSlot.slot));
|
|
641
|
+
// the header only needs rewriting if the root actually moved (it stays put
|
|
642
|
+
// when the whole path was already this-transaction)
|
|
643
|
+
if (writeSlot.nodePtr !== header.rootPtr) {
|
|
644
|
+
const writer = db.core.writer();
|
|
645
|
+
db.core.seek(headerPtr);
|
|
646
|
+
writer.write(new BTreeHeader(writeSlot.nodePtr, header.size).toBytes());
|
|
647
|
+
}
|
|
648
|
+
return finalSlotPtr;
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
export class LinkedArrayListAppend {
|
|
653
|
+
kind = 'LinkedArrayListAppend';
|
|
654
|
+
readSlotPointer(db, isTopLevel, writeMode, path, pathI, slotPtr) {
|
|
655
|
+
if (writeMode === WriteMode.READ_ONLY)
|
|
656
|
+
throw new WriteNotAllowedException();
|
|
657
|
+
if (slotPtr.slot.tag !== 3 /* Tag.LINKED_ARRAY_LIST */)
|
|
658
|
+
throw new UnexpectedTagException();
|
|
659
|
+
const headerPtr = Number(slotPtr.slot.value);
|
|
660
|
+
const reader = db.core.reader();
|
|
661
|
+
db.core.seek(headerPtr);
|
|
662
|
+
const headerBytes = new Uint8Array(BTreeHeader.LENGTH);
|
|
663
|
+
reader.readFully(headerBytes);
|
|
664
|
+
const header = BTreeHeader.fromBytes(headerBytes);
|
|
665
|
+
const result = db.btreeInsert(header.rootPtr, header.size);
|
|
666
|
+
const newRootPtr = db.btreeGrowRoot(result);
|
|
667
|
+
// update the header before filling in the value, so that a failure in the
|
|
668
|
+
// rest of the path leaves the tree and header consistent
|
|
669
|
+
const writer = db.core.writer();
|
|
670
|
+
db.core.seek(headerPtr);
|
|
671
|
+
writer.write(new BTreeHeader(newRootPtr, header.size + 1).toBytes());
|
|
672
|
+
// fill in the value via the rest of the path
|
|
673
|
+
return db.readSlotPointer(writeMode, path, pathI + 1, new SlotPointer(result.valuePosition, new Slot()));
|
|
674
|
+
}
|
|
675
|
+
}
|
|
676
|
+
export class LinkedArrayListSlice {
|
|
677
|
+
offset;
|
|
678
|
+
size;
|
|
679
|
+
kind = 'LinkedArrayListSlice';
|
|
680
|
+
constructor(offset, size) {
|
|
681
|
+
this.offset = offset;
|
|
682
|
+
this.size = size;
|
|
683
|
+
}
|
|
684
|
+
readSlotPointer(db, isTopLevel, writeMode, path, pathI, slotPtr) {
|
|
685
|
+
if (writeMode === WriteMode.READ_ONLY)
|
|
686
|
+
throw new WriteNotAllowedException();
|
|
687
|
+
if (slotPtr.slot.tag !== 3 /* Tag.LINKED_ARRAY_LIST */)
|
|
688
|
+
throw new UnexpectedTagException();
|
|
689
|
+
const headerPtr = Number(slotPtr.slot.value);
|
|
690
|
+
const reader = db.core.reader();
|
|
691
|
+
db.core.seek(headerPtr);
|
|
692
|
+
const headerBytes = new Uint8Array(BTreeHeader.LENGTH);
|
|
693
|
+
reader.readFully(headerBytes);
|
|
694
|
+
const header = BTreeHeader.fromBytes(headerBytes);
|
|
695
|
+
// bounds-checked without overflow (offset + size could wrap)
|
|
696
|
+
if (this.offset > header.size || this.size > header.size - this.offset) {
|
|
697
|
+
throw new KeyNotFoundException();
|
|
698
|
+
}
|
|
699
|
+
// slice = drop [0, offset) then keep [0, size) of what's left
|
|
700
|
+
const afterOffset = db.btreeSplit(header.rootPtr, this.offset);
|
|
701
|
+
const sliced = db.btreeSplit(afterOffset.right, this.size);
|
|
702
|
+
const newRootPtr = sliced.left;
|
|
703
|
+
// update the header before recursing into the rest of the path, so that a
|
|
704
|
+
// failure there leaves the tree and header consistent
|
|
705
|
+
const writer = db.core.writer();
|
|
706
|
+
db.core.seek(headerPtr);
|
|
707
|
+
writer.write(new BTreeHeader(newRootPtr, this.size).toBytes());
|
|
708
|
+
return db.readSlotPointer(writeMode, path, pathI + 1, slotPtr);
|
|
709
|
+
}
|
|
710
|
+
}
|
|
711
|
+
export class LinkedArrayListConcat {
|
|
712
|
+
list;
|
|
713
|
+
kind = 'LinkedArrayListConcat';
|
|
714
|
+
constructor(list) {
|
|
715
|
+
this.list = list;
|
|
716
|
+
}
|
|
717
|
+
readSlotPointer(db, isTopLevel, writeMode, path, pathI, slotPtr) {
|
|
718
|
+
if (writeMode === WriteMode.READ_ONLY)
|
|
719
|
+
throw new WriteNotAllowedException();
|
|
720
|
+
if (slotPtr.slot.tag !== 3 /* Tag.LINKED_ARRAY_LIST */)
|
|
721
|
+
throw new UnexpectedTagException();
|
|
722
|
+
if (this.list.tag !== 3 /* Tag.LINKED_ARRAY_LIST */)
|
|
723
|
+
throw new UnexpectedTagException();
|
|
724
|
+
const headerPtr = Number(slotPtr.slot.value);
|
|
725
|
+
const reader = db.core.reader();
|
|
726
|
+
db.core.seek(headerPtr);
|
|
727
|
+
const headerBytesA = new Uint8Array(BTreeHeader.LENGTH);
|
|
728
|
+
reader.readFully(headerBytesA);
|
|
729
|
+
const headerA = BTreeHeader.fromBytes(headerBytesA);
|
|
730
|
+
db.core.seek(Number(this.list.value));
|
|
731
|
+
const headerBytesB = new Uint8Array(BTreeHeader.LENGTH);
|
|
732
|
+
reader.readFully(headerBytesB);
|
|
733
|
+
const headerB = BTreeHeader.fromBytes(headerBytesB);
|
|
734
|
+
// the join result shares subtrees with both operands (and the second operand
|
|
735
|
+
// stays live), so freeze everything created so far: later in-place mutations
|
|
736
|
+
// will then copy those nodes instead of overwriting a node that is still
|
|
737
|
+
// referenced elsewhere.
|
|
738
|
+
db.txStart = db.core.length();
|
|
739
|
+
const newRootPtr = db.btreeJoin(headerA.rootPtr, headerB.rootPtr);
|
|
740
|
+
// update the header before recursing into the rest of the path, so that a
|
|
741
|
+
// failure there leaves the tree and header consistent
|
|
742
|
+
const writer = db.core.writer();
|
|
743
|
+
db.core.seek(headerPtr);
|
|
744
|
+
writer.write(new BTreeHeader(newRootPtr, headerA.size + headerB.size).toBytes());
|
|
745
|
+
return db.readSlotPointer(writeMode, path, pathI + 1, slotPtr);
|
|
746
|
+
}
|
|
747
|
+
}
|
|
748
|
+
export class LinkedArrayListInsert {
|
|
749
|
+
index;
|
|
750
|
+
kind = 'LinkedArrayListInsert';
|
|
751
|
+
constructor(index) {
|
|
752
|
+
this.index = index;
|
|
753
|
+
}
|
|
754
|
+
readSlotPointer(db, isTopLevel, writeMode, path, pathI, slotPtr) {
|
|
755
|
+
if (writeMode === WriteMode.READ_ONLY)
|
|
756
|
+
throw new WriteNotAllowedException();
|
|
757
|
+
if (slotPtr.slot.tag !== 3 /* Tag.LINKED_ARRAY_LIST */)
|
|
758
|
+
throw new UnexpectedTagException();
|
|
759
|
+
const headerPtr = Number(slotPtr.slot.value);
|
|
760
|
+
const reader = db.core.reader();
|
|
761
|
+
db.core.seek(headerPtr);
|
|
762
|
+
const headerBytes = new Uint8Array(BTreeHeader.LENGTH);
|
|
763
|
+
reader.readFully(headerBytes);
|
|
764
|
+
const header = BTreeHeader.fromBytes(headerBytes);
|
|
765
|
+
const index = this.index;
|
|
766
|
+
if (index >= header.size || index < -header.size) {
|
|
767
|
+
throw new KeyNotFoundException();
|
|
768
|
+
}
|
|
769
|
+
const rank = index < 0 ? header.size - Math.abs(index) : index;
|
|
770
|
+
const result = db.btreeInsert(header.rootPtr, rank);
|
|
771
|
+
const newRootPtr = db.btreeGrowRoot(result);
|
|
772
|
+
// update the header before filling in the value, so that a failure in the
|
|
773
|
+
// rest of the path leaves the tree and header consistent
|
|
774
|
+
const writer = db.core.writer();
|
|
775
|
+
db.core.seek(headerPtr);
|
|
776
|
+
writer.write(new BTreeHeader(newRootPtr, header.size + 1).toBytes());
|
|
777
|
+
return db.readSlotPointer(writeMode, path, pathI + 1, new SlotPointer(result.valuePosition, new Slot()));
|
|
778
|
+
}
|
|
779
|
+
}
|
|
780
|
+
export class LinkedArrayListRemove {
|
|
781
|
+
index;
|
|
782
|
+
kind = 'LinkedArrayListRemove';
|
|
783
|
+
constructor(index) {
|
|
784
|
+
this.index = index;
|
|
785
|
+
}
|
|
786
|
+
readSlotPointer(db, isTopLevel, writeMode, path, pathI, slotPtr) {
|
|
787
|
+
if (writeMode === WriteMode.READ_ONLY)
|
|
788
|
+
throw new WriteNotAllowedException();
|
|
789
|
+
if (slotPtr.slot.tag !== 3 /* Tag.LINKED_ARRAY_LIST */)
|
|
790
|
+
throw new UnexpectedTagException();
|
|
791
|
+
const headerPtr = Number(slotPtr.slot.value);
|
|
792
|
+
const reader = db.core.reader();
|
|
793
|
+
db.core.seek(headerPtr);
|
|
794
|
+
const headerBytes = new Uint8Array(BTreeHeader.LENGTH);
|
|
795
|
+
reader.readFully(headerBytes);
|
|
796
|
+
const header = BTreeHeader.fromBytes(headerBytes);
|
|
797
|
+
const index = this.index;
|
|
798
|
+
if (index >= header.size || index < -header.size) {
|
|
799
|
+
throw new KeyNotFoundException();
|
|
800
|
+
}
|
|
801
|
+
const rank = index < 0 ? header.size - Math.abs(index) : index;
|
|
802
|
+
// remove = join the parts before and after the removed element
|
|
803
|
+
const before = db.btreeSplit(header.rootPtr, rank);
|
|
804
|
+
const after = db.btreeSplit(before.right, 1);
|
|
805
|
+
const newRootPtr = db.btreeJoin(before.left, after.right);
|
|
806
|
+
// update the header before recursing into the rest of the path, so that a
|
|
807
|
+
// failure there leaves the tree and header consistent
|
|
808
|
+
const writer = db.core.writer();
|
|
809
|
+
db.core.seek(headerPtr);
|
|
810
|
+
writer.write(new BTreeHeader(newRootPtr, header.size - 1).toBytes());
|
|
811
|
+
return db.readSlotPointer(writeMode, path, pathI + 1, slotPtr);
|
|
812
|
+
}
|
|
813
|
+
}
|
|
814
|
+
export class HashMapInit {
|
|
815
|
+
counted;
|
|
816
|
+
set;
|
|
817
|
+
kind = 'HashMapInit';
|
|
818
|
+
constructor(counted = false, set = false) {
|
|
819
|
+
this.counted = counted;
|
|
820
|
+
this.set = set;
|
|
821
|
+
}
|
|
822
|
+
readSlotPointer(db, isTopLevel, writeMode, path, pathI, slotPtr) {
|
|
823
|
+
if (writeMode === WriteMode.READ_ONLY)
|
|
824
|
+
throw new WriteNotAllowedException();
|
|
825
|
+
const tag = this.counted
|
|
826
|
+
? (this.set ? 13 /* Tag.COUNTED_HASH_SET */ : 12 /* Tag.COUNTED_HASH_MAP */)
|
|
827
|
+
: (this.set ? 11 /* Tag.HASH_SET */ : 4 /* Tag.HASH_MAP */);
|
|
828
|
+
if (isTopLevel) {
|
|
829
|
+
const writer = db.core.writer();
|
|
830
|
+
if (db.header.tag === 0 /* Tag.NONE */) {
|
|
831
|
+
db.core.seek(Header.LENGTH);
|
|
832
|
+
if (this.counted) {
|
|
833
|
+
writer.writeLong(0);
|
|
834
|
+
}
|
|
835
|
+
writer.write(new Uint8Array(INDEX_BLOCK_SIZE));
|
|
836
|
+
db.core.seek(0);
|
|
837
|
+
db.header = db.header.withTag(tag);
|
|
838
|
+
writer.write(db.header.toBytes());
|
|
839
|
+
}
|
|
840
|
+
const nextSlotPtr = slotPtr.withSlot(slotPtr.slot.withTag(tag));
|
|
841
|
+
return db.readSlotPointer(writeMode, path, pathI + 1, nextSlotPtr);
|
|
842
|
+
}
|
|
843
|
+
if (slotPtr.position === null)
|
|
844
|
+
throw new CursorNotWriteableException();
|
|
845
|
+
const position = slotPtr.position;
|
|
846
|
+
switch (slotPtr.slot.tag) {
|
|
847
|
+
case 0 /* Tag.NONE */: {
|
|
848
|
+
const writer = db.core.writer();
|
|
849
|
+
const mapStart = db.core.length();
|
|
850
|
+
db.core.seek(mapStart);
|
|
851
|
+
if (this.counted) {
|
|
852
|
+
writer.writeLong(0);
|
|
853
|
+
}
|
|
854
|
+
writer.write(new Uint8Array(INDEX_BLOCK_SIZE));
|
|
855
|
+
const nextSlotPtr = new SlotPointer(position, new Slot(mapStart, tag));
|
|
856
|
+
db.core.seek(position);
|
|
857
|
+
writer.write(nextSlotPtr.slot.toBytes());
|
|
858
|
+
return db.readSlotPointer(writeMode, path, pathI + 1, nextSlotPtr);
|
|
859
|
+
}
|
|
860
|
+
case 4 /* Tag.HASH_MAP */:
|
|
861
|
+
case 11 /* Tag.HASH_SET */:
|
|
862
|
+
case 12 /* Tag.COUNTED_HASH_MAP */:
|
|
863
|
+
case 13 /* Tag.COUNTED_HASH_SET */: {
|
|
864
|
+
if (this.counted) {
|
|
865
|
+
switch (slotPtr.slot.tag) {
|
|
866
|
+
case 12 /* Tag.COUNTED_HASH_MAP */:
|
|
867
|
+
case 13 /* Tag.COUNTED_HASH_SET */:
|
|
868
|
+
break;
|
|
869
|
+
default:
|
|
870
|
+
throw new UnexpectedTagException();
|
|
871
|
+
}
|
|
872
|
+
}
|
|
873
|
+
else {
|
|
874
|
+
switch (slotPtr.slot.tag) {
|
|
875
|
+
case 4 /* Tag.HASH_MAP */:
|
|
876
|
+
case 11 /* Tag.HASH_SET */:
|
|
877
|
+
break;
|
|
878
|
+
default:
|
|
879
|
+
throw new UnexpectedTagException();
|
|
880
|
+
}
|
|
881
|
+
}
|
|
882
|
+
const reader = db.core.reader();
|
|
883
|
+
const writer = db.core.writer();
|
|
884
|
+
let mapStart = Number(slotPtr.slot.value);
|
|
885
|
+
if (db.txStart !== null) {
|
|
886
|
+
if (mapStart < db.txStart) {
|
|
887
|
+
db.core.seek(mapStart);
|
|
888
|
+
let mapCountMaybe = null;
|
|
889
|
+
if (this.counted) {
|
|
890
|
+
mapCountMaybe = reader.readLong();
|
|
891
|
+
}
|
|
892
|
+
const mapIndexBlock = new Uint8Array(INDEX_BLOCK_SIZE);
|
|
893
|
+
reader.readFully(mapIndexBlock);
|
|
894
|
+
mapStart = db.core.length();
|
|
895
|
+
db.core.seek(mapStart);
|
|
896
|
+
if (mapCountMaybe !== null) {
|
|
897
|
+
writer.writeLong(mapCountMaybe);
|
|
898
|
+
}
|
|
899
|
+
writer.write(mapIndexBlock);
|
|
900
|
+
}
|
|
901
|
+
}
|
|
902
|
+
else if (db.header.tag === 2 /* Tag.ARRAY_LIST */) {
|
|
903
|
+
throw new ExpectedTxStartException();
|
|
904
|
+
}
|
|
905
|
+
const nextSlotPtr = new SlotPointer(position, new Slot(mapStart, tag));
|
|
906
|
+
db.core.seek(position);
|
|
907
|
+
writer.write(nextSlotPtr.slot.toBytes());
|
|
908
|
+
return db.readSlotPointer(writeMode, path, pathI + 1, nextSlotPtr);
|
|
909
|
+
}
|
|
910
|
+
default:
|
|
911
|
+
throw new UnexpectedTagException();
|
|
912
|
+
}
|
|
913
|
+
}
|
|
914
|
+
}
|
|
915
|
+
export class HashMapGet {
|
|
916
|
+
target;
|
|
917
|
+
kind = 'HashMapGet';
|
|
918
|
+
constructor(target) {
|
|
919
|
+
this.target = target;
|
|
920
|
+
}
|
|
921
|
+
readSlotPointer(db, isTopLevel, writeMode, path, pathI, slotPtr) {
|
|
922
|
+
let counted = false;
|
|
923
|
+
switch (slotPtr.slot.tag) {
|
|
924
|
+
case 0 /* Tag.NONE */:
|
|
925
|
+
throw new KeyNotFoundException();
|
|
926
|
+
case 4 /* Tag.HASH_MAP */:
|
|
927
|
+
case 11 /* Tag.HASH_SET */:
|
|
928
|
+
break;
|
|
929
|
+
case 12 /* Tag.COUNTED_HASH_MAP */:
|
|
930
|
+
case 13 /* Tag.COUNTED_HASH_SET */:
|
|
931
|
+
counted = true;
|
|
932
|
+
break;
|
|
933
|
+
default:
|
|
934
|
+
throw new UnexpectedTagException();
|
|
935
|
+
}
|
|
936
|
+
const indexPos = counted ? Number(slotPtr.slot.value) + 8 : Number(slotPtr.slot.value);
|
|
937
|
+
const hash = db.checkHash(this.target);
|
|
938
|
+
const res = db.readMapSlot(indexPos, hash, 0, writeMode, isTopLevel, this.target);
|
|
939
|
+
if (writeMode === WriteMode.READ_WRITE && counted && res.isEmpty) {
|
|
940
|
+
const reader = db.core.reader();
|
|
941
|
+
const writer = db.core.writer();
|
|
942
|
+
db.core.seek(Number(slotPtr.slot.value));
|
|
943
|
+
const mapCount = reader.readLong();
|
|
944
|
+
db.core.seek(Number(slotPtr.slot.value));
|
|
945
|
+
writer.writeLong(mapCount + 1);
|
|
946
|
+
}
|
|
947
|
+
return db.readSlotPointer(writeMode, path, pathI + 1, res.slotPtr);
|
|
948
|
+
}
|
|
949
|
+
}
|
|
950
|
+
export class HashMapRemove {
|
|
951
|
+
hash;
|
|
952
|
+
kind = 'HashMapRemove';
|
|
953
|
+
constructor(hash) {
|
|
954
|
+
this.hash = hash;
|
|
955
|
+
}
|
|
956
|
+
readSlotPointer(db, isTopLevel, writeMode, path, pathI, slotPtr) {
|
|
957
|
+
if (writeMode === WriteMode.READ_ONLY)
|
|
958
|
+
throw new WriteNotAllowedException();
|
|
959
|
+
let counted = false;
|
|
960
|
+
switch (slotPtr.slot.tag) {
|
|
961
|
+
case 0 /* Tag.NONE */:
|
|
962
|
+
throw new KeyNotFoundException();
|
|
963
|
+
case 4 /* Tag.HASH_MAP */:
|
|
964
|
+
case 11 /* Tag.HASH_SET */:
|
|
965
|
+
break;
|
|
966
|
+
case 12 /* Tag.COUNTED_HASH_MAP */:
|
|
967
|
+
case 13 /* Tag.COUNTED_HASH_SET */:
|
|
968
|
+
counted = true;
|
|
969
|
+
break;
|
|
970
|
+
default:
|
|
971
|
+
throw new UnexpectedTagException();
|
|
972
|
+
}
|
|
973
|
+
const indexPos = counted ? Number(slotPtr.slot.value) + 8 : Number(slotPtr.slot.value);
|
|
974
|
+
const hash = db.checkHashBytes(this.hash);
|
|
975
|
+
let keyFound = true;
|
|
976
|
+
try {
|
|
977
|
+
db.removeMapSlot(indexPos, hash, 0, isTopLevel);
|
|
978
|
+
}
|
|
979
|
+
catch (e) {
|
|
980
|
+
if (e instanceof KeyNotFoundException) {
|
|
981
|
+
keyFound = false;
|
|
982
|
+
}
|
|
983
|
+
else {
|
|
984
|
+
throw e;
|
|
985
|
+
}
|
|
986
|
+
}
|
|
987
|
+
if (writeMode === WriteMode.READ_WRITE && counted && keyFound) {
|
|
988
|
+
const reader = db.core.reader();
|
|
989
|
+
const writer = db.core.writer();
|
|
990
|
+
db.core.seek(Number(slotPtr.slot.value));
|
|
991
|
+
const mapCount = reader.readLong();
|
|
992
|
+
db.core.seek(Number(slotPtr.slot.value));
|
|
993
|
+
writer.writeLong(mapCount - 1);
|
|
994
|
+
}
|
|
995
|
+
if (!keyFound)
|
|
996
|
+
throw new KeyNotFoundException();
|
|
997
|
+
return slotPtr;
|
|
998
|
+
}
|
|
999
|
+
}
|
|
1000
|
+
export class SortedMapInit {
|
|
1001
|
+
set;
|
|
1002
|
+
kind = 'SortedMapInit';
|
|
1003
|
+
constructor(set = false) {
|
|
1004
|
+
this.set = set;
|
|
1005
|
+
}
|
|
1006
|
+
readSlotPointer(db, isTopLevel, writeMode, path, pathI, slotPtr) {
|
|
1007
|
+
if (writeMode === WriteMode.READ_ONLY)
|
|
1008
|
+
throw new WriteNotAllowedException();
|
|
1009
|
+
if (isTopLevel)
|
|
1010
|
+
throw new InvalidTopLevelTypeException();
|
|
1011
|
+
if (slotPtr.position === null)
|
|
1012
|
+
throw new CursorNotWriteableException();
|
|
1013
|
+
const position = slotPtr.position;
|
|
1014
|
+
const tag = this.set ? 15 /* Tag.SORTED_SET */ : 14 /* Tag.SORTED_MAP */;
|
|
1015
|
+
const writer = db.core.writer();
|
|
1016
|
+
switch (slotPtr.slot.tag) {
|
|
1017
|
+
case 0 /* Tag.NONE */: {
|
|
1018
|
+
const rootPtr = db.writeSortedNode(new SortedNode(BTreeNodeKind.LEAF, 0));
|
|
1019
|
+
const headerPtr = db.core.length();
|
|
1020
|
+
db.core.seek(headerPtr);
|
|
1021
|
+
writer.write(new BTreeHeader(rootPtr, 0).toBytes());
|
|
1022
|
+
const nextSlotPtr = new SlotPointer(position, new Slot(headerPtr, tag));
|
|
1023
|
+
db.core.seek(position);
|
|
1024
|
+
writer.write(nextSlotPtr.slot.toBytes());
|
|
1025
|
+
return db.readSlotPointer(writeMode, path, pathI + 1, nextSlotPtr);
|
|
1026
|
+
}
|
|
1027
|
+
case 14 /* Tag.SORTED_MAP */:
|
|
1028
|
+
case 15 /* Tag.SORTED_SET */: {
|
|
1029
|
+
if (slotPtr.slot.tag !== tag)
|
|
1030
|
+
throw new UnexpectedTagException();
|
|
1031
|
+
let headerPtr = Number(slotPtr.slot.value);
|
|
1032
|
+
// copy the header into this transaction unless it was made in it
|
|
1033
|
+
if (db.txStart !== null) {
|
|
1034
|
+
if (headerPtr < db.txStart) {
|
|
1035
|
+
const reader = db.core.reader();
|
|
1036
|
+
db.core.seek(headerPtr);
|
|
1037
|
+
const headerBytes = new Uint8Array(BTreeHeader.LENGTH);
|
|
1038
|
+
reader.readFully(headerBytes);
|
|
1039
|
+
headerPtr = db.core.length();
|
|
1040
|
+
db.core.seek(headerPtr);
|
|
1041
|
+
writer.write(headerBytes);
|
|
1042
|
+
}
|
|
1043
|
+
}
|
|
1044
|
+
else if (db.header.tag === 2 /* Tag.ARRAY_LIST */) {
|
|
1045
|
+
throw new ExpectedTxStartException();
|
|
1046
|
+
}
|
|
1047
|
+
const nextSlotPtr = new SlotPointer(position, new Slot(headerPtr, tag));
|
|
1048
|
+
db.core.seek(position);
|
|
1049
|
+
writer.write(nextSlotPtr.slot.toBytes());
|
|
1050
|
+
return db.readSlotPointer(writeMode, path, pathI + 1, nextSlotPtr);
|
|
1051
|
+
}
|
|
1052
|
+
default:
|
|
1053
|
+
throw new UnexpectedTagException();
|
|
1054
|
+
}
|
|
1055
|
+
}
|
|
1056
|
+
}
|
|
1057
|
+
export class SortedMapGet {
|
|
1058
|
+
target;
|
|
1059
|
+
kind = 'SortedMapGet';
|
|
1060
|
+
constructor(target) {
|
|
1061
|
+
this.target = target;
|
|
1062
|
+
}
|
|
1063
|
+
readSlotPointer(db, isTopLevel, writeMode, path, pathI, slotPtr) {
|
|
1064
|
+
switch (slotPtr.slot.tag) {
|
|
1065
|
+
case 0 /* Tag.NONE */:
|
|
1066
|
+
throw new KeyNotFoundException();
|
|
1067
|
+
case 14 /* Tag.SORTED_MAP */:
|
|
1068
|
+
case 15 /* Tag.SORTED_SET */:
|
|
1069
|
+
break;
|
|
1070
|
+
default:
|
|
1071
|
+
throw new UnexpectedTagException();
|
|
1072
|
+
}
|
|
1073
|
+
const key = this.target.key;
|
|
1074
|
+
const headerPtr = Number(slotPtr.slot.value);
|
|
1075
|
+
const reader = db.core.reader();
|
|
1076
|
+
db.core.seek(headerPtr);
|
|
1077
|
+
const headerBytes = new Uint8Array(BTreeHeader.LENGTH);
|
|
1078
|
+
reader.readFully(headerBytes);
|
|
1079
|
+
const header = BTreeHeader.fromBytes(headerBytes);
|
|
1080
|
+
if (writeMode === WriteMode.READ_ONLY) {
|
|
1081
|
+
const found = db.sortedGet(header.rootPtr, key);
|
|
1082
|
+
if (found === null)
|
|
1083
|
+
throw new KeyNotFoundException();
|
|
1084
|
+
const targetSlot = db.sortedTargetSlot(Number(found.slot.value), this.target);
|
|
1085
|
+
return db.readSlotPointer(writeMode, path, pathI + 1, targetSlot);
|
|
1086
|
+
}
|
|
1087
|
+
else {
|
|
1088
|
+
const result = db.sortedPut(header.rootPtr, key);
|
|
1089
|
+
const newRootPtr = db.sortedGrowRoot(result);
|
|
1090
|
+
// update the header before filling in the value, so that a failure in the
|
|
1091
|
+
// rest of the path leaves the tree and header consistent (the entry exists
|
|
1092
|
+
// with an empty value) rather than inserted-but-uncounted
|
|
1093
|
+
const writer = db.core.writer();
|
|
1094
|
+
db.core.seek(headerPtr);
|
|
1095
|
+
writer.write(new BTreeHeader(newRootPtr, header.size + (result.added ? 1 : 0)).toBytes());
|
|
1096
|
+
const kvPos = result.valuePosition - db.header.hashSize - Slot.LENGTH;
|
|
1097
|
+
const targetSlot = db.sortedTargetSlot(kvPos, this.target);
|
|
1098
|
+
return db.readSlotPointer(writeMode, path, pathI + 1, targetSlot);
|
|
1099
|
+
}
|
|
1100
|
+
}
|
|
1101
|
+
}
|
|
1102
|
+
export class SortedMapGetIndex {
|
|
1103
|
+
index;
|
|
1104
|
+
kind = 'SortedMapGetIndex';
|
|
1105
|
+
constructor(index) {
|
|
1106
|
+
this.index = index;
|
|
1107
|
+
}
|
|
1108
|
+
readSlotPointer(db, isTopLevel, writeMode, path, pathI, slotPtr) {
|
|
1109
|
+
if (writeMode === WriteMode.READ_WRITE)
|
|
1110
|
+
throw new WriteNotAllowedException();
|
|
1111
|
+
switch (slotPtr.slot.tag) {
|
|
1112
|
+
case 0 /* Tag.NONE */:
|
|
1113
|
+
throw new KeyNotFoundException();
|
|
1114
|
+
case 14 /* Tag.SORTED_MAP */:
|
|
1115
|
+
case 15 /* Tag.SORTED_SET */:
|
|
1116
|
+
break;
|
|
1117
|
+
default:
|
|
1118
|
+
throw new UnexpectedTagException();
|
|
1119
|
+
}
|
|
1120
|
+
const headerPtr = Number(slotPtr.slot.value);
|
|
1121
|
+
const reader = db.core.reader();
|
|
1122
|
+
db.core.seek(headerPtr);
|
|
1123
|
+
const headerBytes = new Uint8Array(BTreeHeader.LENGTH);
|
|
1124
|
+
reader.readFully(headerBytes);
|
|
1125
|
+
const header = BTreeHeader.fromBytes(headerBytes);
|
|
1126
|
+
const index = this.index;
|
|
1127
|
+
if (index >= header.size || index < -header.size) {
|
|
1128
|
+
throw new KeyNotFoundException();
|
|
1129
|
+
}
|
|
1130
|
+
const rank = index < 0 ? header.size - Math.abs(index) : index;
|
|
1131
|
+
const found = db.sortedGetByIndex(header.rootPtr, rank);
|
|
1132
|
+
// return the kv_pair entry so the caller can read key and value
|
|
1133
|
+
const targetSlot = new SlotPointer(found.position, found.slot);
|
|
1134
|
+
return db.readSlotPointer(writeMode, path, pathI + 1, targetSlot);
|
|
1135
|
+
}
|
|
1136
|
+
}
|
|
1137
|
+
export class SortedMapRemove {
|
|
1138
|
+
key;
|
|
1139
|
+
kind = 'SortedMapRemove';
|
|
1140
|
+
constructor(key) {
|
|
1141
|
+
this.key = key;
|
|
1142
|
+
}
|
|
1143
|
+
readSlotPointer(db, isTopLevel, writeMode, path, pathI, slotPtr) {
|
|
1144
|
+
if (writeMode === WriteMode.READ_ONLY)
|
|
1145
|
+
throw new WriteNotAllowedException();
|
|
1146
|
+
switch (slotPtr.slot.tag) {
|
|
1147
|
+
case 0 /* Tag.NONE */:
|
|
1148
|
+
throw new KeyNotFoundException();
|
|
1149
|
+
case 14 /* Tag.SORTED_MAP */:
|
|
1150
|
+
case 15 /* Tag.SORTED_SET */:
|
|
1151
|
+
break;
|
|
1152
|
+
default:
|
|
1153
|
+
throw new UnexpectedTagException();
|
|
1154
|
+
}
|
|
1155
|
+
const headerPtr = Number(slotPtr.slot.value);
|
|
1156
|
+
const reader = db.core.reader();
|
|
1157
|
+
db.core.seek(headerPtr);
|
|
1158
|
+
const headerBytes = new Uint8Array(BTreeHeader.LENGTH);
|
|
1159
|
+
reader.readFully(headerBytes);
|
|
1160
|
+
const header = BTreeHeader.fromBytes(headerBytes);
|
|
1161
|
+
const result = db.sortedRemove(header.rootPtr, this.key);
|
|
1162
|
+
if (!result.found)
|
|
1163
|
+
throw new KeyNotFoundException();
|
|
1164
|
+
const writer = db.core.writer();
|
|
1165
|
+
db.core.seek(headerPtr);
|
|
1166
|
+
writer.write(new BTreeHeader(result.nodePtr, header.size - 1).toBytes());
|
|
1167
|
+
return slotPtr;
|
|
1168
|
+
}
|
|
1169
|
+
}
|
|
1170
|
+
export class WriteData {
|
|
1171
|
+
data;
|
|
1172
|
+
kind = 'WriteData';
|
|
1173
|
+
constructor(data) {
|
|
1174
|
+
this.data = data;
|
|
1175
|
+
}
|
|
1176
|
+
readSlotPointer(db, isTopLevel, writeMode, path, pathI, slotPtr) {
|
|
1177
|
+
if (writeMode === WriteMode.READ_ONLY)
|
|
1178
|
+
throw new WriteNotAllowedException();
|
|
1179
|
+
if (slotPtr.position === null)
|
|
1180
|
+
throw new CursorNotWriteableException();
|
|
1181
|
+
const position = slotPtr.position;
|
|
1182
|
+
const writer = db.core.writer();
|
|
1183
|
+
const data = this.data;
|
|
1184
|
+
let slot;
|
|
1185
|
+
if (data === null) {
|
|
1186
|
+
slot = new Slot();
|
|
1187
|
+
}
|
|
1188
|
+
else if (data instanceof Slot) {
|
|
1189
|
+
slot = data;
|
|
1190
|
+
}
|
|
1191
|
+
else if (data instanceof Uint) {
|
|
1192
|
+
slot = new Slot(data.value, 8 /* Tag.UINT */);
|
|
1193
|
+
}
|
|
1194
|
+
else if (data instanceof Int) {
|
|
1195
|
+
slot = new Slot(data.value, 9 /* Tag.INT */);
|
|
1196
|
+
}
|
|
1197
|
+
else if (data instanceof Float) {
|
|
1198
|
+
const buffer = new ArrayBuffer(8);
|
|
1199
|
+
const view = new DataView(buffer);
|
|
1200
|
+
view.setFloat64(0, data.value, false);
|
|
1201
|
+
const longValue = view.getBigInt64(0, false);
|
|
1202
|
+
slot = new Slot(longValue, 10 /* Tag.FLOAT */);
|
|
1203
|
+
}
|
|
1204
|
+
else if (data instanceof Bytes) {
|
|
1205
|
+
if (data.isShort()) {
|
|
1206
|
+
const buffer = new Uint8Array(8);
|
|
1207
|
+
buffer.set(data.value, 0);
|
|
1208
|
+
if (data.formatTag !== null) {
|
|
1209
|
+
buffer.set(data.formatTag, 6);
|
|
1210
|
+
}
|
|
1211
|
+
const view = new DataView(buffer.buffer);
|
|
1212
|
+
// Read 8 bytes big-endian as BigInt for full precision
|
|
1213
|
+
const longValue = view.getBigInt64(0, false);
|
|
1214
|
+
slot = new Slot(longValue, 7 /* Tag.SHORT_BYTES */, data.formatTag !== null);
|
|
1215
|
+
}
|
|
1216
|
+
else {
|
|
1217
|
+
const nextCursor = new WriteCursor(slotPtr, db);
|
|
1218
|
+
const cursorWriter = nextCursor.writer();
|
|
1219
|
+
cursorWriter.formatTag = data.formatTag;
|
|
1220
|
+
cursorWriter.write(data.value);
|
|
1221
|
+
cursorWriter.finish();
|
|
1222
|
+
slot = cursorWriter.slot;
|
|
1223
|
+
}
|
|
1224
|
+
}
|
|
1225
|
+
else {
|
|
1226
|
+
throw new Error('Unknown data type');
|
|
1227
|
+
}
|
|
1228
|
+
if (slot.tag === 0 /* Tag.NONE */) {
|
|
1229
|
+
slot = slot.withFull(true);
|
|
1230
|
+
}
|
|
1231
|
+
db.core.seek(position);
|
|
1232
|
+
writer.write(slot.toBytes());
|
|
1233
|
+
const nextSlotPtr = new SlotPointer(slotPtr.position, slot);
|
|
1234
|
+
return db.readSlotPointer(writeMode, path, pathI + 1, nextSlotPtr);
|
|
1235
|
+
}
|
|
1236
|
+
}
|
|
1237
|
+
export class Context {
|
|
1238
|
+
fn;
|
|
1239
|
+
kind = 'Context';
|
|
1240
|
+
constructor(fn) {
|
|
1241
|
+
this.fn = fn;
|
|
1242
|
+
}
|
|
1243
|
+
readSlotPointer(db, isTopLevel, writeMode, path, pathI, slotPtr) {
|
|
1244
|
+
if (writeMode === WriteMode.READ_ONLY)
|
|
1245
|
+
throw new WriteNotAllowedException();
|
|
1246
|
+
if (pathI !== path.length - 1)
|
|
1247
|
+
throw new PathPartMustBeAtEndException();
|
|
1248
|
+
const nextCursor = new WriteCursor(slotPtr, db);
|
|
1249
|
+
try {
|
|
1250
|
+
this.fn(nextCursor);
|
|
1251
|
+
}
|
|
1252
|
+
catch (e) {
|
|
1253
|
+
try {
|
|
1254
|
+
db.truncate();
|
|
1255
|
+
}
|
|
1256
|
+
catch (_) { }
|
|
1257
|
+
throw e;
|
|
1258
|
+
}
|
|
1259
|
+
return nextCursor.slotPtr;
|
|
1260
|
+
}
|
|
1261
|
+
}
|
|
1262
|
+
// HashMapGetResult
|
|
1263
|
+
class HashMapGetResult {
|
|
1264
|
+
slotPtr;
|
|
1265
|
+
isEmpty;
|
|
1266
|
+
constructor(slotPtr, isEmpty) {
|
|
1267
|
+
this.slotPtr = slotPtr;
|
|
1268
|
+
this.isEmpty = isEmpty;
|
|
1269
|
+
}
|
|
1270
|
+
}
|
|
1271
|
+
// ArrayListAppendResult
|
|
1272
|
+
class ArrayListAppendResult {
|
|
1273
|
+
header;
|
|
1274
|
+
slotPtr;
|
|
1275
|
+
constructor(header, slotPtr) {
|
|
1276
|
+
this.header = header;
|
|
1277
|
+
this.slotPtr = slotPtr;
|
|
1278
|
+
}
|
|
1279
|
+
}
|
|
1280
|
+
// Helper functions
|
|
1281
|
+
function arraysEqual(a, b) {
|
|
1282
|
+
if (a.length !== b.length)
|
|
1283
|
+
return false;
|
|
1284
|
+
for (let i = 0; i < a.length; i++) {
|
|
1285
|
+
if (a[i] !== b[i])
|
|
1286
|
+
return false;
|
|
1287
|
+
}
|
|
1288
|
+
return true;
|
|
1289
|
+
}
|
|
1290
|
+
function checkLong(n) {
|
|
1291
|
+
if (n < 0) {
|
|
1292
|
+
throw new ExpectedUnsignedLongException();
|
|
1293
|
+
}
|
|
1294
|
+
return n;
|
|
1295
|
+
}
|
|
1296
|
+
// lexicographic comparison of two byte strings (unsigned), returns <0, 0, or >0
|
|
1297
|
+
function compareBytesUnsigned(a, b) {
|
|
1298
|
+
const n = Math.min(a.length, b.length);
|
|
1299
|
+
for (let i = 0; i < n; i++) {
|
|
1300
|
+
if (a[i] !== b[i])
|
|
1301
|
+
return a[i] < b[i] ? -1 : 1;
|
|
1302
|
+
}
|
|
1303
|
+
return a.length === b.length ? 0 : a.length < b.length ? -1 : 1;
|
|
1304
|
+
}
|
|
1305
|
+
function bigIntShiftRight(value, bits) {
|
|
1306
|
+
let result = 0n;
|
|
1307
|
+
for (let i = 0; i < value.length; i++) {
|
|
1308
|
+
result = (result << 8n) | BigInt(value[i]);
|
|
1309
|
+
}
|
|
1310
|
+
return result >> BigInt(bits);
|
|
1311
|
+
}
|
|
1312
|
+
// Database class
|
|
1313
|
+
export class Database {
|
|
1314
|
+
core;
|
|
1315
|
+
hasher;
|
|
1316
|
+
header;
|
|
1317
|
+
txStart = null;
|
|
1318
|
+
constructor(core, hasher) {
|
|
1319
|
+
this.core = core;
|
|
1320
|
+
this.hasher = hasher;
|
|
1321
|
+
core.seek(0);
|
|
1322
|
+
if ((core.length()) === 0) {
|
|
1323
|
+
this.header = new Header(hasher.id, hasher.digestLength, VERSION, 0 /* Tag.NONE */, MAGIC_NUMBER);
|
|
1324
|
+
this.header.write(core);
|
|
1325
|
+
core.flush();
|
|
1326
|
+
}
|
|
1327
|
+
else {
|
|
1328
|
+
this.header = Header.read(core);
|
|
1329
|
+
this.header.validate();
|
|
1330
|
+
if (this.header.hashSize !== hasher.digestLength) {
|
|
1331
|
+
throw new InvalidHashSizeException();
|
|
1332
|
+
}
|
|
1333
|
+
this.truncate();
|
|
1334
|
+
}
|
|
1335
|
+
}
|
|
1336
|
+
rootCursor() {
|
|
1337
|
+
// if the header tag is none, try re-reading it.
|
|
1338
|
+
// this may be necessary if the database was initialized on a different thread.
|
|
1339
|
+
if (this.header.tag === 0 /* Tag.NONE */) {
|
|
1340
|
+
this.core.seek(0);
|
|
1341
|
+
this.header = Header.read(this.core);
|
|
1342
|
+
}
|
|
1343
|
+
return new WriteCursor(new SlotPointer(null, new Slot(Header.LENGTH, this.header.tag)), this);
|
|
1344
|
+
}
|
|
1345
|
+
freeze() {
|
|
1346
|
+
if (this.txStart !== null) {
|
|
1347
|
+
this.txStart = this.core.length();
|
|
1348
|
+
}
|
|
1349
|
+
else {
|
|
1350
|
+
throw new ExpectedTxStartException();
|
|
1351
|
+
}
|
|
1352
|
+
}
|
|
1353
|
+
compact(targetCore) {
|
|
1354
|
+
const offsetMap = new Map();
|
|
1355
|
+
const hasher = new Hasher(this.hasher.algorithm, this.header.hashId);
|
|
1356
|
+
const target = new Database(targetCore, hasher);
|
|
1357
|
+
if (this.header.tag === 0 /* Tag.NONE */)
|
|
1358
|
+
return target;
|
|
1359
|
+
if (this.header.tag !== 2 /* Tag.ARRAY_LIST */)
|
|
1360
|
+
throw new UnexpectedTagException();
|
|
1361
|
+
// read source's top-level ArrayListHeader
|
|
1362
|
+
this.core.seek(Header.LENGTH);
|
|
1363
|
+
const sourceReader = this.core.reader();
|
|
1364
|
+
const headerBytes = new Uint8Array(ArrayListHeader.LENGTH);
|
|
1365
|
+
sourceReader.readFully(headerBytes);
|
|
1366
|
+
const sourceHeader = ArrayListHeader.fromBytes(headerBytes);
|
|
1367
|
+
if (sourceHeader.size === 0)
|
|
1368
|
+
return target;
|
|
1369
|
+
// read the last moment's slot
|
|
1370
|
+
const lastKey = sourceHeader.size - 1;
|
|
1371
|
+
const shift = lastKey < SLOT_COUNT ? 0 : Math.floor(Math.log(lastKey) / Math.log(SLOT_COUNT));
|
|
1372
|
+
const lastSlotPtr = this.readArrayListSlot(sourceHeader.ptr, lastKey, shift, WriteMode.READ_ONLY, true);
|
|
1373
|
+
const momentSlot = lastSlotPtr.slot;
|
|
1374
|
+
// write TopLevelArrayListHeader + root index block to target
|
|
1375
|
+
const targetWriter = targetCore.writer();
|
|
1376
|
+
targetCore.seek(Header.LENGTH);
|
|
1377
|
+
const targetArrayListPtr = Header.LENGTH + TopLevelArrayListHeader.LENGTH;
|
|
1378
|
+
targetWriter.write(new TopLevelArrayListHeader(0, new ArrayListHeader(targetArrayListPtr, 1)).toBytes());
|
|
1379
|
+
targetWriter.write(new Uint8Array(INDEX_BLOCK_SIZE));
|
|
1380
|
+
// recursively remap the moment slot
|
|
1381
|
+
const remappedMoment = remapSlot(this.core, targetCore, this.header.hashSize, offsetMap, momentSlot);
|
|
1382
|
+
// write remapped moment slot into position 0 of target's root index block
|
|
1383
|
+
targetCore.seek(targetArrayListPtr);
|
|
1384
|
+
targetWriter.write(remappedMoment.toBytes());
|
|
1385
|
+
// update target's DatabaseHeader tag
|
|
1386
|
+
target.header = target.header.withTag(2 /* Tag.ARRAY_LIST */);
|
|
1387
|
+
targetCore.seek(0);
|
|
1388
|
+
target.header.write(targetCore);
|
|
1389
|
+
// flush, update file_size, flush again
|
|
1390
|
+
targetCore.flush();
|
|
1391
|
+
const fileSize = targetCore.length();
|
|
1392
|
+
targetCore.seek(Header.LENGTH + ArrayListHeader.LENGTH);
|
|
1393
|
+
targetWriter.writeLong(fileSize);
|
|
1394
|
+
targetCore.flush();
|
|
1395
|
+
return target;
|
|
1396
|
+
}
|
|
1397
|
+
truncate() {
|
|
1398
|
+
if (this.header.tag !== 2 /* Tag.ARRAY_LIST */)
|
|
1399
|
+
return;
|
|
1400
|
+
const rootCursor = this.rootCursor();
|
|
1401
|
+
const listSize = rootCursor.count();
|
|
1402
|
+
if (listSize === 0)
|
|
1403
|
+
return;
|
|
1404
|
+
this.core.seek(Header.LENGTH + ArrayListHeader.LENGTH);
|
|
1405
|
+
const reader = this.core.reader();
|
|
1406
|
+
const headerFileSize = reader.readLong();
|
|
1407
|
+
if (headerFileSize === 0)
|
|
1408
|
+
return;
|
|
1409
|
+
const fileSize = this.core.length();
|
|
1410
|
+
if (fileSize === headerFileSize)
|
|
1411
|
+
return;
|
|
1412
|
+
try {
|
|
1413
|
+
this.core.setLength(headerFileSize);
|
|
1414
|
+
}
|
|
1415
|
+
catch (_) { }
|
|
1416
|
+
}
|
|
1417
|
+
checkHashBytes(hash) {
|
|
1418
|
+
if (hash.length !== this.header.hashSize) {
|
|
1419
|
+
throw new InvalidHashSizeException();
|
|
1420
|
+
}
|
|
1421
|
+
return hash;
|
|
1422
|
+
}
|
|
1423
|
+
checkHash(target) {
|
|
1424
|
+
return this.checkHashBytes(target.hash);
|
|
1425
|
+
}
|
|
1426
|
+
readSlotPointer(writeMode, path, pathI, slotPtr) {
|
|
1427
|
+
if (pathI === path.length) {
|
|
1428
|
+
if (writeMode === WriteMode.READ_ONLY && slotPtr.slot.tag === 0 /* Tag.NONE */) {
|
|
1429
|
+
throw new KeyNotFoundException();
|
|
1430
|
+
}
|
|
1431
|
+
return slotPtr;
|
|
1432
|
+
}
|
|
1433
|
+
const part = path[pathI];
|
|
1434
|
+
const isTopLevel = slotPtr.slot.value === BigInt(Header.LENGTH);
|
|
1435
|
+
const isTxStart = isTopLevel && this.header.tag === 2 /* Tag.ARRAY_LIST */ && this.txStart === null;
|
|
1436
|
+
if (isTxStart) {
|
|
1437
|
+
this.txStart = this.core.length();
|
|
1438
|
+
}
|
|
1439
|
+
try {
|
|
1440
|
+
return part.readSlotPointer(this, isTopLevel, writeMode, path, pathI, slotPtr);
|
|
1441
|
+
}
|
|
1442
|
+
finally {
|
|
1443
|
+
if (isTxStart) {
|
|
1444
|
+
this.txStart = null;
|
|
1445
|
+
}
|
|
1446
|
+
}
|
|
1447
|
+
}
|
|
1448
|
+
// HashMap methods
|
|
1449
|
+
readMapSlot(indexPos, keyHash, keyOffset, writeMode, isTopLevel, target) {
|
|
1450
|
+
if (keyOffset > (this.header.hashSize * 8) / BIT_COUNT) {
|
|
1451
|
+
throw new KeyOffsetExceededException();
|
|
1452
|
+
}
|
|
1453
|
+
const reader = this.core.reader();
|
|
1454
|
+
const writer = this.core.writer();
|
|
1455
|
+
const i = Number(bigIntShiftRight(keyHash, keyOffset * BIT_COUNT) & MASK);
|
|
1456
|
+
const slotPos = indexPos + Slot.LENGTH * i;
|
|
1457
|
+
this.core.seek(slotPos);
|
|
1458
|
+
const slotBytes = new Uint8Array(Slot.LENGTH);
|
|
1459
|
+
reader.readFully(slotBytes);
|
|
1460
|
+
const slot = Slot.fromBytes(slotBytes);
|
|
1461
|
+
const ptr = Number(slot.value);
|
|
1462
|
+
switch (slot.tag) {
|
|
1463
|
+
case 0 /* Tag.NONE */: {
|
|
1464
|
+
switch (writeMode) {
|
|
1465
|
+
case WriteMode.READ_ONLY:
|
|
1466
|
+
throw new KeyNotFoundException();
|
|
1467
|
+
case WriteMode.READ_WRITE: {
|
|
1468
|
+
const hashPos = this.core.length();
|
|
1469
|
+
this.core.seek(hashPos);
|
|
1470
|
+
const keySlotPos = hashPos + this.header.hashSize;
|
|
1471
|
+
const valueSlotPos = keySlotPos + Slot.LENGTH;
|
|
1472
|
+
const kvPair = new KeyValuePair(new Slot(), new Slot(), keyHash);
|
|
1473
|
+
writer.write(kvPair.toBytes());
|
|
1474
|
+
const nextSlot = new Slot(hashPos, 5 /* Tag.KV_PAIR */);
|
|
1475
|
+
this.core.seek(slotPos);
|
|
1476
|
+
writer.write(nextSlot.toBytes());
|
|
1477
|
+
let nextSlotPtr;
|
|
1478
|
+
if (target.kind === 'kv_pair') {
|
|
1479
|
+
nextSlotPtr = new SlotPointer(slotPos, nextSlot);
|
|
1480
|
+
}
|
|
1481
|
+
else if (target.kind === 'key') {
|
|
1482
|
+
nextSlotPtr = new SlotPointer(keySlotPos, kvPair.keySlot);
|
|
1483
|
+
}
|
|
1484
|
+
else {
|
|
1485
|
+
nextSlotPtr = new SlotPointer(valueSlotPos, kvPair.valueSlot);
|
|
1486
|
+
}
|
|
1487
|
+
return new HashMapGetResult(nextSlotPtr, true);
|
|
1488
|
+
}
|
|
1489
|
+
default:
|
|
1490
|
+
throw new UnreachableException();
|
|
1491
|
+
}
|
|
1492
|
+
}
|
|
1493
|
+
case 1 /* Tag.INDEX */: {
|
|
1494
|
+
let nextPtr = ptr;
|
|
1495
|
+
if (writeMode === WriteMode.READ_WRITE && !isTopLevel) {
|
|
1496
|
+
if (this.txStart !== null) {
|
|
1497
|
+
if (nextPtr < this.txStart) {
|
|
1498
|
+
this.core.seek(ptr);
|
|
1499
|
+
const indexBlock = new Uint8Array(INDEX_BLOCK_SIZE);
|
|
1500
|
+
reader.readFully(indexBlock);
|
|
1501
|
+
nextPtr = this.core.length();
|
|
1502
|
+
this.core.seek(nextPtr);
|
|
1503
|
+
writer.write(indexBlock);
|
|
1504
|
+
this.core.seek(slotPos);
|
|
1505
|
+
writer.write(new Slot(nextPtr, 1 /* Tag.INDEX */).toBytes());
|
|
1506
|
+
}
|
|
1507
|
+
}
|
|
1508
|
+
else if (this.header.tag === 2 /* Tag.ARRAY_LIST */) {
|
|
1509
|
+
throw new ExpectedTxStartException();
|
|
1510
|
+
}
|
|
1511
|
+
}
|
|
1512
|
+
return this.readMapSlot(nextPtr, keyHash, keyOffset + 1, writeMode, isTopLevel, target);
|
|
1513
|
+
}
|
|
1514
|
+
case 5 /* Tag.KV_PAIR */: {
|
|
1515
|
+
this.core.seek(ptr);
|
|
1516
|
+
const kvPairBytes = new Uint8Array(KeyValuePair.length(this.header.hashSize));
|
|
1517
|
+
reader.readFully(kvPairBytes);
|
|
1518
|
+
const kvPair = KeyValuePair.fromBytes(kvPairBytes, this.header.hashSize);
|
|
1519
|
+
if (arraysEqual(kvPair.hash, keyHash)) {
|
|
1520
|
+
if (writeMode === WriteMode.READ_WRITE && !isTopLevel) {
|
|
1521
|
+
if (this.txStart !== null) {
|
|
1522
|
+
if (ptr < this.txStart) {
|
|
1523
|
+
const hashPos = this.core.length();
|
|
1524
|
+
this.core.seek(hashPos);
|
|
1525
|
+
const keySlotPos = hashPos + this.header.hashSize;
|
|
1526
|
+
const valueSlotPos = keySlotPos + Slot.LENGTH;
|
|
1527
|
+
writer.write(kvPair.toBytes());
|
|
1528
|
+
const nextSlot = new Slot(hashPos, 5 /* Tag.KV_PAIR */);
|
|
1529
|
+
this.core.seek(slotPos);
|
|
1530
|
+
writer.write(nextSlot.toBytes());
|
|
1531
|
+
let nextSlotPtr;
|
|
1532
|
+
if (target.kind === 'kv_pair') {
|
|
1533
|
+
nextSlotPtr = new SlotPointer(slotPos, nextSlot);
|
|
1534
|
+
}
|
|
1535
|
+
else if (target.kind === 'key') {
|
|
1536
|
+
nextSlotPtr = new SlotPointer(keySlotPos, kvPair.keySlot);
|
|
1537
|
+
}
|
|
1538
|
+
else {
|
|
1539
|
+
nextSlotPtr = new SlotPointer(valueSlotPos, kvPair.valueSlot);
|
|
1540
|
+
}
|
|
1541
|
+
return new HashMapGetResult(nextSlotPtr, false);
|
|
1542
|
+
}
|
|
1543
|
+
}
|
|
1544
|
+
else if (this.header.tag === 2 /* Tag.ARRAY_LIST */) {
|
|
1545
|
+
throw new ExpectedTxStartException();
|
|
1546
|
+
}
|
|
1547
|
+
}
|
|
1548
|
+
const keySlotPos = ptr + this.header.hashSize;
|
|
1549
|
+
const valueSlotPos = keySlotPos + Slot.LENGTH;
|
|
1550
|
+
let nextSlotPtr;
|
|
1551
|
+
if (target.kind === 'kv_pair') {
|
|
1552
|
+
nextSlotPtr = new SlotPointer(slotPos, slot);
|
|
1553
|
+
}
|
|
1554
|
+
else if (target.kind === 'key') {
|
|
1555
|
+
nextSlotPtr = new SlotPointer(keySlotPos, kvPair.keySlot);
|
|
1556
|
+
}
|
|
1557
|
+
else {
|
|
1558
|
+
nextSlotPtr = new SlotPointer(valueSlotPos, kvPair.valueSlot);
|
|
1559
|
+
}
|
|
1560
|
+
return new HashMapGetResult(nextSlotPtr, false);
|
|
1561
|
+
}
|
|
1562
|
+
else {
|
|
1563
|
+
switch (writeMode) {
|
|
1564
|
+
case WriteMode.READ_ONLY:
|
|
1565
|
+
throw new KeyNotFoundException();
|
|
1566
|
+
case WriteMode.READ_WRITE: {
|
|
1567
|
+
if (keyOffset + 1 >= (this.header.hashSize * 8) / BIT_COUNT) {
|
|
1568
|
+
throw new KeyOffsetExceededException();
|
|
1569
|
+
}
|
|
1570
|
+
const nextI = Number(bigIntShiftRight(kvPair.hash, (keyOffset + 1) * BIT_COUNT) & MASK);
|
|
1571
|
+
const nextIndexPos = this.core.length();
|
|
1572
|
+
this.core.seek(nextIndexPos);
|
|
1573
|
+
writer.write(new Uint8Array(INDEX_BLOCK_SIZE));
|
|
1574
|
+
this.core.seek(nextIndexPos + Slot.LENGTH * nextI);
|
|
1575
|
+
writer.write(slot.toBytes());
|
|
1576
|
+
const res = this.readMapSlot(nextIndexPos, keyHash, keyOffset + 1, writeMode, isTopLevel, target);
|
|
1577
|
+
this.core.seek(slotPos);
|
|
1578
|
+
writer.write(new Slot(nextIndexPos, 1 /* Tag.INDEX */).toBytes());
|
|
1579
|
+
return res;
|
|
1580
|
+
}
|
|
1581
|
+
default:
|
|
1582
|
+
throw new UnreachableException();
|
|
1583
|
+
}
|
|
1584
|
+
}
|
|
1585
|
+
}
|
|
1586
|
+
default:
|
|
1587
|
+
throw new UnexpectedTagException();
|
|
1588
|
+
}
|
|
1589
|
+
}
|
|
1590
|
+
removeMapSlot(indexPos, keyHash, keyOffset, isTopLevel) {
|
|
1591
|
+
if (keyOffset > (this.header.hashSize * 8) / BIT_COUNT) {
|
|
1592
|
+
throw new KeyOffsetExceededException();
|
|
1593
|
+
}
|
|
1594
|
+
const reader = this.core.reader();
|
|
1595
|
+
const writer = this.core.writer();
|
|
1596
|
+
const slotBlock = new Array(SLOT_COUNT);
|
|
1597
|
+
this.core.seek(indexPos);
|
|
1598
|
+
const indexBlock = new Uint8Array(INDEX_BLOCK_SIZE);
|
|
1599
|
+
reader.readFully(indexBlock);
|
|
1600
|
+
for (let i = 0; i < SLOT_COUNT; i++) {
|
|
1601
|
+
const slotBytes = indexBlock.slice(i * Slot.LENGTH, (i + 1) * Slot.LENGTH);
|
|
1602
|
+
slotBlock[i] = Slot.fromBytes(slotBytes);
|
|
1603
|
+
}
|
|
1604
|
+
const i = Number(bigIntShiftRight(keyHash, keyOffset * BIT_COUNT) & MASK);
|
|
1605
|
+
const slotPos = indexPos + Slot.LENGTH * i;
|
|
1606
|
+
const slot = slotBlock[i];
|
|
1607
|
+
let nextSlot;
|
|
1608
|
+
switch (slot.tag) {
|
|
1609
|
+
case 0 /* Tag.NONE */:
|
|
1610
|
+
throw new KeyNotFoundException();
|
|
1611
|
+
case 1 /* Tag.INDEX */:
|
|
1612
|
+
nextSlot = this.removeMapSlot(Number(slot.value), keyHash, keyOffset + 1, isTopLevel);
|
|
1613
|
+
break;
|
|
1614
|
+
case 5 /* Tag.KV_PAIR */: {
|
|
1615
|
+
this.core.seek(Number(slot.value));
|
|
1616
|
+
const kvPairBytes = new Uint8Array(KeyValuePair.length(this.header.hashSize));
|
|
1617
|
+
reader.readFully(kvPairBytes);
|
|
1618
|
+
const kvPair = KeyValuePair.fromBytes(kvPairBytes, this.header.hashSize);
|
|
1619
|
+
if (arraysEqual(kvPair.hash, keyHash)) {
|
|
1620
|
+
nextSlot = new Slot();
|
|
1621
|
+
}
|
|
1622
|
+
else {
|
|
1623
|
+
throw new KeyNotFoundException();
|
|
1624
|
+
}
|
|
1625
|
+
break;
|
|
1626
|
+
}
|
|
1627
|
+
default:
|
|
1628
|
+
throw new UnexpectedTagException();
|
|
1629
|
+
}
|
|
1630
|
+
if (keyOffset === 0) {
|
|
1631
|
+
this.core.seek(slotPos);
|
|
1632
|
+
writer.write(nextSlot.toBytes());
|
|
1633
|
+
return new Slot(indexPos, 1 /* Tag.INDEX */);
|
|
1634
|
+
}
|
|
1635
|
+
let slotToReturnMaybe = new Slot();
|
|
1636
|
+
slotBlock[i] = nextSlot;
|
|
1637
|
+
for (const blockSlot of slotBlock) {
|
|
1638
|
+
if (blockSlot.tag === 0 /* Tag.NONE */)
|
|
1639
|
+
continue;
|
|
1640
|
+
if (slotToReturnMaybe !== null) {
|
|
1641
|
+
if (slotToReturnMaybe.tag !== 0 /* Tag.NONE */) {
|
|
1642
|
+
slotToReturnMaybe = null;
|
|
1643
|
+
break;
|
|
1644
|
+
}
|
|
1645
|
+
}
|
|
1646
|
+
slotToReturnMaybe = blockSlot;
|
|
1647
|
+
}
|
|
1648
|
+
if (slotToReturnMaybe !== null) {
|
|
1649
|
+
switch (slotToReturnMaybe.tag) {
|
|
1650
|
+
case 0 /* Tag.NONE */:
|
|
1651
|
+
case 5 /* Tag.KV_PAIR */:
|
|
1652
|
+
return slotToReturnMaybe;
|
|
1653
|
+
default:
|
|
1654
|
+
break;
|
|
1655
|
+
}
|
|
1656
|
+
}
|
|
1657
|
+
if (!isTopLevel) {
|
|
1658
|
+
if (this.txStart !== null) {
|
|
1659
|
+
if (indexPos < this.txStart) {
|
|
1660
|
+
const nextIndexPos = this.core.length();
|
|
1661
|
+
this.core.seek(nextIndexPos);
|
|
1662
|
+
writer.write(indexBlock);
|
|
1663
|
+
const nextSlotPos = nextIndexPos + Slot.LENGTH * i;
|
|
1664
|
+
this.core.seek(nextSlotPos);
|
|
1665
|
+
writer.write(nextSlot.toBytes());
|
|
1666
|
+
return new Slot(nextIndexPos, 1 /* Tag.INDEX */);
|
|
1667
|
+
}
|
|
1668
|
+
}
|
|
1669
|
+
else if (this.header.tag === 2 /* Tag.ARRAY_LIST */) {
|
|
1670
|
+
throw new ExpectedTxStartException();
|
|
1671
|
+
}
|
|
1672
|
+
}
|
|
1673
|
+
this.core.seek(slotPos);
|
|
1674
|
+
writer.write(nextSlot.toBytes());
|
|
1675
|
+
return new Slot(indexPos, 1 /* Tag.INDEX */);
|
|
1676
|
+
}
|
|
1677
|
+
// ArrayList methods
|
|
1678
|
+
readArrayListSlotAppend(header, writeMode, isTopLevel) {
|
|
1679
|
+
const writer = this.core.writer();
|
|
1680
|
+
let indexPos = header.ptr;
|
|
1681
|
+
const key = header.size;
|
|
1682
|
+
const prevShift = key < SLOT_COUNT ? 0 : Math.floor(Math.log(key - 1) / Math.log(SLOT_COUNT));
|
|
1683
|
+
const nextShift = key < SLOT_COUNT ? 0 : Math.floor(Math.log(key) / Math.log(SLOT_COUNT));
|
|
1684
|
+
if (prevShift !== nextShift) {
|
|
1685
|
+
const nextIndexPos = this.core.length();
|
|
1686
|
+
this.core.seek(nextIndexPos);
|
|
1687
|
+
writer.write(new Uint8Array(INDEX_BLOCK_SIZE));
|
|
1688
|
+
this.core.seek(nextIndexPos);
|
|
1689
|
+
writer.write(new Slot(indexPos, 1 /* Tag.INDEX */).toBytes());
|
|
1690
|
+
indexPos = nextIndexPos;
|
|
1691
|
+
}
|
|
1692
|
+
const slotPtr = this.readArrayListSlot(indexPos, key, nextShift, writeMode, isTopLevel);
|
|
1693
|
+
return new ArrayListAppendResult(new ArrayListHeader(indexPos, header.size + 1), slotPtr);
|
|
1694
|
+
}
|
|
1695
|
+
readArrayListSlot(indexPos, key, shift, writeMode, isTopLevel) {
|
|
1696
|
+
if (shift >= MAX_BRANCH_LENGTH)
|
|
1697
|
+
throw new MaxShiftExceededException();
|
|
1698
|
+
const reader = this.core.reader();
|
|
1699
|
+
const i = (key >>> (shift * BIT_COUNT)) & (SLOT_COUNT - 1);
|
|
1700
|
+
const slotPos = indexPos + Slot.LENGTH * i;
|
|
1701
|
+
this.core.seek(slotPos);
|
|
1702
|
+
const slotBytes = new Uint8Array(Slot.LENGTH);
|
|
1703
|
+
reader.readFully(slotBytes);
|
|
1704
|
+
const slot = Slot.fromBytes(slotBytes);
|
|
1705
|
+
if (shift === 0) {
|
|
1706
|
+
return new SlotPointer(slotPos, slot);
|
|
1707
|
+
}
|
|
1708
|
+
const ptr = Number(slot.value);
|
|
1709
|
+
switch (slot.tag) {
|
|
1710
|
+
case 0 /* Tag.NONE */: {
|
|
1711
|
+
switch (writeMode) {
|
|
1712
|
+
case WriteMode.READ_ONLY:
|
|
1713
|
+
throw new KeyNotFoundException();
|
|
1714
|
+
case WriteMode.READ_WRITE: {
|
|
1715
|
+
const writer = this.core.writer();
|
|
1716
|
+
const nextIndexPos = this.core.length();
|
|
1717
|
+
this.core.seek(nextIndexPos);
|
|
1718
|
+
writer.write(new Uint8Array(INDEX_BLOCK_SIZE));
|
|
1719
|
+
if (isTopLevel) {
|
|
1720
|
+
const fileSize = this.core.length();
|
|
1721
|
+
this.core.seek(Header.LENGTH + ArrayListHeader.LENGTH);
|
|
1722
|
+
writer.writeLong(fileSize);
|
|
1723
|
+
}
|
|
1724
|
+
this.core.seek(slotPos);
|
|
1725
|
+
writer.write(new Slot(nextIndexPos, 1 /* Tag.INDEX */).toBytes());
|
|
1726
|
+
return this.readArrayListSlot(nextIndexPos, key, shift - 1, writeMode, isTopLevel);
|
|
1727
|
+
}
|
|
1728
|
+
default:
|
|
1729
|
+
throw new UnreachableException();
|
|
1730
|
+
}
|
|
1731
|
+
}
|
|
1732
|
+
case 1 /* Tag.INDEX */: {
|
|
1733
|
+
let nextPtr = ptr;
|
|
1734
|
+
if (writeMode === WriteMode.READ_WRITE && !isTopLevel) {
|
|
1735
|
+
if (this.txStart !== null) {
|
|
1736
|
+
if (nextPtr < this.txStart) {
|
|
1737
|
+
this.core.seek(ptr);
|
|
1738
|
+
const indexBlock = new Uint8Array(INDEX_BLOCK_SIZE);
|
|
1739
|
+
reader.readFully(indexBlock);
|
|
1740
|
+
const writer = this.core.writer();
|
|
1741
|
+
nextPtr = this.core.length();
|
|
1742
|
+
this.core.seek(nextPtr);
|
|
1743
|
+
writer.write(indexBlock);
|
|
1744
|
+
this.core.seek(slotPos);
|
|
1745
|
+
writer.write(new Slot(nextPtr, 1 /* Tag.INDEX */).toBytes());
|
|
1746
|
+
}
|
|
1747
|
+
}
|
|
1748
|
+
else if (this.header.tag === 2 /* Tag.ARRAY_LIST */) {
|
|
1749
|
+
throw new ExpectedTxStartException();
|
|
1750
|
+
}
|
|
1751
|
+
}
|
|
1752
|
+
return this.readArrayListSlot(nextPtr, key, shift - 1, writeMode, isTopLevel);
|
|
1753
|
+
}
|
|
1754
|
+
default:
|
|
1755
|
+
throw new UnexpectedTagException();
|
|
1756
|
+
}
|
|
1757
|
+
}
|
|
1758
|
+
readArrayListSlice(header, size) {
|
|
1759
|
+
const reader = this.core.reader();
|
|
1760
|
+
if (size > header.size || size < 0) {
|
|
1761
|
+
throw new KeyNotFoundException();
|
|
1762
|
+
}
|
|
1763
|
+
const prevShift = header.size < SLOT_COUNT + 1 ? 0 : Math.floor(Math.log(header.size - 1) / Math.log(SLOT_COUNT));
|
|
1764
|
+
const nextShift = size < SLOT_COUNT + 1 ? 0 : Math.floor(Math.log(size - 1) / Math.log(SLOT_COUNT));
|
|
1765
|
+
if (prevShift === nextShift) {
|
|
1766
|
+
return new ArrayListHeader(header.ptr, size);
|
|
1767
|
+
}
|
|
1768
|
+
else {
|
|
1769
|
+
let shift = prevShift;
|
|
1770
|
+
let indexPos = header.ptr;
|
|
1771
|
+
while (shift > nextShift) {
|
|
1772
|
+
this.core.seek(indexPos);
|
|
1773
|
+
const slotBytes = new Uint8Array(Slot.LENGTH);
|
|
1774
|
+
reader.readFully(slotBytes);
|
|
1775
|
+
const slot = Slot.fromBytes(slotBytes);
|
|
1776
|
+
shift -= 1;
|
|
1777
|
+
indexPos = Number(slot.value);
|
|
1778
|
+
}
|
|
1779
|
+
return new ArrayListHeader(indexPos, size);
|
|
1780
|
+
}
|
|
1781
|
+
}
|
|
1782
|
+
// linked_array_list (backed by a count-augmented B-tree)
|
|
1783
|
+
readBTreeNode(ptr) {
|
|
1784
|
+
this.core.seek(ptr);
|
|
1785
|
+
const reader = this.core.reader();
|
|
1786
|
+
const headerBytes = new Uint8Array(BTREE_NODE_HEADER_SIZE);
|
|
1787
|
+
reader.readFully(headerBytes);
|
|
1788
|
+
const kindInt = headerBytes[0];
|
|
1789
|
+
if (kindInt > BTreeNodeKind.BRANCH)
|
|
1790
|
+
throw new InvalidBTreeNodeKindException();
|
|
1791
|
+
const kind = kindInt;
|
|
1792
|
+
const num = headerBytes[1];
|
|
1793
|
+
if (num > BTREE_SLOT_COUNT)
|
|
1794
|
+
throw new InvalidBTreeNodeException();
|
|
1795
|
+
const node = new BTreeNode(kind, num);
|
|
1796
|
+
switch (kind) {
|
|
1797
|
+
case BTreeNodeKind.LEAF: {
|
|
1798
|
+
const body = new Uint8Array(Slot.LENGTH * BTREE_SLOT_COUNT);
|
|
1799
|
+
reader.readFully(body);
|
|
1800
|
+
for (let i = 0; i < BTREE_SLOT_COUNT; i++) {
|
|
1801
|
+
node.values[i] = Slot.fromBytes(body.slice(i * Slot.LENGTH, i * Slot.LENGTH + Slot.LENGTH));
|
|
1802
|
+
}
|
|
1803
|
+
break;
|
|
1804
|
+
}
|
|
1805
|
+
case BTreeNodeKind.BRANCH: {
|
|
1806
|
+
const body = new Uint8Array((Slot.LENGTH + 8) * BTREE_SLOT_COUNT);
|
|
1807
|
+
reader.readFully(body);
|
|
1808
|
+
for (let i = 0; i < BTREE_SLOT_COUNT; i++) {
|
|
1809
|
+
node.children[i] = Slot.fromBytes(body.slice(i * Slot.LENGTH, i * Slot.LENGTH + Slot.LENGTH));
|
|
1810
|
+
}
|
|
1811
|
+
const view = new DataView(body.buffer, body.byteOffset, body.byteLength);
|
|
1812
|
+
const countsOffset = Slot.LENGTH * BTREE_SLOT_COUNT;
|
|
1813
|
+
for (let i = 0; i < BTREE_SLOT_COUNT; i++) {
|
|
1814
|
+
node.counts[i] = Number(view.getBigInt64(countsOffset + i * 8, false));
|
|
1815
|
+
}
|
|
1816
|
+
break;
|
|
1817
|
+
}
|
|
1818
|
+
}
|
|
1819
|
+
return node;
|
|
1820
|
+
}
|
|
1821
|
+
// always writes the node as a block at ptr. b-tree mutations are persistent:
|
|
1822
|
+
// every node on the path from the root is rewritten, while untouched subtrees
|
|
1823
|
+
// are shared by pointer.
|
|
1824
|
+
writeBTreeNodeAt(node, ptr) {
|
|
1825
|
+
this.core.seek(ptr);
|
|
1826
|
+
const writer = this.core.writer();
|
|
1827
|
+
const bodySize = node.kind === BTreeNodeKind.LEAF ? BTREE_LEAF_BLOCK_SIZE : BTREE_BRANCH_BLOCK_SIZE;
|
|
1828
|
+
const buffer = new Uint8Array(bodySize);
|
|
1829
|
+
buffer[0] = node.kind;
|
|
1830
|
+
buffer[1] = node.num;
|
|
1831
|
+
let off = BTREE_NODE_HEADER_SIZE;
|
|
1832
|
+
switch (node.kind) {
|
|
1833
|
+
case BTreeNodeKind.LEAF:
|
|
1834
|
+
for (let i = 0; i < BTREE_SLOT_COUNT; i++) {
|
|
1835
|
+
buffer.set(node.values[i].toBytes(), off);
|
|
1836
|
+
off += Slot.LENGTH;
|
|
1837
|
+
}
|
|
1838
|
+
break;
|
|
1839
|
+
case BTreeNodeKind.BRANCH: {
|
|
1840
|
+
for (let i = 0; i < BTREE_SLOT_COUNT; i++) {
|
|
1841
|
+
buffer.set(node.children[i].toBytes(), off);
|
|
1842
|
+
off += Slot.LENGTH;
|
|
1843
|
+
}
|
|
1844
|
+
const view = new DataView(buffer.buffer, buffer.byteOffset, buffer.byteLength);
|
|
1845
|
+
for (let i = 0; i < BTREE_SLOT_COUNT; i++) {
|
|
1846
|
+
view.setBigInt64(off, BigInt(node.counts[i]), false);
|
|
1847
|
+
off += 8;
|
|
1848
|
+
}
|
|
1849
|
+
break;
|
|
1850
|
+
}
|
|
1851
|
+
}
|
|
1852
|
+
writer.write(buffer);
|
|
1853
|
+
}
|
|
1854
|
+
// appends the node as a fresh block and returns its position
|
|
1855
|
+
writeBTreeNode(node) {
|
|
1856
|
+
const ptr = this.core.length();
|
|
1857
|
+
this.writeBTreeNodeAt(node, ptr);
|
|
1858
|
+
return ptr;
|
|
1859
|
+
}
|
|
1860
|
+
// a node is safe to mutate in place when it was created in the current transaction
|
|
1861
|
+
// (offset >= txStart), since no committed moment and no pre-concat sharing can
|
|
1862
|
+
// reference it. concat advances txStart (an implicit freeze) precisely so its
|
|
1863
|
+
// shared subtrees fall below it here. for an ephemeral (non-array-list) top level
|
|
1864
|
+
// there is no transaction, so everything is mutable in place until a concat first
|
|
1865
|
+
// sets txStart.
|
|
1866
|
+
btreeReusable(ptr) {
|
|
1867
|
+
if (this.txStart !== null)
|
|
1868
|
+
return ptr >= this.txStart;
|
|
1869
|
+
return this.header.tag !== 2 /* Tag.ARRAY_LIST */;
|
|
1870
|
+
}
|
|
1871
|
+
// write a new version of a node, reusing oldPtr's position in place if that node
|
|
1872
|
+
// belongs to this transaction, otherwise appending a copy
|
|
1873
|
+
btreeWriteNode(node, oldPtr) {
|
|
1874
|
+
if (this.btreeReusable(oldPtr)) {
|
|
1875
|
+
this.writeBTreeNodeAt(node, oldPtr);
|
|
1876
|
+
return oldPtr;
|
|
1877
|
+
}
|
|
1878
|
+
return this.writeBTreeNode(node);
|
|
1879
|
+
}
|
|
1880
|
+
btreeNewRoot() {
|
|
1881
|
+
return this.writeBTreeNode(new BTreeNode(BTreeNodeKind.LEAF, 0));
|
|
1882
|
+
}
|
|
1883
|
+
// descend to the value slot at the given rank (0-based), returning a pointer to it
|
|
1884
|
+
// (its file position and current slot).
|
|
1885
|
+
readBTreeSlot(rootPtr, rank) {
|
|
1886
|
+
let nodePtr = rootPtr;
|
|
1887
|
+
let rem = rank;
|
|
1888
|
+
while (true) {
|
|
1889
|
+
const node = this.readBTreeNode(nodePtr);
|
|
1890
|
+
if (node.kind === BTreeNodeKind.LEAF) {
|
|
1891
|
+
const position = nodePtr + BTREE_NODE_HEADER_SIZE + rem * Slot.LENGTH;
|
|
1892
|
+
return new SlotPointer(position, node.values[rem]);
|
|
1893
|
+
}
|
|
1894
|
+
else {
|
|
1895
|
+
let i = 0;
|
|
1896
|
+
while (i + 1 < node.num && rem >= node.counts[i]) {
|
|
1897
|
+
rem -= node.counts[i];
|
|
1898
|
+
i++;
|
|
1899
|
+
}
|
|
1900
|
+
nodePtr = Number(node.children[i].value);
|
|
1901
|
+
}
|
|
1902
|
+
}
|
|
1903
|
+
}
|
|
1904
|
+
// insert a placeholder slot at `rank` within the subtree at nodePtr, writing new
|
|
1905
|
+
// nodes along the path. the caller fills in the value at the returned valuePosition.
|
|
1906
|
+
btreeInsert(nodePtr, rank) {
|
|
1907
|
+
const node = this.readBTreeNode(nodePtr);
|
|
1908
|
+
switch (node.kind) {
|
|
1909
|
+
case BTreeNodeKind.LEAF: {
|
|
1910
|
+
// build the entries with a placeholder spliced in at `rank`. the placeholder
|
|
1911
|
+
// is a NONE slot marked full so that, if the caller never writes a value
|
|
1912
|
+
// (e.g. appendCursor), iteration still counts it as an element rather than
|
|
1913
|
+
// skipping it as padding.
|
|
1914
|
+
const r = rank;
|
|
1915
|
+
const vals = [];
|
|
1916
|
+
for (let k = 0; k < r; k++)
|
|
1917
|
+
vals.push(node.values[k]);
|
|
1918
|
+
vals.push(new Slot(0, 0 /* Tag.NONE */, true));
|
|
1919
|
+
for (let k = r; k < node.num; k++)
|
|
1920
|
+
vals.push(node.values[k]);
|
|
1921
|
+
const total = node.num + 1;
|
|
1922
|
+
if (total <= BTREE_SLOT_COUNT) {
|
|
1923
|
+
const leaf = new BTreeNode(BTreeNodeKind.LEAF, total);
|
|
1924
|
+
for (let k = 0; k < total; k++)
|
|
1925
|
+
leaf.values[k] = vals[k];
|
|
1926
|
+
const ptr = this.btreeWriteNode(leaf, nodePtr);
|
|
1927
|
+
return new BTreeInsertResult(ptr, total, ptr + BTREE_NODE_HEADER_SIZE + r * Slot.LENGTH, null);
|
|
1928
|
+
}
|
|
1929
|
+
// overflow: split into two leaves (reuse this node for the left half)
|
|
1930
|
+
const leftN = BTREE_SPLIT_COUNT;
|
|
1931
|
+
const rightN = total - leftN;
|
|
1932
|
+
const left = new BTreeNode(BTreeNodeKind.LEAF, leftN);
|
|
1933
|
+
for (let k = 0; k < leftN; k++)
|
|
1934
|
+
left.values[k] = vals[k];
|
|
1935
|
+
const right = new BTreeNode(BTreeNodeKind.LEAF, rightN);
|
|
1936
|
+
for (let k = 0; k < rightN; k++)
|
|
1937
|
+
right.values[k] = vals[leftN + k];
|
|
1938
|
+
const leftPtr = this.btreeWriteNode(left, nodePtr);
|
|
1939
|
+
const rightPtr = this.writeBTreeNode(right);
|
|
1940
|
+
const valuePosition = r < leftN
|
|
1941
|
+
? leftPtr + BTREE_NODE_HEADER_SIZE + r * Slot.LENGTH
|
|
1942
|
+
: rightPtr + BTREE_NODE_HEADER_SIZE + (r - leftN) * Slot.LENGTH;
|
|
1943
|
+
return new BTreeInsertResult(leftPtr, leftN, valuePosition, new BTreeNodeRef(rightPtr, rightN));
|
|
1944
|
+
}
|
|
1945
|
+
case BTreeNodeKind.BRANCH: {
|
|
1946
|
+
// pick the child that contains `rank`
|
|
1947
|
+
let i = 0;
|
|
1948
|
+
let rem = rank;
|
|
1949
|
+
while (i + 1 < node.num && rem > node.counts[i]) {
|
|
1950
|
+
rem -= node.counts[i];
|
|
1951
|
+
i++;
|
|
1952
|
+
}
|
|
1953
|
+
const child = this.btreeInsert(Number(node.children[i].value), rem);
|
|
1954
|
+
// rebuild this branch with the (possibly split) child
|
|
1955
|
+
const children = [];
|
|
1956
|
+
const counts = [];
|
|
1957
|
+
for (let k = 0; k < node.num; k++) {
|
|
1958
|
+
children.push(node.children[k]);
|
|
1959
|
+
counts.push(node.counts[k]);
|
|
1960
|
+
}
|
|
1961
|
+
children[i] = new Slot(child.nodePtr, 1 /* Tag.INDEX */);
|
|
1962
|
+
counts[i] = child.count;
|
|
1963
|
+
let total = node.num;
|
|
1964
|
+
if (child.split !== null) {
|
|
1965
|
+
children.splice(i + 1, 0, new Slot(child.split.nodePtr, 1 /* Tag.INDEX */));
|
|
1966
|
+
counts.splice(i + 1, 0, child.split.count);
|
|
1967
|
+
total = node.num + 1;
|
|
1968
|
+
}
|
|
1969
|
+
if (total <= BTREE_SLOT_COUNT) {
|
|
1970
|
+
const branch = new BTreeNode(BTreeNodeKind.BRANCH, total);
|
|
1971
|
+
for (let k = 0; k < total; k++) {
|
|
1972
|
+
branch.children[k] = children[k];
|
|
1973
|
+
branch.counts[k] = counts[k];
|
|
1974
|
+
}
|
|
1975
|
+
const ptr = this.btreeWriteNode(branch, nodePtr);
|
|
1976
|
+
return new BTreeInsertResult(ptr, branch.subtreeCount(), child.valuePosition, null);
|
|
1977
|
+
}
|
|
1978
|
+
// overflow: split into two branches (reuse this node for the left half)
|
|
1979
|
+
const leftN = BTREE_SPLIT_COUNT;
|
|
1980
|
+
const rightN = total - leftN;
|
|
1981
|
+
const left = new BTreeNode(BTreeNodeKind.BRANCH, leftN);
|
|
1982
|
+
for (let k = 0; k < leftN; k++) {
|
|
1983
|
+
left.children[k] = children[k];
|
|
1984
|
+
left.counts[k] = counts[k];
|
|
1985
|
+
}
|
|
1986
|
+
const right = new BTreeNode(BTreeNodeKind.BRANCH, rightN);
|
|
1987
|
+
for (let k = 0; k < rightN; k++) {
|
|
1988
|
+
right.children[k] = children[leftN + k];
|
|
1989
|
+
right.counts[k] = counts[leftN + k];
|
|
1990
|
+
}
|
|
1991
|
+
const leftPtr = this.btreeWriteNode(left, nodePtr);
|
|
1992
|
+
const rightPtr = this.writeBTreeNode(right);
|
|
1993
|
+
return new BTreeInsertResult(leftPtr, left.subtreeCount(), child.valuePosition, new BTreeNodeRef(rightPtr, right.subtreeCount()));
|
|
1994
|
+
}
|
|
1995
|
+
}
|
|
1996
|
+
throw new UnreachableException();
|
|
1997
|
+
}
|
|
1998
|
+
// turn an insert result into a root pointer, growing the tree a level if the old
|
|
1999
|
+
// root split (shares the root-building logic with btreeMakeRoot)
|
|
2000
|
+
btreeGrowRoot(result) {
|
|
2001
|
+
return this.btreeMakeRoot(new BTreeJoinResult(result.nodePtr, result.count, result.split));
|
|
2002
|
+
}
|
|
2003
|
+
// descend to the value slot at `rank` for writing, copy-on-writing only the nodes
|
|
2004
|
+
// that belong to a past transaction. the element count is unchanged, so when the
|
|
2005
|
+
// whole path is already this-transaction nothing is rewritten and the caller writes
|
|
2006
|
+
// straight into the existing leaf.
|
|
2007
|
+
btreeGetForWrite(nodePtr, rank) {
|
|
2008
|
+
const node = this.readBTreeNode(nodePtr);
|
|
2009
|
+
switch (node.kind) {
|
|
2010
|
+
case BTreeNodeKind.LEAF: {
|
|
2011
|
+
const newPtr = this.btreeReusable(nodePtr) ? nodePtr : this.writeBTreeNode(node);
|
|
2012
|
+
return new BTreeWriteSlot(newPtr, newPtr + BTREE_NODE_HEADER_SIZE + rank * Slot.LENGTH, node.values[rank]);
|
|
2013
|
+
}
|
|
2014
|
+
case BTreeNodeKind.BRANCH: {
|
|
2015
|
+
let i = 0;
|
|
2016
|
+
let rem = rank;
|
|
2017
|
+
while (i + 1 < node.num && rem >= node.counts[i]) {
|
|
2018
|
+
rem -= node.counts[i];
|
|
2019
|
+
i++;
|
|
2020
|
+
}
|
|
2021
|
+
const childPtr = Number(node.children[i].value);
|
|
2022
|
+
const child = this.btreeGetForWrite(childPtr, rem);
|
|
2023
|
+
// if the child stayed put, this branch is unchanged too
|
|
2024
|
+
if (child.nodePtr === childPtr) {
|
|
2025
|
+
return new BTreeWriteSlot(nodePtr, child.valuePosition, child.slot);
|
|
2026
|
+
}
|
|
2027
|
+
node.children[i] = new Slot(child.nodePtr, 1 /* Tag.INDEX */);
|
|
2028
|
+
const newPtr = this.btreeWriteNode(node, nodePtr);
|
|
2029
|
+
return new BTreeWriteSlot(newPtr, child.valuePosition, child.slot);
|
|
2030
|
+
}
|
|
2031
|
+
}
|
|
2032
|
+
throw new UnreachableException();
|
|
2033
|
+
}
|
|
2034
|
+
// join (concat): a true O(log n), structure-sharing concatenation of two trees where
|
|
2035
|
+
// every element of `a` precedes every element of `b`. unlike the rebuild helpers
|
|
2036
|
+
// above, untouched subtrees are shared by pointer, so concatenating a list with
|
|
2037
|
+
// itself stays cheap.
|
|
2038
|
+
// height of a tree = number of branch levels above the leaves
|
|
2039
|
+
btreeHeight(rootPtr) {
|
|
2040
|
+
let ptr = rootPtr;
|
|
2041
|
+
let height = 0;
|
|
2042
|
+
while (true) {
|
|
2043
|
+
const node = this.readBTreeNode(ptr);
|
|
2044
|
+
if (node.kind === BTreeNodeKind.LEAF)
|
|
2045
|
+
return height;
|
|
2046
|
+
height++;
|
|
2047
|
+
ptr = Number(node.children[0].value);
|
|
2048
|
+
}
|
|
2049
|
+
}
|
|
2050
|
+
btreeMakeRoot(result) {
|
|
2051
|
+
if (result.split !== null) {
|
|
2052
|
+
const root = new BTreeNode(BTreeNodeKind.BRANCH, 2);
|
|
2053
|
+
root.children[0] = new Slot(result.nodePtr, 1 /* Tag.INDEX */);
|
|
2054
|
+
root.children[1] = new Slot(result.split.nodePtr, 1 /* Tag.INDEX */);
|
|
2055
|
+
root.counts[0] = result.count;
|
|
2056
|
+
root.counts[1] = result.split.count;
|
|
2057
|
+
return this.writeBTreeNode(root);
|
|
2058
|
+
}
|
|
2059
|
+
return result.nodePtr;
|
|
2060
|
+
}
|
|
2061
|
+
// write `vals` as one leaf, or split into two balanced leaves if it exceeds the node
|
|
2062
|
+
// capacity
|
|
2063
|
+
btreeAssembleLeaf(vals, total) {
|
|
2064
|
+
if (total <= BTREE_SLOT_COUNT) {
|
|
2065
|
+
const leaf = new BTreeNode(BTreeNodeKind.LEAF, total);
|
|
2066
|
+
for (let k = 0; k < total; k++)
|
|
2067
|
+
leaf.values[k] = vals[k];
|
|
2068
|
+
return new BTreeJoinResult(this.writeBTreeNode(leaf), total, null);
|
|
2069
|
+
}
|
|
2070
|
+
const leftN = Math.floor(total / 2);
|
|
2071
|
+
const left = new BTreeNode(BTreeNodeKind.LEAF, leftN);
|
|
2072
|
+
for (let k = 0; k < leftN; k++)
|
|
2073
|
+
left.values[k] = vals[k];
|
|
2074
|
+
const right = new BTreeNode(BTreeNodeKind.LEAF, total - leftN);
|
|
2075
|
+
for (let k = 0; k < total - leftN; k++)
|
|
2076
|
+
right.values[k] = vals[leftN + k];
|
|
2077
|
+
return new BTreeJoinResult(this.writeBTreeNode(left), leftN, new BTreeNodeRef(this.writeBTreeNode(right), total - leftN));
|
|
2078
|
+
}
|
|
2079
|
+
// write `children`/`counts` as one branch, or split into two balanced branches
|
|
2080
|
+
btreeAssembleBranch(children, counts, total) {
|
|
2081
|
+
if (total <= BTREE_SLOT_COUNT) {
|
|
2082
|
+
const branch = new BTreeNode(BTreeNodeKind.BRANCH, total);
|
|
2083
|
+
for (let k = 0; k < total; k++) {
|
|
2084
|
+
branch.children[k] = children[k];
|
|
2085
|
+
branch.counts[k] = counts[k];
|
|
2086
|
+
}
|
|
2087
|
+
return new BTreeJoinResult(this.writeBTreeNode(branch), branch.subtreeCount(), null);
|
|
2088
|
+
}
|
|
2089
|
+
const leftN = Math.floor(total / 2);
|
|
2090
|
+
const left = new BTreeNode(BTreeNodeKind.BRANCH, leftN);
|
|
2091
|
+
for (let k = 0; k < leftN; k++) {
|
|
2092
|
+
left.children[k] = children[k];
|
|
2093
|
+
left.counts[k] = counts[k];
|
|
2094
|
+
}
|
|
2095
|
+
const right = new BTreeNode(BTreeNodeKind.BRANCH, total - leftN);
|
|
2096
|
+
for (let k = 0; k < total - leftN; k++) {
|
|
2097
|
+
right.children[k] = children[leftN + k];
|
|
2098
|
+
right.counts[k] = counts[leftN + k];
|
|
2099
|
+
}
|
|
2100
|
+
return new BTreeJoinResult(this.writeBTreeNode(left), left.subtreeCount(), new BTreeNodeRef(this.writeBTreeNode(right), right.subtreeCount()));
|
|
2101
|
+
}
|
|
2102
|
+
// merge two nodes of equal height (a precedes b) into one or two nodes
|
|
2103
|
+
btreeMergeNodes(a, b) {
|
|
2104
|
+
switch (a.kind) {
|
|
2105
|
+
case BTreeNodeKind.LEAF: {
|
|
2106
|
+
const vals = [];
|
|
2107
|
+
for (let k = 0; k < a.num; k++)
|
|
2108
|
+
vals.push(a.values[k]);
|
|
2109
|
+
for (let k = 0; k < b.num; k++)
|
|
2110
|
+
vals.push(b.values[k]);
|
|
2111
|
+
return this.btreeAssembleLeaf(vals, a.num + b.num);
|
|
2112
|
+
}
|
|
2113
|
+
case BTreeNodeKind.BRANCH: {
|
|
2114
|
+
const children = [];
|
|
2115
|
+
const counts = [];
|
|
2116
|
+
for (let k = 0; k < a.num; k++) {
|
|
2117
|
+
children.push(a.children[k]);
|
|
2118
|
+
counts.push(a.counts[k]);
|
|
2119
|
+
}
|
|
2120
|
+
for (let k = 0; k < b.num; k++) {
|
|
2121
|
+
children.push(b.children[k]);
|
|
2122
|
+
counts.push(b.counts[k]);
|
|
2123
|
+
}
|
|
2124
|
+
return this.btreeAssembleBranch(children, counts, a.num + b.num);
|
|
2125
|
+
}
|
|
2126
|
+
}
|
|
2127
|
+
throw new UnreachableException();
|
|
2128
|
+
}
|
|
2129
|
+
// join b (shorter) into the rightmost spine of a (taller), at height hb
|
|
2130
|
+
btreeJoinRight(aPtr, ha, bPtr, hb) {
|
|
2131
|
+
const a = this.readBTreeNode(aPtr);
|
|
2132
|
+
const last = a.num - 1;
|
|
2133
|
+
const sub = ha - 1 === hb
|
|
2134
|
+
? this.btreeMergeNodes(this.readBTreeNode(Number(a.children[last].value)), this.readBTreeNode(bPtr))
|
|
2135
|
+
: this.btreeJoinRight(Number(a.children[last].value), ha - 1, bPtr, hb);
|
|
2136
|
+
const children = [];
|
|
2137
|
+
const counts = [];
|
|
2138
|
+
for (let k = 0; k < a.num; k++) {
|
|
2139
|
+
children.push(a.children[k]);
|
|
2140
|
+
counts.push(a.counts[k]);
|
|
2141
|
+
}
|
|
2142
|
+
children[last] = new Slot(sub.nodePtr, 1 /* Tag.INDEX */);
|
|
2143
|
+
counts[last] = sub.count;
|
|
2144
|
+
let total = a.num;
|
|
2145
|
+
if (sub.split !== null) {
|
|
2146
|
+
children[total] = new Slot(sub.split.nodePtr, 1 /* Tag.INDEX */);
|
|
2147
|
+
counts[total] = sub.split.count;
|
|
2148
|
+
total += 1;
|
|
2149
|
+
}
|
|
2150
|
+
return this.btreeAssembleBranch(children, counts, total);
|
|
2151
|
+
}
|
|
2152
|
+
// join a (shorter) into the leftmost spine of b (taller), at height ha
|
|
2153
|
+
btreeJoinLeft(aPtr, ha, bPtr, hb) {
|
|
2154
|
+
const b = this.readBTreeNode(bPtr);
|
|
2155
|
+
const sub = hb - 1 === ha
|
|
2156
|
+
? this.btreeMergeNodes(this.readBTreeNode(aPtr), this.readBTreeNode(Number(b.children[0].value)))
|
|
2157
|
+
: this.btreeJoinLeft(aPtr, ha, Number(b.children[0].value), hb - 1);
|
|
2158
|
+
const children = [];
|
|
2159
|
+
const counts = [];
|
|
2160
|
+
children[0] = new Slot(sub.nodePtr, 1 /* Tag.INDEX */);
|
|
2161
|
+
counts[0] = sub.count;
|
|
2162
|
+
let head = 1;
|
|
2163
|
+
if (sub.split !== null) {
|
|
2164
|
+
children[1] = new Slot(sub.split.nodePtr, 1 /* Tag.INDEX */);
|
|
2165
|
+
counts[1] = sub.split.count;
|
|
2166
|
+
head = 2;
|
|
2167
|
+
}
|
|
2168
|
+
for (let k = 0; k < b.num - 1; k++) {
|
|
2169
|
+
children[head + k] = b.children[1 + k];
|
|
2170
|
+
counts[head + k] = b.counts[1 + k];
|
|
2171
|
+
}
|
|
2172
|
+
return this.btreeAssembleBranch(children, counts, head + b.num - 1);
|
|
2173
|
+
}
|
|
2174
|
+
btreeJoin(rootA, rootB) {
|
|
2175
|
+
const ha = this.btreeHeight(rootA);
|
|
2176
|
+
const hb = this.btreeHeight(rootB);
|
|
2177
|
+
let result;
|
|
2178
|
+
if (ha === hb) {
|
|
2179
|
+
result = this.btreeMergeNodes(this.readBTreeNode(rootA), this.readBTreeNode(rootB));
|
|
2180
|
+
}
|
|
2181
|
+
else if (ha > hb) {
|
|
2182
|
+
result = this.btreeJoinRight(rootA, ha, rootB, hb);
|
|
2183
|
+
}
|
|
2184
|
+
else {
|
|
2185
|
+
result = this.btreeJoinLeft(rootA, ha, rootB, hb);
|
|
2186
|
+
}
|
|
2187
|
+
return this.btreeMakeRoot(result);
|
|
2188
|
+
}
|
|
2189
|
+
// split (used by slice and remove): a true O(log n), structure-sharing split of a
|
|
2190
|
+
// tree into [0, rank) and [rank, size). partial nodes along the path are reassembled
|
|
2191
|
+
// with join, so the result trees stay balanced.
|
|
2192
|
+
// build a tree from a run of sibling children (already height-h-1 subtrees): empty ->
|
|
2193
|
+
// a new empty leaf, one -> that child unwrapped, many -> a branch
|
|
2194
|
+
btreeSubtree(children, counts, start, len) {
|
|
2195
|
+
if (len === 0)
|
|
2196
|
+
return this.btreeNewRoot();
|
|
2197
|
+
if (len === 1)
|
|
2198
|
+
return Number(children[start].value);
|
|
2199
|
+
// len <= BTREE_SLOT_COUNT here, so this never splits
|
|
2200
|
+
const subChildren = [];
|
|
2201
|
+
const subCounts = [];
|
|
2202
|
+
for (let k = 0; k < len; k++) {
|
|
2203
|
+
subChildren.push(children[start + k]);
|
|
2204
|
+
subCounts.push(counts[start + k]);
|
|
2205
|
+
}
|
|
2206
|
+
return this.btreeAssembleBranch(subChildren, subCounts, len).nodePtr;
|
|
2207
|
+
}
|
|
2208
|
+
btreeSplit(rootPtr, rank) {
|
|
2209
|
+
const node = this.readBTreeNode(rootPtr);
|
|
2210
|
+
switch (node.kind) {
|
|
2211
|
+
case BTreeNodeKind.LEAF: {
|
|
2212
|
+
const r = rank;
|
|
2213
|
+
const left = new BTreeNode(BTreeNodeKind.LEAF, r);
|
|
2214
|
+
for (let k = 0; k < r; k++)
|
|
2215
|
+
left.values[k] = node.values[k];
|
|
2216
|
+
const right = new BTreeNode(BTreeNodeKind.LEAF, node.num - r);
|
|
2217
|
+
for (let k = 0; k < node.num - r; k++)
|
|
2218
|
+
right.values[k] = node.values[r + k];
|
|
2219
|
+
return new BTreeSplitResult(this.writeBTreeNode(left), this.writeBTreeNode(right));
|
|
2220
|
+
}
|
|
2221
|
+
case BTreeNodeKind.BRANCH: {
|
|
2222
|
+
let i = 0;
|
|
2223
|
+
let rem = rank;
|
|
2224
|
+
while (i + 1 < node.num && rem > node.counts[i]) {
|
|
2225
|
+
rem -= node.counts[i];
|
|
2226
|
+
i++;
|
|
2227
|
+
}
|
|
2228
|
+
const child = this.btreeSplit(Number(node.children[i].value), rem);
|
|
2229
|
+
const leftSub = this.btreeSubtree(node.children, node.counts, 0, i);
|
|
2230
|
+
const rightSub = this.btreeSubtree(node.children, node.counts, i + 1, node.num - (i + 1));
|
|
2231
|
+
return new BTreeSplitResult(this.btreeJoin(leftSub, child.left), this.btreeJoin(child.right, rightSub));
|
|
2232
|
+
}
|
|
2233
|
+
}
|
|
2234
|
+
throw new UnreachableException();
|
|
2235
|
+
}
|
|
2236
|
+
// sorted_map / sorted_set
|
|
2237
|
+
readSortedNode(ptr) {
|
|
2238
|
+
this.core.seek(ptr);
|
|
2239
|
+
const reader = this.core.reader();
|
|
2240
|
+
const headerBytes = new Uint8Array(BTREE_NODE_HEADER_SIZE);
|
|
2241
|
+
reader.readFully(headerBytes);
|
|
2242
|
+
const kindInt = headerBytes[0];
|
|
2243
|
+
if (kindInt > BTreeNodeKind.BRANCH)
|
|
2244
|
+
throw new InvalidBTreeNodeKindException();
|
|
2245
|
+
const kind = kindInt;
|
|
2246
|
+
const num = headerBytes[1];
|
|
2247
|
+
if (num > BTREE_SLOT_COUNT)
|
|
2248
|
+
throw new InvalidBTreeNodeException();
|
|
2249
|
+
const node = new SortedNode(kind, num);
|
|
2250
|
+
switch (kind) {
|
|
2251
|
+
case BTreeNodeKind.LEAF: {
|
|
2252
|
+
const body = new Uint8Array(Slot.LENGTH * BTREE_SLOT_COUNT);
|
|
2253
|
+
reader.readFully(body);
|
|
2254
|
+
for (let i = 0; i < BTREE_SLOT_COUNT; i++) {
|
|
2255
|
+
node.entries[i] = Slot.fromBytes(body.slice(i * Slot.LENGTH, i * Slot.LENGTH + Slot.LENGTH));
|
|
2256
|
+
}
|
|
2257
|
+
break;
|
|
2258
|
+
}
|
|
2259
|
+
case BTreeNodeKind.BRANCH: {
|
|
2260
|
+
const body = new Uint8Array((Slot.LENGTH * 2 + 8) * BTREE_SLOT_COUNT);
|
|
2261
|
+
reader.readFully(body);
|
|
2262
|
+
for (let i = 0; i < BTREE_SLOT_COUNT; i++) {
|
|
2263
|
+
node.children[i] = Slot.fromBytes(body.slice(i * Slot.LENGTH, i * Slot.LENGTH + Slot.LENGTH));
|
|
2264
|
+
}
|
|
2265
|
+
const sepOffset = Slot.LENGTH * BTREE_SLOT_COUNT;
|
|
2266
|
+
for (let i = 0; i < BTREE_SLOT_COUNT; i++) {
|
|
2267
|
+
node.separators[i] = Slot.fromBytes(body.slice(sepOffset + i * Slot.LENGTH, sepOffset + i * Slot.LENGTH + Slot.LENGTH));
|
|
2268
|
+
}
|
|
2269
|
+
const view = new DataView(body.buffer, body.byteOffset, body.byteLength);
|
|
2270
|
+
const countsOffset = Slot.LENGTH * 2 * BTREE_SLOT_COUNT;
|
|
2271
|
+
for (let i = 0; i < BTREE_SLOT_COUNT; i++) {
|
|
2272
|
+
node.counts[i] = Number(view.getBigInt64(countsOffset + i * 8, false));
|
|
2273
|
+
}
|
|
2274
|
+
break;
|
|
2275
|
+
}
|
|
2276
|
+
}
|
|
2277
|
+
return node;
|
|
2278
|
+
}
|
|
2279
|
+
writeSortedNodeAt(node, ptr) {
|
|
2280
|
+
this.core.seek(ptr);
|
|
2281
|
+
const writer = this.core.writer();
|
|
2282
|
+
const bodySize = node.kind === BTreeNodeKind.LEAF ? SORTED_LEAF_BLOCK_SIZE : SORTED_BRANCH_BLOCK_SIZE;
|
|
2283
|
+
const buffer = new Uint8Array(bodySize);
|
|
2284
|
+
buffer[0] = node.kind;
|
|
2285
|
+
buffer[1] = node.num;
|
|
2286
|
+
let off = BTREE_NODE_HEADER_SIZE;
|
|
2287
|
+
switch (node.kind) {
|
|
2288
|
+
case BTreeNodeKind.LEAF:
|
|
2289
|
+
for (let i = 0; i < BTREE_SLOT_COUNT; i++) {
|
|
2290
|
+
buffer.set(node.entries[i].toBytes(), off);
|
|
2291
|
+
off += Slot.LENGTH;
|
|
2292
|
+
}
|
|
2293
|
+
break;
|
|
2294
|
+
case BTreeNodeKind.BRANCH: {
|
|
2295
|
+
for (let i = 0; i < BTREE_SLOT_COUNT; i++) {
|
|
2296
|
+
buffer.set(node.children[i].toBytes(), off);
|
|
2297
|
+
off += Slot.LENGTH;
|
|
2298
|
+
}
|
|
2299
|
+
for (let i = 0; i < BTREE_SLOT_COUNT; i++) {
|
|
2300
|
+
buffer.set(node.separators[i].toBytes(), off);
|
|
2301
|
+
off += Slot.LENGTH;
|
|
2302
|
+
}
|
|
2303
|
+
const view = new DataView(buffer.buffer, buffer.byteOffset, buffer.byteLength);
|
|
2304
|
+
for (let i = 0; i < BTREE_SLOT_COUNT; i++) {
|
|
2305
|
+
view.setBigInt64(off, BigInt(node.counts[i]), false);
|
|
2306
|
+
off += 8;
|
|
2307
|
+
}
|
|
2308
|
+
break;
|
|
2309
|
+
}
|
|
2310
|
+
}
|
|
2311
|
+
writer.write(buffer);
|
|
2312
|
+
}
|
|
2313
|
+
writeSortedNode(node) {
|
|
2314
|
+
const ptr = this.core.length();
|
|
2315
|
+
this.writeSortedNodeAt(node, ptr);
|
|
2316
|
+
return ptr;
|
|
2317
|
+
}
|
|
2318
|
+
// reuse oldPtr's position in place when it belongs to this transaction
|
|
2319
|
+
// (mirrors btreeWriteNode / the txStart path-copying model)
|
|
2320
|
+
sortedWriteNode(node, oldPtr) {
|
|
2321
|
+
if (this.btreeReusable(oldPtr)) {
|
|
2322
|
+
this.writeSortedNodeAt(node, oldPtr);
|
|
2323
|
+
return oldPtr;
|
|
2324
|
+
}
|
|
2325
|
+
return this.writeSortedNode(node);
|
|
2326
|
+
}
|
|
2327
|
+
readKvPair(kvSlot) {
|
|
2328
|
+
if (kvSlot.tag !== 5 /* Tag.KV_PAIR */)
|
|
2329
|
+
throw new UnexpectedTagException();
|
|
2330
|
+
this.core.seek(Number(kvSlot.value));
|
|
2331
|
+
const reader = this.core.reader();
|
|
2332
|
+
const bytes = new Uint8Array(KeyValuePair.length(this.header.hashSize));
|
|
2333
|
+
reader.readFully(bytes);
|
|
2334
|
+
return KeyValuePair.fromBytes(bytes, this.header.hashSize);
|
|
2335
|
+
}
|
|
2336
|
+
// lexicographic comparison of the byte key stored at keySlot (a bytes or short_bytes
|
|
2337
|
+
// slot) against the in-memory target. returns <0, 0, or >0. streams external bytes so
|
|
2338
|
+
// keys of any length work without allocation.
|
|
2339
|
+
compareKey(keySlot, target) {
|
|
2340
|
+
switch (keySlot.tag) {
|
|
2341
|
+
case 7 /* Tag.SHORT_BYTES */: {
|
|
2342
|
+
const buf = new Uint8Array(8);
|
|
2343
|
+
new DataView(buf.buffer).setBigInt64(0, keySlot.value, false);
|
|
2344
|
+
const total = keySlot.full ? 6 : 8;
|
|
2345
|
+
let len = total;
|
|
2346
|
+
for (let i = 0; i < total; i++) {
|
|
2347
|
+
if (buf[i] === 0) {
|
|
2348
|
+
len = i;
|
|
2349
|
+
break;
|
|
2350
|
+
}
|
|
2351
|
+
}
|
|
2352
|
+
return compareBytesUnsigned(buf.subarray(0, len), target);
|
|
2353
|
+
}
|
|
2354
|
+
case 6 /* Tag.BYTES */: {
|
|
2355
|
+
const reader = this.core.reader();
|
|
2356
|
+
this.core.seek(Number(keySlot.value));
|
|
2357
|
+
const len = reader.readLong();
|
|
2358
|
+
let i = 0;
|
|
2359
|
+
while (i < len) {
|
|
2360
|
+
const n = Math.min(256, len - i);
|
|
2361
|
+
const chunk = new Uint8Array(n);
|
|
2362
|
+
reader.readFully(chunk);
|
|
2363
|
+
for (let j = 0; j < n; j++) {
|
|
2364
|
+
const ti = i + j;
|
|
2365
|
+
if (ti >= target.length)
|
|
2366
|
+
return 1; // stored has more, equal so far
|
|
2367
|
+
const b = chunk[j];
|
|
2368
|
+
const t = target[ti];
|
|
2369
|
+
if (b < t)
|
|
2370
|
+
return -1;
|
|
2371
|
+
if (b > t)
|
|
2372
|
+
return 1;
|
|
2373
|
+
}
|
|
2374
|
+
i += n;
|
|
2375
|
+
}
|
|
2376
|
+
return target.length > len ? -1 : 0;
|
|
2377
|
+
}
|
|
2378
|
+
default:
|
|
2379
|
+
throw new UnexpectedTagException();
|
|
2380
|
+
}
|
|
2381
|
+
}
|
|
2382
|
+
// descend by key to the matching leaf entry (the .kv_pair slot), or null
|
|
2383
|
+
sortedGet(rootPtr, key) {
|
|
2384
|
+
let nodePtr = rootPtr;
|
|
2385
|
+
while (true) {
|
|
2386
|
+
const node = this.readSortedNode(nodePtr);
|
|
2387
|
+
if (node.kind === BTreeNodeKind.LEAF) {
|
|
2388
|
+
for (let i = 0; i < node.num; i++) {
|
|
2389
|
+
const entry = node.entries[i];
|
|
2390
|
+
const kv = this.readKvPair(entry);
|
|
2391
|
+
const cmp = this.compareKey(kv.keySlot, key);
|
|
2392
|
+
if (cmp === 0)
|
|
2393
|
+
return new SortedSlot(entry, nodePtr + BTREE_NODE_HEADER_SIZE + i * Slot.LENGTH);
|
|
2394
|
+
if (cmp > 0)
|
|
2395
|
+
return null;
|
|
2396
|
+
}
|
|
2397
|
+
return null;
|
|
2398
|
+
}
|
|
2399
|
+
else {
|
|
2400
|
+
let i = 0;
|
|
2401
|
+
while (i + 1 < node.num && this.compareKey(node.separators[i + 1], key) <= 0)
|
|
2402
|
+
i++;
|
|
2403
|
+
nodePtr = Number(node.children[i].value);
|
|
2404
|
+
}
|
|
2405
|
+
}
|
|
2406
|
+
}
|
|
2407
|
+
// descend by rank to the leaf entry at the given 0-based index
|
|
2408
|
+
sortedGetByIndex(rootPtr, rank) {
|
|
2409
|
+
let nodePtr = rootPtr;
|
|
2410
|
+
let rem = rank;
|
|
2411
|
+
while (true) {
|
|
2412
|
+
const node = this.readSortedNode(nodePtr);
|
|
2413
|
+
if (node.kind === BTreeNodeKind.LEAF) {
|
|
2414
|
+
const i = rem;
|
|
2415
|
+
return new SortedSlot(node.entries[i], nodePtr + BTREE_NODE_HEADER_SIZE + i * Slot.LENGTH);
|
|
2416
|
+
}
|
|
2417
|
+
else {
|
|
2418
|
+
let i = 0;
|
|
2419
|
+
while (i + 1 < node.num && rem >= node.counts[i]) {
|
|
2420
|
+
rem -= node.counts[i];
|
|
2421
|
+
i++;
|
|
2422
|
+
}
|
|
2423
|
+
nodePtr = Number(node.children[i].value);
|
|
2424
|
+
}
|
|
2425
|
+
}
|
|
2426
|
+
}
|
|
2427
|
+
// number of keys strictly less than key (the inverse of getByIndex)
|
|
2428
|
+
sortedRank(rootPtr, key) {
|
|
2429
|
+
let nodePtr = rootPtr;
|
|
2430
|
+
let rank = 0;
|
|
2431
|
+
while (true) {
|
|
2432
|
+
const node = this.readSortedNode(nodePtr);
|
|
2433
|
+
if (node.kind === BTreeNodeKind.LEAF) {
|
|
2434
|
+
for (let i = 0; i < node.num; i++) {
|
|
2435
|
+
const kv = this.readKvPair(node.entries[i]);
|
|
2436
|
+
if (this.compareKey(kv.keySlot, key) < 0)
|
|
2437
|
+
rank += 1;
|
|
2438
|
+
else
|
|
2439
|
+
break;
|
|
2440
|
+
}
|
|
2441
|
+
return rank;
|
|
2442
|
+
}
|
|
2443
|
+
else {
|
|
2444
|
+
let i = 0;
|
|
2445
|
+
while (i + 1 < node.num && this.compareKey(node.separators[i + 1], key) <= 0) {
|
|
2446
|
+
rank += node.counts[i];
|
|
2447
|
+
i++;
|
|
2448
|
+
}
|
|
2449
|
+
nodePtr = Number(node.children[i].value);
|
|
2450
|
+
}
|
|
2451
|
+
}
|
|
2452
|
+
}
|
|
2453
|
+
// write a byte key as a short_bytes (inline, <=8 bytes, no interior zero) or external
|
|
2454
|
+
// bytes slot
|
|
2455
|
+
writeKey(key) {
|
|
2456
|
+
let hasZero = false;
|
|
2457
|
+
for (const b of key) {
|
|
2458
|
+
if (b === 0) {
|
|
2459
|
+
hasZero = true;
|
|
2460
|
+
break;
|
|
2461
|
+
}
|
|
2462
|
+
}
|
|
2463
|
+
if (key.length <= 8 && !hasZero) {
|
|
2464
|
+
const value = new Uint8Array(8);
|
|
2465
|
+
value.set(key, 0);
|
|
2466
|
+
const v = new DataView(value.buffer).getBigInt64(0, false);
|
|
2467
|
+
return new Slot(v, 7 /* Tag.SHORT_BYTES */);
|
|
2468
|
+
}
|
|
2469
|
+
const writer = this.core.writer();
|
|
2470
|
+
const pos = this.core.length();
|
|
2471
|
+
this.core.seek(pos);
|
|
2472
|
+
writer.writeLong(key.length);
|
|
2473
|
+
writer.write(key);
|
|
2474
|
+
return new Slot(pos, 6 /* Tag.BYTES */);
|
|
2475
|
+
}
|
|
2476
|
+
// materialize a new leaf entry: write the key bytes and a KeyValuePair with an empty
|
|
2477
|
+
// value (the caller fills it via valuePosition). the hash field is unused by sorted
|
|
2478
|
+
// maps (navigation is by key bytes), so it is left zero.
|
|
2479
|
+
sortedNewEntry(key) {
|
|
2480
|
+
const keySlot = this.writeKey(key);
|
|
2481
|
+
const writer = this.core.writer();
|
|
2482
|
+
const kvPos = this.core.length();
|
|
2483
|
+
const kvPair = new KeyValuePair(new Slot(), keySlot, new Uint8Array(this.header.hashSize));
|
|
2484
|
+
this.core.seek(kvPos);
|
|
2485
|
+
writer.write(kvPair.toBytes());
|
|
2486
|
+
return new SortedEntry(new Slot(kvPos, 5 /* Tag.KV_PAIR */), keySlot, kvPos + this.header.hashSize + Slot.LENGTH);
|
|
2487
|
+
}
|
|
2488
|
+
// insert key (or locate it for replacement) within the subtree at nodePtr,
|
|
2489
|
+
// path-copying nodes and maintaining separators + counts. the caller writes the value
|
|
2490
|
+
// at the returned valuePosition.
|
|
2491
|
+
sortedPut(nodePtr, key) {
|
|
2492
|
+
const node = this.readSortedNode(nodePtr);
|
|
2493
|
+
const writer = this.core.writer();
|
|
2494
|
+
switch (node.kind) {
|
|
2495
|
+
case BTreeNodeKind.LEAF: {
|
|
2496
|
+
// find the matching or insertion index
|
|
2497
|
+
let idx = node.num;
|
|
2498
|
+
let found = false;
|
|
2499
|
+
for (let i = 0; i < node.num; i++) {
|
|
2500
|
+
const kv = this.readKvPair(node.entries[i]);
|
|
2501
|
+
const cmp = this.compareKey(kv.keySlot, key);
|
|
2502
|
+
if (cmp === 0) {
|
|
2503
|
+
idx = i;
|
|
2504
|
+
found = true;
|
|
2505
|
+
break;
|
|
2506
|
+
}
|
|
2507
|
+
if (cmp > 0) {
|
|
2508
|
+
idx = i;
|
|
2509
|
+
break;
|
|
2510
|
+
}
|
|
2511
|
+
}
|
|
2512
|
+
if (found) {
|
|
2513
|
+
// replace: return a writable value slot, copy-on-writing the kv_pair if it
|
|
2514
|
+
// belongs to a past moment
|
|
2515
|
+
const leaf = node;
|
|
2516
|
+
const kvSlot = node.entries[idx];
|
|
2517
|
+
let valuePosition;
|
|
2518
|
+
if (this.btreeReusable(Number(kvSlot.value))) {
|
|
2519
|
+
valuePosition = Number(kvSlot.value) + this.header.hashSize + Slot.LENGTH;
|
|
2520
|
+
}
|
|
2521
|
+
else {
|
|
2522
|
+
const kv = this.readKvPair(kvSlot);
|
|
2523
|
+
const newKvPos = this.core.length();
|
|
2524
|
+
this.core.seek(newKvPos);
|
|
2525
|
+
writer.write(kv.toBytes());
|
|
2526
|
+
leaf.entries[idx] = new Slot(newKvPos, 5 /* Tag.KV_PAIR */);
|
|
2527
|
+
valuePosition = newKvPos + this.header.hashSize + Slot.LENGTH;
|
|
2528
|
+
}
|
|
2529
|
+
const ptr = this.sortedWriteNode(leaf, nodePtr);
|
|
2530
|
+
return new SortedInsertResult(ptr, node.num, valuePosition, false, null);
|
|
2531
|
+
}
|
|
2532
|
+
// insert a new entry at idx
|
|
2533
|
+
const entry = this.sortedNewEntry(key);
|
|
2534
|
+
const entries = [];
|
|
2535
|
+
for (let k = 0; k < idx; k++)
|
|
2536
|
+
entries.push(node.entries[k]);
|
|
2537
|
+
entries.push(entry.kvSlot);
|
|
2538
|
+
for (let k = idx; k < node.num; k++)
|
|
2539
|
+
entries.push(node.entries[k]);
|
|
2540
|
+
const total = node.num + 1;
|
|
2541
|
+
if (total <= BTREE_SLOT_COUNT) {
|
|
2542
|
+
const leaf = new SortedNode(BTreeNodeKind.LEAF, total);
|
|
2543
|
+
for (let k = 0; k < total; k++)
|
|
2544
|
+
leaf.entries[k] = entries[k];
|
|
2545
|
+
const ptr = this.sortedWriteNode(leaf, nodePtr);
|
|
2546
|
+
return new SortedInsertResult(ptr, total, entry.valuePosition, true, null);
|
|
2547
|
+
}
|
|
2548
|
+
// overflow: split into two leaves; the new sibling's separator is the key of its
|
|
2549
|
+
// first entry
|
|
2550
|
+
const leftN = BTREE_SPLIT_COUNT;
|
|
2551
|
+
const rightN = total - leftN;
|
|
2552
|
+
const left = new SortedNode(BTreeNodeKind.LEAF, leftN);
|
|
2553
|
+
for (let k = 0; k < leftN; k++)
|
|
2554
|
+
left.entries[k] = entries[k];
|
|
2555
|
+
const right = new SortedNode(BTreeNodeKind.LEAF, rightN);
|
|
2556
|
+
for (let k = 0; k < rightN; k++)
|
|
2557
|
+
right.entries[k] = entries[leftN + k];
|
|
2558
|
+
const separator = this.readKvPair(entries[leftN]).keySlot;
|
|
2559
|
+
const leftPtr = this.sortedWriteNode(left, nodePtr);
|
|
2560
|
+
const rightPtr = this.writeSortedNode(right);
|
|
2561
|
+
return new SortedInsertResult(leftPtr, leftN, entry.valuePosition, true, new SortedSplit(rightPtr, rightN, separator));
|
|
2562
|
+
}
|
|
2563
|
+
case BTreeNodeKind.BRANCH: {
|
|
2564
|
+
let i = 0;
|
|
2565
|
+
while (i + 1 < node.num && this.compareKey(node.separators[i + 1], key) <= 0)
|
|
2566
|
+
i++;
|
|
2567
|
+
const child = this.sortedPut(Number(node.children[i].value), key);
|
|
2568
|
+
const children = [];
|
|
2569
|
+
const separators = [];
|
|
2570
|
+
const counts = [];
|
|
2571
|
+
for (let k = 0; k < node.num; k++) {
|
|
2572
|
+
children.push(node.children[k]);
|
|
2573
|
+
separators.push(node.separators[k]);
|
|
2574
|
+
counts.push(node.counts[k]);
|
|
2575
|
+
}
|
|
2576
|
+
children[i] = new Slot(child.nodePtr, 1 /* Tag.INDEX */);
|
|
2577
|
+
counts[i] = child.count;
|
|
2578
|
+
let total = node.num;
|
|
2579
|
+
if (child.split !== null) {
|
|
2580
|
+
children.splice(i + 1, 0, new Slot(child.split.nodePtr, 1 /* Tag.INDEX */));
|
|
2581
|
+
separators.splice(i + 1, 0, child.split.separator);
|
|
2582
|
+
counts.splice(i + 1, 0, child.split.count);
|
|
2583
|
+
total = node.num + 1;
|
|
2584
|
+
}
|
|
2585
|
+
if (total <= BTREE_SLOT_COUNT) {
|
|
2586
|
+
const branch = new SortedNode(BTreeNodeKind.BRANCH, total);
|
|
2587
|
+
for (let k = 0; k < total; k++) {
|
|
2588
|
+
branch.children[k] = children[k];
|
|
2589
|
+
branch.separators[k] = separators[k];
|
|
2590
|
+
branch.counts[k] = counts[k];
|
|
2591
|
+
}
|
|
2592
|
+
const ptr = this.sortedWriteNode(branch, nodePtr);
|
|
2593
|
+
return new SortedInsertResult(ptr, branch.subtreeCount(), child.valuePosition, child.added, null);
|
|
2594
|
+
}
|
|
2595
|
+
// overflow: split into two branches; the new sibling's separator is the smallest
|
|
2596
|
+
// key of its first child (separators[leftN] of the combined)
|
|
2597
|
+
const leftN = BTREE_SPLIT_COUNT;
|
|
2598
|
+
const rightN = total - leftN;
|
|
2599
|
+
const left = new SortedNode(BTreeNodeKind.BRANCH, leftN);
|
|
2600
|
+
for (let k = 0; k < leftN; k++) {
|
|
2601
|
+
left.children[k] = children[k];
|
|
2602
|
+
left.separators[k] = separators[k];
|
|
2603
|
+
left.counts[k] = counts[k];
|
|
2604
|
+
}
|
|
2605
|
+
const right = new SortedNode(BTreeNodeKind.BRANCH, rightN);
|
|
2606
|
+
for (let k = 0; k < rightN; k++) {
|
|
2607
|
+
right.children[k] = children[leftN + k];
|
|
2608
|
+
right.separators[k] = separators[leftN + k];
|
|
2609
|
+
right.counts[k] = counts[leftN + k];
|
|
2610
|
+
}
|
|
2611
|
+
const separator = separators[leftN];
|
|
2612
|
+
const leftPtr = this.sortedWriteNode(left, nodePtr);
|
|
2613
|
+
const rightPtr = this.writeSortedNode(right);
|
|
2614
|
+
return new SortedInsertResult(leftPtr, left.subtreeCount(), child.valuePosition, child.added, new SortedSplit(rightPtr, right.subtreeCount(), separator));
|
|
2615
|
+
}
|
|
2616
|
+
}
|
|
2617
|
+
throw new UnreachableException();
|
|
2618
|
+
}
|
|
2619
|
+
// remove key from the subtree at nodePtr, path-copying nodes and decrementing counts.
|
|
2620
|
+
// an emptied leaf is left in place (see SortedRemoveResult).
|
|
2621
|
+
sortedRemove(nodePtr, key) {
|
|
2622
|
+
const node = this.readSortedNode(nodePtr);
|
|
2623
|
+
switch (node.kind) {
|
|
2624
|
+
case BTreeNodeKind.LEAF: {
|
|
2625
|
+
let idx = node.num;
|
|
2626
|
+
let found = false;
|
|
2627
|
+
for (let i = 0; i < node.num; i++) {
|
|
2628
|
+
const kv = this.readKvPair(node.entries[i]);
|
|
2629
|
+
const cmp = this.compareKey(kv.keySlot, key);
|
|
2630
|
+
if (cmp === 0) {
|
|
2631
|
+
idx = i;
|
|
2632
|
+
found = true;
|
|
2633
|
+
break;
|
|
2634
|
+
}
|
|
2635
|
+
if (cmp > 0)
|
|
2636
|
+
break;
|
|
2637
|
+
}
|
|
2638
|
+
if (!found)
|
|
2639
|
+
return new SortedRemoveResult(nodePtr, false);
|
|
2640
|
+
const leaf = new SortedNode(BTreeNodeKind.LEAF, node.num - 1);
|
|
2641
|
+
for (let k = 0; k < idx; k++)
|
|
2642
|
+
leaf.entries[k] = node.entries[k];
|
|
2643
|
+
for (let k = idx; k < node.num - 1; k++)
|
|
2644
|
+
leaf.entries[k] = node.entries[k + 1];
|
|
2645
|
+
const ptr = this.sortedWriteNode(leaf, nodePtr);
|
|
2646
|
+
return new SortedRemoveResult(ptr, true);
|
|
2647
|
+
}
|
|
2648
|
+
case BTreeNodeKind.BRANCH: {
|
|
2649
|
+
let i = 0;
|
|
2650
|
+
while (i + 1 < node.num && this.compareKey(node.separators[i + 1], key) <= 0)
|
|
2651
|
+
i++;
|
|
2652
|
+
const child = this.sortedRemove(Number(node.children[i].value), key);
|
|
2653
|
+
if (!child.found)
|
|
2654
|
+
return new SortedRemoveResult(nodePtr, false);
|
|
2655
|
+
const branch = node;
|
|
2656
|
+
branch.children[i] = new Slot(child.nodePtr, 1 /* Tag.INDEX */);
|
|
2657
|
+
branch.counts[i] -= 1;
|
|
2658
|
+
const ptr = this.sortedWriteNode(branch, nodePtr);
|
|
2659
|
+
return new SortedRemoveResult(ptr, true);
|
|
2660
|
+
}
|
|
2661
|
+
}
|
|
2662
|
+
throw new UnreachableException();
|
|
2663
|
+
}
|
|
2664
|
+
sortedGrowRoot(result) {
|
|
2665
|
+
if (result.split !== null) {
|
|
2666
|
+
const split = result.split;
|
|
2667
|
+
const root = new SortedNode(BTreeNodeKind.BRANCH, 2);
|
|
2668
|
+
root.children[0] = new Slot(result.nodePtr, 1 /* Tag.INDEX */);
|
|
2669
|
+
root.children[1] = new Slot(split.nodePtr, 1 /* Tag.INDEX */);
|
|
2670
|
+
root.separators[1] = split.separator; // separators[0] is an unused sentinel
|
|
2671
|
+
root.counts[0] = result.count;
|
|
2672
|
+
root.counts[1] = split.count;
|
|
2673
|
+
return this.writeSortedNode(root);
|
|
2674
|
+
}
|
|
2675
|
+
return result.nodePtr;
|
|
2676
|
+
}
|
|
2677
|
+
// turn a located/inserted kv_pair (at kvPos) into the slot for the requested target.
|
|
2678
|
+
// only the value is writeable (that is how put works); the key and the kv_pair pointer
|
|
2679
|
+
// are immutable, so they are returned with no writeable position.
|
|
2680
|
+
sortedTargetSlot(kvPos, target) {
|
|
2681
|
+
const kv = this.readKvPair(new Slot(kvPos, 5 /* Tag.KV_PAIR */));
|
|
2682
|
+
if (target instanceof SortedMapGetKVPair) {
|
|
2683
|
+
return new SlotPointer(null, new Slot(kvPos, 5 /* Tag.KV_PAIR */));
|
|
2684
|
+
}
|
|
2685
|
+
else if (target instanceof SortedMapGetKey) {
|
|
2686
|
+
return new SlotPointer(null, kv.keySlot);
|
|
2687
|
+
}
|
|
2688
|
+
else if (target instanceof SortedMapGetValue) {
|
|
2689
|
+
return new SlotPointer(kvPos + this.header.hashSize + Slot.LENGTH, kv.valueSlot);
|
|
2690
|
+
}
|
|
2691
|
+
else {
|
|
2692
|
+
throw new UnexpectedTagException();
|
|
2693
|
+
}
|
|
2694
|
+
}
|
|
2695
|
+
}
|
|
2696
|
+
// compaction helpers
|
|
2697
|
+
function remapSlot(sourceCore, targetCore, hashSize, offsetMap, slot) {
|
|
2698
|
+
switch (slot.tag) {
|
|
2699
|
+
case 0 /* Tag.NONE */:
|
|
2700
|
+
case 8 /* Tag.UINT */:
|
|
2701
|
+
case 9 /* Tag.INT */:
|
|
2702
|
+
case 10 /* Tag.FLOAT */:
|
|
2703
|
+
case 7 /* Tag.SHORT_BYTES */:
|
|
2704
|
+
return slot;
|
|
2705
|
+
case 6 /* Tag.BYTES */: {
|
|
2706
|
+
const mapped = offsetMap.get(Number(slot.value));
|
|
2707
|
+
if (mapped !== undefined)
|
|
2708
|
+
return new Slot(mapped, slot.tag, slot.full);
|
|
2709
|
+
const newOffset = remapBytes(sourceCore, targetCore, slot);
|
|
2710
|
+
offsetMap.set(Number(slot.value), newOffset);
|
|
2711
|
+
return new Slot(newOffset, slot.tag, slot.full);
|
|
2712
|
+
}
|
|
2713
|
+
case 1 /* Tag.INDEX */: {
|
|
2714
|
+
const mapped = offsetMap.get(Number(slot.value));
|
|
2715
|
+
if (mapped !== undefined)
|
|
2716
|
+
return new Slot(mapped, slot.tag, slot.full);
|
|
2717
|
+
const newOffset = remapIndex(sourceCore, targetCore, hashSize, offsetMap, slot);
|
|
2718
|
+
offsetMap.set(Number(slot.value), newOffset);
|
|
2719
|
+
return new Slot(newOffset, slot.tag, slot.full);
|
|
2720
|
+
}
|
|
2721
|
+
case 2 /* Tag.ARRAY_LIST */: {
|
|
2722
|
+
const mapped = offsetMap.get(Number(slot.value));
|
|
2723
|
+
if (mapped !== undefined)
|
|
2724
|
+
return new Slot(mapped, slot.tag, slot.full);
|
|
2725
|
+
const newOffset = remapArrayList(sourceCore, targetCore, hashSize, offsetMap, slot);
|
|
2726
|
+
offsetMap.set(Number(slot.value), newOffset);
|
|
2727
|
+
return new Slot(newOffset, slot.tag, slot.full);
|
|
2728
|
+
}
|
|
2729
|
+
case 3 /* Tag.LINKED_ARRAY_LIST */: {
|
|
2730
|
+
const mapped = offsetMap.get(Number(slot.value));
|
|
2731
|
+
if (mapped !== undefined)
|
|
2732
|
+
return new Slot(mapped, slot.tag, slot.full);
|
|
2733
|
+
const newOffset = remapBTree(sourceCore, targetCore, hashSize, offsetMap, slot);
|
|
2734
|
+
offsetMap.set(Number(slot.value), newOffset);
|
|
2735
|
+
return new Slot(newOffset, slot.tag, slot.full);
|
|
2736
|
+
}
|
|
2737
|
+
case 4 /* Tag.HASH_MAP */:
|
|
2738
|
+
case 11 /* Tag.HASH_SET */: {
|
|
2739
|
+
const mapped = offsetMap.get(Number(slot.value));
|
|
2740
|
+
if (mapped !== undefined)
|
|
2741
|
+
return new Slot(mapped, slot.tag, slot.full);
|
|
2742
|
+
const newOffset = remapHashMapOrSet(sourceCore, targetCore, hashSize, offsetMap, slot, false);
|
|
2743
|
+
offsetMap.set(Number(slot.value), newOffset);
|
|
2744
|
+
return new Slot(newOffset, slot.tag, slot.full);
|
|
2745
|
+
}
|
|
2746
|
+
case 12 /* Tag.COUNTED_HASH_MAP */:
|
|
2747
|
+
case 13 /* Tag.COUNTED_HASH_SET */: {
|
|
2748
|
+
const mapped = offsetMap.get(Number(slot.value));
|
|
2749
|
+
if (mapped !== undefined)
|
|
2750
|
+
return new Slot(mapped, slot.tag, slot.full);
|
|
2751
|
+
const newOffset = remapHashMapOrSet(sourceCore, targetCore, hashSize, offsetMap, slot, true);
|
|
2752
|
+
offsetMap.set(Number(slot.value), newOffset);
|
|
2753
|
+
return new Slot(newOffset, slot.tag, slot.full);
|
|
2754
|
+
}
|
|
2755
|
+
case 5 /* Tag.KV_PAIR */: {
|
|
2756
|
+
const mapped = offsetMap.get(Number(slot.value));
|
|
2757
|
+
if (mapped !== undefined)
|
|
2758
|
+
return new Slot(mapped, slot.tag, slot.full);
|
|
2759
|
+
const newOffset = remapKvPair(sourceCore, targetCore, hashSize, offsetMap, slot);
|
|
2760
|
+
offsetMap.set(Number(slot.value), newOffset);
|
|
2761
|
+
return new Slot(newOffset, slot.tag, slot.full);
|
|
2762
|
+
}
|
|
2763
|
+
case 14 /* Tag.SORTED_MAP */:
|
|
2764
|
+
case 15 /* Tag.SORTED_SET */: {
|
|
2765
|
+
const mapped = offsetMap.get(Number(slot.value));
|
|
2766
|
+
if (mapped !== undefined)
|
|
2767
|
+
return new Slot(mapped, slot.tag, slot.full);
|
|
2768
|
+
const newOffset = remapSortedMap(sourceCore, targetCore, hashSize, offsetMap, slot);
|
|
2769
|
+
offsetMap.set(Number(slot.value), newOffset);
|
|
2770
|
+
return new Slot(newOffset, slot.tag, slot.full);
|
|
2771
|
+
}
|
|
2772
|
+
default:
|
|
2773
|
+
throw new UnexpectedTagException();
|
|
2774
|
+
}
|
|
2775
|
+
}
|
|
2776
|
+
function remapBytes(sourceCore, targetCore, slot) {
|
|
2777
|
+
sourceCore.seek(Number(slot.value));
|
|
2778
|
+
const sourceReader = sourceCore.reader();
|
|
2779
|
+
const length = sourceReader.readLong();
|
|
2780
|
+
// total size: 8-byte length + bytes + optional 2-byte format_tag
|
|
2781
|
+
const formatTagSize = slot.full ? 2 : 0;
|
|
2782
|
+
const totalPayload = length + formatTagSize;
|
|
2783
|
+
const newOffset = targetCore.length();
|
|
2784
|
+
targetCore.seek(newOffset);
|
|
2785
|
+
const targetWriter = targetCore.writer();
|
|
2786
|
+
targetWriter.writeLong(length);
|
|
2787
|
+
// copy bytes in chunks
|
|
2788
|
+
let remaining = totalPayload;
|
|
2789
|
+
while (remaining > 0) {
|
|
2790
|
+
const chunk = Math.min(remaining, 4096);
|
|
2791
|
+
const buf = new Uint8Array(chunk);
|
|
2792
|
+
sourceReader.readFully(buf);
|
|
2793
|
+
targetWriter.write(buf);
|
|
2794
|
+
remaining -= chunk;
|
|
2795
|
+
}
|
|
2796
|
+
return newOffset;
|
|
2797
|
+
}
|
|
2798
|
+
function remapIndex(sourceCore, targetCore, hashSize, offsetMap, slot) {
|
|
2799
|
+
// read 144-byte block (16 slots)
|
|
2800
|
+
sourceCore.seek(Number(slot.value));
|
|
2801
|
+
const sourceReader = sourceCore.reader();
|
|
2802
|
+
const blockBytes = new Uint8Array(INDEX_BLOCK_SIZE);
|
|
2803
|
+
sourceReader.readFully(blockBytes);
|
|
2804
|
+
// remap each slot
|
|
2805
|
+
const remappedSlots = [];
|
|
2806
|
+
for (let i = 0; i < SLOT_COUNT; i++) {
|
|
2807
|
+
const slotBytes = blockBytes.slice(i * Slot.LENGTH, (i + 1) * Slot.LENGTH);
|
|
2808
|
+
const childSlot = Slot.fromBytes(slotBytes);
|
|
2809
|
+
remappedSlots.push(remapSlot(sourceCore, targetCore, hashSize, offsetMap, childSlot));
|
|
2810
|
+
}
|
|
2811
|
+
// write remapped block to target
|
|
2812
|
+
const newOffset = targetCore.length();
|
|
2813
|
+
targetCore.seek(newOffset);
|
|
2814
|
+
const targetWriter = targetCore.writer();
|
|
2815
|
+
for (const s of remappedSlots) {
|
|
2816
|
+
targetWriter.write(s.toBytes());
|
|
2817
|
+
}
|
|
2818
|
+
return newOffset;
|
|
2819
|
+
}
|
|
2820
|
+
function remapArrayList(sourceCore, targetCore, hashSize, offsetMap, slot) {
|
|
2821
|
+
// read ArrayListHeader (16 bytes)
|
|
2822
|
+
sourceCore.seek(Number(slot.value));
|
|
2823
|
+
const sourceReader = sourceCore.reader();
|
|
2824
|
+
const headerBytes = new Uint8Array(ArrayListHeader.LENGTH);
|
|
2825
|
+
sourceReader.readFully(headerBytes);
|
|
2826
|
+
const header = ArrayListHeader.fromBytes(headerBytes);
|
|
2827
|
+
// remap root index block pointer via remapSlot as an .index slot
|
|
2828
|
+
const indexSlot = new Slot(header.ptr, 1 /* Tag.INDEX */);
|
|
2829
|
+
const remappedIndex = remapSlot(sourceCore, targetCore, hashSize, offsetMap, indexSlot);
|
|
2830
|
+
// write new ArrayListHeader with remapped ptr
|
|
2831
|
+
const newOffset = targetCore.length();
|
|
2832
|
+
targetCore.seek(newOffset);
|
|
2833
|
+
const targetWriter = targetCore.writer();
|
|
2834
|
+
targetWriter.write(new ArrayListHeader(Number(remappedIndex.value), header.size).toBytes());
|
|
2835
|
+
return newOffset;
|
|
2836
|
+
}
|
|
2837
|
+
function remapBTree(sourceCore, targetCore, hashSize, offsetMap, slot) {
|
|
2838
|
+
sourceCore.seek(Number(slot.value));
|
|
2839
|
+
const sourceReader = sourceCore.reader();
|
|
2840
|
+
const headerBytes = new Uint8Array(BTreeHeader.LENGTH);
|
|
2841
|
+
sourceReader.readFully(headerBytes);
|
|
2842
|
+
const header = BTreeHeader.fromBytes(headerBytes);
|
|
2843
|
+
const remappedRoot = remapBTreeNode(sourceCore, targetCore, hashSize, offsetMap, header.rootPtr);
|
|
2844
|
+
const newOffset = targetCore.length();
|
|
2845
|
+
targetCore.seek(newOffset);
|
|
2846
|
+
const targetWriter = targetCore.writer();
|
|
2847
|
+
targetWriter.write(new BTreeHeader(remappedRoot, header.size).toBytes());
|
|
2848
|
+
return newOffset;
|
|
2849
|
+
}
|
|
2850
|
+
function remapBTreeNode(sourceCore, targetCore, hashSize, offsetMap, nodeOffset) {
|
|
2851
|
+
// dedup check (subtrees are shared by pointer)
|
|
2852
|
+
const mapped = offsetMap.get(nodeOffset);
|
|
2853
|
+
if (mapped !== undefined)
|
|
2854
|
+
return mapped;
|
|
2855
|
+
sourceCore.seek(nodeOffset);
|
|
2856
|
+
const sourceReader = sourceCore.reader();
|
|
2857
|
+
const nodeHeader = new Uint8Array(BTREE_NODE_HEADER_SIZE);
|
|
2858
|
+
sourceReader.readFully(nodeHeader);
|
|
2859
|
+
const kindInt = nodeHeader[0];
|
|
2860
|
+
if (kindInt > BTreeNodeKind.BRANCH)
|
|
2861
|
+
throw new InvalidBTreeNodeKindException();
|
|
2862
|
+
const kind = kindInt;
|
|
2863
|
+
const num = nodeHeader[1];
|
|
2864
|
+
switch (kind) {
|
|
2865
|
+
case BTreeNodeKind.LEAF: {
|
|
2866
|
+
const body = new Uint8Array(Slot.LENGTH * BTREE_SLOT_COUNT);
|
|
2867
|
+
sourceReader.readFully(body);
|
|
2868
|
+
const slots = [];
|
|
2869
|
+
for (let i = 0; i < BTREE_SLOT_COUNT; i++) {
|
|
2870
|
+
const valueSlot = Slot.fromBytes(body.slice(i * Slot.LENGTH, i * Slot.LENGTH + Slot.LENGTH));
|
|
2871
|
+
slots.push(remapSlot(sourceCore, targetCore, hashSize, offsetMap, valueSlot));
|
|
2872
|
+
}
|
|
2873
|
+
const newOffset = targetCore.length();
|
|
2874
|
+
targetCore.seek(newOffset);
|
|
2875
|
+
const targetWriter = targetCore.writer();
|
|
2876
|
+
targetWriter.write(new Uint8Array([kindInt, num]));
|
|
2877
|
+
for (const s of slots)
|
|
2878
|
+
targetWriter.write(s.toBytes());
|
|
2879
|
+
offsetMap.set(nodeOffset, newOffset);
|
|
2880
|
+
return newOffset;
|
|
2881
|
+
}
|
|
2882
|
+
case BTreeNodeKind.BRANCH: {
|
|
2883
|
+
const body = new Uint8Array((Slot.LENGTH + 8) * BTREE_SLOT_COUNT);
|
|
2884
|
+
sourceReader.readFully(body);
|
|
2885
|
+
const view = new DataView(body.buffer, body.byteOffset, body.byteLength);
|
|
2886
|
+
const children = [];
|
|
2887
|
+
for (let i = 0; i < BTREE_SLOT_COUNT; i++) {
|
|
2888
|
+
const child = Slot.fromBytes(body.slice(i * Slot.LENGTH, i * Slot.LENGTH + Slot.LENGTH));
|
|
2889
|
+
if (child.tag === 1 /* Tag.INDEX */) {
|
|
2890
|
+
const remappedPtr = remapBTreeNode(sourceCore, targetCore, hashSize, offsetMap, Number(child.value));
|
|
2891
|
+
children.push(new Slot(remappedPtr, 1 /* Tag.INDEX */, child.full));
|
|
2892
|
+
}
|
|
2893
|
+
else {
|
|
2894
|
+
children.push(child);
|
|
2895
|
+
}
|
|
2896
|
+
}
|
|
2897
|
+
const countsOffset = Slot.LENGTH * BTREE_SLOT_COUNT;
|
|
2898
|
+
const counts = [];
|
|
2899
|
+
for (let i = 0; i < BTREE_SLOT_COUNT; i++) {
|
|
2900
|
+
counts.push(Number(view.getBigInt64(countsOffset + i * 8, false)));
|
|
2901
|
+
}
|
|
2902
|
+
const newOffset = targetCore.length();
|
|
2903
|
+
targetCore.seek(newOffset);
|
|
2904
|
+
const targetWriter = targetCore.writer();
|
|
2905
|
+
targetWriter.write(new Uint8Array([kindInt, num]));
|
|
2906
|
+
for (const s of children)
|
|
2907
|
+
targetWriter.write(s.toBytes());
|
|
2908
|
+
for (const c of counts)
|
|
2909
|
+
targetWriter.writeLong(c);
|
|
2910
|
+
offsetMap.set(nodeOffset, newOffset);
|
|
2911
|
+
return newOffset;
|
|
2912
|
+
}
|
|
2913
|
+
}
|
|
2914
|
+
throw new UnreachableException();
|
|
2915
|
+
}
|
|
2916
|
+
function remapSortedMap(sourceCore, targetCore, hashSize, offsetMap, slot) {
|
|
2917
|
+
sourceCore.seek(Number(slot.value));
|
|
2918
|
+
const sourceReader = sourceCore.reader();
|
|
2919
|
+
const headerBytes = new Uint8Array(BTreeHeader.LENGTH);
|
|
2920
|
+
sourceReader.readFully(headerBytes);
|
|
2921
|
+
const header = BTreeHeader.fromBytes(headerBytes);
|
|
2922
|
+
const remappedRoot = remapSortedMapNode(sourceCore, targetCore, hashSize, offsetMap, header.rootPtr);
|
|
2923
|
+
const newOffset = targetCore.length();
|
|
2924
|
+
targetCore.seek(newOffset);
|
|
2925
|
+
const targetWriter = targetCore.writer();
|
|
2926
|
+
targetWriter.write(new BTreeHeader(remappedRoot, header.size).toBytes());
|
|
2927
|
+
return newOffset;
|
|
2928
|
+
}
|
|
2929
|
+
function remapSortedMapNode(sourceCore, targetCore, hashSize, offsetMap, nodeOffset) {
|
|
2930
|
+
const mapped = offsetMap.get(nodeOffset);
|
|
2931
|
+
if (mapped !== undefined)
|
|
2932
|
+
return mapped;
|
|
2933
|
+
sourceCore.seek(nodeOffset);
|
|
2934
|
+
const sourceReader = sourceCore.reader();
|
|
2935
|
+
const nodeHeader = new Uint8Array(BTREE_NODE_HEADER_SIZE);
|
|
2936
|
+
sourceReader.readFully(nodeHeader);
|
|
2937
|
+
const kindInt = nodeHeader[0];
|
|
2938
|
+
if (kindInt > BTreeNodeKind.BRANCH)
|
|
2939
|
+
throw new InvalidBTreeNodeKindException();
|
|
2940
|
+
const kind = kindInt;
|
|
2941
|
+
const num = nodeHeader[1];
|
|
2942
|
+
switch (kind) {
|
|
2943
|
+
case BTreeNodeKind.LEAF: {
|
|
2944
|
+
const body = new Uint8Array(Slot.LENGTH * BTREE_SLOT_COUNT);
|
|
2945
|
+
sourceReader.readFully(body);
|
|
2946
|
+
const entries = [];
|
|
2947
|
+
for (let i = 0; i < BTREE_SLOT_COUNT; i++) {
|
|
2948
|
+
const entry = Slot.fromBytes(body.slice(i * Slot.LENGTH, i * Slot.LENGTH + Slot.LENGTH));
|
|
2949
|
+
entries.push(remapSlot(sourceCore, targetCore, hashSize, offsetMap, entry));
|
|
2950
|
+
}
|
|
2951
|
+
const newOffset = targetCore.length();
|
|
2952
|
+
targetCore.seek(newOffset);
|
|
2953
|
+
const targetWriter = targetCore.writer();
|
|
2954
|
+
targetWriter.write(new Uint8Array([kindInt, num]));
|
|
2955
|
+
for (const s of entries)
|
|
2956
|
+
targetWriter.write(s.toBytes());
|
|
2957
|
+
offsetMap.set(nodeOffset, newOffset);
|
|
2958
|
+
return newOffset;
|
|
2959
|
+
}
|
|
2960
|
+
case BTreeNodeKind.BRANCH: {
|
|
2961
|
+
const body = new Uint8Array((Slot.LENGTH * 2 + 8) * BTREE_SLOT_COUNT);
|
|
2962
|
+
sourceReader.readFully(body);
|
|
2963
|
+
const view = new DataView(body.buffer, body.byteOffset, body.byteLength);
|
|
2964
|
+
const children = [];
|
|
2965
|
+
for (let i = 0; i < BTREE_SLOT_COUNT; i++) {
|
|
2966
|
+
const child = Slot.fromBytes(body.slice(i * Slot.LENGTH, i * Slot.LENGTH + Slot.LENGTH));
|
|
2967
|
+
if (child.tag === 1 /* Tag.INDEX */) {
|
|
2968
|
+
const remappedPtr = remapSortedMapNode(sourceCore, targetCore, hashSize, offsetMap, Number(child.value));
|
|
2969
|
+
children.push(new Slot(remappedPtr, 1 /* Tag.INDEX */, child.full));
|
|
2970
|
+
}
|
|
2971
|
+
else {
|
|
2972
|
+
children.push(child);
|
|
2973
|
+
}
|
|
2974
|
+
}
|
|
2975
|
+
const sepOffset = Slot.LENGTH * BTREE_SLOT_COUNT;
|
|
2976
|
+
const separators = [];
|
|
2977
|
+
for (let i = 0; i < BTREE_SLOT_COUNT; i++) {
|
|
2978
|
+
const sep = Slot.fromBytes(body.slice(sepOffset + i * Slot.LENGTH, sepOffset + i * Slot.LENGTH + Slot.LENGTH));
|
|
2979
|
+
separators.push(remapSlot(sourceCore, targetCore, hashSize, offsetMap, sep));
|
|
2980
|
+
}
|
|
2981
|
+
const countsOffset = Slot.LENGTH * 2 * BTREE_SLOT_COUNT;
|
|
2982
|
+
const counts = [];
|
|
2983
|
+
for (let i = 0; i < BTREE_SLOT_COUNT; i++) {
|
|
2984
|
+
counts.push(Number(view.getBigInt64(countsOffset + i * 8, false)));
|
|
2985
|
+
}
|
|
2986
|
+
const newOffset = targetCore.length();
|
|
2987
|
+
targetCore.seek(newOffset);
|
|
2988
|
+
const targetWriter = targetCore.writer();
|
|
2989
|
+
targetWriter.write(new Uint8Array([kindInt, num]));
|
|
2990
|
+
for (const s of children)
|
|
2991
|
+
targetWriter.write(s.toBytes());
|
|
2992
|
+
for (const s of separators)
|
|
2993
|
+
targetWriter.write(s.toBytes());
|
|
2994
|
+
for (const c of counts)
|
|
2995
|
+
targetWriter.writeLong(c);
|
|
2996
|
+
offsetMap.set(nodeOffset, newOffset);
|
|
2997
|
+
return newOffset;
|
|
2998
|
+
}
|
|
2999
|
+
}
|
|
3000
|
+
throw new UnreachableException();
|
|
3001
|
+
}
|
|
3002
|
+
function remapHashMapOrSet(sourceCore, targetCore, hashSize, offsetMap, slot, counted) {
|
|
3003
|
+
sourceCore.seek(Number(slot.value));
|
|
3004
|
+
const sourceReader = sourceCore.reader();
|
|
3005
|
+
let countValue = -1;
|
|
3006
|
+
if (counted) {
|
|
3007
|
+
countValue = sourceReader.readLong();
|
|
3008
|
+
}
|
|
3009
|
+
// read 144-byte root index block
|
|
3010
|
+
const blockBytes = new Uint8Array(INDEX_BLOCK_SIZE);
|
|
3011
|
+
sourceReader.readFully(blockBytes);
|
|
3012
|
+
// remap each child slot in the block
|
|
3013
|
+
const remappedSlots = [];
|
|
3014
|
+
for (let i = 0; i < SLOT_COUNT; i++) {
|
|
3015
|
+
const slotBytes = blockBytes.slice(i * Slot.LENGTH, (i + 1) * Slot.LENGTH);
|
|
3016
|
+
const childSlot = Slot.fromBytes(slotBytes);
|
|
3017
|
+
remappedSlots.push(remapSlot(sourceCore, targetCore, hashSize, offsetMap, childSlot));
|
|
3018
|
+
}
|
|
3019
|
+
// write [optional count][remapped block] contiguously to target
|
|
3020
|
+
const newOffset = targetCore.length();
|
|
3021
|
+
targetCore.seek(newOffset);
|
|
3022
|
+
const targetWriter = targetCore.writer();
|
|
3023
|
+
if (counted) {
|
|
3024
|
+
targetWriter.writeLong(countValue);
|
|
3025
|
+
}
|
|
3026
|
+
for (const s of remappedSlots) {
|
|
3027
|
+
targetWriter.write(s.toBytes());
|
|
3028
|
+
}
|
|
3029
|
+
return newOffset;
|
|
3030
|
+
}
|
|
3031
|
+
function remapKvPair(sourceCore, targetCore, hashSize, offsetMap, slot) {
|
|
3032
|
+
// read KeyValuePair
|
|
3033
|
+
sourceCore.seek(Number(slot.value));
|
|
3034
|
+
const sourceReader = sourceCore.reader();
|
|
3035
|
+
const kvPairBytes = new Uint8Array(KeyValuePair.length(hashSize));
|
|
3036
|
+
sourceReader.readFully(kvPairBytes);
|
|
3037
|
+
const kvPair = KeyValuePair.fromBytes(kvPairBytes, hashSize);
|
|
3038
|
+
// remap key_slot and value_slot
|
|
3039
|
+
const remappedKey = remapSlot(sourceCore, targetCore, hashSize, offsetMap, kvPair.keySlot);
|
|
3040
|
+
const remappedValue = remapSlot(sourceCore, targetCore, hashSize, offsetMap, kvPair.valueSlot);
|
|
3041
|
+
// write remapped KV pair (hash stays unchanged)
|
|
3042
|
+
const newOffset = targetCore.length();
|
|
3043
|
+
targetCore.seek(newOffset);
|
|
3044
|
+
const targetWriter = targetCore.writer();
|
|
3045
|
+
targetWriter.write(new KeyValuePair(remappedValue, remappedKey, kvPair.hash).toBytes());
|
|
3046
|
+
return newOffset;
|
|
3047
|
+
}
|