feat: add search queries with partial matching and fuzzy fallback

- Add SearchMediaItems query with ILIKE partial matching
- Add SearchMediaItemsFuzzy query with word_similarity()
- Use sqlc.narg() for named parameters (search_pattern, search_query)
- Rank results by relevance: title > author > series > tags
- Fuzzy threshold set to 0.3 for word_similarity
- Generated Go models with proper parameter types
This commit is contained in:
2026-01-29 20:20:30 -05:00
parent 46fa8f7c55
commit 3145f64a66
3 changed files with 267 additions and 0 deletions
+51
View File
@@ -284,6 +284,57 @@ DELETE FROM media_ratings WHERE media_item_id = $1 AND user_id = $2;
-- name: GetEbookByFilePath :one
SELECT * FROM ebooks WHERE file_path = $1;
-- Search Media Items queries
-- name: SearchMediaItems :many
SELECT mi.*, l.name as library_name, lt.name as library_type_name
FROM media_items mi
JOIN libraries l ON mi.library_id = l.id
JOIN library_types lt ON l.library_type_id = lt.id
LEFT JOIN library_visibility lv ON l.id = lv.library_id AND lv.user_id = sqlc.narg('user_id')
WHERE COALESCE(lv.is_visible, true) = 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
mi.tags ILIKE sqlc.narg('search_pattern') OR
mi.contributors ILIKE sqlc.narg('search_pattern')
)
ORDER BY
CASE
WHEN mi.title ILIKE sqlc.narg('search_pattern') THEN 1
WHEN mi.author ILIKE sqlc.narg('search_pattern') THEN 2
WHEN mi.series ILIKE sqlc.narg('search_pattern') THEN 3
WHEN mi.tags ILIKE sqlc.narg('search_pattern') THEN 4
ELSE 5
END,
mi.title ASC
LIMIT sqlc.narg('limit') OFFSET sqlc.narg('offset');
-- name: SearchMediaItemsFuzzy :many
SELECT mi.*, l.name as library_name, lt.name as library_type_name
FROM media_items mi
JOIN libraries l ON mi.library_id = l.id
JOIN library_types lt ON l.library_type_id = lt.id
LEFT JOIN library_visibility lv ON l.id = lv.library_id AND lv.user_id = sqlc.narg('user_id')
WHERE COALESCE(lv.is_visible, true) = true
AND (
word_similarity(sqlc.narg('search_query'), mi.title) > 0.3 OR
word_similarity(sqlc.narg('search_query'), COALESCE(mi.author, '')) > 0.3 OR
word_similarity(sqlc.narg('search_query'), COALESCE(mi.series, '')) > 0.3 OR
word_similarity(sqlc.narg('search_query'), COALESCE(mi.tags, '')) > 0.3 OR
word_similarity(sqlc.narg('search_query'), COALESCE(mi.contributors, '')) > 0.3
)
ORDER BY
GREATEST(
word_similarity(sqlc.narg('search_query'), mi.title),
word_similarity(sqlc.narg('search_query'), COALESCE(mi.author, '')),
word_similarity(sqlc.narg('search_query'), COALESCE(mi.series, '')),
word_similarity(sqlc.narg('search_query'), COALESCE(mi.tags, '')),
word_similarity(sqlc.narg('search_query'), COALESCE(mi.contributors, ''))
) DESC,
mi.title ASC
LIMIT sqlc.narg('limit') OFFSET sqlc.narg('offset');
-- Media Notes queries
-- name: CreateMediaNote :one
INSERT INTO media_notes (media_item_id, user_id, content, position)