From d8567eadb2ef2355416a3a31c31c74124565a73a Mon Sep 17 00:00:00 2001 From: Syer10 Date: Fri, 7 Apr 2023 21:10:38 -0400 Subject: [PATCH] Simplify queries --- .../graphql/queries/CategoryQuery.kt | 139 ++++++------- .../tachidesk/graphql/queries/MangaQuery.kt | 186 ++++++++---------- .../graphql/queries/filter/Filter.kt | 87 ++++---- .../graphql/server/primitives/OrderBy.kt | 32 +++ 4 files changed, 228 insertions(+), 216 deletions(-) create mode 100644 server/src/main/kotlin/suwayomi/tachidesk/graphql/server/primitives/OrderBy.kt diff --git a/server/src/main/kotlin/suwayomi/tachidesk/graphql/queries/CategoryQuery.kt b/server/src/main/kotlin/suwayomi/tachidesk/graphql/queries/CategoryQuery.kt index 418bb05b..5d0d5397 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/graphql/queries/CategoryQuery.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/graphql/queries/CategoryQuery.kt @@ -9,25 +9,29 @@ package suwayomi.tachidesk.graphql.queries import com.expediagroup.graphql.server.extensions.getValueFromDataLoader import graphql.schema.DataFetchingEnvironment -import org.jetbrains.exposed.dao.id.EntityID 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.graphql.queries.filter.BooleanFilter import suwayomi.tachidesk.graphql.queries.filter.Filter +import suwayomi.tachidesk.graphql.queries.filter.HasGetOp import suwayomi.tachidesk.graphql.queries.filter.IntFilter import suwayomi.tachidesk.graphql.queries.filter.OpAnd import suwayomi.tachidesk.graphql.queries.filter.StringFilter import suwayomi.tachidesk.graphql.queries.filter.andFilterWithCompare import suwayomi.tachidesk.graphql.queries.filter.andFilterWithCompareEntity import suwayomi.tachidesk.graphql.queries.filter.andFilterWithCompareString -import suwayomi.tachidesk.graphql.queries.filter.getOp +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.maybeSwap import suwayomi.tachidesk.graphql.types.CategoryNodeList import suwayomi.tachidesk.graphql.types.CategoryType import suwayomi.tachidesk.manga.model.table.CategoryTable @@ -51,19 +55,35 @@ class CategoryQuery { return dataFetchingEnvironment.getValueFromDataLoader("CategoryDataLoader", id) } - enum class CategoryOrderBy { - ID, - NAME, - ORDER - } + enum class CategoryOrderBy(override val column: Column>) : OrderBy { + ID(CategoryTable.id), + NAME(CategoryTable.name), + ORDER(CategoryTable.order); - private fun getAsCursor(orderBy: CategoryOrderBy?, type: CategoryType): Cursor { - val value = when (orderBy) { - CategoryOrderBy.ID, null -> type.id.toString() - CategoryOrderBy.NAME -> type.name - CategoryOrderBy.ORDER -> type.order.toString() + override fun greater(cursor: Cursor): Op { + return when (this) { + ID -> CategoryTable.id greater cursor.value.toInt() + NAME -> CategoryTable.name greater cursor.value + ORDER -> CategoryTable.order greater cursor.value.toInt() + } + } + + override fun less(cursor: Cursor): Op { + return when (this) { + ID -> CategoryTable.id less cursor.value.toInt() + NAME -> CategoryTable.name less cursor.value + ORDER -> CategoryTable.order less cursor.value.toInt() + } + } + + override fun asCursor(type: CategoryType): Cursor { + val value = when (this) { + ID -> type.id.toString() + NAME -> type.name + ORDER -> type.order.toString() + } + return Cursor(value) } - return Cursor(value) } data class CategoryCondition( @@ -71,15 +91,13 @@ class CategoryQuery { val order: Int? = null, val name: String? = null, val default: Boolean? = null - ) { - fun getOp(): Op? { + ) : HasGetOp { + override fun getOp(): Op? { val opAnd = OpAnd() - fun eq(value: T?, column: Column) = opAnd.andWhere(value) { column eq it } - fun > eq(value: T?, column: Column>) = opAnd.andWhere(value) { column eq it } - eq(id, CategoryTable.id) - eq(order, CategoryTable.order) - eq(name, CategoryTable.name) - eq(default, CategoryTable.isDefault) + opAnd.eq(id, CategoryTable.id) + opAnd.eq(order, CategoryTable.order) + opAnd.eq(name, CategoryTable.name) + opAnd.eq(default, CategoryTable.isDefault) return opAnd.op } @@ -118,63 +136,26 @@ class CategoryQuery { val queryResults = transaction { val res = CategoryTable.selectAll() - val conditionOp = condition?.getOp() - if (conditionOp != null) { - res.andWhere { conditionOp } - } - val filterOp = filter?.getOp() - if (filterOp != null) { - res.andWhere { filterOp } - } + res.applyOps(condition, filter) + if (orderBy != null || (last != null || before != null)) { - val orderByColumn = when (orderBy) { - CategoryOrderBy.ID, null -> CategoryTable.id - CategoryOrderBy.NAME -> CategoryTable.name - CategoryOrderBy.ORDER -> CategoryTable.order - } - val orderType = if (last != null || before != null) { - when (orderByType) { - SortOrder.ASC -> SortOrder.DESC - SortOrder.DESC -> SortOrder.ASC - SortOrder.ASC_NULLS_FIRST -> SortOrder.DESC_NULLS_LAST - SortOrder.DESC_NULLS_FIRST -> SortOrder.ASC_NULLS_LAST - SortOrder.ASC_NULLS_LAST -> SortOrder.DESC_NULLS_FIRST - SortOrder.DESC_NULLS_LAST -> SortOrder.ASC_NULLS_FIRST - null -> SortOrder.DESC - } - } else { - orderByType ?: SortOrder.ASC - } + val orderByColumn = orderBy?.column ?: CategoryTable.id + val orderType = orderByType.maybeSwap(last ?: before) + res.orderBy(orderByColumn, order = orderType) } val total = res.count() - val firstResult = res.first()[CategoryTable.id].value - val lastResult = res.last()[CategoryTable.id].value + val firstResult = res.firstOrNull()?.get(CategoryTable.id)?.value + val lastResult = res.lastOrNull()?.get(CategoryTable.id)?.value if (after != null) { - when (orderBy) { - CategoryOrderBy.ID, null -> res.andWhere { - CategoryTable.id greater after.value.toInt() - } - CategoryOrderBy.NAME -> res.andWhere { - CategoryTable.name greater after.value - } - CategoryOrderBy.ORDER -> res.andWhere { - CategoryTable.order greater after.value.toInt() - } + res.andWhere { + (orderBy ?: CategoryOrderBy.ID).greater(after) } } else if (before != null) { - when (orderBy) { - CategoryOrderBy.ID, null -> res.andWhere { - CategoryTable.id less before.value.toInt() - } - CategoryOrderBy.NAME -> res.andWhere { - CategoryTable.name less before.value - } - CategoryOrderBy.ORDER -> res.andWhere { - CategoryTable.order less before.value.toInt() - } + res.andWhere { + (orderBy ?: CategoryOrderBy.ID).less(before) } } @@ -187,6 +168,8 @@ class CategoryQuery { QueryResults(total, firstResult, lastResult, res.toList()) } + val getAsCursor: (CategoryType) -> Cursor = (orderBy ?: CategoryOrderBy.ID)::asCursor + val resultsAsType = queryResults.results.map { CategoryType(it) } return CategoryNodeList( @@ -194,26 +177,26 @@ class CategoryQuery { if (resultsAsType.isEmpty()) { emptyList() } else { - listOf( - resultsAsType.first().let { + listOfNotNull( + resultsAsType.firstOrNull()?.let { CategoryNodeList.CategoryEdge( - getAsCursor(orderBy, it), + getAsCursor(it), it ) }, - resultsAsType.last().let { + resultsAsType.lastOrNull()?.let { CategoryNodeList.CategoryEdge( - getAsCursor(orderBy, it), + getAsCursor(it), it ) } ) }, pageInfo = PageInfo( - hasNextPage = queryResults.lastKey != resultsAsType.last().id, - hasPreviousPage = queryResults.firstKey != resultsAsType.first().id, - startCursor = resultsAsType.firstOrNull()?.let { getAsCursor(orderBy, it) }, - endCursor = resultsAsType.lastOrNull()?.let { getAsCursor(orderBy, it) } + hasNextPage = queryResults.lastKey != resultsAsType.lastOrNull()?.id, + hasPreviousPage = queryResults.firstKey != resultsAsType.firstOrNull()?.id, + 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/queries/MangaQuery.kt b/server/src/main/kotlin/suwayomi/tachidesk/graphql/queries/MangaQuery.kt index df1b1dc6..0a79da30 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/graphql/queries/MangaQuery.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/graphql/queries/MangaQuery.kt @@ -9,16 +9,18 @@ package suwayomi.tachidesk.graphql.queries import com.expediagroup.graphql.server.extensions.getValueFromDataLoader import graphql.schema.DataFetchingEnvironment -import org.jetbrains.exposed.dao.id.EntityID 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.select import org.jetbrains.exposed.sql.selectAll import org.jetbrains.exposed.sql.transactions.transaction import suwayomi.tachidesk.graphql.queries.filter.BooleanFilter import suwayomi.tachidesk.graphql.queries.filter.Filter +import suwayomi.tachidesk.graphql.queries.filter.HasGetOp import suwayomi.tachidesk.graphql.queries.filter.IntFilter import suwayomi.tachidesk.graphql.queries.filter.LongFilter import suwayomi.tachidesk.graphql.queries.filter.OpAnd @@ -26,10 +28,13 @@ import suwayomi.tachidesk.graphql.queries.filter.StringFilter import suwayomi.tachidesk.graphql.queries.filter.andFilterWithCompare import suwayomi.tachidesk.graphql.queries.filter.andFilterWithCompareEntity import suwayomi.tachidesk.graphql.queries.filter.andFilterWithCompareString -import suwayomi.tachidesk.graphql.queries.filter.getOp +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.maybeSwap +import suwayomi.tachidesk.graphql.types.CategoryType import suwayomi.tachidesk.graphql.types.MangaNodeList import suwayomi.tachidesk.graphql.types.MangaType import suwayomi.tachidesk.manga.model.table.CategoryMangaTable @@ -56,21 +61,39 @@ class MangaQuery { return dataFetchingEnvironment.getValueFromDataLoader("MangaDataLoader", id) } - enum class MangaOrderBy { - ID, - TITLE, - IN_LIBRARY_AT, - LAST_FETCHED_AT - } + enum class MangaOrderBy(override val column: Column>) : OrderBy { + ID(MangaTable.id), + TITLE(MangaTable.title), + IN_LIBRARY_AT(MangaTable.inLibraryAt), + LAST_FETCHED_AT(MangaTable.lastFetchedAt); - private fun getAsCursor(orderBy: MangaOrderBy?, type: MangaType): Cursor { - val value = when (orderBy) { - MangaOrderBy.ID, null -> type.id.toString() - MangaOrderBy.TITLE -> type.title - MangaOrderBy.IN_LIBRARY_AT -> type.inLibraryAt.toString() - MangaOrderBy.LAST_FETCHED_AT -> type.lastFetchedAt.toString() + override fun greater(cursor: Cursor): Op { + return when (this) { + ID -> MangaTable.id greater cursor.value.toInt() + TITLE -> MangaTable.title greater cursor.value + IN_LIBRARY_AT -> MangaTable.inLibraryAt greater cursor.value.toLong() + LAST_FETCHED_AT -> MangaTable.lastFetchedAt greater cursor.value.toLong() + } + } + + override fun less(cursor: Cursor): Op { + return when (this) { + ID -> MangaTable.id less cursor.value.toInt() + TITLE -> MangaTable.title less cursor.value + IN_LIBRARY_AT -> MangaTable.inLibraryAt less cursor.value.toLong() + LAST_FETCHED_AT -> MangaTable.lastFetchedAt less cursor.value.toLong() + } + } + + override fun asCursor(type: MangaType): Cursor { + val value = when (this) { + ID -> type.id.toString() + TITLE -> type.title + IN_LIBRARY_AT -> type.inLibraryAt.toString() + LAST_FETCHED_AT -> type.lastFetchedAt.toString() + } + return Cursor(value) } - return Cursor(value) } data class MangaCondition( @@ -88,29 +111,27 @@ class MangaQuery { val inLibrary: Boolean? = null, val inLibraryAt: Long? = null, val realUrl: String? = null, - var lastFetchedAt: Long? = null, - var chaptersLastFetchedAt: Long? = null - ) { - fun getOp(): Op? { + val lastFetchedAt: Long? = null, + val chaptersLastFetchedAt: Long? = null + ) : HasGetOp { + override fun getOp(): Op? { val opAnd = OpAnd() - fun eq(value: T?, column: Column) = opAnd.andWhere(value) { column eq it } - fun > eq(value: T?, column: Column>) = opAnd.andWhere(value) { column eq it } - eq(id, MangaTable.id) - eq(sourceId, MangaTable.sourceReference) - eq(url, MangaTable.url) - eq(title, MangaTable.title) - eq(thumbnailUrl, MangaTable.thumbnail_url) - eq(initialized, MangaTable.initialized) - eq(artist, MangaTable.artist) - eq(author, MangaTable.author) - eq(description, MangaTable.description) - eq(genre?.joinToString(), MangaTable.genre) - eq(status?.value, MangaTable.status) - eq(inLibrary, MangaTable.inLibrary) - eq(inLibraryAt, MangaTable.inLibraryAt) - eq(realUrl, MangaTable.realUrl) - eq(lastFetchedAt, MangaTable.lastFetchedAt) - eq(chaptersLastFetchedAt, MangaTable.chaptersLastFetchedAt) + opAnd.eq(id, MangaTable.id) + opAnd.eq(sourceId, MangaTable.sourceReference) + opAnd.eq(url, MangaTable.url) + opAnd.eq(title, MangaTable.title) + opAnd.eq(thumbnailUrl, MangaTable.thumbnail_url) + opAnd.eq(initialized, MangaTable.initialized) + opAnd.eq(artist, MangaTable.artist) + opAnd.eq(author, MangaTable.author) + opAnd.eq(description, MangaTable.description) + opAnd.eq(genre?.joinToString(), MangaTable.genre) + opAnd.eq(status?.value, MangaTable.status) + opAnd.eq(inLibrary, MangaTable.inLibrary) + opAnd.eq(inLibraryAt, MangaTable.inLibraryAt) + opAnd.eq(realUrl, MangaTable.realUrl) + opAnd.eq(lastFetchedAt, MangaTable.lastFetchedAt) + opAnd.eq(chaptersLastFetchedAt, MangaTable.chaptersLastFetchedAt) return opAnd.op } @@ -131,8 +152,8 @@ class MangaQuery { val inLibrary: BooleanFilter? = null, val inLibraryAt: LongFilter? = null, val realUrl: StringFilter? = null, - var lastFetchedAt: LongFilter? = null, - var chaptersLastFetchedAt: LongFilter? = null, + val lastFetchedAt: LongFilter? = null, + val chaptersLastFetchedAt: LongFilter? = null, val category: IntFilter? = null, override val and: List? = null, override val or: List? = null, @@ -179,70 +200,27 @@ class MangaQuery { res = MangaTable.innerJoin(CategoryMangaTable) .select { categoryOp } } - val conditionOp = condition?.getOp() - if (conditionOp != null) { - res.andWhere { conditionOp } - } - val filterOp = filter?.getOp() - if (filterOp != null) { - res.andWhere { filterOp } - } + + res.applyOps(condition, filter) + if (orderBy != null || (last != null || before != null)) { - val orderByColumn = when (orderBy) { - MangaOrderBy.ID, null -> MangaTable.id - MangaOrderBy.TITLE -> MangaTable.title - MangaOrderBy.IN_LIBRARY_AT -> MangaTable.inLibraryAt - MangaOrderBy.LAST_FETCHED_AT -> MangaTable.lastFetchedAt - } - val orderType = if (last != null || before != null) { - when (orderByType) { - SortOrder.ASC -> SortOrder.DESC - SortOrder.DESC -> SortOrder.ASC - SortOrder.ASC_NULLS_FIRST -> SortOrder.DESC_NULLS_LAST - SortOrder.DESC_NULLS_FIRST -> SortOrder.ASC_NULLS_LAST - SortOrder.ASC_NULLS_LAST -> SortOrder.DESC_NULLS_FIRST - SortOrder.DESC_NULLS_LAST -> SortOrder.ASC_NULLS_FIRST - null -> SortOrder.DESC - } - } else { - orderByType ?: SortOrder.ASC - } + val orderByColumn = orderBy?.column ?: MangaTable.id + val orderType = orderByType.maybeSwap(last ?: before) + res.orderBy(orderByColumn, order = orderType) } val total = res.count() - val firstResult = res.first()[MangaTable.id].value - val lastResult = res.last()[MangaTable.id].value + val firstResult = res.firstOrNull()?.get(MangaTable.id)?.value + val lastResult = res.lastOrNull()?.get(MangaTable.id)?.value if (after != null) { - when (orderBy) { - MangaOrderBy.ID, null -> res.andWhere { - MangaTable.id greater after.value.toInt() - } - MangaOrderBy.TITLE -> res.andWhere { - MangaTable.title greater after.value - } - MangaOrderBy.IN_LIBRARY_AT -> res.andWhere { - MangaTable.inLibraryAt greater after.value.toLong() - } - MangaOrderBy.LAST_FETCHED_AT -> res.andWhere { - MangaTable.lastFetchedAt greater after.value.toLong() - } + res.andWhere { + (orderBy ?: MangaOrderBy.ID).greater(after) } } else if (before != null) { - when (orderBy) { - MangaOrderBy.ID, null -> res.andWhere { - MangaTable.id less before.value.toInt() - } - MangaOrderBy.TITLE -> res.andWhere { - MangaTable.title less before.value - } - MangaOrderBy.IN_LIBRARY_AT -> res.andWhere { - MangaTable.inLibraryAt less before.value.toLong() - } - MangaOrderBy.LAST_FETCHED_AT -> res.andWhere { - MangaTable.lastFetchedAt less before.value.toLong() - } + res.andWhere { + (orderBy ?: MangaOrderBy.ID).less(before) } } @@ -255,6 +233,8 @@ class MangaQuery { QueryResults(total, firstResult, lastResult, res.toList()) } + val getAsCursor: (MangaType) -> Cursor = (orderBy ?: MangaOrderBy.ID)::asCursor + val resultsAsType = queryResults.results.map { MangaType(it) } return MangaNodeList( @@ -262,26 +242,26 @@ class MangaQuery { if (resultsAsType.isEmpty()) { emptyList() } else { - listOf( - resultsAsType.first().let { + listOfNotNull( + resultsAsType.firstOrNull()?.let { MangaNodeList.MangaEdge( - getAsCursor(orderBy, it), + getAsCursor(it), it ) }, - resultsAsType.last().let { + resultsAsType.lastOrNull()?.let { MangaNodeList.MangaEdge( - getAsCursor(orderBy, it), + getAsCursor(it), it ) } ) }, pageInfo = PageInfo( - hasNextPage = queryResults.lastKey != resultsAsType.last().id, - hasPreviousPage = queryResults.firstKey != resultsAsType.first().id, - startCursor = resultsAsType.firstOrNull()?.let { getAsCursor(orderBy, it) }, - endCursor = resultsAsType.lastOrNull()?.let { getAsCursor(orderBy, it) } + hasNextPage = queryResults.lastKey != resultsAsType.lastOrNull()?.id, + hasPreviousPage = queryResults.firstKey != resultsAsType.firstOrNull()?.id, + 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/queries/filter/Filter.kt b/server/src/main/kotlin/suwayomi/tachidesk/graphql/queries/filter/Filter.kt index 87283a23..d02340c0 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/graphql/queries/filter/Filter.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/graphql/queries/filter/Filter.kt @@ -7,10 +7,13 @@ import org.jetbrains.exposed.sql.Expression import org.jetbrains.exposed.sql.ExpressionWithColumnType import org.jetbrains.exposed.sql.LikePattern import org.jetbrains.exposed.sql.Op +import org.jetbrains.exposed.sql.Query import org.jetbrains.exposed.sql.QueryBuilder import org.jetbrains.exposed.sql.SqlExpressionBuilder +import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq import org.jetbrains.exposed.sql.SqlExpressionBuilder.wrap import org.jetbrains.exposed.sql.and +import org.jetbrains.exposed.sql.andWhere import org.jetbrains.exposed.sql.not import org.jetbrains.exposed.sql.or import org.jetbrains.exposed.sql.stringParam @@ -68,12 +71,57 @@ class DistinctFromOp(expr1: Expression<*>, expr2: Expression<*>, not: Boolean) : } } -interface Filter> { +interface HasGetOp { + fun getOp(): Op? +} + +fun Query.applyOps(vararg ops: HasGetOp?) { + ops.mapNotNull { it?.getOp() }.forEach { + andWhere { it } + } +} + +interface Filter> : HasGetOp { val and: List? val or: List? val not: T? fun getOpList(): List> + + override fun getOp(): Op? { + var op: Op? = null + fun newOp( + otherOp: Op?, + operator: (Op, Op) -> Op + ) { + when { + op == null && otherOp == null -> Unit + op == null && otherOp != null -> op = otherOp + op != null && otherOp == null -> Unit + op != null && otherOp != null -> op = operator(op!!, otherOp) + } + } + fun andOp(andOp: Op?) { + newOp(andOp, Op::and) + } + fun orOp(orOp: Op?) { + newOp(orOp, Op::or) + } + getOpList().forEach { + andOp(it) + } + and?.forEach { + andOp(it.getOp()) + } + or?.forEach { + orOp(it.getOp()) + } + if (not != null) { + andOp(not!!.getOp()?.let(::not)) + } + return op + } + } interface ScalarFilter { @@ -220,6 +268,9 @@ class OpAnd(var op: Op? = null) { val expr = Op.build { andPart(value) } op = if (op == null) expr else (op!! and expr) } + + fun eq(value: T?, column: Column) = andWhere(value) { column eq it } + fun > eq(value: T?, column: Column>) = andWhere(value) { column eq it } } fun > andFilterWithCompare( @@ -293,37 +344,3 @@ fun > andFilterEntity( } return opAnd.op } - -fun > Filter.getOp(): Op? { - var op: Op? = null - fun newOp( - otherOp: Op?, - operator: (Op, Op) -> Op - ) { - when { - op == null && otherOp == null -> Unit - op == null && otherOp != null -> op = otherOp - op != null && otherOp == null -> Unit - op != null && otherOp != null -> op = operator(op!!, otherOp) - } - } - fun andOp(andOp: Op?) { - newOp(andOp, Op::and) - } - fun orOp(orOp: Op?) { - newOp(orOp, Op::or) - } - getOpList().forEach { - andOp(it) - } - and?.forEach { - andOp(it.getOp()) - } - or?.forEach { - orOp(it.getOp()) - } - if (not != null) { - andOp(not!!.getOp()?.let(::not)) - } - return op -} 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 new file mode 100644 index 00000000..671e4b7b --- /dev/null +++ b/server/src/main/kotlin/suwayomi/tachidesk/graphql/server/primitives/OrderBy.kt @@ -0,0 +1,32 @@ +package suwayomi.tachidesk.graphql.server.primitives + +import org.jetbrains.exposed.sql.Column +import org.jetbrains.exposed.sql.Op +import org.jetbrains.exposed.sql.SortOrder + +interface OrderBy { + val column: Column> + + fun asCursor(type: T): Cursor + + fun greater(cursor: Cursor): Op + + fun less(cursor: Cursor): Op +} + + +fun SortOrder?.maybeSwap(value: Any?): SortOrder { + return if (value != null) { + when (this) { + SortOrder.ASC -> SortOrder.DESC + SortOrder.DESC -> SortOrder.ASC + SortOrder.ASC_NULLS_FIRST -> SortOrder.DESC_NULLS_LAST + SortOrder.DESC_NULLS_FIRST -> SortOrder.ASC_NULLS_LAST + SortOrder.ASC_NULLS_LAST -> SortOrder.DESC_NULLS_FIRST + SortOrder.DESC_NULLS_LAST -> SortOrder.ASC_NULLS_FIRST + null -> SortOrder.DESC + } + } else { + this ?: SortOrder.ASC + } +} \ No newline at end of file