From a90e5d13ea71310db9b2eb76f21aeecf9188a5b3 Mon Sep 17 00:00:00 2001 From: Syer10 Date: Sat, 8 Apr 2023 15:47:10 -0400 Subject: [PATCH] Complete MetaQuery --- .../tachidesk/graphql/queries/MetaQuery.kt | 162 +++++++++++++++++- .../graphql/server/primitives/OrderBy.kt | 30 +++- .../tachidesk/graphql/types/MangaType.kt | 4 +- 3 files changed, 186 insertions(+), 10 deletions(-) diff --git a/server/src/main/kotlin/suwayomi/tachidesk/graphql/queries/MetaQuery.kt b/server/src/main/kotlin/suwayomi/tachidesk/graphql/queries/MetaQuery.kt index 53170fa2..c760c9e0 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/graphql/queries/MetaQuery.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/graphql/queries/MetaQuery.kt @@ -9,13 +9,31 @@ package suwayomi.tachidesk.graphql.queries import com.expediagroup.graphql.server.extensions.getValueFromDataLoader import graphql.schema.DataFetchingEnvironment +import org.jetbrains.exposed.sql.Column +import org.jetbrains.exposed.sql.Op +import org.jetbrains.exposed.sql.SortOrder +import org.jetbrains.exposed.sql.SqlExpressionBuilder.greater +import org.jetbrains.exposed.sql.SqlExpressionBuilder.less +import org.jetbrains.exposed.sql.andWhere import org.jetbrains.exposed.sql.selectAll import org.jetbrains.exposed.sql.transactions.transaction import suwayomi.tachidesk.global.model.table.GlobalMetaTable +import suwayomi.tachidesk.graphql.queries.filter.Filter +import suwayomi.tachidesk.graphql.queries.filter.HasGetOp +import suwayomi.tachidesk.graphql.queries.filter.OpAnd +import suwayomi.tachidesk.graphql.queries.filter.StringFilter +import suwayomi.tachidesk.graphql.queries.filter.andFilterWithCompareString +import suwayomi.tachidesk.graphql.queries.filter.applyOps +import suwayomi.tachidesk.graphql.server.primitives.Cursor +import suwayomi.tachidesk.graphql.server.primitives.OrderBy +import suwayomi.tachidesk.graphql.server.primitives.PageInfo +import suwayomi.tachidesk.graphql.server.primitives.QueryResults +import suwayomi.tachidesk.graphql.server.primitives.greaterNotUnique +import suwayomi.tachidesk.graphql.server.primitives.lessNotUnique +import suwayomi.tachidesk.graphql.server.primitives.maybeSwap import suwayomi.tachidesk.graphql.types.GlobalMetaItem import suwayomi.tachidesk.graphql.types.MetaItem import suwayomi.tachidesk.graphql.types.MetaNodeList -import suwayomi.tachidesk.graphql.types.MetaNodeList.Companion.toNodeList import java.util.concurrent.CompletableFuture /** @@ -32,11 +50,145 @@ class MetaQuery { return dataFetchingEnvironment.getValueFromDataLoader("GlobalMetaDataLoader", key) } - fun metas(): MetaNodeList { - val results = transaction { - GlobalMetaTable.selectAll().toList() + enum class MetaOrderBy(override val column: Column>) : OrderBy { + KEY(GlobalMetaTable.key), + VALUE(GlobalMetaTable.value); + + override fun greater(cursor: Cursor): Op { + return when (this) { + KEY -> GlobalMetaTable.key greater cursor.value + VALUE -> greaterNotUnique(GlobalMetaTable.value, GlobalMetaTable.key, cursor, String::toString) + } } - return results.map { GlobalMetaItem(it) }.toNodeList() + override fun less(cursor: Cursor): Op { + return when (this) { + KEY -> GlobalMetaTable.key less cursor.value + VALUE -> lessNotUnique(GlobalMetaTable.value, GlobalMetaTable.key, cursor, String::toString) + } + } + + override fun asCursor(type: MetaItem): Cursor { + val value = when (this) { + KEY -> type.key + VALUE -> type.key + "-" + type.value + } + return Cursor(value) + } + } + + data class MetaCondition( + val key: String? = null, + val value: String? = null + ) : HasGetOp { + override fun getOp(): Op? { + val opAnd = OpAnd() + opAnd.eq(key, GlobalMetaTable.key) + opAnd.eq(value, GlobalMetaTable.value) + + return opAnd.op + } + } + + data class MetaFilter( + val key: StringFilter? = null, + val value: StringFilter? = null, + override val and: List? = null, + override val or: List? = null, + override val not: MetaFilter? = null + ) : Filter { + override fun getOpList(): List> { + return listOfNotNull( + andFilterWithCompareString(GlobalMetaTable.key, key), + andFilterWithCompareString(GlobalMetaTable.value, value) + ) + } + } + + fun metas( + condition: MetaCondition? = null, + filter: MetaFilter? = null, + orderBy: MetaOrderBy? = null, + orderByType: SortOrder? = null, + before: Cursor? = null, + after: Cursor? = null, + first: Int? = null, + last: Int? = null, + offset: Int? = null + ): MetaNodeList { + val queryResults = transaction { + val res = GlobalMetaTable.selectAll() + + res.applyOps(condition, filter) + + if (orderBy != null || (last != null || before != null)) { + val orderByColumn = orderBy?.column ?: GlobalMetaTable.key + val orderType = orderByType.maybeSwap(last ?: before) + + if (orderBy == MetaOrderBy.KEY || orderBy == null) { + res.orderBy(orderByColumn to orderType) + } else { + res.orderBy( + orderByColumn to orderType, + GlobalMetaTable.key to SortOrder.ASC + ) + } + } + + val total = res.count() + val firstResult = res.firstOrNull()?.get(GlobalMetaTable.key) + val lastResult = res.lastOrNull()?.get(GlobalMetaTable.key) + + if (after != null) { + res.andWhere { + (orderBy ?: MetaOrderBy.KEY).greater(after) + } + } else if (before != null) { + res.andWhere { + (orderBy ?: MetaOrderBy.KEY).less(before) + } + } + + if (first != null) { + res.limit(first, offset?.toLong() ?: 0) + } else if (last != null) { + res.limit(last) + } + + QueryResults(total, firstResult, lastResult, res.toList()) + } + + val getAsCursor: (MetaItem) -> Cursor = (orderBy ?: MetaOrderBy.KEY)::asCursor + + val resultsAsType = queryResults.results.map { GlobalMetaItem(it) } + + return MetaNodeList( + resultsAsType, + if (resultsAsType.isEmpty()) { + emptyList() + } else { + listOfNotNull( + resultsAsType.firstOrNull()?.let { + MetaNodeList.MetaEdge( + getAsCursor(it), + it + ) + }, + resultsAsType.lastOrNull()?.let { + MetaNodeList.MetaEdge( + getAsCursor(it), + it + ) + } + ) + }, + pageInfo = PageInfo( + hasNextPage = queryResults.lastKey != resultsAsType.lastOrNull()?.key, + hasPreviousPage = queryResults.firstKey != resultsAsType.firstOrNull()?.key, + startCursor = resultsAsType.firstOrNull()?.let { getAsCursor(it) }, + endCursor = resultsAsType.lastOrNull()?.let { getAsCursor(it) } + ), + totalCount = queryResults.total.toInt() + ) } } diff --git a/server/src/main/kotlin/suwayomi/tachidesk/graphql/server/primitives/OrderBy.kt b/server/src/main/kotlin/suwayomi/tachidesk/graphql/server/primitives/OrderBy.kt index 0a7bb7bc..a70ceecd 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/graphql/server/primitives/OrderBy.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/graphql/server/primitives/OrderBy.kt @@ -36,14 +36,38 @@ fun SortOrder?.maybeSwap(value: Any?): SortOrder { } } -fun > greaterNotUnique(column: Column, idColumn: Column>, cursor: Cursor, toValue: (String) -> T): Op { +fun > greaterNotUnique(column: Column, idColumn: Column>, cursor: Cursor, toValue: (String) -> T): Op { val id = cursor.value.substringBefore('-').toInt() val value = toValue(cursor.value.substringAfter('-')) return (column greater value) or ((column eq value) and (idColumn greater id)) } -fun > lessNotUnique(column: Column, idColumn: Column>, cursor: Cursor, toValue: (String) -> T): Op { +@JvmName("greaterNotUniqueStringKey") +fun > greaterNotUnique( + column: Column, + idColumn: Column, + cursor: Cursor, + toValue: (String) -> T +): Op { + val id = cursor.value.substringBefore('-') + val value = toValue(cursor.value.substringAfter('-')) + return (column greater value) or ((column eq value) and (idColumn greater id)) +} + +fun > lessNotUnique(column: Column, idColumn: Column>, cursor: Cursor, toValue: (String) -> T): Op { val id = cursor.value.substringBefore('-').toInt() val value = toValue(cursor.value.substringAfter('-')) return (column less value) or ((column eq value) and (idColumn less id)) -} \ No newline at end of file +} + +@JvmName("lessNotUniqueStringKey") +fun > lessNotUnique( + column: Column, + idColumn: Column, + cursor: Cursor, + toValue: (String) -> T +): Op { + val id = cursor.value.substringBefore('-') + val value = toValue(cursor.value.substringAfter('-')) + return (column less value) or ((column eq value) and (idColumn less id)) +} diff --git a/server/src/main/kotlin/suwayomi/tachidesk/graphql/types/MangaType.kt b/server/src/main/kotlin/suwayomi/tachidesk/graphql/types/MangaType.kt index 979f0dfc..9631d854 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/graphql/types/MangaType.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/graphql/types/MangaType.kt @@ -37,8 +37,8 @@ class MangaType( val inLibrary: Boolean, val inLibraryAt: Long, val realUrl: String?, - var lastFetchedAt: Long?, //todo - var chaptersLastFetchedAt: Long? //todo + var lastFetchedAt: Long?, // todo + var chaptersLastFetchedAt: Long? // todo ) : Node { constructor(row: ResultRow) : this( row[MangaTable.id].value,