soulseek: match share filter word by word
A folder's name is rarely something you can type in full — you remember the band and one word of the album, not the year, the format tag or where the apostrophe went. Whole-string matching made those two halves useless together: "pogues hell" matched nothing, because no path has them adjacent. Each word is now its own substring test, AND-ed and order-independent, so the two ends you do remember narrow the tree between them. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -290,9 +290,15 @@ export async function searchSoulseekBrowseTree(params: BrowseTreeSearchParams):
|
||||
const term = q.trim();
|
||||
if (snapshotId === null || !term) return { nodes: [], matched: [], total: 0, truncated: false };
|
||||
|
||||
// Words are AND-ed, each matched anywhere in the path, so 'pogues hell' finds
|
||||
// '…\The Pogues\… - Hell's Ditch …' — a folder you can name from two ends but never type in full.
|
||||
// ILIKE with a leading wildcard can't use the btree index, so this was already a scan of the snapshot's
|
||||
// ~18k rows — which is why folding the column here costs nothing on top.
|
||||
const where = and(eq(soulseekBrowseDirs.snapshotId, snapshotId), sql`${foldedName} ilike ${likePattern(term)}`);
|
||||
// ~18k rows — which is why folding the column, once per word, costs nothing on top.
|
||||
const words = term.split(/\s+/).filter(Boolean);
|
||||
const where = and(
|
||||
eq(soulseekBrowseDirs.snapshotId, snapshotId),
|
||||
...words.map((word) => sql`${foldedName} ilike ${likePattern(word)}`),
|
||||
);
|
||||
|
||||
const [matches, [count]] = await Promise.all([
|
||||
db.select(treeCols).from(soulseekBrowseDirs).where(where).orderBy(asc(soulseekBrowseDirs.name)).limit(limit),
|
||||
|
||||
Reference in New Issue
Block a user