Skip to content

Commit 7c6228d

Browse files
authored
Merge pull request #3 from lever/fix/snapshot-queries
Handle projections, queries on Mongo doc properties (like _id) and fix comparisons to null
2 parents 4abe64b + 3606619 commit 7c6228d

File tree

4 files changed

+130
-13
lines changed

4 files changed

+130
-13
lines changed

.travis.yml

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
language: node_js
22
node_js:
3-
- 5
3+
- 9
4+
- 8
5+
- 6
46
- 4
5-
- 0.10
67
script: "npm run test-cover"
78
# Send coverage data to Coveralls
89
after_script: "cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js"

index.js

Lines changed: 38 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,14 @@
11
var Mingo = require('mingo');
22

3+
// Snapshot properties added to the root doc by `castToDoc()` in sharedb-mongo
4+
var MONGO_DOC_PROPERTIES = {
5+
'_id': 'id',
6+
'_v': 'v',
7+
'_type': 'type',
8+
'_m': 'm',
9+
'_o': 'o'
10+
};
11+
312
function extendMemoryDB(MemoryDB) {
413
function ShareDBMingo(options) {
514
if (!(this instanceof ShareDBMingo)) return new ShareDBMingo(options);
@@ -10,8 +19,11 @@ function extendMemoryDB(MemoryDB) {
1019

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

14-
var filtered = filter(snapshots, parsed.query);
24+
var filtered = snapshots.filter(function(snapshot) {
25+
return mingoQuery.test(snapshot);
26+
});
1527
if (parsed.sort) sort(filtered, parsed.sort);
1628
if (parsed.skip) filtered.splice(0, parsed.skip);
1729
if (parsed.limit) filtered = filtered.slice(0, parsed.limit);
@@ -23,11 +35,11 @@ function extendMemoryDB(MemoryDB) {
2335
};
2436

2537
ShareDBMingo.prototype.queryPollDoc = function(collection, id, query, options, callback) {
26-
var mingoQuery = new Mingo.Query(query);
38+
var mingoQuery = new Mingo.Query(castToSnapshotQuery(query));
2739
this.getSnapshot(collection, id, null, null, function(err, snapshot) {
2840
if (err) return callback(err);
2941
if (snapshot.data) {
30-
callback(null, mingoQuery.test(snapshot.data));
42+
callback(null, mingoQuery.test(snapshot));
3143
} else {
3244
callback(null, false);
3345
}
@@ -71,12 +83,29 @@ function extendMemoryDB(MemoryDB) {
7183
};
7284
}
7385

74-
// Support exact key match filters only
75-
function filter(snapshots, query) {
76-
var mingoQuery = new Mingo.Query(query);
77-
return snapshots.filter(function(snapshot) {
78-
return snapshot.data && mingoQuery.test(snapshot.data);
79-
});
86+
// Build a query object that mimics how the query would be executed if it were
87+
// made against snapshots persisted with `sharedb-mongo`
88+
// FIXME: This doesn't handle nested doc properties with dots like: {'_m.mtime': 12300}
89+
function castToSnapshotQuery(query) {
90+
var snapshotQuery = {};
91+
for (var property in query) {
92+
// Mongo doc property
93+
if (MONGO_DOC_PROPERTIES[property]) {
94+
snapshotQuery[MONGO_DOC_PROPERTIES[property]] = query[property];
95+
96+
// top-level boolean operator
97+
} else if (property[0] === '$' && Array.isArray(query[property])) {
98+
snapshotQuery[property] = [];
99+
for (var i = 0; i < query[property].length; i++) {
100+
snapshotQuery[property].push(castToSnapshotQuery(query[property][i]));
101+
}
102+
103+
// nested `data` document
104+
} else {
105+
snapshotQuery["data." + property] = query[property];
106+
}
107+
}
108+
return snapshotQuery;
80109
}
81110

82111
// Support sorting with the Mongo $orderby syntax

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
},
2626
"homepage": "https://github.com/share/sharedb-mingo-memory#readme",
2727
"dependencies": {
28-
"mingo": "^0.6.3",
28+
"mingo": "^2.2.0",
2929
"sharedb": "^1.0.0-beta"
3030
}
3131
}

test/query.js

Lines changed: 88 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,94 @@ module.exports = function() {
5050
});
5151
});
5252

53-
describe('top-level boolean operator', function(done) {
53+
describe('filtering on special Share properties', function() {
54+
// When sharedb-mongo persists a snapshot into Mongo, any properties
55+
// underneath `data` get "promoted" to top-level, and Share properties
56+
// get underscore-prefixed to avoid name conflicts, like `v` to `_v`.
57+
//
58+
// Query conditions don't undergo this transformation, so if you wanted
59+
// to filter on snapshot version, you'd query with `{_v: 12}`. These tests
60+
// check that sharedb-mingo-memory is consistent with sharedb-mongo for
61+
// queries like those that filter on non-data Share properties.
62+
var snapshots = [
63+
{type: 'json0', v: 1, data: {x: 1, y: 1}, id: "test1", m: {mtime: 1000}},
64+
{type: 'json0', v: 1, data: {x: 1, y: 2}, id: "test2", m: {mtime: 1001}},
65+
{type: 'json0', v: 1, data: {x: 2, y: 2}, id: "test3", m: {mtime: 1002}}
66+
];
67+
var snapshotsNoMeta = snapshots.map(function(snapshot) {
68+
var snapshotCopy = JSON.parse(JSON.stringify(snapshot));
69+
delete snapshotCopy.m;
70+
return snapshotCopy;
71+
});
72+
73+
beforeEach(function(done) {
74+
var db = this.db;
75+
async.each(snapshots, function(snapshot, cb) {
76+
db.commit('testcollection', snapshot.id, {v: 0, create: {}}, snapshot, null, cb);
77+
}, done);
78+
});
79+
80+
it('condition on Mongo _id (Share id)', function(done) {
81+
this.db.query('testcollection', {_id: 'test1'}, null, null, function(err, results, extra) {
82+
if (err) throw err;
83+
expect(results).eql([snapshotsNoMeta[0]]);
84+
done();
85+
});
86+
});
87+
88+
// The simpler query-casting approach doesn't handle queries that filter on
89+
// sub-properties of the Share metadata object. An alternative would be to
90+
// rewrite this module to cast Share snapshots to Mongo docs and vice versa,
91+
// the same way that sharedb-mongo does:
92+
// https://github.com/share/sharedb-mingo-memory/pull/3#pullrequestreview-99017385
93+
it.skip('condition on sub-property under Share metadata', function(done) {
94+
this.db.query('testcollection', {'_m.mtime': 1001}, null, null, function(err, results, extra) {
95+
if (err) throw err;
96+
expect(results).eql([snapshotsNoMeta[1]]);
97+
done();
98+
});
99+
});
100+
101+
it('condition on Mongo _id and Share data', function(done) {
102+
this.db.query('testcollection', {y: 2, _id: {$nin: ['test2']}}, null, null, function(err, results, extra) {
103+
if (err) throw err;
104+
expect(results).eql([snapshotsNoMeta[2]]);
105+
done();
106+
});
107+
});
108+
109+
it('top-level boolean operator', function(done) {
110+
this.db.query('testcollection', {$or: [{y: 1}, {_id: 'test2'}]}, null, null, function(err, results, extra) {
111+
if (err) throw err;
112+
expect(results).eql([snapshotsNoMeta[0], snapshotsNoMeta[1]]);
113+
done();
114+
});
115+
});
116+
});
117+
118+
it('filters with null condition', function(done) {
119+
var snapshots = [
120+
{type: 'json0', v: 1, data: {x: 1, y: 1}, id: "test1"},
121+
{type: 'json0', v: 1, data: {x: 1}, id: "test2"}, // y value intentionally omitted
122+
{type: 'json0', v: 1, data: {x: 2, y: 2}, id: "test3"}
123+
];
124+
var query = {y: null};
125+
126+
var db = this.db;
127+
async.each(snapshots, function(snapshot, cb) {
128+
db.commit('testcollection', snapshot.id, {v: 0, create: {}}, snapshot, null, cb);
129+
}, function(err) {
130+
if (err) return done(err);
131+
132+
db.query('testcollection', query, null, null, function(err, results, extra) {
133+
if (err) throw err;
134+
expect(results).eql([snapshots[1]]);
135+
done();
136+
});
137+
});
138+
});
139+
140+
describe('top-level boolean operator', function() {
54141
var snapshots = [
55142
{type: 'json0', v: 1, data: {x: 1, y: 1}, id: "test1"},
56143
{type: 'json0', v: 1, data: {x: 1, y: 2}, id: "test2"},

0 commit comments

Comments
 (0)