Make notEqual work on relations
* ⚡ regression tests for #1349 * 🎉 fixes #1349 * Adds support for multiple constraints on the same key
This commit is contained in:
@@ -23,6 +23,105 @@ describe('Parse.Query testing', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("notEqualTo with Relation is working", function(done) {
|
||||||
|
var user = new Parse.User();
|
||||||
|
user.setPassword("asdf");
|
||||||
|
user.setUsername("zxcv");
|
||||||
|
|
||||||
|
var user1 = new Parse.User();
|
||||||
|
user1.setPassword("asdf");
|
||||||
|
user1.setUsername("qwerty");
|
||||||
|
|
||||||
|
var user2 = new Parse.User();
|
||||||
|
user2.setPassword("asdf");
|
||||||
|
user2.setUsername("asdf");
|
||||||
|
|
||||||
|
var Cake = Parse.Object.extend("Cake");
|
||||||
|
var cake1 = new Cake();
|
||||||
|
var cake2 = new Cake();
|
||||||
|
var cake3 = new Cake();
|
||||||
|
|
||||||
|
|
||||||
|
user.signUp().then(function(){
|
||||||
|
return user1.signUp();
|
||||||
|
}).then(function(){
|
||||||
|
return user2.signUp();
|
||||||
|
}).then(function(){
|
||||||
|
var relLike1 = cake1.relation("liker");
|
||||||
|
relLike1.add([user, user1]);
|
||||||
|
|
||||||
|
var relDislike1 = cake1.relation("hater");
|
||||||
|
relDislike1.add(user2);
|
||||||
|
return cake1.save();
|
||||||
|
}).then(function(){
|
||||||
|
var rellike2 = cake2.relation("liker");
|
||||||
|
rellike2.add([user, user1]);
|
||||||
|
|
||||||
|
var relDislike2 = cake2.relation("hater");
|
||||||
|
relDislike2.add(user2);
|
||||||
|
|
||||||
|
return cake2.save();
|
||||||
|
}).then(function(){
|
||||||
|
var rellike3 = cake3.relation("liker");
|
||||||
|
rellike3.add(user);
|
||||||
|
|
||||||
|
var relDislike3 = cake3.relation("hater");
|
||||||
|
relDislike3.add([user1, user2]);
|
||||||
|
return cake3.save();
|
||||||
|
}).then(function(){
|
||||||
|
var query = new Parse.Query(Cake);
|
||||||
|
// User2 likes nothing so we should receive 0
|
||||||
|
query.equalTo("liker", user2);
|
||||||
|
return query.find().then(function(results){
|
||||||
|
equal(results.length, 0);
|
||||||
|
});
|
||||||
|
}).then(function(){
|
||||||
|
var query = new Parse.Query(Cake);
|
||||||
|
// User1 likes two of three cakes
|
||||||
|
query.equalTo("liker", user1);
|
||||||
|
return query.find().then(function(results){
|
||||||
|
// It should return 2 -> cake 1 and cake 2
|
||||||
|
equal(results.length, 2);
|
||||||
|
});
|
||||||
|
}).then(function(){
|
||||||
|
var query = new Parse.Query(Cake);
|
||||||
|
// We want to know which cake the user1 is not appreciating -> cake3
|
||||||
|
query.notEqualTo("liker", user1);
|
||||||
|
return query.find().then(function(results){
|
||||||
|
// Should return 1 -> the cake 3
|
||||||
|
equal(results.length, 1);
|
||||||
|
});
|
||||||
|
}).then(function(){
|
||||||
|
var query = new Parse.Query(Cake);
|
||||||
|
// User2 is a hater of everything so we should receive 0
|
||||||
|
query.notEqualTo("hater", user2);
|
||||||
|
return query.find().then(function(results){
|
||||||
|
equal(results.length, 0);
|
||||||
|
});
|
||||||
|
}).then(function(){
|
||||||
|
var query = new Parse.Query(Cake);
|
||||||
|
// Only cake3 is liked by user
|
||||||
|
query.notContainedIn("liker", [user1]);
|
||||||
|
return query.find().then(function(results){
|
||||||
|
equal(results.length, 1);
|
||||||
|
});
|
||||||
|
}).then(function(){
|
||||||
|
var query = new Parse.Query(Cake);
|
||||||
|
// All the users
|
||||||
|
query.containedIn("liker", [user, user1, user2]);
|
||||||
|
// Exclude user 1
|
||||||
|
query.notEqualTo("liker", user1);
|
||||||
|
// Only cake3 is liked only by user1
|
||||||
|
return query.find().then(function(results){
|
||||||
|
equal(results.length, 1);
|
||||||
|
let cake = results[0];
|
||||||
|
expect(cake.id).toBe(cake3.id);
|
||||||
|
});
|
||||||
|
}).then(function(){
|
||||||
|
done();
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
it("query with limit", function(done) {
|
it("query with limit", function(done) {
|
||||||
var baz = new TestObject({ foo: 'baz' });
|
var baz = new TestObject({ foo: 'baz' });
|
||||||
var qux = new TestObject({ foo: 'qux' });
|
var qux = new TestObject({ foo: 'qux' });
|
||||||
|
|||||||
@@ -444,26 +444,60 @@ DatabaseController.prototype.reduceInRelation = function(className, query, schem
|
|||||||
}
|
}
|
||||||
|
|
||||||
let promises = Object.keys(query).map((key) => {
|
let promises = Object.keys(query).map((key) => {
|
||||||
if (query[key] && (query[key]['$in'] || query[key].__type == 'Pointer')) {
|
if (query[key] && (query[key]['$in'] || query[key]['$ne'] || query[key]['$nin'] || query[key].__type == 'Pointer')) {
|
||||||
let t = schema.getExpectedType(className, key);
|
let t = schema.getExpectedType(className, key);
|
||||||
let match = t ? t.match(/^relation<(.*)>$/) : false;
|
let match = t ? t.match(/^relation<(.*)>$/) : false;
|
||||||
if (!match) {
|
if (!match) {
|
||||||
return Promise.resolve(query);
|
return Promise.resolve(query);
|
||||||
}
|
}
|
||||||
let relatedClassName = match[1];
|
let relatedClassName = match[1];
|
||||||
let relatedIds;
|
// Build the list of queries
|
||||||
if (query[key]['$in']) {
|
let queries = Object.keys(query[key]).map((constraintKey) => {
|
||||||
relatedIds = query[key]['$in'].map(r => r.objectId);
|
let relatedIds;
|
||||||
} else {
|
let isNegation = false;
|
||||||
relatedIds = [query[key].objectId];
|
if (constraintKey === 'objectId') {
|
||||||
}
|
relatedIds = [query[key].objectId];
|
||||||
return this.owningIds(className, key, relatedIds).then((ids) => {
|
} else if (constraintKey == '$in') {
|
||||||
delete query[key];
|
relatedIds = query[key]['$in'].map(r => r.objectId);
|
||||||
this.addInObjectIdsIds(ids, query);
|
} else if (constraintKey == '$nin') {
|
||||||
return Promise.resolve(query);
|
isNegation = true;
|
||||||
|
relatedIds = query[key]['$nin'].map(r => r.objectId);
|
||||||
|
} else if (constraintKey == '$ne') {
|
||||||
|
isNegation = true;
|
||||||
|
relatedIds = [query[key]['$ne'].objectId];
|
||||||
|
} else {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
isNegation,
|
||||||
|
relatedIds
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// remove the current queryKey as we don,t need it anymore
|
||||||
|
delete query[key];
|
||||||
|
// execute each query independnently to build the list of
|
||||||
|
// $in / $nin
|
||||||
|
let promises = queries.map((q) => {
|
||||||
|
if (!q) {
|
||||||
|
return Promise.resolve();
|
||||||
|
}
|
||||||
|
return this.owningIds(className, key, q.relatedIds).then((ids) => {
|
||||||
|
if (q.isNegation) {
|
||||||
|
this.addNotInObjectIdsIds(ids, query);
|
||||||
|
} else {
|
||||||
|
this.addInObjectIdsIds(ids, query);
|
||||||
|
}
|
||||||
|
return Promise.resolve();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return Promise.all(promises).then(() => {
|
||||||
|
return Promise.resolve();
|
||||||
|
})
|
||||||
|
|
||||||
}
|
}
|
||||||
return Promise.resolve(query);
|
return Promise.resolve();
|
||||||
})
|
})
|
||||||
|
|
||||||
return Promise.all(promises).then(() => {
|
return Promise.all(promises).then(() => {
|
||||||
@@ -520,6 +554,29 @@ DatabaseController.prototype.addInObjectIdsIds = function(ids = null, query) {
|
|||||||
return query;
|
return query;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
DatabaseController.prototype.addNotInObjectIdsIds = function(ids = null, query) {
|
||||||
|
let idsFromNin = query.objectId && query.objectId['$nin'] ? query.objectId['$nin'] : null;
|
||||||
|
let allIds = [idsFromNin, ids].filter(list => list !== null);
|
||||||
|
let totalLength = allIds.reduce((memo, list) => memo + list.length, 0);
|
||||||
|
|
||||||
|
let idsIntersection = [];
|
||||||
|
if (totalLength > 125) {
|
||||||
|
idsIntersection = intersect.big(allIds);
|
||||||
|
} else {
|
||||||
|
idsIntersection = intersect(allIds);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Need to make sure we don't clobber existing $lt or other constraints on objectId.
|
||||||
|
// Clobbering $eq, $in and shorthand $eq (query.objectId === 'string') constraints
|
||||||
|
// is expected though.
|
||||||
|
if (!('objectId' in query) || typeof query.objectId === 'string') {
|
||||||
|
query.objectId = {};
|
||||||
|
}
|
||||||
|
query.objectId['$nin'] = idsIntersection;
|
||||||
|
|
||||||
|
return query;
|
||||||
|
}
|
||||||
|
|
||||||
// Runs a query on the database.
|
// Runs a query on the database.
|
||||||
// Returns a promise that resolves to a list of items.
|
// Returns a promise that resolves to a list of items.
|
||||||
// Options:
|
// Options:
|
||||||
|
|||||||
Reference in New Issue
Block a user