@tanstack/db-ivm 0.1.9 → 0.1.11
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/d2.cjs +25 -49
- package/dist/cjs/d2.cjs.map +1 -1
- package/dist/cjs/graph.cjs +8 -20
- package/dist/cjs/graph.cjs.map +1 -1
- package/dist/cjs/indexes.cjs +118 -79
- package/dist/cjs/indexes.cjs.map +1 -1
- package/dist/cjs/indexes.d.cts +23 -0
- package/dist/cjs/multiset.cjs +106 -115
- package/dist/cjs/multiset.cjs.map +1 -1
- package/dist/cjs/multiset.d.cts +1 -0
- package/dist/cjs/operators/debug.cjs +5 -15
- package/dist/cjs/operators/debug.cjs.map +1 -1
- package/dist/cjs/operators/distinct.cjs +11 -22
- package/dist/cjs/operators/distinct.cjs.map +1 -1
- package/dist/cjs/operators/filter.cjs +3 -12
- package/dist/cjs/operators/filter.cjs.map +1 -1
- package/dist/cjs/operators/filterBy.cjs.map +1 -1
- package/dist/cjs/operators/filterBy.d.cts +1 -2
- package/dist/cjs/operators/groupBy.cjs +1 -2
- package/dist/cjs/operators/groupBy.cjs.map +1 -1
- package/dist/cjs/operators/join.cjs +104 -93
- package/dist/cjs/operators/join.cjs.map +1 -1
- package/dist/cjs/operators/join.d.cts +11 -8
- package/dist/cjs/operators/map.cjs +3 -12
- package/dist/cjs/operators/map.cjs.map +1 -1
- package/dist/cjs/operators/orderBy.cjs +13 -11
- package/dist/cjs/operators/orderBy.cjs.map +1 -1
- package/dist/cjs/operators/orderBy.d.cts +5 -2
- package/dist/cjs/operators/output.cjs +3 -12
- package/dist/cjs/operators/output.cjs.map +1 -1
- package/dist/cjs/operators/reduce.cjs +11 -22
- package/dist/cjs/operators/reduce.cjs.map +1 -1
- package/dist/cjs/operators/tap.cjs +3 -12
- package/dist/cjs/operators/tap.cjs.map +1 -1
- package/dist/cjs/operators/topK.cjs +4 -4
- package/dist/cjs/operators/topK.cjs.map +1 -1
- package/dist/cjs/operators/topK.d.cts +1 -2
- package/dist/cjs/operators/topKWithFractionalIndex.cjs +117 -82
- package/dist/cjs/operators/topKWithFractionalIndex.cjs.map +1 -1
- package/dist/cjs/operators/topKWithFractionalIndex.d.cts +22 -0
- package/dist/cjs/utils.cjs +21 -0
- package/dist/cjs/utils.cjs.map +1 -1
- package/dist/cjs/utils.d.cts +11 -0
- package/dist/esm/d2.js +25 -49
- package/dist/esm/d2.js.map +1 -1
- package/dist/esm/graph.js +8 -20
- package/dist/esm/graph.js.map +1 -1
- package/dist/esm/indexes.d.ts +23 -0
- package/dist/esm/indexes.js +118 -79
- package/dist/esm/indexes.js.map +1 -1
- package/dist/esm/multiset.d.ts +1 -0
- package/dist/esm/multiset.js +106 -115
- package/dist/esm/multiset.js.map +1 -1
- package/dist/esm/operators/debug.js +5 -15
- package/dist/esm/operators/debug.js.map +1 -1
- package/dist/esm/operators/distinct.js +11 -22
- package/dist/esm/operators/distinct.js.map +1 -1
- package/dist/esm/operators/filter.js +3 -12
- package/dist/esm/operators/filter.js.map +1 -1
- package/dist/esm/operators/filterBy.d.ts +1 -2
- package/dist/esm/operators/filterBy.js.map +1 -1
- package/dist/esm/operators/groupBy.js +1 -2
- package/dist/esm/operators/groupBy.js.map +1 -1
- package/dist/esm/operators/join.d.ts +11 -8
- package/dist/esm/operators/join.js +104 -93
- package/dist/esm/operators/join.js.map +1 -1
- package/dist/esm/operators/map.js +3 -12
- package/dist/esm/operators/map.js.map +1 -1
- package/dist/esm/operators/orderBy.d.ts +5 -2
- package/dist/esm/operators/orderBy.js +13 -11
- package/dist/esm/operators/orderBy.js.map +1 -1
- package/dist/esm/operators/output.js +3 -12
- package/dist/esm/operators/output.js.map +1 -1
- package/dist/esm/operators/reduce.js +11 -22
- package/dist/esm/operators/reduce.js.map +1 -1
- package/dist/esm/operators/tap.js +3 -12
- package/dist/esm/operators/tap.js.map +1 -1
- package/dist/esm/operators/topK.d.ts +1 -2
- package/dist/esm/operators/topK.js +4 -4
- package/dist/esm/operators/topK.js.map +1 -1
- package/dist/esm/operators/topKWithFractionalIndex.d.ts +22 -0
- package/dist/esm/operators/topKWithFractionalIndex.js +118 -83
- package/dist/esm/operators/topKWithFractionalIndex.js.map +1 -1
- package/dist/esm/utils.d.ts +11 -0
- package/dist/esm/utils.js +21 -0
- package/dist/esm/utils.js.map +1 -1
- package/package.json +1 -1
- package/src/indexes.ts +54 -0
- package/src/multiset.ts +6 -0
- package/src/operators/filterBy.ts +1 -2
- package/src/operators/join.ts +249 -139
- package/src/operators/orderBy.ts +6 -2
- package/src/operators/topK.ts +1 -2
- package/src/operators/topKWithFractionalIndex.ts +111 -13
- package/src/utils.ts +33 -0
package/src/operators/join.ts
CHANGED
|
@@ -1,10 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* # Direct Join Algorithms for Incremental View Maintenance
|
|
3
|
+
*
|
|
4
|
+
* High-performance join operations implementing all join types (inner, left, right, full, anti)
|
|
5
|
+
* with minimal state and optimized performance.
|
|
6
|
+
*
|
|
7
|
+
* ## Algorithm
|
|
8
|
+
*
|
|
9
|
+
* For each tick, the algorithm processes incoming changes (deltas) and emits join results:
|
|
10
|
+
*
|
|
11
|
+
* 1. **Build deltas**: Create delta indexes from input messages using `Index.fromMultiSet()`
|
|
12
|
+
* 2. **Inner results**: Emit `ΔA⋈B_old + A_old⋈ΔB + ΔA⋈ΔB` (matched pairs)
|
|
13
|
+
* 3. **Outer results**: For unmatched rows, emit null-extended tuples:
|
|
14
|
+
* - New unmatched rows from deltas (when opposite side empty)
|
|
15
|
+
* - Presence transitions: when key goes `0→>0` (retract nulls) or `>0→0` (emit nulls)
|
|
16
|
+
* 4. **Update state**: Append deltas to indexes (consolidated multiplicity tracking automatic)
|
|
17
|
+
*
|
|
18
|
+
* **Consolidated multiplicity tracking** enables O(1) presence checks instead of scanning index buckets.
|
|
19
|
+
*
|
|
20
|
+
* ## State
|
|
21
|
+
*
|
|
22
|
+
* **Indexes** store the actual data:
|
|
23
|
+
* - `indexA: Index<K, V1>` - all left-side rows accumulated over time
|
|
24
|
+
* - `indexB: Index<K, V2>` - all right-side rows accumulated over time
|
|
25
|
+
*
|
|
26
|
+
* **Consolidated multiplicity tracking** (built into Index):
|
|
27
|
+
* - Each Index maintains sum of multiplicities per key internally
|
|
28
|
+
* - Provides O(1) presence checks: `index.hasPresence(key)` and `index.getConsolidatedMultiplicity(key)`
|
|
29
|
+
* - Avoids scanning entire index buckets just to check if key has any rows
|
|
30
|
+
*
|
|
31
|
+
* ## Join Types
|
|
32
|
+
*
|
|
33
|
+
* - **Inner**: Standard delta terms only
|
|
34
|
+
* - **Outer**: Inner results + null-extended unmatched rows with transition handling
|
|
35
|
+
* - **Anti**: Unmatched rows only (no inner results)
|
|
36
|
+
*
|
|
37
|
+
* ## Key Optimizations
|
|
38
|
+
*
|
|
39
|
+
* - **No temp copying**: Uses `(A⊎ΔA)⋈ΔB = A⋈ΔB ⊎ ΔA⋈ΔB` distributive property
|
|
40
|
+
* - **Early-out checks**: Skip phases when no deltas present
|
|
41
|
+
* - **Zero-entry pruning**: Keep maps compact, O(distinct keys) memory
|
|
42
|
+
* - **Final presence logic**: Avoid emit→retract churn within same tick
|
|
43
|
+
*
|
|
44
|
+
* ## Correctness
|
|
45
|
+
*
|
|
46
|
+
* - **Ordering**: Pre-append snapshots for emissions, post-emit state updates
|
|
47
|
+
* - **Presence**: Key matched iff mass ≠ 0, transitions trigger null handling
|
|
48
|
+
* - **Bag semantics**: Proper multiplicity handling including negatives
|
|
49
|
+
*/
|
|
50
|
+
|
|
1
51
|
import { BinaryOperator, DifferenceStreamWriter } from "../graph.js"
|
|
2
52
|
import { StreamBuilder } from "../d2.js"
|
|
3
53
|
import { MultiSet } from "../multiset.js"
|
|
4
54
|
import { Index } from "../indexes.js"
|
|
5
|
-
import { negate } from "./negate.js"
|
|
6
|
-
import { map } from "./map.js"
|
|
7
|
-
import { concat } from "./concat.js"
|
|
8
55
|
import type { DifferenceStreamReader } from "../graph.js"
|
|
9
56
|
import type { IStreamBuilder, KeyValue, PipedOperator } from "../types.js"
|
|
10
57
|
|
|
@@ -14,66 +61,189 @@ import type { IStreamBuilder, KeyValue, PipedOperator } from "../types.js"
|
|
|
14
61
|
export type JoinType = `inner` | `left` | `right` | `full` | `anti`
|
|
15
62
|
|
|
16
63
|
/**
|
|
17
|
-
* Operator that joins two input streams
|
|
64
|
+
* Operator that joins two input streams using direct join algorithms
|
|
18
65
|
*/
|
|
19
66
|
export class JoinOperator<K, V1, V2> extends BinaryOperator<
|
|
20
|
-
[K, V1] | [K, V2] | [K, [V1, V2]]
|
|
67
|
+
[K, V1] | [K, V2] | [K, [V1, V2]] | [K, [V1 | null, V2 | null]]
|
|
21
68
|
> {
|
|
22
69
|
#indexA = new Index<K, V1>()
|
|
23
70
|
#indexB = new Index<K, V2>()
|
|
71
|
+
#mode: JoinType
|
|
24
72
|
|
|
25
73
|
constructor(
|
|
26
74
|
id: number,
|
|
27
75
|
inputA: DifferenceStreamReader<[K, V1]>,
|
|
28
76
|
inputB: DifferenceStreamReader<[K, V2]>,
|
|
29
|
-
output: DifferenceStreamWriter<
|
|
77
|
+
output: DifferenceStreamWriter<any>,
|
|
78
|
+
mode: JoinType = `inner`
|
|
30
79
|
) {
|
|
31
80
|
super(id, inputA, inputB, output)
|
|
81
|
+
this.#mode = mode
|
|
32
82
|
}
|
|
33
83
|
|
|
34
84
|
run(): void {
|
|
35
|
-
|
|
36
|
-
const
|
|
85
|
+
// Build deltas from input messages
|
|
86
|
+
const deltaA = Index.fromMultiSets<K, V1>(
|
|
87
|
+
this.inputAMessages() as Array<MultiSet<[K, V1]>>
|
|
88
|
+
)
|
|
89
|
+
const deltaB = Index.fromMultiSets<K, V2>(
|
|
90
|
+
this.inputBMessages() as Array<MultiSet<[K, V2]>>
|
|
91
|
+
)
|
|
37
92
|
|
|
38
|
-
//
|
|
39
|
-
|
|
40
|
-
for (const message of messagesA) {
|
|
41
|
-
const multiSetMessage = message as unknown as MultiSet<[K, V1]>
|
|
42
|
-
for (const [item, multiplicity] of multiSetMessage.getInner()) {
|
|
43
|
-
const [key, value] = item
|
|
44
|
-
deltaA.addValue(key, [value, multiplicity])
|
|
45
|
-
}
|
|
46
|
-
}
|
|
93
|
+
// Early-out if nothing changed
|
|
94
|
+
if (deltaA.size === 0 && deltaB.size === 0) return
|
|
47
95
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
const [key, value] = item
|
|
54
|
-
deltaB.addValue(key, [value, multiplicity])
|
|
55
|
-
}
|
|
96
|
+
const results = new MultiSet<any>()
|
|
97
|
+
|
|
98
|
+
// Emit inner results (all modes except anti)
|
|
99
|
+
if (this.#mode !== `anti`) {
|
|
100
|
+
this.emitInnerResults(deltaA, deltaB, results)
|
|
56
101
|
}
|
|
57
102
|
|
|
58
|
-
//
|
|
59
|
-
|
|
103
|
+
// Emit left outer/anti results
|
|
104
|
+
if (
|
|
105
|
+
this.#mode === `left` ||
|
|
106
|
+
this.#mode === `full` ||
|
|
107
|
+
this.#mode === `anti`
|
|
108
|
+
) {
|
|
109
|
+
this.emitLeftOuterResults(deltaA, deltaB, results)
|
|
110
|
+
}
|
|
60
111
|
|
|
61
|
-
//
|
|
62
|
-
|
|
112
|
+
// Emit right outer results
|
|
113
|
+
if (this.#mode === `right` || this.#mode === `full`) {
|
|
114
|
+
this.emitRightOuterResults(deltaA, deltaB, results)
|
|
115
|
+
}
|
|
63
116
|
|
|
64
|
-
//
|
|
117
|
+
// Update state and send results
|
|
118
|
+
// IMPORTANT: All emissions use pre-append snapshots of indexA/indexB.
|
|
119
|
+
// Now append ALL deltas to indices - this happens unconditionally for every key,
|
|
120
|
+
// regardless of whether presence flipped. Consolidated multiplicity tracking is automatic.
|
|
65
121
|
this.#indexA.append(deltaA)
|
|
66
|
-
|
|
67
|
-
// Join existing indexA with deltaB
|
|
68
|
-
results.extend(this.#indexA.join(deltaB))
|
|
122
|
+
this.#indexB.append(deltaB)
|
|
69
123
|
|
|
70
124
|
// Send results
|
|
71
125
|
if (results.getInner().length > 0) {
|
|
72
126
|
this.output.sendData(results)
|
|
73
127
|
}
|
|
128
|
+
}
|
|
74
129
|
|
|
75
|
-
|
|
76
|
-
|
|
130
|
+
private emitInnerResults(
|
|
131
|
+
deltaA: Index<K, V1>,
|
|
132
|
+
deltaB: Index<K, V2>,
|
|
133
|
+
results: MultiSet<any>
|
|
134
|
+
): void {
|
|
135
|
+
// Emit the three standard delta terms: ΔA⋈B_old, A_old⋈ΔB, ΔA⋈ΔB
|
|
136
|
+
if (deltaA.size > 0) results.extend(deltaA.join(this.#indexB))
|
|
137
|
+
if (deltaB.size > 0) results.extend(this.#indexA.join(deltaB))
|
|
138
|
+
if (deltaA.size > 0 && deltaB.size > 0) results.extend(deltaA.join(deltaB))
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
private emitLeftOuterResults(
|
|
142
|
+
deltaA: Index<K, V1>,
|
|
143
|
+
deltaB: Index<K, V2>,
|
|
144
|
+
results: MultiSet<any>
|
|
145
|
+
): void {
|
|
146
|
+
// Emit unmatched left rows from deltaA
|
|
147
|
+
if (deltaA.size > 0) {
|
|
148
|
+
for (const [key, valueIterator] of deltaA.entriesIterators()) {
|
|
149
|
+
const currentMultiplicityB =
|
|
150
|
+
this.#indexB.getConsolidatedMultiplicity(key)
|
|
151
|
+
const deltaMultiplicityB = deltaB.getConsolidatedMultiplicity(key)
|
|
152
|
+
const finalMultiplicityB = currentMultiplicityB + deltaMultiplicityB
|
|
153
|
+
|
|
154
|
+
if (finalMultiplicityB === 0) {
|
|
155
|
+
for (const [value, multiplicity] of valueIterator) {
|
|
156
|
+
if (multiplicity !== 0) {
|
|
157
|
+
results.add([key, [value, null]], multiplicity)
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// Handle presence transitions from right side changes
|
|
165
|
+
if (deltaB.size > 0) {
|
|
166
|
+
for (const key of deltaB.getPresenceKeys()) {
|
|
167
|
+
const before = this.#indexB.getConsolidatedMultiplicity(key)
|
|
168
|
+
const deltaMult = deltaB.getConsolidatedMultiplicity(key)
|
|
169
|
+
if (deltaMult === 0) continue
|
|
170
|
+
const after = before + deltaMult
|
|
171
|
+
|
|
172
|
+
// Skip transition handling if presence doesn't flip (both zero or both non-zero)
|
|
173
|
+
// Note: Index updates happen later regardless - we're only skipping null-extension emissions here
|
|
174
|
+
if ((before === 0) === (after === 0)) continue
|
|
175
|
+
|
|
176
|
+
// Determine the type of transition:
|
|
177
|
+
// - 0 → non-zero: Right becomes non-empty, left rows transition from unmatched to matched
|
|
178
|
+
// → RETRACT previously emitted null-extended rows (emit with negative multiplicity)
|
|
179
|
+
// - non-zero → 0: Right becomes empty, left rows transition from matched to unmatched
|
|
180
|
+
// → EMIT new null-extended rows (emit with positive multiplicity)
|
|
181
|
+
const transitioningToMatched = before === 0
|
|
182
|
+
|
|
183
|
+
for (const [value, multiplicity] of this.#indexA.getIterator(key)) {
|
|
184
|
+
if (multiplicity !== 0) {
|
|
185
|
+
results.add(
|
|
186
|
+
[key, [value, null]],
|
|
187
|
+
transitioningToMatched ? -multiplicity : +multiplicity
|
|
188
|
+
)
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
private emitRightOuterResults(
|
|
196
|
+
deltaA: Index<K, V1>,
|
|
197
|
+
deltaB: Index<K, V2>,
|
|
198
|
+
results: MultiSet<any>
|
|
199
|
+
): void {
|
|
200
|
+
// Emit unmatched right rows from deltaB
|
|
201
|
+
if (deltaB.size > 0) {
|
|
202
|
+
for (const [key, valueIterator] of deltaB.entriesIterators()) {
|
|
203
|
+
const currentMultiplicityA =
|
|
204
|
+
this.#indexA.getConsolidatedMultiplicity(key)
|
|
205
|
+
const deltaMultiplicityA = deltaA.getConsolidatedMultiplicity(key)
|
|
206
|
+
const finalMultiplicityA = currentMultiplicityA + deltaMultiplicityA
|
|
207
|
+
|
|
208
|
+
if (finalMultiplicityA === 0) {
|
|
209
|
+
for (const [value, multiplicity] of valueIterator) {
|
|
210
|
+
if (multiplicity !== 0) {
|
|
211
|
+
results.add([key, [null, value]], multiplicity)
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// Handle presence transitions from left side changes
|
|
219
|
+
if (deltaA.size > 0) {
|
|
220
|
+
for (const key of deltaA.getPresenceKeys()) {
|
|
221
|
+
const before = this.#indexA.getConsolidatedMultiplicity(key)
|
|
222
|
+
const deltaMult = deltaA.getConsolidatedMultiplicity(key)
|
|
223
|
+
if (deltaMult === 0) continue
|
|
224
|
+
const after = before + deltaMult
|
|
225
|
+
|
|
226
|
+
// Skip transition handling if presence doesn't flip (both zero or both non-zero)
|
|
227
|
+
// Note: Index updates happen later regardless - we're only skipping null-extension emissions here
|
|
228
|
+
if ((before === 0) === (after === 0)) continue
|
|
229
|
+
|
|
230
|
+
// Determine the type of transition:
|
|
231
|
+
// - 0 → non-zero: Left becomes non-empty, right rows transition from unmatched to matched
|
|
232
|
+
// → RETRACT previously emitted null-extended rows (emit with negative multiplicity)
|
|
233
|
+
// - non-zero → 0: Left becomes empty, right rows transition from matched to unmatched
|
|
234
|
+
// → EMIT new null-extended rows (emit with positive multiplicity)
|
|
235
|
+
const transitioningToMatched = before === 0
|
|
236
|
+
|
|
237
|
+
for (const [value, multiplicity] of this.#indexB.getIterator(key)) {
|
|
238
|
+
if (multiplicity !== 0) {
|
|
239
|
+
results.add(
|
|
240
|
+
[key, [null, value]],
|
|
241
|
+
transitioningToMatched ? -multiplicity : +multiplicity
|
|
242
|
+
)
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
}
|
|
77
247
|
}
|
|
78
248
|
}
|
|
79
249
|
|
|
@@ -91,39 +261,30 @@ export function join<
|
|
|
91
261
|
other: IStreamBuilder<KeyValue<K, V2>>,
|
|
92
262
|
type: JoinType = `inner`
|
|
93
263
|
): PipedOperator<T, KeyValue<K, [V1 | null, V2 | null]>> {
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
KeyValue<K, [V1 | null, V2]>
|
|
114
|
-
>
|
|
115
|
-
case `full`:
|
|
116
|
-
return fullJoin(other) as unknown as PipedOperator<
|
|
117
|
-
T,
|
|
118
|
-
KeyValue<K, [V1 | null, V2 | null]>
|
|
119
|
-
>
|
|
120
|
-
default:
|
|
121
|
-
throw new Error(`Join type ${type} is invalid`)
|
|
264
|
+
return (
|
|
265
|
+
stream: IStreamBuilder<T>
|
|
266
|
+
): IStreamBuilder<KeyValue<K, [V1 | null, V2 | null]>> => {
|
|
267
|
+
if (stream.graph !== other.graph) {
|
|
268
|
+
throw new Error(`Cannot join streams from different graphs`)
|
|
269
|
+
}
|
|
270
|
+
const output = new StreamBuilder<KeyValue<K, [V1 | null, V2 | null]>>(
|
|
271
|
+
stream.graph,
|
|
272
|
+
new DifferenceStreamWriter<KeyValue<K, [V1 | null, V2 | null]>>()
|
|
273
|
+
)
|
|
274
|
+
const operator = new JoinOperator<K, V1, V2>(
|
|
275
|
+
stream.graph.getNextOperatorId(),
|
|
276
|
+
stream.connectReader() as DifferenceStreamReader<KeyValue<K, V1>>,
|
|
277
|
+
other.connectReader(),
|
|
278
|
+
output.writer,
|
|
279
|
+
type
|
|
280
|
+
)
|
|
281
|
+
stream.graph.addOperator(operator)
|
|
282
|
+
return output
|
|
122
283
|
}
|
|
123
284
|
}
|
|
124
285
|
|
|
125
286
|
/**
|
|
126
|
-
* Joins two input streams
|
|
287
|
+
* Joins two input streams (inner join)
|
|
127
288
|
* @param other - The other stream to join with
|
|
128
289
|
*/
|
|
129
290
|
export function innerJoin<
|
|
@@ -134,27 +295,14 @@ export function innerJoin<
|
|
|
134
295
|
>(
|
|
135
296
|
other: IStreamBuilder<KeyValue<K, V2>>
|
|
136
297
|
): PipedOperator<T, KeyValue<K, [V1, V2]>> {
|
|
137
|
-
return (
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
const output = new StreamBuilder<KeyValue<K, [V1, V2]>>(
|
|
142
|
-
stream.graph,
|
|
143
|
-
new DifferenceStreamWriter<KeyValue<K, [V1, V2]>>()
|
|
144
|
-
)
|
|
145
|
-
const operator = new JoinOperator<K, V1, V2>(
|
|
146
|
-
stream.graph.getNextOperatorId(),
|
|
147
|
-
stream.connectReader() as DifferenceStreamReader<KeyValue<K, V1>>,
|
|
148
|
-
other.connectReader(),
|
|
149
|
-
output.writer
|
|
150
|
-
)
|
|
151
|
-
stream.graph.addOperator(operator)
|
|
152
|
-
return output
|
|
153
|
-
}
|
|
298
|
+
return join(other, `inner`) as unknown as PipedOperator<
|
|
299
|
+
T,
|
|
300
|
+
KeyValue<K, [V1, V2]>
|
|
301
|
+
>
|
|
154
302
|
}
|
|
155
303
|
|
|
156
304
|
/**
|
|
157
|
-
* Joins two input streams
|
|
305
|
+
* Joins two input streams (anti join)
|
|
158
306
|
* @param other - The other stream to join with
|
|
159
307
|
*/
|
|
160
308
|
export function antiJoin<
|
|
@@ -165,24 +313,14 @@ export function antiJoin<
|
|
|
165
313
|
>(
|
|
166
314
|
other: IStreamBuilder<KeyValue<K, V2>>
|
|
167
315
|
): PipedOperator<T, KeyValue<K, [V1, null]>> {
|
|
168
|
-
return (
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
innerJoin(other),
|
|
173
|
-
map(([key, [valueLeft, _valueRight]]) => [key, valueLeft])
|
|
174
|
-
)
|
|
175
|
-
const anti = stream.pipe(
|
|
176
|
-
concat(matchedLeft.pipe(negate())),
|
|
177
|
-
// @ts-ignore TODO: fix this
|
|
178
|
-
map(([key, value]) => [key, [value, null]])
|
|
179
|
-
)
|
|
180
|
-
return anti as IStreamBuilder<KeyValue<K, [V1, null]>>
|
|
181
|
-
}
|
|
316
|
+
return join(other, `anti`) as unknown as PipedOperator<
|
|
317
|
+
T,
|
|
318
|
+
KeyValue<K, [V1, null]>
|
|
319
|
+
>
|
|
182
320
|
}
|
|
183
321
|
|
|
184
322
|
/**
|
|
185
|
-
* Joins two input streams
|
|
323
|
+
* Joins two input streams (left join)
|
|
186
324
|
* @param other - The other stream to join with
|
|
187
325
|
*/
|
|
188
326
|
export function leftJoin<
|
|
@@ -193,21 +331,14 @@ export function leftJoin<
|
|
|
193
331
|
>(
|
|
194
332
|
other: IStreamBuilder<KeyValue<K, V2>>
|
|
195
333
|
): PipedOperator<T, KeyValue<K, [V1, V2 | null]>> {
|
|
196
|
-
return (
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
const right = other
|
|
201
|
-
const inner = left.pipe(innerJoin(right))
|
|
202
|
-
const anti = left.pipe(antiJoin(right))
|
|
203
|
-
return inner.pipe(concat(anti)) as IStreamBuilder<
|
|
204
|
-
KeyValue<K, [V1, V2 | null]>
|
|
205
|
-
>
|
|
206
|
-
}
|
|
334
|
+
return join(other, `left`) as unknown as PipedOperator<
|
|
335
|
+
T,
|
|
336
|
+
KeyValue<K, [V1, V2 | null]>
|
|
337
|
+
>
|
|
207
338
|
}
|
|
208
339
|
|
|
209
340
|
/**
|
|
210
|
-
* Joins two input streams
|
|
341
|
+
* Joins two input streams (right join)
|
|
211
342
|
* @param other - The other stream to join with
|
|
212
343
|
*/
|
|
213
344
|
export function rightJoin<
|
|
@@ -218,24 +349,14 @@ export function rightJoin<
|
|
|
218
349
|
>(
|
|
219
350
|
other: IStreamBuilder<KeyValue<K, V2>>
|
|
220
351
|
): PipedOperator<T, KeyValue<K, [V1 | null, V2]>> {
|
|
221
|
-
return (
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
const right = other
|
|
226
|
-
const inner = left.pipe(innerJoin(right))
|
|
227
|
-
const anti = right.pipe(
|
|
228
|
-
antiJoin(left),
|
|
229
|
-
map(([key, [a, b]]) => [key, [b, a]])
|
|
230
|
-
)
|
|
231
|
-
return inner.pipe(concat(anti)) as IStreamBuilder<
|
|
232
|
-
KeyValue<K, [V1 | null, V2]>
|
|
233
|
-
>
|
|
234
|
-
}
|
|
352
|
+
return join(other, `right`) as unknown as PipedOperator<
|
|
353
|
+
T,
|
|
354
|
+
KeyValue<K, [V1 | null, V2]>
|
|
355
|
+
>
|
|
235
356
|
}
|
|
236
357
|
|
|
237
358
|
/**
|
|
238
|
-
* Joins two input streams
|
|
359
|
+
* Joins two input streams (full join)
|
|
239
360
|
* @param other - The other stream to join with
|
|
240
361
|
*/
|
|
241
362
|
export function fullJoin<
|
|
@@ -246,19 +367,8 @@ export function fullJoin<
|
|
|
246
367
|
>(
|
|
247
368
|
other: IStreamBuilder<KeyValue<K, V2>>
|
|
248
369
|
): PipedOperator<T, KeyValue<K, [V1 | null, V2 | null]>> {
|
|
249
|
-
return (
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
const right = other
|
|
254
|
-
const inner = left.pipe(innerJoin(right))
|
|
255
|
-
const antiLeft = left.pipe(antiJoin(right))
|
|
256
|
-
const antiRight = right.pipe(
|
|
257
|
-
antiJoin(left),
|
|
258
|
-
map(([key, [a, b]]) => [key, [b, a]])
|
|
259
|
-
)
|
|
260
|
-
return inner.pipe(concat(antiLeft), concat(antiRight)) as IStreamBuilder<
|
|
261
|
-
KeyValue<K, [V1 | null, V2 | null]>
|
|
262
|
-
>
|
|
263
|
-
}
|
|
370
|
+
return join(other, `full`) as unknown as PipedOperator<
|
|
371
|
+
T,
|
|
372
|
+
KeyValue<K, [V1 | null, V2 | null]>
|
|
373
|
+
>
|
|
264
374
|
}
|
package/src/operators/orderBy.ts
CHANGED
|
@@ -3,8 +3,7 @@ import { topKWithFractionalIndex } from "./topKWithFractionalIndex.js"
|
|
|
3
3
|
import { map } from "./map.js"
|
|
4
4
|
import { innerJoin } from "./join.js"
|
|
5
5
|
import { consolidate } from "./consolidate.js"
|
|
6
|
-
import type { KeyValue } from "../types.js"
|
|
7
|
-
import type { IStreamBuilder } from "../types"
|
|
6
|
+
import type { IStreamBuilder, KeyValue } from "../types.js"
|
|
8
7
|
|
|
9
8
|
export interface OrderByOptions<Ve> {
|
|
10
9
|
comparator?: (a: Ve, b: Ve) => number
|
|
@@ -14,6 +13,9 @@ export interface OrderByOptions<Ve> {
|
|
|
14
13
|
|
|
15
14
|
type OrderByWithFractionalIndexOptions<Ve> = OrderByOptions<Ve> & {
|
|
16
15
|
setSizeCallback?: (getSize: () => number) => void
|
|
16
|
+
setWindowFn?: (
|
|
17
|
+
windowFn: (options: { offset?: number; limit?: number }) => void
|
|
18
|
+
) => void
|
|
17
19
|
}
|
|
18
20
|
|
|
19
21
|
/**
|
|
@@ -148,6 +150,7 @@ export function orderByWithFractionalIndexBase<
|
|
|
148
150
|
const limit = options?.limit ?? Infinity
|
|
149
151
|
const offset = options?.offset ?? 0
|
|
150
152
|
const setSizeCallback = options?.setSizeCallback
|
|
153
|
+
const setWindowFn = options?.setWindowFn
|
|
151
154
|
const comparator =
|
|
152
155
|
options?.comparator ??
|
|
153
156
|
((a, b) => {
|
|
@@ -168,6 +171,7 @@ export function orderByWithFractionalIndexBase<
|
|
|
168
171
|
limit,
|
|
169
172
|
offset,
|
|
170
173
|
setSizeCallback,
|
|
174
|
+
setWindowFn,
|
|
171
175
|
}
|
|
172
176
|
),
|
|
173
177
|
consolidate()
|
package/src/operators/topK.ts
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { MultiSet } from "../multiset.js"
|
|
2
2
|
import { reduce } from "./reduce.js"
|
|
3
|
-
import type { IStreamBuilder, PipedOperator } from "../types"
|
|
4
|
-
import type { KeyValue } from "../types.js"
|
|
3
|
+
import type { IStreamBuilder, KeyValue, PipedOperator } from "../types"
|
|
5
4
|
|
|
6
5
|
interface TopKOptions {
|
|
7
6
|
limit?: number
|