fix(db): split seed_info CTE so similar-artist works without seed genre

The combined seed_info CTE filtered the seed out when its genre was
NULL/empty. Restored the spec's two-CTE design (seed_artist + seed_tags)
so similar-artist matching works regardless of the seed's genre coverage.
Drops the genre workaround from the SimilarArtist test.
This commit is contained in:
2026-04-29 08:43:42 -04:00
parent 51811c9d35
commit b8e3019654
3 changed files with 22 additions and 19 deletions
+11 -7
View File
@@ -100,9 +100,13 @@ func (q *Queries) LoadRadioCandidates(ctx context.Context, arg LoadRadioCandidat
const loadRadioCandidatesV2 = `-- name: LoadRadioCandidatesV2 :many
WITH
seed_info AS (
SELECT t.artist_id,
trim(g)::text AS tag
seed_artist AS (
SELECT artist_id
FROM tracks
WHERE tracks.id = $2
),
seed_tags AS (
SELECT trim(g) AS tag
FROM tracks t
LEFT JOIN LATERAL regexp_split_to_table(coalesce(t.genre, ''), '[;,]') AS g(tag) ON true
WHERE t.id = $2 AND trim(g) <> ''
@@ -127,7 +131,7 @@ similar_artists AS (
SELECT t.id AS track_id, asim.score * 0.5 AS sim_score
FROM artist_similarity asim
JOIN tracks t ON t.artist_id = asim.artist_b_id
JOIN (SELECT DISTINCT artist_id FROM seed_info) sa ON asim.artist_a_id = sa.artist_id
JOIN seed_artist sa ON asim.artist_a_id = sa.artist_id
WHERE asim.source = 'listenbrainz'
AND t.id NOT IN (SELECT id FROM excluded_ids)
ORDER BY asim.score DESC, random()
@@ -136,10 +140,10 @@ similar_artists AS (
tag_overlap AS (
SELECT t.id AS track_id,
(count(DISTINCT trim(g))::float8
/ GREATEST((SELECT count(DISTINCT tag) FROM seed_info), 1)) AS sim_score
/ GREATEST((SELECT count(*) FROM seed_tags), 1)) AS sim_score
FROM tracks t
LEFT JOIN LATERAL regexp_split_to_table(coalesce(t.genre, ''), '[;,]') AS g_split(g) ON true
WHERE trim(g_split.g) IN (SELECT tag FROM seed_info)
WHERE trim(g_split.g) IN (SELECT tag FROM seed_tags)
AND t.id NOT IN (SELECT id FROM excluded_ids)
AND t.id <> $2
GROUP BY t.id
@@ -156,7 +160,7 @@ likes_overlap AS (
SELECT 1 FROM tracks t
LEFT JOIN LATERAL regexp_split_to_table(coalesce(t.genre, ''), '[;,]') AS g_overlap(g) ON true
WHERE t.id = gl.track_id
AND trim(g_overlap.g) IN (SELECT tag FROM seed_info)
AND trim(g_overlap.g) IN (SELECT tag FROM seed_tags)
)
ORDER BY random()
LIMIT $8
+11 -7
View File
@@ -36,9 +36,13 @@ WHERE t.id <> $2
-- Returns same shape as LoadRadioCandidates plus similarity_score column.
WITH
seed_info AS (
SELECT t.artist_id,
trim(g)::text AS tag
seed_artist AS (
SELECT artist_id
FROM tracks
WHERE tracks.id = $2
),
seed_tags AS (
SELECT trim(g) AS tag
FROM tracks t
LEFT JOIN LATERAL regexp_split_to_table(coalesce(t.genre, ''), '[;,]') AS g(tag) ON true
WHERE t.id = $2 AND trim(g) <> ''
@@ -63,7 +67,7 @@ similar_artists AS (
SELECT t.id AS track_id, asim.score * 0.5 AS sim_score
FROM artist_similarity asim
JOIN tracks t ON t.artist_id = asim.artist_b_id
JOIN (SELECT DISTINCT artist_id FROM seed_info) sa ON asim.artist_a_id = sa.artist_id
JOIN seed_artist sa ON asim.artist_a_id = sa.artist_id
WHERE asim.source = 'listenbrainz'
AND t.id NOT IN (SELECT id FROM excluded_ids)
ORDER BY asim.score DESC, random()
@@ -72,10 +76,10 @@ similar_artists AS (
tag_overlap AS (
SELECT t.id AS track_id,
(count(DISTINCT trim(g))::float8
/ GREATEST((SELECT count(DISTINCT tag) FROM seed_info), 1)) AS sim_score
/ GREATEST((SELECT count(*) FROM seed_tags), 1)) AS sim_score
FROM tracks t
LEFT JOIN LATERAL regexp_split_to_table(coalesce(t.genre, ''), '[;,]') AS g_split(g) ON true
WHERE trim(g_split.g) IN (SELECT tag FROM seed_info)
WHERE trim(g_split.g) IN (SELECT tag FROM seed_tags)
AND t.id NOT IN (SELECT id FROM excluded_ids)
AND t.id <> $2
GROUP BY t.id
@@ -92,7 +96,7 @@ likes_overlap AS (
SELECT 1 FROM tracks t
LEFT JOIN LATERAL regexp_split_to_table(coalesce(t.genre, ''), '[;,]') AS g_overlap(g) ON true
WHERE t.id = gl.track_id
AND trim(g_overlap.g) IN (SELECT tag FROM seed_info)
AND trim(g_overlap.g) IN (SELECT tag FROM seed_tags)
)
ORDER BY random()
LIMIT $8
@@ -73,11 +73,6 @@ func TestLoadCandidatesFromSimilarity_LBSimilarSourceContributes(t *testing.T) {
func TestLoadCandidatesFromSimilarity_SimilarArtistTracksContribute(t *testing.T) {
f := newFixture(t, 1) // creates 1 artist + 1 album + 1 track (the seed)
seed := f.tracks[0]
// seed_info CTE only emits artist_id rows when genre is non-NULL (it
// uses a LATERAL regexp_split_to_table join filtered by trim(g) <> '').
// Give the seed a genre so that seed_info is populated and the
// similar_artists JOIN can find artist_a_id = seed.ArtistID.
helperSetTrackGenre(t, f, seed.ID, "Rock")
// Add a SECOND artist + track in that artist; relate the two artists via artist_similarity.
otherArtist, _ := f.q.UpsertArtist(context.Background(), dbq.UpsertArtistParams{Name: "OtherArtist", SortName: "OtherArtist"})
otherAlbum, _ := f.q.UpsertAlbum(context.Background(), dbq.UpsertAlbumParams{Title: "OtherAlbum", SortTitle: "OtherAlbum", ArtistID: otherArtist.ID})