UNPKG

@electric-sql/d2mini

Version:

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

98 lines 4.13 kB
import { DifferenceStreamWriter, UnaryOperator, } from '../graph.js'; import { StreamBuilder } from '../d2.js'; import { MultiSet } from '../multiset.js'; import { Index } from '../indexes.js'; import { hash } from '../utils.js'; /** * Base operator for reduction operations (version-free) */ export class ReduceOperator extends UnaryOperator { #index = new Index(); #indexOut = new Index(); #f; constructor(id, inputA, output, f) { super(id, inputA, output); this.#f = f; } run() { // Collect all input messages and update the index const keysTodo = new Set(); for (const message of this.inputMessages()) { for (const [item, multiplicity] of message.getInner()) { const [key, value] = item; this.#index.addValue(key, [value, multiplicity]); keysTodo.add(key); } } // For each key, compute the reduction and delta const result = []; for (const key of keysTodo) { const curr = this.#index.get(key); const currOut = this.#indexOut.get(key); const out = this.#f(curr); // Create maps for current and previous outputs const newOutputMap = new Map(); const oldOutputMap = new Map(); // Process new output for (const [value, multiplicity] of out) { const valueKey = hash(value); if (newOutputMap.has(valueKey)) { newOutputMap.get(valueKey).multiplicity += multiplicity; } else { newOutputMap.set(valueKey, { value, multiplicity }); } } // Process previous output for (const [value, multiplicity] of currOut) { const valueKey = hash(value); if (oldOutputMap.has(valueKey)) { oldOutputMap.get(valueKey).multiplicity += multiplicity; } else { oldOutputMap.set(valueKey, { value, multiplicity }); } } // First, emit removals for old values that are no longer present or have changed for (const [valueKey, { value, multiplicity }] of oldOutputMap) { const newEntry = newOutputMap.get(valueKey); if (!newEntry || newEntry.multiplicity !== multiplicity) { // Remove the old value entirely result.push([[key, value], -multiplicity]); this.#indexOut.addValue(key, [value, -multiplicity]); } } // Then, emit additions for new values that are not present in old or have changed for (const [valueKey, { value, multiplicity }] of newOutputMap) { const oldEntry = oldOutputMap.get(valueKey); if (!oldEntry || oldEntry.multiplicity !== multiplicity) { // Add the new value only if it has non-zero multiplicity if (multiplicity !== 0) { result.push([[key, value], multiplicity]); this.#indexOut.addValue(key, [value, multiplicity]); } } } } if (result.length > 0) { this.output.sendData(new MultiSet(result)); } // Compact both indexes to consolidate values and remove zero-multiplicity entries // Only compact changed keys for efficiency this.#index.compact(); this.#indexOut.compact(); } } /** * Reduces the elements in the stream by key (version-free) */ export function reduce(f) { return (stream) => { const output = new StreamBuilder(stream.graph, new DifferenceStreamWriter()); const operator = new ReduceOperator(stream.graph.getNextOperatorId(), stream.connectReader(), output.writer, f); stream.graph.addOperator(operator); stream.graph.addStream(output.connectReader()); return output; }; } //# sourceMappingURL=reduce.js.map