From 2782c960530b82e26ee7a9c817e929b16f86ea58 Mon Sep 17 00:00:00 2001 From: gofabian Date: Wed, 26 Oct 2016 22:01:37 +0200 Subject: [PATCH 1/3] Add Parse.Query.and() to request that all queries must match --- src/ParseQuery.js | 64 +++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 53 insertions(+), 11 deletions(-) diff --git a/src/ParseQuery.js b/src/ParseQuery.js index 9904cd232..aa4409dff 100644 --- a/src/ParseQuery.js +++ b/src/ParseQuery.js @@ -44,6 +44,24 @@ function quote(s: string) { return '\\Q' + s.replace('\\E', '\\E\\\\E\\Q') + '\\E'; } +/** + * Extracts the class name from queries. If not all queries have the same + * class name an error will be thrown. + */ +function _getClassNameFromQueries(queries: Array): string { + var className = null; + queries.forEach((q) => { + if (!className) { + className = q.className; + } + + if (className !== q.className) { + throw new Error('All queries must be for the same class.'); + } + }); + return className; +} + /** * Creates a new parse Parse.Query for the given Parse.Object subclass. * @class Parse.Query @@ -154,6 +172,21 @@ export default class ParseQuery { return this; } + /** + * Adds constraint that all of the passed in queries match. + * @method _andQuery + * @param {Array} queries + * @return {Parse.Query} Returns the query, so you can chain this call. + */ + _andQuery(queries: Array): ParseQuery { + var queryJSON = queries.map((q) => { + return q.toJSON().where; + }); + + this._where.$and = queryJSON; + return this; + } + /** * Helper for condition queries */ @@ -1019,21 +1052,30 @@ export default class ParseQuery { * @return {Parse.Query} The query that is the OR of the passed in queries. */ static or(...queries: Array): ParseQuery { - var className = null; - queries.forEach((q) => { - if (!className) { - className = q.className; - } - - if (className !== q.className) { - throw new Error('All queries must be for the same class.'); - } - }); - + var className = _getClassNameFromQueries(queries); var query = new ParseQuery(className); query._orQuery(queries); return query; } + + /** + * Constructs a Parse.Query that is the AND of the passed in queries. For + * example: + *
var compoundQuery = Parse.Query.and(query1, query2, query3);
+ * + * will create a compoundQuery that is an and of the query1, query2, and + * query3. + * @method and + * @param {...Parse.Query} var_args The list of queries to AND. + * @static + * @return {Parse.Query} The query that is the AND of the passed in queries. + */ + static and(...queries: Array): ParseQuery { + var className = _getClassNameFromQueries(queries); + var query = new ParseQuery(className); + query._andQuery(queries); + return query; + } } var DefaultController = { From ba3cafc8d340f3a9929c7f1f4fe37749f9f4225e Mon Sep 17 00:00:00 2001 From: gofabian Date: Fri, 28 Oct 2016 19:44:02 +0200 Subject: [PATCH 2/3] Add test for Parse.Query.and() --- src/__tests__/ParseQuery-test.js | 34 ++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/src/__tests__/ParseQuery-test.js b/src/__tests__/ParseQuery-test.js index 070dc8569..62f9ab46f 100644 --- a/src/__tests__/ParseQuery-test.js +++ b/src/__tests__/ParseQuery-test.js @@ -879,6 +879,40 @@ describe('ParseQuery', () => { }); }); + it('can combine queries with an AND clause', () => { + var q = new ParseQuery('Item'); + var q2 = new ParseQuery('Purchase'); + expect(ParseQuery.and.bind(null, q, q2)).toThrow( + 'All queries must be for the same class.' + ); + + q2 = new ParseQuery('Item'); + q.equalTo('size', 'medium'); + q2.equalTo('size', 'large'); + + var mediumOrLarge = ParseQuery.and(q, q2); + expect(mediumOrLarge.toJSON()).toEqual({ + where: { + $and: [ + { size: 'medium' }, + { size: 'large' } + ] + } + }); + + // It removes limits, skips, etc + q.limit(10); + mediumOrLarge = ParseQuery.and(q, q2); + expect(mediumOrLarge.toJSON()).toEqual({ + where: { + $and: [ + { size: 'medium' }, + { size: 'large' } + ] + } + }); + }); + it('can get the first object of a query', (done) => { CoreManager.setQueryController({ find(className, params, options) { From 5f3d4669c9e740a3b794fb4ab1d28c315b34cde3 Mon Sep 17 00:00:00 2001 From: gofabian Date: Fri, 28 Oct 2016 20:49:52 +0200 Subject: [PATCH 3/3] Add integration tests for Parse.Query.and() --- integration/test/ParseQueryTest.js | 64 +++++++++++++++++++++++++++--- 1 file changed, 59 insertions(+), 5 deletions(-) diff --git a/integration/test/ParseQueryTest.js b/integration/test/ParseQueryTest.js index b5736c4bf..2efc0a36b 100644 --- a/integration/test/ParseQueryTest.js +++ b/integration/test/ParseQueryTest.js @@ -577,7 +577,7 @@ describe('Parse Query', () => { assert.equal(results[2].get('string'), 'd'); assert.equal(results[3].get('number'), 1); assert.equal(results[3].get('string'), 'b'); - + let query = new Parse.Query(TestObject); query.equalTo('doubleDescending', true); query.descending('number, string'); @@ -607,7 +607,7 @@ describe('Parse Query', () => { assert.equal(results[2].get('string'), 'd'); assert.equal(results[3].get('number'), 1); assert.equal(results[3].get('string'), 'b'); - + let query = new Parse.Query(TestObject); query.equalTo('doubleDescending', true); query.descending('number', 'string'); @@ -622,7 +622,7 @@ describe('Parse Query', () => { assert.equal(results[2].get('string'), 'd'); assert.equal(results[3].get('number'), 1); assert.equal(results[3].get('string'), 'b'); - + done(); }); }); @@ -759,7 +759,7 @@ describe('Parse Query', () => { assert.equal(results.length, 2); assert.equal(results[0].id, objects[0].id); assert.equal(results[1].id, objects[1].id); - + let query = new Parse.Query('TestObject'); query.equalTo('timed2', true); query.greaterThan('createdAt', objects[2].createdAt); @@ -1205,7 +1205,7 @@ describe('Parse Query', () => { }).then((results) => { assert.equal(results.length, 1); assert.equal(results[0].get('name'), 'Bob'); - + let query = new Parse.Query(Restaurant); query.greaterThan('rating', 4); let mainQuery = new Parse.Query(Person); @@ -1305,6 +1305,60 @@ describe('Parse Query', () => { }); }); + it('can build AND queries', (done) => { + let objects = []; + for (let i = 0; i < 10; i++) { + let obj = new Parse.Object('BoxedNumber'); + obj.set({ x: i, and: true }); + objects.push(obj); + } + Parse.Object.saveAll(objects).then(() => { + let q1 = new Parse.Query('BoxedNumber'); + q1.equalTo('and', true); + q1.greaterThan('x', 2); + let q2 = new Parse.Query('BoxedNumber'); + q2.equalTo('and', true); + q2.lessThan('x', 5); + let andQuery = Parse.Query.and(q1, q2); + return andQuery.find(); + }).then((results) => { + assert.equal(results.length, 2); + results.forEach((number) => { + assert(number.get('x') > 2 && number.get('x') < 5); + }); + done(); + }).fail(e => console.log(e)); + }); + + it('can build complex AND queries', (done) => { + let objects = []; + for (let i = 0; i < 10; i++) { + let child = new Parse.Object('Child'); + child.set('x', i); + child.set('and', true); + let parent = new Parse.Object('Parent'); + parent.set('child', child); + parent.set('and', true); + parent.set('y', i); + objects.push(parent); + } + Parse.Object.saveAll(objects).then(() => { + let subQuery = new Parse.Query('Child'); + subQuery.equalTo('x', 4); + subQuery.equalTo('and', true); + let q1 = new Parse.Query('Parent'); + q1.matchesQuery('child', subQuery); + let q2 = new Parse.Query('Parent'); + q2.equalTo('and', true); + q2.equalTo('y', 4); + let andQuery = new Parse.Query.and(q1, q2); + return andQuery.find(); + }).then((results) => { + assert.equal(results.length, 1); + done(); + }).fail(e => console.log(e)); + }); + it('can iterate over results with each', (done) => { let items = []; for (let i = 0; i < 50; i++) {