From 9d6b56628ed21aebf7fff94cb7c0def0469c5991 Mon Sep 17 00:00:00 2001 From: Eric Hwang Date: Fri, 28 Jul 2017 13:16:58 -0700 Subject: [PATCH 1/4] Change Query.idMap to count ids, to be defensive against duplicate ids in query results (see https://github.com/share/sharedb-mongo/issues/55) --- lib/Model/Query.js | 12 +++++++++--- test/Model/query.js | 26 ++++++++++++++++++++++++++ 2 files changed, 35 insertions(+), 3 deletions(-) diff --git a/lib/Model/Query.js b/lib/Model/Query.js index f7c8b4824..585fc2f36 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,12 @@ 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] > 0) { + this.idMap[id]--; + } + if (this.idMap[id] === 0) { + 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 +317,8 @@ 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; + this.idMap[id]++; } }; Query.prototype._diffMapIds = function(ids) { 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']); + }); + }); }); From e4cc334e406caa2612e232f632a39a964815347f Mon Sep 17 00:00:00 2001 From: Eric Hwang Date: Fri, 28 Jul 2017 16:13:23 -0700 Subject: [PATCH 2/4] Switch query.idMap check to use "> 0" --- lib/Model/subscriptions.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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; } } From 34d63dc10714bcdc77991c73c11e1e4aba50d635 Mon Sep 17 00:00:00 2001 From: Eric Hwang Date: Fri, 28 Jul 2017 16:14:27 -0700 Subject: [PATCH 3/4] Query#_addMapIds: Consolidate idMap update to one line --- lib/Model/Query.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/Model/Query.js b/lib/Model/Query.js index 585fc2f36..f47039c7b 100644 --- a/lib/Model/Query.js +++ b/lib/Model/Query.js @@ -317,8 +317,7 @@ Query.prototype._removeMapIds = function(ids) { Query.prototype._addMapIds = function(ids) { for (var i = ids.length; i--;) { var id = ids[i]; - this.idMap[id] = this.idMap[id] || 0; - this.idMap[id]++; + this.idMap[id] = (this.idMap[id] || 0) + 1; } }; Query.prototype._diffMapIds = function(ids) { From 51673d7063d9d34516fe49dcfed3b263a9aa79e5 Mon Sep 17 00:00:00 2001 From: Eric Hwang Date: Fri, 28 Jul 2017 16:20:26 -0700 Subject: [PATCH 4/4] Query#_removeMapIds: Refactoring to remove a conditional check --- lib/Model/Query.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/lib/Model/Query.js b/lib/Model/Query.js index f47039c7b..679f4b280 100644 --- a/lib/Model/Query.js +++ b/lib/Model/Query.js @@ -293,10 +293,9 @@ Query.prototype._shareSubscribe = function(options, cb) { Query.prototype._removeMapIds = function(ids) { for (var i = ids.length; i--;) { var id = ids[i]; - if (this.idMap[id] > 0) { + if (this.idMap[id] > 1) { this.idMap[id]--; - } - if (this.idMap[id] === 0) { + } else { delete this.idMap[id]; } }