Search: a word with a leading ^ matches at a word's start only
"^zed" finds "Zed Dead", "The_Zed_Files", not "Facialized": a regex on release_texts, (^|[^[:alnum:]]) before the word, which the trigram index serves (homelab: 551 texts for ^zed against 6,606 for zed, 35 ms warm). Words without ^ match anywhere as before; the search box says so. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
3f0b5b8be1
commit
5132ab9083
3 files changed
+41
-4
No files matched your search
@@ -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')
|
||||
|
||||
Reference in new issue
Block a user