Revert "handle deep geo-near queries (#1216)"

This reverts commit 6f88cf1930.
This commit is contained in:
Sakib Hasan 2017-04-06 17:45:04 -04:00 committed by GitHub
parent b7e2f2a649
commit befea83a76
3 changed files with 65 additions and 242 deletions

View File

@ -12,109 +12,82 @@ var assert = require('assert');
*/ */
exports.nearFilter = function nearFilter(where) { exports.nearFilter = function nearFilter(where) {
function nearSearch(clause, parentKeys) { var result = false;
if (typeof clause !== 'object') {
return false;
}
parentKeys = parentKeys || [];
Object.keys(clause).forEach(function(clauseKey) { if (where && typeof where === 'object') {
if (Array.isArray(clause[clauseKey])) { Object.keys(where).forEach(function(key) {
clause[clauseKey].forEach(function(el, index) { var ex = where[key];
var ret = nearSearch(el, parentKeys.concat(clauseKey).concat(index));
if (ret) return ret; if (ex && ex.near) {
}); result = {
} else { near: ex.near,
if (clause[clauseKey].hasOwnProperty('near')) { maxDistance: ex.maxDistance,
var result = clause[clauseKey]; minDistance: ex.minDistance,
nearResults.push({ unit: ex.unit,
near: result.near, key: key,
maxDistance: result.maxDistance, };
minDistance: result.minDistance,
unit: result.unit,
// If key is at root, define a single string, otherwise append it to the full path array
mongoKey: parentKeys.length ? parentKeys.concat(clauseKey) : clauseKey,
key: clauseKey,
});
}
} }
}); });
} }
var nearResults = [];
nearSearch(where);
return (!nearResults.length ? false : nearResults); return result;
}; };
/*! /*!
* Filter a set of results using the given filters returned by `nearFilter()`. * Filter a set of objects using the given `nearFilter`.
* Can support multiple locations, but will include results from all of them.
*
* WARNING: "or" operator with GeoPoint does not work as expected, eg:
* {where: {or: [{location: {near: (29,-90)}},{name:'Sean'}]}}
* Will actually work as if you had used "and". This is because geo filtering
* takes place outside of the SQL query, so the result set of "name = Sean" is
* returned by the database, and then the location filtering happens in the app
* logic. So the "near" operator is always an "and" of the SQL filters, and "or"
* of other GeoPoint filters.
*
* Additionally, since this step occurs after the SQL result set is returned,
* if using GeoPoints with pagination the result set may be smaller than the
* page size. The page size is enforced at the DB level, and then we may
* remove results at the Geo-app level. If we "limit: 25", but 4 of those results
* do not have a matching geopoint field, the request will only return 21 results.
* This may make it erroneously look like a given page is the end of the result set.
*/ */
exports.filter = function(rawResults, filters) { exports.filter = function(arr, filter) {
var origin = filter.near;
var max = filter.maxDistance > 0 ? filter.maxDistance : false;
var min = filter.minDistance > 0 ? filter.minDistance : false;
var unit = filter.unit;
var key = filter.key;
// create distance index
var distances = {}; var distances = {};
var results = []; var result = [];
filters.forEach(function(filter) { arr.forEach(function(obj) {
var origin = filter.near; var loc = obj[key];
var max = filter.maxDistance > 0 ? filter.maxDistance : false;
var min = filter.minDistance > 0 ? filter.minDistance : false;
var unit = filter.unit;
var key = filter.key;
// create distance index // filter out objects without locations
rawResults.forEach(function(result) { if (!loc) return;
var loc = result[key];
// filter out results without locations if (!(loc instanceof GeoPoint)) {
if (!loc) return; loc = GeoPoint(loc);
}
if (!(loc instanceof GeoPoint)) loc = GeoPoint(loc); if (typeof loc.lat !== 'number') return;
if (typeof loc.lng !== 'number') return;
if (typeof loc.lat !== 'number') return; var d = GeoPoint.distanceBetween(origin, loc, {type: unit});
if (typeof loc.lng !== 'number') return;
var d = GeoPoint.distanceBetween(origin, loc, {type: unit}); if (min && d < min) {
return;
// filter result if distance is either < minDistance or > maxDistance }
if ((min && d < min) || (max && d > max)) return; if (max && d > max) {
// dont add
distances[result.id] = d; } else {
results.push(result); distances[obj.id] = d;
}); result.push(obj);
}
results.sort(function(resA, resB) {
var a = resA[key];
var b = resB[key];
if (a && b) {
var da = distances[resA.id];
var db = distances[resB.id];
if (db === da) return 0;
return da > db ? 1 : -1;
} else {
return 0;
}
});
}); });
return results; return result.sort(function(objA, objB) {
var a = objA[key];
var b = objB[key];
if (a && b) {
var da = distances[objA.id];
var db = distances[objB.id];
if (db === da) return 0;
return da > db ? 1 : -1;
} else {
return 0;
}
});
}; };
exports.GeoPoint = GeoPoint; exports.GeoPoint = GeoPoint;
@ -312,3 +285,4 @@ function geoDistance(x1, y1, x2, y2, options) {
return 2 * Math.asin(f) * EARTH_RADIUS[type]; return 2 * Math.asin(f) * EARTH_RADIUS[type];
} }

View File

