@rocicorp/zero 1.0.1-canary.0 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/out/_virtual/{_@oxc-project_runtime@0.115.0 → _@oxc-project_runtime@0.122.0}/helpers/usingCtx.js +1 -1
- package/out/replicache/src/mutation-recovery.js +0 -3
- package/out/zero/package.js +7 -6
- package/out/zero/package.js.map +1 -1
- package/out/zero-cache/src/services/analyze.js +1 -1
- package/out/zero-cache/src/services/change-source/pg/change-source.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-source/pg/change-source.js +37 -16
- package/out/zero-cache/src/services/change-source/pg/change-source.js.map +1 -1
- package/out/zero-cache/src/services/change-source/pg/lsn.js +1 -1
- package/out/zero-cache/src/services/life-cycle.d.ts.map +1 -1
- package/out/zero-cache/src/services/life-cycle.js +6 -2
- package/out/zero-cache/src/services/life-cycle.js.map +1 -1
- package/out/zero-cache/src/services/view-syncer/inspect-handler.js +1 -1
- package/out/zero-cache/src/workers/replicator.d.ts.map +1 -1
- package/out/zero-cache/src/workers/replicator.js +1 -0
- package/out/zero-cache/src/workers/replicator.js.map +1 -1
- package/out/zero-client/src/client/version.js +1 -1
- package/out/zql/src/builder/builder.d.ts.map +1 -1
- package/out/zql/src/builder/builder.js +15 -5
- package/out/zql/src/builder/builder.js.map +1 -1
- package/out/zql/src/ivm/cap.d.ts +32 -0
- package/out/zql/src/ivm/cap.d.ts.map +1 -0
- package/out/zql/src/ivm/cap.js +226 -0
- package/out/zql/src/ivm/cap.js.map +1 -0
- package/out/zql/src/ivm/join-utils.d.ts +2 -0
- package/out/zql/src/ivm/join-utils.d.ts.map +1 -1
- package/out/zql/src/ivm/join-utils.js +35 -1
- package/out/zql/src/ivm/join-utils.js.map +1 -1
- package/out/zql/src/ivm/join.d.ts.map +1 -1
- package/out/zql/src/ivm/join.js +6 -2
- package/out/zql/src/ivm/join.js.map +1 -1
- package/out/zql/src/ivm/memory-source.d.ts +15 -2
- package/out/zql/src/ivm/memory-source.d.ts.map +1 -1
- package/out/zql/src/ivm/memory-source.js +69 -8
- package/out/zql/src/ivm/memory-source.js.map +1 -1
- package/out/zql/src/ivm/schema.d.ts +1 -1
- package/out/zql/src/ivm/schema.d.ts.map +1 -1
- package/out/zql/src/ivm/skip.d.ts.map +1 -1
- package/out/zql/src/ivm/skip.js +3 -0
- package/out/zql/src/ivm/skip.js.map +1 -1
- package/out/zql/src/ivm/source.d.ts +1 -1
- package/out/zql/src/ivm/source.d.ts.map +1 -1
- package/out/zql/src/ivm/take.d.ts +4 -1
- package/out/zql/src/ivm/take.d.ts.map +1 -1
- package/out/zql/src/ivm/take.js +4 -2
- package/out/zql/src/ivm/take.js.map +1 -1
- package/out/zql/src/ivm/union-fan-in.d.ts.map +1 -1
- package/out/zql/src/ivm/union-fan-in.js +1 -0
- package/out/zql/src/ivm/union-fan-in.js.map +1 -1
- package/out/zqlite/src/query-builder.d.ts +1 -1
- package/out/zqlite/src/query-builder.d.ts.map +1 -1
- package/out/zqlite/src/query-builder.js +7 -2
- package/out/zqlite/src/query-builder.js.map +1 -1
- package/out/zqlite/src/table-source.d.ts +1 -1
- package/out/zqlite/src/table-source.d.ts.map +1 -1
- package/out/zqlite/src/table-source.js +15 -10
- package/out/zqlite/src/table-source.js.map +1 -1
- package/package.json +7 -6
- package/out/replicache/src/mutation-recovery.js.map +0 -1
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
import { assert } from "../../../shared/src/asserts.js";
|
|
2
|
+
import { throwOutput } from "./operator.js";
|
|
3
|
+
import { constraintMatchesPartitionKey, makePartitionKeyComparator } from "./take.js";
|
|
4
|
+
//#region ../zql/src/ivm/cap.ts
|
|
5
|
+
/**
|
|
6
|
+
* The Cap operator is a count-based limiter for EXISTS subqueries that
|
|
7
|
+
* does not require ordering. Unlike Take, it tracks membership by primary
|
|
8
|
+
* key set rather than by a sorted bound. This means:
|
|
9
|
+
*
|
|
10
|
+
* - No comparator needed (no ordering requirement)
|
|
11
|
+
* - No `start` or `reverse` fetch support
|
|
12
|
+
* - No `#rowHiddenFromFetch` complexity (we can defer when adding to the pk set)
|
|
13
|
+
*
|
|
14
|
+
* Cap is used in EXISTS child pipelines where only the count of matching
|
|
15
|
+
* rows matters, not their order. This allows SQLite to skip ORDER BY
|
|
16
|
+
* entirely, enabling much faster query plans.
|
|
17
|
+
*
|
|
18
|
+
* Cap can count rows globally or by unique value of some partition key
|
|
19
|
+
* (same as Take).
|
|
20
|
+
*/
|
|
21
|
+
var Cap = class {
|
|
22
|
+
#input;
|
|
23
|
+
#storage;
|
|
24
|
+
#limit;
|
|
25
|
+
#partitionKey;
|
|
26
|
+
#partitionKeyComparator;
|
|
27
|
+
#primaryKey;
|
|
28
|
+
#output = throwOutput;
|
|
29
|
+
constructor(input, storage, limit, partitionKey) {
|
|
30
|
+
assert(limit >= 0, "Limit must be non-negative");
|
|
31
|
+
input.setOutput(this);
|
|
32
|
+
this.#input = input;
|
|
33
|
+
this.#storage = storage;
|
|
34
|
+
this.#limit = limit;
|
|
35
|
+
this.#partitionKey = partitionKey;
|
|
36
|
+
this.#partitionKeyComparator = partitionKey && makePartitionKeyComparator(partitionKey);
|
|
37
|
+
this.#primaryKey = input.getSchema().primaryKey;
|
|
38
|
+
}
|
|
39
|
+
setOutput(output) {
|
|
40
|
+
this.#output = output;
|
|
41
|
+
}
|
|
42
|
+
getSchema() {
|
|
43
|
+
return this.#input.getSchema();
|
|
44
|
+
}
|
|
45
|
+
*fetch(req) {
|
|
46
|
+
assert(!req.start, "Cap does not support start");
|
|
47
|
+
assert(!req.reverse, "Cap does not support reverse");
|
|
48
|
+
if (!this.#partitionKey || req.constraint && constraintMatchesPartitionKey(req.constraint, this.#partitionKey)) {
|
|
49
|
+
const capStateKey = getCapStateKey(this.#partitionKey, req.constraint);
|
|
50
|
+
const capState = this.#storage.get(capStateKey);
|
|
51
|
+
if (!capState) {
|
|
52
|
+
yield* this.#initialFetch(req);
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
if (capState.size === 0) return;
|
|
56
|
+
for (const pk of capState.pks) {
|
|
57
|
+
const constraint = deserializePKToConstraint(pk, this.#primaryKey);
|
|
58
|
+
for (const inputNode of this.#input.fetch({ constraint })) {
|
|
59
|
+
if (inputNode === "yield") {
|
|
60
|
+
yield inputNode;
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
63
|
+
yield inputNode;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
const pkSetCache = /* @__PURE__ */ new Map();
|
|
69
|
+
for (const inputNode of this.#input.fetch(req)) {
|
|
70
|
+
if (inputNode === "yield") {
|
|
71
|
+
yield inputNode;
|
|
72
|
+
continue;
|
|
73
|
+
}
|
|
74
|
+
const capStateKey = getCapStateKey(this.#partitionKey, inputNode.row);
|
|
75
|
+
if (!pkSetCache.has(capStateKey)) {
|
|
76
|
+
const capState = this.#storage.get(capStateKey);
|
|
77
|
+
pkSetCache.set(capStateKey, capState && capState.size > 0 ? new Set(capState.pks) : null);
|
|
78
|
+
}
|
|
79
|
+
const pkSet = pkSetCache.get(capStateKey);
|
|
80
|
+
if (pkSet) {
|
|
81
|
+
const pk = serializePK(inputNode.row, this.#primaryKey);
|
|
82
|
+
if (pkSet.has(pk)) yield inputNode;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
*#initialFetch(req) {
|
|
87
|
+
assert(constraintMatchesPartitionKey(req.constraint, this.#partitionKey), "Constraint should match partition key");
|
|
88
|
+
if (this.#limit === 0) return;
|
|
89
|
+
const capStateKey = getCapStateKey(this.#partitionKey, req.constraint);
|
|
90
|
+
assert(this.#storage.get(capStateKey) === void 0, "Cap state should be undefined");
|
|
91
|
+
let size = 0;
|
|
92
|
+
const pks = [];
|
|
93
|
+
let downstreamEarlyReturn = true;
|
|
94
|
+
let exceptionThrown = false;
|
|
95
|
+
try {
|
|
96
|
+
for (const inputNode of this.#input.fetch(req)) {
|
|
97
|
+
if (inputNode === "yield") {
|
|
98
|
+
yield "yield";
|
|
99
|
+
continue;
|
|
100
|
+
}
|
|
101
|
+
yield inputNode;
|
|
102
|
+
pks.push(serializePK(inputNode.row, this.#primaryKey));
|
|
103
|
+
size++;
|
|
104
|
+
if (size === this.#limit) break;
|
|
105
|
+
}
|
|
106
|
+
downstreamEarlyReturn = false;
|
|
107
|
+
} catch (e) {
|
|
108
|
+
exceptionThrown = true;
|
|
109
|
+
throw e;
|
|
110
|
+
} finally {
|
|
111
|
+
if (!exceptionThrown) {
|
|
112
|
+
this.#storage.set(capStateKey, {
|
|
113
|
+
size,
|
|
114
|
+
pks
|
|
115
|
+
});
|
|
116
|
+
assert(!downstreamEarlyReturn, "Unexpected early return prevented full hydration");
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
*push(change) {
|
|
121
|
+
if (change.type === "edit") {
|
|
122
|
+
yield* this.#pushEditChange(change);
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
const capStateKey = getCapStateKey(this.#partitionKey, change.node.row);
|
|
126
|
+
const capState = this.#storage.get(capStateKey);
|
|
127
|
+
if (!capState) return;
|
|
128
|
+
const pk = serializePK(change.node.row, this.#primaryKey);
|
|
129
|
+
if (change.type === "add") {
|
|
130
|
+
if (capState.size < this.#limit) {
|
|
131
|
+
const pks = [...capState.pks, pk];
|
|
132
|
+
this.#storage.set(capStateKey, {
|
|
133
|
+
size: capState.size + 1,
|
|
134
|
+
pks
|
|
135
|
+
});
|
|
136
|
+
yield* this.#output.push(change, this);
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
return;
|
|
140
|
+
} else if (change.type === "remove") {
|
|
141
|
+
const pkIndex = capState.pks.indexOf(pk);
|
|
142
|
+
if (pkIndex === -1) return;
|
|
143
|
+
const pks = [...capState.pks];
|
|
144
|
+
pks.splice(pkIndex, 1);
|
|
145
|
+
const newSize = capState.size - 1;
|
|
146
|
+
const pkSet = new Set(pks);
|
|
147
|
+
const constraint = this.#partitionKey ? Object.fromEntries(this.#partitionKey.map((key) => [key, change.node.row[key]])) : void 0;
|
|
148
|
+
let replacement;
|
|
149
|
+
for (const node of this.#input.fetch({ constraint })) {
|
|
150
|
+
if (node === "yield") {
|
|
151
|
+
yield node;
|
|
152
|
+
continue;
|
|
153
|
+
}
|
|
154
|
+
const nodePK = serializePK(node.row, this.#primaryKey);
|
|
155
|
+
if (!pkSet.has(nodePK)) {
|
|
156
|
+
replacement = node;
|
|
157
|
+
break;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
if (replacement) {
|
|
161
|
+
this.#storage.set(capStateKey, {
|
|
162
|
+
size: newSize,
|
|
163
|
+
pks
|
|
164
|
+
});
|
|
165
|
+
yield* this.#output.push(change, this);
|
|
166
|
+
const replacementPK = serializePK(replacement.row, this.#primaryKey);
|
|
167
|
+
pks.push(replacementPK);
|
|
168
|
+
this.#storage.set(capStateKey, {
|
|
169
|
+
size: newSize + 1,
|
|
170
|
+
pks
|
|
171
|
+
});
|
|
172
|
+
yield* this.#output.push({
|
|
173
|
+
type: "add",
|
|
174
|
+
node: replacement
|
|
175
|
+
}, this);
|
|
176
|
+
} else {
|
|
177
|
+
this.#storage.set(capStateKey, {
|
|
178
|
+
size: newSize,
|
|
179
|
+
pks
|
|
180
|
+
});
|
|
181
|
+
yield* this.#output.push(change, this);
|
|
182
|
+
}
|
|
183
|
+
} else if (change.type === "child") {
|
|
184
|
+
if (new Set(capState.pks).has(pk)) yield* this.#output.push(change, this);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
*#pushEditChange(change) {
|
|
188
|
+
assert(!this.#partitionKeyComparator || this.#partitionKeyComparator(change.oldNode.row, change.node.row) === 0, "Unexpected change of partition key");
|
|
189
|
+
const capStateKey = getCapStateKey(this.#partitionKey, change.oldNode.row);
|
|
190
|
+
const capState = this.#storage.get(capStateKey);
|
|
191
|
+
if (!capState) return;
|
|
192
|
+
const oldPK = serializePK(change.oldNode.row, this.#primaryKey);
|
|
193
|
+
if (new Set(capState.pks).has(oldPK)) {
|
|
194
|
+
const newPK = serializePK(change.node.row, this.#primaryKey);
|
|
195
|
+
if (oldPK !== newPK) {
|
|
196
|
+
const pks = capState.pks.map((p) => p === oldPK ? newPK : p);
|
|
197
|
+
this.#storage.set(capStateKey, {
|
|
198
|
+
size: capState.size,
|
|
199
|
+
pks
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
yield* this.#output.push(change, this);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
destroy() {
|
|
206
|
+
this.#input.destroy();
|
|
207
|
+
}
|
|
208
|
+
};
|
|
209
|
+
function getCapStateKey(partitionKey, rowOrConstraint) {
|
|
210
|
+
const partitionValues = [];
|
|
211
|
+
if (partitionKey && rowOrConstraint) for (const key of partitionKey) partitionValues.push(rowOrConstraint[key]);
|
|
212
|
+
return JSON.stringify(["cap", ...partitionValues]);
|
|
213
|
+
}
|
|
214
|
+
function serializePK(row, primaryKey) {
|
|
215
|
+
return JSON.stringify(primaryKey.map((k) => row[k]));
|
|
216
|
+
}
|
|
217
|
+
function deserializePKToConstraint(pk, primaryKey) {
|
|
218
|
+
const values = JSON.parse(pk);
|
|
219
|
+
const constraint = {};
|
|
220
|
+
for (let i = 0; i < primaryKey.length; i++) constraint[primaryKey[i]] = values[i];
|
|
221
|
+
return constraint;
|
|
222
|
+
}
|
|
223
|
+
//#endregion
|
|
224
|
+
export { Cap };
|
|
225
|
+
|
|
226
|
+
//# sourceMappingURL=cap.js.map
|
|
@@ -0,0 +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 type {Row, Value} from '../../../zero-protocol/src/data.ts';\nimport type {PrimaryKey} from '../../../zero-protocol/src/primary-key.ts';\nimport {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 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}\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 if (\n !this.#partitionKey ||\n (req.constraint &&\n constraintMatchesPartitionKey(req.constraint, this.#partitionKey))\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 constraint = deserializePKToConstraint(pk, this.#primaryKey);\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 return;\n }\n // There is a partition key, but the fetch is not constrained or constrained\n // on a different key. This currently only happens with nested sub-queries.\n const pkSetCache = new Map<string, Set<string> | null>();\n for (const inputNode of this.#input.fetch(req)) {\n if (inputNode === 'yield') {\n yield inputNode;\n continue;\n }\n const capStateKey = getCapStateKey(this.#partitionKey, inputNode.row);\n if (!pkSetCache.has(capStateKey)) {\n const capState = this.#storage.get(capStateKey);\n pkSetCache.set(\n capStateKey,\n capState && capState.size > 0 ? new Set(capState.pks) : null,\n );\n }\n const pkSet = pkSetCache.get(capStateKey);\n if (pkSet) {\n const pk = serializePK(inputNode.row, this.#primaryKey);\n if (pkSet.has(pk)) {\n yield inputNode;\n }\n }\n }\n }\n\n *#initialFetch(req: FetchRequest): Stream<Node | 'yield'> {\n assert(\n constraintMatchesPartitionKey(req.constraint, this.#partitionKey),\n 'Constraint should match partition key',\n );\n\n if (this.#limit === 0) {\n return;\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.type === 'edit') {\n yield* this.#pushEditChange(change);\n return;\n }\n\n const capStateKey = getCapStateKey(this.#partitionKey, change.node.row);\n const capState = this.#storage.get(capStateKey);\n if (!capState) {\n return;\n }\n\n const pk = serializePK(change.node.row, this.#primaryKey);\n\n if (change.type === '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.type === '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(key => [key, change.node.row[key]] as const),\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({type: 'add', node: replacement}, this);\n } else {\n this.#storage.set(capStateKey, {size: newSize, pks});\n yield* this.#output.push(change, this);\n }\n } else if (change.type === '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(change.oldNode.row, change.node.row) === 0,\n 'Unexpected change of partition key',\n );\n const capStateKey = getCapStateKey(this.#partitionKey, change.oldNode.row);\n const capState = this.#storage.get(capStateKey);\n if (!capState) {\n return;\n }\n\n const oldPK = serializePK(change.oldNode.row, this.#primaryKey);\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.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 yield* this.#output.push(change, this);\n }\n // If not in our set, drop\n }\n\n destroy(): void {\n this.#input.destroy();\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":";;;;;;;;;;;;;;;;;;;;AAiDA,IAAa,MAAb,MAAqC;CACnC;CACA;CACA;CACA;CACA;CACA;CAEA,UAAkB;CAElB,YACE,OACA,SACA,OACA,cACA;AACA,SAAO,SAAS,GAAG,6BAA6B;AAChD,QAAM,UAAU,KAAK;AACrB,QAAA,QAAc;AACd,QAAA,UAAgB;AAChB,QAAA,QAAc;AACd,QAAA,eAAqB;AACrB,QAAA,yBACE,gBAAgB,2BAA2B,aAAa;AAC1D,QAAA,aAAmB,MAAM,WAAW,CAAC;;CAGvC,UAAU,QAAsB;AAC9B,QAAA,SAAe;;CAGjB,YAA0B;AACxB,SAAO,MAAA,MAAY,WAAW;;CAGhC,CAAC,MAAM,KAA2C;AAChD,SAAO,CAAC,IAAI,OAAO,6BAA6B;AAChD,SAAO,CAAC,IAAI,SAAS,+BAA+B;AAEpD,MACE,CAAC,MAAA,gBACA,IAAI,cACH,8BAA8B,IAAI,YAAY,MAAA,aAAmB,EACnE;GACA,MAAM,cAAc,eAAe,MAAA,cAAoB,IAAI,WAAW;GACtE,MAAM,WAAW,MAAA,QAAc,IAAI,YAAY;AAC/C,OAAI,CAAC,UAAU;AACb,WAAO,MAAA,aAAmB,IAAI;AAC9B;;AAEF,OAAI,SAAS,SAAS,EACpB;AAIF,QAAK,MAAM,MAAM,SAAS,KAAK;IAC7B,MAAM,aAAa,0BAA0B,IAAI,MAAA,WAAiB;AAClE,SAAK,MAAM,aAAa,MAAA,MAAY,MAAM,EAAC,YAAW,CAAC,EAAE;AACvD,SAAI,cAAc,SAAS;AACzB,YAAM;AACN;;AAEF,WAAM;;;AAGV;;EAIF,MAAM,6BAAa,IAAI,KAAiC;AACxD,OAAK,MAAM,aAAa,MAAA,MAAY,MAAM,IAAI,EAAE;AAC9C,OAAI,cAAc,SAAS;AACzB,UAAM;AACN;;GAEF,MAAM,cAAc,eAAe,MAAA,cAAoB,UAAU,IAAI;AACrE,OAAI,CAAC,WAAW,IAAI,YAAY,EAAE;IAChC,MAAM,WAAW,MAAA,QAAc,IAAI,YAAY;AAC/C,eAAW,IACT,aACA,YAAY,SAAS,OAAO,IAAI,IAAI,IAAI,SAAS,IAAI,GAAG,KACzD;;GAEH,MAAM,QAAQ,WAAW,IAAI,YAAY;AACzC,OAAI,OAAO;IACT,MAAM,KAAK,YAAY,UAAU,KAAK,MAAA,WAAiB;AACvD,QAAI,MAAM,IAAI,GAAG,CACf,OAAM;;;;CAMd,EAAA,aAAe,KAA2C;AACxD,SACE,8BAA8B,IAAI,YAAY,MAAA,aAAmB,EACjE,wCACD;AAED,MAAI,MAAA,UAAgB,EAClB;EAGF,MAAM,cAAc,eAAe,MAAA,cAAoB,IAAI,WAAW;AACtE,SACE,MAAA,QAAc,IAAI,YAAY,KAAK,KAAA,GACnC,gCACD;EAED,IAAI,OAAO;EACX,MAAM,MAAgB,EAAE;EACxB,IAAI,wBAAwB;EAC5B,IAAI,kBAAkB;AACtB,MAAI;AACF,QAAK,MAAM,aAAa,MAAA,MAAY,MAAM,IAAI,EAAE;AAC9C,QAAI,cAAc,SAAS;AACzB,WAAM;AACN;;AAEF,UAAM;AACN,QAAI,KAAK,YAAY,UAAU,KAAK,MAAA,WAAiB,CAAC;AACtD;AACA,QAAI,SAAS,MAAA,MACX;;AAGJ,2BAAwB;WACjB,GAAG;AACV,qBAAkB;AAClB,SAAM;YACE;AACR,OAAI,CAAC,iBAAiB;AACpB,UAAA,QAAc,IAAI,aAAa;KAAC;KAAM;KAAI,CAAC;AAK3C,WACE,CAAC,uBACD,mDACD;;;;CAKP,CAAC,KAAK,QAAiC;AACrC,MAAI,OAAO,SAAS,QAAQ;AAC1B,UAAO,MAAA,eAAqB,OAAO;AACnC;;EAGF,MAAM,cAAc,eAAe,MAAA,cAAoB,OAAO,KAAK,IAAI;EACvE,MAAM,WAAW,MAAA,QAAc,IAAI,YAAY;AAC/C,MAAI,CAAC,SACH;EAGF,MAAM,KAAK,YAAY,OAAO,KAAK,KAAK,MAAA,WAAiB;AAEzD,MAAI,OAAO,SAAS,OAAO;AACzB,OAAI,SAAS,OAAO,MAAA,OAAa;IAC/B,MAAM,MAAM,CAAC,GAAG,SAAS,KAAK,GAAG;AACjC,UAAA,QAAc,IAAI,aAAa;KAAC,MAAM,SAAS,OAAO;KAAG;KAAI,CAAC;AAC9D,WAAO,MAAA,OAAa,KAAK,QAAQ,KAAK;AACtC;;AAGF;aACS,OAAO,SAAS,UAAU;GACnC,MAAM,UAAU,SAAS,IAAI,QAAQ,GAAG;AACxC,OAAI,YAAY,GAEd;GAGF,MAAM,MAAM,CAAC,GAAG,SAAS,IAAI;AAC7B,OAAI,OAAO,SAAS,EAAE;GACtB,MAAM,UAAU,SAAS,OAAO;GAIhC,MAAM,QAAQ,IAAI,IAAI,IAAI;GAC1B,MAAM,aAAa,MAAA,eACd,OAAO,YACN,MAAA,aAAmB,KAAI,QAAO,CAAC,KAAK,OAAO,KAAK,IAAI,KAAK,CAAU,CACpE,GACD,KAAA;GAEJ,IAAI;AACJ,QAAK,MAAM,QAAQ,MAAA,MAAY,MAAM,EAAC,YAAW,CAAC,EAAE;AAClD,QAAI,SAAS,SAAS;AACpB,WAAM;AACN;;IAEF,MAAM,SAAS,YAAY,KAAK,KAAK,MAAA,WAAiB;AACtD,QAAI,CAAC,MAAM,IAAI,OAAO,EAAE;AACtB,mBAAc;AACd;;;AAIJ,OAAI,aAAa;AAGf,UAAA,QAAc,IAAI,aAAa;KAAC,MAAM;KAAS;KAAI,CAAC;AACpD,WAAO,MAAA,OAAa,KAAK,QAAQ,KAAK;IAEtC,MAAM,gBAAgB,YAAY,YAAY,KAAK,MAAA,WAAiB;AACpE,QAAI,KAAK,cAAc;AACvB,UAAA,QAAc,IAAI,aAAa;KAAC,MAAM,UAAU;KAAG;KAAI,CAAC;AACxD,WAAO,MAAA,OAAa,KAAK;KAAC,MAAM;KAAO,MAAM;KAAY,EAAE,KAAK;UAC3D;AACL,UAAA,QAAc,IAAI,aAAa;KAAC,MAAM;KAAS;KAAI,CAAC;AACpD,WAAO,MAAA,OAAa,KAAK,QAAQ,KAAK;;aAE/B,OAAO,SAAS;OACX,IAAI,IAAI,SAAS,IAAI,CACzB,IAAI,GAAG,CACf,QAAO,MAAA,OAAa,KAAK,QAAQ,KAAK;;;CAK5C,EAAA,eAAiB,QAAqC;AACpD,SACE,CAAC,MAAA,0BACC,MAAA,uBAA6B,OAAO,QAAQ,KAAK,OAAO,KAAK,IAAI,KAAK,GACxE,qCACD;EACD,MAAM,cAAc,eAAe,MAAA,cAAoB,OAAO,QAAQ,IAAI;EAC1E,MAAM,WAAW,MAAA,QAAc,IAAI,YAAY;AAC/C,MAAI,CAAC,SACH;EAGF,MAAM,QAAQ,YAAY,OAAO,QAAQ,KAAK,MAAA,WAAiB;AAE/D,MADc,IAAI,IAAI,SAAS,IAAI,CACzB,IAAI,MAAM,EAAE;GAEpB,MAAM,QAAQ,YAAY,OAAO,KAAK,KAAK,MAAA,WAAiB;AAC5D,OAAI,UAAU,OAAO;IACnB,MAAM,MAAM,SAAS,IAAI,KAAI,MAAM,MAAM,QAAQ,QAAQ,EAAG;AAC5D,UAAA,QAAc,IAAI,aAAa;KAAC,MAAM,SAAS;KAAM;KAAI,CAAC;;AAE5D,UAAO,MAAA,OAAa,KAAK,QAAQ,KAAK;;;CAK1C,UAAgB;AACd,QAAA,MAAY,SAAS;;;AAIzB,SAAS,eACP,cACA,iBACQ;CACR,MAAM,kBAA2B,EAAE;AAEnC,KAAI,gBAAgB,gBAClB,MAAK,MAAM,OAAO,aAChB,iBAAgB,KAAK,gBAAgB,KAAK;AAI9C,QAAO,KAAK,UAAU,CAAC,OAAO,GAAG,gBAAgB,CAAC;;AAGpD,SAAS,YAAY,KAAU,YAAgC;AAC7D,QAAO,KAAK,UAAU,WAAW,KAAI,MAAK,IAAI,GAAG,CAAC;;AAGpD,SAAS,0BACP,IACA,YACY;CACZ,MAAM,SAAS,KAAK,MAAM,GAAG;CAC7B,MAAM,aAAoC,EAAE;AAC5C,MAAK,IAAI,IAAI,GAAG,IAAI,WAAW,QAAQ,IACrC,YAAW,WAAW,MAAM,OAAO;AAErC,QAAO"}
|
|
@@ -10,6 +10,8 @@ export type JoinChangeOverlay = {
|
|
|
10
10
|
};
|
|
11
11
|
export declare function generateWithOverlayNoYield(stream: Stream<Node>, overlay: Change, schema: SourceSchema): Stream<Node>;
|
|
12
12
|
export declare function generateWithOverlay(stream: Stream<Node | 'yield'>, overlay: Change, schema: SourceSchema): Stream<Node | 'yield'>;
|
|
13
|
+
export declare function generateWithOverlayNoYieldUnordered(stream: Stream<Node>, overlay: Change, schema: SourceSchema): Stream<Node>;
|
|
14
|
+
export declare function generateWithOverlayUnordered(stream: Stream<Node | 'yield'>, overlay: Change, schema: SourceSchema): Stream<Node | 'yield'>;
|
|
13
15
|
export declare function rowEqualsForCompoundKey(a: Row, b: Row, key: CompoundKey): boolean;
|
|
14
16
|
export declare function isJoinMatch(parent: Row, parentKey: CompoundKey, child: Row, childKey: CompoundKey): boolean;
|
|
15
17
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"join-utils.d.ts","sourceRoot":"","sources":["../../../../../zql/src/ivm/join-utils.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,GAAG,EAAE,KAAK,EAAC,MAAM,oCAAoC,CAAC;AACnE,OAAO,KAAK,EAAC,MAAM,EAAC,MAAM,aAAa,CAAC;AACxC,OAAO,KAAK,EAAC,YAAY,EAAC,MAAM,aAAa,CAAC;AAC9C,OAAO,KAAK,EAAC,MAAM,EAAC,MAAM,aAAa,CAAC;AACxC,OAAO,EAA6B,KAAK,IAAI,EAAC,MAAM,WAAW,CAAC;AAEhE,OAAO,KAAK,EAAC,WAAW,EAAC,MAAM,mCAAmC,CAAC;AAEnE,MAAM,MAAM,iBAAiB,GAAG;IAC9B,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,GAAG,GAAG,SAAS,CAAC;CAC3B,CAAC;AAEF,wBAAgB,0BAA0B,CACxC,MAAM,EAAE,MAAM,CAAC,IAAI,CAAC,EACpB,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,YAAY,GACnB,MAAM,CAAC,IAAI,CAAC,CAEd;AAED,wBAAiB,mBAAmB,CAClC,MAAM,EAAE,MAAM,CAAC,IAAI,GAAG,OAAO,CAAC,EAC9B,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,YAAY,GACnB,MAAM,CAAC,IAAI,GAAG,OAAO,CAAC,CA6FxB;AAED,wBAAgB,uBAAuB,CACrC,CAAC,EAAE,GAAG,EACN,CAAC,EAAE,GAAG,EACN,GAAG,EAAE,WAAW,GACf,OAAO,CAOT;AAED,wBAAgB,WAAW,CACzB,MAAM,EAAE,GAAG,EACX,SAAS,EAAE,WAAW,EACtB,KAAK,EAAE,GAAG,EACV,QAAQ,EAAE,WAAW,WAQtB;AAED;;;;GAIG;AACH,wBAAgB,mBAAmB,CACjC,SAAS,EAAE,GAAG,EACd,SAAS,EAAE,WAAW,EACtB,SAAS,EAAE,WAAW,GACrB,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,GAAG,SAAS,CAUnC"}
|
|
1
|
+
{"version":3,"file":"join-utils.d.ts","sourceRoot":"","sources":["../../../../../zql/src/ivm/join-utils.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,GAAG,EAAE,KAAK,EAAC,MAAM,oCAAoC,CAAC;AACnE,OAAO,KAAK,EAAC,MAAM,EAAC,MAAM,aAAa,CAAC;AACxC,OAAO,KAAK,EAAC,YAAY,EAAC,MAAM,aAAa,CAAC;AAC9C,OAAO,KAAK,EAAC,MAAM,EAAC,MAAM,aAAa,CAAC;AACxC,OAAO,EAA6B,KAAK,IAAI,EAAC,MAAM,WAAW,CAAC;AAEhE,OAAO,KAAK,EAAC,WAAW,EAAC,MAAM,mCAAmC,CAAC;AAEnE,MAAM,MAAM,iBAAiB,GAAG;IAC9B,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,GAAG,GAAG,SAAS,CAAC;CAC3B,CAAC;AAEF,wBAAgB,0BAA0B,CACxC,MAAM,EAAE,MAAM,CAAC,IAAI,CAAC,EACpB,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,YAAY,GACnB,MAAM,CAAC,IAAI,CAAC,CAEd;AAED,wBAAiB,mBAAmB,CAClC,MAAM,EAAE,MAAM,CAAC,IAAI,GAAG,OAAO,CAAC,EAC9B,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,YAAY,GACnB,MAAM,CAAC,IAAI,GAAG,OAAO,CAAC,CA6FxB;AAED,wBAAgB,mCAAmC,CACjD,MAAM,EAAE,MAAM,CAAC,IAAI,CAAC,EACpB,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,YAAY,GACnB,MAAM,CAAC,IAAI,CAAC,CAEd;AAED,wBAAiB,4BAA4B,CAC3C,MAAM,EAAE,MAAM,CAAC,IAAI,GAAG,OAAO,CAAC,EAC9B,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,YAAY,GACnB,MAAM,CAAC,IAAI,GAAG,OAAO,CAAC,CAmDxB;AAED,wBAAgB,uBAAuB,CACrC,CAAC,EAAE,GAAG,EACN,CAAC,EAAE,GAAG,EACN,GAAG,EAAE,WAAW,GACf,OAAO,CAOT;AAED,wBAAgB,WAAW,CACzB,MAAM,EAAE,GAAG,EACX,SAAS,EAAE,WAAW,EACtB,KAAK,EAAE,GAAG,EACV,QAAQ,EAAE,WAAW,WAQtB;AAED;;;;GAIG;AACH,wBAAgB,mBAAmB,CACjC,SAAS,EAAE,GAAG,EACd,SAAS,EAAE,WAAW,EACtB,SAAS,EAAE,WAAW,GACrB,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,GAAG,SAAS,CAUnC"}
|
|
@@ -68,6 +68,40 @@ function* generateWithOverlay(stream, overlay, schema) {
|
|
|
68
68
|
}
|
|
69
69
|
assert(applied, "overlayGenerator: overlay was never applied to any fetched node");
|
|
70
70
|
}
|
|
71
|
+
function* generateWithOverlayUnordered(stream, overlay, schema) {
|
|
72
|
+
if (overlay.type === "remove") yield overlay.node;
|
|
73
|
+
else if (overlay.type === "edit") yield overlay.oldNode;
|
|
74
|
+
let suppressed = false;
|
|
75
|
+
for (const node of stream) {
|
|
76
|
+
if (node === "yield") {
|
|
77
|
+
yield node;
|
|
78
|
+
continue;
|
|
79
|
+
}
|
|
80
|
+
if (!suppressed) {
|
|
81
|
+
if (overlay.type === "add" || overlay.type === "edit") {
|
|
82
|
+
if (rowEqualsForCompoundKey(overlay.node.row, node.row, schema.primaryKey)) {
|
|
83
|
+
suppressed = true;
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
if (overlay.type === "child") {
|
|
88
|
+
if (rowEqualsForCompoundKey(overlay.node.row, node.row, schema.primaryKey)) {
|
|
89
|
+
suppressed = true;
|
|
90
|
+
yield {
|
|
91
|
+
row: node.row,
|
|
92
|
+
relationships: {
|
|
93
|
+
...node.relationships,
|
|
94
|
+
[overlay.child.relationshipName]: () => generateWithOverlay(node.relationships[overlay.child.relationshipName](), overlay.child.change, schema.relationships[overlay.child.relationshipName])
|
|
95
|
+
}
|
|
96
|
+
};
|
|
97
|
+
continue;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
yield node;
|
|
102
|
+
}
|
|
103
|
+
assert(suppressed || overlay.type === "remove", "overlayGenerator: overlay was never applied to any fetched node");
|
|
104
|
+
}
|
|
71
105
|
function rowEqualsForCompoundKey(a, b, key) {
|
|
72
106
|
for (let i = 0; i < key.length; i++) if (compareValues(a[key[i]], b[key[i]]) !== 0) return false;
|
|
73
107
|
return true;
|
|
@@ -91,6 +125,6 @@ function buildJoinConstraint(sourceRow, sourceKey, targetKey) {
|
|
|
91
125
|
return constraint;
|
|
92
126
|
}
|
|
93
127
|
//#endregion
|
|
94
|
-
export { buildJoinConstraint, generateWithOverlay, generateWithOverlayNoYield, isJoinMatch, rowEqualsForCompoundKey };
|
|
128
|
+
export { buildJoinConstraint, generateWithOverlay, generateWithOverlayNoYield, generateWithOverlayUnordered, isJoinMatch, rowEqualsForCompoundKey };
|
|
95
129
|
|
|
96
130
|
//# sourceMappingURL=join-utils.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"join-utils.js","names":[],"sources":["../../../../../zql/src/ivm/join-utils.ts"],"sourcesContent":["import type {Row, Value} from '../../../zero-protocol/src/data.ts';\nimport type {Change} from './change.ts';\nimport type {SourceSchema} from './schema.ts';\nimport type {Stream} from './stream.ts';\nimport {compareValues, valuesEqual, type Node} from './data.ts';\nimport {assert} from '../../../shared/src/asserts.ts';\nimport type {CompoundKey} from '../../../zero-protocol/src/ast.ts';\n\nexport type JoinChangeOverlay = {\n change: Change;\n position: Row | undefined;\n};\n\nexport function generateWithOverlayNoYield(\n stream: Stream<Node>,\n overlay: Change,\n schema: SourceSchema,\n): Stream<Node> {\n return generateWithOverlay(stream, overlay, schema) as Stream<Node>;\n}\n\nexport function* generateWithOverlay(\n stream: Stream<Node | 'yield'>,\n overlay: Change,\n schema: SourceSchema,\n): Stream<Node | 'yield'> {\n let applied = false;\n let editOldApplied = false;\n let editNewApplied = false;\n for (const node of stream) {\n if (node === 'yield') {\n yield node;\n continue;\n }\n let yieldNode = true;\n if (!applied) {\n switch (overlay.type) {\n case 'add': {\n if (schema.compareRows(overlay.node.row, node.row) === 0) {\n applied = true;\n yieldNode = false;\n }\n break;\n }\n case 'remove': {\n if (schema.compareRows(overlay.node.row, node.row) < 0) {\n applied = true;\n yield overlay.node;\n }\n break;\n }\n case 'edit': {\n if (\n !editOldApplied &&\n schema.compareRows(overlay.oldNode.row, node.row) < 0\n ) {\n editOldApplied = true;\n if (editNewApplied) {\n applied = true;\n }\n yield overlay.oldNode;\n }\n if (\n !editNewApplied &&\n schema.compareRows(overlay.node.row, node.row) === 0\n ) {\n editNewApplied = true;\n if (editOldApplied) {\n applied = true;\n }\n yieldNode = false;\n }\n break;\n }\n case 'child': {\n if (schema.compareRows(overlay.node.row, node.row) === 0) {\n applied = true;\n yield {\n row: node.row,\n relationships: {\n ...node.relationships,\n [overlay.child.relationshipName]: () =>\n generateWithOverlay(\n node.relationships[overlay.child.relationshipName](),\n overlay.child.change,\n schema.relationships[overlay.child.relationshipName],\n ),\n },\n };\n yieldNode = false;\n }\n break;\n }\n }\n }\n if (yieldNode) {\n yield node;\n }\n }\n if (!applied) {\n if (overlay.type === 'remove') {\n applied = true;\n yield overlay.node;\n } else if (overlay.type === 'edit') {\n assert(\n editNewApplied,\n 'edit overlay: new node must be applied before old node',\n );\n editOldApplied = true;\n applied = true;\n yield overlay.oldNode;\n }\n }\n\n assert(\n applied,\n 'overlayGenerator: overlay was never applied to any fetched node',\n );\n}\n\nexport function rowEqualsForCompoundKey(\n a: Row,\n b: Row,\n key: CompoundKey,\n): boolean {\n for (let i = 0; i < key.length; i++) {\n if (compareValues(a[key[i]], b[key[i]]) !== 0) {\n return false;\n }\n }\n return true;\n}\n\nexport function isJoinMatch(\n parent: Row,\n parentKey: CompoundKey,\n child: Row,\n childKey: CompoundKey,\n) {\n for (let i = 0; i < parentKey.length; i++) {\n if (!valuesEqual(parent[parentKey[i]], child[childKey[i]])) {\n return false;\n }\n }\n return true;\n}\n\n/**\n * Builds a constraint object by mapping values from `sourceRow` using `sourceKey`\n * to keys specified by `targetKey`. Returns `undefined` if any source value is `null`,\n * since null foreign keys cannot match any rows.\n */\nexport function buildJoinConstraint(\n sourceRow: Row,\n sourceKey: CompoundKey,\n targetKey: CompoundKey,\n): Record<string, Value> | undefined {\n const constraint: Record<string, Value> = {};\n for (let i = 0; i < targetKey.length; i++) {\n const value = sourceRow[sourceKey[i]];\n if (value === null) {\n return undefined;\n }\n constraint[targetKey[i]] = value;\n }\n return constraint;\n}\n"],"mappings":";;;AAaA,SAAgB,2BACd,QACA,SACA,QACc;AACd,QAAO,oBAAoB,QAAQ,SAAS,OAAO;;AAGrD,UAAiB,oBACf,QACA,SACA,QACwB;CACxB,IAAI,UAAU;CACd,IAAI,iBAAiB;CACrB,IAAI,iBAAiB;AACrB,MAAK,MAAM,QAAQ,QAAQ;AACzB,MAAI,SAAS,SAAS;AACpB,SAAM;AACN;;EAEF,IAAI,YAAY;AAChB,MAAI,CAAC,QACH,SAAQ,QAAQ,MAAhB;GACE,KAAK;AACH,QAAI,OAAO,YAAY,QAAQ,KAAK,KAAK,KAAK,IAAI,KAAK,GAAG;AACxD,eAAU;AACV,iBAAY;;AAEd;GAEF,KAAK;AACH,QAAI,OAAO,YAAY,QAAQ,KAAK,KAAK,KAAK,IAAI,GAAG,GAAG;AACtD,eAAU;AACV,WAAM,QAAQ;;AAEhB;GAEF,KAAK;AACH,QACE,CAAC,kBACD,OAAO,YAAY,QAAQ,QAAQ,KAAK,KAAK,IAAI,GAAG,GACpD;AACA,sBAAiB;AACjB,SAAI,eACF,WAAU;AAEZ,WAAM,QAAQ;;AAEhB,QACE,CAAC,kBACD,OAAO,YAAY,QAAQ,KAAK,KAAK,KAAK,IAAI,KAAK,GACnD;AACA,sBAAiB;AACjB,SAAI,eACF,WAAU;AAEZ,iBAAY;;AAEd;GAEF,KAAK;AACH,QAAI,OAAO,YAAY,QAAQ,KAAK,KAAK,KAAK,IAAI,KAAK,GAAG;AACxD,eAAU;AACV,WAAM;MACJ,KAAK,KAAK;MACV,eAAe;OACb,GAAG,KAAK;QACP,QAAQ,MAAM,yBACb,oBACE,KAAK,cAAc,QAAQ,MAAM,mBAAmB,EACpD,QAAQ,MAAM,QACd,OAAO,cAAc,QAAQ,MAAM,kBACpC;OACJ;MACF;AACD,iBAAY;;AAEd;;AAIN,MAAI,UACF,OAAM;;AAGV,KAAI,CAAC;MACC,QAAQ,SAAS,UAAU;AAC7B,aAAU;AACV,SAAM,QAAQ;aACL,QAAQ,SAAS,QAAQ;AAClC,UACE,gBACA,yDACD;AACD,oBAAiB;AACjB,aAAU;AACV,SAAM,QAAQ;;;AAIlB,QACE,SACA,kEACD;;AAGH,SAAgB,wBACd,GACA,GACA,KACS;AACT,MAAK,IAAI,IAAI,GAAG,IAAI,IAAI,QAAQ,IAC9B,KAAI,cAAc,EAAE,IAAI,KAAK,EAAE,IAAI,IAAI,KAAK,EAC1C,QAAO;AAGX,QAAO;;AAGT,SAAgB,YACd,QACA,WACA,OACA,UACA;AACA,MAAK,IAAI,IAAI,GAAG,IAAI,UAAU,QAAQ,IACpC,KAAI,CAAC,YAAY,OAAO,UAAU,KAAK,MAAM,SAAS,IAAI,CACxD,QAAO;AAGX,QAAO;;;;;;;AAQT,SAAgB,oBACd,WACA,WACA,WACmC;CACnC,MAAM,aAAoC,EAAE;AAC5C,MAAK,IAAI,IAAI,GAAG,IAAI,UAAU,QAAQ,KAAK;EACzC,MAAM,QAAQ,UAAU,UAAU;AAClC,MAAI,UAAU,KACZ;AAEF,aAAW,UAAU,MAAM;;AAE7B,QAAO"}
|
|
1
|
+
{"version":3,"file":"join-utils.js","names":[],"sources":["../../../../../zql/src/ivm/join-utils.ts"],"sourcesContent":["import type {Row, Value} from '../../../zero-protocol/src/data.ts';\nimport type {Change} from './change.ts';\nimport type {SourceSchema} from './schema.ts';\nimport type {Stream} from './stream.ts';\nimport {compareValues, valuesEqual, type Node} from './data.ts';\nimport {assert} from '../../../shared/src/asserts.ts';\nimport type {CompoundKey} from '../../../zero-protocol/src/ast.ts';\n\nexport type JoinChangeOverlay = {\n change: Change;\n position: Row | undefined;\n};\n\nexport function generateWithOverlayNoYield(\n stream: Stream<Node>,\n overlay: Change,\n schema: SourceSchema,\n): Stream<Node> {\n return generateWithOverlay(stream, overlay, schema) as Stream<Node>;\n}\n\nexport function* generateWithOverlay(\n stream: Stream<Node | 'yield'>,\n overlay: Change,\n schema: SourceSchema,\n): Stream<Node | 'yield'> {\n let applied = false;\n let editOldApplied = false;\n let editNewApplied = false;\n for (const node of stream) {\n if (node === 'yield') {\n yield node;\n continue;\n }\n let yieldNode = true;\n if (!applied) {\n switch (overlay.type) {\n case 'add': {\n if (schema.compareRows(overlay.node.row, node.row) === 0) {\n applied = true;\n yieldNode = false;\n }\n break;\n }\n case 'remove': {\n if (schema.compareRows(overlay.node.row, node.row) < 0) {\n applied = true;\n yield overlay.node;\n }\n break;\n }\n case 'edit': {\n if (\n !editOldApplied &&\n schema.compareRows(overlay.oldNode.row, node.row) < 0\n ) {\n editOldApplied = true;\n if (editNewApplied) {\n applied = true;\n }\n yield overlay.oldNode;\n }\n if (\n !editNewApplied &&\n schema.compareRows(overlay.node.row, node.row) === 0\n ) {\n editNewApplied = true;\n if (editOldApplied) {\n applied = true;\n }\n yieldNode = false;\n }\n break;\n }\n case 'child': {\n if (schema.compareRows(overlay.node.row, node.row) === 0) {\n applied = true;\n yield {\n row: node.row,\n relationships: {\n ...node.relationships,\n [overlay.child.relationshipName]: () =>\n generateWithOverlay(\n node.relationships[overlay.child.relationshipName](),\n overlay.child.change,\n schema.relationships[overlay.child.relationshipName],\n ),\n },\n };\n yieldNode = false;\n }\n break;\n }\n }\n }\n if (yieldNode) {\n yield node;\n }\n }\n if (!applied) {\n if (overlay.type === 'remove') {\n applied = true;\n yield overlay.node;\n } else if (overlay.type === 'edit') {\n assert(\n editNewApplied,\n 'edit overlay: new node must be applied before old node',\n );\n editOldApplied = true;\n applied = true;\n yield overlay.oldNode;\n }\n }\n\n assert(\n applied,\n 'overlayGenerator: overlay was never applied to any fetched node',\n );\n}\n\nexport function generateWithOverlayNoYieldUnordered(\n stream: Stream<Node>,\n overlay: Change,\n schema: SourceSchema,\n): Stream<Node> {\n return generateWithOverlayUnordered(stream, overlay, schema) as Stream<Node>;\n}\n\nexport function* generateWithOverlayUnordered(\n stream: Stream<Node | 'yield'>,\n overlay: Change,\n schema: SourceSchema,\n): Stream<Node | 'yield'> {\n // Eager inject\n if (overlay.type === 'remove') {\n yield overlay.node;\n } else if (overlay.type === 'edit') {\n yield overlay.oldNode;\n }\n\n // Stream with inline suppress\n let suppressed = false;\n for (const node of stream) {\n if (node === 'yield') {\n yield node;\n continue;\n }\n if (!suppressed) {\n if (overlay.type === 'add' || overlay.type === 'edit') {\n if (\n rowEqualsForCompoundKey(overlay.node.row, node.row, schema.primaryKey)\n ) {\n suppressed = true;\n continue;\n }\n }\n if (overlay.type === 'child') {\n if (\n rowEqualsForCompoundKey(overlay.node.row, node.row, schema.primaryKey)\n ) {\n suppressed = true;\n yield {\n row: node.row,\n relationships: {\n ...node.relationships,\n [overlay.child.relationshipName]: () =>\n generateWithOverlay(\n node.relationships[overlay.child.relationshipName](),\n overlay.child.change,\n schema.relationships[overlay.child.relationshipName],\n ),\n },\n };\n continue;\n }\n }\n }\n yield node;\n }\n assert(\n suppressed || overlay.type === 'remove',\n 'overlayGenerator: overlay was never applied to any fetched node',\n );\n}\n\nexport function rowEqualsForCompoundKey(\n a: Row,\n b: Row,\n key: CompoundKey,\n): boolean {\n for (let i = 0; i < key.length; i++) {\n if (compareValues(a[key[i]], b[key[i]]) !== 0) {\n return false;\n }\n }\n return true;\n}\n\nexport function isJoinMatch(\n parent: Row,\n parentKey: CompoundKey,\n child: Row,\n childKey: CompoundKey,\n) {\n for (let i = 0; i < parentKey.length; i++) {\n if (!valuesEqual(parent[parentKey[i]], child[childKey[i]])) {\n return false;\n }\n }\n return true;\n}\n\n/**\n * Builds a constraint object by mapping values from `sourceRow` using `sourceKey`\n * to keys specified by `targetKey`. Returns `undefined` if any source value is `null`,\n * since null foreign keys cannot match any rows.\n */\nexport function buildJoinConstraint(\n sourceRow: Row,\n sourceKey: CompoundKey,\n targetKey: CompoundKey,\n): Record<string, Value> | undefined {\n const constraint: Record<string, Value> = {};\n for (let i = 0; i < targetKey.length; i++) {\n const value = sourceRow[sourceKey[i]];\n if (value === null) {\n return undefined;\n }\n constraint[targetKey[i]] = value;\n }\n return constraint;\n}\n"],"mappings":";;;AAaA,SAAgB,2BACd,QACA,SACA,QACc;AACd,QAAO,oBAAoB,QAAQ,SAAS,OAAO;;AAGrD,UAAiB,oBACf,QACA,SACA,QACwB;CACxB,IAAI,UAAU;CACd,IAAI,iBAAiB;CACrB,IAAI,iBAAiB;AACrB,MAAK,MAAM,QAAQ,QAAQ;AACzB,MAAI,SAAS,SAAS;AACpB,SAAM;AACN;;EAEF,IAAI,YAAY;AAChB,MAAI,CAAC,QACH,SAAQ,QAAQ,MAAhB;GACE,KAAK;AACH,QAAI,OAAO,YAAY,QAAQ,KAAK,KAAK,KAAK,IAAI,KAAK,GAAG;AACxD,eAAU;AACV,iBAAY;;AAEd;GAEF,KAAK;AACH,QAAI,OAAO,YAAY,QAAQ,KAAK,KAAK,KAAK,IAAI,GAAG,GAAG;AACtD,eAAU;AACV,WAAM,QAAQ;;AAEhB;GAEF,KAAK;AACH,QACE,CAAC,kBACD,OAAO,YAAY,QAAQ,QAAQ,KAAK,KAAK,IAAI,GAAG,GACpD;AACA,sBAAiB;AACjB,SAAI,eACF,WAAU;AAEZ,WAAM,QAAQ;;AAEhB,QACE,CAAC,kBACD,OAAO,YAAY,QAAQ,KAAK,KAAK,KAAK,IAAI,KAAK,GACnD;AACA,sBAAiB;AACjB,SAAI,eACF,WAAU;AAEZ,iBAAY;;AAEd;GAEF,KAAK;AACH,QAAI,OAAO,YAAY,QAAQ,KAAK,KAAK,KAAK,IAAI,KAAK,GAAG;AACxD,eAAU;AACV,WAAM;MACJ,KAAK,KAAK;MACV,eAAe;OACb,GAAG,KAAK;QACP,QAAQ,MAAM,yBACb,oBACE,KAAK,cAAc,QAAQ,MAAM,mBAAmB,EACpD,QAAQ,MAAM,QACd,OAAO,cAAc,QAAQ,MAAM,kBACpC;OACJ;MACF;AACD,iBAAY;;AAEd;;AAIN,MAAI,UACF,OAAM;;AAGV,KAAI,CAAC;MACC,QAAQ,SAAS,UAAU;AAC7B,aAAU;AACV,SAAM,QAAQ;aACL,QAAQ,SAAS,QAAQ;AAClC,UACE,gBACA,yDACD;AACD,oBAAiB;AACjB,aAAU;AACV,SAAM,QAAQ;;;AAIlB,QACE,SACA,kEACD;;AAWH,UAAiB,6BACf,QACA,SACA,QACwB;AAExB,KAAI,QAAQ,SAAS,SACnB,OAAM,QAAQ;UACL,QAAQ,SAAS,OAC1B,OAAM,QAAQ;CAIhB,IAAI,aAAa;AACjB,MAAK,MAAM,QAAQ,QAAQ;AACzB,MAAI,SAAS,SAAS;AACpB,SAAM;AACN;;AAEF,MAAI,CAAC,YAAY;AACf,OAAI,QAAQ,SAAS,SAAS,QAAQ,SAAS;QAE3C,wBAAwB,QAAQ,KAAK,KAAK,KAAK,KAAK,OAAO,WAAW,EACtE;AACA,kBAAa;AACb;;;AAGJ,OAAI,QAAQ,SAAS;QAEjB,wBAAwB,QAAQ,KAAK,KAAK,KAAK,KAAK,OAAO,WAAW,EACtE;AACA,kBAAa;AACb,WAAM;MACJ,KAAK,KAAK;MACV,eAAe;OACb,GAAG,KAAK;QACP,QAAQ,MAAM,yBACb,oBACE,KAAK,cAAc,QAAQ,MAAM,mBAAmB,EACpD,QAAQ,MAAM,QACd,OAAO,cAAc,QAAQ,MAAM,kBACpC;OACJ;MACF;AACD;;;;AAIN,QAAM;;AAER,QACE,cAAc,QAAQ,SAAS,UAC/B,kEACD;;AAGH,SAAgB,wBACd,GACA,GACA,KACS;AACT,MAAK,IAAI,IAAI,GAAG,IAAI,IAAI,QAAQ,IAC9B,KAAI,cAAc,EAAE,IAAI,KAAK,EAAE,IAAI,IAAI,KAAK,EAC1C,QAAO;AAGX,QAAO;;AAGT,SAAgB,YACd,QACA,WACA,OACA,UACA;AACA,MAAK,IAAI,IAAI,GAAG,IAAI,UAAU,QAAQ,IACpC,KAAI,CAAC,YAAY,OAAO,UAAU,KAAK,MAAM,SAAS,IAAI,CACxD,QAAO;AAGX,QAAO;;;;;;;AAQT,SAAgB,oBACd,WACA,WACA,WACmC;CACnC,MAAM,aAAoC,EAAE;AAC5C,MAAK,IAAI,IAAI,GAAG,IAAI,UAAU,QAAQ,KAAK;EACzC,MAAM,QAAQ,UAAU,UAAU;AAClC,MAAI,UAAU,KACZ;AAEF,aAAW,UAAU,MAAM;;AAE7B,QAAO"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"join.d.ts","sourceRoot":"","sources":["../../../../../zql/src/ivm/join.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAC,WAAW,EAAE,MAAM,EAAC,MAAM,mCAAmC,CAAC;AAG3E,OAAO,KAAK,EAAC,IAAI,EAAC,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"join.d.ts","sourceRoot":"","sources":["../../../../../zql/src/ivm/join.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAC,WAAW,EAAE,MAAM,EAAC,MAAM,mCAAmC,CAAC;AAG3E,OAAO,KAAK,EAAC,IAAI,EAAC,MAAM,WAAW,CAAC;AASpC,OAAO,EAEL,KAAK,YAAY,EACjB,KAAK,KAAK,EACV,KAAK,MAAM,EACZ,MAAM,eAAe,CAAC;AACvB,OAAO,KAAK,EAAC,YAAY,EAAC,MAAM,aAAa,CAAC;AAC9C,OAAO,EAAC,KAAK,MAAM,EAAC,MAAM,aAAa,CAAC;AAExC,KAAK,IAAI,GAAG;IACV,MAAM,EAAE,KAAK,CAAC;IACd,KAAK,EAAE,KAAK,CAAC;IAEb,SAAS,EAAE,WAAW,CAAC;IACvB,QAAQ,EAAE,WAAW,CAAC;IACtB,gBAAgB,EAAE,MAAM,CAAC;IACzB,MAAM,EAAE,OAAO,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF;;;;;;;;;GASG;AACH,qBAAa,IAAK,YAAW,KAAK;;gBAYpB,EACV,MAAM,EACN,KAAK,EACL,SAAS,EACT,QAAQ,EACR,gBAAgB,EAChB,MAAM,EACN,MAAM,GACP,EAAE,IAAI;IAkCP,OAAO,IAAI,IAAI;IAKf,SAAS,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI;IAI/B,SAAS,IAAI,YAAY;IAIxB,KAAK,CAAC,GAAG,EAAE,YAAY,GAAG,MAAM,CAAC,IAAI,GAAG,OAAO,CAAC;CAkMlD"}
|
package/out/zql/src/ivm/join.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { assert, unreachable } from "../../../shared/src/asserts.js";
|
|
2
2
|
import { throwOutput } from "./operator.js";
|
|
3
|
-
import { buildJoinConstraint, generateWithOverlay, isJoinMatch, rowEqualsForCompoundKey } from "./join-utils.js";
|
|
3
|
+
import { buildJoinConstraint, generateWithOverlay, generateWithOverlayUnordered, isJoinMatch, rowEqualsForCompoundKey } from "./join-utils.js";
|
|
4
4
|
//#region ../zql/src/ivm/join.ts
|
|
5
5
|
/**
|
|
6
6
|
* The Join operator joins the output from two upstream inputs. Zero's join
|
|
@@ -147,7 +147,11 @@ var Join = class {
|
|
|
147
147
|
const childStream = () => {
|
|
148
148
|
const constraint = buildJoinConstraint(parentNodeRow, this.#parentKey, this.#childKey);
|
|
149
149
|
const stream = constraint ? this.#child.fetch({ constraint }) : [];
|
|
150
|
-
if (this.#inprogressChildChange && isJoinMatch(parentNodeRow, this.#parentKey, this.#inprogressChildChange.change.node.row, this.#childKey) && this.#inprogressChildChange.position && this.#schema.compareRows(parentNodeRow, this.#inprogressChildChange.position) > 0)
|
|
150
|
+
if (this.#inprogressChildChange && isJoinMatch(parentNodeRow, this.#parentKey, this.#inprogressChildChange.change.node.row, this.#childKey) && this.#inprogressChildChange.position && this.#schema.compareRows(parentNodeRow, this.#inprogressChildChange.position) > 0) {
|
|
151
|
+
const childSchema = this.#child.getSchema();
|
|
152
|
+
if (childSchema.sort === void 0) return generateWithOverlayUnordered(stream, this.#inprogressChildChange.change, childSchema);
|
|
153
|
+
return generateWithOverlay(stream, this.#inprogressChildChange.change, childSchema);
|
|
154
|
+
}
|
|
151
155
|
return stream;
|
|
152
156
|
};
|
|
153
157
|
return {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"join.js","names":["#parent","#child","#parentKey","#childKey","#relationshipName","#schema","#pushParent","#pushChild","#output","#processParentNode","#pushChildChange","#inprogressChildChange"],"sources":["../../../../../zql/src/ivm/join.ts"],"sourcesContent":["import {assert, unreachable} from '../../../shared/src/asserts.ts';\nimport type {CompoundKey, System} from '../../../zero-protocol/src/ast.ts';\nimport type {Row} from '../../../zero-protocol/src/data.ts';\nimport type {Change, ChildChange} from './change.ts';\nimport type {Node} from './data.ts';\nimport {\n buildJoinConstraint,\n generateWithOverlay,\n isJoinMatch,\n rowEqualsForCompoundKey,\n type JoinChangeOverlay,\n} from './join-utils.ts';\nimport {\n throwOutput,\n type FetchRequest,\n type Input,\n type Output,\n} from './operator.ts';\nimport type {SourceSchema} from './schema.ts';\nimport {type Stream} from './stream.ts';\n\ntype Args = {\n parent: Input;\n child: Input;\n // The nth key in parentKey corresponds to the nth key in childKey.\n parentKey: CompoundKey;\n childKey: CompoundKey;\n relationshipName: string;\n hidden: boolean;\n system: System;\n};\n\n/**\n * The Join operator joins the output from two upstream inputs. Zero's join\n * is a little different from SQL's join in that we output hierarchical data,\n * not a flat table. This makes it a lot more useful for UI programming and\n * avoids duplicating tons of data like left join would.\n *\n * The Nodes output from Join have a new relationship added to them, which has\n * the name #relationshipName. The value of the relationship is a stream of\n * child nodes which are the corresponding values from the child source.\n */\nexport class Join implements Input {\n readonly #parent: Input;\n readonly #child: Input;\n readonly #parentKey: CompoundKey;\n readonly #childKey: CompoundKey;\n readonly #relationshipName: string;\n readonly #schema: SourceSchema;\n\n #output: Output = throwOutput;\n\n #inprogressChildChange: JoinChangeOverlay | undefined;\n\n constructor({\n parent,\n child,\n parentKey,\n childKey,\n relationshipName,\n hidden,\n system,\n }: Args) {\n assert(parent !== child, 'Parent and child must be different operators');\n assert(\n parentKey.length === childKey.length,\n 'The parentKey and childKey keys must have same length',\n );\n this.#parent = parent;\n this.#child = child;\n this.#parentKey = parentKey;\n this.#childKey = childKey;\n this.#relationshipName = relationshipName;\n\n const parentSchema = parent.getSchema();\n const childSchema = child.getSchema();\n this.#schema = {\n ...parentSchema,\n relationships: {\n ...parentSchema.relationships,\n [relationshipName]: {\n ...childSchema,\n isHidden: hidden,\n system,\n },\n },\n };\n\n parent.setOutput({\n push: (change: Change) => this.#pushParent(change),\n });\n child.setOutput({\n push: (change: Change) => this.#pushChild(change),\n });\n }\n\n destroy(): void {\n this.#parent.destroy();\n this.#child.destroy();\n }\n\n setOutput(output: Output): void {\n this.#output = output;\n }\n\n getSchema(): SourceSchema {\n return this.#schema;\n }\n\n *fetch(req: FetchRequest): Stream<Node | 'yield'> {\n for (const parentNode of this.#parent.fetch(req)) {\n if (parentNode === 'yield') {\n yield parentNode;\n continue;\n }\n yield this.#processParentNode(parentNode.row, parentNode.relationships);\n }\n }\n\n *#pushParent(change: Change): Stream<'yield'> {\n switch (change.type) {\n case 'add':\n yield* this.#output.push(\n {\n type: 'add',\n node: this.#processParentNode(\n change.node.row,\n change.node.relationships,\n ),\n },\n this,\n );\n break;\n case 'remove':\n yield* this.#output.push(\n {\n type: 'remove',\n node: this.#processParentNode(\n change.node.row,\n change.node.relationships,\n ),\n },\n this,\n );\n break;\n case 'child':\n yield* this.#output.push(\n {\n type: 'child',\n node: this.#processParentNode(\n change.node.row,\n change.node.relationships,\n ),\n child: change.child,\n },\n this,\n );\n break;\n case 'edit': {\n // Assert the edit could not change the relationship.\n assert(\n rowEqualsForCompoundKey(\n change.oldNode.row,\n change.node.row,\n this.#parentKey,\n ),\n `Parent edit must not change relationship.`,\n );\n yield* this.#output.push(\n {\n type: 'edit',\n oldNode: this.#processParentNode(\n change.oldNode.row,\n change.oldNode.relationships,\n ),\n node: this.#processParentNode(\n change.node.row,\n change.node.relationships,\n ),\n },\n this,\n );\n break;\n }\n default:\n unreachable(change);\n }\n }\n\n *#pushChild(change: Change): Stream<'yield'> {\n switch (change.type) {\n case 'add':\n case 'remove':\n yield* this.#pushChildChange(change.node.row, change);\n break;\n case 'child':\n yield* this.#pushChildChange(change.node.row, change);\n break;\n case 'edit': {\n const childRow = change.node.row;\n const oldChildRow = change.oldNode.row;\n // Assert the edit could not change the relationship.\n assert(\n rowEqualsForCompoundKey(oldChildRow, childRow, this.#childKey),\n 'Child edit must not change relationship.',\n );\n yield* this.#pushChildChange(childRow, change);\n break;\n }\n\n default:\n unreachable(change);\n }\n }\n\n *#pushChildChange(childRow: Row, change: Change): Stream<'yield'> {\n this.#inprogressChildChange = {\n change,\n position: undefined,\n };\n try {\n const constraint = buildJoinConstraint(\n childRow,\n this.#childKey,\n this.#parentKey,\n );\n const parentNodes = constraint ? this.#parent.fetch({constraint}) : [];\n\n for (const parentNode of parentNodes) {\n if (parentNode === 'yield') {\n yield parentNode;\n continue;\n }\n this.#inprogressChildChange.position = parentNode.row;\n const childChange: ChildChange = {\n type: 'child',\n node: this.#processParentNode(\n parentNode.row,\n parentNode.relationships,\n ),\n child: {\n relationshipName: this.#relationshipName,\n change,\n },\n };\n yield* this.#output.push(childChange, this);\n }\n } finally {\n this.#inprogressChildChange = undefined;\n }\n }\n\n #processParentNode(\n parentNodeRow: Row,\n parentNodeRelations: Record<string, () => Stream<Node | 'yield'>>,\n ): Node {\n const childStream = () => {\n const constraint = buildJoinConstraint(\n parentNodeRow,\n this.#parentKey,\n this.#childKey,\n );\n const stream = constraint ? this.#child.fetch({constraint}) : [];\n\n if (\n this.#inprogressChildChange &&\n isJoinMatch(\n parentNodeRow,\n this.#parentKey,\n this.#inprogressChildChange.change.node.row,\n this.#childKey,\n ) &&\n this.#inprogressChildChange.position &&\n this.#schema.compareRows(\n parentNodeRow,\n this.#inprogressChildChange.position,\n ) > 0\n ) {\n return generateWithOverlay(\n stream,\n this.#inprogressChildChange.change,\n this.#child.getSchema(),\n );\n }\n return stream;\n };\n\n return {\n row: parentNodeRow,\n relationships: {\n ...parentNodeRelations,\n [this.#relationshipName]: childStream,\n },\n };\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"join.js","names":["#parent","#child","#parentKey","#childKey","#relationshipName","#schema","#pushParent","#pushChild","#output","#processParentNode","#pushChildChange","#inprogressChildChange"],"sources":["../../../../../zql/src/ivm/join.ts"],"sourcesContent":["import {assert, unreachable} from '../../../shared/src/asserts.ts';\nimport type {CompoundKey, System} from '../../../zero-protocol/src/ast.ts';\nimport type {Row} from '../../../zero-protocol/src/data.ts';\nimport type {Change, ChildChange} from './change.ts';\nimport type {Node} from './data.ts';\nimport {\n buildJoinConstraint,\n generateWithOverlay,\n generateWithOverlayUnordered,\n isJoinMatch,\n rowEqualsForCompoundKey,\n type JoinChangeOverlay,\n} from './join-utils.ts';\nimport {\n throwOutput,\n type FetchRequest,\n type Input,\n type Output,\n} from './operator.ts';\nimport type {SourceSchema} from './schema.ts';\nimport {type Stream} from './stream.ts';\n\ntype Args = {\n parent: Input;\n child: Input;\n // The nth key in parentKey corresponds to the nth key in childKey.\n parentKey: CompoundKey;\n childKey: CompoundKey;\n relationshipName: string;\n hidden: boolean;\n system: System;\n};\n\n/**\n * The Join operator joins the output from two upstream inputs. Zero's join\n * is a little different from SQL's join in that we output hierarchical data,\n * not a flat table. This makes it a lot more useful for UI programming and\n * avoids duplicating tons of data like left join would.\n *\n * The Nodes output from Join have a new relationship added to them, which has\n * the name #relationshipName. The value of the relationship is a stream of\n * child nodes which are the corresponding values from the child source.\n */\nexport class Join implements Input {\n readonly #parent: Input;\n readonly #child: Input;\n readonly #parentKey: CompoundKey;\n readonly #childKey: CompoundKey;\n readonly #relationshipName: string;\n readonly #schema: SourceSchema;\n\n #output: Output = throwOutput;\n\n #inprogressChildChange: JoinChangeOverlay | undefined;\n\n constructor({\n parent,\n child,\n parentKey,\n childKey,\n relationshipName,\n hidden,\n system,\n }: Args) {\n assert(parent !== child, 'Parent and child must be different operators');\n assert(\n parentKey.length === childKey.length,\n 'The parentKey and childKey keys must have same length',\n );\n this.#parent = parent;\n this.#child = child;\n this.#parentKey = parentKey;\n this.#childKey = childKey;\n this.#relationshipName = relationshipName;\n\n const parentSchema = parent.getSchema();\n const childSchema = child.getSchema();\n this.#schema = {\n ...parentSchema,\n relationships: {\n ...parentSchema.relationships,\n [relationshipName]: {\n ...childSchema,\n isHidden: hidden,\n system,\n },\n },\n };\n\n parent.setOutput({\n push: (change: Change) => this.#pushParent(change),\n });\n child.setOutput({\n push: (change: Change) => this.#pushChild(change),\n });\n }\n\n destroy(): void {\n this.#parent.destroy();\n this.#child.destroy();\n }\n\n setOutput(output: Output): void {\n this.#output = output;\n }\n\n getSchema(): SourceSchema {\n return this.#schema;\n }\n\n *fetch(req: FetchRequest): Stream<Node | 'yield'> {\n for (const parentNode of this.#parent.fetch(req)) {\n if (parentNode === 'yield') {\n yield parentNode;\n continue;\n }\n yield this.#processParentNode(parentNode.row, parentNode.relationships);\n }\n }\n\n *#pushParent(change: Change): Stream<'yield'> {\n switch (change.type) {\n case 'add':\n yield* this.#output.push(\n {\n type: 'add',\n node: this.#processParentNode(\n change.node.row,\n change.node.relationships,\n ),\n },\n this,\n );\n break;\n case 'remove':\n yield* this.#output.push(\n {\n type: 'remove',\n node: this.#processParentNode(\n change.node.row,\n change.node.relationships,\n ),\n },\n this,\n );\n break;\n case 'child':\n yield* this.#output.push(\n {\n type: 'child',\n node: this.#processParentNode(\n change.node.row,\n change.node.relationships,\n ),\n child: change.child,\n },\n this,\n );\n break;\n case 'edit': {\n // Assert the edit could not change the relationship.\n assert(\n rowEqualsForCompoundKey(\n change.oldNode.row,\n change.node.row,\n this.#parentKey,\n ),\n `Parent edit must not change relationship.`,\n );\n yield* this.#output.push(\n {\n type: 'edit',\n oldNode: this.#processParentNode(\n change.oldNode.row,\n change.oldNode.relationships,\n ),\n node: this.#processParentNode(\n change.node.row,\n change.node.relationships,\n ),\n },\n this,\n );\n break;\n }\n default:\n unreachable(change);\n }\n }\n\n *#pushChild(change: Change): Stream<'yield'> {\n switch (change.type) {\n case 'add':\n case 'remove':\n yield* this.#pushChildChange(change.node.row, change);\n break;\n case 'child':\n yield* this.#pushChildChange(change.node.row, change);\n break;\n case 'edit': {\n const childRow = change.node.row;\n const oldChildRow = change.oldNode.row;\n // Assert the edit could not change the relationship.\n assert(\n rowEqualsForCompoundKey(oldChildRow, childRow, this.#childKey),\n 'Child edit must not change relationship.',\n );\n yield* this.#pushChildChange(childRow, change);\n break;\n }\n\n default:\n unreachable(change);\n }\n }\n\n *#pushChildChange(childRow: Row, change: Change): Stream<'yield'> {\n this.#inprogressChildChange = {\n change,\n position: undefined,\n };\n try {\n const constraint = buildJoinConstraint(\n childRow,\n this.#childKey,\n this.#parentKey,\n );\n const parentNodes = constraint ? this.#parent.fetch({constraint}) : [];\n\n for (const parentNode of parentNodes) {\n if (parentNode === 'yield') {\n yield parentNode;\n continue;\n }\n this.#inprogressChildChange.position = parentNode.row;\n const childChange: ChildChange = {\n type: 'child',\n node: this.#processParentNode(\n parentNode.row,\n parentNode.relationships,\n ),\n child: {\n relationshipName: this.#relationshipName,\n change,\n },\n };\n yield* this.#output.push(childChange, this);\n }\n } finally {\n this.#inprogressChildChange = undefined;\n }\n }\n\n #processParentNode(\n parentNodeRow: Row,\n parentNodeRelations: Record<string, () => Stream<Node | 'yield'>>,\n ): Node {\n const childStream = () => {\n const constraint = buildJoinConstraint(\n parentNodeRow,\n this.#parentKey,\n this.#childKey,\n );\n const stream = constraint ? this.#child.fetch({constraint}) : [];\n\n if (\n this.#inprogressChildChange &&\n isJoinMatch(\n parentNodeRow,\n this.#parentKey,\n this.#inprogressChildChange.change.node.row,\n this.#childKey,\n ) &&\n this.#inprogressChildChange.position &&\n this.#schema.compareRows(\n parentNodeRow,\n this.#inprogressChildChange.position,\n ) > 0\n ) {\n const childSchema = this.#child.getSchema();\n if (childSchema.sort === undefined) {\n return generateWithOverlayUnordered(\n stream,\n this.#inprogressChildChange.change,\n childSchema,\n );\n }\n return generateWithOverlay(\n stream,\n this.#inprogressChildChange.change,\n childSchema,\n );\n }\n return stream;\n };\n\n return {\n row: parentNodeRow,\n relationships: {\n ...parentNodeRelations,\n [this.#relationshipName]: childStream,\n },\n };\n }\n}\n"],"mappings":"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"}
|
|
@@ -21,7 +21,7 @@ export type Overlays = {
|
|
|
21
21
|
export type Connection = {
|
|
22
22
|
input: Input;
|
|
23
23
|
output: Output | undefined;
|
|
24
|
-
sort
|
|
24
|
+
sort?: Ordering | undefined;
|
|
25
25
|
splitEditKeys: Set<string> | undefined;
|
|
26
26
|
compareRows: Comparator;
|
|
27
27
|
filters: {
|
|
@@ -48,7 +48,7 @@ export declare class MemorySource implements Source {
|
|
|
48
48
|
};
|
|
49
49
|
fork(): MemorySource;
|
|
50
50
|
get data(): BTreeSet<Row>;
|
|
51
|
-
connect(sort: Ordering, filters?: Condition, splitEditKeys?: Set<string>): SourceInput;
|
|
51
|
+
connect(sort: Ordering | undefined, filters?: Condition, splitEditKeys?: Set<string>): SourceInput;
|
|
52
52
|
getIndexKeys(): string[];
|
|
53
53
|
push(change: SourceChange): Stream<'yield'>;
|
|
54
54
|
genPush(change: SourceChange): Generator<"yield" | undefined, void, unknown>;
|
|
@@ -79,5 +79,18 @@ export declare function generateWithOverlayInner(rowIterator: Iterable<Row>, ove
|
|
|
79
79
|
row: Readonly<Record<string, import("../../../shared/src/json.ts").ReadonlyJSONValue | undefined>>;
|
|
80
80
|
relationships: {};
|
|
81
81
|
}, void, unknown>;
|
|
82
|
+
/**
|
|
83
|
+
* Like {@link generateWithOverlay} but for unordered streams.
|
|
84
|
+
* No `startAt` or comparator needed. Injects remove/old-edit rows eagerly
|
|
85
|
+
* at the start, and suppresses add/new-edit rows inline by PK match.
|
|
86
|
+
*/
|
|
87
|
+
export declare function generateWithOverlayUnordered(rows: Iterable<Row>, constraint: Constraint | undefined, overlay: Overlay | undefined, lastPushedEpoch: number, primaryKey: PrimaryKey, filterPredicate?: ((row: Row) => boolean) | undefined): Generator<{
|
|
88
|
+
row: Readonly<Record<string, import("../../../shared/src/json.ts").ReadonlyJSONValue | undefined>>;
|
|
89
|
+
relationships: {};
|
|
90
|
+
}, void, unknown>;
|
|
91
|
+
export declare function generateWithOverlayInnerUnordered(rowIterator: Iterable<Row>, overlays: Overlays, primaryKey: PrimaryKey): Generator<{
|
|
92
|
+
row: Readonly<Record<string, import("../../../shared/src/json.ts").ReadonlyJSONValue | undefined>>;
|
|
93
|
+
relationships: {};
|
|
94
|
+
}, void, unknown>;
|
|
82
95
|
export declare function stringify(change: SourceChange): string;
|
|
83
96
|
//# sourceMappingURL=memory-source.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"memory-source.d.ts","sourceRoot":"","sources":["../../../../../zql/src/ivm/memory-source.ts"],"names":[],"mappings":"AACA,OAAO,EAAC,QAAQ,EAAC,MAAM,kCAAkC,CAAC;
|
|
1
|
+
{"version":3,"file":"memory-source.d.ts","sourceRoot":"","sources":["../../../../../zql/src/ivm/memory-source.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { assert, unreachable } from "../../../shared/src/asserts.js";
|
|
2
2
|
import { hasOwn } from "../../../shared/src/has-own.js";
|
|
3
3
|
import { once } from "../../../shared/src/iterables.js";
|
|
4
|
+
import { must } from "../../../shared/src/must.js";
|
|
4
5
|
import { skipYields } from "./operator.js";
|
|
5
6
|
import { compareValues, makeComparator, valuesEqual } from "./data.js";
|
|
6
7
|
import { filterPush } from "./filter-push.js";
|
|
@@ -51,12 +52,12 @@ var MemorySource = class MemorySource {
|
|
|
51
52
|
get data() {
|
|
52
53
|
return this.#getPrimaryIndex().data;
|
|
53
54
|
}
|
|
54
|
-
#getSchema(connection) {
|
|
55
|
+
#getSchema(connection, unordered) {
|
|
55
56
|
return {
|
|
56
57
|
tableName: this.#tableName,
|
|
57
58
|
columns: this.#columns,
|
|
58
59
|
primaryKey: this.#primaryKey,
|
|
59
|
-
sort: connection.sort,
|
|
60
|
+
sort: unordered ? void 0 : connection.sort,
|
|
60
61
|
system: "client",
|
|
61
62
|
relationships: {},
|
|
62
63
|
isHidden: false,
|
|
@@ -65,6 +66,8 @@ var MemorySource = class MemorySource {
|
|
|
65
66
|
}
|
|
66
67
|
connect(sort, filters, splitEditKeys) {
|
|
67
68
|
const transformedFilters = transformFilters(filters);
|
|
69
|
+
const unordered = sort === void 0;
|
|
70
|
+
const internalSort = sort ?? this.#primaryIndexSort;
|
|
68
71
|
const input = {
|
|
69
72
|
getSchema: () => schema,
|
|
70
73
|
fetch: (req) => this.#fetch(req, connection),
|
|
@@ -79,17 +82,17 @@ var MemorySource = class MemorySource {
|
|
|
79
82
|
const connection = {
|
|
80
83
|
input,
|
|
81
84
|
output: void 0,
|
|
82
|
-
sort,
|
|
85
|
+
sort: internalSort,
|
|
83
86
|
splitEditKeys,
|
|
84
|
-
compareRows: makeComparator(
|
|
87
|
+
compareRows: makeComparator(internalSort),
|
|
85
88
|
filters: transformedFilters.filters ? {
|
|
86
89
|
condition: transformedFilters.filters,
|
|
87
90
|
predicate: createPredicate(transformedFilters.filters)
|
|
88
91
|
} : void 0,
|
|
89
92
|
lastPushedEpoch: 0
|
|
90
93
|
};
|
|
91
|
-
const schema = this.#getSchema(connection);
|
|
92
|
-
assertOrderingIncludesPK(
|
|
94
|
+
const schema = this.#getSchema(connection, unordered);
|
|
95
|
+
if (!unordered) assertOrderingIncludesPK(internalSort, this.#primaryKey);
|
|
93
96
|
this.#connections.push(connection);
|
|
94
97
|
return input;
|
|
95
98
|
}
|
|
@@ -125,7 +128,8 @@ var MemorySource = class MemorySource {
|
|
|
125
128
|
return [...this.#indexes.keys()];
|
|
126
129
|
}
|
|
127
130
|
*#fetch(req, conn) {
|
|
128
|
-
const
|
|
131
|
+
const requestedSort = must(conn.sort);
|
|
132
|
+
const { compareRows } = conn;
|
|
129
133
|
const connectionComparator = (r1, r2) => compareRows(r1, r2) * (req.reverse ? -1 : 1);
|
|
130
134
|
const pkConstraint = primaryKeyConstraintFromFilters(conn.filters?.condition, this.#primaryKey);
|
|
131
135
|
const fetchOrPkConstraint = pkConstraint ?? req.constraint;
|
|
@@ -367,6 +371,63 @@ function* generateWithOverlayInner(rowIterator, overlays, compare) {
|
|
|
367
371
|
relationships: {}
|
|
368
372
|
};
|
|
369
373
|
}
|
|
374
|
+
/**
|
|
375
|
+
* Like {@link generateWithOverlay} but for unordered streams.
|
|
376
|
+
* No `startAt` or comparator needed. Injects remove/old-edit rows eagerly
|
|
377
|
+
* at the start, and suppresses add/new-edit rows inline by PK match.
|
|
378
|
+
*/
|
|
379
|
+
function* generateWithOverlayUnordered(rows, constraint, overlay, lastPushedEpoch, primaryKey, filterPredicate) {
|
|
380
|
+
let overlayToApply = void 0;
|
|
381
|
+
if (overlay && lastPushedEpoch >= overlay.epoch) overlayToApply = overlay;
|
|
382
|
+
let overlays = {
|
|
383
|
+
add: void 0,
|
|
384
|
+
remove: void 0
|
|
385
|
+
};
|
|
386
|
+
switch (overlayToApply?.change.type) {
|
|
387
|
+
case "add":
|
|
388
|
+
overlays = {
|
|
389
|
+
add: overlayToApply.change.row,
|
|
390
|
+
remove: void 0
|
|
391
|
+
};
|
|
392
|
+
break;
|
|
393
|
+
case "remove":
|
|
394
|
+
overlays = {
|
|
395
|
+
add: void 0,
|
|
396
|
+
remove: overlayToApply.change.row
|
|
397
|
+
};
|
|
398
|
+
break;
|
|
399
|
+
case "edit":
|
|
400
|
+
overlays = {
|
|
401
|
+
add: overlayToApply.change.row,
|
|
402
|
+
remove: overlayToApply.change.oldRow
|
|
403
|
+
};
|
|
404
|
+
break;
|
|
405
|
+
}
|
|
406
|
+
if (constraint) overlays = overlaysForConstraint(overlays, constraint);
|
|
407
|
+
if (filterPredicate) overlays = overlaysForFilterPredicate(overlays, filterPredicate);
|
|
408
|
+
yield* generateWithOverlayInnerUnordered(rows, overlays, primaryKey);
|
|
409
|
+
}
|
|
410
|
+
function* generateWithOverlayInnerUnordered(rowIterator, overlays, primaryKey) {
|
|
411
|
+
if (overlays.add) yield {
|
|
412
|
+
row: overlays.add,
|
|
413
|
+
relationships: {}
|
|
414
|
+
};
|
|
415
|
+
let removeSkipped = false;
|
|
416
|
+
for (const row of rowIterator) {
|
|
417
|
+
if (!removeSkipped && overlays.remove && rowMatchesPK(overlays.remove, row, primaryKey)) {
|
|
418
|
+
removeSkipped = true;
|
|
419
|
+
continue;
|
|
420
|
+
}
|
|
421
|
+
yield {
|
|
422
|
+
row,
|
|
423
|
+
relationships: {}
|
|
424
|
+
};
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
function rowMatchesPK(a, b, primaryKey) {
|
|
428
|
+
for (const key of primaryKey) if (!valuesEqual(a[key], b[key])) return false;
|
|
429
|
+
return true;
|
|
430
|
+
}
|
|
370
431
|
var minValue = Symbol("min-value");
|
|
371
432
|
var maxValue = Symbol("max-value");
|
|
372
433
|
function makeBoundComparator(sort) {
|
|
@@ -394,6 +455,6 @@ function stringify(change) {
|
|
|
394
455
|
return JSON.stringify(change, (_, v) => typeof v === "bigint" ? v.toString() : v);
|
|
395
456
|
}
|
|
396
457
|
//#endregion
|
|
397
|
-
export { MemorySource, genPushAndWriteWithSplitEdit, generateWithOverlay, generateWithStart };
|
|
458
|
+
export { MemorySource, genPushAndWriteWithSplitEdit, generateWithOverlay, generateWithOverlayUnordered, generateWithStart };
|
|
398
459
|
|
|
399
460
|
//# sourceMappingURL=memory-source.js.map
|