diff --git a/spec/ParseRelation.spec.js b/spec/ParseRelation.spec.js index e8e7258c10..a3fbe82ce5 100644 --- a/spec/ParseRelation.spec.js +++ b/spec/ParseRelation.spec.js @@ -237,7 +237,7 @@ describe('Parse.Relation testing', () => { success: function(list) { equal(list.length, 1, "There should be only one result"); equal(list[0].id, parent2.id, - "Should have gotten back the right result"); + "Should have gotten back the right result"); done(); } }); @@ -246,6 +246,94 @@ describe('Parse.Relation testing', () => { } }); }); + + it("queries on relation fields with multiple ins", (done) => { + var ChildObject = Parse.Object.extend("ChildObject"); + var childObjects = []; + for (var i = 0; i < 10; i++) { + childObjects.push(new ChildObject({x: i})); + } + + Parse.Object.saveAll(childObjects).then(() => { + var ParentObject = Parse.Object.extend("ParentObject"); + var parent = new ParentObject(); + parent.set("x", 4); + var relation = parent.relation("child"); + relation.add(childObjects[0]); + relation.add(childObjects[1]); + relation.add(childObjects[2]); + var parent2 = new ParentObject(); + parent2.set("x", 3); + var relation2 = parent2.relation("child"); + relation2.add(childObjects[4]); + relation2.add(childObjects[5]); + relation2.add(childObjects[6]); + + var otherChild2 = parent2.relation("otherChild"); + otherChild2.add(childObjects[0]); + otherChild2.add(childObjects[1]); + otherChild2.add(childObjects[2]); + + var parents = []; + parents.push(parent); + parents.push(parent2); + return Parse.Object.saveAll(parents); + }).then(() => { + var query = new Parse.Query(ParentObject); + var objects = []; + objects.push(childObjects[0]); + query.containedIn("child", objects); + query.containedIn("otherChild", [childObjects[0]]); + return query.find(); + }).then((list) => { + equal(list.length, 2, "There should be 2 results"); + done(); + }); + }); + + it("or queries on pointer and relation fields", (done) => { + var ChildObject = Parse.Object.extend("ChildObject"); + var childObjects = []; + for (var i = 0; i < 10; i++) { + childObjects.push(new ChildObject({x: i})); + } + + Parse.Object.saveAll(childObjects).then(() => { + var ParentObject = Parse.Object.extend("ParentObject"); + var parent = new ParentObject(); + parent.set("x", 4); + var relation = parent.relation("toChilds"); + relation.add(childObjects[0]); + relation.add(childObjects[1]); + relation.add(childObjects[2]); + + var parent2 = new ParentObject(); + parent2.set("x", 3); + parent2.set("toChild", childObjects[2]); + + var parents = []; + parents.push(parent); + parents.push(parent2); + parents.push(new ParentObject()); + + return Parse.Object.saveAll(parents).then(() => { + var query1 = new Parse.Query(ParentObject); + query1.containedIn("toChilds", [childObjects[2]]); + var query2 = new Parse.Query(ParentObject); + query2.equalTo("toChild", childObjects[2]); + var query = Parse.Query.or(query1, query2); + return query.find().then((list) => { + var objectIds = list.map(function(item){ + return item.id; + }); + expect(objectIds.indexOf(parent.id)).not.toBe(-1); + expect(objectIds.indexOf(parent2.id)).not.toBe(-1); + equal(list.length, 2, "There should be 2 results"); + done(); + }); + }); + }); + }); it("Get query on relation using un-fetched parent object", (done) => { // Setup data model diff --git a/src/Controllers/DatabaseController.js b/src/Controllers/DatabaseController.js index a7d26245a7..d1c6dde59c 100644 --- a/src/Controllers/DatabaseController.js +++ b/src/Controllers/DatabaseController.js @@ -366,13 +366,11 @@ DatabaseController.prototype.deleteEverything = function() { function keysForQuery(query) { var sublist = query['$and'] || query['$or']; if (sublist) { - var answer = new Set(); - for (var subquery of sublist) { - for (var key of keysForQuery(subquery)) { - answer.add(key); - } - } - return answer; + let answer = sublist.reduce((memo, subquery) => { + return memo.concat(keysForQuery(subquery)); + }, []); + + return new Set(answer); } return new Set(Object.keys(query)); @@ -397,19 +395,28 @@ DatabaseController.prototype.owningIds = function(className, key, relatedIds) { // Modifies query so that it no longer has $in on relation fields, or // equal-to-pointer constraints on relation fields. // Returns a promise that resolves when query is mutated -// TODO: this only handles one of these at a time - make it handle more DatabaseController.prototype.reduceInRelation = function(className, query, schema) { + // Search for an in-relation or equal-to-relation - for (var key in query) { - if (query[key] && - (query[key]['$in'] || query[key].__type == 'Pointer')) { - var t = schema.getExpectedType(className, key); - var match = t ? t.match(/^relation<(.*)>$/) : false; + // Make it sequential for now, not sure of paralleization side effects + if (query['$or']) { + let ors = query['$or']; + return Promise.all(ors.map((aQuery, index) => { + return this.reduceInRelation(className, aQuery, schema).then((aQuery) => { + query['$or'][index] = aQuery; + }) + })); + } + + let promises = Object.keys(query).map((key) => { + if (query[key] && (query[key]['$in'] || query[key].__type == 'Pointer')) { + let t = schema.getExpectedType(className, key); + let match = t ? t.match(/^relation<(.*)>$/) : false; if (!match) { - continue; + return Promise.resolve(query); } - var relatedClassName = match[1]; - var relatedIds; + let relatedClassName = match[1]; + let relatedIds; if (query[key]['$in']) { relatedIds = query[key]['$in'].map(r => r.objectId); } else { @@ -417,16 +424,29 @@ DatabaseController.prototype.reduceInRelation = function(className, query, schem } return this.owningIds(className, key, relatedIds).then((ids) => { delete query[key]; - query.objectId = {'$in': ids}; + query.objectId = Object.assign({'$in': []}, query.objectId); + query.objectId['$in'] = query.objectId['$in'].concat(ids); + return Promise.resolve(query); }); } - } - return Promise.resolve(); + return Promise.resolve(query); + }) + + return Promise.all(promises).then(() => { + return Promise.resolve(query); + }) }; // Modifies query so that it no longer has $relatedTo // Returns a promise that resolves when query is mutated DatabaseController.prototype.reduceRelationKeys = function(className, query) { + + if (query['$or']) { + return Promise.all(query['$or'].map((aQuery) => { + return this.reduceRelationKeys(className, aQuery); + })); + } + var relatedTo = query['$relatedTo']; if (relatedTo) { return this.relatedIds( @@ -434,7 +454,10 @@ DatabaseController.prototype.reduceRelationKeys = function(className, query) { relatedTo.key, relatedTo.object.objectId).then((ids) => { delete query['$relatedTo']; - query['objectId'] = {'$in': ids}; + query.objectId = query.objectId || {}; + let queryIn = query.objectId['$in'] || []; + queryIn = queryIn.concat(ids); + query['objectId'] = {'$in': queryIn}; return this.reduceRelationKeys(className, query); }); }