Skip to content

Commit dac7101

Browse files
committed
Merge pull request #769 from ParsePlatform/flovilmart.fixFailingQueries
Adds support for multiple $in, pointer / relation queries on $or
2 parents d8d3743 + d872f52 commit dac7101

File tree

2 files changed

+132
-21
lines changed

2 files changed

+132
-21
lines changed

spec/ParseRelation.spec.js

Lines changed: 89 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -237,7 +237,7 @@ describe('Parse.Relation testing', () => {
237237
success: function(list) {
238238
equal(list.length, 1, "There should be only one result");
239239
equal(list[0].id, parent2.id,
240-
"Should have gotten back the right result");
240+
"Should have gotten back the right result");
241241
done();
242242
}
243243
});
@@ -246,6 +246,94 @@ describe('Parse.Relation testing', () => {
246246
}
247247
});
248248
});
249+
250+
it("queries on relation fields with multiple ins", (done) => {
251+
var ChildObject = Parse.Object.extend("ChildObject");
252+
var childObjects = [];
253+
for (var i = 0; i < 10; i++) {
254+
childObjects.push(new ChildObject({x: i}));
255+
}
256+
257+
Parse.Object.saveAll(childObjects).then(() => {
258+
var ParentObject = Parse.Object.extend("ParentObject");
259+
var parent = new ParentObject();
260+
parent.set("x", 4);
261+
var relation = parent.relation("child");
262+
relation.add(childObjects[0]);
263+
relation.add(childObjects[1]);
264+
relation.add(childObjects[2]);
265+
var parent2 = new ParentObject();
266+
parent2.set("x", 3);
267+
var relation2 = parent2.relation("child");
268+
relation2.add(childObjects[4]);
269+
relation2.add(childObjects[5]);
270+
relation2.add(childObjects[6]);
271+
272+
var otherChild2 = parent2.relation("otherChild");
273+
otherChild2.add(childObjects[0]);
274+
otherChild2.add(childObjects[1]);
275+
otherChild2.add(childObjects[2]);
276+
277+
var parents = [];
278+
parents.push(parent);
279+
parents.push(parent2);
280+
return Parse.Object.saveAll(parents);
281+
}).then(() => {
282+
var query = new Parse.Query(ParentObject);
283+
var objects = [];
284+
objects.push(childObjects[0]);
285+
query.containedIn("child", objects);
286+
query.containedIn("otherChild", [childObjects[0]]);
287+
return query.find();
288+
}).then((list) => {
289+
equal(list.length, 2, "There should be 2 results");
290+
done();
291+
});
292+
});
293+
294+
it("or queries on pointer and relation fields", (done) => {
295+
var ChildObject = Parse.Object.extend("ChildObject");
296+
var childObjects = [];
297+
for (var i = 0; i < 10; i++) {
298+
childObjects.push(new ChildObject({x: i}));
299+
}
300+
301+
Parse.Object.saveAll(childObjects).then(() => {
302+
var ParentObject = Parse.Object.extend("ParentObject");
303+
var parent = new ParentObject();
304+
parent.set("x", 4);
305+
var relation = parent.relation("toChilds");
306+
relation.add(childObjects[0]);
307+
relation.add(childObjects[1]);
308+
relation.add(childObjects[2]);
309+
310+
var parent2 = new ParentObject();
311+
parent2.set("x", 3);
312+
parent2.set("toChild", childObjects[2]);
313+
314+
var parents = [];
315+
parents.push(parent);
316+
parents.push(parent2);
317+
parents.push(new ParentObject());
318+
319+
return Parse.Object.saveAll(parents).then(() => {
320+
var query1 = new Parse.Query(ParentObject);
321+
query1.containedIn("toChilds", [childObjects[2]]);
322+
var query2 = new Parse.Query(ParentObject);
323+
query2.equalTo("toChild", childObjects[2]);
324+
var query = Parse.Query.or(query1, query2);
325+
return query.find().then((list) => {
326+
var objectIds = list.map(function(item){
327+
return item.id;
328+
});
329+
expect(objectIds.indexOf(parent.id)).not.toBe(-1);
330+
expect(objectIds.indexOf(parent2.id)).not.toBe(-1);
331+
equal(list.length, 2, "There should be 2 results");
332+
done();
333+
});
334+
});
335+
});
336+
});
249337

