UNPKG

@electric-sql/d2mini

Version:

D2Mini is a minimal implementation of Differential Dataflow for performing in-memory incremental view maintenance.

144 lines 4.89 kB
import { DifferenceStreamWriter, BinaryOperator, } from '../graph.js'; import { StreamBuilder } from '../d2.js'; import { MultiSet } from '../multiset.js'; import { Index } from '../indexes.js'; import { negate } from './negate.js'; import { map } from './map.js'; import { concat } from './concat.js'; /** * Operator that joins two input streams */ export class JoinOperator extends BinaryOperator { #indexA = new Index(); #indexB = new Index(); constructor(id, inputA, inputB, output) { super(id, inputA, inputB, output); } run() { const deltaA = new Index(); const deltaB = new Index(); // Process input A - process ALL messages, not just the first one const messagesA = this.inputAMessages(); for (const message of messagesA) { const multiSetMessage = message; for (const [item, multiplicity] of multiSetMessage.getInner()) { const [key, value] = item; deltaA.addValue(key, [value, multiplicity]); } } // Process input B - process ALL messages, not just the first one const messagesB = this.inputBMessages(); for (const message of messagesB) { const multiSetMessage = message; for (const [item, multiplicity] of multiSetMessage.getInner()) { const [key, value] = item; deltaB.addValue(key, [value, multiplicity]); } } // Process results const results = new MultiSet(); // Join deltaA with existing indexB results.extend(deltaA.join(this.#indexB)); // Append deltaA to indexA this.#indexA.append(deltaA); // Join existing indexA with deltaB results.extend(this.#indexA.join(deltaB)); // Send results if (results.getInner().length > 0) { this.output.sendData(results); } // Append deltaB to indexB this.#indexB.append(deltaB); } } /** * Joins two input streams * @param other - The other stream to join with * @param type - The type of join to perform */ export function join(other, type = 'inner') { switch (type) { case 'inner': return innerJoin(other); case 'anti': return antiJoin(other); case 'left': return leftJoin(other); case 'right': return rightJoin(other); case 'full': return fullJoin(other); default: throw new Error(`Join type ${type} is invalid`); } } /** * Joins two input streams * @param other - The other stream to join with */ export function innerJoin(other) { return (stream) => { if (stream.graph !== other.graph) { throw new Error('Cannot join streams from different graphs'); } const output = new StreamBuilder(stream.graph, new DifferenceStreamWriter()); const operator = new JoinOperator(stream.graph.getNextOperatorId(), stream.connectReader(), other.connectReader(), output.writer); stream.graph.addOperator(operator); stream.graph.addStream(output.connectReader()); return output; }; } /** * Joins two input streams * @param other - The other stream to join with */ export function antiJoin(other) { return (stream) => { const matchedLeft = stream.pipe(innerJoin(other), map(([key, [valueLeft, _valueRight]]) => [key, valueLeft])); const anti = stream.pipe(concat(matchedLeft.pipe(negate())), // @ts-ignore TODO: fix this map(([key, value]) => [key, [value, null]])); return anti; }; } /** * Joins two input streams * @param other - The other stream to join with */ export function leftJoin(other) { return (stream) => { const left = stream; const right = other; const inner = left.pipe(innerJoin(right)); const anti = left.pipe(antiJoin(right)); return inner.pipe(concat(anti)); }; } /** * Joins two input streams * @param other - The other stream to join with */ export function rightJoin(other) { return (stream) => { const left = stream; const right = other; const inner = left.pipe(innerJoin(right)); const anti = right.pipe(antiJoin(left), map(([key, [a, b]]) => [key, [b, a]])); return inner.pipe(concat(anti)); }; } /** * Joins two input streams * @param other - The other stream to join with */ export function fullJoin(other) { return (stream) => { const left = stream; const right = other; const inner = left.pipe(innerJoin(right)); const antiLeft = left.pipe(antiJoin(right)); const antiRight = right.pipe(antiJoin(left), map(([key, [a, b]]) => [key, [b, a]])); return inner.pipe(concat(antiLeft), concat(antiRight)); }; } //# sourceMappingURL=join.js.map