diff --git a/spec/InstallationsRouter.spec.js b/spec/InstallationsRouter.spec.js index 82416aa42f..2d7224a0d6 100644 --- a/spec/InstallationsRouter.spec.js +++ b/spec/InstallationsRouter.spec.js @@ -123,11 +123,9 @@ describe('InstallationsRouter', () => { var router = new InstallationsRouter(); rest.create(config, auth.nobody(config), '_Installation', androidDeviceRequest) - .then(() => { - return rest.create(config, auth.nobody(config), '_Installation', iosDeviceRequest); - }).then(() => { - return router.handleFind(request); - }).then((res) => { + .then(() => rest.create(config, auth.nobody(config), '_Installation', iosDeviceRequest)) + .then(() => router.handleFind(request)) + .then((res) => { var response = res.response; expect(response.results.length).toEqual(2); expect(response.count).toEqual(2); diff --git a/spec/MongoTransform.spec.js b/spec/MongoTransform.spec.js index ca142134f0..f667a2eee4 100644 --- a/spec/MongoTransform.spec.js +++ b/spec/MongoTransform.spec.js @@ -17,9 +17,6 @@ var dummySchema = { } return; }, - getRelationFields: function() { - return {} - } }; @@ -39,7 +36,7 @@ describe('parseObjectToMongoObjectForCreate', () => { createdAt: "2015-10-06T21:24:50.332Z", updatedAt: "2015-10-06T21:24:50.332Z" }; - var output = transform.parseObjectToMongoObjectForCreate(dummySchema, null, input); + var output = transform.parseObjectToMongoObjectForCreate(dummySchema, null, input, { fields: {} }); expect(output._created_at instanceof Date).toBe(true); expect(output._updated_at instanceof Date).toBe(true); done(); @@ -62,14 +59,14 @@ describe('parseObjectToMongoObjectForCreate', () => { //have __op delete in a new object. Figure out what this should actually be testing. notWorking('a delete op', (done) => { var input = {deleteMe: {__op: 'Delete'}}; - var output = transform.parseObjectToMongoObjectForCreate(dummySchema, null, input); + var output = transform.parseObjectToMongoObjectForCreate(dummySchema, null, input, { fields: {} }); jequal(output, {}); done(); }); it('basic ACL', (done) => { var input = {ACL: {'0123': {'read': true, 'write': true}}}; - var output = transform.parseObjectToMongoObjectForCreate(dummySchema, null, input); + var output = transform.parseObjectToMongoObjectForCreate(dummySchema, null, input, { fields: {} }); // This just checks that it doesn't crash, but it should check format. done(); }); @@ -124,7 +121,7 @@ describe('transformWhere', () => { describe('mongoObjectToParseObject', () => { it('built-in timestamps', (done) => { var input = {createdAt: new Date(), updatedAt: new Date()}; - var output = transform.mongoObjectToParseObject(dummySchema, null, input); + var output = transform.mongoObjectToParseObject(null, input, { fields: {} }); expect(typeof output.createdAt).toEqual('string'); expect(typeof output.updatedAt).toEqual('string'); done(); @@ -132,7 +129,9 @@ describe('mongoObjectToParseObject', () => { it('pointer', (done) => { var input = {_p_userPointer: '_User$123'}; - var output = transform.mongoObjectToParseObject(dummySchema, null, input); + var output = transform.mongoObjectToParseObject(null, input, { + fields: { userPointer: { type: 'Pointer', targetClass: '_User' } }, + }); expect(typeof output.userPointer).toEqual('object'); expect(output.userPointer).toEqual( {__type: 'Pointer', className: '_User', objectId: '123'} @@ -142,14 +141,18 @@ describe('mongoObjectToParseObject', () => { it('null pointer', (done) => { var input = {_p_userPointer: null}; - var output = transform.mongoObjectToParseObject(dummySchema, null, input); + var output = transform.mongoObjectToParseObject(null, input, { + fields: { userPointer: { type: 'Pointer', targetClass: '_User' } }, + }); expect(output.userPointer).toBeUndefined(); done(); }); it('file', (done) => { var input = {picture: 'pic.jpg'}; - var output = transform.mongoObjectToParseObject(dummySchema, null, input); + var output = transform.mongoObjectToParseObject(null, input, { + fields: { picture: { type: 'File' }}, + }); expect(typeof output.picture).toEqual('object'); expect(output.picture).toEqual({__type: 'File', name: 'pic.jpg'}); done(); @@ -157,7 +160,9 @@ describe('mongoObjectToParseObject', () => { it('geopoint', (done) => { var input = {location: [180, -180]}; - var output = transform.mongoObjectToParseObject(dummySchema, null, input); + 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} @@ -167,7 +172,9 @@ describe('mongoObjectToParseObject', () => { it('nested array', (done) => { var input = {arr: [{_testKey: 'testValue' }]}; - var output = transform.mongoObjectToParseObject(dummySchema, null, input); + var output = transform.mongoObjectToParseObject(null, input, { + fields: { arr: { type: 'Array' } }, + }); expect(Array.isArray(output.arr)).toEqual(true); expect(output.arr).toEqual([{ _testKey: 'testValue'}]); done(); @@ -185,7 +192,9 @@ describe('mongoObjectToParseObject', () => { }, regularKey: "some data", }]} - let output = transform.mongoObjectToParseObject(dummySchema, null, input); + let output = transform.mongoObjectToParseObject(null, input, { + fields: { array: { type: 'Array' }}, + }); expect(dd(output, input)).toEqual(undefined); done(); }); @@ -224,7 +233,7 @@ describe('transform schema key changes', () => { "Kevin": { "write": true } } }; - var output = transform.parseObjectToMongoObjectForCreate(dummySchema, null, input); + var output = transform.parseObjectToMongoObjectForCreate(dummySchema, null, input, { fields: {} }); expect(typeof output._rperm).toEqual('object'); expect(typeof output._wperm).toEqual('object'); expect(output.ACL).toBeUndefined(); @@ -241,7 +250,7 @@ describe('transform schema key changes', () => { } }; - var output = transform.parseObjectToMongoObjectForCreate(dummySchema, null, input); + var output = transform.parseObjectToMongoObjectForCreate(dummySchema, null, input, { fields: {} }); expect(typeof output._acl).toEqual('object'); expect(output._acl["Kevin"].w).toBeTruthy(); expect(output._acl["Kevin"].r).toBeUndefined(); @@ -253,7 +262,7 @@ describe('transform schema key changes', () => { _rperm: ["*"], _wperm: ["Kevin"] }; - var output = transform.mongoObjectToParseObject(dummySchema, null, input); + var output = transform.mongoObjectToParseObject(null, input, { fields: {} }); expect(typeof output.ACL).toEqual('object'); expect(output._rperm).toBeUndefined(); expect(output._wperm).toBeUndefined(); @@ -267,7 +276,12 @@ describe('transform schema key changes', () => { long: mongodb.Long.fromNumber(Number.MAX_SAFE_INTEGER), double: new mongodb.Double(Number.MAX_VALUE) } - var output = transform.mongoObjectToParseObject(dummySchema, null, input); + var output = transform.mongoObjectToParseObject(null, input, { + fields: { + long: { type: 'Number' }, + double: { type: 'Number' }, + }, + }); expect(output.long).toBe(Number.MAX_SAFE_INTEGER); expect(output.double).toBe(Number.MAX_VALUE); done(); diff --git a/spec/ParseAPI.spec.js b/spec/ParseAPI.spec.js index 80fe442c1f..cceb818f9d 100644 --- a/spec/ParseAPI.spec.js +++ b/spec/ParseAPI.spec.js @@ -1121,7 +1121,7 @@ describe('miscellaneous', function() { }) }); - it('does not change inner object key names _auth_data_something', done => { + it('does not change inner object keys named _auth_data_something', done => { new Parse.Object('O').save({ innerObj: {_auth_data_facebook: 7}}) .then(object => new Parse.Query('O').get(object.id)) .then(object => { diff --git a/src/Adapters/Storage/Mongo/MongoStorageAdapter.js b/src/Adapters/Storage/Mongo/MongoStorageAdapter.js index b4e1f84eb1..c9bb6d6706 100644 --- a/src/Adapters/Storage/Mongo/MongoStorageAdapter.js +++ b/src/Adapters/Storage/Mongo/MongoStorageAdapter.js @@ -198,11 +198,18 @@ export class MongoStorageAdapter { } // Executes a find. Accepts: className, query in Parse format, and { skip, limit, sort }. - // Accepts the schemaController for legacy reasons. - find(className, query, { skip, limit, sort }, schemaController) { + find(className, query, schema, { skip, limit, sort }) { + let mongoWhere = this.transform.transformWhere(className, query, schema); + let mongoSort = _.mapKeys(sort, (value, fieldName) => transform.transformKey(className, fieldName, schema)); return this.adaptiveCollection(className) - .then(collection => collection.find(query, { skip, limit, sort })) - .then(objects => objects.map(object => transform.mongoObjectToParseObject(schemaController, className, object))); + .then(collection => collection.find(mongoWhere, { skip, limit, sort: mongoSort })) + .then(objects => objects.map(object => transform.mongoObjectToParseObject(className, object, schema))); + } + + // Executs a count. + count(className, query, schema) { + return this.adaptiveCollection(className) + .then(collection => collection.count(transform.transformWhere(className, query, schema))); } get transform() { diff --git a/src/Adapters/Storage/Mongo/MongoTransform.js b/src/Adapters/Storage/Mongo/MongoTransform.js index 3bb008f78a..5b7cc1e3d2 100644 --- a/src/Adapters/Storage/Mongo/MongoTransform.js +++ b/src/Adapters/Storage/Mongo/MongoTransform.js @@ -755,7 +755,7 @@ const nestedMongoObjectToNestedParseObject = mongoObject => { // Converts from a mongo-format object to a REST-format object. // Does not strip out anything based on a lack of authentication. -const mongoObjectToParseObject = (schema, className, mongoObject) => { +const mongoObjectToParseObject = (className, mongoObject, schema) => { switch(typeof mongoObject) { case 'string': case 'number': @@ -830,17 +830,11 @@ const mongoObjectToParseObject = (schema, className, mongoObject) => { if (key.indexOf('_p_') == 0) { var newKey = key.substring(3); - var expected; - if (schema && schema.getExpectedType) { - expected = schema.getExpectedType(className, newKey); - } - if (!expected) { - log.info('transform.js', - 'Found a pointer column not in the schema, dropping it.', - className, newKey); + if (!schema.fields[newKey]) { + log.info('transform.js', 'Found a pointer column not in the schema, dropping it.', className, newKey); break; } - if (expected && expected.type !== 'Pointer') { + if (schema.fields[newKey].type !== 'Pointer') { log.info('transform.js', 'Found a pointer in a non-pointer column, dropping it.', className, key); break; } @@ -848,8 +842,7 @@ const mongoObjectToParseObject = (schema, className, mongoObject) => { break; } var objData = mongoObject[key].split('$'); - var newClass = (expected ? expected.targetClass : objData[0]); - if (objData[0] !== newClass) { + if (objData[0] !== schema.fields[newKey].targetClass) { throw 'pointer to incorrect className'; } restObject[newKey] = { @@ -861,13 +854,12 @@ const mongoObjectToParseObject = (schema, className, mongoObject) => { } else if (key[0] == '_' && key != '__type') { throw ('bad key in untransform: ' + key); } else { - var expectedType = schema.getExpectedType(className, key); var value = mongoObject[key]; - if (expectedType && expectedType.type === 'File' && FileCoder.isValidDatabaseObject(value)) { + if (schema.fields[key] && schema.fields[key].type === 'File' && FileCoder.isValidDatabaseObject(value)) { restObject[key] = FileCoder.databaseToJSON(value); break; } - if (expectedType && expectedType.type === 'GeoPoint' && GeoPointCoder.isValidDatabaseObject(value)) { + if (schema.fields[key] && schema.fields[key].type === 'GeoPoint' && GeoPointCoder.isValidDatabaseObject(value)) { restObject[key] = GeoPointCoder.databaseToJSON(value); break; } @@ -876,7 +868,16 @@ const mongoObjectToParseObject = (schema, className, mongoObject) => { } } - return { ...restObject, ...schema.getRelationFields(className) }; + const relationFieldNames = Object.keys(schema.fields).filter(fieldName => schema.fields[fieldName].type === 'Relation'); + let relationFields = {}; + relationFieldNames.forEach(relationFieldName => { + relationFields[relationFieldName] = { + __type: 'Relation', + className: schema.fields[relationFieldName].targetClass, + } + }); + + return { ...restObject, ...relationFields }; default: throw 'unknown js type'; } diff --git a/src/Controllers/DatabaseController.js b/src/Controllers/DatabaseController.js index 67240a9128..76fc5589cc 100644 --- a/src/Controllers/DatabaseController.js +++ b/src/Controllers/DatabaseController.js @@ -628,16 +628,9 @@ DatabaseController.prototype.find = function(className, query, { skip, limit, acl, - sort, + sort = {}, count, } = {}) { - let mongoOptions = {}; - if (skip) { - mongoOptions.skip = skip; - } - if (limit) { - mongoOptions.limit = limit; - } let isMaster = acl === undefined; let aclGroup = acl || []; let op = typeof query.objectId == 'string' && Object.keys(query).length === 1 ? 'get' : 'find'; @@ -653,34 +646,29 @@ DatabaseController.prototype.find = function(className, query, { throw error; }) .then(schema => { - if (sort) { - mongoOptions.sort = {}; - for (let fieldName in sort) { - // Parse.com treats queries on _created_at and _updated_at as if they were queries on createdAt and updatedAt, - // so duplicate that behaviour here. - if (fieldName === '_created_at') { - fieldName = 'createdAt'; - sort['createdAt'] = sort['_created_at']; - } else if (fieldName === '_updated_at') { - fieldName = 'updatedAt'; - sort['updatedAt'] = sort['_updated_at']; - } - - if (!SchemaController.fieldNameIsValid(fieldName)) { - throw new Parse.Error(Parse.Error.INVALID_KEY_NAME, `Invalid field name: ${fieldName}.`); - } - if (fieldName.match(/^authData\.([a-zA-Z0-9_]+)\.id$/)) { - throw new Parse.Error(Parse.Error.INVALID_KEY_NAME, `Cannot sort by ${fieldName}`); - } - const mongoKey = this.transform.transformKey(className, fieldName, schema); - mongoOptions.sort[mongoKey] = sort[fieldName]; - } + // Parse.com treats queries on _created_at and _updated_at as if they were queries on createdAt and updatedAt, + // so duplicate that behaviour here. If both are specified, the corrent behaviour to match Parse.com is to + // use the one that appears first in the sort list. + if (sort._created_at) { + sort.createdAt = sort._created_at; + delete sort._created_at; + } + if (sort._updated_at) { + sort.updatedAt = sort._updated_at; + delete sort._updated_at; } + Object.keys(sort).forEach(fieldName => { + if (fieldName.match(/^authData\.([a-zA-Z0-9_]+)\.id$/)) { + throw new Parse.Error(Parse.Error.INVALID_KEY_NAME, `Cannot sort by ${fieldName}`); + } + if (!SchemaController.fieldNameIsValid(fieldName)) { + throw new Parse.Error(Parse.Error.INVALID_KEY_NAME, `Invalid field name: ${fieldName}.`); + } + }); return (isMaster ? Promise.resolve() : schemaController.validatePermission(className, aclGroup, op)) .then(() => this.reduceRelationKeys(className, query)) .then(() => this.reduceInRelation(className, query, schemaController)) - .then(() => this.adapter.adaptiveCollection(className)) - .then(collection => { + .then(() => { if (!isMaster) { query = this.addPointerPermissions(schemaController, className, op, query, aclGroup); } @@ -696,12 +684,10 @@ DatabaseController.prototype.find = function(className, query, { query = addReadACL(query, aclGroup); } validateQuery(query); - let mongoWhere = this.transform.transformWhere(className, query, schema); if (count) { - delete mongoOptions.limit; - return collection.count(mongoWhere, mongoOptions); + return this.adapter.count(className, query, schema); } else { - return this.adapter.find(className, mongoWhere, mongoOptions, schemaController) + return this.adapter.find(className, query, schema, { skip, limit, sort }) .then(objects => objects.map(object => filterSensitiveData(isMaster, aclGroup, className, object))); } }); diff --git a/src/Controllers/SchemaController.js b/src/Controllers/SchemaController.js index b3fdc7bb77..f855021974 100644 --- a/src/Controllers/SchemaController.js +++ b/src/Controllers/SchemaController.js @@ -686,23 +686,6 @@ class SchemaController { hasClass(className) { return this.reloadData().then(() => !!(this.data[className])); } - - getRelationFields(className) { - if (this.data && this.data[className]) { - let classData = this.data[className]; - return Object.keys(classData).filter((field) => { - return classData[field].type === 'Relation'; - }).reduce((memo, field) => { - let type = classData[field]; - memo[field] = { - __type: 'Relation', - className: type.targetClass - }; - return memo; - }, {}); - } - return {}; - } } // Returns a promise for a new Schema.