author | Alan Dipert
<alan@dipert.org> 2021-06-14 03:27:54 UTC |
committer | Alan Dipert
<alan@dipert.org> 2021-06-14 03:27:54 UTC |
parent | 4ee02a000635777b53bc55025e698f0b397180ba |
datalog.mjs | +67 | -10 |
tests.mjs | +12 | -0 |
diff --git a/datalog.mjs b/datalog.mjs index aa79859..27b31f8 100644 --- a/datalog.mjs +++ b/datalog.mjs @@ -41,6 +41,15 @@ function tupleEqual(x, y) { return true; } +function relEqual(o1, o2) { + let [ks1, ks2] = [o1, o2].map(o => Object.keys(o)); + if (ks1.length !== ks2.length) return false; + for (let k of ks1) { + if (o1[k] !== o2[k]) return false; + } + return true; +} + class StupidTupleSet { constructor(tuples = []) { this.tuples = []; @@ -82,14 +91,19 @@ class StupidTupleSet { class Relation { constructor(vars) { this.vars = new Set(vars); - this.rels = {}; + this.rels = []; } add(rel) { - this.rels[JSON.stringify([...this.vars].map(k => rel[k]))] = rel; + if (!this.has(rel)) { + this.rels.push(rel); + } return this; } + has(rel) { + return this.rels.some(v => relEqual(v, rel)); + } [Symbol.iterator]() { - return Object.values(this.rels)[Symbol.iterator](); + return this.rels[Symbol.iterator](); } } @@ -187,6 +201,45 @@ function parseVals(use, vals) { } } +function isPredicateClause(clause) { + return clause.length === 1 && clause[0] instanceof Array; +} + +function isSimpleClause(clause) { + return !isPredicateClause(clause); +} + +function evalPred(predExpr) { + let [op, ...args] = predExpr; + if (op === '<') { + for (let i = 0; i < args.length-1; i += 2) { + if (!(args[i] < args[i+1])) return false; + } + return true; + } else { + throw new Error(`Unknown predicate: ${op}`); + } +} + +function filterByPredicate(rel, predClause) { + return [...rel].reduce((xs, x) => { + let predExpr = predClause.map(y => { + if (isBlank(y)) { + throw new Error("Blanks not allowed in predicates"); + } else if (isVar(y)) { + if (!x.hasOwnProperty(y.description)) { + throw new Error("Unknown variable ${y.description} in predicate"); + } + return x[y.description]; + } else { + return y; + } + }); + if (evalPred(predExpr)) xs.add(x); + return xs; + }, new Relation(rel.vars)); +} + function query({find = [], use = [], where = []}, ...vals) { let parsed = parseVals(use, vals); if (parsed.multipleSources) { @@ -199,13 +252,17 @@ function query({find = [], use = [], where = []}, ...vals) { } return select(rels.reduce(join), varNames(find)); } else { - return select( - [ - initRel(parsed.valueNames, parsed.values), - ...where.map(c => scan(parsed.source, c)) - ].reduce(join), - varNames(find) - ); + let rel = [ + initRel(parsed.valueNames, parsed.values), + ...where + .filter(isSimpleClause) + .map(c => scan(parsed.source, c)) + ].reduce(join); + let preds = where.filter(isPredicateClause).map(x => x[0]); + if (preds.length) { + rel = preds.reduce(filterByPredicate, rel); + } + return select(rel, varNames(find)); } } diff --git a/tests.mjs b/tests.mjs index 0906a2f..51bb6bb 100644 --- a/tests.mjs +++ b/tests.mjs @@ -52,4 +52,16 @@ module('datalog', () => { [["ethel"], ["fred"]] ) }) + test('simple predicate', assert => { + assert.deepEqual( + query({ + find: [_.e], + where: [ + [_.e, "age", _.age], + [['<', _.age, 30]] + ] + }, db1).toArray(), + [["sally"]] + ) + }) });