diff --git a/integration/test/ParseQueryTest.js b/integration/test/ParseQueryTest.js index b01d51f7d..a25a6c156 100644 --- a/integration/test/ParseQueryTest.js +++ b/integration/test/ParseQueryTest.js @@ -1310,6 +1310,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++) { diff --git a/src/ParseQuery.js b/src/ParseQuery.js index b6e5b042a..cfcc6fd84 100644 --- a/src/ParseQuery.js +++ b/src/ParseQuery.js @@ -46,6 +46,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; +} + /* * Handles pre-populating the result data of a query with select fields, * making sure that the data object contains keys for all objects that have @@ -229,6 +247,21 @@ 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 */ @@ -1288,21 +1321,30 @@ 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 = { diff --git a/src/__tests__/ParseQuery-test.js b/src/__tests__/ParseQuery-test.js index 9abb43514..0bc2ed9c5 100644 --- a/src/__tests__/ParseQuery-test.js +++ b/src/__tests__/ParseQuery-test.js @@ -883,6 +883,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({ aggregate() {},