1
1
import { ascComparator } from "../utils/comparison.js"
2
- import { findInsertPosition } from "../utils/array-utils .js"
2
+ import { BTree } from "../utils/btree .js"
3
3
import { BaseIndex } from "./base-index.js"
4
+ import type { BasicExpression } from "../query/ir.js"
4
5
import type { IndexOperation } from "./base-index.js"
5
6
6
7
/**
7
8
* Options for Ordered index
8
9
*/
9
- export interface OrderedIndexOptions {
10
+ export interface BTreeIndexOptions {
10
11
compareFn ?: ( a : any , b : any ) => number
11
12
}
12
13
@@ -21,10 +22,10 @@ export interface RangeQueryOptions {
21
22
}
22
23
23
24
/**
24
- * Ordered index for sorted data with range queries
25
+ * B+Tree index for sorted data with range queries
25
26
* This maintains items in sorted order and provides efficient range operations
26
27
*/
27
- export class OrderedIndex <
28
+ export class BTreeIndex <
28
29
TKey extends string | number = string | number ,
29
30
> extends BaseIndex < TKey > {
30
31
public readonly supportedOperations = new Set < IndexOperation > ( [
@@ -37,15 +38,26 @@ export class OrderedIndex<
37
38
] )
38
39
39
40
// Internal data structures - private to hide implementation details
40
- private orderedEntries : Array < [ any , Set < TKey > ] > = [ ]
41
- private valueMap = new Map < any , Set < TKey > > ( )
41
+ // The `orderedEntries` B+ tree is used for efficient range queries
42
+ // The `valueMap` is used for O(1) lookups of PKs by indexed value
43
+ private orderedEntries : BTree < any , undefined > // we don't associate values with the keys of the B+ tree (the keys are indexed values)
44
+ private valueMap = new Map < any , Set < TKey > > ( ) // instead we store a mapping of indexed values to a set of PKs
42
45
private indexedKeys = new Set < TKey > ( )
43
46
private compareFn : ( a : any , b : any ) => number = ascComparator
44
47
45
- protected initialize ( options ?: OrderedIndexOptions ) : void {
48
+ constructor (
49
+ id : number ,
50
+ expression : BasicExpression ,
51
+ name ?: string ,
52
+ options ?: any
53
+ ) {
54
+ super ( id , expression , name , options )
46
55
this . compareFn = options ?. compareFn ?? ascComparator
56
+ this . orderedEntries = new BTree ( this . compareFn )
47
57
}
48
58
59
+ protected initialize ( _options ?: BTreeIndexOptions ) : void { }
60
+
49
61
/**
50
62
* Adds a value to the index
51
63
*/
@@ -67,14 +79,7 @@ export class OrderedIndex<
67
79
// Create new set for this value
68
80
const keySet = new Set < TKey > ( [ key ] )
69
81
this . valueMap . set ( indexedValue , keySet )
70
-
71
- // Find correct position in ordered entries using binary search
72
- const insertIndex = findInsertPosition (
73
- this . orderedEntries ,
74
- indexedValue ,
75
- this . compareFn
76
- )
77
- this . orderedEntries . splice ( insertIndex , 0 , [ indexedValue , keySet ] )
82
+ this . orderedEntries . set ( indexedValue , undefined )
78
83
}
79
84
80
85
this . indexedKeys . add ( key )
@@ -104,13 +109,8 @@ export class OrderedIndex<
104
109
if ( keySet . size === 0 ) {
105
110
this . valueMap . delete ( indexedValue )
106
111
107
- // Find and remove from ordered entries
108
- const index = this . orderedEntries . findIndex (
109
- ( [ value ] ) => this . compareFn ( value , indexedValue ) === 0
110
- )
111
- if ( index !== - 1 ) {
112
- this . orderedEntries . splice ( index , 1 )
113
- }
112
+ // Remove from ordered entries
113
+ this . orderedEntries . delete ( indexedValue )
114
114
}
115
115
}
116
116
@@ -141,7 +141,7 @@ export class OrderedIndex<
141
141
* Clears all data from the index
142
142
*/
143
143
clear ( ) : void {
144
- this . orderedEntries = [ ]
144
+ this . orderedEntries . clear ( )
145
145
this . valueMap . clear ( )
146
146
this . indexedKeys . clear ( )
147
147
this . updateTimestamp ( )
@@ -175,7 +175,7 @@ export class OrderedIndex<
175
175
result = this . inArrayLookup ( value )
176
176
break
177
177
default :
178
- throw new Error ( `Operation ${ operation } not supported by OrderedIndex ` )
178
+ throw new Error ( `Operation ${ operation } not supported by BTreeIndex ` )
179
179
}
180
180
181
181
this . trackLookup ( startTime )
@@ -206,70 +206,26 @@ export class OrderedIndex<
206
206
const { from, to, fromInclusive = true , toInclusive = true } = options
207
207
const result = new Set < TKey > ( )
208
208
209
- if ( this . orderedEntries . length === 0 ) {
210
- return result
211
- }
212
-
213
- // Find start position
214
- let startIndex = 0
215
- if ( from !== undefined ) {
216
- const fromInsertIndex = findInsertPosition (
217
- this . orderedEntries ,
218
- from ,
219
- this . compareFn
220
- )
221
-
222
- if ( fromInclusive ) {
223
- // Include values equal to 'from'
224
- startIndex = fromInsertIndex
225
- } else {
226
- // Exclude values equal to 'from'
227
- startIndex = fromInsertIndex
228
- // Skip the value if it exists at this position
229
- if (
230
- startIndex < this . orderedEntries . length &&
231
- this . compareFn ( this . orderedEntries [ startIndex ] ! [ 0 ] , from ) === 0
232
- ) {
233
- startIndex ++
209
+ const fromKey = from ?? this . orderedEntries . minKey ( )
210
+ const toKey = to ?? this . orderedEntries . maxKey ( )
211
+
212
+ this . orderedEntries . forRange (
213
+ fromKey ,
214
+ toKey ,
215
+ toInclusive ,
216
+ ( indexedValue , _ ) => {
217
+ if ( ! fromInclusive && this . compareFn ( indexedValue , from ) === 0 ) {
218
+ // the B+ tree `forRange` method does not support exclusive lower bounds
219
+ // so we need to exclude it manually
220
+ return
234
221
}
235
- }
236
- }
237
-
238
- // Find end position
239
- let endIndex = this . orderedEntries . length
240
- if ( to !== undefined ) {
241
- const toInsertIndex = findInsertPosition (
242
- this . orderedEntries ,
243
- to ,
244
- this . compareFn
245
- )
246
222
247
- if ( toInclusive ) {
248
- // Include values equal to 'to'
249
- endIndex = toInsertIndex
250
- // Include the value if it exists at this position
251
- if (
252
- toInsertIndex < this . orderedEntries . length &&
253
- this . compareFn ( this . orderedEntries [ toInsertIndex ] ! [ 0 ] , to ) === 0
254
- ) {
255
- endIndex = toInsertIndex + 1
223
+ const keys = this . valueMap . get ( indexedValue )
224
+ if ( keys ) {
225
+ keys . forEach ( ( key ) => result . add ( key ) )
256
226
}
257
- } else {
258
- // Exclude values equal to 'to'
259
- endIndex = toInsertIndex
260
227
}
261
- }
262
-
263
- // Ensure startIndex doesn't exceed endIndex
264
- if ( startIndex >= endIndex ) {
265
- return result
266
- }
267
-
268
- // Collect keys from the range
269
- for ( let i = startIndex ; i < endIndex ; i ++ ) {
270
- const keys = this . orderedEntries [ i ] ! [ 1 ]
271
- keys . forEach ( ( key ) => result . add ( key ) )
272
- }
228
+ )
273
229
274
230
return result
275
231
}
@@ -297,6 +253,8 @@ export class OrderedIndex<
297
253
298
254
get orderedEntriesArray ( ) : Array < [ any , Set < TKey > ] > {
299
255
return this . orderedEntries
256
+ . keysArray ( )
257
+ . map ( ( key ) => [ key , this . valueMap . get ( key ) ?? new Set ( ) ] )
300
258
}
301
259
302
260
get valueMapData ( ) : Map < any , Set < TKey > > {
0 commit comments