Skip to content
Merged
Show file tree
Hide file tree
Changes from 13 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
57 changes: 57 additions & 0 deletions spec/ParseQuery.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -2567,6 +2567,63 @@ describe('Parse.Query testing', () => {
});
});

it('$nor valid query', (done) => {
const objects = Array.from(Array(10).keys()).map((rating) => {
return new TestObject({ 'rating': rating });
});

const highValue = 5;
const lowValue = 3;
const options = Object.assign({}, masterKeyOptions, {
body: {
where: {
$nor: [
{ rating : { $gt : highValue } },
{ rating : { $lte : lowValue } },
]
},
}
});

Parse.Object.saveAll(objects).then(() => {
return rp.get(Parse.serverURL + "/classes/TestObject", options);
}).then((results) => {
expect(results.results.length).toBe(highValue - lowValue);
expect(results.results.every(res => res.rating > lowValue && res.rating <= highValue)).toBe(true);
done();
});
});

it('$nor invalid query - empty array', (done) => {
const options = Object.assign({}, masterKeyOptions, {
body: {
where: { $nor: [] },
}
});
const obj = new TestObject();
obj.save().then(() => {
return rp.get(Parse.serverURL + "/classes/TestObject", options);
}).then(done.fail).catch((error) => {
equal(error.error.code, Parse.Error.INVALID_QUERY);
done();
});
});

it('$nor invalid query - wrong type', (done) => {
const options = Object.assign({}, masterKeyOptions, {
body: {
where: { $nor: 1337 },
}
});
const obj = new TestObject();
obj.save().then(() => {
return rp.get(Parse.serverURL + "/classes/TestObject", options);
}).then(done.fail).catch((error) => {
equal(error.error.code, Parse.Error.INVALID_QUERY);
done();
});
});

it("dontSelect query", function(done) {
const RestaurantObject = Parse.Object.extend("Restaurant");
const PersonObject = Parse.Object.extend("Person");
Expand Down
4 changes: 2 additions & 2 deletions src/Adapters/Storage/Mongo/MongoTransform.js
Original file line number Diff line number Diff line change
Expand Up @@ -247,9 +247,9 @@ function transformQueryKeyValue(className, key, value, schema) {
case '_perishable_token':
case '_email_verify_token': return {key, value}
case '$or':
return {key: '$or', value: value.map(subQuery => transformWhere(className, subQuery, schema))};
case '$and':
return {key: '$and', value: value.map(subQuery => transformWhere(className, subQuery, schema))};
case '$nor':
return {key: key, value: value.map(subQuery => transformWhere(className, subQuery, schema))};
case 'lastUsed':
if (valueAsDate(value)) {
return {key: '_last_used', value: valueAsDate(value)}
Expand Down
5 changes: 4 additions & 1 deletion src/Adapters/Storage/Postgres/PostgresStorageAdapter.js
Original file line number Diff line number Diff line change
Expand Up @@ -306,12 +306,15 @@ const buildWhereClause = ({ schema, query, index }): WhereClause => {
patterns.push(`$${index}:name = $${index + 1}`);
values.push(fieldName, fieldValue);
index += 2;
} else if (fieldName === '$or' || fieldName === '$and') {
} else if (['$or', '$nor', '$and'].includes(fieldName)) {
const clauses = [];
const clauseValues = [];
fieldValue.forEach((subQuery) => {
const clause = buildWhereClause({ schema, query: subQuery, index });
if (clause.pattern.length > 0) {
if (fieldName === '$nor') {
clause.pattern = `(NOT ${clause.pattern})`;
}
Copy link
Contributor

@flovilmart flovilmart May 18, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you're doing (NOT query1) AND (NOT query2) instead of NOT (query1 OR query2) whhich tecnically work but can be a bit misleading for maintenance. Can« you document it or revert the logic with the help of line 323? Event if it's the same, rtaht will help for comprehension in the future ;)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

True I was pretty lazy 😅

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah I figured it worked but letMs write it cleanly please

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll fix that

clauses.push(clause.pattern);
clauseValues.push(...clause.values);
index += clause.values.length;
Expand Down
10 changes: 9 additions & 1 deletion src/Controllers/DatabaseController.js
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ const transformObjectACL = ({ ACL, ...result }) => {
return result;
}

const specialQuerykeys = ['$and', '$or', '_rperm', '_wperm', '_perishable_token', '_email_verify_token', '_email_verify_token_expires_at', '_account_lockout_expires_at', '_failed_login_count'];
const specialQuerykeys = ['$and', '$or', '$nor', '_rperm', '_wperm', '_perishable_token', '_email_verify_token', '_email_verify_token_expires_at', '_account_lockout_expires_at', '_failed_login_count'];

const isSpecialQueryKey = key => {
return specialQuerykeys.indexOf(key) >= 0;
Expand Down Expand Up @@ -111,6 +111,14 @@ const validateQuery = (query: any): void => {
}
}

if (query.$nor) {
if (query.$nor instanceof Array && query.$nor.length > 0) {
query.$nor.forEach(validateQuery);
} else {
throw new Parse.Error(Parse.Error.INVALID_QUERY, 'Bad $nor format - use an array of at least 1 value.');
}
}

Object.keys(query).forEach(key => {
if (query && query[key] && query[key].$regex) {
if (typeof query[key].$options === 'string') {
Expand Down