mirror of
https://github.com/Suwayomi/Suwayomi-Server.git
synced 2026-06-30 09:24:34 -05:00
fix(opds): resolve sql group by syntax error when filtering library (#2118)
This commit is contained in:
@@ -11,6 +11,7 @@ import org.jetbrains.exposed.v1.core.and
|
|||||||
import org.jetbrains.exposed.v1.core.eq
|
import org.jetbrains.exposed.v1.core.eq
|
||||||
import org.jetbrains.exposed.v1.core.greater
|
import org.jetbrains.exposed.v1.core.greater
|
||||||
import org.jetbrains.exposed.v1.core.inList
|
import org.jetbrains.exposed.v1.core.inList
|
||||||
|
import org.jetbrains.exposed.v1.core.inSubQuery
|
||||||
import org.jetbrains.exposed.v1.core.intLiteral
|
import org.jetbrains.exposed.v1.core.intLiteral
|
||||||
import org.jetbrains.exposed.v1.core.like
|
import org.jetbrains.exposed.v1.core.like
|
||||||
import org.jetbrains.exposed.v1.core.lowerCase
|
import org.jetbrains.exposed.v1.core.lowerCase
|
||||||
@@ -75,13 +76,28 @@ fun Query.applyOpdsMangaFilter(
|
|||||||
}
|
}
|
||||||
if (excludeField != "filter") {
|
if (excludeField != "filter") {
|
||||||
criteria.filter?.let { filterVal ->
|
criteria.filter?.let { filterVal ->
|
||||||
val unreadCountExpr = Case().When(ChapterTable.isRead eq false, intLiteral(1)).Else(intLiteral(0)).sum()
|
|
||||||
val downloadedCountExpr = Case().When(ChapterTable.isDownloaded eq true, intLiteral(1)).Else(intLiteral(0)).sum()
|
|
||||||
when (filterVal) {
|
when (filterVal) {
|
||||||
"unread" -> having { unreadCountExpr greater 0 }
|
"unread" -> {
|
||||||
"downloaded" -> having { downloadedCountExpr greater 0 }
|
andWhere {
|
||||||
"ongoing" -> andWhere { MangaTable.status eq MangaStatus.ONGOING.value }
|
MangaTable.id inSubQuery
|
||||||
"completed" -> andWhere { MangaTable.status eq MangaStatus.COMPLETED.value }
|
ChapterTable.select(ChapterTable.manga).where { ChapterTable.isRead eq false }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
"downloaded" -> {
|
||||||
|
andWhere {
|
||||||
|
MangaTable.id inSubQuery
|
||||||
|
ChapterTable.select(ChapterTable.manga).where { ChapterTable.isDownloaded eq true }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
"ongoing" -> {
|
||||||
|
andWhere { MangaTable.status eq MangaStatus.ONGOING.value }
|
||||||
|
}
|
||||||
|
|
||||||
|
"completed" -> {
|
||||||
|
andWhere { MangaTable.status eq MangaStatus.COMPLETED.value }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -133,11 +149,17 @@ object MangaRepository {
|
|||||||
val unreadCount = unreadCountExpr.alias("unread_count")
|
val unreadCount = unreadCountExpr.alias("unread_count")
|
||||||
|
|
||||||
// Base query with necessary joins for filtering and sorting
|
// Base query with necessary joins for filtering and sorting
|
||||||
val query =
|
var baseJoin =
|
||||||
MangaTable
|
MangaTable
|
||||||
.join(SourceTable, JoinType.INNER, MangaTable.sourceReference, SourceTable.id)
|
.join(SourceTable, JoinType.INNER, MangaTable.sourceReference, SourceTable.id)
|
||||||
.join(ChapterTable, JoinType.LEFT, MangaTable.id, ChapterTable.manga)
|
.join(ChapterTable, JoinType.LEFT, MangaTable.id, ChapterTable.manga)
|
||||||
.join(CategoryMangaTable, JoinType.LEFT, MangaTable.id, CategoryMangaTable.manga)
|
|
||||||
|
if (criteria.categoryId != null) {
|
||||||
|
baseJoin = baseJoin.join(CategoryMangaTable, JoinType.LEFT, MangaTable.id, CategoryMangaTable.manga)
|
||||||
|
}
|
||||||
|
|
||||||
|
val query =
|
||||||
|
baseJoin
|
||||||
.select(MangaTable.columns + SourceTable.lang + SourceTable.name + unreadCount)
|
.select(MangaTable.columns + SourceTable.lang + SourceTable.name + unreadCount)
|
||||||
.where { MangaTable.inLibrary eq true }
|
.where { MangaTable.inLibrary eq true }
|
||||||
|
|
||||||
@@ -301,7 +323,6 @@ object MangaRepository {
|
|||||||
* Applies sorting and filtering logic to a manga library query.
|
* Applies sorting and filtering logic to a manga library query.
|
||||||
* @param query The Exposed SQL query to modify.
|
* @param query The Exposed SQL query to modify.
|
||||||
* @param sort The sorting parameter.
|
* @param sort The sorting parameter.
|
||||||
* @param filter The filtering parameter.
|
|
||||||
*/
|
*/
|
||||||
private fun applyMangaLibrarySort(
|
private fun applyMangaLibrarySort(
|
||||||
query: Query,
|
query: Query,
|
||||||
@@ -330,36 +351,38 @@ object MangaRepository {
|
|||||||
*/
|
*/
|
||||||
fun getLibraryFilterCounts(activeFilters: OpdsMangaFilter): Map<String, Long> =
|
fun getLibraryFilterCounts(activeFilters: OpdsMangaFilter): Map<String, Long> =
|
||||||
transaction {
|
transaction {
|
||||||
val unreadCountExpr = Case().When(ChapterTable.isRead eq false, intLiteral(1)).Else(intLiteral(0)).sum()
|
var baseJoin =
|
||||||
val downloadedCountExpr = Case().When(ChapterTable.isDownloaded eq true, intLiteral(1)).Else(intLiteral(0)).sum()
|
MangaTable
|
||||||
|
.join(SourceTable, JoinType.INNER, MangaTable.sourceReference, SourceTable.id)
|
||||||
|
|
||||||
|
if (activeFilters.categoryId != null) {
|
||||||
|
baseJoin = baseJoin.join(CategoryMangaTable, JoinType.LEFT, MangaTable.id, CategoryMangaTable.manga)
|
||||||
|
}
|
||||||
|
|
||||||
val baseQuery =
|
val baseQuery =
|
||||||
MangaTable
|
baseJoin
|
||||||
.join(ChapterTable, JoinType.LEFT, MangaTable.id, ChapterTable.manga)
|
|
||||||
.join(SourceTable, JoinType.INNER, MangaTable.sourceReference, SourceTable.id)
|
|
||||||
.join(CategoryMangaTable, JoinType.LEFT, MangaTable.id, CategoryMangaTable.manga)
|
|
||||||
.select(MangaTable.id)
|
.select(MangaTable.id)
|
||||||
.where { MangaTable.inLibrary eq true }
|
.where { MangaTable.inLibrary eq true }
|
||||||
|
.withDistinct()
|
||||||
|
|
||||||
baseQuery.applyOpdsMangaFilter(activeFilters, excludeField = "filter")
|
baseQuery.applyOpdsMangaFilter(activeFilters, excludeField = "filter")
|
||||||
baseQuery.groupBy(MangaTable.id)
|
|
||||||
|
|
||||||
val unreadCount = baseQuery.copy().having { unreadCountExpr greater 0 }.count()
|
val unreadCount =
|
||||||
val downloadedCount = baseQuery.copy().having { downloadedCountExpr greater 0 }.count()
|
baseQuery
|
||||||
|
.copy()
|
||||||
val statusBaseQuery =
|
.andWhere {
|
||||||
MangaTable
|
MangaTable.id inSubQuery
|
||||||
.join(SourceTable, JoinType.INNER, MangaTable.sourceReference, SourceTable.id)
|
ChapterTable.select(ChapterTable.manga).where { ChapterTable.isRead eq false }
|
||||||
.join(CategoryMangaTable, JoinType.LEFT, MangaTable.id, CategoryMangaTable.manga)
|
}.count()
|
||||||
.join(ChapterTable, JoinType.LEFT, MangaTable.id, ChapterTable.manga)
|
val downloadedCount =
|
||||||
.select(MangaTable.id)
|
baseQuery
|
||||||
.where { MangaTable.inLibrary eq true }
|
.copy()
|
||||||
|
.andWhere {
|
||||||
statusBaseQuery.applyOpdsMangaFilter(activeFilters, excludeField = "filter")
|
MangaTable.id inSubQuery
|
||||||
statusBaseQuery.groupBy(MangaTable.id)
|
ChapterTable.select(ChapterTable.manga).where { ChapterTable.isDownloaded eq true }
|
||||||
|
}.count()
|
||||||
val ongoingCount = statusBaseQuery.copy().andWhere { MangaTable.status eq MangaStatus.ONGOING.value }.count()
|
val ongoingCount = baseQuery.copy().andWhere { MangaTable.status eq MangaStatus.ONGOING.value }.count()
|
||||||
val completedCount = statusBaseQuery.copy().andWhere { MangaTable.status eq MangaStatus.COMPLETED.value }.count()
|
val completedCount = baseQuery.copy().andWhere { MangaTable.status eq MangaStatus.COMPLETED.value }.count()
|
||||||
|
|
||||||
mapOf(
|
mapOf(
|
||||||
"unread" to unreadCount,
|
"unread" to unreadCount,
|
||||||
|
|||||||
@@ -13,7 +13,6 @@ import suwayomi.tachidesk.i18n.MR
|
|||||||
import suwayomi.tachidesk.manga.impl.extension.Extension
|
import suwayomi.tachidesk.manga.impl.extension.Extension
|
||||||
import suwayomi.tachidesk.manga.model.table.CategoryMangaTable
|
import suwayomi.tachidesk.manga.model.table.CategoryMangaTable
|
||||||
import suwayomi.tachidesk.manga.model.table.CategoryTable
|
import suwayomi.tachidesk.manga.model.table.CategoryTable
|
||||||
import suwayomi.tachidesk.manga.model.table.ChapterTable
|
|
||||||
import suwayomi.tachidesk.manga.model.table.ExtensionTable
|
import suwayomi.tachidesk.manga.model.table.ExtensionTable
|
||||||
import suwayomi.tachidesk.manga.model.table.MangaStatus
|
import suwayomi.tachidesk.manga.model.table.MangaStatus
|
||||||
import suwayomi.tachidesk.manga.model.table.MangaTable
|
import suwayomi.tachidesk.manga.model.table.MangaTable
|
||||||
@@ -167,12 +166,17 @@ object NavigationRepository {
|
|||||||
transaction {
|
transaction {
|
||||||
val mangaCount = MangaTable.id.countDistinct().alias("manga_count")
|
val mangaCount = MangaTable.id.countDistinct().alias("manga_count")
|
||||||
|
|
||||||
val query =
|
var baseJoin =
|
||||||
SourceTable
|
SourceTable
|
||||||
.join(MangaTable, JoinType.INNER, SourceTable.id, MangaTable.sourceReference)
|
.join(MangaTable, JoinType.INNER, SourceTable.id, MangaTable.sourceReference)
|
||||||
.join(ExtensionTable, JoinType.LEFT, onColumn = SourceTable.extension, otherColumn = ExtensionTable.id)
|
.join(ExtensionTable, JoinType.LEFT, onColumn = SourceTable.extension, otherColumn = ExtensionTable.id)
|
||||||
.join(CategoryMangaTable, JoinType.LEFT, MangaTable.id, CategoryMangaTable.manga)
|
|
||||||
.join(ChapterTable, JoinType.LEFT, MangaTable.id, ChapterTable.manga)
|
if (activeFilters.categoryId != null) {
|
||||||
|
baseJoin = baseJoin.join(CategoryMangaTable, JoinType.LEFT, MangaTable.id, CategoryMangaTable.manga)
|
||||||
|
}
|
||||||
|
|
||||||
|
val query =
|
||||||
|
baseJoin
|
||||||
.select(SourceTable.id, SourceTable.name, SourceTable.lang, ExtensionTable.apkName, mangaCount)
|
.select(SourceTable.id, SourceTable.name, SourceTable.lang, ExtensionTable.apkName, mangaCount)
|
||||||
.where { MangaTable.inLibrary eq true }
|
.where { MangaTable.inLibrary eq true }
|
||||||
|
|
||||||
@@ -228,7 +232,6 @@ object NavigationRepository {
|
|||||||
.join(CategoryMangaTable, JoinType.INNER, CategoryTable.id, CategoryMangaTable.category)
|
.join(CategoryMangaTable, JoinType.INNER, CategoryTable.id, CategoryMangaTable.category)
|
||||||
.join(MangaTable, JoinType.INNER, CategoryMangaTable.manga, MangaTable.id)
|
.join(MangaTable, JoinType.INNER, CategoryMangaTable.manga, MangaTable.id)
|
||||||
.join(SourceTable, JoinType.INNER, MangaTable.sourceReference, SourceTable.id)
|
.join(SourceTable, JoinType.INNER, MangaTable.sourceReference, SourceTable.id)
|
||||||
.join(ChapterTable, JoinType.LEFT, MangaTable.id, ChapterTable.manga)
|
|
||||||
.select(CategoryTable.id, CategoryTable.name, mangaCount)
|
.select(CategoryTable.id, CategoryTable.name, mangaCount)
|
||||||
.where { MangaTable.inLibrary eq true }
|
.where { MangaTable.inLibrary eq true }
|
||||||
|
|
||||||
@@ -263,11 +266,15 @@ object NavigationRepository {
|
|||||||
activeFilters: OpdsMangaFilter = OpdsMangaFilter(),
|
activeFilters: OpdsMangaFilter = OpdsMangaFilter(),
|
||||||
): Pair<List<OpdsGenreNavEntry>, Long> =
|
): Pair<List<OpdsGenreNavEntry>, Long> =
|
||||||
transaction {
|
transaction {
|
||||||
val query =
|
var baseJoin =
|
||||||
MangaTable
|
MangaTable
|
||||||
.join(SourceTable, JoinType.INNER, MangaTable.sourceReference, SourceTable.id)
|
.join(SourceTable, JoinType.INNER, MangaTable.sourceReference, SourceTable.id)
|
||||||
.join(CategoryMangaTable, JoinType.LEFT, MangaTable.id, CategoryMangaTable.manga)
|
if (activeFilters.categoryId != null) {
|
||||||
.join(ChapterTable, JoinType.LEFT, MangaTable.id, ChapterTable.manga)
|
baseJoin = baseJoin.join(CategoryMangaTable, JoinType.LEFT, MangaTable.id, CategoryMangaTable.manga)
|
||||||
|
}
|
||||||
|
|
||||||
|
val query =
|
||||||
|
baseJoin
|
||||||
.select(MangaTable.genre)
|
.select(MangaTable.genre)
|
||||||
.where { MangaTable.inLibrary eq true }
|
.where { MangaTable.inLibrary eq true }
|
||||||
|
|
||||||
@@ -322,11 +329,16 @@ object NavigationRepository {
|
|||||||
val statusCounts =
|
val statusCounts =
|
||||||
transaction {
|
transaction {
|
||||||
val countExpr = MangaTable.id.countDistinct().alias("manga_count")
|
val countExpr = MangaTable.id.countDistinct().alias("manga_count")
|
||||||
val query =
|
|
||||||
|
var baseJoin =
|
||||||
MangaTable
|
MangaTable
|
||||||
.join(SourceTable, JoinType.INNER, MangaTable.sourceReference, SourceTable.id)
|
.join(SourceTable, JoinType.INNER, MangaTable.sourceReference, SourceTable.id)
|
||||||
.join(CategoryMangaTable, JoinType.LEFT, MangaTable.id, CategoryMangaTable.manga)
|
if (activeFilters.categoryId != null) {
|
||||||
.join(ChapterTable, JoinType.LEFT, MangaTable.id, ChapterTable.manga)
|
baseJoin = baseJoin.join(CategoryMangaTable, JoinType.LEFT, MangaTable.id, CategoryMangaTable.manga)
|
||||||
|
}
|
||||||
|
|
||||||
|
val query =
|
||||||
|
baseJoin
|
||||||
.select(MangaTable.status, countExpr)
|
.select(MangaTable.status, countExpr)
|
||||||
.where { MangaTable.inLibrary eq true }
|
.where { MangaTable.inLibrary eq true }
|
||||||
|
|
||||||
@@ -369,11 +381,16 @@ object NavigationRepository {
|
|||||||
): Pair<List<OpdsLanguageNavEntry>, Long> =
|
): Pair<List<OpdsLanguageNavEntry>, Long> =
|
||||||
transaction {
|
transaction {
|
||||||
val mangaCount = MangaTable.id.countDistinct().alias("manga_count")
|
val mangaCount = MangaTable.id.countDistinct().alias("manga_count")
|
||||||
val query =
|
|
||||||
|
var baseJoin =
|
||||||
SourceTable
|
SourceTable
|
||||||
.join(MangaTable, JoinType.INNER, SourceTable.id, MangaTable.sourceReference)
|
.join(MangaTable, JoinType.INNER, SourceTable.id, MangaTable.sourceReference)
|
||||||
.join(CategoryMangaTable, JoinType.LEFT, MangaTable.id, CategoryMangaTable.manga)
|
if (activeFilters.categoryId != null) {
|
||||||
.join(ChapterTable, JoinType.LEFT, MangaTable.id, ChapterTable.manga)
|
baseJoin = baseJoin.join(CategoryMangaTable, JoinType.LEFT, MangaTable.id, CategoryMangaTable.manga)
|
||||||
|
}
|
||||||
|
|
||||||
|
val query =
|
||||||
|
baseJoin
|
||||||
.select(SourceTable.lang, mangaCount)
|
.select(SourceTable.lang, mangaCount)
|
||||||
.where { MangaTable.inLibrary eq true }
|
.where { MangaTable.inLibrary eq true }
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user