From 2c6e6e7ca208ac2dd1e287297a5065d794c4b3b2 Mon Sep 17 00:00:00 2001 From: Claudio Ortolina Date: Tue, 14 Apr 2026 16:05:51 +0100 Subject: [PATCH] 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. --- lib/music_library/listening_stats.ex | 55 ++++++++++++++++----- test/music_library/listening_stats_test.exs | 1 - 2 files changed, 42 insertions(+), 14 deletions(-) diff --git a/lib/music_library/listening_stats.ex b/lib/music_library/listening_stats.ex index 81b5733d..36bd0e40 100644 --- a/lib/music_library/listening_stats.ex +++ b/lib/music_library/listening_stats.ex @@ -282,6 +282,7 @@ defmodule MusicLibrary.ListeningStats do |> top_albums_aggregate_query(limit) |> top_albums_attach_metadata() |> Repo.all() + |> parse_top_albums_matching_records() end @doc """ @@ -299,6 +300,7 @@ defmodule MusicLibrary.ListeningStats do |> top_albums_aggregate_query(limit) |> top_albums_attach_metadata() |> Repo.all() + |> parse_top_albums_matching_records() end @doc """ @@ -437,10 +439,11 @@ defmodule MusicLibrary.ListeningStats do limit: ^limit end - # Attaches `record_releases` and `cover_hash` lookups to the LIMIT'd - # aggregate result via correlated scalar subqueries. The cost of these - # lookups scales with the number of result rows (≤ limit), not with the - # size of `record_releases`. + # Attaches `matching_records` and `cover_hash` lookups to the LIMIT'd + # aggregate result via correlated subqueries. Groups by release group + # (records.musicbrainz_id) so all records for the same album appear. + # 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 from g in subquery(aggregate_query), select: %{ @@ -450,20 +453,40 @@ defmodule MusicLibrary.ListeningStats do play_count: g.play_count, cover_url: g.cover_url, album_musicbrainz_id: g.album_musicbrainz_id, - collected_record_id: + matching_records: fragment( - "(SELECT min(record_id) FROM record_releases WHERE release_id = ? AND purchased_at IS NOT NULL)", - g.album_musicbrainz_id - ), - wishlisted_record_id: - fragment( - "(SELECT min(record_id) FROM record_releases WHERE release_id = ? AND purchased_at IS NULL)", + """ + (SELECT json_group_array(json_object(\ + 'id', r.id, \ + 'title', r.title, \ + 'format', r.format, \ + '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 ), cover_hash: 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 ) } @@ -571,6 +594,12 @@ defmodule MusicLibrary.ListeningStats do 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("[]"), do: [] diff --git a/test/music_library/listening_stats_test.exs b/test/music_library/listening_stats_test.exs index 56e1ddd9..44525ba7 100644 --- a/test/music_library/listening_stats_test.exs +++ b/test/music_library/listening_stats_test.exs @@ -417,7 +417,6 @@ defmodule MusicLibrary.ListeningStatsTest do end end - @tag :skip test "get_top_albums returns one entry per album", %{expected_record_id: expected_record_id} do results = ListeningStats.get_top_albums(limit: 10)