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
This commit is contained in:
Jack Wearden
2017-05-10 05:32:08 -07:00
committed by Florent Vilmart
parent 64e6f40779
commit 7b9ebc4e8e
2 changed files with 34 additions and 1 deletions

View File

@@ -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();

View File

@@ -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.');
}