author | Alan Dipert
<alan@dipert.org> 2021-06-14 14:12:11 UTC |
committer | Alan Dipert
<alan@dipert.org> 2021-06-14 14:12:11 UTC |
parent | 0a5b716b4c4483cb0bab4a9d5fc55ee55281fd7f |
datalog.mjs | +21 | -4 |
tests.mjs | +57 | -19 |
diff --git a/datalog.mjs b/datalog.mjs index 27b31f8..785495c 100644 --- a/datalog.mjs +++ b/datalog.mjs @@ -216,6 +216,21 @@ function evalPred(predExpr) { if (!(args[i] < args[i+1])) return false; } return true; + } else if (op === '<=') { + for (let i = 0; i < args.length-1; i += 2) { + if (!(args[i] <= args[i+1])) return false; + } + return true; + } else if (op === '>') { + for (let i = 0; i < args.length-1; i += 2) { + if (!(args[i] > args[i+1])) return false; + } + return true; + } else 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}`); } @@ -223,18 +238,20 @@ function evalPred(predExpr) { function filterByPredicate(rel, predClause) { return [...rel].reduce((xs, x) => { - let predExpr = predClause.map(y => { + let predExpr = []; + for (let y of predClause) { 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]; + if (x[y.description] === ANY.VALUE) continue; + predExpr.push(x[y.description]); } else { - return y; + predExpr.push(y); } - }); + }; if (evalPred(predExpr)) xs.add(x); return xs; }, new Relation(rel.vars)); diff --git a/tests.mjs b/tests.mjs index 51bb6bb..5a61927 100644 --- a/tests.mjs +++ b/tests.mjs @@ -1,4 +1,4 @@ -import { _, query, StupidTupleSet } from './datalog.mjs'; +import { _, query, StupidTupleSet, ANY } from './datalog.mjs'; const { module, test } = QUnit; // QUnit.assert.queryEquals = function(expect, q, ...args) { @@ -17,42 +17,54 @@ module('datalog', () => { let db2 = new StupidTupleSet([ ["ethel", "sex", "female"], - ["fred", "sex", "male"] + ["fred", "sex", "male"], + ["sally", "sex", "female"] ]); - test('simple query', assert => { + test('basic query', assert => { assert.deepEqual( query({ find: [_.e], - use: [[_.db]], where: [ - [_.db, _.e, "age", 42] + [_.e, "age", 42] ] }, db1).toArray(), - [["fred"], ["ethel"]] + [["fred"], ["ethel"]], + "implicit database" ); - }); - test('implicit database', assert => { assert.deepEqual( query({ find: [_.e], + use: [[_.db]], where: [ - [_.e, "age", 42] + [_.db, _.e, "age", 42] ] }, db1).toArray(), - [["fred"], ["ethel"]] + [["fred"], ["ethel"]], + "explicit database" + ); + assert.deepEqual( + query({ + find: [_.sex], + use: [[_.db1, _.db2], _.age], + where: [ + [_.db1, _.e, "age", _.age], + [_.db2, _.e, "sex", _.sex] + ] + }, db1, db2, 21).toArray(), + [["female"]], + "multiple databases with variable" ); - }) - test('partial unify', assert => { assert.deepEqual( query({ find: [_.e], where: [[_.e]] }, db2).toArray(), - [["ethel"], ["fred"]] - ) - }) - test('simple predicate', assert => { + [["ethel"], ["fred"], ["sally"]], + "partial tuple match" + ); + }); + test('predicates', assert => { assert.deepEqual( query({ find: [_.e], @@ -61,7 +73,33 @@ module('datalog', () => { [['<', _.age, 30]] ] }, db1).toArray(), - [["sally"]] - ) - }) + [["sally"]], + "no variable" + ); + assert.deepEqual( + query({ + find: [_.e], + use: [_.maxAge], + where: [ + [_.e, "age", _.age], + [['<', _.age, _.maxAge]] + ] + }, db1, ANY.VALUE).toArray(), + [["sally"], ["fred"], ["ethel"]], + "ANY.VALUE variable" + ); + assert.deepEqual( + query({ + find: [_.e], + use: [_.minAge, _.maxAge], + where: [ + [_.e, "age", _.age], + [['>', _.age, _.minAge]], + [['<=', _.age, _.maxAge]] + ] + }, db1, 30, 50).toArray(), + [["fred"], ["ethel"]], + "multiple variable" + ); + }); });