From 21e73ac276d1d2fbd5642d98b56e55016e780a87 Mon Sep 17 00:00:00 2001 From: Drew Gross Date: Mon, 25 Apr 2016 17:21:24 -0700 Subject: [PATCH 01/17] Destructuring --- src/Adapters/Storage/Mongo/MongoTransform.js | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/Adapters/Storage/Mongo/MongoTransform.js b/src/Adapters/Storage/Mongo/MongoTransform.js index 34f0f157..87ef89b0 100644 --- a/src/Adapters/Storage/Mongo/MongoTransform.js +++ b/src/Adapters/Storage/Mongo/MongoTransform.js @@ -187,15 +187,13 @@ export function transformKeyValue(schema, className, restKey, restValue, { // restWhere is the "where" clause in REST API form. // Returns the mongo form of the query. // Throws a Parse.Error if the input query is invalid. -function transformWhere(schema, className, restWhere, options = {validate: true}) { +function transformWhere(schema, className, restWhere, { validate = true } = {}) { let mongoWhere = {}; if (restWhere['ACL']) { throw new Parse.Error(Parse.Error.INVALID_QUERY, 'Cannot query on ACL.'); } - let transformKeyOptions = {query: true}; - transformKeyOptions.validate = options.validate; for (let restKey in restWhere) { - let out = transformKeyValue(schema, className, restKey, restWhere[restKey], transformKeyOptions); + let out = transformKeyValue(schema, className, restKey, restWhere[restKey], { query: true, validate }); mongoWhere[out.key] = out.value; } return mongoWhere; From e0a9f198952987f023e3ade718eeb1c59bd38a26 Mon Sep 17 00:00:00 2001 From: Drew Gross Date: Mon, 25 Apr 2016 18:22:20 -0700 Subject: [PATCH 02/17] Split logic for transforming queries into it's own function --- src/Adapters/Storage/Mongo/MongoTransform.js | 199 +++++++++++++------ 1 file changed, 140 insertions(+), 59 deletions(-) diff --git a/src/Adapters/Storage/Mongo/MongoTransform.js b/src/Adapters/Storage/Mongo/MongoTransform.js index 87ef89b0..95c5fee5 100644 --- a/src/Adapters/Storage/Mongo/MongoTransform.js +++ b/src/Adapters/Storage/Mongo/MongoTransform.js @@ -11,9 +11,6 @@ var Parse = require('parse/node').Parse; // // There are several options that can help transform: // -// query: true indicates that query constraints like $lt are allowed in -// the value. -// // update: true indicates that __op operators like Add and Delete // in the value are converted to a mongo update form. Otherwise they are // converted to static data. @@ -21,10 +18,9 @@ var Parse = require('parse/node').Parse; // validate: true indicates that key names are to be validated. // // Returns an object with {key: key, value: value}. -export function transformKeyValue(schema, className, restKey, restValue, { +function transformKeyValue(schema, className, restKey, restValue, { inArray, inObject, - query, update, validate, } = {}) { @@ -66,47 +62,17 @@ export function transformKeyValue(schema, className, restKey, restValue, { return {key: key, value: restValue}; break; case '$or': - if (!query) { - throw new Parse.Error(Parse.Error.INVALID_KEY_NAME, - 'you can only use $or in queries'); - } - if (!(restValue instanceof Array)) { - throw new Parse.Error(Parse.Error.INVALID_QUERY, - 'bad $or format - use an array value'); - } - var mongoSubqueries = restValue.map((s) => { - return transformWhere(schema, className, s); - }); - return {key: '$or', value: mongoSubqueries}; + throw new Parse.Error(Parse.Error.INVALID_KEY_NAME, 'you can only use $or in queries'); case '$and': - if (!query) { - throw new Parse.Error(Parse.Error.INVALID_KEY_NAME, - 'you can only use $and in queries'); - } - if (!(restValue instanceof Array)) { - throw new Parse.Error(Parse.Error.INVALID_QUERY, - 'bad $and format - use an array value'); - } - var mongoSubqueries = restValue.map((s) => { - return transformWhere(schema, className, s); - }); - return {key: '$and', value: mongoSubqueries}; + throw new Parse.Error(Parse.Error.INVALID_KEY_NAME, 'you can only use $and in queries'); default: // Other auth data var authDataMatch = key.match(/^authData\.([a-zA-Z0-9_]+)\.id$/); if (authDataMatch) { - if (query) { - var provider = authDataMatch[1]; - // Special-case auth data. - return {key: '_auth_data_'+provider+'.id', value: restValue}; - } - throw new Parse.Error(Parse.Error.INVALID_KEY_NAME, - 'can only query on ' + key); - break; - }; + throw new Parse.Error(Parse.Error.INVALID_KEY_NAME, 'can only query on ' + key); + } if (validate && !key.match(/^[a-zA-Z][a-zA-Z0-9_\.]*$/)) { - throw new Parse.Error(Parse.Error.INVALID_KEY_NAME, - 'invalid key name: ' + key); + throw new Parse.Error(Parse.Error.INVALID_KEY_NAME, 'invalid key name: ' + key); } } @@ -123,20 +89,6 @@ export function transformKeyValue(schema, className, restKey, restValue, { } var expectedTypeIsArray = (expected && expected.type === 'Array'); - // Handle query constraints - if (query) { - value = transformConstraint(restValue, expectedTypeIsArray); - if (value !== CannotTransform) { - return {key: key, value: value}; - } - } - - if (expectedTypeIsArray && query && !(restValue instanceof Array)) { - return { - key: key, value: { '$all' : [restValue] } - }; - } - // Handle atomic values var value = transformAtom(restValue, false, { inArray, inObject }); if (value !== CannotTransform) { @@ -154,10 +106,6 @@ export function transformKeyValue(schema, className, restKey, restValue, { // Handle arrays if (restValue instanceof Array) { - if (query) { - throw new Parse.Error(Parse.Error.INVALID_JSON, - 'cannot use array as query param'); - } value = restValue.map((restObj) => { var out = transformKeyValue(schema, className, restKey, restObj, { inArray: true }); return out.value; @@ -182,6 +130,139 @@ export function transformKeyValue(schema, className, restKey, restValue, { return {key: key, value: value}; } +function transformQueryKeyValue(schema, className, restKey, restValue, { + inArray, + inObject, + update, + validate, +} = {}) { + // Check if the schema is known since it's a built-in field. + var key = restKey; + var timeField = false; + switch(key) { + case 'objectId': + case '_id': + key = '_id'; + break; + case 'createdAt': + case '_created_at': + key = '_created_at'; + timeField = true; + break; + case 'updatedAt': + case '_updated_at': + key = '_updated_at'; + timeField = true; + break; + case '_email_verify_token': + key = "_email_verify_token"; + break; + case '_perishable_token': + key = "_perishable_token"; + break; + case 'sessionToken': + case '_session_token': + key = '_session_token'; + break; + case 'expiresAt': + case '_expiresAt': + key = 'expiresAt'; + timeField = true; + break; + case '_rperm': + case '_wperm': + return {key: key, value: restValue}; + break; + case '$or': + if (!(restValue instanceof Array)) { + throw new Parse.Error(Parse.Error.INVALID_QUERY, 'bad $or format - use an array value'); + } + var mongoSubqueries = restValue.map((s) => { + return transformWhere(schema, className, s); + }); + return {key: '$or', value: mongoSubqueries}; + case '$and': + if (!(restValue instanceof Array)) { + throw new Parse.Error(Parse.Error.INVALID_QUERY, 'bad $and format - use an array value'); + } + var mongoSubqueries = restValue.map((s) => { + return transformWhere(schema, className, s); + }); + return {key: '$and', value: mongoSubqueries}; + default: + // Other auth data + var authDataMatch = key.match(/^authData\.([a-zA-Z0-9_]+)\.id$/); + if (authDataMatch ) { + var provider = authDataMatch[1]; + // Special-case auth data. + return {key: '_auth_data_'+provider+'.id', value: restValue}; + } + if (validate && !key.match(/^[a-zA-Z][a-zA-Z0-9_\.]*$/)) { + throw new Parse.Error(Parse.Error.INVALID_KEY_NAME, 'invalid key name: ' + key); + } + } + + // Handle special schema key changes + // TODO: it seems like this is likely to have edge cases where + // pointer types are missed + var expected = undefined; + if (schema && schema.getExpectedType) { + expected = schema.getExpectedType(className, key); + } + if ((expected && expected.type == 'Pointer') || + (!expected && restValue && restValue.__type == 'Pointer')) { + key = '_p_' + key; + } + var expectedTypeIsArray = (expected && expected.type === 'Array'); + + // Handle query constraints + value = transformConstraint(restValue, expectedTypeIsArray); + if (value !== CannotTransform) { + return {key: key, value: value}; + } + + if (expectedTypeIsArray && !(restValue instanceof Array)) { + return { + key: key, value: { '$all' : [restValue] } + }; + } + + // Handle atomic values + var value = transformAtom(restValue, false, { inArray, inObject }); + if (value !== CannotTransform) { + if (timeField && (typeof value === 'string')) { + value = new Date(value); + } + return {key: key, value: value}; + } + + // ACLs are handled before this method is called + // If an ACL key still exists here, something is wrong. + if (key === 'ACL') { + throw 'There was a problem transforming an ACL.'; + } + + // Handle arrays + if (restValue instanceof Array) { + throw new Parse.Error(Parse.Error.INVALID_JSON,'cannot use array as query param'); + } + + // Handle update operators + value = transformUpdateOperator(restValue, !update); + if (value !== CannotTransform) { + return {key: key, value: value}; + } + + // Handle normal objects by recursing + value = {}; + for (var subRestKey in restValue) { + var subRestValue = restValue[subRestKey]; + var out = transformKeyValue(schema, className, subRestKey, subRestValue, { inObject: true }); + // For recursed objects, keep the keys in rest format + value[subRestKey] = out.value; + } + return {key: key, value: value}; +} // Main exposed method to help run queries. // restWhere is the "where" clause in REST API form. @@ -193,7 +274,7 @@ function transformWhere(schema, className, restWhere, { validate = true } = {}) throw new Parse.Error(Parse.Error.INVALID_QUERY, 'Cannot query on ACL.'); } for (let restKey in restWhere) { - let out = transformKeyValue(schema, className, restKey, restWhere[restKey], { query: true, validate }); + let out = transformQueryKeyValue(schema, className, restKey, restWhere[restKey], { validate }); mongoWhere[out.key] = out.value; } return mongoWhere; From 5c7b346292e5661bd06d2f72584d3b05581cc6f5 Mon Sep 17 00:00:00 2001 From: Drew Gross Date: Mon, 25 Apr 2016 18:48:22 -0700 Subject: [PATCH 03/17] Remove unnecessary logic --- src/Adapters/Storage/Mongo/MongoTransform.js | 17 +++-------------- 1 file changed, 3 insertions(+), 14 deletions(-) diff --git a/src/Adapters/Storage/Mongo/MongoTransform.js b/src/Adapters/Storage/Mongo/MongoTransform.js index 95c5fee5..7526c9cb 100644 --- a/src/Adapters/Storage/Mongo/MongoTransform.js +++ b/src/Adapters/Storage/Mongo/MongoTransform.js @@ -130,12 +130,7 @@ function transformKeyValue(schema, className, restKey, restValue, { return {key: key, value: value}; } -function transformQueryKeyValue(schema, className, restKey, restValue, { - inArray, - inObject, - update, - validate, -} = {}) { +function transformQueryKeyValue(schema, className, restKey, restValue, { validate } = {}) { // Check if the schema is known since it's a built-in field. var key = restKey; var timeField = false; @@ -228,7 +223,7 @@ function transformQueryKeyValue(schema, className, restKey, restValue, { } // Handle atomic values - var value = transformAtom(restValue, false, { inArray, inObject }); + var value = transformAtom(restValue, false); if (value !== CannotTransform) { if (timeField && (typeof value === 'string')) { value = new Date(value); @@ -236,19 +231,13 @@ function transformQueryKeyValue(schema, className, restKey, restValue, { return {key: key, value: value}; } - // ACLs are handled before this method is called - // If an ACL key still exists here, something is wrong. - if (key === 'ACL') { - throw 'There was a problem transforming an ACL.'; - } - // Handle arrays if (restValue instanceof Array) { throw new Parse.Error(Parse.Error.INVALID_JSON,'cannot use array as query param'); } // Handle update operators - value = transformUpdateOperator(restValue, !update); + value = transformUpdateOperator(restValue, true); if (value !== CannotTransform) { return {key: key, value: value}; } From e2a321335157a7052f9eb663e0b026634509addd Mon Sep 17 00:00:00 2001 From: Drew Gross Date: Mon, 25 Apr 2016 19:34:07 -0700 Subject: [PATCH 04/17] Remove update logic from query --- src/Adapters/Storage/Mongo/MongoTransform.js | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/Adapters/Storage/Mongo/MongoTransform.js b/src/Adapters/Storage/Mongo/MongoTransform.js index 7526c9cb..783ff98e 100644 --- a/src/Adapters/Storage/Mongo/MongoTransform.js +++ b/src/Adapters/Storage/Mongo/MongoTransform.js @@ -236,12 +236,6 @@ function transformQueryKeyValue(schema, className, restKey, restValue, { validat throw new Parse.Error(Parse.Error.INVALID_JSON,'cannot use array as query param'); } - // Handle update operators - value = transformUpdateOperator(restValue, true); - if (value !== CannotTransform) { - return {key: key, value: value}; - } - // Handle normal objects by recursing value = {}; for (var subRestKey in restValue) { From ee8a3c94d51643f6957cc79e6a207402d6d3f661 Mon Sep 17 00:00:00 2001 From: Drew Gross Date: Mon, 25 Apr 2016 20:42:19 -0700 Subject: [PATCH 05/17] No need to transform post-transform keys in mongo adapter --- spec/ParseHooks.spec.js | 2 +- src/Adapters/Storage/Mongo/MongoTransform.js | 98 +++++++++----------- src/Auth.js | 6 +- src/Routers/GlobalConfigRouter.js | 4 +- src/Routers/SessionsRouter.js | 6 +- src/Routers/UsersRouter.js | 4 +- 6 files changed, 51 insertions(+), 69 deletions(-) diff --git a/spec/ParseHooks.spec.js b/spec/ParseHooks.spec.js index 2a0cec5a..d3c04f69 100644 --- a/spec/ParseHooks.spec.js +++ b/spec/ParseHooks.spec.js @@ -76,7 +76,7 @@ describe('Hooks', () => { }) }); - it("should CRUD a trigger registration", (done) => { + it("should CRUD a trigger registration", (done) => { // Create Parse.Hooks.createTrigger("MyClass","beforeDelete", "http://someurl").then((res) => { expect(res.className).toBe("MyClass"); diff --git a/src/Adapters/Storage/Mongo/MongoTransform.js b/src/Adapters/Storage/Mongo/MongoTransform.js index 783ff98e..c537a1c4 100644 --- a/src/Adapters/Storage/Mongo/MongoTransform.js +++ b/src/Adapters/Storage/Mongo/MongoTransform.js @@ -130,59 +130,52 @@ function transformKeyValue(schema, className, restKey, restValue, { return {key: key, value: value}; } -function transformQueryKeyValue(schema, className, restKey, restValue, { validate } = {}) { +const valueAsDate = value => { + if (typeof value === 'string') { + return new Date(value); + } else if (value instanceof Date) { + return value; + } + return false; +} + +function transformQueryKeyValue(schema, className, key, value, { validate } = {}) { // Check if the schema is known since it's a built-in field. - var key = restKey; - var timeField = false; switch(key) { - case 'objectId': - case '_id': - key = '_id'; - break; case 'createdAt': - case '_created_at': + if (valueAsDate(value)) { + return {key: '_created_at', value: valueAsDate(value)} + } key = '_created_at'; - timeField = true; break; case 'updatedAt': - case '_updated_at': + if (valueAsDate(value)) { + return {key: '_updated_at', value: valueAsDate(value)} + } key = '_updated_at'; - timeField = true; - break; - case '_email_verify_token': - key = "_email_verify_token"; - break; - case '_perishable_token': - key = "_perishable_token"; - break; - case 'sessionToken': - case '_session_token': - key = '_session_token'; break; case 'expiresAt': - case '_expiresAt': - key = 'expiresAt'; - timeField = true; + if (valueAsDate(value)) { + return {key: 'expiresAt', value: valueAsDate(value)} + } break; + case 'objectId': return {key: '_id', value} + case 'sessionToken': return {key: '_session_token', value} case '_rperm': case '_wperm': - return {key: key, value: restValue}; - break; + case '_perishable_token': + case '_email_verify_token': return {key, value} case '$or': - if (!(restValue instanceof Array)) { + if (!(value instanceof Array)) { throw new Parse.Error(Parse.Error.INVALID_QUERY, 'bad $or format - use an array value'); } - var mongoSubqueries = restValue.map((s) => { - return transformWhere(schema, className, s); - }); + var mongoSubqueries = value.map(subQuery => transformWhere(schema, className, subQuery)); return {key: '$or', value: mongoSubqueries}; case '$and': - if (!(restValue instanceof Array)) { + if (!(value instanceof Array)) { throw new Parse.Error(Parse.Error.INVALID_QUERY, 'bad $and format - use an array value'); } - var mongoSubqueries = restValue.map((s) => { - return transformWhere(schema, className, s); - }); + var mongoSubqueries = value.map(subQuery => transformWhere(schema, className, subQuery)); return {key: '$and', value: mongoSubqueries}; default: // Other auth data @@ -190,7 +183,7 @@ function transformQueryKeyValue(schema, className, restKey, restValue, { validat if (authDataMatch ) { var provider = authDataMatch[1]; // Special-case auth data. - return {key: '_auth_data_'+provider+'.id', value: restValue}; + return {key: `_auth_data_${provider}.id`, value}; } if (validate && !key.match(/^[a-zA-Z][a-zA-Z0-9_\.]*$/)) { throw new Parse.Error(Parse.Error.INVALID_KEY_NAME, 'invalid key name: ' + key); @@ -205,46 +198,39 @@ function transformQueryKeyValue(schema, className, restKey, restValue, { validat expected = schema.getExpectedType(className, key); } if ((expected && expected.type == 'Pointer') || - (!expected && restValue && restValue.__type == 'Pointer')) { + (!expected && value && value.__type == 'Pointer')) { key = '_p_' + key; } var expectedTypeIsArray = (expected && expected.type === 'Array'); // Handle query constraints - value = transformConstraint(restValue, expectedTypeIsArray); - if (value !== CannotTransform) { - return {key: key, value: value}; + if (transformConstraint(value, expectedTypeIsArray) !== CannotTransform) { + return {key, value: transformConstraint(value, expectedTypeIsArray)}; } - if (expectedTypeIsArray && !(restValue instanceof Array)) { - return { - key: key, value: { '$all' : [restValue] } - }; + if (expectedTypeIsArray && !(value instanceof Array)) { + return {key, value: { '$all' : [value] }}; } // Handle atomic values - var value = transformAtom(restValue, false); - if (value !== CannotTransform) { - if (timeField && (typeof value === 'string')) { - value = new Date(value); - } - return {key: key, value: value}; + if (transformAtom(value, false) !== CannotTransform) { + return {key, value: transformAtom(value, false)}; } // Handle arrays - if (restValue instanceof Array) { - throw new Parse.Error(Parse.Error.INVALID_JSON,'cannot use array as query param'); + if (value instanceof Array) { + throw new Parse.Error(Parse.Error.INVALID_JSON, 'cannot use array as query param'); } // Handle normal objects by recursing - value = {}; - for (var subRestKey in restValue) { - var subRestValue = restValue[subRestKey]; + let result = {}; + for (var subRestKey in value) { + var subRestValue = value[subRestKey]; var out = transformKeyValue(schema, className, subRestKey, subRestValue, { inObject: true }); // For recursed objects, keep the keys in rest format - value[subRestKey] = out.value; + result[subRestKey] = out.value; } - return {key: key, value: value}; + return {key, result}; } // Main exposed method to help run queries. diff --git a/src/Auth.js b/src/Auth.js index bcee1783..f21bdc76 100644 --- a/src/Auth.js +++ b/src/Auth.js @@ -52,11 +52,7 @@ var getAuthForSessionToken = function({ config, sessionToken, installationId } = limit: 1, include: 'user' }; - var restWhere = { - _session_token: sessionToken - }; - var query = new RestQuery(config, master(config), '_Session', - restWhere, restOptions); + var query = new RestQuery(config, master(config), '_Session', { sessionToken }, restOptions); return query.execute().then((response) => { var results = response.results; if (results.length !== 1 || !results[0]['user']) { diff --git a/src/Routers/GlobalConfigRouter.js b/src/Routers/GlobalConfigRouter.js index c6a82dbb..ab498522 100644 --- a/src/Routers/GlobalConfigRouter.js +++ b/src/Routers/GlobalConfigRouter.js @@ -1,12 +1,12 @@ // global_config.js -import PromiseRouter from '../PromiseRouter'; +import PromiseRouter from '../PromiseRouter'; import * as middleware from "../middlewares"; export class GlobalConfigRouter extends PromiseRouter { getGlobalConfig(req) { let database = req.config.database.WithoutValidation(); - return database.find('_GlobalConfig', { '_id': 1 }, { limit: 1 }).then((results) => { + return database.find('_GlobalConfig', { objectId: 1 }, { limit: 1 }).then((results) => { if (results.length != 1) { // If there is no config in the database - return empty config. return { response: { params: {} } }; diff --git a/src/Routers/SessionsRouter.js b/src/Routers/SessionsRouter.js index 1a8d8cbf..1bae3344 100644 --- a/src/Routers/SessionsRouter.js +++ b/src/Routers/SessionsRouter.js @@ -1,8 +1,8 @@ import ClassesRouter from './ClassesRouter'; import PromiseRouter from '../PromiseRouter'; -import rest from '../rest'; -import Auth from '../Auth'; +import rest from '../rest'; +import Auth from '../Auth'; export class SessionsRouter extends ClassesRouter { handleFind(req) { @@ -36,7 +36,7 @@ export class SessionsRouter extends ClassesRouter { throw new Parse.Error(Parse.Error.INVALID_SESSION_TOKEN, 'Session token required.'); } - return rest.find(req.config, Auth.master(req.config), '_Session', { _session_token: req.info.sessionToken }) + return rest.find(req.config, Auth.master(req.config), '_Session', { sessionToken: req.info.sessionToken }) .then((response) => { if (!response.results || response.results.length == 0) { throw new Parse.Error(Parse.Error.INVALID_SESSION_TOKEN, diff --git a/src/Routers/UsersRouter.js b/src/Routers/UsersRouter.js index 4d4cb503..adba752f 100644 --- a/src/Routers/UsersRouter.js +++ b/src/Routers/UsersRouter.js @@ -46,7 +46,7 @@ export class UsersRouter extends ClassesRouter { } let sessionToken = req.info.sessionToken; return rest.find(req.config, Auth.master(req.config), '_Session', - { _session_token: sessionToken }, + { sessionToken }, { include: 'user' }) .then((response) => { if (!response.results || @@ -139,7 +139,7 @@ export class UsersRouter extends ClassesRouter { let success = {response: {}}; if (req.info && req.info.sessionToken) { return rest.find(req.config, Auth.master(req.config), '_Session', - { _session_token: req.info.sessionToken } + { sessionToken: req.info.sessionToken } ).then((records) => { if (records.results && records.results.length) { return rest.del(req.config, Auth.master(req.config), '_Session', From 7b431ad0647a3163bae64a7d0cd685660efc3ed1 Mon Sep 17 00:00:00 2001 From: Drew Gross Date: Mon, 25 Apr 2016 20:54:47 -0700 Subject: [PATCH 06/17] nits --- src/Adapters/Storage/Mongo/MongoTransform.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Adapters/Storage/Mongo/MongoTransform.js b/src/Adapters/Storage/Mongo/MongoTransform.js index c537a1c4..5056c7a4 100644 --- a/src/Adapters/Storage/Mongo/MongoTransform.js +++ b/src/Adapters/Storage/Mongo/MongoTransform.js @@ -180,7 +180,7 @@ function transformQueryKeyValue(schema, className, key, value, { validate } = {} default: // Other auth data var authDataMatch = key.match(/^authData\.([a-zA-Z0-9_]+)\.id$/); - if (authDataMatch ) { + if (authDataMatch) { var provider = authDataMatch[1]; // Special-case auth data. return {key: `_auth_data_${provider}.id`, value}; From a3179e43fbe3d7559cdaf1b9d6824e7fc56dd7d8 Mon Sep 17 00:00:00 2001 From: Drew Gross Date: Mon, 25 Apr 2016 21:13:34 -0700 Subject: [PATCH 07/17] No need to handle object or reject arrays when transforming queries --- src/Adapters/Storage/Mongo/MongoTransform.js | 17 ++--------------- 1 file changed, 2 insertions(+), 15 deletions(-) diff --git a/src/Adapters/Storage/Mongo/MongoTransform.js b/src/Adapters/Storage/Mongo/MongoTransform.js index 5056c7a4..67c3282c 100644 --- a/src/Adapters/Storage/Mongo/MongoTransform.js +++ b/src/Adapters/Storage/Mongo/MongoTransform.js @@ -215,22 +215,9 @@ function transformQueryKeyValue(schema, className, key, value, { validate } = {} // Handle atomic values if (transformAtom(value, false) !== CannotTransform) { return {key, value: transformAtom(value, false)}; + } else { + throw new Parse.Error(Parse.Error.INVALID_JSON, `You cannot use ${value} as a query parameter.`); } - - // Handle arrays - if (value instanceof Array) { - throw new Parse.Error(Parse.Error.INVALID_JSON, 'cannot use array as query param'); - } - - // Handle normal objects by recursing - let result = {}; - for (var subRestKey in value) { - var subRestValue = value[subRestKey]; - var out = transformKeyValue(schema, className, subRestKey, subRestValue, { inObject: true }); - // For recursed objects, keep the keys in rest format - result[subRestKey] = out.value; - } - return {key, result}; } // Main exposed method to help run queries. From 37953d146b5243288cf2fb7a1bb396baf63b3b5a Mon Sep 17 00:00:00 2001 From: Drew Gross Date: Mon, 25 Apr 2016 21:53:57 -0700 Subject: [PATCH 08/17] some cleanup --- src/Adapters/Storage/Mongo/MongoTransform.js | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/src/Adapters/Storage/Mongo/MongoTransform.js b/src/Adapters/Storage/Mongo/MongoTransform.js index 67c3282c..bd7fb895 100644 --- a/src/Adapters/Storage/Mongo/MongoTransform.js +++ b/src/Adapters/Storage/Mongo/MongoTransform.js @@ -169,19 +169,17 @@ function transformQueryKeyValue(schema, className, key, value, { validate } = {} if (!(value instanceof Array)) { throw new Parse.Error(Parse.Error.INVALID_QUERY, 'bad $or format - use an array value'); } - var mongoSubqueries = value.map(subQuery => transformWhere(schema, className, subQuery)); - return {key: '$or', value: mongoSubqueries}; + return {key: '$or', value: value.map(subQuery => transformWhere(schema, className, subQuery))}; case '$and': if (!(value instanceof Array)) { throw new Parse.Error(Parse.Error.INVALID_QUERY, 'bad $and format - use an array value'); } - var mongoSubqueries = value.map(subQuery => transformWhere(schema, className, subQuery)); - return {key: '$and', value: mongoSubqueries}; + return {key: '$and', value: value.map(subQuery => transformWhere(schema, className, subQuery))}; default: // Other auth data - var authDataMatch = key.match(/^authData\.([a-zA-Z0-9_]+)\.id$/); + const authDataMatch = key.match(/^authData\.([a-zA-Z0-9_]+)\.id$/); if (authDataMatch) { - var provider = authDataMatch[1]; + const provider = authDataMatch[1]; // Special-case auth data. return {key: `_auth_data_${provider}.id`, value}; } @@ -193,7 +191,7 @@ function transformQueryKeyValue(schema, className, key, value, { validate } = {} // Handle special schema key changes // TODO: it seems like this is likely to have edge cases where // pointer types are missed - var expected = undefined; + let expected = undefined; if (schema && schema.getExpectedType) { expected = schema.getExpectedType(className, key); } @@ -201,7 +199,7 @@ function transformQueryKeyValue(schema, className, key, value, { validate } = {} (!expected && value && value.__type == 'Pointer')) { key = '_p_' + key; } - var expectedTypeIsArray = (expected && expected.type === 'Array'); + const expectedTypeIsArray = (expected && expected.type === 'Array'); // Handle query constraints if (transformConstraint(value, expectedTypeIsArray) !== CannotTransform) { From 5cbf3eb8dd8e712829e2277453fe5c7b4ed74e34 Mon Sep 17 00:00:00 2001 From: Drew Gross Date: Mon, 25 Apr 2016 22:01:50 -0700 Subject: [PATCH 09/17] Tidy up db controller --- src/Adapters/Storage/Mongo/MongoTransform.js | 4 --- src/Controllers/DatabaseController.js | 31 ++++++++++++-------- 2 files changed, 18 insertions(+), 17 deletions(-) diff --git a/src/Adapters/Storage/Mongo/MongoTransform.js b/src/Adapters/Storage/Mongo/MongoTransform.js index bd7fb895..5c724c57 100644 --- a/src/Adapters/Storage/Mongo/MongoTransform.js +++ b/src/Adapters/Storage/Mongo/MongoTransform.js @@ -140,7 +140,6 @@ const valueAsDate = value => { } function transformQueryKeyValue(schema, className, key, value, { validate } = {}) { - // Check if the schema is known since it's a built-in field. switch(key) { case 'createdAt': if (valueAsDate(value)) { @@ -188,9 +187,6 @@ function transformQueryKeyValue(schema, className, key, value, { validate } = {} } } - // Handle special schema key changes - // TODO: it seems like this is likely to have edge cases where - // pointer types are missed let expected = undefined; if (schema && schema.getExpectedType) { expected = schema.getExpectedType(className, key); diff --git a/src/Controllers/DatabaseController.js b/src/Controllers/DatabaseController.js index fca02829..86dd737a 100644 --- a/src/Controllers/DatabaseController.js +++ b/src/Controllers/DatabaseController.js @@ -158,20 +158,15 @@ DatabaseController.prototype.update = function(className, query, update, { var isMaster = acl === undefined; var aclGroup = acl || []; - var mongoUpdate, schema; + var mongoUpdate; return this.loadSchema() - .then(s => { - schema = s; - if (!isMaster) { - return schema.validatePermission(className, aclGroup, 'update'); - } - return Promise.resolve(); - }) + .then(schemaController => { + return (isMaster ? Promise.resolve() : schemaController.validatePermission(className, aclGroup, 'update')) .then(() => this.handleRelationUpdates(className, query.objectId, update)) .then(() => this.adapter.adaptiveCollection(className)) .then(collection => { if (!isMaster) { - query = this.addPointerPermissions(schema, className, 'update', query, aclGroup); + query = this.addPointerPermissions(schemaController, className, 'update', query, aclGroup); } if (!query) { return Promise.resolve(); @@ -179,8 +174,18 @@ DatabaseController.prototype.update = function(className, query, update, { if (acl) { query = addWriteACL(query, acl); } - var mongoWhere = this.transform.transformWhere(schema, className, query, {validate: !this.skipValidation}); - mongoUpdate = this.transform.transformUpdate(schema, className, update, {validate: !this.skipValidation}); + var mongoWhere = this.transform.transformWhere( + schemaController, + className, + query, + {validate: !this.skipValidation} + ); + mongoUpdate = this.transform.transformUpdate( + schemaController, + className, + update, + {validate: !this.skipValidation} + ); if (many) { return collection.updateMany(mongoWhere, mongoUpdate); } else if (upsert) { @@ -191,14 +196,14 @@ DatabaseController.prototype.update = function(className, query, update, { }) .then(result => { if (!result) { - return Promise.reject(new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, - 'Object not found.')); + return Promise.reject(new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'Object not found.')); } if (this.skipValidation) { return Promise.resolve(result); } return sanitizeDatabaseResult(originalUpdate, result); }); + }); }; function sanitizeDatabaseResult(originalObject, result) { From 608cba9e8cf6a38d5ea4655b73e6d55f8e9adccb Mon Sep 17 00:00:00 2001 From: Drew Gross Date: Mon, 25 Apr 2016 22:12:03 -0700 Subject: [PATCH 10/17] Clearer names in DatabaseController --- src/Controllers/DatabaseController.js | 70 ++++++++++++--------------- 1 file changed, 31 insertions(+), 39 deletions(-) diff --git a/src/Controllers/DatabaseController.js b/src/Controllers/DatabaseController.js index 86dd737a..907da41e 100644 --- a/src/Controllers/DatabaseController.js +++ b/src/Controllers/DatabaseController.js @@ -598,56 +598,48 @@ DatabaseController.prototype.find = function(className, query, { } let isMaster = acl === undefined; let aclGroup = acl || []; - let schema = null; - let op = typeof query.objectId == 'string' && Object.keys(query).length === 1 ? - 'get' : - 'find'; - return this.loadSchema().then(s => { - schema = s; + let op = typeof query.objectId == 'string' && Object.keys(query).length === 1 ? 'get' : 'find'; + return this.loadSchema() + .then(schemaController => { if (sort) { mongoOptions.sort = {}; for (let key in sort) { - let mongoKey = this.transform.transformKey(schema, className, key); + let mongoKey = this.transform.transformKey(schemaController, className, key); mongoOptions.sort[mongoKey] = sort[key]; } } - - if (!isMaster) { - return schema.validatePermission(className, aclGroup, op); - } - return Promise.resolve(); - }) - .then(() => this.reduceRelationKeys(className, query)) - .then(() => this.reduceInRelation(className, query, schema)) - .then(() => this.adapter.adaptiveCollection(className)) - .then(collection => { - if (!isMaster) { - query = this.addPointerPermissions(schema, className, op, query, aclGroup); - } - if (!query) { - if (op == 'get') { - return Promise.reject(new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, - 'Object not found.')); - } else { - return Promise.resolve([]); + 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 => { + if (!isMaster) { + query = this.addPointerPermissions(schemaController, className, op, query, aclGroup); } - } - if (!isMaster) { - query = addReadACL(query, aclGroup); - } - let mongoWhere = this.transform.transformWhere(schema, className, query); - if (count) { - delete mongoOptions.limit; - return collection.count(mongoWhere, mongoOptions); - } else { - return collection.find(mongoWhere, mongoOptions) + if (!query) { + if (op == 'get') { + return Promise.reject(new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, + 'Object not found.')); + } else { + return Promise.resolve([]); + } + } + if (!isMaster) { + query = addReadACL(query, aclGroup); + } + let mongoWhere = this.transform.transformWhere(schemaController, className, query); + if (count) { + delete mongoOptions.limit; + return collection.count(mongoWhere, mongoOptions); + } else { + return collection.find(mongoWhere, mongoOptions) .then((mongoResults) => { return mongoResults.map((r) => { - return this.untransformObject( - schema, isMaster, aclGroup, className, r); + return this.untransformObject(schemaController, isMaster, aclGroup, className, r); }); }); - } + } + }); }); }; From 4371ca164cb4dceee36af4c7142a4a191bf63ad7 Mon Sep 17 00:00:00 2001 From: Drew Gross Date: Mon, 25 Apr 2016 22:45:16 -0700 Subject: [PATCH 11/17] Pass parse format schema to transformWhere --- .../Storage/Mongo/MongoStorageAdapter.js | 5 +- src/Adapters/Storage/Mongo/MongoTransform.js | 10 +- src/Controllers/DatabaseController.js | 100 ++++++++++++------ 3 files changed, 77 insertions(+), 38 deletions(-) diff --git a/src/Adapters/Storage/Mongo/MongoStorageAdapter.js b/src/Adapters/Storage/Mongo/MongoStorageAdapter.js index 803b3c63..a6a642da 100644 --- a/src/Adapters/Storage/Mongo/MongoStorageAdapter.js +++ b/src/Adapters/Storage/Mongo/MongoStorageAdapter.js @@ -174,14 +174,15 @@ export class MongoStorageAdapter { // If there is some other error, reject with INTERNAL_SERVER_ERROR. // Currently accepts the schemaController, and validate for lecacy reasons - deleteObjectsByQuery(className, query, schemaController, validate) { + deleteObjectsByQuery(className, query, schemaController, validate, parseFormatSchema) { return this.adaptiveCollection(className) .then(collection => { let mongoWhere = transform.transformWhere( schemaController, className, query, - { validate } + { validate }, + parseFormatSchema ); return collection.deleteMany(mongoWhere) }) diff --git a/src/Adapters/Storage/Mongo/MongoTransform.js b/src/Adapters/Storage/Mongo/MongoTransform.js index 5c724c57..ac056a20 100644 --- a/src/Adapters/Storage/Mongo/MongoTransform.js +++ b/src/Adapters/Storage/Mongo/MongoTransform.js @@ -139,7 +139,7 @@ const valueAsDate = value => { return false; } -function transformQueryKeyValue(schema, className, key, value, { validate } = {}) { +function transformQueryKeyValue(schema, className, key, value, { validate } = {}, parseFormatSchema) { switch(key) { case 'createdAt': if (valueAsDate(value)) { @@ -168,12 +168,12 @@ function transformQueryKeyValue(schema, className, key, value, { validate } = {} if (!(value instanceof Array)) { throw new Parse.Error(Parse.Error.INVALID_QUERY, 'bad $or format - use an array value'); } - return {key: '$or', value: value.map(subQuery => transformWhere(schema, className, subQuery))}; + return {key: '$or', value: value.map(subQuery => transformWhere(schema, className, subQuery, parseFormatSchema))}; case '$and': if (!(value instanceof Array)) { throw new Parse.Error(Parse.Error.INVALID_QUERY, 'bad $and format - use an array value'); } - return {key: '$and', value: value.map(subQuery => transformWhere(schema, className, subQuery))}; + return {key: '$and', value: value.map(subQuery => transformWhere(schema, className, subQuery, parseFormatSchema))}; default: // Other auth data const authDataMatch = key.match(/^authData\.([a-zA-Z0-9_]+)\.id$/); @@ -218,13 +218,13 @@ function transformQueryKeyValue(schema, className, key, value, { validate } = {} // restWhere is the "where" clause in REST API form. // Returns the mongo form of the query. // Throws a Parse.Error if the input query is invalid. -function transformWhere(schema, className, restWhere, { validate = true } = {}) { +function transformWhere(schema, className, restWhere, { validate = true } = {}, parseFormatSchema) { let mongoWhere = {}; if (restWhere['ACL']) { throw new Parse.Error(Parse.Error.INVALID_QUERY, 'Cannot query on ACL.'); } for (let restKey in restWhere) { - let out = transformQueryKeyValue(schema, className, restKey, restWhere[restKey], { validate }); + let out = transformQueryKeyValue(schema, className, restKey, restWhere[restKey], { validate }, parseFormatSchema); mongoWhere[out.key] = out.value; } return mongoWhere; diff --git a/src/Controllers/DatabaseController.js b/src/Controllers/DatabaseController.js index 907da41e..b6f6b9a6 100644 --- a/src/Controllers/DatabaseController.js +++ b/src/Controllers/DatabaseController.js @@ -174,25 +174,37 @@ DatabaseController.prototype.update = function(className, query, update, { if (acl) { query = addWriteACL(query, acl); } - var mongoWhere = this.transform.transformWhere( - schemaController, - className, - query, - {validate: !this.skipValidation} - ); - mongoUpdate = this.transform.transformUpdate( - schemaController, - className, - update, - {validate: !this.skipValidation} - ); - if (many) { - return collection.updateMany(mongoWhere, mongoUpdate); - } else if (upsert) { - return collection.upsertOne(mongoWhere, mongoUpdate); - } else { - return collection.findOneAndUpdate(mongoWhere, mongoUpdate); - } + return schemaController.getOneSchema(className) + .catch(error => { + // If the schema doesn't exist, pretend it exists with no fields. This behaviour + // will likely need revisiting. + if (error === undefined) { + return { fields: {} }; + } + throw error; + }) + .then(parseFormatSchema => { + var mongoWhere = this.transform.transformWhere( + schemaController, + className, + query, + {validate: !this.skipValidation}, + parseFormatSchema + ); + mongoUpdate = this.transform.transformUpdate( + schemaController, + className, + update, + {validate: !this.skipValidation} + ); + if (many) { + return collection.updateMany(mongoWhere, mongoUpdate); + } else if (upsert) { + return collection.upsertOne(mongoWhere, mongoUpdate); + } else { + return collection.findOneAndUpdate(mongoWhere, mongoUpdate); + } + }); }) .then(result => { if (!result) { @@ -322,7 +334,22 @@ DatabaseController.prototype.destroy = function(className, query, { acl } = {}) if (acl) { query = addWriteACL(query, acl); } - return this.adapter.deleteObjectsByQuery(className, query, schemaController, !this.skipValidation) + return schemaController.getOneSchema(className) + .catch(error => { + // If the schema doesn't exist, pretend it exists with no fields. This behaviour + // will likely need revisiting. + if (error === undefined) { + return { fields: {} }; + } + throw error; + }) + .then(parseFormatSchema => this.adapter.deleteObjectsByQuery( + className, + query, + schemaController, + !this.skipValidation, + parseFormatSchema + )) .catch(error => { // When deleting sessions while changing passwords, don't throw an error if they don't have any sessions. if (className === "_Session" && error.code === Parse.Error.OBJECT_NOT_FOUND) { @@ -627,18 +654,29 @@ DatabaseController.prototype.find = function(className, query, { if (!isMaster) { query = addReadACL(query, aclGroup); } - let mongoWhere = this.transform.transformWhere(schemaController, className, query); - if (count) { - delete mongoOptions.limit; - return collection.count(mongoWhere, mongoOptions); - } else { - return collection.find(mongoWhere, mongoOptions) - .then((mongoResults) => { - return mongoResults.map((r) => { - return this.untransformObject(schemaController, isMaster, aclGroup, className, r); + return schemaController.getOneSchema(className) + .catch(error => { + // If the schema doesn't exist, pretend it exists with no fields. This behaviour + // will likely need revisiting. + if (error === undefined) { + return { fields: {} }; + } + throw error; + }) + .then(parseFormatSchema => { + let mongoWhere = this.transform.transformWhere(schemaController, className, query, parseFormatSchema); + if (count) { + delete mongoOptions.limit; + return collection.count(mongoWhere, mongoOptions); + } else { + return collection.find(mongoWhere, mongoOptions) + .then((mongoResults) => { + return mongoResults.map((r) => { + return this.untransformObject(schemaController, isMaster, aclGroup, className, r); + }); }); - }); - } + } + }); }); }); }; From 874d10fc746d73915de3d3509185bc3fc7c8af6f Mon Sep 17 00:00:00 2001 From: Drew Gross Date: Mon, 25 Apr 2016 23:36:25 -0700 Subject: [PATCH 12/17] Check expected type is array from Parse Format Schema --- src/Adapters/Storage/Mongo/MongoTransform.js | 5 ++++- src/Controllers/DatabaseController.js | 5 +++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/Adapters/Storage/Mongo/MongoTransform.js b/src/Adapters/Storage/Mongo/MongoTransform.js index ac056a20..307783ab 100644 --- a/src/Adapters/Storage/Mongo/MongoTransform.js +++ b/src/Adapters/Storage/Mongo/MongoTransform.js @@ -195,7 +195,10 @@ function transformQueryKeyValue(schema, className, key, value, { validate } = {} (!expected && value && value.__type == 'Pointer')) { key = '_p_' + key; } - const expectedTypeIsArray = (expected && expected.type === 'Array'); + const expectedTypeIsArray = + parseFormatSchema && + parseFormatSchema.fields[key] && + parseFormatSchema.fields[key].type === 'Array' // Handle query constraints if (transformConstraint(value, expectedTypeIsArray) !== CannotTransform) { diff --git a/src/Controllers/DatabaseController.js b/src/Controllers/DatabaseController.js index b6f6b9a6..cb40d101 100644 --- a/src/Controllers/DatabaseController.js +++ b/src/Controllers/DatabaseController.js @@ -654,7 +654,8 @@ DatabaseController.prototype.find = function(className, query, { if (!isMaster) { query = addReadACL(query, aclGroup); } - return schemaController.getOneSchema(className) + return schemaController.reloadData() + .then(() => schemaController.getOneSchema(className)) .catch(error => { // If the schema doesn't exist, pretend it exists with no fields. This behaviour // will likely need revisiting. @@ -664,7 +665,7 @@ DatabaseController.prototype.find = function(className, query, { throw error; }) .then(parseFormatSchema => { - let mongoWhere = this.transform.transformWhere(schemaController, className, query, parseFormatSchema); + let mongoWhere = this.transform.transformWhere(schemaController, className, query, {}, parseFormatSchema); if (count) { delete mongoOptions.limit; return collection.count(mongoWhere, mongoOptions); From 5f564f32f5e5f9d2e114ac7ed7bd5c187d957771 Mon Sep 17 00:00:00 2001 From: Drew Gross Date: Mon, 25 Apr 2016 23:39:42 -0700 Subject: [PATCH 13/17] simplify a little --- src/Controllers/DatabaseController.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Controllers/DatabaseController.js b/src/Controllers/DatabaseController.js index cb40d101..74ffd443 100644 --- a/src/Controllers/DatabaseController.js +++ b/src/Controllers/DatabaseController.js @@ -654,8 +654,7 @@ DatabaseController.prototype.find = function(className, query, { if (!isMaster) { query = addReadACL(query, aclGroup); } - return schemaController.reloadData() - .then(() => schemaController.getOneSchema(className)) + return schemaController.getOneSchema(className) .catch(error => { // If the schema doesn't exist, pretend it exists with no fields. This behaviour // will likely need revisiting. From a926712951db54984cd15fd8a252b7f5c042fd26 Mon Sep 17 00:00:00 2001 From: Drew Gross Date: Tue, 26 Apr 2016 10:12:45 -0700 Subject: [PATCH 14/17] Start using parse format schema in transformQueryKeyValue --- spec/Schema.spec.js | 1 - src/Adapters/Storage/Mongo/MongoTransform.js | 23 ++++++++++---------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/spec/Schema.spec.js b/spec/Schema.spec.js index 67367b46..d824d2b0 100644 --- a/spec/Schema.spec.js +++ b/spec/Schema.spec.js @@ -124,7 +124,6 @@ describe('SchemaController', () => { var obj; createTestUser() .then(user => { - console.log(user); return config.database.loadSchema() // Create a valid class .then(schema => schema.validateObject('Stuff', {foo: 'bar'})) diff --git a/src/Adapters/Storage/Mongo/MongoTransform.js b/src/Adapters/Storage/Mongo/MongoTransform.js index 307783ab..0fe8e40e 100644 --- a/src/Adapters/Storage/Mongo/MongoTransform.js +++ b/src/Adapters/Storage/Mongo/MongoTransform.js @@ -168,12 +168,12 @@ function transformQueryKeyValue(schema, className, key, value, { validate } = {} if (!(value instanceof Array)) { throw new Parse.Error(Parse.Error.INVALID_QUERY, 'bad $or format - use an array value'); } - return {key: '$or', value: value.map(subQuery => transformWhere(schema, className, subQuery, parseFormatSchema))}; + return {key: '$or', value: value.map(subQuery => transformWhere(schema, className, subQuery, {}, parseFormatSchema))}; case '$and': if (!(value instanceof Array)) { throw new Parse.Error(Parse.Error.INVALID_QUERY, 'bad $and format - use an array value'); } - return {key: '$and', value: value.map(subQuery => transformWhere(schema, className, subQuery, parseFormatSchema))}; + return {key: '$and', value: value.map(subQuery => transformWhere(schema, className, subQuery, {}, parseFormatSchema))}; default: // Other auth data const authDataMatch = key.match(/^authData\.([a-zA-Z0-9_]+)\.id$/); @@ -187,18 +187,19 @@ function transformQueryKeyValue(schema, className, key, value, { validate } = {} } } - let expected = undefined; - if (schema && schema.getExpectedType) { - expected = schema.getExpectedType(className, key); - } - if ((expected && expected.type == 'Pointer') || - (!expected && value && value.__type == 'Pointer')) { - key = '_p_' + key; - } const expectedTypeIsArray = parseFormatSchema && parseFormatSchema.fields[key] && - parseFormatSchema.fields[key].type === 'Array' + parseFormatSchema.fields[key].type === 'Array'; + + const expectedTypeIsPointer = + parseFormatSchema && + parseFormatSchema.fields[key] && + parseFormatSchema.fields[key].type === 'Pointer'; + + if (expectedTypeIsPointer || !parseFormatSchema && value && value.__type === 'Pointer') { + key = '_p_' + key; + } // Handle query constraints if (transformConstraint(value, expectedTypeIsArray) !== CannotTransform) { From d4bd21fcbc8e5513861a4023d5e903b4c15c843e Mon Sep 17 00:00:00 2001 From: Drew Gross Date: Tue, 26 Apr 2016 10:20:17 -0700 Subject: [PATCH 15/17] remove schema from transformWhere --- spec/MongoTransform.spec.js | 4 ++-- src/Adapters/Storage/Mongo/MongoStorageAdapter.js | 8 +------- src/Adapters/Storage/Mongo/MongoTransform.js | 10 +++++----- src/Controllers/DatabaseController.js | 10 ++-------- 4 files changed, 10 insertions(+), 22 deletions(-) diff --git a/spec/MongoTransform.spec.js b/spec/MongoTransform.spec.js index 59fee087..bae5805f 100644 --- a/spec/MongoTransform.spec.js +++ b/spec/MongoTransform.spec.js @@ -106,7 +106,7 @@ describe('parseObjectToMongoObjectForCreate', () => { describe('transformWhere', () => { it('objectId', (done) => { - var out = transform.transformWhere(dummySchema, null, {objectId: 'foo'}); + var out = transform.transformWhere(null, {objectId: 'foo'}); expect(out._id).toEqual('foo'); done(); }); @@ -115,7 +115,7 @@ describe('transformWhere', () => { var input = { objectId: {'$in': ['one', 'two', 'three']}, }; - var output = transform.transformWhere(dummySchema, null, input); + var output = transform.transformWhere(null, input); jequal(input.objectId, output._id); done(); }); diff --git a/src/Adapters/Storage/Mongo/MongoStorageAdapter.js b/src/Adapters/Storage/Mongo/MongoStorageAdapter.js index a6a642da..091129a8 100644 --- a/src/Adapters/Storage/Mongo/MongoStorageAdapter.js +++ b/src/Adapters/Storage/Mongo/MongoStorageAdapter.js @@ -177,13 +177,7 @@ export class MongoStorageAdapter { deleteObjectsByQuery(className, query, schemaController, validate, parseFormatSchema) { return this.adaptiveCollection(className) .then(collection => { - let mongoWhere = transform.transformWhere( - schemaController, - className, - query, - { validate }, - parseFormatSchema - ); + let mongoWhere = transform.transformWhere(className, query, { validate }, parseFormatSchema); return collection.deleteMany(mongoWhere) }) .then(({ result }) => { diff --git a/src/Adapters/Storage/Mongo/MongoTransform.js b/src/Adapters/Storage/Mongo/MongoTransform.js index 0fe8e40e..ba9d3992 100644 --- a/src/Adapters/Storage/Mongo/MongoTransform.js +++ b/src/Adapters/Storage/Mongo/MongoTransform.js @@ -139,7 +139,7 @@ const valueAsDate = value => { return false; } -function transformQueryKeyValue(schema, className, key, value, { validate } = {}, parseFormatSchema) { +function transformQueryKeyValue(className, key, value, { validate } = {}, parseFormatSchema) { switch(key) { case 'createdAt': if (valueAsDate(value)) { @@ -168,12 +168,12 @@ function transformQueryKeyValue(schema, className, key, value, { validate } = {} if (!(value instanceof Array)) { throw new Parse.Error(Parse.Error.INVALID_QUERY, 'bad $or format - use an array value'); } - return {key: '$or', value: value.map(subQuery => transformWhere(schema, className, subQuery, {}, parseFormatSchema))}; + return {key: '$or', value: value.map(subQuery => transformWhere(className, subQuery, {}, parseFormatSchema))}; case '$and': if (!(value instanceof Array)) { throw new Parse.Error(Parse.Error.INVALID_QUERY, 'bad $and format - use an array value'); } - return {key: '$and', value: value.map(subQuery => transformWhere(schema, className, subQuery, {}, parseFormatSchema))}; + return {key: '$and', value: value.map(subQuery => transformWhere(className, subQuery, {}, parseFormatSchema))}; default: // Other auth data const authDataMatch = key.match(/^authData\.([a-zA-Z0-9_]+)\.id$/); @@ -222,13 +222,13 @@ function transformQueryKeyValue(schema, className, key, value, { validate } = {} // restWhere is the "where" clause in REST API form. // Returns the mongo form of the query. // Throws a Parse.Error if the input query is invalid. -function transformWhere(schema, className, restWhere, { validate = true } = {}, parseFormatSchema) { +function transformWhere(className, restWhere, { validate = true } = {}, parseFormatSchema) { let mongoWhere = {}; if (restWhere['ACL']) { throw new Parse.Error(Parse.Error.INVALID_QUERY, 'Cannot query on ACL.'); } for (let restKey in restWhere) { - let out = transformQueryKeyValue(schema, className, restKey, restWhere[restKey], { validate }, parseFormatSchema); + let out = transformQueryKeyValue(className, restKey, restWhere[restKey], { validate }, parseFormatSchema); mongoWhere[out.key] = out.value; } return mongoWhere; diff --git a/src/Controllers/DatabaseController.js b/src/Controllers/DatabaseController.js index 74ffd443..9ec26236 100644 --- a/src/Controllers/DatabaseController.js +++ b/src/Controllers/DatabaseController.js @@ -184,13 +184,7 @@ DatabaseController.prototype.update = function(className, query, update, { throw error; }) .then(parseFormatSchema => { - var mongoWhere = this.transform.transformWhere( - schemaController, - className, - query, - {validate: !this.skipValidation}, - parseFormatSchema - ); + var mongoWhere = this.transform.transformWhere(className, query, {validate: !this.skipValidation}, parseFormatSchema); mongoUpdate = this.transform.transformUpdate( schemaController, className, @@ -664,7 +658,7 @@ DatabaseController.prototype.find = function(className, query, { throw error; }) .then(parseFormatSchema => { - let mongoWhere = this.transform.transformWhere(schemaController, className, query, {}, parseFormatSchema); + let mongoWhere = this.transform.transformWhere(className, query, {}, parseFormatSchema); if (count) { delete mongoOptions.limit; return collection.count(mongoWhere, mongoOptions); From 449ca115b13c5a1f7f24a375faa2354c07fdf7c0 Mon Sep 17 00:00:00 2001 From: Drew Gross Date: Tue, 26 Apr 2016 10:23:14 -0700 Subject: [PATCH 16/17] Break object deletions's dependency on schemaController --- src/Adapters/Storage/Mongo/MongoStorageAdapter.js | 6 +++--- src/Controllers/DatabaseController.js | 8 +------- 2 files changed, 4 insertions(+), 10 deletions(-) diff --git a/src/Adapters/Storage/Mongo/MongoStorageAdapter.js b/src/Adapters/Storage/Mongo/MongoStorageAdapter.js index 091129a8..f6467386 100644 --- a/src/Adapters/Storage/Mongo/MongoStorageAdapter.js +++ b/src/Adapters/Storage/Mongo/MongoStorageAdapter.js @@ -173,11 +173,11 @@ export class MongoStorageAdapter { // If no objects match, reject with OBJECT_NOT_FOUND. If objects are found and deleted, resolve with undefined. // If there is some other error, reject with INTERNAL_SERVER_ERROR. - // Currently accepts the schemaController, and validate for lecacy reasons - deleteObjectsByQuery(className, query, schemaController, validate, parseFormatSchema) { + // Currently accepts the validate for lecacy reasons. Currently accepts the schema, that may not actually be necessary. + deleteObjectsByQuery(className, query, validate, schema) { return this.adaptiveCollection(className) .then(collection => { - let mongoWhere = transform.transformWhere(className, query, { validate }, parseFormatSchema); + let mongoWhere = transform.transformWhere(className, query, { validate }, schema); return collection.deleteMany(mongoWhere) }) .then(({ result }) => { diff --git a/src/Controllers/DatabaseController.js b/src/Controllers/DatabaseController.js index 9ec26236..6897766e 100644 --- a/src/Controllers/DatabaseController.js +++ b/src/Controllers/DatabaseController.js @@ -337,13 +337,7 @@ DatabaseController.prototype.destroy = function(className, query, { acl } = {}) } throw error; }) - .then(parseFormatSchema => this.adapter.deleteObjectsByQuery( - className, - query, - schemaController, - !this.skipValidation, - parseFormatSchema - )) + .then(parseFormatSchema => this.adapter.deleteObjectsByQuery(className, query, !this.skipValidation, parseFormatSchema)) .catch(error => { // When deleting sessions while changing passwords, don't throw an error if they don't have any sessions. if (className === "_Session" && error.code === Parse.Error.OBJECT_NOT_FOUND) { From 71ae7bee945c7a341b3935e6b7fce14bfa058438 Mon Sep 17 00:00:00 2001 From: Drew Gross Date: Tue, 26 Apr 2016 13:08:58 -0700 Subject: [PATCH 17/17] better names and comments --- .../Storage/Mongo/MongoStorageAdapter.js | 2 +- src/Adapters/Storage/Mongo/MongoTransform.js | 24 +++++++++---------- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/Adapters/Storage/Mongo/MongoStorageAdapter.js b/src/Adapters/Storage/Mongo/MongoStorageAdapter.js index f6467386..d61df40c 100644 --- a/src/Adapters/Storage/Mongo/MongoStorageAdapter.js +++ b/src/Adapters/Storage/Mongo/MongoStorageAdapter.js @@ -173,7 +173,7 @@ export class MongoStorageAdapter { // If no objects match, reject with OBJECT_NOT_FOUND. If objects are found and deleted, resolve with undefined. // If there is some other error, reject with INTERNAL_SERVER_ERROR. - // Currently accepts the validate for lecacy reasons. Currently accepts the schema, that may not actually be necessary. + // Currently accepts validate for legacy reasons. Currently accepts the schema, that may not actually be necessary. deleteObjectsByQuery(className, query, validate, schema) { return this.adaptiveCollection(className) .then(collection => { diff --git a/src/Adapters/Storage/Mongo/MongoTransform.js b/src/Adapters/Storage/Mongo/MongoTransform.js index ba9d3992..0013a39d 100644 --- a/src/Adapters/Storage/Mongo/MongoTransform.js +++ b/src/Adapters/Storage/Mongo/MongoTransform.js @@ -139,7 +139,7 @@ const valueAsDate = value => { return false; } -function transformQueryKeyValue(className, key, value, { validate } = {}, parseFormatSchema) { +function transformQueryKeyValue(className, key, value, { validate } = {}, schema) { switch(key) { case 'createdAt': if (valueAsDate(value)) { @@ -168,12 +168,12 @@ function transformQueryKeyValue(className, key, value, { validate } = {}, parseF if (!(value instanceof Array)) { throw new Parse.Error(Parse.Error.INVALID_QUERY, 'bad $or format - use an array value'); } - return {key: '$or', value: value.map(subQuery => transformWhere(className, subQuery, {}, parseFormatSchema))}; + return {key: '$or', value: value.map(subQuery => transformWhere(className, subQuery, {}, schema))}; case '$and': if (!(value instanceof Array)) { throw new Parse.Error(Parse.Error.INVALID_QUERY, 'bad $and format - use an array value'); } - return {key: '$and', value: value.map(subQuery => transformWhere(className, subQuery, {}, parseFormatSchema))}; + return {key: '$and', value: value.map(subQuery => transformWhere(className, subQuery, {}, schema))}; default: // Other auth data const authDataMatch = key.match(/^authData\.([a-zA-Z0-9_]+)\.id$/); @@ -188,16 +188,16 @@ function transformQueryKeyValue(className, key, value, { validate } = {}, parseF } const expectedTypeIsArray = - parseFormatSchema && - parseFormatSchema.fields[key] && - parseFormatSchema.fields[key].type === 'Array'; + schema && + schema.fields[key] && + schema.fields[key].type === 'Array'; const expectedTypeIsPointer = - parseFormatSchema && - parseFormatSchema.fields[key] && - parseFormatSchema.fields[key].type === 'Pointer'; + schema && + schema.fields[key] && + schema.fields[key].type === 'Pointer'; - if (expectedTypeIsPointer || !parseFormatSchema && value && value.__type === 'Pointer') { + if (expectedTypeIsPointer || !schema && value && value.__type === 'Pointer') { key = '_p_' + key; } @@ -222,13 +222,13 @@ function transformQueryKeyValue(className, key, value, { validate } = {}, parseF // restWhere is the "where" clause in REST API form. // Returns the mongo form of the query. // Throws a Parse.Error if the input query is invalid. -function transformWhere(className, restWhere, { validate = true } = {}, parseFormatSchema) { +function transformWhere(className, restWhere, { validate = true } = {}, schema) { let mongoWhere = {}; if (restWhere['ACL']) { throw new Parse.Error(Parse.Error.INVALID_QUERY, 'Cannot query on ACL.'); } for (let restKey in restWhere) { - let out = transformQueryKeyValue(className, restKey, restWhere[restKey], { validate }, parseFormatSchema); + let out = transformQueryKeyValue(className, restKey, restWhere[restKey], { validate }, schema); mongoWhere[out.key] = out.value; } return mongoWhere;