Skip to content

Adding $nor operator support #4768

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 14 commits into from
May 18, 2018
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
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
9 changes: 6 additions & 3 deletions src/Adapters/Storage/Postgres/PostgresStorageAdapter.js
Original file line number Diff line number Diff line change
Expand Up @@ -306,7 +306,7 @@ 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) => {
Expand All @@ -317,8 +317,11 @@ const buildWhereClause = ({ schema, query, index }): WhereClause => {
index += clause.values.length;
}
});
const orOrAnd = fieldName === '$or' ? ' OR ' : ' AND ';
patterns.push(`(${clauses.join(orOrAnd)})`);

const orOrAnd = fieldName === '$and' ? ' AND ' : ' OR ';
const not = fieldName === '$nor' ? ' NOT ' : '';

patterns.push(`${not}(${clauses.join(orOrAnd)})`);
values.push(...clauseValues);
}

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