Skip to content

Commit 19aa15b

Browse files
committed
tmp
1 parent 4d89c32 commit 19aa15b

File tree

3 files changed

+233
-34
lines changed

3 files changed

+233
-34
lines changed

server/src/main/kotlin/suwayomi/tachidesk/graphql/dataLoaders/ChapterDataLoader.kt

Lines changed: 144 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,28 @@
88
package suwayomi.tachidesk.graphql.dataLoaders
99

1010
import com.expediagroup.graphql.dataloader.KotlinDataLoader
11+
import mu.KotlinLogging
12+
import org.dataloader.CacheKey
1113
import org.dataloader.DataLoader
1214
import org.dataloader.DataLoaderFactory
15+
import org.dataloader.DataLoaderOptions
1316
import org.jetbrains.exposed.sql.Slf4jSqlDebugLogger
17+
import org.jetbrains.exposed.sql.SortOrder
1418
import org.jetbrains.exposed.sql.addLogger
19+
import org.jetbrains.exposed.sql.andWhere
20+
import org.jetbrains.exposed.sql.orWhere
1521
import org.jetbrains.exposed.sql.select
22+
import org.jetbrains.exposed.sql.selectAll
1623
import org.jetbrains.exposed.sql.transactions.transaction
24+
import suwayomi.tachidesk.graphql.queries.ChapterQuery.BaseChapterCondition
25+
import suwayomi.tachidesk.graphql.queries.ChapterQuery.ChapterFilter
26+
import suwayomi.tachidesk.graphql.queries.ChapterQuery.ChapterOrderBy
27+
import suwayomi.tachidesk.graphql.queries.filter.applyOps
28+
import suwayomi.tachidesk.graphql.server.primitives.Cursor
29+
import suwayomi.tachidesk.graphql.server.primitives.PageInfo
30+
import suwayomi.tachidesk.graphql.server.primitives.QueryResults
31+
import suwayomi.tachidesk.graphql.server.primitives.maybeSwap
1732
import suwayomi.tachidesk.graphql.types.ChapterNodeList
18-
import suwayomi.tachidesk.graphql.types.ChapterNodeList.Companion.toNodeList
1933
import suwayomi.tachidesk.graphql.types.ChapterType
2034
import suwayomi.tachidesk.manga.model.table.ChapterTable
2135
import suwayomi.tachidesk.server.JavalinSetup.future
@@ -35,17 +49,136 @@ class ChapterDataLoader : KotlinDataLoader<Int, ChapterType?> {
3549
}
3650
}
3751