@ -38,7 +38,6 @@ describe('basic-querying', function() {
name: String, name: String,
}, },
], ],
addressLoc: {type: 'GeoPoint'},
}); });
db.automigrate(done); db.automigrate(done);
@ -554,147 +553,6 @@ describe('basic-querying', function() {
}); });
}); });
describe('geo queries', function() {
describe('near filter', function() {
it('supports a basic "near" query', function(done) {
User.find({
where: {
addressLoc: {
near: '29.9,-90.07',
},
},
}, function(err, users) {
if (err) done(err);
users.should.have.property('length', 3);
users[0].name.should.equal('John Lennon');
users[0].addressLoc.should.not.be.null();
done();
});
});
it('supports "near" inside a coumpound query with "and"', function(done) {
User.find({
where: {
and: [
{
addressLoc: {
near: '29.9,-90.07',
},
},
{
vip: true,
},
],
},
}, function(err, users) {
if (err) done(err);
users.should.have.property('length', 2);
users[0].name.should.equal('John Lennon');
users[0].addressLoc.should.not.be.null();
users[0].vip.should.be.true();
done();
});
});
it('supports "near" inside a complex coumpound query with multiple "and"', function(done) {
User.find({
where: {
and: [
{
and: [
{
addressLoc: {
near: '29.9,-90.07',
},
},
{
order: 2,
},
],
},
{
vip: true,
},
],
},
}, function(err, users) {
if (err) done(err);
users.should.have.property('length', 1);
users[0].name.should.equal('John Lennon');
users[0].addressLoc.should.not.be.null();
users[0].vip.should.be.true();
users[0].order.should.equal(2);
done();
});
});
it('supports multiple "near" queries with "or"', function(done) {
User.find({
where: {
or: [
{
addressLoc: {
near: '29.9,-90.04',
maxDistance: 300,
},
},
{
addressLoc: {
near: '22.97,-88.03',
maxDistance: 50,
},
},
],
},
}, function(err, users) {
if (err) done(err);
users.should.have.property('length', 2);
users[0].addressLoc.should.not.be.null();
users[0].name.should.equal('Paul McCartney');
users[1].addressLoc.should.not.equal(null);
users[1].name.should.equal('John Lennon');
done();
});
});
it('supports multiple "near" queries with "or" ' +
'inside a coumpound query with "and"', function(done) {
User.find({
where: {
and: [
{
or: [
{
addressLoc: {
near: '29.9,-90.04',
maxDistance: 300,
},
},
{
addressLoc: {
near: '22.7,-89.03',
maxDistance: 50,
},
},
],
},
{
vip: true,
},
],
},
}, function(err, users) {
if (err) done(err);
users.should.have.property('length', 1);
users[0].addressLoc.should.not.be.null();
users[0].name.should.equal('John Lennon');
users[0].vip.should.be.true();
done();
});
});
});
});
it('should only include fields as specified', function(done) { it('should only include fields as specified', function(done) {
var remaining = 0; var remaining = 0;
@ -732,9 +590,8 @@ describe('basic-querying', function() {
} }
sample({name: true}).expect(['name']); sample({name: true}).expect(['name']);
sample({name: false}).expect([ sample({name: false}).expect(['id', 'seq', 'email', 'role', 'order', 'birthday', 'vip',
'id', 'seq', 'email', 'role', 'order', 'birthday', 'vip', 'address', 'friends', 'addressLoc', 'address', 'friends']);
]);
sample({name: false, id: true}).expect(['id']); sample({name: false, id: true}).expect(['id']);
sample({id: true}).expect(['id']); sample({id: true}).expect(['id']);
sample('id').expect(['id']); sample('id').expect(['id']);
@ -1148,7 +1005,6 @@ function seed(done) {
{name: 'George Harrison'}, {name: 'George Harrison'},
{name: 'Ringo Starr'}, {name: 'Ringo Starr'},
], ],
addressLoc: {lat: 29.97, lng: -90.03},
}, },
{ {
seq: 1, seq: 1,
@ -1169,15 +1025,8 @@ function seed(done) {
{name: 'George Harrison'}, {name: 'George Harrison'},
{name: 'Ringo Starr'}, {name: 'Ringo Starr'},
], ],
addressLoc: {lat: 22.97, lng: -88.03},
},
{
seq: 2,
name: 'George Harrison',
order: 5,
vip: false,
addressLoc: {lat: 22.7, lng: -89.03},
}, },
{seq: 2, name: 'George Harrison', order: 5, vip: false},
{seq: 3, name: 'Ringo Starr', order: 6, vip: false}, {seq: 3, name: 'Ringo Starr', order: 6, vip: false},
{seq: 4, name: 'Pete Best', order: 4}, {seq: 4, name: 'Pete Best', order: 4},
{seq: 5, name: 'Stuart Sutcliffe', order: 3, vip: true}, {seq: 5, name: 'Stuart Sutcliffe', order: 3, vip: true},

View File

@ -147,8 +147,8 @@ describe('GeoPoint', function() {
}, },
}; };
var filter = nearFilter(where); var filter = nearFilter(where);
filter[0].key.should.equal('location'); filter.key.should.equal('location');
filter[0].should.have.properties({ filter.should.have.properties({
key: 'location', key: 'location',
near: { near: {
lat: 40.77492964101182, lat: 40.77492964101182,
@ -187,7 +187,7 @@ describe('GeoPoint', function() {
lng: 121.483687, lng: 121.483687,
}, },
}]; }];
var filter = [{ var filter = {
key: 'location', key: 'location',
near: { near: {
lat: 30.278562, lat: 30.278562,
@ -195,7 +195,7 @@ describe('GeoPoint', function() {
}, },
unit: 'meters', unit: 'meters',
minDistance: 10000, minDistance: 10000,
}]; };
var results = geoFilter(points, filter); var results = geoFilter(points, filter);
results.length.should.be.equal(3); results.length.should.be.equal(3);
}); });