diff --git a/data/src/main/java/tachiyomi/data/DatabaseBindings.kt b/data/src/main/java/tachiyomi/data/DatabaseBindings.kt index d9151c5d2..e84037e6a 100644 --- a/data/src/main/java/tachiyomi/data/DatabaseBindings.kt +++ b/data/src/main/java/tachiyomi/data/DatabaseBindings.kt @@ -36,15 +36,15 @@ object DatabaseBindings { return Database( driver = driver, historyAdapter = History.Adapter( - last_readAdapter = DateColumnAdapter, + read_atAdapter = DateColumnAdapter, ), - mangasAdapter = Mangas.Adapter( - genreAdapter = StringListColumnAdapter, - update_strategyAdapter = UpdateStrategyColumnAdapter, - memoAdapter = MemoColumnAdapter, + mangaAdapter = Manga.Adapter( + remote_genreAdapter = StringListColumnAdapter, + remote_update_strategyAdapter = UpdateStrategyColumnAdapter, + remote_memoAdapter = MemoColumnAdapter, ), - chaptersAdapter = Chapters.Adapter( - memoAdapter = MemoColumnAdapter, + chapterAdapter = Chapter.Adapter( + remote_memoAdapter = MemoColumnAdapter, ), ) } diff --git a/data/src/main/java/tachiyomi/data/backup/RestoreRepositoryImpl.kt b/data/src/main/java/tachiyomi/data/backup/RestoreRepositoryImpl.kt index cf9dd19ab..31610b0ab 100644 --- a/data/src/main/java/tachiyomi/data/backup/RestoreRepositoryImpl.kt +++ b/data/src/main/java/tachiyomi/data/backup/RestoreRepositoryImpl.kt @@ -35,10 +35,10 @@ class RestoreRepositoryImpl( ) : RestoreRepository { override suspend fun getMangaUrlsBySourceId(): Map> { - return database.mangasQueries + return database.mangaQueries .getAllMangaSourceAndUrl() .awaitAsList() - .groupBy({ it.source }, { it.url }) + .groupBy({ it.source_id }, { it.remote_url }) } override suspend fun restoreManga(entries: List, update: suspend (Manga) -> MangaUpdate) { @@ -92,7 +92,7 @@ class RestoreRepositoryImpl( } private suspend fun updateManga(manga: Manga): Manga { - database.mangasQueries.updateFromBackup( + database.mangaQueries.updateFromBackup( favoriteAt = manga.favoriteAt, artist = manga.artist, author = manga.author, @@ -115,7 +115,7 @@ class RestoreRepositoryImpl( } private suspend fun insertManga(manga: Manga): Long { - return database.mangasQueries.insertReturningId( + return database.mangaQueries.insertReturningId( favoriteAt = manga.favoriteAt, source = manga.source, url = manga.url, @@ -183,23 +183,23 @@ class RestoreRepositoryImpl( .partition { it.id > 0 } newChapters.forEach { chapter -> - database.chaptersQueries.insert( - chapter.mangaId, - chapter.url, - chapter.name, - chapter.scanlator, - chapter.read, - chapter.bookmark, - chapter.lastPageRead, - chapter.chapterNumber, - chapter.sourceOrder, - chapter.dateFetch, - chapter.dateUpload, - chapter.memo, + database.chapterQueries.insert( + mangaId = chapter.mangaId, + url = chapter.url, + name = chapter.name, + scanlator = chapter.scanlator, + read = chapter.read, + bookmark = chapter.bookmark, + lastPageRead = chapter.lastPageRead, + chapterNumber = chapter.chapterNumber, + sourceOrder = chapter.sourceOrder, + dateFetch = chapter.dateFetch, + dateUpload = chapter.dateUpload, + memo = chapter.memo, ) } existingChapters.forEach { chapter -> - database.chaptersQueries.updateFromBackup( + database.chapterQueries.updateFromBackup( read = chapter.read, bookmark = chapter.bookmark, lastPageRead = chapter.lastPageRead, @@ -223,25 +223,25 @@ class RestoreRepositoryImpl( .awaitAsOneOrNull() if (dbHistory == null) { - val chapter = database.chaptersQueries + val chapter = database.chapterQueries .getChapterByUrlAndMangaId(chapterUrl, manga.id) .awaitAsOneOrNull() // Chapter doesn't exist; skip ?: return@mapNotNull null // New history entry - return@mapNotNull Triple(chapter._id, Date(readAt), readDuration) + return@mapNotNull Triple(chapter.id, Date(readAt), readDuration) } // Update history entry Triple( dbHistory.chapter_id, - Date(max(readAt, dbHistory.last_read?.time ?: 0L)), - max(readDuration, dbHistory.time_read) - dbHistory.time_read, + Date(max(readAt, dbHistory.read_at?.time ?: 0L)), + max(readDuration, dbHistory.read_duration) - dbHistory.read_duration, ) } toUpdate.forEach { (chapterId, readAt, readDuration) -> - database.historyQueries.upsert(chapterId, readAt, readDuration) + database.historyQueries.upsert(chapterId = chapterId, readAt = readAt, readDuration = readDuration) } } @@ -269,31 +269,31 @@ class RestoreRepositoryImpl( } existingTracks.forEach { track -> - database.manga_syncQueries.update( - track.mangaId, - track.trackerId, - track.remoteId, - track.libraryId, - track.title, - track.lastChapterRead, - track.totalChapters, - track.status, - track.score, - track.remoteUrl, - track.startDate, - track.finishDate, - track.private, - track.id, + database.manga_trackQueries.update( + mangaId = track.mangaId, + syncId = track.trackerId, + mediaId = track.remoteId, + libraryId = track.libraryId, + title = track.title, + lastChapterRead = track.lastChapterRead, + totalChapter = track.totalChapters, + status = track.status, + score = track.score, + trackingUrl = track.remoteUrl, + startDate = track.startDate, + finishDate = track.finishDate, + `private` = track.private, + id = track.id, ) } } private suspend fun restoreExcludedScanlators(manga: Manga, excludedScanlators: List) { if (excludedScanlators.isEmpty()) return - val existingExcludedScanlators = database.excluded_scanlatorsQueries + val existingExcludedScanlators = database.excluded_scanlatorQueries .getExcludedScanlatorsByMangaId(manga.id) .awaitAsList() val toInsert = excludedScanlators.filter { it !in existingExcludedScanlators } - toInsert.forEach { database.excluded_scanlatorsQueries.insert(manga.id, it) } + toInsert.forEach { database.excluded_scanlatorQueries.insert(manga.id, it) } } } diff --git a/data/src/main/java/tachiyomi/data/category/CategoryRepositoryImpl.kt b/data/src/main/java/tachiyomi/data/category/CategoryRepositoryImpl.kt index 271fc7796..fed1d96df 100644 --- a/data/src/main/java/tachiyomi/data/category/CategoryRepositoryImpl.kt +++ b/data/src/main/java/tachiyomi/data/category/CategoryRepositoryImpl.kt @@ -21,37 +21,37 @@ class CategoryRepositoryImpl( ) : CategoryRepository { override suspend fun get(id: Long): Category? { - return database.categoriesQueries + return database.categoryQueries .getCategory(id, ::mapCategory) .awaitAsOneOrNull() } override suspend fun getAll(): List { - return database.categoriesQueries + return database.categoryQueries .getCategories(::mapCategory) .awaitAsList() } override fun getAllAsFlow(): Flow> { - return database.categoriesQueries + return database.categoryQueries .getCategories(::mapCategory) .subscribeToList() } override suspend fun getCategoriesByMangaId(mangaId: Long): List { - return database.categoriesQueries + return database.categoryQueries .getCategoriesByMangaId(mangaId, ::mapCategory) .awaitAsList() } override fun getCategoriesByMangaIdAsFlow(mangaId: Long): Flow> { - return database.categoriesQueries + return database.categoryQueries .getCategoriesByMangaId(mangaId, ::mapCategory) .subscribeToList() } override suspend fun insert(category: NewCategory) { - database.categoriesQueries.insert(name = category.name, flags = category.flags) + database.categoryQueries.insert(name = category.name, flags = category.flags) } override suspend fun insertAll(categories: List) { @@ -61,32 +61,32 @@ class CategoryRepositoryImpl( } override suspend fun updateName(categoryId: Long, name: String) { - database.categoriesQueries.updateName(name = name, categoryId = categoryId) + database.categoryQueries.updateName(name = name, categoryId = categoryId) } override suspend fun updateFlags(categoryId: Long, flags: Long) { - database.categoriesQueries.updateFlags(flags = flags, categoryId = categoryId) + database.categoryQueries.updateFlags(flags = flags, categoryId = categoryId) } override suspend fun updateAllFlags(flags: Long?) { - database.categoriesQueries.updateAllFlags(flags = flags) + database.categoryQueries.updateAllFlags(flags = flags) } override suspend fun updateAllOrders(orderedIds: List) { database.transaction { - val current = database.categoriesQueries.getUserCategoryIds().awaitAsList() + val current = database.categoryQueries.getUserCategoryIds().awaitAsList() val ids = orderedIds.filter { it in current } + current.filterNot { it in orderedIds } ids.forEachIndexed { index, categoryId -> - database.categoriesQueries.updateOrder(order = -index - 2L, categoryId = categoryId) + database.categoryQueries.updateOrder(order = -index - 2L, categoryId = categoryId) } ids.forEachIndexed { index, categoryId -> - database.categoriesQueries.updateOrder(order = index.toLong(), categoryId = categoryId) + database.categoryQueries.updateOrder(order = index.toLong(), categoryId = categoryId) } } } override suspend fun delete(categoryId: Long) { - database.categoriesQueries.delete(categoryId = categoryId) + database.categoryQueries.delete(categoryId = categoryId) } private fun mapCategory( diff --git a/data/src/main/java/tachiyomi/data/chapter/ChapterRepositoryImpl.kt b/data/src/main/java/tachiyomi/data/chapter/ChapterRepositoryImpl.kt index 9c978f7a2..cb20c697f 100644 --- a/data/src/main/java/tachiyomi/data/chapter/ChapterRepositoryImpl.kt +++ b/data/src/main/java/tachiyomi/data/chapter/ChapterRepositoryImpl.kt @@ -39,37 +39,41 @@ class ChapterRepositoryImpl( ): List { return database.transactionWithResult { if (removedIds.isNotEmpty()) { - database.chaptersQueries.removeChaptersWithIds(removedIds) + database.chapterQueries.removeChaptersWithIds(removedIds) } val existing = added.map { it.mangaId } .distinct() .flatMap { mangaId -> - database.chaptersQueries - .getChaptersByMangaId(mangaId, applyScanlatorFilter = false.toLong(), ::mapChapter) + database.chapterQueries + .getChaptersByMangaId( + mangaId = mangaId, + applyScanlatorFilter = false.toLong(), + mapper = ::mapChapter, + ) .awaitAsList() .map { mangaId to it.url } } .toMutableSet() val stored = added.filter { existing.add(it.mangaId to it.url) }.map { chapter -> - val chapterId = database.chaptersQueries.insertReturningId( - chapter.mangaId, - chapter.url, - chapter.name, - chapter.scanlator, - chapter.read, - chapter.bookmark, - chapter.lastPageRead, - chapter.chapterNumber, - chapter.sourceOrder, - chapter.dateFetch, - chapter.dateUpload, - chapter.memo, + val chapterId = database.chapterQueries.insertReturningId( + mangaId = chapter.mangaId, + url = chapter.url, + name = chapter.name, + scanlator = chapter.scanlator, + read = chapter.read, + bookmark = chapter.bookmark, + lastPageRead = chapter.lastPageRead, + chapterNumber = chapter.chapterNumber, + sourceOrder = chapter.sourceOrder, + dateFetch = chapter.dateFetch, + dateUpload = chapter.dateUpload, + memo = chapter.memo, ) .awaitAsOne() chapter.copy(id = chapterId) } updated.forEach { chapterUpdate -> - database.chaptersQueries.updateRemote( + database.chapterQueries.updateRemote( name = chapterUpdate.name, scanlator = chapterUpdate.scanlator, chapterNumber = chapterUpdate.chapterNumber, @@ -86,7 +90,7 @@ class ChapterRepositoryImpl( private suspend fun partialUpdate(vararg chapterUpdates: ChapterUpdate) { database.transaction { chapterUpdates.forEach { chapterUpdate -> - database.chaptersQueries.update( + database.chapterQueries.update( read = chapterUpdate.read, bookmark = chapterUpdate.bookmark, lastPageRead = chapterUpdate.lastPageRead, @@ -98,43 +102,51 @@ class ChapterRepositoryImpl( } override suspend fun getChapterByMangaId(mangaId: Long, applyScanlatorFilter: Boolean): List { - return database.chaptersQueries - .getChaptersByMangaId(mangaId, applyScanlatorFilter.toLong(), ::mapChapter) + return database.chapterQueries + .getChaptersByMangaId( + mangaId = mangaId, + applyScanlatorFilter = applyScanlatorFilter.toLong(), + mapper = ::mapChapter, + ) .awaitAsList() } override suspend fun getScanlatorsByMangaId(mangaId: Long): List { - return database.chaptersQueries + return database.chapterQueries .getScanlatorsByMangaId(mangaId) { it.orEmpty() } .awaitAsList() } override fun getScanlatorsByMangaIdAsFlow(mangaId: Long): Flow> { - return database.chaptersQueries + return database.chapterQueries .getScanlatorsByMangaId(mangaId) { it.orEmpty() } .subscribeToList() } override suspend fun getBookmarkedChaptersByMangaId(mangaId: Long): List { - return database.chaptersQueries + return database.chapterQueries .getBookmarkedChaptersByMangaId(mangaId, ::mapChapter) .awaitAsList() } override suspend fun getChapterById(id: Long): Chapter? { - return database.chaptersQueries + return database.chapterQueries .getChapterById(id, ::mapChapter) .awaitAsOneOrNull() } override suspend fun getChapterByMangaIdAsFlow(mangaId: Long, applyScanlatorFilter: Boolean): Flow> { - return database.chaptersQueries - .getChaptersByMangaId(mangaId, applyScanlatorFilter.toLong(), ::mapChapter) + return database.chapterQueries + .getChaptersByMangaId( + mangaId = mangaId, + applyScanlatorFilter = applyScanlatorFilter.toLong(), + mapper = ::mapChapter, + ) .subscribeToList() } override suspend fun getChapterByUrlAndMangaId(url: String, mangaId: Long): Chapter? { - return database.chaptersQueries + return database.chapterQueries .getChapterByUrlAndMangaId(url, mangaId, ::mapChapter) .awaitAsOneOrNull() } diff --git a/data/src/main/java/tachiyomi/data/history/HistoryRepositoryImpl.kt b/data/src/main/java/tachiyomi/data/history/HistoryRepositoryImpl.kt index 4830bbf78..275325286 100644 --- a/data/src/main/java/tachiyomi/data/history/HistoryRepositoryImpl.kt +++ b/data/src/main/java/tachiyomi/data/history/HistoryRepositoryImpl.kt @@ -81,9 +81,9 @@ class HistoryRepositoryImpl( override suspend fun upsertHistory(historyUpdate: HistoryUpdate) { try { database.historyQueries.upsert( - historyUpdate.chapterId, - historyUpdate.readAt, - historyUpdate.sessionReadDuration, + chapterId = historyUpdate.chapterId, + readAt = historyUpdate.readAt, + readDuration = historyUpdate.sessionReadDuration, ) } catch (e: Exception) { logcat(LogPriority.ERROR, throwable = e) diff --git a/data/src/main/java/tachiyomi/data/manga/MangaMapper.kt b/data/src/main/java/tachiyomi/data/manga/MangaMapper.kt index 1a77acaa7..ef7225827 100644 --- a/data/src/main/java/tachiyomi/data/manga/MangaMapper.kt +++ b/data/src/main/java/tachiyomi/data/manga/MangaMapper.kt @@ -84,27 +84,27 @@ object MangaMapper { categories: String, ): LibraryManga = LibraryManga( manga = mapManga( - id, - source, - url, - artist, - author, - description, - genre, - title, - status, - thumbnailUrl, - lastUpdate, - nextUpdate, - initialized, - viewerFlags, - chapterFlags, - coverLastModified, - updateStrategy, - calculateInterval, - notes, - memo, - favoriteAt, + id = id, + source = source, + url = url, + artist = artist, + author = author, + description = description, + genre = genre, + title = title, + status = status, + thumbnailUrl = thumbnailUrl, + lastUpdate = lastUpdate, + nextUpdate = nextUpdate, + initialized = initialized, + viewerFlags = viewerFlags, + chapterFlags = chapterFlags, + coverLastModified = coverLastModified, + updateStrategy = updateStrategy, + calculateInterval = calculateInterval, + notes = notes, + memo = memo, + favoriteAt = favoriteAt, ), categories = categories.split(",").map { it.toLong() }, totalChapters = totalCount, @@ -140,27 +140,27 @@ object MangaMapper { totalCount: Long, ): MangaWithChapterCount = MangaWithChapterCount( manga = mapManga( - id, - source, - url, - artist, - author, - description, - genre, - title, - status, - thumbnailUrl, - lastUpdate, - nextUpdate, - initialized, - viewerFlags, - chapterFlags, - coverLastModified, - updateStrategy, - calculateInterval, - notes, - memo, - favoriteAt, + id = id, + source = source, + url = url, + artist = artist, + author = author, + description = description, + genre = genre, + title = title, + status = status, + thumbnailUrl = thumbnailUrl, + lastUpdate = lastUpdate, + nextUpdate = nextUpdate, + initialized = initialized, + viewerFlags = viewerFlags, + chapterFlags = chapterFlags, + coverLastModified = coverLastModified, + updateStrategy = updateStrategy, + calculateInterval = calculateInterval, + notes = notes, + memo = memo, + favoriteAt = favoriteAt, ), chapterCount = totalCount, ) diff --git a/data/src/main/java/tachiyomi/data/manga/MangaRepositoryImpl.kt b/data/src/main/java/tachiyomi/data/manga/MangaRepositoryImpl.kt index 4253ec19a..dc3b44549 100644 --- a/data/src/main/java/tachiyomi/data/manga/MangaRepositoryImpl.kt +++ b/data/src/main/java/tachiyomi/data/manga/MangaRepositoryImpl.kt @@ -35,37 +35,37 @@ class MangaRepositoryImpl( ) : MangaRepository { override suspend fun getMangaById(id: Long): Manga { - return database.mangasQueries + return database.mangaQueries .getMangaById(id, MangaMapper::mapManga) .awaitAsOne() } override fun getMangaByIdAsFlow(id: Long): Flow { - return database.mangasQueries + return database.mangaQueries .getMangaById(id, MangaMapper::mapManga) .subscribeToOne() } override suspend fun getMangaByUrlAndSourceId(url: String, sourceId: Long): Manga? { - return database.mangasQueries + return database.mangaQueries .getMangaByUrlAndSource(url, sourceId, MangaMapper::mapManga) .awaitAsOneOrNull() } override fun getMangaByUrlAndSourceIdAsFlow(url: String, sourceId: Long): Flow { - return database.mangasQueries + return database.mangaQueries .getMangaByUrlAndSource(url, sourceId, MangaMapper::mapManga) .subscribeToOneOrNull() } override suspend fun getFavorites(): List { - return database.mangasQueries + return database.mangaQueries .getFavorites(MangaMapper::mapManga) .awaitAsList() } override suspend fun getReadMangaNotInLibrary(): List { - return database.mangasQueries + return database.mangaQueries .getReadMangaNotInLibrary(MangaMapper::mapManga) .awaitAsList() } @@ -83,13 +83,13 @@ class MangaRepositoryImpl( } override fun getFavoritesBySourceId(sourceId: Long): Flow> { - return database.mangasQueries + return database.mangaQueries .getFavoriteBySourceId(sourceId, MangaMapper::mapManga) .subscribeToList() } override suspend fun getDuplicateLibraryManga(id: Long, title: String): List { - return database.mangasQueries + return database.mangaQueries .getDuplicateLibraryManga(id, title, MangaMapper::mapMangaWithChapterCount) .awaitAsList() } @@ -102,7 +102,7 @@ class MangaRepositoryImpl( val timeZone = TimeZone.currentSystemDefault() val epochMillis = Clock.System.now().toLocalDateTime(timeZone).date.atStartOfDayIn(timeZone).toEpochMilliseconds() - return database.mangasQueries + return database.mangaQueries .getUpcomingManga( startOfDay = epochMillis, statuses = statuses, @@ -117,7 +117,7 @@ class MangaRepositoryImpl( override suspend fun resetViewerFlags(): Boolean { return try { - database.mangasQueries.resetViewerFlags() + database.mangaQueries.resetViewerFlags() true } catch (e: Exception) { logcat(LogPriority.ERROR, e) @@ -126,27 +126,27 @@ class MangaRepositoryImpl( } override suspend fun deleteNonLibraryManga(sourceIds: List, keepReadManga: Boolean) { - database.mangasQueries.deleteNonLibraryManga(sourceIds, keepReadManga.toLong()) + database.mangaQueries.deleteNonLibraryManga(sourceIds, keepReadManga.toLong()) } override suspend fun setMangaCategories(mangaId: Long, categoryIds: List) { database.transaction { - database.mangas_categoriesQueries.deleteMangaCategoryByMangaId(mangaId) + database.manga_categoryQueries.deleteMangaCategoryByMangaId(mangaId) categoryIds.forEach { categoryId -> - database.mangas_categoriesQueries.insert(mangaId, categoryId) + database.manga_categoryQueries.insert(mangaId = mangaId, categoryId = categoryId) } } } override suspend fun getExcludedScanlators(mangaId: Long): Set { - return database.excluded_scanlatorsQueries + return database.excluded_scanlatorQueries .getExcludedScanlatorsByMangaId(mangaId) .awaitAsList() .toSet() } override fun getExcludedScanlatorsAsFlow(mangaId: Long): Flow> { - return database.excluded_scanlatorsQueries + return database.excluded_scanlatorQueries .getExcludedScanlatorsByMangaId(mangaId) .subscribeToList() .map { it.toSet() } @@ -154,14 +154,14 @@ class MangaRepositoryImpl( override suspend fun setExcludedScanlators(mangaId: Long, excludedScanlators: Set) { database.transaction { - val current = database.excluded_scanlatorsQueries + val current = database.excluded_scanlatorQueries .getExcludedScanlatorsByMangaId(mangaId) .awaitAsList() .toSet() excludedScanlators.minus(current).forEach { scanlator -> - database.excluded_scanlatorsQueries.insert(mangaId, scanlator) + database.excluded_scanlatorQueries.insert(mangaId, scanlator) } - database.excluded_scanlatorsQueries.remove(mangaId, current.minus(excludedScanlators)) + database.excluded_scanlatorQueries.remove(mangaId, current.minus(excludedScanlators)) } } @@ -188,7 +188,7 @@ class MangaRepositoryImpl( override suspend fun insertNetworkManga(manga: List): List { return database.transactionWithResult { manga.map { - database.mangasQueries.insertNetworkManga( + database.mangaQueries.insertNetworkManga( source = it.source, url = it.url, artist = it.artist, @@ -220,7 +220,7 @@ class MangaRepositoryImpl( override suspend fun updateRemote(update: MangaRemoteUpdate): Boolean { return try { - database.mangasQueries.updateRemote( + database.mangaQueries.updateRemote( artist = update.artist, author = update.author, description = update.description, @@ -245,7 +245,7 @@ class MangaRepositoryImpl( database.transaction { mangaUpdates.forEach { value -> with(value) { - database.mangasQueries.update( + database.mangaQueries.update( favoriteAtSet = isSet(::favoriteAt), favoriteAt = favoriteAt, lastUpdate = lastUpdate, diff --git a/data/src/main/java/tachiyomi/data/source/SourceRepositoryImpl.kt b/data/src/main/java/tachiyomi/data/source/SourceRepositoryImpl.kt index 43337d01b..8e94120c0 100644 --- a/data/src/main/java/tachiyomi/data/source/SourceRepositoryImpl.kt +++ b/data/src/main/java/tachiyomi/data/source/SourceRepositoryImpl.kt @@ -48,7 +48,7 @@ class SourceRepositoryImpl( } override fun getSourcesWithFavoriteCount(): Flow>> { - val sourceIdWithFavoriteCountFlow = database.mangasQueries + val sourceIdWithFavoriteCountFlow = database.mangaQueries .getSourceIdWithFavoriteCount() .subscribeToList() return combine(sourceIdWithFavoriteCountFlow, sourceManager.sources) { sourceIdWithFavoriteCount, _ -> @@ -66,7 +66,7 @@ class SourceRepositoryImpl( } override fun getSourcesWithNonLibraryManga(): Flow> { - return database.mangasQueries + return database.mangaQueries .getSourceIdsWithNonLibraryManga() .subscribeToList() .map { sourceId -> diff --git a/data/src/main/java/tachiyomi/data/source/StubSourceRepositoryImpl.kt b/data/src/main/java/tachiyomi/data/source/StubSourceRepositoryImpl.kt index 8aee2e36f..8797d3c26 100644 --- a/data/src/main/java/tachiyomi/data/source/StubSourceRepositoryImpl.kt +++ b/data/src/main/java/tachiyomi/data/source/StubSourceRepositoryImpl.kt @@ -19,19 +19,19 @@ class StubSourceRepositoryImpl( ) : StubSourceRepository { override fun subscribeAll(): Flow> { - return database.sourcesQueries + return database.sourceQueries .findAll(::mapStubSource) .subscribeToList() } override suspend fun getStubSource(id: Long): StubSource? { - return database.sourcesQueries + return database.sourceQueries .findOne(id, ::mapStubSource) .awaitAsOneOrNull() } override suspend fun upsertStubSource(id: Long, lang: String, name: String) { - database.sourcesQueries.upsert(id, lang, name) + database.sourceQueries.upsert(id = id, lang = lang, name = name) } private fun mapStubSource( diff --git a/data/src/main/java/tachiyomi/data/track/TrackRepositoryImpl.kt b/data/src/main/java/tachiyomi/data/track/TrackRepositoryImpl.kt index 02c27cb39..af5aaaa1d 100644 --- a/data/src/main/java/tachiyomi/data/track/TrackRepositoryImpl.kt +++ b/data/src/main/java/tachiyomi/data/track/TrackRepositoryImpl.kt @@ -20,31 +20,31 @@ class TrackRepositoryImpl( ) : TrackRepository { override suspend fun getTrackById(id: Long): Track? { - return database.manga_syncQueries + return database.manga_trackQueries .getTrackById(id, TrackMapper::mapTrack) .awaitAsOneOrNull() } override suspend fun getTracksByMangaId(mangaId: Long): List { - return database.manga_syncQueries + return database.manga_trackQueries .getTracksByMangaId(mangaId, TrackMapper::mapTrack) .awaitAsList() } override fun getTracksAsFlow(): Flow> { - return database.manga_syncQueries + return database.manga_trackQueries .getTracks(TrackMapper::mapTrack) .subscribeToList() } override fun getTracksByMangaIdAsFlow(mangaId: Long): Flow> { - return database.manga_syncQueries + return database.manga_trackQueries .getTracksByMangaId(mangaId, TrackMapper::mapTrack) .subscribeToList() } override suspend fun delete(mangaId: Long, trackerId: Long) { - database.manga_syncQueries.delete( + database.manga_trackQueries.delete( mangaId = mangaId, syncId = trackerId, ) @@ -61,7 +61,7 @@ class TrackRepositoryImpl( private suspend fun upsertValues(vararg tracks: Track) { database.transaction { tracks.forEach { mangaTrack -> - database.manga_syncQueries.upsert( + database.manga_trackQueries.upsert( mangaId = mangaTrack.mangaId, syncId = mangaTrack.trackerId, remoteId = mangaTrack.remoteId, diff --git a/data/src/main/sqldelight/tachiyomi/data/categories.sq b/data/src/main/sqldelight/tachiyomi/data/categories.sq deleted file mode 100644 index f1e88b8f6..000000000 --- a/data/src/main/sqldelight/tachiyomi/data/categories.sq +++ /dev/null @@ -1,78 +0,0 @@ -CREATE TABLE categories( - _id INTEGER NOT NULL PRIMARY KEY, - name TEXT NOT NULL, - sort INTEGER NOT NULL, - flags INTEGER NOT NULL -); - --- Insert system category -INSERT OR IGNORE INTO categories(_id, name, sort, flags) VALUES (0, "", -1, 0); --- Disallow deletion of default category -CREATE TRIGGER IF NOT EXISTS system_category_delete_trigger BEFORE DELETE -ON categories -BEGIN SELECT CASE - WHEN old._id <= 0 THEN - RAISE(ABORT, "System category can't be deleted") - END; -END; - -getCategory: -SELECT * -FROM categories -WHERE _id = :id -LIMIT 1; - -getCategories: -SELECT -_id AS id, -name, -sort AS `order`, -flags -FROM categories -ORDER BY sort; - -getCategoriesByMangaId: -SELECT -C._id AS id, -C.name, -C.sort AS `order`, -C.flags -FROM categories C -JOIN mangas_categories MC -ON C._id = MC.category_id -WHERE MC.manga_id = :mangaId -ORDER BY C.sort; - -insert: -INSERT INTO categories(name, sort, flags) -SELECT :name, coalesce(max(sort), -1) + 1, :flags -FROM categories; - -delete: -DELETE FROM categories -WHERE _id = :categoryId; - -updateName: -UPDATE categories -SET name = :name -WHERE _id = :categoryId; - -getUserCategoryIds: -SELECT _id -FROM categories -WHERE _id > 0 -ORDER BY sort; - -updateOrder: -UPDATE categories -SET sort = :order -WHERE _id = :categoryId; - -updateFlags: -UPDATE categories -SET flags = :flags -WHERE _id = :categoryId; - -updateAllFlags: -UPDATE categories SET -flags = coalesce(:flags, flags); diff --git a/data/src/main/sqldelight/tachiyomi/data/category.sq b/data/src/main/sqldelight/tachiyomi/data/category.sq new file mode 100644 index 000000000..4cd3d0343 --- /dev/null +++ b/data/src/main/sqldelight/tachiyomi/data/category.sq @@ -0,0 +1,78 @@ +CREATE TABLE category( + id INTEGER NOT NULL PRIMARY KEY, + name TEXT NOT NULL, + `order` INTEGER NOT NULL UNIQUE, + flags INTEGER NOT NULL +); + +-- Insert system category +INSERT OR IGNORE INTO category(id, name, `order`, flags) VALUES (0, "", -1, 0); +-- Disallow deletion of default category +CREATE TRIGGER IF NOT EXISTS system_category_delete_trigger BEFORE DELETE +ON category +BEGIN SELECT CASE + WHEN old.id <= 0 THEN + RAISE(ABORT, "System category can't be deleted") + END; +END; + +getCategory: +SELECT id, name, `order`, flags +FROM category +WHERE id = :id +LIMIT 1; + +getCategories: +SELECT +id, +name, +`order`, +flags +FROM category +ORDER BY `order`; + +getCategoriesByMangaId: +SELECT +C.id, +C.name, +C.`order`, +C.flags +FROM category C +JOIN manga_category MC +ON C.id = MC.category_id +WHERE MC.manga_id = :mangaId +ORDER BY C.`order`; + +insert: +INSERT INTO category(name, `order`, flags) +SELECT :name, coalesce(max(`order`), -1) + 1, :flags +FROM category; + +delete: +DELETE FROM category +WHERE id = :categoryId; + +updateName: +UPDATE category +SET name = :name +WHERE id = :categoryId; + +getUserCategoryIds: +SELECT id +FROM category +WHERE id > 0 +ORDER BY `order`; + +updateOrder: +UPDATE category +SET `order` = :order +WHERE id = :categoryId; + +updateFlags: +UPDATE category +SET flags = :flags +WHERE id = :categoryId; + +updateAllFlags: +UPDATE category SET +flags = coalesce(:flags, flags); diff --git a/data/src/main/sqldelight/tachiyomi/data/chapter.sq b/data/src/main/sqldelight/tachiyomi/data/chapter.sq new file mode 100644 index 000000000..97dcd3c00 --- /dev/null +++ b/data/src/main/sqldelight/tachiyomi/data/chapter.sq @@ -0,0 +1,123 @@ +import kotlin.Boolean; +import kotlinx.serialization.json.JsonObject; + +CREATE TABLE chapter( + id INTEGER NOT NULL PRIMARY KEY, + manga_id INTEGER NOT NULL, + remote_url TEXT NOT NULL, + remote_name TEXT NOT NULL, + remote_scanlator TEXT, + remote_chapter_number REAL NOT NULL, + remote_date_upload INTEGER NOT NULL, + remote_order INTEGER NOT NULL, + remote_memo BLOB AS JsonObject NOT NULL, + user_read INTEGER AS Boolean NOT NULL, + user_bookmark INTEGER AS Boolean NOT NULL, + user_last_page_read INTEGER NOT NULL, + state_date_fetch INTEGER NOT NULL, + UNIQUE(manga_id, remote_url), + FOREIGN KEY(manga_id) REFERENCES manga (id) + ON DELETE CASCADE +); + +CREATE INDEX chapter_state_date_fetch_index ON chapter(state_date_fetch); +CREATE INDEX chapter_unread_state_date_fetch_index ON chapter(state_date_fetch) WHERE user_read = 0; + +getChapterById: +SELECT + id, manga_id, remote_url, remote_name, remote_scanlator, + user_read, user_bookmark, user_last_page_read, remote_chapter_number, remote_order, + state_date_fetch, remote_date_upload, + remote_memo +FROM chapter +WHERE id = :id; + +getChaptersByMangaId: +SELECT + C.id, C.manga_id, C.remote_url, C.remote_name, C.remote_scanlator, + C.user_read, C.user_bookmark, C.user_last_page_read, C.remote_chapter_number, C.remote_order, + C.state_date_fetch, C.remote_date_upload, + C.remote_memo +FROM chapter C +LEFT JOIN excluded_scanlator ES +ON C.manga_id = ES.manga_id +AND C.remote_scanlator = ES.scanlator +WHERE C.manga_id = :mangaId +AND ( + :applyScanlatorFilter = 0 + OR ES.scanlator IS NULL +); + +getScanlatorsByMangaId: +SELECT remote_scanlator +FROM chapter +WHERE manga_id = :mangaId; + +getBookmarkedChaptersByMangaId: +SELECT + id, manga_id, remote_url, remote_name, remote_scanlator, + user_read, user_bookmark, user_last_page_read, remote_chapter_number, remote_order, + state_date_fetch, remote_date_upload, + remote_memo +FROM chapter +WHERE user_bookmark +AND manga_id = :mangaId; + +getChapterByUrl: +SELECT + id, manga_id, remote_url, remote_name, remote_scanlator, + user_read, user_bookmark, user_last_page_read, remote_chapter_number, remote_order, + state_date_fetch, remote_date_upload, + remote_memo +FROM chapter +WHERE remote_url = :chapterUrl; + +getChapterByUrlAndMangaId: +SELECT + id, manga_id, remote_url, remote_name, remote_scanlator, + user_read, user_bookmark, user_last_page_read, remote_chapter_number, remote_order, + state_date_fetch, remote_date_upload, + remote_memo +FROM chapter +WHERE remote_url = :chapterUrl +AND manga_id = :mangaId +LIMIT 1; + +removeChaptersWithIds: +DELETE FROM chapter +WHERE id IN :chapterIds; + +insert: +INSERT INTO chapter(manga_id, remote_url, remote_name, remote_scanlator, user_read, user_bookmark, user_last_page_read, remote_chapter_number, remote_order, state_date_fetch, remote_date_upload, remote_memo) +VALUES (:mangaId, :url, :name, :scanlator, :read, :bookmark, :lastPageRead, :chapterNumber, :sourceOrder, :dateFetch, :dateUpload, :memo); + +insertReturningId: +INSERT INTO chapter(manga_id, remote_url, remote_name, remote_scanlator, user_read, user_bookmark, user_last_page_read, remote_chapter_number, remote_order, state_date_fetch, remote_date_upload, remote_memo) +VALUES (:mangaId, :url, :name, :scanlator, :read, :bookmark, :lastPageRead, :chapterNumber, :sourceOrder, :dateFetch, :dateUpload, :memo) +RETURNING id; + +update: +UPDATE chapter +SET user_read = coalesce(:read, user_read), + user_bookmark = coalesce(:bookmark, user_bookmark), + user_last_page_read = coalesce(:lastPageRead, user_last_page_read), + state_date_fetch = coalesce(:dateFetch, state_date_fetch) +WHERE id = :chapterId; + +updateFromBackup: +UPDATE chapter +SET user_read = coalesce(:read, user_read), + user_bookmark = coalesce(:bookmark, user_bookmark), + user_last_page_read = coalesce(:lastPageRead, user_last_page_read), + remote_memo = coalesce(:memo, remote_memo) +WHERE id = :chapterId; + +updateRemote: +UPDATE chapter +SET remote_name = :name, + remote_scanlator = coalesce(:scanlator, remote_scanlator), + remote_chapter_number = :chapterNumber, + remote_order = :sourceOrder, + remote_date_upload = coalesce(:dateUpload, remote_date_upload), + remote_memo = :memo +WHERE id = :chapterId; diff --git a/data/src/main/sqldelight/tachiyomi/data/chapters.sq b/data/src/main/sqldelight/tachiyomi/data/chapters.sq deleted file mode 100644 index c9ea3ebe4..000000000 --- a/data/src/main/sqldelight/tachiyomi/data/chapters.sq +++ /dev/null @@ -1,104 +0,0 @@ -import kotlin.Boolean; -import kotlinx.serialization.json.JsonObject; - -CREATE TABLE chapters( - _id INTEGER NOT NULL PRIMARY KEY, - manga_id INTEGER NOT NULL, - url TEXT NOT NULL, - name TEXT NOT NULL, - scanlator TEXT, - read INTEGER AS Boolean NOT NULL, - bookmark INTEGER AS Boolean NOT NULL, - last_page_read INTEGER NOT NULL, - chapter_number REAL NOT NULL, - source_order INTEGER NOT NULL, - date_fetch INTEGER NOT NULL, - date_upload INTEGER NOT NULL, - memo BLOB AS JsonObject NOT NULL DEFAULT '{}', - FOREIGN KEY(manga_id) REFERENCES mangas (_id) - ON DELETE CASCADE -); - -CREATE INDEX chapters_manga_id_index ON chapters(manga_id); -CREATE INDEX chapters_unread_by_manga_index ON chapters(manga_id, read) WHERE read = 0; -CREATE INDEX idx_chapters_url ON chapters(url); - - -getChapterById: -SELECT * -FROM chapters -WHERE _id = :id; - -getChaptersByMangaId: -SELECT C.* -FROM chapters C -LEFT JOIN excluded_scanlators ES -ON C.manga_id = ES.manga_id -AND C.scanlator = ES.scanlator -WHERE C.manga_id = :mangaId -AND ( - :applyScanlatorFilter = 0 - OR ES.scanlator IS NULL -); - -getScanlatorsByMangaId: -SELECT scanlator -FROM chapters -WHERE manga_id = :mangaId; - -getBookmarkedChaptersByMangaId: -SELECT * -FROM chapters -WHERE bookmark -AND manga_id = :mangaId; - -getChapterByUrl: -SELECT * -FROM chapters -WHERE url = :chapterUrl; - -getChapterByUrlAndMangaId: -SELECT * -FROM chapters -WHERE url = :chapterUrl -AND manga_id = :mangaId -LIMIT 1; - -removeChaptersWithIds: -DELETE FROM chapters -WHERE _id IN :chapterIds; - -insert: -INSERT INTO chapters(manga_id, url, name, scanlator, read, bookmark, last_page_read, chapter_number, source_order, date_fetch, date_upload, memo) -VALUES (:mangaId, :url, :name, :scanlator, :read, :bookmark, :lastPageRead, :chapterNumber, :sourceOrder, :dateFetch, :dateUpload, :memo); - -insertReturningId: -INSERT INTO chapters(manga_id, url, name, scanlator, read, bookmark, last_page_read, chapter_number, source_order, date_fetch, date_upload, memo) -VALUES (:mangaId, :url, :name, :scanlator, :read, :bookmark, :lastPageRead, :chapterNumber, :sourceOrder, :dateFetch, :dateUpload, :memo) -RETURNING _id; - -update: -UPDATE chapters -SET read = coalesce(:read, read), - bookmark = coalesce(:bookmark, bookmark), - last_page_read = coalesce(:lastPageRead, last_page_read), - date_fetch = coalesce(:dateFetch, date_fetch) -WHERE _id = :chapterId; - -updateFromBackup: -UPDATE chapters -SET read = coalesce(:read, read), - bookmark = coalesce(:bookmark, bookmark), - last_page_read = coalesce(:lastPageRead, last_page_read), - memo = coalesce(:memo, memo) -WHERE _id = :chapterId; - -updateRemote: -UPDATE chapters -SET name = :name, - scanlator = coalesce(:scanlator, scanlator), - chapter_number = :chapterNumber, - source_order = :sourceOrder, - date_upload = coalesce(:dateUpload, date_upload), - memo = :memo -WHERE _id = :chapterId; diff --git a/data/src/main/sqldelight/tachiyomi/data/excluded_scanlator.sq b/data/src/main/sqldelight/tachiyomi/data/excluded_scanlator.sq new file mode 100644 index 000000000..114a0f5c3 --- /dev/null +++ b/data/src/main/sqldelight/tachiyomi/data/excluded_scanlator.sq @@ -0,0 +1,22 @@ +CREATE TABLE excluded_scanlator( + manga_id INTEGER NOT NULL, + scanlator TEXT NOT NULL, + UNIQUE(manga_id, scanlator), + FOREIGN KEY(manga_id) REFERENCES manga (id) + ON DELETE CASCADE +); + + +insert: +INSERT INTO excluded_scanlator(manga_id, scanlator) +VALUES (:mangaId, :scanlator); + +remove: +DELETE FROM excluded_scanlator +WHERE manga_id = :mangaId +AND scanlator IN :scanlators; + +getExcludedScanlatorsByMangaId: +SELECT scanlator +FROM excluded_scanlator +WHERE manga_id = :mangaId; diff --git a/data/src/main/sqldelight/tachiyomi/data/excluded_scanlators.sq b/data/src/main/sqldelight/tachiyomi/data/excluded_scanlators.sq deleted file mode 100644 index 37ed6489e..000000000 --- a/data/src/main/sqldelight/tachiyomi/data/excluded_scanlators.sq +++ /dev/null @@ -1,23 +0,0 @@ -CREATE TABLE excluded_scanlators( - manga_id INTEGER NOT NULL, - scanlator TEXT NOT NULL, - FOREIGN KEY(manga_id) REFERENCES mangas (_id) - ON DELETE CASCADE -); - -CREATE INDEX excluded_scanlators_manga_id_index ON excluded_scanlators(manga_id); -CREATE INDEX idx_excluded_scanlators_scanlator ON excluded_scanlators(scanlator); - -insert: -INSERT INTO excluded_scanlators(manga_id, scanlator) -VALUES (:mangaId, :scanlator); - -remove: -DELETE FROM excluded_scanlators -WHERE manga_id = :mangaId -AND scanlator IN :scanlators; - -getExcludedScanlatorsByMangaId: -SELECT scanlator -FROM excluded_scanlators -WHERE manga_id = :mangaId; diff --git a/data/src/main/sqldelight/tachiyomi/data/extension_store.sq b/data/src/main/sqldelight/tachiyomi/data/extension_store.sq index b30583262..5b395104d 100644 --- a/data/src/main/sqldelight/tachiyomi/data/extension_store.sq +++ b/data/src/main/sqldelight/tachiyomi/data/extension_store.sq @@ -15,7 +15,9 @@ contains: SELECT EXISTS(SELECT 0 FROM extension_store WHERE index_url = :indexUrl); getAll: -SELECT * +SELECT + index_url, name, badge_label, signing_key, + contact_website, contact_discord, is_legacy, extension_list_url FROM extension_store; getCount: diff --git a/data/src/main/sqldelight/tachiyomi/data/history.sq b/data/src/main/sqldelight/tachiyomi/data/history.sq index 6cecd5784..ab0554e42 100644 --- a/data/src/main/sqldelight/tachiyomi/data/history.sq +++ b/data/src/main/sqldelight/tachiyomi/data/history.sq @@ -1,75 +1,70 @@ import java.util.Date; CREATE TABLE history( - _id INTEGER NOT NULL PRIMARY KEY, + id INTEGER NOT NULL PRIMARY KEY, + manga_id INTEGER NOT NULL, chapter_id INTEGER NOT NULL UNIQUE, - last_read INTEGER AS Date, - time_read INTEGER NOT NULL, - FOREIGN KEY(chapter_id) REFERENCES chapters (_id) + read_at INTEGER AS Date, + read_duration INTEGER NOT NULL, + FOREIGN KEY(manga_id) REFERENCES manga (id) + ON DELETE CASCADE, + FOREIGN KEY(chapter_id) REFERENCES chapter (id) ON DELETE CASCADE ); -CREATE INDEX history_history_chapter_id_index ON history(chapter_id); -CREATE INDEX idx_history_last_read ON history(last_read); +CREATE INDEX history_manga_id_index ON history(manga_id); +CREATE INDEX history_read_at_index ON history(read_at); getHistoryByMangaId: SELECT -H._id, +H.id, H.chapter_id, -H.last_read, -H.time_read +H.read_at, +H.read_duration FROM history H -JOIN chapters C -ON H.chapter_id = C._id -WHERE C.manga_id = :mangaId AND C._id = H.chapter_id; +WHERE H.manga_id = :mangaId; getHistoryByChapterUrlAndMangaId: SELECT -H._id, +H.id, H.chapter_id, -H.last_read, -H.time_read +H.read_at, +H.read_duration FROM history H -JOIN chapters C -ON H.chapter_id = C._id -WHERE C.url = :chapterUrl AND C.manga_id = :mangaId AND C._id = H.chapter_id +JOIN chapter C +ON H.chapter_id = C.id +WHERE C.remote_url = :chapterUrl AND C.manga_id = :mangaId LIMIT 1; resetHistoryById: UPDATE history -SET last_read = 0 -WHERE _id = :historyId; +SET read_at = 0 +WHERE id = :historyId; resetHistoryByMangaId: UPDATE history -SET last_read = 0 -WHERE _id IN ( - SELECT H._id - FROM mangas M - INNER JOIN chapters C - ON M._id = C.manga_id - INNER JOIN history H - ON C._id = H.chapter_id - WHERE M._id = :mangaId -); +SET read_at = 0 +WHERE manga_id = :mangaId; removeAllHistory: DELETE FROM history; removeResettedHistory: DELETE FROM history -WHERE last_read = 0; +WHERE read_at = 0; upsert: -INSERT INTO history(chapter_id, last_read, time_read) -VALUES (:chapterId, :readAt, :time_read) +INSERT INTO history(manga_id, chapter_id, read_at, read_duration) +SELECT C.manga_id, :chapterId, :readAt, :readDuration +FROM chapter C +WHERE C.id = :chapterId ON CONFLICT(chapter_id) DO UPDATE SET - last_read = :readAt, - time_read = time_read + :time_read + read_at = :readAt, + read_duration = read_duration + :readDuration WHERE chapter_id = :chapterId; getReadDuration: -SELECT coalesce(sum(time_read), 0) +SELECT coalesce(sum(read_duration), 0) FROM history; diff --git a/data/src/main/sqldelight/tachiyomi/data/manga.sq b/data/src/main/sqldelight/tachiyomi/data/manga.sq new file mode 100644 index 000000000..fa570e857 --- /dev/null +++ b/data/src/main/sqldelight/tachiyomi/data/manga.sq @@ -0,0 +1,311 @@ +import eu.kanade.tachiyomi.source.model.UpdateStrategy; +import kotlin.Boolean; +import kotlin.String; +import kotlin.collections.List; +import kotlinx.serialization.json.JsonObject; + +CREATE TABLE manga( + id INTEGER NOT NULL PRIMARY KEY, + source_id INTEGER NOT NULL, + remote_url TEXT NOT NULL, + remote_title TEXT NOT NULL, + remote_author TEXT, + remote_artist TEXT, + remote_description TEXT, + remote_genre TEXT AS List, + remote_status INTEGER NOT NULL, + remote_cover TEXT, + remote_update_strategy INTEGER AS UpdateStrategy NOT NULL, + remote_memo BLOB AS JsonObject NOT NULL, + user_favorite_at INTEGER, + user_notes TEXT NOT NULL, + user_reader_flags INTEGER NOT NULL, + user_chapter_flags INTEGER NOT NULL, + state_chapter_last_update INTEGER, + state_chapter_next_update INTEGER, + state_chapter_fetch_interval INTEGER NOT NULL, + state_cover_last_modified INTEGER NOT NULL, + state_initialized INTEGER AS Boolean NOT NULL, + UNIQUE(source_id, remote_url) +); + +CREATE INDEX manga_user_favorite_at_index ON manga(user_favorite_at) WHERE user_favorite_at IS NOT NULL; + +getMangaById: +SELECT + id, source_id, remote_url, remote_artist, remote_author, remote_description, remote_genre, remote_title, + remote_status, remote_cover, state_chapter_last_update, state_chapter_next_update, state_initialized, + user_reader_flags, user_chapter_flags, state_cover_last_modified, remote_update_strategy, + state_chapter_fetch_interval, user_notes, remote_memo, user_favorite_at +FROM manga +WHERE id = :id; + +-- TODO: this should ideally never really have more than 1 result +getMangaByUrlAndSource: +SELECT + id, source_id, remote_url, remote_artist, remote_author, remote_description, remote_genre, remote_title, + remote_status, remote_cover, state_chapter_last_update, state_chapter_next_update, state_initialized, + user_reader_flags, user_chapter_flags, state_cover_last_modified, remote_update_strategy, + state_chapter_fetch_interval, user_notes, remote_memo, user_favorite_at +FROM manga +WHERE remote_url = :url +AND source_id = :source +LIMIT 1; + +getFavorites: +SELECT + id, source_id, remote_url, remote_artist, remote_author, remote_description, remote_genre, remote_title, + remote_status, remote_cover, state_chapter_last_update, state_chapter_next_update, state_initialized, + user_reader_flags, user_chapter_flags, state_cover_last_modified, remote_update_strategy, + state_chapter_fetch_interval, user_notes, remote_memo, user_favorite_at +FROM manga +WHERE user_favorite_at IS NOT NULL; + +getReadMangaNotInLibrary: +SELECT + id, source_id, remote_url, remote_artist, remote_author, remote_description, remote_genre, remote_title, + remote_status, remote_cover, state_chapter_last_update, state_chapter_next_update, state_initialized, + user_reader_flags, user_chapter_flags, state_cover_last_modified, remote_update_strategy, + state_chapter_fetch_interval, user_notes, remote_memo, user_favorite_at +FROM manga +WHERE user_favorite_at IS NULL AND id IN ( + SELECT DISTINCT chapter.manga_id + FROM chapter + WHERE user_read = 1 OR user_last_page_read != 0 +); + +getAllManga: +SELECT + id, source_id, remote_url, remote_artist, remote_author, remote_description, remote_genre, remote_title, + remote_status, remote_cover, state_chapter_last_update, state_chapter_next_update, state_initialized, + user_reader_flags, user_chapter_flags, state_cover_last_modified, remote_update_strategy, + state_chapter_fetch_interval, user_notes, remote_memo, user_favorite_at +FROM manga; + +getAllMangaSourceAndUrl: +SELECT source_id, remote_url +FROM manga; + +getSourceIdWithFavoriteCount: +SELECT +source_id, +count(*) +FROM manga +WHERE user_favorite_at IS NOT NULL +GROUP BY source_id; + +getFavoriteBySourceId: +SELECT + id, source_id, remote_url, remote_artist, remote_author, remote_description, remote_genre, remote_title, + remote_status, remote_cover, state_chapter_last_update, state_chapter_next_update, state_initialized, + user_reader_flags, user_chapter_flags, state_cover_last_modified, remote_update_strategy, + state_chapter_fetch_interval, user_notes, remote_memo, user_favorite_at +FROM manga +WHERE user_favorite_at IS NOT NULL +AND source_id = :sourceId; + +getDuplicateLibraryManga: +WITH +track_dupes AS ( + SELECT DISTINCT S2.manga_id + FROM manga_track S1 + INNER JOIN manga_track S2 + ON S1.tracker_id = S2.tracker_id + AND S1.remote_id = S2.remote_id + AND S1.remote_id != 0 + AND S1.manga_id != S2.manga_id + WHERE S1.manga_id = :id +), +duplicates AS ( + SELECT + M.id, M.source_id, M.remote_url, M.remote_artist, M.remote_author, M.remote_description, M.remote_genre, M.remote_title, + M.remote_status, M.remote_cover, M.state_chapter_last_update, M.state_chapter_next_update, M.state_initialized, + M.user_reader_flags, M.user_chapter_flags, M.state_cover_last_modified, M.remote_update_strategy, + M.state_chapter_fetch_interval, M.user_notes, M.remote_memo, M.user_favorite_at + FROM manga M + LEFT JOIN track_dupes D + ON D.manga_id = id + WHERE user_favorite_at IS NOT NULL + AND id != :id + AND (lower(remote_title) LIKE '%' || lower(:title) || '%' OR D.manga_id IS NOT NULL) +), +chapter_counts AS ( + SELECT + M.id AS manga_id, + count(*) AS chapter_count + FROM duplicates M + JOIN chapter C + ON M.id = C.manga_id + LEFT JOIN excluded_scanlator ES + ON C.manga_id = ES.manga_id + AND C.remote_scanlator = ES.scanlator + WHERE ES.scanlator IS NULL + GROUP BY M.id +) +SELECT + M.id, M.source_id, M.remote_url, M.remote_artist, M.remote_author, M.remote_description, M.remote_genre, M.remote_title, + M.remote_status, M.remote_cover, M.state_chapter_last_update, M.state_chapter_next_update, M.state_initialized, + M.user_reader_flags, M.user_chapter_flags, M.state_cover_last_modified, M.remote_update_strategy, + M.state_chapter_fetch_interval, M.user_notes, M.remote_memo, M.user_favorite_at, + coalesce(CC.chapter_count, 0) AS chapter_count +FROM duplicates M +LEFT JOIN chapter_counts CC +ON M.id = CC.manga_id; + +getUpcomingManga: +SELECT + id, source_id, remote_url, remote_artist, remote_author, remote_description, remote_genre, remote_title, + remote_status, remote_cover, state_chapter_last_update, state_chapter_next_update, state_initialized, + user_reader_flags, user_chapter_flags, state_cover_last_modified, remote_update_strategy, + state_chapter_fetch_interval, user_notes, remote_memo, user_favorite_at +FROM manga +WHERE state_chapter_next_update >= :startOfDay +AND user_favorite_at IS NOT NULL +AND remote_status IN :statuses +AND ( + -- includedEmpty being true expresses "don't care" state and bypasses the membership filter + :includedEmpty + OR EXISTS ( + SELECT 1 FROM manga_category + WHERE manga_category.manga_id = manga.id + AND COALESCE(manga_category.category_id, 0) IN :includedCategories + ) + OR (0 IN :includedCategories AND NOT EXISTS ( + SELECT 1 FROM manga_category WHERE manga_category.manga_id = manga.id) + ) +) +AND ( + -- excludedEmpty being true expresses "don't care" state and bypasses the membership filter + :excludedEmpty + OR ( + NOT EXISTS ( + SELECT 1 FROM manga_category + WHERE manga_category.manga_id = manga.id + AND COALESCE(manga_category.category_id, 0) IN :excludedCategories + ) + AND ( + 0 NOT IN :excludedCategories + OR EXISTS (SELECT 1 FROM manga_category WHERE manga_category.manga_id = manga.id) + ) + ) +) +ORDER BY state_chapter_next_update ASC; + +resetViewerFlags: +UPDATE manga +SET user_reader_flags = 0; + +getSourceIdsWithNonLibraryManga: +SELECT source_id, COUNT(*) AS manga_count +FROM manga +WHERE user_favorite_at IS NULL +GROUP BY source_id; + +deleteNonLibraryManga: +DELETE FROM manga +WHERE user_favorite_at IS NULL +AND source_id IN :sourceIds +AND ( + :keepReadManga = 0 + OR id NOT IN ( + SELECT DISTINCT manga_id + FROM chapter + WHERE user_read = 1 + OR user_last_page_read != 0 + ) +); + +insertReturningId: +INSERT INTO manga(source_id, remote_url, remote_artist, remote_author, remote_description, remote_genre, remote_title, remote_status, remote_cover, user_favorite_at, state_chapter_last_update, state_chapter_next_update, state_initialized, user_reader_flags, user_chapter_flags, state_cover_last_modified, remote_update_strategy, state_chapter_fetch_interval, user_notes, remote_memo) +VALUES (:source, :url, :artist, :author, :description, :genre, :title, :status, :thumbnailUrl, :favoriteAt, :lastUpdate, :nextUpdate, :initialized, :viewerFlags, :chapterFlags, :coverLastModified, :updateStrategy, :calculateInterval, :notes, :memo) +RETURNING id; + +update: +UPDATE manga SET + -- NULL is a real value here (not in the library), which coalesce cannot write + user_favorite_at = CASE WHEN :favoriteAtSet THEN :favoriteAt ELSE user_favorite_at END, + state_chapter_last_update = coalesce(:lastUpdate, state_chapter_last_update), + state_chapter_next_update = coalesce(:nextUpdate, state_chapter_next_update), + user_reader_flags = coalesce(:viewer, user_reader_flags), + user_chapter_flags = coalesce(:chapterFlags, user_chapter_flags), + state_cover_last_modified = coalesce(:coverLastModified, state_cover_last_modified), + state_chapter_fetch_interval = coalesce(:calculateInterval, state_chapter_fetch_interval), + user_notes = coalesce(:notes, user_notes) +WHERE id = :mangaId; + +updateFromBackup: +UPDATE manga SET + remote_artist = coalesce(:artist, remote_artist), + remote_author = coalesce(:author, remote_author), + remote_description = coalesce(:description, remote_description), + remote_genre = coalesce(:genre, remote_genre), + remote_title = coalesce(:title, remote_title), + remote_status = coalesce(:status, remote_status), + remote_cover = coalesce(:thumbnailUrl, remote_cover), + user_favorite_at = :favoriteAt, + state_chapter_last_update = coalesce(:lastUpdate, state_chapter_last_update), + state_initialized = coalesce(:initialized, state_initialized), + user_reader_flags = coalesce(:viewer, user_reader_flags), + user_chapter_flags = coalesce(:chapterFlags, user_chapter_flags), + state_cover_last_modified = coalesce(:coverLastModified, state_cover_last_modified), + remote_update_strategy = coalesce(:updateStrategy, remote_update_strategy), + user_notes = coalesce(:notes, user_notes), + remote_memo = coalesce(:memo, remote_memo) +WHERE id = :mangaId; + +updateRemote: +UPDATE manga SET + remote_artist = coalesce(:artist, remote_artist), + remote_author = coalesce(:author, remote_author), + remote_description = coalesce(:description, remote_description), + remote_genre = coalesce(:genre, remote_genre), + remote_title = coalesce(:title, remote_title), + remote_status = :status, + remote_cover = coalesce(:thumbnailUrl, remote_cover), + state_initialized = :initialized, + state_cover_last_modified = coalesce(:coverLastModified, state_cover_last_modified), + remote_update_strategy = :updateStrategy, + remote_memo = :memo +WHERE id = :mangaId; + +insertNetworkManga { + -- Insert the manga if it doesn't exist already + INSERT INTO manga( + source_id, remote_url, remote_artist, remote_author, remote_description, remote_genre, remote_title, remote_status, remote_cover, user_favorite_at, + state_chapter_last_update, state_chapter_next_update, state_initialized, user_reader_flags, user_chapter_flags, state_cover_last_modified, + remote_update_strategy, state_chapter_fetch_interval, user_notes, remote_memo + ) + SELECT + :source, :url, :artist, :author, :description, :genre, :title, :status, :thumbnailUrl, :favoriteAt, + :lastUpdate, :nextUpdate, :initialized, :viewerFlags, :chapterFlags, :coverLastModified, + :updateStrategy, :calculateInterval, '', :memo + WHERE NOT EXISTS(SELECT 0 FROM manga WHERE source_id = :source AND remote_url = :url); + + -- Update the relevant details if applicable and not favorite + UPDATE manga + SET + remote_title = CASE WHEN :updateTitle THEN :title ELSE remote_title END, + remote_cover = CASE WHEN :updateCover THEN :thumbnailUrl ELSE remote_cover END, + remote_author = CASE WHEN :updateDetails THEN :author ELSE remote_author END, + remote_artist = CASE WHEN :updateDetails THEN :artist ELSE remote_artist END, + remote_description = CASE WHEN :updateDetails THEN :description ELSE remote_description END, + remote_genre = CASE WHEN :updateDetails THEN :genre ELSE remote_genre END, + remote_status = CASE WHEN :updateDetails THEN :status ELSE remote_status END, + remote_update_strategy = CASE WHEN :updateDetails THEN :updateStrategy ELSE remote_update_strategy END, + state_initialized = :updateDetails, + remote_memo = :memo + WHERE source_id = :source + AND remote_url = :url + AND user_favorite_at IS NULL; + + -- Finally return the manga + SELECT + id, source_id, remote_url, remote_artist, remote_author, remote_description, remote_genre, remote_title, + remote_status, remote_cover, state_chapter_last_update, state_chapter_next_update, state_initialized, + user_reader_flags, user_chapter_flags, state_cover_last_modified, remote_update_strategy, + state_chapter_fetch_interval, user_notes, remote_memo, user_favorite_at + FROM manga + WHERE source_id = :source + AND remote_url = :url + LIMIT 1; +} diff --git a/data/src/main/sqldelight/tachiyomi/data/manga_category.sq b/data/src/main/sqldelight/tachiyomi/data/manga_category.sq new file mode 100644 index 000000000..4974db32d --- /dev/null +++ b/data/src/main/sqldelight/tachiyomi/data/manga_category.sq @@ -0,0 +1,19 @@ +CREATE TABLE manga_category( + manga_id INTEGER NOT NULL, + category_id INTEGER NOT NULL, + UNIQUE(manga_id, category_id), + FOREIGN KEY(category_id) REFERENCES category (id) + ON DELETE CASCADE, + FOREIGN KEY(manga_id) REFERENCES manga (id) + ON DELETE CASCADE +); + +CREATE INDEX manga_category_category_id_index ON manga_category(category_id); + +insert: +INSERT INTO manga_category(manga_id, category_id) +VALUES (:mangaId, :categoryId); + +deleteMangaCategoryByMangaId: +DELETE FROM manga_category +WHERE manga_id = :mangaId; diff --git a/data/src/main/sqldelight/tachiyomi/data/manga_sync.sq b/data/src/main/sqldelight/tachiyomi/data/manga_track.sq similarity index 54% rename from data/src/main/sqldelight/tachiyomi/data/manga_sync.sq rename to data/src/main/sqldelight/tachiyomi/data/manga_track.sq index 7c9d88575..9e1eef029 100644 --- a/data/src/main/sqldelight/tachiyomi/data/manga_sync.sq +++ b/data/src/main/sqldelight/tachiyomi/data/manga_track.sq @@ -1,49 +1,58 @@ import kotlin.Boolean; -CREATE TABLE manga_sync( - _id INTEGER NOT NULL PRIMARY KEY, +CREATE TABLE manga_track( + id INTEGER NOT NULL PRIMARY KEY, manga_id INTEGER NOT NULL, - sync_id INTEGER NOT NULL, + tracker_id INTEGER NOT NULL, remote_id INTEGER NOT NULL, + remote_url TEXT NOT NULL, library_id INTEGER, title TEXT NOT NULL, last_chapter_read REAL NOT NULL, total_chapters INTEGER NOT NULL, status INTEGER NOT NULL, score REAL NOT NULL, - remote_url TEXT NOT NULL, start_date INTEGER NOT NULL, finish_date INTEGER NOT NULL, - private INTEGER AS Boolean DEFAULT 0 NOT NULL, - UNIQUE (manga_id, sync_id) ON CONFLICT REPLACE, - FOREIGN KEY(manga_id) REFERENCES mangas (_id) + private INTEGER AS Boolean NOT NULL DEFAULT 0, + UNIQUE (manga_id, tracker_id), + FOREIGN KEY(manga_id) REFERENCES manga (id) ON DELETE CASCADE ); -CREATE INDEX idx_manga_sync_manga_id ON manga_sync(manga_id); +CREATE INDEX manga_track_tracker_id_remote_id_index ON manga_track(tracker_id, remote_id); delete: -DELETE FROM manga_sync -WHERE manga_id = :mangaId AND sync_id = :syncId; +DELETE FROM manga_track +WHERE manga_id = :mangaId AND tracker_id = :syncId; getTracks: -SELECT * -FROM manga_sync; +SELECT + id, manga_id, tracker_id, remote_id, library_id, + title, last_chapter_read, total_chapters, status, score, + remote_url, start_date, finish_date, private +FROM manga_track; getTrackById: -SELECT * -FROM manga_sync -WHERE _id = :id; +SELECT + id, manga_id, tracker_id, remote_id, library_id, + title, last_chapter_read, total_chapters, status, score, + remote_url, start_date, finish_date, private +FROM manga_track +WHERE id = :id; getTracksByMangaId: -SELECT * -FROM manga_sync +SELECT + id, manga_id, tracker_id, remote_id, library_id, + title, last_chapter_read, total_chapters, status, score, + remote_url, start_date, finish_date, private +FROM manga_track WHERE manga_id = :mangaId; upsert: -INSERT INTO manga_sync(manga_id,sync_id,remote_id,library_id,title,last_chapter_read,total_chapters,status,score,remote_url,start_date,finish_date,private) +INSERT INTO manga_track(manga_id,tracker_id,remote_id,library_id,title,last_chapter_read,total_chapters,status,score,remote_url,start_date,finish_date,private) VALUES (:mangaId,:syncId,:remoteId,:libraryId,:title,:lastChapterRead,:totalChapters,:status,:score,:remoteUrl,:startDate,:finishDate,:private) -ON CONFLICT(manga_id, sync_id) +ON CONFLICT(manga_id, tracker_id) DO UPDATE SET remote_id = :remoteId, @@ -57,13 +66,13 @@ SET start_date = :startDate, finish_date = :finishDate, private = :private -WHERE manga_id = :mangaId AND sync_id = :syncId; +WHERE manga_id = :mangaId AND tracker_id = :syncId; update: -UPDATE manga_sync +UPDATE manga_track SET manga_id = coalesce(:mangaId, manga_id), - sync_id = coalesce(:syncId, sync_id), + tracker_id = coalesce(:syncId, tracker_id), remote_id = coalesce(:mediaId, remote_id), library_id = coalesce(:libraryId, library_id), title = coalesce(:title, title), @@ -75,4 +84,4 @@ SET start_date = coalesce(:startDate, start_date), finish_date = coalesce(:finishDate, finish_date), private = coalesce(:private, private) -WHERE _id = :id; +WHERE id = :id; diff --git a/data/src/main/sqldelight/tachiyomi/data/mangas.sq b/data/src/main/sqldelight/tachiyomi/data/mangas.sq deleted file mode 100644 index 16e715a36..000000000 --- a/data/src/main/sqldelight/tachiyomi/data/mangas.sq +++ /dev/null @@ -1,273 +0,0 @@ -import eu.kanade.tachiyomi.source.model.UpdateStrategy; -import kotlin.Boolean; -import kotlin.String; -import kotlin.collections.List; -import kotlinx.serialization.json.JsonObject; - -CREATE TABLE mangas( - _id INTEGER NOT NULL PRIMARY KEY, - source INTEGER NOT NULL, - url TEXT NOT NULL, - artist TEXT, - author TEXT, - description TEXT, - genre TEXT AS List, - title TEXT NOT NULL, - status INTEGER NOT NULL, - thumbnail_url TEXT, - last_update INTEGER, - next_update INTEGER, - initialized INTEGER AS Boolean NOT NULL, - viewer INTEGER NOT NULL, - chapter_flags INTEGER NOT NULL, - cover_last_modified INTEGER NOT NULL, - update_strategy INTEGER AS UpdateStrategy NOT NULL DEFAULT 0, - calculate_interval INTEGER DEFAULT 0 NOT NULL, - notes TEXT NOT NULL DEFAULT "", - memo BLOB AS JsonObject NOT NULL DEFAULT '{}', - favorite_at INTEGER -); - -CREATE INDEX library_favorite_at_index ON mangas(favorite_at) WHERE favorite_at IS NOT NULL; -CREATE INDEX mangas_url_index ON mangas(url); -CREATE INDEX idx_mangas_source ON mangas(source); - -getMangaById: -SELECT * -FROM mangas -WHERE _id = :id; - --- TODO: this should ideally never really have more than 1 result -getMangaByUrlAndSource: -SELECT * -FROM mangas -WHERE url = :url -AND source = :source -LIMIT 1; - -getFavorites: -SELECT * -FROM mangas -WHERE favorite_at IS NOT NULL; - -getReadMangaNotInLibrary: -SELECT * -FROM mangas -WHERE favorite_at IS NULL AND _id IN ( - SELECT DISTINCT chapters.manga_id - FROM chapters - WHERE read = 1 OR last_page_read != 0 -); - -getAllManga: -SELECT * -FROM mangas; - -getAllMangaSourceAndUrl: -SELECT source, url -FROM mangas; - -getSourceIdWithFavoriteCount: -SELECT -source, -count(*) -FROM mangas -WHERE favorite_at IS NOT NULL -GROUP BY source; - -getFavoriteBySourceId: -SELECT * -FROM mangas -WHERE favorite_at IS NOT NULL -AND source = :sourceId; - -getDuplicateLibraryManga: -WITH -track_dupes AS ( - SELECT DISTINCT S2.manga_id - FROM manga_sync S1 - INNER JOIN manga_sync S2 - ON S1.sync_id = S2.sync_id - AND S1.remote_id = S2.remote_id - AND S1.remote_id != 0 - AND S1.manga_id != S2.manga_id - WHERE S1.manga_id = :id -), -duplicates AS ( - SELECT M.* - FROM mangas M - LEFT JOIN track_dupes D - ON D.manga_id = _id - WHERE favorite_at IS NOT NULL - AND _id != :id - AND (lower(title) LIKE '%' || lower(:title) || '%' OR D.manga_id IS NOT NULL) -), -chapter_counts AS ( - SELECT - M._id AS manga_id, - count(*) AS chapter_count - FROM duplicates M - JOIN chapters C - ON M._id = C.manga_id - LEFT JOIN excluded_scanlators ES - ON C.manga_id = ES.manga_id - AND C.scanlator = ES.scanlator - WHERE ES.scanlator IS NULL - GROUP BY M._id -) -SELECT - M.*, - coalesce(CC.chapter_count, 0) AS chapter_count -FROM duplicates M -LEFT JOIN chapter_counts CC -ON M._id = CC.manga_id; - -getUpcomingManga: -SELECT * -FROM mangas -WHERE next_update >= :startOfDay -AND favorite_at IS NOT NULL -AND status IN :statuses -AND ( - -- includedEmpty being true expresses "don't care" state and bypasses the membership filter - :includedEmpty - OR EXISTS ( - SELECT 1 FROM mangas_categories - WHERE mangas_categories.manga_id = mangas._id - AND COALESCE(mangas_categories.category_id, 0) IN :includedCategories - ) - OR (0 IN :includedCategories AND NOT EXISTS ( - SELECT 1 FROM mangas_categories WHERE mangas_categories.manga_id = mangas._id) - ) -) -AND ( - -- excludedEmpty being true expresses "don't care" state and bypasses the membership filter - :excludedEmpty - OR ( - NOT EXISTS ( - SELECT 1 FROM mangas_categories - WHERE mangas_categories.manga_id = mangas._id - AND COALESCE(mangas_categories.category_id, 0) IN :excludedCategories - ) - AND ( - 0 NOT IN :excludedCategories - OR EXISTS (SELECT 1 FROM mangas_categories WHERE mangas_categories.manga_id = mangas._id) - ) - ) -) -ORDER BY next_update ASC; - -resetViewerFlags: -UPDATE mangas -SET viewer = 0; - -getSourceIdsWithNonLibraryManga: -SELECT source, COUNT(*) AS manga_count -FROM mangas -WHERE favorite_at IS NULL -GROUP BY source; - -deleteNonLibraryManga: -DELETE FROM mangas -WHERE favorite_at IS NULL -AND source IN :sourceIds -AND ( - :keepReadManga = 0 - OR _id NOT IN ( - SELECT DISTINCT manga_id - FROM chapters - WHERE read = 1 - OR last_page_read != 0 - ) -); - -insertReturningId: -INSERT INTO mangas(source, url, artist, author, description, genre, title, status, thumbnail_url, favorite_at, last_update, next_update, initialized, viewer, chapter_flags, cover_last_modified, update_strategy, calculate_interval, notes, memo) -VALUES (:source, :url, :artist, :author, :description, :genre, :title, :status, :thumbnailUrl, :favoriteAt, :lastUpdate, :nextUpdate, :initialized, :viewerFlags, :chapterFlags, :coverLastModified, :updateStrategy, :calculateInterval, :notes, :memo) -RETURNING _id; - -update: -UPDATE mangas SET - -- NULL is a real value here (not in the library), which coalesce cannot write - favorite_at = CASE WHEN :favoriteAtSet THEN :favoriteAt ELSE favorite_at END, - last_update = coalesce(:lastUpdate, last_update), - next_update = coalesce(:nextUpdate, next_update), - viewer = coalesce(:viewer, viewer), - chapter_flags = coalesce(:chapterFlags, chapter_flags), - cover_last_modified = coalesce(:coverLastModified, cover_last_modified), - calculate_interval = coalesce(:calculateInterval, calculate_interval), - notes = coalesce(:notes, notes) -WHERE _id = :mangaId; - -updateFromBackup: -UPDATE mangas SET - artist = coalesce(:artist, artist), - author = coalesce(:author, author), - description = coalesce(:description, description), - genre = coalesce(:genre, genre), - title = coalesce(:title, title), - status = coalesce(:status, status), - thumbnail_url = coalesce(:thumbnailUrl, thumbnail_url), - favorite_at = :favoriteAt, - last_update = coalesce(:lastUpdate, last_update), - initialized = coalesce(:initialized, initialized), - viewer = coalesce(:viewer, viewer), - chapter_flags = coalesce(:chapterFlags, chapter_flags), - cover_last_modified = coalesce(:coverLastModified, cover_last_modified), - update_strategy = coalesce(:updateStrategy, update_strategy), - notes = coalesce(:notes, notes), - memo = coalesce(:memo, memo) -WHERE _id = :mangaId; - -updateRemote: -UPDATE mangas SET - artist = coalesce(:artist, artist), - author = coalesce(:author, author), - description = coalesce(:description, description), - genre = coalesce(:genre, genre), - title = coalesce(:title, title), - status = :status, - thumbnail_url = coalesce(:thumbnailUrl, thumbnail_url), - initialized = :initialized, - cover_last_modified = coalesce(:coverLastModified, cover_last_modified), - update_strategy = :updateStrategy, - memo = :memo -WHERE _id = :mangaId; - -insertNetworkManga { - -- Insert the manga if it doesn't exist already - INSERT INTO mangas( - source, url, artist, author, description, genre, title, status, thumbnail_url, favorite_at, - last_update, next_update, initialized, viewer, chapter_flags, cover_last_modified, - update_strategy, calculate_interval, memo - ) - SELECT - :source, :url, :artist, :author, :description, :genre, :title, :status, :thumbnailUrl, :favoriteAt, - :lastUpdate, :nextUpdate, :initialized, :viewerFlags, :chapterFlags, :coverLastModified, - :updateStrategy, :calculateInterval, :memo - WHERE NOT EXISTS(SELECT 0 FROM mangas WHERE source = :source AND url = :url); - - -- Update the relevant details if applicable and not favorite - UPDATE mangas - SET - title = CASE WHEN :updateTitle THEN :title ELSE title END, - thumbnail_url = CASE WHEN :updateCover THEN :thumbnailUrl ELSE thumbnail_url END, - author = CASE WHEN :updateDetails THEN :author ELSE author END, - artist = CASE WHEN :updateDetails THEN :artist ELSE artist END, - description = CASE WHEN :updateDetails THEN :description ELSE description END, - genre = CASE WHEN :updateDetails THEN :genre ELSE genre END, - status = CASE WHEN :updateDetails THEN :status ELSE status END, - update_strategy = CASE WHEN :updateDetails THEN :updateStrategy ELSE update_strategy END, - initialized = :updateDetails, - memo = :memo - WHERE source = :source - AND url = :url - AND favorite_at IS NULL; - - -- Finally return the manga - SELECT * - FROM mangas - WHERE source = :source - AND url = :url - LIMIT 1; -} diff --git a/data/src/main/sqldelight/tachiyomi/data/mangas_categories.sq b/data/src/main/sqldelight/tachiyomi/data/mangas_categories.sq deleted file mode 100644 index 070d8fd73..000000000 --- a/data/src/main/sqldelight/tachiyomi/data/mangas_categories.sq +++ /dev/null @@ -1,20 +0,0 @@ -CREATE TABLE mangas_categories( - _id INTEGER NOT NULL PRIMARY KEY, - manga_id INTEGER NOT NULL, - category_id INTEGER NOT NULL, - FOREIGN KEY(category_id) REFERENCES categories (_id) - ON DELETE CASCADE, - FOREIGN KEY(manga_id) REFERENCES mangas (_id) - ON DELETE CASCADE -); - -CREATE INDEX idx_mangas_categories_manga_id ON mangas_categories(manga_id); -CREATE INDEX idx_mangas_categories_category_id ON mangas_categories(category_id); - -insert: -INSERT INTO mangas_categories(manga_id, category_id) -VALUES (:mangaId, :categoryId); - -deleteMangaCategoryByMangaId: -DELETE FROM mangas_categories -WHERE manga_id = :mangaId; \ No newline at end of file diff --git a/data/src/main/sqldelight/tachiyomi/data/source.sq b/data/src/main/sqldelight/tachiyomi/data/source.sq new file mode 100644 index 000000000..93477cec6 --- /dev/null +++ b/data/src/main/sqldelight/tachiyomi/data/source.sq @@ -0,0 +1,24 @@ +CREATE TABLE source( + id INTEGER NOT NULL PRIMARY KEY, + name TEXT NOT NULL, + language TEXT NOT NULL +); + +findAll: +SELECT id, language, name +FROM source; + +findOne: +SELECT id, language, name +FROM source +WHERE id = :id; + +upsert: +INSERT INTO source(id, language, name) +VALUES (:id, :lang, :name) +ON CONFLICT(id) +DO UPDATE +SET + language = :lang, + name = :name +WHERE id = :id; diff --git a/data/src/main/sqldelight/tachiyomi/data/sources.sq b/data/src/main/sqldelight/tachiyomi/data/sources.sq deleted file mode 100644 index aac60bb78..000000000 --- a/data/src/main/sqldelight/tachiyomi/data/sources.sq +++ /dev/null @@ -1,24 +0,0 @@ -CREATE TABLE sources( - _id INTEGER NOT NULL PRIMARY KEY, - lang TEXT NOT NULL, - name TEXT NOT NULL -); - -findAll: -SELECT * -FROM sources; - -findOne: -SELECT * -FROM sources -WHERE _id = :id; - -upsert: -INSERT INTO sources(_id, lang, name) -VALUES (:id, :lang, :name) -ON CONFLICT(_id) -DO UPDATE -SET - lang = :lang, - name = :name -WHERE _id = :id; \ No newline at end of file diff --git a/data/src/main/sqldelight/tachiyomi/migrations/17.sqm b/data/src/main/sqldelight/tachiyomi/migrations/17.sqm new file mode 100644 index 000000000..b0d945b5d --- /dev/null +++ b/data/src/main/sqldelight/tachiyomi/migrations/17.sqm @@ -0,0 +1,545 @@ +import eu.kanade.tachiyomi.source.model.UpdateStrategy; +import kotlin.Boolean; +import kotlin.String; +import kotlin.collections.List; +import kotlinx.serialization.json.JsonObject; + +-- --------------------------------------------------------------------------------------------------------- +-- table - manga +-- --------------------------------------------------------------------------------------------------------- + +-- Every view reads `mangas`, so they are dropped here and recreated against the new table below +DROP VIEW historyView; +DROP VIEW libraryView; +DROP VIEW updatesView; + +-- Entries sharing a source and url are merged into one, since the new table won't hold both. The copies are +-- ranked with the ones in the library first, since that is the entry the user has, then by age. The first +-- one stays, takes each detail it is missing from the first copy that has it, and takes over everything +-- attached to the others. +CREATE TEMP TABLE duplicate_mangas AS +SELECT + _id, + first_value(_id) OVER (PARTITION BY source, url ORDER BY favorite_at IS NOT NULL DESC, _id ASC) AS keep_id, + row_number() OVER (PARTITION BY source, url ORDER BY favorite_at IS NOT NULL DESC, _id ASC) AS precedence, + favorite_at IS NOT NULL AS favorite +FROM mangas +WHERE (source, url) IN ( + SELECT source, url + FROM mangas + GROUP BY source, url + HAVING count(*) > 1 +); + +UPDATE mangas +SET favorite_at = merged.favorite_at, + initialized = merged.initialized, + artist = merged.artist, + author = merged.author, + description = merged.description, + genre = merged.genre, + thumbnail_url = merged.thumbnail_url, + status = merged.status, + viewer = merged.viewer, + chapter_flags = merged.chapter_flags, + notes = merged.notes, + memo = merged.memo, + last_update = merged.last_update, + cover_last_modified = merged.cover_last_modified +FROM ( + SELECT DISTINCT + D.keep_id, + -- The earliest real date if any copy has one, else 0 if any copy is in the library, else NULL + coalesce( + min(nullif(M.favorite_at, 0)) OVER (PARTITION BY D.keep_id), + min(M.favorite_at) OVER (PARTITION BY D.keep_id) + ) AS favorite_at, + max(M.initialized) OVER (PARTITION BY D.keep_id) AS initialized, + first_value(M.artist) OVER (PARTITION BY D.keep_id ORDER BY M.artist IS NULL, D.precedence) AS artist, + first_value(M.author) OVER (PARTITION BY D.keep_id ORDER BY M.author IS NULL, D.precedence) AS author, + first_value(M.description) OVER (PARTITION BY D.keep_id ORDER BY M.description IS NULL, D.precedence) AS description, + first_value(M.genre) OVER (PARTITION BY D.keep_id ORDER BY M.genre IS NULL, D.precedence) AS genre, + first_value(M.thumbnail_url) OVER (PARTITION BY D.keep_id ORDER BY M.thumbnail_url IS NULL, D.precedence) AS thumbnail_url, + first_value(M.status) OVER (PARTITION BY D.keep_id ORDER BY M.status = 0, D.precedence) AS status, + first_value(M.viewer) OVER (PARTITION BY D.keep_id ORDER BY M.viewer = 0, D.precedence) AS viewer, + first_value(M.chapter_flags) OVER (PARTITION BY D.keep_id ORDER BY M.chapter_flags = 0, D.precedence) AS chapter_flags, + first_value(M.notes) OVER (PARTITION BY D.keep_id ORDER BY M.notes = '', D.precedence) AS notes, + first_value(M.memo) OVER (PARTITION BY D.keep_id ORDER BY M.memo = '{}', D.precedence) AS memo, + max(M.last_update) OVER (PARTITION BY D.keep_id) AS last_update, + max(M.cover_last_modified) OVER (PARTITION BY D.keep_id) AS cover_last_modified + FROM duplicate_mangas D + JOIN mangas M + ON M._id = D._id +) AS merged +WHERE mangas._id = merged.keep_id; + +-- Every child table is rebuilt further down and resolves the surviving entry there, so nothing +-- else has to be rewritten here first. +DELETE FROM mangas +WHERE _id IN (SELECT _id FROM duplicate_mangas WHERE _id != keep_id); + +CREATE TABLE manga( + id INTEGER NOT NULL PRIMARY KEY, + source_id INTEGER NOT NULL, + remote_url TEXT NOT NULL, + remote_title TEXT NOT NULL, + remote_author TEXT, + remote_artist TEXT, + remote_description TEXT, + remote_genre TEXT AS List, + remote_status INTEGER NOT NULL, + remote_cover TEXT, + remote_update_strategy INTEGER AS UpdateStrategy NOT NULL, + remote_memo BLOB AS JsonObject NOT NULL, + user_favorite_at INTEGER, + user_notes TEXT NOT NULL, + user_reader_flags INTEGER NOT NULL, + user_chapter_flags INTEGER NOT NULL, + state_chapter_last_update INTEGER, + state_chapter_next_update INTEGER, + state_chapter_fetch_interval INTEGER NOT NULL, + state_cover_last_modified INTEGER NOT NULL, + state_initialized INTEGER AS Boolean NOT NULL, + UNIQUE(source_id, remote_url) +); + +INSERT INTO manga( + id, source_id, remote_url, remote_title, remote_author, remote_artist, remote_description, remote_genre, + remote_status, remote_cover, remote_update_strategy, remote_memo, user_favorite_at, user_notes, + user_reader_flags, user_chapter_flags, state_chapter_last_update, state_chapter_next_update, state_chapter_fetch_interval, + state_cover_last_modified, state_initialized +) +SELECT + _id, source, url, title, author, artist, description, genre, + status, thumbnail_url, update_strategy, memo, + favorite_at, notes, + viewer, chapter_flags, last_update, next_update, calculate_interval, + cover_last_modified, initialized +FROM mangas; + +DROP TABLE mangas; + +CREATE INDEX manga_user_favorite_at_index ON manga(user_favorite_at) WHERE user_favorite_at IS NOT NULL; + +-- --------------------------------------------------------------------------------------------------------- +-- table - chapter +-- --------------------------------------------------------------------------------------------------------- + +-- The entries that merged in the previous group brought their chapters with them +UPDATE chapters +SET manga_id = (SELECT keep_id FROM duplicate_mangas WHERE _id = chapters.manga_id) +WHERE manga_id IN (SELECT _id FROM duplicate_mangas WHERE _id != keep_id); + +-- Moving the chapters over brings rows for the same chapter together, and duplicates like those are +-- what issue #647 is about in the first place. The one with reading history is kept, the oldest +-- otherwise, and the read state and history of the others are merged into it. +CREATE TEMP TABLE duplicate_chapters AS +SELECT + _id, + first_value(_id) OVER (PARTITION BY manga_id, url ORDER BY has_history DESC, _id ASC) AS keep_id +FROM ( + SELECT + _id, + manga_id, + url, + EXISTS(SELECT 1 FROM history WHERE history.chapter_id = chapters._id) AS has_history + FROM chapters + WHERE (manga_id, url) IN ( + SELECT manga_id, url + FROM chapters + GROUP BY manga_id, url + HAVING count(*) > 1 + ) +); + +UPDATE chapters +SET read = merged.read, + bookmark = merged.bookmark, + last_page_read = merged.last_page_read, + date_fetch = merged.date_fetch, + date_upload = merged.date_upload +FROM ( + SELECT + D.keep_id, + max(C.read) AS read, + max(C.bookmark) AS bookmark, + max(C.last_page_read) AS last_page_read, + -- 0 means the fetch date was never recorded rather than "the beginning of time", so it only + -- wins when no copy recorded one. Updates only lists chapters fetched after the entry joined + -- the library, so letting a 0 through would hide the merged chapter from it. + coalesce(min(nullif(C.date_fetch, 0)), 0) AS date_fetch, + max(C.date_upload) AS date_upload + FROM duplicate_chapters D + JOIN chapters C + ON C._id = D._id + GROUP BY D.keep_id +) AS merged +WHERE chapters._id = merged.keep_id; + +-- The kept chapter has a history entry whenever any of its duplicates has one +UPDATE history +SET last_read = merged.last_read, + time_read = merged.time_read +FROM ( + SELECT + D.keep_id, + max(H.last_read) AS last_read, + -- Each duplicate accumulated its own reading time, so the durations add up; only the + -- last-read timestamp is a point in time and takes the latest. + sum(H.time_read) AS time_read + FROM duplicate_chapters D + JOIN history H + ON H.chapter_id = D._id + GROUP BY D.keep_id +) AS merged +WHERE history.chapter_id = merged.keep_id; + +-- Foreign keys are off during migrations, so the history of a discarded chapter goes explicitly +DELETE FROM history +WHERE chapter_id IN (SELECT _id FROM duplicate_chapters WHERE _id != keep_id); + +DELETE FROM chapters +WHERE _id IN (SELECT _id FROM duplicate_chapters WHERE _id != keep_id); + +DROP TABLE duplicate_chapters; + +CREATE TABLE chapter( + id INTEGER NOT NULL PRIMARY KEY, + manga_id INTEGER NOT NULL, + remote_url TEXT NOT NULL, + remote_name TEXT NOT NULL, + remote_scanlator TEXT, + remote_chapter_number REAL NOT NULL, + remote_date_upload INTEGER NOT NULL, + remote_order INTEGER NOT NULL, + remote_memo BLOB NOT NULL, + user_read INTEGER NOT NULL, + user_bookmark INTEGER NOT NULL, + user_last_page_read INTEGER NOT NULL, + state_date_fetch INTEGER NOT NULL, + UNIQUE(manga_id, remote_url), + FOREIGN KEY(manga_id) REFERENCES manga (id) + ON DELETE CASCADE +); + +INSERT INTO chapter( + id, manga_id, remote_url, remote_name, remote_scanlator, remote_chapter_number, remote_date_upload, + remote_order, remote_memo, user_read, user_bookmark, user_last_page_read, state_date_fetch +) +SELECT + _id, manga_id, url, name, scanlator, chapter_number, date_upload, + source_order, memo, read, bookmark, last_page_read, date_fetch +FROM chapters; + +DROP TABLE chapters; + +CREATE INDEX chapter_state_date_fetch_index ON chapter(state_date_fetch); +CREATE INDEX chapter_unread_state_date_fetch_index ON chapter(state_date_fetch) WHERE user_read = 0; + +-- --------------------------------------------------------------------------------------------------------- +-- table - category +-- --------------------------------------------------------------------------------------------------------- + +CREATE TABLE category( + id INTEGER NOT NULL PRIMARY KEY, + name TEXT NOT NULL, + `order` INTEGER NOT NULL UNIQUE, + flags INTEGER NOT NULL +); + +-- `sort` was never unique, and reordering wrote colliding values often enough that it cannot simply be +-- copied under the new constraint. The rows keep the order they were displayed in and are renumbered from +-- 0, with ties broken by id. The system category is pinned to -1 rather than renumbered, because it is +-- identified by id 0 everywhere else and sorts ahead of the categories the user made. +INSERT INTO category(id, name, `order`, flags) +SELECT + _id, + name, + CASE + WHEN _id <= 0 THEN -1 + ELSE row_number() OVER (PARTITION BY (_id <= 0) ORDER BY sort, _id) - 1 + END, + flags +FROM categories; + +-- Dropping the table takes `system_category_delete_trigger` with it +DROP TABLE categories; + +CREATE TRIGGER IF NOT EXISTS system_category_delete_trigger BEFORE DELETE +ON category +BEGIN SELECT CASE + WHEN old.id <= 0 THEN + RAISE(ABORT, "System category can't be deleted") + END; +END; + +-- --------------------------------------------------------------------------------------------------------- +-- table - manga_category +-- --------------------------------------------------------------------------------------------------------- + +-- The surrogate key is gone: a manga belongs to a category once, and that pair is the identity of the row +CREATE TABLE manga_category( + manga_id INTEGER NOT NULL, + category_id INTEGER NOT NULL, + UNIQUE(manga_id, category_id), + FOREIGN KEY(category_id) REFERENCES category (id) + ON DELETE CASCADE, + FOREIGN KEY(manga_id) REFERENCES manga (id) + ON DELETE CASCADE +); + +-- Entries merged in the manga group brought their category rows with them. When the entry is in the library, +-- only the copies in it say where it belongs; a copy outside it can still hold the categories it had before +-- it was removed. The same pair can appear twice by now, and membership carries nothing else, so the copies +-- simply collapse. +INSERT INTO manga_category(manga_id, category_id) +SELECT DISTINCT coalesce(D.keep_id, MC.manga_id), MC.category_id +FROM mangas_categories MC +LEFT JOIN duplicate_mangas D +ON D._id = MC.manga_id +LEFT JOIN manga K +ON K.id = D.keep_id +WHERE D._id IS NULL OR D.favorite OR K.user_favorite_at IS NULL; + +DROP TABLE mangas_categories; + +CREATE INDEX manga_category_category_id_index ON manga_category(category_id); + +-- --------------------------------------------------------------------------------------------------------- +-- table - history +-- --------------------------------------------------------------------------------------------------------- + +-- `last_read` is when the chapter was last read and `time_read` is how long it has been read for, so +-- both are renamed to say so. The chapter it belongs to is already unique, which leaves +-- `history_history_chapter_id_index` a prefix of that constraint. +-- The table keeps its name, so the original is renamed aside and the new one built under +-- the real name; renaming a table into place makes SQLite rewrite its stored definition, +-- leaving it spelled differently from the same table on a fresh install. The views were +-- dropped at the top of this migration, so nothing refers to the name being moved. +ALTER TABLE history RENAME TO history_temp; + +CREATE TABLE history( + id INTEGER NOT NULL PRIMARY KEY, + manga_id INTEGER NOT NULL, + chapter_id INTEGER NOT NULL UNIQUE, + read_at INTEGER, + read_duration INTEGER NOT NULL, + FOREIGN KEY(manga_id) REFERENCES manga (id) + ON DELETE CASCADE, + FOREIGN KEY(chapter_id) REFERENCES chapter (id) + ON DELETE CASCADE +); + +-- Reaching the entry a row belongs to meant going through its chapter; it names the entry +-- itself now, taken from that same chapter. A row whose chapter is already gone cannot name +-- one and is dropped, which is what the foreign key would do to it once it is enforced again. +INSERT INTO history(id, manga_id, chapter_id, read_at, read_duration) +SELECT H._id, C.manga_id, H.chapter_id, H.last_read, H.time_read +FROM history_temp H +JOIN chapter C +ON C.id = H.chapter_id; + +DROP TABLE history_temp; + +CREATE INDEX history_manga_id_index ON history(manga_id); +CREATE INDEX history_read_at_index ON history(read_at); + +-- --------------------------------------------------------------------------------------------------------- +-- table - excluded_scanlator +-- --------------------------------------------------------------------------------------------------------- + +CREATE TABLE excluded_scanlator( + manga_id INTEGER NOT NULL, + scanlator TEXT NOT NULL, + UNIQUE(manga_id, scanlator), + FOREIGN KEY(manga_id) REFERENCES manga (id) + ON DELETE CASCADE +); + +-- Excluding a scanlator twice excludes it once, and merged entries brought their exclusions along, so +-- the copies collapse. `excluded_scanlators_manga_id_index` is a prefix of the new constraint. +INSERT INTO excluded_scanlator(manga_id, scanlator) +SELECT DISTINCT coalesce(D.keep_id, ES.manga_id), ES.scanlator +FROM excluded_scanlators ES +LEFT JOIN duplicate_mangas D +ON D._id = ES.manga_id; + +DROP TABLE excluded_scanlators; + + +-- --------------------------------------------------------------------------------------------------------- +-- table - manga_track +-- --------------------------------------------------------------------------------------------------------- + +-- `manga_sync` is what tracking was called when it was still called syncing; `sync_id` is the tracker. +-- An entry that merged can leave two rows for the same tracker on the survivor, which the unique constraint +-- rejects, so the one to keep is chosen first: one on a copy in the library, then the one furthest read, then +-- the one on the copy ranked first, then the oldest. A track is taken whole rather than merged field by field, +-- because two rows on the same tracker can point at different remote entries. +CREATE TEMP TABLE duplicate_tracks AS +SELECT + T._id, + first_value(T._id) OVER ( + PARTITION BY D.keep_id, T.sync_id + ORDER BY D.favorite DESC, T.last_chapter_read DESC, D.precedence ASC, T._id ASC + ) AS keep_track_id +FROM manga_sync T +JOIN duplicate_mangas D +ON D._id = T.manga_id; + +DELETE FROM manga_sync +WHERE _id IN (SELECT _id FROM duplicate_tracks WHERE _id != keep_track_id); + +DROP TABLE duplicate_tracks; + +CREATE TABLE manga_track( + id INTEGER NOT NULL PRIMARY KEY, + manga_id INTEGER NOT NULL, + tracker_id INTEGER NOT NULL, + remote_id INTEGER NOT NULL, + remote_url TEXT NOT NULL, + library_id INTEGER, + title TEXT NOT NULL, + last_chapter_read REAL NOT NULL, + total_chapters INTEGER NOT NULL, + status INTEGER NOT NULL, + score REAL NOT NULL, + start_date INTEGER NOT NULL, + finish_date INTEGER NOT NULL, + private INTEGER NOT NULL DEFAULT 0, + UNIQUE (manga_id, tracker_id), + FOREIGN KEY(manga_id) REFERENCES manga (id) + ON DELETE CASCADE +); + +INSERT INTO manga_track( + id, manga_id, tracker_id, remote_id, remote_url, library_id, title, last_chapter_read, + total_chapters, status, score, start_date, finish_date, private +) +SELECT + T._id, coalesce(D.keep_id, T.manga_id), T.sync_id, T.remote_id, T.remote_url, T.library_id, + T.title, T.last_chapter_read, T.total_chapters, T.status, T.score, T.start_date, + T.finish_date, T.private +FROM manga_sync T +LEFT JOIN duplicate_mangas D +ON D._id = T.manga_id; + +DROP TABLE duplicate_mangas; + +-- `idx_manga_sync_manga_id` is a prefix of the unique constraint +DROP TABLE manga_sync; + +CREATE INDEX manga_track_tracker_id_remote_id_index ON manga_track(tracker_id, remote_id); + +-- --------------------------------------------------------------------------------------------------------- +-- table - source +-- --------------------------------------------------------------------------------------------------------- + +-- The last of the plural table names, and `lang` spelled out. Nothing references this table, so it is a +-- straight copy; name comes before language to read the way the row does. +CREATE TABLE source( + id INTEGER NOT NULL PRIMARY KEY, + name TEXT NOT NULL, + language TEXT NOT NULL +); + +INSERT INTO source(id, name, language) +SELECT _id, name, lang +FROM sources; + +DROP TABLE sources; + +-- The views come back reading the new table +CREATE VIEW historyView AS +SELECT + history.id AS id, + manga.id AS mangaId, + chapter.id AS chapterId, + manga.remote_title AS title, + manga.remote_cover AS thumbnailUrl, + manga.source_id AS sourceId, + manga.user_favorite_at IS NOT NULL AS favorite, + manga.state_cover_last_modified AS coverLastModified, + chapter.remote_chapter_number AS chapterNumber, + history.read_at AS readAt, + history.read_duration AS readDuration, + max_read.read_at AS maxReadAt, + max_read.chapter_id AS maxReadAtChapterId +FROM manga +JOIN chapter +ON manga.id = chapter.manga_id +JOIN history +ON chapter.id = history.chapter_id +JOIN ( + SELECT manga_id, chapter_id, MAX(read_at) AS read_at + FROM history + GROUP BY manga_id +) AS max_read +ON chapter.manga_id = max_read.manga_id; + +CREATE VIEW libraryView AS +SELECT + M.*, + coalesce(C.total, 0) AS totalCount, + coalesce(C.readCount, 0) AS readCount, + coalesce(C.latestUpload, 0) AS latestUpload, + coalesce(C.fetchedAt, 0) AS chapterFetchedAt, + coalesce(C.lastRead, 0) AS lastRead, + coalesce(C.bookmarkCount, 0) AS bookmarkCount, + coalesce(MC.categories, '0') AS categories +FROM manga M +LEFT JOIN ( + SELECT + chapter.manga_id, + count(*) AS total, + sum(user_read) AS readCount, + coalesce(max(chapter.remote_date_upload), 0) AS latestUpload, + coalesce(max(history.read_at), 0) AS lastRead, + coalesce(max(chapter.state_date_fetch), 0) AS fetchedAt, + sum(chapter.user_bookmark) AS bookmarkCount + FROM chapter + LEFT JOIN excluded_scanlator + ON chapter.manga_id = excluded_scanlator.manga_id + AND chapter.remote_scanlator = excluded_scanlator.scanlator + LEFT JOIN history + ON chapter.id = history.chapter_id + WHERE excluded_scanlator.scanlator IS NULL + AND chapter.manga_id IN (SELECT id FROM manga WHERE user_favorite_at IS NOT NULL) + GROUP BY chapter.manga_id +) AS C +ON M.id = C.manga_id +LEFT JOIN ( + SELECT manga_id, group_concat(category_id) AS categories + FROM manga_category + WHERE manga_id IN (SELECT id FROM manga WHERE user_favorite_at IS NOT NULL) + GROUP BY manga_id +) AS MC +ON MC.manga_id = M.id +WHERE M.user_favorite_at IS NOT NULL; + +CREATE VIEW updatesView AS +SELECT + manga.id AS mangaId, + manga.remote_title AS mangaTitle, + chapter.id AS chapterId, + chapter.remote_name AS chapterName, + chapter.remote_scanlator AS scanlator, + chapter.remote_url AS chapterUrl, + chapter.user_read AS read, + chapter.user_bookmark AS bookmark, + chapter.user_last_page_read AS lastPageRead, + manga.source_id AS sourceId, + manga.user_favorite_at IS NOT NULL AS favorite, + manga.remote_cover AS thumbnailUrl, + manga.state_cover_last_modified AS coverLastModified, + chapter.remote_date_upload AS dateUpload, + chapter.state_date_fetch AS dateFetch, + excluded_scanlator.scanlator AS excludedScanlator +FROM manga JOIN chapter +ON manga.id = chapter.manga_id +LEFT JOIN excluded_scanlator +ON manga.id = excluded_scanlator.manga_id +AND chapter.remote_scanlator = excluded_scanlator.scanlator +WHERE user_favorite_at IS NOT NULL +AND state_date_fetch > user_favorite_at +ORDER BY state_date_fetch DESC; diff --git a/data/src/main/sqldelight/tachiyomi/view/historyView.sq b/data/src/main/sqldelight/tachiyomi/view/historyView.sq index f037c4329..de09bc2e9 100644 --- a/data/src/main/sqldelight/tachiyomi/view/historyView.sq +++ b/data/src/main/sqldelight/tachiyomi/view/historyView.sq @@ -1,30 +1,29 @@ CREATE VIEW historyView AS SELECT - history._id AS id, - mangas._id AS mangaId, - chapters._id AS chapterId, - mangas.title, - mangas.thumbnail_url AS thumbnailUrl, - mangas.source, - mangas.favorite_at IS NOT NULL AS favorite, - mangas.cover_last_modified, - chapters.chapter_number AS chapterNumber, - history.last_read AS readAt, - history.time_read AS readDuration, - max_last_read.last_read AS maxReadAt, - max_last_read.chapter_id AS maxReadAtChapterId -FROM mangas -JOIN chapters -ON mangas._id = chapters.manga_id + history.id AS id, + manga.id AS mangaId, + chapter.id AS chapterId, + manga.remote_title AS title, + manga.remote_cover AS thumbnailUrl, + manga.source_id AS sourceId, + manga.user_favorite_at IS NOT NULL AS favorite, + manga.state_cover_last_modified AS coverLastModified, + chapter.remote_chapter_number AS chapterNumber, + history.read_at AS readAt, + history.read_duration AS readDuration, + max_read.read_at AS maxReadAt, + max_read.chapter_id AS maxReadAtChapterId +FROM manga +JOIN chapter +ON manga.id = chapter.manga_id JOIN history -ON chapters._id = history.chapter_id +ON chapter.id = history.chapter_id JOIN ( - SELECT chapters.manga_id,chapters._id AS chapter_id, MAX(history.last_read) AS last_read - FROM chapters JOIN history - ON chapters._id = history.chapter_id - GROUP BY chapters.manga_id -) AS max_last_read -ON chapters.manga_id = max_last_read.manga_id; + SELECT manga_id, chapter_id, MAX(read_at) AS read_at + FROM history + GROUP BY manga_id +) AS max_read +ON chapter.manga_id = max_read.manga_id; history: SELECT @@ -33,9 +32,9 @@ mangaId, chapterId, title, thumbnailUrl, -source, +sourceId, favorite, -cover_last_modified, +coverLastModified, chapterNumber, readAt, readDuration @@ -52,9 +51,9 @@ mangaId, chapterId, title, thumbnailUrl, -source, +sourceId, favorite, -cover_last_modified, +coverLastModified, chapterNumber, readAt, readDuration diff --git a/data/src/main/sqldelight/tachiyomi/view/libraryView.sq b/data/src/main/sqldelight/tachiyomi/view/libraryView.sq index 1d9c98edf..96449ec01 100644 --- a/data/src/main/sqldelight/tachiyomi/view/libraryView.sq +++ b/data/src/main/sqldelight/tachiyomi/view/libraryView.sq @@ -8,34 +8,43 @@ SELECT coalesce(C.lastRead, 0) AS lastRead, coalesce(C.bookmarkCount, 0) AS bookmarkCount, coalesce(MC.categories, '0') AS categories -FROM mangas M +FROM manga M LEFT JOIN ( SELECT - chapters.manga_id, + chapter.manga_id, count(*) AS total, - sum(read) AS readCount, - coalesce(max(chapters.date_upload), 0) AS latestUpload, - coalesce(max(history.last_read), 0) AS lastRead, - coalesce(max(chapters.date_fetch), 0) AS fetchedAt, - sum(chapters.bookmark) AS bookmarkCount - FROM chapters - LEFT JOIN excluded_scanlators - ON chapters.manga_id = excluded_scanlators.manga_id - AND chapters.scanlator = excluded_scanlators.scanlator + sum(user_read) AS readCount, + coalesce(max(chapter.remote_date_upload), 0) AS latestUpload, + coalesce(max(history.read_at), 0) AS lastRead, + coalesce(max(chapter.state_date_fetch), 0) AS fetchedAt, + sum(chapter.user_bookmark) AS bookmarkCount + FROM chapter + LEFT JOIN excluded_scanlator + ON chapter.manga_id = excluded_scanlator.manga_id + AND chapter.remote_scanlator = excluded_scanlator.scanlator LEFT JOIN history - ON chapters._id = history.chapter_id - WHERE excluded_scanlators.scanlator IS NULL - GROUP BY chapters.manga_id + ON chapter.id = history.chapter_id + WHERE excluded_scanlator.scanlator IS NULL + AND chapter.manga_id IN (SELECT id FROM manga WHERE user_favorite_at IS NOT NULL) + GROUP BY chapter.manga_id ) AS C -ON M._id = C.manga_id +ON M.id = C.manga_id LEFT JOIN ( SELECT manga_id, group_concat(category_id) AS categories - FROM mangas_categories + FROM manga_category + WHERE manga_id IN (SELECT id FROM manga WHERE user_favorite_at IS NOT NULL) GROUP BY manga_id ) AS MC -ON MC.manga_id = M._id -WHERE M.favorite_at IS NOT NULL; +ON MC.manga_id = M.id +WHERE M.user_favorite_at IS NOT NULL; library: -SELECT * +SELECT + id, source_id, remote_url, remote_artist, remote_author, + remote_description, remote_genre, remote_title, remote_status, remote_cover, + state_chapter_last_update, state_chapter_next_update, state_initialized, user_reader_flags, + user_chapter_flags, state_cover_last_modified, remote_update_strategy, state_chapter_fetch_interval, + user_notes, remote_memo, + user_favorite_at, totalCount, readCount, latestUpload, chapterFetchedAt, + lastRead, bookmarkCount, categories FROM libraryView; diff --git a/data/src/main/sqldelight/tachiyomi/view/updatesView.sq b/data/src/main/sqldelight/tachiyomi/view/updatesView.sq index 37df2babc..9fb01696d 100644 --- a/data/src/main/sqldelight/tachiyomi/view/updatesView.sq +++ b/data/src/main/sqldelight/tachiyomi/view/updatesView.sq @@ -1,47 +1,55 @@ CREATE VIEW updatesView AS SELECT - mangas._id AS mangaId, - mangas.title AS mangaTitle, - chapters._id AS chapterId, - chapters.name AS chapterName, - chapters.scanlator, - chapters.url AS chapterUrl, - chapters.read, - chapters.bookmark, - chapters.last_page_read, - mangas.source, - mangas.favorite_at IS NOT NULL AS favorite, - mangas.thumbnail_url AS thumbnailUrl, - mangas.cover_last_modified AS coverLastModified, - chapters.date_upload AS dateUpload, - chapters.date_fetch AS datefetch, - excluded_scanlators.scanlator AS excludedScanlator -FROM mangas JOIN chapters -ON mangas._id = chapters.manga_id -LEFT JOIN excluded_scanlators -ON mangas._id = excluded_scanlators.manga_id -AND chapters.scanlator = excluded_scanlators.scanlator -WHERE favorite_at IS NOT NULL -AND date_fetch > favorite_at -ORDER BY date_fetch DESC; + manga.id AS mangaId, + manga.remote_title AS mangaTitle, + chapter.id AS chapterId, + chapter.remote_name AS chapterName, + chapter.remote_scanlator AS scanlator, + chapter.remote_url AS chapterUrl, + chapter.user_read AS read, + chapter.user_bookmark AS bookmark, + chapter.user_last_page_read AS lastPageRead, + manga.source_id AS sourceId, + manga.user_favorite_at IS NOT NULL AS favorite, + manga.remote_cover AS thumbnailUrl, + manga.state_cover_last_modified AS coverLastModified, + chapter.remote_date_upload AS dateUpload, + chapter.state_date_fetch AS dateFetch, + excluded_scanlator.scanlator AS excludedScanlator +FROM manga JOIN chapter +ON manga.id = chapter.manga_id +LEFT JOIN excluded_scanlator +ON manga.id = excluded_scanlator.manga_id +AND chapter.remote_scanlator = excluded_scanlator.scanlator +WHERE user_favorite_at IS NOT NULL +AND state_date_fetch > user_favorite_at +ORDER BY state_date_fetch DESC; getRecentUpdates: -SELECT * +SELECT + mangaId, mangaTitle, chapterId, chapterName, + scanlator, chapterUrl, read, bookmark, + lastPageRead, sourceId, favorite, thumbnailUrl, + coverLastModified, dateUpload, dateFetch, excludedScanlator FROM updatesView WHERE dateUpload > :after -ORDER BY datefetch DESC +ORDER BY dateFetch DESC LIMIT :limit; getRecentUpdatesWithFilters: -SELECT * +SELECT + mangaId, mangaTitle, chapterId, chapterName, + scanlator, chapterUrl, read, bookmark, + lastPageRead, sourceId, favorite, thumbnailUrl, + coverLastModified, dateUpload, dateFetch, excludedScanlator FROM updatesView WHERE dateUpload > :after AND (:read IS NULL OR read = :read) -- Started means some progress but not finished, Read means finished chapter, thus: AND ( :started IS NULL - OR (:started = 1 AND last_page_read > 0 AND read = 0) - OR (:started = 0 AND last_page_read = 0 AND read = 0) + OR (:started = 1 AND lastPageRead > 0 AND read = 0) + OR (:started = 0 AND lastPageRead = 0 AND read = 0) ) AND (:bookmarked IS NULL OR bookmark = :bookmarked) AND ( @@ -51,12 +59,12 @@ AND ( -- includedEmpty being true expresses "don't care" state and bypasses the membership filter :includedEmpty OR EXISTS ( - SELECT 1 FROM mangas_categories - WHERE mangas_categories.manga_id = mangaId - AND COALESCE(mangas_categories.category_id, 0) IN :includedCategories + SELECT 1 FROM manga_category + WHERE manga_category.manga_id = mangaId + AND COALESCE(manga_category.category_id, 0) IN :includedCategories ) OR (0 IN :includedCategories AND NOT EXISTS ( - SELECT 1 FROM mangas_categories mc WHERE mc.manga_id = mangaId) + SELECT 1 FROM manga_category mc WHERE mc.manga_id = mangaId) ) ) AND ( @@ -64,23 +72,27 @@ AND ( :excludedEmpty OR ( NOT EXISTS ( - SELECT 1 FROM mangas_categories - WHERE mangas_categories.manga_id = mangaId - AND COALESCE(mangas_categories.category_id, 0) IN :excludedCategories + SELECT 1 FROM manga_category + WHERE manga_category.manga_id = mangaId + AND COALESCE(manga_category.category_id, 0) IN :excludedCategories ) AND ( 0 NOT IN :excludedCategories - OR EXISTS (SELECT 1 FROM mangas_categories WHERE mangas_categories.manga_id = mangaId) + OR EXISTS (SELECT 1 FROM manga_category WHERE manga_category.manga_id = mangaId) ) ) ) -ORDER BY datefetch DESC +ORDER BY dateFetch DESC LIMIT :limit; getUpdatesByReadStatus: -SELECT * +SELECT + mangaId, mangaTitle, chapterId, chapterName, + scanlator, chapterUrl, read, bookmark, + lastPageRead, sourceId, favorite, thumbnailUrl, + coverLastModified, dateUpload, dateFetch, excludedScanlator FROM updatesView WHERE read = :read AND dateUpload > :after -ORDER BY datefetch DESC +ORDER BY dateFetch DESC LIMIT :limit;