fix(search): full-text indexes+caching

Adds a few new indexes to handle full-text searches instead of doing
`ILIKE` matching, improving performance substantially.
Alongside that, a few other things were done in order to improve search
performance, such as caching filter values so they're not computed on
each request to /api/roms. Overall, this should have a very noticeable
impact on large collections when using the search feature.
This commit is contained in:
Daniel Bonofiglio
2026-06-16 20:32:14 -03:00
parent 00af28821c
commit d5ffeeeddb
13 changed files with 504 additions and 67 deletions

View File

@@ -219,6 +219,55 @@ def test_filter_by_search_term_with_multiple_terms(platform: Platform):
assert actual_rom_ids_single == expected_rom_ids_single
def test_filter_by_search_term_multi_word_and_ranking(platform: Platform):
def _add(name: str) -> Rom:
fs = name.replace(" ", "_")
return db_rom_handler.add_rom(
Rom(
platform_id=platform.id,
name=name,
slug=name.lower().replace(" ", "-"),
fs_name=f"{fs}.zip",
fs_name_no_tags=fs,
fs_name_no_ext=fs,
fs_extension="zip",
fs_path=f"{platform.slug}/roms",
)
)
ff = _add("Final Fantasy")
ff7 = _add("Final Fantasy VII")
fantasy_final = _add("Fantasy Final") # both words, reversed order
_add("Final Combat") # only "final"
_add("Angelique - Voice Fantasy") # only "fantasy"
_add("Super Mario World") # neither word
results = db_rom_handler.get_roms_scalar(search_term="final fantasy")
result_ids = [r.id for r in results]
# Only titles containing BOTH words appear (AND semantics).
assert set(result_ids) == {ff.id, ff7.id, fantasy_final.id}
# Exact-order phrase matches rank above the reversed-order match.
assert result_ids.index(ff.id) < result_ids.index(fantasy_final.id)
assert result_ids.index(ff7.id) < result_ids.index(fantasy_final.id)
# The relevance ORDER BY must also survive the group_by_meta_id subquery
# wrapping used by the gallery (each ROM here is its own group).
grouped = db_rom_handler.get_roms_scalar(
search_term="final fantasy", group_by_meta_id=True
)
assert {r.id for r in grouped} == {ff.id, ff7.id, fantasy_final.id}
# An explicit sort takes priority over relevance: ordering by name asc puts
# "Fantasy Final" first (relevance is only the tiebreaker here).
explicit = db_rom_handler.get_roms_scalar(
search_term="final fantasy", order_by="name", order_dir="asc"
)
explicit_ids = [r.id for r in explicit]
assert explicit_ids.index(fantasy_final.id) < explicit_ids.index(ff.id)
def test_sibling_roms_empty_fs_name_no_tags_not_matched(platform: Platform):
"""ROMs with empty fs_name_no_tags should NOT be matched as siblings.