From c337032412c4a8087f495e12df03aa98e795db0a Mon Sep 17 00:00:00 2001 From: Mads Bjerre Date: Fri, 28 Jul 2017 13:04:52 +0200 Subject: [PATCH 1/4] Add support for withinCenterSphere queries commit fc79959846a59a38009ca7babeb3e0e9fd989441 Author: Mads Bjerre Date: Fri Jul 28 13:02:32 2017 +0200 Add tests for withinCenterSphere queries commit 57d3216895df6decc8c3a50a9aca5ccf7b545d9c Author: Mads Bjerre Date: Fri Jul 28 13:02:12 2017 +0200 Whitespace commit e5f7249488b8e8a7b1ec9e292febd944c8f734e2 Author: Mads Bjerre Date: Thu Jul 27 17:10:11 2017 +0200 Add $centerSphere shape to $geoWithin query --- spec/ParseGeoPoint.spec.js | 42 +++++++++++ src/Adapters/Storage/Mongo/MongoTransform.js | 71 +++++++++++++------ .../Postgres/PostgresStorageAdapter.js | 10 +-- src/vendor/mongodbUrl.js | 4 +- 4 files changed, 99 insertions(+), 28 deletions(-) diff --git a/spec/ParseGeoPoint.spec.js b/spec/ParseGeoPoint.spec.js index f2f8201e98..d6e85d2abc 100644 --- a/spec/ParseGeoPoint.spec.js +++ b/spec/ParseGeoPoint.spec.js @@ -628,4 +628,46 @@ describe('Parse.GeoPoint testing', () => { done(); }); }); + + it('works with withinCenterSphereKilometers queries', (done) => { + makeSomeGeoPoints(function() { + var sfo = new Parse.GeoPoint(37.6189722, -122.3748889); + var query = new Parse.Query(TestObject); + query.withinCenterSphereKilometers('location', sfo, 2000); + query.find({ + success: function(results) { + equal(results.length, 2); + done(); + } + }); + }); + }); + + it('works with withinCenterSphereMiles queries', (done) => { + makeSomeGeoPoints(function() { + var sfo = new Parse.GeoPoint(37.6189722, -122.3748889); + var query = new Parse.Query(TestObject); + query.withinCenterSphereMiles('location', sfo, 1243); + query.find({ + success: function(results) { + equal(results.length, 2); + done(); + } + }); + }); + }); + + it('works with withinCenterSphereRadians queries', (done) => { + makeSomeGeoPoints(function() { + var sfo = new Parse.GeoPoint(37.6189722, -122.3748889); + var query = new Parse.Query(TestObject); + query.withinCenterSphereRadians('location', sfo, 0.313922461); + query.find({ + success: function(results) { + equal(results.length, 2); + done(); + } + }); + }); + }); }); diff --git a/src/Adapters/Storage/Mongo/MongoTransform.js b/src/Adapters/Storage/Mongo/MongoTransform.js index f46c310e23..0ee1470335 100644 --- a/src/Adapters/Storage/Mongo/MongoTransform.js +++ b/src/Adapters/Storage/Mongo/MongoTransform.js @@ -669,30 +669,59 @@ function transformConstraint(constraint, inArray) { break; case '$geoWithin': { - const polygon = constraint[key]['$polygon']; - if (!(polygon instanceof Array)) { - throw new Parse.Error( - Parse.Error.INVALID_JSON, - 'bad $geoWithin value; $polygon should contain at least 3 GeoPoints' - ); - } - if (polygon.length < 3) { - throw new Parse.Error( - Parse.Error.INVALID_JSON, - 'bad $geoWithin value; $polygon should contain at least 3 GeoPoints' - ); + const query = Object.keys(constraint[key])[0]; + const shape = constraint[key][query]; + switch(query) { + case '$polygon': { + if (!(shape instanceof Array)) { + throw new Parse.Error( + Parse.Error.INVALID_JSON, + 'bad $geoWithin value; $polygon should contain at least 3 GeoPoints' + ); + } + if (shape.length < 3) { + throw new Parse.Error( + Parse.Error.INVALID_JSON, + 'bad $geoWithin value; $polygon should contain at least 3 GeoPoints' + ); + } + const points = shape.map((point) => { + if (!GeoPointCoder.isValidJSON(point)) { + throw new Parse.Error(Parse.Error.INVALID_JSON, 'bad $geoWithin value'); + } else { + Parse.GeoPoint._validate(point.latitude, point.longitude); + } + return [point.longitude, point.latitude]; + }); + answer[key] = { + '$polygon': points + }; + break; } - const points = polygon.map((point) => { - if (!GeoPointCoder.isValidJSON(point)) { - throw new Parse.Error(Parse.Error.INVALID_JSON, 'bad $geoWithin value'); + + case '$centerSphere': { + if (!(shape instanceof Array) || shape.length != 2) { + throw new Parse.Error( + Parse.Error.INVALID_JSON, + 'bad $geoWithin value; $centerSphere malformatted' + ); + } + const centerPoint = shape[0] + const radius = shape[1] + if (!GeoPointCoder.isValidJSON(centerPoint)) { + throw new Parse.Error(Parse.Error.INVALID_JSON, 'bad $geoWithin value; centerPoint malformatted'); } else { - Parse.GeoPoint._validate(point.latitude, point.longitude); + Parse.GeoPoint._validate(centerPoint.latitude, centerPoint.longitude) } - return [point.longitude, point.latitude]; - }); - answer[key] = { - '$polygon': points - }; + answer[key] = { + '$centerSphere': [ + [centerPoint.longitude, centerPoint.latitude], + radius + ] + }; + break; + } + } break; } case '$geoIntersects': { diff --git a/src/Adapters/Storage/Postgres/PostgresStorageAdapter.js b/src/Adapters/Storage/Postgres/PostgresStorageAdapter.js index 6cdac7ad79..6ccab203f1 100644 --- a/src/Adapters/Storage/Postgres/PostgresStorageAdapter.js +++ b/src/Adapters/Storage/Postgres/PostgresStorageAdapter.js @@ -1452,11 +1452,11 @@ function literalizeRegexPart(s) { // remove all instances of \Q and \E from the remaining text & escape single quotes return ( s.replace(/([^\\])(\\E)/, '$1') - .replace(/([^\\])(\\Q)/, '$1') - .replace(/^\\E/, '') - .replace(/^\\Q/, '') - .replace(/([^'])'/, `$1''`) - .replace(/^'([^'])/, `''$1`) + .replace(/([^\\])(\\Q)/, '$1') + .replace(/^\\E/, '') + .replace(/^\\Q/, '') + .replace(/([^'])'/, `$1''`) + .replace(/^'([^'])/, `''$1`) ); } diff --git a/src/vendor/mongodbUrl.js b/src/vendor/mongodbUrl.js index 1616c3dfc0..4e3689f0c3 100644 --- a/src/vendor/mongodbUrl.js +++ b/src/vendor/mongodbUrl.js @@ -255,8 +255,8 @@ Url.prototype.parse = function(url, parseQueryString, slashesDenoteHost) { hostEnd = i; break; case 64: // '@' - // At this point, either we have an explicit point where the - // auth portion cannot go past, or the last @ char is the decider. + // At this point, either we have an explicit point where the + // auth portion cannot go past, or the last @ char is the decider. atSign = i; nonHost = -1; break; From 9bb26a3c01205e6228a53bce89e694b05416fd42 Mon Sep 17 00:00:00 2001 From: Mads Bjerre Date: Fri, 28 Jul 2017 15:21:04 +0200 Subject: [PATCH 2/4] Fix whitespace local testing and remote testing apparently has different settings for indentations --- .../Storage/Postgres/PostgresStorageAdapter.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Adapters/Storage/Postgres/PostgresStorageAdapter.js b/src/Adapters/Storage/Postgres/PostgresStorageAdapter.js index 6ccab203f1..6cdac7ad79 100644 --- a/src/Adapters/Storage/Postgres/PostgresStorageAdapter.js +++ b/src/Adapters/Storage/Postgres/PostgresStorageAdapter.js @@ -1452,11 +1452,11 @@ function literalizeRegexPart(s) { // remove all instances of \Q and \E from the remaining text & escape single quotes return ( s.replace(/([^\\])(\\E)/, '$1') - .replace(/([^\\])(\\Q)/, '$1') - .replace(/^\\E/, '') - .replace(/^\\Q/, '') - .replace(/([^'])'/, `$1''`) - .replace(/^'([^'])/, `''$1`) + .replace(/([^\\])(\\Q)/, '$1') + .replace(/^\\E/, '') + .replace(/^\\Q/, '') + .replace(/([^'])'/, `$1''`) + .replace(/^'([^'])/, `''$1`) ); } From 39441cbbf14f37dc2a381a40b19b58a3f1ae0edd Mon Sep 17 00:00:00 2001 From: Mads Bjerre Date: Sat, 29 Jul 2017 14:22:59 +0200 Subject: [PATCH 3/4] Allow centerPoint to be Array --- src/Adapters/Storage/Mongo/MongoTransform.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Adapters/Storage/Mongo/MongoTransform.js b/src/Adapters/Storage/Mongo/MongoTransform.js index 0ee1470335..26b47b253c 100644 --- a/src/Adapters/Storage/Mongo/MongoTransform.js +++ b/src/Adapters/Storage/Mongo/MongoTransform.js @@ -706,8 +706,11 @@ function transformConstraint(constraint, inArray) { 'bad $geoWithin value; $centerSphere malformatted' ); } - const centerPoint = shape[0] + let centerPoint = shape[0] const radius = shape[1] + if (centerPoint instanceof Array && centerPoint.length == 2) { + centerPoint = new Parse.GeoPoint(centerPoint[0], centerPoint[1]).toJSON(); + } if (!GeoPointCoder.isValidJSON(centerPoint)) { throw new Parse.Error(Parse.Error.INVALID_JSON, 'bad $geoWithin value; centerPoint malformatted'); } else { From a06ed0a51da010ac77a1a7b6f82bc01f913ed61d Mon Sep 17 00:00:00 2001 From: Mads Bjerre Date: Sat, 29 Jul 2017 14:39:05 +0200 Subject: [PATCH 4/4] Make tests use REST API --- spec/ParseGeoPoint.spec.js | 149 ++++++++++++++++++++++++++++++------- 1 file changed, 121 insertions(+), 28 deletions(-) diff --git a/spec/ParseGeoPoint.spec.js b/spec/ParseGeoPoint.spec.js index d6e85d2abc..36e559eab5 100644 --- a/spec/ParseGeoPoint.spec.js +++ b/spec/ParseGeoPoint.spec.js @@ -629,45 +629,138 @@ describe('Parse.GeoPoint testing', () => { }); }); - it('works with withinCenterSphereKilometers queries', (done) => { - makeSomeGeoPoints(function() { - var sfo = new Parse.GeoPoint(37.6189722, -122.3748889); - var query = new Parse.Query(TestObject); - query.withinCenterSphereKilometers('location', sfo, 2000); - query.find({ - success: function(results) { - equal(results.length, 2); - done(); + it('works with $centerSphere queries where centerPoint is Array', (done) => { + const CenterSphereTestObject = Parse.Object.extend('CenterSphereTestObject'); + var odense = new CenterSphereTestObject(); + odense.set('location', new Parse.GeoPoint(55.398167, 10.386916)); + odense.set('name', 'Odense'); + + var slagelse = new CenterSphereTestObject(); + slagelse.set('location', new Parse.GeoPoint(55.401286, 11.364699)); + slagelse.set('name', 'Slagelse'); + + var copenhagen = new CenterSphereTestObject(); + copenhagen.set('location', new Parse.GeoPoint(55.674820, 12.589675)); + copenhagen.set('name', 'Copenhagen'); + + Parse.Object.saveAll([odense, slagelse, copenhagen]).then(() => { + // const center = new Parse.GeoPoint(55.461373, 11.891823); + const center = [55.461373, 11.891823]; + const radius = 60 / 6371.0 // 60 km in radians + const where = { + location: { + $geoWithin: { + $centerSphere: [ + center, + radius + ] + } + } + }; + + return rp.get({ + uri: Parse.serverURL + '/classes/CenterSphereTestObject', + json: { + where + }, + headers: { + 'X-Parse-Application-Id': Parse.applicationId, + 'X-Parse-Javascript-Key': Parse.javaScriptKey } }); + }).then(resp => { + const names = ['Slagelse', 'Copenhagen']; + equal(resp.results.length, 2); + expect(names).toContain(resp.results[0].name); + expect(names).toContain(resp.results[1].name); + done(); }); }); - it('works with withinCenterSphereMiles queries', (done) => { - makeSomeGeoPoints(function() { - var sfo = new Parse.GeoPoint(37.6189722, -122.3748889); - var query = new Parse.Query(TestObject); - query.withinCenterSphereMiles('location', sfo, 1243); - query.find({ - success: function(results) { - equal(results.length, 2); - done(); + it('works with $centerSphere queries where centerPoint is Parse.GeoPoint', (done) => { + const CenterSphereTestObject = Parse.Object.extend('CenterSphereTestObject'); + var odense = new CenterSphereTestObject(); + odense.set('location', new Parse.GeoPoint(55.398167, 10.386916)); + odense.set('name', 'Odense'); + + var slagelse = new CenterSphereTestObject(); + slagelse.set('location', new Parse.GeoPoint(55.401286, 11.364699)); + slagelse.set('name', 'Slagelse'); + + var copenhagen = new CenterSphereTestObject(); + copenhagen.set('location', new Parse.GeoPoint(55.674820, 12.589675)); + copenhagen.set('name', 'Copenhagen'); + + Parse.Object.saveAll([odense, slagelse, copenhagen]).then(() => { + const center = new Parse.GeoPoint(55.461373, 11.891823); + const radius = 60 / 6371.0 // 60 km in radians + const where = { + location: { + $geoWithin: { + $centerSphere: [ + center, + radius + ] + } + } + }; + + return rp.get({ + uri: Parse.serverURL + '/classes/CenterSphereTestObject', + json: { + where + }, + headers: { + 'X-Parse-Application-Id': Parse.applicationId, + 'X-Parse-Javascript-Key': Parse.javaScriptKey } }); + }).then(resp => { + const names = ['Slagelse', 'Copenhagen']; + equal(resp.results.length, 2); + expect(names).toContain(resp.results[0].name); + expect(names).toContain(resp.results[1].name); + done(); }); }); - it('works with withinCenterSphereRadians queries', (done) => { - makeSomeGeoPoints(function() { - var sfo = new Parse.GeoPoint(37.6189722, -122.3748889); - var query = new Parse.Query(TestObject); - query.withinCenterSphereRadians('location', sfo, 0.313922461); - query.find({ - success: function(results) { - equal(results.length, 2); - done(); + it('should respond with error with invalid centerPoint in $centerSphere query', (done) => { + // const center = 'john'; + const CenterSphereTestObject = Parse.Object.extend('CenterSphereTestObject'); + var odense = new CenterSphereTestObject(); + odense.set('location', new Parse.GeoPoint(55.398167, 10.386916)); + odense.set('name', 'Odense'); + + odense.save().then(() => { + const centerPoint = 'foo'; + const radius = 60 / 6371.0 // 60 km in radians + const where = { + location: { + $geoWithin: { + $centerSphere: [ + centerPoint, + radius + ] + } } - }); + }; + + return rp.get({ + uri: Parse.serverURL + '/classes/CenterSphereTestObject', + json: { + where + }, + headers: { + 'X-Parse-Application-Id': Parse.applicationId, + 'X-Parse-Javascript-Key': Parse.javaScriptKey + } + }) + }).then((resp) => { + fail(`no request should succeed: ${JSON.stringify(resp)}`); + done(); + }).catch((err) => { + expect(err.error.code).toEqual(107); + done(); }); }); });