Skip to content

Commit a0d1a35

Browse files
davimacedoflovilmart
authored andcommitted
fix(DatabaseController): Do not match any entry when searching for null in relation field (#3924)
1 parent 4509d25 commit a0d1a35

File tree

4 files changed

+216
-29
lines changed

4 files changed

+216
-29
lines changed

spec/ParseQuery.spec.js

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

26+
it("searching for null", function(done) {
27+
var baz = new TestObject({ foo: null });
28+
var qux = new TestObject({ foo: 'qux' });
29+
var qux2 = new TestObject({ });
30+
Parse.Object.saveAll([baz, qux, qux2], function() {
31+
var query = new Parse.Query(TestObject);
32+
query.equalTo('foo', null);
33+
query.find({
34+
success: function(results) {
35+
equal(results.length, 2);
36+
qux.set('foo', null);
37+
qux.save({
38+
success: function () {
39+
query.find({
40+
success: function (results) {
41+
equal(results.length, 3);
42+
done();
43+
}
44+
});
45+
}
46+
});
47+
}
48+
});
49+
});
50+
});
51+
52+
it("searching for not null", function(done) {
53+
var baz = new TestObject({ foo: null });
54+
var qux = new TestObject({ foo: 'qux' });
55+
var qux2 = new TestObject({ });
56+
Parse.Object.saveAll([baz, qux, qux2], function() {
57+
var query = new Parse.Query(TestObject);
58+
query.notEqualTo('foo', null);
59+
query.find({
60+
success: function(results) {
61+
equal(results.length, 1);
62+
qux.set('foo', null);
63+
qux.save({
64+
success: function () {
65+
query.find({
66+
success: function (results) {
67+
equal(results.length, 0);
68+
done();
69+
}
70+
});
71+
},
72+
error: function (error) { console.log(error); }
73+
});
74+
},
75+
error: function (error) { console.log(error); }
76+
});
77+
});
78+
});
79+
2680
it("notEqualTo with Relation is working", function(done) {
2781
var user = new Parse.User();
2882
user.setPassword("asdf");
@@ -52,6 +106,7 @@ describe('Parse.Query testing', () => {
52106

53107
var relDislike1 = cake1.relation("hater");
54108
relDislike1.add(user2);
109+
55110
return cake1.save();
56111
}).then(function(){
57112
var rellike2 = cake2.relation("liker");
@@ -60,6 +115,9 @@ describe('Parse.Query testing', () => {
60115
var relDislike2 = cake2.relation("hater");
61116
relDislike2.add(user2);
62117

118+
var relSomething = cake2.relation("something");
119+
relSomething.add(user);
120+
63121
return cake2.save();
64122
}).then(function(){
65123
var rellike3 = cake3.relation("liker");
@@ -143,6 +201,21 @@ describe('Parse.Query testing', () => {
143201
return query.find().then(function(results){
144202
equal(results.length, 0);
145203
});
204+
}).then(function(){
205+
var query = new Parse.Query(Cake);
206+
query.equalTo("hater", null);
207+
query.equalTo("liker", null);
208+
// user doesn't hate any cake so this should be 0
209+
return query.find().then(function(results){
210+
equal(results.length, 0);
211+
});
212+
}).then(function(){
213+
var query = new Parse.Query(Cake);
214+
query.equalTo("something", null);
215+
// user doesn't hate any cake so this should be 0
216+
return query.find().then(function(results){
217+
equal(results.length, 0);
218+
});
146219
}).then(function(){
147220
done();
148221
}).catch((err) => {
@@ -2485,6 +2558,24 @@ describe('Parse.Query testing', () => {
24852558
});
24862559
});
24872560

2561+
it('query should not match on array when searching for null', (done) => {
2562+
var target = {__type: 'Pointer', className: 'TestObject', objectId: '123'};
2563+
var obj = new Parse.Object('TestObject');
2564+
obj.set('someKey', 'someValue');
2565+
obj.set('someObjs', [target]);
2566+
obj.save().then(() => {
2567+
var query = new Parse.Query('TestObject');
2568+
query.equalTo('someKey', 'someValue');
2569+
query.equalTo('someObjs', null);
2570+
return query.find();
2571+
}).then((results) => {
2572+
expect(results.length).toEqual(0);
2573+
done();
2574+
}, (error) => {
2575+
console.log(error);
2576+
});
2577+
});
2578+
24882579
// #371
24892580
it('should properly interpret a query v1', (done) => {
24902581
var query = new Parse.Query("C1");

spec/ParseRole.spec.js

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -448,4 +448,83 @@ describe('Parse Role testing', () => {
448448
.catch(done.fail);
449449
});
450450

451+
it('should match when matching in users relation', (done) => {
452+
var user = new Parse.User();
453+
user
454+
.save({ username: 'admin', password: 'admin' })
455+
.then((user) => {
456+
var aCL = new Parse.ACL();
457+
aCL.setPublicReadAccess(true);
458+
aCL.setPublicWriteAccess(true);
459+
var role = new Parse.Role('admin', aCL);
460+
var users = role.relation('users');
461+
users.add(user);
462+
role
463+
.save({}, { useMasterKey: true })
464+
.then(() => {
465+
var query = new Parse.Query(Parse.Role);
466+
query.equalTo('name', 'admin');
467+
query.equalTo('users', user);
468+
query.find().then(function (roles) {
469+
expect(roles.length).toEqual(1);
470+
done();
471+
});
472+
});
473+
});
474+
});
475+
476+
it('should not match any entry when not matching in users relation', (done) => {
477+
var user = new Parse.User();
478+
user
479+
.save({ username: 'admin', password: 'admin' })
480+
.then((user) => {
481+
var aCL = new Parse.ACL();
482+
aCL.setPublicReadAccess(true);
483+
aCL.setPublicWriteAccess(true);
484+
var role = new Parse.Role('admin', aCL);
485+
var users = role.relation('users');
486+
users.add(user);
487+
role
488+
.save({}, { useMasterKey: true })
489+
.then(() => {
490+
var otherUser = new Parse.User();
491+
otherUser
492+
.save({ username: 'otherUser', password: 'otherUser' })
493+
.then((otherUser) => {
494+
var query = new Parse.Query(Parse.Role);
495+
query.equalTo('name', 'admin');
496+
query.equalTo('users', otherUser);
497+
query.find().then(function(roles) {
498+
expect(roles.length).toEqual(0);
499+
done();
500+
});
501+
});
502+
});
503+
});
504+
});
505+
506+
it('should not match any entry when searching for null in users relation', (done) => {
507+
var user = new Parse.User();
508+
user
509+
.save({ username: 'admin', password: 'admin' })
510+
.then((user) => {
511+
var aCL = new Parse.ACL();
512+
aCL.setPublicReadAccess(true);
513+
aCL.setPublicWriteAccess(true);
514+
var role = new Parse.Role('admin', aCL);
515+
var users = role.relation('users');
516+
users.add(user);
517+
role
518+
.save({}, { useMasterKey: true })
519+
.then(() => {
520+
var query = new Parse.Query(Parse.Role);
521+
query.equalTo('name', 'admin');
522+
query.equalTo('users', null);
523+
query.find().then(function (roles) {
524+
expect(roles.length).toEqual(0);
525+
done();
526+
});
527+
});
528+
});
529+
});
451530
});

src/Adapters/Storage/Postgres/PostgresStorageAdapter.js

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -188,7 +188,7 @@ const buildWhereClause = ({ schema, query, index }) => {
188188
// nothingin the schema, it's gonna blow up
189189
if (!schema.fields[fieldName]) {
190190
// as it won't exist
191-
if (fieldValue.$exists === false) {
191+
if (fieldValue && fieldValue.$exists === false) {
192192
continue;
193193
}
194194
}
@@ -202,7 +202,16 @@ const buildWhereClause = ({ schema, query, index }) => {
202202
});
203203
let name = components.slice(0, components.length - 1).join('->');
204204
name += '->>' + components[components.length - 1];
205-
patterns.push(`${name} = '${fieldValue}'`);
205+
if (fieldValue === null) {
206+
patterns.push(`${name} IS NULL`);
207+
} else {
208+
patterns.push(`${name} = '${fieldValue}'`);
209+
}
210+
} else if (fieldValue === null) {
211+
patterns.push(`$${index}:name IS NULL`);
212+
values.push(fieldName);
213+
index += 1;
214+
continue;
206215
} else if (typeof fieldValue === 'string') {
207216
patterns.push(`$${index}:name = $${index + 1}`);
208217
values.push(fieldName, fieldValue);
@@ -231,13 +240,16 @@ const buildWhereClause = ({ schema, query, index }) => {
231240
values.push(...clauseValues);
232241
}
233242

234-
if (fieldValue.$ne) {
243+
if (fieldValue.$ne !== undefined) {
235244
if (isArrayField) {
236245
fieldValue.$ne = JSON.stringify([fieldValue.$ne]);
237246
patterns.push(`NOT array_contains($${index}:name, $${index + 1})`);
238247
} else {
239248
if (fieldValue.$ne === null) {
240-
patterns.push(`$${index}:name <> $${index + 1}`);
249+
patterns.push(`$${index}:name IS NOT NULL`);
250+
values.push(fieldName);
251+
index += 1;
252+
continue;
241253
} else {
242254
// if not null, we need to manually exclude null
243255
patterns.push(`($${index}:name <> $${index + 1} OR $${index}:name IS NULL)`);
@@ -764,6 +776,9 @@ export class PostgresStorageAdapter {
764776
validateKeys(object);
765777

766778
Object.keys(object).forEach(fieldName => {
779+
if (object[fieldName] === null) {
780+
return;
781+
}
767782
var authDataMatch = fieldName.match(/^_auth_data_([a-zA-Z0-9_]+)$/);
768783
if (authDataMatch) {
769784
var provider = authDataMatch[1];

src/Controllers/DatabaseController.js

Lines changed: 27 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -616,13 +616,14 @@ DatabaseController.prototype.reduceInRelation = function(className, query, schem
616616
}
617617

618618
const promises = Object.keys(query).map((key) => {
619+
const t = schema.getExpectedType(className, key);
620+
if (!t || t.type !== 'Relation') {
621+
return Promise.resolve(query);
622+
}
623+
let queries = null;
619624
if (query[key] && (query[key]['$in'] || query[key]['$ne'] || query[key]['$nin'] || query[key].__type == 'Pointer')) {
620-
const t = schema.getExpectedType(className, key);
621-
if (!t || t.type !== 'Relation') {
622-
return Promise.resolve(query);
623-
}
624625
// Build the list of queries
625-
const queries = Object.keys(query[key]).map((constraintKey) => {
626+
queries = Object.keys(query[key]).map((constraintKey) => {
626627
let relatedIds;
627628
let isNegation = false;
628629
if (constraintKey === 'objectId') {
@@ -643,31 +644,32 @@ DatabaseController.prototype.reduceInRelation = function(className, query, schem
643644
relatedIds
644645
}
645646
});
647+
} else {
648+
queries = [{isNegation: false, relatedIds: []}];
649+
}
646650

647-
// remove the current queryKey as we don,t need it anymore
648-
delete query[key];
649-
// execute each query independnently to build the list of
650-
// $in / $nin
651-
const promises = queries.map((q) => {
652-
if (!q) {
653-
return Promise.resolve();
651+
// remove the current queryKey as we don,t need it anymore
652+
delete query[key];
653+
// execute each query independnently to build the list of
654+
// $in / $nin
655+
const promises = queries.map((q) => {
656+
if (!q) {
657+
return Promise.resolve();
658+
}
659+
return this.owningIds(className, key, q.relatedIds).then((ids) => {
660+
if (q.isNegation) {
661+
this.addNotInObjectIdsIds(ids, query);
662+
} else {
663+
this.addInObjectIdsIds(ids, query);
654664
}
655-
return this.owningIds(className, key, q.relatedIds).then((ids) => {
656-
if (q.isNegation) {
657-
this.addNotInObjectIdsIds(ids, query);
658-
} else {
659-
this.addInObjectIdsIds(ids, query);
660-
}
661-
return Promise.resolve();
662-
});
665+
return Promise.resolve();
663666
});
667+
});
664668

665-
return Promise.all(promises).then(() => {
666-
return Promise.resolve();
667-
})
669+
return Promise.all(promises).then(() => {
670+
return Promise.resolve();
671+
})
668672

669-
}
670-
return Promise.resolve();
671673
})
672674

673675
return Promise.all(promises).then(() => {

0 commit comments

Comments
 (0)