perf(search): a one- or two-letter last word is matched whole, not as a prefix

A 1-2 letter prefix matched most of the library and ts_rank scored every
match: "m" took ~4-8 s on prod against ~150 ms now. The prefix match
starts at three characters (MIN_PREFIX_LEN); shared by the YouTube feed
and the Plex library search.
This commit is contained in:
2026-10-02 21:52:47 +02:00
parent e6de18b8c0
commit ac53a3b757
2 changed files with 28 additions and 4 deletions
+10 -2
View File
@@ -60,16 +60,24 @@ def now_utc() -> datetime:
return datetime.now(timezone.utc)
# The shortest last word that is prefix-matched. A 1–2 letter prefix matches a large share of the
# library and ts_rank then scores every match: "m" took ~5–8 s on prod against ~100 ms for a whole
# word (E31 S4e). A shorter last word is matched whole instead ("tv", "4k" still find themselves).
MIN_PREFIX_LEN = 3
def to_tsquery_str(q: str) -> str | None:
"""Turn free-text into a to_tsquery() string: word runs ANDed together, the LAST word
prefix-matched (`:*`) for as-you-type search — e.g. "ti amo magyar" → "ti & amo & magyar:*".
prefix-matched (`:*`) for as-you-type search — e.g. "ti amo magyar" → "ti & amo & magyar:*" —
once it has MIN_PREFIX_LEN characters ("ti amo m" → "ti & amo & m").
Only `\\w` runs containing an alphanumeric are kept, so no tsquery operators can leak in
(the string is built from sanitised tokens, never the raw query). Shared by the YouTube feed
search and the Plex library search, hence this neutral home rather than either domain module."""
tokens = [t for t in re.findall(r"\w+", q, flags=re.UNICODE) if any(c.isalnum() for c in t)]
if not tokens:
return None
tokens[-1] = tokens[-1] + ":*"
if len(tokens[-1]) >= MIN_PREFIX_LEN:
tokens[-1] = tokens[-1] + ":*"
return " & ".join(tokens)