@rocicorp/zero 1.11.0-canary.18 → 1.11.0-canary.19

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.
Files changed (47) hide show
  1. package/out/zero/package.js +1 -1
  2. package/out/zero/package.js.map +1 -1
  3. package/out/zero-cache/src/server/inspector-delegate.d.ts +10 -0
  4. package/out/zero-cache/src/server/inspector-delegate.d.ts.map +1 -1
  5. package/out/zero-cache/src/server/inspector-delegate.js +17 -4
  6. package/out/zero-cache/src/server/inspector-delegate.js.map +1 -1
  7. package/out/zero-cache/src/server/replicator.d.ts +3 -0
  8. package/out/zero-cache/src/server/replicator.d.ts.map +1 -1
  9. package/out/zero-cache/src/server/replicator.js +45 -3
  10. package/out/zero-cache/src/server/replicator.js.map +1 -1
  11. package/out/zero-client/src/client/version.js +1 -1
  12. package/out/zero-protocol/src/down.d.ts +2 -2
  13. package/out/zero-protocol/src/inspect-down.d.ts +81 -81
  14. package/out/zql/src/builder/builder.d.ts.map +1 -1
  15. package/out/zql/src/builder/builder.js +37 -16
  16. package/out/zql/src/builder/builder.js.map +1 -1
  17. package/out/zql/src/ivm/flipped-join.d.ts +5 -3
  18. package/out/zql/src/ivm/flipped-join.d.ts.map +1 -1
  19. package/out/zql/src/ivm/flipped-join.js +26 -14
  20. package/out/zql/src/ivm/flipped-join.js.map +1 -1
  21. package/out/zql/src/ivm/join-index-tap.d.ts +33 -0
  22. package/out/zql/src/ivm/join-index-tap.d.ts.map +1 -0
  23. package/out/zql/src/ivm/join-index-tap.js +70 -0
  24. package/out/zql/src/ivm/join-index-tap.js.map +1 -0
  25. package/out/zql/src/ivm/join-index.d.ts +35 -0
  26. package/out/zql/src/ivm/join-index.d.ts.map +1 -0
  27. package/out/zql/src/ivm/join-index.js +141 -0
  28. package/out/zql/src/ivm/join-index.js.map +1 -0
  29. package/out/zql/src/ivm/join-utils.d.ts +0 -22
  30. package/out/zql/src/ivm/join-utils.d.ts.map +1 -1
  31. package/out/zql/src/ivm/join-utils.js +1 -85
  32. package/out/zql/src/ivm/join-utils.js.map +1 -1
  33. package/out/zql/src/ivm/join.d.ts +17 -3
  34. package/out/zql/src/ivm/join.d.ts.map +1 -1
  35. package/out/zql/src/ivm/join.js +35 -13
  36. package/out/zql/src/ivm/join.js.map +1 -1
  37. package/out/zqlite/src/db.d.ts.map +1 -1
  38. package/out/zqlite/src/db.js +23 -25
  39. package/out/zqlite/src/db.js.map +1 -1
  40. package/out/zqlite/src/query-builder.d.ts +17 -1
  41. package/out/zqlite/src/query-builder.d.ts.map +1 -1
  42. package/out/zqlite/src/query-builder.js +111 -20
  43. package/out/zqlite/src/query-builder.js.map +1 -1
  44. package/out/zqlite/src/table-source.d.ts.map +1 -1
  45. package/out/zqlite/src/table-source.js +39 -23
  46. package/out/zqlite/src/table-source.js.map +1 -1
  47. package/package.json +1 -1
@@ -3,7 +3,8 @@ import { binarySearch } from "../../../shared/src/binary-search.js";
3
3
  import { throwOutput } from "./operator.js";
4
4
  import { makeAddChange, makeChildChange, makeEditChange, makeRemoveChange } from "./change.js";
5
5
  import { constraintsAreCompatible } from "./constraint.js";
6
- import { buildJoinConstraint, canonicalKey, generateWithOverlayNoYield, getMatchingParentEntries, indexParentInStorage, isJoinMatch, rowEqualsForCompoundKey, unindexParentInStorage } from "./join-utils.js";
6
+ import { buildJoinConstraint, canonicalKey, generateWithOverlayNoYield, isJoinMatch, rowEqualsForCompoundKey } from "./join-utils.js";
7
+ import { JoinIndex } from "./join-index.js";
7
8
  import { mergeSortedStreams } from "./memory-source.js";
8
9
  import { isInParentFetch, readParentFetchBounds } from "./take-gate.js";
9
10
  //#region ../zql/src/ivm/flipped-join.ts
