diff --git a/lib/Model/Query.js b/lib/Model/Query.js index f7c8b4824..679f4b280 100644 --- a/lib/Model/Query.js +++ b/lib/Model/Query.js @@ -158,7 +158,7 @@ function Query(model, collectionName, expression, options) { // because otherwise maybeUnload would be looping through the entire results // set of each query on the same collection for every doc checked // - // Map of id -> true + // Map of id -> count of ids this.idMap = {}; } @@ -293,7 +293,11 @@ Query.prototype._shareSubscribe = function(options, cb) { Query.prototype._removeMapIds = function(ids) { for (var i = ids.length; i--;) { var id = ids[i]; - delete this.idMap[id]; + if (this.idMap[id] > 1) { + this.idMap[id]--; + } else { + delete this.idMap[id]; + } } // Technically this isn't quite right and we might not wait the full unload // delay if someone else calls maybeUnload for the same doc id. However, @@ -312,7 +316,7 @@ Query.prototype._removeMapIds = function(ids) { Query.prototype._addMapIds = function(ids) { for (var i = ids.length; i--;) { var id = ids[i]; - this.idMap[id] = true; + this.idMap[id] = (this.idMap[id] || 0) + 1; } }; Query.prototype._diffMapIds = function(ids) { diff --git a/lib/Model/subscriptions.js b/lib/Model/subscriptions.js index b7a94346c..e9d9147d7 100644 --- a/lib/Model/subscriptions.js +++ b/lib/Model/subscriptions.js @@ -191,7 +191,7 @@ Model.prototype._hasDocReferences = function(collectionName, id) { for (var hash in queries) { var query = queries[hash]; if (!query.subscribeCount && !query.fetchCount) continue; - if (query.idMap[id]) return true; + if (query.idMap[id] > 0) return true; } } diff --git a/test/Model/query.js b/test/Model/query.js index e8d69c03c..29cdc54c0 100644 --- a/test/Model/query.js +++ b/test/Model/query.js @@ -1,4 +1,5 @@ var expect = require('../util').expect; +var racer = require('../../lib'); var Model = require('../../lib/Model'); describe('query', function() { @@ -14,4 +15,29 @@ describe('query', function() { expect(query.expression).eql([{x: null}, {x: {y: null, z: 0}}]); }); }); + + describe('idMap', function() { + beforeEach('create in-memory backend and model', function() { + this.backend = racer.createBackend(); + this.model = this.backend.createModel(); + }); + it('handles insert and remove of a duplicate id', function() { + var query = this.model.query('myCollection', {key: 'myVal'}); + query.subscribe(); + query.shareQuery.emit('insert', [ + {id: 'a'}, + {id: 'b'}, + {id: 'c'}, + ], 0); + // Add and immediately remove a duplicate id. + query.shareQuery.emit('insert', [ + {id: 'a'}, + ], 3); + query.shareQuery.emit('remove', [ + {id: 'a'}, + ], 3); + // 'a' is still present once in the results, should still be in the map. + expect(query.idMap).to.only.have.keys(['a', 'b', 'c']); + }); + }); });