mirror of
https://github.com/zotero/zotero.git
synced 2026-10-11 03:38:25 +00:00
Match multi-level search condition against any ancestor level for descendant results
Some checks failed
CI / Build, Upload, Test (push) Has been cancelled
Some checks failed
CI / Build, Upload, Test (push) Has been cancelled
A field that exists at more than one level (e.g., Title, on both top-level items and attachments) targeting a descendant result level only matched the closest ancestor, so searching annotations by Title found nothing, since it checked the parent attachment's title rather than the top-level item's. Map down from each ancestor level and union them, testing the predicate once so its bound parameters aren't duplicated. Addresses #5978
This commit is contained in:
parent
340ba92513
commit
02b19d0cde
2 changed files with 70 additions and 0 deletions
|
|
@ -2311,6 +2311,41 @@ Zotero.Search.mapPredicate = function (sql, fromLevel, toLevel, negated = false)
|
|||
return s;
|
||||
};
|
||||
|
||||
// A multi-level field (e.g. title, which exists on both the top-level item and an attachment)
|
||||
// targeting a descendant should match if *any* of those ancestor levels has the value -- an
|
||||
// annotation matches when its parent attachment OR its top-level item has the title (and a
|
||||
// snapshot's URL stays matchable at the attachment level). Map down from each such ancestor
|
||||
// and union them, testing the predicate once (a single `anc IN (...)`) so its bound parameters
|
||||
// aren't duplicated. A negation keeps the single-level behavior below.
|
||||
let ancestorLevels = fromLevels.filter(
|
||||
l => l != 'any' && Zotero.Search._isAncestorLevel(l, toLevel));
|
||||
if (!negated && ancestorLevels.length > 1) {
|
||||
// (toLevel itemID, ancestor itemID) pairs, walking from toLevel up to `anc`. A standalone
|
||||
// attachment/note has no parent, so it stands in as its own top-level item (COALESCE).
|
||||
let pairsTo = (anc) => {
|
||||
let levels = [];
|
||||
let l = toLevel;
|
||||
while (l != anc) {
|
||||
levels.push(l);
|
||||
l = Zotero.Search._levelParent[l];
|
||||
}
|
||||
let aliases = levels.map((_, i) => 't' + i);
|
||||
let fromSQL = `${Zotero.Search._levelChildTable[levels[0]]} ${aliases[0]}`;
|
||||
for (let i = 1; i < levels.length; i++) {
|
||||
fromSQL += ` JOIN ${Zotero.Search._levelChildTable[levels[i]]} ${aliases[i]}`
|
||||
+ ` ON ${aliases[i]}.itemID = ${aliases[i - 1]}.parentItemID`;
|
||||
}
|
||||
let last = aliases[aliases.length - 1];
|
||||
let lastLevel = levels[levels.length - 1];
|
||||
let ancExpr = Zotero.Search._levelCanBeStandalone[lastLevel]
|
||||
? `COALESCE(${last}.parentItemID, ${last}.itemID)`
|
||||
: `${last}.parentItemID`;
|
||||
return `SELECT ${aliases[0]}.itemID AS itemID, ${ancExpr} AS anc FROM ${fromSQL}`;
|
||||
};
|
||||
let union = ancestorLevels.map(pairsTo).join(' UNION ALL ');
|
||||
return `itemID IN (SELECT itemID FROM (${union}) WHERE anc IN (${matches}))`;
|
||||
}
|
||||
|
||||
let mapped = Zotero.Search._isAncestorLevel(toLevel, from)
|
||||
? mapUp(matches, from, toLevel)
|
||||
: mapDown(matches, from, toLevel);
|
||||
|
|
|
|||
|
|
@ -356,6 +356,41 @@ describe("Zotero.Search", function () {
|
|||
await other.eraseTx();
|
||||
});
|
||||
|
||||
it("should match an annotation by its top-level item's title", async function () {
|
||||
// Title exists on both items and attachments, so targeting annotations must
|
||||
// reach the top-level item's title, not just the parent attachment's
|
||||
var title = 'zanntitle' + Zotero.Utilities.randomString();
|
||||
var item = await createDataObject('item', { title });
|
||||
var attachment = await importPDFAttachment(item);
|
||||
var annotation = await createAnnotation('highlight', attachment);
|
||||
|
||||
var s = new Zotero.Search();
|
||||
s.libraryID = userLibraryID;
|
||||
s.addCondition('resultLevel', 'annotation');
|
||||
s.addCondition('title', 'contains', title);
|
||||
assert.sameMembers(await s.search(), [annotation.id]);
|
||||
|
||||
await item.eraseTx();
|
||||
});
|
||||
|
||||
it("should match an annotation by its parent attachment's title", async function () {
|
||||
// The other half of the union: an attachment's own title still matches
|
||||
var title = 'zatttitle' + Zotero.Utilities.randomString();
|
||||
var item = await createDataObject('item');
|
||||
var attachment = await importPDFAttachment(item);
|
||||
attachment.setField('title', title);
|
||||
await attachment.saveTx();
|
||||
var annotation = await createAnnotation('highlight', attachment);
|
||||
|
||||
var s = new Zotero.Search();
|
||||
s.libraryID = userLibraryID;
|
||||
s.addCondition('resultLevel', 'annotation');
|
||||
s.addCondition('title', 'contains', title);
|
||||
assert.sameMembers(await s.search(), [annotation.id]);
|
||||
|
||||
await item.eraseTx();
|
||||
});
|
||||
|
||||
it("should match annotations by type and color", async function () {
|
||||
var item = await createDataObject('item');
|
||||
var attachment = await importPDFAttachment(item);
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue