address review: NULL disc/track in backward album seeks; drop redundant index

- `albumSongsBefore` used the null-intolerant row-value `<`, so an album
  with NULL `disc`/`track` (untagged files) dropped exactly the rows a
  backward window needs, returning the wrong track at a position. Use a
  NULL-aware predicate (NULL sorts first, so it belongs to any `before`
  set); `albumSongsFrom` is unaffected.
- The new `(sourceId, albumId, disc, track, id)` index has the old
  `(sourceId, albumId)` index as a prefix, so drop the latter in the
  migration and from the entity.
- Tests: mixed NULL disc/track album for the before-window and a full
  backward scan; both fail on the previous commit.
This commit is contained in:
austinried
2026-10-04 00:30:01 +00:00
parent 4aaeb4871b
commit 649528e338
4 changed files with 50 additions and 5 deletions
@@ -103,6 +103,37 @@ class QueueKeysetTest {
assertEquals(listOf("t1a", "t1z"), all)
}
@Test
fun albumKeysetBeforeIncludesNullDiscAndTrack() =
runTest {
seedLibrary()
val all = allAlbumRows("mixed").map { it.song.id }
assertEquals(listOf("n1", "n2", "s1", "s2"), all)
val actual =
db
.queueDao()
.albumSongsBefore(1, "mixed", disc = 1, track = 2, id = "s2", skip = 0, limit = 2)
.asReversed()
.map { it.song.id }
assertEquals(listOf("n2", "s1"), actual)
}
@Test
fun scanningAnAlbumWithNullDiscAndTrackBackwardsResolvesEveryRow() =
runTest {
seedLibrary()
repository.replace(listOf(repository.albumEntry(1, "mixed")))
val snapshot = repository.snapshot()
val expected = allAlbumRows("mixed").map { it.song.id }
val resolved =
(snapshot.size - 1 downTo 0).map { repository.itemAt(snapshot, it)?.song?.id }.asReversed()
assertEquals(expected, resolved)
}
@Test
fun playlistKeysetMatchesOffsetWindowWithGapsAndGhosts() =
runTest {
@@ -234,6 +265,7 @@ class QueueKeysetTest {
listOf(
album("big", "Big Album"),
album("tie", "Tie Album"),
album("mixed", "Mixed Album"),
),
)
val big =
@@ -247,7 +279,14 @@ class QueueKeysetTest {
song(id = "t1z", albumId = "tie", disc = 1, track = 1),
song(id = "t1a", albumId = "tie", disc = 1, track = 1),
)
db.libraryDao().upsertSongs(big + tie)
val mixed =
listOf(
song(id = "n1", albumId = "mixed"),
song(id = "n2", albumId = "mixed", track = 3),
song(id = "s1", albumId = "mixed", disc = 1, track = 1),
song(id = "s2", albumId = "mixed", disc = 1, track = 2),
)
db.libraryDao().upsertSongs(big + tie + mixed)
val positions = (0 until 20).map { it * 2L }
val playlistSongs =
@@ -281,8 +320,8 @@ class QueueKeysetTest {
private fun song(
id: String,
albumId: String,
disc: Long,
track: Long,
disc: Long? = null,
track: Long? = null,
) = Song(
sourceId = 1,
id = id,