@rocicorp/zero 1.12.0-canary.2 → 1.12.0-canary.3
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/out/analyze-query/src/analyze-cli.js +2 -2
- package/out/analyze-query/src/analyze-cli.js.map +1 -1
- package/out/zero/package.js +1 -1
- package/out/zero/package.js.map +1 -1
- package/out/zero-cache/src/config/zero-config.d.ts +4 -0
- package/out/zero-cache/src/config/zero-config.d.ts.map +1 -1
- package/out/zero-cache/src/config/zero-config.js +4 -0
- package/out/zero-cache/src/config/zero-config.js.map +1 -1
- package/out/zero-cache/src/server/change-streamer.d.ts.map +1 -1
- package/out/zero-cache/src/server/change-streamer.js +2 -1
- package/out/zero-cache/src/server/change-streamer.js.map +1 -1
- package/out/zero-cache/src/services/change-source/custom/change-source.js +2 -2
- package/out/zero-cache/src/services/change-source/custom/change-source.js.map +1 -1
- package/out/zero-cache/src/services/change-source/pg/change-source-init.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-source/pg/change-source-init.js +1 -1
- package/out/zero-cache/src/services/change-source/pg/change-source-init.js.map +1 -1
- package/out/zero-cache/src/services/change-source/pg/initial-sync.d.ts +1 -0
- package/out/zero-cache/src/services/change-source/pg/initial-sync.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-source/pg/initial-sync.js +5 -5
- package/out/zero-cache/src/services/change-source/pg/initial-sync.js.map +1 -1
- package/out/zero-cache/src/services/change-source/pg/schema/ddl.d.ts +1 -1
- package/out/zero-cache/src/services/change-source/pg/schema/ddl.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-source/pg/schema/ddl.js +2 -2
- package/out/zero-cache/src/services/change-source/pg/schema/ddl.js.map +1 -1
- package/out/zero-cache/src/services/change-source/pg/schema/init.d.ts +1 -1
- package/out/zero-cache/src/services/change-source/pg/schema/init.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-source/pg/schema/init.js +8 -8
- package/out/zero-cache/src/services/change-source/pg/schema/init.js.map +1 -1
- package/out/zero-cache/src/services/change-source/pg/schema/published.d.ts +1 -1
- package/out/zero-cache/src/services/change-source/pg/schema/published.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-source/pg/schema/published.js +2 -1
- package/out/zero-cache/src/services/change-source/pg/schema/published.js.map +1 -1
- package/out/zero-cache/src/services/change-source/pg/schema/shard.d.ts +2 -2
- package/out/zero-cache/src/services/change-source/pg/schema/shard.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-source/pg/schema/shard.js +4 -4
- package/out/zero-cache/src/services/change-source/pg/schema/shard.js.map +1 -1
- package/out/zero-cache/src/services/change-streamer/change-streamer-http.js +2 -2
- package/out/zero-cache/src/services/change-streamer/change-streamer-http.js.map +1 -1
- package/out/zero-cache/src/services/view-syncer/client-handler.d.ts +18 -0
- package/out/zero-cache/src/services/view-syncer/client-handler.d.ts.map +1 -1
- package/out/zero-cache/src/services/view-syncer/client-handler.js +46 -20
- package/out/zero-cache/src/services/view-syncer/client-handler.js.map +1 -1
- package/out/zero-cache/src/services/view-syncer/pipeline-driver.d.ts.map +1 -1
- package/out/zero-cache/src/services/view-syncer/pipeline-driver.js.map +1 -1
- package/out/zero-cache/src/services/view-syncer/snapshotter.d.ts +4 -3
- package/out/zero-cache/src/services/view-syncer/snapshotter.d.ts.map +1 -1
- package/out/zero-cache/src/services/view-syncer/snapshotter.js +5 -2
- package/out/zero-cache/src/services/view-syncer/snapshotter.js.map +1 -1
- package/out/zero-cache/src/workers/connection.js +3 -3
- package/out/zero-cache/src/workers/connection.js.map +1 -1
- package/out/zero-cache/src/workers/syncer.js +2 -2
- package/out/zero-cache/src/workers/syncer.js.map +1 -1
- package/out/zero-client/src/client/version.js +1 -1
- package/out/zero-protocol/src/inspect-down.d.ts +81 -81
- package/out/zero-protocol/src/poke.d.ts +6 -6
- package/out/zero-protocol/src/up.d.ts +60 -60
- package/out/zql/src/ivm/cap.js +2 -4
- package/out/zql/src/ivm/cap.js.map +1 -1
- package/out/zql/src/ivm/take.js +2 -4
- package/out/zql/src/ivm/take.js.map +1 -1
- package/out/zqlite/src/database-storage.d.ts.map +1 -1
- package/out/zqlite/src/database-storage.js +38 -6
- package/out/zqlite/src/database-storage.js.map +1 -1
- package/out/zqlite/src/fetch-sql.d.ts +27 -0
- package/out/zqlite/src/fetch-sql.d.ts.map +1 -0
- package/out/zqlite/src/fetch-sql.js +112 -0
- package/out/zqlite/src/fetch-sql.js.map +1 -0
- package/out/zqlite/src/query-builder.d.ts +11 -1
- package/out/zqlite/src/query-builder.d.ts.map +1 -1
- package/out/zqlite/src/query-builder.js +22 -7
- package/out/zqlite/src/query-builder.js.map +1 -1
- package/out/zqlite/src/table-source.d.ts +9 -2
- package/out/zqlite/src/table-source.d.ts.map +1 -1
- package/out/zqlite/src/table-source.js +11 -14
- package/out/zqlite/src/table-source.js.map +1 -1
- package/package.json +1 -1
|
@@ -4,17 +4,17 @@ export declare const upstreamSchema: v.Type<v.UnrecognizedTuple | ["ackMutationR
|
|
|
4
4
|
clientID: string;
|
|
5
5
|
}] | ["changeDesiredQueries", {
|
|
6
6
|
desiredQueriesPatch: ({
|
|
7
|
+
op: "del";
|
|
8
|
+
hash: string;
|
|
9
|
+
} | {
|
|
10
|
+
op: "clear";
|
|
11
|
+
} | {
|
|
7
12
|
op: "put";
|
|
8
13
|
hash: string;
|
|
9
14
|
ttl?: number | undefined;
|
|
10
15
|
ast?: import("./ast.ts").AST | undefined;
|
|
11
16
|
name?: string | undefined;
|
|
12
17
|
args?: readonly import("../../shared/src/json.ts").ReadonlyJSONValue[] | undefined;
|
|
13
|
-
} | {
|
|
14
|
-
op: "del";
|
|
15
|
-
hash: string;
|
|
16
|
-
} | {
|
|
17
|
-
op: "clear";
|
|
18
18
|
})[];
|
|
19
19
|
traceparent?: string | undefined;
|
|
20
20
|
}] | ["closeConnection", unknown[]] | ["deleteClients", {
|
|
@@ -22,17 +22,17 @@ export declare const upstreamSchema: v.Type<v.UnrecognizedTuple | ["ackMutationR
|
|
|
22
22
|
readonly clientGroupIDs?: readonly string[] | undefined;
|
|
23
23
|
}] | ["initConnection", {
|
|
24
24
|
desiredQueriesPatch: ({
|
|
25
|
+
op: "del";
|
|
26
|
+
hash: string;
|
|
27
|
+
} | {
|
|
28
|
+
op: "clear";
|
|
29
|
+
} | {
|
|
25
30
|
op: "put";
|
|
26
31
|
hash: string;
|
|
27
32
|
ttl?: number | undefined;
|
|
28
33
|
ast?: import("./ast.ts").AST | undefined;
|
|
29
34
|
name?: string | undefined;
|
|
30
35
|
args?: readonly import("../../shared/src/json.ts").ReadonlyJSONValue[] | undefined;
|
|
31
|
-
} | {
|
|
32
|
-
op: "del";
|
|
33
|
-
hash: string;
|
|
34
|
-
} | {
|
|
35
|
-
op: "clear";
|
|
36
36
|
})[];
|
|
37
37
|
clientSchema?: {
|
|
38
38
|
tables: Record<string, {
|
|
@@ -135,17 +135,17 @@ export declare const upstreamSchema: v.Type<v.UnrecognizedTuple | ["ackMutationR
|
|
|
135
135
|
clientID: string;
|
|
136
136
|
}] | ["changeDesiredQueries", {
|
|
137
137
|
desiredQueriesPatch: ({
|
|
138
|
+
op: "del";
|
|
139
|
+
hash: string;
|
|
140
|
+
} | {
|
|
141
|
+
op: "clear";
|
|
142
|
+
} | {
|
|
138
143
|
op: "put";
|
|
139
144
|
hash: string;
|
|
140
145
|
ttl?: number | undefined;
|
|
141
146
|
ast?: import("./ast.ts").AST | undefined;
|
|
142
147
|
name?: string | undefined;
|
|
143
148
|
args?: readonly import("../../shared/src/json.ts").ReadonlyJSONValue[] | undefined;
|
|
144
|
-
} | {
|
|
145
|
-
op: "del";
|
|
146
|
-
hash: string;
|
|
147
|
-
} | {
|
|
148
|
-
op: "clear";
|
|
149
149
|
})[];
|
|
150
150
|
traceparent?: string | undefined;
|
|
151
151
|
}] | ["closeConnection", unknown[]] | ["deleteClients", {
|
|
@@ -153,17 +153,17 @@ export declare const upstreamSchema: v.Type<v.UnrecognizedTuple | ["ackMutationR
|
|
|
153
153
|
readonly clientGroupIDs?: readonly string[] | undefined;
|
|
154
154
|
}] | ["initConnection", {
|
|
155
155
|
desiredQueriesPatch: ({
|
|
156
|
+
op: "del";
|
|
157
|
+
hash: string;
|
|
158
|
+
} | {
|
|
159
|
+
op: "clear";
|
|
160
|
+
} | {
|
|
156
161
|
op: "put";
|
|
157
162
|
hash: string;
|
|
158
163
|
ttl?: number | undefined;
|
|
159
164
|
ast?: import("./ast.ts").AST | undefined;
|
|
160
165
|
name?: string | undefined;
|
|
161
166
|
args?: readonly import("../../shared/src/json.ts").ReadonlyJSONValue[] | undefined;
|
|
162
|
-
} | {
|
|
163
|
-
op: "del";
|
|
164
|
-
hash: string;
|
|
165
|
-
} | {
|
|
166
|
-
op: "clear";
|
|
167
167
|
})[];
|
|
168
168
|
clientSchema?: {
|
|
169
169
|
tables: Record<string, {
|
|
@@ -265,17 +265,17 @@ export declare const upstreamSchema: v.Type<v.UnrecognizedTuple | ["ackMutationR
|
|
|
265
265
|
clientID: string;
|
|
266
266
|
}] | ["changeDesiredQueries", {
|
|
267
267
|
desiredQueriesPatch: ({
|
|
268
|
+
op: "del";
|
|
269
|
+
hash: string;
|
|
270
|
+
} | {
|
|
271
|
+
op: "clear";
|
|
272
|
+
} | {
|
|
268
273
|
op: "put";
|
|
269
274
|
hash: string;
|
|
270
275
|
ttl?: number | undefined;
|
|
271
276
|
ast?: import("./ast.ts").AST | undefined;
|
|
272
277
|
name?: string | undefined;
|
|
273
278
|
args?: readonly import("../../shared/src/json.ts").ReadonlyJSONValue[] | undefined;
|
|
274
|
-
} | {
|
|
275
|
-
op: "del";
|
|
276
|
-
hash: string;
|
|
277
|
-
} | {
|
|
278
|
-
op: "clear";
|
|
279
279
|
})[];
|
|
280
280
|
traceparent?: string | undefined;
|
|
281
281
|
}] | ["closeConnection", unknown[]] | ["deleteClients", {
|
|
@@ -283,17 +283,17 @@ export declare const upstreamSchema: v.Type<v.UnrecognizedTuple | ["ackMutationR
|
|
|
283
283
|
readonly clientGroupIDs?: readonly string[] | undefined;
|
|
284
284
|
}] | ["initConnection", {
|
|
285
285
|
desiredQueriesPatch: ({
|
|
286
|
+
op: "del";
|
|
287
|
+
hash: string;
|
|
288
|
+
} | {
|
|
289
|
+
op: "clear";
|
|
290
|
+
} | {
|
|
286
291
|
op: "put";
|
|
287
292
|
hash: string;
|
|
288
293
|
ttl?: number | undefined;
|
|
289
294
|
ast?: import("./ast.ts").AST | undefined;
|
|
290
295
|
name?: string | undefined;
|
|
291
296
|
args?: readonly import("../../shared/src/json.ts").ReadonlyJSONValue[] | undefined;
|
|
292
|
-
} | {
|
|
293
|
-
op: "del";
|
|
294
|
-
hash: string;
|
|
295
|
-
} | {
|
|
296
|
-
op: "clear";
|
|
297
297
|
})[];
|
|
298
298
|
clientSchema?: {
|
|
299
299
|
tables: Record<string, {
|
|
@@ -397,17 +397,17 @@ export declare const upstreamSchemaWithUnparsedAnalyzeQuery: v.Type<v.Unrecogniz
|
|
|
397
397
|
clientID: string;
|
|
398
398
|
}] | ["changeDesiredQueries", {
|
|
399
399
|
desiredQueriesPatch: ({
|
|
400
|
+
op: "del";
|
|
401
|
+
hash: string;
|
|
402
|
+
} | {
|
|
403
|
+
op: "clear";
|
|
404
|
+
} | {
|
|
400
405
|
op: "put";
|
|
401
406
|
hash: string;
|
|
402
407
|
ttl?: number | undefined;
|
|
403
408
|
ast?: import("./ast.ts").AST | undefined;
|
|
404
409
|
name?: string | undefined;
|
|
405
410
|
args?: readonly import("../../shared/src/json.ts").ReadonlyJSONValue[] | undefined;
|
|
406
|
-
} | {
|
|
407
|
-
op: "del";
|
|
408
|
-
hash: string;
|
|
409
|
-
} | {
|
|
410
|
-
op: "clear";
|
|
411
411
|
})[];
|
|
412
412
|
traceparent?: string | undefined;
|
|
413
413
|
}] | ["closeConnection", unknown[]] | ["deleteClients", {
|
|
@@ -415,17 +415,17 @@ export declare const upstreamSchemaWithUnparsedAnalyzeQuery: v.Type<v.Unrecogniz
|
|
|
415
415
|
readonly clientGroupIDs?: readonly string[] | undefined;
|
|
416
416
|
}] | ["initConnection", {
|
|
417
417
|
desiredQueriesPatch: ({
|
|
418
|
+
op: "del";
|
|
419
|
+
hash: string;
|
|
420
|
+
} | {
|
|
421
|
+
op: "clear";
|
|
422
|
+
} | {
|
|
418
423
|
op: "put";
|
|
419
424
|
hash: string;
|
|
420
425
|
ttl?: number | undefined;
|
|
421
426
|
ast?: import("./ast.ts").AST | undefined;
|
|
422
427
|
name?: string | undefined;
|
|
423
428
|
args?: readonly import("../../shared/src/json.ts").ReadonlyJSONValue[] | undefined;
|
|
424
|
-
} | {
|
|
425
|
-
op: "del";
|
|
426
|
-
hash: string;
|
|
427
|
-
} | {
|
|
428
|
-
op: "clear";
|
|
429
429
|
})[];
|
|
430
430
|
clientSchema?: {
|
|
431
431
|
tables: Record<string, {
|
|
@@ -528,17 +528,17 @@ export declare const upstreamSchemaWithUnparsedAnalyzeQuery: v.Type<v.Unrecogniz
|
|
|
528
528
|
clientID: string;
|
|
529
529
|
}] | ["changeDesiredQueries", {
|
|
530
530
|
desiredQueriesPatch: ({
|
|
531
|
+
op: "del";
|
|
532
|
+
hash: string;
|
|
533
|
+
} | {
|
|
534
|
+
op: "clear";
|
|
535
|
+
} | {
|
|
531
536
|
op: "put";
|
|
532
537
|
hash: string;
|
|
533
538
|
ttl?: number | undefined;
|
|
534
539
|
ast?: import("./ast.ts").AST | undefined;
|
|
535
540
|
name?: string | undefined;
|
|
536
541
|
args?: readonly import("../../shared/src/json.ts").ReadonlyJSONValue[] | undefined;
|
|
537
|
-
} | {
|
|
538
|
-
op: "del";
|
|
539
|
-
hash: string;
|
|
540
|
-
} | {
|
|
541
|
-
op: "clear";
|
|
542
542
|
})[];
|
|
543
543
|
traceparent?: string | undefined;
|
|
544
544
|
}] | ["closeConnection", unknown[]] | ["deleteClients", {
|
|
@@ -546,17 +546,17 @@ export declare const upstreamSchemaWithUnparsedAnalyzeQuery: v.Type<v.Unrecogniz
|
|
|
546
546
|
readonly clientGroupIDs?: readonly string[] | undefined;
|
|
547
547
|
}] | ["initConnection", {
|
|
548
548
|
desiredQueriesPatch: ({
|
|
549
|
+
op: "del";
|
|
550
|
+
hash: string;
|
|
551
|
+
} | {
|
|
552
|
+
op: "clear";
|
|
553
|
+
} | {
|
|
549
554
|
op: "put";
|
|
550
555
|
hash: string;
|
|
551
556
|
ttl?: number | undefined;
|
|
552
557
|
ast?: import("./ast.ts").AST | undefined;
|
|
553
558
|
name?: string | undefined;
|
|
554
559
|
args?: readonly import("../../shared/src/json.ts").ReadonlyJSONValue[] | undefined;
|
|
555
|
-
} | {
|
|
556
|
-
op: "del";
|
|
557
|
-
hash: string;
|
|
558
|
-
} | {
|
|
559
|
-
op: "clear";
|
|
560
560
|
})[];
|
|
561
561
|
clientSchema?: {
|
|
562
562
|
tables: Record<string, {
|
|
@@ -658,17 +658,17 @@ export declare const upstreamSchemaWithUnparsedAnalyzeQuery: v.Type<v.Unrecogniz
|
|
|
658
658
|
clientID: string;
|
|
659
659
|
}] | ["changeDesiredQueries", {
|
|
660
660
|
desiredQueriesPatch: ({
|
|
661
|
+
op: "del";
|
|
662
|
+
hash: string;
|
|
663
|
+
} | {
|
|
664
|
+
op: "clear";
|
|
665
|
+
} | {
|
|
661
666
|
op: "put";
|
|
662
667
|
hash: string;
|
|
663
668
|
ttl?: number | undefined;
|
|
664
669
|
ast?: import("./ast.ts").AST | undefined;
|
|
665
670
|
name?: string | undefined;
|
|
666
671
|
args?: readonly import("../../shared/src/json.ts").ReadonlyJSONValue[] | undefined;
|
|
667
|
-
} | {
|
|
668
|
-
op: "del";
|
|
669
|
-
hash: string;
|
|
670
|
-
} | {
|
|
671
|
-
op: "clear";
|
|
672
672
|
})[];
|
|
673
673
|
traceparent?: string | undefined;
|
|
674
674
|
}] | ["closeConnection", unknown[]] | ["deleteClients", {
|
|
@@ -676,17 +676,17 @@ export declare const upstreamSchemaWithUnparsedAnalyzeQuery: v.Type<v.Unrecogniz
|
|
|
676
676
|
readonly clientGroupIDs?: readonly string[] | undefined;
|
|
677
677
|
}] | ["initConnection", {
|
|
678
678
|
desiredQueriesPatch: ({
|
|
679
|
+
op: "del";
|
|
680
|
+
hash: string;
|
|
681
|
+
} | {
|
|
682
|
+
op: "clear";
|
|
683
|
+
} | {
|
|
679
684
|
op: "put";
|
|
680
685
|
hash: string;
|
|
681
686
|
ttl?: number | undefined;
|
|
682
687
|
ast?: import("./ast.ts").AST | undefined;
|
|
683
688
|
name?: string | undefined;
|
|
684
689
|
args?: readonly import("../../shared/src/json.ts").ReadonlyJSONValue[] | undefined;
|
|
685
|
-
} | {
|
|
686
|
-
op: "del";
|
|
687
|
-
hash: string;
|
|
688
|
-
} | {
|
|
689
|
-
op: "clear";
|
|
690
690
|
})[];
|
|
691
691
|
clientSchema?: {
|
|
692
692
|
tables: Record<string, {
|
package/out/zql/src/ivm/cap.js
CHANGED
|
@@ -51,7 +51,7 @@ var Cap = class {
|
|
|
51
51
|
const capStateKey = getCapStateKey(this.#partitionKey, req.constraint);
|
|
52
52
|
const capState = this.#storage.get(capStateKey);
|
|
53
53
|
if (!capState) {
|
|
54
|
-
yield* this.#initialFetch(req);
|
|
54
|
+
yield* this.#initialFetch(req, capStateKey);
|
|
55
55
|
return;
|
|
56
56
|
}
|
|
57
57
|
if (capState.size === 0) return;
|
|
@@ -70,11 +70,9 @@ var Cap = class {
|
|
|
70
70
|
}
|
|
71
71
|
}
|
|
72
72
|
}
|
|
73
|
-
*#initialFetch(req) {
|
|
73
|
+
*#initialFetch(req, capStateKey) {
|
|
74
74
|
if (this.#limit === 0) return;
|
|
75
75
|
assert(constraintMatchesPartitionKey(req.constraint, this.#partitionKey), "Constraint should match partition key");
|
|
76
|
-
const capStateKey = getCapStateKey(this.#partitionKey, req.constraint);
|
|
77
|
-
assert(this.#storage.get(capStateKey) === void 0, "Cap state should be undefined");
|
|
78
76
|
let size = 0;
|
|
79
77
|
const pks = [];
|
|
80
78
|
let downstreamEarlyReturn = true;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cap.js","names":["#input","#storage","#limit","#partitionKey","#partitionKeyComparator","#primaryKey","#output","#initialFetch","#pushEditChange"],"sources":["../../../../../zql/src/ivm/cap.ts"],"sourcesContent":["import {assert} from '../../../shared/src/asserts.ts';\nimport {emptyArray} from '../../../shared/src/sentinels.ts';\nimport type {Row, Value} from '../../../zero-protocol/src/data.ts';\nimport type {PrimaryKey} from '../../../zero-protocol/src/primary-key.ts';\nimport {ChangeIndex} from './change-index.ts';\nimport {ChangeType} from './change-type.ts';\nimport {makeAddChange, type Change, type EditChange} from './change.ts';\nimport type {Constraint} from './constraint.ts';\nimport type {Comparator, Node} from './data.ts';\nimport {\n throwOutput,\n type FetchRequest,\n type Input,\n type InputBase,\n type Operator,\n type Output,\n type Storage,\n} from './operator.ts';\nimport type {SourceSchema} from './schema.ts';\nimport {type Stream} from './stream.ts';\nimport {\n constraintMatchesPartitionKey,\n makePartitionKeyComparator,\n type PartitionKey,\n} from './take.ts';\n\ntype CapState = {\n size: number;\n pks: string[];\n};\n\ninterface CapStorage {\n get(key: string): CapState | undefined;\n set(key: string, value: CapState): void;\n del(key: string): void;\n destroy(): void;\n}\n\n/**\n * The Cap operator is a count-based limiter for EXISTS subqueries that\n * does not require ordering. Unlike Take, it tracks membership by primary\n * key set rather than by a sorted bound. This means:\n *\n * - No comparator needed (no ordering requirement)\n * - No `start` or `reverse` fetch support\n * - No `#rowHiddenFromFetch` complexity (we can defer when adding to the pk set)\n *\n * Cap is used in EXISTS child pipelines where only the count of matching\n * rows matters, not their order. This allows SQLite to skip ORDER BY\n * entirely, enabling much faster query plans.\n *\n * Cap can count rows globally or by unique value of some partition key\n * (same as Take).\n */\nexport class Cap implements Operator {\n readonly #input: Input;\n readonly #storage: CapStorage;\n readonly #limit: number;\n readonly #partitionKey: PartitionKey | undefined;\n readonly #partitionKeyComparator: Comparator | undefined;\n readonly #primaryKey: PrimaryKey;\n\n #output: Output = throwOutput;\n\n constructor(\n input: Input,\n storage: Storage,\n limit: number,\n partitionKey?: PartitionKey,\n ) {\n assert(limit >= 0, 'Limit must be non-negative');\n input.setOutput(this);\n this.#input = input;\n this.#storage = storage as CapStorage;\n this.#limit = limit;\n this.#partitionKey = partitionKey;\n this.#partitionKeyComparator =\n partitionKey && makePartitionKeyComparator(partitionKey);\n this.#primaryKey = input.getSchema().primaryKey;\n }\n\n setOutput(output: Output): void {\n this.#output = output;\n }\n\n getSchema(): SourceSchema {\n return this.#input.getSchema();\n }\n\n *fetch(req: FetchRequest): Stream<Node | 'yield'> {\n assert(!req.start, 'Cap does not support start');\n assert(!req.reverse, 'Cap does not support reverse');\n\n // Cap is only built for non-flipped EXISTS subqueries, whose only\n // downstream consumer is a Join that always fetches with a constraint\n // built from the correlation's childField — which is Cap's partition\n // key. So either partitionKey is undefined, or constraint matches.\n assert(\n !this.#partitionKey ||\n (req.constraint !== undefined &&\n constraintMatchesPartitionKey(req.constraint, this.#partitionKey)),\n 'Cap fetch: constraint must match partition key when partitioned',\n );\n\n const capStateKey = getCapStateKey(this.#partitionKey, req.constraint);\n const capState = this.#storage.get(capStateKey);\n if (!capState) {\n yield* this.#initialFetch(req);\n return;\n }\n if (capState.size === 0) {\n return;\n }\n // PK-based point lookups: fetch each tracked row by its PK directly,\n // rather than scanning the partition and filtering.\n for (const pk of capState.pks) {\n const pkConstraint = deserializePKToConstraint(pk, this.#primaryKey);\n // Preserve req.constraint (the partition key) so upstream partitioned\n // operators (e.g. Take) retain their partition scope and do not throw.\n const constraint = req.constraint\n ? {...req.constraint, ...pkConstraint}\n : pkConstraint;\n for (const inputNode of this.#input.fetch({constraint})) {\n if (inputNode === 'yield') {\n yield inputNode;\n continue;\n }\n yield inputNode;\n }\n }\n }\n\n *#initialFetch(req: FetchRequest): Stream<Node | 'yield'> {\n if (this.#limit === 0) {\n return;\n }\n\n assert(\n constraintMatchesPartitionKey(req.constraint, this.#partitionKey),\n 'Constraint should match partition key',\n );\n\n const capStateKey = getCapStateKey(this.#partitionKey, req.constraint);\n assert(\n this.#storage.get(capStateKey) === undefined,\n 'Cap state should be undefined',\n );\n\n let size = 0;\n const pks: string[] = [];\n let downstreamEarlyReturn = true;\n let exceptionThrown = false;\n try {\n for (const inputNode of this.#input.fetch(req)) {\n if (inputNode === 'yield') {\n yield 'yield';\n continue;\n }\n yield inputNode;\n pks.push(serializePK(inputNode.row, this.#primaryKey));\n size++;\n if (size === this.#limit) {\n break;\n }\n }\n downstreamEarlyReturn = false;\n } catch (e) {\n exceptionThrown = true;\n throw e;\n } finally {\n if (!exceptionThrown) {\n this.#storage.set(capStateKey, {size, pks});\n // If it becomes necessary to support downstream early return, this\n // assert should be removed, and replaced with code that consumes\n // the input stream until limit is reached or the input stream is\n // exhausted so that capState is properly hydrated.\n assert(\n !downstreamEarlyReturn,\n 'Unexpected early return prevented full hydration',\n );\n }\n }\n }\n\n *push(change: Change): Stream<'yield'> {\n if (change[ChangeIndex.TYPE] === ChangeType.EDIT) {\n yield* this.#pushEditChange(change);\n return;\n }\n\n const capStateKey = getCapStateKey(\n this.#partitionKey,\n change[ChangeIndex.NODE].row,\n );\n const capState = this.#storage.get(capStateKey);\n if (!capState) {\n return;\n }\n\n const pk = serializePK(change[ChangeIndex.NODE].row, this.#primaryKey);\n\n if (change[ChangeIndex.TYPE] === ChangeType.ADD) {\n if (capState.size < this.#limit) {\n const pks = [...capState.pks, pk];\n this.#storage.set(capStateKey, {size: capState.size + 1, pks});\n yield* this.#output.push(change, this);\n return;\n }\n // Full — drop\n return;\n } else if (change[ChangeIndex.TYPE] === ChangeType.REMOVE) {\n const pkIndex = capState.pks.indexOf(pk);\n if (pkIndex === -1) {\n // Not in our set — drop\n return;\n }\n // Remove from set\n const pks = [...capState.pks];\n pks.splice(pkIndex, 1);\n const newSize = capState.size - 1;\n\n // Try to refill: fetch from input with partition constraint,\n // find first row NOT in PK set\n const pkSet = new Set(pks);\n const constraint = this.#partitionKey\n ? (Object.fromEntries(\n this.#partitionKey.map(\n key => [key, change[ChangeIndex.NODE].row[key]] as const,\n ),\n ) as Constraint)\n : undefined;\n\n let replacement: Node | undefined;\n for (const node of this.#input.fetch({constraint})) {\n if (node === 'yield') {\n yield node;\n continue;\n }\n const nodePK = serializePK(node.row, this.#primaryKey);\n if (!pkSet.has(nodePK)) {\n replacement = node;\n break;\n }\n }\n\n if (replacement) {\n // Store state WITHOUT replacement during remove forward,\n // matching Take's pattern of hiding in-flight changes from re-fetches.\n this.#storage.set(capStateKey, {size: newSize, pks});\n yield* this.#output.push(change, this);\n // Now add replacement to set and forward the add.\n const replacementPK = serializePK(replacement.row, this.#primaryKey);\n pks.push(replacementPK);\n this.#storage.set(capStateKey, {size: newSize + 1, pks});\n yield* this.#output.push(makeAddChange(replacement), this);\n } else {\n this.#storage.set(capStateKey, {size: newSize, pks});\n yield* this.#output.push(change, this);\n }\n } else if (change[ChangeIndex.TYPE] === ChangeType.CHILD) {\n const pkSet = new Set(capState.pks);\n if (pkSet.has(pk)) {\n yield* this.#output.push(change, this);\n }\n }\n }\n\n #pushEditChange(change: EditChange): Stream<'yield'> {\n assert(\n !this.#partitionKeyComparator ||\n this.#partitionKeyComparator(\n change[ChangeIndex.OLD_NODE].row,\n change[ChangeIndex.NODE].row,\n ) === 0,\n 'Unexpected change of partition key',\n );\n const capStateKey = getCapStateKey(\n this.#partitionKey,\n change[ChangeIndex.OLD_NODE].row,\n );\n const capState = this.#storage.get(capStateKey);\n if (!capState) {\n return emptyArray;\n }\n\n const oldPK = serializePK(\n change[ChangeIndex.OLD_NODE].row,\n this.#primaryKey,\n );\n const pkSet = new Set(capState.pks);\n if (pkSet.has(oldPK)) {\n // Update the PK in our set if it changed\n const newPK = serializePK(change[ChangeIndex.NODE].row, this.#primaryKey);\n if (oldPK !== newPK) {\n const pks = capState.pks.map(p => (p === oldPK ? newPK : p));\n this.#storage.set(capStateKey, {size: capState.size, pks});\n }\n return this.#output.push(change, this);\n }\n // If not in our set, drop\n return emptyArray;\n }\n\n destroy(): void {\n this.#input.destroy();\n this.#storage.destroy();\n }\n\n *reconcile(_pusher: InputBase): Stream<'yield'> {\n if (this.#output.reconcile) {\n yield* this.#output.reconcile(this);\n }\n }\n}\n\nfunction getCapStateKey(\n partitionKey: PartitionKey | undefined,\n rowOrConstraint: Row | Constraint | undefined,\n): string {\n const partitionValues: Value[] = [];\n\n if (partitionKey && rowOrConstraint) {\n for (const key of partitionKey) {\n partitionValues.push(rowOrConstraint[key]);\n }\n }\n\n return JSON.stringify(['cap', ...partitionValues]);\n}\n\nfunction serializePK(row: Row, primaryKey: PrimaryKey): string {\n return JSON.stringify(primaryKey.map(k => row[k]));\n}\n\nfunction deserializePKToConstraint(\n pk: string,\n primaryKey: PrimaryKey,\n): Constraint {\n const values = JSON.parse(pk) as Value[];\n const constraint: Record<string, Value> = {};\n for (let i = 0; i < primaryKey.length; i++) {\n constraint[primaryKey[i]] = values[i];\n }\n return constraint;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;AAsDA,IAAa,MAAb,MAAqC;CACnC;CACA;CACA;CACA;CACA;CACA;CAEA,UAAkB;CAElB,YACE,OACA,SACA,OACA,cACA;EACA,OAAO,SAAS,GAAG,4BAA4B;EAC/C,MAAM,UAAU,IAAI;EACpB,KAAKA,SAAS;EACd,KAAKC,WAAW;EAChB,KAAKC,SAAS;EACd,KAAKC,gBAAgB;EACrB,KAAKC,0BACH,gBAAgB,2BAA2B,YAAY;EACzD,KAAKC,cAAc,MAAM,UAAU,EAAE;CACvC;CAEA,UAAU,QAAsB;EAC9B,KAAKC,UAAU;CACjB;CAEA,YAA0B;EACxB,OAAO,KAAKN,OAAO,UAAU;CAC/B;CAEA,CAAC,MAAM,KAA2C;EAChD,OAAO,CAAC,IAAI,OAAO,4BAA4B;EAC/C,OAAO,CAAC,IAAI,SAAS,8BAA8B;EAMnD,OACE,CAAC,KAAKG,iBACH,IAAI,eAAe,KAAA,KAClB,8BAA8B,IAAI,YAAY,KAAKA,aAAa,GACpE,iEACF;EAEA,MAAM,cAAc,eAAe,KAAKA,eAAe,IAAI,UAAU;EACrE,MAAM,WAAW,KAAKF,SAAS,IAAI,WAAW;EAC9C,IAAI,CAAC,UAAU;GACb,OAAO,KAAKM,cAAc,GAAG;GAC7B;EACF;EACA,IAAI,SAAS,SAAS,GACpB;EAIF,KAAK,MAAM,MAAM,SAAS,KAAK;GAC7B,MAAM,eAAe,0BAA0B,IAAI,KAAKF,WAAW;GAGnE,MAAM,aAAa,IAAI,aACnB;IAAC,GAAG,IAAI;IAAY,GAAG;GAAY,IACnC;GACJ,KAAK,MAAM,aAAa,KAAKL,OAAO,MAAM,EAAC,WAAU,CAAC,GAAG;IACvD,IAAI,cAAc,SAAS;KACzB,MAAM;KACN;IACF;IACA,MAAM;GACR;EACF;CACF;CAEA,CAACO,cAAc,KAA2C;EACxD,IAAI,KAAKL,WAAW,GAClB;EAGF,OACE,8BAA8B,IAAI,YAAY,KAAKC,aAAa,GAChE,uCACF;EAEA,MAAM,cAAc,eAAe,KAAKA,eAAe,IAAI,UAAU;EACrE,OACE,KAAKF,SAAS,IAAI,WAAW,MAAM,KAAA,GACnC,+BACF;EAEA,IAAI,OAAO;EACX,MAAM,MAAgB,CAAC;EACvB,IAAI,wBAAwB;EAC5B,IAAI,kBAAkB;EACtB,IAAI;GACF,KAAK,MAAM,aAAa,KAAKD,OAAO,MAAM,GAAG,GAAG;IAC9C,IAAI,cAAc,SAAS;KACzB,MAAM;KACN;IACF;IACA,MAAM;IACN,IAAI,KAAK,YAAY,UAAU,KAAK,KAAKK,WAAW,CAAC;IACrD;IACA,IAAI,SAAS,KAAKH,QAChB;GAEJ;GACA,wBAAwB;EAC1B,SAAS,GAAG;GACV,kBAAkB;GAClB,MAAM;EACR,UAAU;GACR,IAAI,CAAC,iBAAiB;IACpB,KAAKD,SAAS,IAAI,aAAa;KAAC;KAAM;IAAG,CAAC;IAK1C,OACE,CAAC,uBACD,kDACF;GACF;EACF;CACF;CAEA,CAAC,KAAK,QAAiC;EACrC,IAAI,OAAO,OAAsB,GAAiB;GAChD,OAAO,KAAKO,gBAAgB,MAAM;GAClC;EACF;EAEA,MAAM,cAAc,eAClB,KAAKL,eACL,OAAO,GAAkB,GAC3B;EACA,MAAM,WAAW,KAAKF,SAAS,IAAI,WAAW;EAC9C,IAAI,CAAC,UACH;EAGF,MAAM,KAAK,YAAY,OAAO,GAAkB,KAAK,KAAKI,WAAW;EAErE,IAAI,OAAO,OAAsB,GAAgB;GAC/C,IAAI,SAAS,OAAO,KAAKH,QAAQ;IAC/B,MAAM,MAAM,CAAC,GAAG,SAAS,KAAK,EAAE;IAChC,KAAKD,SAAS,IAAI,aAAa;KAAC,MAAM,SAAS,OAAO;KAAG;IAAG,CAAC;IAC7D,OAAO,KAAKK,QAAQ,KAAK,QAAQ,IAAI;IACrC;GACF;GAEA;EACF,OAAO,IAAI,OAAO,OAAsB,GAAmB;GACzD,MAAM,UAAU,SAAS,IAAI,QAAQ,EAAE;GACvC,IAAI,YAAY,IAEd;GAGF,MAAM,MAAM,CAAC,GAAG,SAAS,GAAG;GAC5B,IAAI,OAAO,SAAS,CAAC;GACrB,MAAM,UAAU,SAAS,OAAO;GAIhC,MAAM,QAAQ,IAAI,IAAI,GAAG;GACzB,MAAM,aAAa,KAAKH,gBACnB,OAAO,YACN,KAAKA,cAAc,KACjB,QAAO,CAAC,KAAK,OAAO,GAAkB,IAAI,IAAI,CAChD,CACF,IACA,KAAA;GAEJ,IAAI;GACJ,KAAK,MAAM,QAAQ,KAAKH,OAAO,MAAM,EAAC,WAAU,CAAC,GAAG;IAClD,IAAI,SAAS,SAAS;KACpB,MAAM;KACN;IACF;IACA,MAAM,SAAS,YAAY,KAAK,KAAK,KAAKK,WAAW;IACrD,IAAI,CAAC,MAAM,IAAI,MAAM,GAAG;KACtB,cAAc;KACd;IACF;GACF;GAEA,IAAI,aAAa;IAGf,KAAKJ,SAAS,IAAI,aAAa;KAAC,MAAM;KAAS;IAAG,CAAC;IACnD,OAAO,KAAKK,QAAQ,KAAK,QAAQ,IAAI;IAErC,MAAM,gBAAgB,YAAY,YAAY,KAAK,KAAKD,WAAW;IACnE,IAAI,KAAK,aAAa;IACtB,KAAKJ,SAAS,IAAI,aAAa;KAAC,MAAM,UAAU;KAAG;IAAG,CAAC;IACvD,OAAO,KAAKK,QAAQ,KAAK,cAAc,WAAW,GAAG,IAAI;GAC3D,OAAO;IACL,KAAKL,SAAS,IAAI,aAAa;KAAC,MAAM;KAAS;IAAG,CAAC;IACnD,OAAO,KAAKK,QAAQ,KAAK,QAAQ,IAAI;GACvC;EACF,OAAO,IAAI,OAAO,OAAsB;OAElC,IADc,IAAI,SAAS,GAC3B,EAAM,IAAI,EAAE,GACd,OAAO,KAAKA,QAAQ,KAAK,QAAQ,IAAI;EAAA;CAG3C;CAEA,gBAAgB,QAAqC;EACnD,OACE,CAAC,KAAKF,2BACJ,KAAKA,wBACH,OAAO,GAAsB,KAC7B,OAAO,GAAkB,GAC3B,MAAM,GACR,oCACF;EACA,MAAM,cAAc,eAClB,KAAKD,eACL,OAAO,GAAsB,GAC/B;EACA,MAAM,WAAW,KAAKF,SAAS,IAAI,WAAW;EAC9C,IAAI,CAAC,UACH,OAAO;EAGT,MAAM,QAAQ,YACZ,OAAO,GAAsB,KAC7B,KAAKI,WACP;EAEA,IAAI,IADc,IAAI,SAAS,GAC3B,EAAM,IAAI,KAAK,GAAG;GAEpB,MAAM,QAAQ,YAAY,OAAO,GAAkB,KAAK,KAAKA,WAAW;GACxE,IAAI,UAAU,OAAO;IACnB,MAAM,MAAM,SAAS,IAAI,KAAI,MAAM,MAAM,QAAQ,QAAQ,CAAE;IAC3D,KAAKJ,SAAS,IAAI,aAAa;KAAC,MAAM,SAAS;KAAM;IAAG,CAAC;GAC3D;GACA,OAAO,KAAKK,QAAQ,KAAK,QAAQ,IAAI;EACvC;EAEA,OAAO;CACT;CAEA,UAAgB;EACd,KAAKN,OAAO,QAAQ;EACpB,KAAKC,SAAS,QAAQ;CACxB;CAEA,CAAC,UAAU,SAAqC;EAC9C,IAAI,KAAKK,QAAQ,WACf,OAAO,KAAKA,QAAQ,UAAU,IAAI;CAEtC;AACF;AAEA,SAAS,eACP,cACA,iBACQ;CACR,MAAM,kBAA2B,CAAC;CAElC,IAAI,gBAAgB,iBAClB,KAAK,MAAM,OAAO,cAChB,gBAAgB,KAAK,gBAAgB,IAAI;CAI7C,OAAO,KAAK,UAAU,CAAC,OAAO,GAAG,eAAe,CAAC;AACnD;AAEA,SAAS,YAAY,KAAU,YAAgC;CAC7D,OAAO,KAAK,UAAU,WAAW,KAAI,MAAK,IAAI,EAAE,CAAC;AACnD;AAEA,SAAS,0BACP,IACA,YACY;CACZ,MAAM,SAAS,KAAK,MAAM,EAAE;CAC5B,MAAM,aAAoC,CAAC;CAC3C,KAAK,IAAI,IAAI,GAAG,IAAI,WAAW,QAAQ,KACrC,WAAW,WAAW,MAAM,OAAO;CAErC,OAAO;AACT"}
|
|
1
|
+
{"version":3,"file":"cap.js","names":["#input","#storage","#limit","#partitionKey","#partitionKeyComparator","#primaryKey","#output","#initialFetch","#pushEditChange"],"sources":["../../../../../zql/src/ivm/cap.ts"],"sourcesContent":["import {assert} from '../../../shared/src/asserts.ts';\nimport {emptyArray} from '../../../shared/src/sentinels.ts';\nimport type {Row, Value} from '../../../zero-protocol/src/data.ts';\nimport type {PrimaryKey} from '../../../zero-protocol/src/primary-key.ts';\nimport {ChangeIndex} from './change-index.ts';\nimport {ChangeType} from './change-type.ts';\nimport {makeAddChange, type Change, type EditChange} from './change.ts';\nimport type {Constraint} from './constraint.ts';\nimport type {Comparator, Node} from './data.ts';\nimport {\n throwOutput,\n type FetchRequest,\n type Input,\n type InputBase,\n type Operator,\n type Output,\n type Storage,\n} from './operator.ts';\nimport type {SourceSchema} from './schema.ts';\nimport {type Stream} from './stream.ts';\nimport {\n constraintMatchesPartitionKey,\n makePartitionKeyComparator,\n type PartitionKey,\n} from './take.ts';\n\ntype CapState = {\n size: number;\n pks: string[];\n};\n\ninterface CapStorage {\n get(key: string): CapState | undefined;\n set(key: string, value: CapState): void;\n del(key: string): void;\n destroy(): void;\n}\n\n/**\n * The Cap operator is a count-based limiter for EXISTS subqueries that\n * does not require ordering. Unlike Take, it tracks membership by primary\n * key set rather than by a sorted bound. This means:\n *\n * - No comparator needed (no ordering requirement)\n * - No `start` or `reverse` fetch support\n * - No `#rowHiddenFromFetch` complexity (we can defer when adding to the pk set)\n *\n * Cap is used in EXISTS child pipelines where only the count of matching\n * rows matters, not their order. This allows SQLite to skip ORDER BY\n * entirely, enabling much faster query plans.\n *\n * Cap can count rows globally or by unique value of some partition key\n * (same as Take).\n */\nexport class Cap implements Operator {\n readonly #input: Input;\n readonly #storage: CapStorage;\n readonly #limit: number;\n readonly #partitionKey: PartitionKey | undefined;\n readonly #partitionKeyComparator: Comparator | undefined;\n readonly #primaryKey: PrimaryKey;\n\n #output: Output = throwOutput;\n\n constructor(\n input: Input,\n storage: Storage,\n limit: number,\n partitionKey?: PartitionKey,\n ) {\n assert(limit >= 0, 'Limit must be non-negative');\n input.setOutput(this);\n this.#input = input;\n this.#storage = storage as CapStorage;\n this.#limit = limit;\n this.#partitionKey = partitionKey;\n this.#partitionKeyComparator =\n partitionKey && makePartitionKeyComparator(partitionKey);\n this.#primaryKey = input.getSchema().primaryKey;\n }\n\n setOutput(output: Output): void {\n this.#output = output;\n }\n\n getSchema(): SourceSchema {\n return this.#input.getSchema();\n }\n\n *fetch(req: FetchRequest): Stream<Node | 'yield'> {\n assert(!req.start, 'Cap does not support start');\n assert(!req.reverse, 'Cap does not support reverse');\n\n // Cap is only built for non-flipped EXISTS subqueries, whose only\n // downstream consumer is a Join that always fetches with a constraint\n // built from the correlation's childField — which is Cap's partition\n // key. So either partitionKey is undefined, or constraint matches.\n assert(\n !this.#partitionKey ||\n (req.constraint !== undefined &&\n constraintMatchesPartitionKey(req.constraint, this.#partitionKey)),\n 'Cap fetch: constraint must match partition key when partitioned',\n );\n\n const capStateKey = getCapStateKey(this.#partitionKey, req.constraint);\n const capState = this.#storage.get(capStateKey);\n if (!capState) {\n yield* this.#initialFetch(req, capStateKey);\n return;\n }\n if (capState.size === 0) {\n return;\n }\n // PK-based point lookups: fetch each tracked row by its PK directly,\n // rather than scanning the partition and filtering.\n for (const pk of capState.pks) {\n const pkConstraint = deserializePKToConstraint(pk, this.#primaryKey);\n // Preserve req.constraint (the partition key) so upstream partitioned\n // operators (e.g. Take) retain their partition scope and do not throw.\n const constraint = req.constraint\n ? {...req.constraint, ...pkConstraint}\n : pkConstraint;\n for (const inputNode of this.#input.fetch({constraint})) {\n if (inputNode === 'yield') {\n yield inputNode;\n continue;\n }\n yield inputNode;\n }\n }\n }\n\n *#initialFetch(\n req: FetchRequest,\n capStateKey: string,\n ): Stream<Node | 'yield'> {\n if (this.#limit === 0) {\n return;\n }\n\n assert(\n constraintMatchesPartitionKey(req.constraint, this.#partitionKey),\n 'Constraint should match partition key',\n );\n\n // The caller (fetch) has already checked that there is no state for\n // capStateKey.\n\n let size = 0;\n const pks: string[] = [];\n let downstreamEarlyReturn = true;\n let exceptionThrown = false;\n try {\n for (const inputNode of this.#input.fetch(req)) {\n if (inputNode === 'yield') {\n yield 'yield';\n continue;\n }\n yield inputNode;\n pks.push(serializePK(inputNode.row, this.#primaryKey));\n size++;\n if (size === this.#limit) {\n break;\n }\n }\n downstreamEarlyReturn = false;\n } catch (e) {\n exceptionThrown = true;\n throw e;\n } finally {\n if (!exceptionThrown) {\n this.#storage.set(capStateKey, {size, pks});\n // If it becomes necessary to support downstream early return, this\n // assert should be removed, and replaced with code that consumes\n // the input stream until limit is reached or the input stream is\n // exhausted so that capState is properly hydrated.\n assert(\n !downstreamEarlyReturn,\n 'Unexpected early return prevented full hydration',\n );\n }\n }\n }\n\n *push(change: Change): Stream<'yield'> {\n if (change[ChangeIndex.TYPE] === ChangeType.EDIT) {\n yield* this.#pushEditChange(change);\n return;\n }\n\n const capStateKey = getCapStateKey(\n this.#partitionKey,\n change[ChangeIndex.NODE].row,\n );\n const capState = this.#storage.get(capStateKey);\n if (!capState) {\n return;\n }\n\n const pk = serializePK(change[ChangeIndex.NODE].row, this.#primaryKey);\n\n if (change[ChangeIndex.TYPE] === ChangeType.ADD) {\n if (capState.size < this.#limit) {\n const pks = [...capState.pks, pk];\n this.#storage.set(capStateKey, {size: capState.size + 1, pks});\n yield* this.#output.push(change, this);\n return;\n }\n // Full — drop\n return;\n } else if (change[ChangeIndex.TYPE] === ChangeType.REMOVE) {\n const pkIndex = capState.pks.indexOf(pk);\n if (pkIndex === -1) {\n // Not in our set — drop\n return;\n }\n // Remove from set\n const pks = [...capState.pks];\n pks.splice(pkIndex, 1);\n const newSize = capState.size - 1;\n\n // Try to refill: fetch from input with partition constraint,\n // find first row NOT in PK set\n const pkSet = new Set(pks);\n const constraint = this.#partitionKey\n ? (Object.fromEntries(\n this.#partitionKey.map(\n key => [key, change[ChangeIndex.NODE].row[key]] as const,\n ),\n ) as Constraint)\n : undefined;\n\n let replacement: Node | undefined;\n for (const node of this.#input.fetch({constraint})) {\n if (node === 'yield') {\n yield node;\n continue;\n }\n const nodePK = serializePK(node.row, this.#primaryKey);\n if (!pkSet.has(nodePK)) {\n replacement = node;\n break;\n }\n }\n\n if (replacement) {\n // Store state WITHOUT replacement during remove forward,\n // matching Take's pattern of hiding in-flight changes from re-fetches.\n this.#storage.set(capStateKey, {size: newSize, pks});\n yield* this.#output.push(change, this);\n // Now add replacement to set and forward the add.\n const replacementPK = serializePK(replacement.row, this.#primaryKey);\n pks.push(replacementPK);\n this.#storage.set(capStateKey, {size: newSize + 1, pks});\n yield* this.#output.push(makeAddChange(replacement), this);\n } else {\n this.#storage.set(capStateKey, {size: newSize, pks});\n yield* this.#output.push(change, this);\n }\n } else if (change[ChangeIndex.TYPE] === ChangeType.CHILD) {\n const pkSet = new Set(capState.pks);\n if (pkSet.has(pk)) {\n yield* this.#output.push(change, this);\n }\n }\n }\n\n #pushEditChange(change: EditChange): Stream<'yield'> {\n assert(\n !this.#partitionKeyComparator ||\n this.#partitionKeyComparator(\n change[ChangeIndex.OLD_NODE].row,\n change[ChangeIndex.NODE].row,\n ) === 0,\n 'Unexpected change of partition key',\n );\n const capStateKey = getCapStateKey(\n this.#partitionKey,\n change[ChangeIndex.OLD_NODE].row,\n );\n const capState = this.#storage.get(capStateKey);\n if (!capState) {\n return emptyArray;\n }\n\n const oldPK = serializePK(\n change[ChangeIndex.OLD_NODE].row,\n this.#primaryKey,\n );\n const pkSet = new Set(capState.pks);\n if (pkSet.has(oldPK)) {\n // Update the PK in our set if it changed\n const newPK = serializePK(change[ChangeIndex.NODE].row, this.#primaryKey);\n if (oldPK !== newPK) {\n const pks = capState.pks.map(p => (p === oldPK ? newPK : p));\n this.#storage.set(capStateKey, {size: capState.size, pks});\n }\n return this.#output.push(change, this);\n }\n // If not in our set, drop\n return emptyArray;\n }\n\n destroy(): void {\n this.#input.destroy();\n this.#storage.destroy();\n }\n\n *reconcile(_pusher: InputBase): Stream<'yield'> {\n if (this.#output.reconcile) {\n yield* this.#output.reconcile(this);\n }\n }\n}\n\nfunction getCapStateKey(\n partitionKey: PartitionKey | undefined,\n rowOrConstraint: Row | Constraint | undefined,\n): string {\n const partitionValues: Value[] = [];\n\n if (partitionKey && rowOrConstraint) {\n for (const key of partitionKey) {\n partitionValues.push(rowOrConstraint[key]);\n }\n }\n\n return JSON.stringify(['cap', ...partitionValues]);\n}\n\nfunction serializePK(row: Row, primaryKey: PrimaryKey): string {\n return JSON.stringify(primaryKey.map(k => row[k]));\n}\n\nfunction deserializePKToConstraint(\n pk: string,\n primaryKey: PrimaryKey,\n): Constraint {\n const values = JSON.parse(pk) as Value[];\n const constraint: Record<string, Value> = {};\n for (let i = 0; i < primaryKey.length; i++) {\n constraint[primaryKey[i]] = values[i];\n }\n return constraint;\n}\n"],"mappings":"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"}
|
package/out/zql/src/ivm/take.js
CHANGED
|
@@ -67,7 +67,7 @@ var Take = class {
|
|
|
67
67
|
const takeStateKey = getTakeStateKey(this.#partitionKey, req.constraint);
|
|
68
68
|
const takeState = this.#storage.get(takeStateKey);
|
|
69
69
|
if (!takeState) {
|
|
70
|
-
if (constraintMatchesPartitionKey(req.constraint, this.#partitionKey)) yield* this.#initialFetch(req);
|
|
70
|
+
if (constraintMatchesPartitionKey(req.constraint, this.#partitionKey)) yield* this.#initialFetch(req, takeStateKey);
|
|
71
71
|
return;
|
|
72
72
|
}
|
|
73
73
|
if (takeState.bound === void 0) return;
|
|
@@ -84,13 +84,11 @@ var Take = class {
|
|
|
84
84
|
if (count >= this.#limit) return;
|
|
85
85
|
}
|
|
86
86
|
}
|
|
87
|
-
*#initialFetch(req) {
|
|
87
|
+
*#initialFetch(req, takeStateKey) {
|
|
88
88
|
assert(req.start === void 0, "Start should be undefined");
|
|
89
89
|
assert(!req.reverse, "Reverse should be false");
|
|
90
90
|
if (this.#limit === 0) return;
|
|
91
91
|
assert(constraintMatchesPartitionKey(req.constraint, this.#partitionKey), "Constraint should match partition key");
|
|
92
|
-
const takeStateKey = getTakeStateKey(this.#partitionKey, req.constraint);
|
|
93
|
-
assert(this.#storage.get(takeStateKey) === void 0, "Take state should be undefined");
|
|
94
92
|
let size = 0;
|
|
95
93
|
let bound;
|
|
96
94
|
let downstreamEarlyReturn = true;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"take.js","names":["#input","#storage","#limit","#partitionKey","#partitionKeyComparator","#dirtyPartitions","#takeGate","#output","#initialFetch","#rowHiddenFromFetch","#setTakeState","#pushEditChange","#getStateAndConstraint","#pushWithRowHiddenFromFetch"],"sources":["../../../../../zql/src/ivm/take.ts"],"sourcesContent":["import {assert, unreachable} from '../../../shared/src/asserts.ts';\nimport {hasOwn} from '../../../shared/src/has-own.ts';\nimport type {Row, Value} from '../../../zero-protocol/src/data.ts';\nimport type {PrimaryKey} from '../../../zero-protocol/src/primary-key.ts';\nimport {assertOrderingIncludesPK} from '../query/complete-ordering.ts';\nimport {ChangeIndex} from './change-index.ts';\nimport {ChangeType} from './change-type.ts';\nimport {\n makeAddChange,\n makeRemoveChange,\n type Change,\n type EditChange,\n} from './change.ts';\nimport type {Constraint} from './constraint.ts';\nimport {compareValues, type Comparator, type Node} from './data.ts';\nimport {\n throwOutput,\n type FetchRequest,\n type Input,\n type InputBase,\n type Operator,\n type Output,\n type Storage,\n} from './operator.ts';\nimport type {SourceSchema} from './schema.ts';\nimport {type Stream} from './stream.ts';\nimport type {TakeBoundProvider, TakeGate} from './take-gate.ts';\n\ntype TakeState = {\n size: number;\n bound: Row | undefined;\n};\n\ninterface TakeStorage {\n get(key: string): TakeState | undefined;\n set(key: string, value: TakeState): void;\n del(key: string): void;\n destroy(): void;\n}\n\nexport type PartitionKey = PrimaryKey;\n\ntype DirtyPartitionState = {\n constraint: Constraint | undefined;\n /**\n * The bound to report to upstream TakeGate via getBound() until Phase 2\n * reconciliation completes.\n *\n * When removals reduce partition size below limit in Phase 1, normal getBound()\n * would return undefined (unbounded), which would cause upstream TakeGate to open\n * and allow unbounded parent pushes through. Preserving gateBound keeps TakeGate\n * capped at the pre-removal boundary until Phase 2 refills the deficit.\n */\n gateBound: Row | undefined;\n};\n\n/**\n * The Take operator is for implementing limit queries. It takes the first n\n * nodes of its input as determined by the input’s comparator. It then keeps\n * a *bound* of the last item it has accepted so that it can evaluate whether\n * new incoming pushes should be accepted or rejected.\n *\n * Take can count rows globally or by unique value of some field.\n *\n * Maintains the invariant that its output size is always <= limit, even\n * mid processing of a push.\n */\nexport class Take implements Operator, TakeBoundProvider {\n readonly #input: Input;\n readonly #storage: TakeStorage;\n readonly #limit: number;\n readonly #partitionKey: PartitionKey | undefined;\n readonly #partitionKeyComparator: Comparator | undefined;\n // Fetch overlay needed for some split push cases.\n #rowHiddenFromFetch: Row | undefined;\n\n #takeGate: TakeGate | undefined;\n /**\n * Partitions that incurred removals in Phase 1 and require deficit refills in\n * Phase 2 (reconcile). Keyed by `takeStateKey` (see `getTakeStateKey`), matching\n * `#storage` keys: `'[\"take\"]'` when unpartitioned, or `'[\"take\", ...partitionValues]'`\n * when partitioned.\n */\n readonly #dirtyPartitions = new Map<string, DirtyPartitionState>();\n\n #output: Output = throwOutput;\n\n setTakeGate(gate: TakeGate): void {\n this.#takeGate = gate;\n }\n\n constructor(\n input: Input,\n storage: Storage,\n limit: number,\n partitionKey?: PartitionKey,\n ) {\n assert(limit >= 0, 'Limit must be non-negative');\n const {sort} = input.getSchema();\n assert(sort !== undefined, 'Take requires sorted input');\n assertOrderingIncludesPK(sort, input.getSchema().primaryKey);\n input.setOutput(this);\n this.#input = input;\n this.#storage = storage as TakeStorage;\n this.#limit = limit;\n this.#partitionKey = partitionKey;\n this.#partitionKeyComparator =\n partitionKey && makePartitionKeyComparator(partitionKey);\n }\n\n setOutput(output: Output): void {\n this.#output = output;\n }\n\n getSchema(): SourceSchema {\n return this.#input.getSchema();\n }\n\n getBound(constraint?: Constraint): Row | undefined {\n if (\n this.#partitionKey &&\n !constraintContainsPartitionKey(constraint, this.#partitionKey)\n ) {\n return undefined;\n }\n const takeStateKey = getTakeStateKey(this.#partitionKey, constraint);\n const dirty = this.#dirtyPartitions.get(takeStateKey);\n if (dirty) {\n // While dirty in Phase 1, return gateBound (the bound prior to removals) so\n // upstream operators (e.g. TakeGate) remain capped rather than unbounding during push.\n return dirty.gateBound;\n }\n const takeState = this.#storage.get(takeStateKey);\n if (!takeState || takeState.size < this.#limit) {\n return undefined;\n }\n return takeState.bound;\n }\n\n *fetch(req: FetchRequest): Stream<Node | 'yield'> {\n assert(\n !this.#partitionKey ||\n (req.constraint !== undefined &&\n constraintContainsPartitionKey(req.constraint, this.#partitionKey)),\n 'Partitioned take does not allow unpartitioned fetches',\n );\n\n const takeStateKey = getTakeStateKey(this.#partitionKey, req.constraint);\n const takeState = this.#storage.get(takeStateKey);\n if (!takeState) {\n if (constraintMatchesPartitionKey(req.constraint, this.#partitionKey)) {\n yield* this.#initialFetch(req);\n }\n return;\n }\n if (takeState.bound === undefined) {\n return;\n }\n let count = 0;\n for (const inputNode of this.#input.fetch(req)) {\n if (inputNode === 'yield') {\n yield inputNode;\n continue;\n }\n if (this.getSchema().compareRows(takeState.bound, inputNode.row) < 0) {\n return;\n }\n if (\n this.#rowHiddenFromFetch &&\n this.getSchema().compareRows(\n this.#rowHiddenFromFetch,\n inputNode.row,\n ) === 0\n ) {\n continue;\n }\n yield inputNode;\n count++;\n if (count >= this.#limit) {\n return;\n }\n }\n }\n\n *#initialFetch(req: FetchRequest): Stream<Node | 'yield'> {\n assert(req.start === undefined, 'Start should be undefined');\n assert(!req.reverse, 'Reverse should be false');\n\n if (this.#limit === 0) {\n return;\n }\n\n assert(\n constraintMatchesPartitionKey(req.constraint, this.#partitionKey),\n 'Constraint should match partition key',\n );\n\n const takeStateKey = getTakeStateKey(this.#partitionKey, req.constraint);\n assert(\n this.#storage.get(takeStateKey) === undefined,\n 'Take state should be undefined',\n );\n\n let size = 0;\n let bound: Row | undefined;\n let downstreamEarlyReturn = true;\n let exceptionThrown = false;\n try {\n for (const inputNode of this.#input.fetch(req)) {\n if (inputNode === 'yield') {\n yield 'yield';\n continue;\n }\n yield inputNode;\n bound = inputNode.row;\n size++;\n if (size === this.#limit) {\n break;\n }\n }\n downstreamEarlyReturn = false;\n } catch (e) {\n exceptionThrown = true;\n throw e;\n } finally {\n if (!exceptionThrown) {\n this.#setTakeState(takeStateKey, size, bound);\n // If it becomes necessary to support downstream early return, this\n // assert should be removed, and replaced with code that consumes\n // the input stream until limit is reached or the input stream is\n // exhausted so that takeState is properly hydrated.\n assert(\n !downstreamEarlyReturn,\n 'Unexpected early return prevented full hydration',\n );\n }\n }\n }\n\n #getStateAndConstraint(row: Row) {\n const takeStateKey = getTakeStateKey(this.#partitionKey, row);\n const takeState = this.#storage.get(takeStateKey);\n let constraint: Constraint | undefined;\n if (takeState) {\n constraint =\n this.#partitionKey &&\n Object.fromEntries(\n this.#partitionKey.map(key => [key, row[key]] as const),\n );\n }\n\n return {takeState, takeStateKey, constraint} as\n | {\n takeState: undefined;\n takeStateKey: string;\n constraint: undefined;\n }\n | {\n takeState: TakeState;\n takeStateKey: string;\n constraint: Constraint | undefined;\n };\n }\n\n *push(change: Change): Stream<'yield'> {\n if (change[ChangeIndex.TYPE] === ChangeType.EDIT) {\n yield* this.#pushEditChange(change);\n return;\n }\n\n const {takeState, takeStateKey, constraint} = this.#getStateAndConstraint(\n change[ChangeIndex.NODE].row,\n );\n if (!takeState) {\n return;\n }\n\n const {compareRows} = this.getSchema();\n\n if (change[ChangeIndex.TYPE] === ChangeType.ADD) {\n if (takeState.size < this.#limit) {\n if (this.#dirtyPartitions.has(takeStateKey)) {\n // While dirty with an unresolved deficit in Phase 1, only admit rows that\n // sort strictly before the current window boundary. We refuse to expand the\n // bound forward during Phase 1 so that Phase 2 can backfill earlier candidate\n // rows from storage in sort order.\n if (\n takeState.bound === undefined ||\n compareRows(change[ChangeIndex.NODE].row, takeState.bound) >= 0\n ) {\n return;\n }\n }\n const nextSize = takeState.size + 1;\n if (nextSize === this.#limit) {\n this.#dirtyPartitions.delete(takeStateKey);\n }\n this.#setTakeState(\n takeStateKey,\n nextSize,\n takeState.bound === undefined ||\n compareRows(takeState.bound, change[ChangeIndex.NODE].row) < 0\n ? change[ChangeIndex.NODE].row\n : takeState.bound,\n );\n yield* this.#output.push(change, this);\n return;\n }\n // size === limit\n if (\n takeState.bound === undefined ||\n compareRows(change[ChangeIndex.NODE].row, takeState.bound) >= 0\n ) {\n return;\n }\n // added row < bound\n let beforeBoundNode: Node | undefined;\n let boundNode: Node | undefined;\n if (this.#limit === 1) {\n for (const node of this.#input.fetch({\n start: {\n row: takeState.bound,\n basis: 'at',\n },\n constraint,\n })) {\n if (node === 'yield') {\n yield node;\n continue;\n }\n boundNode = node;\n break;\n }\n } else {\n for (const node of this.#input.fetch({\n start: {\n row: takeState.bound,\n basis: 'at',\n },\n constraint,\n reverse: true,\n })) {\n if (node === 'yield') {\n yield node;\n continue;\n } else if (boundNode === undefined) {\n boundNode = node;\n } else {\n beforeBoundNode = node;\n break;\n }\n }\n }\n assert(\n boundNode !== undefined,\n 'Take: boundNode must be found during fetch',\n );\n const removeChange = makeRemoveChange(boundNode);\n // Remove before add to maintain invariant that\n // output size <= limit.\n this.#setTakeState(\n takeStateKey,\n takeState.size,\n beforeBoundNode === undefined ||\n compareRows(change[ChangeIndex.NODE].row, beforeBoundNode.row) > 0\n ? change[ChangeIndex.NODE].row\n : beforeBoundNode.row,\n );\n yield* this.#pushWithRowHiddenFromFetch(\n change[ChangeIndex.NODE].row,\n removeChange,\n );\n yield* this.#output.push(change, this);\n } else if (change[ChangeIndex.TYPE] === ChangeType.REMOVE) {\n if (takeState.bound === undefined) {\n // change is after bound\n return;\n }\n const compToBound = compareRows(\n change[ChangeIndex.NODE].row,\n takeState.bound,\n );\n if (compToBound > 0 || (compToBound < 0 && this.#limit === 1)) {\n // change is not in window\n return;\n }\n let beforeBoundNode: Node | undefined;\n for (const node of this.#input.fetch({\n start: {\n row: takeState.bound,\n basis: 'after',\n },\n constraint,\n reverse: true,\n })) {\n if (node === 'yield') {\n yield node;\n continue;\n }\n beforeBoundNode = node;\n break;\n }\n\n const finalBound =\n takeState.size - 1 === 0\n ? undefined\n : compToBound < 0\n ? takeState.bound\n : beforeBoundNode?.row;\n if (!this.#dirtyPartitions.has(takeStateKey)) {\n // Snapshot the pre-removal bound as the gate bound on the first removal that\n // dirties this partition. Subsequent getBound() calls throughout Phase 1 will return\n // this bound to keep upstream TakeGate bounded until Phase 2 refills deficits.\n this.#dirtyPartitions.set(takeStateKey, {\n constraint,\n gateBound: takeState.bound,\n });\n }\n this.#setTakeState(takeStateKey, takeState.size - 1, finalBound);\n yield* this.#output.push(change, this);\n } else if (change[ChangeIndex.TYPE] === ChangeType.CHILD) {\n // A 'child' change should be pushed to output if its row\n // is <= bound.\n if (\n takeState.bound &&\n compareRows(change[ChangeIndex.NODE].row, takeState.bound) <= 0\n ) {\n yield* this.#output.push(change, this);\n }\n }\n }\n\n *#pushEditChange(change: EditChange): Stream<'yield'> {\n assert(\n !this.#partitionKeyComparator ||\n this.#partitionKeyComparator(\n change[ChangeIndex.OLD_NODE].row,\n change[ChangeIndex.NODE].row,\n ) === 0,\n 'Unexpected change of partition key',\n );\n\n const {takeState, takeStateKey, constraint} = this.#getStateAndConstraint(\n change[ChangeIndex.OLD_NODE].row,\n );\n if (!takeState) {\n return;\n }\n\n assert(takeState.bound, 'Bound should be set');\n const {compareRows} = this.getSchema();\n const oldCmp = compareRows(\n change[ChangeIndex.OLD_NODE].row,\n takeState.bound,\n );\n const newCmp = compareRows(change[ChangeIndex.NODE].row, takeState.bound);\n\n const replaceBoundAndForwardChange = () => {\n this.#setTakeState(\n takeStateKey,\n takeState.size,\n change[ChangeIndex.NODE].row,\n );\n return this.#output.push(change, this);\n };\n\n // The bounds row was changed.\n if (oldCmp === 0) {\n // The new row is the new bound.\n if (newCmp === 0) {\n // no need to update the state since we are keeping the bounds\n yield* this.#output.push(change, this);\n return;\n }\n\n if (newCmp < 0) {\n if (this.#limit === 1) {\n yield* replaceBoundAndForwardChange();\n return;\n }\n\n // New row will be in the result but it might not be the bounds any\n // more. We need to find the row before the bounds to determine the new\n // bounds.\n\n let beforeBoundNode: Node | undefined;\n for (const node of this.#input.fetch({\n start: {\n row: takeState.bound,\n basis: 'after',\n },\n constraint,\n reverse: true,\n })) {\n if (node === 'yield') {\n yield node;\n continue;\n }\n beforeBoundNode = node;\n break;\n }\n assert(\n beforeBoundNode !== undefined,\n 'Take: beforeBoundNode must be found during fetch',\n );\n\n this.#setTakeState(takeStateKey, takeState.size, beforeBoundNode.row);\n yield* this.#output.push(change, this);\n return;\n }\n\n assert(newCmp > 0, 'New comparison must be greater than 0');\n // Find the first item at the old bounds. This will be the new bounds.\n let newBoundNode: Node | undefined;\n this.#takeGate?.open();\n try {\n for (const node of this.#input.fetch({\n start: {\n row: takeState.bound,\n basis: 'at',\n },\n constraint,\n })) {\n if (node === 'yield') {\n yield node;\n continue;\n }\n newBoundNode = node;\n break;\n }\n } finally {\n this.#takeGate?.close();\n }\n assert(\n newBoundNode !== undefined,\n 'Take: newBoundNode must be found during fetch',\n );\n\n // The next row is the new row. We can replace the bounds and keep the\n // edit change.\n if (compareRows(newBoundNode.row, change[ChangeIndex.NODE].row) === 0) {\n yield* replaceBoundAndForwardChange();\n return;\n }\n\n // The new row is now outside the bounds, so we need to remove the old\n // row and add the new bounds row.\n this.#setTakeState(takeStateKey, takeState.size, newBoundNode.row);\n yield* this.#pushWithRowHiddenFromFetch(\n newBoundNode.row,\n makeRemoveChange(change[ChangeIndex.OLD_NODE]),\n );\n yield* this.#output.push(makeAddChange(newBoundNode), this);\n return;\n }\n\n if (oldCmp > 0) {\n assert(newCmp !== 0, 'Invalid state. Row has duplicate primary key');\n\n // Both old and new outside of bounds\n if (newCmp > 0) {\n return;\n }\n\n // old was outside, new is inside. Pushing out the old bounds\n assert(newCmp < 0, 'New comparison must be less than 0');\n\n let oldBoundNode: Node | undefined;\n let newBoundNode: Node | undefined;\n for (const node of this.#input.fetch({\n start: {\n row: takeState.bound,\n basis: 'at',\n },\n constraint,\n reverse: true,\n })) {\n if (node === 'yield') {\n yield node;\n continue;\n } else if (oldBoundNode === undefined) {\n oldBoundNode = node;\n } else {\n newBoundNode = node;\n break;\n }\n }\n assert(\n oldBoundNode !== undefined,\n 'Take: oldBoundNode must be found during fetch',\n );\n assert(\n newBoundNode !== undefined,\n 'Take: newBoundNode must be found during fetch',\n );\n\n // Remove before add to maintain invariant that\n // output size <= limit.\n this.#setTakeState(takeStateKey, takeState.size, newBoundNode.row);\n yield* this.#pushWithRowHiddenFromFetch(\n change[ChangeIndex.NODE].row,\n makeRemoveChange(oldBoundNode),\n );\n yield* this.#output.push(makeAddChange(change[ChangeIndex.NODE]), this);\n\n return;\n }\n\n if (oldCmp < 0) {\n assert(newCmp !== 0, 'Invalid state. Row has duplicate primary key');\n\n // Both old and new inside of bounds\n if (newCmp < 0) {\n yield* this.#output.push(change, this);\n return;\n }\n\n // old was inside, new is larger than old bound\n\n assert(newCmp > 0, 'New comparison must be greater than 0');\n\n // at this point we need to find the row after the bound and use that or\n // the newRow as the new bound.\n let afterBoundNode: Node | undefined;\n this.#takeGate?.open();\n try {\n for (const node of this.#input.fetch({\n start: {\n row: takeState.bound,\n basis: 'after',\n },\n constraint,\n })) {\n if (node === 'yield') {\n yield node;\n continue;\n }\n afterBoundNode = node;\n break;\n }\n } finally {\n this.#takeGate?.close();\n }\n assert(\n afterBoundNode !== undefined,\n 'Take: afterBoundNode must be found during fetch',\n );\n\n // The new row is the new bound. Use an edit change.\n if (compareRows(afterBoundNode.row, change[ChangeIndex.NODE].row) === 0) {\n yield* replaceBoundAndForwardChange();\n return;\n }\n\n yield* this.#output.push(\n makeRemoveChange(change[ChangeIndex.OLD_NODE]),\n this,\n );\n this.#setTakeState(takeStateKey, takeState.size, afterBoundNode.row);\n yield* this.#output.push(makeAddChange(afterBoundNode), this);\n return;\n }\n\n unreachable();\n }\n\n *#pushWithRowHiddenFromFetch(row: Row, change: Change) {\n this.#rowHiddenFromFetch = row;\n try {\n yield* this.#output.push(change, this);\n } finally {\n this.#rowHiddenFromFetch = undefined;\n }\n }\n\n #setTakeState(takeStateKey: string, size: number, bound: Row | undefined) {\n this.#storage.set(takeStateKey, {\n size,\n bound,\n });\n }\n\n destroy(): void {\n this.#input.destroy();\n this.#storage.destroy();\n }\n\n *reconcile(_pusher?: InputBase): Stream<'yield'> {\n if (this.#dirtyPartitions.size > 0) {\n const dirty = [...this.#dirtyPartitions.entries()];\n this.#dirtyPartitions.clear();\n\n for (const [takeStateKey, {constraint}] of dirty) {\n const takeState = this.#storage.get(takeStateKey);\n assert(\n takeState !== undefined,\n 'Take: dirty partition must exist in storage',\n );\n const deficit = this.#limit - takeState.size;\n assert(deficit > 0, 'Take: dirty partition must have a deficit');\n\n const toPush: Node[] = [];\n this.#takeGate?.open();\n try {\n const stream = this.#input.fetch({\n start: takeState.bound\n ? {\n row: takeState.bound,\n basis: 'after',\n }\n : undefined,\n constraint,\n });\n\n for (const node of stream) {\n if (node === 'yield') {\n yield 'yield';\n continue;\n }\n toPush.push(node);\n if (toPush.length === deficit) {\n break;\n }\n }\n } finally {\n this.#takeGate?.close();\n }\n\n let currentSize = takeState.size;\n for (const node of toPush) {\n currentSize++;\n this.#setTakeState(takeStateKey, currentSize, node.row);\n yield* this.#output.push(makeAddChange(node), this);\n }\n }\n }\n\n if (this.#output.reconcile) {\n yield* this.#output.reconcile(this);\n }\n }\n}\n\nfunction getTakeStateKey(\n partitionKey: PartitionKey | undefined,\n rowOrConstraint: Row | Constraint | undefined,\n): string {\n // The order must be consistent. We always use the order as defined by the\n // partition key.\n const partitionValues: Value[] = [];\n\n if (partitionKey && rowOrConstraint) {\n for (const key of partitionKey) {\n partitionValues.push(rowOrConstraint[key]);\n }\n }\n\n return JSON.stringify(['take', ...partitionValues]);\n}\n\nexport function constraintMatchesPartitionKey(\n constraint: Constraint | undefined,\n partitionKey: PartitionKey | undefined,\n): boolean {\n if (constraint === undefined || partitionKey === undefined) {\n return constraint === partitionKey;\n }\n if (partitionKey.length !== Object.keys(constraint).length) {\n return false;\n }\n for (const key of partitionKey) {\n if (!hasOwn(constraint, key)) {\n return false;\n }\n }\n return true;\n}\n\nexport function constraintContainsPartitionKey(\n constraint: Constraint | undefined,\n partitionKey: PartitionKey | undefined,\n): boolean {\n if (constraint === undefined || partitionKey === undefined) {\n return false;\n }\n for (const key of partitionKey) {\n if (!hasOwn(constraint, key)) {\n return false;\n }\n }\n return true;\n}\n\nexport function makePartitionKeyComparator(\n partitionKey: PartitionKey,\n): Comparator {\n return (a, b) => {\n for (const key of partitionKey) {\n const cmp = compareValues(a[key], b[key]);\n if (cmp !== 0) {\n return cmp;\n }\n }\n return 0;\n };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"take.js","names":["#input","#storage","#limit","#partitionKey","#partitionKeyComparator","#dirtyPartitions","#takeGate","#output","#initialFetch","#rowHiddenFromFetch","#setTakeState","#pushEditChange","#getStateAndConstraint","#pushWithRowHiddenFromFetch"],"sources":["../../../../../zql/src/ivm/take.ts"],"sourcesContent":["import {assert, unreachable} from '../../../shared/src/asserts.ts';\nimport {hasOwn} from '../../../shared/src/has-own.ts';\nimport type {Row, Value} from '../../../zero-protocol/src/data.ts';\nimport type {PrimaryKey} from '../../../zero-protocol/src/primary-key.ts';\nimport {assertOrderingIncludesPK} from '../query/complete-ordering.ts';\nimport {ChangeIndex} from './change-index.ts';\nimport {ChangeType} from './change-type.ts';\nimport {\n makeAddChange,\n makeRemoveChange,\n type Change,\n type EditChange,\n} from './change.ts';\nimport type {Constraint} from './constraint.ts';\nimport {compareValues, type Comparator, type Node} from './data.ts';\nimport {\n throwOutput,\n type FetchRequest,\n type Input,\n type InputBase,\n type Operator,\n type Output,\n type Storage,\n} from './operator.ts';\nimport type {SourceSchema} from './schema.ts';\nimport {type Stream} from './stream.ts';\nimport type {TakeBoundProvider, TakeGate} from './take-gate.ts';\n\ntype TakeState = {\n size: number;\n bound: Row | undefined;\n};\n\ninterface TakeStorage {\n get(key: string): TakeState | undefined;\n set(key: string, value: TakeState): void;\n del(key: string): void;\n destroy(): void;\n}\n\nexport type PartitionKey = PrimaryKey;\n\ntype DirtyPartitionState = {\n constraint: Constraint | undefined;\n /**\n * The bound to report to upstream TakeGate via getBound() until Phase 2\n * reconciliation completes.\n *\n * When removals reduce partition size below limit in Phase 1, normal getBound()\n * would return undefined (unbounded), which would cause upstream TakeGate to open\n * and allow unbounded parent pushes through. Preserving gateBound keeps TakeGate\n * capped at the pre-removal boundary until Phase 2 refills the deficit.\n */\n gateBound: Row | undefined;\n};\n\n/**\n * The Take operator is for implementing limit queries. It takes the first n\n * nodes of its input as determined by the input’s comparator. It then keeps\n * a *bound* of the last item it has accepted so that it can evaluate whether\n * new incoming pushes should be accepted or rejected.\n *\n * Take can count rows globally or by unique value of some field.\n *\n * Maintains the invariant that its output size is always <= limit, even\n * mid processing of a push.\n */\nexport class Take implements Operator, TakeBoundProvider {\n readonly #input: Input;\n readonly #storage: TakeStorage;\n readonly #limit: number;\n readonly #partitionKey: PartitionKey | undefined;\n readonly #partitionKeyComparator: Comparator | undefined;\n // Fetch overlay needed for some split push cases.\n #rowHiddenFromFetch: Row | undefined;\n\n #takeGate: TakeGate | undefined;\n /**\n * Partitions that incurred removals in Phase 1 and require deficit refills in\n * Phase 2 (reconcile). Keyed by `takeStateKey` (see `getTakeStateKey`), matching\n * `#storage` keys: `'[\"take\"]'` when unpartitioned, or `'[\"take\", ...partitionValues]'`\n * when partitioned.\n */\n readonly #dirtyPartitions = new Map<string, DirtyPartitionState>();\n\n #output: Output = throwOutput;\n\n setTakeGate(gate: TakeGate): void {\n this.#takeGate = gate;\n }\n\n constructor(\n input: Input,\n storage: Storage,\n limit: number,\n partitionKey?: PartitionKey,\n ) {\n assert(limit >= 0, 'Limit must be non-negative');\n const {sort} = input.getSchema();\n assert(sort !== undefined, 'Take requires sorted input');\n assertOrderingIncludesPK(sort, input.getSchema().primaryKey);\n input.setOutput(this);\n this.#input = input;\n this.#storage = storage as TakeStorage;\n this.#limit = limit;\n this.#partitionKey = partitionKey;\n this.#partitionKeyComparator =\n partitionKey && makePartitionKeyComparator(partitionKey);\n }\n\n setOutput(output: Output): void {\n this.#output = output;\n }\n\n getSchema(): SourceSchema {\n return this.#input.getSchema();\n }\n\n getBound(constraint?: Constraint): Row | undefined {\n if (\n this.#partitionKey &&\n !constraintContainsPartitionKey(constraint, this.#partitionKey)\n ) {\n return undefined;\n }\n const takeStateKey = getTakeStateKey(this.#partitionKey, constraint);\n const dirty = this.#dirtyPartitions.get(takeStateKey);\n if (dirty) {\n // While dirty in Phase 1, return gateBound (the bound prior to removals) so\n // upstream operators (e.g. TakeGate) remain capped rather than unbounding during push.\n return dirty.gateBound;\n }\n const takeState = this.#storage.get(takeStateKey);\n if (!takeState || takeState.size < this.#limit) {\n return undefined;\n }\n return takeState.bound;\n }\n\n *fetch(req: FetchRequest): Stream<Node | 'yield'> {\n assert(\n !this.#partitionKey ||\n (req.constraint !== undefined &&\n constraintContainsPartitionKey(req.constraint, this.#partitionKey)),\n 'Partitioned take does not allow unpartitioned fetches',\n );\n\n const takeStateKey = getTakeStateKey(this.#partitionKey, req.constraint);\n const takeState = this.#storage.get(takeStateKey);\n if (!takeState) {\n if (constraintMatchesPartitionKey(req.constraint, this.#partitionKey)) {\n yield* this.#initialFetch(req, takeStateKey);\n }\n return;\n }\n if (takeState.bound === undefined) {\n return;\n }\n let count = 0;\n for (const inputNode of this.#input.fetch(req)) {\n if (inputNode === 'yield') {\n yield inputNode;\n continue;\n }\n if (this.getSchema().compareRows(takeState.bound, inputNode.row) < 0) {\n return;\n }\n if (\n this.#rowHiddenFromFetch &&\n this.getSchema().compareRows(\n this.#rowHiddenFromFetch,\n inputNode.row,\n ) === 0\n ) {\n continue;\n }\n yield inputNode;\n count++;\n if (count >= this.#limit) {\n return;\n }\n }\n }\n\n *#initialFetch(\n req: FetchRequest,\n takeStateKey: string,\n ): Stream<Node | 'yield'> {\n assert(req.start === undefined, 'Start should be undefined');\n assert(!req.reverse, 'Reverse should be false');\n\n if (this.#limit === 0) {\n return;\n }\n\n assert(\n constraintMatchesPartitionKey(req.constraint, this.#partitionKey),\n 'Constraint should match partition key',\n );\n\n // The caller (fetch) has already checked that there is no state for\n // takeStateKey.\n\n let size = 0;\n let bound: Row | undefined;\n let downstreamEarlyReturn = true;\n let exceptionThrown = false;\n try {\n for (const inputNode of this.#input.fetch(req)) {\n if (inputNode === 'yield') {\n yield 'yield';\n continue;\n }\n yield inputNode;\n bound = inputNode.row;\n size++;\n if (size === this.#limit) {\n break;\n }\n }\n downstreamEarlyReturn = false;\n } catch (e) {\n exceptionThrown = true;\n throw e;\n } finally {\n if (!exceptionThrown) {\n this.#setTakeState(takeStateKey, size, bound);\n // If it becomes necessary to support downstream early return, this\n // assert should be removed, and replaced with code that consumes\n // the input stream until limit is reached or the input stream is\n // exhausted so that takeState is properly hydrated.\n assert(\n !downstreamEarlyReturn,\n 'Unexpected early return prevented full hydration',\n );\n }\n }\n }\n\n #getStateAndConstraint(row: Row) {\n const takeStateKey = getTakeStateKey(this.#partitionKey, row);\n const takeState = this.#storage.get(takeStateKey);\n let constraint: Constraint | undefined;\n if (takeState) {\n constraint =\n this.#partitionKey &&\n Object.fromEntries(\n this.#partitionKey.map(key => [key, row[key]] as const),\n );\n }\n\n return {takeState, takeStateKey, constraint} as\n | {\n takeState: undefined;\n takeStateKey: string;\n constraint: undefined;\n }\n | {\n takeState: TakeState;\n takeStateKey: string;\n constraint: Constraint | undefined;\n };\n }\n\n *push(change: Change): Stream<'yield'> {\n if (change[ChangeIndex.TYPE] === ChangeType.EDIT) {\n yield* this.#pushEditChange(change);\n return;\n }\n\n const {takeState, takeStateKey, constraint} = this.#getStateAndConstraint(\n change[ChangeIndex.NODE].row,\n );\n if (!takeState) {\n return;\n }\n\n const {compareRows} = this.getSchema();\n\n if (change[ChangeIndex.TYPE] === ChangeType.ADD) {\n if (takeState.size < this.#limit) {\n if (this.#dirtyPartitions.has(takeStateKey)) {\n // While dirty with an unresolved deficit in Phase 1, only admit rows that\n // sort strictly before the current window boundary. We refuse to expand the\n // bound forward during Phase 1 so that Phase 2 can backfill earlier candidate\n // rows from storage in sort order.\n if (\n takeState.bound === undefined ||\n compareRows(change[ChangeIndex.NODE].row, takeState.bound) >= 0\n ) {\n return;\n }\n }\n const nextSize = takeState.size + 1;\n if (nextSize === this.#limit) {\n this.#dirtyPartitions.delete(takeStateKey);\n }\n this.#setTakeState(\n takeStateKey,\n nextSize,\n takeState.bound === undefined ||\n compareRows(takeState.bound, change[ChangeIndex.NODE].row) < 0\n ? change[ChangeIndex.NODE].row\n : takeState.bound,\n );\n yield* this.#output.push(change, this);\n return;\n }\n // size === limit\n if (\n takeState.bound === undefined ||\n compareRows(change[ChangeIndex.NODE].row, takeState.bound) >= 0\n ) {\n return;\n }\n // added row < bound\n let beforeBoundNode: Node | undefined;\n let boundNode: Node | undefined;\n if (this.#limit === 1) {\n for (const node of this.#input.fetch({\n start: {\n row: takeState.bound,\n basis: 'at',\n },\n constraint,\n })) {\n if (node === 'yield') {\n yield node;\n continue;\n }\n boundNode = node;\n break;\n }\n } else {\n for (const node of this.#input.fetch({\n start: {\n row: takeState.bound,\n basis: 'at',\n },\n constraint,\n reverse: true,\n })) {\n if (node === 'yield') {\n yield node;\n continue;\n } else if (boundNode === undefined) {\n boundNode = node;\n } else {\n beforeBoundNode = node;\n break;\n }\n }\n }\n assert(\n boundNode !== undefined,\n 'Take: boundNode must be found during fetch',\n );\n const removeChange = makeRemoveChange(boundNode);\n // Remove before add to maintain invariant that\n // output size <= limit.\n this.#setTakeState(\n takeStateKey,\n takeState.size,\n beforeBoundNode === undefined ||\n compareRows(change[ChangeIndex.NODE].row, beforeBoundNode.row) > 0\n ? change[ChangeIndex.NODE].row\n : beforeBoundNode.row,\n );\n yield* this.#pushWithRowHiddenFromFetch(\n change[ChangeIndex.NODE].row,\n removeChange,\n );\n yield* this.#output.push(change, this);\n } else if (change[ChangeIndex.TYPE] === ChangeType.REMOVE) {\n if (takeState.bound === undefined) {\n // change is after bound\n return;\n }\n const compToBound = compareRows(\n change[ChangeIndex.NODE].row,\n takeState.bound,\n );\n if (compToBound > 0 || (compToBound < 0 && this.#limit === 1)) {\n // change is not in window\n return;\n }\n let beforeBoundNode: Node | undefined;\n for (const node of this.#input.fetch({\n start: {\n row: takeState.bound,\n basis: 'after',\n },\n constraint,\n reverse: true,\n })) {\n if (node === 'yield') {\n yield node;\n continue;\n }\n beforeBoundNode = node;\n break;\n }\n\n const finalBound =\n takeState.size - 1 === 0\n ? undefined\n : compToBound < 0\n ? takeState.bound\n : beforeBoundNode?.row;\n if (!this.#dirtyPartitions.has(takeStateKey)) {\n // Snapshot the pre-removal bound as the gate bound on the first removal that\n // dirties this partition. Subsequent getBound() calls throughout Phase 1 will return\n // this bound to keep upstream TakeGate bounded until Phase 2 refills deficits.\n this.#dirtyPartitions.set(takeStateKey, {\n constraint,\n gateBound: takeState.bound,\n });\n }\n this.#setTakeState(takeStateKey, takeState.size - 1, finalBound);\n yield* this.#output.push(change, this);\n } else if (change[ChangeIndex.TYPE] === ChangeType.CHILD) {\n // A 'child' change should be pushed to output if its row\n // is <= bound.\n if (\n takeState.bound &&\n compareRows(change[ChangeIndex.NODE].row, takeState.bound) <= 0\n ) {\n yield* this.#output.push(change, this);\n }\n }\n }\n\n *#pushEditChange(change: EditChange): Stream<'yield'> {\n assert(\n !this.#partitionKeyComparator ||\n this.#partitionKeyComparator(\n change[ChangeIndex.OLD_NODE].row,\n change[ChangeIndex.NODE].row,\n ) === 0,\n 'Unexpected change of partition key',\n );\n\n const {takeState, takeStateKey, constraint} = this.#getStateAndConstraint(\n change[ChangeIndex.OLD_NODE].row,\n );\n if (!takeState) {\n return;\n }\n\n assert(takeState.bound, 'Bound should be set');\n const {compareRows} = this.getSchema();\n const oldCmp = compareRows(\n change[ChangeIndex.OLD_NODE].row,\n takeState.bound,\n );\n const newCmp = compareRows(change[ChangeIndex.NODE].row, takeState.bound);\n\n const replaceBoundAndForwardChange = () => {\n this.#setTakeState(\n takeStateKey,\n takeState.size,\n change[ChangeIndex.NODE].row,\n );\n return this.#output.push(change, this);\n };\n\n // The bounds row was changed.\n if (oldCmp === 0) {\n // The new row is the new bound.\n if (newCmp === 0) {\n // no need to update the state since we are keeping the bounds\n yield* this.#output.push(change, this);\n return;\n }\n\n if (newCmp < 0) {\n if (this.#limit === 1) {\n yield* replaceBoundAndForwardChange();\n return;\n }\n\n // New row will be in the result but it might not be the bounds any\n // more. We need to find the row before the bounds to determine the new\n // bounds.\n\n let beforeBoundNode: Node | undefined;\n for (const node of this.#input.fetch({\n start: {\n row: takeState.bound,\n basis: 'after',\n },\n constraint,\n reverse: true,\n })) {\n if (node === 'yield') {\n yield node;\n continue;\n }\n beforeBoundNode = node;\n break;\n }\n assert(\n beforeBoundNode !== undefined,\n 'Take: beforeBoundNode must be found during fetch',\n );\n\n this.#setTakeState(takeStateKey, takeState.size, beforeBoundNode.row);\n yield* this.#output.push(change, this);\n return;\n }\n\n assert(newCmp > 0, 'New comparison must be greater than 0');\n // Find the first item at the old bounds. This will be the new bounds.\n let newBoundNode: Node | undefined;\n this.#takeGate?.open();\n try {\n for (const node of this.#input.fetch({\n start: {\n row: takeState.bound,\n basis: 'at',\n },\n constraint,\n })) {\n if (node === 'yield') {\n yield node;\n continue;\n }\n newBoundNode = node;\n break;\n }\n } finally {\n this.#takeGate?.close();\n }\n assert(\n newBoundNode !== undefined,\n 'Take: newBoundNode must be found during fetch',\n );\n\n // The next row is the new row. We can replace the bounds and keep the\n // edit change.\n if (compareRows(newBoundNode.row, change[ChangeIndex.NODE].row) === 0) {\n yield* replaceBoundAndForwardChange();\n return;\n }\n\n // The new row is now outside the bounds, so we need to remove the old\n // row and add the new bounds row.\n this.#setTakeState(takeStateKey, takeState.size, newBoundNode.row);\n yield* this.#pushWithRowHiddenFromFetch(\n newBoundNode.row,\n makeRemoveChange(change[ChangeIndex.OLD_NODE]),\n );\n yield* this.#output.push(makeAddChange(newBoundNode), this);\n return;\n }\n\n if (oldCmp > 0) {\n assert(newCmp !== 0, 'Invalid state. Row has duplicate primary key');\n\n // Both old and new outside of bounds\n if (newCmp > 0) {\n return;\n }\n\n // old was outside, new is inside. Pushing out the old bounds\n assert(newCmp < 0, 'New comparison must be less than 0');\n\n let oldBoundNode: Node | undefined;\n let newBoundNode: Node | undefined;\n for (const node of this.#input.fetch({\n start: {\n row: takeState.bound,\n basis: 'at',\n },\n constraint,\n reverse: true,\n })) {\n if (node === 'yield') {\n yield node;\n continue;\n } else if (oldBoundNode === undefined) {\n oldBoundNode = node;\n } else {\n newBoundNode = node;\n break;\n }\n }\n assert(\n oldBoundNode !== undefined,\n 'Take: oldBoundNode must be found during fetch',\n );\n assert(\n newBoundNode !== undefined,\n 'Take: newBoundNode must be found during fetch',\n );\n\n // Remove before add to maintain invariant that\n // output size <= limit.\n this.#setTakeState(takeStateKey, takeState.size, newBoundNode.row);\n yield* this.#pushWithRowHiddenFromFetch(\n change[ChangeIndex.NODE].row,\n makeRemoveChange(oldBoundNode),\n );\n yield* this.#output.push(makeAddChange(change[ChangeIndex.NODE]), this);\n\n return;\n }\n\n if (oldCmp < 0) {\n assert(newCmp !== 0, 'Invalid state. Row has duplicate primary key');\n\n // Both old and new inside of bounds\n if (newCmp < 0) {\n yield* this.#output.push(change, this);\n return;\n }\n\n // old was inside, new is larger than old bound\n\n assert(newCmp > 0, 'New comparison must be greater than 0');\n\n // at this point we need to find the row after the bound and use that or\n // the newRow as the new bound.\n let afterBoundNode: Node | undefined;\n this.#takeGate?.open();\n try {\n for (const node of this.#input.fetch({\n start: {\n row: takeState.bound,\n basis: 'after',\n },\n constraint,\n })) {\n if (node === 'yield') {\n yield node;\n continue;\n }\n afterBoundNode = node;\n break;\n }\n } finally {\n this.#takeGate?.close();\n }\n assert(\n afterBoundNode !== undefined,\n 'Take: afterBoundNode must be found during fetch',\n );\n\n // The new row is the new bound. Use an edit change.\n if (compareRows(afterBoundNode.row, change[ChangeIndex.NODE].row) === 0) {\n yield* replaceBoundAndForwardChange();\n return;\n }\n\n yield* this.#output.push(\n makeRemoveChange(change[ChangeIndex.OLD_NODE]),\n this,\n );\n this.#setTakeState(takeStateKey, takeState.size, afterBoundNode.row);\n yield* this.#output.push(makeAddChange(afterBoundNode), this);\n return;\n }\n\n unreachable();\n }\n\n *#pushWithRowHiddenFromFetch(row: Row, change: Change) {\n this.#rowHiddenFromFetch = row;\n try {\n yield* this.#output.push(change, this);\n } finally {\n this.#rowHiddenFromFetch = undefined;\n }\n }\n\n #setTakeState(takeStateKey: string, size: number, bound: Row | undefined) {\n this.#storage.set(takeStateKey, {\n size,\n bound,\n });\n }\n\n destroy(): void {\n this.#input.destroy();\n this.#storage.destroy();\n }\n\n *reconcile(_pusher?: InputBase): Stream<'yield'> {\n if (this.#dirtyPartitions.size > 0) {\n const dirty = [...this.#dirtyPartitions.entries()];\n this.#dirtyPartitions.clear();\n\n for (const [takeStateKey, {constraint}] of dirty) {\n const takeState = this.#storage.get(takeStateKey);\n assert(\n takeState !== undefined,\n 'Take: dirty partition must exist in storage',\n );\n const deficit = this.#limit - takeState.size;\n assert(deficit > 0, 'Take: dirty partition must have a deficit');\n\n const toPush: Node[] = [];\n this.#takeGate?.open();\n try {\n const stream = this.#input.fetch({\n start: takeState.bound\n ? {\n row: takeState.bound,\n basis: 'after',\n }\n : undefined,\n constraint,\n });\n\n for (const node of stream) {\n if (node === 'yield') {\n yield 'yield';\n continue;\n }\n toPush.push(node);\n if (toPush.length === deficit) {\n break;\n }\n }\n } finally {\n this.#takeGate?.close();\n }\n\n let currentSize = takeState.size;\n for (const node of toPush) {\n currentSize++;\n this.#setTakeState(takeStateKey, currentSize, node.row);\n yield* this.#output.push(makeAddChange(node), this);\n }\n }\n }\n\n if (this.#output.reconcile) {\n yield* this.#output.reconcile(this);\n }\n }\n}\n\nfunction getTakeStateKey(\n partitionKey: PartitionKey | undefined,\n rowOrConstraint: Row | Constraint | undefined,\n): string {\n // The order must be consistent. We always use the order as defined by the\n // partition key.\n const partitionValues: Value[] = [];\n\n if (partitionKey && rowOrConstraint) {\n for (const key of partitionKey) {\n partitionValues.push(rowOrConstraint[key]);\n }\n }\n\n return JSON.stringify(['take', ...partitionValues]);\n}\n\nexport function constraintMatchesPartitionKey(\n constraint: Constraint | undefined,\n partitionKey: PartitionKey | undefined,\n): boolean {\n if (constraint === undefined || partitionKey === undefined) {\n return constraint === partitionKey;\n }\n if (partitionKey.length !== Object.keys(constraint).length) {\n return false;\n }\n for (const key of partitionKey) {\n if (!hasOwn(constraint, key)) {\n return false;\n }\n }\n return true;\n}\n\nexport function constraintContainsPartitionKey(\n constraint: Constraint | undefined,\n partitionKey: PartitionKey | undefined,\n): boolean {\n if (constraint === undefined || partitionKey === undefined) {\n return false;\n }\n for (const key of partitionKey) {\n if (!hasOwn(constraint, key)) {\n return false;\n }\n }\n return true;\n}\n\nexport function makePartitionKeyComparator(\n partitionKey: PartitionKey,\n): Comparator {\n return (a, b) => {\n for (const key of partitionKey) {\n const cmp = compareValues(a[key], b[key]);\n if (cmp !== 0) {\n return cmp;\n }\n }\n return 0;\n };\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"database-storage.d.ts","sourceRoot":"","sources":["../../../../zqlite/src/database-storage.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,kBAAkB,CAAC;AAEjD,OAAO,KAAK,EAAC,OAAO,EAAC,MAAM,+BAA+B,CAAC;AAG3D,OAAO,EAAC,QAAQ,EAAC,MAAM,SAAS,CAAC;AAEjC,MAAM,WAAW,kBAAkB;IACjC,gEAAgE;IAChE,aAAa,IAAI,OAAO,CAAC;IAEzB,gDAAgD;IAChD,OAAO,IAAI,IAAI,CAAC;CACjB;AAcD,eAAO,MAAM,oBAAoB,wJAQ9B,CAAC;
|
|
1
|
+
{"version":3,"file":"database-storage.d.ts","sourceRoot":"","sources":["../../../../zqlite/src/database-storage.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,kBAAkB,CAAC;AAEjD,OAAO,KAAK,EAAC,OAAO,EAAC,MAAM,+BAA+B,CAAC;AAG3D,OAAO,EAAC,QAAQ,EAAC,MAAM,SAAS,CAAC;AAEjC,MAAM,WAAW,kBAAkB;IACjC,gEAAgE;IAChE,aAAa,IAAI,OAAO,CAAC;IAEzB,gDAAgD;IAChD,OAAO,IAAI,IAAI,CAAC;CACjB;AAcD,eAAO,MAAM,oBAAoB,wJAQ9B,CAAC;AAeJ,qBAAa,eAAe;;IAC1B,MAAM,CAAC,MAAM,CACX,EAAE,EAAE,UAAU,EACd,IAAI,EAAE,MAAM,EACZ,OAAO;;;KAAiB,GACvB,eAAe,CAiBjB;IAOD,YAAY,EAAE,EAAE,QAAQ,EAAE,OAAO;;;KAAiB,EAiCjD;IAED,KAAK,SAGJ;IA0DD,wBAAwB,CAAC,IAAI,EAAE,MAAM,GAAG,kBAAkB,CA4DzD;CACF"}
|
|
@@ -9,6 +9,13 @@ var CREATE_STORAGE_TABLE = `
|
|
|
9
9
|
PRIMARY KEY(clientGroupID, op, key)
|
|
10
10
|
)
|
|
11
11
|
`;
|
|
12
|
+
/**
|
|
13
|
+
* The most values each operator's storage keeps in memory. The storage is in
|
|
14
|
+
* SQLite so that operator state can spill to disk, so only a few values per
|
|
15
|
+
* operator are kept: profiles of the view-syncer gain as much from 64 as from
|
|
16
|
+
* 1,024.
|
|
17
|
+
*/
|
|
18
|
+
var MAX_CACHED_ENTRIES_PER_OP = 64;
|
|
12
19
|
var defaultOptions = {
|
|
13
20
|
commitInterval: 5e3,
|
|
14
21
|
compactionThresholdBytes: 50 * 1024 * 1024
|
|
@@ -68,10 +75,10 @@ var DatabaseStorage = class DatabaseStorage {
|
|
|
68
75
|
this.#checkpoint();
|
|
69
76
|
this.#db.close();
|
|
70
77
|
}
|
|
71
|
-
#get(cgID, opID, key
|
|
78
|
+
#get(cgID, opID, key) {
|
|
72
79
|
this.#maybeCheckpoint();
|
|
73
80
|
const row = this.#stmts.get.get(cgID, opID, key);
|
|
74
|
-
return row ? JSON.parse(row.val) :
|
|
81
|
+
return row ? JSON.parse(row.val) : void 0;
|
|
75
82
|
}
|
|
76
83
|
#set(cgID, opID, key, val) {
|
|
77
84
|
this.#maybeCheckpoint();
|
|
@@ -107,8 +114,10 @@ var DatabaseStorage = class DatabaseStorage {
|
|
|
107
114
|
}
|
|
108
115
|
}
|
|
109
116
|
createClientGroupStorage(cgID) {
|
|
117
|
+
const caches = /* @__PURE__ */ new Set();
|
|
110
118
|
const destroy = () => {
|
|
111
119
|
this.#stmts.clear.run(cgID);
|
|
120
|
+
for (const cache of caches) cache.clear();
|
|
112
121
|
this.#checkpoint();
|
|
113
122
|
this.#db.compact(this.#options.compactionThresholdBytes);
|
|
114
123
|
};
|
|
@@ -117,12 +126,35 @@ var DatabaseStorage = class DatabaseStorage {
|
|
|
117
126
|
return {
|
|
118
127
|
createStorage: () => {
|
|
119
128
|
const opID = nextOpID++;
|
|
129
|
+
const cache = /* @__PURE__ */ new Map();
|
|
130
|
+
caches.add(cache);
|
|
131
|
+
const cacheSet = (key, val) => {
|
|
132
|
+
if (cache.size >= MAX_CACHED_ENTRIES_PER_OP && !cache.has(key)) cache.clear();
|
|
133
|
+
cache.set(key, val);
|
|
134
|
+
};
|
|
120
135
|
return {
|
|
121
|
-
get: (key, def) =>
|
|
122
|
-
|
|
123
|
-
|
|
136
|
+
get: (key, def) => {
|
|
137
|
+
let val = cache.get(key);
|
|
138
|
+
if (val === void 0 && !cache.has(key)) {
|
|
139
|
+
val = this.#get(cgID, opID, key);
|
|
140
|
+
cacheSet(key, val);
|
|
141
|
+
}
|
|
142
|
+
return val === void 0 ? def : val;
|
|
143
|
+
},
|
|
144
|
+
set: (key, val) => {
|
|
145
|
+
this.#set(cgID, opID, key, val);
|
|
146
|
+
cacheSet(key, val);
|
|
147
|
+
},
|
|
148
|
+
del: (key) => {
|
|
149
|
+
this.#del(cgID, opID, key);
|
|
150
|
+
cacheSet(key, void 0);
|
|
151
|
+
},
|
|
124
152
|
scan: (opts) => this.#scan(cgID, opID, opts),
|
|
125
|
-
destroy: () =>
|
|
153
|
+
destroy: () => {
|
|
154
|
+
this.#destroy(cgID, opID);
|
|
155
|
+
cache.clear();
|
|
156
|
+
caches.delete(cache);
|
|
157
|
+
}
|
|
126
158
|
};
|
|
127
159
|
},
|
|
128
160
|
destroy
|