@electric-sql/d2mini
Version:
D2Mini is a minimal implementation of Differential Dataflow for performing in-memory incremental view maintenance.
128 lines • 4.42 kB
JavaScript
import { MultiSet } from './multiset.js';
import { DefaultMap, hash } from './utils.js';
/**
* A map from a difference collection trace's keys -> (value, multiplicities) that changed.
* Used in operations like join and reduce where the operation needs to
* exploit the key-value structure of the data to run efficiently.
*/
export class Index {
#inner;
#changedKeys;
constructor() {
this.#inner = new DefaultMap(() => []);
this.#changedKeys = new Set();
}
toString(indent = false) {
return `Index(${JSON.stringify([...this.#inner], undefined, indent ? ' ' : undefined)})`;
}
get(key) {
return this.#inner.get(key);
}
entries() {
return this.#inner.entries();
}
keys() {
return this.#inner.keys();
}
has(key) {
return this.#inner.has(key) && this.#inner.get(key).length > 0;
}
get size() {
let count = 0;
for (const [, values] of this.#inner.entries()) {
if (values.length > 0) {
count++;
}
}
return count;
}
addValue(key, value) {
const values = this.#inner.get(key);
values.push(value);
this.#changedKeys.add(key);
}
append(other) {
for (const [key, otherValues] of other.entries()) {
const thisValues = this.#inner.get(key);
for (const value of otherValues) {
thisValues.push(value);
}
this.#changedKeys.add(key);
}
}
compact(keys = []) {
// If no keys specified, use the changed keys
const keysToProcess = keys.length === 0 ? [...this.#changedKeys] : keys;
for (const key of keysToProcess) {
if (!this.#inner.has(key))
continue;
const values = this.#inner.get(key);
const consolidated = this.consolidateValues(values);
// Remove the key entirely and re-add only if there are non-zero values
this.#inner.delete(key);
if (consolidated.length > 0) {
this.#inner.get(key).push(...consolidated);
}
}
// Clear the changed keys after compaction
if (keys.length === 0) {
this.#changedKeys.clear();
}
else {
// Only remove the keys that were explicitly compacted
for (const key of keys) {
this.#changedKeys.delete(key);
}
}
}
consolidateValues(values) {
const consolidated = new Map();
for (const [value, multiplicity] of values) {
const valueHash = hash(value);
if (consolidated.has(valueHash)) {
consolidated.get(valueHash).multiplicity += multiplicity;
}
else {
consolidated.set(valueHash, { value, multiplicity });
}
}
return [...consolidated.values()]
.filter(({ multiplicity }) => multiplicity !== 0)
.map(({ value, multiplicity }) => [value, multiplicity]);
}
join(other) {
const result = [];
// We want to iterate over the smaller of the two indexes to reduce the
// number of operations we need to do.
if (this.size <= other.size) {
for (const [key, values1] of this.entries()) {
if (!other.has(key))
continue;
const values2 = other.get(key);
for (const [val1, mul1] of values1) {
for (const [val2, mul2] of values2) {
if (mul1 !== 0 && mul2 !== 0) {
result.push([[key, [val1, val2]], mul1 * mul2]);
}
}
}
}
}
else {
for (const [key, values2] of other.entries()) {
if (!this.has(key))
continue;
const values1 = this.get(key);
for (const [val2, mul2] of values2) {
for (const [val1, mul1] of values1) {
if (mul1 !== 0 && mul2 !== 0) {
result.push([[key, [val1, val2]], mul1 * mul2]);
}
}
}
}
}
return new MultiSet(result);
}
}
//# sourceMappingURL=indexes.js.map