Skip to content

Handle projections, queries on Mongo doc properties (like _id) and fix comparisons to null #3

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 11 commits into from
Feb 27, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
language: node_js
node_js:
- 5
- 9
- 8
- 6
- 4
- 0.10
script: "npm run test-cover"
# Send coverage data to Coveralls
after_script: "cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js"
47 changes: 38 additions & 9 deletions index.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,14 @@
var Mingo = require('mingo');

// Snapshot properties added to the root doc by `castToDoc()` in sharedb-mongo
var MONGO_DOC_PROPERTIES = {
'_id': 'id',
'_v': 'v',
'_type': 'type',
'_m': 'm',
'_o': 'o'
};

function extendMemoryDB(MemoryDB) {
function ShareDBMingo(options) {
if (!(this instanceof ShareDBMingo)) return new ShareDBMingo(options);
Expand All @@ -10,8 +19,11 @@ function extendMemoryDB(MemoryDB) {

ShareDBMingo.prototype._querySync = function(snapshots, query, options) {
var parsed = parseQuery(query);
var mingoQuery = new Mingo.Query(castToSnapshotQuery(parsed.query));

var filtered = filter(snapshots, parsed.query);
var filtered = snapshots.filter(function(snapshot) {
return mingoQuery.test(snapshot);
});
if (parsed.sort) sort(filtered, parsed.sort);
if (parsed.skip) filtered.splice(0, parsed.skip);
if (parsed.limit) filtered = filtered.slice(0, parsed.limit);
Expand All @@ -23,11 +35,11 @@ function extendMemoryDB(MemoryDB) {
};

ShareDBMingo.prototype.queryPollDoc = function(collection, id, query, options, callback) {
var mingoQuery = new Mingo.Query(query);
var mingoQuery = new Mingo.Query(castToSnapshotQuery(query));
this.getSnapshot(collection, id, null, null, function(err, snapshot) {
if (err) return callback(err);
if (snapshot.data) {
callback(null, mingoQuery.test(snapshot.data));
callback(null, mingoQuery.test(snapshot));
} else {
callback(null, false);
}
Expand Down Expand Up @@ -71,12 +83,29 @@ function extendMemoryDB(MemoryDB) {
};
}

// Support exact key match filters only
function filter(snapshots, query) {
var mingoQuery = new Mingo.Query(query);
return snapshots.filter(function(snapshot) {
return snapshot.data && mingoQuery.test(snapshot.data);
});
// Build a query object that mimics how the query would be executed if it were
// made against snapshots persisted with `sharedb-mongo`
// FIXME: This doesn't handle nested doc properties with dots like: {'_m.mtime': 12300}
function castToSnapshotQuery(query) {
var snapshotQuery = {};
for (var property in query) {
// Mongo doc property
if (MONGO_DOC_PROPERTIES[property]) {
snapshotQuery[MONGO_DOC_PROPERTIES[property]] = query[property];

// top-level boolean operator
} else if (property[0] === '$' && Array.isArray(query[property])) {
snapshotQuery[property] = [];
for (var i = 0; i < query[property].length; i++) {
snapshotQuery[property].push(castToSnapshotQuery(query[property][i]));
}

// nested `data` document
} else {
snapshotQuery["data." + property] = query[property];
}
}
return snapshotQuery;
}

// Support sorting with the Mongo $orderby syntax
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
},
"homepage": "https://github.com/share/sharedb-mingo-memory#readme",
"dependencies": {
"mingo": "^0.6.3",
"mingo": "^2.2.0",
"sharedb": "^1.0.0-beta"
}
}
89 changes: 88 additions & 1 deletion test/query.js
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,94 @@ module.exports = function() {
});
});

describe('top-level boolean operator', function(done) {
describe('filtering on special Share properties', function() {
// When sharedb-mongo persists a snapshot into Mongo, any properties
// underneath `data` get "promoted" to top-level, and Share properties
// get underscore-prefixed to avoid name conflicts, like `v` to `_v`.
//
// Query conditions don't undergo this transformation, so if you wanted
// to filter on snapshot version, you'd query with `{_v: 12}`. These tests
// check that sharedb-mingo-memory is consistent with sharedb-mongo for
// queries like those that filter on non-data Share properties.
var snapshots = [
{type: 'json0', v: 1, data: {x: 1, y: 1}, id: "test1", m: {mtime: 1000}},
{type: 'json0', v: 1, data: {x: 1, y: 2}, id: "test2", m: {mtime: 1001}},
{type: 'json0', v: 1, data: {x: 2, y: 2}, id: "test3", m: {mtime: 1002}}
];
var snapshotsNoMeta = snapshots.map(function(snapshot) {
var snapshotCopy = JSON.parse(JSON.stringify(snapshot));
delete snapshotCopy.m;
return snapshotCopy;
});

beforeEach(function(done) {
var db = this.db;
async.each(snapshots, function(snapshot, cb) {
db.commit('testcollection', snapshot.id, {v: 0, create: {}}, snapshot, null, cb);
}, done);
});

it('condition on Mongo _id (Share id)', function(done) {
this.db.query('testcollection', {_id: 'test1'}, null, null, function(err, results, extra) {
if (err) throw err;
expect(results).eql([snapshotsNoMeta[0]]);
done();
});
});

// The simpler query-casting approach doesn't handle queries that filter on
// sub-properties of the Share metadata object. An alternative would be to
// rewrite this module to cast Share snapshots to Mongo docs and vice versa,
// the same way that sharedb-mongo does:
// https://github.com/share/sharedb-mingo-memory/pull/3#pullrequestreview-99017385
it.skip('condition on sub-property under Share metadata', function(done) {
this.db.query('testcollection', {'_m.mtime': 1001}, null, null, function(err, results, extra) {
if (err) throw err;
expect(results).eql([snapshotsNoMeta[1]]);
done();
});
});

it('condition on Mongo _id and Share data', function(done) {
this.db.query('testcollection', {y: 2, _id: {$nin: ['test2']}}, null, null, function(err, results, extra) {
if (err) throw err;
expect(results).eql([snapshotsNoMeta[2]]);
done();
});
});

it('top-level boolean operator', function(done) {
this.db.query('testcollection', {$or: [{y: 1}, {_id: 'test2'}]}, null, null, function(err, results, extra) {
if (err) throw err;
expect(results).eql([snapshotsNoMeta[0], snapshotsNoMeta[1]]);
done();
});
});
});

it('filters with null condition', function(done) {
var snapshots = [
{type: 'json0', v: 1, data: {x: 1, y: 1}, id: "test1"},
{type: 'json0', v: 1, data: {x: 1}, id: "test2"}, // y value intentionally omitted
{type: 'json0', v: 1, data: {x: 2, y: 2}, id: "test3"}
];
var query = {y: null};

var db = this.db;
async.each(snapshots, function(snapshot, cb) {
db.commit('testcollection', snapshot.id, {v: 0, create: {}}, snapshot, null, cb);
}, function(err) {
if (err) return done(err);

db.query('testcollection', query, null, null, function(err, results, extra) {
if (err) throw err;
expect(results).eql([snapshots[1]]);
done();
});
});
});

describe('top-level boolean operator', function() {
var snapshots = [
{type: 'json0', v: 1, data: {x: 1, y: 1}, id: "test1"},
{type: 'json0', v: 1, data: {x: 1, y: 2}, id: "test2"},
Expand Down