index the album, artist and playlist list sorts

This commit is contained in:
austinried
2026-10-04 00:29:59 +00:00
parent f05a1c79a8
commit d703b8ec5b
10 changed files with 1618 additions and 136 deletions
@@ -23,6 +23,48 @@ internal const val PLAYLIST_SONGS_SQL =
"WHERE playlist_songs.sourceId = :sourceId AND playlist_songs.playlistId = :playlistId " +
"ORDER BY playlist_songs.position"
internal const val ALBUMS_FILTER =
"FROM albums WHERE sourceId = :sourceId " +
"AND (:starredFilter = 0 OR (:starredFilter = 1 AND starred IS NOT NULL) OR (:starredFilter = 2 AND starred IS NULL)) " +
"AND (:search = '' OR instr(lower(name), lower(:search)) > 0 OR instr(lower(albumArtist), lower(:search)) > 0) "
internal const val ALBUM_ORDER_BY_NAME = "name COLLATE NOCASE, id"
internal const val ALBUM_ORDER_BY_NAME_REVERSED = "name COLLATE NOCASE DESC, id DESC"
internal const val ALBUM_ORDER_BY_ARTIST = "albumArtist COLLATE NOCASE, year, name COLLATE NOCASE, id"
internal const val ALBUM_ORDER_BY_ARTIST_REVERSED = "albumArtist COLLATE NOCASE DESC, year DESC, name COLLATE NOCASE DESC, id DESC"
internal const val ALBUM_ORDER_BY_YEAR = "year DESC, name COLLATE NOCASE, id"
internal const val ALBUM_ORDER_BY_YEAR_REVERSED = "year ASC, name COLLATE NOCASE DESC, id DESC"
internal const val ALBUM_ORDER_BY_ADDED = "created DESC, name COLLATE NOCASE, id"
internal const val ALBUM_ORDER_BY_ADDED_REVERSED = "created ASC, name COLLATE NOCASE DESC, id DESC"
// Starred orders keep unstarred rows last in both directions, matching the list grouping, so the
// reversed order is not a literal mirror of the base order.
internal const val ALBUM_ORDER_BY_STARRED = "starred DESC NULLS LAST, name COLLATE NOCASE, id"
internal const val ALBUM_ORDER_BY_STARRED_REVERSED = "starred ASC NULLS LAST, name COLLATE NOCASE DESC, id DESC"
internal const val ARTISTS_FILTER =
"FROM artists WHERE sourceId = :sourceId " +
"AND (:starredFilter = 0 OR (:starredFilter = 1 AND starred IS NOT NULL) OR (:starredFilter = 2 AND starred IS NULL)) " +
"AND (:search = '' OR instr(lower(name), lower(:search)) > 0) "
internal const val ARTIST_ORDER_BY_NAME = "name COLLATE NOCASE, id"
internal const val ARTIST_ORDER_BY_NAME_REVERSED = "name COLLATE NOCASE DESC, id DESC"
internal const val ARTIST_ORDER_BY_ALBUM_COUNT = "albumCount DESC, name COLLATE NOCASE, id"
internal const val ARTIST_ORDER_BY_ALBUM_COUNT_REVERSED = "albumCount ASC, name COLLATE NOCASE DESC, id DESC"
internal const val ARTIST_ORDER_BY_STARRED = "starred DESC NULLS LAST, name COLLATE NOCASE, id"
internal const val ARTIST_ORDER_BY_STARRED_REVERSED = "starred ASC NULLS LAST, name COLLATE NOCASE DESC, id DESC"
internal const val PLAYLISTS_FILTER =
"FROM playlists WHERE sourceId = :sourceId " +
"AND (:search = '' OR instr(lower(name), lower(:search)) > 0) "
internal const val PLAYLIST_ORDER_BY_NAME = "name COLLATE NOCASE, id"
internal const val PLAYLIST_ORDER_BY_NAME_REVERSED = "name COLLATE NOCASE DESC, id DESC"
internal const val PLAYLIST_ORDER_BY_ADDED = "created DESC, name COLLATE NOCASE, id"
internal const val PLAYLIST_ORDER_BY_ADDED_REVERSED = "created ASC, name COLLATE NOCASE DESC, id DESC"
internal const val PLAYLIST_ORDER_BY_UPDATED = "changed DESC, name COLLATE NOCASE, id"
internal const val PLAYLIST_ORDER_BY_UPDATED_REVERSED = "changed ASC, name COLLATE NOCASE DESC, id DESC"
@Dao
@DaoReturnTypeConverters(PagingSourceDaoReturnTypeConverter::class)
interface LibraryDao {
@@ -155,253 +197,149 @@ interface LibraryDao {
)
suspend fun deleteOrphanPlaylistSongs(sourceId: Long)
@Query(
"SELECT * FROM albums WHERE sourceId = :sourceId " +
"AND (:starredFilter = 0 OR (:starredFilter = 1 AND starred IS NOT NULL) OR (:starredFilter = 2 AND starred IS NULL)) " +
"AND (:search = '' OR instr(lower(name), lower(:search)) > 0 OR instr(lower(albumArtist), lower(:search)) > 0) " +
"ORDER BY name COLLATE NOCASE, id",
)
@Query("SELECT * $ALBUMS_FILTER ORDER BY $ALBUM_ORDER_BY_NAME")
fun albumsByName(
sourceId: Long,
starredFilter: Int,
search: String,
): PagingSource<Int, Album>
@Query(
"SELECT * FROM albums WHERE sourceId = :sourceId " +
"AND (:starredFilter = 0 OR (:starredFilter = 1 AND starred IS NOT NULL) OR (:starredFilter = 2 AND starred IS NULL)) " +
"AND (:search = '' OR instr(lower(name), lower(:search)) > 0 OR instr(lower(albumArtist), lower(:search)) > 0) " +
"ORDER BY name COLLATE NOCASE DESC, id",
)
@Query("SELECT * $ALBUMS_FILTER ORDER BY $ALBUM_ORDER_BY_NAME_REVERSED")
fun albumsByNameReversed(
sourceId: Long,
starredFilter: Int,
search: String,
): PagingSource<Int, Album>
@Query(
"SELECT * FROM albums WHERE sourceId = :sourceId " +
"AND (:starredFilter = 0 OR (:starredFilter = 1 AND starred IS NOT NULL) OR (:starredFilter = 2 AND starred IS NULL)) " +
"AND (:search = '' OR instr(lower(name), lower(:search)) > 0 OR instr(lower(albumArtist), lower(:search)) > 0) " +
"ORDER BY albumArtist COLLATE NOCASE, year, name COLLATE NOCASE, id",
)
@Query("SELECT * $ALBUMS_FILTER ORDER BY $ALBUM_ORDER_BY_ARTIST")
fun albumsByArtist(
sourceId: Long,
starredFilter: Int,
search: String,
): PagingSource<Int, Album>
@Query(
"SELECT * FROM albums WHERE sourceId = :sourceId " +
"AND (:starredFilter = 0 OR (:starredFilter = 1 AND starred IS NOT NULL) OR (:starredFilter = 2 AND starred IS NULL)) " +
"AND (:search = '' OR instr(lower(name), lower(:search)) > 0 OR instr(lower(albumArtist), lower(:search)) > 0) " +
"ORDER BY albumArtist COLLATE NOCASE DESC, year DESC, name COLLATE NOCASE DESC, id",
)
@Query("SELECT * $ALBUMS_FILTER ORDER BY $ALBUM_ORDER_BY_ARTIST_REVERSED")
fun albumsByArtistReversed(
sourceId: Long,
starredFilter: Int,
search: String,
): PagingSource<Int, Album>
@Query(
"SELECT * FROM albums WHERE sourceId = :sourceId " +
"AND (:starredFilter = 0 OR (:starredFilter = 1 AND starred IS NOT NULL) OR (:starredFilter = 2 AND starred IS NULL)) " +
"AND (:search = '' OR instr(lower(name), lower(:search)) > 0 OR instr(lower(albumArtist), lower(:search)) > 0) " +
"ORDER BY year DESC, name COLLATE NOCASE, id",
)
@Query("SELECT * $ALBUMS_FILTER ORDER BY $ALBUM_ORDER_BY_YEAR")
fun albumsByYear(
sourceId: Long,
starredFilter: Int,
search: String,
): PagingSource<Int, Album>
@Query(
"SELECT * FROM albums WHERE sourceId = :sourceId " +
"AND (:starredFilter = 0 OR (:starredFilter = 1 AND starred IS NOT NULL) OR (:starredFilter = 2 AND starred IS NULL)) " +
"AND (:search = '' OR instr(lower(name), lower(:search)) > 0 OR instr(lower(albumArtist), lower(:search)) > 0) " +
"ORDER BY year ASC, name COLLATE NOCASE, id",
)
@Query("SELECT * $ALBUMS_FILTER ORDER BY $ALBUM_ORDER_BY_YEAR_REVERSED")
fun albumsByYearReversed(
sourceId: Long,
starredFilter: Int,
search: String,
): PagingSource<Int, Album>
@Query(
"SELECT * FROM albums WHERE sourceId = :sourceId " +
"AND (:starredFilter = 0 OR (:starredFilter = 1 AND starred IS NOT NULL) OR (:starredFilter = 2 AND starred IS NULL)) " +
"AND (:search = '' OR instr(lower(name), lower(:search)) > 0 OR instr(lower(albumArtist), lower(:search)) > 0) " +
"ORDER BY created DESC, name COLLATE NOCASE, id",
)
@Query("SELECT * $ALBUMS_FILTER ORDER BY $ALBUM_ORDER_BY_ADDED")
fun albumsByRecentlyAdded(
sourceId: Long,
starredFilter: Int,
search: String,
): PagingSource<Int, Album>
@Query(
"SELECT * FROM albums WHERE sourceId = :sourceId " +
"AND (:starredFilter = 0 OR (:starredFilter = 1 AND starred IS NOT NULL) OR (:starredFilter = 2 AND starred IS NULL)) " +
"AND (:search = '' OR instr(lower(name), lower(:search)) > 0 OR instr(lower(albumArtist), lower(:search)) > 0) " +
"ORDER BY created ASC, name COLLATE NOCASE, id",
)
@Query("SELECT * $ALBUMS_FILTER ORDER BY $ALBUM_ORDER_BY_ADDED_REVERSED")
fun albumsByRecentlyAddedReversed(
sourceId: Long,
starredFilter: Int,
search: String,
): PagingSource<Int, Album>
@Query(
"SELECT * FROM albums WHERE sourceId = :sourceId " +
"AND (:starredFilter = 0 OR (:starredFilter = 1 AND starred IS NOT NULL) OR (:starredFilter = 2 AND starred IS NULL)) " +
"AND (:search = '' OR instr(lower(name), lower(:search)) > 0 OR instr(lower(albumArtist), lower(:search)) > 0) " +
"ORDER BY starred IS NULL, starred DESC, name COLLATE NOCASE, id",
)
@Query("SELECT * $ALBUMS_FILTER ORDER BY $ALBUM_ORDER_BY_STARRED")
fun albumsByStarred(
sourceId: Long,
starredFilter: Int,
search: String,
): PagingSource<Int, Album>
@Query(
"SELECT * FROM albums WHERE sourceId = :sourceId " +
"AND (:starredFilter = 0 OR (:starredFilter = 1 AND starred IS NOT NULL) OR (:starredFilter = 2 AND starred IS NULL)) " +
"AND (:search = '' OR instr(lower(name), lower(:search)) > 0 OR instr(lower(albumArtist), lower(:search)) > 0) " +
"ORDER BY starred IS NULL, starred ASC, name COLLATE NOCASE, id",
)
@Query("SELECT * $ALBUMS_FILTER ORDER BY $ALBUM_ORDER_BY_STARRED_REVERSED")
fun albumsByStarredReversed(
sourceId: Long,
starredFilter: Int,
search: String,
): PagingSource<Int, Album>
@Query(
"SELECT * FROM artists WHERE sourceId = :sourceId " +
"AND (:starredFilter = 0 OR (:starredFilter = 1 AND starred IS NOT NULL) OR (:starredFilter = 2 AND starred IS NULL)) " +
"AND (:search = '' OR instr(lower(name), lower(:search)) > 0) " +
"ORDER BY name COLLATE NOCASE, id",
)
@Query("SELECT * $ARTISTS_FILTER ORDER BY $ARTIST_ORDER_BY_NAME")
fun artistsByName(
sourceId: Long,
starredFilter: Int,
search: String,
): PagingSource<Int, Artist>
@Query(
"SELECT * FROM artists WHERE sourceId = :sourceId " +
"AND (:starredFilter = 0 OR (:starredFilter = 1 AND starred IS NOT NULL) OR (:starredFilter = 2 AND starred IS NULL)) " +
"AND (:search = '' OR instr(lower(name), lower(:search)) > 0) " +
"ORDER BY name COLLATE NOCASE DESC, id",
)
@Query("SELECT * $ARTISTS_FILTER ORDER BY $ARTIST_ORDER_BY_NAME_REVERSED")
fun artistsByNameReversed(
sourceId: Long,
starredFilter: Int,
search: String,
): PagingSource<Int, Artist>
@Query(
"SELECT * FROM artists WHERE sourceId = :sourceId " +
"AND (:starredFilter = 0 OR (:starredFilter = 1 AND starred IS NOT NULL) OR (:starredFilter = 2 AND starred IS NULL)) " +
"AND (:search = '' OR instr(lower(name), lower(:search)) > 0) " +
"ORDER BY albumCount DESC, name COLLATE NOCASE, id",
)
@Query("SELECT * $ARTISTS_FILTER ORDER BY $ARTIST_ORDER_BY_ALBUM_COUNT")
fun artistsByAlbumCount(
sourceId: Long,
starredFilter: Int,
search: String,
): PagingSource<Int, Artist>
@Query(
"SELECT * FROM artists WHERE sourceId = :sourceId " +
"AND (:starredFilter = 0 OR (:starredFilter = 1 AND starred IS NOT NULL) OR (:starredFilter = 2 AND starred IS NULL)) " +
"AND (:search = '' OR instr(lower(name), lower(:search)) > 0) " +
"ORDER BY albumCount ASC, name COLLATE NOCASE, id",
)
@Query("SELECT * $ARTISTS_FILTER ORDER BY $ARTIST_ORDER_BY_ALBUM_COUNT_REVERSED")
fun artistsByAlbumCountReversed(
sourceId: Long,
starredFilter: Int,
search: String,
): PagingSource<Int, Artist>
@Query(
"SELECT * FROM artists WHERE sourceId = :sourceId " +
"AND (:starredFilter = 0 OR (:starredFilter = 1 AND starred IS NOT NULL) OR (:starredFilter = 2 AND starred IS NULL)) " +
"AND (:search = '' OR instr(lower(name), lower(:search)) > 0) " +
"ORDER BY starred IS NULL, starred DESC, name COLLATE NOCASE, id",
)
@Query("SELECT * $ARTISTS_FILTER ORDER BY $ARTIST_ORDER_BY_STARRED")
fun artistsByStarred(
sourceId: Long,
starredFilter: Int,
search: String,
): PagingSource<Int, Artist>
@Query(
"SELECT * FROM artists WHERE sourceId = :sourceId " +
"AND (:starredFilter = 0 OR (:starredFilter = 1 AND starred IS NOT NULL) OR (:starredFilter = 2 AND starred IS NULL)) " +
"AND (:search = '' OR instr(lower(name), lower(:search)) > 0) " +
"ORDER BY starred IS NULL, starred ASC, name COLLATE NOCASE, id",
)
@Query("SELECT * $ARTISTS_FILTER ORDER BY $ARTIST_ORDER_BY_STARRED_REVERSED")
fun artistsByStarredReversed(
sourceId: Long,
starredFilter: Int,
search: String,
): PagingSource<Int, Artist>
@Query(
"SELECT * FROM playlists WHERE sourceId = :sourceId " +
"AND (:search = '' OR instr(lower(name), lower(:search)) > 0) " +
"ORDER BY name COLLATE NOCASE, id",
)
@Query("SELECT * $PLAYLISTS_FILTER ORDER BY $PLAYLIST_ORDER_BY_NAME")
fun playlistsByName(
sourceId: Long,
search: String,
): PagingSource<Int, Playlist>
@Query(
"SELECT * FROM playlists WHERE sourceId = :sourceId " +
"AND (:search = '' OR instr(lower(name), lower(:search)) > 0) " +
"ORDER BY name COLLATE NOCASE DESC, id",
)
@Query("SELECT * $PLAYLISTS_FILTER ORDER BY $PLAYLIST_ORDER_BY_NAME_REVERSED")
fun playlistsByNameReversed(
sourceId: Long,
search: String,
): PagingSource<Int, Playlist>
@Query(
"SELECT * FROM playlists WHERE sourceId = :sourceId " +
"AND (:search = '' OR instr(lower(name), lower(:search)) > 0) " +
"ORDER BY created DESC, name COLLATE NOCASE, id",
)
@Query("SELECT * $PLAYLISTS_FILTER ORDER BY $PLAYLIST_ORDER_BY_ADDED")
fun playlistsByAdded(
sourceId: Long,
search: String,
): PagingSource<Int, Playlist>
@Query(
"SELECT * FROM playlists WHERE sourceId = :sourceId " +
"AND (:search = '' OR instr(lower(name), lower(:search)) > 0) " +
"ORDER BY created ASC, name COLLATE NOCASE, id",
)
@Query("SELECT * $PLAYLISTS_FILTER ORDER BY $PLAYLIST_ORDER_BY_ADDED_REVERSED")
fun playlistsByAddedReversed(
sourceId: Long,
search: String,
): PagingSource<Int, Playlist>
@Query(
"SELECT * FROM playlists WHERE sourceId = :sourceId " +
"AND (:search = '' OR instr(lower(name), lower(:search)) > 0) " +
"ORDER BY changed DESC, name COLLATE NOCASE, id",
)
@Query("SELECT * $PLAYLISTS_FILTER ORDER BY $PLAYLIST_ORDER_BY_UPDATED")
fun playlistsByUpdated(
sourceId: Long,
search: String,
): PagingSource<Int, Playlist>
@Query(
"SELECT * FROM playlists WHERE sourceId = :sourceId " +
"AND (:search = '' OR instr(lower(name), lower(:search)) > 0) " +
"ORDER BY changed ASC, name COLLATE NOCASE, id",
)
@Query("SELECT * $PLAYLISTS_FILTER ORDER BY $PLAYLIST_ORDER_BY_UPDATED_REVERSED")
fun playlistsByUpdatedReversed(
sourceId: Long,
search: String,
@@ -135,6 +135,87 @@ val MIGRATION_13_14 =
}
}
val MIGRATION_14_15 =
object : Migration(14, 15) {
override suspend fun migrate(connection: SQLiteConnection) {
rebuildTable(connection, "albums", ALBUMS_V14, ALBUMS_V14_COPY, ALBUMS_V14_INDICES)
rebuildTable(connection, "artists", ARTISTS_V14, ARTISTS_V14_COPY, ARTISTS_V14_INDICES)
rebuildTable(connection, "playlists", PLAYLISTS_V14, PLAYLISTS_V14_COPY, PLAYLISTS_V14_INDICES)
}
}
private suspend fun rebuildTable(
connection: SQLiteConnection,
table: String,
createSql: String,
copySql: String,
indices: List<String>,
) {
val temp = "${table}_new"
connection.execSQL(createSql.replace("\${TABLE_NAME}", temp))
connection.execSQL(copySql.replace("\${TABLE_NAME}", temp))
connection.execSQL("DROP TABLE `$table`")
connection.execSQL("ALTER TABLE `$temp` RENAME TO `$table`")
indices.forEach { connection.execSQL(it.replace("\${TABLE_NAME}", table)) }
}
private const val ALBUMS_V14 =
"CREATE TABLE IF NOT EXISTS `\${TABLE_NAME}` (`sourceId` INTEGER NOT NULL, `id` TEXT NOT NULL, `artistId` TEXT, " +
"`name` TEXT NOT NULL COLLATE NOCASE, `albumArtist` TEXT COLLATE NOCASE, `created` INTEGER NOT NULL, " +
"`coverArt` TEXT, `genre` TEXT, `year` INTEGER, `starred` INTEGER, `songCount` INTEGER NOT NULL, " +
"PRIMARY KEY(`sourceId`, `id`), " +
"FOREIGN KEY(`sourceId`) REFERENCES `sources`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )"
private const val ALBUMS_V14_COPY =
"INSERT INTO `\${TABLE_NAME}` (sourceId, id, artistId, name, albumArtist, created, coverArt, genre, year, starred, songCount) " +
"SELECT sourceId, id, artistId, name, albumArtist, created, coverArt, genre, year, starred, songCount FROM `albums`"
private val ALBUMS_V14_INDICES =
listOf(
"CREATE INDEX IF NOT EXISTS `index_albums_sourceId` ON `\${TABLE_NAME}` (`sourceId`)",
"CREATE INDEX IF NOT EXISTS `index_albums_sourceId_artistId` ON `\${TABLE_NAME}` (`sourceId`, `artistId`)",
"CREATE INDEX IF NOT EXISTS `index_albums_name` ON `\${TABLE_NAME}` (`sourceId`, `name`, `id`)",
"CREATE INDEX IF NOT EXISTS `index_albums_artist` ON `\${TABLE_NAME}` (`sourceId`, `albumArtist`, `year`, `name`, `id`)",
"CREATE INDEX IF NOT EXISTS `index_albums_year` ON `\${TABLE_NAME}` (`sourceId` ASC, `year` DESC, `name` ASC, `id` ASC)",
"CREATE INDEX IF NOT EXISTS `index_albums_added` ON `\${TABLE_NAME}` (`sourceId` ASC, `created` DESC, `name` ASC, `id` ASC)",
"CREATE INDEX IF NOT EXISTS `index_albums_starred` ON `\${TABLE_NAME}` (`sourceId` ASC, `starred` DESC, `name` ASC, `id` ASC)",
)
private const val ARTISTS_V14 =
"CREATE TABLE IF NOT EXISTS `\${TABLE_NAME}` (`sourceId` INTEGER NOT NULL, `id` TEXT NOT NULL, " +
"`name` TEXT NOT NULL COLLATE NOCASE, `albumCount` INTEGER NOT NULL, `starred` INTEGER, `coverArt` TEXT, " +
"PRIMARY KEY(`sourceId`, `id`), FOREIGN KEY(`sourceId`) REFERENCES `sources`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )"
private const val ARTISTS_V14_COPY =
"INSERT INTO `\${TABLE_NAME}` (sourceId, id, name, albumCount, starred, coverArt) " +
"SELECT sourceId, id, name, albumCount, starred, coverArt FROM `artists`"
private val ARTISTS_V14_INDICES =
listOf(
"CREATE INDEX IF NOT EXISTS `index_artists_sourceId` ON `\${TABLE_NAME}` (`sourceId`)",
"CREATE INDEX IF NOT EXISTS `index_artists_name` ON `\${TABLE_NAME}` (`sourceId`, `name`, `id`)",
"CREATE INDEX IF NOT EXISTS `index_artists_albumCount` ON `\${TABLE_NAME}` (`sourceId` ASC, `albumCount` DESC, `name` ASC, `id` ASC)",
"CREATE INDEX IF NOT EXISTS `index_artists_starred` ON `\${TABLE_NAME}` (`sourceId` ASC, `starred` DESC, `name` ASC, `id` ASC)",
)
private const val PLAYLISTS_V14 =
"CREATE TABLE IF NOT EXISTS `\${TABLE_NAME}` (`sourceId` INTEGER NOT NULL, `id` TEXT NOT NULL, " +
"`name` TEXT NOT NULL COLLATE NOCASE, `comment` TEXT, `coverArt` TEXT, `songCount` INTEGER NOT NULL, " +
"`created` INTEGER NOT NULL, `changed` INTEGER NOT NULL DEFAULT 0, `duration` INTEGER NOT NULL DEFAULT 0, " +
"PRIMARY KEY(`sourceId`, `id`), FOREIGN KEY(`sourceId`) REFERENCES `sources`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )"
private const val PLAYLISTS_V14_COPY =
"INSERT INTO `\${TABLE_NAME}` (sourceId, id, name, comment, coverArt, songCount, created, changed, duration) " +
"SELECT sourceId, id, name, comment, coverArt, songCount, created, changed, duration FROM `playlists`"
private val PLAYLISTS_V14_INDICES =
listOf(
"CREATE INDEX IF NOT EXISTS `index_playlists_sourceId` ON `\${TABLE_NAME}` (`sourceId`)",
"CREATE INDEX IF NOT EXISTS `index_playlists_name` ON `\${TABLE_NAME}` (`sourceId`, `name`, `id`)",
"CREATE INDEX IF NOT EXISTS `index_playlists_added` ON `\${TABLE_NAME}` (`sourceId` ASC, `created` DESC, `name` ASC, `id` ASC)",
"CREATE INDEX IF NOT EXISTS `index_playlists_updated` ON `\${TABLE_NAME}` (`sourceId` ASC, `changed` DESC, `name` ASC, `id` ASC)",
)
val MIGRATIONS: Array<Migration> =
arrayOf(
MIGRATION_1_2,
@@ -150,4 +231,5 @@ val MIGRATIONS: Array<Migration> =
MIGRATION_11_12,
MIGRATION_12_13,
MIGRATION_13_14,
MIGRATION_14_15,
)
@@ -32,7 +32,7 @@ import com.subtracks.data.model.SubsonicSource
ShuffleOrder::class,
ArtworkSeed::class,
],
version = 14,
version = 15,
exportSchema = true,
)
@ColumnTypeConverters(QueueKindConverter::class)
@@ -48,12 +48,25 @@ data class SubsonicSource(
onDelete = ForeignKey.CASCADE,
),
],
indices = [Index("sourceId")],
indices = [
Index("sourceId"),
Index(name = "index_artists_name", value = ["sourceId", "name", "id"]),
Index(
name = "index_artists_albumCount",
value = ["sourceId", "albumCount", "name", "id"],
orders = [Index.Order.ASC, Index.Order.DESC, Index.Order.ASC, Index.Order.ASC],
),
Index(
name = "index_artists_starred",
value = ["sourceId", "starred", "name", "id"],
orders = [Index.Order.ASC, Index.Order.DESC, Index.Order.ASC, Index.Order.ASC],
),
],
)
data class Artist(
val sourceId: Long,
val id: String,
val name: String,
@ColumnInfo(collate = ColumnInfo.NOCASE) val name: String,
val albumCount: Long,
val starred: Long?,
val coverArt: String? = null,
@@ -70,14 +83,34 @@ data class Artist(
onDelete = ForeignKey.CASCADE,
),
],
indices = [Index("sourceId"), Index("sourceId", "artistId")],
indices = [
Index("sourceId"),
Index("sourceId", "artistId"),
Index(name = "index_albums_name", value = ["sourceId", "name", "id"]),
Index(name = "index_albums_artist", value = ["sourceId", "albumArtist", "year", "name", "id"]),
Index(
name = "index_albums_year",
value = ["sourceId", "year", "name", "id"],
orders = [Index.Order.ASC, Index.Order.DESC, Index.Order.ASC, Index.Order.ASC],
),
Index(
name = "index_albums_added",
value = ["sourceId", "created", "name", "id"],
orders = [Index.Order.ASC, Index.Order.DESC, Index.Order.ASC, Index.Order.ASC],
),
Index(
name = "index_albums_starred",
value = ["sourceId", "starred", "name", "id"],
orders = [Index.Order.ASC, Index.Order.DESC, Index.Order.ASC, Index.Order.ASC],
),
],
)
data class Album(
val sourceId: Long,
val id: String,
val artistId: String?,
val name: String,
val albumArtist: String?,
@ColumnInfo(collate = ColumnInfo.NOCASE) val name: String,
@ColumnInfo(collate = ColumnInfo.NOCASE) val albumArtist: String?,
val created: Long,
val coverArt: String?,
val genre: String?,
@@ -118,12 +151,25 @@ data class Disc(
onDelete = ForeignKey.CASCADE,
),
],
indices = [Index("sourceId")],
indices = [
Index("sourceId"),
Index(name = "index_playlists_name", value = ["sourceId", "name", "id"]),
Index(
name = "index_playlists_added",
value = ["sourceId", "created", "name", "id"],
orders = [Index.Order.ASC, Index.Order.DESC, Index.Order.ASC, Index.Order.ASC],
),
Index(
name = "index_playlists_updated",
value = ["sourceId", "changed", "name", "id"],
orders = [Index.Order.ASC, Index.Order.DESC, Index.Order.ASC, Index.Order.ASC],
),
],
)
data class Playlist(
val sourceId: Long,
val id: String,
val name: String,
@ColumnInfo(collate = ColumnInfo.NOCASE) val name: String,
val comment: String?,
val coverArt: String?,
val songCount: Long,
@@ -285,7 +285,7 @@ private suspend fun <T> PooledConnection.upsertChanged(
"ON CONFLICT(${key.joinToString()}) DO UPDATE SET " +
updates.joinToString { "$it = excluded.$it" } +
" WHERE " +
updates.joinToString(" OR ") { "$table.$it IS NOT excluded.$it" }
updates.joinToString(" OR ") { "$table.$it COLLATE BINARY IS NOT excluded.$it" }
usePrepared(sql) { statement ->
rows.forEach { row ->
values(row).forEachIndexed { index, value -> statement.bind(index + 1, value) }