@electric-sql/d2mini
Version:
D2Mini is a minimal implementation of Differential Dataflow for performing in-memory incremental view maintenance.
144 lines • 4.89 kB
JavaScript
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