diff --git a/app/services/search_service.ts b/app/services/search_service.ts index c5e26c9..f0d9955 100644 --- a/app/services/search_service.ts +++ b/app/services/search_service.ts @@ -85,6 +85,19 @@ function contains(word: string): string { return `%${word.replace(/[\\%_]/g, (c) => `\\${c}`)}%` } +/** + * A search word's condition on release_texts: anywhere in a text (ILIKE), or with a leading "^" at a word's start, the + * text's or after a character that's no letter or digit ("^zed" finds "Zed.Dead", "The_Zed", not "Facialized"). Both + * use the trigram index. + */ +function wordMatch(word: string): { sql: string; binding: string } { + if (word.startsWith('^') && word.length > 1) { + const rest = word.slice(1).replace(/[\\^$.|?*+()[\]{}]/g, (c) => `\\${c}`) + return { sql: 't.text ~* ?', binding: `(^|[^[:alnum:]])${rest}` } + } + return { sql: 't.text ILIKE ?', binding: contains(word) } +} + /** Searching and browsing the catalog, for the web interface and Newznab. */ class SearchService { async search(params: SearchParams) { @@ -93,10 +106,12 @@ class SearchService { if (!params.judged) where.push('NOT EXISTS (SELECT 1 FROM release_verdicts v WHERE v.release_id = r.id)') const bindings: unknown[] = [] // Every word in one of the release's texts (its name, the names the prober found inside; release_texts, by its - // trigram index, which yields ids: never an OR across the two tables); "The.Block.AU" and "the block au" alike. + // trigram index, which yields ids: never an OR across the two tables); "The.Block.AU" and "the block au" alike; + // "^zed" only at a word's start. for (const word of (params.q ?? '').split(/[\s._-]+/).filter(Boolean).slice(0, 8)) { - where.push('r.id IN (SELECT t.release_id FROM release_texts t WHERE t.text ILIKE ?)') - bindings.push(contains(word)) + const match = wordMatch(word) + where.push(`r.id IN (SELECT t.release_id FROM release_texts t WHERE ${match.sql})`) + bindings.push(match.binding) } if (params.readable) where.push('(r.readable OR r.inner_name IS NOT NULL)') if (params.complete) where.push('r.complete') diff --git a/public/index.html b/public/index.html index 6b73f85..80291ea 100644 --- a/public/index.html +++ b/public/index.html @@ -22,7 +22,7 @@