Skip to content

Commit f751cb4

Browse files
authored
Incremental topK with fractional index (electric-sql#72)
* WIP incremental topKWithFractionalIndex * Incremental topKWithFractionalIndex * Fix tests to not assume particular fractional indices as those are implementation details. * Introduce a TopK data structure * B+ tree variant of topKWithFractionalIndex * Extend unit tests to test all insertion and deletion cases * Formatting * Unit test for duplicate values * Expose useTree option also on sortBy operator * Split array and B+ tree variants in separate operators * Add missing imports * Add a orderBy operator that uses topK with B+ tree variant * Formatting * Trigger CI * Remove useTree option * Changeset * Dynamically import B+ tree library * Also run orderByWithFractionalIndex tests for both the array and B+ tree variant * Split tree version of orderBy into its own file to enable tree shaking the sorted-btree dependency. * Improved changeset
1 parent 939f2ea commit f751cb4

File tree

11 files changed

+1061
-355
lines changed

11 files changed

+1061
-355
lines changed

.changeset/chilly-icons-design.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@electric-sql/d2mini': patch
3+
---
4+
5+
Introduce topKWithFractionalIndexBTree and orderByWithFractionalIndexBTree operators. These variants use a B+ tree which is more efficient on big collections as its time complexity is logarithmic.

packages/d2mini/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@
5050
},
5151
"dependencies": {
5252
"fractional-indexing": "^3.2.0",
53-
"murmurhash-js": "^1.0.0"
53+
"murmurhash-js": "^1.0.0",
54+
"sorted-btree": "^1.8.1"
5455
}
5556
}

packages/d2mini/src/indexes.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,13 @@ export class Index<K, V> {
3535
return [...valueMap.values()]
3636
}
3737

38+
getMultiplicity(key: K, value: V): number {
39+
const valueMap = this.#inner.get(key)
40+
const valueHash = hash(value)
41+
const [, multiplicity] = valueMap.get(valueHash)
42+
return multiplicity
43+
}
44+
3845
entries() {
3946
return this.#inner.entries()
4047
}

packages/d2mini/src/operators/orderBy.ts

Lines changed: 29 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import { map } from './map.js'
66
import { innerJoin } from './join.js'
77
import { consolidate } from './consolidate.js'
88

9-
interface OrderByOptions<Ve> {
9+
export interface OrderByOptions<Ve> {
1010
comparator?: (a: Ve, b: Ve) => number
1111
limit?: number
1212
offset?: number
@@ -128,19 +128,11 @@ export function orderByWithIndex<
128128
}
129129
}
130130

131-
/**
132-
* Orders the elements and limits the number of results, with optional offset and
133-
* annotates the value with a fractional index.
134-
* This requires a keyed stream, and uses the `topKWithFractionalIndex` operator to order all the elements.
135-
*
136-
* @param valueExtractor - A function that extracts the value to order by from the element
137-
* @param options - An optional object containing comparator, limit and offset properties
138-
* @returns A piped operator that orders the elements and limits the number of results
139-
*/
140-
export function orderByWithFractionalIndex<
131+
export function orderByWithFractionalIndexBase<
141132
T extends KeyValue<unknown, unknown>,
142133
Ve = unknown,
143134
>(
135+
topK: typeof topKWithFractionalIndex,
144136
valueExtractor: (
145137
value: T extends KeyValue<unknown, infer V> ? V : never,
146138
) => Ve,
@@ -181,7 +173,7 @@ export function orderByWithFractionalIndex<
181173
],
182174
] as KeyValue<null, [K, Ve]>,
183175
),
184-
topKWithFractionalIndex((a, b) => comparator(a[1], b[1]), {
176+
topK((a, b) => comparator(a[1], b[1]), {
185177
limit,
186178
offset,
187179
}),
@@ -194,3 +186,28 @@ export function orderByWithFractionalIndex<
194186
)
195187
}
196188
}
189+
190+
/**
191+
* Orders the elements and limits the number of results, with optional offset and
192+
* annotates the value with a fractional index.
193+
* This requires a keyed stream, and uses the `topKWithFractionalIndex` operator to order all the elements.
194+
*
195+
* @param valueExtractor - A function that extracts the value to order by from the element
196+
* @param options - An optional object containing comparator, limit and offset properties
197+
* @returns A piped operator that orders the elements and limits the number of results
198+
*/
199+
export function orderByWithFractionalIndex<
200+
T extends KeyValue<unknown, unknown>,
201+
Ve = unknown,
202+
>(
203+
valueExtractor: (
204+
value: T extends KeyValue<unknown, infer V> ? V : never,
205+
) => Ve,
206+
options?: OrderByOptions<Ve>,
207+
) {
208+
return orderByWithFractionalIndexBase(
209+
topKWithFractionalIndex,
210+
valueExtractor,
211+
options,
212+
)
213+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import { KeyValue } from '../types.js'
2+
import { OrderByOptions, orderByWithFractionalIndexBase } from './orderBy.js'
3+
import { topKWithFractionalIndexBTree } from './topKWithFractionalIndexBTree.js'
4+
5+
export function orderByWithFractionalIndexBTree<
6+
T extends KeyValue<unknown, unknown>,
7+
Ve = unknown,
8+
>(
9+
valueExtractor: (
10+
value: T extends KeyValue<unknown, infer V> ? V : never,
11+
) => Ve,
12+
options?: OrderByOptions<Ve>,
13+
) {
14+
return orderByWithFractionalIndexBase(
15+
topKWithFractionalIndexBTree,
16+
valueExtractor,
17+
options,
18+
)
19+
}

0 commit comments

Comments
 (0)