@rocicorp/zero 1.11.0-canary.11 → 1.11.0-canary.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/out/analyze-query/src/analyze-cli.js +2 -2
- package/out/analyze-query/src/analyze-cli.js.map +1 -1
- package/out/shared/src/ring-buffer.d.ts +2 -0
- package/out/shared/src/ring-buffer.d.ts.map +1 -1
- package/out/shared/src/ring-buffer.js +4 -0
- package/out/shared/src/ring-buffer.js.map +1 -1
- package/out/zero/package.js +1 -1
- package/out/zero/package.js.map +1 -1
- package/out/zero-cache/src/server/replicator.d.ts.map +1 -1
- package/out/zero-cache/src/server/replicator.js +26 -13
- package/out/zero-cache/src/server/replicator.js.map +1 -1
- package/out/zero-cache/src/services/change-source/common/backfill-manager.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-source/common/backfill-manager.js +169 -19
- package/out/zero-cache/src/services/change-source/common/backfill-manager.js.map +1 -1
- package/out/zero-cache/src/services/change-source/custom/change-source.js +2 -2
- package/out/zero-cache/src/services/change-source/custom/change-source.js.map +1 -1
- package/out/zero-cache/src/services/change-streamer/change-streamer-http.d.ts +45 -6
- package/out/zero-cache/src/services/change-streamer/change-streamer-http.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-streamer/change-streamer-http.js +118 -33
- package/out/zero-cache/src/services/change-streamer/change-streamer-http.js.map +1 -1
- package/out/zero-cache/src/services/change-streamer/change-streamer-service.js +2 -2
- package/out/zero-cache/src/services/change-streamer/change-streamer-service.js.map +1 -1
- package/out/zero-cache/src/services/change-streamer/change-streamer.d.ts +10 -17
- package/out/zero-cache/src/services/change-streamer/change-streamer.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-streamer/change-streamer.js.map +1 -1
- package/out/zero-cache/src/services/change-streamer/schema/tables.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-streamer/schema/tables.js +15 -4
- package/out/zero-cache/src/services/change-streamer/schema/tables.js.map +1 -1
- package/out/zero-cache/src/services/change-streamer/snapshot-message.d.ts +92 -0
- package/out/zero-cache/src/services/change-streamer/snapshot-message.d.ts.map +1 -0
- package/out/zero-cache/src/services/change-streamer/snapshot-message.js +42 -0
- package/out/zero-cache/src/services/change-streamer/snapshot-message.js.map +1 -0
- package/out/zero-cache/src/services/change-streamer/snapshot-reservations.d.ts +1 -1
- package/out/zero-cache/src/services/change-streamer/snapshot-reservations.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-streamer/snapshot-reservations.js +1 -1
- package/out/zero-cache/src/services/change-streamer/snapshot-reservations.js.map +1 -1
- package/out/zero-cache/src/services/change-streamer/snapshot.d.ts +7 -89
- package/out/zero-cache/src/services/change-streamer/snapshot.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-streamer/snapshot.js +26 -98
- package/out/zero-cache/src/services/change-streamer/snapshot.js.map +1 -1
- package/out/zero-cache/src/services/change-streamer/subscribe.d.ts +358 -0
- package/out/zero-cache/src/services/change-streamer/subscribe.d.ts.map +1 -0
- package/out/zero-cache/src/services/change-streamer/subscribe.js +68 -0
- package/out/zero-cache/src/services/change-streamer/subscribe.js.map +1 -0
- package/out/zero-cache/src/services/litestream/commands.d.ts +2 -3
- package/out/zero-cache/src/services/litestream/commands.d.ts.map +1 -1
- package/out/zero-cache/src/services/litestream/commands.js +3 -2
- package/out/zero-cache/src/services/litestream/commands.js.map +1 -1
- package/out/zero-cache/src/services/replicator/change-processor.js +12 -1
- package/out/zero-cache/src/services/replicator/change-processor.js.map +1 -1
- package/out/zero-cache/src/services/replicator/incremental-sync.d.ts +2 -1
- package/out/zero-cache/src/services/replicator/incremental-sync.d.ts.map +1 -1
- package/out/zero-cache/src/services/replicator/incremental-sync.js +13 -7
- package/out/zero-cache/src/services/replicator/incremental-sync.js.map +1 -1
- package/out/zero-cache/src/services/replicator/replicator.d.ts +2 -1
- package/out/zero-cache/src/services/replicator/replicator.d.ts.map +1 -1
- package/out/zero-cache/src/services/replicator/replicator.js +2 -2
- package/out/zero-cache/src/services/replicator/replicator.js.map +1 -1
- package/out/zero-cache/src/services/view-syncer/client-handler.d.ts +19 -2
- package/out/zero-cache/src/services/view-syncer/client-handler.d.ts.map +1 -1
- package/out/zero-cache/src/services/view-syncer/client-handler.js +8 -1
- package/out/zero-cache/src/services/view-syncer/client-handler.js.map +1 -1
- package/out/zero-cache/src/services/view-syncer/cvr-store.d.ts +19 -1
- package/out/zero-cache/src/services/view-syncer/cvr-store.d.ts.map +1 -1
- package/out/zero-cache/src/services/view-syncer/cvr-store.js +34 -11
- package/out/zero-cache/src/services/view-syncer/cvr-store.js.map +1 -1
- package/out/zero-cache/src/services/view-syncer/cvr.d.ts +6 -2
- package/out/zero-cache/src/services/view-syncer/cvr.d.ts.map +1 -1
- package/out/zero-cache/src/services/view-syncer/cvr.js +9 -5
- package/out/zero-cache/src/services/view-syncer/cvr.js.map +1 -1
- package/out/zero-cache/src/services/view-syncer/pipeline-driver.d.ts.map +1 -1
- package/out/zero-cache/src/services/view-syncer/pipeline-driver.js +44 -11
- package/out/zero-cache/src/services/view-syncer/pipeline-driver.js.map +1 -1
- package/out/zero-cache/src/services/view-syncer/snapshotter.d.ts +21 -1
- package/out/zero-cache/src/services/view-syncer/snapshotter.d.ts.map +1 -1
- package/out/zero-cache/src/services/view-syncer/snapshotter.js +14 -6
- package/out/zero-cache/src/services/view-syncer/snapshotter.js.map +1 -1
- package/out/zero-cache/src/services/view-syncer/view-syncer.d.ts.map +1 -1
- package/out/zero-cache/src/services/view-syncer/view-syncer.js +28 -4
- package/out/zero-cache/src/services/view-syncer/view-syncer.js.map +1 -1
- package/out/zero-cache/src/types/streams.d.ts +33 -7
- package/out/zero-cache/src/types/streams.d.ts.map +1 -1
- package/out/zero-cache/src/types/streams.js +93 -50
- package/out/zero-cache/src/types/streams.js.map +1 -1
- package/out/zero-cache/src/types/subscription.d.ts +11 -0
- package/out/zero-cache/src/types/subscription.d.ts.map +1 -1
- package/out/zero-cache/src/types/subscription.js +56 -35
- package/out/zero-cache/src/types/subscription.js.map +1 -1
- package/out/zero-cache/src/types/ws.js +1 -31
- package/out/zero-cache/src/types/ws.js.map +1 -1
- package/out/zero-cache/src/workers/connection.js +3 -3
- package/out/zero-cache/src/workers/connection.js.map +1 -1
- package/out/zero-cache/src/workers/syncer.js +2 -2
- package/out/zero-cache/src/workers/syncer.js.map +1 -1
- package/out/zero-client/src/client/version.js +1 -1
- package/out/zero-client/src/client/zero.d.ts.map +1 -1
- package/out/zero-client/src/client/zero.js +5 -5
- package/out/zero-client/src/client/zero.js.map +1 -1
- package/out/zero-protocol/src/inspect-down.d.ts +81 -81
- package/out/zql/src/builder/builder.d.ts.map +1 -1
- package/out/zql/src/builder/builder.js +6 -4
- package/out/zql/src/builder/builder.js.map +1 -1
- package/out/zql/src/ivm/cap.d.ts +2 -1
- package/out/zql/src/ivm/cap.d.ts.map +1 -1
- package/out/zql/src/ivm/cap.js +3 -0
- package/out/zql/src/ivm/cap.js.map +1 -1
- package/out/zql/src/ivm/exists.d.ts +2 -0
- package/out/zql/src/ivm/exists.d.ts.map +1 -1
- package/out/zql/src/ivm/exists.js +3 -0
- package/out/zql/src/ivm/exists.js.map +1 -1
- package/out/zql/src/ivm/fan-in.d.ts +5 -1
- package/out/zql/src/ivm/fan-in.d.ts.map +1 -1
- package/out/zql/src/ivm/fan-in.js +7 -0
- package/out/zql/src/ivm/fan-in.js.map +1 -1
- package/out/zql/src/ivm/fan-out.d.ts +3 -0
- package/out/zql/src/ivm/fan-out.d.ts.map +1 -1
- package/out/zql/src/ivm/fan-out.js +4 -0
- package/out/zql/src/ivm/fan-out.js.map +1 -1
- package/out/zql/src/ivm/filter-operators.d.ts +2 -0
- package/out/zql/src/ivm/filter-operators.d.ts.map +1 -1
- package/out/zql/src/ivm/filter-operators.js +9 -0
- package/out/zql/src/ivm/filter-operators.js.map +1 -1
- package/out/zql/src/ivm/filter.d.ts +4 -1
- package/out/zql/src/ivm/filter.d.ts.map +1 -1
- package/out/zql/src/ivm/filter.js +3 -0
- package/out/zql/src/ivm/filter.js.map +1 -1
- package/out/zql/src/ivm/flipped-join.d.ts +4 -3
- package/out/zql/src/ivm/flipped-join.d.ts.map +1 -1
- package/out/zql/src/ivm/flipped-join.js +73 -22
- package/out/zql/src/ivm/flipped-join.js.map +1 -1
- package/out/zql/src/ivm/join-utils.d.ts +24 -7
- package/out/zql/src/ivm/join-utils.d.ts.map +1 -1
- package/out/zql/src/ivm/join-utils.js +87 -4
- package/out/zql/src/ivm/join-utils.js.map +1 -1
- package/out/zql/src/ivm/join.d.ts +4 -4
- package/out/zql/src/ivm/join.d.ts.map +1 -1
- package/out/zql/src/ivm/join.js +55 -64
- package/out/zql/src/ivm/join.js.map +1 -1
- package/out/zql/src/ivm/memory-source.d.ts.map +1 -1
- package/out/zql/src/ivm/memory-source.js +33 -23
- package/out/zql/src/ivm/memory-source.js.map +1 -1
- package/out/zql/src/ivm/operator.d.ts +39 -10
- package/out/zql/src/ivm/operator.d.ts.map +1 -1
- package/out/zql/src/ivm/operator.js +9 -4
- package/out/zql/src/ivm/operator.js.map +1 -1
- package/out/zql/src/ivm/push-accumulated.d.ts +6 -1
- package/out/zql/src/ivm/push-accumulated.d.ts.map +1 -1
- package/out/zql/src/ivm/push-accumulated.js +47 -2
- package/out/zql/src/ivm/push-accumulated.js.map +1 -1
- package/out/zql/src/ivm/skip.d.ts +2 -1
- package/out/zql/src/ivm/skip.d.ts.map +1 -1
- package/out/zql/src/ivm/skip.js +3 -0
- package/out/zql/src/ivm/skip.js.map +1 -1
- package/out/zql/src/ivm/take-gate.d.ts +24 -2
- package/out/zql/src/ivm/take-gate.d.ts.map +1 -1
- package/out/zql/src/ivm/take-gate.js +26 -1
- package/out/zql/src/ivm/take-gate.js.map +1 -1
- package/out/zql/src/ivm/take.d.ts +2 -1
- package/out/zql/src/ivm/take.d.ts.map +1 -1
- package/out/zql/src/ivm/take.js +59 -40
- package/out/zql/src/ivm/take.js.map +1 -1
- package/out/zql/src/ivm/union-fan-in.d.ts +3 -0
- package/out/zql/src/ivm/union-fan-in.d.ts.map +1 -1
- package/out/zql/src/ivm/union-fan-in.js +56 -5
- package/out/zql/src/ivm/union-fan-in.js.map +1 -1
- package/out/zql/src/ivm/union-fan-out.d.ts +2 -1
- package/out/zql/src/ivm/union-fan-out.d.ts.map +1 -1
- package/out/zql/src/ivm/union-fan-out.js +5 -0
- package/out/zql/src/ivm/union-fan-out.js.map +1 -1
- package/out/zql/src/query/measure-push-operator.d.ts +2 -1
- package/out/zql/src/query/measure-push-operator.d.ts.map +1 -1
- package/out/zql/src/query/measure-push-operator.js +7 -0
- package/out/zql/src/query/measure-push-operator.js.map +1 -1
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"flipped-join.js","names":["#parent","#child","#parentKey","#childKey","#relationshipName","#schema","#parentPartitionKey","#boundProvider","#pushParent","#pushChild","#output","#inprogressChildChange","#fetchBatched","#fetchChunked","#yieldParentWithOverlay","#inprogressChildChangePosition","#pushChildChange"],"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 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 type {TakeBoundProvider} 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};\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\n #output: Output = throwOutput;\n\n #inprogressChildChange: Change | undefined;\n #inprogressChildChangePosition: Row | undefined;\n\n constructor({\n parent,\n child,\n parentKey,\n childKey,\n relationshipName,\n hidden,\n system,\n parentPartitionKey,\n boundProvider,\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\n const parentSchema = parent.getSchema();\n const childSchema = child.getSchema();\n this.#schema = {\n ...parentSchema,\n relationships: {\n ...parentSchema.relationships,\n [relationshipName]: {\n ...childSchema,\n isHidden: hidden,\n system,\n },\n },\n };\n\n parent.setOutput({\n push: (change: Change) => this.#pushParent(change),\n });\n child.setOutput({\n push: (change: Change) => this.#pushChild(change),\n });\n }\n\n destroy(): void {\n this.#child.destroy();\n this.#parent.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 let isParentInPushQueue: boolean;\n if (this.#boundProvider) {\n const partitionConstraint = this.#parentPartitionKey\n ? Object.fromEntries(\n this.#parentPartitionKey.map(k => [k, minParentNode.row[k]]),\n )\n : undefined;\n const bound = this.#boundProvider.getBound(partitionConstraint);\n isParentInPushQueue =\n bound !== undefined &&\n this.#inprogressChildChangePosition !== undefined &&\n this.#parent\n .getSchema()\n .compareRows(minParentNode.row, this.#inprogressChildChangePosition) >\n 0 &&\n this.#parent.getSchema().compareRows(minParentNode.row, bound) <= 0;\n } else {\n isParentInPushQueue =\n this.#inprogressChildChangePosition !== undefined &&\n this.#parent\n .getSchema()\n .compareRows(minParentNode.row, this.#inprogressChildChangePosition) >\n 0;\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 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 parentNodeStream = this.#parent.fetch({constraint});\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 if (!exists) {\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 exists = true;\n break;\n }\n }\n }\n if (exists) {\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 yield* this.#output.push(\n change[ChangeIndex.TYPE] === ChangeType.ADD\n ? makeAddChange(newNode)\n : makeRemoveChange(newNode),\n this,\n );\n }\n }\n } finally {\n this.#inprogressChildChange = undefined;\n this.#inprogressChildChangePosition = undefined;\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 yield* this.#output.push(\n makeAddChange(flip(change[ChangeIndex.NODE])),\n this,\n );\n break;\n case ChangeType.REMOVE:\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 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","#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 }\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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"}
|
|
@@ -16,14 +16,31 @@ export declare function isJoinMatch(parent: Row, parentKey: CompoundKey, child:
|
|
|
16
16
|
* since null foreign keys cannot match any rows.
|
|
17
17
|
*/
|
|
18
18
|
export declare function buildJoinConstraint(sourceRow: Row, sourceKey: CompoundKey, targetKey: CompoundKey): Record<string, Value> | undefined;
|
|
19
|
-
export
|
|
20
|
-
readonly constraint: Record<string, Value>;
|
|
21
|
-
readonly pks: Set<string>;
|
|
22
|
-
};
|
|
23
|
-
export declare function canonicalKeyForTest(record: Record<string, Value | bigint | undefined>, keys: CompoundKey): string;
|
|
19
|
+
export declare function canonicalKeyForTest(record: Record<string, Value | undefined>, keys: CompoundKey): string;
|
|
24
20
|
/**
|
|
25
|
-
* Canonical string key over `keys` of `record
|
|
26
|
-
*
|
|
21
|
+
* Canonical string key over `keys` of `record`. Tags values by type
|
|
22
|
+
* so distinct types (e.g. 1 and "1") do not collide.
|
|
27
23
|
*/
|
|
28
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
|
+
}
|
|
33
|
+
export declare function makeUnpartitionedStorageKey(joinKey: string, primaryKey: string): string;
|
|
34
|
+
export declare function makeJoinPrefix(joinKey: string): string;
|
|
35
|
+
export declare function makePartitionStorageKey(joinKey: string, partitionKey: string, primaryKey: string): string;
|
|
36
|
+
export declare function splitPartitionAndPk(suffix: string, numPartitionKeys: number): [partitionKey: string, pk: string];
|
|
37
|
+
export declare function decodeCanonicalValue(s: string): Value;
|
|
38
|
+
export declare function decodePartitionConstraint(partitionKey: string, keys: CompoundKey): Record<string, Value | undefined>;
|
|
39
|
+
export declare function indexParentInStorage(storage: JoinStorage, row: Row, parentKey: CompoundKey, primaryKey: CompoundKey, parentPartitionKey?: CompoundKey): void;
|
|
40
|
+
export declare function unindexParentInStorage(storage: JoinStorage, row: Row, parentKey: CompoundKey, primaryKey: CompoundKey, parentPartitionKey?: CompoundKey): void;
|
|
41
|
+
export type MatchingParentEntry = {
|
|
42
|
+
pks: Set<string>;
|
|
43
|
+
partitionConstraint?: Record<string, Value | undefined> | undefined;
|
|
44
|
+
};
|
|
45
|
+
export declare function getMatchingParentEntries(storage: JoinStorage, childRow: Row, childKey: CompoundKey, parentPartitionKey?: CompoundKey): MatchingParentEntry[] | undefined;
|
|
29
46
|
//# 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":"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,MAAM,MAAM,
|
|
1
|
+
{"version":3,"file":"join-utils.d.ts","sourceRoot":"","sources":["../../../../../zql/src/ivm/join-utils.ts"],"names":[],"mappings":"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"}
|
|
@@ -125,8 +125,8 @@ function buildJoinConstraint(sourceRow, sourceKey, targetKey) {
|
|
|
125
125
|
return constraint;
|
|
126
126
|
}
|
|
127
127
|
/**
|
|
128
|
-
* Canonical string key over `keys` of `record
|
|
129
|
-
*
|
|
128
|
+
* Canonical string key over `keys` of `record`. Tags values by type
|
|
129
|
+
* so distinct types (e.g. 1 and "1") do not collide.
|
|
130
130
|
*/
|
|
131
131
|
function canonicalKey(record, keys) {
|
|
132
132
|
if (keys.length === 1) return canonicalValue(record[keys[0]]);
|
|
@@ -142,11 +142,94 @@ function canonicalValue(v) {
|
|
|
142
142
|
const t = typeof v;
|
|
143
143
|
if (t === "string") return "s" + v;
|
|
144
144
|
if (t === "number") return "d" + v;
|
|
145
|
-
if (t === "bigint") return "b" + v.toString();
|
|
146
145
|
if (t === "boolean") return v ? "t" : "f";
|
|
147
146
|
return "j" + JSON.stringify(v);
|
|
148
147
|
}
|
|
148
|
+
function makeUnpartitionedStorageKey(joinKey, primaryKey) {
|
|
149
|
+
return `j\x00${joinKey}\x00${primaryKey}`;
|
|
150
|
+
}
|
|
151
|
+
function makeJoinPrefix(joinKey) {
|
|
152
|
+
return `j\x00${joinKey}\x00`;
|
|
153
|
+
}
|
|
154
|
+
function makePartitionStorageKey(joinKey, partitionKey, primaryKey) {
|
|
155
|
+
return `j\x00${joinKey}\x00${partitionKey}\x00${primaryKey}`;
|
|
156
|
+
}
|
|
157
|
+
function splitPartitionAndPk(suffix, numPartitionKeys) {
|
|
158
|
+
let idx = 0;
|
|
159
|
+
for (let i = 0; i < numPartitionKeys; i++) {
|
|
160
|
+
const next = suffix.indexOf("\0", idx);
|
|
161
|
+
assert(next !== -1, "Malformed join storage key: missing delimiter");
|
|
162
|
+
if (i === numPartitionKeys - 1) return [suffix.slice(0, next), suffix.slice(next + 1)];
|
|
163
|
+
idx = next + 1;
|
|
164
|
+
}
|
|
165
|
+
throw new Error("Malformed join storage key");
|
|
166
|
+
}
|
|
167
|
+
function decodeCanonicalValue(s) {
|
|
168
|
+
const tag = s[0];
|
|
169
|
+
const rest = s.slice(1);
|
|
170
|
+
switch (tag) {
|
|
171
|
+
case "s": return rest;
|
|
172
|
+
case "d": return Number(rest);
|
|
173
|
+
case "n": return null;
|
|
174
|
+
case "t": return true;
|
|
175
|
+
case "f": return false;
|
|
176
|
+
case "j": return JSON.parse(rest);
|
|
177
|
+
default: throw new Error(`Unknown canonical tag: ${tag}`);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
function decodePartitionConstraint(partitionKey, keys) {
|
|
181
|
+
const parts = keys.length === 1 ? [partitionKey] : partitionKey.split("\0");
|
|
182
|
+
const constraint = {};
|
|
183
|
+
for (let i = 0; i < keys.length; i++) constraint[keys[i]] = decodeCanonicalValue(parts[i]);
|
|
184
|
+
return constraint;
|
|
185
|
+
}
|
|
186
|
+
function indexParentInStorage(storage, row, parentKey, primaryKey, parentPartitionKey) {
|
|
187
|
+
if (parentKey.some((k) => row[k] === null)) return;
|
|
188
|
+
const joinKey = canonicalKey(row, parentKey);
|
|
189
|
+
const parentPk = canonicalKey(row, primaryKey);
|
|
190
|
+
const storageKey = parentPartitionKey ? makePartitionStorageKey(joinKey, canonicalKey(row, parentPartitionKey), parentPk) : makeUnpartitionedStorageKey(joinKey, parentPk);
|
|
191
|
+
storage.set(storageKey, 1);
|
|
192
|
+
}
|
|
193
|
+
function unindexParentInStorage(storage, row, parentKey, primaryKey, parentPartitionKey) {
|
|
194
|
+
if (parentKey.some((k) => row[k] === null)) return;
|
|
195
|
+
const joinKey = canonicalKey(row, parentKey);
|
|
196
|
+
const parentPk = canonicalKey(row, primaryKey);
|
|
197
|
+
const storageKey = parentPartitionKey ? makePartitionStorageKey(joinKey, canonicalKey(row, parentPartitionKey), parentPk) : makeUnpartitionedStorageKey(joinKey, parentPk);
|
|
198
|
+
storage.del(storageKey);
|
|
199
|
+
}
|
|
200
|
+
function getMatchingParentEntries(storage, childRow, childKey, parentPartitionKey) {
|
|
201
|
+
if (childKey.some((k) => childRow[k] === null)) return;
|
|
202
|
+
const prefix = makeJoinPrefix(canonicalKey(childRow, childKey));
|
|
203
|
+
if (!parentPartitionKey) {
|
|
204
|
+
const pks = /* @__PURE__ */ new Set();
|
|
205
|
+
for (const [key] of storage.scan({ prefix })) {
|
|
206
|
+
const pk = key.slice(prefix.length);
|
|
207
|
+
pks.add(pk);
|
|
208
|
+
}
|
|
209
|
+
return pks.size > 0 ? [{ pks }] : void 0;
|
|
210
|
+
}
|
|
211
|
+
const entries = [];
|
|
212
|
+
let currentPartitionKey;
|
|
213
|
+
let currentPks;
|
|
214
|
+
for (const [key] of storage.scan({ prefix })) {
|
|
215
|
+
const [partitionKey, pk] = splitPartitionAndPk(key.slice(prefix.length), parentPartitionKey.length);
|
|
216
|
+
if (partitionKey !== currentPartitionKey) {
|
|
217
|
+
if (currentPartitionKey !== void 0 && currentPks && currentPks.size > 0) entries.push({
|
|
218
|
+
pks: currentPks,
|
|
219
|
+
partitionConstraint: decodePartitionConstraint(currentPartitionKey, parentPartitionKey)
|
|
220
|
+
});
|
|
221
|
+
currentPartitionKey = partitionKey;
|
|
222
|
+
currentPks = /* @__PURE__ */ new Set();
|
|
223
|
+
}
|
|
224
|
+
currentPks?.add(pk);
|
|
225
|
+
}
|
|
226
|
+
if (currentPartitionKey !== void 0 && currentPks && currentPks.size > 0) entries.push({
|
|
227
|
+
pks: currentPks,
|
|
228
|
+
partitionConstraint: decodePartitionConstraint(currentPartitionKey, parentPartitionKey)
|
|
229
|
+
});
|
|
230
|
+
return entries.length > 0 ? entries : void 0;
|
|
231
|
+
}
|
|
149
232
|
//#endregion
|
|
150
|
-
export { buildJoinConstraint, canonicalKey, generateWithOverlay, generateWithOverlayNoYield, generateWithOverlayUnordered, isJoinMatch, rowEqualsForCompoundKey };
|
|
233
|
+
export { buildJoinConstraint, canonicalKey, generateWithOverlay, generateWithOverlayNoYield, generateWithOverlayUnordered, getMatchingParentEntries, indexParentInStorage, isJoinMatch, rowEqualsForCompoundKey, unindexParentInStorage };
|
|
151
234
|
|
|
152
235
|
//# sourceMappingURL=join-utils.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"join-utils.js","names":[],"sources":["../../../../../zql/src/ivm/join-utils.ts"],"sourcesContent":["import {assert} from '../../../shared/src/asserts.ts';\nimport type {CompoundKey} 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 type {Change} from './change.ts';\nimport {compareValues, valuesEqual, type Node} from './data.ts';\nimport type {SourceSchema} from './schema.ts';\nimport type {Stream} from './stream.ts';\n\nexport function generateWithOverlayNoYield(\n stream: Stream<Node>,\n overlay: Change,\n schema: SourceSchema,\n): Stream<Node> {\n return generateWithOverlay(stream, overlay, schema) as Stream<Node>;\n}\n\nexport function* generateWithOverlay(\n stream: Stream<Node | 'yield'>,\n overlay: Change,\n schema: SourceSchema,\n): Stream<Node | 'yield'> {\n let applied = false;\n let editOldApplied = false;\n let editNewApplied = false;\n for (const node of stream) {\n if (node === 'yield') {\n yield node;\n continue;\n }\n let yieldNode = true;\n if (!applied) {\n switch (overlay[ChangeIndex.TYPE]) {\n case ChangeType.ADD: {\n if (\n schema.compareRows(overlay[ChangeIndex.NODE].row, node.row) === 0\n ) {\n applied = true;\n yieldNode = false;\n }\n break;\n }\n case ChangeType.REMOVE: {\n if (schema.compareRows(overlay[ChangeIndex.NODE].row, node.row) < 0) {\n applied = true;\n yield overlay[ChangeIndex.NODE];\n }\n break;\n }\n case ChangeType.EDIT: {\n if (\n !editOldApplied &&\n schema.compareRows(overlay[ChangeIndex.OLD_NODE].row, node.row) < 0\n ) {\n editOldApplied = true;\n if (editNewApplied) {\n applied = true;\n }\n yield overlay[ChangeIndex.OLD_NODE];\n }\n if (\n !editNewApplied &&\n schema.compareRows(overlay[ChangeIndex.NODE].row, node.row) === 0\n ) {\n editNewApplied = true;\n if (editOldApplied) {\n applied = true;\n }\n yieldNode = false;\n }\n break;\n }\n case ChangeType.CHILD: {\n if (\n schema.compareRows(overlay[ChangeIndex.NODE].row, node.row) === 0\n ) {\n applied = true;\n yield {\n row: node.row,\n relationships: {\n ...node.relationships,\n [overlay[ChangeIndex.CHILD_DATA].relationshipName]: () =>\n generateWithOverlay(\n node.relationships[\n overlay[ChangeIndex.CHILD_DATA].relationshipName\n ](),\n overlay[ChangeIndex.CHILD_DATA].change,\n schema.relationships[\n overlay[ChangeIndex.CHILD_DATA].relationshipName\n ],\n ),\n },\n };\n yieldNode = false;\n }\n break;\n }\n }\n }\n if (yieldNode) {\n yield node;\n }\n }\n if (!applied) {\n if (overlay[ChangeIndex.TYPE] === ChangeType.REMOVE) {\n applied = true;\n yield overlay[ChangeIndex.NODE];\n } else if (overlay[ChangeIndex.TYPE] === ChangeType.EDIT) {\n assert(\n editNewApplied,\n 'edit overlay: new node must be applied before old node',\n );\n editOldApplied = true;\n applied = true;\n yield overlay[ChangeIndex.OLD_NODE];\n }\n }\n\n assert(\n applied,\n 'overlayGenerator: overlay was never applied to any fetched node',\n );\n}\n\nexport function generateWithOverlayNoYieldUnordered(\n stream: Stream<Node>,\n overlay: Change,\n schema: SourceSchema,\n): Stream<Node> {\n return generateWithOverlayUnordered(stream, overlay, schema) as Stream<Node>;\n}\n\nexport function* generateWithOverlayUnordered(\n stream: Stream<Node | 'yield'>,\n overlay: Change,\n schema: SourceSchema,\n): Stream<Node | 'yield'> {\n // Eager inject\n if (overlay[ChangeIndex.TYPE] === ChangeType.REMOVE) {\n yield overlay[ChangeIndex.NODE];\n } else if (overlay[ChangeIndex.TYPE] === ChangeType.EDIT) {\n yield overlay[ChangeIndex.OLD_NODE];\n }\n\n // Stream with inline suppress\n let suppressed = false;\n for (const node of stream) {\n if (node === 'yield') {\n yield node;\n continue;\n }\n if (!suppressed) {\n if (\n overlay[ChangeIndex.TYPE] === ChangeType.ADD ||\n overlay[ChangeIndex.TYPE] === ChangeType.EDIT\n ) {\n if (\n rowEqualsForCompoundKey(\n overlay[ChangeIndex.NODE].row,\n node.row,\n schema.primaryKey,\n )\n ) {\n suppressed = true;\n continue;\n }\n }\n if (overlay[ChangeIndex.TYPE] === ChangeType.CHILD) {\n if (\n rowEqualsForCompoundKey(\n overlay[ChangeIndex.NODE].row,\n node.row,\n schema.primaryKey,\n )\n ) {\n suppressed = true;\n yield {\n row: node.row,\n relationships: {\n ...node.relationships,\n [overlay[ChangeIndex.CHILD_DATA].relationshipName]: () =>\n generateWithOverlay(\n node.relationships[\n overlay[ChangeIndex.CHILD_DATA].relationshipName\n ](),\n overlay[ChangeIndex.CHILD_DATA].change,\n schema.relationships[\n overlay[ChangeIndex.CHILD_DATA].relationshipName\n ],\n ),\n },\n };\n continue;\n }\n }\n }\n yield node;\n }\n assert(\n suppressed || overlay[ChangeIndex.TYPE] === ChangeType.REMOVE,\n 'overlayGenerator: overlay was never applied to any fetched node',\n );\n}\n\nexport function rowEqualsForCompoundKey(\n a: Row,\n b: Row,\n key: CompoundKey,\n): boolean {\n for (let i = 0; i < key.length; i++) {\n if (compareValues(a[key[i]], b[key[i]]) !== 0) {\n return false;\n }\n }\n return true;\n}\n\nexport function isJoinMatch(\n parent: Row,\n parentKey: CompoundKey,\n child: Row,\n childKey: CompoundKey,\n) {\n for (let i = 0; i < parentKey.length; i++) {\n if (!valuesEqual(parent[parentKey[i]], child[childKey[i]])) {\n return false;\n }\n }\n return true;\n}\n\n/**\n * Builds a constraint object by mapping values from `sourceRow` using `sourceKey`\n * to keys specified by `targetKey`. Returns `undefined` if any source value is `null`,\n * since null foreign keys cannot match any rows.\n */\nexport function buildJoinConstraint(\n sourceRow: Row,\n sourceKey: CompoundKey,\n targetKey: CompoundKey,\n): Record<string, Value> | undefined {\n const constraint: Record<string, Value> = {};\n for (let i = 0; i < targetKey.length; i++) {\n const value = sourceRow[sourceKey[i]];\n if (value === null) {\n return undefined;\n }\n constraint[targetKey[i]] = value;\n }\n return constraint;\n}\n\nexport type PartitionEntry = {\n readonly constraint: Record<string, Value>;\n readonly pks: Set<string>;\n};\n\n// Test seam with a widened record type — canonicalValue handles bigint\n// at runtime (zqlite's safeIntegers) but `Value` doesn't list it.\nexport function canonicalKeyForTest(\n record: Record<string, Value | bigint | undefined>,\n keys: CompoundKey,\n): string {\n return canonicalKey(record as Record<string, Value | undefined>, keys);\n}\n\n/**\n * Canonical string key over `keys` of `record`, handling bigint values\n * safely without throwing on JSON.stringify.\n */\nexport function canonicalKey(\n record: Record<string, Value | undefined>,\n keys: CompoundKey,\n): string {\n if (keys.length === 1) {\n return canonicalValue(record[keys[0]]);\n }\n let s = '';\n for (let i = 0; i < keys.length; i++) {\n if (i > 0) s += '\\x00';\n s += canonicalValue(record[keys[i]]);\n }\n return s;\n}\n\nfunction canonicalValue(v: Value | bigint | undefined): string {\n // Tag by type so we don't conflate e.g. `1` (number) with `\"1\"` (string).\n // Bigint shows up at runtime when zqlite's safeIntegers is on, even\n // though the static `Value` type doesn't list it.\n if (v === null || v === undefined) return 'n';\n const t = typeof v;\n if (t === 'string') return 's' + (v as string);\n if (t === 'number') return 'd' + (v as number);\n if (t === 'bigint') return 'b' + (v as bigint).toString();\n if (t === 'boolean') return v ? 't' : 'f';\n return 'j' + JSON.stringify(v);\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"join-utils.js","names":[],"sources":["../../../../../zql/src/ivm/join-utils.ts"],"sourcesContent":["import {assert} from '../../../shared/src/asserts.ts';\nimport type {CompoundKey} 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 type {Change} from './change.ts';\nimport {compareValues, valuesEqual, type Node} from './data.ts';\nimport type {SourceSchema} from './schema.ts';\nimport type {Stream} from './stream.ts';\n\nexport function generateWithOverlayNoYield(\n stream: Stream<Node>,\n overlay: Change,\n schema: SourceSchema,\n): Stream<Node> {\n return generateWithOverlay(stream, overlay, schema) as Stream<Node>;\n}\n\nexport function* generateWithOverlay(\n stream: Stream<Node | 'yield'>,\n overlay: Change,\n schema: SourceSchema,\n): Stream<Node | 'yield'> {\n let applied = false;\n let editOldApplied = false;\n let editNewApplied = false;\n for (const node of stream) {\n if (node === 'yield') {\n yield node;\n continue;\n }\n let yieldNode = true;\n if (!applied) {\n switch (overlay[ChangeIndex.TYPE]) {\n case ChangeType.ADD: {\n if (\n schema.compareRows(overlay[ChangeIndex.NODE].row, node.row) === 0\n ) {\n applied = true;\n yieldNode = false;\n }\n break;\n }\n case ChangeType.REMOVE: {\n if (schema.compareRows(overlay[ChangeIndex.NODE].row, node.row) < 0) {\n applied = true;\n yield overlay[ChangeIndex.NODE];\n }\n break;\n }\n case ChangeType.EDIT: {\n if (\n !editOldApplied &&\n schema.compareRows(overlay[ChangeIndex.OLD_NODE].row, node.row) < 0\n ) {\n editOldApplied = true;\n if (editNewApplied) {\n applied = true;\n }\n yield overlay[ChangeIndex.OLD_NODE];\n }\n if (\n !editNewApplied &&\n schema.compareRows(overlay[ChangeIndex.NODE].row, node.row) === 0\n ) {\n editNewApplied = true;\n if (editOldApplied) {\n applied = true;\n }\n yieldNode = false;\n }\n break;\n }\n case ChangeType.CHILD: {\n if (\n schema.compareRows(overlay[ChangeIndex.NODE].row, node.row) === 0\n ) {\n applied = true;\n yield {\n row: node.row,\n relationships: {\n ...node.relationships,\n [overlay[ChangeIndex.CHILD_DATA].relationshipName]: () =>\n generateWithOverlay(\n node.relationships[\n overlay[ChangeIndex.CHILD_DATA].relationshipName\n ](),\n overlay[ChangeIndex.CHILD_DATA].change,\n schema.relationships[\n overlay[ChangeIndex.CHILD_DATA].relationshipName\n ],\n ),\n },\n };\n yieldNode = false;\n }\n break;\n }\n }\n }\n if (yieldNode) {\n yield node;\n }\n }\n if (!applied) {\n if (overlay[ChangeIndex.TYPE] === ChangeType.REMOVE) {\n applied = true;\n yield overlay[ChangeIndex.NODE];\n } else if (overlay[ChangeIndex.TYPE] === ChangeType.EDIT) {\n assert(\n editNewApplied,\n 'edit overlay: new node must be applied before old node',\n );\n editOldApplied = true;\n applied = true;\n yield overlay[ChangeIndex.OLD_NODE];\n }\n }\n\n assert(\n applied,\n 'overlayGenerator: overlay was never applied to any fetched node',\n );\n}\n\nexport function generateWithOverlayNoYieldUnordered(\n stream: Stream<Node>,\n overlay: Change,\n schema: SourceSchema,\n): Stream<Node> {\n return generateWithOverlayUnordered(stream, overlay, schema) as Stream<Node>;\n}\n\nexport function* generateWithOverlayUnordered(\n stream: Stream<Node | 'yield'>,\n overlay: Change,\n schema: SourceSchema,\n): Stream<Node | 'yield'> {\n // Eager inject\n if (overlay[ChangeIndex.TYPE] === ChangeType.REMOVE) {\n yield overlay[ChangeIndex.NODE];\n } else if (overlay[ChangeIndex.TYPE] === ChangeType.EDIT) {\n yield overlay[ChangeIndex.OLD_NODE];\n }\n\n // Stream with inline suppress\n let suppressed = false;\n for (const node of stream) {\n if (node === 'yield') {\n yield node;\n continue;\n }\n if (!suppressed) {\n if (\n overlay[ChangeIndex.TYPE] === ChangeType.ADD ||\n overlay[ChangeIndex.TYPE] === ChangeType.EDIT\n ) {\n if (\n rowEqualsForCompoundKey(\n overlay[ChangeIndex.NODE].row,\n node.row,\n schema.primaryKey,\n )\n ) {\n suppressed = true;\n continue;\n }\n }\n if (overlay[ChangeIndex.TYPE] === ChangeType.CHILD) {\n if (\n rowEqualsForCompoundKey(\n overlay[ChangeIndex.NODE].row,\n node.row,\n schema.primaryKey,\n )\n ) {\n suppressed = true;\n yield {\n row: node.row,\n relationships: {\n ...node.relationships,\n [overlay[ChangeIndex.CHILD_DATA].relationshipName]: () =>\n generateWithOverlay(\n node.relationships[\n overlay[ChangeIndex.CHILD_DATA].relationshipName\n ](),\n overlay[ChangeIndex.CHILD_DATA].change,\n schema.relationships[\n overlay[ChangeIndex.CHILD_DATA].relationshipName\n ],\n ),\n },\n };\n continue;\n }\n }\n }\n yield node;\n }\n assert(\n suppressed || overlay[ChangeIndex.TYPE] === ChangeType.REMOVE,\n 'overlayGenerator: overlay was never applied to any fetched node',\n );\n}\n\nexport function rowEqualsForCompoundKey(\n a: Row,\n b: Row,\n key: CompoundKey,\n): boolean {\n for (let i = 0; i < key.length; i++) {\n if (compareValues(a[key[i]], b[key[i]]) !== 0) {\n return false;\n }\n }\n return true;\n}\n\nexport function isJoinMatch(\n parent: Row,\n parentKey: CompoundKey,\n child: Row,\n childKey: CompoundKey,\n) {\n for (let i = 0; i < parentKey.length; i++) {\n if (!valuesEqual(parent[parentKey[i]], child[childKey[i]])) {\n return false;\n }\n }\n return true;\n}\n\n/**\n * Builds a constraint object by mapping values from `sourceRow` using `sourceKey`\n * to keys specified by `targetKey`. Returns `undefined` if any source value is `null`,\n * since null foreign keys cannot match any rows.\n */\nexport function buildJoinConstraint(\n sourceRow: Row,\n sourceKey: CompoundKey,\n targetKey: CompoundKey,\n): Record<string, Value> | undefined {\n const constraint: Record<string, Value> = {};\n for (let i = 0; i < targetKey.length; i++) {\n const value = sourceRow[sourceKey[i]];\n if (value === null) {\n return undefined;\n }\n constraint[targetKey[i]] = value;\n }\n return constraint;\n}\n\nexport function canonicalKeyForTest(\n record: Record<string, Value | undefined>,\n keys: CompoundKey,\n): string {\n return canonicalKey(record, keys);\n}\n\n/**\n * Canonical string key over `keys` of `record`. Tags values by type\n * so distinct types (e.g. 1 and \"1\") do not collide.\n */\nexport function canonicalKey(\n record: Record<string, Value | undefined>,\n keys: CompoundKey,\n): string {\n if (keys.length === 1) {\n return canonicalValue(record[keys[0]]);\n }\n let s = '';\n for (let i = 0; i < keys.length; i++) {\n if (i > 0) s += '\\x00';\n s += canonicalValue(record[keys[i]]);\n }\n return s;\n}\n\nfunction canonicalValue(v: Value): string {\n // Tag by type so we don't conflate e.g. `1` (number) with `\"1\"` (string).\n if (v === null || v === undefined) return 'n';\n const t = typeof v;\n if (t === 'string') return 's' + (v as string);\n if (t === 'number') return 'd' + (v as number);\n if (t === 'boolean') return v ? 't' : 'f';\n return 'j' + JSON.stringify(v);\n}\n\nexport interface JoinStorage {\n get(key: string): unknown;\n set(key: string, value: unknown): void;\n del(key: string): void;\n scan(options?: {prefix: string}): Stream<[string, unknown]>;\n}\n\nexport function makeUnpartitionedStorageKey(\n joinKey: string,\n primaryKey: string,\n): string {\n return `j\\x00${joinKey}\\x00${primaryKey}`;\n}\n\nexport function makeJoinPrefix(joinKey: string): string {\n return `j\\x00${joinKey}\\x00`;\n}\n\nexport function makePartitionStorageKey(\n joinKey: string,\n partitionKey: string,\n primaryKey: string,\n): string {\n return `j\\x00${joinKey}\\x00${partitionKey}\\x00${primaryKey}`;\n}\n\nexport function splitPartitionAndPk(\n suffix: string,\n numPartitionKeys: number,\n): [partitionKey: string, pk: string] {\n let idx = 0;\n for (let i = 0; i < numPartitionKeys; i++) {\n const next = suffix.indexOf('\\x00', idx);\n assert(next !== -1, 'Malformed join storage key: missing delimiter');\n if (i === numPartitionKeys - 1) {\n return [suffix.slice(0, next), suffix.slice(next + 1)];\n }\n idx = next + 1;\n }\n throw new Error('Malformed join storage key');\n}\n\nexport function decodeCanonicalValue(s: string): Value {\n const tag = s[0];\n const rest = s.slice(1);\n switch (tag) {\n case 's':\n return rest;\n case 'd':\n return Number(rest);\n case 'n':\n return null;\n case 't':\n return true;\n case 'f':\n return false;\n case 'j':\n return JSON.parse(rest);\n default:\n throw new Error(`Unknown canonical tag: ${tag}`);\n }\n}\n\nexport function decodePartitionConstraint(\n partitionKey: string,\n keys: CompoundKey,\n): Record<string, Value | undefined> {\n const parts = keys.length === 1 ? [partitionKey] : partitionKey.split('\\x00');\n const constraint: Record<string, Value | undefined> = {};\n for (let i = 0; i < keys.length; i++) {\n constraint[keys[i]] = decodeCanonicalValue(parts[i]);\n }\n return constraint;\n}\n\nexport function indexParentInStorage(\n storage: JoinStorage,\n row: Row,\n parentKey: CompoundKey,\n primaryKey: CompoundKey,\n parentPartitionKey?: CompoundKey,\n): void {\n if (parentKey.some(k => row[k] === null)) {\n return;\n }\n const joinKey = canonicalKey(row, parentKey);\n const parentPk = canonicalKey(row, primaryKey);\n const storageKey = parentPartitionKey\n ? makePartitionStorageKey(\n joinKey,\n canonicalKey(row, parentPartitionKey),\n parentPk,\n )\n : makeUnpartitionedStorageKey(joinKey, parentPk);\n storage.set(storageKey, 1);\n}\n\nexport function unindexParentInStorage(\n storage: JoinStorage,\n row: Row,\n parentKey: CompoundKey,\n primaryKey: CompoundKey,\n parentPartitionKey?: CompoundKey,\n): void {\n if (parentKey.some(k => row[k] === null)) {\n return;\n }\n const joinKey = canonicalKey(row, parentKey);\n const parentPk = canonicalKey(row, primaryKey);\n const storageKey = parentPartitionKey\n ? makePartitionStorageKey(\n joinKey,\n canonicalKey(row, parentPartitionKey),\n parentPk,\n )\n : makeUnpartitionedStorageKey(joinKey, parentPk);\n storage.del(storageKey);\n}\n\nexport type MatchingParentEntry = {\n pks: Set<string>;\n partitionConstraint?: Record<string, Value | undefined> | undefined;\n};\n\nexport function getMatchingParentEntries(\n storage: JoinStorage,\n childRow: Row,\n childKey: CompoundKey,\n parentPartitionKey?: CompoundKey,\n): MatchingParentEntry[] | undefined {\n if (childKey.some(k => childRow[k] === null)) {\n return undefined;\n }\n const joinKey = canonicalKey(childRow, childKey);\n const prefix = makeJoinPrefix(joinKey);\n\n if (!parentPartitionKey) {\n const pks = new Set<string>();\n for (const [key] of storage.scan({prefix})) {\n const pk = key.slice(prefix.length);\n pks.add(pk);\n }\n return pks.size > 0 ? [{pks}] : undefined;\n }\n\n const entries: MatchingParentEntry[] = [];\n let currentPartitionKey: string | undefined;\n let currentPks: Set<string> | undefined;\n\n for (const [key] of storage.scan({prefix})) {\n const suffix = key.slice(prefix.length);\n const [partitionKey, pk] = splitPartitionAndPk(\n suffix,\n parentPartitionKey.length,\n );\n if (partitionKey !== currentPartitionKey) {\n if (\n currentPartitionKey !== undefined &&\n currentPks &&\n currentPks.size > 0\n ) {\n entries.push({\n pks: currentPks,\n partitionConstraint: decodePartitionConstraint(\n currentPartitionKey,\n parentPartitionKey,\n ),\n });\n }\n currentPartitionKey = partitionKey;\n currentPks = new Set<string>();\n }\n currentPks?.add(pk);\n }\n if (currentPartitionKey !== undefined && currentPks && currentPks.size > 0) {\n entries.push({\n pks: currentPks,\n partitionConstraint: decodePartitionConstraint(\n currentPartitionKey,\n parentPartitionKey,\n ),\n });\n }\n return entries.length > 0 ? entries : undefined;\n}\n"],"mappings":"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"}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import type { CompoundKey, System } from '../../../zero-protocol/src/ast.ts';
|
|
2
2
|
import type { Node } from './data.ts';
|
|
3
|
-
import { type FetchRequest, type Input, type Output } from './operator.ts';
|
|
3
|
+
import { type FetchRequest, type Input, type Output, type Storage } from './operator.ts';
|
|
4
4
|
import type { SourceSchema } from './schema.ts';
|
|
5
5
|
import { type Stream } from './stream.ts';
|
|
6
|
-
import type
|
|
6
|
+
import { type TakeBoundProvider } from './take-gate.ts';
|
|
7
7
|
type Args = {
|
|
8
8
|
parent: Input;
|
|
9
9
|
child: Input;
|
|
@@ -14,7 +14,7 @@ type Args = {
|
|
|
14
14
|
system: System;
|
|
15
15
|
parentPartitionKey?: CompoundKey | undefined;
|
|
16
16
|
boundProvider?: TakeBoundProvider | undefined;
|
|
17
|
-
|
|
17
|
+
storage: Storage;
|
|
18
18
|
};
|
|
19
19
|
/**
|
|
20
20
|
* The Join operator joins the output from two upstream inputs. Zero's join
|
|
@@ -28,7 +28,7 @@ type Args = {
|
|
|
28
28
|
*/
|
|
29
29
|
export declare class Join implements Input {
|
|
30
30
|
#private;
|
|
31
|
-
constructor({ parent, child, parentKey, childKey, relationshipName, hidden, system, parentPartitionKey, boundProvider,
|
|
31
|
+
constructor({ parent, child, parentKey, childKey, relationshipName, hidden, system, parentPartitionKey, boundProvider, storage, }: Args);
|
|
32
32
|
destroy(): void;
|
|
33
33
|
setOutput(output: Output): void;
|
|
34
34
|
getSchema(): SourceSchema;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"join.d.ts","sourceRoot":"","sources":["../../../../../zql/src/ivm/join.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAC,WAAW,EAAE,MAAM,EAAC,MAAM,mCAAmC,CAAC;AAW3E,OAAO,KAAK,EAAC,IAAI,EAAC,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"join.d.ts","sourceRoot":"","sources":["../../../../../zql/src/ivm/join.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAC,WAAW,EAAE,MAAM,EAAC,MAAM,mCAAmC,CAAC;AAW3E,OAAO,KAAK,EAAC,IAAI,EAAC,MAAM,WAAW,CAAC;AAcpC,OAAO,EAEL,KAAK,YAAY,EACjB,KAAK,KAAK,EACV,KAAK,MAAM,EACX,KAAK,OAAO,EACb,MAAM,eAAe,CAAC;AACvB,OAAO,KAAK,EAAC,YAAY,EAAC,MAAM,aAAa,CAAC;AAC9C,OAAO,EAAC,KAAK,MAAM,EAAC,MAAM,aAAa,CAAC;AACxC,OAAO,EAIL,KAAK,iBAAiB,EACvB,MAAM,gBAAgB,CAAC;AAExB,KAAK,IAAI,GAAG;IACV,MAAM,EAAE,KAAK,CAAC;IACd,KAAK,EAAE,KAAK,CAAC;IAEb,SAAS,EAAE,WAAW,CAAC;IACvB,QAAQ,EAAE,WAAW,CAAC;IACtB,gBAAgB,EAAE,MAAM,CAAC;IACzB,MAAM,EAAE,OAAO,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,kBAAkB,CAAC,EAAE,WAAW,GAAG,SAAS,CAAC;IAC7C,aAAa,CAAC,EAAE,iBAAiB,GAAG,SAAS,CAAC;IAC9C,OAAO,EAAE,OAAO,CAAC;CAClB,CAAC;AAEF;;;;;;;;;GASG;AACH,qBAAa,IAAK,YAAW,KAAK;;IAyBhC,YAAY,EACV,MAAM,EACN,KAAK,EACL,SAAS,EACT,QAAQ,EACR,gBAAgB,EAChB,MAAM,EACN,MAAM,EACN,kBAAkB,EAClB,aAAa,EACb,OAAO,GACR,EAAE,IAAI,EAqCN;IAQD,OAAO,IAAI,IAAI,CAGd;IAED,SAAS,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAE9B;IAED,SAAS,IAAI,YAAY,CAExB;IAEA,KAAK,CAAC,GAAG,EAAE,YAAY,GAAG,MAAM,CAAC,IAAI,GAAG,OAAO,CAAC,CAShD;CAmRF"}
|
package/out/zql/src/ivm/join.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { assert, unreachable } from "../../../shared/src/asserts.js";
|
|
2
2
|
import { throwOutput } from "./operator.js";
|
|
3
3
|
import { makeAddChange, makeChildChange, makeEditChange, makeRemoveChange } from "./change.js";
|
|
4
|
-
import { buildJoinConstraint, canonicalKey, generateWithOverlay, generateWithOverlayUnordered, isJoinMatch, rowEqualsForCompoundKey } from "./join-utils.js";
|
|
4
|
+
import { buildJoinConstraint, canonicalKey, generateWithOverlay, generateWithOverlayUnordered, getMatchingParentEntries, indexParentInStorage, isJoinMatch, rowEqualsForCompoundKey, unindexParentInStorage } from "./join-utils.js";
|
|
5
5
|
import { mergeSortedStreams } from "./memory-source.js";
|
|
6
|
+
import { isInParentFetch, readParentFetchBounds } from "./take-gate.js";
|
|
6
7
|
//#region ../zql/src/ivm/join.ts
|
|
7
8
|
/**
|
|
8
9
|
* The Join operator joins the output from two upstream inputs. Zero's join
|
|
@@ -22,12 +23,21 @@ var Join = class {
|
|
|
22
23
|
#relationshipName;
|
|
23
24
|
#schema;
|
|
24
25
|
#parentPartitionKey;
|
|
25
|
-
#
|
|
26
|
+
#storage;
|
|
26
27
|
#boundProvider;
|
|
27
28
|
#output = throwOutput;
|
|
28
29
|
#inprogressChildChange;
|
|
29
30
|
#inprogressChildChangePosition;
|
|
30
|
-
|
|
31
|
+
/**
|
|
32
|
+
* Primary keys of the parents #inprogressChildChange has reached so far,
|
|
33
|
+
* kept only when the parent input is unordered. An unordered stream is not
|
|
34
|
+
* in `compareRows` order (SQLite returns it in whatever order its plan
|
|
35
|
+
* visits, e.g. rowid order), so whether a parent is still in the push queue
|
|
36
|
+
* cannot be decided by comparing it to #inprogressChildChangePosition.
|
|
37
|
+
*/
|
|
38
|
+
#inprogressReachedParents;
|
|
39
|
+
#inprogressParentFetchBounds;
|
|
40
|
+
constructor({ parent, child, parentKey, childKey, relationshipName, hidden, system, parentPartitionKey, boundProvider, storage }) {
|
|
31
41
|
assert(parent !== child, "Parent and child must be different operators");
|
|
32
42
|
assert(parentKey.length === childKey.length, "The parentKey and childKey keys must have same length");
|
|
33
43
|
this.#parent = parent;
|
|
@@ -36,7 +46,7 @@ var Join = class {
|
|
|
36
46
|
this.#childKey = childKey;
|
|
37
47
|
this.#relationshipName = relationshipName;
|
|
38
48
|
this.#parentPartitionKey = parentPartitionKey;
|
|
39
|
-
this.#
|
|
49
|
+
this.#storage = storage;
|
|
40
50
|
this.#boundProvider = boundProvider;
|
|
41
51
|
const parentSchema = parent.getSchema();
|
|
42
52
|
const childSchema = child.getSchema();
|
|
@@ -51,8 +61,17 @@ var Join = class {
|
|
|
51
61
|
}
|
|
52
62
|
}
|
|
53
63
|
};
|
|
54
|
-
parent.setOutput({
|
|
55
|
-
|
|
64
|
+
parent.setOutput({
|
|
65
|
+
push: (change) => this.#pushParent(change),
|
|
66
|
+
reconcile: () => this.#reconcile()
|
|
67
|
+
});
|
|
68
|
+
child.setOutput({
|
|
69
|
+
push: (change) => this.#pushChild(change),
|
|
70
|
+
reconcile: () => this.#reconcile()
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
*#reconcile() {
|
|
74
|
+
if (this.#output.reconcile) yield* this.#output.reconcile(this);
|
|
56
75
|
}
|
|
57
76
|
destroy() {
|
|
58
77
|
this.#parent.destroy();
|
|
@@ -118,35 +137,31 @@ var Join = class {
|
|
|
118
137
|
*#pushChildChange(childRow, change) {
|
|
119
138
|
this.#inprogressChildChange = change;
|
|
120
139
|
this.#inprogressChildChangePosition = void 0;
|
|
140
|
+
this.#inprogressReachedParents = this.#parent.getSchema().sort === void 0 ? /* @__PURE__ */ new Set() : void 0;
|
|
121
141
|
try {
|
|
122
142
|
const constraint = buildJoinConstraint(childRow, this.#childKey, this.#parentKey);
|
|
123
143
|
if (constraint) {
|
|
144
|
+
const matching = getMatchingParentEntries(this.#storage, childRow, this.#childKey, this.#parentPartitionKey);
|
|
145
|
+
if (!matching) return;
|
|
146
|
+
const fetchConstraints = matching.map((entry) => entry.partitionConstraint ? {
|
|
147
|
+
...constraint,
|
|
148
|
+
...entry.partitionConstraint
|
|
149
|
+
} : constraint);
|
|
150
|
+
if (this.#boundProvider) this.#inprogressParentFetchBounds = readParentFetchBounds(this.#boundProvider, fetchConstraints);
|
|
124
151
|
let parentNodeStream;
|
|
125
|
-
if (this.#
|
|
126
|
-
|
|
127
|
-
const
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
parentNodeStream = this.#parent.fetch({ constraint: {
|
|
132
|
-
...constraint,
|
|
133
|
-
...entry.constraint
|
|
134
|
-
} });
|
|
135
|
-
} else {
|
|
136
|
-
const streams = Array.from(partitionEntries.values(), (entry) => this.#parent.fetch({ constraint: {
|
|
137
|
-
...constraint,
|
|
138
|
-
...entry.constraint
|
|
139
|
-
} }));
|
|
140
|
-
const compare = (a, b) => this.#schema.compareRows(a.row, b.row);
|
|
141
|
-
parentNodeStream = mergeSortedStreams(streams, compare);
|
|
142
|
-
}
|
|
143
|
-
} else parentNodeStream = this.#parent.fetch({ constraint });
|
|
152
|
+
if (fetchConstraints.length === 1) parentNodeStream = this.#parent.fetch({ constraint: fetchConstraints[0] });
|
|
153
|
+
else {
|
|
154
|
+
const streams = fetchConstraints.map((c) => this.#parent.fetch({ constraint: c }));
|
|
155
|
+
const compare = (a, b) => this.#schema.compareRows(a.row, b.row);
|
|
156
|
+
parentNodeStream = mergeSortedStreams(streams, compare);
|
|
157
|
+
}
|
|
144
158
|
for (const parentNode of parentNodeStream) {
|
|
145
159
|
if (parentNode === "yield") {
|
|
146
160
|
yield parentNode;
|
|
147
161
|
continue;
|
|
148
162
|
}
|
|
149
163
|
this.#inprogressChildChangePosition = parentNode.row;
|
|
164
|
+
this.#inprogressReachedParents?.add(canonicalKey(parentNode.row, this.#schema.primaryKey));
|
|
150
165
|
const childChange = makeChildChange(this.#processParentNode(parentNode.row, parentNode.relationships), {
|
|
151
166
|
relationshipName: this.#relationshipName,
|
|
152
167
|
change
|
|
@@ -157,55 +172,31 @@ var Join = class {
|
|
|
157
172
|
} finally {
|
|
158
173
|
this.#inprogressChildChange = void 0;
|
|
159
174
|
this.#inprogressChildChangePosition = void 0;
|
|
175
|
+
this.#inprogressReachedParents = void 0;
|
|
176
|
+
this.#inprogressParentFetchBounds = void 0;
|
|
160
177
|
}
|
|
161
178
|
}
|
|
179
|
+
/**
|
|
180
|
+
* Whether the in-progress child change has yet to reach `parentNodeRow`,
|
|
181
|
+
* i.e. the row comes after #inprogressChildChangePosition in the parent
|
|
182
|
+
* stream.
|
|
183
|
+
*/
|
|
184
|
+
#isAfterInprogressPosition(parentNodeRow) {
|
|
185
|
+
if (this.#inprogressChildChangePosition === void 0) return false;
|
|
186
|
+
if (this.#inprogressReachedParents) return !this.#inprogressReachedParents.has(canonicalKey(parentNodeRow, this.#schema.primaryKey));
|
|
187
|
+
return this.#schema.compareRows(parentNodeRow, this.#inprogressChildChangePosition) > 0;
|
|
188
|
+
}
|
|
162
189
|
#indexParentRow(row) {
|
|
163
|
-
|
|
164
|
-
const junctionKey = canonicalKey(row, this.#parentKey);
|
|
165
|
-
const partitionKey = canonicalKey(row, this.#parentPartitionKey);
|
|
166
|
-
const parentPk = canonicalKey(row, this.#parent.getSchema().primaryKey);
|
|
167
|
-
let map = this.#partitionMap.get(junctionKey);
|
|
168
|
-
if (!map) {
|
|
169
|
-
map = /* @__PURE__ */ new Map();
|
|
170
|
-
this.#partitionMap.set(junctionKey, map);
|
|
171
|
-
}
|
|
172
|
-
let entry = map.get(partitionKey);
|
|
173
|
-
if (!entry) {
|
|
174
|
-
entry = {
|
|
175
|
-
constraint: Object.fromEntries(this.#parentPartitionKey.map((k) => [k, row[k]])),
|
|
176
|
-
pks: /* @__PURE__ */ new Set()
|
|
177
|
-
};
|
|
178
|
-
map.set(partitionKey, entry);
|
|
179
|
-
}
|
|
180
|
-
entry.pks.add(parentPk);
|
|
190
|
+
indexParentInStorage(this.#storage, row, this.#parentKey, this.#parent.getSchema().primaryKey, this.#parentPartitionKey);
|
|
181
191
|
}
|
|
182
192
|
#unindexParentRow(row) {
|
|
183
|
-
|
|
184
|
-
const junctionKey = canonicalKey(row, this.#parentKey);
|
|
185
|
-
const partitionKey = canonicalKey(row, this.#parentPartitionKey);
|
|
186
|
-
const parentPk = canonicalKey(row, this.#parent.getSchema().primaryKey);
|
|
187
|
-
const map = this.#partitionMap.get(junctionKey);
|
|
188
|
-
if (map) {
|
|
189
|
-
const entry = map.get(partitionKey);
|
|
190
|
-
if (entry) {
|
|
191
|
-
entry.pks.delete(parentPk);
|
|
192
|
-
if (entry.pks.size === 0) {
|
|
193
|
-
map.delete(partitionKey);
|
|
194
|
-
if (map.size === 0) this.#partitionMap.delete(junctionKey);
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
}
|
|
193
|
+
unindexParentInStorage(this.#storage, row, this.#parentKey, this.#parent.getSchema().primaryKey, this.#parentPartitionKey);
|
|
198
194
|
}
|
|
199
195
|
#processParentNode(parentNodeRow, parentNodeRelations) {
|
|
200
196
|
const childStream = () => {
|
|
201
197
|
const constraint = buildJoinConstraint(parentNodeRow, this.#parentKey, this.#childKey);
|
|
202
198
|
const stream = constraint ? this.#child.fetch({ constraint }) : [];
|
|
203
|
-
|
|
204
|
-
if (this.#boundProvider) {
|
|
205
|
-
const partitionConstraint = this.#parentPartitionKey ? Object.fromEntries(this.#parentPartitionKey.map((k) => [k, parentNodeRow[k]])) : void 0;
|
|
206
|
-
const bound = this.#boundProvider.getBound(partitionConstraint);
|
|
207
|
-
inPushQueue = bound !== void 0 && this.#inprogressChildChangePosition !== void 0 && this.#schema.compareRows(parentNodeRow, this.#inprogressChildChangePosition) > 0 && this.#schema.compareRows(parentNodeRow, bound) <= 0;
|
|
208
|
-
} else inPushQueue = this.#inprogressChildChangePosition !== void 0 && this.#schema.compareRows(parentNodeRow, this.#inprogressChildChangePosition) > 0;
|
|
199
|
+
const inPushQueue = this.#isAfterInprogressPosition(parentNodeRow) && (this.#inprogressParentFetchBounds === void 0 || isInParentFetch(this.#inprogressParentFetchBounds, parentNodeRow, this.#schema.compareRows));
|
|
209
200
|
if (this.#inprogressChildChange && isJoinMatch(parentNodeRow, this.#parentKey, this.#inprogressChildChange[1].row, this.#childKey) && inPushQueue) {
|
|
210
201
|
const childSchema = this.#child.getSchema();
|
|
211
202
|
if (childSchema.sort === void 0) return generateWithOverlayUnordered(stream, this.#inprogressChildChange, childSchema);
|