Use one combined tag query for a multiple-row selection

This commit is contained in:
Dan Stillman 2026-06-16 15:54:57 -04:00
parent 2dae3615d9
commit 6bbcaba7ff
2 changed files with 40 additions and 3 deletions

View file

@ -130,9 +130,12 @@ Zotero.TagSelector = class TagSelectorContainer extends React.PureComponent {
*/
async _safeGetTags(...args) {
try {
let tags = (await Promise.all(
this.collectionTreeRows.map(row => row.getTags(...args))
)).flat();
let rows = this.collectionTreeRows || [];
// A single row uses its own cached temp table directly; multiple rows are
// combined into one temp table and query to avoid a per-row query
let tags = rows.length == 1
? await rows[0].getTags(...args)
: await Zotero.CollectionTreeRow.getTagsAcrossRows(rows, ...args);
// Multiple rows (collections, or collections across libraries) can return
// the same tag, so dedupe by name
return this._dedupeTags(tags);

View file

@ -567,6 +567,40 @@ Zotero.CollectionTreeRow.prototype.getTags = async function (types, tagIDs) {
};
/**
* Returns all the tags used by items across multiple rows' views
*
* Combines the rows' search results into a single temporary table and runs one tag
* query, rather than creating a temp table and running a separate query per row.
*
* @param {Zotero.CollectionTreeRow[]} rows
* @param {Number[]} [types]
* @param {Number[]} [tagIDs]
* @return {Promise<Object[]>}
*/
Zotero.CollectionTreeRow.getTagsAcrossRows = async function (rows, types, tagIDs) {
// share/bucket/feeds rows never contribute tags (see getTags())
var tagRows = rows.filter(row => !['share', 'bucket', 'feeds'].includes(row.type));
if (!tagRows.length) {
return [];
}
// Combine the rows' search results into a single set of item IDs
var itemIDs = new Set();
for (let ids of await Promise.all(tagRows.map(row => row.getSearchResults(false)))) {
for (let id of ids) {
itemIDs.add(id);
}
}
var tmpTable = await Zotero.Search.idsToTempTable([...itemIDs]);
try {
return await Zotero.Tags.getAllWithin({ tmpTable, types, tagIDs });
}
finally {
await Zotero.DB.queryAsync(`DROP TABLE IF EXISTS ${tmpTable}`, false, { noCache: true });
}
};
/**
* Clear the per-instance search cache. Call this at the start of a refresh cycle
* or when search/tag filters change, so the next getSearchResults()/getSearchObject()