From 6a7efafd9f20500d87507122f8ff88c349868936 Mon Sep 17 00:00:00 2001 From: Mitchell Syer Date: Sat, 27 May 2023 20:41:27 -0400 Subject: [PATCH] Improve database column references and default category handling (#563) * Improve default category handling and add cascade to references where possible * Minor fix for default category * Make the default category always first in the normalization --- .../graphql/mutations/CategoryMutation.kt | 2 +- .../graphql/mutations/ChapterMutation.kt | 2 +- .../suwayomi/tachidesk/manga/impl/Category.kt | 19 +++++---- .../tachidesk/manga/impl/CategoryManga.kt | 13 +----- .../suwayomi/tachidesk/manga/impl/Library.kt | 1 - .../manga/model/table/CategoryMangaTable.kt | 5 ++- .../manga/model/table/ChapterTable.kt | 3 +- .../tachidesk/manga/model/table/MangaTable.kt | 1 - .../tachidesk/manga/model/table/PageTable.kt | 3 +- .../database/migration/M0028_AddCascade.kt | 40 +++++++++++++++++++ .../M0029_DropMangaDefaultCategory.kt | 16 ++++++++ .../manga/controller/UpdateControllerTest.kt | 1 - .../suwayomi/tachidesk/test/TestUtils.kt | 1 - 13 files changed, 76 insertions(+), 31 deletions(-) create mode 100644 server/src/main/kotlin/suwayomi/tachidesk/server/database/migration/M0028_AddCascade.kt create mode 100644 server/src/main/kotlin/suwayomi/tachidesk/server/database/migration/M0029_DropMangaDefaultCategory.kt diff --git a/server/src/main/kotlin/suwayomi/tachidesk/graphql/mutations/CategoryMutation.kt b/server/src/main/kotlin/suwayomi/tachidesk/graphql/mutations/CategoryMutation.kt index 45c177e5..58bbe99e 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/graphql/mutations/CategoryMutation.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/graphql/mutations/CategoryMutation.kt @@ -59,7 +59,7 @@ class CategoryMutation { CategoryMetaTable.deleteWhere { (CategoryMetaTable.ref eq categoryId) and (CategoryMetaTable.key eq key) } - val category= transaction { + val category = transaction { CategoryType(CategoryTable.select { CategoryTable.id eq categoryId }.first()) } diff --git a/server/src/main/kotlin/suwayomi/tachidesk/graphql/mutations/ChapterMutation.kt b/server/src/main/kotlin/suwayomi/tachidesk/graphql/mutations/ChapterMutation.kt index 7036c8a1..83947ab8 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/graphql/mutations/ChapterMutation.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/graphql/mutations/ChapterMutation.kt @@ -170,7 +170,7 @@ class ChapterMutation { ChapterMetaTable.deleteWhere { (ChapterMetaTable.ref eq chapterId) and (ChapterMetaTable.key eq key) } - val chapter= transaction { + val chapter = transaction { ChapterType(ChapterTable.select { ChapterTable.id eq chapterId }.first()) } diff --git a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/Category.kt b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/Category.kt index 86c9723c..536a2b1e 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/Category.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/Category.kt @@ -53,8 +53,8 @@ object Category { fun updateCategory(categoryId: Int, name: String?, isDefault: Boolean?, includeInUpdate: Int?) { transaction { CategoryTable.update({ CategoryTable.id eq categoryId }) { - if (name != null && !name.equals(DEFAULT_CATEGORY_NAME, ignoreCase = true)) it[CategoryTable.name] = name - if (isDefault != null && categoryId != DEFAULT_CATEGORY_ID) it[CategoryTable.isDefault] = isDefault + if (categoryId != DEFAULT_CATEGORY_ID && name != null && !name.equals(DEFAULT_CATEGORY_NAME, ignoreCase = true)) it[CategoryTable.name] = name + if (categoryId != DEFAULT_CATEGORY_ID && isDefault != null) it[CategoryTable.isDefault] = isDefault if (includeInUpdate != null) it[CategoryTable.includeInUpdate] = includeInUpdate } } @@ -79,9 +79,6 @@ object Category { fun removeCategory(categoryId: Int) { if (categoryId == DEFAULT_CATEGORY_ID) return transaction { - CategoryMangaTable.select { CategoryMangaTable.category eq categoryId }.forEach { - removeMangaFromCategory(it[CategoryMangaTable.manga].value, categoryId) - } CategoryTable.deleteWhere { CategoryTable.id eq categoryId } normalizeCategories() } @@ -90,12 +87,14 @@ object Category { /** make sure category order numbers starts from 1 and is consecutive */ private fun normalizeCategories() { transaction { - val categories = CategoryTable.selectAll().orderBy(CategoryTable.order to SortOrder.ASC) - categories.forEachIndexed { index, cat -> - CategoryTable.update({ CategoryTable.id eq cat[CategoryTable.id].value }) { - it[CategoryTable.order] = index + 1 + CategoryTable.selectAll() + .orderBy(CategoryTable.order to SortOrder.ASC) + .sortedWith(compareBy({ it[CategoryTable.id].value != 0 }, { it[CategoryTable.order] })) + .forEachIndexed { index, cat -> + CategoryTable.update({ CategoryTable.id eq cat[CategoryTable.id].value }) { + it[CategoryTable.order] = index + } } - } } } diff --git a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/CategoryManga.kt b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/CategoryManga.kt index 28bc208d..db6dd1a9 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/CategoryManga.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/CategoryManga.kt @@ -19,7 +19,6 @@ import org.jetbrains.exposed.sql.leftJoin import org.jetbrains.exposed.sql.max import org.jetbrains.exposed.sql.select import org.jetbrains.exposed.sql.transactions.transaction -import org.jetbrains.exposed.sql.update import org.jetbrains.exposed.sql.wrapAsExpression import suwayomi.tachidesk.manga.impl.Category.DEFAULT_CATEGORY_ID import suwayomi.tachidesk.manga.impl.util.lang.isEmpty @@ -42,10 +41,6 @@ object CategoryManga { it[CategoryMangaTable.category] = categoryId it[CategoryMangaTable.manga] = mangaId } - - MangaTable.update({ MangaTable.id eq mangaId }) { - it[MangaTable.defaultCategory] = false - } } } } @@ -54,11 +49,6 @@ object CategoryManga { if (categoryId == DEFAULT_CATEGORY_ID) return transaction { CategoryMangaTable.deleteWhere { (CategoryMangaTable.category eq categoryId) and (CategoryMangaTable.manga eq mangaId) } - if (CategoryMangaTable.select { CategoryMangaTable.manga eq mangaId }.count() == 0L) { - MangaTable.update({ MangaTable.id eq mangaId }) { - it[MangaTable.defaultCategory] = true - } - } } } @@ -93,8 +83,9 @@ object CategoryManga { val query = if (categoryId == DEFAULT_CATEGORY_ID) { MangaTable .leftJoin(ChapterTable, { MangaTable.id }, { ChapterTable.manga }) + .leftJoin(CategoryMangaTable) .slice(columns = selectedColumns) - .select { (MangaTable.inLibrary eq true) and (MangaTable.defaultCategory eq true) } + .select { (MangaTable.inLibrary eq true) and CategoryMangaTable.category.isNull() } } else { MangaTable .innerJoin(CategoryMangaTable) diff --git a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/Library.kt b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/Library.kt index 9da580cc..fc6a68f9 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/Library.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/Library.kt @@ -31,7 +31,6 @@ object Library { MangaTable.update({ MangaTable.id eq manga.id }) { it[inLibrary] = true it[inLibraryAt] = Instant.now().epochSecond - it[defaultCategory] = defaultCategories.isEmpty() && existingCategories.isEmpty() } if (existingCategories.isEmpty()) { diff --git a/server/src/main/kotlin/suwayomi/tachidesk/manga/model/table/CategoryMangaTable.kt b/server/src/main/kotlin/suwayomi/tachidesk/manga/model/table/CategoryMangaTable.kt index ff24d470..5cf445ab 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/manga/model/table/CategoryMangaTable.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/manga/model/table/CategoryMangaTable.kt @@ -8,8 +8,9 @@ package suwayomi.tachidesk.manga.model.table * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ import org.jetbrains.exposed.dao.id.IntIdTable +import org.jetbrains.exposed.sql.ReferenceOption object CategoryMangaTable : IntIdTable() { - val category = reference("category", CategoryTable) - val manga = reference("manga", MangaTable) + val category = reference("category", CategoryTable, ReferenceOption.CASCADE) + val manga = reference("manga", MangaTable, ReferenceOption.CASCADE) } diff --git a/server/src/main/kotlin/suwayomi/tachidesk/manga/model/table/ChapterTable.kt b/server/src/main/kotlin/suwayomi/tachidesk/manga/model/table/ChapterTable.kt index d3b24dba..4de3b082 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/manga/model/table/ChapterTable.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/manga/model/table/ChapterTable.kt @@ -8,6 +8,7 @@ package suwayomi.tachidesk.manga.model.table * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ import org.jetbrains.exposed.dao.id.IntIdTable +import org.jetbrains.exposed.sql.ReferenceOption import org.jetbrains.exposed.sql.ResultRow import org.jetbrains.exposed.sql.select import org.jetbrains.exposed.sql.transactions.transaction @@ -37,7 +38,7 @@ object ChapterTable : IntIdTable() { val pageCount = integer("page_count").default(-1) - val manga = reference("manga", MangaTable) + val manga = reference("manga", MangaTable, ReferenceOption.CASCADE) } fun ChapterTable.toDataClass(chapterEntry: ResultRow) = diff --git a/server/src/main/kotlin/suwayomi/tachidesk/manga/model/table/MangaTable.kt b/server/src/main/kotlin/suwayomi/tachidesk/manga/model/table/MangaTable.kt index ca18cbac..a6349a5c 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/manga/model/table/MangaTable.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/manga/model/table/MangaTable.kt @@ -32,7 +32,6 @@ object MangaTable : IntIdTable() { val thumbnailUrlLastFetched = long("thumbnail_url_last_fetched").default(0) val inLibrary = bool("in_library").default(false) - val defaultCategory = bool("default_category").default(true) val inLibraryAt = long("in_library_at").default(0) // the [source] field name is used by some ancestor of IntIdTable diff --git a/server/src/main/kotlin/suwayomi/tachidesk/manga/model/table/PageTable.kt b/server/src/main/kotlin/suwayomi/tachidesk/manga/model/table/PageTable.kt index db79f7b1..76034343 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/manga/model/table/PageTable.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/manga/model/table/PageTable.kt @@ -8,11 +8,12 @@ package suwayomi.tachidesk.manga.model.table * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ import org.jetbrains.exposed.dao.id.IntIdTable +import org.jetbrains.exposed.sql.ReferenceOption object PageTable : IntIdTable() { val index = integer("index") val url = varchar("url", 2048) val imageUrl = varchar("imageUrl", 2048).nullable() - val chapter = reference("chapter", ChapterTable) + val chapter = reference("chapter", ChapterTable, ReferenceOption.CASCADE) } diff --git a/server/src/main/kotlin/suwayomi/tachidesk/server/database/migration/M0028_AddCascade.kt b/server/src/main/kotlin/suwayomi/tachidesk/server/database/migration/M0028_AddCascade.kt new file mode 100644 index 00000000..f8542981 --- /dev/null +++ b/server/src/main/kotlin/suwayomi/tachidesk/server/database/migration/M0028_AddCascade.kt @@ -0,0 +1,40 @@ +package suwayomi.tachidesk.server.database.migration + +import de.neonew.exposed.migrations.helpers.SQLMigration + +@Suppress("ClassName", "unused") +class M0028_AddCascade : SQLMigration() { + override val sql: String = """ + alter table CATEGORYMANGA + drop constraint FK_CATEGORYMANGA_CATEGORY_ID; + + alter table CATEGORYMANGA + add constraint FK_CATEGORYMANGA_CATEGORY_ID + foreign key (CATEGORY) references CATEGORY + on delete cascade; + + alter table CATEGORYMANGA + drop constraint FK_CATEGORYMANGA_MANGA_ID; + + alter table CATEGORYMANGA + add constraint FK_CATEGORYMANGA_MANGA_ID + foreign key (MANGA) references MANGA + on delete cascade; + + alter table CHAPTER + drop constraint FK_CHAPTER_MANGA_ID; + + alter table CHAPTER + add constraint FK_CHAPTER_MANGA_ID + foreign key (MANGA) references MANGA + on delete cascade; + + alter table PAGE + drop constraint FK_PAGE_CHAPTER_ID; + + alter table PAGE + add constraint FK_PAGE_CHAPTER_ID + foreign key (CHAPTER) references CHAPTER + on delete cascade; + """.trimIndent() +} diff --git a/server/src/main/kotlin/suwayomi/tachidesk/server/database/migration/M0029_DropMangaDefaultCategory.kt b/server/src/main/kotlin/suwayomi/tachidesk/server/database/migration/M0029_DropMangaDefaultCategory.kt new file mode 100644 index 00000000..c7da22aa --- /dev/null +++ b/server/src/main/kotlin/suwayomi/tachidesk/server/database/migration/M0029_DropMangaDefaultCategory.kt @@ -0,0 +1,16 @@ +package suwayomi.tachidesk.server.database.migration + +/* + * Copyright (C) Contributors to the Suwayomi project + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +import de.neonew.exposed.migrations.helpers.DropColumnMigration + +@Suppress("ClassName", "unused") +class M0029_DropMangaDefaultCategory : DropColumnMigration( + "Manga", + "default_category" +) diff --git a/server/src/test/kotlin/suwayomi/tachidesk/manga/controller/UpdateControllerTest.kt b/server/src/test/kotlin/suwayomi/tachidesk/manga/controller/UpdateControllerTest.kt index 6f5ab8d6..9c1a3ad3 100644 --- a/server/src/test/kotlin/suwayomi/tachidesk/manga/controller/UpdateControllerTest.kt +++ b/server/src/test/kotlin/suwayomi/tachidesk/manga/controller/UpdateControllerTest.kt @@ -71,7 +71,6 @@ internal class UpdateControllerTest : ApplicationTest() { it[title] = _title it[url] = _title it[sourceReference] = 1 - it[defaultCategory] = true it[inLibrary] = true }.value } diff --git a/server/src/test/kotlin/suwayomi/tachidesk/test/TestUtils.kt b/server/src/test/kotlin/suwayomi/tachidesk/test/TestUtils.kt index d6b82737..ec912bfa 100644 --- a/server/src/test/kotlin/suwayomi/tachidesk/test/TestUtils.kt +++ b/server/src/test/kotlin/suwayomi/tachidesk/test/TestUtils.kt @@ -38,7 +38,6 @@ fun createLibraryManga( it[title] = _title it[url] = _title it[sourceReference] = 1 - it[defaultCategory] = true it[inLibrary] = true }.value }