Skip to content

Break dependency of deleteObjectsByQuery on schemaController #1644

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 17 commits into from
May 9, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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