mirror of
https://github.com/Suwayomi/Suwayomi-Server.git
synced 2026-07-03 19:04:39 -05:00
Simplify queries
This commit is contained in:
@@ -9,25 +9,29 @@ package suwayomi.tachidesk.graphql.queries
|
|||||||
|
|
||||||
import com.expediagroup.graphql.server.extensions.getValueFromDataLoader
|
import com.expediagroup.graphql.server.extensions.getValueFromDataLoader
|
||||||
import graphql.schema.DataFetchingEnvironment
|
import graphql.schema.DataFetchingEnvironment
|
||||||
import org.jetbrains.exposed.dao.id.EntityID
|
|
||||||
import org.jetbrains.exposed.sql.Column
|
import org.jetbrains.exposed.sql.Column
|
||||||
import org.jetbrains.exposed.sql.Op
|
import org.jetbrains.exposed.sql.Op
|
||||||
import org.jetbrains.exposed.sql.SortOrder
|
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.andWhere
|
||||||
import org.jetbrains.exposed.sql.selectAll
|
import org.jetbrains.exposed.sql.selectAll
|
||||||
import org.jetbrains.exposed.sql.transactions.transaction
|
import org.jetbrains.exposed.sql.transactions.transaction
|
||||||
import suwayomi.tachidesk.graphql.queries.filter.BooleanFilter
|
import suwayomi.tachidesk.graphql.queries.filter.BooleanFilter
|
||||||
import suwayomi.tachidesk.graphql.queries.filter.Filter
|
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.IntFilter
|
||||||
import suwayomi.tachidesk.graphql.queries.filter.OpAnd
|
import suwayomi.tachidesk.graphql.queries.filter.OpAnd
|
||||||
import suwayomi.tachidesk.graphql.queries.filter.StringFilter
|
import suwayomi.tachidesk.graphql.queries.filter.StringFilter
|
||||||
import suwayomi.tachidesk.graphql.queries.filter.andFilterWithCompare
|
import suwayomi.tachidesk.graphql.queries.filter.andFilterWithCompare
|
||||||
import suwayomi.tachidesk.graphql.queries.filter.andFilterWithCompareEntity
|
import suwayomi.tachidesk.graphql.queries.filter.andFilterWithCompareEntity
|
||||||
import suwayomi.tachidesk.graphql.queries.filter.andFilterWithCompareString
|
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.Cursor
|
||||||
|
import suwayomi.tachidesk.graphql.server.primitives.OrderBy
|
||||||
import suwayomi.tachidesk.graphql.server.primitives.PageInfo
|
import suwayomi.tachidesk.graphql.server.primitives.PageInfo
|
||||||
import suwayomi.tachidesk.graphql.server.primitives.QueryResults
|
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.CategoryNodeList
|
||||||
import suwayomi.tachidesk.graphql.types.CategoryType
|
import suwayomi.tachidesk.graphql.types.CategoryType
|
||||||
import suwayomi.tachidesk.manga.model.table.CategoryTable
|
import suwayomi.tachidesk.manga.model.table.CategoryTable
|
||||||
@@ -51,19 +55,35 @@ class CategoryQuery {
|
|||||||
return dataFetchingEnvironment.getValueFromDataLoader("CategoryDataLoader", id)
|
return dataFetchingEnvironment.getValueFromDataLoader("CategoryDataLoader", id)
|
||||||
}
|
}
|
||||||
|
|
||||||
enum class CategoryOrderBy {
|
enum class CategoryOrderBy(override val column: Column<out Comparable<*>>) : OrderBy<CategoryType> {
|
||||||
ID,
|
ID(CategoryTable.id),
|
||||||
NAME,
|
NAME(CategoryTable.name),
|
||||||
ORDER
|
ORDER(CategoryTable.order);
|
||||||
}
|
|
||||||
|
|
||||||
private fun getAsCursor(orderBy: CategoryOrderBy?, type: CategoryType): Cursor {
|
override fun greater(cursor: Cursor): Op<Boolean> {
|
||||||
val value = when (orderBy) {
|
return when (this) {
|
||||||
CategoryOrderBy.ID, null -> type.id.toString()
|
ID -> CategoryTable.id greater cursor.value.toInt()
|
||||||
CategoryOrderBy.NAME -> type.name
|
NAME -> CategoryTable.name greater cursor.value
|
||||||
CategoryOrderBy.ORDER -> type.order.toString()
|
ORDER -> CategoryTable.order greater cursor.value.toInt()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun less(cursor: Cursor): Op<Boolean> {
|
||||||
|
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(
|
data class CategoryCondition(
|
||||||
@@ -71,15 +91,13 @@ class CategoryQuery {
|
|||||||
val order: Int? = null,
|
val order: Int? = null,
|
||||||
val name: String? = null,
|
val name: String? = null,
|
||||||
val default: Boolean? = null
|
val default: Boolean? = null
|
||||||
) {
|
) : HasGetOp {
|
||||||
fun getOp(): Op<Boolean>? {
|
override fun getOp(): Op<Boolean>? {
|
||||||
val opAnd = OpAnd()
|
val opAnd = OpAnd()
|
||||||
fun <T> eq(value: T?, column: Column<T>) = opAnd.andWhere(value) { column eq it }
|
opAnd.eq(id, CategoryTable.id)
|
||||||
fun <T : Comparable<T>> eq(value: T?, column: Column<EntityID<T>>) = opAnd.andWhere(value) { column eq it }
|
opAnd.eq(order, CategoryTable.order)
|
||||||
eq(id, CategoryTable.id)
|
opAnd.eq(name, CategoryTable.name)
|
||||||
eq(order, CategoryTable.order)
|
opAnd.eq(default, CategoryTable.isDefault)
|
||||||
eq(name, CategoryTable.name)
|
|
||||||
eq(default, CategoryTable.isDefault)
|
|
||||||
|
|
||||||
return opAnd.op
|
return opAnd.op
|
||||||
}
|
}
|
||||||
@@ -118,63 +136,26 @@ class CategoryQuery {
|
|||||||
val queryResults = transaction {
|
val queryResults = transaction {
|
||||||
val res = CategoryTable.selectAll()
|
val res = CategoryTable.selectAll()
|
||||||
|
|
||||||
val conditionOp = condition?.getOp()
|
res.applyOps(condition, filter)
|
||||||
if (conditionOp != null) {
|
|
||||||
res.andWhere { conditionOp }
|
|
||||||
}
|
|
||||||
val filterOp = filter?.getOp()
|
|
||||||
if (filterOp != null) {
|
|
||||||
res.andWhere { filterOp }
|
|
||||||
}
|
|
||||||
if (orderBy != null || (last != null || before != null)) {
|
if (orderBy != null || (last != null || before != null)) {
|
||||||
val orderByColumn = when (orderBy) {
|
val orderByColumn = orderBy?.column ?: CategoryTable.id
|
||||||
CategoryOrderBy.ID, null -> CategoryTable.id
|
val orderType = orderByType.maybeSwap(last ?: before)
|
||||||
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
|
|
||||||
}
|
|
||||||
res.orderBy(orderByColumn, order = orderType)
|
res.orderBy(orderByColumn, order = orderType)
|
||||||
}
|
}
|
||||||
|
|
||||||
val total = res.count()
|
val total = res.count()
|
||||||
val firstResult = res.first()[CategoryTable.id].value
|
val firstResult = res.firstOrNull()?.get(CategoryTable.id)?.value
|
||||||
val lastResult = res.last()[CategoryTable.id].value
|
val lastResult = res.lastOrNull()?.get(CategoryTable.id)?.value
|
||||||
|
|
||||||
if (after != null) {
|
if (after != null) {
|
||||||
when (orderBy) {
|
res.andWhere {
|
||||||
CategoryOrderBy.ID, null -> res.andWhere {
|
(orderBy ?: CategoryOrderBy.ID).greater(after)
|
||||||
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()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else if (before != null) {
|
} else if (before != null) {
|
||||||
when (orderBy) {
|
res.andWhere {
|
||||||
CategoryOrderBy.ID, null -> res.andWhere {
|
(orderBy ?: CategoryOrderBy.ID).less(before)
|
||||||
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()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -187,6 +168,8 @@ class CategoryQuery {
|
|||||||
QueryResults(total, firstResult, lastResult, res.toList())
|
QueryResults(total, firstResult, lastResult, res.toList())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val getAsCursor: (CategoryType) -> Cursor = (orderBy ?: CategoryOrderBy.ID)::asCursor
|
||||||
|
|
||||||
val resultsAsType = queryResults.results.map { CategoryType(it) }
|
val resultsAsType = queryResults.results.map { CategoryType(it) }
|
||||||
|
|
||||||
return CategoryNodeList(
|
return CategoryNodeList(
|
||||||
@@ -194,26 +177,26 @@ class CategoryQuery {
|
|||||||
if (resultsAsType.isEmpty()) {
|
if (resultsAsType.isEmpty()) {
|
||||||
emptyList()
|
emptyList()
|
||||||
} else {
|
} else {
|
||||||
listOf(
|
listOfNotNull(
|
||||||
resultsAsType.first().let {
|
resultsAsType.firstOrNull()?.let {
|
||||||
CategoryNodeList.CategoryEdge(
|
CategoryNodeList.CategoryEdge(
|
||||||
getAsCursor(orderBy, it),
|
getAsCursor(it),
|
||||||
it
|
it
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
resultsAsType.last().let {
|
resultsAsType.lastOrNull()?.let {
|
||||||
CategoryNodeList.CategoryEdge(
|
CategoryNodeList.CategoryEdge(
|
||||||
getAsCursor(orderBy, it),
|
getAsCursor(it),
|
||||||
it
|
it
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
pageInfo = PageInfo(
|
pageInfo = PageInfo(
|
||||||
hasNextPage = queryResults.lastKey != resultsAsType.last().id,
|
hasNextPage = queryResults.lastKey != resultsAsType.lastOrNull()?.id,
|
||||||
hasPreviousPage = queryResults.firstKey != resultsAsType.first().id,
|
hasPreviousPage = queryResults.firstKey != resultsAsType.firstOrNull()?.id,
|
||||||
startCursor = resultsAsType.firstOrNull()?.let { getAsCursor(orderBy, it) },
|
startCursor = resultsAsType.firstOrNull()?.let { getAsCursor(it) },
|
||||||
endCursor = resultsAsType.lastOrNull()?.let { getAsCursor(orderBy, it) }
|
endCursor = resultsAsType.lastOrNull()?.let { getAsCursor(it) }
|
||||||
),
|
),
|
||||||
totalCount = queryResults.total.toInt()
|
totalCount = queryResults.total.toInt()
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -9,16 +9,18 @@ package suwayomi.tachidesk.graphql.queries
|
|||||||
|
|
||||||
import com.expediagroup.graphql.server.extensions.getValueFromDataLoader
|
import com.expediagroup.graphql.server.extensions.getValueFromDataLoader
|
||||||
import graphql.schema.DataFetchingEnvironment
|
import graphql.schema.DataFetchingEnvironment
|
||||||
import org.jetbrains.exposed.dao.id.EntityID
|
|
||||||
import org.jetbrains.exposed.sql.Column
|
import org.jetbrains.exposed.sql.Column
|
||||||
import org.jetbrains.exposed.sql.Op
|
import org.jetbrains.exposed.sql.Op
|
||||||
import org.jetbrains.exposed.sql.SortOrder
|
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.andWhere
|
||||||
import org.jetbrains.exposed.sql.select
|
import org.jetbrains.exposed.sql.select
|
||||||
import org.jetbrains.exposed.sql.selectAll
|
import org.jetbrains.exposed.sql.selectAll
|
||||||
import org.jetbrains.exposed.sql.transactions.transaction
|
import org.jetbrains.exposed.sql.transactions.transaction
|
||||||
import suwayomi.tachidesk.graphql.queries.filter.BooleanFilter
|
import suwayomi.tachidesk.graphql.queries.filter.BooleanFilter
|
||||||
import suwayomi.tachidesk.graphql.queries.filter.Filter
|
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.IntFilter
|
||||||
import suwayomi.tachidesk.graphql.queries.filter.LongFilter
|
import suwayomi.tachidesk.graphql.queries.filter.LongFilter
|
||||||
import suwayomi.tachidesk.graphql.queries.filter.OpAnd
|
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.andFilterWithCompare
|
||||||
import suwayomi.tachidesk.graphql.queries.filter.andFilterWithCompareEntity
|
import suwayomi.tachidesk.graphql.queries.filter.andFilterWithCompareEntity
|
||||||
import suwayomi.tachidesk.graphql.queries.filter.andFilterWithCompareString
|
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.Cursor
|
||||||
|
import suwayomi.tachidesk.graphql.server.primitives.OrderBy
|
||||||
import suwayomi.tachidesk.graphql.server.primitives.PageInfo
|
import suwayomi.tachidesk.graphql.server.primitives.PageInfo
|
||||||
import suwayomi.tachidesk.graphql.server.primitives.QueryResults
|
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.MangaNodeList
|
||||||
import suwayomi.tachidesk.graphql.types.MangaType
|
import suwayomi.tachidesk.graphql.types.MangaType
|
||||||
import suwayomi.tachidesk.manga.model.table.CategoryMangaTable
|
import suwayomi.tachidesk.manga.model.table.CategoryMangaTable
|
||||||
@@ -56,21 +61,39 @@ class MangaQuery {
|
|||||||
return dataFetchingEnvironment.getValueFromDataLoader("MangaDataLoader", id)
|
return dataFetchingEnvironment.getValueFromDataLoader("MangaDataLoader", id)
|
||||||
}
|
}
|
||||||
|
|
||||||
enum class MangaOrderBy {
|
enum class MangaOrderBy(override val column: Column<out Comparable<*>>) : OrderBy<MangaType> {
|
||||||
ID,
|
ID(MangaTable.id),
|
||||||
TITLE,
|
TITLE(MangaTable.title),
|
||||||
IN_LIBRARY_AT,
|
IN_LIBRARY_AT(MangaTable.inLibraryAt),
|
||||||
LAST_FETCHED_AT
|
LAST_FETCHED_AT(MangaTable.lastFetchedAt);
|
||||||
}
|
|
||||||
|
|
||||||
private fun getAsCursor(orderBy: MangaOrderBy?, type: MangaType): Cursor {
|
override fun greater(cursor: Cursor): Op<Boolean> {
|
||||||
val value = when (orderBy) {
|
return when (this) {
|
||||||
MangaOrderBy.ID, null -> type.id.toString()
|
ID -> MangaTable.id greater cursor.value.toInt()
|
||||||
MangaOrderBy.TITLE -> type.title
|
TITLE -> MangaTable.title greater cursor.value
|
||||||
MangaOrderBy.IN_LIBRARY_AT -> type.inLibraryAt.toString()
|
IN_LIBRARY_AT -> MangaTable.inLibraryAt greater cursor.value.toLong()
|
||||||
MangaOrderBy.LAST_FETCHED_AT -> type.lastFetchedAt.toString()
|
LAST_FETCHED_AT -> MangaTable.lastFetchedAt greater cursor.value.toLong()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun less(cursor: Cursor): Op<Boolean> {
|
||||||
|
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(
|
data class MangaCondition(
|
||||||
@@ -88,29 +111,27 @@ class MangaQuery {
|
|||||||
val inLibrary: Boolean? = null,
|
val inLibrary: Boolean? = null,
|
||||||
val inLibraryAt: Long? = null,
|
val inLibraryAt: Long? = null,
|
||||||
val realUrl: String? = null,
|
val realUrl: String? = null,
|
||||||
var lastFetchedAt: Long? = null,
|
val lastFetchedAt: Long? = null,
|
||||||
var chaptersLastFetchedAt: Long? = null
|
val chaptersLastFetchedAt: Long? = null
|
||||||
) {
|
) : HasGetOp {
|
||||||
fun getOp(): Op<Boolean>? {
|
override fun getOp(): Op<Boolean>? {
|
||||||
val opAnd = OpAnd()
|
val opAnd = OpAnd()
|
||||||
fun <T> eq(value: T?, column: Column<T>) = opAnd.andWhere(value) { column eq it }
|
opAnd.eq(id, MangaTable.id)
|
||||||
fun <T : Comparable<T>> eq(value: T?, column: Column<EntityID<T>>) = opAnd.andWhere(value) { column eq it }
|
opAnd.eq(sourceId, MangaTable.sourceReference)
|
||||||
eq(id, MangaTable.id)
|
opAnd.eq(url, MangaTable.url)
|
||||||
eq(sourceId, MangaTable.sourceReference)
|
opAnd.eq(title, MangaTable.title)
|
||||||
eq(url, MangaTable.url)
|
opAnd.eq(thumbnailUrl, MangaTable.thumbnail_url)
|
||||||
eq(title, MangaTable.title)
|
opAnd.eq(initialized, MangaTable.initialized)
|
||||||
eq(thumbnailUrl, MangaTable.thumbnail_url)
|
opAnd.eq(artist, MangaTable.artist)
|
||||||
eq(initialized, MangaTable.initialized)
|
opAnd.eq(author, MangaTable.author)
|
||||||
eq(artist, MangaTable.artist)
|
opAnd.eq(description, MangaTable.description)
|
||||||
eq(author, MangaTable.author)
|
opAnd.eq(genre?.joinToString(), MangaTable.genre)
|
||||||
eq(description, MangaTable.description)
|
opAnd.eq(status?.value, MangaTable.status)
|
||||||
eq(genre?.joinToString(), MangaTable.genre)
|
opAnd.eq(inLibrary, MangaTable.inLibrary)
|
||||||
eq(status?.value, MangaTable.status)
|
opAnd.eq(inLibraryAt, MangaTable.inLibraryAt)
|
||||||
eq(inLibrary, MangaTable.inLibrary)
|
opAnd.eq(realUrl, MangaTable.realUrl)
|
||||||
eq(inLibraryAt, MangaTable.inLibraryAt)
|
opAnd.eq(lastFetchedAt, MangaTable.lastFetchedAt)
|
||||||
eq(realUrl, MangaTable.realUrl)
|
opAnd.eq(chaptersLastFetchedAt, MangaTable.chaptersLastFetchedAt)
|
||||||
eq(lastFetchedAt, MangaTable.lastFetchedAt)
|
|
||||||
eq(chaptersLastFetchedAt, MangaTable.chaptersLastFetchedAt)
|
|
||||||
|
|
||||||
return opAnd.op
|
return opAnd.op
|
||||||
}
|
}
|
||||||
@@ -131,8 +152,8 @@ class MangaQuery {
|
|||||||
val inLibrary: BooleanFilter? = null,
|
val inLibrary: BooleanFilter? = null,
|
||||||
val inLibraryAt: LongFilter? = null,
|
val inLibraryAt: LongFilter? = null,
|
||||||
val realUrl: StringFilter? = null,
|
val realUrl: StringFilter? = null,
|
||||||
var lastFetchedAt: LongFilter? = null,
|
val lastFetchedAt: LongFilter? = null,
|
||||||
var chaptersLastFetchedAt: LongFilter? = null,
|
val chaptersLastFetchedAt: LongFilter? = null,
|
||||||
val category: IntFilter? = null,
|
val category: IntFilter? = null,
|
||||||
override val and: List<MangaFilter>? = null,
|
override val and: List<MangaFilter>? = null,
|
||||||
override val or: List<MangaFilter>? = null,
|
override val or: List<MangaFilter>? = null,
|
||||||
@@ -179,70 +200,27 @@ class MangaQuery {
|
|||||||
res = MangaTable.innerJoin(CategoryMangaTable)
|
res = MangaTable.innerJoin(CategoryMangaTable)
|
||||||
.select { categoryOp }
|
.select { categoryOp }
|
||||||
}
|
}
|
||||||
val conditionOp = condition?.getOp()
|
|
||||||
if (conditionOp != null) {
|
res.applyOps(condition, filter)
|
||||||
res.andWhere { conditionOp }
|
|
||||||
}
|
|
||||||
val filterOp = filter?.getOp()
|
|
||||||
if (filterOp != null) {
|
|
||||||
res.andWhere { filterOp }
|
|
||||||
}
|
|
||||||
if (orderBy != null || (last != null || before != null)) {
|
if (orderBy != null || (last != null || before != null)) {
|
||||||
val orderByColumn = when (orderBy) {
|
val orderByColumn = orderBy?.column ?: MangaTable.id
|
||||||
MangaOrderBy.ID, null -> MangaTable.id
|
val orderType = orderByType.maybeSwap(last ?: before)
|
||||||
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
|
|
||||||
}
|
|
||||||
res.orderBy(orderByColumn, order = orderType)
|
res.orderBy(orderByColumn, order = orderType)
|
||||||
}
|
}
|
||||||
|
|
||||||
val total = res.count()
|
val total = res.count()
|
||||||
val firstResult = res.first()[MangaTable.id].value
|
val firstResult = res.firstOrNull()?.get(MangaTable.id)?.value
|
||||||
val lastResult = res.last()[MangaTable.id].value
|
val lastResult = res.lastOrNull()?.get(MangaTable.id)?.value
|
||||||
|
|
||||||
if (after != null) {
|
if (after != null) {
|
||||||
when (orderBy) {
|
res.andWhere {
|
||||||
MangaOrderBy.ID, null -> res.andWhere {
|
(orderBy ?: MangaOrderBy.ID).greater(after)
|
||||||
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()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else if (before != null) {
|
} else if (before != null) {
|
||||||
when (orderBy) {
|
res.andWhere {
|
||||||
MangaOrderBy.ID, null -> res.andWhere {
|
(orderBy ?: MangaOrderBy.ID).less(before)
|
||||||
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()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -255,6 +233,8 @@ class MangaQuery {
|
|||||||
QueryResults(total, firstResult, lastResult, res.toList())
|
QueryResults(total, firstResult, lastResult, res.toList())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val getAsCursor: (MangaType) -> Cursor = (orderBy ?: MangaOrderBy.ID)::asCursor
|
||||||
|
|
||||||
val resultsAsType = queryResults.results.map { MangaType(it) }
|
val resultsAsType = queryResults.results.map { MangaType(it) }
|
||||||
|
|
||||||
return MangaNodeList(
|
return MangaNodeList(
|
||||||
@@ -262,26 +242,26 @@ class MangaQuery {
|
|||||||
if (resultsAsType.isEmpty()) {
|
if (resultsAsType.isEmpty()) {
|
||||||
emptyList()
|
emptyList()
|
||||||
} else {
|
} else {
|
||||||
listOf(
|
listOfNotNull(
|
||||||
resultsAsType.first().let {
|
resultsAsType.firstOrNull()?.let {
|
||||||
MangaNodeList.MangaEdge(
|
MangaNodeList.MangaEdge(
|
||||||
getAsCursor(orderBy, it),
|
getAsCursor(it),
|
||||||
it
|
it
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
resultsAsType.last().let {
|
resultsAsType.lastOrNull()?.let {
|
||||||
MangaNodeList.MangaEdge(
|
MangaNodeList.MangaEdge(
|
||||||
getAsCursor(orderBy, it),
|
getAsCursor(it),
|
||||||
it
|
it
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
pageInfo = PageInfo(
|
pageInfo = PageInfo(
|
||||||
hasNextPage = queryResults.lastKey != resultsAsType.last().id,
|
hasNextPage = queryResults.lastKey != resultsAsType.lastOrNull()?.id,
|
||||||
hasPreviousPage = queryResults.firstKey != resultsAsType.first().id,
|
hasPreviousPage = queryResults.firstKey != resultsAsType.firstOrNull()?.id,
|
||||||
startCursor = resultsAsType.firstOrNull()?.let { getAsCursor(orderBy, it) },
|
startCursor = resultsAsType.firstOrNull()?.let { getAsCursor(it) },
|
||||||
endCursor = resultsAsType.lastOrNull()?.let { getAsCursor(orderBy, it) }
|
endCursor = resultsAsType.lastOrNull()?.let { getAsCursor(it) }
|
||||||
),
|
),
|
||||||
totalCount = queryResults.total.toInt()
|
totalCount = queryResults.total.toInt()
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -7,10 +7,13 @@ import org.jetbrains.exposed.sql.Expression
|
|||||||
import org.jetbrains.exposed.sql.ExpressionWithColumnType
|
import org.jetbrains.exposed.sql.ExpressionWithColumnType
|
||||||
import org.jetbrains.exposed.sql.LikePattern
|
import org.jetbrains.exposed.sql.LikePattern
|
||||||
import org.jetbrains.exposed.sql.Op
|
import org.jetbrains.exposed.sql.Op
|
||||||
|
import org.jetbrains.exposed.sql.Query
|
||||||
import org.jetbrains.exposed.sql.QueryBuilder
|
import org.jetbrains.exposed.sql.QueryBuilder
|
||||||
import org.jetbrains.exposed.sql.SqlExpressionBuilder
|
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.SqlExpressionBuilder.wrap
|
||||||
import org.jetbrains.exposed.sql.and
|
import org.jetbrains.exposed.sql.and
|
||||||
|
import org.jetbrains.exposed.sql.andWhere
|
||||||
import org.jetbrains.exposed.sql.not
|
import org.jetbrains.exposed.sql.not
|
||||||
import org.jetbrains.exposed.sql.or
|
import org.jetbrains.exposed.sql.or
|
||||||
import org.jetbrains.exposed.sql.stringParam
|
import org.jetbrains.exposed.sql.stringParam
|
||||||
@@ -68,12 +71,57 @@ class DistinctFromOp(expr1: Expression<*>, expr2: Expression<*>, not: Boolean) :
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Filter<T : Filter<T>> {
|
interface HasGetOp {
|
||||||
|
fun getOp(): Op<Boolean>?
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Query.applyOps(vararg ops: HasGetOp?) {
|
||||||
|
ops.mapNotNull { it?.getOp() }.forEach {
|
||||||
|
andWhere { it }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Filter<T : Filter<T>> : HasGetOp {
|
||||||
val and: List<T>?
|
val and: List<T>?
|
||||||
val or: List<T>?
|
val or: List<T>?
|
||||||
val not: T?
|
val not: T?
|
||||||
|
|
||||||
fun getOpList(): List<Op<Boolean>>
|
fun getOpList(): List<Op<Boolean>>
|
||||||
|
|
||||||
|
override fun getOp(): Op<Boolean>? {
|
||||||
|
var op: Op<Boolean>? = null
|
||||||
|
fun newOp(
|
||||||
|
otherOp: Op<Boolean>?,
|
||||||
|
operator: (Op<Boolean>, Op<Boolean>) -> Op<Boolean>
|
||||||
|
) {
|
||||||
|
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<Boolean>?) {
|
||||||
|
newOp(andOp, Op<Boolean>::and)
|
||||||
|
}
|
||||||
|
fun orOp(orOp: Op<Boolean>?) {
|
||||||
|
newOp(orOp, Op<Boolean>::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<T> {
|
interface ScalarFilter<T> {
|
||||||
@@ -220,6 +268,9 @@ class OpAnd(var op: Op<Boolean>? = null) {
|
|||||||
val expr = Op.build { andPart(value) }
|
val expr = Op.build { andPart(value) }
|
||||||
op = if (op == null) expr else (op!! and expr)
|
op = if (op == null) expr else (op!! and expr)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun <T> eq(value: T?, column: Column<T>) = andWhere(value) { column eq it }
|
||||||
|
fun <T : Comparable<T>> eq(value: T?, column: Column<EntityID<T>>) = andWhere(value) { column eq it }
|
||||||
}
|
}
|
||||||
|
|
||||||
fun <T : Comparable<T>> andFilterWithCompare(
|
fun <T : Comparable<T>> andFilterWithCompare(
|
||||||
@@ -293,37 +344,3 @@ fun <T : Comparable<T>> andFilterEntity(
|
|||||||
}
|
}
|
||||||
return opAnd.op
|
return opAnd.op
|
||||||
}
|
}
|
||||||
|
|
||||||
fun <T : Filter<T>> Filter<T>.getOp(): Op<Boolean>? {
|
|
||||||
var op: Op<Boolean>? = null
|
|
||||||
fun newOp(
|
|
||||||
otherOp: Op<Boolean>?,
|
|
||||||
operator: (Op<Boolean>, Op<Boolean>) -> Op<Boolean>
|
|
||||||
) {
|
|
||||||
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<Boolean>?) {
|
|
||||||
newOp(andOp, Op<Boolean>::and)
|
|
||||||
}
|
|
||||||
fun orOp(orOp: Op<Boolean>?) {
|
|
||||||
newOp(orOp, Op<Boolean>::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
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -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<T> {
|
||||||
|
val column: Column<out Comparable<*>>
|
||||||
|
|
||||||
|
fun asCursor(type: T): Cursor
|
||||||
|
|
||||||
|
fun greater(cursor: Cursor): Op<Boolean>
|
||||||
|
|
||||||
|
fun less(cursor: Cursor): Op<Boolean>
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user