Skip to content

Commit

Permalink
Merge pull request #1644 from drew-gross/refactor-query-transform
Browse files Browse the repository at this point in the history
Break dependency of deleteObjectsByQuery on schemaController
  • Loading branch information
gfosco committed May 9, 2016
2 parents 10d2988 + 71ae7be commit b2f36e1
Show file tree
Hide file tree
Showing 10 changed files with 195 additions and 149 deletions.
4 changes: 2 additions & 2 deletions spec/MongoTransform.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -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();
});
Expand All @@ -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();
});
Expand Down
2 changes: 1 addition & 1 deletion spec/ParseHooks.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -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");
Expand Down
1 change: 0 additions & 1 deletion spec/Schema.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -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'}))
Expand Down
11 changes: 3 additions & 8 deletions src/Adapters/Storage/Mongo/MongoStorageAdapter.js
Original file line number Diff line number Diff line change
Expand Up @@ -173,16 +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) {
// 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 => {
let mongoWhere = transform.transformWhere(
schemaController,
className,
query,
{ validate }
);
let mongoWhere = transform.transformWhere(className, query, { validate }, schema);
return collection.deleteMany(mongoWhere)
})
.then(({ result }) => {
Expand Down
157 changes: 95 additions & 62 deletions src/Adapters/Storage/Mongo/MongoTransform.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,20 +11,16 @@ 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.
//
// 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,
} = {}) {
Expand Down Expand Up @@ -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);
}
}

Expand All @@ -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) {
Expand All @@ -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;
Expand All @@ -182,20 +130,105 @@ export function transformKeyValue(schema, className, restKey, restValue, {
return {key: key, value: value};
}

const valueAsDate = value => {
if (typeof value === 'string') {
return new Date(value);
} else if (value instanceof Date) {
return value;
}
return false;
}

function transformQueryKeyValue(className, key, value, { validate } = {}, schema) {
switch(key) {
case 'createdAt':
if (valueAsDate(value)) {
return {key: '_created_at', value: valueAsDate(value)}
}
key = '_created_at';
break;
case 'updatedAt':
if (valueAsDate(value)) {
return {key: '_updated_at', value: valueAsDate(value)}
}
key = '_updated_at';
break;
case 'expiresAt':
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':
case '_perishable_token':
case '_email_verify_token': return {key, value}
case '$or':
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, {}, 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, {}, schema))};
default:
// Other auth data
const authDataMatch = key.match(/^authData\.([a-zA-Z0-9_]+)\.id$/);
if (authDataMatch) {
const provider = authDataMatch[1];
// Special-case auth data.
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);
}
}

const expectedTypeIsArray =
schema &&
schema.fields[key] &&
schema.fields[key].type === 'Array';

const expectedTypeIsPointer =
schema &&
schema.fields[key] &&
schema.fields[key].type === 'Pointer';

if (expectedTypeIsPointer || !schema && value && value.__type === 'Pointer') {
key = '_p_' + key;
}

// Handle query constraints
if (transformConstraint(value, expectedTypeIsArray) !== CannotTransform) {
return {key, value: transformConstraint(value, expectedTypeIsArray)};
}

if (expectedTypeIsArray && !(value instanceof Array)) {
return {key, value: { '$all' : [value] }};
}

// 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.`);
}
}

// Main exposed method to help run queries.
// 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(className, restWhere, { validate = true } = {}, schema) {
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 = transformQueryKeyValue(className, restKey, restWhere[restKey], { validate }, schema);
mongoWhere[out.key] = out.value;
}
return mongoWhere;
Expand Down
6 changes: 1 addition & 5 deletions src/Auth.js
Original file line number Diff line number Diff line change
Expand Up @@ -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']) {
Expand Down
Loading

0 comments on commit b2f36e1

Please sign in to comment.