@rocicorp/zero 1.11.0-canary.0 → 1.11.0-canary.1
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/zero/package.js +1 -1
- package/out/zero/package.js.map +1 -1
- package/out/zero-cache/src/config/zero-config.d.ts +15 -2
- package/out/zero-cache/src/config/zero-config.d.ts.map +1 -1
- package/out/zero-cache/src/config/zero-config.js +77 -25
- package/out/zero-cache/src/config/zero-config.js.map +1 -1
- package/out/zero-cache/src/server/replicator.d.ts.map +1 -1
- package/out/zero-cache/src/server/replicator.js +6 -2
- package/out/zero-cache/src/server/replicator.js.map +1 -1
- package/out/zero-cache/src/server/runner/run-worker.js +1 -1
- package/out/zero-cache/src/services/litestream/commands.d.ts.map +1 -1
- package/out/zero-cache/src/services/litestream/commands.js +6 -3
- package/out/zero-cache/src/services/litestream/commands.js.map +1 -1
- package/out/zero-cache/src/services/litestream/config-v5.yml +16 -1
- package/out/zero-cache/src/services/litestream/litestream-checkpointer.js +104 -0
- package/out/zero-cache/src/services/litestream/litestream-checkpointer.js.map +1 -0
- package/out/zero-cache/src/services/litestream/litestream-controller.d.ts +70 -0
- package/out/zero-cache/src/services/litestream/litestream-controller.d.ts.map +1 -0
- package/out/zero-cache/src/services/litestream/litestream-controller.js +101 -0
- package/out/zero-cache/src/services/litestream/litestream-controller.js.map +1 -0
- package/out/zero-cache/src/services/replicator/incremental-sync.d.ts.map +1 -1
- package/out/zero-cache/src/services/replicator/incremental-sync.js +1 -0
- package/out/zero-cache/src/services/replicator/incremental-sync.js.map +1 -1
- package/out/zero-cache/src/services/replicator/write-worker-client.d.ts +27 -2
- package/out/zero-cache/src/services/replicator/write-worker-client.d.ts.map +1 -1
- package/out/zero-cache/src/services/replicator/write-worker-client.js +3 -2
- package/out/zero-cache/src/services/replicator/write-worker-client.js.map +1 -1
- package/out/zero-cache/src/services/replicator/write-worker.js +24 -5
- package/out/zero-cache/src/services/replicator/write-worker.js.map +1 -1
- package/out/zero-cache/src/workers/connection.js +2 -2
- package/out/zero-cache/src/workers/replicator.d.ts +1 -0
- package/out/zero-cache/src/workers/replicator.d.ts.map +1 -1
- package/out/zero-cache/src/workers/replicator.js +4 -2
- package/out/zero-cache/src/workers/replicator.js.map +1 -1
- package/out/zero-client/src/client/version.js +1 -1
- package/out/zero-client/src/client/zero.js +2 -2
- package/out/zero-protocol/src/analyze-query-result.d.ts +5 -5
- package/out/zero-protocol/src/analyze-query-result.d.ts.map +1 -1
- package/out/zero-protocol/src/analyze-query-result.js +1 -1
- package/out/zero-protocol/src/analyze-query-result.js.map +1 -1
- package/out/zero-protocol/src/down.d.ts +2 -2
- package/out/zero-protocol/src/inspect-down.d.ts +84 -84
- package/out/zero-protocol/src/poke.d.ts +6 -6
- package/out/zero-protocol/src/protocol-version.d.ts +1 -1
- package/out/zero-protocol/src/protocol-version.d.ts.map +1 -1
- package/out/zero-protocol/src/protocol-version.js.map +1 -1
- package/out/zql/src/builder/builder.d.ts.map +1 -1
- package/out/zql/src/builder/builder.js +16 -10
- package/out/zql/src/builder/builder.js.map +1 -1
- package/out/zql/src/ivm/filter-operators.d.ts +3 -2
- package/out/zql/src/ivm/filter-operators.d.ts.map +1 -1
- package/out/zql/src/ivm/filter-operators.js +19 -4
- package/out/zql/src/ivm/filter-operators.js.map +1 -1
- package/out/zql/src/ivm/memory-source.d.ts.map +1 -1
- package/out/zql/src/ivm/memory-source.js +17 -4
- package/out/zql/src/ivm/memory-source.js.map +1 -1
- package/out/zql/src/ivm/operator.d.ts +40 -0
- package/out/zql/src/ivm/operator.d.ts.map +1 -1
- package/out/zql/src/ivm/operator.js.map +1 -1
- package/out/zql/src/planner/planner-builder.d.ts.map +1 -1
- package/out/zql/src/planner/planner-builder.js +14 -4
- package/out/zql/src/planner/planner-builder.js.map +1 -1
- package/out/zql/src/planner/planner-connection.d.ts +13 -0
- package/out/zql/src/planner/planner-connection.d.ts.map +1 -1
- package/out/zql/src/planner/planner-connection.js +42 -7
- package/out/zql/src/planner/planner-connection.js.map +1 -1
- package/out/zql/src/planner/planner-debug.d.ts +1 -1
- package/out/zql/src/planner/planner-debug.d.ts.map +1 -1
- package/out/zql/src/planner/planner-debug.js.map +1 -1
- package/out/zql/src/planner/planner-fan-out.d.ts +1 -0
- package/out/zql/src/planner/planner-fan-out.d.ts.map +1 -1
- package/out/zql/src/planner/planner-fan-out.js +3 -0
- package/out/zql/src/planner/planner-fan-out.js.map +1 -1
- package/out/zql/src/planner/planner-filter.d.ts +59 -0
- package/out/zql/src/planner/planner-filter.d.ts.map +1 -0
- package/out/zql/src/planner/planner-filter.js +116 -0
- package/out/zql/src/planner/planner-filter.js.map +1 -0
- package/out/zql/src/planner/planner-graph.d.ts +2 -0
- package/out/zql/src/planner/planner-graph.d.ts.map +1 -1
- package/out/zql/src/planner/planner-graph.js +2 -0
- package/out/zql/src/planner/planner-graph.js.map +1 -1
- package/out/zql/src/planner/planner-join.d.ts +2 -0
- package/out/zql/src/planner/planner-join.d.ts.map +1 -1
- package/out/zql/src/planner/planner-join.js +7 -0
- package/out/zql/src/planner/planner-join.js.map +1 -1
- package/out/zql/src/planner/planner-node.d.ts +2 -1
- package/out/zql/src/planner/planner-node.d.ts.map +1 -1
- package/out/zql/src/planner/planner-node.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 +2 -1
- package/out/zqlite/src/query-builder.js.map +1 -1
- package/out/zqlite/src/sqlite-cost-model.d.ts.map +1 -1
- package/out/zqlite/src/sqlite-cost-model.js +2 -30
- package/out/zqlite/src/sqlite-cost-model.js.map +1 -1
- package/out/zqlite/src/table-source.d.ts.map +1 -1
- package/out/zqlite/src/table-source.js +10 -3
- package/out/zqlite/src/table-source.js.map +1 -1
- package/package.json +1 -1
|
@@ -17,9 +17,11 @@ var throwFilterOutput = {
|
|
|
17
17
|
};
|
|
18
18
|
var FilterStart = class {
|
|
19
19
|
#input;
|
|
20
|
+
#condition;
|
|
20
21
|
#output = throwFilterOutput;
|
|
21
|
-
constructor(input) {
|
|
22
|
+
constructor(input, condition) {
|
|
22
23
|
this.#input = input;
|
|
24
|
+
this.#condition = condition;
|
|
23
25
|
input.setOutput(this);
|
|
24
26
|
}
|
|
25
27
|
setFilterOutput(output) {
|
|
@@ -35,9 +37,14 @@ var FilterStart = class {
|
|
|
35
37
|
yield* this.#output.push(change, this);
|
|
36
38
|
}
|
|
37
39
|
*fetch(req) {
|
|
40
|
+
const mergedFilter = mergeFilters(req.filter, this.#condition);
|
|
41
|
+
const childReq = mergedFilter === req.filter ? req : {
|
|
42
|
+
...req,
|
|
43
|
+
filter: mergedFilter
|
|
44
|
+
};
|
|
38
45
|
this.#output.beginFilter();
|
|
39
46
|
try {
|
|
40
|
-
for (const node of this.#input.fetch(
|
|
47
|
+
for (const node of this.#input.fetch(childReq)) {
|
|
41
48
|
if (node === "yield") {
|
|
42
49
|
yield node;
|
|
43
50
|
continue;
|
|
@@ -49,6 +56,14 @@ var FilterStart = class {
|
|
|
49
56
|
}
|
|
50
57
|
}
|
|
51
58
|
};
|
|
59
|
+
function mergeFilters(reqFilter, ownCondition) {
|
|
60
|
+
if (!ownCondition) return reqFilter;
|
|
61
|
+
if (!reqFilter) return ownCondition;
|
|
62
|
+
return {
|
|
63
|
+
type: "and",
|
|
64
|
+
conditions: [reqFilter, ownCondition]
|
|
65
|
+
};
|
|
66
|
+
}
|
|
52
67
|
var FilterEnd = class {
|
|
53
68
|
#start;
|
|
54
69
|
#input;
|
|
@@ -79,8 +94,8 @@ var FilterEnd = class {
|
|
|
79
94
|
yield* this.#output.push(change, this);
|
|
80
95
|
}
|
|
81
96
|
};
|
|
82
|
-
function buildFilterPipeline(input, delegate, pipeline) {
|
|
83
|
-
const filterStart = new FilterStart(input);
|
|
97
|
+
function buildFilterPipeline(input, delegate, pipeline, condition) {
|
|
98
|
+
const filterStart = new FilterStart(input, condition);
|
|
84
99
|
delegate.addEdge(input, filterStart);
|
|
85
100
|
const middle = pipeline(filterStart);
|
|
86
101
|
delegate.addEdge(filterStart, middle);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"filter-operators.js","names":["#input","#output","#start"],"sources":["../../../../../zql/src/ivm/filter-operators.ts"],"sourcesContent":["import type {BuilderDelegate} from '../builder/builder.ts';\nimport type {Change} from './change.ts';\nimport {type Node} from './data.ts';\nimport type {FetchRequest, Input, InputBase, Output} from './operator.ts';\nimport type {SourceSchema} from './schema.ts';\nimport {type Stream} from './stream.ts';\n\n/**\n * The `where` clause of a ZQL query is implemented using a sub-graph of\n * `FilterOperators`. This sub-graph starts with a `FilterStart` operator,\n * that adapts from the normal `Operator` `Output`, to the\n * `FilterOperator` `FilterInput`, and ends with a `FilterEnd` operator that\n * adapts from a `FilterOperator` `FilterOutput` to a normal `Operator` `Input`.\n * `FilterOperator`'s do not have `fetch` instead they have a\n * `filter(node: Node): boolean` method.\n * They also have `push` which is just like normal `Operator` push.\n * Not having a `fetch` means these `FilterOperator`'s cannot modify\n * `Node` `row`s or `relationship`s, but they shouldn't, they should just\n * filter.\n *\n * This `FilterOperator` abstraction enables much more efficient processing of\n * `fetch` for `where` clauses containing OR conditions.\n *\n * See https://github.com/rocicorp/mono/pull/4339\n */\n\nexport interface FilterInput extends InputBase {\n /** Tell the input where to send its output. */\n setFilterOutput(output: FilterOutput): void;\n}\n\nexport interface FilterOutput extends Output {\n // Lets the operator know that we're in a loop of filtering\n // nodes. E.g., so the operator can cache results for the\n // duration of the loop.\n beginFilter(): void;\n filter(node: Node): Generator<'yield', boolean>;\n endFilter(): void;\n}\n\nexport interface FilterOperator extends FilterInput, FilterOutput {}\n\n/**\n * An implementation of FilterOutput that throws if push or filter is called.\n * It is used as the initial value for for an operator's output before it is\n * set.\n */\nexport const throwFilterOutput: FilterOutput = {\n *push(_change: Change): Stream<'yield'> {\n throw new Error('Output not set');\n },\n\n *filter(_node: Node): Generator<'yield', boolean> {\n throw new Error('Output not set');\n },\n\n beginFilter() {},\n endFilter() {},\n};\n\nexport class FilterStart implements FilterInput, Output {\n readonly #input: Input;\n #output: FilterOutput = throwFilterOutput;\n\n constructor(input: Input) {\n this.#input = input;\n input.setOutput(this);\n }\n\n setFilterOutput(output: FilterOutput) {\n this.#output = output;\n }\n\n destroy(): void {\n this.#input.destroy();\n }\n\n getSchema(): SourceSchema {\n return this.#input.getSchema();\n }\n\n *push(change: Change) {\n yield* this.#output.push(change, this);\n }\n\n *fetch(req: FetchRequest): Stream<Node | 'yield'> {\n this.#output.beginFilter();\n try {\n for (const node of this.#input.fetch(
|
|
1
|
+
{"version":3,"file":"filter-operators.js","names":["#input","#condition","#output","#start"],"sources":["../../../../../zql/src/ivm/filter-operators.ts"],"sourcesContent":["import type {BuilderDelegate} from '../builder/builder.ts';\nimport type {NoSubqueryCondition} from '../builder/filter.ts';\nimport type {Change} from './change.ts';\nimport {type Node} from './data.ts';\nimport type {FetchRequest, Input, InputBase, Output} from './operator.ts';\nimport type {SourceSchema} from './schema.ts';\nimport {type Stream} from './stream.ts';\n\n/**\n * The `where` clause of a ZQL query is implemented using a sub-graph of\n * `FilterOperators`. This sub-graph starts with a `FilterStart` operator,\n * that adapts from the normal `Operator` `Output`, to the\n * `FilterOperator` `FilterInput`, and ends with a `FilterEnd` operator that\n * adapts from a `FilterOperator` `FilterOutput` to a normal `Operator` `Input`.\n * `FilterOperator`'s do not have `fetch` instead they have a\n * `filter(node: Node): boolean` method.\n * They also have `push` which is just like normal `Operator` push.\n * Not having a `fetch` means these `FilterOperator`'s cannot modify\n * `Node` `row`s or `relationship`s, but they shouldn't, they should just\n * filter.\n *\n * This `FilterOperator` abstraction enables much more efficient processing of\n * `fetch` for `where` clauses containing OR conditions.\n *\n * See https://github.com/rocicorp/mono/pull/4339\n */\n\nexport interface FilterInput extends InputBase {\n /** Tell the input where to send its output. */\n setFilterOutput(output: FilterOutput): void;\n}\n\nexport interface FilterOutput extends Output {\n // Lets the operator know that we're in a loop of filtering\n // nodes. E.g., so the operator can cache results for the\n // duration of the loop.\n beginFilter(): void;\n filter(node: Node): Generator<'yield', boolean>;\n endFilter(): void;\n}\n\nexport interface FilterOperator extends FilterInput, FilterOutput {}\n\n/**\n * An implementation of FilterOutput that throws if push or filter is called.\n * It is used as the initial value for for an operator's output before it is\n * set.\n */\nexport const throwFilterOutput: FilterOutput = {\n *push(_change: Change): Stream<'yield'> {\n throw new Error('Output not set');\n },\n\n *filter(_node: Node): Generator<'yield', boolean> {\n throw new Error('Output not set');\n },\n\n beginFilter() {},\n endFilter() {},\n};\n\nexport class FilterStart implements FilterInput, Output {\n readonly #input: Input;\n readonly #condition: NoSubqueryCondition | undefined;\n #output: FilterOutput = throwFilterOutput;\n\n constructor(input: Input, condition?: NoSubqueryCondition | undefined) {\n this.#input = input;\n this.#condition = condition;\n input.setOutput(this);\n }\n\n setFilterOutput(output: FilterOutput) {\n this.#output = output;\n }\n\n destroy(): void {\n this.#input.destroy();\n }\n\n getSchema(): SourceSchema {\n return this.#input.getSchema();\n }\n\n *push(change: Change) {\n yield* this.#output.push(change, this);\n }\n\n *fetch(req: FetchRequest): Stream<Node | 'yield'> {\n const mergedFilter = mergeFilters(req.filter, this.#condition);\n const childReq =\n mergedFilter === req.filter ? req : {...req, filter: mergedFilter};\n this.#output.beginFilter();\n try {\n for (const node of this.#input.fetch(childReq)) {\n if (node === 'yield') {\n yield node;\n continue;\n }\n if (yield* this.#output.filter(node)) {\n yield node;\n }\n }\n } finally {\n // finally is important if an exception is thrown or\n // if the stream is not fully consumed.\n this.#output.endFilter();\n }\n }\n}\n\nfunction mergeFilters(\n reqFilter: NoSubqueryCondition | undefined,\n ownCondition: NoSubqueryCondition | undefined,\n): NoSubqueryCondition | undefined {\n if (!ownCondition) {\n return reqFilter;\n }\n if (!reqFilter) {\n return ownCondition;\n }\n return {type: 'and', conditions: [reqFilter, ownCondition]};\n}\n\nexport class FilterEnd implements Input, FilterOutput {\n readonly #start: FilterStart;\n readonly #input: FilterInput;\n\n #output: Output = throwFilterOutput;\n\n constructor(start: FilterStart, input: FilterInput) {\n this.#start = start;\n this.#input = input;\n input.setFilterOutput(this);\n }\n\n *fetch(req: FetchRequest): Stream<Node | 'yield'> {\n for (const node of this.#start.fetch(req)) {\n yield node;\n }\n }\n\n beginFilter() {}\n endFilter() {}\n\n *filter(_node: Node) {\n return true;\n }\n\n setOutput(output: Output) {\n this.#output = output;\n }\n\n destroy(): void {\n this.#input.destroy();\n }\n\n getSchema(): SourceSchema {\n return this.#input.getSchema();\n }\n\n *push(change: Change) {\n yield* this.#output.push(change, this);\n }\n}\n\nexport function buildFilterPipeline(\n input: Input,\n delegate: BuilderDelegate,\n pipeline: (filterInput: FilterInput) => FilterInput,\n condition?: NoSubqueryCondition | undefined,\n): Input {\n const filterStart = new FilterStart(input, condition);\n delegate.addEdge(input, filterStart);\n const middle = pipeline(filterStart);\n delegate.addEdge(filterStart, middle);\n const filterEnd = new FilterEnd(filterStart, middle);\n delegate.addEdge(middle, filterEnd);\n return filterEnd;\n}\n"],"mappings":";;;;;;;AAgDA,IAAa,oBAAkC;CAC7C,CAAC,KAAK,SAAkC;EACtC,MAAM,IAAI,MAAM,gBAAgB;CAClC;CAEA,CAAC,OAAO,OAA0C;EAChD,MAAM,IAAI,MAAM,gBAAgB;CAClC;CAEA,cAAc,CAAC;CACf,YAAY,CAAC;AACf;AAEA,IAAa,cAAb,MAAwD;CACtD;CACA;CACA,UAAwB;CAExB,YAAY,OAAc,WAA6C;EACrE,KAAKA,SAAS;EACd,KAAKC,aAAa;EAClB,MAAM,UAAU,IAAI;CACtB;CAEA,gBAAgB,QAAsB;EACpC,KAAKC,UAAU;CACjB;CAEA,UAAgB;EACd,KAAKF,OAAO,QAAQ;CACtB;CAEA,YAA0B;EACxB,OAAO,KAAKA,OAAO,UAAU;CAC/B;CAEA,CAAC,KAAK,QAAgB;EACpB,OAAO,KAAKE,QAAQ,KAAK,QAAQ,IAAI;CACvC;CAEA,CAAC,MAAM,KAA2C;EAChD,MAAM,eAAe,aAAa,IAAI,QAAQ,KAAKD,UAAU;EAC7D,MAAM,WACJ,iBAAiB,IAAI,SAAS,MAAM;GAAC,GAAG;GAAK,QAAQ;EAAY;EACnE,KAAKC,QAAQ,YAAY;EACzB,IAAI;GACF,KAAK,MAAM,QAAQ,KAAKF,OAAO,MAAM,QAAQ,GAAG;IAC9C,IAAI,SAAS,SAAS;KACpB,MAAM;KACN;IACF;IACA,IAAI,OAAO,KAAKE,QAAQ,OAAO,IAAI,GACjC,MAAM;GAEV;EACF,UAAU;GAGR,KAAKA,QAAQ,UAAU;EACzB;CACF;AACF;AAEA,SAAS,aACP,WACA,cACiC;CACjC,IAAI,CAAC,cACH,OAAO;CAET,IAAI,CAAC,WACH,OAAO;CAET,OAAO;EAAC,MAAM;EAAO,YAAY,CAAC,WAAW,YAAY;CAAC;AAC5D;AAEA,IAAa,YAAb,MAAsD;CACpD;CACA;CAEA,UAAkB;CAElB,YAAY,OAAoB,OAAoB;EAClD,KAAKC,SAAS;EACd,KAAKH,SAAS;EACd,MAAM,gBAAgB,IAAI;CAC5B;CAEA,CAAC,MAAM,KAA2C;EAChD,KAAK,MAAM,QAAQ,KAAKG,OAAO,MAAM,GAAG,GACtC,MAAM;CAEV;CAEA,cAAc,CAAC;CACf,YAAY,CAAC;CAEb,CAAC,OAAO,OAAa;EACnB,OAAO;CACT;CAEA,UAAU,QAAgB;EACxB,KAAKD,UAAU;CACjB;CAEA,UAAgB;EACd,KAAKF,OAAO,QAAQ;CACtB;CAEA,YAA0B;EACxB,OAAO,KAAKA,OAAO,UAAU;CAC/B;CAEA,CAAC,KAAK,QAAgB;EACpB,OAAO,KAAKE,QAAQ,KAAK,QAAQ,IAAI;CACvC;AACF;AAEA,SAAgB,oBACd,OACA,UACA,UACA,WACO;CACP,MAAM,cAAc,IAAI,YAAY,OAAO,SAAS;CACpD,SAAS,QAAQ,OAAO,WAAW;CACnC,MAAM,SAAS,SAAS,WAAW;CACnC,SAAS,QAAQ,aAAa,MAAM;CACpC,MAAM,YAAY,IAAI,UAAU,aAAa,MAAM;CACnD,SAAS,QAAQ,QAAQ,SAAS;CAClC,OAAO;AACT"}
|
|
@@ -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;AAI1D,OAAO,KAAK,EACV,SAAS,EACT,QAAQ,EAET,MAAM,mCAAmC,CAAC;AAC3C,OAAO,KAAK,EAAC,GAAG,EAAE,KAAK,EAAC,MAAM,oCAAoC,CAAC;AACnE,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,2CAA2C,CAAC;AAC1E,OAAO,KAAK,EAAC,WAAW,EAAC,MAAM,yCAAyC,CAAC;AACzE,OAAO,KAAK,EAAC,aAAa,EAAC,MAAM,8BAA8B,CAAC;AAChE,OAAO,EAGL,KAAK,mBAAmB,EACzB,MAAM,sBAAsB,CAAC;AAK9B,OAAO,EAIL,KAAK,UAAU,EAChB,MAAM,iBAAiB,CAAC;AACzB,OAAO,EAIL,KAAK,UAAU,EACf,KAAK,IAAI,EACV,MAAM,WAAW,CAAC;AAEnB,OAAO,EAGL,KAAK,KAAK,EACV,KAAK,eAAe,EACpB,KAAK,MAAM,EACX,KAAK,KAAK,EACX,MAAM,eAAe,CAAC;AAGvB,OAAO,KAAK,EACV,MAAM,EACN,YAAY,EAIZ,WAAW,EACZ,MAAM,aAAa,CAAC;AAErB,OAAO,KAAK,EAAC,MAAM,EAAC,MAAM,aAAa,CAAC;AAExC,MAAM,MAAM,OAAO,GAAG;IACpB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,YAAY,CAAC;CACtB,CAAC;AAEF,MAAM,MAAM,QAAQ,GAAG;IACrB,GAAG,EAAE,GAAG,GAAG,SAAS,CAAC;IACrB,MAAM,EAAE,GAAG,GAAG,SAAS,CAAC;CACzB,CAAC;AAQF,MAAM,MAAM,UAAU,GAAG;IACvB,KAAK,EAAE,KAAK,CAAC;IACb,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC;IAC3B,IAAI,CAAC,EAAE,QAAQ,GAAG,SAAS,CAAC;IAC5B,aAAa,EAAE,GAAG,CAAC,MAAM,CAAC,GAAG,SAAS,CAAC;IACvC,WAAW,EAAE,UAAU,CAAC;IACxB,OAAO,EACH;QACE,SAAS,EAAE,mBAAmB,CAAC;QAC/B,SAAS,EAAE,CAAC,GAAG,EAAE,GAAG,KAAK,OAAO,CAAC;KAClC,GACD,SAAS,CAAC;IACd,QAAQ,CAAC,KAAK,CAAC,EAAE,aAAa,GAAG,SAAS,CAAC;IAC3C,eAAe,EAAE,MAAM,CAAC;CACzB,CAAC;AAEF;;;;;;GAMG;AACH,qBAAa,YAAa,YAAW,MAAM;;IAWzC,YACE,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,EACpC,UAAU,EAAE,UAAU,EACtB,gBAAgB,CAAC,EAAE,QAAQ,CAAC,GAAG,CAAC,EAYjC;IAED,IAAI,WAAW;QAEX,IAAI;QACJ,OAAO;QACP,UAAU;MAEb;IAED,IAAI,iBAQH;IAED,IAAI,IAAI,IAAI,QAAQ,CAAC,GAAG,CAAC,CAExB;IAeD,OAAO,CACL,IAAI,EAAE,QAAQ,GAAG,SAAS,EAC1B,OAAO,CAAC,EAAE,SAAS,EACnB,aAAa,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,GAC1B,WAAW,CAqCb;IAkDD,YAAY,IAAI,MAAM,EAAE,CAEvB;
|
|
1
|
+
{"version":3,"file":"memory-source.d.ts","sourceRoot":"","sources":["../../../../../zql/src/ivm/memory-source.ts"],"names":[],"mappings":"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"}
|
|
@@ -136,7 +136,14 @@ var MemorySource = class MemorySource {
|
|
|
136
136
|
const requestedSort = must(conn.sort);
|
|
137
137
|
const { compareRows } = conn;
|
|
138
138
|
const connectionComparator = req.reverse ? (r1, r2) => compareRows(r2, r1) : compareRows;
|
|
139
|
-
const
|
|
139
|
+
const reqFilter = req.filter;
|
|
140
|
+
const connFilterCondition = conn.filters?.condition;
|
|
141
|
+
const mergedFilterCondition = connFilterCondition && reqFilter ? {
|
|
142
|
+
type: "and",
|
|
143
|
+
conditions: [connFilterCondition, reqFilter]
|
|
144
|
+
} : connFilterCondition ?? reqFilter;
|
|
145
|
+
const mergedFilterPredicate = mergePredicates(conn.filters?.predicate, reqFilter);
|
|
146
|
+
const pkConstraint = primaryKeyConstraintFromFilters(mergedFilterCondition, this.#primaryKey);
|
|
140
147
|
const fetchOrPkConstraint = pkConstraint ?? req.constraint;
|
|
141
148
|
const indexSort = [];
|
|
142
149
|
if (fetchOrPkConstraint) for (const key of Object.keys(fetchOrPkConstraint)) indexSort.push([key, "asc"]);
|
|
@@ -152,7 +159,7 @@ var MemorySource = class MemorySource {
|
|
|
152
159
|
else scanStart[key] = dir === "asc" ? minValue : maxValue;
|
|
153
160
|
} else scanStart = startAt;
|
|
154
161
|
const rowsIterable = generateRows(data, scanStart, req.reverse);
|
|
155
|
-
if (!(this.#overlay && conn.lastPushedEpoch >= this.#overlay.epoch) && !req.start && !conn.filters) {
|
|
162
|
+
if (!(this.#overlay && conn.lastPushedEpoch >= this.#overlay.epoch) && !req.start && !conn.filters && !req.filter) {
|
|
156
163
|
const { constraint } = req;
|
|
157
164
|
for (const row of rowsIterable) {
|
|
158
165
|
if (constraint && !constraintMatchesRow(constraint, row)) break;
|
|
@@ -163,8 +170,8 @@ var MemorySource = class MemorySource {
|
|
|
163
170
|
}
|
|
164
171
|
return;
|
|
165
172
|
}
|
|
166
|
-
const withConstraint = generateWithConstraint(skipYields(generateWithStart(generateWithOverlay(startAt, pkConstraint ? once(rowsIterable) : rowsIterable, req.constraint, this.#overlay, conn.lastPushedEpoch, indexComparator, connectionComparator,
|
|
167
|
-
yield*
|
|
173
|
+
const withConstraint = generateWithConstraint(skipYields(generateWithStart(generateWithOverlay(startAt, pkConstraint ? once(rowsIterable) : rowsIterable, req.constraint, this.#overlay, conn.lastPushedEpoch, indexComparator, connectionComparator, mergedFilterPredicate), req.start, connectionComparator)), req.constraint);
|
|
174
|
+
yield* mergedFilterPredicate ? generateWithFilter(withConstraint, mergedFilterPredicate) : withConstraint;
|
|
168
175
|
}
|
|
169
176
|
*#fetchMulti(req, conn) {
|
|
170
177
|
const multis = must(req.multiConstraints).filter((mc) => mc.length > 0);
|
|
@@ -232,6 +239,12 @@ var MemorySource = class MemorySource {
|
|
|
232
239
|
}
|
|
233
240
|
}
|
|
234
241
|
};
|
|
242
|
+
function mergePredicates(connPredicate, reqFilter) {
|
|
243
|
+
if (!reqFilter) return connPredicate;
|
|
244
|
+
const reqPredicate = createPredicate(reqFilter);
|
|
245
|
+
if (!connPredicate) return reqPredicate;
|
|
246
|
+
return (row) => connPredicate(row) && reqPredicate(row);
|
|
247
|
+
}
|
|
235
248
|
function* generateWithConstraint(it, constraint) {
|
|
236
249
|
for (const node of it) {
|
|
237
250
|
if (constraint && !constraintMatchesRow(constraint, node.row)) break;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"memory-source.js","names":["#tableName","#columns","#primaryKey","#primaryIndexSort","#indexes","#connections","#getPrimaryIndex","#fetch","#disconnect","#getSchema","#fetchMulti","#getOrCreateIndex","#overlay","#writeChange","#pushEpoch"],"sources":["../../../../../zql/src/ivm/memory-source.ts"],"sourcesContent":["import {assert, unreachable} from '../../../shared/src/asserts.ts';\nimport {BTreeSet} from '../../../shared/src/btree-set.ts';\nimport {hasOwn} from '../../../shared/src/has-own.ts';\nimport {once, toSorted} from '../../../shared/src/iterables.ts';\nimport {must} from '../../../shared/src/must.ts';\nimport type {\n Condition,\n Ordering,\n OrderPart,\n} from '../../../zero-protocol/src/ast.ts';\nimport type {Row, Value} from '../../../zero-protocol/src/data.ts';\nimport type {PrimaryKey} from '../../../zero-protocol/src/primary-key.ts';\nimport type {SchemaValue} from '../../../zero-types/src/schema-value.ts';\nimport type {DebugDelegate} from '../builder/debug-delegate.ts';\nimport {\n createPredicate,\n transformFilters,\n type NoSubqueryCondition,\n} from '../builder/filter.ts';\nimport {assertOrderingIncludesPK} from '../query/complete-ordering.ts';\nimport {ChangeType} from './change-type.ts';\nimport type {Change} from './change.ts';\nimport {makeAddChange, makeEditChange, makeRemoveChange} from './change.ts';\nimport {\n constraintMatchesPrimaryKey,\n constraintMatchesRow,\n primaryKeyConstraintFromFilters,\n type Constraint,\n} from './constraint.ts';\nimport {\n compareValues,\n makeComparator,\n valuesEqual,\n type Comparator,\n type Node,\n} from './data.ts';\nimport {filterPush} from './filter-push.ts';\nimport {\n skipYields,\n type FetchRequest,\n type Input,\n type MultiConstraint,\n type Output,\n type Start,\n} from './operator.ts';\nimport type {SourceSchema} from './schema.ts';\nimport {SourceChangeIndex} from './source-change-index.ts';\nimport type {\n Source,\n SourceChange,\n SourceChangeAdd,\n SourceChangeEdit,\n SourceChangeRemove,\n SourceInput,\n} from './source.ts';\nimport {makeSourceChangeAdd, makeSourceChangeRemove} from './source.ts';\nimport type {Stream} from './stream.ts';\n\nexport type Overlay = {\n epoch: number;\n change: SourceChange;\n};\n\nexport type Overlays = {\n add: Row | undefined;\n remove: Row | undefined;\n};\n\ntype Index = {\n comparator: Comparator;\n data: BTreeSet<Row>;\n usedBy: Set<Connection>;\n};\n\nexport type Connection = {\n input: Input;\n output: Output | undefined;\n sort?: Ordering | undefined;\n splitEditKeys: Set<string> | undefined;\n compareRows: Comparator;\n filters:\n | {\n condition: NoSubqueryCondition;\n predicate: (row: Row) => boolean;\n }\n | undefined;\n readonly debug?: DebugDelegate | undefined;\n lastPushedEpoch: number;\n};\n\n/**\n * A `MemorySource` is a source that provides data to the pipeline from an\n * in-memory data source.\n *\n * This data is kept in sorted order as downstream pipelines will always expect\n * the data they receive from `pull` to be in sorted order.\n */\nexport class MemorySource implements Source {\n readonly #tableName: string;\n readonly #columns: Record<string, SchemaValue>;\n readonly #primaryKey: PrimaryKey;\n readonly #primaryIndexSort: Ordering;\n readonly #indexes: Map<string, Index> = new Map();\n readonly #connections: Connection[] = [];\n\n #overlay: Overlay | undefined;\n #pushEpoch = 0;\n\n constructor(\n tableName: string,\n columns: Record<string, SchemaValue>,\n primaryKey: PrimaryKey,\n primaryIndexData?: BTreeSet<Row>,\n ) {\n this.#tableName = tableName;\n this.#columns = columns;\n this.#primaryKey = primaryKey;\n this.#primaryIndexSort = primaryKey.map(k => [k, 'asc']);\n const comparator = makeBoundComparator(this.#primaryIndexSort);\n this.#indexes.set(JSON.stringify(this.#primaryIndexSort), {\n comparator,\n data: primaryIndexData ?? new BTreeSet<Row>(comparator),\n usedBy: new Set(),\n });\n }\n\n get tableSchema() {\n return {\n name: this.#tableName,\n columns: this.#columns,\n primaryKey: this.#primaryKey,\n };\n }\n\n fork() {\n const primaryIndex = this.#getPrimaryIndex();\n return new MemorySource(\n this.#tableName,\n this.#columns,\n this.#primaryKey,\n primaryIndex.data.clone(),\n );\n }\n\n get data(): BTreeSet<Row> {\n return this.#getPrimaryIndex().data;\n }\n\n #getSchema(connection: Connection, unordered: boolean): SourceSchema {\n return {\n tableName: this.#tableName,\n columns: this.#columns,\n primaryKey: this.#primaryKey,\n sort: unordered ? undefined : connection.sort,\n system: 'client',\n relationships: {},\n isHidden: false,\n compareRows: connection.compareRows,\n };\n }\n\n connect(\n sort: Ordering | undefined,\n filters?: Condition,\n splitEditKeys?: Set<string>,\n ): SourceInput {\n const transformedFilters = transformFilters(filters);\n const unordered = sort === undefined;\n const internalSort = sort ?? this.#primaryIndexSort;\n\n const input: SourceInput = {\n getSchema: () => schema,\n fetch: req => this.#fetch(req, connection),\n setOutput: output => {\n connection.output = output;\n },\n destroy: () => {\n this.#disconnect(input);\n },\n fullyAppliedFilters: !transformedFilters.conditionsRemoved,\n };\n\n const connection: Connection = {\n input,\n output: undefined,\n sort: internalSort,\n splitEditKeys,\n compareRows: makeComparator(internalSort),\n filters: transformedFilters.filters\n ? {\n condition: transformedFilters.filters,\n predicate: createPredicate(transformedFilters.filters),\n }\n : undefined,\n lastPushedEpoch: 0,\n };\n const schema = this.#getSchema(connection, unordered);\n if (!unordered) {\n assertOrderingIncludesPK(internalSort, this.#primaryKey);\n }\n this.#connections.push(connection);\n return input;\n }\n\n #disconnect(input: Input): void {\n const idx = this.#connections.findIndex(c => c.input === input);\n assert(idx !== -1, 'Connection not found');\n this.#connections.splice(idx, 1);\n\n // TODO: We used to delete unused indexes here. But in common cases like\n // navigating into issue detail pages it caused a ton of constantly\n // building and destroying indexes.\n //\n // Perhaps some intelligent LRU or something is needed here but for now,\n // the opposite extreme of keeping all indexes for the lifetime of the\n // page seems better.\n }\n\n #getPrimaryIndex(): Index {\n const index = this.#indexes.get(JSON.stringify(this.#primaryIndexSort));\n assert(index, 'Primary index not found');\n return index;\n }\n\n #getOrCreateIndex(sort: Ordering, usedBy: Connection): Index {\n const key = JSON.stringify(sort);\n const index = this.#indexes.get(key);\n // Future optimization could use existing index if it's the same just sorted\n // in reverse of needed.\n if (index) {\n index.usedBy.add(usedBy);\n return index;\n }\n\n const comparator = makeBoundComparator(sort);\n\n // When creating these synchronously becomes a problem, a few options:\n // 1. Allow users to specify needed indexes up front\n // 2. Create indexes in a different thread asynchronously (this would require\n // modifying the BTree to be able to be passed over structured-clone, or using\n // a different library.)\n // 3. We could even theoretically do (2) on multiple threads and then merge the\n // results!\n const rows = toSorted(this.#getPrimaryIndex().data, comparator);\n const data = BTreeSet.fromSorted(comparator, rows);\n\n const newIndex = {comparator, data, usedBy: new Set([usedBy])};\n this.#indexes.set(key, newIndex);\n return newIndex;\n }\n\n // For unit testing that we correctly clean up indexes.\n getIndexKeys(): string[] {\n return [...this.#indexes.keys()];\n }\n\n *#fetch(req: FetchRequest, conn: Connection): Stream<Node | 'yield'> {\n // multiConstraints is handled by driving sub-fetches off the first\n // entry's values and post-filtering matches against any remaining\n // entries. TableSource implements multi-IN natively via SQL `AND` of\n // `IN` clauses; the in-memory path is correct but not specially\n // optimized — it pays the index-seek cost per primary value, then\n // walks each result against the rest.\n if (\n req.multiConstraints &&\n req.multiConstraints.some(mc => mc.length > 0)\n ) {\n yield* this.#fetchMulti(req, conn);\n return;\n }\n const requestedSort = must(conn.sort);\n const {compareRows} = conn;\n // Avoid allocating a new closure when not reversing (the common case).\n const connectionComparator: Comparator = req.reverse\n ? (r1, r2) => compareRows(r2, r1)\n : compareRows;\n\n const pkConstraint = primaryKeyConstraintFromFilters(\n conn.filters?.condition,\n this.#primaryKey,\n );\n // The primary key constraint will be more limiting than the constraint\n // so swap out to that if it exists.\n const fetchOrPkConstraint = pkConstraint ?? req.constraint;\n\n // If there is a constraint, we need an index sorted by it first.\n const indexSort: OrderPart[] = [];\n if (fetchOrPkConstraint) {\n for (const key of Object.keys(fetchOrPkConstraint)) {\n indexSort.push([key, 'asc']);\n }\n }\n\n // For the special case of constraining by PK, we don't need to worry about\n // any requested sort since there can only be one result. Otherwise we also\n // need the index sorted by the requested sort.\n if (\n this.#primaryKey.length > 1 ||\n !fetchOrPkConstraint ||\n !constraintMatchesPrimaryKey(fetchOrPkConstraint, this.#primaryKey)\n ) {\n indexSort.push(...requestedSort);\n }\n\n const index = this.#getOrCreateIndex(indexSort, conn);\n const {data, comparator: compare} = index;\n // Avoid allocating a new closure when not reversing (the common case).\n const indexComparator: Comparator = req.reverse\n ? (r1, r2) => compare(r2, r1)\n : compare;\n\n const startAt = req.start?.row;\n\n // If there is a constraint, we want to start our scan at the first row that\n // matches the constraint. But because the next OrderPart can be `desc`,\n // it's not true that {[constraintKey]: constraintValue} is the first\n // matching row. Because in that case, the other fields will all be\n // `undefined`, and in Zero `undefined` is always less than any other value.\n // So if the second OrderPart is descending then `undefined` values will\n // actually be the *last* row. We need a way to stay \"start at the first row\n // with this constraint value\". RowBound with the corresponding compareBound\n // comparator accomplishes this. The right thing is probably to teach the\n // btree library to support this concept.\n let scanStart: RowBound | undefined;\n\n if (fetchOrPkConstraint) {\n scanStart = {};\n for (const [key, dir] of indexSort) {\n if (hasOwn(fetchOrPkConstraint, key)) {\n scanStart[key] = fetchOrPkConstraint[key];\n } else {\n if (req.reverse) {\n scanStart[key] = dir === 'asc' ? maxValue : minValue;\n } else {\n scanStart[key] = dir === 'asc' ? minValue : maxValue;\n }\n }\n }\n } else {\n scanStart = startAt;\n }\n\n const rowsIterable = generateRows(data, scanStart, req.reverse);\n\n // Fast path: no overlay to apply, no `start`, and no connection filters.\n // This is the common shape for join child-lookups, plain scans, and source\n // fetches that sit beneath a downstream `Filter` operator. In that case the\n // whole generator stack (generateWithOverlay → generateWithStart →\n // skipYields → generateWithConstraint) reduces to: walk rows from\n // `scanStart`, wrap each as a Node, and stop once the constraint no longer\n // matches (rows are sorted by the constraint key first, so matches are\n // contiguous). Collapsing the five chained generators into one loop removes\n // a large amount of per-row generator-resume overhead on the hottest path.\n const overlayActive =\n this.#overlay && conn.lastPushedEpoch >= this.#overlay.epoch;\n if (!overlayActive && !req.start && !conn.filters) {\n const {constraint} = req;\n for (const row of rowsIterable) {\n if (constraint && !constraintMatchesRow(constraint, row)) {\n break;\n }\n yield {row, relationships: {}};\n }\n return;\n }\n\n const withOverlay = generateWithOverlay(\n startAt,\n pkConstraint ? once(rowsIterable) : rowsIterable,\n // use `req.constraint` here and not `fetchOrPkConstraint` since `fetchOrPkConstraint` could be the\n // primary key constraint. The primary key constraint comes from filters and is acting as a filter\n // rather than as the fetch constraint.\n req.constraint,\n this.#overlay,\n conn.lastPushedEpoch,\n // Use indexComparator, generateWithOverlayInner has a subtle dependency\n // on this. Since generateWithConstraint is done after\n // generateWithOverlay, the generator consumed by generateWithOverlayInner\n // does not end when the constraint stops matching and so the final\n // check to yield an add overlay if not yet yielded is not reached.\n // However, using the indexComparator the add overlay will be less than\n // the first row that does not match the constraint, and so any\n // not yet yielded add overlay will be yielded when the first row\n // not matching the constraint is reached.\n indexComparator,\n // `startAt` is `req.start.row`, a bound in the connection's sort order,\n // so it must be compared in that order -- not `indexComparator`, which\n // with a constraint leads with the constraint keys. The two agree only\n // when the overlay row and `startAt` share those key values; they do not\n // for `#fetchMulti`'s per-value sub-fetches, which pin one primary key\n // while still carrying the caller's connection-sort `start`. Comparing\n // there by the constraint key dropped the in-flight overlay and served\n // pre-push data. Same distinction #4926 drew for `generateWithStart`.\n connectionComparator,\n conn.filters?.predicate,\n );\n\n const withConstraint = generateWithConstraint(\n skipYields(\n generateWithStart(withOverlay, req.start, connectionComparator),\n ),\n // we use `req.constraint` and not `fetchOrPkConstraint` here because we need to\n // AND the constraint with what could have been the primary key constraint\n req.constraint,\n );\n\n yield* conn.filters\n ? generateWithFilter(withConstraint, conn.filters.predicate)\n : withConstraint;\n }\n\n *#fetchMulti(req: FetchRequest, conn: Connection): Stream<Node | 'yield'> {\n // Caller (`#fetch`) guards entry on `req.multiConstraints.some(mc =>\n // mc.length > 0)`, so `multis` is guaranteed non-empty after the\n // empty-entry filter. Per the MultiConstraint contract (operator.ts),\n // entries are non-empty, share key shape, are unique, and are\n // key-compatible with `req.constraint`.\n const multis = must(req.multiConstraints).filter(mc => mc.length > 0);\n const primary = multis[0];\n const rest = multis.slice(1);\n const baseConstraint = req.constraint;\n\n // Drive sub-fetches off `primary`. Within each sub-fetch the source\n // can use its index for `primary`; we then post-filter rows by\n // checking they also match every entry in `rest`. Per the\n // MultiConstraint contract (see operator.ts), entries are unique and\n // key-compatible with `baseConstraint`, so we don't dedupe or check\n // compatibility here.\n const subStreams: Stream<Node | 'yield'>[] = primary.map(c => {\n const merged: Constraint = baseConstraint ? {...baseConstraint, ...c} : c;\n return this.#fetch(\n {...req, constraint: merged, multiConstraints: undefined},\n conn,\n );\n });\n const merged = mergeSortedStreams(\n subStreams,\n req.reverse\n ? (a, b) => conn.compareRows(b.row, a.row)\n : (a, b) => conn.compareRows(a.row, b.row),\n );\n\n if (rest.length === 0) {\n yield* merged;\n return;\n }\n\n for (const node of merged) {\n if (node === 'yield') {\n yield 'yield';\n continue;\n }\n let matchesAll = true;\n for (const mc of rest) {\n let any = false;\n for (const c of mc) {\n if (constraintMatchesRow(c, node.row)) {\n any = true;\n break;\n }\n }\n if (!any) {\n matchesAll = false;\n break;\n }\n }\n if (matchesAll) yield node;\n }\n }\n\n *push(change: SourceChange): Stream<'yield'> {\n for (const result of this.genPush(change)) {\n if (result === 'yield') {\n yield result;\n }\n }\n }\n\n *genPush(change: SourceChange) {\n const primaryIndex = this.#getPrimaryIndex();\n const {data} = primaryIndex;\n const exists = (row: Row) => data.has(row);\n const setOverlay = (o: Overlay | undefined) => (this.#overlay = o);\n const writeChange = (c: SourceChange) => this.#writeChange(c);\n yield* genPushAndWriteWithSplitEdit(\n this.#connections,\n change,\n exists,\n setOverlay,\n writeChange,\n () => ++this.#pushEpoch,\n );\n }\n\n #writeChange(change: SourceChange) {\n for (const {data} of this.#indexes.values()) {\n switch (change[SourceChangeIndex.TYPE]) {\n case ChangeType.ADD: {\n const added = data.add(change[SourceChangeIndex.ROW]);\n // must succeed since we checked has() above.\n assert(\n added,\n 'MemorySource: add must succeed since row existence was already checked',\n );\n break;\n }\n case ChangeType.REMOVE: {\n const removed = data.delete(change[SourceChangeIndex.ROW]);\n // must succeed since we checked has() above.\n assert(\n removed,\n 'MemorySource: remove must succeed since row existence was already checked',\n );\n break;\n }\n case ChangeType.EDIT: {\n // TODO: We could see if the PK (form the index tree's perspective)\n // changed and if not we could use set.\n // We cannot just do `set` with the new value since the `oldRow` might\n // not map to the same entry as the new `row` in the index btree.\n const removed = data.delete(change[SourceChangeIndex.OLD_ROW]);\n // must succeed since we checked has() above.\n assert(\n removed,\n 'MemorySource: edit remove must succeed since row existence was already checked',\n );\n data.add(change[SourceChangeIndex.ROW]);\n break;\n }\n default:\n unreachable(change);\n }\n }\n }\n}\n\nfunction* generateWithConstraint(\n it: Stream<Node>,\n constraint: Constraint | undefined,\n) {\n for (const node of it) {\n if (constraint && !constraintMatchesRow(constraint, node.row)) {\n break;\n }\n yield node;\n }\n}\n\nfunction* generateWithFilter(it: Stream<Node>, filter: (row: Row) => boolean) {\n for (const node of it) {\n if (filter(node.row)) {\n yield node;\n }\n }\n}\n\nexport function* genPushAndWriteWithSplitEdit(\n connections: readonly Connection[],\n change: SourceChange,\n exists: (row: Row) => boolean,\n setOverlay: (o: Overlay | undefined) => Overlay | undefined,\n writeChange: (c: SourceChange) => void,\n getNextEpoch: () => number,\n) {\n let shouldSplitEdit = false;\n if (change[SourceChangeIndex.TYPE] === ChangeType.EDIT) {\n for (const {splitEditKeys} of connections) {\n if (splitEditKeys) {\n for (const key of splitEditKeys) {\n if (\n !valuesEqual(\n change[SourceChangeIndex.ROW][key],\n change[SourceChangeIndex.OLD_ROW][key],\n )\n ) {\n shouldSplitEdit = true;\n break;\n }\n }\n }\n }\n }\n\n if (change[SourceChangeIndex.TYPE] === ChangeType.EDIT && shouldSplitEdit) {\n yield* genPushAndWrite(\n connections,\n makeSourceChangeRemove(change[SourceChangeIndex.OLD_ROW]),\n exists,\n setOverlay,\n writeChange,\n getNextEpoch(),\n );\n yield* genPushAndWrite(\n connections,\n makeSourceChangeAdd(change[SourceChangeIndex.ROW]),\n exists,\n setOverlay,\n writeChange,\n getNextEpoch(),\n );\n } else {\n yield* genPushAndWrite(\n connections,\n change,\n exists,\n setOverlay,\n writeChange,\n getNextEpoch(),\n );\n }\n}\n\nfunction* genPushAndWrite(\n connections: readonly Connection[],\n change: SourceChangeAdd | SourceChangeRemove | SourceChangeEdit,\n exists: (row: Row) => boolean,\n setOverlay: (o: Overlay | undefined) => Overlay | undefined,\n writeChange: (c: SourceChange) => void,\n pushEpoch: number,\n) {\n for (const x of genPush(connections, change, exists, setOverlay, pushEpoch)) {\n yield x;\n }\n writeChange(change);\n}\n\nfunction* genPush(\n connections: readonly Connection[],\n change: SourceChange,\n exists: (row: Row) => boolean,\n setOverlay: (o: Overlay | undefined) => void,\n pushEpoch: number,\n) {\n switch (change[SourceChangeIndex.TYPE]) {\n case ChangeType.ADD:\n assert(\n !exists(change[SourceChangeIndex.ROW]),\n () => `Row already exists ${stringify(change)}`,\n );\n break;\n case ChangeType.REMOVE:\n assert(\n exists(change[SourceChangeIndex.ROW]),\n () => `Row not found ${stringify(change)}`,\n );\n break;\n case ChangeType.EDIT:\n assert(\n exists(change[SourceChangeIndex.OLD_ROW]),\n () => `Row not found ${stringify(change)}`,\n );\n break;\n default:\n unreachable(change);\n }\n\n for (const conn of connections) {\n const {output, filters, input} = conn;\n if (output) {\n conn.lastPushedEpoch = pushEpoch;\n setOverlay({epoch: pushEpoch, change});\n const outputChange: Change =\n change[SourceChangeIndex.TYPE] === ChangeType.EDIT\n ? makeEditChange(\n {row: change[SourceChangeIndex.ROW], relationships: {}},\n {row: change[SourceChangeIndex.OLD_ROW], relationships: {}},\n )\n : change[SourceChangeIndex.TYPE] === ChangeType.ADD\n ? makeAddChange({\n row: change[SourceChangeIndex.ROW],\n relationships: {},\n })\n : makeRemoveChange({\n row: change[SourceChangeIndex.ROW],\n relationships: {},\n });\n yield* filterPush(outputChange, output, input, filters?.predicate);\n yield undefined;\n }\n }\n\n setOverlay(undefined);\n}\n\nexport function* generateWithStart(\n nodes: Iterable<Node | 'yield'>,\n start: Start | undefined,\n compare: (r1: Row, r2: Row) => number,\n): Stream<Node | 'yield'> {\n if (!start) {\n yield* nodes;\n return;\n }\n let started = false;\n for (const node of nodes) {\n if (node === 'yield') {\n yield node;\n continue;\n }\n if (!started) {\n if (start.basis === 'at') {\n if (compare(node.row, start.row) >= 0) {\n started = true;\n }\n } else if (start.basis === 'after') {\n if (compare(node.row, start.row) > 0) {\n started = true;\n }\n }\n }\n if (started) {\n yield node;\n }\n }\n}\n\n/**\n * Takes an iterator and overlay.\n * Splices the overlay into the iterator at the correct position.\n *\n * @param startAt - if there is a lower bound to the stream. If the lower bound of the stream\n * is above the overlay, the overlay will be skipped.\n * @param rows - the stream into which the overlay should be spliced\n * @param constraint - constraint that was applied to the rowIterator and should\n * also be applied to the overlay.\n * @param overlay - the overlay values to splice in\n * @param compare - the comparator to use to find the position for the overlay.\n * Must order rows the same way `rows` is ordered. For `MemorySource` that is\n * *index* order, which leads with the constraint keys.\n * @param startAtCompare - the comparator for `startAt`, which is a bound in the\n * *connection's* sort order. Only the same as `compare` when the stream's order\n * happens to be the connection's -- true for `TableSource` (SQL does the\n * ordering) but not for a constrained `MemorySource` fetch. Conflating the two\n * is what #4926 fixed for `generateWithStart`; this parameter is the same\n * distinction for the overlay's own `startAt` pruning.\n */\nexport function* generateWithOverlay(\n startAt: Row | undefined,\n rows: Iterable<Row>,\n constraint: Constraint | undefined,\n overlay: Overlay | undefined,\n lastPushedEpoch: number,\n compare: Comparator,\n startAtCompare: Comparator,\n filterPredicate?: (row: Row) => boolean | undefined,\n multiConstraints?: readonly MultiConstraint[] | undefined,\n) {\n let overlayToApply: Overlay | undefined = undefined;\n if (overlay && lastPushedEpoch >= overlay.epoch) {\n overlayToApply = overlay;\n }\n const overlays = computeOverlays(\n startAt,\n constraint,\n overlayToApply,\n startAtCompare,\n filterPredicate,\n multiConstraints,\n );\n yield* generateWithOverlayInner(rows, overlays, compare);\n}\n\nfunction computeOverlays(\n startAt: Row | undefined,\n constraint: Constraint | undefined,\n overlay: Overlay | undefined,\n // Only ever used for `overlaysForStartAt`, so this is the *connection*-order\n // comparator, not the one used to splice the overlay into the row stream.\n startAtCompare: Comparator,\n filterPredicate?: (row: Row) => boolean | undefined,\n multiConstraints?: readonly MultiConstraint[] | undefined,\n): Overlays {\n let overlays: Overlays = {\n add: undefined,\n remove: undefined,\n };\n switch (overlay?.change[SourceChangeIndex.TYPE]) {\n case ChangeType.ADD:\n overlays = {\n add: overlay.change[SourceChangeIndex.ROW],\n remove: undefined,\n };\n break;\n case ChangeType.REMOVE:\n overlays = {\n add: undefined,\n remove: overlay.change[SourceChangeIndex.ROW],\n };\n break;\n case ChangeType.EDIT:\n overlays = {\n add: overlay.change[SourceChangeIndex.ROW],\n remove: overlay.change[SourceChangeIndex.OLD_ROW],\n };\n break;\n }\n\n if (startAt) {\n overlays = overlaysForStartAt(overlays, startAt, startAtCompare);\n }\n\n if (constraint) {\n overlays = overlaysForConstraint(overlays, constraint);\n }\n\n overlays = applyMultiConstraintsToOverlays(overlays, multiConstraints);\n\n if (filterPredicate) {\n overlays = overlaysForFilterPredicate(overlays, filterPredicate);\n }\n\n return overlays;\n}\n\nfunction applyMultiConstraintsToOverlays(\n overlays: Overlays,\n multiConstraints: readonly MultiConstraint[] | undefined,\n): Overlays {\n if (!multiConstraints) return overlays;\n for (const mc of multiConstraints) {\n if (mc.length > 0) {\n overlays = overlaysForMultiConstraint(overlays, mc);\n }\n }\n return overlays;\n}\n\nexport {overlaysForMultiConstraint as overlaysForMultiConstraintForTest};\n\nfunction overlaysForMultiConstraint(\n {add, remove}: Overlays,\n multiConstraint: MultiConstraint,\n): Overlays {\n const matchesAny = (row: Row | undefined) => {\n if (row === undefined) return false;\n for (const c of multiConstraint) {\n if (constraintMatchesRow(c, row)) return true;\n }\n return false;\n };\n return {\n add: matchesAny(add) ? add : undefined,\n remove: matchesAny(remove) ? remove : undefined,\n };\n}\n\nexport {overlaysForStartAt as overlaysForStartAtForTest};\n\nfunction overlaysForStartAt(\n {add, remove}: Overlays,\n startAt: Row,\n compare: Comparator,\n): Overlays {\n const undefinedIfBeforeStartAt = (row: Row | undefined) =>\n row === undefined || compare(row, startAt) < 0 ? undefined : row;\n return {\n add: undefinedIfBeforeStartAt(add),\n remove: undefinedIfBeforeStartAt(remove),\n };\n}\n\nexport {overlaysForConstraint as overlaysForConstraintForTest};\n\nfunction overlaysForConstraint(\n {add, remove}: Overlays,\n constraint: Constraint,\n): Overlays {\n const undefinedIfDoesntMatchConstraint = (row: Row | undefined) =>\n row === undefined || !constraintMatchesRow(constraint, row)\n ? undefined\n : row;\n\n return {\n add: undefinedIfDoesntMatchConstraint(add),\n remove: undefinedIfDoesntMatchConstraint(remove),\n };\n}\n\nfunction overlaysForFilterPredicate(\n {add, remove}: Overlays,\n filterPredicate: (row: Row) => boolean | undefined,\n): Overlays {\n const undefinedIfDoesntMatchFilter = (row: Row | undefined) =>\n row === undefined || !filterPredicate(row) ? undefined : row;\n\n return {\n add: undefinedIfDoesntMatchFilter(add),\n remove: undefinedIfDoesntMatchFilter(remove),\n };\n}\n\nexport function* generateWithOverlayInner(\n rowIterator: Iterable<Row>,\n overlays: Overlays,\n compare: (r1: Row, r2: Row) => number,\n) {\n let addOverlayYielded = false;\n let removeOverlaySkipped = false;\n for (const row of rowIterator) {\n if (!addOverlayYielded && overlays.add) {\n const cmp = compare(overlays.add, row);\n if (cmp < 0) {\n addOverlayYielded = true;\n yield {row: overlays.add, relationships: {}};\n }\n }\n\n if (!removeOverlaySkipped && overlays.remove) {\n const cmp = compare(overlays.remove, row);\n if (cmp === 0) {\n removeOverlaySkipped = true;\n continue;\n }\n }\n yield {row, relationships: {}};\n }\n\n if (!addOverlayYielded && overlays.add) {\n yield {row: overlays.add, relationships: {}};\n }\n}\n\n/**\n * Like {@link generateWithOverlay} but for unordered streams.\n * No `startAt` or comparator needed. Injects remove/old-edit rows eagerly\n * at the start, and suppresses add/new-edit rows inline by PK match.\n */\nexport function* generateWithOverlayUnordered(\n rows: Iterable<Row>,\n constraint: Constraint | undefined,\n overlay: Overlay | undefined,\n lastPushedEpoch: number,\n primaryKey: PrimaryKey,\n filterPredicate?: (row: Row) => boolean,\n multiConstraints?: readonly MultiConstraint[] | undefined,\n) {\n let overlayToApply: Overlay | undefined = undefined;\n if (overlay && lastPushedEpoch >= overlay.epoch) {\n overlayToApply = overlay;\n }\n let overlays: Overlays = {add: undefined, remove: undefined};\n switch (overlayToApply?.change[SourceChangeIndex.TYPE]) {\n case ChangeType.ADD:\n overlays = {\n add: overlayToApply.change[SourceChangeIndex.ROW],\n remove: undefined,\n };\n break;\n case ChangeType.REMOVE:\n overlays = {\n add: undefined,\n remove: overlayToApply.change[SourceChangeIndex.ROW],\n };\n break;\n case ChangeType.EDIT:\n overlays = {\n add: overlayToApply.change[SourceChangeIndex.ROW],\n remove: overlayToApply.change[SourceChangeIndex.OLD_ROW],\n };\n break;\n }\n if (constraint) {\n overlays = overlaysForConstraint(overlays, constraint);\n }\n overlays = applyMultiConstraintsToOverlays(overlays, multiConstraints);\n if (filterPredicate) {\n overlays = overlaysForFilterPredicate(overlays, filterPredicate);\n }\n yield* generateWithOverlayInnerUnordered(rows, overlays, primaryKey);\n}\n\nexport function* generateWithOverlayInnerUnordered(\n rowIterator: Iterable<Row>,\n overlays: Overlays,\n primaryKey: PrimaryKey,\n) {\n // Eager inject: yield the add overlay at the start (row not yet in storage)\n if (overlays.add) {\n yield {row: overlays.add, relationships: {}};\n }\n // Stream with inline suppress: skip the remove overlay (row still in storage)\n let removeSkipped = false;\n for (const row of rowIterator) {\n if (\n !removeSkipped &&\n overlays.remove &&\n rowMatchesPK(overlays.remove, row, primaryKey)\n ) {\n removeSkipped = true;\n continue;\n }\n yield {row, relationships: {}};\n }\n}\n\nfunction rowMatchesPK(a: Row, b: Row, primaryKey: PrimaryKey): boolean {\n for (const key of primaryKey) {\n if (!valuesEqual(a[key], b[key])) {\n return false;\n }\n }\n return true;\n}\n\n/**\n * A location to begin scanning an index from. Can either be a specific value\n * or the min or max possible value for the type. This is used to start a scan\n * at the beginning of the rows matching a constraint.\n */\ntype Bound = Value | MinValue | MaxValue;\ntype RowBound = Record<string, Bound>;\nconst minValue = Symbol('min-value');\ntype MinValue = typeof minValue;\nconst maxValue = Symbol('max-value');\ntype MaxValue = typeof maxValue;\n\nfunction makeBoundComparator(sort: Ordering): Comparator {\n // Pre-extract the first two keys/directions to avoid per-call array access.\n // All paths share one function literal (single SFI) so the BTree comparator call site\n // stays monomorphic across indexes with different sort orderings, preventing V8 IC deopt.\n // Even a 2-SFI split (e.g. separate len=1 path) creates a polymorphic IC that\n // measurably regresses performance, so we keep a single return body.\n const len = sort.length;\n const k0 = sort[0][0];\n const a0 = sort[0][1] === 'asc';\n const k1 = len > 1 ? sort[1][0] : '';\n const a1 = len > 1 ? sort[1][1] === 'asc' : true;\n\n return (a: RowBound, b: RowBound) => {\n const c0 = a0 ? compareBounds(a[k0], b[k0]) : compareBounds(b[k0], a[k0]);\n if (len === 1 || c0 !== 0) return c0;\n const c1 = a1 ? compareBounds(a[k1], b[k1]) : compareBounds(b[k1], a[k1]);\n if (len === 2 || c1 !== 0) return c1;\n // Hot! Do not use destructuring\n for (let i = 2; i < len; i++) {\n const cmp = compareBounds(a[sort[i][0]], b[sort[i][0]]);\n if (cmp !== 0) return sort[i][1] === 'asc' ? cmp : -cmp;\n }\n return 0;\n };\n}\n\nfunction compareBounds(a: Bound, b: Bound): number {\n if (a === b) {\n return 0;\n }\n // Use typeof to guard the Symbol sentinel checks first. This gives V8 a\n // clear type discriminant so the common non-symbol path compiles as a\n // specialised numeric/string fast-path without a Smi deopt when the\n // minValue/maxValue sentinel symbols appear.\n if (typeof a === 'symbol') {\n return a === minValue ? -1 : 1;\n }\n if (typeof b === 'symbol') {\n return b === minValue ? 1 : -1;\n }\n return compareValues(a, b);\n}\n\nfunction* generateRows(\n data: BTreeSet<Row>,\n scanStart: RowBound | undefined,\n reverse: boolean | undefined,\n) {\n yield* data[reverse ? 'valuesFromReversed' : 'valuesFrom'](\n scanStart as Row | undefined,\n );\n}\n\nexport function stringify(change: SourceChange) {\n return JSON.stringify(change, (_, v) =>\n typeof v === 'bigint' ? v.toString() : v,\n );\n}\n\n/**\n * N-way merge of pre-sorted Node streams. Each input stream must yield\n * Nodes in `compare` order (and may interleave 'yield' values, which are\n * forwarded as-is). The merged stream yields Nodes in `compare` order.\n *\n * Implemented as a binary min-heap keyed by `compare(entry.row, ...)`, so\n * each emit costs O(log K) (K = number of streams) rather than the O(K)\n * a linear-scan-of-heads merge would. Matters when FlippedJoin chunks a\n * large `multiConstraints` IN-list into hundreds of sub-fetches.\n *\n * If the merged stream is closed early (e.g. downstream `Take` `break`s\n * after hitting its limit, prompting JS to call `.return()` on this\n * generator), the `finally` block propagates `.return()` to each\n * non-exhausted sub-iterator so the underlying sources (SQLite cursors,\n * etc.) can run their cleanup. Without this, mid-merge early-termination\n * leaves cursors open, causing later writes on the same connection to\n * fail with \"database connection is busy executing a query\".\n */\nexport function* mergeSortedStreams(\n streams: readonly Stream<Node | 'yield'>[],\n compare: (a: Node, b: Node) => number,\n): Stream<Node | 'yield'> {\n const iterators: Iterator<Node | 'yield'>[] = streams.map(s =>\n s[Symbol.iterator](),\n );\n // True while iterators[i] hasn't yet returned `done`. The finally\n // block uses this to skip already-exhausted streams when propagating\n // `.return()`.\n const active: boolean[] = new Array(iterators.length).fill(true);\n\n // Min-heap of entries; `idx` tells us which stream to refill from\n // after the entry's row is emitted.\n type Entry = {row: Node; idx: number};\n const heap: Entry[] = [];\n\n const siftUp = (start: number) => {\n let i = start;\n while (i > 0) {\n const p = (i - 1) >> 1;\n if (compare(heap[i].row, heap[p].row) >= 0) return;\n const t = heap[i];\n heap[i] = heap[p];\n heap[p] = t;\n i = p;\n }\n };\n\n const siftDown = (start: number) => {\n let i = start;\n const n = heap.length;\n while (true) {\n const l = (i << 1) + 1;\n const r = l + 1;\n let smallest = i;\n if (l < n && compare(heap[l].row, heap[smallest].row) < 0) smallest = l;\n if (r < n && compare(heap[r].row, heap[smallest].row) < 0) smallest = r;\n if (smallest === i) return;\n const t = heap[i];\n heap[i] = heap[smallest];\n heap[smallest] = t;\n i = smallest;\n }\n };\n\n // Pull the next Node from iterator `idx`, forwarding any 'yield's.\n // Returns the Node, or `undefined` once the stream is exhausted.\n const pullNext = function* (\n idx: number,\n ): Generator<'yield', Node | undefined, undefined> {\n while (true) {\n const r = iterators[idx].next();\n if (r.done) {\n active[idx] = false;\n return undefined;\n }\n if (r.value === 'yield') {\n yield 'yield';\n continue;\n }\n return r.value;\n }\n };\n\n try {\n // Prime: push the first row of each non-empty stream onto the heap.\n for (let i = 0; i < iterators.length; i++) {\n const row = yield* pullNext(i);\n if (row !== undefined) {\n heap.push({row, idx: i});\n siftUp(heap.length - 1);\n }\n }\n\n while (heap.length > 0) {\n // Root is the global min across all active streams.\n const top = heap[0];\n yield top.row;\n const next = yield* pullNext(top.idx);\n if (next !== undefined) {\n // Refill root in place (top === heap[0]) and sift down. The\n // already-yielded `top.row` value is captured by the yield, so\n // mutating it here doesn't affect what was emitted.\n top.row = next;\n siftDown(0);\n } else {\n // Stream exhausted. Move tail to root and shrink. Pop returns\n // the last entry; if the heap had only one entry it was the\n // root we just yielded, so we just leave the heap empty.\n const last = must(heap.pop());\n if (heap.length > 0) {\n heap[0] = last;\n siftDown(0);\n }\n }\n }\n } finally {\n // Close any iterators that aren't already exhausted so their\n // `finally` blocks (which release cursors / cached statements) run.\n for (let i = 0; i < iterators.length; i++) {\n if (active[i]) {\n iterators[i].return?.();\n }\n }\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"memory-source.js","names":["#tableName","#columns","#primaryKey","#primaryIndexSort","#indexes","#connections","#getPrimaryIndex","#fetch","#disconnect","#getSchema","#fetchMulti","#getOrCreateIndex","#overlay","#writeChange","#pushEpoch"],"sources":["../../../../../zql/src/ivm/memory-source.ts"],"sourcesContent":["import {assert, unreachable} from '../../../shared/src/asserts.ts';\nimport {BTreeSet} from '../../../shared/src/btree-set.ts';\nimport {hasOwn} from '../../../shared/src/has-own.ts';\nimport {once, toSorted} from '../../../shared/src/iterables.ts';\nimport {must} from '../../../shared/src/must.ts';\nimport type {\n Condition,\n Ordering,\n OrderPart,\n} from '../../../zero-protocol/src/ast.ts';\nimport type {Row, Value} from '../../../zero-protocol/src/data.ts';\nimport type {PrimaryKey} from '../../../zero-protocol/src/primary-key.ts';\nimport type {SchemaValue} from '../../../zero-types/src/schema-value.ts';\nimport type {DebugDelegate} from '../builder/debug-delegate.ts';\nimport {\n createPredicate,\n transformFilters,\n type NoSubqueryCondition,\n} from '../builder/filter.ts';\nimport {assertOrderingIncludesPK} from '../query/complete-ordering.ts';\nimport {ChangeType} from './change-type.ts';\nimport type {Change} from './change.ts';\nimport {makeAddChange, makeEditChange, makeRemoveChange} from './change.ts';\nimport {\n constraintMatchesPrimaryKey,\n constraintMatchesRow,\n primaryKeyConstraintFromFilters,\n type Constraint,\n} from './constraint.ts';\nimport {\n compareValues,\n makeComparator,\n valuesEqual,\n type Comparator,\n type Node,\n} from './data.ts';\nimport {filterPush} from './filter-push.ts';\nimport {\n skipYields,\n type FetchRequest,\n type Input,\n type MultiConstraint,\n type Output,\n type Start,\n} from './operator.ts';\nimport type {SourceSchema} from './schema.ts';\nimport {SourceChangeIndex} from './source-change-index.ts';\nimport type {\n Source,\n SourceChange,\n SourceChangeAdd,\n SourceChangeEdit,\n SourceChangeRemove,\n SourceInput,\n} from './source.ts';\nimport {makeSourceChangeAdd, makeSourceChangeRemove} from './source.ts';\nimport type {Stream} from './stream.ts';\n\nexport type Overlay = {\n epoch: number;\n change: SourceChange;\n};\n\nexport type Overlays = {\n add: Row | undefined;\n remove: Row | undefined;\n};\n\ntype Index = {\n comparator: Comparator;\n data: BTreeSet<Row>;\n usedBy: Set<Connection>;\n};\n\nexport type Connection = {\n input: Input;\n output: Output | undefined;\n sort?: Ordering | undefined;\n splitEditKeys: Set<string> | undefined;\n compareRows: Comparator;\n filters:\n | {\n condition: NoSubqueryCondition;\n predicate: (row: Row) => boolean;\n }\n | undefined;\n readonly debug?: DebugDelegate | undefined;\n lastPushedEpoch: number;\n};\n\n/**\n * A `MemorySource` is a source that provides data to the pipeline from an\n * in-memory data source.\n *\n * This data is kept in sorted order as downstream pipelines will always expect\n * the data they receive from `pull` to be in sorted order.\n */\nexport class MemorySource implements Source {\n readonly #tableName: string;\n readonly #columns: Record<string, SchemaValue>;\n readonly #primaryKey: PrimaryKey;\n readonly #primaryIndexSort: Ordering;\n readonly #indexes: Map<string, Index> = new Map();\n readonly #connections: Connection[] = [];\n\n #overlay: Overlay | undefined;\n #pushEpoch = 0;\n\n constructor(\n tableName: string,\n columns: Record<string, SchemaValue>,\n primaryKey: PrimaryKey,\n primaryIndexData?: BTreeSet<Row>,\n ) {\n this.#tableName = tableName;\n this.#columns = columns;\n this.#primaryKey = primaryKey;\n this.#primaryIndexSort = primaryKey.map(k => [k, 'asc']);\n const comparator = makeBoundComparator(this.#primaryIndexSort);\n this.#indexes.set(JSON.stringify(this.#primaryIndexSort), {\n comparator,\n data: primaryIndexData ?? new BTreeSet<Row>(comparator),\n usedBy: new Set(),\n });\n }\n\n get tableSchema() {\n return {\n name: this.#tableName,\n columns: this.#columns,\n primaryKey: this.#primaryKey,\n };\n }\n\n fork() {\n const primaryIndex = this.#getPrimaryIndex();\n return new MemorySource(\n this.#tableName,\n this.#columns,\n this.#primaryKey,\n primaryIndex.data.clone(),\n );\n }\n\n get data(): BTreeSet<Row> {\n return this.#getPrimaryIndex().data;\n }\n\n #getSchema(connection: Connection, unordered: boolean): SourceSchema {\n return {\n tableName: this.#tableName,\n columns: this.#columns,\n primaryKey: this.#primaryKey,\n sort: unordered ? undefined : connection.sort,\n system: 'client',\n relationships: {},\n isHidden: false,\n compareRows: connection.compareRows,\n };\n }\n\n connect(\n sort: Ordering | undefined,\n filters?: Condition,\n splitEditKeys?: Set<string>,\n ): SourceInput {\n const transformedFilters = transformFilters(filters);\n const unordered = sort === undefined;\n const internalSort = sort ?? this.#primaryIndexSort;\n\n const input: SourceInput = {\n getSchema: () => schema,\n fetch: req => this.#fetch(req, connection),\n setOutput: output => {\n connection.output = output;\n },\n destroy: () => {\n this.#disconnect(input);\n },\n fullyAppliedFilters: !transformedFilters.conditionsRemoved,\n };\n\n const connection: Connection = {\n input,\n output: undefined,\n sort: internalSort,\n splitEditKeys,\n compareRows: makeComparator(internalSort),\n filters: transformedFilters.filters\n ? {\n condition: transformedFilters.filters,\n predicate: createPredicate(transformedFilters.filters),\n }\n : undefined,\n lastPushedEpoch: 0,\n };\n const schema = this.#getSchema(connection, unordered);\n if (!unordered) {\n assertOrderingIncludesPK(internalSort, this.#primaryKey);\n }\n this.#connections.push(connection);\n return input;\n }\n\n #disconnect(input: Input): void {\n const idx = this.#connections.findIndex(c => c.input === input);\n assert(idx !== -1, 'Connection not found');\n this.#connections.splice(idx, 1);\n\n // TODO: We used to delete unused indexes here. But in common cases like\n // navigating into issue detail pages it caused a ton of constantly\n // building and destroying indexes.\n //\n // Perhaps some intelligent LRU or something is needed here but for now,\n // the opposite extreme of keeping all indexes for the lifetime of the\n // page seems better.\n }\n\n #getPrimaryIndex(): Index {\n const index = this.#indexes.get(JSON.stringify(this.#primaryIndexSort));\n assert(index, 'Primary index not found');\n return index;\n }\n\n #getOrCreateIndex(sort: Ordering, usedBy: Connection): Index {\n const key = JSON.stringify(sort);\n const index = this.#indexes.get(key);\n // Future optimization could use existing index if it's the same just sorted\n // in reverse of needed.\n if (index) {\n index.usedBy.add(usedBy);\n return index;\n }\n\n const comparator = makeBoundComparator(sort);\n\n // When creating these synchronously becomes a problem, a few options:\n // 1. Allow users to specify needed indexes up front\n // 2. Create indexes in a different thread asynchronously (this would require\n // modifying the BTree to be able to be passed over structured-clone, or using\n // a different library.)\n // 3. We could even theoretically do (2) on multiple threads and then merge the\n // results!\n const rows = toSorted(this.#getPrimaryIndex().data, comparator);\n const data = BTreeSet.fromSorted(comparator, rows);\n\n const newIndex = {comparator, data, usedBy: new Set([usedBy])};\n this.#indexes.set(key, newIndex);\n return newIndex;\n }\n\n // For unit testing that we correctly clean up indexes.\n getIndexKeys(): string[] {\n return [...this.#indexes.keys()];\n }\n\n *#fetch(req: FetchRequest, conn: Connection): Stream<Node | 'yield'> {\n // multiConstraints is handled by driving sub-fetches off the first\n // entry's values and post-filtering matches against any remaining\n // entries. TableSource implements multi-IN natively via SQL `AND` of\n // `IN` clauses; the in-memory path is correct but not specially\n // optimized — it pays the index-seek cost per primary value, then\n // walks each result against the rest.\n if (\n req.multiConstraints &&\n req.multiConstraints.some(mc => mc.length > 0)\n ) {\n yield* this.#fetchMulti(req, conn);\n return;\n }\n const requestedSort = must(conn.sort);\n const {compareRows} = conn;\n // Avoid allocating a new closure when not reversing (the common case).\n const connectionComparator: Comparator = req.reverse\n ? (r1, r2) => compareRows(r2, r1)\n : compareRows;\n\n const reqFilter = req.filter;\n const connFilterCondition = conn.filters?.condition;\n const mergedFilterCondition: NoSubqueryCondition | undefined =\n connFilterCondition && reqFilter\n ? {type: 'and', conditions: [connFilterCondition, reqFilter]}\n : (connFilterCondition ?? reqFilter);\n const mergedFilterPredicate = mergePredicates(\n conn.filters?.predicate,\n reqFilter,\n );\n\n const pkConstraint = primaryKeyConstraintFromFilters(\n mergedFilterCondition,\n this.#primaryKey,\n );\n // The primary key constraint will be more limiting than the constraint\n // so swap out to that if it exists.\n const fetchOrPkConstraint = pkConstraint ?? req.constraint;\n\n // If there is a constraint, we need an index sorted by it first.\n const indexSort: OrderPart[] = [];\n if (fetchOrPkConstraint) {\n for (const key of Object.keys(fetchOrPkConstraint)) {\n indexSort.push([key, 'asc']);\n }\n }\n\n // For the special case of constraining by PK, we don't need to worry about\n // any requested sort since there can only be one result. Otherwise we also\n // need the index sorted by the requested sort.\n if (\n this.#primaryKey.length > 1 ||\n !fetchOrPkConstraint ||\n !constraintMatchesPrimaryKey(fetchOrPkConstraint, this.#primaryKey)\n ) {\n indexSort.push(...requestedSort);\n }\n\n const index = this.#getOrCreateIndex(indexSort, conn);\n const {data, comparator: compare} = index;\n // Avoid allocating a new closure when not reversing (the common case).\n const indexComparator: Comparator = req.reverse\n ? (r1, r2) => compare(r2, r1)\n : compare;\n\n const startAt = req.start?.row;\n\n // If there is a constraint, we want to start our scan at the first row that\n // matches the constraint. But because the next OrderPart can be `desc`,\n // it's not true that {[constraintKey]: constraintValue} is the first\n // matching row. Because in that case, the other fields will all be\n // `undefined`, and in Zero `undefined` is always less than any other value.\n // So if the second OrderPart is descending then `undefined` values will\n // actually be the *last* row. We need a way to stay \"start at the first row\n // with this constraint value\". RowBound with the corresponding compareBound\n // comparator accomplishes this. The right thing is probably to teach the\n // btree library to support this concept.\n let scanStart: RowBound | undefined;\n\n if (fetchOrPkConstraint) {\n scanStart = {};\n for (const [key, dir] of indexSort) {\n if (hasOwn(fetchOrPkConstraint, key)) {\n scanStart[key] = fetchOrPkConstraint[key];\n } else {\n if (req.reverse) {\n scanStart[key] = dir === 'asc' ? maxValue : minValue;\n } else {\n scanStart[key] = dir === 'asc' ? minValue : maxValue;\n }\n }\n }\n } else {\n scanStart = startAt;\n }\n\n const rowsIterable = generateRows(data, scanStart, req.reverse);\n\n // Fast path: no overlay to apply, no `start`, and no connection filters.\n // This is the common shape for join child-lookups, plain scans, and source\n // fetches that sit beneath a downstream `Filter` operator. In that case the\n // whole generator stack (generateWithOverlay → generateWithStart →\n // skipYields → generateWithConstraint) reduces to: walk rows from\n // `scanStart`, wrap each as a Node, and stop once the constraint no longer\n // matches (rows are sorted by the constraint key first, so matches are\n // contiguous). Collapsing the five chained generators into one loop removes\n // a large amount of per-row generator-resume overhead on the hottest path.\n const overlayActive =\n this.#overlay && conn.lastPushedEpoch >= this.#overlay.epoch;\n if (!overlayActive && !req.start && !conn.filters && !req.filter) {\n const {constraint} = req;\n for (const row of rowsIterable) {\n if (constraint && !constraintMatchesRow(constraint, row)) {\n break;\n }\n yield {row, relationships: {}};\n }\n return;\n }\n\n const withOverlay = generateWithOverlay(\n startAt,\n pkConstraint ? once(rowsIterable) : rowsIterable,\n // use `req.constraint` here and not `fetchOrPkConstraint` since `fetchOrPkConstraint` could be the\n // primary key constraint. The primary key constraint comes from filters and is acting as a filter\n // rather than as the fetch constraint.\n req.constraint,\n this.#overlay,\n conn.lastPushedEpoch,\n // Use indexComparator, generateWithOverlayInner has a subtle dependency\n // on this. Since generateWithConstraint is done after\n // generateWithOverlay, the generator consumed by generateWithOverlayInner\n // does not end when the constraint stops matching and so the final\n // check to yield an add overlay if not yet yielded is not reached.\n // However, using the indexComparator the add overlay will be less than\n // the first row that does not match the constraint, and so any\n // not yet yielded add overlay will be yielded when the first row\n // not matching the constraint is reached.\n indexComparator,\n // `startAt` is `req.start.row`, a bound in the connection's sort order,\n // so it must be compared in that order -- not `indexComparator`, which\n // with a constraint leads with the constraint keys. The two agree only\n // when the overlay row and `startAt` share those key values; they do not\n // for `#fetchMulti`'s per-value sub-fetches, which pin one primary key\n // while still carrying the caller's connection-sort `start`. Comparing\n // there by the constraint key dropped the in-flight overlay and served\n // pre-push data. Same distinction #4926 drew for `generateWithStart`.\n connectionComparator,\n mergedFilterPredicate,\n );\n\n const withConstraint = generateWithConstraint(\n skipYields(\n generateWithStart(withOverlay, req.start, connectionComparator),\n ),\n // we use `req.constraint` and not `fetchOrPkConstraint` here because we need to\n // AND the constraint with what could have been the primary key constraint\n req.constraint,\n );\n\n yield* mergedFilterPredicate\n ? generateWithFilter(withConstraint, mergedFilterPredicate)\n : withConstraint;\n }\n\n *#fetchMulti(req: FetchRequest, conn: Connection): Stream<Node | 'yield'> {\n // Caller (`#fetch`) guards entry on `req.multiConstraints.some(mc =>\n // mc.length > 0)`, so `multis` is guaranteed non-empty after the\n // empty-entry filter. Per the MultiConstraint contract (operator.ts),\n // entries are non-empty, share key shape, are unique, and are\n // key-compatible with `req.constraint`.\n const multis = must(req.multiConstraints).filter(mc => mc.length > 0);\n const primary = multis[0];\n const rest = multis.slice(1);\n const baseConstraint = req.constraint;\n\n // Drive sub-fetches off `primary`. Within each sub-fetch the source\n // can use its index for `primary`; we then post-filter rows by\n // checking they also match every entry in `rest`. Per the\n // MultiConstraint contract (see operator.ts), entries are unique and\n // key-compatible with `baseConstraint`, so we don't dedupe or check\n // compatibility here.\n const subStreams: Stream<Node | 'yield'>[] = primary.map(c => {\n const merged: Constraint = baseConstraint ? {...baseConstraint, ...c} : c;\n return this.#fetch(\n {...req, constraint: merged, multiConstraints: undefined},\n conn,\n );\n });\n const merged = mergeSortedStreams(\n subStreams,\n req.reverse\n ? (a, b) => conn.compareRows(b.row, a.row)\n : (a, b) => conn.compareRows(a.row, b.row),\n );\n\n if (rest.length === 0) {\n yield* merged;\n return;\n }\n\n for (const node of merged) {\n if (node === 'yield') {\n yield 'yield';\n continue;\n }\n let matchesAll = true;\n for (const mc of rest) {\n let any = false;\n for (const c of mc) {\n if (constraintMatchesRow(c, node.row)) {\n any = true;\n break;\n }\n }\n if (!any) {\n matchesAll = false;\n break;\n }\n }\n if (matchesAll) yield node;\n }\n }\n\n *push(change: SourceChange): Stream<'yield'> {\n for (const result of this.genPush(change)) {\n if (result === 'yield') {\n yield result;\n }\n }\n }\n\n *genPush(change: SourceChange) {\n const primaryIndex = this.#getPrimaryIndex();\n const {data} = primaryIndex;\n const exists = (row: Row) => data.has(row);\n const setOverlay = (o: Overlay | undefined) => (this.#overlay = o);\n const writeChange = (c: SourceChange) => this.#writeChange(c);\n yield* genPushAndWriteWithSplitEdit(\n this.#connections,\n change,\n exists,\n setOverlay,\n writeChange,\n () => ++this.#pushEpoch,\n );\n }\n\n #writeChange(change: SourceChange) {\n for (const {data} of this.#indexes.values()) {\n switch (change[SourceChangeIndex.TYPE]) {\n case ChangeType.ADD: {\n const added = data.add(change[SourceChangeIndex.ROW]);\n // must succeed since we checked has() above.\n assert(\n added,\n 'MemorySource: add must succeed since row existence was already checked',\n );\n break;\n }\n case ChangeType.REMOVE: {\n const removed = data.delete(change[SourceChangeIndex.ROW]);\n // must succeed since we checked has() above.\n assert(\n removed,\n 'MemorySource: remove must succeed since row existence was already checked',\n );\n break;\n }\n case ChangeType.EDIT: {\n // TODO: We could see if the PK (form the index tree's perspective)\n // changed and if not we could use set.\n // We cannot just do `set` with the new value since the `oldRow` might\n // not map to the same entry as the new `row` in the index btree.\n const removed = data.delete(change[SourceChangeIndex.OLD_ROW]);\n // must succeed since we checked has() above.\n assert(\n removed,\n 'MemorySource: edit remove must succeed since row existence was already checked',\n );\n data.add(change[SourceChangeIndex.ROW]);\n break;\n }\n default:\n unreachable(change);\n }\n }\n }\n}\n\nfunction mergePredicates(\n connPredicate: ((row: Row) => boolean) | undefined,\n reqFilter: NoSubqueryCondition | undefined,\n): ((row: Row) => boolean) | undefined {\n if (!reqFilter) {\n return connPredicate;\n }\n const reqPredicate = createPredicate(reqFilter);\n if (!connPredicate) {\n return reqPredicate;\n }\n return row => connPredicate(row) && reqPredicate(row);\n}\n\nfunction* generateWithConstraint(\n it: Stream<Node>,\n constraint: Constraint | undefined,\n) {\n for (const node of it) {\n if (constraint && !constraintMatchesRow(constraint, node.row)) {\n break;\n }\n yield node;\n }\n}\n\nfunction* generateWithFilter(it: Stream<Node>, filter: (row: Row) => boolean) {\n for (const node of it) {\n if (filter(node.row)) {\n yield node;\n }\n }\n}\n\nexport function* genPushAndWriteWithSplitEdit(\n connections: readonly Connection[],\n change: SourceChange,\n exists: (row: Row) => boolean,\n setOverlay: (o: Overlay | undefined) => Overlay | undefined,\n writeChange: (c: SourceChange) => void,\n getNextEpoch: () => number,\n) {\n let shouldSplitEdit = false;\n if (change[SourceChangeIndex.TYPE] === ChangeType.EDIT) {\n for (const {splitEditKeys} of connections) {\n if (splitEditKeys) {\n for (const key of splitEditKeys) {\n if (\n !valuesEqual(\n change[SourceChangeIndex.ROW][key],\n change[SourceChangeIndex.OLD_ROW][key],\n )\n ) {\n shouldSplitEdit = true;\n break;\n }\n }\n }\n }\n }\n\n if (change[SourceChangeIndex.TYPE] === ChangeType.EDIT && shouldSplitEdit) {\n yield* genPushAndWrite(\n connections,\n makeSourceChangeRemove(change[SourceChangeIndex.OLD_ROW]),\n exists,\n setOverlay,\n writeChange,\n getNextEpoch(),\n );\n yield* genPushAndWrite(\n connections,\n makeSourceChangeAdd(change[SourceChangeIndex.ROW]),\n exists,\n setOverlay,\n writeChange,\n getNextEpoch(),\n );\n } else {\n yield* genPushAndWrite(\n connections,\n change,\n exists,\n setOverlay,\n writeChange,\n getNextEpoch(),\n );\n }\n}\n\nfunction* genPushAndWrite(\n connections: readonly Connection[],\n change: SourceChangeAdd | SourceChangeRemove | SourceChangeEdit,\n exists: (row: Row) => boolean,\n setOverlay: (o: Overlay | undefined) => Overlay | undefined,\n writeChange: (c: SourceChange) => void,\n pushEpoch: number,\n) {\n for (const x of genPush(connections, change, exists, setOverlay, pushEpoch)) {\n yield x;\n }\n writeChange(change);\n}\n\nfunction* genPush(\n connections: readonly Connection[],\n change: SourceChange,\n exists: (row: Row) => boolean,\n setOverlay: (o: Overlay | undefined) => void,\n pushEpoch: number,\n) {\n switch (change[SourceChangeIndex.TYPE]) {\n case ChangeType.ADD:\n assert(\n !exists(change[SourceChangeIndex.ROW]),\n () => `Row already exists ${stringify(change)}`,\n );\n break;\n case ChangeType.REMOVE:\n assert(\n exists(change[SourceChangeIndex.ROW]),\n () => `Row not found ${stringify(change)}`,\n );\n break;\n case ChangeType.EDIT:\n assert(\n exists(change[SourceChangeIndex.OLD_ROW]),\n () => `Row not found ${stringify(change)}`,\n );\n break;\n default:\n unreachable(change);\n }\n\n for (const conn of connections) {\n const {output, filters, input} = conn;\n if (output) {\n conn.lastPushedEpoch = pushEpoch;\n setOverlay({epoch: pushEpoch, change});\n const outputChange: Change =\n change[SourceChangeIndex.TYPE] === ChangeType.EDIT\n ? makeEditChange(\n {row: change[SourceChangeIndex.ROW], relationships: {}},\n {row: change[SourceChangeIndex.OLD_ROW], relationships: {}},\n )\n : change[SourceChangeIndex.TYPE] === ChangeType.ADD\n ? makeAddChange({\n row: change[SourceChangeIndex.ROW],\n relationships: {},\n })\n : makeRemoveChange({\n row: change[SourceChangeIndex.ROW],\n relationships: {},\n });\n yield* filterPush(outputChange, output, input, filters?.predicate);\n yield undefined;\n }\n }\n\n setOverlay(undefined);\n}\n\nexport function* generateWithStart(\n nodes: Iterable<Node | 'yield'>,\n start: Start | undefined,\n compare: (r1: Row, r2: Row) => number,\n): Stream<Node | 'yield'> {\n if (!start) {\n yield* nodes;\n return;\n }\n let started = false;\n for (const node of nodes) {\n if (node === 'yield') {\n yield node;\n continue;\n }\n if (!started) {\n if (start.basis === 'at') {\n if (compare(node.row, start.row) >= 0) {\n started = true;\n }\n } else if (start.basis === 'after') {\n if (compare(node.row, start.row) > 0) {\n started = true;\n }\n }\n }\n if (started) {\n yield node;\n }\n }\n}\n\n/**\n * Takes an iterator and overlay.\n * Splices the overlay into the iterator at the correct position.\n *\n * @param startAt - if there is a lower bound to the stream. If the lower bound of the stream\n * is above the overlay, the overlay will be skipped.\n * @param rows - the stream into which the overlay should be spliced\n * @param constraint - constraint that was applied to the rowIterator and should\n * also be applied to the overlay.\n * @param overlay - the overlay values to splice in\n * @param compare - the comparator to use to find the position for the overlay.\n * Must order rows the same way `rows` is ordered. For `MemorySource` that is\n * *index* order, which leads with the constraint keys.\n * @param startAtCompare - the comparator for `startAt`, which is a bound in the\n * *connection's* sort order. Only the same as `compare` when the stream's order\n * happens to be the connection's -- true for `TableSource` (SQL does the\n * ordering) but not for a constrained `MemorySource` fetch. Conflating the two\n * is what #4926 fixed for `generateWithStart`; this parameter is the same\n * distinction for the overlay's own `startAt` pruning.\n */\nexport function* generateWithOverlay(\n startAt: Row | undefined,\n rows: Iterable<Row>,\n constraint: Constraint | undefined,\n overlay: Overlay | undefined,\n lastPushedEpoch: number,\n compare: Comparator,\n startAtCompare: Comparator,\n filterPredicate?: (row: Row) => boolean | undefined,\n multiConstraints?: readonly MultiConstraint[] | undefined,\n) {\n let overlayToApply: Overlay | undefined = undefined;\n if (overlay && lastPushedEpoch >= overlay.epoch) {\n overlayToApply = overlay;\n }\n const overlays = computeOverlays(\n startAt,\n constraint,\n overlayToApply,\n startAtCompare,\n filterPredicate,\n multiConstraints,\n );\n yield* generateWithOverlayInner(rows, overlays, compare);\n}\n\nfunction computeOverlays(\n startAt: Row | undefined,\n constraint: Constraint | undefined,\n overlay: Overlay | undefined,\n // Only ever used for `overlaysForStartAt`, so this is the *connection*-order\n // comparator, not the one used to splice the overlay into the row stream.\n startAtCompare: Comparator,\n filterPredicate?: (row: Row) => boolean | undefined,\n multiConstraints?: readonly MultiConstraint[] | undefined,\n): Overlays {\n let overlays: Overlays = {\n add: undefined,\n remove: undefined,\n };\n switch (overlay?.change[SourceChangeIndex.TYPE]) {\n case ChangeType.ADD:\n overlays = {\n add: overlay.change[SourceChangeIndex.ROW],\n remove: undefined,\n };\n break;\n case ChangeType.REMOVE:\n overlays = {\n add: undefined,\n remove: overlay.change[SourceChangeIndex.ROW],\n };\n break;\n case ChangeType.EDIT:\n overlays = {\n add: overlay.change[SourceChangeIndex.ROW],\n remove: overlay.change[SourceChangeIndex.OLD_ROW],\n };\n break;\n }\n\n if (startAt) {\n overlays = overlaysForStartAt(overlays, startAt, startAtCompare);\n }\n\n if (constraint) {\n overlays = overlaysForConstraint(overlays, constraint);\n }\n\n overlays = applyMultiConstraintsToOverlays(overlays, multiConstraints);\n\n if (filterPredicate) {\n overlays = overlaysForFilterPredicate(overlays, filterPredicate);\n }\n\n return overlays;\n}\n\nfunction applyMultiConstraintsToOverlays(\n overlays: Overlays,\n multiConstraints: readonly MultiConstraint[] | undefined,\n): Overlays {\n if (!multiConstraints) return overlays;\n for (const mc of multiConstraints) {\n if (mc.length > 0) {\n overlays = overlaysForMultiConstraint(overlays, mc);\n }\n }\n return overlays;\n}\n\nexport {overlaysForMultiConstraint as overlaysForMultiConstraintForTest};\n\nfunction overlaysForMultiConstraint(\n {add, remove}: Overlays,\n multiConstraint: MultiConstraint,\n): Overlays {\n const matchesAny = (row: Row | undefined) => {\n if (row === undefined) return false;\n for (const c of multiConstraint) {\n if (constraintMatchesRow(c, row)) return true;\n }\n return false;\n };\n return {\n add: matchesAny(add) ? add : undefined,\n remove: matchesAny(remove) ? remove : undefined,\n };\n}\n\nexport {overlaysForStartAt as overlaysForStartAtForTest};\n\nfunction overlaysForStartAt(\n {add, remove}: Overlays,\n startAt: Row,\n compare: Comparator,\n): Overlays {\n const undefinedIfBeforeStartAt = (row: Row | undefined) =>\n row === undefined || compare(row, startAt) < 0 ? undefined : row;\n return {\n add: undefinedIfBeforeStartAt(add),\n remove: undefinedIfBeforeStartAt(remove),\n };\n}\n\nexport {overlaysForConstraint as overlaysForConstraintForTest};\n\nfunction overlaysForConstraint(\n {add, remove}: Overlays,\n constraint: Constraint,\n): Overlays {\n const undefinedIfDoesntMatchConstraint = (row: Row | undefined) =>\n row === undefined || !constraintMatchesRow(constraint, row)\n ? undefined\n : row;\n\n return {\n add: undefinedIfDoesntMatchConstraint(add),\n remove: undefinedIfDoesntMatchConstraint(remove),\n };\n}\n\nfunction overlaysForFilterPredicate(\n {add, remove}: Overlays,\n filterPredicate: (row: Row) => boolean | undefined,\n): Overlays {\n const undefinedIfDoesntMatchFilter = (row: Row | undefined) =>\n row === undefined || !filterPredicate(row) ? undefined : row;\n\n return {\n add: undefinedIfDoesntMatchFilter(add),\n remove: undefinedIfDoesntMatchFilter(remove),\n };\n}\n\nexport function* generateWithOverlayInner(\n rowIterator: Iterable<Row>,\n overlays: Overlays,\n compare: (r1: Row, r2: Row) => number,\n) {\n let addOverlayYielded = false;\n let removeOverlaySkipped = false;\n for (const row of rowIterator) {\n if (!addOverlayYielded && overlays.add) {\n const cmp = compare(overlays.add, row);\n if (cmp < 0) {\n addOverlayYielded = true;\n yield {row: overlays.add, relationships: {}};\n }\n }\n\n if (!removeOverlaySkipped && overlays.remove) {\n const cmp = compare(overlays.remove, row);\n if (cmp === 0) {\n removeOverlaySkipped = true;\n continue;\n }\n }\n yield {row, relationships: {}};\n }\n\n if (!addOverlayYielded && overlays.add) {\n yield {row: overlays.add, relationships: {}};\n }\n}\n\n/**\n * Like {@link generateWithOverlay} but for unordered streams.\n * No `startAt` or comparator needed. Injects remove/old-edit rows eagerly\n * at the start, and suppresses add/new-edit rows inline by PK match.\n */\nexport function* generateWithOverlayUnordered(\n rows: Iterable<Row>,\n constraint: Constraint | undefined,\n overlay: Overlay | undefined,\n lastPushedEpoch: number,\n primaryKey: PrimaryKey,\n filterPredicate?: (row: Row) => boolean,\n multiConstraints?: readonly MultiConstraint[] | undefined,\n) {\n let overlayToApply: Overlay | undefined = undefined;\n if (overlay && lastPushedEpoch >= overlay.epoch) {\n overlayToApply = overlay;\n }\n let overlays: Overlays = {add: undefined, remove: undefined};\n switch (overlayToApply?.change[SourceChangeIndex.TYPE]) {\n case ChangeType.ADD:\n overlays = {\n add: overlayToApply.change[SourceChangeIndex.ROW],\n remove: undefined,\n };\n break;\n case ChangeType.REMOVE:\n overlays = {\n add: undefined,\n remove: overlayToApply.change[SourceChangeIndex.ROW],\n };\n break;\n case ChangeType.EDIT:\n overlays = {\n add: overlayToApply.change[SourceChangeIndex.ROW],\n remove: overlayToApply.change[SourceChangeIndex.OLD_ROW],\n };\n break;\n }\n if (constraint) {\n overlays = overlaysForConstraint(overlays, constraint);\n }\n overlays = applyMultiConstraintsToOverlays(overlays, multiConstraints);\n if (filterPredicate) {\n overlays = overlaysForFilterPredicate(overlays, filterPredicate);\n }\n yield* generateWithOverlayInnerUnordered(rows, overlays, primaryKey);\n}\n\nexport function* generateWithOverlayInnerUnordered(\n rowIterator: Iterable<Row>,\n overlays: Overlays,\n primaryKey: PrimaryKey,\n) {\n // Eager inject: yield the add overlay at the start (row not yet in storage)\n if (overlays.add) {\n yield {row: overlays.add, relationships: {}};\n }\n // Stream with inline suppress: skip the remove overlay (row still in storage)\n let removeSkipped = false;\n for (const row of rowIterator) {\n if (\n !removeSkipped &&\n overlays.remove &&\n rowMatchesPK(overlays.remove, row, primaryKey)\n ) {\n removeSkipped = true;\n continue;\n }\n yield {row, relationships: {}};\n }\n}\n\nfunction rowMatchesPK(a: Row, b: Row, primaryKey: PrimaryKey): boolean {\n for (const key of primaryKey) {\n if (!valuesEqual(a[key], b[key])) {\n return false;\n }\n }\n return true;\n}\n\n/**\n * A location to begin scanning an index from. Can either be a specific value\n * or the min or max possible value for the type. This is used to start a scan\n * at the beginning of the rows matching a constraint.\n */\ntype Bound = Value | MinValue | MaxValue;\ntype RowBound = Record<string, Bound>;\nconst minValue = Symbol('min-value');\ntype MinValue = typeof minValue;\nconst maxValue = Symbol('max-value');\ntype MaxValue = typeof maxValue;\n\nfunction makeBoundComparator(sort: Ordering): Comparator {\n // Pre-extract the first two keys/directions to avoid per-call array access.\n // All paths share one function literal (single SFI) so the BTree comparator call site\n // stays monomorphic across indexes with different sort orderings, preventing V8 IC deopt.\n // Even a 2-SFI split (e.g. separate len=1 path) creates a polymorphic IC that\n // measurably regresses performance, so we keep a single return body.\n const len = sort.length;\n const k0 = sort[0][0];\n const a0 = sort[0][1] === 'asc';\n const k1 = len > 1 ? sort[1][0] : '';\n const a1 = len > 1 ? sort[1][1] === 'asc' : true;\n\n return (a: RowBound, b: RowBound) => {\n const c0 = a0 ? compareBounds(a[k0], b[k0]) : compareBounds(b[k0], a[k0]);\n if (len === 1 || c0 !== 0) return c0;\n const c1 = a1 ? compareBounds(a[k1], b[k1]) : compareBounds(b[k1], a[k1]);\n if (len === 2 || c1 !== 0) return c1;\n // Hot! Do not use destructuring\n for (let i = 2; i < len; i++) {\n const cmp = compareBounds(a[sort[i][0]], b[sort[i][0]]);\n if (cmp !== 0) return sort[i][1] === 'asc' ? cmp : -cmp;\n }\n return 0;\n };\n}\n\nfunction compareBounds(a: Bound, b: Bound): number {\n if (a === b) {\n return 0;\n }\n // Use typeof to guard the Symbol sentinel checks first. This gives V8 a\n // clear type discriminant so the common non-symbol path compiles as a\n // specialised numeric/string fast-path without a Smi deopt when the\n // minValue/maxValue sentinel symbols appear.\n if (typeof a === 'symbol') {\n return a === minValue ? -1 : 1;\n }\n if (typeof b === 'symbol') {\n return b === minValue ? 1 : -1;\n }\n return compareValues(a, b);\n}\n\nfunction* generateRows(\n data: BTreeSet<Row>,\n scanStart: RowBound | undefined,\n reverse: boolean | undefined,\n) {\n yield* data[reverse ? 'valuesFromReversed' : 'valuesFrom'](\n scanStart as Row | undefined,\n );\n}\n\nexport function stringify(change: SourceChange) {\n return JSON.stringify(change, (_, v) =>\n typeof v === 'bigint' ? v.toString() : v,\n );\n}\n\n/**\n * N-way merge of pre-sorted Node streams. Each input stream must yield\n * Nodes in `compare` order (and may interleave 'yield' values, which are\n * forwarded as-is). The merged stream yields Nodes in `compare` order.\n *\n * Implemented as a binary min-heap keyed by `compare(entry.row, ...)`, so\n * each emit costs O(log K) (K = number of streams) rather than the O(K)\n * a linear-scan-of-heads merge would. Matters when FlippedJoin chunks a\n * large `multiConstraints` IN-list into hundreds of sub-fetches.\n *\n * If the merged stream is closed early (e.g. downstream `Take` `break`s\n * after hitting its limit, prompting JS to call `.return()` on this\n * generator), the `finally` block propagates `.return()` to each\n * non-exhausted sub-iterator so the underlying sources (SQLite cursors,\n * etc.) can run their cleanup. Without this, mid-merge early-termination\n * leaves cursors open, causing later writes on the same connection to\n * fail with \"database connection is busy executing a query\".\n */\nexport function* mergeSortedStreams(\n streams: readonly Stream<Node | 'yield'>[],\n compare: (a: Node, b: Node) => number,\n): Stream<Node | 'yield'> {\n const iterators: Iterator<Node | 'yield'>[] = streams.map(s =>\n s[Symbol.iterator](),\n );\n // True while iterators[i] hasn't yet returned `done`. The finally\n // block uses this to skip already-exhausted streams when propagating\n // `.return()`.\n const active: boolean[] = new Array(iterators.length).fill(true);\n\n // Min-heap of entries; `idx` tells us which stream to refill from\n // after the entry's row is emitted.\n type Entry = {row: Node; idx: number};\n const heap: Entry[] = [];\n\n const siftUp = (start: number) => {\n let i = start;\n while (i > 0) {\n const p = (i - 1) >> 1;\n if (compare(heap[i].row, heap[p].row) >= 0) return;\n const t = heap[i];\n heap[i] = heap[p];\n heap[p] = t;\n i = p;\n }\n };\n\n const siftDown = (start: number) => {\n let i = start;\n const n = heap.length;\n while (true) {\n const l = (i << 1) + 1;\n const r = l + 1;\n let smallest = i;\n if (l < n && compare(heap[l].row, heap[smallest].row) < 0) smallest = l;\n if (r < n && compare(heap[r].row, heap[smallest].row) < 0) smallest = r;\n if (smallest === i) return;\n const t = heap[i];\n heap[i] = heap[smallest];\n heap[smallest] = t;\n i = smallest;\n }\n };\n\n // Pull the next Node from iterator `idx`, forwarding any 'yield's.\n // Returns the Node, or `undefined` once the stream is exhausted.\n const pullNext = function* (\n idx: number,\n ): Generator<'yield', Node | undefined, undefined> {\n while (true) {\n const r = iterators[idx].next();\n if (r.done) {\n active[idx] = false;\n return undefined;\n }\n if (r.value === 'yield') {\n yield 'yield';\n continue;\n }\n return r.value;\n }\n };\n\n try {\n // Prime: push the first row of each non-empty stream onto the heap.\n for (let i = 0; i < iterators.length; i++) {\n const row = yield* pullNext(i);\n if (row !== undefined) {\n heap.push({row, idx: i});\n siftUp(heap.length - 1);\n }\n }\n\n while (heap.length > 0) {\n // Root is the global min across all active streams.\n const top = heap[0];\n yield top.row;\n const next = yield* pullNext(top.idx);\n if (next !== undefined) {\n // Refill root in place (top === heap[0]) and sift down. The\n // already-yielded `top.row` value is captured by the yield, so\n // mutating it here doesn't affect what was emitted.\n top.row = next;\n siftDown(0);\n } else {\n // Stream exhausted. Move tail to root and shrink. Pop returns\n // the last entry; if the heap had only one entry it was the\n // root we just yielded, so we just leave the heap empty.\n const last = must(heap.pop());\n if (heap.length > 0) {\n heap[0] = last;\n siftDown(0);\n }\n }\n }\n } finally {\n // Close any iterators that aren't already exhausted so their\n // `finally` blocks (which release cursors / cached statements) run.\n for (let i = 0; i < iterators.length; i++) {\n if (active[i]) {\n iterators[i].return?.();\n }\n }\n }\n}\n"],"mappings":"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"}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { JSONValue } from '../../../shared/src/json.ts';
|
|
2
2
|
import type { Row } from '../../../zero-protocol/src/data.ts';
|
|
3
|
+
import type { NoSubqueryCondition } from '../builder/filter.ts';
|
|
3
4
|
import type { Change } from './change.ts';
|
|
4
5
|
import type { Constraint } from './constraint.ts';
|
|
5
6
|
import type { Node } from './data.ts';
|
|
@@ -69,6 +70,45 @@ export type FetchRequest = {
|
|
|
69
70
|
readonly start?: Start | undefined;
|
|
70
71
|
/** Whether to fetch in reverse order of the SourceSchema's sort. */
|
|
71
72
|
readonly reverse?: boolean | undefined;
|
|
73
|
+
/**
|
|
74
|
+
* Additional predicate the consumer wants the source/intermediate operators
|
|
75
|
+
* to apply to the fetched rows. The receiver must AND this with whatever
|
|
76
|
+
* filtering it already applies. Sources may use this to drive index
|
|
77
|
+
* selection (e.g. PK lookup) the same way they use the connection-time
|
|
78
|
+
* filter.
|
|
79
|
+
*
|
|
80
|
+
* Always a `NoSubqueryCondition` — anything passed via `req.filter` must
|
|
81
|
+
* already have had correlated subqueries stripped (see `transformFilters`).
|
|
82
|
+
*
|
|
83
|
+
* Contract:
|
|
84
|
+
* - The only operator that *introduces* `req.filter` is `FilterStart`. It
|
|
85
|
+
* AND-merges its static condition with whatever `req.filter` it receives
|
|
86
|
+
* and forwards the merged value upstream. No other operator should
|
|
87
|
+
* construct a `req.filter`.
|
|
88
|
+
* - "Pass-through" fetch operators (`Skip`, `Take`, `Join`, `FlippedJoin`,
|
|
89
|
+
* `UnionFanIn`, `UnionFanOut`) MUST preserve `req.filter` when forwarding
|
|
90
|
+
* to their input — typically via `{...req, ...overrides}` spread. Dropping
|
|
91
|
+
* it silently regresses predicate pushdown for queries below the
|
|
92
|
+
* forwarding operator.
|
|
93
|
+
* - Operators that *initiate* internal fetches (e.g., `Join.#pushChildChange`,
|
|
94
|
+
* `FlippedJoin.#pushChildChange`, `UnionFanIn.#pushInternalChange`,
|
|
95
|
+
* `Take`'s push-time bound recomputation) construct fresh
|
|
96
|
+
* `FetchRequest`s without `req.filter`, and that is correct: there is no
|
|
97
|
+
* inbound consumer-side filter at those call sites. Any `FilterStart`
|
|
98
|
+
* that sits between such an internal fetch and the source will still
|
|
99
|
+
* apply its own condition on the way through, so the source still gets
|
|
100
|
+
* the right WHERE.
|
|
101
|
+
* - `Cap` is *not* a pass-through: it intentionally builds fresh
|
|
102
|
+
* `{constraint}` requests when replaying tracked PKs and drops any
|
|
103
|
+
* incoming `req.filter`. This is sound only because `Cap`'s sole
|
|
104
|
+
* consumer in production is a `Join`'s child fetch (see
|
|
105
|
+
* `Join.#processParentNode` in `join.ts`), which itself never carries
|
|
106
|
+
* `req.filter` — Join's child fetches are constructed with just
|
|
107
|
+
* `{constraint}` derived from join keys. If `Cap` is ever wired below
|
|
108
|
+
* something that does push a `req.filter`, this carve-out becomes a
|
|
109
|
+
* correctness bug.
|
|
110
|
+
*/
|
|
111
|
+
readonly filter?: NoSubqueryCondition | undefined;
|
|
72
112
|
};
|
|
73
113
|
export type Start = {
|
|
74
114
|
readonly row: Row;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"operator.d.ts","sourceRoot":"","sources":["../../../../../zql/src/ivm/operator.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,6BAA6B,CAAC;AAC3D,OAAO,KAAK,EAAC,GAAG,EAAC,MAAM,oCAAoC,CAAC;AAC5D,OAAO,KAAK,EAAC,MAAM,EAAC,MAAM,aAAa,CAAC;AACxC,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,iBAAiB,CAAC;AAChD,OAAO,KAAK,EAAC,IAAI,EAAC,MAAM,WAAW,CAAC;AACpC,OAAO,KAAK,EAAC,YAAY,EAAC,MAAM,aAAa,CAAC;AAC9C,OAAO,KAAK,EAAC,MAAM,EAAC,MAAM,aAAa,CAAC;AAExC,OAAO,EAAC,UAAU,EAAC,MAAM,kBAAkB,CAAC;AAE5C;;GAEG;AACH,MAAM,WAAW,SAAS;IACxB,iDAAiD;IACjD,SAAS,IAAI,YAAY,CAAC;IAE1B;;;;OAIG;IACH,OAAO,IAAI,IAAI,CAAC;CACjB;AAED,MAAM,WAAW,KAAM,SAAQ,SAAS;IACtC,+CAA+C;IAC/C,SAAS,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IAEhC;;;;;;;;;;;;OAYG;IACH,KAAK,CAAC,GAAG,EAAE,YAAY,GAAG,MAAM,CAAC,IAAI,GAAG,OAAO,CAAC,CAAC;CAClD;AAED;;;;;;;;;;;;;;GAcG;AACH,MAAM,MAAM,eAAe,GAAG,SAAS,UAAU,EAAE,CAAC;AAEpD,MAAM,MAAM,YAAY,GAAG;IACzB,QAAQ,CAAC,UAAU,CAAC,EAAE,UAAU,GAAG,SAAS,CAAC;IAE7C;;;;;;;;OAQG;IACH,QAAQ,CAAC,gBAAgB,CAAC,EAAE,SAAS,eAAe,EAAE,GAAG,SAAS,CAAC;IAEnE,kFAAkF;IAClF,QAAQ,CAAC,KAAK,CAAC,EAAE,KAAK,GAAG,SAAS,CAAC;IAEnC,oEAAoE;IACpE,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"operator.d.ts","sourceRoot":"","sources":["../../../../../zql/src/ivm/operator.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,6BAA6B,CAAC;AAC3D,OAAO,KAAK,EAAC,GAAG,EAAC,MAAM,oCAAoC,CAAC;AAC5D,OAAO,KAAK,EAAC,mBAAmB,EAAC,MAAM,sBAAsB,CAAC;AAC9D,OAAO,KAAK,EAAC,MAAM,EAAC,MAAM,aAAa,CAAC;AACxC,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,iBAAiB,CAAC;AAChD,OAAO,KAAK,EAAC,IAAI,EAAC,MAAM,WAAW,CAAC;AACpC,OAAO,KAAK,EAAC,YAAY,EAAC,MAAM,aAAa,CAAC;AAC9C,OAAO,KAAK,EAAC,MAAM,EAAC,MAAM,aAAa,CAAC;AAExC,OAAO,EAAC,UAAU,EAAC,MAAM,kBAAkB,CAAC;AAE5C;;GAEG;AACH,MAAM,WAAW,SAAS;IACxB,iDAAiD;IACjD,SAAS,IAAI,YAAY,CAAC;IAE1B;;;;OAIG;IACH,OAAO,IAAI,IAAI,CAAC;CACjB;AAED,MAAM,WAAW,KAAM,SAAQ,SAAS;IACtC,+CAA+C;IAC/C,SAAS,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IAEhC;;;;;;;;;;;;OAYG;IACH,KAAK,CAAC,GAAG,EAAE,YAAY,GAAG,MAAM,CAAC,IAAI,GAAG,OAAO,CAAC,CAAC;CAClD;AAED;;;;;;;;;;;;;;GAcG;AACH,MAAM,MAAM,eAAe,GAAG,SAAS,UAAU,EAAE,CAAC;AAEpD,MAAM,MAAM,YAAY,GAAG;IACzB,QAAQ,CAAC,UAAU,CAAC,EAAE,UAAU,GAAG,SAAS,CAAC;IAE7C;;;;;;;;OAQG;IACH,QAAQ,CAAC,gBAAgB,CAAC,EAAE,SAAS,eAAe,EAAE,GAAG,SAAS,CAAC;IAEnE,kFAAkF;IAClF,QAAQ,CAAC,KAAK,CAAC,EAAE,KAAK,GAAG,SAAS,CAAC;IAEnC,oEAAoE;IACpE,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IAEvC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OAqCG;IACH,QAAQ,CAAC,MAAM,CAAC,EAAE,mBAAmB,GAAG,SAAS,CAAC;CACnD,CAAC;AAEF,MAAM,MAAM,KAAK,GAAG;IAClB,QAAQ,CAAC,GAAG,EAAE,GAAG,CAAC;IAClB,QAAQ,CAAC,KAAK,EAAE,IAAI,GAAG,OAAO,CAAC;CAChC,CAAC;AAEF;;;GAGG;AACH,MAAM,WAAW,MAAM;IACrB;;;;;;;;;;;;OAYG;IACH,IAAI,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,SAAS,GAAG,MAAM,CAAC,OAAO,CAAC,CAAC;CAC1D;AAED;;;GAGG;AACH,eAAO,MAAM,WAAW,EAAE,MAIzB,CAAC;AAEF;;;;;GAKG;AACH,MAAM,WAAW,QAAS,SAAQ,KAAK,EAAE,MAAM;CAAG;AAElD;;;GAGG;AACH,MAAM,WAAW,OAAO;IACtB,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,SAAS,GAAG,IAAI,CAAC;IACzC,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,SAAS,GAAG,SAAS,GAAG,SAAS,CAAC;IACzD;;OAEG;IACH,IAAI,CAAC,OAAO,CAAC,EAAE;QAAC,MAAM,EAAE,MAAM,CAAA;KAAC,GAAG,MAAM,CAAC,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC,CAAC;IAC9D,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;CACxB"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"operator.js","names":[],"sources":["../../../../../zql/src/ivm/operator.ts"],"sourcesContent":["import type {JSONValue} from '../../../shared/src/json.ts';\nimport type {Row} from '../../../zero-protocol/src/data.ts';\nimport type {Change} from './change.ts';\nimport type {Constraint} from './constraint.ts';\nimport type {Node} from './data.ts';\nimport type {SourceSchema} from './schema.ts';\nimport type {Stream} from './stream.ts';\n\nexport {skipYields} from './skip-yields.ts';\n\n/**\n * Input to an operator.\n */\nexport interface InputBase {\n /** The schema of the data this input returns. */\n getSchema(): SourceSchema;\n\n /**\n * Completely destroy the input. Destroying an input\n * causes it to call destroy on its upstreams, fully\n * cleaning up a pipeline.\n */\n destroy(): void;\n}\n\nexport interface Input extends InputBase {\n /** Tell the input where to send its output. */\n setOutput(output: Output): void;\n\n /**\n * Fetch data. May modify the data in place.\n * Returns nodes sorted in order of `SourceSchema.compareRows`.\n *\n * The stream may contain 'yield' to yield control to the caller for purposes\n * of responsiveness.\n *\n * Contract:\n * - During fetch: If an input yields 'yield', 'yield' must be yielded to the\n * caller of fetch immediately.\n * - During push: If a fetch to an input consumed by the push logic yields\n * 'yield', it must be yielded to the caller of push immediately.\n */\n fetch(req: FetchRequest): Stream<Node | 'yield'>;\n}\n\n/**\n * A single multi-row IN clause: a non-empty list of Constraints all\n * sharing the same column shape. Sources treat it as\n * `(col_a, col_b, …) IN VALUES (…)`.\n *\n * Caller invariants (sources rely on these — they are not re-checked):\n * - **Unique entries.** TableSource gets set semantics for free from SQL\n * `IN`; MemorySource fans sub-fetches out per entry, so duplicates\n * would yield the same row N times. FlippedJoin groups by canonical\n * parent-key (`flipped-join.ts:#fetchBatched`) before emitting.\n * - **Key-compatible with `req.constraint`.** Entries that contradict\n * `constraint` should be dropped upstream. FlippedJoin filters\n * incompatible children via `constraintsAreCompatible` before adding\n * them to a batch.\n */\nexport type MultiConstraint = readonly Constraint[];\n\nexport type FetchRequest = {\n readonly constraint?: Constraint | undefined;\n\n /**\n * List of multi-row IN clauses, all ANDed together (and ANDed with\n * `constraint` if both are provided). Each entry is a `MultiConstraint`\n * over its own set of columns.\n *\n * Used by FlippedJoin to push child→parent fetches into a single\n * batched statement, and to AND together constraints contributed by\n * chained FlippedJoins (e.g. `assigneeID IN (…) AND creatorID IN (…)`).\n */\n readonly multiConstraints?: readonly MultiConstraint[] | undefined;\n\n /** If supplied, `start.row` must have previously been output by fetch or push. */\n readonly start?: Start | undefined;\n\n /** Whether to fetch in reverse order of the SourceSchema's sort. */\n readonly reverse?: boolean | undefined;\n};\n\nexport type Start = {\n readonly row: Row;\n readonly basis: 'at' | 'after';\n};\n\n/**\n * An output for an operator. Typically another Operator but can also be\n * the code running the pipeline.\n */\nexport interface Output {\n /**\n * Push incremental changes to data previously received with fetch().\n * Consumers must apply all pushed changes or incremental result will\n * be incorrect.\n * Callers must maintain some invariants for correct operation:\n * - Only add rows which do not already exist (by deep equality).\n * - Only remove rows which do exist (by deep equality).\n * Implmentation can yield 'yield' to yield control to the caller for purposes\n * of responsiveness.\n * Yield contract:\n * - During a push: If a push call to an output yields 'yield', it must be\n * yielded to the caller of push immediately.\n */\n push(change: Change, pusher: InputBase): Stream<'yield'>;\n}\n\n/**\n * An implementation of Output that throws if pushed to. It is used as the\n * initial value for for an operator's output before it is set.\n */\nexport const throwOutput: Output = {\n push(_change: Change): Stream<'yield'> {\n throw new Error('Output not set');\n },\n};\n\n/**\n * Operators are arranged into pipelines.\n * They are stateful.\n * Each operator is an input to the next operator in the chain and an output\n * to the previous.\n */\nexport interface Operator extends Input, Output {}\n\n/**\n * Operators get access to storage that they can store their internal\n * state in.\n */\nexport interface Storage {\n set(key: string, value: JSONValue): void;\n get(key: string, def?: JSONValue): JSONValue | undefined;\n /**\n * If options is not specified, defaults to scanning all entries.\n */\n scan(options?: {prefix: string}): Stream<[string, JSONValue]>;\n del(key: string): void;\n}\n"],"mappings":";;;;;;
|
|
1
|
+
{"version":3,"file":"operator.js","names":[],"sources":["../../../../../zql/src/ivm/operator.ts"],"sourcesContent":["import type {JSONValue} from '../../../shared/src/json.ts';\nimport type {Row} from '../../../zero-protocol/src/data.ts';\nimport type {NoSubqueryCondition} from '../builder/filter.ts';\nimport type {Change} from './change.ts';\nimport type {Constraint} from './constraint.ts';\nimport type {Node} from './data.ts';\nimport type {SourceSchema} from './schema.ts';\nimport type {Stream} from './stream.ts';\n\nexport {skipYields} from './skip-yields.ts';\n\n/**\n * Input to an operator.\n */\nexport interface InputBase {\n /** The schema of the data this input returns. */\n getSchema(): SourceSchema;\n\n /**\n * Completely destroy the input. Destroying an input\n * causes it to call destroy on its upstreams, fully\n * cleaning up a pipeline.\n */\n destroy(): void;\n}\n\nexport interface Input extends InputBase {\n /** Tell the input where to send its output. */\n setOutput(output: Output): void;\n\n /**\n * Fetch data. May modify the data in place.\n * Returns nodes sorted in order of `SourceSchema.compareRows`.\n *\n * The stream may contain 'yield' to yield control to the caller for purposes\n * of responsiveness.\n *\n * Contract:\n * - During fetch: If an input yields 'yield', 'yield' must be yielded to the\n * caller of fetch immediately.\n * - During push: If a fetch to an input consumed by the push logic yields\n * 'yield', it must be yielded to the caller of push immediately.\n */\n fetch(req: FetchRequest): Stream<Node | 'yield'>;\n}\n\n/**\n * A single multi-row IN clause: a non-empty list of Constraints all\n * sharing the same column shape. Sources treat it as\n * `(col_a, col_b, …) IN VALUES (…)`.\n *\n * Caller invariants (sources rely on these — they are not re-checked):\n * - **Unique entries.** TableSource gets set semantics for free from SQL\n * `IN`; MemorySource fans sub-fetches out per entry, so duplicates\n * would yield the same row N times. FlippedJoin groups by canonical\n * parent-key (`flipped-join.ts:#fetchBatched`) before emitting.\n * - **Key-compatible with `req.constraint`.** Entries that contradict\n * `constraint` should be dropped upstream. FlippedJoin filters\n * incompatible children via `constraintsAreCompatible` before adding\n * them to a batch.\n */\nexport type MultiConstraint = readonly Constraint[];\n\nexport type FetchRequest = {\n readonly constraint?: Constraint | undefined;\n\n /**\n * List of multi-row IN clauses, all ANDed together (and ANDed with\n * `constraint` if both are provided). Each entry is a `MultiConstraint`\n * over its own set of columns.\n *\n * Used by FlippedJoin to push child→parent fetches into a single\n * batched statement, and to AND together constraints contributed by\n * chained FlippedJoins (e.g. `assigneeID IN (…) AND creatorID IN (…)`).\n */\n readonly multiConstraints?: readonly MultiConstraint[] | undefined;\n\n /** If supplied, `start.row` must have previously been output by fetch or push. */\n readonly start?: Start | undefined;\n\n /** Whether to fetch in reverse order of the SourceSchema's sort. */\n readonly reverse?: boolean | undefined;\n\n /**\n * Additional predicate the consumer wants the source/intermediate operators\n * to apply to the fetched rows. The receiver must AND this with whatever\n * filtering it already applies. Sources may use this to drive index\n * selection (e.g. PK lookup) the same way they use the connection-time\n * filter.\n *\n * Always a `NoSubqueryCondition` — anything passed via `req.filter` must\n * already have had correlated subqueries stripped (see `transformFilters`).\n *\n * Contract:\n * - The only operator that *introduces* `req.filter` is `FilterStart`. It\n * AND-merges its static condition with whatever `req.filter` it receives\n * and forwards the merged value upstream. No other operator should\n * construct a `req.filter`.\n * - \"Pass-through\" fetch operators (`Skip`, `Take`, `Join`, `FlippedJoin`,\n * `UnionFanIn`, `UnionFanOut`) MUST preserve `req.filter` when forwarding\n * to their input — typically via `{...req, ...overrides}` spread. Dropping\n * it silently regresses predicate pushdown for queries below the\n * forwarding operator.\n * - Operators that *initiate* internal fetches (e.g., `Join.#pushChildChange`,\n * `FlippedJoin.#pushChildChange`, `UnionFanIn.#pushInternalChange`,\n * `Take`'s push-time bound recomputation) construct fresh\n * `FetchRequest`s without `req.filter`, and that is correct: there is no\n * inbound consumer-side filter at those call sites. Any `FilterStart`\n * that sits between such an internal fetch and the source will still\n * apply its own condition on the way through, so the source still gets\n * the right WHERE.\n * - `Cap` is *not* a pass-through: it intentionally builds fresh\n * `{constraint}` requests when replaying tracked PKs and drops any\n * incoming `req.filter`. This is sound only because `Cap`'s sole\n * consumer in production is a `Join`'s child fetch (see\n * `Join.#processParentNode` in `join.ts`), which itself never carries\n * `req.filter` — Join's child fetches are constructed with just\n * `{constraint}` derived from join keys. If `Cap` is ever wired below\n * something that does push a `req.filter`, this carve-out becomes a\n * correctness bug.\n */\n readonly filter?: NoSubqueryCondition | undefined;\n};\n\nexport type Start = {\n readonly row: Row;\n readonly basis: 'at' | 'after';\n};\n\n/**\n * An output for an operator. Typically another Operator but can also be\n * the code running the pipeline.\n */\nexport interface Output {\n /**\n * Push incremental changes to data previously received with fetch().\n * Consumers must apply all pushed changes or incremental result will\n * be incorrect.\n * Callers must maintain some invariants for correct operation:\n * - Only add rows which do not already exist (by deep equality).\n * - Only remove rows which do exist (by deep equality).\n * Implmentation can yield 'yield' to yield control to the caller for purposes\n * of responsiveness.\n * Yield contract:\n * - During a push: If a push call to an output yields 'yield', it must be\n * yielded to the caller of push immediately.\n */\n push(change: Change, pusher: InputBase): Stream<'yield'>;\n}\n\n/**\n * An implementation of Output that throws if pushed to. It is used as the\n * initial value for for an operator's output before it is set.\n */\nexport const throwOutput: Output = {\n push(_change: Change): Stream<'yield'> {\n throw new Error('Output not set');\n },\n};\n\n/**\n * Operators are arranged into pipelines.\n * They are stateful.\n * Each operator is an input to the next operator in the chain and an output\n * to the previous.\n */\nexport interface Operator extends Input, Output {}\n\n/**\n * Operators get access to storage that they can store their internal\n * state in.\n */\nexport interface Storage {\n set(key: string, value: JSONValue): void;\n get(key: string, def?: JSONValue): JSONValue | undefined;\n /**\n * If options is not specified, defaults to scanning all entries.\n */\n scan(options?: {prefix: string}): Stream<[string, JSONValue]>;\n del(key: string): void;\n}\n"],"mappings":";;;;;;AA0JA,IAAa,cAAsB,EACjC,KAAK,SAAkC;CACrC,MAAM,IAAI,MAAM,gBAAgB;AAClC,EACF"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"planner-builder.d.ts","sourceRoot":"","sources":["../../../../../zql/src/planner/planner-builder.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,kBAAkB,CAAC;AAGjD,OAAO,KAAK,EACV,GAAG,EAKJ,MAAM,mCAAmC,CAAC;
|
|
1
|
+
{"version":3,"file":"planner-builder.d.ts","sourceRoot":"","sources":["../../../../../zql/src/planner/planner-builder.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,kBAAkB,CAAC;AAGjD,OAAO,KAAK,EACV,GAAG,EAKJ,MAAM,mCAAmC,CAAC;AAG3C,OAAO,KAAK,EAAC,mBAAmB,EAAC,MAAM,yBAAyB,CAAC;AACjE,OAAO,KAAK,EAAC,iBAAiB,EAAC,MAAM,yBAAyB,CAAC;AAC/D,OAAO,KAAK,EAAC,YAAY,EAAC,MAAM,oBAAoB,CAAC;AAIrD,OAAO,EAAC,YAAY,EAAC,MAAM,oBAAoB,CAAC;AAqBhD,MAAM,MAAM,KAAK,GAAG;IAClB,IAAI,EAAE,YAAY,CAAC;IACnB,QAAQ,EAAE;QAAC,CAAC,GAAG,EAAE,MAAM,GAAG,KAAK,CAAA;KAAC,CAAC;CAClC,CAAC;AAEF,wBAAgB,cAAc,CAC5B,GAAG,EAAE,GAAG,EACR,KAAK,EAAE,mBAAmB,EAC1B,MAAM,EAAE,OAAO,EACf,eAAe,CAAC,EAAE,iBAAiB,GAClC,KAAK,CAmDP;AAsOD,wBAAgB,SAAS,CACvB,GAAG,EAAE,GAAG,EACR,KAAK,EAAE,mBAAmB,EAC1B,YAAY,CAAC,EAAE,YAAY,EAC3B,EAAE,CAAC,EAAE,UAAU,GACd,GAAG,CAIL;AAqCD,wBAAgB,eAAe,CAAC,GAAG,EAAE,GAAG,EAAE,KAAK,EAAE,KAAK,GAAG,GAAG,CAyB3D"}
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import { assert } from "../../../shared/src/asserts.js";
|
|
2
2
|
import { must } from "../../../shared/src/must.js";
|
|
3
3
|
import { planIdSymbol } from "../../../zero-protocol/src/ast.js";
|
|
4
|
+
import { transformFilters } from "../builder/filter.js";
|
|
4
5
|
import { PlannerFanIn } from "./planner-fan-in.js";
|
|
5
6
|
import { PlannerFanOut } from "./planner-fan-out.js";
|
|
7
|
+
import { PlannerFilter } from "./planner-filter.js";
|
|
6
8
|
import { PlannerGraph } from "./planner-graph.js";
|
|
7
9
|
import { PlannerJoin } from "./planner-join.js";
|
|
8
10
|
import { PlannerTerminus } from "./planner-terminus.js";
|
|
@@ -12,6 +14,7 @@ function wireOutput(from, to) {
|
|
|
12
14
|
case "connection":
|
|
13
15
|
case "join":
|
|
14
16
|
case "fan-in":
|
|
17
|
+
case "filter":
|
|
15
18
|
from.setOutput(to);
|
|
16
19
|
break;
|
|
17
20
|
case "fan-out":
|
|
@@ -55,14 +58,21 @@ function processAnd(condition, input, graph, model, parentTable, getPlanId) {
|
|
|
55
58
|
return end;
|
|
56
59
|
}
|
|
57
60
|
function processOr(condition, input, graph, model, parentTable, getPlanId) {
|
|
58
|
-
|
|
59
|
-
if (subqueryConditions.length === 0) return input;
|
|
61
|
+
if (!condition.conditions.some((c) => c.type === "correlatedSubquery" || hasCorrelatedSubquery(c))) return input;
|
|
60
62
|
const fanOut = new PlannerFanOut(input);
|
|
61
63
|
graph.fanOuts.push(fanOut);
|
|
62
64
|
wireOutput(input, fanOut);
|
|
63
65
|
const branches = [];
|
|
64
|
-
for (const subCondition of
|
|
65
|
-
|
|
66
|
+
for (const subCondition of condition.conditions) {
|
|
67
|
+
let branch;
|
|
68
|
+
if (subCondition.type === "correlatedSubquery" || hasCorrelatedSubquery(subCondition)) branch = processCondition(subCondition, fanOut, graph, model, parentTable, getPlanId);
|
|
69
|
+
else {
|
|
70
|
+
const transformed = transformFilters(subCondition).filters;
|
|
71
|
+
const filter = new PlannerFilter(fanOut, transformed);
|
|
72
|
+
graph.filters.push(filter);
|
|
73
|
+
wireOutput(fanOut, filter);
|
|
74
|
+
branch = filter;
|
|
75
|
+
}
|
|
66
76
|
branches.push(branch);
|
|
67
77
|
fanOut.addOutput(branch);
|
|
68
78
|
}
|