Fix issue on count with Geo constraints and mongo (issue #5285) (#5286)

* Add a tests that fails due to issue #5285

* Make test code much simpler

* Fix #5285 by rewriting query (replacing $nearSphere by $geoWithin)

All credit goes to @dplewis !

* move logic to transform
This commit is contained in:
Julien Quéré
2019-04-25 03:28:13 +02:00
committed by Diamond Lewis
parent 7e130c5421
commit 7122ca05c4
3 changed files with 73 additions and 16 deletions

View File

@@ -11,9 +11,8 @@ describe('Parse.GeoPoint testing', () => {
obj.set('location', point); obj.set('location', point);
obj.set('name', 'Ferndale'); obj.set('name', 'Ferndale');
await obj.save(); await obj.save();
const results = await new Parse.Query(TestObject).find(); const result = await new Parse.Query(TestObject).get(obj.id);
equal(results.length, 1); const pointAgain = result.get('location');
const pointAgain = results[0].get('location');
ok(pointAgain); ok(pointAgain);
equal(pointAgain.latitude, 44.0); equal(pointAgain.latitude, 44.0);
equal(pointAgain.longitude, -11.0); equal(pointAgain.longitude, -11.0);
@@ -727,4 +726,49 @@ describe('Parse.GeoPoint testing', () => {
done(); done();
}); });
}); });
it('withinKilometers supports count', async () => {
const inside = new Parse.GeoPoint(10, 10);
const outside = new Parse.GeoPoint(20, 20);
const obj1 = new Parse.Object('TestObject', { location: inside });
const obj2 = new Parse.Object('TestObject', { location: outside });
await Parse.Object.saveAll([obj1, obj2]);
const q = new Parse.Query(TestObject).withinKilometers(
'location',
inside,
5
);
const count = await q.count();
equal(count, 1);
});
it('withinKilometers complex supports count', async () => {
const inside = new Parse.GeoPoint(10, 10);
const middle = new Parse.GeoPoint(20, 20);
const outside = new Parse.GeoPoint(30, 30);
const obj1 = new Parse.Object('TestObject', { location: inside });
const obj2 = new Parse.Object('TestObject', { location: middle });
const obj3 = new Parse.Object('TestObject', { location: outside });
await Parse.Object.saveAll([obj1, obj2, obj3]);
const q1 = new Parse.Query(TestObject).withinKilometers(
'location',
inside,
5
);
const q2 = new Parse.Query(TestObject).withinKilometers(
'location',
middle,
5
);
const query = Parse.Query.or(q1, q2);
const count = await query.count();
equal(count, 2);
});
}); });

View File

@@ -690,7 +690,7 @@ export class MongoStorageAdapter implements StorageAdapter {
readPreference = this._parseReadPreference(readPreference); readPreference = this._parseReadPreference(readPreference);
return this._adaptiveCollection(className) return this._adaptiveCollection(className)
.then(collection => .then(collection =>
collection.count(transformWhere(className, query, schema), { collection.count(transformWhere(className, query, schema, true), {
maxTimeMS: this._maxTimeMS, maxTimeMS: this._maxTimeMS,
readPreference, readPreference,
}) })

View File

@@ -224,7 +224,7 @@ const valueAsDate = value => {
return false; return false;
}; };
function transformQueryKeyValue(className, key, value, schema) { function transformQueryKeyValue(className, key, value, schema, count = false) {
switch (key) { switch (key) {
case 'createdAt': case 'createdAt':
if (valueAsDate(value)) { if (valueAsDate(value)) {
@@ -293,7 +293,7 @@ function transformQueryKeyValue(className, key, value, schema) {
return { return {
key: key, key: key,
value: value.map(subQuery => value: value.map(subQuery =>
transformWhere(className, subQuery, schema) transformWhere(className, subQuery, schema, count)
), ),
}; };
case 'lastUsed': case 'lastUsed':
@@ -330,7 +330,7 @@ function transformQueryKeyValue(className, key, value, schema) {
} }
// Handle query constraints // Handle query constraints
const transformedConstraint = transformConstraint(value, field); const transformedConstraint = transformConstraint(value, field, count);
if (transformedConstraint !== CannotTransform) { if (transformedConstraint !== CannotTransform) {
if (transformedConstraint.$text) { if (transformedConstraint.$text) {
return { key: '$text', value: transformedConstraint.$text }; return { key: '$text', value: transformedConstraint.$text };
@@ -359,14 +359,15 @@ function transformQueryKeyValue(className, key, value, schema) {
// Main exposed method to help run queries. // Main exposed method to help run queries.
// restWhere is the "where" clause in REST API form. // restWhere is the "where" clause in REST API form.
// Returns the mongo form of the query. // Returns the mongo form of the query.
function transformWhere(className, restWhere, schema) { function transformWhere(className, restWhere, schema, count = false) {
const mongoWhere = {}; const mongoWhere = {};
for (const restKey in restWhere) { for (const restKey in restWhere) {
const out = transformQueryKeyValue( const out = transformQueryKeyValue(
className, className,
restKey, restKey,
restWhere[restKey], restWhere[restKey],
schema schema,
count
); );
mongoWhere[out.key] = out.value; mongoWhere[out.key] = out.value;
} }
@@ -816,7 +817,7 @@ function relativeTimeToDate(text, now = new Date()) {
// If it is not a valid constraint but it could be a valid something // If it is not a valid constraint but it could be a valid something
// else, return CannotTransform. // else, return CannotTransform.
// inArray is whether this is an array field. // inArray is whether this is an array field.
function transformConstraint(constraint, field) { function transformConstraint(constraint, field, count = false) {
const inArray = field && field.type && field.type === 'Array'; const inArray = field && field.type && field.type === 'Array';
if (typeof constraint !== 'object' || !constraint) { if (typeof constraint !== 'object' || !constraint) {
return CannotTransform; return CannotTransform;
@@ -1002,15 +1003,27 @@ function transformConstraint(constraint, field) {
} }
break; break;
} }
case '$nearSphere': case '$nearSphere': {
var point = constraint[key]; const point = constraint[key];
answer[key] = [point.longitude, point.latitude]; if (count) {
answer.$geoWithin = {
$centerSphere: [
[point.longitude, point.latitude],
constraint.$maxDistance,
],
};
} else {
answer[key] = [point.longitude, point.latitude];
}
break; break;
}
case '$maxDistance': case '$maxDistance': {
if (count) {
break;
}
answer[key] = constraint[key]; answer[key] = constraint[key];
break; break;
}
// The SDKs don't seem to use these but they are documented in the // The SDKs don't seem to use these but they are documented in the
// REST API docs. // REST API docs.
case '$maxDistanceInRadians': case '$maxDistanceInRadians':