52+
data class ChaptersContext<Condition : BaseChapterCondition>(
53+
val condition: Condition? = null,
54+
val filter: ChapterFilter? = null,
55+
val orderBy: ChapterOrderBy? = null,
56+
val orderByType: SortOrder? = null,
57+
val before: Cursor? = null,
58+
val after: Cursor? = null,
59+
val first: Int? = null,
60+
val last: Int? = null,
61+
val offset: Int? = null
62+
)
63+
64+
/**
65+
* This data loader requires a context to be passed, if it is missing a NullPointerException will be thrown
66+
*/
3867
class ChaptersForMangaDataLoader : KotlinDataLoader<Int, ChapterNodeList> {
3968
override val dataLoaderName = "ChaptersForMangaDataLoader"
40-
override fun getDataLoader(): DataLoader<Int, ChapterNodeList> = DataLoaderFactory.newDataLoader<Int, ChapterNodeList> { ids ->
41-
future {
42-
transaction {
43-
addLogger(Slf4jSqlDebugLogger)
44-
val chaptersByMangaId = ChapterTable.select { ChapterTable.manga inList ids }
45-
.map { ChapterType(it) }
46-
.groupBy { it.mangaId }
47-
ids.map { (chaptersByMangaId[it] ?: emptyList()).toNodeList() }
69+
override fun getDataLoader(): DataLoader<Int, ChapterNodeList> = DataLoaderFactory.newDataLoader<Int, ChapterNodeList> (
70+
{ ids, env ->
71+
future {
72+
transaction {
73+
addLogger(Slf4jSqlDebugLogger)
74+
75+
// it's not possible to select the chapters in one query, since there can be multiple different filter/condition for the chapters of manga due to batching
76+
// val baseQuery = ChapterTable.selectAll()
77+
//
78+
// // filter chapters for each manga
79+
// ids.mapIndexed { index, id ->
80+
// val (condition, filter) = env.keyContextsList[index] as ChaptersContext<*>
81+
// baseQuery.orWhere { ChapterTable.manga eq id }.applyOps(condition, filter)
82+
// }
83+
//
84+
// val chapters = baseQuery
85+
// .map { ChapterType(it) }
86+
// .associateBy { it.id }
87+
//
88+
// val chapterToMangaMap = ids.map { chapters[it] }
89+
90+
val result = ids.mapIndexed { index, mangaId ->
91+
val (condition, filter, orderBy, orderByType, before, after, first, last, offset) = env.keyContextsList[index]!! as ChaptersContext<*>
92+
93+
val res = ChapterTable.select { (ChapterTable.manga eq mangaId) }
94+
res.applyOps(condition, filter)
95+
96+
if (orderBy != null || (last != null || before != null)) {
97+
val orderByColumn = orderBy?.column ?: ChapterTable.id
98+
val orderType = orderByType.maybeSwap(last ?: before)
99+
100+
if (orderBy == ChapterOrderBy.ID || orderBy == null) {
101+
res.orderBy(orderByColumn to orderType)
102+
} else {
103+
res.orderBy(
104+
orderByColumn to orderType,
105+
ChapterTable.id to SortOrder.ASC
106+
)
107+
}
108+
}
109+
110+
val total = res.count()
111+
val firstResult = res.firstOrNull()?.get(ChapterTable.id)?.value
112+
val lastResult = res.lastOrNull()?.get(ChapterTable.id)?.value
113+
114+
if (after != null) {
115+
res.andWhere {
116+
(orderBy ?: ChapterOrderBy.ID).greater(after)
117+
}
118+
} else if (before != null) {
119+
res.andWhere {
120+
(orderBy ?: ChapterOrderBy.ID).less(before)
121+
}
122+
}
123+
124+
if (first != null) {
125+
res.limit(first, offset?.toLong() ?: 0)
126+
} else if (last != null) {
127+
res.limit(last)
128+
}
129+
130+
val queryResults = QueryResults(total, firstResult, lastResult, res.toList())
131+
132+
val getAsCursor: (ChapterType) -> Cursor = (orderBy ?: ChapterOrderBy.ID)::asCursor
133+
134+
val resultsAsType = queryResults.results.map { ChapterType(it) }
135+
136+
val result = ChapterNodeList(
137+
resultsAsType,
138+
if (resultsAsType.isEmpty()) {
139+
emptyList()
140+
} else {
141+
listOfNotNull(
142+
resultsAsType.firstOrNull()?.let {
143+
ChapterNodeList.ChapterEdge(
144+
getAsCursor(it),
145+
it
146+
)
147+
},
148+
resultsAsType.lastOrNull()?.let {
149+
ChapterNodeList.ChapterEdge(
150+
getAsCursor(it),
151+
it
152+
)
153+
}
154+
)
155+
},
156+
pageInfo = PageInfo(
157+
hasNextPage = queryResults.lastKey != resultsAsType.lastOrNull()?.id,
158+
hasPreviousPage = queryResults.firstKey != resultsAsType.firstOrNull()?.id,
159+
startCursor = resultsAsType.firstOrNull()?.let { getAsCursor(it) },
160+
endCursor = resultsAsType.lastOrNull()?.let { getAsCursor(it) }
161+
),
162+
totalCount = queryResults.total.toInt()
163+
)
164+
165+
result
166+
}
167+
168+
result
169+
}
48170
}
49-
}
50-
}
171+
},
172+
DataLoaderOptions.newOptions().setCacheKeyFunction(
173+
object : CacheKey<Int> {
174+
override fun getKey(input: Int): String {
175+
return input.toString()
176+
}
177+
178+
override fun getKeyWithContext(input: Int, context: Any): String {
179+
return "${input}_$context"
180+
}
181+
}
182+
)
183+
)
51184
}

server/src/main/kotlin/suwayomi/tachidesk/graphql/queries/ChapterQuery.kt

Lines changed: 66 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import graphql.schema.DataFetchingEnvironment
1212
import org.jetbrains.exposed.sql.Column
1313
import org.jetbrains.exposed.sql.Op
1414
import org.jetbrains.exposed.sql.SortOrder
15+
import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq
1516
import org.jetbrains.exposed.sql.SqlExpressionBuilder.greater
1617
import org.jetbrains.exposed.sql.SqlExpressionBuilder.less
1718
import org.jetbrains.exposed.sql.andWhere
@@ -99,33 +100,30 @@ class ChapterQuery {
99100
}
100101
}
101102

