db: collapse paged library queries into one builder per entity

Replace the thirty hand-written order variants with one query builder per
entity plus a default and a downloaded @RawQuery entry point. The
repository's sort dispatch trees and the duplicated positional filters in
LibraryPaging are gone, and the SQL now lives in multi-line strings with
named parameters.

The generated statements are identical to the previous ones modulo
whitespace: the downloaded variants keep the same CASE/download conditions,
and the observed entities match what Room inferred for the old queries. A
small helper rewrites the named parameters to RoomRawQuery's positional
form, including the repeated :sourceId bindings.
This commit is contained in:
austinried
2026-11-03 01:03:45 +00:00
parent 29935b7bee
commit 8d6f7e2341
7 changed files with 472 additions and 674 deletions
@@ -67,17 +67,17 @@ class LibraryDaoTest {
assertEquals(
listOf("Alpha", "Beta", "Gamma"),
dao.albumsByName(sourceId, starredFilter = 0, search = "").page().map { it.name },
dao.albumPages(sourceId, starredFilter = 0, search = "").page().map { it.name },
)
assertEquals(
listOf("Gamma", "Beta", "Alpha"),
dao.albumsByNameReversed(sourceId, starredFilter = 0, search = "").page().map { it.name },
dao.albumPages(sourceId, starredFilter = 0, search = "", descending = true).page().map { it.name },
)
assertEquals(
listOf("Alpha", "Beta", "Gamma"),
dao.albumsByYear(sourceId, starredFilter = 0, search = "").page().map { it.name },
dao.albumPages(sourceId, starredFilter = 0, search = "", sort = AlbumSort.Year).page().map { it.name },
)
assertEquals(listOf("Alpha"), dao.albumsByName(sourceId, starredFilter = 1, search = "").page().map { it.name })
assertEquals(listOf("Alpha"), dao.albumPages(sourceId, starredFilter = 1, search = "").page().map { it.name })
}
@Test
@@ -95,14 +95,14 @@ class LibraryDaoTest {
assertEquals(
listOf("Beta", "Gamma", "Alpha"),
dao.albumsByRecentlyAdded(sourceId, starredFilter = 0, search = "").page().map { it.name },
dao.albumPages(sourceId, starredFilter = 0, search = "", sort = AlbumSort.Added).page().map { it.name },
)
assertEquals(
listOf("Alpha", "Gamma", "Beta"),
dao.albumsByStarred(sourceId, starredFilter = 0, search = "").page().map { it.name },
dao.albumPages(sourceId, starredFilter = 0, search = "", sort = AlbumSort.Starred).page().map { it.name },
)
assertEquals(listOf("Gamma"), dao.albumsByName(sourceId, starredFilter = 0, search = "gam").page().map { it.name })
assertEquals(listOf("Beta"), dao.albumsByName(sourceId, starredFilter = 2, search = "").page().map { it.name })
assertEquals(listOf("Gamma"), dao.albumPages(sourceId, starredFilter = 0, search = "gam").page().map { it.name })
assertEquals(listOf("Beta"), dao.albumPages(sourceId, starredFilter = 2, search = "").page().map { it.name })
}
@Test
@@ -148,19 +148,19 @@ class LibraryDaoTest {
assertEquals(
listOf("Beta", "Alpha", "Delta", "Gamma"),
dao.albumsByStarred(sourceId, 0, "").page().map { it.name },
dao.albumPages(sourceId, 0, "", sort = AlbumSort.Starred).page().map { it.name },
)
assertEquals(
listOf("Alpha", "Beta", "Gamma", "Delta"),
dao.albumsByStarredReversed(sourceId, 0, "").page().map { it.name },
dao.albumPages(sourceId, 0, "", sort = AlbumSort.Starred, descending = true).page().map { it.name },
)
assertEquals(
listOf("Beta", "Alpha", "Delta", "Gamma"),
dao.artistsByStarred(sourceId, 0, "").page().map { it.name },
dao.artistPages(sourceId, 0, "", sort = ArtistSort.Starred).page().map { it.name },
)
assertEquals(
listOf("Alpha", "Beta", "Gamma", "Delta"),
dao.artistsByStarredReversed(sourceId, 0, "").page().map { it.name },
dao.artistPages(sourceId, 0, "", sort = ArtistSort.Starred, descending = true).page().map { it.name },
)
}
@@ -206,19 +206,19 @@ class LibraryDaoTest {
assertEquals(
listOf("Two", "One", "Three"),
dao.albumsByFrequent(sourceId, 0, "").page().map { it.name },
dao.albumPages(sourceId, 0, "", sort = AlbumSort.Frequent).page().map { it.name },
)
assertEquals(
listOf("Two", "One", "Three"),
dao.albumsByRecent(sourceId, 0, "").page().map { it.name },
dao.albumPages(sourceId, 0, "", sort = AlbumSort.Recent).page().map { it.name },
)
assertEquals(
listOf("Many", "Few"),
dao.artistsByFrequent(sourceId, 0, "").page().map { it.name },
dao.artistPages(sourceId, 0, "", sort = ArtistSort.Frequent).page().map { it.name },
)
assertEquals(
listOf("Many", "Few"),
dao.artistsByRecent(sourceId, 0, "").page().map { it.name },
dao.artistPages(sourceId, 0, "", sort = ArtistSort.Recent).page().map { it.name },
)
}
@@ -268,12 +268,17 @@ class LibraryDaoTest {
val pairs =
listOf(
dao.albumsByName(sourceId, 0, "") to dao.albumsByNameReversed(sourceId, 0, ""),
dao.albumsByArtist(sourceId, 0, "") to dao.albumsByArtistReversed(sourceId, 0, ""),
dao.albumsByYear(sourceId, 0, "") to dao.albumsByYearReversed(sourceId, 0, ""),
dao.albumsByRecentlyAdded(sourceId, 0, "") to dao.albumsByRecentlyAddedReversed(sourceId, 0, ""),
dao.albumsByFrequent(sourceId, 0, "") to dao.albumsByFrequentReversed(sourceId, 0, ""),
dao.albumsByRecent(sourceId, 0, "") to dao.albumsByRecentReversed(sourceId, 0, ""),
dao.albumPages(sourceId, 0, "") to dao.albumPages(sourceId, 0, "", descending = true),
dao.albumPages(sourceId, 0, "", sort = AlbumSort.Artist) to
dao.albumPages(sourceId, 0, "", sort = AlbumSort.Artist, descending = true),
dao.albumPages(sourceId, 0, "", sort = AlbumSort.Year) to
dao.albumPages(sourceId, 0, "", sort = AlbumSort.Year, descending = true),
dao.albumPages(sourceId, 0, "", sort = AlbumSort.Added) to
dao.albumPages(sourceId, 0, "", sort = AlbumSort.Added, descending = true),
dao.albumPages(sourceId, 0, "", sort = AlbumSort.Frequent) to
dao.albumPages(sourceId, 0, "", sort = AlbumSort.Frequent, descending = true),
dao.albumPages(sourceId, 0, "", sort = AlbumSort.Recent) to
dao.albumPages(sourceId, 0, "", sort = AlbumSort.Recent, descending = true),
)
for ((base, reversed) in pairs) {
val ids = base.page().map { it.id }
@@ -303,23 +308,23 @@ class LibraryDaoTest {
val artistPairs =
listOf(
dao.artistsByName(sourceId, 0, "").page().map { it.id } to
dao.artistsByNameReversed(sourceId, 0, "").page().map { it.id },
dao.artistsByAlbumCount(sourceId, 0, "").page().map { it.id } to
dao.artistsByAlbumCountReversed(sourceId, 0, "").page().map { it.id },
dao.artistsByFrequent(sourceId, 0, "").page().map { it.id } to
dao.artistsByFrequentReversed(sourceId, 0, "").page().map { it.id },
dao.artistsByRecent(sourceId, 0, "").page().map { it.id } to
dao.artistsByRecentReversed(sourceId, 0, "").page().map { it.id },
dao.artistPages(sourceId, 0, "").page().map { it.id } to
dao.artistPages(sourceId, 0, "", descending = true).page().map { it.id },
dao.artistPages(sourceId, 0, "", sort = ArtistSort.AlbumCount).page().map { it.id } to
dao.artistPages(sourceId, 0, "", sort = ArtistSort.AlbumCount, descending = true).page().map { it.id },
dao.artistPages(sourceId, 0, "", sort = ArtistSort.Frequent).page().map { it.id } to
dao.artistPages(sourceId, 0, "", sort = ArtistSort.Frequent, descending = true).page().map { it.id },
dao.artistPages(sourceId, 0, "", sort = ArtistSort.Recent).page().map { it.id } to
dao.artistPages(sourceId, 0, "", sort = ArtistSort.Recent, descending = true).page().map { it.id },
)
val playlistPairs =
listOf(
dao.playlistsByName(sourceId, "").page().map { it.id } to
dao.playlistsByNameReversed(sourceId, "").page().map { it.id },
dao.playlistsByAdded(sourceId, "").page().map { it.id } to
dao.playlistsByAddedReversed(sourceId, "").page().map { it.id },
dao.playlistsByUpdated(sourceId, "").page().map { it.id } to
dao.playlistsByUpdatedReversed(sourceId, "").page().map { it.id },
dao.playlistPages(sourceId, "").page().map { it.id } to
dao.playlistPages(sourceId, "", descending = true).page().map { it.id },
dao.playlistPages(sourceId, "", sort = PlaylistSort.Added).page().map { it.id } to
dao.playlistPages(sourceId, "", sort = PlaylistSort.Added, descending = true).page().map { it.id },
dao.playlistPages(sourceId, "", sort = PlaylistSort.Updated).page().map { it.id } to
dao.playlistPages(sourceId, "", sort = PlaylistSort.Updated, descending = true).page().map { it.id },
)
for ((base, reversed) in artistPairs + playlistPairs) {
assertEquals(base.reversed(), reversed)
@@ -357,9 +362,11 @@ class LibraryDaoTest {
ALBUM_ORDER_BY_RECENT_REVERSED to "index_albums_recent",
)
for ((order, index) in orders) {
val plan = plan("SELECT * $ALBUMS_FILTER ORDER BY $order LIMIT 20 OFFSET 0")
assertTrue(plan, plan.contains("USING INDEX $index"))
assertFalse(plan, plan.contains("TEMP B-TREE"))
for (filter in listOf(ALBUMS_FILTER, ALBUMS_FILTER_DOWNLOADED)) {
val plan = plan("SELECT * $filter ORDER BY $order LIMIT 20 OFFSET 0")
assertTrue(plan, plan.contains("USING INDEX $index"))
assertFalse(plan, plan.contains("TEMP B-TREE"))
}
}
}
@@ -382,9 +389,11 @@ class LibraryDaoTest {
ARTIST_ORDER_BY_RECENT_REVERSED to "index_artists_recent",
)
for ((order, index) in orders) {
val plan = plan("SELECT * $ARTISTS_FILTER ORDER BY $order LIMIT 20 OFFSET 0")
assertTrue(plan, plan.contains("USING INDEX $index"))
assertFalse(plan, plan.contains("TEMP B-TREE"))
for (filter in listOf(ARTISTS_FILTER, ARTISTS_FILTER_DOWNLOADED)) {
val plan = plan("SELECT * $filter ORDER BY $order LIMIT 20 OFFSET 0")
assertTrue(plan, plan.contains("USING INDEX $index"))
assertFalse(plan, plan.contains("TEMP B-TREE"))
}
}
}
@@ -401,9 +410,11 @@ class LibraryDaoTest {
PLAYLIST_ORDER_BY_UPDATED_REVERSED to "index_playlists_updated",
)
for ((order, index) in orders) {
val plan = plan("SELECT * $PLAYLISTS_FILTER ORDER BY $order LIMIT 20 OFFSET 0")
assertTrue(plan, plan.contains("USING INDEX $index"))
assertFalse(plan, plan.contains("TEMP B-TREE"))
for (filter in listOf(PLAYLISTS_FILTER, PLAYLISTS_FILTER_DOWNLOADED)) {
val plan = plan("SELECT * $filter ORDER BY $order LIMIT 20 OFFSET 0")
assertTrue(plan, plan.contains("USING INDEX $index"))
assertFalse(plan, plan.contains("TEMP B-TREE"))
}
}
}
@@ -456,17 +467,17 @@ class LibraryDaoTest {
assertEquals(
listOf("Downloaded"),
dao.albumsByName(sourceId, 0, "", downloadedFilter = 1).page().map { it.name },
dao.albumPages(sourceId, 0, "", downloaded = true).page().map { it.name },
)
assertEquals(
listOf("ar-1"),
dao.artistsByName(sourceId, 0, "", downloadedFilter = 1).page().map { it.id },
dao.artistPages(sourceId, 0, "", downloaded = true).page().map { it.id },
)
assertEquals(
listOf("pl-1"),
dao.playlistsByName(sourceId, "", downloadedFilter = 1).page().map { it.id },
dao.playlistPages(sourceId, "", downloaded = true).page().map { it.id },
)
assertEquals(3, dao.albumsByName(sourceId, 0, "").page().size)
assertEquals(3, dao.albumPages(sourceId, 0, "").page().size)
}
@Test
@@ -628,7 +639,7 @@ class LibraryDaoTest {
dao.recomputeArtistArt(sourceId)
val artists = dao.artistsByName(sourceId, 0, "").page().associateBy { it.id }
val artists = dao.artistPages(sourceId, 0, "").page().associateBy { it.id }
assertEquals("own-art", artists.getValue("ar-own").coverArt)
assertNull(artists.getValue("ar-album").coverArt)
@@ -648,7 +659,7 @@ class LibraryDaoTest {
assertEquals(
listOf("album-ref-art"),
dao
.artistsPaging(artistsDefaultQuery(sourceId, 0, "", ArtistSort.Name, false))
.artistPages(sourceId, 0, "", ArtistSort.Name, false)
.page()
.first { it.id == "ar-album-ref" }
.coverArts,
@@ -904,7 +915,7 @@ class LibraryDaoTest {
db.downloadDao().upsert(SongDownload(sourceId, "s1", DownloadStatus.Completed))
assertEquals(
listOf("Album Artist"),
dao.artistsByName(sourceId, 0, "", downloadedFilter = 1).page().map { it.name },
dao.artistPages(sourceId, 0, "", downloaded = true).page().map { it.name },
)
assertEquals(
listOf("Album Artist"),
@@ -913,7 +924,7 @@ class LibraryDaoTest {
db.downloadDao().upsert(SongDownload(sourceId, "s2", DownloadStatus.Completed))
val filtered = dao.artistsByName(sourceId, 0, "", downloadedFilter = 1).page()
val filtered = dao.artistPages(sourceId, 0, "", downloaded = true).page()
assertEquals(listOf("Album Artist", "Guest"), filtered.map { it.name })
assertEquals(listOf(1L, 1L), filtered.map { it.albumCount })
@@ -961,14 +972,14 @@ class LibraryDaoTest {
)
db.downloadDao().upsert(SongDownload(sourceId, "s1", DownloadStatus.Completed))
val filtered = dao.artistsByName(sourceId, 0, "", downloadedFilter = 1).page()
val filtered = dao.artistPages(sourceId, 0, "", downloaded = true).page()
assertEquals(1, filtered.size)
assertEquals(1L, filtered.first().albumCount)
assertEquals(
5L,
dao
.artistsByName(sourceId, 0, "")
.artistPages(sourceId, 0, "")
.page()
.first { it.id == "ar-1" }
.albumCount,
@@ -1006,7 +1017,7 @@ class LibraryDaoTest {
// ar-1 has two downloaded albums, ar-2 one, so the downloaded order is ar-1 first even
// though the synced counts would put ar-2 first.
val filtered = dao.artistsByAlbumCount(sourceId, 0, "", downloadedFilter = 1).page()
val filtered = dao.artistPages(sourceId, 0, "", downloaded = true, sort = ArtistSort.AlbumCount).page()
assertEquals(listOf("ar-1", "ar-2"), filtered.map { it.id })
assertEquals(listOf(2L, 1L), filtered.map { it.albumCount })
}
@@ -1036,7 +1047,7 @@ class LibraryDaoTest {
)
db.downloadDao().upsert(SongDownload(sourceId, "s1", DownloadStatus.Completed))
val filtered = dao.playlistsByName(sourceId, "", downloadedFilter = 1).page()
val filtered = dao.playlistPages(sourceId, "", downloaded = true).page()
assertEquals(1, filtered.size)
assertEquals(1L, filtered.first().songCount)
assertEquals("austin", filtered.first().owner)
@@ -1045,7 +1056,7 @@ class LibraryDaoTest {
assertEquals(
5L,
dao
.playlistsByName(sourceId, "")
.playlistPages(sourceId, "")
.page()
.first { it.id == "pl-1" }
.songCount,
@@ -1064,11 +1075,11 @@ class LibraryDaoTest {
),
)
assertEquals(listOf("Gamma"), dao.albumsByName(sourceId, 0, "amm").page().map { it.name })
assertEquals(listOf("Gamma"), dao.albumsByName(sourceId, 0, "GAM").page().map { it.name })
assertEquals(listOf("Gamma"), dao.albumsByName(sourceId, 0, "ga").page().map { it.name })
assertEquals(listOf("Alpha"), dao.albumsByName(sourceId, 0, "Else").page().map { it.name })
assertTrue(dao.albumsByName(sourceId, 0, "zzz").page().isEmpty())
assertEquals(listOf("Gamma"), dao.albumPages(sourceId, 0, "amm").page().map { it.name })
assertEquals(listOf("Gamma"), dao.albumPages(sourceId, 0, "GAM").page().map { it.name })
assertEquals(listOf("Gamma"), dao.albumPages(sourceId, 0, "ga").page().map { it.name })
assertEquals(listOf("Alpha"), dao.albumPages(sourceId, 0, "Else").page().map { it.name })
assertTrue(dao.albumPages(sourceId, 0, "zzz").page().isEmpty())
}
@Test
@@ -1077,17 +1088,17 @@ class LibraryDaoTest {
val sourceId = source()
val dao = db.libraryDao()
dao.upsertAlbums(listOf(album(sourceId, "a", "Gamma", year = null, starred = null)))
assertEquals(listOf("Gamma"), dao.albumsByName(sourceId, 0, "gam").page().map { it.name })
assertEquals(listOf("Gamma"), dao.albumPages(sourceId, 0, "gam").page().map { it.name })
dao.setAlbumStar(sourceId, "a", 7)
assertEquals(listOf("Gamma"), dao.albumsByName(sourceId, 0, "gam").page().map { it.name })
assertEquals(listOf("Gamma"), dao.albumPages(sourceId, 0, "gam").page().map { it.name })
dao.upsertAlbums(listOf(album(sourceId, "a", "Delta", year = null, starred = null)))
assertTrue(dao.albumsByName(sourceId, 0, "gam").page().isEmpty())
assertEquals(listOf("Delta"), dao.albumsByName(sourceId, 0, "elt").page().map { it.name })
assertTrue(dao.albumPages(sourceId, 0, "gam").page().isEmpty())
assertEquals(listOf("Delta"), dao.albumPages(sourceId, 0, "elt").page().map { it.name })
dao.deleteAlbums(sourceId, listOf("a"))
assertTrue(dao.albumsByName(sourceId, 0, "elt").page().isEmpty())
assertTrue(dao.albumPages(sourceId, 0, "elt").page().isEmpty())
}
@Test
@@ -1101,8 +1112,8 @@ class LibraryDaoTest {
val second = source()
dao.upsertAlbums(listOf(album(second, "a", "Beta", year = null, starred = null)))
assertEquals(listOf("Beta"), dao.albumsByName(second, 0, "eta").page().map { it.name })
assertTrue(dao.albumsByName(second, 0, "gam").page().isEmpty())
assertEquals(listOf("Beta"), dao.albumPages(second, 0, "eta").page().map { it.name })
assertTrue(dao.albumPages(second, 0, "gam").page().isEmpty())
}
@Test
@@ -1113,10 +1124,10 @@ class LibraryDaoTest {
dao.upsertArtists(listOf(artist(sourceId, "r1", "The Beatles", albumCount = 1)))
dao.upsertPlaylists(listOf(playlist(sourceId, "l1", "Road Trip", created = 0, changed = 0)))
assertEquals(listOf("r1"), dao.artistsByName(sourceId, 0, "eat").page().map { it.id })
assertEquals(listOf("l1"), dao.playlistsByName(sourceId, "rip").page().map { it.id })
assertTrue(dao.artistsByName(sourceId, 0, "rip").page().isEmpty())
assertTrue(dao.playlistsByName(sourceId, "eat").page().isEmpty())
assertEquals(listOf("r1"), dao.artistPages(sourceId, 0, "eat").page().map { it.id })
assertEquals(listOf("l1"), dao.playlistPages(sourceId, "rip").page().map { it.id })
assertTrue(dao.artistPages(sourceId, 0, "rip").page().isEmpty())
assertTrue(dao.playlistPages(sourceId, "eat").page().isEmpty())
}
@Test
@@ -1131,10 +1142,10 @@ class LibraryDaoTest {
),
)
assertEquals(listOf("Say \"Hello\""), dao.albumsByName(sourceId, 0, "\"Hel").page().map { it.name })
assertEquals(listOf("Say \"Hello\""), dao.albumPages(sourceId, 0, "\"Hel").page().map { it.name })
assertEquals(
listOf("Say \"Hello\""),
dao.albumsPaging(albumsDefaultQuery(sourceId, 0, "\"Hel", AlbumSort.Name, false)).page().map { it.name },
dao.albumPages(sourceId, 0, "\"Hel", AlbumSort.Name, false).page().map { it.name },
)
}
@@ -1145,7 +1156,7 @@ class LibraryDaoTest {
val dao = db.libraryDao()
dao.upsertAlbums(listOf(album(sourceId, "a", "Gamma", year = null, starred = null).copy(albumArtist = null)))
assertEquals(listOf("Gamma"), dao.albumsByName(sourceId, 0, "amm").page().map { it.name })
assertEquals(listOf("Gamma"), dao.albumPages(sourceId, 0, "amm").page().map { it.name })
}
@Test
@@ -1274,35 +1285,35 @@ class LibraryDaoTest {
assertEquals(
listOf("Alpha", "Beta", "Gamma"),
dao.albumsPaging(albumsDefaultQuery(sourceId, 0, "", AlbumSort.Name, false)).page().map { it.name },
dao.albumPages(sourceId, 0, "", AlbumSort.Name, false).page().map { it.name },
)
assertEquals(
listOf("Gamma", "Beta", "Alpha"),
dao.albumsPaging(albumsDefaultQuery(sourceId, 0, "", AlbumSort.Name, true)).page().map { it.name },
dao.albumPages(sourceId, 0, "", AlbumSort.Name, true).page().map { it.name },
)
assertEquals(
listOf("Alpha", "Beta", "Gamma"),
dao.albumsPaging(albumsDefaultQuery(sourceId, 0, "", AlbumSort.Year, false)).page().map { it.name },
dao.albumPages(sourceId, 0, "", AlbumSort.Year, false).page().map { it.name },
)
assertEquals(
listOf("Gamma", "Beta", "Alpha"),
dao.albumsPaging(albumsDefaultQuery(sourceId, 0, "", AlbumSort.Artist, false)).page().map { it.name },
dao.albumPages(sourceId, 0, "", AlbumSort.Artist, false).page().map { it.name },
)
assertEquals(
listOf("Alpha"),
dao.albumsPaging(albumsDefaultQuery(sourceId, 0, "alp", AlbumSort.Name, false)).page().map { it.name },
dao.albumPages(sourceId, 0, "alp", AlbumSort.Name, false).page().map { it.name },
)
assertEquals(
listOf("Gamma"),
dao.albumsPaging(albumsDefaultQuery(sourceId, 0, "am", AlbumSort.Name, false)).page().map { it.name },
dao.albumPages(sourceId, 0, "am", AlbumSort.Name, false).page().map { it.name },
)
assertEquals(
listOf("Alpha"),
dao.albumsPaging(albumsDefaultQuery(sourceId, 1, "", AlbumSort.Name, false)).page().map { it.name },
dao.albumPages(sourceId, 1, "", AlbumSort.Name, false).page().map { it.name },
)
assertEquals(
listOf("Beta", "Gamma"),
dao.albumsPaging(albumsDefaultQuery(sourceId, 2, "", AlbumSort.Name, false)).page().map { it.name },
dao.albumPages(sourceId, 2, "", AlbumSort.Name, false).page().map { it.name },
)
}
@@ -1326,23 +1337,23 @@ class LibraryDaoTest {
assertEquals(
listOf("Alpha", "Beta"),
dao.artistsPaging(artistsDefaultQuery(sourceId, 0, "a", ArtistSort.Name, false)).page().map { it.name },
dao.artistPages(sourceId, 0, "a", ArtistSort.Name, false).page().map { it.name },
)
assertEquals(
listOf("Beta", "Alpha"),
dao.artistsPaging(artistsDefaultQuery(sourceId, 0, "", ArtistSort.AlbumCount, false)).page().map { it.name },
dao.artistPages(sourceId, 0, "", ArtistSort.AlbumCount, false).page().map { it.name },
)
assertEquals(
listOf("Alpha", "Beta"),
dao.playlistsPaging(playlistsDefaultQuery(sourceId, "a", PlaylistSort.Name, false)).page().map { it.name },
dao.playlistPages(sourceId, "a", PlaylistSort.Name, false).page().map { it.name },
)
assertEquals(
listOf("Alpha", "Beta"),
dao.playlistsPaging(playlistsDefaultQuery(sourceId, "", PlaylistSort.Added, false)).page().map { it.name },
dao.playlistPages(sourceId, "", PlaylistSort.Added, false).page().map { it.name },
)
assertEquals(
listOf("Beta", "Alpha"),
dao.playlistsPaging(playlistsDefaultQuery(sourceId, "", PlaylistSort.Updated, false)).page().map { it.name },
dao.playlistPages(sourceId, "", PlaylistSort.Updated, false).page().map { it.name },
)
}
@@ -1355,12 +1366,12 @@ class LibraryDaoTest {
dao.upsertSongs(listOf(song(sourceId, "s1", "One", starred = null).copy(albumId = "a")))
val defaultInvalidated = AtomicBoolean(false)
val default = dao.albumsPaging(albumsDefaultQuery(sourceId, 0, "", AlbumSort.Name, false))
val default = dao.albumPages(sourceId, 0, "", AlbumSort.Name, false)
default.registerInvalidatedCallback { defaultInvalidated.set(true) }
default.page()
val downloadedInvalidated = AtomicBoolean(false)
val downloaded = dao.albumsByName(sourceId, 0, "", downloadedFilter = 1)
val downloaded = dao.albumPages(sourceId, 0, "", downloaded = true)
downloaded.registerInvalidatedCallback { downloadedInvalidated.set(true) }
downloaded.page()
@@ -1395,27 +1406,27 @@ class LibraryDaoTest {
assertEquals(
listOf("宇多田ヒカル", "あいみょん"),
dao.artistsByName(sourceId, 0, "").page().map { it.name },
dao.artistPages(sourceId, 0, "").page().map { it.name },
)
assertEquals(
listOf("あいみょん", "宇多田ヒカル"),
dao.artistsByNameReversed(sourceId, 0, "").page().map { it.name },
dao.artistPages(sourceId, 0, "", descending = true).page().map { it.name },
)
assertEquals(
listOf("Zzz", "Aaa"),
dao.albumsByName(sourceId, 0, "").page().map { it.name },
dao.albumPages(sourceId, 0, "").page().map { it.name },
)
assertEquals(
listOf("Aaa", "Zzz"),
dao.albumsByNameReversed(sourceId, 0, "").page().map { it.name },
dao.albumPages(sourceId, 0, "", descending = true).page().map { it.name },
)
assertEquals(
listOf("Aaa", "Zzz"),
dao.albumsByArtist(sourceId, 0, "").page().map { it.name },
dao.albumPages(sourceId, 0, "", sort = AlbumSort.Artist).page().map { it.name },
)
assertEquals(
listOf("Zzz", "Aaa"),
dao.albumsByArtistReversed(sourceId, 0, "").page().map { it.name },
dao.albumPages(sourceId, 0, "", sort = AlbumSort.Artist, descending = true).page().map { it.name },
)
}
@@ -1445,40 +1456,48 @@ class LibraryDaoTest {
assertEquals(
listOf("宇多田ヒカル", "Zebra", "Beatles", "あいみょん"),
dao.artistsByName(sourceId, 0, "", sortMode = SortNameMode.SortName.name).page().map { it.name },
dao.artistPages(sourceId, 0, "", sortNameMode = SortNameMode.SortName).page().map { it.name },
)
assertEquals(
listOf("Beatles", "Zebra", "あいみょん", "宇多田ヒカル"),
dao.artistsByName(sourceId, 0, "", sortMode = SortNameMode.Name.name).page().map { it.name },
dao.artistPages(sourceId, 0, "", sortNameMode = SortNameMode.Name).page().map { it.name },
)
assertEquals(
listOf("宇多田ヒカル", "あいみょん", "Zebra", "Beatles"),
dao.artistsByNameReversed(sourceId, 0, "", sortMode = SortNameMode.Name.name).page().map { it.name },
dao
.artistPages(sourceId, 0, "", descending = true, sortNameMode = SortNameMode.Name)
.page()
.map { it.name },
)
assertEquals(
listOf("宇多田ヒカル", "Beatles", "Zebra", "あいみょん"),
dao.artistsByName(sourceId, 0, "", sortMode = SortNameMode.NonLatin.name).page().map { it.name },
dao.artistPages(sourceId, 0, "", sortNameMode = SortNameMode.NonLatin).page().map { it.name },
)
assertEquals(
listOf("Aaa", "Beatles", "Zzz"),
dao.albumsByName(sourceId, 0, "", sortMode = SortNameMode.Name.name).page().map { it.name },
dao.albumPages(sourceId, 0, "", sortNameMode = SortNameMode.Name).page().map { it.name },
)
assertEquals(
listOf("Aaa", "Zzz", "Beatles"),
dao.albumsByArtist(sourceId, 0, "", sortMode = SortNameMode.Name.name).page().map { it.name },
dao
.albumPages(sourceId, 0, "", sort = AlbumSort.Artist, sortNameMode = SortNameMode.Name)
.page()
.map { it.name },
)
assertEquals(
listOf("Beatles", "Zebra", "あいみょん", "宇多田ヒカル"),
dao
.artistsPaging(artistsDefaultQuery(sourceId, 0, "", ArtistSort.Name, false, SortNameMode.Name))
.page()
.artistsPaging(
artistsPageQuery(sourceId, 0, "", ArtistSort.Name, false, false, SortNameMode.Name),
).page()
.map { it.name },
)
assertEquals(
listOf("Aaa", "Beatles", "Zzz"),
dao
.albumsPaging(albumsDefaultQuery(sourceId, 0, "", AlbumSort.Name, false, SortNameMode.Name))
.page()
.albumsPaging(
albumsPageQuery(sourceId, 0, "", AlbumSort.Name, false, false, SortNameMode.Name),
).page()
.map { it.name },
)
}
@@ -1507,11 +1526,11 @@ class LibraryDaoTest {
),
)
assertEquals(listOf("宇多田ヒカル"), dao.artistsByName(sourceId, 0, "utada").page().map { it.name })
assertEquals(listOf("宇多田ヒカル"), dao.artistsByName(sourceId, 0, "ut").page().map { it.name })
assertEquals(listOf("宇多田ヒカル"), dao.artistPages(sourceId, 0, "utada").page().map { it.name })
assertEquals(listOf("宇多田ヒカル"), dao.artistPages(sourceId, 0, "ut").page().map { it.name })
assertEquals(listOf("宇多田ヒカル"), dao.searchArtists(sourceId, "utada", 10).first().map { it.name })
assertEquals(listOf("First Love"), dao.albumsByName(sourceId, 0, "first").page().map { it.name })
assertEquals(listOf("First Love"), dao.albumsByName(sourceId, 0, "fi").page().map { it.name })
assertEquals(listOf("First Love"), dao.albumPages(sourceId, 0, "first").page().map { it.name })
assertEquals(listOf("First Love"), dao.albumPages(sourceId, 0, "fi").page().map { it.name })
assertEquals(listOf("First Love"), dao.searchAlbums(sourceId, "first", 10).first().map { it.name })
assertEquals(listOf("Automatic"), dao.searchSongs(sourceId, "utada", 10).first().map { it.song.title })
}
@@ -0,0 +1,53 @@
package com.subtracks.data.db
import androidx.paging.PagingSource
import com.subtracks.data.model.Album
import com.subtracks.data.model.Artist
import com.subtracks.data.model.Playlist
import com.subtracks.data.prefs.AlbumSort
import com.subtracks.data.prefs.ArtistSort
import com.subtracks.data.prefs.PlaylistSort
import com.subtracks.data.prefs.SortNameMode
internal fun LibraryDao.albumPages(
sourceId: Long,
starredFilter: Int = 0,
search: String = "",
sort: AlbumSort = AlbumSort.Name,
descending: Boolean = false,
downloaded: Boolean = false,
sortNameMode: SortNameMode = SortNameMode.SortName,
): PagingSource<Int, Album> =
if (downloaded) {
downloadedAlbumsPaging(albumsPageQuery(sourceId, starredFilter, search, sort, descending, true, sortNameMode))
} else {
albumsPaging(albumsPageQuery(sourceId, starredFilter, search, sort, descending, false, sortNameMode))
}
internal fun LibraryDao.artistPages(
sourceId: Long,
starredFilter: Int = 0,
search: String = "",
sort: ArtistSort = ArtistSort.Name,
descending: Boolean = false,
downloaded: Boolean = false,
sortNameMode: SortNameMode = SortNameMode.SortName,
): PagingSource<Int, Artist> =
if (downloaded) {
downloadedArtistsPaging(artistsPageQuery(sourceId, starredFilter, search, sort, descending, true, sortNameMode))
} else {
artistsPaging(artistsPageQuery(sourceId, starredFilter, search, sort, descending, false, sortNameMode))
}
internal fun LibraryDao.playlistPages(
sourceId: Long,
search: String = "",
sort: PlaylistSort = PlaylistSort.Name,
descending: Boolean = false,
downloaded: Boolean = false,
): PagingSource<Int, Playlist> =
if (downloaded) {
downloadedPlaylistsPaging(playlistsPageQuery(sourceId, search, sort, descending, true))
} else {
playlistsPaging(playlistsPageQuery(sourceId, search, sort, descending, false))
}
@@ -14,6 +14,9 @@ import com.subtracks.data.model.Playlist
import com.subtracks.data.model.PlaylistSong
import com.subtracks.data.model.Song
import com.subtracks.data.model.Source
import com.subtracks.data.prefs.AlbumSort
import com.subtracks.data.prefs.ArtistSort
import com.subtracks.data.prefs.PlaylistSort
import com.subtracks.data.source.MusicSource
import com.subtracks.data.source.StarType
import com.subtracks.data.sync.SyncService
@@ -110,28 +113,31 @@ class QueryTimingMeasurementTest {
private suspend fun measureReads() {
val dao = db.libraryDao()
timed("albums first page (name)") { dao.albumsByName(1, 0, "").firstPage() }
timed("albums first page (artist)") { dao.albumsByArtist(1, 0, "").firstPage() }
timed("albums first page (year)") { dao.albumsByYear(1, 0, "").firstPage() }
timed("albums first page (added)") { dao.albumsByRecentlyAdded(1, 0, "").firstPage() }
timed("albums first page (starred)") { dao.albumsByStarred(1, 0, "").firstPage() }
timed("albums first page (frequent)") { dao.albumsByFrequent(1, 0, "").firstPage() }
timed("albums first page (recent)") { dao.albumsByRecent(1, 0, "").firstPage() }
timed("artists first page (name)") { dao.artistsByName(1, 0, "").firstPage() }
timed("artists first page (frequent)") { dao.artistsByFrequent(1, 0, "").firstPage() }
timed("albums first page (name)") { dao.albumPages(1, 0, "", sort = AlbumSort.Name, downloaded = true).firstPage() }
timed("albums first page (name, default)") { dao.albumPages(1, 0, "", sort = AlbumSort.Name).firstPage() }
timed("albums first page (artist)") { dao.albumPages(1, 0, "", sort = AlbumSort.Artist, downloaded = true).firstPage() }
timed("albums first page (year)") { dao.albumPages(1, 0, "", sort = AlbumSort.Year, downloaded = true).firstPage() }
timed("albums first page (added)") { dao.albumPages(1, 0, "", sort = AlbumSort.Added, downloaded = true).firstPage() }
timed("albums first page (starred)") { dao.albumPages(1, 0, "", sort = AlbumSort.Starred, downloaded = true).firstPage() }
timed("albums first page (frequent)") { dao.albumPages(1, 0, "", sort = AlbumSort.Frequent, downloaded = true).firstPage() }
timed("albums first page (recent)") { dao.albumPages(1, 0, "", sort = AlbumSort.Recent, downloaded = true).firstPage() }
timed("artists first page (name)") { dao.artistPages(1, 0, "", sort = ArtistSort.Name, downloaded = true).firstPage() }
timed("artists first page (name, default)") { dao.artistPages(1, 0, "", sort = ArtistSort.Name).firstPage() }
timed("artists first page (frequent)") { dao.artistPages(1, 0, "", sort = ArtistSort.Frequent, downloaded = true).firstPage() }
timed("artist albums") { dao.albumsForArtist(1, "ar0").first() }
timed("artist name") { dao.artistName(1, "ar0").first() }
timed("artist songs") { db.queueDao().artistSongs(1, "ar0", 0, 100) }
timed("artist length") { db.queueDao().artistLength(1, "ar0") }
timed("artist song ids") { db.queueDao().artistSongIds(1, "ar0") }
timed("downloaded artists") { dao.downloadedArtists(1).firstPage() }
timed("artists downloaded filter") { dao.artistsByName(1, 0, "", downloadedFilter = 1).firstPage() }
timed("artists downloaded filter") { dao.artistPages(1, 0, "", downloaded = true).firstPage() }
timed("artist statuses") { db.downloadDao().artistStatuses(1).first() }
timed("artist status") { db.downloadDao().artistStatus(1, "ar0").first() }
timed("playlists first page (name)") { dao.playlistsByName(1, "").firstPage() }
timed("albums deep page") { dao.albumsByName(1, 0, "").deepPage(1000) }
timed("albums search FTS") { dao.albumsByName(1, 0, "Album 1").firstPage() }
timed("albums search short") { dao.albumsByName(1, 0, "al").firstPage() }
timed("playlists first page (name)") { dao.playlistPages(1, "", sort = PlaylistSort.Name, downloaded = true).firstPage() }
timed("playlists first page (name, default)") { dao.playlistPages(1, "", sort = PlaylistSort.Name).firstPage() }
timed("albums deep page") { dao.albumPages(1, 0, "", sort = AlbumSort.Name, downloaded = true).deepPage(1000) }
timed("albums search FTS") { dao.albumPages(1, 0, "Album 1", sort = AlbumSort.Name, downloaded = true).firstPage() }
timed("albums search short") { dao.albumPages(1, 0, "al", sort = AlbumSort.Name, downloaded = true).firstPage() }
timed("songs search FTS") { dao.searchSongs(1, "Song", 50).first() }
timed("home recently played") { dao.recentlyPlayedAlbums(1, 20).first() }
timed("home most played") { dao.mostPlayedAlbums(1, 20).first() }
@@ -10,7 +10,8 @@ import androidx.test.ext.junit.runners.AndroidJUnit4
import com.subtracks.data.db.LoggingSQLiteDriver
import com.subtracks.data.db.SqlExecution
import com.subtracks.data.db.SubtracksDatabase
import com.subtracks.data.db.artistsDefaultQuery
import com.subtracks.data.db.artistPages
import com.subtracks.data.db.artistsPageQuery
import com.subtracks.data.model.Album
import com.subtracks.data.model.Artist
import com.subtracks.data.model.ArtistRef
@@ -75,7 +76,7 @@ class SyncServiceTest {
2,
db
.libraryDao()
.artistsByName(1, 0, "")
.artistPages(1, 0, "")
.allRows()
.size,
)
@@ -98,7 +99,7 @@ class SyncServiceTest {
listOf("a1"),
db
.libraryDao()
.artistsByName(1, 0, "")
.artistPages(1, 0, "")
.allRows()
.map { it.id },
)
@@ -172,7 +173,7 @@ class SyncServiceTest {
"Old",
db
.libraryDao()
.artistsByName(1, 0, "")
.artistPages(1, 0, "")
.allRows()
.single()
.name,
@@ -185,7 +186,7 @@ class SyncServiceTest {
"New",
db
.libraryDao()
.artistsByName(1, 0, "")
.artistPages(1, 0, "")
.allRows()
.single()
.name,
@@ -1107,16 +1108,16 @@ class SyncServiceTest {
listOf("a1"),
db
.libraryDao()
.artistsByName(1, 0, "")
.artistPages(1, 0, "")
.allRows()
.map { it.id },
)
val query = artistsDefaultQuery(1, 0, "alpha", ArtistSort.Name, false)
val query = artistsPageQuery(1, 0, "alpha", ArtistSort.Name, false, false)
assertEquals(
listOf("a1"),
db
.libraryDao()
.artistsByName(1, 0, "alpha")
.artistPages(1, 0, "alpha")
.allRows()
.map { it.id },
)
@@ -1203,7 +1204,7 @@ class SyncServiceTest {
listOf("a1", "combo"),
db
.libraryDao()
.artistsByName(1, 0, "")
.artistPages(1, 0, "")
.allRows()
.map { it.id },
)
@@ -1216,7 +1217,7 @@ class SyncServiceTest {
listOf("a1"),
db
.libraryDao()
.artistsByName(1, 0, "")
.artistPages(1, 0, "")
.allRows()
.map { it.id },
)
@@ -1434,7 +1435,7 @@ class SyncServiceTest {
SyncService(db, source).sync()
db.downloadDao().upsert(SongDownload(1, "s1", DownloadStatus.Completed))
val filtered = db.libraryDao().artistsByName(1, 0, "", downloadedFilter = 1).allRows()
val filtered = db.libraryDao().artistPages(1, 0, "", downloaded = true).allRows()
assertEquals(listOf("A", "B"), filtered.map { it.name })
assertEquals(listOf(1L, 1L), filtered.map { it.albumCount })
assertEquals(ListDownloadStatus(total = 1, downloaded = 1), db.downloadDao().artistStatus(1, "a2").first())
@@ -1538,7 +1539,7 @@ class SyncServiceTest {
albums.size.toLong(),
db
.libraryDao()
.artistsByName(1, 0, "")
.artistPages(1, 0, "")
.allRows()
.first { it.id == "a2" }
.albumCount,
@@ -1780,7 +1781,7 @@ class SyncServiceTest {
assertEquals("$artistId downloaded", downloaded.toLong(), status.downloaded)
}
val downloadedArtists = db.libraryDao().artistsByName(1, 0, "", downloadedFilter = 1).allRows()
val downloadedArtists = db.libraryDao().artistPages(1, 0, "", downloaded = true).allRows()
for (artist in downloadedArtists) {
assertEquals(
"${artist.id} downloaded albums",