serializable-bptree 6.0.2 → 6.1.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 +15 -374
- package/dist/cjs/index.cjs +137 -14
- package/dist/esm/index.mjs +137 -14
- package/dist/types/BPTreeAsync.d.ts +8 -3
- package/dist/types/BPTreeSync.d.ts +8 -3
- package/dist/types/base/BPTree.d.ts +16 -3
- package/dist/types/base/ValueComparator.d.ts +19 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -109,386 +109,27 @@ import {
|
|
|
109
109
|
</script>
|
|
110
110
|
```
|
|
111
111
|
|
|
112
|
+
## Documentation
|
|
113
|
+
|
|
114
|
+
Explore the detailed guides and concepts of `serializable-bptree`:
|
|
115
|
+
|
|
116
|
+
- **Core Concepts**
|
|
117
|
+
- [Value Comparators](./docs/COMPARATORS.md): How sorting and matching works.
|
|
118
|
+
- [Serialize Strategies](./docs/STRATEGIES.md): How to persist nodes to storage.
|
|
119
|
+
- **API & Usage**
|
|
120
|
+
- [Query Conditions](./docs/QUERY.md): Detailed explanation of the `where()` operators.
|
|
121
|
+
- [Asynchronous Usage](./docs/ASYNC.md): How to use the tree in an async environment.
|
|
122
|
+
- **Advanced Topics**
|
|
123
|
+
- [Duplicate Value Handling](./docs/DUPLICATE_VALUES.md): Strategies for managing large amounts of duplicate data.
|
|
124
|
+
- [Concurrency & Synchronization](./docs/CONCURRENCY.md): Multi-instance usage and locking mechanisms.
|
|
125
|
+
|
|
112
126
|
## Migration from v5.x.x to v6.0.0
|
|
113
127
|
|
|
114
128
|
Version 6.0.0 includes a critical fix for how internal nodes are sorted.
|
|
115
129
|
|
|
116
130
|
> [!IMPORTANT]
|
|
117
131
|
> **Breaking Changes & Incompatibility**
|
|
118
|
-
>
|
|
119
|
-
> v6.0.0 enforces strict value sorting. **Data structures created with v5.x.x or earlier may be incompatible** with v6.0.0 if they contain unsorted internal nodes. It is highly recommended to rebuild your tree from scratch when upgrading.
|
|
120
|
-
|
|
121
|
-
## Conceptualization
|
|
122
|
-
|
|
123
|
-
### Value comparator
|
|
124
|
-
|
|
125
|
-
B+tree needs to keep values in sorted order. Therefore, a process to compare the sizes of values is needed, and that role is played by the **ValueComparator**.
|
|
126
|
-
|
|
127
|
-
Commonly used numerical and string comparisons are natively supported by the **serializable-bptree** library. Use it as follows:
|
|
128
|
-
|
|
129
|
-
```typescript
|
|
130
|
-
import { NumericComparator, StringComparator } from 'serializable-bptree'
|
|
131
|
-
```
|
|
132
|
-
|
|
133
|
-
However, you may want to sort complex objects other than numbers and strings. For example, if you want to sort by the **age** property order of an object, you need to create a new class that inherits from the **ValueComparator** class. Use it as follows:
|
|
134
|
-
|
|
135
|
-
```typescript
|
|
136
|
-
import { ValueComparator } from 'serializable-bptree'
|
|
137
|
-
|
|
138
|
-
interface MyObject {
|
|
139
|
-
age: number
|
|
140
|
-
name: string
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
class AgeComparator extends ValueComparator<MyObject> {
|
|
144
|
-
asc(a: MyObject, b: MyObject): number {
|
|
145
|
-
return a.age - b.age
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
match(value: MyObject): string {
|
|
149
|
-
return value.age.toString()
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
```
|
|
153
|
-
|
|
154
|
-
#### asc
|
|
155
|
-
|
|
156
|
-
The **asc** method should return values in ascending order. If the return value is negative, it means that the parameter **a** is smaller than **b**. If the return value is positive, it means that **a** is greater than **b**. If the return value is **0**, it indicates that **a** and **b** are of the same size.
|
|
157
|
-
|
|
158
|
-
#### match
|
|
159
|
-
|
|
160
|
-
The `match` method is used for the **LIKE** operator. This method specifies which value to test against a regular expression. For example, if you have a tree with values of the structure `{ country: string, capital: string }`, and you want to perform a **LIKE** operation based on the **capital** value, the method should return **value.capital**. In this case, you **CANNOT** perform a **LIKE** operation based on the **country** attribute. The returned value must be a string.
|
|
161
|
-
|
|
162
|
-
```typescript
|
|
163
|
-
interface MyObject {
|
|
164
|
-
country: string
|
|
165
|
-
capital: string
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
class CompositeComparator extends ValueComparator<MyObject> {
|
|
169
|
-
...
|
|
170
|
-
match(value: MyObject): string {
|
|
171
|
-
return value.capital
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
```
|
|
175
|
-
|
|
176
|
-
For a tree with simple structure, without complex nesting, returning the value directly would be sufficient.
|
|
177
|
-
|
|
178
|
-
```typescript
|
|
179
|
-
class StringComparator extends ValueComparator<string> {
|
|
180
|
-
match(value: string): string {
|
|
181
|
-
return value
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
|
-
```
|
|
185
|
-
|
|
186
|
-
### Serialize strategy
|
|
187
|
-
|
|
188
|
-
A B+tree instance is made up of numerous nodes. You would want to store this value when such nodes are created or updated. Let's assume you want to save it to a file.
|
|
189
|
-
|
|
190
|
-
You need to construct a logic for input/output from the file by inheriting the SerializeStrategy class. Look at the class structure below:
|
|
191
|
-
|
|
192
|
-
```typescript
|
|
193
|
-
import { SerializeStrategySync } from 'serializable-bptree'
|
|
194
|
-
|
|
195
|
-
class MyFileIOStrategySync extends SerializeStrategySync {
|
|
196
|
-
id(): string
|
|
197
|
-
read(id: string): BPTreeNode<K, V>
|
|
198
|
-
write(id: string, node: BPTreeNode<K, V>): void
|
|
199
|
-
delete(id: string): void
|
|
200
|
-
readHead(): SerializeStrategyHead|null
|
|
201
|
-
writeHead(head: SerializeStrategyHead): void
|
|
202
|
-
}
|
|
203
|
-
```
|
|
204
|
-
|
|
205
|
-
What does this method mean? And why do we need to construct such a method?
|
|
206
|
-
|
|
207
|
-
#### id(isLeaf: `boolean`): `string`
|
|
208
|
-
|
|
209
|
-
When a node is created in the B+tree, the node needs a unique value to represent itself. This is the **node.id** attribute, and you can specify this attribute yourself.
|
|
210
|
-
|
|
211
|
-
Typically, such an **id** value can be a unique string like a UUID. Below is an example of usage:
|
|
212
|
-
|
|
213
|
-
```typescript
|
|
214
|
-
id(isLeaf: boolean): string {
|
|
215
|
-
return crypto.randomUUID()
|
|
216
|
-
}
|
|
217
|
-
```
|
|
218
|
-
|
|
219
|
-
The **id** method is called before a node is created in the tree. Therefore, it can also be used to allocate space for storing the node.
|
|
220
|
-
|
|
221
|
-
#### read(id: `string`): `BPTreeNode<K, V>`
|
|
222
|
-
|
|
223
|
-
This is a method to load the saved value as a tree instance. If you have previously saved the node as a file, you should use this method to convert it back to JavaScript JSON format and return it.
|
|
224
|
-
|
|
225
|
-
Please refer to the example below:
|
|
226
|
-
|
|
227
|
-
```typescript
|
|
228
|
-
read(id: string): BPTreeNode<K, V> {
|
|
229
|
-
const filePath = `./my-store/${id}`
|
|
230
|
-
const raw = fs.readFileSync(filePath, 'utf8')
|
|
231
|
-
return JSON.parse(raw)
|
|
232
|
-
}
|
|
233
|
-
```
|
|
234
|
-
|
|
235
|
-
This method is called only once when loading a node from a tree instance. The loaded node is loaded into memory, and subsequently, when the tree references the node, it operates based on the values in memory **without** re-invoking this method.
|
|
236
|
-
|
|
237
|
-
#### write(id: `string`, node: `BPTreeNode<K, V>`): `void`
|
|
238
|
-
|
|
239
|
-
This method is called when there are changes in the internal nodes due to the insert or delete operations of the tree instance. In other words, it's a necessary method for synchronizing the in-memory nodes into a file.
|
|
240
|
-
|
|
241
|
-
Since this method is called frequently, be mindful of performance. There are ways to optimize it using a write-back caching technique.
|
|
242
|
-
|
|
243
|
-
Please refer to the example below:
|
|
244
|
-
|
|
245
|
-
```typescript
|
|
246
|
-
let queue = 0
|
|
247
|
-
function writeBack(id: string, node: BPTreeNode<K, V>, timer: number) {
|
|
248
|
-
clearTimeout(queue)
|
|
249
|
-
queue = setTimeout(() => {
|
|
250
|
-
const filePath = `./my-store/${id}`
|
|
251
|
-
const stringify = JSON.stringify(node)
|
|
252
|
-
writeFileSync(filePath, stringify, 'utf8')
|
|
253
|
-
}, timer)
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
...
|
|
257
|
-
write(id: string, node: BPTreeNode<K, V>): void {
|
|
258
|
-
const writeBackInterval = 10
|
|
259
|
-
writeBack(id, node, writeBackInterval)
|
|
260
|
-
}
|
|
261
|
-
```
|
|
262
|
-
|
|
263
|
-
This kind of delay writing should ideally occur within a few milliseconds. If this is not feasible, consider other approaches.
|
|
264
|
-
|
|
265
|
-
#### delete(id: `string`): `void`
|
|
266
|
-
|
|
267
|
-
This method is called when previously created nodes become no longer needed due to deletion or other processes. It can be used to free up space by deleting existing stored nodes.
|
|
268
|
-
|
|
269
|
-
```typescript
|
|
270
|
-
delete(id: string): void {
|
|
271
|
-
const filePath = `./my-store/${id}`
|
|
272
|
-
fs.unlinkSync(filePath)
|
|
273
|
-
}
|
|
274
|
-
```
|
|
275
|
-
|
|
276
|
-
#### readHead(): `SerializeStrategyHead`|`null`
|
|
277
|
-
|
|
278
|
-
This method is called only once when the tree is created. It's a method to restore the saved tree information. If it is the initial creation and there is no stored root node, it should return **null**.
|
|
279
|
-
|
|
280
|
-
This method should return the value stored in the **writeHead** method.
|
|
281
|
-
|
|
282
|
-
#### writeHead(head: `SerializeStrategyHead`): `void`
|
|
283
|
-
|
|
284
|
-
This method is called whenever the head information of the tree changes, typically when the root node changes. This method also works when the tree's **setHeadData** method is called. This is because the method attempts to store head data in the root node.
|
|
285
|
-
|
|
286
|
-
As a parameter, it receives the header information of the tree. This value should be serialized and stored. Later, the **readHead** method should convert this serialized value into a json format and return it.
|
|
287
|
-
|
|
288
|
-
### The Default `ValueComparator` and `SerializeStrategy`
|
|
289
|
-
|
|
290
|
-
To utilize **serializable-bptree**, you need to implement certain functions. However, a few basic helper classes are provided by default.
|
|
291
|
-
|
|
292
|
-
#### ValueComparator
|
|
293
|
-
|
|
294
|
-
* `NumericComparator`
|
|
295
|
-
* `StringComparator`
|
|
296
|
-
|
|
297
|
-
If the values being inserted into the tree are numeric, please use the **NumericComparator** class.
|
|
298
|
-
|
|
299
|
-
```typescript
|
|
300
|
-
import { NumericComparator } from 'serializable-bptree'
|
|
301
|
-
```
|
|
302
|
-
|
|
303
|
-
If the values being inserted into the tree can be strings, you can use the **StringComparator** class in this case.
|
|
304
|
-
|
|
305
|
-
```typescript
|
|
306
|
-
import { StringComparator } from 'serializable-bptree'
|
|
307
|
-
```
|
|
308
|
-
|
|
309
|
-
#### SerializeStrategy
|
|
310
|
-
|
|
311
|
-
* `InMemoryStoreStrategySync`
|
|
312
|
-
* `InMemoryStoreStrategyAsync`
|
|
313
|
-
|
|
314
|
-
As of now, the only class supported by default is the **InMemoryStoreStrategy**. This class is suitable for use when you prefer to operate the tree solely in-memory, similar to a typical B+ tree.
|
|
315
|
-
|
|
316
|
-
```typescript
|
|
317
|
-
import {
|
|
318
|
-
InMemoryStoreStrategySync,
|
|
319
|
-
InMemoryStoreStrategyAsync
|
|
320
|
-
} from 'serializable-bptree'
|
|
321
|
-
```
|
|
322
|
-
|
|
323
|
-
## Data Query Condition Clause
|
|
324
|
-
|
|
325
|
-
This library supports various conditional clauses. Currently, it supports **gte**, **gt**, **lte**, **lt**, **equal**, **notEqual**, **or**, and **like** conditions. Each condition is as follows:
|
|
326
|
-
|
|
327
|
-
### `gte`
|
|
328
|
-
|
|
329
|
-
Queries values that are greater than or equal to the given value.
|
|
330
|
-
|
|
331
|
-
```typescript
|
|
332
|
-
tree.where({ gte: 1 })
|
|
333
|
-
```
|
|
334
|
-
|
|
335
|
-
### `gt`
|
|
336
|
-
|
|
337
|
-
Queries values that are greater than the given value.
|
|
338
|
-
|
|
339
|
-
```typescript
|
|
340
|
-
tree.where({ gt: 1 })
|
|
341
|
-
```
|
|
342
|
-
|
|
343
|
-
### `lte`
|
|
344
|
-
|
|
345
|
-
Queries values that are less than or equal to the given value.
|
|
346
|
-
|
|
347
|
-
```typescript
|
|
348
|
-
tree.where({ lte: 5 })
|
|
349
|
-
```
|
|
350
|
-
|
|
351
|
-
### `lt`
|
|
352
|
-
|
|
353
|
-
Queries values that are less than the given value.
|
|
354
|
-
|
|
355
|
-
```typescript
|
|
356
|
-
tree.where({ lt: 5 })
|
|
357
|
-
```
|
|
358
|
-
|
|
359
|
-
### `equal`
|
|
360
|
-
|
|
361
|
-
Queries values that match the given value.
|
|
362
|
-
|
|
363
|
-
```typescript
|
|
364
|
-
tree.where({ equal: 3 })
|
|
365
|
-
```
|
|
366
|
-
|
|
367
|
-
### `notEqual`
|
|
368
|
-
|
|
369
|
-
Queries values that do not match the given value.
|
|
370
|
-
|
|
371
|
-
```typescript
|
|
372
|
-
tree.where({ notEqual: 3 })
|
|
373
|
-
```
|
|
374
|
-
|
|
375
|
-
### `or`
|
|
376
|
-
|
|
377
|
-
Queries values that satisfy at least one of the given conditions. It accepts an array of conditions, and if any of these conditions are met, the data is included in the result.
|
|
378
|
-
|
|
379
|
-
```typescript
|
|
380
|
-
tree.where({ or: [1, 2, 3] })
|
|
381
|
-
```
|
|
382
|
-
|
|
383
|
-
### `like`
|
|
384
|
-
|
|
385
|
-
Queries values that contain the given value in a manner similar to regular expressions. Special characters such as % and _ can be used.
|
|
386
|
-
|
|
387
|
-
**%** matches zero or more characters. For example, **%ada%** means all strings that contain "ada" anywhere in the string. **%ada** means strings that end with "ada". **ada%** means strings that start with **"ada"**.
|
|
388
|
-
|
|
389
|
-
**_** matches exactly one character.
|
|
390
|
-
Using **p_t**, it can match any string where the underscore is replaced by any character, such as "pit", "put", etc.
|
|
391
|
-
|
|
392
|
-
You can obtain matching data by combining these condition clauses. If there are multiple conditions, an **AND** operation is used to retrieve only the data that satisfies all conditions.
|
|
393
|
-
|
|
394
|
-
```typescript
|
|
395
|
-
tree.where({ like: 'hello%' })
|
|
396
|
-
tree.where({ like: 'he__o%' })
|
|
397
|
-
tree.where({ like: '%world!' })
|
|
398
|
-
tree.where({ like: '%lo, wor%' })
|
|
399
|
-
```
|
|
400
|
-
|
|
401
|
-
## Using Asynchronously
|
|
402
|
-
|
|
403
|
-
Support for asynchronous trees has been available since version 3.0.0. Asynchronous is useful for operations with delays, such as file input/output and remote storage. Here is an example of how to use it:
|
|
404
|
-
|
|
405
|
-
```typescript
|
|
406
|
-
import { existsSync } from 'fs'
|
|
407
|
-
import { readFile, writeFile, unlink } from 'fs/promises'
|
|
408
|
-
import {
|
|
409
|
-
BPTreeAsync,
|
|
410
|
-
SerializeStrategyAsync,
|
|
411
|
-
NumericComparator,
|
|
412
|
-
StringComparator
|
|
413
|
-
} from 'serializable-bptree'
|
|
414
|
-
|
|
415
|
-
class FileStoreStrategyAsync extends SerializeStrategyAsync<K, V> {
|
|
416
|
-
async id(isLeaf: boolean): Promise<string> {
|
|
417
|
-
return crypto.randomUUID()
|
|
418
|
-
}
|
|
419
|
-
|
|
420
|
-
async read(id: string): Promise<BPTreeNode<K, V>> {
|
|
421
|
-
const raw = await readFile(id, 'utf8')
|
|
422
|
-
return JSON.parse(raw)
|
|
423
|
-
}
|
|
424
|
-
|
|
425
|
-
async write(id: string, node: BPTreeNode<K, V>): Promise<void> {
|
|
426
|
-
const stringify = JSON.stringify(node)
|
|
427
|
-
await writeFile(id, stringify, 'utf8')
|
|
428
|
-
}
|
|
429
|
-
|
|
430
|
-
async delete(id: string): Promise<void> {
|
|
431
|
-
await unlink(id)
|
|
432
|
-
}
|
|
433
|
-
|
|
434
|
-
async readHead(): Promise<SerializeStrategyHead|null> {
|
|
435
|
-
if (!existsSync('head')) {
|
|
436
|
-
return null
|
|
437
|
-
}
|
|
438
|
-
const raw = await readFile('head', 'utf8')
|
|
439
|
-
return JSON.parse(raw)
|
|
440
|
-
}
|
|
441
|
-
|
|
442
|
-
async writeHead(head: SerializeStrategyHead): Promise<void> {
|
|
443
|
-
const stringify = JSON.stringify(head)
|
|
444
|
-
await writeFile('head', stringify, 'utf8')
|
|
445
|
-
}
|
|
446
|
-
}
|
|
447
|
-
|
|
448
|
-
const order = 5
|
|
449
|
-
const tree = new BPTreeAsync(
|
|
450
|
-
new FileStoreStrategyAsync(order),
|
|
451
|
-
new NumericComparator()
|
|
452
|
-
)
|
|
453
|
-
|
|
454
|
-
await tree.init()
|
|
455
|
-
await tree.insert('a', 1)
|
|
456
|
-
await tree.insert('b', 2)
|
|
457
|
-
await tree.insert('c', 3)
|
|
458
|
-
|
|
459
|
-
await tree.delete('b', 2)
|
|
460
|
-
|
|
461
|
-
await tree.where({ equal: 1 }) // Map([{ key: 'a', value: 1 }])
|
|
462
|
-
await tree.where({ gt: 1 }) // Map([{ key: 'c', value: 3 }])
|
|
463
|
-
await tree.where({ lt: 2 }) // Map([{ key: 'a', value: 1 }])
|
|
464
|
-
await tree.where({ gt: 0, lt: 4 }) // Map([{ key: 'a', value: 1 }, { key: 'c', value: 3 }])
|
|
465
|
-
|
|
466
|
-
tree.clear()
|
|
467
|
-
```
|
|
468
|
-
|
|
469
|
-
The implementation method for asynchronous operations is not significantly different. The **-Async** suffix is used instead of the **-Sync** suffix in the **BPTree** and **SerializeStrategy** classes. The only difference is that the methods become asynchronous. The **ValueComparator** class and similar value comparators do not use asynchronous operations.
|
|
470
|
-
|
|
471
|
-
## Precautions for Use
|
|
472
|
-
|
|
473
|
-
### Synchronization Issue
|
|
474
|
-
|
|
475
|
-
The serializable-bptree minimizes file I/O by storing loaded nodes in-memory (caching). This approach works perfectly when a single tree instance is used for a given storage.
|
|
476
|
-
|
|
477
|
-
However, if **multiple BPTree instances** (e.g., across different processes or servers) read from and write to a **single shared storage**, data inconsistency can occur. This is because each instance maintains its own independent in-memory cache, and changes made by one instance are not automatically reflected in the others.
|
|
478
|
-
|
|
479
|
-
To solve this problem, you must synchronize the cached nodes across all instances. The `forceUpdate` method can be used to refresh the nodes cached in a tree instance. When one instance saves data to the shared storage, you should implement a signaling mechanism (e.g., via Pub/Sub or WebSockets) to notify other instances that a node has been updated. Upon receiving this signal, the other instances should call the `forceUpdate` method to ensure they are working with the latest data.
|
|
480
|
-
|
|
481
|
-
### Concurrency Issue in Asynchronous Trees
|
|
482
|
-
|
|
483
|
-
This issue occurs only in asynchronous trees and can also occur in a 1:1 relationship between remote storage and client.
|
|
484
|
-
|
|
485
|
-
Since version 5.x.x, **serializable-bptree** provides a built-in read/write lock for the `BPTreeAsync` class to prevent data inconsistency during concurrent operations. Calling public methods like `insert`, `delete`, `where`, `exists`, `keys`, `setHeadData`, and `forceUpdate` will automatically acquire the appropriate lock.
|
|
486
|
-
|
|
487
|
-
However, please be aware of the following technical limitations:
|
|
488
|
-
- **Locking is only applied to public methods**: The internal `protected` methods (e.g., `_insertInParent`, `_deleteEntry`, etc.) do not automatically acquire locks.
|
|
489
|
-
- **Inheritance Caution**: If you extend the `BPTreeAsync` class and call `protected` methods directly, you must manually manage the locks using `readLock` or `writeLock` to ensure data integrity.
|
|
490
|
-
|
|
491
|
-
Despite these safeguards, it is still recommended to avoid unnecessary concurrent operations whenever possible to maintain optimal performance and predictability.
|
|
132
|
+
> v6.0.0 enforces strict value sorting. **Data structures created with v5.x.x or earlier are incompatible** with v6.0.0. It is highly recommended to rebuild your tree from scratch. For more details, see the [Concurrency & Synchronization](./docs/CONCURRENCY.md) guide.
|
|
492
133
|
|
|
493
134
|
## LICENSE
|
|
494
135
|
|
package/dist/cjs/index.cjs
CHANGED
|
@@ -43,6 +43,33 @@ var ValueComparator = class {
|
|
|
43
43
|
isHigher(value, than) {
|
|
44
44
|
return this.asc(value, than) > 0;
|
|
45
45
|
}
|
|
46
|
+
/**
|
|
47
|
+
* This method is used for range queries with composite values.
|
|
48
|
+
* By default, it calls the `asc` method, so existing code works without changes.
|
|
49
|
+
*
|
|
50
|
+
* When using composite values (e.g., `{ k: number, v: number }`),
|
|
51
|
+
* override this method to compare only the primary sorting field (e.g., `v`),
|
|
52
|
+
* ignoring the unique identifier field (e.g., `k`).
|
|
53
|
+
*
|
|
54
|
+
* This enables efficient range queries like `primaryEqual` that find all entries
|
|
55
|
+
* with the same primary value regardless of their unique identifiers.
|
|
56
|
+
*
|
|
57
|
+
* @param a Value a.
|
|
58
|
+
* @param b Value b.
|
|
59
|
+
* @returns Negative if a < b, 0 if equal, positive if a > b (based on primary field only).
|
|
60
|
+
*/
|
|
61
|
+
primaryAsc(a, b) {
|
|
62
|
+
return this.asc(a, b);
|
|
63
|
+
}
|
|
64
|
+
isPrimarySame(value, than) {
|
|
65
|
+
return this.primaryAsc(value, than) === 0;
|
|
66
|
+
}
|
|
67
|
+
isPrimaryLower(value, than) {
|
|
68
|
+
return this.primaryAsc(value, than) < 0;
|
|
69
|
+
}
|
|
70
|
+
isPrimaryHigher(value, than) {
|
|
71
|
+
return this.primaryAsc(value, than) > 0;
|
|
72
|
+
}
|
|
46
73
|
};
|
|
47
74
|
var NumericComparator = class extends ValueComparator {
|
|
48
75
|
asc(a, b) {
|
|
@@ -456,6 +483,7 @@ var BPTree = class {
|
|
|
456
483
|
lt: (nv, v) => this.comparator.isLower(nv, v),
|
|
457
484
|
lte: (nv, v) => this.comparator.isLower(nv, v) || this.comparator.isSame(nv, v),
|
|
458
485
|
equal: (nv, v) => this.comparator.isSame(nv, v),
|
|
486
|
+
primaryEqual: (nv, v) => this.comparator.isPrimarySame(nv, v),
|
|
459
487
|
notEqual: (nv, v) => this.comparator.isSame(nv, v) === false,
|
|
460
488
|
or: (nv, v) => this.ensureValues(v).some((v2) => this.comparator.isSame(nv, v2)),
|
|
461
489
|
like: (nv, v) => {
|
|
@@ -472,6 +500,7 @@ var BPTree = class {
|
|
|
472
500
|
lt: (v) => this.insertableNode(v),
|
|
473
501
|
lte: (v) => this.insertableNode(v),
|
|
474
502
|
equal: (v) => this.insertableNode(v),
|
|
503
|
+
primaryEqual: (v) => this.insertableNodeByPrimary(v),
|
|
475
504
|
notEqual: (v) => this.leftestNode(),
|
|
476
505
|
or: (v) => this.insertableNode(this.lowestValue(this.ensureValues(v))),
|
|
477
506
|
like: (v) => this.leftestNode()
|
|
@@ -482,6 +511,7 @@ var BPTree = class {
|
|
|
482
511
|
lt: (v) => null,
|
|
483
512
|
lte: (v) => null,
|
|
484
513
|
equal: (v) => this.insertableEndNode(v, this.verifierDirection.equal),
|
|
514
|
+
primaryEqual: (v) => null,
|
|
485
515
|
notEqual: (v) => null,
|
|
486
516
|
or: (v) => this.insertableEndNode(
|
|
487
517
|
this.highestValue(this.ensureValues(v)),
|
|
@@ -495,10 +525,27 @@ var BPTree = class {
|
|
|
495
525
|
lt: -1,
|
|
496
526
|
lte: -1,
|
|
497
527
|
equal: 1,
|
|
528
|
+
primaryEqual: 1,
|
|
498
529
|
notEqual: 1,
|
|
499
530
|
or: 1,
|
|
500
531
|
like: 1
|
|
501
532
|
};
|
|
533
|
+
/**
|
|
534
|
+
* Determines whether early termination is allowed for each condition.
|
|
535
|
+
* When true, the search will stop once a match is found and then a non-match is encountered.
|
|
536
|
+
* Only applicable for conditions that guarantee contiguous matches in a sorted B+Tree.
|
|
537
|
+
*/
|
|
538
|
+
verifierEarlyTerminate = {
|
|
539
|
+
gt: false,
|
|
540
|
+
gte: false,
|
|
541
|
+
lt: false,
|
|
542
|
+
lte: false,
|
|
543
|
+
equal: true,
|
|
544
|
+
primaryEqual: true,
|
|
545
|
+
notEqual: false,
|
|
546
|
+
or: false,
|
|
547
|
+
like: false
|
|
548
|
+
};
|
|
502
549
|
constructor(strategy, comparator, option) {
|
|
503
550
|
this.strategy = strategy;
|
|
504
551
|
this.comparator = comparator;
|
|
@@ -611,10 +658,11 @@ var BPTreeSync = class extends BPTree {
|
|
|
611
658
|
capacity: this.option.capacity ?? 1e3
|
|
612
659
|
});
|
|
613
660
|
}
|
|
614
|
-
getPairsRightToLeft(value, startNode, endNode, comparator) {
|
|
661
|
+
getPairsRightToLeft(value, startNode, endNode, comparator, earlyTerminate) {
|
|
615
662
|
const pairs = [];
|
|
616
663
|
let node = startNode;
|
|
617
664
|
let done = false;
|
|
665
|
+
let hasMatched = false;
|
|
618
666
|
while (!done) {
|
|
619
667
|
if (endNode && node.id === endNode.id) {
|
|
620
668
|
done = true;
|
|
@@ -625,12 +673,17 @@ var BPTreeSync = class extends BPTree {
|
|
|
625
673
|
const nValue = node.values[i];
|
|
626
674
|
const keys = node.keys[i];
|
|
627
675
|
if (comparator(nValue, value)) {
|
|
676
|
+
hasMatched = true;
|
|
628
677
|
let j = keys.length;
|
|
629
678
|
while (j--) {
|
|
630
679
|
pairs.push([keys[j], nValue]);
|
|
631
680
|
}
|
|
681
|
+
} else if (earlyTerminate && hasMatched) {
|
|
682
|
+
done = true;
|
|
683
|
+
break;
|
|
632
684
|
}
|
|
633
685
|
}
|
|
686
|
+
if (done) break;
|
|
634
687
|
if (!node.prev) {
|
|
635
688
|
done = true;
|
|
636
689
|
break;
|
|
@@ -639,10 +692,11 @@ var BPTreeSync = class extends BPTree {
|
|
|
639
692
|
}
|
|
640
693
|
return new Map(pairs.reverse());
|
|
641
694
|
}
|
|
642
|
-
getPairsLeftToRight(value, startNode, endNode, comparator) {
|
|
695
|
+
getPairsLeftToRight(value, startNode, endNode, comparator, earlyTerminate) {
|
|
643
696
|
const pairs = [];
|
|
644
697
|
let node = startNode;
|
|
645
698
|
let done = false;
|
|
699
|
+
let hasMatched = false;
|
|
646
700
|
while (!done) {
|
|
647
701
|
if (endNode && node.id === endNode.id) {
|
|
648
702
|
done = true;
|
|
@@ -652,12 +706,17 @@ var BPTreeSync = class extends BPTree {
|
|
|
652
706
|
const nValue = node.values[i];
|
|
653
707
|
const keys = node.keys[i];
|
|
654
708
|
if (comparator(nValue, value)) {
|
|
709
|
+
hasMatched = true;
|
|
655
710
|
for (let j = 0, len2 = keys.length; j < len2; j++) {
|
|
656
711
|
const key = keys[j];
|
|
657
712
|
pairs.push([key, nValue]);
|
|
658
713
|
}
|
|
714
|
+
} else if (earlyTerminate && hasMatched) {
|
|
715
|
+
done = true;
|
|
716
|
+
break;
|
|
659
717
|
}
|
|
660
718
|
}
|
|
719
|
+
if (done) break;
|
|
661
720
|
if (!node.next) {
|
|
662
721
|
done = true;
|
|
663
722
|
break;
|
|
@@ -666,12 +725,12 @@ var BPTreeSync = class extends BPTree {
|
|
|
666
725
|
}
|
|
667
726
|
return new Map(pairs);
|
|
668
727
|
}
|
|
669
|
-
getPairs(value, startNode, endNode, comparator, direction) {
|
|
728
|
+
getPairs(value, startNode, endNode, comparator, direction, earlyTerminate) {
|
|
670
729
|
switch (direction) {
|
|
671
730
|
case -1:
|
|
672
|
-
return this.getPairsRightToLeft(value, startNode, endNode, comparator);
|
|
731
|
+
return this.getPairsRightToLeft(value, startNode, endNode, comparator, earlyTerminate);
|
|
673
732
|
case 1:
|
|
674
|
-
return this.getPairsLeftToRight(value, startNode, endNode, comparator);
|
|
733
|
+
return this.getPairsLeftToRight(value, startNode, endNode, comparator, earlyTerminate);
|
|
675
734
|
default:
|
|
676
735
|
throw new Error(`Direction must be -1 or 1. but got a ${direction}`);
|
|
677
736
|
}
|
|
@@ -1020,6 +1079,30 @@ var BPTreeSync = class extends BPTree {
|
|
|
1020
1079
|
}
|
|
1021
1080
|
return node;
|
|
1022
1081
|
}
|
|
1082
|
+
/**
|
|
1083
|
+
* Find the insertable node using primaryAsc comparison.
|
|
1084
|
+
* This allows finding nodes by primary value only, ignoring unique identifiers.
|
|
1085
|
+
*/
|
|
1086
|
+
insertableNodeByPrimary(value) {
|
|
1087
|
+
let node = this.getNode(this.root.id);
|
|
1088
|
+
while (!node.leaf) {
|
|
1089
|
+
for (let i = 0, len = node.values.length; i < len; i++) {
|
|
1090
|
+
const nValue = node.values[i];
|
|
1091
|
+
const k = node.keys;
|
|
1092
|
+
if (this.comparator.isPrimarySame(value, nValue)) {
|
|
1093
|
+
node = this.getNode(k[i]);
|
|
1094
|
+
break;
|
|
1095
|
+
} else if (this.comparator.isPrimaryLower(value, nValue)) {
|
|
1096
|
+
node = this.getNode(k[i]);
|
|
1097
|
+
break;
|
|
1098
|
+
} else if (i + 1 === node.values.length) {
|
|
1099
|
+
node = this.getNode(k[i + 1]);
|
|
1100
|
+
break;
|
|
1101
|
+
}
|
|
1102
|
+
}
|
|
1103
|
+
}
|
|
1104
|
+
return node;
|
|
1105
|
+
}
|
|
1023
1106
|
insertableEndNode(value, direction) {
|
|
1024
1107
|
const insertableNode = this.insertableNode(value);
|
|
1025
1108
|
let key;
|
|
@@ -1088,7 +1171,8 @@ var BPTreeSync = class extends BPTree {
|
|
|
1088
1171
|
const endNode = this.verifierEndNode[key](value);
|
|
1089
1172
|
const direction = this.verifierDirection[key];
|
|
1090
1173
|
const comparator = this.verifierMap[key];
|
|
1091
|
-
const
|
|
1174
|
+
const earlyTerminate = this.verifierEarlyTerminate[key];
|
|
1175
|
+
const pairs = this.getPairs(value, startNode, endNode, comparator, direction, earlyTerminate);
|
|
1092
1176
|
if (!filterValues) {
|
|
1093
1177
|
filterValues = new Set(pairs.keys());
|
|
1094
1178
|
} else {
|
|
@@ -1113,7 +1197,8 @@ var BPTreeSync = class extends BPTree {
|
|
|
1113
1197
|
const endNode = this.verifierEndNode[key](value);
|
|
1114
1198
|
const direction = this.verifierDirection[key];
|
|
1115
1199
|
const comparator = this.verifierMap[key];
|
|
1116
|
-
const
|
|
1200
|
+
const earlyTerminate = this.verifierEarlyTerminate[key];
|
|
1201
|
+
const pairs = this.getPairs(value, startNode, endNode, comparator, direction, earlyTerminate);
|
|
1117
1202
|
if (result === null) {
|
|
1118
1203
|
result = pairs;
|
|
1119
1204
|
} else {
|
|
@@ -1501,10 +1586,11 @@ var BPTreeAsync = class extends BPTree {
|
|
|
1501
1586
|
this.lock.writeUnlock(lockId);
|
|
1502
1587
|
});
|
|
1503
1588
|
}
|
|
1504
|
-
async getPairsRightToLeft(value, startNode, endNode, comparator) {
|
|
1589
|
+
async getPairsRightToLeft(value, startNode, endNode, comparator, earlyTerminate) {
|
|
1505
1590
|
const pairs = [];
|
|
1506
1591
|
let node = startNode;
|
|
1507
1592
|
let done = false;
|
|
1593
|
+
let hasMatched = false;
|
|
1508
1594
|
while (!done) {
|
|
1509
1595
|
if (endNode && node.id === endNode.id) {
|
|
1510
1596
|
done = true;
|
|
@@ -1515,12 +1601,17 @@ var BPTreeAsync = class extends BPTree {
|
|
|
1515
1601
|
const nValue = node.values[i];
|
|
1516
1602
|
const keys = node.keys[i];
|
|
1517
1603
|
if (comparator(nValue, value)) {
|
|
1604
|
+
hasMatched = true;
|
|
1518
1605
|
let j = keys.length;
|
|
1519
1606
|
while (j--) {
|
|
1520
1607
|
pairs.push([keys[j], nValue]);
|
|
1521
1608
|
}
|
|
1609
|
+
} else if (earlyTerminate && hasMatched) {
|
|
1610
|
+
done = true;
|
|
1611
|
+
break;
|
|
1522
1612
|
}
|
|
1523
1613
|
}
|
|
1614
|
+
if (done) break;
|
|
1524
1615
|
if (!node.prev) {
|
|
1525
1616
|
done = true;
|
|
1526
1617
|
break;
|
|
@@ -1529,10 +1620,11 @@ var BPTreeAsync = class extends BPTree {
|
|
|
1529
1620
|
}
|
|
1530
1621
|
return new Map(pairs.reverse());
|
|
1531
1622
|
}
|
|
1532
|
-
async getPairsLeftToRight(value, startNode, endNode, comparator) {
|
|
1623
|
+
async getPairsLeftToRight(value, startNode, endNode, comparator, earlyTerminate) {
|
|
1533
1624
|
const pairs = [];
|
|
1534
1625
|
let node = startNode;
|
|
1535
1626
|
let done = false;
|
|
1627
|
+
let hasMatched = false;
|
|
1536
1628
|
while (!done) {
|
|
1537
1629
|
if (endNode && node.id === endNode.id) {
|
|
1538
1630
|
done = true;
|
|
@@ -1542,12 +1634,17 @@ var BPTreeAsync = class extends BPTree {
|
|
|
1542
1634
|
const nValue = node.values[i];
|
|
1543
1635
|
const keys = node.keys[i];
|
|
1544
1636
|
if (comparator(nValue, value)) {
|
|
1637
|
+
hasMatched = true;
|
|
1545
1638
|
for (let j = 0, len2 = keys.length; j < len2; j++) {
|
|
1546
1639
|
const key = keys[j];
|
|
1547
1640
|
pairs.push([key, nValue]);
|
|
1548
1641
|
}
|
|
1642
|
+
} else if (earlyTerminate && hasMatched) {
|
|
1643
|
+
done = true;
|
|
1644
|
+
break;
|
|
1549
1645
|
}
|
|
1550
1646
|
}
|
|
1647
|
+
if (done) break;
|
|
1551
1648
|
if (!node.next) {
|
|
1552
1649
|
done = true;
|
|
1553
1650
|
break;
|
|
@@ -1556,12 +1653,12 @@ var BPTreeAsync = class extends BPTree {
|
|
|
1556
1653
|
}
|
|
1557
1654
|
return new Map(pairs);
|
|
1558
1655
|
}
|
|
1559
|
-
async getPairs(value, startNode, endNode, comparator, direction) {
|
|
1656
|
+
async getPairs(value, startNode, endNode, comparator, direction, earlyTerminate) {
|
|
1560
1657
|
switch (direction) {
|
|
1561
1658
|
case -1:
|
|
1562
|
-
return await this.getPairsRightToLeft(value, startNode, endNode, comparator);
|
|
1659
|
+
return await this.getPairsRightToLeft(value, startNode, endNode, comparator, earlyTerminate);
|
|
1563
1660
|
case 1:
|
|
1564
|
-
return await this.getPairsLeftToRight(value, startNode, endNode, comparator);
|
|
1661
|
+
return await this.getPairsLeftToRight(value, startNode, endNode, comparator, earlyTerminate);
|
|
1565
1662
|
default:
|
|
1566
1663
|
throw new Error(`Direction must be -1 or 1. but got a ${direction}`);
|
|
1567
1664
|
}
|
|
@@ -1910,6 +2007,30 @@ var BPTreeAsync = class extends BPTree {
|
|
|
1910
2007
|
}
|
|
1911
2008
|
return node;
|
|
1912
2009
|
}
|
|
2010
|
+
/**
|
|
2011
|
+
* Find the insertable node using primaryAsc comparison.
|
|
2012
|
+
* This allows finding nodes by primary value only, ignoring unique identifiers.
|
|
2013
|
+
*/
|
|
2014
|
+
async insertableNodeByPrimary(value) {
|
|
2015
|
+
let node = await this.getNode(this.root.id);
|
|
2016
|
+
while (!node.leaf) {
|
|
2017
|
+
for (let i = 0, len = node.values.length; i < len; i++) {
|
|
2018
|
+
const nValue = node.values[i];
|
|
2019
|
+
const k = node.keys;
|
|
2020
|
+
if (this.comparator.isPrimarySame(value, nValue)) {
|
|
2021
|
+
node = await this.getNode(k[i]);
|
|
2022
|
+
break;
|
|
2023
|
+
} else if (this.comparator.isPrimaryLower(value, nValue)) {
|
|
2024
|
+
node = await this.getNode(k[i]);
|
|
2025
|
+
break;
|
|
2026
|
+
} else if (i + 1 === node.values.length) {
|
|
2027
|
+
node = await this.getNode(k[i + 1]);
|
|
2028
|
+
break;
|
|
2029
|
+
}
|
|
2030
|
+
}
|
|
2031
|
+
}
|
|
2032
|
+
return node;
|
|
2033
|
+
}
|
|
1913
2034
|
async insertableEndNode(value, direction) {
|
|
1914
2035
|
const insertableNode = await this.insertableNode(value);
|
|
1915
2036
|
let key;
|
|
@@ -1979,7 +2100,8 @@ var BPTreeAsync = class extends BPTree {
|
|
|
1979
2100
|
const endNode = await this.verifierEndNode[key](value);
|
|
1980
2101
|
const direction = this.verifierDirection[key];
|
|
1981
2102
|
const comparator = this.verifierMap[key];
|
|
1982
|
-
const
|
|
2103
|
+
const earlyTerminate = this.verifierEarlyTerminate[key];
|
|
2104
|
+
const pairs = await this.getPairs(value, startNode, endNode, comparator, direction, earlyTerminate);
|
|
1983
2105
|
if (!filterValues) {
|
|
1984
2106
|
filterValues = new Set(pairs.keys());
|
|
1985
2107
|
} else {
|
|
@@ -2006,7 +2128,8 @@ var BPTreeAsync = class extends BPTree {
|
|
|
2006
2128
|
const endNode = await this.verifierEndNode[key](value);
|
|
2007
2129
|
const direction = this.verifierDirection[key];
|
|
2008
2130
|
const comparator = this.verifierMap[key];
|
|
2009
|
-
const
|
|
2131
|
+
const earlyTerminate = this.verifierEarlyTerminate[key];
|
|
2132
|
+
const pairs = await this.getPairs(value, startNode, endNode, comparator, direction, earlyTerminate);
|
|
2010
2133
|
if (result === null) {
|
|
2011
2134
|
result = pairs;
|
|
2012
2135
|
} else {
|
package/dist/esm/index.mjs
CHANGED
|
@@ -9,6 +9,33 @@ var ValueComparator = class {
|
|
|
9
9
|
isHigher(value, than) {
|
|
10
10
|
return this.asc(value, than) > 0;
|
|
11
11
|
}
|
|
12
|
+
/**
|
|
13
|
+
* This method is used for range queries with composite values.
|
|
14
|
+
* By default, it calls the `asc` method, so existing code works without changes.
|
|
15
|
+
*
|
|
16
|
+
* When using composite values (e.g., `{ k: number, v: number }`),
|
|
17
|
+
* override this method to compare only the primary sorting field (e.g., `v`),
|
|
18
|
+
* ignoring the unique identifier field (e.g., `k`).
|
|
19
|
+
*
|
|
20
|
+
* This enables efficient range queries like `primaryEqual` that find all entries
|
|
21
|
+
* with the same primary value regardless of their unique identifiers.
|
|
22
|
+
*
|
|
23
|
+
* @param a Value a.
|
|
24
|
+
* @param b Value b.
|
|
25
|
+
* @returns Negative if a < b, 0 if equal, positive if a > b (based on primary field only).
|
|
26
|
+
*/
|
|
27
|
+
primaryAsc(a, b) {
|
|
28
|
+
return this.asc(a, b);
|
|
29
|
+
}
|
|
30
|
+
isPrimarySame(value, than) {
|
|
31
|
+
return this.primaryAsc(value, than) === 0;
|
|
32
|
+
}
|
|
33
|
+
isPrimaryLower(value, than) {
|
|
34
|
+
return this.primaryAsc(value, than) < 0;
|
|
35
|
+
}
|
|
36
|
+
isPrimaryHigher(value, than) {
|
|
37
|
+
return this.primaryAsc(value, than) > 0;
|
|
38
|
+
}
|
|
12
39
|
};
|
|
13
40
|
var NumericComparator = class extends ValueComparator {
|
|
14
41
|
asc(a, b) {
|
|
@@ -422,6 +449,7 @@ var BPTree = class {
|
|
|
422
449
|
lt: (nv, v) => this.comparator.isLower(nv, v),
|
|
423
450
|
lte: (nv, v) => this.comparator.isLower(nv, v) || this.comparator.isSame(nv, v),
|
|
424
451
|
equal: (nv, v) => this.comparator.isSame(nv, v),
|
|
452
|
+
primaryEqual: (nv, v) => this.comparator.isPrimarySame(nv, v),
|
|
425
453
|
notEqual: (nv, v) => this.comparator.isSame(nv, v) === false,
|
|
426
454
|
or: (nv, v) => this.ensureValues(v).some((v2) => this.comparator.isSame(nv, v2)),
|
|
427
455
|
like: (nv, v) => {
|
|
@@ -438,6 +466,7 @@ var BPTree = class {
|
|
|
438
466
|
lt: (v) => this.insertableNode(v),
|
|
439
467
|
lte: (v) => this.insertableNode(v),
|
|
440
468
|
equal: (v) => this.insertableNode(v),
|
|
469
|
+
primaryEqual: (v) => this.insertableNodeByPrimary(v),
|
|
441
470
|
notEqual: (v) => this.leftestNode(),
|
|
442
471
|
or: (v) => this.insertableNode(this.lowestValue(this.ensureValues(v))),
|
|
443
472
|
like: (v) => this.leftestNode()
|
|
@@ -448,6 +477,7 @@ var BPTree = class {
|
|
|
448
477
|
lt: (v) => null,
|
|
449
478
|
lte: (v) => null,
|
|
450
479
|
equal: (v) => this.insertableEndNode(v, this.verifierDirection.equal),
|
|
480
|
+
primaryEqual: (v) => null,
|
|
451
481
|
notEqual: (v) => null,
|
|
452
482
|
or: (v) => this.insertableEndNode(
|
|
453
483
|
this.highestValue(this.ensureValues(v)),
|
|
@@ -461,10 +491,27 @@ var BPTree = class {
|
|
|
461
491
|
lt: -1,
|
|
462
492
|
lte: -1,
|
|
463
493
|
equal: 1,
|
|
494
|
+
primaryEqual: 1,
|
|
464
495
|
notEqual: 1,
|
|
465
496
|
or: 1,
|
|
466
497
|
like: 1
|
|
467
498
|
};
|
|
499
|
+
/**
|
|
500
|
+
* Determines whether early termination is allowed for each condition.
|
|
501
|
+
* When true, the search will stop once a match is found and then a non-match is encountered.
|
|
502
|
+
* Only applicable for conditions that guarantee contiguous matches in a sorted B+Tree.
|
|
503
|
+
*/
|
|
504
|
+
verifierEarlyTerminate = {
|
|
505
|
+
gt: false,
|
|
506
|
+
gte: false,
|
|
507
|
+
lt: false,
|
|
508
|
+
lte: false,
|
|
509
|
+
equal: true,
|
|
510
|
+
primaryEqual: true,
|
|
511
|
+
notEqual: false,
|
|
512
|
+
or: false,
|
|
513
|
+
like: false
|
|
514
|
+
};
|
|
468
515
|
constructor(strategy, comparator, option) {
|
|
469
516
|
this.strategy = strategy;
|
|
470
517
|
this.comparator = comparator;
|
|
@@ -577,10 +624,11 @@ var BPTreeSync = class extends BPTree {
|
|
|
577
624
|
capacity: this.option.capacity ?? 1e3
|
|
578
625
|
});
|
|
579
626
|
}
|
|
580
|
-
getPairsRightToLeft(value, startNode, endNode, comparator) {
|
|
627
|
+
getPairsRightToLeft(value, startNode, endNode, comparator, earlyTerminate) {
|
|
581
628
|
const pairs = [];
|
|
582
629
|
let node = startNode;
|
|
583
630
|
let done = false;
|
|
631
|
+
let hasMatched = false;
|
|
584
632
|
while (!done) {
|
|
585
633
|
if (endNode && node.id === endNode.id) {
|
|
586
634
|
done = true;
|
|
@@ -591,12 +639,17 @@ var BPTreeSync = class extends BPTree {
|
|
|
591
639
|
const nValue = node.values[i];
|
|
592
640
|
const keys = node.keys[i];
|
|
593
641
|
if (comparator(nValue, value)) {
|
|
642
|
+
hasMatched = true;
|
|
594
643
|
let j = keys.length;
|
|
595
644
|
while (j--) {
|
|
596
645
|
pairs.push([keys[j], nValue]);
|
|
597
646
|
}
|
|
647
|
+
} else if (earlyTerminate && hasMatched) {
|
|
648
|
+
done = true;
|
|
649
|
+
break;
|
|
598
650
|
}
|
|
599
651
|
}
|
|
652
|
+
if (done) break;
|
|
600
653
|
if (!node.prev) {
|
|
601
654
|
done = true;
|
|
602
655
|
break;
|
|
@@ -605,10 +658,11 @@ var BPTreeSync = class extends BPTree {
|
|
|
605
658
|
}
|
|
606
659
|
return new Map(pairs.reverse());
|
|
607
660
|
}
|
|
608
|
-
getPairsLeftToRight(value, startNode, endNode, comparator) {
|
|
661
|
+
getPairsLeftToRight(value, startNode, endNode, comparator, earlyTerminate) {
|
|
609
662
|
const pairs = [];
|
|
610
663
|
let node = startNode;
|
|
611
664
|
let done = false;
|
|
665
|
+
let hasMatched = false;
|
|
612
666
|
while (!done) {
|
|
613
667
|
if (endNode && node.id === endNode.id) {
|
|
614
668
|
done = true;
|
|
@@ -618,12 +672,17 @@ var BPTreeSync = class extends BPTree {
|
|
|
618
672
|
const nValue = node.values[i];
|
|
619
673
|
const keys = node.keys[i];
|
|
620
674
|
if (comparator(nValue, value)) {
|
|
675
|
+
hasMatched = true;
|
|
621
676
|
for (let j = 0, len2 = keys.length; j < len2; j++) {
|
|
622
677
|
const key = keys[j];
|
|
623
678
|
pairs.push([key, nValue]);
|
|
624
679
|
}
|
|
680
|
+
} else if (earlyTerminate && hasMatched) {
|
|
681
|
+
done = true;
|
|
682
|
+
break;
|
|
625
683
|
}
|
|
626
684
|
}
|
|
685
|
+
if (done) break;
|
|
627
686
|
if (!node.next) {
|
|
628
687
|
done = true;
|
|
629
688
|
break;
|
|
@@ -632,12 +691,12 @@ var BPTreeSync = class extends BPTree {
|
|
|
632
691
|
}
|
|
633
692
|
return new Map(pairs);
|
|
634
693
|
}
|
|
635
|
-
getPairs(value, startNode, endNode, comparator, direction) {
|
|
694
|
+
getPairs(value, startNode, endNode, comparator, direction, earlyTerminate) {
|
|
636
695
|
switch (direction) {
|
|
637
696
|
case -1:
|
|
638
|
-
return this.getPairsRightToLeft(value, startNode, endNode, comparator);
|
|
697
|
+
return this.getPairsRightToLeft(value, startNode, endNode, comparator, earlyTerminate);
|
|
639
698
|
case 1:
|
|
640
|
-
return this.getPairsLeftToRight(value, startNode, endNode, comparator);
|
|
699
|
+
return this.getPairsLeftToRight(value, startNode, endNode, comparator, earlyTerminate);
|
|
641
700
|
default:
|
|
642
701
|
throw new Error(`Direction must be -1 or 1. but got a ${direction}`);
|
|
643
702
|
}
|
|
@@ -986,6 +1045,30 @@ var BPTreeSync = class extends BPTree {
|
|
|
986
1045
|
}
|
|
987
1046
|
return node;
|
|
988
1047
|
}
|
|
1048
|
+
/**
|
|
1049
|
+
* Find the insertable node using primaryAsc comparison.
|
|
1050
|
+
* This allows finding nodes by primary value only, ignoring unique identifiers.
|
|
1051
|
+
*/
|
|
1052
|
+
insertableNodeByPrimary(value) {
|
|
1053
|
+
let node = this.getNode(this.root.id);
|
|
1054
|
+
while (!node.leaf) {
|
|
1055
|
+
for (let i = 0, len = node.values.length; i < len; i++) {
|
|
1056
|
+
const nValue = node.values[i];
|
|
1057
|
+
const k = node.keys;
|
|
1058
|
+
if (this.comparator.isPrimarySame(value, nValue)) {
|
|
1059
|
+
node = this.getNode(k[i]);
|
|
1060
|
+
break;
|
|
1061
|
+
} else if (this.comparator.isPrimaryLower(value, nValue)) {
|
|
1062
|
+
node = this.getNode(k[i]);
|
|
1063
|
+
break;
|
|
1064
|
+
} else if (i + 1 === node.values.length) {
|
|
1065
|
+
node = this.getNode(k[i + 1]);
|
|
1066
|
+
break;
|
|
1067
|
+
}
|
|
1068
|
+
}
|
|
1069
|
+
}
|
|
1070
|
+
return node;
|
|
1071
|
+
}
|
|
989
1072
|
insertableEndNode(value, direction) {
|
|
990
1073
|
const insertableNode = this.insertableNode(value);
|
|
991
1074
|
let key;
|
|
@@ -1054,7 +1137,8 @@ var BPTreeSync = class extends BPTree {
|
|
|
1054
1137
|
const endNode = this.verifierEndNode[key](value);
|
|
1055
1138
|
const direction = this.verifierDirection[key];
|
|
1056
1139
|
const comparator = this.verifierMap[key];
|
|
1057
|
-
const
|
|
1140
|
+
const earlyTerminate = this.verifierEarlyTerminate[key];
|
|
1141
|
+
const pairs = this.getPairs(value, startNode, endNode, comparator, direction, earlyTerminate);
|
|
1058
1142
|
if (!filterValues) {
|
|
1059
1143
|
filterValues = new Set(pairs.keys());
|
|
1060
1144
|
} else {
|
|
@@ -1079,7 +1163,8 @@ var BPTreeSync = class extends BPTree {
|
|
|
1079
1163
|
const endNode = this.verifierEndNode[key](value);
|
|
1080
1164
|
const direction = this.verifierDirection[key];
|
|
1081
1165
|
const comparator = this.verifierMap[key];
|
|
1082
|
-
const
|
|
1166
|
+
const earlyTerminate = this.verifierEarlyTerminate[key];
|
|
1167
|
+
const pairs = this.getPairs(value, startNode, endNode, comparator, direction, earlyTerminate);
|
|
1083
1168
|
if (result === null) {
|
|
1084
1169
|
result = pairs;
|
|
1085
1170
|
} else {
|
|
@@ -1467,10 +1552,11 @@ var BPTreeAsync = class extends BPTree {
|
|
|
1467
1552
|
this.lock.writeUnlock(lockId);
|
|
1468
1553
|
});
|
|
1469
1554
|
}
|
|
1470
|
-
async getPairsRightToLeft(value, startNode, endNode, comparator) {
|
|
1555
|
+
async getPairsRightToLeft(value, startNode, endNode, comparator, earlyTerminate) {
|
|
1471
1556
|
const pairs = [];
|
|
1472
1557
|
let node = startNode;
|
|
1473
1558
|
let done = false;
|
|
1559
|
+
let hasMatched = false;
|
|
1474
1560
|
while (!done) {
|
|
1475
1561
|
if (endNode && node.id === endNode.id) {
|
|
1476
1562
|
done = true;
|
|
@@ -1481,12 +1567,17 @@ var BPTreeAsync = class extends BPTree {
|
|
|
1481
1567
|
const nValue = node.values[i];
|
|
1482
1568
|
const keys = node.keys[i];
|
|
1483
1569
|
if (comparator(nValue, value)) {
|
|
1570
|
+
hasMatched = true;
|
|
1484
1571
|
let j = keys.length;
|
|
1485
1572
|
while (j--) {
|
|
1486
1573
|
pairs.push([keys[j], nValue]);
|
|
1487
1574
|
}
|
|
1575
|
+
} else if (earlyTerminate && hasMatched) {
|
|
1576
|
+
done = true;
|
|
1577
|
+
break;
|
|
1488
1578
|
}
|
|
1489
1579
|
}
|
|
1580
|
+
if (done) break;
|
|
1490
1581
|
if (!node.prev) {
|
|
1491
1582
|
done = true;
|
|
1492
1583
|
break;
|
|
@@ -1495,10 +1586,11 @@ var BPTreeAsync = class extends BPTree {
|
|
|
1495
1586
|
}
|
|
1496
1587
|
return new Map(pairs.reverse());
|
|
1497
1588
|
}
|
|
1498
|
-
async getPairsLeftToRight(value, startNode, endNode, comparator) {
|
|
1589
|
+
async getPairsLeftToRight(value, startNode, endNode, comparator, earlyTerminate) {
|
|
1499
1590
|
const pairs = [];
|
|
1500
1591
|
let node = startNode;
|
|
1501
1592
|
let done = false;
|
|
1593
|
+
let hasMatched = false;
|
|
1502
1594
|
while (!done) {
|
|
1503
1595
|
if (endNode && node.id === endNode.id) {
|
|
1504
1596
|
done = true;
|
|
@@ -1508,12 +1600,17 @@ var BPTreeAsync = class extends BPTree {
|
|
|
1508
1600
|
const nValue = node.values[i];
|
|
1509
1601
|
const keys = node.keys[i];
|
|
1510
1602
|
if (comparator(nValue, value)) {
|
|
1603
|
+
hasMatched = true;
|
|
1511
1604
|
for (let j = 0, len2 = keys.length; j < len2; j++) {
|
|
1512
1605
|
const key = keys[j];
|
|
1513
1606
|
pairs.push([key, nValue]);
|
|
1514
1607
|
}
|
|
1608
|
+
} else if (earlyTerminate && hasMatched) {
|
|
1609
|
+
done = true;
|
|
1610
|
+
break;
|
|
1515
1611
|
}
|
|
1516
1612
|
}
|
|
1613
|
+
if (done) break;
|
|
1517
1614
|
if (!node.next) {
|
|
1518
1615
|
done = true;
|
|
1519
1616
|
break;
|
|
@@ -1522,12 +1619,12 @@ var BPTreeAsync = class extends BPTree {
|
|
|
1522
1619
|
}
|
|
1523
1620
|
return new Map(pairs);
|
|
1524
1621
|
}
|
|
1525
|
-
async getPairs(value, startNode, endNode, comparator, direction) {
|
|
1622
|
+
async getPairs(value, startNode, endNode, comparator, direction, earlyTerminate) {
|
|
1526
1623
|
switch (direction) {
|
|
1527
1624
|
case -1:
|
|
1528
|
-
return await this.getPairsRightToLeft(value, startNode, endNode, comparator);
|
|
1625
|
+
return await this.getPairsRightToLeft(value, startNode, endNode, comparator, earlyTerminate);
|
|
1529
1626
|
case 1:
|
|
1530
|
-
return await this.getPairsLeftToRight(value, startNode, endNode, comparator);
|
|
1627
|
+
return await this.getPairsLeftToRight(value, startNode, endNode, comparator, earlyTerminate);
|
|
1531
1628
|
default:
|
|
1532
1629
|
throw new Error(`Direction must be -1 or 1. but got a ${direction}`);
|
|
1533
1630
|
}
|
|
@@ -1876,6 +1973,30 @@ var BPTreeAsync = class extends BPTree {
|
|
|
1876
1973
|
}
|
|
1877
1974
|
return node;
|
|
1878
1975
|
}
|
|
1976
|
+
/**
|
|
1977
|
+
* Find the insertable node using primaryAsc comparison.
|
|
1978
|
+
* This allows finding nodes by primary value only, ignoring unique identifiers.
|
|
1979
|
+
*/
|
|
1980
|
+
async insertableNodeByPrimary(value) {
|
|
1981
|
+
let node = await this.getNode(this.root.id);
|
|
1982
|
+
while (!node.leaf) {
|
|
1983
|
+
for (let i = 0, len = node.values.length; i < len; i++) {
|
|
1984
|
+
const nValue = node.values[i];
|
|
1985
|
+
const k = node.keys;
|
|
1986
|
+
if (this.comparator.isPrimarySame(value, nValue)) {
|
|
1987
|
+
node = await this.getNode(k[i]);
|
|
1988
|
+
break;
|
|
1989
|
+
} else if (this.comparator.isPrimaryLower(value, nValue)) {
|
|
1990
|
+
node = await this.getNode(k[i]);
|
|
1991
|
+
break;
|
|
1992
|
+
} else if (i + 1 === node.values.length) {
|
|
1993
|
+
node = await this.getNode(k[i + 1]);
|
|
1994
|
+
break;
|
|
1995
|
+
}
|
|
1996
|
+
}
|
|
1997
|
+
}
|
|
1998
|
+
return node;
|
|
1999
|
+
}
|
|
1879
2000
|
async insertableEndNode(value, direction) {
|
|
1880
2001
|
const insertableNode = await this.insertableNode(value);
|
|
1881
2002
|
let key;
|
|
@@ -1945,7 +2066,8 @@ var BPTreeAsync = class extends BPTree {
|
|
|
1945
2066
|
const endNode = await this.verifierEndNode[key](value);
|
|
1946
2067
|
const direction = this.verifierDirection[key];
|
|
1947
2068
|
const comparator = this.verifierMap[key];
|
|
1948
|
-
const
|
|
2069
|
+
const earlyTerminate = this.verifierEarlyTerminate[key];
|
|
2070
|
+
const pairs = await this.getPairs(value, startNode, endNode, comparator, direction, earlyTerminate);
|
|
1949
2071
|
if (!filterValues) {
|
|
1950
2072
|
filterValues = new Set(pairs.keys());
|
|
1951
2073
|
} else {
|
|
@@ -1972,7 +2094,8 @@ var BPTreeAsync = class extends BPTree {
|
|
|
1972
2094
|
const endNode = await this.verifierEndNode[key](value);
|
|
1973
2095
|
const direction = this.verifierDirection[key];
|
|
1974
2096
|
const comparator = this.verifierMap[key];
|
|
1975
|
-
const
|
|
2097
|
+
const earlyTerminate = this.verifierEarlyTerminate[key];
|
|
2098
|
+
const pairs = await this.getPairs(value, startNode, endNode, comparator, direction, earlyTerminate);
|
|
1976
2099
|
if (result === null) {
|
|
1977
2100
|
result = pairs;
|
|
1978
2101
|
} else {
|
|
@@ -10,9 +10,9 @@ export declare class BPTreeAsync<K, V> extends BPTree<K, V> {
|
|
|
10
10
|
private _createCachedNode;
|
|
11
11
|
protected readLock<T>(callback: () => Promise<T>): Promise<T>;
|
|
12
12
|
protected writeLock<T>(callback: () => Promise<T>): Promise<T>;
|
|
13
|
-
protected getPairsRightToLeft(value: V, startNode: BPTreeLeafNode<K, V>, endNode: BPTreeLeafNode<K, V> | null, comparator: (nodeValue: V, value: V) => boolean): Promise<BPTreePair<K, V>>;
|
|
14
|
-
protected getPairsLeftToRight(value: V, startNode: BPTreeLeafNode<K, V>, endNode: BPTreeLeafNode<K, V> | null, comparator: (nodeValue: V, value: V) => boolean): Promise<BPTreePair<K, V>>;
|
|
15
|
-
protected getPairs(value: V, startNode: BPTreeLeafNode<K, V>, endNode: BPTreeLeafNode<K, V> | null, comparator: (nodeValue: V, value: V) => boolean, direction: 1 | -1): Promise<BPTreePair<K, V>>;
|
|
13
|
+
protected getPairsRightToLeft(value: V, startNode: BPTreeLeafNode<K, V>, endNode: BPTreeLeafNode<K, V> | null, comparator: (nodeValue: V, value: V) => boolean, earlyTerminate: boolean): Promise<BPTreePair<K, V>>;
|
|
14
|
+
protected getPairsLeftToRight(value: V, startNode: BPTreeLeafNode<K, V>, endNode: BPTreeLeafNode<K, V> | null, comparator: (nodeValue: V, value: V) => boolean, earlyTerminate: boolean): Promise<BPTreePair<K, V>>;
|
|
15
|
+
protected getPairs(value: V, startNode: BPTreeLeafNode<K, V>, endNode: BPTreeLeafNode<K, V> | null, comparator: (nodeValue: V, value: V) => boolean, direction: 1 | -1, earlyTerminate: boolean): Promise<BPTreePair<K, V>>;
|
|
16
16
|
protected _createNodeId(isLeaf: boolean): Promise<string>;
|
|
17
17
|
protected _createNode(isLeaf: boolean, keys: string[] | K[][], values: V[], leaf?: boolean, parent?: string | null, next?: string | null, prev?: string | null): Promise<BPTreeUnknownNode<K, V>>;
|
|
18
18
|
protected _deleteEntry(node: BPTreeUnknownNode<K, V>, key: BPTreeNodeKey<K>, value: V): Promise<void>;
|
|
@@ -20,6 +20,11 @@ export declare class BPTreeAsync<K, V> extends BPTree<K, V> {
|
|
|
20
20
|
init(): Promise<void>;
|
|
21
21
|
protected getNode(id: string): Promise<BPTreeUnknownNode<K, V>>;
|
|
22
22
|
protected insertableNode(value: V): Promise<BPTreeLeafNode<K, V>>;
|
|
23
|
+
/**
|
|
24
|
+
* Find the insertable node using primaryAsc comparison.
|
|
25
|
+
* This allows finding nodes by primary value only, ignoring unique identifiers.
|
|
26
|
+
*/
|
|
27
|
+
protected insertableNodeByPrimary(value: V): Promise<BPTreeLeafNode<K, V>>;
|
|
23
28
|
protected insertableEndNode(value: V, direction: 1 | -1): Promise<BPTreeLeafNode<K, V> | null>;
|
|
24
29
|
protected leftestNode(): Promise<BPTreeLeafNode<K, V>>;
|
|
25
30
|
protected rightestNode(): Promise<BPTreeLeafNode<K, V>>;
|
|
@@ -7,9 +7,9 @@ export declare class BPTreeSync<K, V> extends BPTree<K, V> {
|
|
|
7
7
|
protected readonly nodes: ReturnType<typeof this._createCachedNode>;
|
|
8
8
|
constructor(strategy: SerializeStrategySync<K, V>, comparator: ValueComparator<V>, option?: BPTreeConstructorOption);
|
|
9
9
|
private _createCachedNode;
|
|
10
|
-
protected getPairsRightToLeft(value: V, startNode: BPTreeLeafNode<K, V>, endNode: BPTreeLeafNode<K, V> | null, comparator: (nodeValue: V, value: V) => boolean): BPTreePair<K, V>;
|
|
11
|
-
protected getPairsLeftToRight(value: V, startNode: BPTreeLeafNode<K, V>, endNode: BPTreeLeafNode<K, V> | null, comparator: (nodeValue: V, value: V) => boolean): BPTreePair<K, V>;
|
|
12
|
-
protected getPairs(value: V, startNode: BPTreeLeafNode<K, V>, endNode: BPTreeLeafNode<K, V> | null, comparator: (nodeValue: V, value: V) => boolean, direction: 1 | -1): BPTreePair<K, V>;
|
|
10
|
+
protected getPairsRightToLeft(value: V, startNode: BPTreeLeafNode<K, V>, endNode: BPTreeLeafNode<K, V> | null, comparator: (nodeValue: V, value: V) => boolean, earlyTerminate: boolean): BPTreePair<K, V>;
|
|
11
|
+
protected getPairsLeftToRight(value: V, startNode: BPTreeLeafNode<K, V>, endNode: BPTreeLeafNode<K, V> | null, comparator: (nodeValue: V, value: V) => boolean, earlyTerminate: boolean): BPTreePair<K, V>;
|
|
12
|
+
protected getPairs(value: V, startNode: BPTreeLeafNode<K, V>, endNode: BPTreeLeafNode<K, V> | null, comparator: (nodeValue: V, value: V) => boolean, direction: 1 | -1, earlyTerminate: boolean): BPTreePair<K, V>;
|
|
13
13
|
protected _createNodeId(isLeaf: boolean): string;
|
|
14
14
|
protected _createNode(isLeaf: boolean, keys: string[] | K[][], values: V[], leaf?: boolean, parent?: string | null, next?: string | null, prev?: string | null): BPTreeUnknownNode<K, V>;
|
|
15
15
|
protected _deleteEntry(node: BPTreeUnknownNode<K, V>, key: BPTreeNodeKey<K>, value: V): void;
|
|
@@ -17,6 +17,11 @@ export declare class BPTreeSync<K, V> extends BPTree<K, V> {
|
|
|
17
17
|
init(): void;
|
|
18
18
|
protected getNode(id: string): BPTreeUnknownNode<K, V>;
|
|
19
19
|
protected insertableNode(value: V): BPTreeLeafNode<K, V>;
|
|
20
|
+
/**
|
|
21
|
+
* Find the insertable node using primaryAsc comparison.
|
|
22
|
+
* This allows finding nodes by primary value only, ignoring unique identifiers.
|
|
23
|
+
*/
|
|
24
|
+
protected insertableNodeByPrimary(value: V): BPTreeLeafNode<K, V>;
|
|
20
25
|
protected insertableEndNode(value: V, direction: 1 | -1): BPTreeLeafNode<K, V> | null;
|
|
21
26
|
protected leftestNode(): BPTreeLeafNode<K, V>;
|
|
22
27
|
protected rightestNode(): BPTreeLeafNode<K, V>;
|
|
@@ -22,6 +22,12 @@ export type BPTreeCondition<V> = Partial<{
|
|
|
22
22
|
or: Partial<V>[];
|
|
23
23
|
/** Searches for values matching the given pattern. '%' matches zero or more characters, and '_' matches exactly one character. */
|
|
24
24
|
like: Partial<V>;
|
|
25
|
+
/**
|
|
26
|
+
* Searches for pairs where the primary field equals the given value.
|
|
27
|
+
* Uses `primaryAsc` method for comparison, which compares only the primary sorting field.
|
|
28
|
+
* Useful for composite values where you want to find all entries with the same primary value.
|
|
29
|
+
*/
|
|
30
|
+
primaryEqual: Partial<V>;
|
|
25
31
|
}>;
|
|
26
32
|
export type BPTreePair<K, V> = Map<K, V>;
|
|
27
33
|
export type BPTreeUnknownNode<K, V> = BPTreeInternalNode<K, V> | BPTreeLeafNode<K, V>;
|
|
@@ -66,17 +72,24 @@ export declare abstract class BPTree<K, V> {
|
|
|
66
72
|
protected readonly verifierStartNode: Record<keyof BPTreeCondition<V>, (value: V) => Deferred<BPTreeLeafNode<K, V>>>;
|
|
67
73
|
protected readonly verifierEndNode: Record<keyof BPTreeCondition<V>, (value: V) => Deferred<BPTreeLeafNode<K, V> | null>>;
|
|
68
74
|
protected readonly verifierDirection: Record<keyof BPTreeCondition<V>, -1 | 1>;
|
|
75
|
+
/**
|
|
76
|
+
* Determines whether early termination is allowed for each condition.
|
|
77
|
+
* When true, the search will stop once a match is found and then a non-match is encountered.
|
|
78
|
+
* Only applicable for conditions that guarantee contiguous matches in a sorted B+Tree.
|
|
79
|
+
*/
|
|
80
|
+
protected readonly verifierEarlyTerminate: Record<keyof BPTreeCondition<V>, boolean>;
|
|
69
81
|
protected constructor(strategy: SerializeStrategy<K, V>, comparator: ValueComparator<V>, option?: BPTreeConstructorOption);
|
|
70
82
|
private _createCachedRegexp;
|
|
71
|
-
protected abstract getPairsRightToLeft(value: V, startNode: BPTreeLeafNode<K, V>, endNode: BPTreeLeafNode<K, V> | null, comparator: (nodeValue: V, value: V) => boolean): Deferred<BPTreePair<K, V>>;
|
|
72
|
-
protected abstract getPairsLeftToRight(value: V, startNode: BPTreeLeafNode<K, V>, endNode: BPTreeLeafNode<K, V> | null, comparator: (nodeValue: V, value: V) => boolean): Deferred<BPTreePair<K, V>>;
|
|
73
|
-
protected abstract getPairs(value: V, startNode: BPTreeLeafNode<K, V>, endNode: BPTreeLeafNode<K, V> | null, comparator: (nodeValue: V, value: V) => boolean, direction: -1 | 1): Deferred<BPTreePair<K, V>>;
|
|
83
|
+
protected abstract getPairsRightToLeft(value: V, startNode: BPTreeLeafNode<K, V>, endNode: BPTreeLeafNode<K, V> | null, comparator: (nodeValue: V, value: V) => boolean, earlyTerminate: boolean): Deferred<BPTreePair<K, V>>;
|
|
84
|
+
protected abstract getPairsLeftToRight(value: V, startNode: BPTreeLeafNode<K, V>, endNode: BPTreeLeafNode<K, V> | null, comparator: (nodeValue: V, value: V) => boolean, earlyTerminate: boolean): Deferred<BPTreePair<K, V>>;
|
|
85
|
+
protected abstract getPairs(value: V, startNode: BPTreeLeafNode<K, V>, endNode: BPTreeLeafNode<K, V> | null, comparator: (nodeValue: V, value: V) => boolean, direction: -1 | 1, earlyTerminate: boolean): Deferred<BPTreePair<K, V>>;
|
|
74
86
|
protected abstract _createNodeId(isLeaf: boolean): Deferred<string>;
|
|
75
87
|
protected abstract _createNode(isLeaf: boolean, keys: string[] | K[][], values: V[], leaf?: boolean, parent?: string | null, next?: string | null, prev?: string | null): Deferred<BPTreeUnknownNode<K, V>>;
|
|
76
88
|
protected abstract _deleteEntry(node: BPTreeUnknownNode<K, V>, key: BPTreeNodeKey<K>, value: V): Deferred<void>;
|
|
77
89
|
protected abstract _insertInParent(node: BPTreeUnknownNode<K, V>, value: V, pointer: BPTreeUnknownNode<K, V>): Deferred<void>;
|
|
78
90
|
protected abstract getNode(id: string): Deferred<BPTreeUnknownNode<K, V>>;
|
|
79
91
|
protected abstract insertableNode(value: V): Deferred<BPTreeLeafNode<K, V>>;
|
|
92
|
+
protected abstract insertableNodeByPrimary(value: V): Deferred<BPTreeLeafNode<K, V>>;
|
|
80
93
|
protected abstract insertableEndNode(value: V, direction: 1 | -1): Deferred<BPTreeLeafNode<K, V> | null>;
|
|
81
94
|
protected abstract leftestNode(): Deferred<BPTreeLeafNode<K, V>>;
|
|
82
95
|
protected abstract rightestNode(): Deferred<BPTreeLeafNode<K, V>>;
|
|
@@ -45,6 +45,25 @@ export declare abstract class ValueComparator<V> {
|
|
|
45
45
|
isLower(value: V, than: V): boolean;
|
|
46
46
|
isSame(value: V, than: V): boolean;
|
|
47
47
|
isHigher(value: V, than: V): boolean;
|
|
48
|
+
/**
|
|
49
|
+
* This method is used for range queries with composite values.
|
|
50
|
+
* By default, it calls the `asc` method, so existing code works without changes.
|
|
51
|
+
*
|
|
52
|
+
* When using composite values (e.g., `{ k: number, v: number }`),
|
|
53
|
+
* override this method to compare only the primary sorting field (e.g., `v`),
|
|
54
|
+
* ignoring the unique identifier field (e.g., `k`).
|
|
55
|
+
*
|
|
56
|
+
* This enables efficient range queries like `primaryEqual` that find all entries
|
|
57
|
+
* with the same primary value regardless of their unique identifiers.
|
|
58
|
+
*
|
|
59
|
+
* @param a Value a.
|
|
60
|
+
* @param b Value b.
|
|
61
|
+
* @returns Negative if a < b, 0 if equal, positive if a > b (based on primary field only).
|
|
62
|
+
*/
|
|
63
|
+
primaryAsc(a: V, b: V): number;
|
|
64
|
+
isPrimarySame(value: V, than: V): boolean;
|
|
65
|
+
isPrimaryLower(value: V, than: V): boolean;
|
|
66
|
+
isPrimaryHigher(value: V, than: V): boolean;
|
|
48
67
|
}
|
|
49
68
|
export declare class NumericComparator extends ValueComparator<number> {
|
|
50
69
|
asc(a: number, b: number): number;
|