@@ -26,14 +27,18 @@ var FlippedJoin = class {
26
27
  #childKey;
27
28
  #relationshipName;
28
29
  #schema;
29
- #parentPartitionKey;
30
30
  #boundProvider;
31
- #storage;
31
+ /**
32
+ * The parents this join has output: those with at least one related
33
+ * child.
34
+ */
35
+ #parentIndex;
36
+ #indexesParents;
32
37
  #output = throwOutput;
33
38
  #inprogressChildChange;
34
39
  #inprogressChildChangePosition;
35
40
  #inprogressParentFetchBounds;
36
- constructor({ parent, child, parentKey, childKey, relationshipName, hidden, system, parentPartitionKey, boundProvider, storage }) {
41
+ constructor({ parent, child, parentKey, childKey, relationshipName, hidden, system, parentPartitionKey, boundProvider, parentIndex }) {
37
42
  assert(parent !== child, "Parent and child must be different operators");
38
43
  assert(parentKey.length === childKey.length, "The parentKey and childKey keys must have same length");
39
44
  this.#parent = parent;
@@ -41,9 +46,14 @@ var FlippedJoin = class {
41
46
  this.#parentKey = parentKey;
42
47
  this.#childKey = childKey;
43
48
  this.#relationshipName = relationshipName;
44
- this.#parentPartitionKey = parentPartitionKey;
45
49
  this.#boundProvider = boundProvider;
46
- this.#storage = storage;
50
+ if (parentIndex) {
51
+ this.#parentIndex = parentIndex;
52
+ this.#indexesParents = false;
53
+ } else {
54
+ this.#parentIndex = new JoinIndex(parentKey, parent.getSchema().primaryKey, parentPartitionKey);
55
+ this.#indexesParents = true;
56
+ }
47
57
  const parentSchema = parent.getSchema();
48
58
  const childSchema = child.getSchema();
49
59
  this.#schema = {
@@ -72,7 +82,9 @@ var FlippedJoin = class {
72
82
  destroy() {
73
83
  this.#child.destroy();
74
84
  this.#parent.destroy();
75
- this.#storage.destroy();
85
+ }
86
+ get parentIndexForTest() {
87
+ return this.#parentIndex;
76
88
  }
77
89
  setOutput(output) {
78
90
  this.#output = output;
@@ -216,16 +228,16 @@ var FlippedJoin = class {
216
228
  if (!constraint) return;
217
229
  const childRow = change[1].row;
218
230
  const changeType = change[0];
219
- const matching = getMatchingParentEntries(this.#storage, childRow, this.#childKey, this.#parentPartitionKey);
220
- if (!matching) {
231
+ const partitions = this.#parentIndex.lookup(childRow, this.#childKey);
232
+ if (!partitions) {
221
233
  if (changeType !== 0 && changeType !== 3) return;
222
234
  }
223
235
  let fetchConstraints;
224
236
  if (changeType !== 0 && changeType !== 3) {
225
- assert(matching, "Matching entries must exist for non-add child change");
226
- fetchConstraints = matching.map((entry) => entry.partitionConstraint ? {
237
+ assert(partitions, "Matching entries must exist for non-add child change");
238
+ fetchConstraints = partitions.map((partitionConstraint) => partitionConstraint ? {
227
239
  ...constraint,
228
- ...entry.partitionConstraint
240
+ ...partitionConstraint
229
241
  } : constraint);
230
242
  } else fetchConstraints = [constraint];
231
243
  if (this.#boundProvider) this.#inprogressParentFetchBounds = readParentFetchBounds(this.#boundProvider, fetchConstraints);
@@ -292,10 +304,10 @@ var FlippedJoin = class {
292
304
  }
293
305
  }
294
306
  #indexParentRow(row) {
295
- indexParentInStorage(this.#storage, row, this.#parentKey, this.#parent.getSchema().primaryKey, this.#parentPartitionKey);
307
+ if (this.#indexesParents) this.#parentIndex.add(row);
296
308
  }
297
309
  #unindexParentRow(row) {
298
- unindexParentInStorage(this.#storage, row, this.#parentKey, this.#parent.getSchema().primaryKey, this.#parentPartitionKey);
310
+ if (this.#indexesParents) this.#parentIndex.remove(row);
299
311
  }
300
312
  *#pushParent(change) {
301
313
  const childNodeStream = (node) => () => {
@@ -1 +1 @@
1
- {"version":3,"file":"flipped-join.js","names":["#parent","#child","#parentKey","#childKey","#relationshipName","#schema","#parentPartitionKey","#boundProvider","#storage","#pushParent","#reconcile","#pushChild","#output","#inprogressChildChange","#fetchBatched","#fetchChunked","#yieldParentWithOverlay","#inprogressChildChangePosition","#inprogressParentFetchBounds","#indexParentRow","#pushChildChange","#unindexParentRow"],"sources":["../../../../../zql/src/ivm/flipped-join.ts"],"sourcesContent":["import {assert, unreachable} from '../../../shared/src/asserts.ts';\nimport {binarySearch} from '../../../shared/src/binary-search.ts';\nimport type {CompoundKey, System} from '../../../zero-protocol/src/ast.ts';\nimport type {Row, Value} from '../../../zero-protocol/src/data.ts';\nimport {ChangeIndex} from './change-index.ts';\nimport {ChangeType} from './change-type.ts';\nimport {\n makeAddChange,\n makeChildChange,\n makeEditChange,\n makeRemoveChange,\n type Change,\n} from './change.ts';\nimport {constraintsAreCompatible, type Constraint} from './constraint.ts';\nimport type {Node} from './data.ts';\nimport {\n buildJoinConstraint,\n canonicalKey,\n canonicalKeyForTest,\n generateWithOverlayNoYield,\n getMatchingParentEntries,\n indexParentInStorage,\n isJoinMatch,\n rowEqualsForCompoundKey,\n unindexParentInStorage,\n type JoinStorage,\n} from './join-utils.ts';\nimport {mergeSortedStreams} from './memory-source.ts';\nimport {\n throwOutput,\n type FetchRequest,\n type Input,\n type MultiConstraint,\n type Output,\n type Storage,\n} from './operator.ts';\nimport type {SourceSchema} from './schema.ts';\nimport {type Stream} from './stream.ts';\nimport {\n isInParentFetch,\n readParentFetchBounds,\n type ParentFetchBound,\n type TakeBoundProvider,\n} from './take-gate.ts';\n\n/**\n * Maximum number of entries sent in a single batched `parent.fetch`\n * call. Larger child-node sets are split into multiple fetches whose\n * sorted streams are merged in JS.\n *\n * Why bound this:\n * - **Bounded fetch on early termination.** `mergeSortedStreams` primes\n * one row from every chunk before yielding the first output, so all\n * chunks open their cursors up front. Smaller chunks cap the\n * worst-case overfetch when downstream `Take` consumes only a few\n * rows — at chunk N, we may waste up to ~N index seeks before\n * `.return()` propagates.\n * - **Parameter limit.** Well under SQLite's default\n * `SQLITE_MAX_VARIABLE_NUMBER` (32766). Compound keys multiply the\n * parameter count by key length, so we leave headroom.\n *\n * Tested 64/128/256; 256 had the best worst-case across planner suites\n * and perf tests.\n *\n * We should, however, start doing shadow tests of the planner\n * against cloudzero queries.\n */\nconst MULTI_CONSTRAINT_CHUNK_SIZE = 256;\n\n// Mutable test seam — production code reads this via the getter.\nlet multiConstraintChunkSize: number = MULTI_CONSTRAINT_CHUNK_SIZE;\n\nexport function getMultiConstraintChunkSize(): number {\n return multiConstraintChunkSize;\n}\n\n/** Test only. Returns a restore function. */\nexport function setMultiConstraintChunkSizeForTest(size: number): () => void {\n const prev = multiConstraintChunkSize;\n multiConstraintChunkSize = size;\n return () => {\n multiConstraintChunkSize = prev;\n };\n}\n\ntype Args = {\n parent: Input;\n child: Input;\n // The nth key in childKey corresponds to the nth key in parentKey.\n parentKey: CompoundKey;\n childKey: CompoundKey;\n\n relationshipName: string;\n hidden: boolean;\n system: System;\n parentPartitionKey?: CompoundKey | undefined;\n boundProvider?: TakeBoundProvider | undefined;\n storage: Storage;\n};\n\n/**\n * An *inner* join which fetches nodes from its child input first and then\n * fetches their related nodes from its parent input. Output nodes are the\n * nodes from parent input (in parent input order), which have at least one\n * related child. These output nodes have a new relationship added to them,\n * which has the name `relationshipName`. The value of the relationship is a\n * stream of related nodes from the child input (in child input order).\n */\nexport class FlippedJoin implements Input {\n readonly #parent: Input;\n readonly #child: Input;\n readonly #parentKey: CompoundKey;\n readonly #childKey: CompoundKey;\n readonly #relationshipName: string;\n readonly #schema: SourceSchema;\n readonly #parentPartitionKey: CompoundKey | undefined;\n readonly #boundProvider: TakeBoundProvider | undefined;\n readonly #storage: JoinStorage;\n\n #output: Output = throwOutput;\n\n #inprogressChildChange: Change | undefined;\n #inprogressChildChangePosition: Row | undefined;\n #inprogressParentFetchBounds: ParentFetchBound[] | undefined;\n\n constructor({\n parent,\n child,\n parentKey,\n childKey,\n relationshipName,\n hidden,\n system,\n parentPartitionKey,\n boundProvider,\n storage,\n }: Args) {\n assert(parent !== child, 'Parent and child must be different operators');\n assert(\n parentKey.length === childKey.length,\n 'The parentKey and childKey keys must have same length',\n );\n this.#parent = parent;\n this.#child = child;\n this.#parentKey = parentKey;\n this.#childKey = childKey;\n this.#relationshipName = relationshipName;\n this.#parentPartitionKey = parentPartitionKey;\n this.#boundProvider = boundProvider;\n this.#storage = storage as unknown as JoinStorage;\n\n const parentSchema = parent.getSchema();\n const childSchema = child.getSchema();\n this.#schema = {\n ...parentSchema,\n relationships: {\n ...parentSchema.relationships,\n [relationshipName]: {\n ...childSchema,\n isHidden: hidden,\n system,\n },\n },\n };\n\n parent.setOutput({\n push: (change: Change) => this.#pushParent(change),\n reconcile: () => this.#reconcile(),\n });\n child.setOutput({\n push: (change: Change) => this.#pushChild(change),\n reconcile: () => this.#reconcile(),\n });\n }\n\n *#reconcile(): Stream<'yield'> {\n if (this.#output.reconcile) {\n yield* this.#output.reconcile(this);\n }\n }\n\n destroy(): void {\n this.#child.destroy();\n this.#parent.destroy();\n this.#storage.destroy();\n }\n\n setOutput(output: Output): void {\n this.#output = output;\n }\n\n getSchema(): SourceSchema {\n return this.#schema;\n }\n\n *fetch(req: FetchRequest): Stream<Node | 'yield'> {\n // Translate constraints for the parent on parts of the join key to\n // constraints for the child.\n const childConstraint: Record<string, Value> = {};\n let hasChildConstraint = false;\n if (req.constraint) {\n for (const [key, value] of Object.entries(req.constraint)) {\n const index = this.#parentKey.indexOf(key);\n if (index !== -1) {\n hasChildConstraint = true;\n childConstraint[this.#childKey[index]] = value;\n }\n }\n }\n\n const childNodes: Node[] = [];\n for (const node of this.#child.fetch(\n hasChildConstraint ? {constraint: childConstraint} : {},\n )) {\n if (node === 'yield') {\n yield node;\n continue;\n }\n childNodes.push(node);\n }\n\n // FlippedJoin's split-push change overlay logic is largely\n // the same as Join's with the exception of remove. For remove,\n // the change is undone here, and then re-applied to parents with order\n // less than or equal to change.position below. This is necessary\n // because if the removed node was the last related child, the\n // related parents with position greater than change.position\n // (which should not yet have the node removed), would not even\n // be fetched here, and would be absent from the output all together.\n if (this.#inprogressChildChange?.[ChangeIndex.TYPE] === ChangeType.REMOVE) {\n const removedNode = this.#inprogressChildChange[ChangeIndex.NODE];\n const compare = this.#child.getSchema().compareRows;\n const insertPos = binarySearch(childNodes.length, i =>\n compare(removedNode.row, childNodes[i].row),\n );\n childNodes.splice(insertPos, 0, removedNode);\n }\n\n yield* this.#fetchBatched(req, childNodes);\n }\n\n /**\n * Fetches parents for `childNodes` in batched calls, using\n * `multiConstraint` so the source can issue one query per chunk (e.g.\n * SQL `IN` with index-aware seek) instead of N per-child cursors.\n *\n * Multi-constraint values are split into chunks of `CHUNK_SIZE`, so\n * SQL `IN` lists stay bounded — predictable plans, statement-cache\n * hits across calls of the same chunk size, well below SQLite's\n * parameter limit.\n *\n * Within each chunk, the source returns parents in `compareRows` order.\n * Across chunks, we merge with `mergeSortedStreams` so the overall\n * stream is also in order. Note: the merge primes one row from every\n * chunk before yielding the first output, so all chunks open their\n * cursors up front. Early termination downstream then prevents any\n * further work on un-advanced chunks (cursors get `.return()`'d via\n * `mergeSortedStreams`'s finally block).\n *\n * Replaces the previous split between `#fetchMergeSort` and\n * `#fetchQuicksort`. The unique-vs-not distinction is no longer needed:\n * the source handles cardinality (single index seek for each value) and\n * ordering (SQL `ORDER BY` / index walk).\n */\n *#fetchBatched(\n req: FetchRequest,\n childNodes: Node[],\n ): Stream<Node | 'yield'> {\n const parentReqConstraint = req.constraint;\n const parentKey = this.#parentKey;\n const childKey = this.#childKey;\n\n // Build (deduped) multi-constraint and a key→child-indexes map. Same\n // parent-key value across multiple children groups them together.\n const computedMulti: Constraint[] = [];\n const childIndexesByKey = new Map<string, number[]>();\n for (let i = 0; i < childNodes.length; i++) {\n const constraintFromChild = buildJoinConstraint(\n childNodes[i].row,\n childKey,\n parentKey,\n );\n if (\n !constraintFromChild ||\n (parentReqConstraint &&\n !constraintsAreCompatible(constraintFromChild, parentReqConstraint))\n ) {\n continue;\n }\n const key = canonicalKey(constraintFromChild, parentKey);\n const existing = childIndexesByKey.get(key);\n if (existing === undefined) {\n childIndexesByKey.set(key, [i]);\n computedMulti.push(constraintFromChild);\n } else {\n existing.push(i);\n }\n }\n\n if (computedMulti.length === 0) {\n return;\n }\n\n // Source returns parents in compareRows order within each chunk.\n // Merge across chunks to yield a globally ordered stream.\n const compareRows = this.#schema.compareRows;\n const compare: (a: Node, b: Node) => number = req.reverse\n ? (a, b) => compareRows(b.row, a.row)\n : (a, b) => compareRows(a.row, b.row);\n\n // Append our computed multi to whatever req.multiConstraints already\n // contained — chained FlippedJoins each contribute one entry, so the\n // source ANDs them all (e.g. `assigneeID IN (…) AND creatorID IN (…)`).\n const incoming = req.multiConstraints ?? [];\n const parentStream =\n computedMulti.length <= multiConstraintChunkSize\n ? this.#parent.fetch({\n ...req,\n multiConstraints: [...incoming, computedMulti],\n })\n : this.#fetchChunked(req, incoming, computedMulti, compare);\n\n for (const node of parentStream) {\n if (node === 'yield') {\n yield 'yield';\n continue;\n }\n const key = canonicalKey(node.row, parentKey);\n const idxs = childIndexesByKey.get(key);\n if (idxs === undefined) {\n // This row's parent-key doesn't match any of our computed\n // multi-constraint entries. Happens when our parent is an\n // intermediate operator (e.g. a chained FlippedJoin) that passes\n // multiConstraints through unchanged instead of filtering — see\n // FetchRequest.multiConstraints contract. The lookup miss here\n // performs the required filter, so just skip the row.\n continue;\n }\n // Children retain their original input order within the group\n // because we appended to `idxs` in iteration order.\n const relatedChildNodes: Node[] = idxs.map(i => childNodes[i]);\n yield* this.#yieldParentWithOverlay(node, relatedChildNodes);\n }\n }\n\n #fetchChunked(\n req: FetchRequest,\n incomingMultis: readonly MultiConstraint[],\n computedMulti: MultiConstraint,\n compare: (a: Node, b: Node) => number,\n ): Stream<Node | 'yield'> {\n const chunkStreams: Stream<Node | 'yield'>[] = [];\n for (let i = 0; i < computedMulti.length; i += multiConstraintChunkSize) {\n chunkStreams.push(\n this.#parent.fetch({\n ...req,\n multiConstraints: [\n ...incomingMultis,\n computedMulti.slice(i, i + multiConstraintChunkSize),\n ],\n }),\n );\n }\n return mergeSortedStreams(chunkStreams, compare);\n }\n\n *#yieldParentWithOverlay(\n minParentNode: Node,\n relatedChildNodes: Node[],\n ): Stream<Node> {\n let overlaidRelatedChildNodes = relatedChildNodes;\n\n // The parent has yet to get the in-progress child change if it comes\n // after the current position and a parent fetch of the push yields it.\n // With a TakeGate the fetches are capped at the bounds read when they\n // started.\n const {compareRows} = this.#parent.getSchema();\n const isParentInPushQueue =\n this.#inprogressChildChangePosition !== undefined &&\n compareRows(minParentNode.row, this.#inprogressChildChangePosition) > 0 &&\n (this.#inprogressParentFetchBounds === undefined ||\n isInParentFetch(\n this.#inprogressParentFetchBounds,\n minParentNode.row,\n compareRows,\n ));\n\n if (\n this.#inprogressChildChange &&\n this.#inprogressChildChangePosition &&\n isJoinMatch(\n this.#inprogressChildChange[ChangeIndex.NODE].row,\n this.#childKey,\n minParentNode.row,\n this.#parentKey,\n )\n ) {\n if (this.#inprogressChildChange[ChangeIndex.TYPE] === ChangeType.REMOVE) {\n if (!isParentInPushQueue) {\n // Remove from relatedChildNodes since the removed child\n // was inserted into childNodes above.\n overlaidRelatedChildNodes = relatedChildNodes.filter(\n n => n !== this.#inprogressChildChange?.[ChangeIndex.NODE],\n );\n }\n } else if (isParentInPushQueue) {\n overlaidRelatedChildNodes = [\n ...generateWithOverlayNoYield(\n relatedChildNodes,\n this.#inprogressChildChange,\n this.#child.getSchema(),\n ),\n ];\n }\n }\n\n // yield node if after the overlay it still has relationship nodes\n if (overlaidRelatedChildNodes.length > 0) {\n this.#indexParentRow(minParentNode.row);\n yield {\n ...minParentNode,\n relationships: {\n ...minParentNode.relationships,\n [this.#relationshipName]: () => overlaidRelatedChildNodes,\n },\n };\n }\n }\n\n *#pushChild(change: Change): Stream<'yield'> {\n switch (change[ChangeIndex.TYPE]) {\n case ChangeType.ADD:\n case ChangeType.REMOVE:\n yield* this.#pushChildChange(change);\n break;\n case ChangeType.EDIT: {\n assert(\n rowEqualsForCompoundKey(\n change[ChangeIndex.OLD_NODE].row,\n change[ChangeIndex.NODE].row,\n this.#childKey,\n ),\n `Child edit must not change relationship.`,\n );\n yield* this.#pushChildChange(change, true);\n break;\n }\n case ChangeType.CHILD:\n yield* this.#pushChildChange(change, true);\n break;\n }\n }\n\n *#pushChildChange(change: Change, exists?: boolean): Stream<'yield'> {\n this.#inprogressChildChange = change;\n this.#inprogressChildChangePosition = undefined;\n try {\n const constraint = buildJoinConstraint(\n change[ChangeIndex.NODE].row,\n this.#childKey,\n this.#parentKey,\n );\n if (!constraint) {\n return;\n }\n const childRow = change[ChangeIndex.NODE].row;\n const changeType = change[ChangeIndex.TYPE];\n\n const matching = getMatchingParentEntries(\n this.#storage,\n childRow,\n this.#childKey,\n this.#parentPartitionKey,\n );\n if (!matching) {\n // If no matching parent is resident in the view, REMOVE and EDIT\n // cannot affect any view-resident parent. (ADD and CHILD can qualify\n // previously absent parents).\n if (changeType !== ChangeType.ADD && changeType !== ChangeType.CHILD) {\n return;\n }\n }\n\n let fetchConstraints: Constraint[];\n if (changeType !== ChangeType.ADD && changeType !== ChangeType.CHILD) {\n assert(\n matching,\n 'Matching entries must exist for non-add child change',\n );\n fetchConstraints = matching.map(entry =>\n entry.partitionConstraint\n ? {...constraint, ...entry.partitionConstraint}\n : constraint,\n );\n } else {\n fetchConstraints = [constraint];\n }\n if (this.#boundProvider) {\n this.#inprogressParentFetchBounds = readParentFetchBounds(\n this.#boundProvider,\n fetchConstraints,\n );\n }\n\n let parentNodeStream: Stream<Node | 'yield'>;\n if (fetchConstraints.length === 1) {\n parentNodeStream = this.#parent.fetch({\n constraint: fetchConstraints[0],\n });\n } else {\n const streams = fetchConstraints.map(c =>\n this.#parent.fetch({constraint: c}),\n );\n const compare = (a: Node, b: Node) =>\n this.#schema.compareRows(a.row, b.row);\n parentNodeStream = mergeSortedStreams(streams, compare);\n }\n\n for (const parentNode of parentNodeStream) {\n if (parentNode === 'yield') {\n yield 'yield';\n continue;\n }\n this.#inprogressChildChange = change;\n this.#inprogressChildChangePosition = parentNode.row;\n const childNodeStream = () => {\n const constraint = buildJoinConstraint(\n parentNode.row,\n this.#parentKey,\n this.#childKey,\n );\n return constraint ? this.#child.fetch({constraint}) : [];\n };\n let parentExists = exists;\n if (!parentExists) {\n for (const childNode of childNodeStream()) {\n if (childNode === 'yield') {\n yield 'yield';\n continue;\n }\n if (\n this.#child\n .getSchema()\n .compareRows(childNode.row, change[ChangeIndex.NODE].row) !== 0\n ) {\n parentExists = true;\n break;\n }\n }\n }\n if (parentExists) {\n yield* this.#output.push(\n makeChildChange(\n {\n ...parentNode,\n relationships: {\n ...parentNode.relationships,\n [this.#relationshipName]: childNodeStream,\n },\n },\n {\n relationshipName: this.#relationshipName,\n change,\n },\n ),\n this,\n );\n } else {\n const newNode = {\n ...parentNode,\n relationships: {\n ...parentNode.relationships,\n [this.#relationshipName]: () => [change[ChangeIndex.NODE]],\n },\n };\n if (change[ChangeIndex.TYPE] === ChangeType.ADD) {\n this.#indexParentRow(parentNode.row);\n yield* this.#output.push(makeAddChange(newNode), this);\n } else {\n this.#unindexParentRow(parentNode.row);\n yield* this.#output.push(makeRemoveChange(newNode), this);\n }\n }\n }\n } finally {\n this.#inprogressChildChange = undefined;\n this.#inprogressChildChangePosition = undefined;\n this.#inprogressParentFetchBounds = undefined;\n }\n }\n\n #indexParentRow(row: Row): void {\n indexParentInStorage(\n this.#storage,\n row,\n this.#parentKey,\n this.#parent.getSchema().primaryKey,\n this.#parentPartitionKey,\n );\n }\n\n #unindexParentRow(row: Row): void {\n unindexParentInStorage(\n this.#storage,\n row,\n this.#parentKey,\n this.#parent.getSchema().primaryKey,\n this.#parentPartitionKey,\n );\n }\n\n *#pushParent(change: Change): Stream<'yield'> {\n const childNodeStream = (node: Node) => () => {\n const constraint = buildJoinConstraint(\n node.row,\n this.#parentKey,\n this.#childKey,\n );\n return constraint ? this.#child.fetch({constraint}) : [];\n };\n\n const flip = (node: Node) => ({\n ...node,\n relationships: {\n ...node.relationships,\n [this.#relationshipName]: childNodeStream(node),\n },\n });\n\n // If no related child don't push as this is an inner join.\n let hasRelatedChild = false;\n for (const node of childNodeStream(change[ChangeIndex.NODE])()) {\n if (node === 'yield') {\n yield 'yield';\n continue;\n } else {\n hasRelatedChild = true;\n break;\n }\n }\n if (!hasRelatedChild) {\n return;\n }\n\n switch (change[ChangeIndex.TYPE]) {\n case ChangeType.ADD:\n this.#indexParentRow(change[ChangeIndex.NODE].row);\n yield* this.#output.push(\n makeAddChange(flip(change[ChangeIndex.NODE])),\n this,\n );\n break;\n case ChangeType.REMOVE:\n this.#unindexParentRow(change[ChangeIndex.NODE].row);\n yield* this.#output.push(\n makeRemoveChange(flip(change[ChangeIndex.NODE])),\n this,\n );\n break;\n case ChangeType.CHILD: {\n yield* this.#output.push(\n makeChildChange(\n flip(change[ChangeIndex.NODE]),\n change[ChangeIndex.CHILD_DATA],\n ),\n this,\n );\n break;\n }\n case ChangeType.EDIT: {\n assert(\n rowEqualsForCompoundKey(\n change[ChangeIndex.OLD_NODE].row,\n change[ChangeIndex.NODE].row,\n this.#parentKey,\n ),\n 'Parent edit must not change relationship.',\n );\n this.#unindexParentRow(change[ChangeIndex.OLD_NODE].row);\n this.#indexParentRow(change[ChangeIndex.NODE].row);\n yield* this.#output.push(\n makeEditChange(\n flip(change[ChangeIndex.NODE]),\n flip(change[ChangeIndex.OLD_NODE]),\n ),\n this,\n );\n break;\n }\n default:\n unreachable(change);\n }\n }\n}\n\nexport {canonicalKeyForTest};\n"],"mappings":"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"}
1
+ {"version":3,"file":"flipped-join.js","names":["#parent","#child","#parentKey","#childKey","#relationshipName","#schema","#boundProvider","#parentIndex","#indexesParents","#pushParent","#reconcile","#pushChild","#output","#inprogressChildChange","#fetchBatched","#fetchChunked","#yieldParentWithOverlay","#inprogressChildChangePosition","#inprogressParentFetchBounds","#indexParentRow","#pushChildChange","#unindexParentRow"],"sources":["../../../../../zql/src/ivm/flipped-join.ts"],"sourcesContent":["import {assert, unreachable} from '../../../shared/src/asserts.ts';\nimport {binarySearch} from '../../../shared/src/binary-search.ts';\nimport type {CompoundKey, System} from '../../../zero-protocol/src/ast.ts';\nimport type {Row, Value} from '../../../zero-protocol/src/data.ts';\nimport {ChangeIndex} from './change-index.ts';\nimport {ChangeType} from './change-type.ts';\nimport {\n makeAddChange,\n makeChildChange,\n makeEditChange,\n makeRemoveChange,\n type Change,\n} from './change.ts';\nimport {constraintsAreCompatible, type Constraint} from './constraint.ts';\nimport type {Node} from './data.ts';\nimport {JoinIndex} from './join-index.ts';\nimport {\n buildJoinConstraint,\n canonicalKey,\n canonicalKeyForTest,\n generateWithOverlayNoYield,\n isJoinMatch,\n rowEqualsForCompoundKey,\n} from './join-utils.ts';\nimport {mergeSortedStreams} from './memory-source.ts';\nimport {\n throwOutput,\n type FetchRequest,\n type Input,\n type MultiConstraint,\n type Output,\n} from './operator.ts';\nimport type {SourceSchema} from './schema.ts';\nimport {type Stream} from './stream.ts';\nimport {\n isInParentFetch,\n readParentFetchBounds,\n type ParentFetchBound,\n type TakeBoundProvider,\n} from './take-gate.ts';\n\n/**\n * Maximum number of entries sent in a single batched `parent.fetch`\n * call. Larger child-node sets are split into multiple fetches whose\n * sorted streams are merged in JS.\n *\n * Why bound this:\n * - **Bounded fetch on early termination.** `mergeSortedStreams` primes\n * one row from every chunk before yielding the first output, so all\n * chunks open their cursors up front. Smaller chunks cap the\n * worst-case overfetch when downstream `Take` consumes only a few\n * rows — at chunk N, we may waste up to ~N index seeks before\n * `.return()` propagates.\n * - **Parameter limit.** Well under SQLite's default\n * `SQLITE_MAX_VARIABLE_NUMBER` (32766). Compound keys multiply the\n * parameter count by key length, so we leave headroom.\n *\n * Tested 64/128/256; 256 had the best worst-case across planner suites\n * and perf tests.\n *\n * We should, however, start doing shadow tests of the planner\n * against cloudzero queries.\n */\nconst MULTI_CONSTRAINT_CHUNK_SIZE = 256;\n\n// Mutable test seam — production code reads this via the getter.\nlet multiConstraintChunkSize: number = MULTI_CONSTRAINT_CHUNK_SIZE;\n\nexport function getMultiConstraintChunkSize(): number {\n return multiConstraintChunkSize;\n}\n\n/** Test only. Returns a restore function. */\nexport function setMultiConstraintChunkSizeForTest(size: number): () => void {\n const prev = multiConstraintChunkSize;\n multiConstraintChunkSize = size;\n return () => {\n multiConstraintChunkSize = prev;\n };\n}\n\ntype Args = {\n parent: Input;\n child: Input;\n // The nth key in childKey corresponds to the nth key in parentKey.\n parentKey: CompoundKey;\n childKey: CompoundKey;\n\n relationshipName: string;\n hidden: boolean;\n system: System;\n parentPartitionKey?: CompoundKey | undefined;\n boundProvider?: TakeBoundProvider | undefined;\n parentIndex?: JoinIndex | undefined;\n};\n\n/**\n * An *inner* join which fetches nodes from its child input first and then\n * fetches their related nodes from its parent input. Output nodes are the\n * nodes from parent input (in parent input order), which have at least one\n * related child. These output nodes have a new relationship added to them,\n * which has the name `relationshipName`. The value of the relationship is a\n * stream of related nodes from the child input (in child input order).\n */\nexport class FlippedJoin implements Input {\n readonly #parent: Input;\n readonly #child: Input;\n readonly #parentKey: CompoundKey;\n readonly #childKey: CompoundKey;\n readonly #relationshipName: string;\n readonly #schema: SourceSchema;\n readonly #boundProvider: TakeBoundProvider | undefined;\n /**\n * The parents this join has output: those with at least one related\n * child.\n */\n readonly #parentIndex: JoinIndex;\n readonly #indexesParents: boolean;\n\n #output: Output = throwOutput;\n\n #inprogressChildChange: Change | undefined;\n #inprogressChildChangePosition: Row | undefined;\n #inprogressParentFetchBounds: ParentFetchBound[] | undefined;\n\n constructor({\n parent,\n child,\n parentKey,\n childKey,\n relationshipName,\n hidden,\n system,\n parentPartitionKey,\n boundProvider,\n parentIndex,\n }: Args) {\n assert(parent !== child, 'Parent and child must be different operators');\n assert(\n parentKey.length === childKey.length,\n 'The parentKey and childKey keys must have same length',\n );\n this.#parent = parent;\n this.#child = child;\n this.#parentKey = parentKey;\n this.#childKey = childKey;\n this.#relationshipName = relationshipName;\n this.#boundProvider = boundProvider;\n if (parentIndex) {\n this.#parentIndex = parentIndex;\n this.#indexesParents = false;\n } else {\n this.#parentIndex = new JoinIndex(\n parentKey,\n parent.getSchema().primaryKey,\n parentPartitionKey,\n );\n this.#indexesParents = true;\n }\n\n const parentSchema = parent.getSchema();\n const childSchema = child.getSchema();\n this.#schema = {\n ...parentSchema,\n relationships: {\n ...parentSchema.relationships,\n [relationshipName]: {\n ...childSchema,\n isHidden: hidden,\n system,\n },\n },\n };\n\n parent.setOutput({\n push: (change: Change) => this.#pushParent(change),\n reconcile: () => this.#reconcile(),\n });\n child.setOutput({\n push: (change: Change) => this.#pushChild(change),\n reconcile: () => this.#reconcile(),\n });\n }\n\n *#reconcile(): Stream<'yield'> {\n if (this.#output.reconcile) {\n yield* this.#output.reconcile(this);\n }\n }\n\n destroy(): void {\n this.#child.destroy();\n this.#parent.destroy();\n }\n\n get parentIndexForTest(): JoinIndex {\n return this.#parentIndex;\n }\n\n setOutput(output: Output): void {\n this.#output = output;\n }\n\n getSchema(): SourceSchema {\n return this.#schema;\n }\n\n *fetch(req: FetchRequest): Stream<Node | 'yield'> {\n // Translate constraints for the parent on parts of the join key to\n // constraints for the child.\n const childConstraint: Record<string, Value> = {};\n let hasChildConstraint = false;\n if (req.constraint) {\n for (const [key, value] of Object.entries(req.constraint)) {\n const index = this.#parentKey.indexOf(key);\n if (index !== -1) {\n hasChildConstraint = true;\n childConstraint[this.#childKey[index]] = value;\n }\n }\n }\n\n const childNodes: Node[] = [];\n for (const node of this.#child.fetch(\n hasChildConstraint ? {constraint: childConstraint} : {},\n )) {\n if (node === 'yield') {\n yield node;\n continue;\n }\n childNodes.push(node);\n }\n\n // FlippedJoin's split-push change overlay logic is largely\n // the same as Join's with the exception of remove. For remove,\n // the change is undone here, and then re-applied to parents with order\n // less than or equal to change.position below. This is necessary\n // because if the removed node was the last related child, the\n // related parents with position greater than change.position\n // (which should not yet have the node removed), would not even\n // be fetched here, and would be absent from the output all together.\n if (this.#inprogressChildChange?.[ChangeIndex.TYPE] === ChangeType.REMOVE) {\n const removedNode = this.#inprogressChildChange[ChangeIndex.NODE];\n const compare = this.#child.getSchema().compareRows;\n const insertPos = binarySearch(childNodes.length, i =>\n compare(removedNode.row, childNodes[i].row),\n );\n childNodes.splice(insertPos, 0, removedNode);\n }\n\n yield* this.#fetchBatched(req, childNodes);\n }\n\n /**\n * Fetches parents for `childNodes` in batched calls, using\n * `multiConstraint` so the source can issue one query per chunk (e.g.\n * SQL `IN` with index-aware seek) instead of N per-child cursors.\n *\n * Multi-constraint values are split into chunks of `CHUNK_SIZE`, so\n * SQL `IN` lists stay bounded — predictable plans, statement-cache\n * hits across calls of the same chunk size, well below SQLite's\n * parameter limit.\n *\n * Within each chunk, the source returns parents in `compareRows` order.\n * Across chunks, we merge with `mergeSortedStreams` so the overall\n * stream is also in order. Note: the merge primes one row from every\n * chunk before yielding the first output, so all chunks open their\n * cursors up front. Early termination downstream then prevents any\n * further work on un-advanced chunks (cursors get `.return()`'d via\n * `mergeSortedStreams`'s finally block).\n *\n * Replaces the previous split between `#fetchMergeSort` and\n * `#fetchQuicksort`. The unique-vs-not distinction is no longer needed:\n * the source handles cardinality (single index seek for each value) and\n * ordering (SQL `ORDER BY` / index walk).\n */\n *#fetchBatched(\n req: FetchRequest,\n childNodes: Node[],\n ): Stream<Node | 'yield'> {\n const parentReqConstraint = req.constraint;\n const parentKey = this.#parentKey;\n const childKey = this.#childKey;\n\n // Build (deduped) multi-constraint and a key→child-indexes map. Same\n // parent-key value across multiple children groups them together.\n const computedMulti: Constraint[] = [];\n const childIndexesByKey = new Map<string, number[]>();\n for (let i = 0; i < childNodes.length; i++) {\n const constraintFromChild = buildJoinConstraint(\n childNodes[i].row,\n childKey,\n parentKey,\n );\n if (\n !constraintFromChild ||\n (parentReqConstraint &&\n !constraintsAreCompatible(constraintFromChild, parentReqConstraint))\n ) {\n continue;\n }\n const key = canonicalKey(constraintFromChild, parentKey);\n const existing = childIndexesByKey.get(key);\n if (existing === undefined) {\n childIndexesByKey.set(key, [i]);\n computedMulti.push(constraintFromChild);\n } else {\n existing.push(i);\n }\n }\n\n if (computedMulti.length === 0) {\n return;\n }\n\n // Source returns parents in compareRows order within each chunk.\n // Merge across chunks to yield a globally ordered stream.\n const compareRows = this.#schema.compareRows;\n const compare: (a: Node, b: Node) => number = req.reverse\n ? (a, b) => compareRows(b.row, a.row)\n : (a, b) => compareRows(a.row, b.row);\n\n // Append our computed multi to whatever req.multiConstraints already\n // contained — chained FlippedJoins each contribute one entry, so the\n // source ANDs them all (e.g. `assigneeID IN (…) AND creatorID IN (…)`).\n const incoming = req.multiConstraints ?? [];\n const parentStream =\n computedMulti.length <= multiConstraintChunkSize\n ? this.#parent.fetch({\n ...req,\n multiConstraints: [...incoming, computedMulti],\n })\n : this.#fetchChunked(req, incoming, computedMulti, compare);\n\n for (const node of parentStream) {\n if (node === 'yield') {\n yield 'yield';\n continue;\n }\n const key = canonicalKey(node.row, parentKey);\n const idxs = childIndexesByKey.get(key);\n if (idxs === undefined) {\n // This row's parent-key doesn't match any of our computed\n // multi-constraint entries. Happens when our parent is an\n // intermediate operator (e.g. a chained FlippedJoin) that passes\n // multiConstraints through unchanged instead of filtering — see\n // FetchRequest.multiConstraints contract. The lookup miss here\n // performs the required filter, so just skip the row.\n continue;\n }\n // Children retain their original input order within the group\n // because we appended to `idxs` in iteration order.\n const relatedChildNodes: Node[] = idxs.map(i => childNodes[i]);\n yield* this.#yieldParentWithOverlay(node, relatedChildNodes);\n }\n }\n\n #fetchChunked(\n req: FetchRequest,\n incomingMultis: readonly MultiConstraint[],\n computedMulti: MultiConstraint,\n compare: (a: Node, b: Node) => number,\n ): Stream<Node | 'yield'> {\n const chunkStreams: Stream<Node | 'yield'>[] = [];\n for (let i = 0; i < computedMulti.length; i += multiConstraintChunkSize) {\n chunkStreams.push(\n this.#parent.fetch({\n ...req,\n multiConstraints: [\n ...incomingMultis,\n computedMulti.slice(i, i + multiConstraintChunkSize),\n ],\n }),\n );\n }\n return mergeSortedStreams(chunkStreams, compare);\n }\n\n *#yieldParentWithOverlay(\n minParentNode: Node,\n relatedChildNodes: Node[],\n ): Stream<Node> {\n let overlaidRelatedChildNodes = relatedChildNodes;\n\n // The parent has yet to get the in-progress child change if it comes\n // after the current position and a parent fetch of the push yields it.\n // With a TakeGate the fetches are capped at the bounds read when they\n // started.\n const {compareRows} = this.#parent.getSchema();\n const isParentInPushQueue =\n this.#inprogressChildChangePosition !== undefined &&\n compareRows(minParentNode.row, this.#inprogressChildChangePosition) > 0 &&\n (this.#inprogressParentFetchBounds === undefined ||\n isInParentFetch(\n this.#inprogressParentFetchBounds,\n minParentNode.row,\n compareRows,\n ));\n\n if (\n this.#inprogressChildChange &&\n this.#inprogressChildChangePosition &&\n isJoinMatch(\n this.#inprogressChildChange[ChangeIndex.NODE].row,\n this.#childKey,\n minParentNode.row,\n this.#parentKey,\n )\n ) {\n if (this.#inprogressChildChange[ChangeIndex.TYPE] === ChangeType.REMOVE) {\n if (!isParentInPushQueue) {\n // Remove from relatedChildNodes since the removed child\n // was inserted into childNodes above.\n overlaidRelatedChildNodes = relatedChildNodes.filter(\n n => n !== this.#inprogressChildChange?.[ChangeIndex.NODE],\n );\n }\n } else if (isParentInPushQueue) {\n overlaidRelatedChildNodes = [\n ...generateWithOverlayNoYield(\n relatedChildNodes,\n this.#inprogressChildChange,\n this.#child.getSchema(),\n ),\n ];\n }\n }\n\n // yield node if after the overlay it still has relationship nodes\n if (overlaidRelatedChildNodes.length > 0) {\n this.#indexParentRow(minParentNode.row);\n yield {\n ...minParentNode,\n relationships: {\n ...minParentNode.relationships,\n [this.#relationshipName]: () => overlaidRelatedChildNodes,\n },\n };\n }\n }\n\n *#pushChild(change: Change): Stream<'yield'> {\n switch (change[ChangeIndex.TYPE]) {\n case ChangeType.ADD:\n case ChangeType.REMOVE:\n yield* this.#pushChildChange(change);\n break;\n case ChangeType.EDIT: {\n assert(\n rowEqualsForCompoundKey(\n change[ChangeIndex.OLD_NODE].row,\n change[ChangeIndex.NODE].row,\n this.#childKey,\n ),\n `Child edit must not change relationship.`,\n );\n yield* this.#pushChildChange(change, true);\n break;\n }\n case ChangeType.CHILD:\n yield* this.#pushChildChange(change, true);\n break;\n }\n }\n\n *#pushChildChange(change: Change, exists?: boolean): Stream<'yield'> {\n this.#inprogressChildChange = change;\n this.#inprogressChildChangePosition = undefined;\n try {\n const constraint = buildJoinConstraint(\n change[ChangeIndex.NODE].row,\n this.#childKey,\n this.#parentKey,\n );\n if (!constraint) {\n return;\n }\n const childRow = change[ChangeIndex.NODE].row;\n const changeType = change[ChangeIndex.TYPE];\n\n const partitions = this.#parentIndex.lookup(childRow, this.#childKey);\n if (!partitions) {\n // If no matching parent is resident in the view, REMOVE and EDIT\n // cannot affect any view-resident parent. (ADD and CHILD can qualify\n // previously absent parents).\n if (changeType !== ChangeType.ADD && changeType !== ChangeType.CHILD) {\n return;\n }\n }\n\n let fetchConstraints: Constraint[];\n if (changeType !== ChangeType.ADD && changeType !== ChangeType.CHILD) {\n assert(\n partitions,\n 'Matching entries must exist for non-add child change',\n );\n fetchConstraints = partitions.map(partitionConstraint =>\n partitionConstraint\n ? {...constraint, ...partitionConstraint}\n : constraint,\n );\n } else {\n fetchConstraints = [constraint];\n }\n if (this.#boundProvider) {\n this.#inprogressParentFetchBounds = readParentFetchBounds(\n this.#boundProvider,\n fetchConstraints,\n );\n }\n\n let parentNodeStream: Stream<Node | 'yield'>;\n if (fetchConstraints.length === 1) {\n parentNodeStream = this.#parent.fetch({\n constraint: fetchConstraints[0],\n });\n } else {\n const streams = fetchConstraints.map(c =>\n this.#parent.fetch({constraint: c}),\n );\n const compare = (a: Node, b: Node) =>\n this.#schema.compareRows(a.row, b.row);\n parentNodeStream = mergeSortedStreams(streams, compare);\n }\n\n for (const parentNode of parentNodeStream) {\n if (parentNode === 'yield') {\n yield 'yield';\n continue;\n }\n this.#inprogressChildChange = change;\n this.#inprogressChildChangePosition = parentNode.row;\n const childNodeStream = () => {\n const constraint = buildJoinConstraint(\n parentNode.row,\n this.#parentKey,\n this.#childKey,\n );\n return constraint ? this.#child.fetch({constraint}) : [];\n };\n let parentExists = exists;\n if (!parentExists) {\n for (const childNode of childNodeStream()) {\n if (childNode === 'yield') {\n yield 'yield';\n continue;\n }\n if (\n this.#child\n .getSchema()\n .compareRows(childNode.row, change[ChangeIndex.NODE].row) !== 0\n ) {\n parentExists = true;\n break;\n }\n }\n }\n if (parentExists) {\n yield* this.#output.push(\n makeChildChange(\n {\n ...parentNode,\n relationships: {\n ...parentNode.relationships,\n [this.#relationshipName]: childNodeStream,\n },\n },\n {\n relationshipName: this.#relationshipName,\n change,\n },\n ),\n this,\n );\n } else {\n const newNode = {\n ...parentNode,\n relationships: {\n ...parentNode.relationships,\n [this.#relationshipName]: () => [change[ChangeIndex.NODE]],\n },\n };\n if (change[ChangeIndex.TYPE] === ChangeType.ADD) {\n this.#indexParentRow(parentNode.row);\n yield* this.#output.push(makeAddChange(newNode), this);\n } else {\n this.#unindexParentRow(parentNode.row);\n yield* this.#output.push(makeRemoveChange(newNode), this);\n }\n }\n }\n } finally {\n this.#inprogressChildChange = undefined;\n this.#inprogressChildChangePosition = undefined;\n this.#inprogressParentFetchBounds = undefined;\n }\n }\n\n #indexParentRow(row: Row): void {\n if (this.#indexesParents) {\n this.#parentIndex.add(row);\n }\n }\n\n #unindexParentRow(row: Row): void {\n if (this.#indexesParents) {\n this.#parentIndex.remove(row);\n }\n }\n\n *#pushParent(change: Change): Stream<'yield'> {\n const childNodeStream = (node: Node) => () => {\n const constraint = buildJoinConstraint(\n node.row,\n this.#parentKey,\n this.#childKey,\n );\n return constraint ? this.#child.fetch({constraint}) : [];\n };\n\n const flip = (node: Node) => ({\n ...node,\n relationships: {\n ...node.relationships,\n [this.#relationshipName]: childNodeStream(node),\n },\n });\n\n // If no related child don't push as this is an inner join.\n let hasRelatedChild = false;\n for (const node of childNodeStream(change[ChangeIndex.NODE])()) {\n if (node === 'yield') {\n yield 'yield';\n continue;\n } else {\n hasRelatedChild = true;\n break;\n }\n }\n if (!hasRelatedChild) {\n return;\n }\n\n switch (change[ChangeIndex.TYPE]) {\n case ChangeType.ADD:\n this.#indexParentRow(change[ChangeIndex.NODE].row);\n yield* this.#output.push(\n makeAddChange(flip(change[ChangeIndex.NODE])),\n this,\n );\n break;\n case ChangeType.REMOVE:\n this.#unindexParentRow(change[ChangeIndex.NODE].row);\n yield* this.#output.push(\n makeRemoveChange(flip(change[ChangeIndex.NODE])),\n this,\n );\n break;\n case ChangeType.CHILD: {\n yield* this.#output.push(\n makeChildChange(\n flip(change[ChangeIndex.NODE]),\n change[ChangeIndex.CHILD_DATA],\n ),\n this,\n );\n break;\n }\n case ChangeType.EDIT: {\n assert(\n rowEqualsForCompoundKey(\n change[ChangeIndex.OLD_NODE].row,\n change[ChangeIndex.NODE].row,\n this.#parentKey,\n ),\n 'Parent edit must not change relationship.',\n );\n this.#unindexParentRow(change[ChangeIndex.OLD_NODE].row);\n this.#indexParentRow(change[ChangeIndex.NODE].row);\n yield* this.#output.push(\n makeEditChange(\n flip(change[ChangeIndex.NODE]),\n flip(change[ChangeIndex.OLD_NODE]),\n ),\n this,\n );\n break;\n }\n default:\n unreachable(change);\n }\n }\n}\n\nexport {canonicalKeyForTest};\n"],"mappings":"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"}
@@ -0,0 +1,33 @@
1
+ import type { Change } from './change.ts';
2
+ import type { Node } from './data.ts';
3
+ import type { JoinIndex } from './join-index.ts';
4
+ import { type FetchRequest, type Input, type InputBase, type Operator, type Output } from './operator.ts';
5
+ import type { SourceSchema } from './schema.ts';
6
+ import type { Stream } from './stream.ts';
7
+ /**
8
+ * Keeps the parent indexes of a pipeline's EXISTS joins equal to the rows
9
+ * the pipeline emits: rows it yields on fetch or adds on push are indexed,
10
+ * and rows it removes are dropped.
11
+ *
12
+ * An EXISTS join sits upstream of the conditions and the limit, so it sees
13
+ * every parent the pipeline scans, including those a condition rejects or the
14
+ * limit never keeps. Indexing them there would grow with the scan (the whole
15
+ * table for an unlimited or selective EXISTS). This operator is placed after
16
+ * the limit instead, where a row is only seen once it is part of the result,
17
+ * and a row the limit evicts arrives as a remove.
18
+ *
19
+ * It cannot live in the Exists filter: an OR does not evaluate the branches
20
+ * after the first that passes, but every branch's relationship is still
21
+ * synced for the emitted row.
22
+ */
23
+ export declare class JoinIndexTap implements Operator {
24
+ #private;
25
+ constructor(input: Input, indexes: readonly JoinIndex[]);
26
+ setOutput(output: Output): void;
27
+ getSchema(): SourceSchema;
28
+ destroy(): void;
29
+ fetch(req: FetchRequest): Stream<Node | 'yield'>;
30
+ push(change: Change): Stream<'yield'>;
31
+ reconcile(_pusher: InputBase): Stream<'yield'>;
32
+ }
33
+ //# sourceMappingURL=join-index-tap.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"join-index-tap.d.ts","sourceRoot":"","sources":["../../../../../zql/src/ivm/join-index-tap.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAC,MAAM,EAAC,MAAM,aAAa,CAAC;AACxC,OAAO,KAAK,EAAC,IAAI,EAAC,MAAM,WAAW,CAAC;AACpC,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,iBAAiB,CAAC;AAC/C,OAAO,EAEL,KAAK,YAAY,EACjB,KAAK,KAAK,EACV,KAAK,SAAS,EACd,KAAK,QAAQ,EACb,KAAK,MAAM,EACZ,MAAM,eAAe,CAAC;AACvB,OAAO,KAAK,EAAC,YAAY,EAAC,MAAM,aAAa,CAAC;AAC9C,OAAO,KAAK,EAAC,MAAM,EAAC,MAAM,aAAa,CAAC;AAExC;;;;;;;;;;;;;;;GAeG;AACH,qBAAa,YAAa,YAAW,QAAQ;;IAM3C,YAAY,KAAK,EAAE,KAAK,EAAE,OAAO,EAAE,SAAS,SAAS,EAAE,EAItD;IAED,SAAS,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAE9B;IAED,SAAS,IAAI,YAAY,CAExB;IAED,OAAO,IAAI,IAAI,CAEd;IAEA,KAAK,CAAC,GAAG,EAAE,YAAY,GAAG,MAAM,CAAC,IAAI,GAAG,OAAO,CAAC,CAShD;IAED,IAAI,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC,OAAO,CAAC,CAwBpC;IAEA,SAAS,CAAC,OAAO,EAAE,SAAS,GAAG,MAAM,CAAC,OAAO,CAAC,CAI9C;CACF"}
@@ -0,0 +1,70 @@
1
+ import { unreachable } from "../../../shared/src/asserts.js";
2
+ import { throwOutput } from "./operator.js";
3
+ //#region ../zql/src/ivm/join-index-tap.ts
4
+ /**
5
+ * Keeps the parent indexes of a pipeline's EXISTS joins equal to the rows
6
+ * the pipeline emits: rows it yields on fetch or adds on push are indexed,
7
+ * and rows it removes are dropped.
8
+ *
9
+ * An EXISTS join sits upstream of the conditions and the limit, so it sees
10
+ * every parent the pipeline scans, including those a condition rejects or the
11
+ * limit never keeps. Indexing them there would grow with the scan (the whole
12
+ * table for an unlimited or selective EXISTS). This operator is placed after
13
+ * the limit instead, where a row is only seen once it is part of the result,
14
+ * and a row the limit evicts arrives as a remove.
15
+ *
16
+ * It cannot live in the Exists filter: an OR does not evaluate the branches
17
+ * after the first that passes, but every branch's relationship is still
18
+ * synced for the emitted row.
19
+ */
20
+ var JoinIndexTap = class {
21
+ #input;
22
+ #indexes;
23
+ #output = throwOutput;
24
+ constructor(input, indexes) {
25
+ this.#input = input;
26
+ this.#indexes = indexes;
27
+ input.setOutput(this);
28
+ }
29
+ setOutput(output) {
30
+ this.#output = output;
31
+ }
32
+ getSchema() {
33
+ return this.#input.getSchema();
34
+ }
35
+ destroy() {
36
+ this.#input.destroy();
37
+ }
38
+ *fetch(req) {
39
+ for (const node of this.#input.fetch(req)) {
40
+ if (node !== "yield") for (const index of this.#indexes) index.add(node.row);
41
+ yield node;
42
+ }
43
+ }
44
+ push(change) {
45
+ switch (change[0]) {
46
+ case 0:
47
+ for (const index of this.#indexes) index.add(change[1].row);
48
+ break;
49
+ case 1:
50
+ for (const index of this.#indexes) index.remove(change[1].row);
51
+ break;
52
+ case 2:
53
+ for (const index of this.#indexes) {
54
+ index.remove(change[2].row);
55
+ index.add(change[1].row);
56
+ }
57
+ break;
58
+ case 3: break;
59
+ default: unreachable(change);
60
+ }
61
+ return this.#output.push(change, this);
62
+ }
63
+ *reconcile(_pusher) {
64
+ if (this.#output.reconcile) yield* this.#output.reconcile(this);
65
+ }
66
+ };
67
+ //#endregion
68
+ export { JoinIndexTap };
69
+
70
+ //# sourceMappingURL=join-index-tap.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"join-index-tap.js","names":["#input","#indexes","#output"],"sources":["../../../../../zql/src/ivm/join-index-tap.ts"],"sourcesContent":["import {unreachable} from '../../../shared/src/asserts.ts';\nimport {ChangeIndex} from './change-index.ts';\nimport {ChangeType} from './change-type.ts';\nimport type {Change} from './change.ts';\nimport type {Node} from './data.ts';\nimport type {JoinIndex} from './join-index.ts';\nimport {\n throwOutput,\n type FetchRequest,\n type Input,\n type InputBase,\n type Operator,\n type Output,\n} from './operator.ts';\nimport type {SourceSchema} from './schema.ts';\nimport type {Stream} from './stream.ts';\n\n/**\n * Keeps the parent indexes of a pipeline's EXISTS joins equal to the rows\n * the pipeline emits: rows it yields on fetch or adds on push are indexed,\n * and rows it removes are dropped.\n *\n * An EXISTS join sits upstream of the conditions and the limit, so it sees\n * every parent the pipeline scans, including those a condition rejects or the\n * limit never keeps. Indexing them there would grow with the scan (the whole\n * table for an unlimited or selective EXISTS). This operator is placed after\n * the limit instead, where a row is only seen once it is part of the result,\n * and a row the limit evicts arrives as a remove.\n *\n * It cannot live in the Exists filter: an OR does not evaluate the branches\n * after the first that passes, but every branch's relationship is still\n * synced for the emitted row.\n */\nexport class JoinIndexTap implements Operator {\n readonly #input: Input;\n readonly #indexes: readonly JoinIndex[];\n\n #output: Output = throwOutput;\n\n constructor(input: Input, indexes: readonly JoinIndex[]) {\n this.#input = input;\n this.#indexes = indexes;\n input.setOutput(this);\n }\n\n setOutput(output: Output): void {\n this.#output = output;\n }\n\n getSchema(): SourceSchema {\n return this.#input.getSchema();\n }\n\n destroy(): void {\n this.#input.destroy();\n }\n\n *fetch(req: FetchRequest): Stream<Node | 'yield'> {\n for (const node of this.#input.fetch(req)) {\n if (node !== 'yield') {\n for (const index of this.#indexes) {\n index.add(node.row);\n }\n }\n yield node;\n }\n }\n\n push(change: Change): Stream<'yield'> {\n switch (change[ChangeIndex.TYPE]) {\n case ChangeType.ADD:\n for (const index of this.#indexes) {\n index.add(change[ChangeIndex.NODE].row);\n }\n break;\n case ChangeType.REMOVE:\n for (const index of this.#indexes) {\n index.remove(change[ChangeIndex.NODE].row);\n }\n break;\n case ChangeType.EDIT:\n for (const index of this.#indexes) {\n index.remove(change[ChangeIndex.OLD_NODE].row);\n index.add(change[ChangeIndex.NODE].row);\n }\n break;\n case ChangeType.CHILD:\n break;\n default:\n unreachable(change);\n }\n return this.#output.push(change, this);\n }\n\n *reconcile(_pusher: InputBase): Stream<'yield'> {\n if (this.#output.reconcile) {\n yield* this.#output.reconcile(this);\n }\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;AAiCA,IAAa,eAAb,MAA8C;CAC5C;CACA;CAEA,UAAkB;CAElB,YAAY,OAAc,SAA+B;EACvD,KAAKA,SAAS;EACd,KAAKC,WAAW;EAChB,MAAM,UAAU,IAAI;CACtB;CAEA,UAAU,QAAsB;EAC9B,KAAKC,UAAU;CACjB;CAEA,YAA0B;EACxB,OAAO,KAAKF,OAAO,UAAU;CAC/B;CAEA,UAAgB;EACd,KAAKA,OAAO,QAAQ;CACtB;CAEA,CAAC,MAAM,KAA2C;EAChD,KAAK,MAAM,QAAQ,KAAKA,OAAO,MAAM,GAAG,GAAG;GACzC,IAAI,SAAS,SACX,KAAK,MAAM,SAAS,KAAKC,UACvB,MAAM,IAAI,KAAK,GAAG;GAGtB,MAAM;EACR;CACF;CAEA,KAAK,QAAiC;EACpC,QAAQ,OAAO,IAAf;GACE,KAAK;IACH,KAAK,MAAM,SAAS,KAAKA,UACvB,MAAM,IAAI,OAAO,GAAkB,GAAG;IAExC;GACF,KAAK;IACH,KAAK,MAAM,SAAS,KAAKA,UACvB,MAAM,OAAO,OAAO,GAAkB,GAAG;IAE3C;GACF,KAAK;IACH,KAAK,MAAM,SAAS,KAAKA,UAAU;KACjC,MAAM,OAAO,OAAO,GAAsB,GAAG;KAC7C,MAAM,IAAI,OAAO,GAAkB,GAAG;IACxC;IACA;GACF,KAAK,GACH;GACF,SACE,YAAY,MAAM;EACtB;EACA,OAAO,KAAKC,QAAQ,KAAK,QAAQ,IAAI;CACvC;CAEA,CAAC,UAAU,SAAqC;EAC9C,IAAI,KAAKA,QAAQ,WACf,OAAO,KAAKA,QAAQ,UAAU,IAAI;CAEtC;AACF"}
@@ -0,0 +1,35 @@
1
+ import type { CompoundKey } from '../../../zero-protocol/src/ast.ts';
2
+ import type { Row, Value } from '../../../zero-protocol/src/data.ts';
3
+ /**
4
+ * The partitions of the indexed parents that share a join key, as fetch
5
+ * constraints. An unpartitioned index reports a single `undefined`.
6
+ */
7
+ export type ParentPartitions = readonly (Record<string, Value> | undefined)[];
8
+ /**
9
+ * An in-memory index from a join key to the parent rows that have it. Join
10
+ * and FlippedJoin look up a child change's join key here to skip the parent
11
+ * fetch when no indexed parent can see the change.
12
+ *
13
+ * Parents are tracked by primary key so that adding or removing a row twice
14
+ * is harmless. Lookups only report whether any parent has the key and, when
15
+ * the index is partitioned, in which partitions.
16
+ */
17
+ export declare class JoinIndex {
18
+ #private;
19
+ constructor(parentKey: CompoundKey, primaryKey: CompoundKey, partitionKey?: CompoundKey | undefined);
20
+ /** The number of parent rows in the index. */
21
+ get size(): number;
22
+ add(row: Row): void;
23
+ remove(row: Row): void;
24
+ /**
25
+ * The partitions of the indexed parents whose join key matches `childRow`
26
+ * over `childKey`, or `undefined` if there are none.
27
+ */
28
+ lookup(childRow: Row, childKey: CompoundKey): ParentPartitions | undefined;
29
+ /**
30
+ * The index as the operator storage entries it replaced, keyed
31
+ * `j\0<joinKey>\0[<partitionKey>\0]<primaryKey>`. For tests.
32
+ */
33
+ entriesForTest(): Record<string, 1>;
34
+ }
35
+ //# sourceMappingURL=join-index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"join-index.d.ts","sourceRoot":"","sources":["../../../../../zql/src/ivm/join-index.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAC,WAAW,EAAC,MAAM,mCAAmC,CAAC;AACnE,OAAO,KAAK,EAAC,GAAG,EAAE,KAAK,EAAC,MAAM,oCAAoC,CAAC;AAGnE;;;GAGG;AACH,MAAM,MAAM,gBAAgB,GAAG,SAAS,CAAC,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,GAAG,SAAS,CAAC,EAAE,CAAC;AAS9E;;;;;;;;GAQG;AACH,qBAAa,SAAS;;IAUpB,YACE,SAAS,EAAE,WAAW,EACtB,UAAU,EAAE,WAAW,EACvB,YAAY,CAAC,EAAE,WAAW,GAAG,SAAS,EAQvC;IAED,8CAA8C;IAC9C,IAAI,IAAI,IAAI,MAAM,CAEjB;IAED,GAAG,CAAC,GAAG,EAAE,GAAG,GAAG,IAAI,CAuClB;IAED,MAAM,CAAC,GAAG,EAAE,GAAG,GAAG,IAAI,CAuCrB;IAED;;;OAGG;IACH,MAAM,CAAC,QAAQ,EAAE,GAAG,EAAE,QAAQ,EAAE,WAAW,GAAG,gBAAgB,GAAG,SAAS,CAazE;IAED;;;OAGG;IACH,cAAc,IAAI,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC,CAiBlC;CAKF"}
@@ -0,0 +1,141 @@
1
+ import { must } from "../../../shared/src/must.js";
2
+ import { canonicalKey } from "./join-utils.js";
3
+ //#region ../zql/src/ivm/join-index.ts
4
+ var UNPARTITIONED = [void 0];
5
+ /**
6
+ * An in-memory index from a join key to the parent rows that have it. Join
7
+ * and FlippedJoin look up a child change's join key here to skip the parent
8
+ * fetch when no indexed parent can see the change.
9
+ *
10
+ * Parents are tracked by primary key so that adding or removing a row twice
11
+ * is harmless. Lookups only report whether any parent has the key and, when
12
+ * the index is partitioned, in which partitions.
13
+ */
14
+ var JoinIndex = class {
15
+ #parentKey;
16
+ #primaryKey;
17
+ /** The join key is the primary key, so one string serves as both. */
18
+ #joinKeyIsPK;
19
+ #partitionKey;
20
+ #unpartitioned = /* @__PURE__ */ new Map();
21
+ #partitioned = /* @__PURE__ */ new Map();
22
+ #size = 0;
23
+ constructor(parentKey, primaryKey, partitionKey) {
24
+ this.#parentKey = parentKey;
25
+ this.#primaryKey = primaryKey;
26
+ this.#joinKeyIsPK = parentKey.length === primaryKey.length && parentKey.every((k, i) => k === primaryKey[i]);
27
+ this.#partitionKey = partitionKey;
28
+ }
29
+ /** The number of parent rows in the index. */
30
+ get size() {
31
+ return this.#size;
32
+ }
33
+ add(row) {
34
+ const joinKey = joinKeyOf(row, this.#parentKey);
35
+ if (joinKey === void 0) return;
36
+ const pk = this.#pkOf(row, joinKey);
37
+ const partitionKey = this.#partitionKey;
38
+ if (partitionKey === void 0) {
39
+ let pks = this.#unpartitioned.get(joinKey);
40
+ if (pks === void 0) {
41
+ pks = /* @__PURE__ */ new Set();
42
+ this.#unpartitioned.set(joinKey, pks);
43
+ }
44
+ if (!pks.has(pk)) {
45
+ pks.add(pk);
46
+ this.#size++;
47
+ }
48
+ return;
49
+ }
50
+ const partKey = canonicalKey(row, partitionKey);
51
+ let partitions = this.#partitioned.get(joinKey);
52
+ if (partitions === void 0) {
53
+ partitions = /* @__PURE__ */ new Map();
54
+ this.#partitioned.set(joinKey, partitions);
55
+ }
56
+ let partition = partitions.get(partKey);
57
+ if (partition === void 0) {
58
+ const constraint = {};
59
+ for (const key of partitionKey) constraint[key] = row[key];
60
+ partition = {
61
+ constraint,
62
+ pks: /* @__PURE__ */ new Set()
63
+ };
64
+ partitions.set(partKey, partition);
65
+ }
66
+ if (!partition.pks.has(pk)) {
67
+ partition.pks.add(pk);
68
+ this.#size++;
69
+ }
70
+ }
71
+ remove(row) {
72
+ const joinKey = joinKeyOf(row, this.#parentKey);
73
+ if (joinKey === void 0) return;
74
+ const pk = this.#pkOf(row, joinKey);
75
+ const partitionKey = this.#partitionKey;
76
+ if (partitionKey === void 0) {
77
+ const pks = this.#unpartitioned.get(joinKey);
78
+ if (pks === void 0) return;
79
+ if (pks.delete(pk)) {
80
+ this.#size--;
81
+ if (pks.size === 0) this.#unpartitioned.delete(joinKey);
82
+ }
83
+ return;
84
+ }
85
+ const partitions = this.#partitioned.get(joinKey);
86
+ if (partitions === void 0) return;
87
+ const partKey = canonicalKey(row, partitionKey);
88
+ const partition = partitions.get(partKey);
89
+ if (partition === void 0) return;
90
+ if (partition.pks.delete(pk)) {
91
+ this.#size--;
92
+ if (partition.pks.size === 0) {
93
+ partitions.delete(partKey);
94
+ if (partitions.size === 0) this.#partitioned.delete(joinKey);
95
+ }
96
+ }
97
+ }
98
+ /**
99
+ * The partitions of the indexed parents whose join key matches `childRow`
100
+ * over `childKey`, or `undefined` if there are none.
101
+ */
102
+ lookup(childRow, childKey) {
103
+ const joinKey = joinKeyOf(childRow, childKey);
104
+ if (joinKey === void 0) return;
105
+ if (this.#partitionKey === void 0) return this.#unpartitioned.has(joinKey) ? UNPARTITIONED : void 0;
106
+ const partitions = this.#partitioned.get(joinKey);
107
+ if (partitions === void 0) return;
108
+ return Array.from(partitions.values(), (p) => p.constraint);
109
+ }
110
+ /**
111
+ * The index as the operator storage entries it replaced, keyed
112
+ * `j\0<joinKey>\0[<partitionKey>\0]<primaryKey>`. For tests.
113
+ */
114
+ entriesForTest() {
115
+ const entries = {};
116
+ for (const [joinKey, pks] of this.#unpartitioned) for (const pk of pks) entries[`j\x00${joinKey}\x00${pk}`] = 1;
117
+ for (const [joinKey, partitions] of this.#partitioned) {
118
+ const partitionKey = must(this.#partitionKey);
119
+ for (const { constraint, pks } of partitions.values()) {
120
+ const partKey = canonicalKey(constraint, partitionKey);
121
+ for (const pk of pks) entries[`j\x00${joinKey}\x00${partKey}\x00${pk}`] = 1;
122
+ }
123
+ }
124
+ return entries;
125
+ }
126
+ #pkOf(row, joinKey) {
127
+ return this.#joinKeyIsPK ? joinKey : canonicalKey(row, this.#primaryKey);
128
+ }
129
+ };
130
+ /**
131
+ * The canonical join key of `row`, or `undefined` if any part of it is null:
132
+ * a null key cannot match anything.
133
+ */
134
+ function joinKeyOf(row, key) {
135
+ for (const k of key) if (row[k] === null) return;
136
+ return canonicalKey(row, key);
137
+ }
138
+ //#endregion
139
+ export { JoinIndex };
140
+
141
+ //# sourceMappingURL=join-index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"join-index.js","names":["#parentKey","#primaryKey","#joinKeyIsPK","#partitionKey","#unpartitioned","#partitioned","#size","#pkOf"],"sources":["../../../../../zql/src/ivm/join-index.ts"],"sourcesContent":["import {must} from '../../../shared/src/must.ts';\nimport type {CompoundKey} from '../../../zero-protocol/src/ast.ts';\nimport type {Row, Value} from '../../../zero-protocol/src/data.ts';\nimport {canonicalKey} from './join-utils.ts';\n\n/**\n * The partitions of the indexed parents that share a join key, as fetch\n * constraints. An unpartitioned index reports a single `undefined`.\n */\nexport type ParentPartitions = readonly (Record<string, Value> | undefined)[];\n\nconst UNPARTITIONED: ParentPartitions = [undefined];\n\ntype Partition = {\n readonly constraint: Record<string, Value>;\n readonly pks: Set<string>;\n};\n\n/**\n * An in-memory index from a join key to the parent rows that have it. Join\n * and FlippedJoin look up a child change's join key here to skip the parent\n * fetch when no indexed parent can see the change.\n *\n * Parents are tracked by primary key so that adding or removing a row twice\n * is harmless. Lookups only report whether any parent has the key and, when\n * the index is partitioned, in which partitions.\n */\nexport class JoinIndex {\n readonly #parentKey: CompoundKey;\n readonly #primaryKey: CompoundKey;\n /** The join key is the primary key, so one string serves as both. */\n readonly #joinKeyIsPK: boolean;\n readonly #partitionKey: CompoundKey | undefined;\n readonly #unpartitioned = new Map<string, Set<string>>();\n readonly #partitioned = new Map<string, Map<string, Partition>>();\n #size = 0;\n\n constructor(\n parentKey: CompoundKey,\n primaryKey: CompoundKey,\n partitionKey?: CompoundKey | undefined,\n ) {\n this.#parentKey = parentKey;\n this.#primaryKey = primaryKey;\n this.#joinKeyIsPK =\n parentKey.length === primaryKey.length &&\n parentKey.every((k, i) => k === primaryKey[i]);\n this.#partitionKey = partitionKey;\n }\n\n /** The number of parent rows in the index. */\n get size(): number {\n return this.#size;\n }\n\n add(row: Row): void {\n const joinKey = joinKeyOf(row, this.#parentKey);\n if (joinKey === undefined) {\n return;\n }\n const pk = this.#pkOf(row, joinKey);\n const partitionKey = this.#partitionKey;\n if (partitionKey === undefined) {\n let pks = this.#unpartitioned.get(joinKey);\n if (pks === undefined) {\n pks = new Set();\n this.#unpartitioned.set(joinKey, pks);\n }\n if (!pks.has(pk)) {\n pks.add(pk);\n this.#size++;\n }\n return;\n }\n\n const partKey = canonicalKey(row, partitionKey);\n let partitions = this.#partitioned.get(joinKey);\n if (partitions === undefined) {\n partitions = new Map();\n this.#partitioned.set(joinKey, partitions);\n }\n let partition = partitions.get(partKey);\n if (partition === undefined) {\n const constraint: Record<string, Value> = {};\n for (const key of partitionKey) {\n constraint[key] = row[key];\n }\n partition = {constraint, pks: new Set()};\n partitions.set(partKey, partition);\n }\n if (!partition.pks.has(pk)) {\n partition.pks.add(pk);\n this.#size++;\n }\n }\n\n remove(row: Row): void {\n const joinKey = joinKeyOf(row, this.#parentKey);\n if (joinKey === undefined) {\n return;\n }\n const pk = this.#pkOf(row, joinKey);\n const partitionKey = this.#partitionKey;\n if (partitionKey === undefined) {\n const pks = this.#unpartitioned.get(joinKey);\n if (pks === undefined) {\n return;\n }\n if (pks.delete(pk)) {\n this.#size--;\n if (pks.size === 0) {\n this.#unpartitioned.delete(joinKey);\n }\n }\n return;\n }\n\n const partitions = this.#partitioned.get(joinKey);\n if (partitions === undefined) {\n return;\n }\n const partKey = canonicalKey(row, partitionKey);\n const partition = partitions.get(partKey);\n if (partition === undefined) {\n return;\n }\n if (partition.pks.delete(pk)) {\n this.#size--;\n if (partition.pks.size === 0) {\n partitions.delete(partKey);\n if (partitions.size === 0) {\n this.#partitioned.delete(joinKey);\n }\n }\n }\n }\n\n /**\n * The partitions of the indexed parents whose join key matches `childRow`\n * over `childKey`, or `undefined` if there are none.\n */\n lookup(childRow: Row, childKey: CompoundKey): ParentPartitions | undefined {\n const joinKey = joinKeyOf(childRow, childKey);\n if (joinKey === undefined) {\n return undefined;\n }\n if (this.#partitionKey === undefined) {\n return this.#unpartitioned.has(joinKey) ? UNPARTITIONED : undefined;\n }\n const partitions = this.#partitioned.get(joinKey);\n if (partitions === undefined) {\n return undefined;\n }\n return Array.from(partitions.values(), p => p.constraint);\n }\n\n /**\n * The index as the operator storage entries it replaced, keyed\n * `j\\0<joinKey>\\0[<partitionKey>\\0]<primaryKey>`. For tests.\n */\n entriesForTest(): Record<string, 1> {\n const entries: Record<string, 1> = {};\n for (const [joinKey, pks] of this.#unpartitioned) {\n for (const pk of pks) {\n entries[`j\\x00${joinKey}\\x00${pk}`] = 1;\n }\n }\n for (const [joinKey, partitions] of this.#partitioned) {\n const partitionKey = must(this.#partitionKey);\n for (const {constraint, pks} of partitions.values()) {\n const partKey = canonicalKey(constraint, partitionKey);\n for (const pk of pks) {\n entries[`j\\x00${joinKey}\\x00${partKey}\\x00${pk}`] = 1;\n }\n }\n }\n return entries;\n }\n\n #pkOf(row: Row, joinKey: string): string {\n return this.#joinKeyIsPK ? joinKey : canonicalKey(row, this.#primaryKey);\n }\n}\n\n/**\n * The canonical join key of `row`, or `undefined` if any part of it is null:\n * a null key cannot match anything.\n */\nfunction joinKeyOf(row: Row, key: CompoundKey): string | undefined {\n for (const k of key) {\n if (row[k] === null) {\n return undefined;\n }\n }\n return canonicalKey(row, key);\n}\n"],"mappings":"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"}
@@ -22,26 +22,4 @@ export declare function canonicalKeyForTest(record: Record<string, Value | undef
22
22
  * so distinct types (e.g. 1 and "1") do not collide.
23
23
  */
24
24
  export declare function canonicalKey(record: Record<string, Value | undefined>, keys: CompoundKey): string;
25
- export interface JoinStorage {
26
- get(key: string): unknown;
27
- set(key: string, value: unknown): void;
28
- del(key: string): void;
29
- scan(options?: {
30
- prefix: string;
31
- }): Stream<[string, unknown]>;
32
- destroy(): void;
33
- }
34
- export declare function makeUnpartitionedStorageKey(joinKey: string, primaryKey: string): string;
35
- export declare function makeJoinPrefix(joinKey: string): string;
36
- export declare function makePartitionStorageKey(joinKey: string, partitionKey: string, primaryKey: string): string;
37
- export declare function splitPartitionAndPk(suffix: string, numPartitionKeys: number): [partitionKey: string, pk: string];
38
- export declare function decodeCanonicalValue(s: string): Value;
39
- export declare function decodePartitionConstraint(partitionKey: string, keys: CompoundKey): Record<string, Value | undefined>;
40
- export declare function indexParentInStorage(storage: JoinStorage, row: Row, parentKey: CompoundKey, primaryKey: CompoundKey, parentPartitionKey?: CompoundKey): void;
41
- export declare function unindexParentInStorage(storage: JoinStorage, row: Row, parentKey: CompoundKey, primaryKey: CompoundKey, parentPartitionKey?: CompoundKey): void;
42
- export type MatchingParentEntry = {
43
- pks: Set<string>;
44
- partitionConstraint?: Record<string, Value | undefined> | undefined;
45
- };
46
- export declare function getMatchingParentEntries(storage: JoinStorage, childRow: Row, childKey: CompoundKey, parentPartitionKey?: CompoundKey): MatchingParentEntry[] | undefined;
47
25
  //# sourceMappingURL=join-utils.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"join-utils.d.ts","sourceRoot":"","sources":["../../../../../zql/src/ivm/join-utils.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"join-utils.d.ts","sourceRoot":"","sources":["../../../../../zql/src/ivm/join-utils.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAC,WAAW,EAAC,MAAM,mCAAmC,CAAC;AACnE,OAAO,KAAK,EAAC,GAAG,EAAE,KAAK,EAAC,MAAM,oCAAoC,CAAC;AAGnE,OAAO,KAAK,EAAC,MAAM,EAAC,MAAM,aAAa,CAAC;AACxC,OAAO,EAA6B,KAAK,IAAI,EAAC,MAAM,WAAW,CAAC;AAChE,OAAO,KAAK,EAAC,YAAY,EAAC,MAAM,aAAa,CAAC;AAC9C,OAAO,KAAK,EAAC,MAAM,EAAC,MAAM,aAAa,CAAC;AAExC,wBAAgB,0BAA0B,CACxC,MAAM,EAAE,MAAM,CAAC,IAAI,CAAC,EACpB,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,YAAY,GACnB,MAAM,CAAC,IAAI,CAAC,CAEd;AAED,wBAAiB,mBAAmB,CAClC,MAAM,EAAE,MAAM,CAAC,IAAI,GAAG,OAAO,CAAC,EAC9B,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,YAAY,GACnB,MAAM,CAAC,IAAI,GAAG,OAAO,CAAC,CAqGxB;AAED,wBAAgB,mCAAmC,CACjD,MAAM,EAAE,MAAM,CAAC,IAAI,CAAC,EACpB,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,YAAY,GACnB,MAAM,CAAC,IAAI,CAAC,CAEd;AAED,wBAAiB,4BAA4B,CAC3C,MAAM,EAAE,MAAM,CAAC,IAAI,GAAG,OAAO,CAAC,EAC9B,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,YAAY,GACnB,MAAM,CAAC,IAAI,GAAG,OAAO,CAAC,CAkExB;AAED,wBAAgB,uBAAuB,CACrC,CAAC,EAAE,GAAG,EACN,CAAC,EAAE,GAAG,EACN,GAAG,EAAE,WAAW,GACf,OAAO,CAOT;AAED,wBAAgB,WAAW,CACzB,MAAM,EAAE,GAAG,EACX,SAAS,EAAE,WAAW,EACtB,KAAK,EAAE,GAAG,EACV,QAAQ,EAAE,WAAW,WAQtB;AAED;;;;GAIG;AACH,wBAAgB,mBAAmB,CACjC,SAAS,EAAE,GAAG,EACd,SAAS,EAAE,WAAW,EACtB,SAAS,EAAE,WAAW,GACrB,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,GAAG,SAAS,CAUnC;AAED,wBAAgB,mBAAmB,CACjC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,KAAK,GAAG,SAAS,CAAC,EACzC,IAAI,EAAE,WAAW,GAChB,MAAM,CAER;AAED;;;GAGG;AACH,wBAAgB,YAAY,CAC1B,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,KAAK,GAAG,SAAS,CAAC,EACzC,IAAI,EAAE,WAAW,GAChB,MAAM,CAUR"}