102-
data class ChapterCondition(
103-
val id: Int? = null,
104-
val url: String? = null,
105-
val name: String? = null,
106-
val uploadDate: Long? = null,
107-
val chapterNumber: Float? = null,
108-
val scanlator: String? = null,
109-
val mangaId: Int? = null,
110-
val isRead: Boolean? = null,
111-
val isBookmarked: Boolean? = null,
112-
val lastPageRead: Int? = null,
113-
val lastReadAt: Long? = null,
114-
val sourceOrder: Int? = null,
115-
val realUrl: String? = null,
116-
val fetchedAt: Long? = null,
117-
val isDownloaded: Boolean? = null,
118-
val pageCount: Int? = null
119-
) : HasGetOp {
120-
override fun getOp(): Op<Boolean>? {
103+
abstract class BaseChapterCondition : HasGetOp {
104+
abstract val id: Int?
105+
abstract val url: String?
106+
abstract val name: String?
107+
abstract val uploadDate: Long?
108+
abstract val chapterNumber: Float?
109+
abstract val scanlator: String?
110+
abstract val isRead: Boolean?
111+
abstract val isBookmarked: Boolean?
112+
abstract val lastPageRead: Int?
113+
abstract val lastReadAt: Long?
114+
abstract val sourceOrder: Int?
115+
abstract val realUrl: String?
116+
abstract val fetchedAt: Long?
117+
abstract val isDownloaded: Boolean?
118+
abstract val pageCount: Int?
119+
open fun buildOp(): OpAnd {
121120
val opAnd = OpAnd()
122121
opAnd.eq(id, ChapterTable.id)
123122
opAnd.eq(url, ChapterTable.url)
124123
opAnd.eq(name, ChapterTable.name)
125124
opAnd.eq(uploadDate, ChapterTable.date_upload)
126125
opAnd.eq(chapterNumber, ChapterTable.chapter_number)
127126
opAnd.eq(scanlator, ChapterTable.scanlator)
128-
opAnd.eq(mangaId, ChapterTable.manga)
129127
opAnd.eq(isRead, ChapterTable.isRead)
130128
opAnd.eq(isBookmarked, ChapterTable.isBookmarked)
131129
opAnd.eq(lastPageRead, ChapterTable.lastPageRead)
@@ -136,7 +134,54 @@ class ChapterQuery {
136134
opAnd.eq(isDownloaded, ChapterTable.isDownloaded)
137135
opAnd.eq(pageCount, ChapterTable.pageCount)
138136

139-
return opAnd.op
137+
return opAnd
138+
}
139+
140+
override fun getOp(): Op<Boolean>? {
141+
return buildOp().op
142+
}
143+
}
144+
145+
data class MangaChapterCondition(
146+
override val id: Int? = null,
147+
override val url: String? = null,
148+
override val name: String? = null,
149+
override val uploadDate: Long? = null,
150+
override val chapterNumber: Float? = null,
151+
override val scanlator: String? = null,
152+
override val isRead: Boolean? = null,
153+
override val isBookmarked: Boolean? = null,
154+
override val lastPageRead: Int? = null,
155+
override val lastReadAt: Long? = null,
156+
override val sourceOrder: Int? = null,
157+
override val realUrl: String? = null,
158+
override val fetchedAt: Long? = null,
159+
override val isDownloaded: Boolean? = null,
160+
override val pageCount: Int? = null
161+
) : BaseChapterCondition(), HasGetOp
162+
163+
data class ChapterCondition(
164+
override val id: Int? = null,
165+
override val url: String? = null,
166+
override val name: String? = null,
167+
override val uploadDate: Long? = null,
168+
override val chapterNumber: Float? = null,
169+
override val scanlator: String? = null,
170+
override val isRead: Boolean? = null,
171+
override val isBookmarked: Boolean? = null,
172+
override val lastPageRead: Int? = null,
173+
override val lastReadAt: Long? = null,
174+
override val sourceOrder: Int? = null,
175+
override val realUrl: String? = null,
176+
override val fetchedAt: Long? = null,
177+
override val isDownloaded: Boolean? = null,
178+
override val pageCount: Int? = null,
179+
val mangaId: Int? = null
180+
) : BaseChapterCondition(), HasGetOp {
181+
override fun buildOp(): OpAnd {
182+
val opAnd = super.buildOp()
183+
opAnd.eq(mangaId, ChapterTable.manga)
184+
return opAnd
140185
}
141186
}
142187

server/src/main/kotlin/suwayomi/tachidesk/graphql/types/MangaType.kt

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,13 @@ package suwayomi.tachidesk.graphql.types
99

1010
import com.expediagroup.graphql.server.extensions.getValueFromDataLoader
1111
import graphql.schema.DataFetchingEnvironment
12+
import mu.KotlinLogging
1213
import org.jetbrains.exposed.sql.ResultRow
14+
import org.jetbrains.exposed.sql.SortOrder
15+
import suwayomi.tachidesk.graphql.dataLoaders.ChaptersContext
16+
import suwayomi.tachidesk.graphql.queries.ChapterQuery.ChapterFilter
17+
import suwayomi.tachidesk.graphql.queries.ChapterQuery.ChapterOrderBy
18+
import suwayomi.tachidesk.graphql.queries.ChapterQuery.MangaChapterCondition
1319
import suwayomi.tachidesk.graphql.server.primitives.Cursor
1420
import suwayomi.tachidesk.graphql.server.primitives.Edge
1521
import suwayomi.tachidesk.graphql.server.primitives.Node
@@ -79,8 +85,23 @@ class MangaType(
7985
dataClass.chaptersLastFetchedAt
8086
)
8187

82-
fun chapters(dataFetchingEnvironment: DataFetchingEnvironment): CompletableFuture<ChapterNodeList> {
83-
return dataFetchingEnvironment.getValueFromDataLoader<Int, ChapterNodeList>("ChaptersForMangaDataLoader", id)
88+
fun chapters(
89+
dataFetchingEnvironment: DataFetchingEnvironment,
90+
condition: MangaChapterCondition? = null,
91+
filter: ChapterFilter? = null,
92+
orderBy: ChapterOrderBy? = null,
93+
orderByType: SortOrder? = null,
94+
before: Cursor? = null,
95+
after: Cursor? = null,
96+
first: Int? = null,
97+
last: Int? = null,
98+
offset: Int? = null
99+
): CompletableFuture<ChapterNodeList> {
100+
val context = ChaptersContext(condition, filter, orderBy, orderByType, before, after, first, last, offset)
101+
KotlinLogging.logger { }.info { "@Daniel $context" }
102+
val dataLoader = dataFetchingEnvironment.getDataLoader<Int, ChapterNodeList>("ChaptersForMangaDataLoader")
103+
return dataLoader.load(id, context)
104+
// return dataFetchingEnvironment.getValueFromDataLoader<Int, ChapterNodeList>("ChaptersForMangaDataLoader", id)
84105
}
85106

86107
fun age(): Long? {

0 commit comments

Comments
 (0)