From 27815b18aa7fd26393097ba0994dcd0d29aad121 Mon Sep 17 00:00:00 2001 From: Florent Vilmart Date: Wed, 2 Mar 2016 12:01:49 -0500 Subject: [PATCH 1/4] Adds support for multiple $in --- spec/ParseRelation.spec.js | 52 ++++++++++++++++++++++++++- src/Controllers/DatabaseController.js | 46 ++++++++++++------------ 2 files changed, 75 insertions(+), 23 deletions(-) diff --git a/spec/ParseRelation.spec.js b/spec/ParseRelation.spec.js index e8e7258c10..664a9ba9d5 100644 --- a/spec/ParseRelation.spec.js +++ b/spec/ParseRelation.spec.js @@ -237,7 +237,57 @@ 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(); + } + }); + } + }); + } + }); + }); + + 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, { + success: function() { + 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); + Parse.Object.saveAll(parents, { + success: function() { + var query = new Parse.Query(ParentObject); + var objects = []; + objects.push(childObjects[0]); + query.containedIn("child", objects); + query.containedIn("otherChild", [childObjects[0]]); + query.find({ + success: function(list) { + equal(list.length, 2, "There should be only one result"); done(); } }); diff --git a/src/Controllers/DatabaseController.js b/src/Controllers/DatabaseController.js index a7d26245a7..8901004d5a 100644 --- a/src/Controllers/DatabaseController.js +++ b/src/Controllers/DatabaseController.js @@ -397,31 +397,33 @@ 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; - if (!match) { - continue; - } - var relatedClassName = match[1]; - var relatedIds; - if (query[key]['$in']) { - relatedIds = query[key]['$in'].map(r => r.objectId); - } else { - relatedIds = [query[key].objectId]; + // Make it sequential for now, not sure of paralleization side effects + return Object.keys(query).reduce((promise, key) => { + return promise.then(() => { + 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) { + return; + } + let relatedClassName = match[1]; + let relatedIds; + if (query[key]['$in']) { + relatedIds = query[key]['$in'].map(r => r.objectId); + } else { + relatedIds = [query[key].objectId]; + } + return this.owningIds(className, key, relatedIds).then((ids) => { + delete query[key]; + query.objectId = Object.assign({'$in': []}, query.objectId); + query.objectId['$in'] = query.objectId['$in'].concat(ids); + }); } - return this.owningIds(className, key, relatedIds).then((ids) => { - delete query[key]; - query.objectId = {'$in': ids}; - }); - } - } - return Promise.resolve(); + }); + }, Promise.resolve()); }; // Modifies query so that it no longer has $relatedTo From 3629c40036a2be8c84dfe39a1f9a44002fb48608 Mon Sep 17 00:00:00 2001 From: Florent Vilmart Date: Wed, 2 Mar 2016 14:33:51 -0500 Subject: [PATCH 2/4] Adds support for or queries on pointer and relations --- spec/ParseRelation.spec.js | 50 ++++++++++++++++++++++++++- src/Controllers/DatabaseController.js | 35 ++++++++++++++----- 2 files changed, 76 insertions(+), 9 deletions(-) diff --git a/spec/ParseRelation.spec.js b/spec/ParseRelation.spec.js index 664a9ba9d5..550e4b3328 100644 --- a/spec/ParseRelation.spec.js +++ b/spec/ParseRelation.spec.js @@ -287,7 +287,55 @@ describe('Parse.Relation testing', () => { query.containedIn("otherChild", [childObjects[0]]); query.find({ success: function(list) { - equal(list.length, 2, "There should be only one result"); + 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, { + success: function() { + 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()); + + Parse.Object.saveAll(parents, { + success: function() { + 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); + query.find({ + success: function(list) { + list = list.filter(function(item){ + return item.id == parent.id || item.id == parent2.id; + }); + equal(list.length, 2, "There should be 2 results"); done(); } }); diff --git a/src/Controllers/DatabaseController.js b/src/Controllers/DatabaseController.js index 8901004d5a..923e6e491a 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)); @@ -400,6 +398,17 @@ DatabaseController.prototype.owningIds = function(className, key, relatedIds) { DatabaseController.prototype.reduceInRelation = function(className, query, schema) { // Search for an in-relation or equal-to-relation // 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) => { + if (aQuery) { + query['$or'][index] = aQuery; + } + }) + })); + } + return Object.keys(query).reduce((promise, key) => { return promise.then(() => { if (query[key] && @@ -420,15 +429,25 @@ DatabaseController.prototype.reduceInRelation = function(className, query, schem delete query[key]; query.objectId = Object.assign({'$in': []}, query.objectId); query.objectId['$in'] = query.objectId['$in'].concat(ids); + return Promise.resolve(query); }); } }); - }, Promise.resolve()); + }, Promise.resolve()).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( From 43f014a47d780c5c7dce08c7ae3b9390c393afdb Mon Sep 17 00:00:00 2001 From: Florent Vilmart Date: Wed, 2 Mar 2016 15:16:48 -0500 Subject: [PATCH 3/4] nits --- src/Controllers/DatabaseController.js | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/Controllers/DatabaseController.js b/src/Controllers/DatabaseController.js index 923e6e491a..ecccbb2492 100644 --- a/src/Controllers/DatabaseController.js +++ b/src/Controllers/DatabaseController.js @@ -396,15 +396,14 @@ DatabaseController.prototype.owningIds = function(className, key, relatedIds) { // equal-to-pointer constraints on relation fields. // Returns a promise that resolves when query is mutated DatabaseController.prototype.reduceInRelation = function(className, query, schema) { + // Search for an in-relation or equal-to-relation // 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) => { - if (aQuery) { - query['$or'][index] = aQuery; - } + query['$or'][index] = aQuery; }) })); } @@ -416,7 +415,7 @@ DatabaseController.prototype.reduceInRelation = function(className, query, schem let t = schema.getExpectedType(className, key); let match = t ? t.match(/^relation<(.*)>$/) : false; if (!match) { - return; + return Promise.resolve(query); } let relatedClassName = match[1]; let relatedIds; @@ -455,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); }); } From d872f52eff7387ff4b26bb9e4bc1e2b34974263d Mon Sep 17 00:00:00 2001 From: Florent Vilmart Date: Wed, 2 Mar 2016 20:28:00 -0500 Subject: [PATCH 4/4] backbone style is BAD! --- spec/ParseRelation.spec.js | 106 ++++++++++++-------------- src/Controllers/DatabaseController.js | 48 ++++++------ 2 files changed, 72 insertions(+), 82 deletions(-) diff --git a/spec/ParseRelation.spec.js b/spec/ParseRelation.spec.js index 550e4b3328..a3fbe82ce5 100644 --- a/spec/ParseRelation.spec.js +++ b/spec/ParseRelation.spec.js @@ -254,46 +254,40 @@ describe('Parse.Relation testing', () => { childObjects.push(new ChildObject({x: i})); } - Parse.Object.saveAll(childObjects, { - success: function() { - 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]); + 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); - Parse.Object.saveAll(parents, { - success: function() { - var query = new Parse.Query(ParentObject); - var objects = []; - objects.push(childObjects[0]); - query.containedIn("child", objects); - query.containedIn("otherChild", [childObjects[0]]); - query.find({ - success: function(list) { - equal(list.length, 2, "There should be 2 results"); - done(); - } - }); - } - }); - } + 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(); }); }); @@ -304,8 +298,7 @@ describe('Parse.Relation testing', () => { childObjects.push(new ChildObject({x: i})); } - Parse.Object.saveAll(childObjects, { - success: function() { + Parse.Object.saveAll(childObjects).then(() => { var ParentObject = Parse.Object.extend("ParentObject"); var parent = new ParentObject(); parent.set("x", 4); @@ -323,25 +316,22 @@ describe('Parse.Relation testing', () => { parents.push(parent2); parents.push(new ParentObject()); - Parse.Object.saveAll(parents, { - success: function() { - 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); - query.find({ - success: function(list) { - list = list.filter(function(item){ - return item.id == parent.id || item.id == parent2.id; - }); - equal(list.length, 2, "There should be 2 results"); - done(); - } + 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(); + }); }); - } }); }); diff --git a/src/Controllers/DatabaseController.js b/src/Controllers/DatabaseController.js index ecccbb2492..d1c6dde59c 100644 --- a/src/Controllers/DatabaseController.js +++ b/src/Controllers/DatabaseController.js @@ -408,31 +408,31 @@ DatabaseController.prototype.reduceInRelation = function(className, query, schem })); } - return Object.keys(query).reduce((promise, key) => { - return promise.then(() => { - 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) { - return Promise.resolve(query); - } - let relatedClassName = match[1]; - let relatedIds; - if (query[key]['$in']) { - relatedIds = query[key]['$in'].map(r => r.objectId); - } else { - relatedIds = [query[key].objectId]; - } - return this.owningIds(className, key, relatedIds).then((ids) => { - delete query[key]; - query.objectId = Object.assign({'$in': []}, query.objectId); - query.objectId['$in'] = query.objectId['$in'].concat(ids); - return Promise.resolve(query); - }); + 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) { + return Promise.resolve(query); } - }); - }, Promise.resolve()).then(() => { + let relatedClassName = match[1]; + let relatedIds; + if (query[key]['$in']) { + relatedIds = query[key]['$in'].map(r => r.objectId); + } else { + relatedIds = [query[key].objectId]; + } + return this.owningIds(className, key, relatedIds).then((ids) => { + delete query[key]; + query.objectId = Object.assign({'$in': []}, query.objectId); + query.objectId['$in'] = query.objectId['$in'].concat(ids); + return Promise.resolve(query); + }); + } + return Promise.resolve(query); + }) + + return Promise.all(promises).then(() => { return Promise.resolve(query); }) };