library: share the artist credit membership queries

The artist page, shuffle, download attribution and artist badge each spelled
out the four credit paths. Reuse the shared credited song and album rows, and
keep only the download-aware album queries hand-written, where the per-credit
download visibility genuinely differs.

The shared song and album rows use UNION ALL so the artist list filter can push
its artist predicate into every branch instead of materialising a deduplicated
union; callers that need a set use DISTINCT or COUNT(DISTINCT).
This commit is contained in:
austinried
2026-10-19 01:27:24 +00:00
parent d03dd982b2
commit d908b5b26d
5 changed files with 138 additions and 93 deletions
@@ -857,6 +857,25 @@ class LibraryDaoTest {
assertTrue(dao.searchSongs(sourceId, "ga", 50).first().isEmpty())
}
@Test
fun theArtistPageAlbumQueryFindsArtistCreditsThroughArtistIndexes() =
runTest {
val plan =
plan(
"SELECT albums.* FROM (SELECT DISTINCT albumId FROM ($CREDITED_ALBUM_ROWS) " +
"WHERE artistId = :artistId) credits " +
"JOIN albums ON albums.sourceId = :sourceId AND albums.id = credits.albumId " +
"ORDER BY year DESC, sortName COLLATE NOCASE, id",
)
assertTrue(plan, plan.contains("index_albums_sourceId_artistId"))
assertTrue(plan, plan.contains("index_album_artists_sourceId_artistId"))
assertTrue(plan, plan.contains("index_songs_sourceId_artistId"))
assertTrue(plan, plan.contains("index_song_artists_sourceId_artistId"))
assertFalse(plan, plan.contains("SCAN albums"))
assertFalse(plan, plan.contains("SCAN songs"))
}
private suspend fun plan(sql: String): String =
db.useReaderConnection { connection ->
connection.usePrepared("EXPLAIN QUERY PLAN $sql") { statement ->
@@ -1636,6 +1636,68 @@ class SyncServiceTest {
)
}
@Test
fun everyArtistConsumerAgreesOnTheCreditedSet() =
runTest {
insertSource()
val source =
FakeMusicSource(
artists = listOf(artist("a1", "Album Artist"), artist("a2", "Guest"), artist("a3", "Track Only")),
albums =
listOf(
album("al1").copy(artistId = "a1", artists = listOf(ArtistRef("a1", "Album Artist"))),
album("al2").copy(artistId = "a2", artists = listOf(ArtistRef("a2", "Guest"))),
),
songs =
listOf(
song("s1").copy(
albumId = "al1",
artistId = "a1",
artists = listOf(ArtistRef("a1", "Album Artist"), ArtistRef("a2", "Guest")),
playCount = 1,
played = 100,
),
song("s2").copy(
albumId = "al1",
artistId = "a2",
artists = listOf(ArtistRef("a2", "Guest"), ArtistRef("a3", "Track Only")),
),
song("s3").copy(
albumId = null,
artistId = "a3",
artists = listOf(ArtistRef("a3", "Track Only")),
),
song("s4").copy(
albumId = "missing",
artistId = "a2",
artists = listOf(ArtistRef("a2", "Guest")),
),
),
)
SyncService(db, source).sync()
db.downloadDao().upsert(SongDownload(1, "s1", DownloadStatus.Completed))
db.downloadDao().upsert(SongDownload(1, "s3", DownloadStatus.Completed))
for (artistId in listOf("a1", "a2", "a3")) {
val artist = db.libraryDao().artistOnce(1, artistId)!!
val songIds = db.queueDao().artistSongIds(1, artistId)
val albums = db.libraryDao().albumsForArtist(1, artistId).first()
val status = db.downloadDao().artistStatus(1, artistId).first()
val downloaded =
db
.downloadDao()
.all()
.count { it.status == DownloadStatus.Completed && it.songId in songIds }
assertEquals("$artistId albums", albums.size.toLong(), artist.albumCount)
assertEquals("$artistId songs", songIds.size.toLong(), artist.songCount)
assertEquals("$artistId length", songIds.size.toLong(), db.queueDao().artistLength(1, artistId))
assertEquals("$artistId total", songIds.size.toLong(), status.total)
assertEquals("$artistId downloaded", downloaded.toLong(), status.downloaded)
}
}
@Test
fun changingOnlyTheLeadArtistRecomputesTheCreditedCounts() =
runTest {