feat(search): add phonetic/fuzzy search via pg_trgm

This commit is contained in:
senke 2026-02-20 18:36:07 +01:00
parent eb2f7e0d8c
commit d4f1d08518

View file

@ -52,13 +52,25 @@ func NewTrackSearchServiceWithDB(db *database.Database) *TrackSearchService {
func (s *TrackSearchService) SearchTracks(ctx context.Context, params TrackSearchParams) ([]*models.Track, int64, error) {
query := s.db.Model(&models.Track{}).Where("is_public = ? AND deleted_at IS NULL", true)
// Full-text search on title, artist, album
// Full-text search on title, artist, album (v0.203 Lot K: pg_trgm similarity, fallback ILIKE for SQLite)
if params.Query != "" {
searchTerm := "%" + strings.ToLower(params.Query) + "%"
query = query.Where(
"LOWER(title) LIKE ? OR LOWER(artist) LIKE ? OR LOWER(album) LIKE ?",
searchTerm, searchTerm, searchTerm,
)
q := strings.ToLower(strings.TrimSpace(params.Query))
if q != "" {
searchTerm := "%" + q + "%"
if s.db.Dialector.Name() == "sqlite" {
// SQLite: use LIKE (no pg_trgm)
query = query.Where(
"LOWER(title) LIKE ? OR LOWER(artist) LIKE ? OR LOWER(album) LIKE ?",
searchTerm, searchTerm, searchTerm,
)
} else {
// PostgreSQL: pg_trgm similarity for fuzzy match
query = query.Where(
`(similarity(LOWER(title), ?) > 0.1 OR similarity(LOWER(artist), ?) > 0.1 OR similarity(LOWER(album), ?) > 0.1)`,
q, q, q,
)
}
}
}
// Tag search (tracks.tags pq.StringArray, migration 085)
@ -142,7 +154,8 @@ func (s *TrackSearchService) SearchTracks(ctx context.Context, params TrackSearc
// Handle different sorting options
switch sortBy {
case "relevance":
// Fallback: order by play_count when no full-text rank available
// When query is set, similarity() in WHERE already filters; order by play_count as proxy for relevance
// (GORM Order doesn't support placeholders for similarity-based sort)
query = query.Order("play_count DESC")
case "popularity":
// Sort by like_count (popularity)