From 7b9ebc4e8e09553715c9fd8f342c4bf0e58309ed Mon Sep 17 00:00:00 2001 From: Jack Wearden Date: Wed, 10 May 2017 05:32:08 -0700 Subject: [PATCH] Avoid multiple $nears in one query (#3798) Mongo has a hard limit on 1 $near operation per query. Restructuring to avoid SERVER-13732 should not invalidate a query by creating multiple $near operations. Additionally, queries with multiple $ors are now recursively handled, whereas before, ops at the top level would only have been pushed one level deeper. https://github.com/parse-community/parse-server/issues/3767 --- spec/DatabaseController.spec.js | 23 +++++++++++++++++++++++ src/Controllers/DatabaseController.js | 12 +++++++++++- 2 files changed, 34 insertions(+), 1 deletion(-) diff --git a/spec/DatabaseController.spec.js b/spec/DatabaseController.spec.js index 8479f5ad58..a402aef8f8 100644 --- a/spec/DatabaseController.spec.js +++ b/spec/DatabaseController.spec.js @@ -13,6 +13,29 @@ describe('DatabaseController', function() { done(); }); + it('should not restructure SERVER-13732 queries with $nears', (done) => { + var query = {$or: [{a: 1}, {b: 1}], c: {$nearSphere: {}}}; + validateQuery(query); + expect(query).toEqual({$or: [{a: 1}, {b: 1}], c: {$nearSphere: {}}}); + + query = {$or: [{a: 1}, {b: 1}], c: {$near: {}}}; + validateQuery(query); + expect(query).toEqual({$or: [{a: 1}, {b: 1}], c: {$near: {}}}); + + done(); + }); + + + it('should push refactored keys down a tree for SERVER-13732', (done) => { + var query = {a: 1, $or: [{$or: [{b: 1}, {b: 2}]}, + {$or: [{c: 1}, {c: 2}]}]}; + validateQuery(query); + expect(query).toEqual({$or: [{$or: [{b: 1, a: 1}, {b: 2, a: 1}]}, + {$or: [{c: 1, a: 1}, {c: 2, a: 1}]}]}); + + done(); + }); + it('should reject invalid queries', (done) => { expect(() => validateQuery({$or: {'a': 1}})).toThrow(); done(); diff --git a/src/Controllers/DatabaseController.js b/src/Controllers/DatabaseController.js index 99105682c2..2287543b10 100644 --- a/src/Controllers/DatabaseController.js +++ b/src/Controllers/DatabaseController.js @@ -69,17 +69,27 @@ const validateQuery = query => { * EG: {$or: [{a: 1}, {a: 2}], b: 2} * Becomes: {$or: [{a: 1, b: 2}, {a: 2, b: 2}]} * + * The only exceptions are $near and $nearSphere operators, which are + * constrained to only 1 operator per query. As a result, these ops + * remain at the top level + * * https://jira.mongodb.org/browse/SERVER-13732 + * https://github.com/parse-community/parse-server/issues/3767 */ Object.keys(query).forEach(key => { const noCollisions = !query.$or.some(subq => subq.hasOwnProperty(key)) - if (key != '$or' && noCollisions) { + let hasNears = false + if (query[key] != null && typeof query[key] == 'object') { + hasNears = ('$near' in query[key] || '$nearSphere' in query[key]) + } + if (key != '$or' && noCollisions && !hasNears) { query.$or.forEach(subquery => { subquery[key] = query[key]; }); delete query[key]; } }); + query.$or.forEach(validateQuery); } else { throw new Parse.Error(Parse.Error.INVALID_QUERY, 'Bad $or format - use an array value.'); }