From 10618939986fabd3fa6244740650657eebb70f26 Mon Sep 17 00:00:00 2001 From: Dan Stillman Date: Tue, 19 Feb 2019 01:58:22 -0500 Subject: [PATCH] "Attachment Content" search improvements - Fix incorrect results for ANY search with multiple "Attachment Content" conditions and no other conditions - Dramatically speed up single-word searches by avoiding unnecessary text scans (which probably addresses #1595) - Clean up code --- chrome/content/zotero/xpcom/data/search.js | 246 ++++++++++----------- test/tests/data/search/baz.pdf | Bin 0 -> 12373 bytes test/tests/searchTest.js | 74 ++++++- 3 files changed, 176 insertions(+), 144 deletions(-) create mode 100644 test/tests/data/search/baz.pdf diff --git a/chrome/content/zotero/xpcom/data/search.js b/chrome/content/zotero/xpcom/data/search.js index ccae5c4bbd..02b3629f61 100644 --- a/chrome/content/zotero/xpcom/data/search.js +++ b/chrome/content/zotero/xpcom/data/search.js @@ -617,148 +617,130 @@ Zotero.Search.prototype.search = Zotero.Promise.coroutine(function* (asTempTable //Zotero.debug('IDs from main search or subsearch: '); //Zotero.debug(ids); - //Zotero.debug('Join mode: ' + joinMode); - // Filter results with fulltext search + // Filter results with full-text search // - // If join mode ALL, return the (intersection of main and fulltext word search) - // filtered by fulltext content + // If join mode ALL, return the (intersection of main and full-text word search) + // filtered by full-text content. // - // If join mode ANY or there's a quicksearch (which we assume - // fulltextContent is part of), return the union of the main search and - // (a separate fulltext word search filtered by fulltext content) - for (let condition of Object.values(this._conditions)){ - if (condition['condition']=='fulltextContent'){ - var fulltextWordIntersectionFilter = (val, index, array) => !!hash[val]; - var fulltextWordIntersectionConditionFilter = function(val, index, array) { - return hash[val] ? - (condition.operator == 'contains') : - (condition.operator == 'doesNotContain'); - }; - - // Regexp mode -- don't use fulltext word index - if (condition.mode && condition.mode.startsWith('regexp')) { - // In an ANY search with other conditions that alter the results, only bother - // scanning items that haven't already been found by the main search, as long as - // they're in the right library - if (joinMode == 'any' && this._hasPrimaryConditions) { - if (!tmpTable) { - tmpTable = yield Zotero.Search.idsToTempTable(ids); - } - - var sql = "SELECT GROUP_CONCAT(itemID) FROM items WHERE " - + "itemID NOT IN (SELECT itemID FROM " + tmpTable + ")"; - if (this.libraryID) { - sql += " AND libraryID=" + parseInt(this.libraryID); - } - - var res = yield Zotero.DB.valueQueryAsync(sql); - var scopeIDs = res ? res.split(",").map(id => parseInt(id)) : []; - } - // If an ALL search, scan only items from the main search - else { - var scopeIDs = ids; - } - } - // If not regexp mode, run a new search against the fulltext word - // index for words in this phrase - else { - Zotero.debug('Running subsearch against fulltext word index'); - var s = new Zotero.Search(); + // If join mode ANY or there's a quicksearch (which we assume fulltextContent is part of) + // and the main search is filtered by other conditions, return the union of the main search + // and (separate full-text word searches filtered by fulltext content). + // + // If join mode ANY or there's a quicksearch and the main search isn't filtered, return just + // the union of (separate full-text word searches filtered by full-text content). + var fullTextResults; + var joinModeAny = joinMode == 'any' || hasQuicksearch; + for (let condition of Object.values(this._conditions)) { + if (condition.condition != 'fulltextContent') continue; + + if (!fullTextResults) { + // For join mode ANY, if we already filtered the main set, add those as results. + // Otherwise, start with an empty set. + fullTextResults = joinModeAny && this._hasPrimaryConditions + ? ids + : []; + } + + let scopeIDs; + // Regexp mode -- don't use full-text word index + let numSplits; + if (condition.mode && condition.mode.startsWith('regexp')) { + // In ANY mode, include items that haven't already been found, as long as they're in + // the right library + if (joinModeAny) { + let tmpTable = yield Zotero.Search.idsToTempTable(fullTextResults); + let sql = "SELECT GROUP_CONCAT(itemID) FROM items WHERE " + + "itemID NOT IN (SELECT itemID FROM " + tmpTable + ")"; if (this.libraryID) { - s.libraryID = this.libraryID; - } - - // Add any necessary conditions to the fulltext word search -- - // those that are required in an ANY search and any outside the - // quicksearch in an ALL search - for (let c of Object.values(this._conditions)) { - if (c.condition == 'blockStart') { - var inQS = true; - continue; - } - else if (c.condition == 'blockEnd') { - inQS = false; - continue; - } - else if (c.condition == 'fulltextContent' || inQS) { - continue; - } - else if (joinMode == 'any' && !c.required) { - continue; - } - s.addCondition(c.condition, c.operator, c.value); - } - - var splits = Zotero.Fulltext.semanticSplitter(condition.value); - for (let split of splits){ - s.addCondition('fulltextWord', condition.operator, split); - } - var fulltextWordIDs = yield s.search(); - - //Zotero.debug("Fulltext word IDs"); - //Zotero.debug(fulltextWordIDs); - - // If ALL mode, set intersection of main search and fulltext word index - // as the scope for the fulltext content search - if (joinMode == 'all' && !hasQuicksearch) { - var hash = {}; - for (let i=0; i parseInt(id)) : []; + yield Zotero.DB.queryAsync("DROP TABLE " + tmpTable); } - - if (scopeIDs && scopeIDs.length) { - var fulltextIDs = yield Zotero.Fulltext.findTextInItems(scopeIDs, - condition['value'], condition['mode']); - - var hash = {}; - for (let i=0; i !resultsSet.has(id)); + } + // In ALL mode, include the intersection of hits from word index and remaining + // main search matches + else { + let wordIDs = new Set(wordMatches); + scopeIDs = ids.filter(id => wordIDs.has(id)); + } + } + + // If only one word, just use the results from the word index + let filteredIDs = []; + if (numSplits === 1) { + filteredIDs = scopeIDs; + } + // Search the full-text content + else if (scopeIDs.length) { + let found = new Set( + yield Zotero.Fulltext.findTextInItems( + scopeIDs, + condition.value, + condition.mode + ).map(x => x.id) + ); + // Either include or exclude the results, depending on the operator + filteredIDs = scopeIDs.filter((id) => { + return found.has(id) + ? condition.operator == 'contains' + : condition.operator == 'doesNotContain'; + }); + } + + //Zotero.debug("Filtered IDs:") + //Zotero.debug(filteredIDs); + + // If join mode ANY, add any new items from the full-text content search to the results, + // and remove from the scope so that we don't search through items we already matched + if (joinModeAny) { + //Zotero.debug("Adding filtered IDs to results and removing from scope"); + fullTextResults = fullTextResults.concat(filteredIDs); + + let idSet = new Set(ids); + for (let id of filteredIDs) { + idSet.delete(id); + } + ids = Array.from(idSet); + } + else { + //Zotero.debug("Replacing results with filtered IDs"); + ids = filteredIDs; + fullTextResults = filteredIDs; + } + } + if (fullTextResults) { + ids = Array.from(new Set(fullTextResults)); } if (this.hasPostSearchFilter() && diff --git a/test/tests/data/search/baz.pdf b/test/tests/data/search/baz.pdf new file mode 100644 index 0000000000000000000000000000000000000000..1b3c8d860f61b27281d50bc535b6e0bd8b93691f GIT binary patch literal 12373 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 literal 0 HcmV?d00001 diff --git a/test/tests/searchTest.js b/test/tests/searchTest.js index 43f5a9fc6d..d66c687eee 100644 --- a/test/tests/searchTest.js +++ b/test/tests/searchTest.js @@ -120,20 +120,29 @@ describe("Zotero.Search", function() { var userLibraryID; var fooItem; var foobarItem; + var bazItem; var fooItemGroup; var foobarItemGroup; + var bazItemGroup; - before(function* () { + before(async function () { + await resetDB({ + thisArg: this, + skipBundledFiles: true + }); + // Hidden browser, which requires a browser window, needed for charset detection // (until we figure out a better way) - win = yield loadBrowserWindow(); - fooItem = yield importFileAttachment("search/foo.html"); - foobarItem = yield importFileAttachment("search/foobar.html"); + win = await loadBrowserWindow(); + fooItem = await importFileAttachment("search/foo.html"); + foobarItem = await importFileAttachment("search/foobar.html"); + bazItem = await importFileAttachment("search/baz.pdf"); userLibraryID = fooItem.libraryID; - let group = yield getGroup(); - fooItemGroup = yield importFileAttachment("search/foo.html", { libraryID: group.libraryID }); - foobarItemGroup = yield importFileAttachment("search/foobar.html", { libraryID: group.libraryID }); + let group = await getGroup(); + fooItemGroup = await importFileAttachment("search/foo.html", { libraryID: group.libraryID }); + foobarItemGroup = await importFileAttachment("search/foobar.html", { libraryID: group.libraryID }); + bazItemGroup = await importFileAttachment("search/baz.pdf", { libraryID: group.libraryID }); }); after(function* () { @@ -142,8 +151,10 @@ describe("Zotero.Search", function() { } yield fooItem.eraseTx(); yield foobarItem.eraseTx(); + yield bazItem.eraseTx(); yield fooItemGroup.eraseTx(); yield foobarItemGroup.eraseTx(); + yield bazItemGroup.eraseTx(); }); describe("Conditions", function () { @@ -238,14 +249,25 @@ describe("Zotero.Search", function() { assert.sameMembers(matches, [foobarItem.id]); }); - it("should find matching item with joinMode=ANY and non-matching other condition", function* () { + it("should find matching items with joinMode=ANY with no other conditions", async function () { var s = new Zotero.Search(); s.libraryID = userLibraryID; s.addCondition('joinMode', 'any'); - s.addCondition('fulltextContent', 'contains', 'foo bar'); + s.addCondition('fulltextContent', 'contains', 'foo'); + s.addCondition('fulltextContent', 'contains', 'bar'); + var matches = await s.search(); + assert.sameMembers(matches, [fooItem.id, foobarItem.id]); + }); + + it("should find matching items with joinMode=ANY and non-matching other condition", function* () { + var s = new Zotero.Search(); + s.libraryID = userLibraryID; + s.addCondition('joinMode', 'any'); + s.addCondition('fulltextContent', 'contains', 'foo'); + s.addCondition('fulltextContent', 'contains', 'bar'); s.addCondition('title', 'contains', 'nomatch'); var matches = yield s.search(); - assert.sameMembers(matches, [foobarItem.id]); + assert.sameMembers(matches, [fooItem.id, foobarItem.id]); }); it("should find matching items in regexp mode with joinMode=ANY with matching other condition", function* () { @@ -287,6 +309,34 @@ describe("Zotero.Search", function() { var matches = yield s.search(); assert.sameMembers(matches, [foobarItem.id]); }); + + it("should find items that don't contain a single word with joinMode=ANY", async function () { + var s = new Zotero.Search(); + s.libraryID = userLibraryID; + s.addCondition('joinMode', 'any'); + s.addCondition('fulltextContent', 'doesNotContain', 'foo'); + var matches = await s.search(); + assert.notIncludeMembers(matches, [fooItem.id, foobarItem.id]); + }); + + it("should find items that don't contain a phrase with joinMode=ANY", async function () { + var s = new Zotero.Search(); + s.libraryID = userLibraryID; + s.addCondition('joinMode', 'any'); + s.addCondition('fulltextContent', 'doesNotContain', 'foo bar'); + var matches = await s.search(); + assert.notIncludeMembers(matches, [foobarItem.id]); + }); + + it("should find items that don't contain a regexp pattern with joinMode=ANY", async function () { + var s = new Zotero.Search(); + s.libraryID = userLibraryID; + s.addCondition('joinMode', 'any'); + s.addCondition('fulltextContent/regexp', 'doesNotContain', 'foo.+bar'); + var matches = await s.search(); + assert.notIncludeMembers(matches, [foobarItem.id]); + assert.includeMembers(matches, [fooItem.id, bazItem.id]); + }); }); describe("fulltextWord", function () { @@ -302,7 +352,7 @@ describe("Zotero.Search", function() { it("should not return non-matches with full-text conditions", function* () { let s = new Zotero.Search(); s.libraryID = userLibraryID; - s.addCondition('fulltextWord', 'contains', 'baz'); + s.addCondition('fulltextWord', 'contains', 'nomatch'); let matches = yield s.search(); assert.lengthOf(matches, 0); }); @@ -332,7 +382,7 @@ describe("Zotero.Search", function() { s.libraryID = userLibraryID; s.addCondition('joinMode', 'any'); s.addCondition('fulltextWord', 'contains', 'bar'); - s.addCondition('fulltextWord', 'contains', 'baz'); + s.addCondition('fulltextWord', 'contains', 'nomatch'); let matches = yield s.search(); assert.deepEqual(matches, [foobarItem.id]); });