diff --git a/src/ParseQuery.js b/src/ParseQuery.js index 796856c79..c05fd0587 100644 --- a/src/ParseQuery.js +++ b/src/ParseQuery.js @@ -1032,6 +1032,37 @@ export default class ParseQuery { return this._addCondition(key, '$geoIntersects', { '$point': point }); } + /** + * Method to find by full text. + * The key and the search fields are required the others are optionals. + * @method fullTextSearch + * @param {String} key The key to structure the where query + * @param {String} search The string to search + * @param {String} language Determine the list of stop words + * @param {Boolean} caseSensitive Dis/en-able the case sensitive search + * @param {Boolean} diacriticSensitive Dis/en-able diacritic sensitive search + * @return {Parse.Query} Returns the query, so you can chain this call. + */ + fullTextSearch(key: string, search: string, language: string, caseSensitive: boolean, diacriticSensitive: boolean): ParseQuery { + if (!key) { + throw new Error('A key is required.'); + } + if (typeof search !== 'string') { + throw new Error('The value being searched for must be a string.'); + } + var options = { '$term': search }; + if (typeof language === 'string') { + options['$language'] = language; + } + if (typeof caseSensitive === "boolean") { + options['$caseSensitive'] = caseSensitive; + } + if (typeof diacriticSensitive === "boolean") { + options['$diacriticSensitive'] = diacriticSensitive; + } + return this._addCondition(key, '$text', { '$search': options }); + } + /** Query Orderings **/ /** @@ -1110,6 +1141,17 @@ export default class ParseQuery { return this; } + /** + * Method to sort the full text search by text score + * @method sortByTextScore + * @return {Parse.Query} Returns the query, so you can chain this call. + */ + sortByTextScore() { + this.ascending('$score'); + this.select(['$score']); + return this; + } + /** Query Options **/ /** diff --git a/src/__tests__/ParseQuery-test.js b/src/__tests__/ParseQuery-test.js index d1d329b39..72a6b50c8 100644 --- a/src/__tests__/ParseQuery-test.js +++ b/src/__tests__/ParseQuery-test.js @@ -1338,7 +1338,7 @@ describe('ParseQuery', () => { }); - + it('overrides cached object with query results', (done) => { jest.dontMock("../ParseObject"); jest.resetModules(); @@ -1347,12 +1347,12 @@ describe('ParseQuery', () => { ParseQuery = require('../ParseQuery').default; ParseObject.enableSingleInstance(); - - var objectToReturn = { - objectId: 'T01', - name: 'Name', - other: 'other', - className:"Thing", + + var objectToReturn = { + objectId: 'T01', + name: 'Name', + other: 'other', + className:"Thing", createdAt: '2017-01-10T10:00:00Z' }; @@ -1368,10 +1368,10 @@ describe('ParseQuery', () => { var testObject; q.find().then((results) => { testObject = results[0]; - + expect(testObject.get("name")).toBe("Name"); expect(testObject.get("other")).toBe("other"); - + objectToReturn = { objectId: 'T01', name: 'Name2'}; var q2 = new ParseQuery("Thing"); return q2.find(); @@ -1393,13 +1393,13 @@ describe('ParseQuery', () => { ParseQuery = require('../ParseQuery').default; ParseObject.enableSingleInstance(); - - var objectToReturn = { - objectId: 'T01', - name: 'Name', - other: 'other', - tbd: 'exists', - className:"Thing", + + var objectToReturn = { + objectId: 'T01', + name: 'Name', + other: 'other', + tbd: 'exists', + className:"Thing", createdAt: '2017-01-10T10:00:00Z', subObject: {key1:"value", key2:"value2", key3:"thisWillGoAway"} }; @@ -1416,14 +1416,14 @@ describe('ParseQuery', () => { var testObject; return q.find().then((results) => { testObject = results[0]; - + expect(testObject.get("name")).toBe("Name"); expect(testObject.get("other")).toBe("other"); expect(testObject.has("tbd")).toBe(true); expect(testObject.get("subObject").key1).toBe("value"); expect(testObject.get("subObject").key2).toBe("value2"); expect(testObject.get("subObject").key3).toBe("thisWillGoAway"); - + var q2 = new ParseQuery("Thing"); q2.select("other", "tbd", "subObject.key1", "subObject.key3"); objectToReturn = { objectId: 'T01', other: 'other2', subObject:{key1:"updatedValue"}}; @@ -1442,7 +1442,7 @@ describe('ParseQuery', () => { expect(testObject.has("tbd")).toBe(false); expect(testObject.get("subObject").key1).toBe("updatedValue"); expect(testObject.get("subObject").key2).toBe("value2"); - expect(testObject.get("subObject").key3).toBeUndefined(); + expect(testObject.get("subObject").key3).toBeUndefined(); done(); }, (error) => { done.fail(error); @@ -1457,12 +1457,12 @@ describe('ParseQuery', () => { ParseQuery = require('../ParseQuery').default; ParseObject.enableSingleInstance(); - - var objectToReturn = { - objectId: 'T01', - name: 'Name', - other: 'other', - className:"Thing", + + var objectToReturn = { + objectId: 'T01', + name: 'Name', + other: 'other', + className:"Thing", createdAt: '2017-01-10T10:00:00Z' }; @@ -1478,10 +1478,10 @@ describe('ParseQuery', () => { var testObject; q.first().then((result) => { testObject = result; - + expect(testObject.get("name")).toBe("Name"); expect(testObject.get("other")).toBe("other"); - + objectToReturn = { objectId: 'T01', name: 'Name2'}; var q2 = new ParseQuery("Thing"); return q2.first(); @@ -1503,13 +1503,13 @@ describe('ParseQuery', () => { ParseQuery = require('../ParseQuery').default; ParseObject.enableSingleInstance(); - - var objectToReturn = { - objectId: 'T01', - name: 'Name', - other: 'other', - tbd: 'exists', - className:"Thing", + + var objectToReturn = { + objectId: 'T01', + name: 'Name', + other: 'other', + tbd: 'exists', + className:"Thing", subObject: {key1:"value", key2:"value2", key3:"thisWillGoAway"}, createdAt: '2017-01-10T10:00:00Z', }; @@ -1526,11 +1526,11 @@ describe('ParseQuery', () => { var testObject; return q.first().then((result) => { testObject = result; - + expect(testObject.get("name")).toBe("Name"); expect(testObject.get("other")).toBe("other"); expect(testObject.has("tbd")).toBe(true); - + var q2 = new ParseQuery("Thing"); q2.select("other", "tbd", "subObject.key1", "subObject.key3"); objectToReturn = { objectId: 'T01', other: 'other2', subObject:{key1:"updatedValue"}}; @@ -1546,10 +1546,10 @@ describe('ParseQuery', () => { }).then(() => { expect(testObject.get("name")).toBe("Name"); expect(testObject.get("other")).toBe("other2"); - expect(testObject.has("tbd")).toBe(false); + expect(testObject.has("tbd")).toBe(false); expect(testObject.get("subObject").key1).toBe("updatedValue"); expect(testObject.get("subObject").key2).toBe("value2"); - expect(testObject.get("subObject").key3).toBeUndefined(); + expect(testObject.get("subObject").key3).toBeUndefined(); done(); }, (error) => { done.fail(error); @@ -1593,12 +1593,12 @@ describe('ParseQuery', () => { ParseQuery = require('../ParseQuery').default; ParseObject.enableSingleInstance(); - - var objectToReturn = { - objectId: 'T01', - name: 'Name', - tbd: 'exists', - className:"Thing", + + var objectToReturn = { + objectId: 'T01', + name: 'Name', + tbd: 'exists', + className:"Thing", createdAt: '2017-01-10T10:00:00Z' }; @@ -1615,7 +1615,7 @@ describe('ParseQuery', () => { var testObject; return q.find().then((results) => { testObject = results[0]; - + expect(testObject.get("name")).toBe("Name"); expect(testObject.has("other")).toBe(false); expect(testObject.has("subObject")).toBe(false); @@ -1635,12 +1635,12 @@ describe('ParseQuery', () => { ParseQuery = require('../ParseQuery').default; ParseObject.enableSingleInstance(); - - var objectToReturn = { - objectId: 'T01', - name: 'Name', - tbd: 'exists', - className:"Thing", + + var objectToReturn = { + objectId: 'T01', + name: 'Name', + tbd: 'exists', + className:"Thing", subObject1: {foo:"bar"}, subObject2: {foo:"bar"}, subObject3: {foo:"bar"}, @@ -1660,7 +1660,7 @@ describe('ParseQuery', () => { var testObject; return q.find().then((results) => { testObject = results[0]; - + expect(testObject.has("subObject1")).toBe(true); expect(testObject.has("subObject2")).toBe(true); expect(testObject.has("subObject3")).toBe(true); @@ -1675,7 +1675,7 @@ describe('ParseQuery', () => { expect(testObject.has("subObject2")).toBe(false); //selected and not returned expect(testObject.has("subObject3")).toBe(true); //not selected, so should still be there expect(testObject.has("subObject4")).toBe(true); //selected and just added - expect(testObject.has("subObject5")).toBe(true); + expect(testObject.has("subObject5")).toBe(true); expect(testObject.get("subObject5").subSubObject).toBeDefined(); expect(testObject.get("subObject5").subSubObject.bar).toBeDefined(); //not selected but a sibiling was, so should still be there }).then(() => { @@ -1685,4 +1685,67 @@ describe('ParseQuery', () => { }); }); + it('full text search with one parameter', () => { + let query = new ParseQuery('Item'); + + query.fullTextSearch('size', 'small'); + + expect(query.toJSON()).toEqual({ + where: { + size: { + $text: { + $search: { + $term: "small" + } + } + } + } + }); + }); + + it('full text search with all parameters', () => { + let query = new ParseQuery('Item'); + + query.fullTextSearch('size', 'medium', 'en', false, true); + + expect(query.toJSON()).toEqual({ + where: { + size: { + $text: { + $search: { + $term: "medium", + $language: "en", + $caseSensitive: false, + $diacriticSensitive: true + } + } + } + } + }); + + }); + + it('add the score for the full text search', () => { + let query = new ParseQuery('Item'); + + query.fullTextSearch('size', 'medium', 'fr'); + query.sortByTextScore(); + + expect(query.toJSON()).toEqual({ + where: { + size: { + $text: { + $search: { + $term: "medium", + $language: "fr" + } + } + } + }, + keys : "$score", + order : "$score" + }); + + }); + });