From 9d6608b09b24fd74cba671dded532299a6a5689b Mon Sep 17 00:00:00 2001 From: Diamond Lewis Date: Wed, 21 Jun 2017 05:20:21 -0500 Subject: [PATCH 01/12] Added type polygon to schema --- package.json | 3 +- spec/MongoTransform.spec.js | 12 ++++++ spec/ParseObject.spec.js | 2 +- spec/ParsePolygon.spec.js | 35 ++++++++++++++++ spec/Schema.spec.js | 2 + .../Storage/Mongo/MongoSchemaCollection.js | 2 + src/Adapters/Storage/Mongo/MongoTransform.js | 41 +++++++++++++++++++ .../Postgres/PostgresStorageAdapter.js | 34 +++++++++++++++ src/Controllers/SchemaController.js | 8 +++- 9 files changed, 136 insertions(+), 3 deletions(-) create mode 100644 spec/ParsePolygon.spec.js diff --git a/package.json b/package.json index ef193f750c..b672f2d31a 100644 --- a/package.json +++ b/package.json @@ -67,7 +67,8 @@ }, "scripts": { "dev": "npm run build && node bin/dev", - "lint": "eslint --cache ./", + "lint2": "eslint --cache ./", + "lint": "echo 'no-lint'", "build": "babel src/ -d lib/ --copy-files", "pretest": "npm run lint", "test": "cross-env MONGODB_VERSION=${MONGODB_VERSION:=3.2.6} MONGODB_STORAGE_ENGINE=mmapv1 TESTING=1 $COVERAGE_OPTION jasmine", diff --git a/spec/MongoTransform.spec.js b/spec/MongoTransform.spec.js index c432e7960e..ed35ac4719 100644 --- a/spec/MongoTransform.spec.js +++ b/spec/MongoTransform.spec.js @@ -156,6 +156,18 @@ describe('parseObjectToMongoObjectForCreate', () => { done(); }); + it('polygon', (done) => { + var input = {location: { type: 'Polygon', coordinates: [[180, -180],[180, -180]]}}; + var output = transform.mongoObjectToParseObject(null, input, { + fields: { location: { type: 'Polygon' }}, + }); + expect(typeof output.location).toEqual('object'); + expect(output.location).toEqual( + {__type: 'Polygon', coordinates: [[180, -180],[180, -180]]} + ); + done(); + }); + it('bytes', (done) => { var input = {binaryData: "aGVsbG8gd29ybGQ="}; var output = transform.mongoObjectToParseObject(null, input, { diff --git a/spec/ParseObject.spec.js b/spec/ParseObject.spec.js index a5d64eebad..cc449458ce 100644 --- a/spec/ParseObject.spec.js +++ b/spec/ParseObject.spec.js @@ -344,7 +344,7 @@ describe('Parse.Object testing', () => { it("invalid __type", function(done) { var item = new Parse.Object("Item"); - var types = ['Pointer', 'File', 'Date', 'GeoPoint', 'Bytes']; + var types = ['Pointer', 'File', 'Date', 'GeoPoint', 'Bytes', 'Polygon']; var tests = types.map(type => { var test = new Parse.Object("Item"); test.set('foo', { diff --git a/spec/ParsePolygon.spec.js b/spec/ParsePolygon.spec.js new file mode 100644 index 0000000000..3c5d016df8 --- /dev/null +++ b/spec/ParsePolygon.spec.js @@ -0,0 +1,35 @@ +const TestObject = Parse.Object.extend('TestObject'); + +describe('Parse.Polygon testing', () => { + it('polygon save', (done) => { + const coords = [[0,0],[0,1],[1,0],[1,1]]; + const obj = new TestObject(); + obj.set('polygon', {__type: 'Polygon', coordinates: coords}); + return obj.save().then(() => { + const query = new Parse.Query(TestObject); + return query.get(obj.id); + }).then((result) => { + const polygon = result.get('polygon'); + equal(polygon.__type, 'Polygon'); + equal(polygon.coordinates, coords); + done(); + }, done.fail); + }); + + it('polygon equalTo', (done) => { + const coords = [[0,0],[0,1],[1,0],[1,1]]; + const polygon = {__type: 'Polygon', coordinates: coords}; + const obj = new TestObject(); + obj.set('polygon', polygon); + return obj.save().then(() => { + const query = new Parse.Query(TestObject); + query.equalTo('polygon', polygon); + return query.find(); + }).then((results) => { + const polygon = results[0].get('polygon'); + equal(polygon.__type, 'Polygon'); + equal(polygon.coordinates, coords); + done(); + }, done.fail); + }); +}); diff --git a/spec/Schema.spec.js b/spec/Schema.spec.js index 74d2212a7d..dcccb970f3 100644 --- a/spec/Schema.spec.js +++ b/spec/Schema.spec.js @@ -524,6 +524,7 @@ describe('SchemaController', () => { aPointer: {type: 'Pointer', targetClass: 'ThisClassDoesNotExistYet'}, aRelation: {type: 'Relation', targetClass: 'NewClass'}, aBytes: {type: 'Bytes'}, + aPolygon: {type: 'Polygon'}, })) .then(actualSchema => { const expectedSchema = { @@ -544,6 +545,7 @@ describe('SchemaController', () => { aPointer: { type: 'Pointer', targetClass: 'ThisClassDoesNotExistYet' }, aRelation: { type: 'Relation', targetClass: 'NewClass' }, aBytes: {type: 'Bytes'}, + aPolygon: {type: 'Polygon'}, }, classLevelPermissions: { find: { '*': true }, diff --git a/src/Adapters/Storage/Mongo/MongoSchemaCollection.js b/src/Adapters/Storage/Mongo/MongoSchemaCollection.js index 74303eae36..bebd6ed24c 100644 --- a/src/Adapters/Storage/Mongo/MongoSchemaCollection.js +++ b/src/Adapters/Storage/Mongo/MongoSchemaCollection.js @@ -25,6 +25,7 @@ function mongoFieldToParseSchemaField(type) { case 'geopoint': return {type: 'GeoPoint'}; case 'file': return {type: 'File'}; case 'bytes': return {type: 'Bytes'}; + case 'polygon': return {type: 'Polygon'}; } } @@ -98,6 +99,7 @@ function parseFieldTypeToMongoFieldType({ type, targetClass }) { case 'GeoPoint': return 'geopoint'; case 'File': return 'file'; case 'Bytes': return 'bytes'; + case 'Polygon': return 'polygon'; } } diff --git a/src/Adapters/Storage/Mongo/MongoTransform.js b/src/Adapters/Storage/Mongo/MongoTransform.js index 80c7be9430..f3ef0b2d8a 100644 --- a/src/Adapters/Storage/Mongo/MongoTransform.js +++ b/src/Adapters/Storage/Mongo/MongoTransform.js @@ -495,6 +495,9 @@ function transformTopLevelAtom(atom) { if (GeoPointCoder.isValidJSON(atom)) { return GeoPointCoder.JSONToDatabase(atom); } + if (PolygonCoder.isValidJSON(atom)) { + return PolygonCoder.JSONToDatabase(atom); + } if (FileCoder.isValidJSON(atom)) { return FileCoder.JSONToDatabase(atom); } @@ -940,6 +943,10 @@ const mongoObjectToParseObject = (className, mongoObject, schema) => { restObject[key] = GeoPointCoder.databaseToJSON(value); break; } + if (schema.fields[key] && schema.fields[key].type === 'Polygon' && PolygonCoder.isValidDatabaseObject(value)) { + restObject[key] = PolygonCoder.databaseToJSON(value); + break; + } if (schema.fields[key] && schema.fields[key].type === 'Bytes' && BytesCoder.isValidDatabaseObject(value)) { restObject[key] = BytesCoder.databaseToJSON(value); break; @@ -1043,6 +1050,40 @@ var GeoPointCoder = { } }; +var PolygonCoder = { + databaseToJSON(object) { + return { + __type: 'Polygon', + coordinates: object['coordinates'] + } + }, + + isValidDatabaseObject(object) { + const coords = object.coordinates; + if (object.type !== 'Polygon' || !(coords instanceof Array)) { + return false; + } + for (let i = 0; i < coords.length; i++) { + const point = coords[i]; + if (!GeoPointCoder.isValidDatabaseObject(point)) { + return false; + } + } + return true; + }, + + JSONToDatabase(json) { + return { type: 'Polygon', coordinates: json.coordinates }; + }, + + isValidJSON(value) { + return (typeof value === 'object' && + value !== null && + value.__type === 'Polygon' + ); + } +}; + var FileCoder = { databaseToJSON(object) { return { diff --git a/src/Adapters/Storage/Postgres/PostgresStorageAdapter.js b/src/Adapters/Storage/Postgres/PostgresStorageAdapter.js index 4858967a8e..a85d87c40b 100644 --- a/src/Adapters/Storage/Postgres/PostgresStorageAdapter.js +++ b/src/Adapters/Storage/Postgres/PostgresStorageAdapter.js @@ -29,6 +29,7 @@ const parseTypeToPostgresType = type => { case 'Number': return 'double precision'; case 'GeoPoint': return 'point'; case 'Bytes': return 'jsonb'; + case 'Polygon': return 'polygon'; case 'Array': if (type.contents && type.contents.type === 'String') { return 'text[]'; @@ -468,6 +469,17 @@ const buildWhereClause = ({ schema, query, index }) => { index += 3; } + if (fieldValue.__type === 'Polygon') { + const polygon = fieldValue.coordinates; + const points = polygon.map((point) => { + return `(${point[1]}, ${point[0]})`; + }).join(', '); + + patterns.push(`$${index}:name ~= $${index + 1}::polygon`); + values.push(fieldName, `(${points})`); + index += 2; + } + Object.keys(ParseToPosgresComparator).forEach(cmp => { if (fieldValue[cmp]) { const pgComparator = ParseToPosgresComparator[cmp]; @@ -829,6 +841,14 @@ export class PostgresStorageAdapter { case 'File': valuesArray.push(object[fieldName].name); break; + case 'Polygon': { + const coords = object[fieldName].coordinates; + const points = coords.map((point) => { + return `(${point[1]}, ${point[0]})`; + }).join(', '); + valuesArray.push(`(${points})`); + break; + } case 'GeoPoint': // pop the point and process later geoPoints[fieldName] = object[fieldName]; @@ -1170,6 +1190,20 @@ export class PostgresStorageAdapter { longitude: object[fieldName].x } } + if (object[fieldName] && schema.fields[fieldName].type === 'Polygon') { + let coords = object[fieldName]; + coords = coords.substr(2, coords.length - 4).split('),('); + coords = coords.map((point) => { + return [ + parseFloat(point.split(',')[1]), + parseFloat(point.split(',')[0]) + ]; + }); + object[fieldName] = { + __type: "Polygon", + coordinates: coords + } + } if (object[fieldName] && schema.fields[fieldName].type === 'File') { object[fieldName] = { __type: 'File', diff --git a/src/Controllers/SchemaController.js b/src/Controllers/SchemaController.js index 59b306a200..16de76f426 100644 --- a/src/Controllers/SchemaController.js +++ b/src/Controllers/SchemaController.js @@ -226,7 +226,8 @@ const validNonRelationOrPointerTypes = [ 'Array', 'GeoPoint', 'File', - 'Bytes' + 'Bytes', + 'Polygon' ]; // Returns an error suitable for throwing if the type is invalid const fieldTypeIsInvalid = ({ type, targetClass }) => { @@ -986,6 +987,11 @@ function getObjectType(obj) { return 'Bytes'; } break; + case 'Polygon' : + if(obj.coordinates) { + return 'Polygon'; + } + break; } throw new Parse.Error(Parse.Error.INCORRECT_TYPE, "This is not a valid " + obj.__type); } From f7d02a7ef5dcfcb1bad0121cc72d2846df453807 Mon Sep 17 00:00:00 2001 From: Diamond Lewis Date: Wed, 21 Jun 2017 05:57:21 -0500 Subject: [PATCH 02/12] refactoring and more tests --- package.json | 3 +-- spec/ParsePolygon.spec.js | 21 +++++++++++++++ .../Postgres/PostgresStorageAdapter.js | 27 +++++++++++-------- 3 files changed, 38 insertions(+), 13 deletions(-) diff --git a/package.json b/package.json index b672f2d31a..ef193f750c 100644 --- a/package.json +++ b/package.json @@ -67,8 +67,7 @@ }, "scripts": { "dev": "npm run build && node bin/dev", - "lint2": "eslint --cache ./", - "lint": "echo 'no-lint'", + "lint": "eslint --cache ./", "build": "babel src/ -d lib/ --copy-files", "pretest": "npm run lint", "test": "cross-env MONGODB_VERSION=${MONGODB_VERSION:=3.2.6} MONGODB_STORAGE_ENGINE=mmapv1 TESTING=1 $COVERAGE_OPTION jasmine", diff --git a/spec/ParsePolygon.spec.js b/spec/ParsePolygon.spec.js index 3c5d016df8..d65eb251aa 100644 --- a/spec/ParsePolygon.spec.js +++ b/spec/ParsePolygon.spec.js @@ -32,4 +32,25 @@ describe('Parse.Polygon testing', () => { done(); }, done.fail); }); + + it('polygon update', (done) => { + const oldCoords = [[0,0],[0,1],[1,0],[1,1]]; + const oldPolygon = {__type: 'Polygon', coordinates: oldCoords}; + const newCoords = [[0,0],[0,1],[1,0],[1,1]]; + const newPolygon = {__type: 'Polygon', coordinates: newCoords}; + const obj = new TestObject(); + obj.set('polygon', oldPolygon); + return obj.save().then(() => { + obj.set('polygon', newPolygon); + return obj.save(); + }).then(() => { + const query = new Parse.Query(TestObject); + return query.get(obj.id); + }).then((result) => { + const polygon = result.get('polygon'); + equal(polygon.__type, 'Polygon'); + equal(polygon.coordinates, newCoords); + done(); + }, done.fail); + }); }); diff --git a/src/Adapters/Storage/Postgres/PostgresStorageAdapter.js b/src/Adapters/Storage/Postgres/PostgresStorageAdapter.js index a85d87c40b..2a6ae6130c 100644 --- a/src/Adapters/Storage/Postgres/PostgresStorageAdapter.js +++ b/src/Adapters/Storage/Postgres/PostgresStorageAdapter.js @@ -470,13 +470,9 @@ const buildWhereClause = ({ schema, query, index }) => { } if (fieldValue.__type === 'Polygon') { - const polygon = fieldValue.coordinates; - const points = polygon.map((point) => { - return `(${point[1]}, ${point[0]})`; - }).join(', '); - + const value = convertPolygonToSQL(fieldValue.coordinates); patterns.push(`$${index}:name ~= $${index + 1}::polygon`); - values.push(fieldName, `(${points})`); + values.push(fieldName, value); index += 2; } @@ -842,11 +838,8 @@ export class PostgresStorageAdapter { valuesArray.push(object[fieldName].name); break; case 'Polygon': { - const coords = object[fieldName].coordinates; - const points = coords.map((point) => { - return `(${point[1]}, ${point[0]})`; - }).join(', '); - valuesArray.push(`(${points})`); + const value = convertPolygonToSQL(object[fieldName].coordinates); + valuesArray.push(value); break; } case 'GeoPoint': @@ -1029,6 +1022,11 @@ export class PostgresStorageAdapter { updatePatterns.push(`$${index}:name = POINT($${index + 1}, $${index + 2})`); values.push(fieldName, fieldValue.longitude, fieldValue.latitude); index += 3; + } else if (fieldValue.__type === 'Polygon') { + const value = convertPolygonToSQL(fieldValue.coordinates); + updatePatterns.push(`$${index}:name = $${index + 1}::polygon`); + values.push(fieldName, value); + index += 2; } else if (fieldValue.__type === 'Relation') { // noop } else if (typeof fieldValue === 'number') { @@ -1321,6 +1319,13 @@ export class PostgresStorageAdapter { } } +function convertPolygonToSQL(polygon) { + const points = polygon.map((point) => { + return `(${point[1]}, ${point[0]})`; + }).join(', '); + return `(${points})`; +} + function removeWhiteSpace(regex) { if (!regex.endsWith('\n')){ regex += '\n'; From 230a2eb200bfc286027bc5feacd992f73ee2fc40 Mon Sep 17 00:00:00 2001 From: Diamond Lewis Date: Wed, 21 Jun 2017 10:47:14 -0500 Subject: [PATCH 03/12] fix tests --- spec/ParsePolygon.spec.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/ParsePolygon.spec.js b/spec/ParsePolygon.spec.js index d65eb251aa..a2f91245e3 100644 --- a/spec/ParsePolygon.spec.js +++ b/spec/ParsePolygon.spec.js @@ -17,7 +17,7 @@ describe('Parse.Polygon testing', () => { }); it('polygon equalTo', (done) => { - const coords = [[0,0],[0,1],[1,0],[1,1]]; + const coords = [[1,0],[1,1],[1,0],[0,0]]; const polygon = {__type: 'Polygon', coordinates: coords}; const obj = new TestObject(); obj.set('polygon', polygon); @@ -36,7 +36,7 @@ describe('Parse.Polygon testing', () => { it('polygon update', (done) => { const oldCoords = [[0,0],[0,1],[1,0],[1,1]]; const oldPolygon = {__type: 'Polygon', coordinates: oldCoords}; - const newCoords = [[0,0],[0,1],[1,0],[1,1]]; + const newCoords = [[1,0],[1,1],[1,0],[0,0]]; const newPolygon = {__type: 'Polygon', coordinates: newCoords}; const obj = new TestObject(); obj.set('polygon', oldPolygon); From 079ed12322c2df7f86c8a8f137b1346b8025b8bf Mon Sep 17 00:00:00 2001 From: Diamond Lewis Date: Wed, 21 Jun 2017 17:04:06 -0500 Subject: [PATCH 04/12] update test and transform --- spec/MongoTransform.spec.js | 2 +- spec/ParsePolygon.spec.js | 111 +++++++++++++++++- src/Adapters/Storage/Mongo/MongoTransform.js | 30 ++++- .../Postgres/PostgresStorageAdapter.js | 5 + 4 files changed, 141 insertions(+), 7 deletions(-) diff --git a/spec/MongoTransform.spec.js b/spec/MongoTransform.spec.js index ed35ac4719..5926796e07 100644 --- a/spec/MongoTransform.spec.js +++ b/spec/MongoTransform.spec.js @@ -157,7 +157,7 @@ describe('parseObjectToMongoObjectForCreate', () => { }); it('polygon', (done) => { - var input = {location: { type: 'Polygon', coordinates: [[180, -180],[180, -180]]}}; + var input = {location: { type: 'Polygon', coordinates: [[[180, -180],[180, -180]]]}}; var output = transform.mongoObjectToParseObject(null, input, { fields: { location: { type: 'Polygon' }}, }); diff --git a/spec/ParsePolygon.spec.js b/spec/ParsePolygon.spec.js index a2f91245e3..c55d68bac8 100644 --- a/spec/ParsePolygon.spec.js +++ b/spec/ParsePolygon.spec.js @@ -1,8 +1,31 @@ const TestObject = Parse.Object.extend('TestObject'); +const MongoStorageAdapter = require('../src/Adapters/Storage/Mongo/MongoStorageAdapter'); +const mongoURI = 'mongodb://localhost:27017/parseServerMongoAdapterTestDatabase'; +const rp = require('request-promise'); +const defaultHeaders = { + 'X-Parse-Application-Id': 'test', + 'X-Parse-Rest-API-Key': 'rest' +} describe('Parse.Polygon testing', () => { - it('polygon save', (done) => { + it('polygon save open path', (done) => { const coords = [[0,0],[0,1],[1,0],[1,1]]; + const closed = [[0,0],[0,1],[1,0],[1,1],[0,0]]; + const obj = new TestObject(); + obj.set('polygon', {__type: 'Polygon', coordinates: coords}); + return obj.save().then(() => { + const query = new Parse.Query(TestObject); + return query.get(obj.id); + }).then((result) => { + const polygon = result.get('polygon'); + equal(polygon.__type, 'Polygon'); + equal(polygon.coordinates, closed); + done(); + }, done.fail); + }); + + it('polygon save closed path', (done) => { + const coords = [[0,0],[0,1],[1,0],[1,1],[0,0]]; const obj = new TestObject(); obj.set('polygon', {__type: 'Polygon', coordinates: coords}); return obj.save().then(() => { @@ -17,7 +40,7 @@ describe('Parse.Polygon testing', () => { }); it('polygon equalTo', (done) => { - const coords = [[1,0],[1,1],[1,0],[0,0]]; + const coords = [[0,0],[0,1],[1,0],[1,1]]; const polygon = {__type: 'Polygon', coordinates: coords}; const obj = new TestObject(); obj.set('polygon', polygon); @@ -27,6 +50,7 @@ describe('Parse.Polygon testing', () => { return query.find(); }).then((results) => { const polygon = results[0].get('polygon'); + coords.push(coords[0]); equal(polygon.__type, 'Polygon'); equal(polygon.coordinates, coords); done(); @@ -36,7 +60,7 @@ describe('Parse.Polygon testing', () => { it('polygon update', (done) => { const oldCoords = [[0,0],[0,1],[1,0],[1,1]]; const oldPolygon = {__type: 'Polygon', coordinates: oldCoords}; - const newCoords = [[1,0],[1,1],[1,0],[0,0]]; + const newCoords = [[2,2],[2,3],[3,3],[3,2]]; const newPolygon = {__type: 'Polygon', coordinates: newCoords}; const obj = new TestObject(); obj.set('polygon', oldPolygon); @@ -48,9 +72,90 @@ describe('Parse.Polygon testing', () => { return query.get(obj.id); }).then((result) => { const polygon = result.get('polygon'); + newCoords.push(newCoords[0]); equal(polygon.__type, 'Polygon'); equal(polygon.coordinates, newCoords); done(); }, done.fail); }); + + it('polygon invalid value', (done) => { + const coords = [['foo','bar'],[0,1],[1,0],[1,1],[0,0]]; + const obj = new TestObject(); + obj.set('polygon', {__type: 'Polygon', coordinates: coords}); + return obj.save().then(() => { + const query = new Parse.Query(TestObject); + return query.get(obj.id); + }).then(done.fail, done); + }); +}); + +const buildIndexes = () => { + const databaseAdapter = new MongoStorageAdapter({ uri: mongoURI }); + return reconfigureServer({ + appId: 'test', + restAPIKey: 'rest', + publicServerURL: 'http://localhost:8378/1', + databaseAdapter + }).then(() => { + return databaseAdapter.createIndex('TestObject', {location: '2d'}); + }).then(() => { + return databaseAdapter.createIndex('TestObject', {polygon: '2dsphere'}); + }); +}; + +describe_only_db('mongo')('Parse.Polygon testing', () => { + it('support 2d and 2dsphere', (done) => { + const coords = [[0,0],[0,1],[1,1],[1,0],[0,0]]; + const polygon = {__type: 'Polygon', coordinates: coords}; + const location = {__type: 'GeoPoint', latitude:10, longitude:10}; + buildIndexes().then(() => { + return rp.post({ + url: 'http://localhost:8378/1/classes/TestObject', + json: { + '_method': 'POST', + location, + polygon + }, + headers: defaultHeaders + }); + }).then((resp) => { + return rp.post({ + url: `http://localhost:8378/1/classes/TestObject/${resp.objectId}`, + json: {'_method': 'GET'}, + headers: defaultHeaders + }); + }).then((resp) => { + equal(resp.location, location); + equal(resp.polygon, polygon); + done(); + }, done.fail); + }); + + it('polygon three points minimum', (done) => { + const coords = [[0,0]]; + const obj = new TestObject(); + obj.set('polygon', {__type: 'Polygon', coordinates: coords}); + buildIndexes().then(() => { + return obj.save(); + }).then(done.fail, done); + }); + + it('polygon three different points minimum', (done) => { + const coords = [[0,0],[0,1]]; + const obj = new TestObject(); + obj.set('polygon', {__type: 'Polygon', coordinates: coords}); + buildIndexes().then(() => { + return obj.save(); + }).then(done.fail, done); + }); +}); + +describe_only_db('postgres')('[postgres] Parse.Polygon testing', () => { + it('polygon three different points minimum', (done) => { + const coords = [[0,0],[0,1]]; + const obj = new TestObject(); + obj.set('polygon', {__type: 'Polygon', coordinates: coords}); + obj.save().then(done, done.fail); + }); }); diff --git a/src/Adapters/Storage/Mongo/MongoTransform.js b/src/Adapters/Storage/Mongo/MongoTransform.js index f3ef0b2d8a..3da77fbc5e 100644 --- a/src/Adapters/Storage/Mongo/MongoTransform.js +++ b/src/Adapters/Storage/Mongo/MongoTransform.js @@ -1054,12 +1054,12 @@ var PolygonCoder = { databaseToJSON(object) { return { __type: 'Polygon', - coordinates: object['coordinates'] + coordinates: object['coordinates'][0] } }, isValidDatabaseObject(object) { - const coords = object.coordinates; + const coords = object.coordinates[0]; if (object.type !== 'Polygon' || !(coords instanceof Array)) { return false; } @@ -1068,12 +1068,36 @@ var PolygonCoder = { if (!GeoPointCoder.isValidDatabaseObject(point)) { return false; } + Parse.GeoPoint._validate(parseFloat(point[1]), parseFloat(point[0])); } return true; }, JSONToDatabase(json) { - return { type: 'Polygon', coordinates: json.coordinates }; + const coords = json.coordinates; + if (coords[0][0] !== coords[coords.length - 1][0] || + coords[0][1] !== coords[coords.length - 1][1]) { + coords.push(coords[0]); + } + const unique = coords.filter((item, index, ar) => { + let foundIndex = -1; + for (let i = 0; i < ar.length; i += 1) { + const pt = ar[i]; + if (pt[0] === item[0] && + pt[1] === item[1]) { + foundIndex = i; + break; + } + } + return foundIndex === index; + }); + if (unique.length < 3) { + throw new Parse.Error( + Parse.Error.INTERNAL_SERVER_ERROR, + 'GeoJSON: Loop must have at least 3 different vertices' + ); + } + return { type: 'Polygon', coordinates: [coords] }; }, isValidJSON(value) { diff --git a/src/Adapters/Storage/Postgres/PostgresStorageAdapter.js b/src/Adapters/Storage/Postgres/PostgresStorageAdapter.js index 2a6ae6130c..1cd2b3b087 100644 --- a/src/Adapters/Storage/Postgres/PostgresStorageAdapter.js +++ b/src/Adapters/Storage/Postgres/PostgresStorageAdapter.js @@ -1320,7 +1320,12 @@ export class PostgresStorageAdapter { } function convertPolygonToSQL(polygon) { + if (polygon[0][0] !== polygon[polygon.length - 1][0] || + polygon[0][1] !== polygon[polygon.length - 1][1]) { + polygon.push(polygon[0]); + } const points = polygon.map((point) => { + Parse.GeoPoint._validate(parseFloat(point[1]), parseFloat(point[0])); return `(${point[1]}, ${point[0]})`; }).join(', '); return `(${points})`; From 3f0ed3886c6863addde6c47237a25b23397fd0f0 Mon Sep 17 00:00:00 2001 From: Diamond Lewis Date: Wed, 21 Jun 2017 17:35:13 -0500 Subject: [PATCH 05/12] add support for polygonContains --- spec/ParsePolygon.spec.js | 114 +++++++++++++++++- src/Adapters/Storage/Mongo/MongoTransform.js | 18 +++ .../Postgres/PostgresStorageAdapter.js | 20 +++ 3 files changed, 151 insertions(+), 1 deletion(-) diff --git a/spec/ParsePolygon.spec.js b/spec/ParsePolygon.spec.js index c55d68bac8..331e7a6a6d 100644 --- a/spec/ParsePolygon.spec.js +++ b/spec/ParsePolygon.spec.js @@ -88,6 +88,84 @@ describe('Parse.Polygon testing', () => { return query.get(obj.id); }).then(done.fail, done); }); + + it('polygonContain query', (done) => { + const points1 = [[0,0],[0,1],[1,1],[1,0]]; + const points2 = [[0,0],[0,2],[2,2],[2,0]]; + const points3 = [[10,10],[10,15],[15,15],[15,10],[10,10]]; + const polygon1 = {__type: 'Polygon', coordinates: points1}; + const polygon2 = {__type: 'Polygon', coordinates: points2}; + const polygon3 = {__type: 'Polygon', coordinates: points3}; + const obj1 = new TestObject({location: polygon1}); + const obj2 = new TestObject({location: polygon2}); + const obj3 = new TestObject({location: polygon3}); + Parse.Object.saveAll([obj1, obj2, obj3]).then(() => { + const where = { + location: { + $geoIntersects: { + $point: { __type: 'GeoPoint', latitude: 0.5, longitude: 0.5 } + } + } + }; + return rp.post({ + url: Parse.serverURL + '/classes/TestObject', + json: { where, '_method': 'GET' }, + headers: { + 'X-Parse-Application-Id': Parse.applicationId, + 'X-Parse-Javascript-Key': Parse.javaScriptKey + } + }); + }).then((resp) => { + expect(resp.results.length).toBe(2); + done(); + }, done.fail); + }); + + it('polygonContain invalid input', (done) => { + const points = [[0,0],[0,1],[1,1],[1,0]]; + const polygon = {__type: 'Polygon', coordinates: points}; + const obj = new TestObject({location: polygon}); + obj.save().then(() => { + const where = { + location: { + $geoIntersects: { + $point: { __type: 'GeoPoint', latitude: 181, longitude: 181 } + } + } + }; + return rp.post({ + url: Parse.serverURL + '/classes/TestObject', + json: { where, '_method': 'GET' }, + headers: { + 'X-Parse-Application-Id': Parse.applicationId, + 'X-Parse-Javascript-Key': Parse.javaScriptKey + } + }); + }).then(done.fail, done); + }); + + it('polygonContain invalid geoPoint', (done) => { + const points = [[0,0],[0,1],[1,1],[1,0]]; + const polygon = {__type: 'Polygon', coordinates: points}; + const obj = new TestObject({location: polygon}); + obj.save().then(() => { + const where = { + location: { + $geoIntersects: { + $point: [] + } + } + }; + return rp.post({ + url: Parse.serverURL + '/classes/TestObject', + json: { where, '_method': 'GET' }, + headers: { + 'X-Parse-Application-Id': Parse.applicationId, + 'X-Parse-Javascript-Key': Parse.javaScriptKey + } + }); + }).then(done.fail, done); + }); }); const buildIndexes = () => { @@ -149,6 +227,40 @@ describe_only_db('mongo')('Parse.Polygon testing', () => { return obj.save(); }).then(done.fail, done); }); + + it('polygonContain query with indexes', (done) => { + const points1 = [[0,0],[0,1],[1,1],[1,0]]; + const points2 = [[0,0],[0,2],[2,2],[2,0]]; + const points3 = [[10,10],[10,15],[15,15],[15,10],[10,10]]; + const polygon1 = {__type: 'Polygon', coordinates: points1}; + const polygon2 = {__type: 'Polygon', coordinates: points2}; + const polygon3 = {__type: 'Polygon', coordinates: points3}; + const obj1 = new TestObject({polygon: polygon1}); + const obj2 = new TestObject({polygon: polygon2}); + const obj3 = new TestObject({polygon: polygon3}); + buildIndexes().then(() => { + return Parse.Object.saveAll([obj1, obj2, obj3]); + }).then(() => { + const where = { + polygon: { + $geoIntersects: { + $point: { __type: 'GeoPoint', latitude: 0.5, longitude: 0.5 } + } + } + }; + return rp.post({ + url: Parse.serverURL + '/classes/TestObject', + json: { where, '_method': 'GET' }, + headers: { + 'X-Parse-Application-Id': Parse.applicationId, + 'X-Parse-Javascript-Key': Parse.javaScriptKey + } + }); + }).then((resp) => { + expect(resp.results.length).toBe(2); + done(); + }, done.fail); + }); }); describe_only_db('postgres')('[postgres] Parse.Polygon testing', () => { @@ -156,6 +268,6 @@ describe_only_db('postgres')('[postgres] Parse.Polygon testing', () => { const coords = [[0,0],[0,1]]; const obj = new TestObject(); obj.set('polygon', {__type: 'Polygon', coordinates: coords}); - obj.save().then(done, done.fail); + obj.save().then(done.fail, done); }); }); diff --git a/src/Adapters/Storage/Mongo/MongoTransform.js b/src/Adapters/Storage/Mongo/MongoTransform.js index 3da77fbc5e..f46c310e23 100644 --- a/src/Adapters/Storage/Mongo/MongoTransform.js +++ b/src/Adapters/Storage/Mongo/MongoTransform.js @@ -695,6 +695,24 @@ function transformConstraint(constraint, inArray) { }; break; } + case '$geoIntersects': { + const point = constraint[key]['$point']; + if (!GeoPointCoder.isValidJSON(point)) { + throw new Parse.Error( + Parse.Error.INVALID_JSON, + 'bad $geoIntersect value; $point should be GeoPoint' + ); + } else { + Parse.GeoPoint._validate(point.latitude, point.longitude); + } + answer[key] = { + $geometry: { + type: 'Point', + coordinates: [point.longitude, point.latitude] + } + }; + break; + } default: if (key.match(/^\$+/)) { throw new Parse.Error( diff --git a/src/Adapters/Storage/Postgres/PostgresStorageAdapter.js b/src/Adapters/Storage/Postgres/PostgresStorageAdapter.js index 1cd2b3b087..26b0ba3130 100644 --- a/src/Adapters/Storage/Postgres/PostgresStorageAdapter.js +++ b/src/Adapters/Storage/Postgres/PostgresStorageAdapter.js @@ -424,6 +424,20 @@ const buildWhereClause = ({ schema, query, index }) => { values.push(fieldName, `(${points})`); index += 2; } + if (fieldValue.$geoIntersects && fieldValue.$geoIntersects.$point) { + const point = fieldValue.$geoIntersects.$point; + if (typeof point !== 'object' || point.__type !== 'GeoPoint') { + throw new Parse.Error( + Parse.Error.INVALID_JSON, + 'bad $geoIntersect value; $point should be GeoPoint' + ); + } else { + Parse.GeoPoint._validate(point.latitude, point.longitude); + } + patterns.push(`$${index}:name::polygon @> $${index + 1}::point`); + values.push(fieldName, `(${point.longitude}, ${point.latitude})`); + index += 2; + } if (fieldValue.$regex) { let regex = fieldValue.$regex; @@ -1320,6 +1334,12 @@ export class PostgresStorageAdapter { } function convertPolygonToSQL(polygon) { + if (polygon.length < 3) { + throw new Parse.Error( + Parse.Error.INVALID_JSON, + `Polygon must have at least 3 values` + ); + } if (polygon[0][0] !== polygon[polygon.length - 1][0] || polygon[0][1] !== polygon[polygon.length - 1][1]) { polygon.push(polygon[0]); From 369f7359a5a0db2cd9e21d1f5aa6cdf3b478035f Mon Sep 17 00:00:00 2001 From: Diamond Lewis Date: Wed, 21 Jun 2017 18:15:48 -0500 Subject: [PATCH 06/12] fix transform.mongoObjectToParseObject test --- spec/MongoTransform.spec.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/spec/MongoTransform.spec.js b/spec/MongoTransform.spec.js index 5926796e07..4c99f658f2 100644 --- a/spec/MongoTransform.spec.js +++ b/spec/MongoTransform.spec.js @@ -145,25 +145,25 @@ describe('parseObjectToMongoObjectForCreate', () => { }); it('geopoint', (done) => { - var input = {location: [180, -180]}; + var input = {location: [45, -45]}; var output = transform.mongoObjectToParseObject(null, input, { fields: { location: { type: 'GeoPoint' }}, }); expect(typeof output.location).toEqual('object'); expect(output.location).toEqual( - {__type: 'GeoPoint', longitude: 180, latitude: -180} + {__type: 'GeoPoint', longitude: 45, latitude: -45} ); done(); }); it('polygon', (done) => { - var input = {location: { type: 'Polygon', coordinates: [[[180, -180],[180, -180]]]}}; + var input = {location: { type: 'Polygon', coordinates: [[[45, -45],[45, -45]]]}}; var output = transform.mongoObjectToParseObject(null, input, { fields: { location: { type: 'Polygon' }}, }); expect(typeof output.location).toEqual('object'); expect(output.location).toEqual( - {__type: 'Polygon', coordinates: [[180, -180],[180, -180]]} + {__type: 'Polygon', coordinates: [[45, -45],[45, -45]]} ); done(); }); From c640ced226a1aa255f22c55ec6e79ddd3994d2f7 Mon Sep 17 00:00:00 2001 From: Diamond Lewis Date: Fri, 23 Jun 2017 22:16:08 -0500 Subject: [PATCH 07/12] add indexes for polygon --- spec/ParsePolygon.spec.js | 154 ++++++++---------- .../Storage/Mongo/MongoStorageAdapter.js | 17 +- 2 files changed, 87 insertions(+), 84 deletions(-) diff --git a/spec/ParsePolygon.spec.js b/spec/ParsePolygon.spec.js index 331e7a6a6d..e7fa17dacc 100644 --- a/spec/ParsePolygon.spec.js +++ b/spec/ParsePolygon.spec.js @@ -9,8 +9,8 @@ const defaultHeaders = { describe('Parse.Polygon testing', () => { it('polygon save open path', (done) => { - const coords = [[0,0],[0,1],[1,0],[1,1]]; - const closed = [[0,0],[0,1],[1,0],[1,1],[0,0]]; + const coords = [[0,0],[0,1],[1,1],[1,0]]; + const closed = [[0,0],[0,1],[1,1],[1,0],[0,0]]; const obj = new TestObject(); obj.set('polygon', {__type: 'Polygon', coordinates: coords}); return obj.save().then(() => { @@ -25,7 +25,7 @@ describe('Parse.Polygon testing', () => { }); it('polygon save closed path', (done) => { - const coords = [[0,0],[0,1],[1,0],[1,1],[0,0]]; + const coords = [[0,0],[0,1],[1,1],[1,0],[0,0]]; const obj = new TestObject(); obj.set('polygon', {__type: 'Polygon', coordinates: coords}); return obj.save().then(() => { @@ -39,26 +39,34 @@ describe('Parse.Polygon testing', () => { }, done.fail); }); - it('polygon equalTo', (done) => { - const coords = [[0,0],[0,1],[1,0],[1,1]]; - const polygon = {__type: 'Polygon', coordinates: coords}; + it('polygon equalTo (open/closed) path', (done) => { + const openPoints = [[0,0],[0,1],[1,1],[1,0]]; + const closedPoints = [[0,0],[0,1],[1,1],[1,0],[0,0]]; + const openPolygon = {__type: 'Polygon', coordinates: openPoints}; + const closedPolygon = {__type: 'Polygon', coordinates: closedPoints}; const obj = new TestObject(); - obj.set('polygon', polygon); + obj.set('polygon', openPolygon); return obj.save().then(() => { const query = new Parse.Query(TestObject); - query.equalTo('polygon', polygon); + query.equalTo('polygon', openPolygon); return query.find(); }).then((results) => { const polygon = results[0].get('polygon'); - coords.push(coords[0]); equal(polygon.__type, 'Polygon'); - equal(polygon.coordinates, coords); + equal(polygon.coordinates, closedPoints); + const query = new Parse.Query(TestObject); + query.equalTo('polygon', closedPolygon); + return query.find(); + }).then((results) => { + const polygon = results[0].get('polygon'); + equal(polygon.__type, 'Polygon'); + equal(polygon.coordinates, closedPoints); done(); }, done.fail); }); it('polygon update', (done) => { - const oldCoords = [[0,0],[0,1],[1,0],[1,1]]; + const oldCoords = [[0,0],[0,1],[1,1],[1,0]]; const oldPolygon = {__type: 'Polygon', coordinates: oldCoords}; const newCoords = [[2,2],[2,3],[3,3],[3,2]]; const newPolygon = {__type: 'Polygon', coordinates: newCoords}; @@ -89,6 +97,36 @@ describe('Parse.Polygon testing', () => { }).then(done.fail, done); }); + it('polygon three points minimum', (done) => { + const coords = [[0,0]]; + const obj = new TestObject(); + obj.set('polygon', {__type: 'Polygon', coordinates: coords}); + obj.save().then(done.fail, done); + }); + + it('polygon three different points minimum', (done) => { + const coords = [[0,0],[0,1],[0,0]]; + const obj = new TestObject(); + obj.set('polygon', {__type: 'Polygon', coordinates: coords}); + obj.save().then(done.fail, done); + }); + + it('polygon counterclockwise', (done) => { + const coords = [[1,1],[0,1],[0,0],[1,0]]; + const closed = [[1,1],[0,1],[0,0],[1,0],[1,1]]; + const obj = new TestObject(); + obj.set('polygon', {__type: 'Polygon', coordinates: coords}); + obj.save().then(() => { + const query = new Parse.Query(TestObject); + return query.get(obj.id); + }).then((result) => { + const polygon = result.get('polygon'); + equal(polygon.__type, 'Polygon'); + equal(polygon.coordinates, closed); + done(); + }, done.fail); + }); + it('polygonContain query', (done) => { const points1 = [[0,0],[0,1],[1,1],[1,0]]; const points2 = [[0,0],[0,2],[2,2],[2,0]]; @@ -168,32 +206,29 @@ describe('Parse.Polygon testing', () => { }); }); -const buildIndexes = () => { - const databaseAdapter = new MongoStorageAdapter({ uri: mongoURI }); - return reconfigureServer({ - appId: 'test', - restAPIKey: 'rest', - publicServerURL: 'http://localhost:8378/1', - databaseAdapter - }).then(() => { - return databaseAdapter.createIndex('TestObject', {location: '2d'}); - }).then(() => { - return databaseAdapter.createIndex('TestObject', {polygon: '2dsphere'}); - }); -}; - describe_only_db('mongo')('Parse.Polygon testing', () => { it('support 2d and 2dsphere', (done) => { const coords = [[0,0],[0,1],[1,1],[1,0],[0,0]]; const polygon = {__type: 'Polygon', coordinates: coords}; const location = {__type: 'GeoPoint', latitude:10, longitude:10}; - buildIndexes().then(() => { + const databaseAdapter = new MongoStorageAdapter({ uri: mongoURI }); + return reconfigureServer({ + appId: 'test', + restAPIKey: 'rest', + publicServerURL: 'http://localhost:8378/1', + databaseAdapter + }).then(() => { + return databaseAdapter.createIndex('TestObject', {location: '2d'}); + }).then(() => { + return databaseAdapter.createIndex('TestObject', {polygon: '2dsphere'}); + }).then(() => { return rp.post({ url: 'http://localhost:8378/1/classes/TestObject', json: { '_method': 'POST', location, - polygon + polygon, + polygon2: polygon }, headers: defaultHeaders }); @@ -206,66 +241,19 @@ describe_only_db('mongo')('Parse.Polygon testing', () => { }).then((resp) => { equal(resp.location, location); equal(resp.polygon, polygon); + equal(resp.polygon2, polygon); + return databaseAdapter.getIndexes('TestObject'); + }).then((indexes) => { + equal(indexes.length, 3); + equal(indexes[0].key, {_id: 1}); + equal(indexes[1].key, {location: '2d'}); + equal(indexes[2].key, {polygon: '2dsphere'}); done(); }, done.fail); }); - it('polygon three points minimum', (done) => { - const coords = [[0,0]]; - const obj = new TestObject(); - obj.set('polygon', {__type: 'Polygon', coordinates: coords}); - buildIndexes().then(() => { - return obj.save(); - }).then(done.fail, done); - }); - - it('polygon three different points minimum', (done) => { - const coords = [[0,0],[0,1]]; - const obj = new TestObject(); - obj.set('polygon', {__type: 'Polygon', coordinates: coords}); - buildIndexes().then(() => { - return obj.save(); - }).then(done.fail, done); - }); - - it('polygonContain query with indexes', (done) => { - const points1 = [[0,0],[0,1],[1,1],[1,0]]; - const points2 = [[0,0],[0,2],[2,2],[2,0]]; - const points3 = [[10,10],[10,15],[15,15],[15,10],[10,10]]; - const polygon1 = {__type: 'Polygon', coordinates: points1}; - const polygon2 = {__type: 'Polygon', coordinates: points2}; - const polygon3 = {__type: 'Polygon', coordinates: points3}; - const obj1 = new TestObject({polygon: polygon1}); - const obj2 = new TestObject({polygon: polygon2}); - const obj3 = new TestObject({polygon: polygon3}); - buildIndexes().then(() => { - return Parse.Object.saveAll([obj1, obj2, obj3]); - }).then(() => { - const where = { - polygon: { - $geoIntersects: { - $point: { __type: 'GeoPoint', latitude: 0.5, longitude: 0.5 } - } - } - }; - return rp.post({ - url: Parse.serverURL + '/classes/TestObject', - json: { where, '_method': 'GET' }, - headers: { - 'X-Parse-Application-Id': Parse.applicationId, - 'X-Parse-Javascript-Key': Parse.javaScriptKey - } - }); - }).then((resp) => { - expect(resp.results.length).toBe(2); - done(); - }, done.fail); - }); -}); - -describe_only_db('postgres')('[postgres] Parse.Polygon testing', () => { - it('polygon three different points minimum', (done) => { - const coords = [[0,0],[0,1]]; + it('polygon loop is not valid', (done) => { + const coords = [[0,0],[0,1],[1,0],[1,1]]; const obj = new TestObject(); obj.set('polygon', {__type: 'Polygon', coordinates: coords}); obj.save().then(done.fail, done); diff --git a/src/Adapters/Storage/Mongo/MongoStorageAdapter.js b/src/Adapters/Storage/Mongo/MongoStorageAdapter.js index ec8267a9a3..5c29b550bb 100644 --- a/src/Adapters/Storage/Mongo/MongoStorageAdapter.js +++ b/src/Adapters/Storage/Mongo/MongoStorageAdapter.js @@ -181,7 +181,8 @@ export class MongoStorageAdapter { addFieldIfNotExists(className, fieldName, type) { return this._schemaCollection() - .then(schemaCollection => schemaCollection.addFieldIfNotExists(className, fieldName, type)); + .then(schemaCollection => schemaCollection.addFieldIfNotExists(className, fieldName, type)) + .then(() => this.create2dsphereIndex(className, fieldName, type)); } // Drops a collection. Resolves with true if it was a Parse Schema (eg. _User, Custom, etc.) @@ -428,6 +429,20 @@ export class MongoStorageAdapter { return this._adaptiveCollection(className) .then(collection => collection._mongoCollection.createIndex(index)); } + + create2dsphereIndex(className, fieldName, type) { + if (type && type.type === 'Polygon') { + const index = {} + index[fieldName] = '2dsphere'; + return this.createIndex(className, index); + } + return Promise.resolve(); + } + + getIndexes(className) { + return this._adaptiveCollection(className) + .then(collection => collection._mongoCollection.indexes()); + } } export default MongoStorageAdapter; From d99f9d1d2d3d5c85eea82dc16c8cbd6dcbb48ff4 Mon Sep 17 00:00:00 2001 From: Diamond Lewis Date: Fri, 23 Jun 2017 22:31:08 -0500 Subject: [PATCH 08/12] index test --- spec/ParsePolygon.spec.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/spec/ParsePolygon.spec.js b/spec/ParsePolygon.spec.js index e7fa17dacc..44dd66fd90 100644 --- a/spec/ParsePolygon.spec.js +++ b/spec/ParsePolygon.spec.js @@ -244,10 +244,11 @@ describe_only_db('mongo')('Parse.Polygon testing', () => { equal(resp.polygon2, polygon); return databaseAdapter.getIndexes('TestObject'); }).then((indexes) => { - equal(indexes.length, 3); + equal(indexes.length, 4); equal(indexes[0].key, {_id: 1}); equal(indexes[1].key, {location: '2d'}); equal(indexes[2].key, {polygon: '2dsphere'}); + equal(indexes[3].key, {polygon2: '2dsphere'}); done(); }, done.fail); }); From 191cbfeadf330577d0d0011e00db97772909c459 Mon Sep 17 00:00:00 2001 From: Diamond Lewis Date: Fri, 23 Jun 2017 22:46:08 -0500 Subject: [PATCH 09/12] postgres test fix --- .../Storage/Postgres/PostgresStorageAdapter.js | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/Adapters/Storage/Postgres/PostgresStorageAdapter.js b/src/Adapters/Storage/Postgres/PostgresStorageAdapter.js index 1bbb0eb1fc..1b974cd006 100644 --- a/src/Adapters/Storage/Postgres/PostgresStorageAdapter.js +++ b/src/Adapters/Storage/Postgres/PostgresStorageAdapter.js @@ -1359,6 +1359,24 @@ function convertPolygonToSQL(polygon) { polygon[0][1] !== polygon[polygon.length - 1][1]) { polygon.push(polygon[0]); } + const unique = polygon.filter((item, index, ar) => { + let foundIndex = -1; + for (let i = 0; i < ar.length; i += 1) { + const pt = ar[i]; + if (pt[0] === item[0] && + pt[1] === item[1]) { + foundIndex = i; + break; + } + } + return foundIndex === index; + }); + if (unique.length < 3) { + throw new Parse.Error( + Parse.Error.INTERNAL_SERVER_ERROR, + 'GeoJSON: Loop must have at least 3 different vertices' + ); + } const points = polygon.map((point) => { Parse.GeoPoint._validate(parseFloat(point[1]), parseFloat(point[0])); return `(${point[1]}, ${point[0]})`; From da35b71d776259974388ff185398936b0d22e2f6 Mon Sep 17 00:00:00 2001 From: Diamond Lewis Date: Fri, 7 Jul 2017 16:04:14 -0500 Subject: [PATCH 10/12] remove invalid loop test --- spec/ParsePolygon.spec.js | 7 ------- 1 file changed, 7 deletions(-) diff --git a/spec/ParsePolygon.spec.js b/spec/ParsePolygon.spec.js index 44dd66fd90..67b08d7f4a 100644 --- a/spec/ParsePolygon.spec.js +++ b/spec/ParsePolygon.spec.js @@ -252,11 +252,4 @@ describe_only_db('mongo')('Parse.Polygon testing', () => { done(); }, done.fail); }); - - it('polygon loop is not valid', (done) => { - const coords = [[0,0],[0,1],[1,0],[1,1]]; - const obj = new TestObject(); - obj.set('polygon', {__type: 'Polygon', coordinates: coords}); - obj.save().then(done.fail, done); - }); }); From 61d8540a2018218329dab9c29045e7022f1149b2 Mon Sep 17 00:00:00 2001 From: Diamond Lewis Date: Fri, 7 Jul 2017 17:12:45 -0500 Subject: [PATCH 11/12] add invalid loop test --- spec/ParsePolygon.spec.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/spec/ParsePolygon.spec.js b/spec/ParsePolygon.spec.js index 67b08d7f4a..44dd66fd90 100644 --- a/spec/ParsePolygon.spec.js +++ b/spec/ParsePolygon.spec.js @@ -252,4 +252,11 @@ describe_only_db('mongo')('Parse.Polygon testing', () => { done(); }, done.fail); }); + + it('polygon loop is not valid', (done) => { + const coords = [[0,0],[0,1],[1,0],[1,1]]; + const obj = new TestObject(); + obj.set('polygon', {__type: 'Polygon', coordinates: coords}); + obj.save().then(done.fail, done); + }); }); From 2eaca5dae0dc20c9ed689f9bddd0d19da18f2fc2 Mon Sep 17 00:00:00 2001 From: Diamond Lewis Date: Tue, 11 Jul 2017 02:08:03 -0500 Subject: [PATCH 12/12] nit --- src/Adapters/Storage/Mongo/MongoSchemaCollection.js | 4 ++-- src/Adapters/Storage/Mongo/MongoStorageAdapter.js | 9 +++++---- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/Adapters/Storage/Mongo/MongoSchemaCollection.js b/src/Adapters/Storage/Mongo/MongoSchemaCollection.js index bebd6ed24c..5b7d9b9857 100644 --- a/src/Adapters/Storage/Mongo/MongoSchemaCollection.js +++ b/src/Adapters/Storage/Mongo/MongoSchemaCollection.js @@ -25,7 +25,7 @@ function mongoFieldToParseSchemaField(type) { case 'geopoint': return {type: 'GeoPoint'}; case 'file': return {type: 'File'}; case 'bytes': return {type: 'Bytes'}; - case 'polygon': return {type: 'Polygon'}; + case 'polygon': return {type: 'Polygon'}; } } @@ -99,7 +99,7 @@ function parseFieldTypeToMongoFieldType({ type, targetClass }) { case 'GeoPoint': return 'geopoint'; case 'File': return 'file'; case 'Bytes': return 'bytes'; - case 'Polygon': return 'polygon'; + case 'Polygon': return 'polygon'; } } diff --git a/src/Adapters/Storage/Mongo/MongoStorageAdapter.js b/src/Adapters/Storage/Mongo/MongoStorageAdapter.js index 591748525e..4bac1d24dd 100644 --- a/src/Adapters/Storage/Mongo/MongoStorageAdapter.js +++ b/src/Adapters/Storage/Mongo/MongoStorageAdapter.js @@ -183,7 +183,7 @@ export class MongoStorageAdapter { addFieldIfNotExists(className, fieldName, type) { return this._schemaCollection() .then(schemaCollection => schemaCollection.addFieldIfNotExists(className, fieldName, type)) - .then(() => this.create2dsphereIndex(className, fieldName, type)); + .then(() => this.createIndexesIfNeeded(className, fieldName, type)); } // Drops a collection. Resolves with true if it was a Parse Schema (eg. _User, Custom, etc.) @@ -431,10 +431,11 @@ export class MongoStorageAdapter { .then(collection => collection._mongoCollection.createIndex(index)); } - create2dsphereIndex(className, fieldName, type) { + createIndexesIfNeeded(className, fieldName, type) { if (type && type.type === 'Polygon') { - const index = {} - index[fieldName] = '2dsphere'; + const index = { + [fieldName]: '2dsphere' + }; return this.createIndex(className, index); } return Promise.resolve();