diff --git a/src/document/search.js b/src/document/search.js index d1db2ec1..86a48077 100644 --- a/src/document/search.js +++ b/src/document/search.js @@ -89,7 +89,7 @@ Document.prototype.search = function(query, limit, options, _promises){ let result = []; let result_field = []; let pluck, enrich, merge, suggest, boost, cache; - let field, tag, offset, count = 0, resolve = true, highlight; + let field, tag, offset, count = 0, resolve = true, highlight, merge_limit = 0; if(options){ @@ -123,6 +123,10 @@ Document.prototype.search = function(query, limit, options, _promises){ limit = options.limit || limit; offset = options.offset || 0; limit || (limit = (resolve ? 100 : 0)); + // per-field options below may override "limit" for individual fields, + // so the overall limit requested by the caller has to be captured here + // to cap the merged result correctly. + merge_limit = limit; if(tag && (!SUPPORT_PERSISTENT || !this.db || !_promises)){ @@ -550,7 +554,7 @@ Document.prototype.search = function(query, limit, options, _promises){ result = highlight_fields(/** @type {string} */ (query), result, self.index, pluck, highlight); } return merge - ? merge_fields(result) + ? merge_fields(result, merge_limit) : /** @type {DocumentSearchResults} */ (result); }); } @@ -559,7 +563,7 @@ Document.prototype.search = function(query, limit, options, _promises){ result = highlight_fields(/** @type {string} */ (query), result, this.index, pluck, highlight); } return merge - ? merge_fields(result) + ? merge_fields(result, merge_limit) : /** @type {DocumentSearchResults} */ (result); } @@ -570,14 +574,15 @@ Document.prototype.search = function(query, limit, options, _promises){ /** * @param {DocumentSearchResults} fields + * @param {number=} limit * @return {MergedDocumentSearchResults} */ -function merge_fields(fields){ +function merge_fields(fields, limit){ /** @type {MergedDocumentSearchResults} */ const final = []; const group_field = create_object(); const group_highlight = create_object(); - for(let i = 0, field, key, res, id, entry, tmp, highlight; i < fields.length; i++){ + outer: for(let i = 0, field, key, res, id, entry, tmp, highlight; i < fields.length; i++){ field = fields[i]; key = field.field; res = field.result; @@ -589,6 +594,12 @@ function merge_fields(fields){ : id = entry["id"]; tmp = group_field[id]; if(!tmp){ + // results are merged across fields, so the per-field limit + // applied earlier does not cap the merged total; stop once + // the overall requested limit has been reached + if(limit && final.length === limit){ + break outer; + } entry["field"] = group_field[id] = [key]; final.push(/** @type {!MergedDocumentSearchEntry} */ (entry)); } diff --git a/test/document.js b/test/document.js index 0bbc696d..83723066 100644 --- a/test/document.js +++ b/test/document.js @@ -458,6 +458,42 @@ if(!build_light) describe("Document (Multi-Field Search)", function(){ ]); }); + it("Should cap merged results at the requested limit", function(){ + + const document = new Document({ + document: { + id: "id", + field: [ + "name_1", + "name_2", + "name_3" + ] + } + }); + + const names = ["foo", "bar"]; + + // each field matches a different, non-overlapping half of the ids, + // so merging the per-field results (each already capped at "limit") + // produces more unique ids than "limit" unless the merge step + // re-applies the cap itself + for(let i = 0; i < 30; i++){ + document.add({ + id: i, + name_1: names[i % 2], + name_2: names[(i + 1) % 2], + name_3: names[i % 2] + }); + } + + const result = document.search("foo", { + merge: true, + limit: 10 + }); + + expect(result.length).to.equal(10); + }); + it("Using BigInt", function(){ const document = new Document({