feat: fuzzy search with parallel DB/MusicBrainz execution

- Add pg_trgm fuzzy search to PostgreSQL artist repository
- Add Lucene fuzzy query (~0.7) for MusicBrainz artist search
- Run DB and MusicBrainz searches in parallel goroutines
- Cache MusicBrainz results to DB for accumulation
- Deduplicate results by external ID
- Filter out special purpose artists (type=Other)
- Add SaveAll method for batch artist inserts
- Move database schema to containers repo
This commit is contained in:
Alexander
2026-05-10 21:13:17 +02:00
parent b4e763be44
commit 2756cc1043
9 changed files with 277 additions and 263 deletions
+27 -2
View File
@@ -42,8 +42,8 @@ func (p *Provider) SearchArtists(ctx context.Context, query string, limit, offse
limit = 25
}
escapedQuery := escapeQuery(query)
data, err := p.client.search(ctx, "artist", fmt.Sprintf("artist:%s", escapedQuery), limit, offset)
luceneQuery := buildFuzzyArtistQuery(query, 0.7)
data, err := p.client.search(ctx, "artist", luceneQuery, limit, offset)
if err != nil {
return nil, fmt.Errorf("search artists: %w", err)
}
@@ -311,6 +311,31 @@ func selectCanonicalRelease(releases []*mbRelease) *mbRelease {
return best
}
func buildFuzzyArtistQuery(query string, similarity float64) string {
terms := strings.Fields(query)
if len(terms) == 0 {
return ""
}
var parts []string
for _, term := range terms {
escaped := escapeForFuzzy(term)
parts = append(parts, fmt.Sprintf("%s~%.1f", escaped, similarity))
}
termQuery := strings.Join(parts, " AND ")
return fmt.Sprintf("(artist:(%s) OR alias:(%s))", termQuery, termQuery)
}
func escapeForFuzzy(s string) string {
special := []string{`+`, `-`, `&`, `|`, `!`, `(`, `)`, `{`, `}`, `[`, `]`, `^`, `"`, `:`, `/`, `\`}
result := s
for _, char := range special {
result = strings.ReplaceAll(result, char, `\`+char)
}
return result
}
func escapeQuery(s string) string {
special := []string{`+`, `-`, `&`, `|`, `!`, `(`, `)`, `{`, `}`, `[`, `]`, `^`, `"`, `~`, `*`, `?`, `:`, `/`, `\`}
result := s