Skip to content

Commit 0e3636d

Browse files
flovilmartdrew-gross
authored andcommitted
Make notEqual work on relations
* ⚡ regression tests for #1349 * 🎉 fixes #1349 * Adds support for multiple constraints on the same key
1 parent 89df778 commit 0e3636d

File tree

2 files changed

+168
-12
lines changed

2 files changed

+168
-12
lines changed

spec/ParseQuery.spec.js

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,105 @@ describe('Parse.Query testing', () => {
2323
});
2424
});
2525

26+
it("notEqualTo with Relation is working", function(done) {
27+
var user = new Parse.User();
28+
user.setPassword("asdf");
29+
user.setUsername("zxcv");
30+
31+
var user1 = new Parse.User();
32+
user1.setPassword("asdf");
33+
user1.setUsername("qwerty");
34+
35+
var user2 = new Parse.User();
36+
user2.setPassword("asdf");
37+
user2.setUsername("asdf");
38+
39+
var Cake = Parse.Object.extend("Cake");
40+
var cake1 = new Cake();
41+
var cake2 = new Cake();
42+
var cake3 = new Cake();
43+
44+
45+
user.signUp().then(function(){
46+
return user1.signUp();
47+
}).then(function(){
48+
return user2.signUp();
49+
}).then(function(){
50+
var relLike1 = cake1.relation("liker");
51+
relLike1.add([user, user1]);
52+
53+
var relDislike1 = cake1.relation("hater");
54+
relDislike1.add(user2);
55+
return cake1.save();
56+
}).then(function(){
57+
var rellike2 = cake2.relation("liker");
58+
rellike2.add([user, user1]);
59+
60+
var relDislike2 = cake2.relation("hater");
61+
relDislike2.add(user2);
62+
63+
return cake2.save();
64+
}).then(function(){
65+
var rellike3 = cake3.relation("liker");
66+
rellike3.add(user);
67+
68+
var relDislike3 = cake3.relation("hater");
69+
relDislike3.add([user1, user2]);
70+
return cake3.save();
71+
}).then(function(){
72+
var query = new Parse.Query(Cake);
73+
// User2 likes nothing so we should receive 0
74+
query.equalTo("liker", user2);
75+
return query.find().then(function(results){
76+
equal(results.length, 0);
77+
});
78+
}).then(function(){
79+
var query = new Parse.Query(Cake);
80+
// User1 likes two of three cakes
81+
query.equalTo("liker", user1);
82+
return query.find().then(function(results){
83+
// It should return 2 -> cake 1 and cake 2
84+
equal(results.length, 2);
85+
});
86+
}).then(function(){
87+
var query = new Parse.Query(Cake);
88+
// We want to know which cake the user1 is not appreciating -> cake3
89+
query.notEqualTo("liker", user1);
90+
return query.find().then(function(results){
91+
// Should return 1 -> the cake 3
92+
equal(results.length, 1);
93+
});
94+
}).then(function(){
95+
var query = new Parse.Query(Cake);
96+
// User2 is a hater of everything so we should receive 0
97+
query.notEqualTo("hater", user2);
98+
return query.find().then(function(results){
99+
equal(results.length, 0);
100+
});
101+
}).then(function(){
102+
var query = new Parse.Query(Cake);
103+
// Only cake3 is liked by user
104+
query.notContainedIn("liker", [user1]);
105+
return query.find().then(function(results){
106+
equal(results.length, 1);
107+
});
108+
}).then(function(){
109+
var query = new Parse.Query(Cake);
110+
// All the users
111+
query.containedIn("liker", [user, user1, user2]);
112+
// Exclude user 1
113+
query.notEqualTo("liker", user1);
114+
// Only cake3 is liked only by user1
115+
return query.find().then(function(results){
116+
equal(results.length, 1);
117+
let cake = results[0];
118+
expect(cake.id).toBe(cake3.id);
119+
});
120+
}).then(function(){
121+
done();
122+
})
123+
});
124+
26125
it("query with limit", function(done) {
27126
var baz = new TestObject({ foo: 'baz' });
28127
var qux = new TestObject({ foo: 'qux' });

src/Controllers/DatabaseController.js

Lines changed: 69 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -444,26 +444,60 @@ DatabaseController.prototype.reduceInRelation = function(className, query, schem
444444
}
445445

446446
let promises = Object.keys(query).map((key) => {
447-
if (query[key] && (query[key]['$in'] || query[key].__type == 'Pointer')) {
447+
if (query[key] && (query[key]['$in'] || query[key]['$ne'] || query[key]['$nin'] || query[key].__type == 'Pointer')) {
448448
let t = schema.getExpectedType(className, key);
449449
let match = t ? t.match(/^relation<(.*)>$/) : false;
450450
if (!match) {
451451
return Promise.resolve(query);
452452
}
453453
let relatedClassName = match[1];
454-
let relatedIds;
455-
if (query[key]['$in']) {
456-
relatedIds = query[key]['$in'].map(r => r.objectId);
457-
} else {
458-
relatedIds = [query[key].objectId];
459-
}
460-
return this.owningIds(className, key, relatedIds).then((ids) => {
461-
delete query[key];
462-
this.addInObjectIdsIds(ids, query);
463-
return Promise.resolve(query);
454+
// Build the list of queries
455+
let queries = Object.keys(query[key]).map((constraintKey) => {
456+
let relatedIds;
457+
let isNegation = false;
458+
if (constraintKey === 'objectId') {
459+
relatedIds = [query[key].objectId];
460+
} else if (constraintKey == '$in') {
461+
relatedIds = query[key]['$in'].map(r => r.objectId);
462+
} else if (constraintKey == '$nin') {
463+
isNegation = true;
464+
relatedIds = query[key]['$nin'].map(r => r.objectId);
465+
} else if (constraintKey == '$ne') {
466+
isNegation = true;
467+
relatedIds = [query[key]['$ne'].objectId];
468+
} else {
469+
return;
470+
}
471+
return {
472+
isNegation,
473+
relatedIds
474+
}
475+
});
476+
477+
// remove the current queryKey as we don,t need it anymore
478+
delete query[key];
479+
// execute each query independnently to build the list of
480+
// $in / $nin
481+
let promises = queries.map((q) => {
482+
if (!q) {
483+
return Promise.resolve();
484+
}
485+
return this.owningIds(className, key, q.relatedIds).then((ids) => {
486+
if (q.isNegation) {
487+
this.addNotInObjectIdsIds(ids, query);
488+
} else {
489+
this.addInObjectIdsIds(ids, query);
490+
}
491+
return Promise.resolve();
492+
});
464493
});
494+
495+
return Promise.all(promises).then(() => {
496+
return Promise.resolve();
497+
})
498+
465499
}
466-
return Promise.resolve(query);
500+
return Promise.resolve();
467501
})
468502

469503
return Promise.all(promises).then(() => {
@@ -520,6 +554,29 @@ DatabaseController.prototype.addInObjectIdsIds = function(ids = null, query) {
520554
return query;
521555
}
522556

557+
DatabaseController.prototype.addNotInObjectIdsIds = function(ids = null, query) {
558+
let idsFromNin = query.objectId && query.objectId['$nin'] ? query.objectId['$nin'] : null;
559+
let allIds = [idsFromNin, ids].filter(list => list !== null);
560+
let totalLength = allIds.reduce((memo, list) => memo + list.length, 0);
561+
562+
let idsIntersection = [];
563+
if (totalLength > 125) {
564+
idsIntersection = intersect.big(allIds);
565+
} else {
566+
idsIntersection = intersect(allIds);
567+
}
568+
569+
// Need to make sure we don't clobber existing $lt or other constraints on objectId.
570+
// Clobbering $eq, $in and shorthand $eq (query.objectId === 'string') constraints
571+
// is expected though.
572+
if (!('objectId' in query) || typeof query.objectId === 'string') {
573+
query.objectId = {};
574+
}
575+
query.objectId['$nin'] = idsIntersection;
576+
577+
return query;
578+
}
579+
523580
// Runs a query on the database.
524581
// Returns a promise that resolves to a list of items.
525582
// Options:

0 commit comments

Comments
 (0)