feat: enhance search with date-prioritized year filtering and true exact matching

Improve media item search functionality with two key enhancements:

1. Date-prioritized year filtering:
   - Prioritize date_published over copyright_year for year range queries
   - Fall back to copyright_year when date_published is NULL
   - Extract year from date_published timestamp for comparison

2. True exact search matching:
   - Replace ILIKE pattern matching with exact equality for quoted queries
   - Use search_query directly instead of wildcard pattern for exact matches
   - Remove SearchPattern parameter and related wildcard logic
   - Add COALESCE handling for author/series NULL values in exact matches

These changes make year filtering more accurate with published dates
and provide genuine exact matching when users wrap queries in quotes.

Refs internal/database/queries/queries.sql:475, internal/services/search.go:62
This commit is contained in:
2026-03-26 15:35:31 -04:00
parent 6b518462f9
commit be31cc88f1
4 changed files with 72 additions and 39 deletions
+25 -8
View File
@@ -440,9 +440,17 @@ WHERE COALESCE(lv.is_visible, true) = true
SELECT 1 FROM unnest(mi.tags_search) AS tag
WHERE word_similarity(sqlc.narg('tags_filter'), tag) > 0.3
))
-- Year range (exact)
AND (sqlc.narg('year_min') = 0 OR mi.copyright_year >= sqlc.narg('year_min'))
AND (sqlc.narg('year_max') = 0 OR mi.copyright_year <= sqlc.narg('year_max'))
-- Year range (exact) - prioritize date_published, fallback to copyright_year
AND (
sqlc.narg('year_min') = 0 OR
EXTRACT(YEAR FROM mi.date_published) >= sqlc.narg('year_min') OR
(mi.date_published IS NULL AND mi.copyright_year >= sqlc.narg('year_min'))
)
AND (
sqlc.narg('year_max') = 0 OR
EXTRACT(YEAR FROM mi.date_published) <= sqlc.narg('year_max') OR
(mi.date_published IS NULL AND mi.copyright_year <= sqlc.narg('year_max'))
)
-- Boolean (exact)
AND (sqlc.narg('has_cover') = false OR mi.cover_image_path IS NOT NULL)
-- Search query (fuzzy or exact based on quotes)
@@ -465,12 +473,21 @@ WHERE COALESCE(lv.is_visible, true) = true
)
) OR
-- Exact search (with quotes)
-- Keeping old pattern commented out in case we want wildcard exact back. See search.go line 62
-- sqlc.narg('is_exact_search') = true AND (
-- mi.title ILIKE sqlc.narg('search_pattern') OR
-- mi.author ILIKE sqlc.narg('search_pattern') OR
-- mi.series ILIKE sqlc.narg('search_pattern') OR
-- sqlc.narg('search_pattern') = ANY(mi.tags_search) OR
-- sqlc.narg('search_pattern') = ANY(mi.contributors_search)
-- )
-- Exact search (with quotes) - true exact match, not substring
sqlc.narg('is_exact_search') = true AND (
mi.title ILIKE sqlc.narg('search_pattern') OR
mi.author ILIKE sqlc.narg('search_pattern') OR
mi.series ILIKE sqlc.narg('search_pattern') OR
sqlc.narg('search_pattern') = ANY(mi.tags_search) OR
sqlc.narg('search_pattern') = ANY(mi.contributors_search)
mi.title = sqlc.narg('search_query') OR
COALESCE(mi.author, '') = sqlc.narg('search_query') OR
COALESCE(mi.series, '') = sqlc.narg('search_query') OR
sqlc.narg('search_query') = ANY(mi.tags_search) OR
sqlc.narg('search_query') = ANY(mi.contributors_search)
)
)
ORDER BY