250338
it("Get query on relation using un-fetched parent object", (done) => {
251339
// Setup data model

src/Controllers/DatabaseController.js

Lines changed: 43 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -360,13 +360,11 @@ DatabaseController.prototype.deleteEverything = function() {
360360
function keysForQuery(query) {
361361
var sublist = query['$and'] || query['$or'];
362362
if (sublist) {
363-
var answer = new Set();
364-
for (var subquery of sublist) {
365-
for (var key of keysForQuery(subquery)) {
366-
answer.add(key);
367-
}
368-
}
369-
return answer;
363+
let answer = sublist.reduce((memo, subquery) => {
364+
return memo.concat(keysForQuery(subquery));
365+
}, []);
366+
367+
return new Set(answer);
370368
}
371369

372370
return new Set(Object.keys(query));
@@ -391,44 +389,69 @@ DatabaseController.prototype.owningIds = function(className, key, relatedIds) {
391389
// Modifies query so that it no longer has $in on relation fields, or
392390
// equal-to-pointer constraints on relation fields.
393391
// Returns a promise that resolves when query is mutated
394-
// TODO: this only handles one of these at a time - make it handle more
395392
DatabaseController.prototype.reduceInRelation = function(className, query, schema) {
393+
396394
// Search for an in-relation or equal-to-relation
397-
for (var key in query) {
398-
if (query[key] &&
399-
(query[key]['$in'] || query[key].__type == 'Pointer')) {
400-
var t = schema.getExpectedType(className, key);
401-
var match = t ? t.match(/^relation<(.*)>$/) : false;
395+
// Make it sequential for now, not sure of paralleization side effects
396+
if (query['$or']) {
397+
let ors = query['$or'];
398+
return Promise.all(ors.map((aQuery, index) => {
399+
return this.reduceInRelation(className, aQuery, schema).then((aQuery) => {
400+
query['$or'][index] = aQuery;
401+
})
402+
}));
403+
}
404+
405+
let promises = Object.keys(query).map((key) => {
406+
if (query[key] && (query[key]['$in'] || query[key].__type == 'Pointer')) {
407+
let t = schema.getExpectedType(className, key);
408+
let match = t ? t.match(/^relation<(.*)>$/) : false;
402409
if (!match) {
403-
continue;
410+
return Promise.resolve(query);
404411
}
405-
var relatedClassName = match[1];
406-
var relatedIds;
412+
let relatedClassName = match[1];
413+
let relatedIds;
407414
if (query[key]['$in']) {
408415
relatedIds = query[key]['$in'].map(r => r.objectId);
409416
} else {
410417
relatedIds = [query[key].objectId];
411418
}
412419
return this.owningIds(className, key, relatedIds).then((ids) => {
413420
delete query[key];
414-
query.objectId = {'$in': ids};
421+
query.objectId = Object.assign({'$in': []}, query.objectId);
422+
query.objectId['$in'] = query.objectId['$in'].concat(ids);
423+
return Promise.resolve(query);
415424
});
416425
}
417-
}
418-
return Promise.resolve();
426+
return Promise.resolve(query);
427+
})
428+
429+
return Promise.all(promises).then(() => {
430+
return Promise.resolve(query);
431+
})
419432
};
420433

421434
// Modifies query so that it no longer has $relatedTo
422435
// Returns a promise that resolves when query is mutated
423436
DatabaseController.prototype.reduceRelationKeys = function(className, query) {
437+
438+
if (query['$or']) {
439+
return Promise.all(query['$or'].map((aQuery) => {
440+
return this.reduceRelationKeys(className, aQuery);
441+
}));
442+
}
443+
424444
var relatedTo = query['$relatedTo'];
425445
if (relatedTo) {
426446
return this.relatedIds(
427447
relatedTo.object.className,
428448
relatedTo.key,
429449
relatedTo.object.objectId).then((ids) => {
430450
delete query['$relatedTo'];
431-
query['objectId'] = {'$in': ids};
451+
query.objectId = query.objectId || {};
452+
let queryIn = query.objectId['$in'] || [];
453+
queryIn = queryIn.concat(ids);
454+
query['objectId'] = {'$in': queryIn};
432455
return this.reduceRelationKeys(className, query);
433456
});
434457
}

0 commit comments

Comments
 (0)