UNPKG

levelgraph

Version:

A graph database for Node.js and the browser built on top of LevelUp

578 lines (500 loc) 14.3 kB
var queryplanner = require('../lib/queryplanner') , v = require('../lib/variable') , SortJoinStream = require('../lib/sortjoinstream') , JoinStream = require('../lib/joinstream'); var upperBoundChar = '\udbff\udfff'; describe('query planner', function() { var db, query, planner, stub, expected; function buildBefore(algorithm) { beforeEach(function() { db = { db: { approximateSize: function() {} } }; stub = sinon.stub(db.db, 'approximateSize'); planner = queryplanner(db, { joinAlgorithm: algorithm }); }); } describe('with basic algorithm', function() { buildBefore('basic'); it('should return a single condition as-is', function(done) { query = [ { predicate: 'friend' } ]; expected = [ { predicate: 'friend', stream: JoinStream } ]; stub .withArgs('pos::friend::', 'pos::friend::'+upperBoundChar, sinon.match.func) .yields(null, 10); planner(query, function(err, result) { expect(result).to.eql(expected); done(); }); }); it('should order two conditions based on their size', function(done) { query = [{ subject: 'matteo' , predicate: 'friend' }, { predicate: 'friend' }]; expected = [{ subject: 'matteo' , predicate: 'friend' , stream: JoinStream }, { predicate: 'friend' , stream: JoinStream }]; stub .withArgs('pso::friend::matteo::', 'pso::friend::matteo::'+upperBoundChar) .yields(null, 1); stub .withArgs('pos::friend::', 'pos::friend::'+upperBoundChar) .yields(null, 10); planner(query, function(err, result) { expect(result).to.eql(expected); done(); }); }); it('should order two conditions based on their size (bis)', function(done) { query = [{ predicate: 'friend' }, { subject: 'matteo' , predicate: 'friend' }]; expected = [{ subject: 'matteo' , predicate: 'friend' , stream: JoinStream }, { predicate: 'friend' , stream: JoinStream }]; db.db.approximateSize .withArgs('pos::friend::', 'pos::friend::'+upperBoundChar) .yields(null, 10); db.db.approximateSize .withArgs('pso::friend::matteo::', 'pso::friend::matteo::'+upperBoundChar) .yields(null, 1); planner(query, function(err, result) { expect(result).to.eql(expected); done(); }); }); it('should avoid variables', function(done) { query = [ { predicate: 'friend', subject: v('x') } ]; expected = [ { predicate: 'friend', subject: v('x'), stream: JoinStream } ]; stub .withArgs('pos::friend::', 'pos::friend::'+upperBoundChar, sinon.match.func) .yields(null, 10); planner(query, function(err, result) { expect(result).to.eql(query); done(); }); }); }); describe('with sort algorithm', function() { buildBefore('sort'); it('should return a single condition with the JoinStream', function(done) { query = [ { predicate: 'friend' } ]; expected = [ { predicate: 'friend', stream: JoinStream } ]; stub .withArgs('pos::friend::', 'pos::friend::'+upperBoundChar, sinon.match.func) .yields(null, 10); planner(query, function(err, result) { expect(result).to.eql(expected); done(); }); }); it('should put the second condition in the same order as the first', function(done) { query = [{ subject: v('x') , predicate: 'friend' , object: v('c') }, { subject: v('x') , predicate: 'abc' , object: v('c') }]; expected = [{ subject: v('x') , predicate: 'friend' , object: v('c') , stream: JoinStream , index: 'pos' }, { subject: v('x') , predicate: 'abc' , object: v('c') , stream: SortJoinStream , index: 'pos' }]; stub .withArgs('pos::friend::', 'pos::friend::'+upperBoundChar, sinon.match.func) .yields(null, 1); stub .withArgs('pos::abc::', 'pos::abc::'+upperBoundChar, sinon.match.func) .yields(null, 10); planner(query, function(err, result) { expect(result).to.eql(expected); done(); }); }); it('should create the proper index for the friend-of-a-friend query', function(done) { query = [{ subject: v('x') , predicate: 'friend' , object: v('c') }, { subject: v('c') , predicate: 'friend' , object: v('x') }]; expected = [{ subject: v('x') , predicate: 'friend' , object: v('c') , stream: JoinStream , index: 'pos' }, { subject: v('c') , predicate: 'friend' , object: v('x') , stream: SortJoinStream , index: 'pso' }]; stub .withArgs('pos::friend::', 'pos::friend::'+upperBoundChar, sinon.match.func) .yields(null, 10); planner(query, function(err, result) { expect(result).to.eql(expected); done(); }); }); it('should use a SortJoinStream for a three-conditions query', function(done) { query = [{ subject: v('x') , predicate: 'friend' , object: v('c') }, { subject: v('c') , predicate: 'friend' , object: v('x') }, { subject: 'bob' , predicate: 'father' , object: v('c') }]; expected = [{ subject: v('x') , predicate: 'friend' , object: v('c') , stream: JoinStream , index: 'pos' }, { subject: v('c') , predicate: 'friend' , object: v('x') , stream: SortJoinStream , index: 'pso' }, { subject: 'bob' , predicate: 'father' , object: v('c') , stream: SortJoinStream , index: 'pso' }]; stub .withArgs('pos::friend::', 'pos::friend::'+upperBoundChar, sinon.match.func) .yields(null, 10); stub .withArgs('pso::father::bob::', 'pso::father::bob::'+upperBoundChar, sinon.match.func) .yields(null, 100); planner(query, function(err, result) { expect(result).to.eql(expected); done(); }); }); it('should support inverting the index even on three-conditions queries', function(done) { query = [{ subject: v('x') , predicate: 'friend' , object: v('c') }, { subject: v('c') , predicate: 'friend' , object: v('y') }, { subject: v('y') , predicate: 'friend' , object: v('z') }]; expected = [{ subject: v('x') , predicate: 'friend' , object: v('c') , stream: JoinStream , index: 'pos' }, { subject: v('c') , predicate: 'friend' , object: v('y') , stream: SortJoinStream , index: 'pso' }, { subject: v('y') , predicate: 'friend' , object: v('z') , stream: SortJoinStream , index: 'pso' }]; stub .withArgs('pos::friend::', 'pos::friend::'+upperBoundChar, sinon.match.func) .yields(null, 10); planner(query, function(err, result) { expect(result).to.eql(expected); done(); }); }); it('should put the variables from the previous condition in the same order', function(done) { query = [{ subject: v('x0') , predicate: 'friend' , object: 'davide' }, { subject: v('x1') , predicate: 'friend' , object: v('x0') }, { subject: v('x1') , predicate: 'friend' , object: v('x2') }]; expected = [{ subject: v('x0') , predicate: 'friend' , object: 'davide' , stream: JoinStream , index: 'pos' }, { subject: v('x1') , predicate: 'friend' , object: v('x0') , stream: SortJoinStream , index: 'pos' }, { subject: v('x1') , predicate: 'friend' , object: v('x2') , stream: SortJoinStream , index: 'pso' }]; stub .withArgs('pos::friend::', 'pos::friend::'+upperBoundChar, sinon.match.func) .yields(null, 10); stub .withArgs('ops::davide::friend::', 'ops::davide::friend::'+upperBoundChar, sinon.match.func) .yields(null, 1); planner(query, function(err, result) { expect(result).to.eql(expected); done(); }); }); it('should use a SortJoinStream for another three-conditions query', function(done) { query = [{ subject: 'matteo' , predicate: 'friend' , object: v('x') }, { subject: v('x') , predicate: 'friend' , object: v('y') }, { subject: v('y') , predicate: 'friend' , object: 'daniele' }]; expected = [{ subject: 'matteo' , predicate: 'friend' , object: v('x') , stream: JoinStream , index: 'pso' }, { subject: v('x') , predicate: 'friend' , object: v('y') , stream: SortJoinStream , index: 'pso' }, { subject: v('y') , predicate: 'friend' , object: 'daniele' , stream: SortJoinStream , index: 'pos' }]; stub .withArgs('pos::friend::', 'pos::friend::'+upperBoundChar, sinon.match.func) .yields(null, 10); stub .withArgs('pso::friend::matteo::', 'pso::friend::matteo::'+upperBoundChar, sinon.match.func) .yields(null, 1); stub .withArgs('ops::daniele::friend::', 'ops::daniele::friend::'+upperBoundChar, sinon.match.func) .yields(null, 100); planner(query, function(err, result) { expect(result).to.eql(expected); done(); }); }); it('should use a SortJoinStream for the friend-of-a-friend-of-a-friend scenario', function(done) { query = [{ subject: 'matteo' , predicate: 'friend' , object: v('x') }, { subject: v('x') , predicate: 'friend' , object: v('y') }, { subject: v('y') , predicate: 'friend' , object: v('z') }]; expected = [{ subject: 'matteo' , predicate: 'friend' , object: v('x') , stream: JoinStream , index: 'pso' }, { subject: v('x') , predicate: 'friend' , object: v('y') , stream: SortJoinStream , index: 'pso' }, { subject: v('y') , predicate: 'friend' , object: v('z') , stream: SortJoinStream , index: 'pso' }]; stub .withArgs('pos::friend::', 'pos::friend::'+upperBoundChar, sinon.match.func) .yields(null, 10); stub .withArgs('pso::friend::matteo::', 'pso::friend::matteo::'+upperBoundChar, sinon.match.func) .yields(null, 1); planner(query, function(err, result) { expect(result).to.eql(expected); done(); }); }); it('should pick the correct indexes with multiple predicates going out the same subject', function(done) { query = [{ subject: v('a') , predicate: 'friend' , object: 'marco' }, { subject: v('a') , predicate: 'friend' , object: v('x1') }, { subject: v('x1') , predicate: 'friend' , object: v('a') }]; expected = [{ subject: v('a') , predicate: 'friend' , object: 'marco' , stream: JoinStream , index: 'pos' }, { subject: v('a') , predicate: 'friend' , object: v('x1') , stream: SortJoinStream , index: 'pso' }, { subject: v('x1') , predicate: 'friend' , object: v('a') , stream: SortJoinStream , index: 'pos' }]; stub .withArgs('pos::friend::', 'pos::friend::'+upperBoundChar, sinon.match.func) .yields(null, 10); stub .withArgs('ops::marco::friend::', 'ops::marco::friend::'+upperBoundChar, sinon.match.func) .yields(null, 1); planner(query, function(err, result) { expect(result).to.eql(expected); done(); }); }); }); describe('without approximateSize', function() { beforeEach(function() { db = { db: { } }; planner = queryplanner(db, { joinAlgorithm: 'sort' }); }); it('should order two conditions based on their size', function(done) { query = [{ subject: 'matteo' , predicate: 'friend' , object: v('a') }, { subject: v('b') , predicate: 'friend' , object: v('c') }]; expected = [{ subject: 'matteo' , predicate: 'friend' , object: v('a') , stream: JoinStream }, { subject: v('b') , predicate: 'friend' , object: v('c') , stream: JoinStream }]; planner(query, function(err, result) { expect(result).to.eql(expected); done(); }); }); it('should order two conditions based on their size (bis)', function(done) { query = [{ subject: v('b') , predicate: 'friend' , object: v('c') }, { subject: 'matteo' , predicate: 'friend' , object: v('a') }]; expected = [{ subject: 'matteo' , predicate: 'friend' , object: v('a') , stream: JoinStream }, { subject: v('b') , predicate: 'friend' , object: v('c') , stream: JoinStream }]; planner(query, function(err, result) { expect(result).to.eql(expected); done(); }); }); }); });