Return matching_records list from top_albums_attach_metadata

Apply the same release-group JSON aggregation pattern to top albums
queries. Add JSON parsing in get_top_albums/1 and
get_top_albums_by_days/2.
This commit is contained in:
Claudio Ortolina
2026-04-14 16:05:51 +01:00
parent cfd267627c
commit 2c6e6e7ca2
2 changed files with 42 additions and 14 deletions
+42 -13
View File
@@ -282,6 +282,7 @@ defmodule MusicLibrary.ListeningStats do
|> top_albums_aggregate_query(limit) |> top_albums_aggregate_query(limit)
|> top_albums_attach_metadata() |> top_albums_attach_metadata()
|> Repo.all() |> Repo.all()
|> parse_top_albums_matching_records()
end end
@doc """ @doc """
@@ -299,6 +300,7 @@ defmodule MusicLibrary.ListeningStats do
|> top_albums_aggregate_query(limit) |> top_albums_aggregate_query(limit)
|> top_albums_attach_metadata() |> top_albums_attach_metadata()
|> Repo.all() |> Repo.all()
|> parse_top_albums_matching_records()
end end
@doc """ @doc """
@@ -437,10 +439,11 @@ defmodule MusicLibrary.ListeningStats do
limit: ^limit limit: ^limit
end end
# Attaches `record_releases` and `cover_hash` lookups to the LIMIT'd # Attaches `matching_records` and `cover_hash` lookups to the LIMIT'd
# aggregate result via correlated scalar subqueries. The cost of these # aggregate result via correlated subqueries. Groups by release group
# lookups scales with the number of result rows (≤ limit), not with the # (records.musicbrainz_id) so all records for the same album appear.
# size of `record_releases`. # The cost scales with the number of result rows (≤ limit), not with
# the size of `record_releases`.
defp top_albums_attach_metadata(aggregate_query) do defp top_albums_attach_metadata(aggregate_query) do
from g in subquery(aggregate_query), from g in subquery(aggregate_query),
select: %{ select: %{
@@ -450,20 +453,40 @@ defmodule MusicLibrary.ListeningStats do
play_count: g.play_count, play_count: g.play_count,
cover_url: g.cover_url, cover_url: g.cover_url,
album_musicbrainz_id: g.album_musicbrainz_id, album_musicbrainz_id: g.album_musicbrainz_id,
collected_record_id: matching_records:
fragment( fragment(
"(SELECT min(record_id) FROM record_releases WHERE release_id = ? AND purchased_at IS NOT NULL)", """
g.album_musicbrainz_id (SELECT json_group_array(json_object(\
), 'id', r.id, \
wishlisted_record_id: 'title', r.title, \
fragment( 'format', r.format, \
"(SELECT min(record_id) FROM record_releases WHERE release_id = ? AND purchased_at IS NULL)", 'type', r.type, \
'purchased_at', r.purchased_at, \
'cover_hash', r.cover_hash\
)) \
FROM records r \
WHERE r.musicbrainz_id = (\
SELECT r2.musicbrainz_id FROM records r2 \
INNER JOIN record_releases rr ON rr.record_id = r2.id \
WHERE rr.release_id = ? \
LIMIT 1\
))\
""",
g.album_musicbrainz_id g.album_musicbrainz_id
), ),
cover_hash: cover_hash:
fragment( fragment(
"coalesce((SELECT min(cover_hash) FROM record_releases WHERE release_id = ? AND purchased_at IS NOT NULL), (SELECT min(cover_hash) FROM record_releases WHERE release_id = ? AND purchased_at IS NULL))", """
g.album_musicbrainz_id, (SELECT r.cover_hash FROM records r \
WHERE r.musicbrainz_id = (\
SELECT r2.musicbrainz_id FROM records r2 \
INNER JOIN record_releases rr ON rr.record_id = r2.id \
WHERE rr.release_id = ? \
LIMIT 1\
) \
ORDER BY (CASE WHEN r.purchased_at IS NOT NULL THEN 0 ELSE 1 END), r.id \
LIMIT 1)\
""",
g.album_musicbrainz_id g.album_musicbrainz_id
) )
} }
@@ -571,6 +594,12 @@ defmodule MusicLibrary.ListeningStats do
end end
end end
defp parse_top_albums_matching_records(albums) do
Enum.map(albums, fn album ->
%{album | matching_records: parse_matching_records(album.matching_records)}
end)
end
defp parse_matching_records(nil), do: [] defp parse_matching_records(nil), do: []
defp parse_matching_records("[]"), do: [] defp parse_matching_records("[]"), do: []
@@ -417,7 +417,6 @@ defmodule MusicLibrary.ListeningStatsTest do
end end
end end
@tag :skip
test "get_top_albums returns one entry per album", %{expected_record_id: expected_record_id} do test "get_top_albums returns one entry per album", %{expected_record_id: expected_record_id} do
results = ListeningStats.get_top_albums(limit: 10) results = ListeningStats.get_top_albums(limit: 10)