Skip to content

Instantly share code, notes, and snippets.

@jackrusher
Created March 29, 2021 09:23
Show Gist options
  • Star 6 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jackrusher/243411921ece513be355349adcd1c744 to your computer and use it in GitHub Desktop.
Save jackrusher/243411921ece513be355349adcd1c744 to your computer and use it in GitHub Desktop.
A fast, minimal JS triple store implementation in ~70 lines of code
// three indices to efficiently support all lookups
function createDB() {
return {eav: new Map(),
ave: new Map(),
vea: new Map()};
}
function addToIndex(xs, x, y, z, triple) {
let ys = xs.get(x);
if(ys == undefined) {
ys = new Map();
xs.set(x, ys);
}
let zs = ys.get(y);
if(zs == undefined) {
zs = new Map();
ys.set(y, zs);
}
zs.set(z, triple);
}
function removeFromIndex(table, x, y, z) {
table.get(x)?.get(y)?.delete(z);
}
// always called with at least x
function lookupInIndex(index, x, y, z) {
let triples = new Set();
if (y == undefined) {
index.get(x)?.forEach(function(y) {
y.forEach((z) => triples.add(z));
});
} else if (z == undefined ) {
index.get(x)?.get(y)?.forEach((z) => triples.add(z));
} else {
let triple = index.get(x)?.get(y)?.get(z);
if (triple) {
triples.add(triple);
}
}
return triples;
}
// N.B. upsert semantics!
function add(db, e, a, v) {
// index payloads are all pointers to a single frozen array, thus we
// are able to return an immutable value and JS's questionable equality
// semantics actually work properly for triples (Sets work, &c).
let triple = Object.freeze([e, a, v]);
addToIndex(db.eav, e, a, v, triple);
addToIndex(db.ave, a, v, e, triple);
addToIndex(db.vea, v, e, a, triple);
}
function remove(db, e, a, v) {
removeFromIndex(db.eav, e, a, v);
removeFromIndex(db.ave, a, v, e);
removeFromIndex(db.vea, v, e, a);
}
function get(db, e, a, v) {
if(e != undefined) {
if(a == undefined && v != undefined) {
return lookupInIndex(db.vea, v, e); // e, null, v
}
return lookupInIndex(db.eav, e, a, v); // e, null, null / e, a, null / e, a, v
} else if(a != undefined) {
return lookupInIndex(db.ave, a, v); // null, a, null / null, a, v
} else if (v != undefined) {
return lookupInIndex(db.vea, v); // null, null, v
}
throw 'get() requires at least one of {e,a,v}!';
}
////////////////////////////////////////////////////////////////////////////////
// testing
test_data = [[1, ":username", "arslonga"],
[1, ":firstname", "Alvin"],
[1, ":lastname", "Pencilpoint"],
[1, ":role", "designer"],
[1, ":group", 47],
[2, ":username", "l33t"],
[2, ":firstname", "Edward"],
[2, ":lastname", "Bughands"],
[2, ":role", "developer"],
[2, ":workgroup", 47],
[3, ":username", "baker"],
[3, ":firstname", "Pippin"],
[3, ":lastname", "Apfelbrot"],
[3, ":role", "designer"],
[3, ":role", "developer"],
[3, ":workgroup", 23]];
function addTestData(db) {
test_data.forEach((triple) => add(db, triple[0], triple[1], triple[2]));
}
test_db = createDB();
addTestData(test_db);
console.log(
JSON.stringify(
Array.from(
get(test_db, 1,null,null)
// get(null,":lastname",null)
// get(null,null,"Bughands")
// get(null,":firstname","Alvin")
// get(3,":role",null)
// get(3,null,"Pippin")
// get(2, ":workgroup", 47)
// get(null,null,null)
)
));
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment