@tanstack/db 0.0.29 → 0.0.31
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/collection.cjs +30 -26
- package/dist/cjs/collection.cjs.map +1 -1
- package/dist/cjs/collection.d.cts +4 -4
- package/dist/cjs/index.cjs +2 -2
- package/dist/cjs/index.d.cts +1 -1
- package/dist/cjs/indexes/auto-index.cjs +2 -2
- package/dist/cjs/indexes/auto-index.cjs.map +1 -1
- package/dist/cjs/indexes/{ordered-index.cjs → btree-index.cjs} +27 -63
- package/dist/cjs/indexes/btree-index.cjs.map +1 -0
- package/dist/{esm/indexes/ordered-index.d.ts → cjs/indexes/btree-index.d.cts} +6 -4
- package/dist/cjs/utils/btree.cjs +677 -0
- package/dist/cjs/utils/btree.cjs.map +1 -0
- package/dist/cjs/utils/btree.d.cts +197 -0
- package/dist/esm/collection.d.ts +4 -4
- package/dist/esm/collection.js +30 -26
- package/dist/esm/collection.js.map +1 -1
- package/dist/esm/index.d.ts +1 -1
- package/dist/esm/index.js +2 -2
- package/dist/esm/indexes/auto-index.js +2 -2
- package/dist/esm/indexes/auto-index.js.map +1 -1
- package/dist/{cjs/indexes/ordered-index.d.cts → esm/indexes/btree-index.d.ts} +6 -4
- package/dist/esm/indexes/{ordered-index.js → btree-index.js} +27 -63
- package/dist/esm/indexes/btree-index.js.map +1 -0
- package/dist/esm/utils/btree.d.ts +197 -0
- package/dist/esm/utils/btree.js +677 -0
- package/dist/esm/utils/btree.js.map +1 -0
- package/package.json +1 -1
- package/src/collection.ts +45 -33
- package/src/index.ts +1 -1
- package/src/indexes/auto-index.ts +2 -2
- package/src/indexes/{ordered-index.ts → btree-index.ts} +42 -84
- package/src/utils/btree.ts +1010 -0
- package/dist/cjs/indexes/ordered-index.cjs.map +0 -1
- package/dist/cjs/utils/array-utils.cjs +0 -18
- package/dist/cjs/utils/array-utils.cjs.map +0 -1
- package/dist/esm/indexes/ordered-index.js.map +0 -1
- package/dist/esm/utils/array-utils.js +0 -18
- package/dist/esm/utils/array-utils.js.map +0 -1
|
@@ -0,0 +1,677 @@
|
|
|
1
|
+
class BTree {
|
|
2
|
+
/**
|
|
3
|
+
* Initializes an empty B+ tree.
|
|
4
|
+
* @param compare Custom function to compare pairs of elements in the tree.
|
|
5
|
+
* If not specified, defaultComparator will be used which is valid as long as K extends DefaultComparable.
|
|
6
|
+
* @param entries A set of key-value pairs to initialize the tree
|
|
7
|
+
* @param maxNodeSize Branching factor (maximum items or children per node)
|
|
8
|
+
* Must be in range 4..256. If undefined or <4 then default is used; if >256 then 256.
|
|
9
|
+
*/
|
|
10
|
+
constructor(compare, entries, maxNodeSize) {
|
|
11
|
+
this._root = EmptyLeaf;
|
|
12
|
+
this._size = 0;
|
|
13
|
+
this._maxNodeSize = maxNodeSize >= 4 ? Math.min(maxNodeSize, 256) : 32;
|
|
14
|
+
this._compare = compare;
|
|
15
|
+
if (entries) this.setPairs(entries);
|
|
16
|
+
}
|
|
17
|
+
// ///////////////////////////////////////////////////////////////////////////
|
|
18
|
+
// ES6 Map<K,V> methods /////////////////////////////////////////////////////
|
|
19
|
+
/** Gets the number of key-value pairs in the tree. */
|
|
20
|
+
get size() {
|
|
21
|
+
return this._size;
|
|
22
|
+
}
|
|
23
|
+
/** Gets the number of key-value pairs in the tree. */
|
|
24
|
+
get length() {
|
|
25
|
+
return this._size;
|
|
26
|
+
}
|
|
27
|
+
/** Returns true iff the tree contains no key-value pairs. */
|
|
28
|
+
get isEmpty() {
|
|
29
|
+
return this._size === 0;
|
|
30
|
+
}
|
|
31
|
+
/** Releases the tree so that its size is 0. */
|
|
32
|
+
clear() {
|
|
33
|
+
this._root = EmptyLeaf;
|
|
34
|
+
this._size = 0;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Finds a pair in the tree and returns the associated value.
|
|
38
|
+
* @param defaultValue a value to return if the key was not found.
|
|
39
|
+
* @returns the value, or defaultValue if the key was not found.
|
|
40
|
+
* @description Computational complexity: O(log size)
|
|
41
|
+
*/
|
|
42
|
+
get(key, defaultValue) {
|
|
43
|
+
return this._root.get(key, defaultValue, this);
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Adds or overwrites a key-value pair in the B+ tree.
|
|
47
|
+
* @param key the key is used to determine the sort order of
|
|
48
|
+
* data in the tree.
|
|
49
|
+
* @param value data to associate with the key (optional)
|
|
50
|
+
* @param overwrite Whether to overwrite an existing key-value pair
|
|
51
|
+
* (default: true). If this is false and there is an existing
|
|
52
|
+
* key-value pair then this method has no effect.
|
|
53
|
+
* @returns true if a new key-value pair was added.
|
|
54
|
+
* @description Computational complexity: O(log size)
|
|
55
|
+
* Note: when overwriting a previous entry, the key is updated
|
|
56
|
+
* as well as the value. This has no effect unless the new key
|
|
57
|
+
* has data that does not affect its sort order.
|
|
58
|
+
*/
|
|
59
|
+
set(key, value, overwrite) {
|
|
60
|
+
if (this._root.isShared) this._root = this._root.clone();
|
|
61
|
+
const result = this._root.set(key, value, overwrite, this);
|
|
62
|
+
if (result === true || result === false) return result;
|
|
63
|
+
this._root = new BNodeInternal([this._root, result]);
|
|
64
|
+
return true;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Returns true if the key exists in the B+ tree, false if not.
|
|
68
|
+
* Use get() for best performance; use has() if you need to
|
|
69
|
+
* distinguish between "undefined value" and "key not present".
|
|
70
|
+
* @param key Key to detect
|
|
71
|
+
* @description Computational complexity: O(log size)
|
|
72
|
+
*/
|
|
73
|
+
has(key) {
|
|
74
|
+
return this.forRange(key, key, true, void 0) !== 0;
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Removes a single key-value pair from the B+ tree.
|
|
78
|
+
* @param key Key to find
|
|
79
|
+
* @returns true if a pair was found and removed, false otherwise.
|
|
80
|
+
* @description Computational complexity: O(log size)
|
|
81
|
+
*/
|
|
82
|
+
delete(key) {
|
|
83
|
+
return this.editRange(key, key, true, DeleteRange) !== 0;
|
|
84
|
+
}
|
|
85
|
+
// ///////////////////////////////////////////////////////////////////////////
|
|
86
|
+
// Additional methods ///////////////////////////////////////////////////////
|
|
87
|
+
/** Returns the maximum number of children/values before nodes will split. */
|
|
88
|
+
get maxNodeSize() {
|
|
89
|
+
return this._maxNodeSize;
|
|
90
|
+
}
|
|
91
|
+
/** Gets the lowest key in the tree. Complexity: O(log size) */
|
|
92
|
+
minKey() {
|
|
93
|
+
return this._root.minKey();
|
|
94
|
+
}
|
|
95
|
+
/** Gets the highest key in the tree. Complexity: O(1) */
|
|
96
|
+
maxKey() {
|
|
97
|
+
return this._root.maxKey();
|
|
98
|
+
}
|
|
99
|
+
/** Gets an array of all keys, sorted */
|
|
100
|
+
keysArray() {
|
|
101
|
+
const results = [];
|
|
102
|
+
this._root.forRange(
|
|
103
|
+
this.minKey(),
|
|
104
|
+
this.maxKey(),
|
|
105
|
+
true,
|
|
106
|
+
false,
|
|
107
|
+
this,
|
|
108
|
+
0,
|
|
109
|
+
(k, _v) => {
|
|
110
|
+
results.push(k);
|
|
111
|
+
}
|
|
112
|
+
);
|
|
113
|
+
return results;
|
|
114
|
+
}
|
|
115
|
+
/** Returns the next pair whose key is larger than the specified key (or undefined if there is none).
|
|
116
|
+
* If key === undefined, this function returns the lowest pair.
|
|
117
|
+
* @param key The key to search for.
|
|
118
|
+
* @param reusedArray Optional array used repeatedly to store key-value pairs, to
|
|
119
|
+
* avoid creating a new array on every iteration.
|
|
120
|
+
*/
|
|
121
|
+
nextHigherPair(key, reusedArray) {
|
|
122
|
+
reusedArray = reusedArray || [];
|
|
123
|
+
if (key === void 0) {
|
|
124
|
+
return this._root.minPair(reusedArray);
|
|
125
|
+
}
|
|
126
|
+
return this._root.getPairOrNextHigher(
|
|
127
|
+
key,
|
|
128
|
+
this._compare,
|
|
129
|
+
false,
|
|
130
|
+
reusedArray
|
|
131
|
+
);
|
|
132
|
+
}
|
|
133
|
+
/** Returns the next pair whose key is smaller than the specified key (or undefined if there is none).
|
|
134
|
+
* If key === undefined, this function returns the highest pair.
|
|
135
|
+
* @param key The key to search for.
|
|
136
|
+
* @param reusedArray Optional array used repeatedly to store key-value pairs, to
|
|
137
|
+
* avoid creating a new array each time you call this method.
|
|
138
|
+
*/
|
|
139
|
+
nextLowerPair(key, reusedArray) {
|
|
140
|
+
reusedArray = reusedArray || [];
|
|
141
|
+
if (key === void 0) {
|
|
142
|
+
return this._root.maxPair(reusedArray);
|
|
143
|
+
}
|
|
144
|
+
return this._root.getPairOrNextLower(key, this._compare, false, reusedArray);
|
|
145
|
+
}
|
|
146
|
+
/** Adds all pairs from a list of key-value pairs.
|
|
147
|
+
* @param pairs Pairs to add to this tree. If there are duplicate keys,
|
|
148
|
+
* later pairs currently overwrite earlier ones (e.g. [[0,1],[0,7]]
|
|
149
|
+
* associates 0 with 7.)
|
|
150
|
+
* @param overwrite Whether to overwrite pairs that already exist (if false,
|
|
151
|
+
* pairs[i] is ignored when the key pairs[i][0] already exists.)
|
|
152
|
+
* @returns The number of pairs added to the collection.
|
|
153
|
+
* @description Computational complexity: O(pairs.length * log(size + pairs.length))
|
|
154
|
+
*/
|
|
155
|
+
setPairs(pairs, overwrite) {
|
|
156
|
+
let added = 0;
|
|
157
|
+
for (const pair of pairs) {
|
|
158
|
+
if (this.set(pair[0], pair[1], overwrite)) added++;
|
|
159
|
+
}
|
|
160
|
+
return added;
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Scans the specified range of keys, in ascending order by key.
|
|
164
|
+
* Note: the callback `onFound` must not insert or remove items in the
|
|
165
|
+
* collection. Doing so may cause incorrect data to be sent to the
|
|
166
|
+
* callback afterward.
|
|
167
|
+
* @param low The first key scanned will be greater than or equal to `low`.
|
|
168
|
+
* @param high Scanning stops when a key larger than this is reached.
|
|
169
|
+
* @param includeHigh If the `high` key is present, `onFound` is called for
|
|
170
|
+
* that final pair if and only if this parameter is true.
|
|
171
|
+
* @param onFound A function that is called for each key-value pair. This
|
|
172
|
+
* function can return {break:R} to stop early with result R.
|
|
173
|
+
* @param initialCounter Initial third argument of onFound. This value
|
|
174
|
+
* increases by one each time `onFound` is called. Default: 0
|
|
175
|
+
* @returns The number of values found, or R if the callback returned
|
|
176
|
+
* `{break:R}` to stop early.
|
|
177
|
+
* @description Computational complexity: O(number of items scanned + log size)
|
|
178
|
+
*/
|
|
179
|
+
forRange(low, high, includeHigh, onFound, initialCounter) {
|
|
180
|
+
const r = this._root.forRange(
|
|
181
|
+
low,
|
|
182
|
+
high,
|
|
183
|
+
includeHigh,
|
|
184
|
+
false,
|
|
185
|
+
this,
|
|
186
|
+
initialCounter || 0,
|
|
187
|
+
onFound
|
|
188
|
+
);
|
|
189
|
+
return typeof r === `number` ? r : r.break;
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* Scans and potentially modifies values for a subsequence of keys.
|
|
193
|
+
* Note: the callback `onFound` should ideally be a pure function.
|
|
194
|
+
* Specfically, it must not insert items, call clone(), or change
|
|
195
|
+
* the collection except via return value; out-of-band editing may
|
|
196
|
+
* cause an exception or may cause incorrect data to be sent to
|
|
197
|
+
* the callback (duplicate or missed items). It must not cause a
|
|
198
|
+
* clone() of the collection, otherwise the clone could be modified
|
|
199
|
+
* by changes requested by the callback.
|
|
200
|
+
* @param low The first key scanned will be greater than or equal to `low`.
|
|
201
|
+
* @param high Scanning stops when a key larger than this is reached.
|
|
202
|
+
* @param includeHigh If the `high` key is present, `onFound` is called for
|
|
203
|
+
* that final pair if and only if this parameter is true.
|
|
204
|
+
* @param onFound A function that is called for each key-value pair. This
|
|
205
|
+
* function can return `{value:v}` to change the value associated
|
|
206
|
+
* with the current key, `{delete:true}` to delete the current pair,
|
|
207
|
+
* `{break:R}` to stop early with result R, or it can return nothing
|
|
208
|
+
* (undefined or {}) to cause no effect and continue iterating.
|
|
209
|
+
* `{break:R}` can be combined with one of the other two commands.
|
|
210
|
+
* The third argument `counter` is the number of items iterated
|
|
211
|
+
* previously; it equals 0 when `onFound` is called the first time.
|
|
212
|
+
* @returns The number of values scanned, or R if the callback returned
|
|
213
|
+
* `{break:R}` to stop early.
|
|
214
|
+
* @description
|
|
215
|
+
* Computational complexity: O(number of items scanned + log size)
|
|
216
|
+
* Note: if the tree has been cloned with clone(), any shared
|
|
217
|
+
* nodes are copied before `onFound` is called. This takes O(n) time
|
|
218
|
+
* where n is proportional to the amount of shared data scanned.
|
|
219
|
+
*/
|
|
220
|
+
editRange(low, high, includeHigh, onFound, initialCounter) {
|
|
221
|
+
let root = this._root;
|
|
222
|
+
if (root.isShared) this._root = root = root.clone();
|
|
223
|
+
try {
|
|
224
|
+
const r = root.forRange(
|
|
225
|
+
low,
|
|
226
|
+
high,
|
|
227
|
+
includeHigh,
|
|
228
|
+
true,
|
|
229
|
+
this,
|
|
230
|
+
initialCounter || 0,
|
|
231
|
+
onFound
|
|
232
|
+
);
|
|
233
|
+
return typeof r === `number` ? r : r.break;
|
|
234
|
+
} finally {
|
|
235
|
+
let isShared;
|
|
236
|
+
while (root.keys.length <= 1 && !root.isLeaf) {
|
|
237
|
+
isShared || (isShared = root.isShared);
|
|
238
|
+
this._root = root = root.keys.length === 0 ? EmptyLeaf : root.children[0];
|
|
239
|
+
}
|
|
240
|
+
if (isShared) {
|
|
241
|
+
root.isShared = true;
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
class BNode {
|
|
247
|
+
get isLeaf() {
|
|
248
|
+
return this.children === void 0;
|
|
249
|
+
}
|
|
250
|
+
constructor(keys = [], values) {
|
|
251
|
+
this.keys = keys;
|
|
252
|
+
this.values = values || undefVals;
|
|
253
|
+
this.isShared = void 0;
|
|
254
|
+
}
|
|
255
|
+
// /////////////////////////////////////////////////////////////////////////
|
|
256
|
+
// Shared methods /////////////////////////////////////////////////////////
|
|
257
|
+
maxKey() {
|
|
258
|
+
return this.keys[this.keys.length - 1];
|
|
259
|
+
}
|
|
260
|
+
// If key not found, returns i^failXor where i is the insertion index.
|
|
261
|
+
// Callers that don't care whether there was a match will set failXor=0.
|
|
262
|
+
indexOf(key, failXor, cmp) {
|
|
263
|
+
const keys = this.keys;
|
|
264
|
+
let lo = 0, hi = keys.length, mid = hi >> 1;
|
|
265
|
+
while (lo < hi) {
|
|
266
|
+
const c = cmp(keys[mid], key);
|
|
267
|
+
if (c < 0) lo = mid + 1;
|
|
268
|
+
else if (c > 0)
|
|
269
|
+
hi = mid;
|
|
270
|
+
else if (c === 0) return mid;
|
|
271
|
+
else {
|
|
272
|
+
if (key === key)
|
|
273
|
+
return keys.length;
|
|
274
|
+
else throw new Error(`BTree: NaN was used as a key`);
|
|
275
|
+
}
|
|
276
|
+
mid = lo + hi >> 1;
|
|
277
|
+
}
|
|
278
|
+
return mid ^ failXor;
|
|
279
|
+
}
|
|
280
|
+
// ///////////////////////////////////////////////////////////////////////////
|
|
281
|
+
// Leaf Node: misc //////////////////////////////////////////////////////////
|
|
282
|
+
minKey() {
|
|
283
|
+
return this.keys[0];
|
|
284
|
+
}
|
|
285
|
+
minPair(reusedArray) {
|
|
286
|
+
if (this.keys.length === 0) return void 0;
|
|
287
|
+
reusedArray[0] = this.keys[0];
|
|
288
|
+
reusedArray[1] = this.values[0];
|
|
289
|
+
return reusedArray;
|
|
290
|
+
}
|
|
291
|
+
maxPair(reusedArray) {
|
|
292
|
+
if (this.keys.length === 0) return void 0;
|
|
293
|
+
const lastIndex = this.keys.length - 1;
|
|
294
|
+
reusedArray[0] = this.keys[lastIndex];
|
|
295
|
+
reusedArray[1] = this.values[lastIndex];
|
|
296
|
+
return reusedArray;
|
|
297
|
+
}
|
|
298
|
+
clone() {
|
|
299
|
+
const v = this.values;
|
|
300
|
+
return new BNode(this.keys.slice(0), v === undefVals ? v : v.slice(0));
|
|
301
|
+
}
|
|
302
|
+
get(key, defaultValue, tree) {
|
|
303
|
+
const i = this.indexOf(key, -1, tree._compare);
|
|
304
|
+
return i < 0 ? defaultValue : this.values[i];
|
|
305
|
+
}
|
|
306
|
+
getPairOrNextLower(key, compare, inclusive, reusedArray) {
|
|
307
|
+
const i = this.indexOf(key, -1, compare);
|
|
308
|
+
const indexOrLower = i < 0 ? ~i - 1 : inclusive ? i : i - 1;
|
|
309
|
+
if (indexOrLower >= 0) {
|
|
310
|
+
reusedArray[0] = this.keys[indexOrLower];
|
|
311
|
+
reusedArray[1] = this.values[indexOrLower];
|
|
312
|
+
return reusedArray;
|
|
313
|
+
}
|
|
314
|
+
return void 0;
|
|
315
|
+
}
|
|
316
|
+
getPairOrNextHigher(key, compare, inclusive, reusedArray) {
|
|
317
|
+
const i = this.indexOf(key, -1, compare);
|
|
318
|
+
const indexOrLower = i < 0 ? ~i : inclusive ? i : i + 1;
|
|
319
|
+
const keys = this.keys;
|
|
320
|
+
if (indexOrLower < keys.length) {
|
|
321
|
+
reusedArray[0] = keys[indexOrLower];
|
|
322
|
+
reusedArray[1] = this.values[indexOrLower];
|
|
323
|
+
return reusedArray;
|
|
324
|
+
}
|
|
325
|
+
return void 0;
|
|
326
|
+
}
|
|
327
|
+
// ///////////////////////////////////////////////////////////////////////////
|
|
328
|
+
// Leaf Node: set & node splitting //////////////////////////////////////////
|
|
329
|
+
set(key, value, overwrite, tree) {
|
|
330
|
+
let i = this.indexOf(key, -1, tree._compare);
|
|
331
|
+
if (i < 0) {
|
|
332
|
+
i = ~i;
|
|
333
|
+
tree._size++;
|
|
334
|
+
if (this.keys.length < tree._maxNodeSize) {
|
|
335
|
+
return this.insertInLeaf(i, key, value, tree);
|
|
336
|
+
} else {
|
|
337
|
+
const newRightSibling = this.splitOffRightSide();
|
|
338
|
+
let target = this;
|
|
339
|
+
if (i > this.keys.length) {
|
|
340
|
+
i -= this.keys.length;
|
|
341
|
+
target = newRightSibling;
|
|
342
|
+
}
|
|
343
|
+
target.insertInLeaf(i, key, value, tree);
|
|
344
|
+
return newRightSibling;
|
|
345
|
+
}
|
|
346
|
+
} else {
|
|
347
|
+
if (overwrite !== false) {
|
|
348
|
+
if (value !== void 0) this.reifyValues();
|
|
349
|
+
this.keys[i] = key;
|
|
350
|
+
this.values[i] = value;
|
|
351
|
+
}
|
|
352
|
+
return false;
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
reifyValues() {
|
|
356
|
+
if (this.values === undefVals)
|
|
357
|
+
return this.values = this.values.slice(0, this.keys.length);
|
|
358
|
+
return this.values;
|
|
359
|
+
}
|
|
360
|
+
insertInLeaf(i, key, value, tree) {
|
|
361
|
+
this.keys.splice(i, 0, key);
|
|
362
|
+
if (this.values === undefVals) {
|
|
363
|
+
while (undefVals.length < tree._maxNodeSize) undefVals.push(void 0);
|
|
364
|
+
if (value === void 0) {
|
|
365
|
+
return true;
|
|
366
|
+
} else {
|
|
367
|
+
this.values = undefVals.slice(0, this.keys.length - 1);
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
this.values.splice(i, 0, value);
|
|
371
|
+
return true;
|
|
372
|
+
}
|
|
373
|
+
takeFromRight(rhs) {
|
|
374
|
+
let v = this.values;
|
|
375
|
+
if (rhs.values === undefVals) {
|
|
376
|
+
if (v !== undefVals) v.push(void 0);
|
|
377
|
+
} else {
|
|
378
|
+
v = this.reifyValues();
|
|
379
|
+
v.push(rhs.values.shift());
|
|
380
|
+
}
|
|
381
|
+
this.keys.push(rhs.keys.shift());
|
|
382
|
+
}
|
|
383
|
+
takeFromLeft(lhs) {
|
|
384
|
+
let v = this.values;
|
|
385
|
+
if (lhs.values === undefVals) {
|
|
386
|
+
if (v !== undefVals) v.unshift(void 0);
|
|
387
|
+
} else {
|
|
388
|
+
v = this.reifyValues();
|
|
389
|
+
v.unshift(lhs.values.pop());
|
|
390
|
+
}
|
|
391
|
+
this.keys.unshift(lhs.keys.pop());
|
|
392
|
+
}
|
|
393
|
+
splitOffRightSide() {
|
|
394
|
+
const half = this.keys.length >> 1, keys = this.keys.splice(half);
|
|
395
|
+
const values = this.values === undefVals ? undefVals : this.values.splice(half);
|
|
396
|
+
return new BNode(keys, values);
|
|
397
|
+
}
|
|
398
|
+
// ///////////////////////////////////////////////////////////////////////////
|
|
399
|
+
// Leaf Node: scanning & deletions //////////////////////////////////////////
|
|
400
|
+
forRange(low, high, includeHigh, editMode, tree, count, onFound) {
|
|
401
|
+
const cmp = tree._compare;
|
|
402
|
+
let iLow, iHigh;
|
|
403
|
+
if (high === low) {
|
|
404
|
+
if (!includeHigh) return count;
|
|
405
|
+
iHigh = (iLow = this.indexOf(low, -1, cmp)) + 1;
|
|
406
|
+
if (iLow < 0) return count;
|
|
407
|
+
} else {
|
|
408
|
+
iLow = this.indexOf(low, 0, cmp);
|
|
409
|
+
iHigh = this.indexOf(high, -1, cmp);
|
|
410
|
+
if (iHigh < 0) iHigh = ~iHigh;
|
|
411
|
+
else if (includeHigh === true) iHigh++;
|
|
412
|
+
}
|
|
413
|
+
const keys = this.keys, values = this.values;
|
|
414
|
+
if (onFound !== void 0) {
|
|
415
|
+
for (let i = iLow; i < iHigh; i++) {
|
|
416
|
+
const key = keys[i];
|
|
417
|
+
const result = onFound(key, values[i], count++);
|
|
418
|
+
if (result !== void 0) {
|
|
419
|
+
if (editMode === true) {
|
|
420
|
+
if (key !== keys[i] || this.isShared === true)
|
|
421
|
+
throw new Error(`BTree illegally changed or cloned in editRange`);
|
|
422
|
+
if (result.delete) {
|
|
423
|
+
this.keys.splice(i, 1);
|
|
424
|
+
if (this.values !== undefVals) this.values.splice(i, 1);
|
|
425
|
+
tree._size--;
|
|
426
|
+
i--;
|
|
427
|
+
iHigh--;
|
|
428
|
+
} else if (result.hasOwnProperty(`value`)) {
|
|
429
|
+
values[i] = result.value;
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
if (result.break !== void 0) return result;
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
} else count += iHigh - iLow;
|
|
436
|
+
return count;
|
|
437
|
+
}
|
|
438
|
+
/** Adds entire contents of right-hand sibling (rhs is left unchanged) */
|
|
439
|
+
mergeSibling(rhs, _) {
|
|
440
|
+
this.keys.push.apply(this.keys, rhs.keys);
|
|
441
|
+
if (this.values === undefVals) {
|
|
442
|
+
if (rhs.values === undefVals) return;
|
|
443
|
+
this.values = this.values.slice(0, this.keys.length);
|
|
444
|
+
}
|
|
445
|
+
this.values.push.apply(this.values, rhs.reifyValues());
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
class BNodeInternal extends BNode {
|
|
449
|
+
/**
|
|
450
|
+
* This does not mark `children` as shared, so it is the responsibility of the caller
|
|
451
|
+
* to ensure children are either marked shared, or aren't included in another tree.
|
|
452
|
+
*/
|
|
453
|
+
constructor(children, keys) {
|
|
454
|
+
if (!keys) {
|
|
455
|
+
keys = [];
|
|
456
|
+
for (let i = 0; i < children.length; i++) keys[i] = children[i].maxKey();
|
|
457
|
+
}
|
|
458
|
+
super(keys);
|
|
459
|
+
this.children = children;
|
|
460
|
+
}
|
|
461
|
+
minKey() {
|
|
462
|
+
return this.children[0].minKey();
|
|
463
|
+
}
|
|
464
|
+
minPair(reusedArray) {
|
|
465
|
+
return this.children[0].minPair(reusedArray);
|
|
466
|
+
}
|
|
467
|
+
maxPair(reusedArray) {
|
|
468
|
+
return this.children[this.children.length - 1].maxPair(reusedArray);
|
|
469
|
+
}
|
|
470
|
+
get(key, defaultValue, tree) {
|
|
471
|
+
const i = this.indexOf(key, 0, tree._compare), children = this.children;
|
|
472
|
+
return i < children.length ? children[i].get(key, defaultValue, tree) : void 0;
|
|
473
|
+
}
|
|
474
|
+
getPairOrNextLower(key, compare, inclusive, reusedArray) {
|
|
475
|
+
const i = this.indexOf(key, 0, compare), children = this.children;
|
|
476
|
+
if (i >= children.length) return this.maxPair(reusedArray);
|
|
477
|
+
const result = children[i].getPairOrNextLower(
|
|
478
|
+
key,
|
|
479
|
+
compare,
|
|
480
|
+
inclusive,
|
|
481
|
+
reusedArray
|
|
482
|
+
);
|
|
483
|
+
if (result === void 0 && i > 0) {
|
|
484
|
+
return children[i - 1].maxPair(reusedArray);
|
|
485
|
+
}
|
|
486
|
+
return result;
|
|
487
|
+
}
|
|
488
|
+
getPairOrNextHigher(key, compare, inclusive, reusedArray) {
|
|
489
|
+
const i = this.indexOf(key, 0, compare), children = this.children, length = children.length;
|
|
490
|
+
if (i >= length) return void 0;
|
|
491
|
+
const result = children[i].getPairOrNextHigher(
|
|
492
|
+
key,
|
|
493
|
+
compare,
|
|
494
|
+
inclusive,
|
|
495
|
+
reusedArray
|
|
496
|
+
);
|
|
497
|
+
if (result === void 0 && i < length - 1) {
|
|
498
|
+
return children[i + 1].minPair(reusedArray);
|
|
499
|
+
}
|
|
500
|
+
return result;
|
|
501
|
+
}
|
|
502
|
+
// ///////////////////////////////////////////////////////////////////////////
|
|
503
|
+
// Internal Node: set & node splitting //////////////////////////////////////
|
|
504
|
+
set(key, value, overwrite, tree) {
|
|
505
|
+
const c = this.children, max = tree._maxNodeSize, cmp = tree._compare;
|
|
506
|
+
let i = Math.min(this.indexOf(key, 0, cmp), c.length - 1), child = c[i];
|
|
507
|
+
if (child.isShared) c[i] = child = child.clone();
|
|
508
|
+
if (child.keys.length >= max) {
|
|
509
|
+
let other;
|
|
510
|
+
if (i > 0 && (other = c[i - 1]).keys.length < max && cmp(child.keys[0], key) < 0) {
|
|
511
|
+
if (other.isShared) c[i - 1] = other = other.clone();
|
|
512
|
+
other.takeFromRight(child);
|
|
513
|
+
this.keys[i - 1] = other.maxKey();
|
|
514
|
+
} else if ((other = c[i + 1]) !== void 0 && other.keys.length < max && cmp(child.maxKey(), key) < 0) {
|
|
515
|
+
if (other.isShared) c[i + 1] = other = other.clone();
|
|
516
|
+
other.takeFromLeft(child);
|
|
517
|
+
this.keys[i] = c[i].maxKey();
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
const result = child.set(key, value, overwrite, tree);
|
|
521
|
+
if (result === false) return false;
|
|
522
|
+
this.keys[i] = child.maxKey();
|
|
523
|
+
if (result === true) return true;
|
|
524
|
+
if (this.keys.length < max) {
|
|
525
|
+
this.insert(i + 1, result);
|
|
526
|
+
return true;
|
|
527
|
+
} else {
|
|
528
|
+
const newRightSibling = this.splitOffRightSide();
|
|
529
|
+
let target = this;
|
|
530
|
+
if (cmp(result.maxKey(), this.maxKey()) > 0) {
|
|
531
|
+
target = newRightSibling;
|
|
532
|
+
i -= this.keys.length;
|
|
533
|
+
}
|
|
534
|
+
target.insert(i + 1, result);
|
|
535
|
+
return newRightSibling;
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
/**
|
|
539
|
+
* Inserts `child` at index `i`.
|
|
540
|
+
* This does not mark `child` as shared, so it is the responsibility of the caller
|
|
541
|
+
* to ensure that either child is marked shared, or it is not included in another tree.
|
|
542
|
+
*/
|
|
543
|
+
insert(i, child) {
|
|
544
|
+
this.children.splice(i, 0, child);
|
|
545
|
+
this.keys.splice(i, 0, child.maxKey());
|
|
546
|
+
}
|
|
547
|
+
/**
|
|
548
|
+
* Split this node.
|
|
549
|
+
* Modifies this to remove the second half of the items, returning a separate node containing them.
|
|
550
|
+
*/
|
|
551
|
+
splitOffRightSide() {
|
|
552
|
+
const half = this.children.length >> 1;
|
|
553
|
+
return new BNodeInternal(
|
|
554
|
+
this.children.splice(half),
|
|
555
|
+
this.keys.splice(half)
|
|
556
|
+
);
|
|
557
|
+
}
|
|
558
|
+
takeFromRight(rhs) {
|
|
559
|
+
this.keys.push(rhs.keys.shift());
|
|
560
|
+
this.children.push(rhs.children.shift());
|
|
561
|
+
}
|
|
562
|
+
takeFromLeft(lhs) {
|
|
563
|
+
this.keys.unshift(lhs.keys.pop());
|
|
564
|
+
this.children.unshift(lhs.children.pop());
|
|
565
|
+
}
|
|
566
|
+
// ///////////////////////////////////////////////////////////////////////////
|
|
567
|
+
// Internal Node: scanning & deletions //////////////////////////////////////
|
|
568
|
+
// Note: `count` is the next value of the third argument to `onFound`.
|
|
569
|
+
// A leaf node's `forRange` function returns a new value for this counter,
|
|
570
|
+
// unless the operation is to stop early.
|
|
571
|
+
forRange(low, high, includeHigh, editMode, tree, count, onFound) {
|
|
572
|
+
const cmp = tree._compare;
|
|
573
|
+
const keys = this.keys, children = this.children;
|
|
574
|
+
let iLow = this.indexOf(low, 0, cmp), i = iLow;
|
|
575
|
+
const iHigh = Math.min(
|
|
576
|
+
high === low ? iLow : this.indexOf(high, 0, cmp),
|
|
577
|
+
keys.length - 1
|
|
578
|
+
);
|
|
579
|
+
if (!editMode) {
|
|
580
|
+
for (; i <= iHigh; i++) {
|
|
581
|
+
const result = children[i].forRange(
|
|
582
|
+
low,
|
|
583
|
+
high,
|
|
584
|
+
includeHigh,
|
|
585
|
+
editMode,
|
|
586
|
+
tree,
|
|
587
|
+
count,
|
|
588
|
+
onFound
|
|
589
|
+
);
|
|
590
|
+
if (typeof result !== `number`) return result;
|
|
591
|
+
count = result;
|
|
592
|
+
}
|
|
593
|
+
} else if (i <= iHigh) {
|
|
594
|
+
try {
|
|
595
|
+
for (; i <= iHigh; i++) {
|
|
596
|
+
if (children[i].isShared) children[i] = children[i].clone();
|
|
597
|
+
const result = children[i].forRange(
|
|
598
|
+
low,
|
|
599
|
+
high,
|
|
600
|
+
includeHigh,
|
|
601
|
+
editMode,
|
|
602
|
+
tree,
|
|
603
|
+
count,
|
|
604
|
+
onFound
|
|
605
|
+
);
|
|
606
|
+
keys[i] = children[i].maxKey();
|
|
607
|
+
if (typeof result !== `number`) return result;
|
|
608
|
+
count = result;
|
|
609
|
+
}
|
|
610
|
+
} finally {
|
|
611
|
+
const half = tree._maxNodeSize >> 1;
|
|
612
|
+
if (iLow > 0) iLow--;
|
|
613
|
+
for (i = iHigh; i >= iLow; i--) {
|
|
614
|
+
if (children[i].keys.length <= half) {
|
|
615
|
+
if (children[i].keys.length !== 0) {
|
|
616
|
+
this.tryMerge(i, tree._maxNodeSize);
|
|
617
|
+
} else {
|
|
618
|
+
keys.splice(i, 1);
|
|
619
|
+
children.splice(i, 1);
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
if (children.length !== 0 && children[0].keys.length === 0)
|
|
624
|
+
check(false, `emptiness bug`);
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
return count;
|
|
628
|
+
}
|
|
629
|
+
/** Merges child i with child i+1 if their combined size is not too large */
|
|
630
|
+
tryMerge(i, maxSize) {
|
|
631
|
+
const children = this.children;
|
|
632
|
+
if (i >= 0 && i + 1 < children.length) {
|
|
633
|
+
if (children[i].keys.length + children[i + 1].keys.length <= maxSize) {
|
|
634
|
+
if (children[i].isShared)
|
|
635
|
+
children[i] = children[i].clone();
|
|
636
|
+
children[i].mergeSibling(children[i + 1], maxSize);
|
|
637
|
+
children.splice(i + 1, 1);
|
|
638
|
+
this.keys.splice(i + 1, 1);
|
|
639
|
+
this.keys[i] = children[i].maxKey();
|
|
640
|
+
return true;
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
return false;
|
|
644
|
+
}
|
|
645
|
+
/**
|
|
646
|
+
* Move children from `rhs` into this.
|
|
647
|
+
* `rhs` must be part of this tree, and be removed from it after this call
|
|
648
|
+
* (otherwise isShared for its children could be incorrect).
|
|
649
|
+
*/
|
|
650
|
+
mergeSibling(rhs, maxNodeSize) {
|
|
651
|
+
const oldLength = this.keys.length;
|
|
652
|
+
this.keys.push.apply(this.keys, rhs.keys);
|
|
653
|
+
const rhsChildren = rhs.children;
|
|
654
|
+
this.children.push.apply(this.children, rhsChildren);
|
|
655
|
+
if (rhs.isShared && !this.isShared) {
|
|
656
|
+
for (const child of rhsChildren) child.isShared = true;
|
|
657
|
+
}
|
|
658
|
+
this.tryMerge(oldLength - 1, maxNodeSize);
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
const undefVals = [];
|
|
662
|
+
const Delete = { delete: true }, DeleteRange = () => Delete;
|
|
663
|
+
const EmptyLeaf = function() {
|
|
664
|
+
const n = new BNode();
|
|
665
|
+
n.isShared = true;
|
|
666
|
+
return n;
|
|
667
|
+
}();
|
|
668
|
+
function check(fact, ...args) {
|
|
669
|
+
{
|
|
670
|
+
args.unshift(`B+ tree`);
|
|
671
|
+
throw new Error(args.join(` `));
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
export {
|
|
675
|
+
BTree
|
|
676
|
+
};
|
|
677
|
+
//# sourceMappingURL=btree.js.map
|