Feature/gql simpilify filtering for multiple values (#960)

* Remove code duplication

* Remove unnecessary functions

* Simplify filtering for multiple values in queries

Makes it easier to filter for multiple values at ones without having to nest filters with multiple "and".

e.g.

```gql
query MyQuery {
 mangas(
  filter: {genre: {includesInsensitive: "action"}, and: {genre: {includesInsensitive: "adventure"}, and: { ... }}}
 ) {
  nodes {
   id
  }
 }
}
```

can be simplified to

```gql
query MyQuery {
 mangas(
  filter: {genre: {includesInsensitive: ["action", "adventure", ...]}}
 ) {
  nodes {
   id
  }
 }
}
```

* Add filter for matching "any" value in list

Makes it easier to filter for entries that match any value without having to nest filters with multiple "or".

e.g.

```gql
query MyQuery {
 mangas(
  filter: {genre: {includesInsensitiveAny: ["action", "adventure", ...]}}
 ) {
  nodes {
   id
  }
 }
}
```

instead of

```gql
query MyQuery {
 mangas(
  filter: {genre: {includesInsensitive: "action", or: {genre: includesInsensitive: "adventure", or: {...}}}}
 ) {
  nodes {
   id
  }
 }
}
```

* Add util function to apply "andWhere/All/Any"
This commit is contained in:
schroda
2024-06-28 02:34:29 +02:00
committed by GitHub
parent 7c54ad54fc
commit af9ad61174
2 changed files with 146 additions and 70 deletions
@@ -126,14 +126,14 @@ class MangaQuery {
opAnd.eq(artist, MangaTable.artist)
opAnd.eq(author, MangaTable.author)
opAnd.eq(description, MangaTable.description)
genre?.forEach { opAnd.like("%$it%", MangaTable.genre) }
opAnd.andWhereAll(genre) { MangaTable.genre like "%$it%" }
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)
opAnd.inList(categoryIds, CategoryMangaTable.category)
opAnd.andWhere(categoryIds) { CategoryMangaTable.category inList it }
return opAnd.op
}
@@ -143,7 +143,11 @@ class MangaQuery {
override val isNull: Boolean? = null,
override val equalTo: MangaStatus? = null,
override val notEqualTo: MangaStatus? = null,
override val notEqualToAll: List<MangaStatus>? = null,
override val notEqualToAny: List<MangaStatus>? = null,
override val distinctFrom: MangaStatus? = null,
override val distinctFromAll: List<MangaStatus>? = null,
override val distinctFromAny: List<MangaStatus>? = null,
override val notDistinctFrom: MangaStatus? = null,
override val `in`: List<MangaStatus>? = null,
override val notIn: List<MangaStatus>? = null,
@@ -156,7 +160,11 @@ class MangaQuery {
IntFilter(
equalTo = equalTo?.value,
notEqualTo = notEqualTo?.value,
notEqualToAll = notEqualToAll?.map { it.value },
notEqualToAny = notEqualToAny?.map { it.value },
distinctFrom = distinctFrom?.value,
distinctFromAll = distinctFromAll?.map { it.value },
distinctFromAny = distinctFromAny?.map { it.value },
notDistinctFrom = notDistinctFrom?.value,
`in` = `in`?.map { it.value },
notIn = notIn?.map { it.value },
@@ -182,7 +182,11 @@ interface ScalarFilter<T> {
val isNull: Boolean?
val equalTo: T?
val notEqualTo: T?
val notEqualToAll: List<T>?
val notEqualToAny: List<T>?
val distinctFrom: T?
val distinctFromAll: List<T>?
val distinctFromAny: List<T>?
val notDistinctFrom: T?
@Suppress("ktlint:standard:property-naming")
@@ -207,7 +211,11 @@ data class LongFilter(
override val isNull: Boolean? = null,
override val equalTo: Long? = null,
override val notEqualTo: Long? = null,
override val notEqualToAll: List<Long>? = null,
override val notEqualToAny: List<Long>? = null,
override val distinctFrom: Long? = null,
override val distinctFromAll: List<Long>? = null,
override val distinctFromAny: List<Long>? = null,
override val notDistinctFrom: Long? = null,
override val `in`: List<Long>? = null,
override val notIn: List<Long>? = null,
@@ -221,7 +229,11 @@ data class BooleanFilter(
override val isNull: Boolean? = null,
override val equalTo: Boolean? = null,
override val notEqualTo: Boolean? = null,
override val notEqualToAll: List<Boolean>? = null,
override val notEqualToAny: List<Boolean>? = null,
override val distinctFrom: Boolean? = null,
override val distinctFromAll: List<Boolean>? = null,
override val distinctFromAny: List<Boolean>? = null,
override val notDistinctFrom: Boolean? = null,
override val `in`: List<Boolean>? = null,
override val notIn: List<Boolean>? = null,
@@ -235,7 +247,11 @@ data class IntFilter(
override val isNull: Boolean? = null,
override val equalTo: Int? = null,
override val notEqualTo: Int? = null,
override val notEqualToAll: List<Int>? = null,
override val notEqualToAny: List<Int>? = null,
override val distinctFrom: Int? = null,
override val distinctFromAll: List<Int>? = null,
override val distinctFromAny: List<Int>? = null,
override val notDistinctFrom: Int? = null,
override val `in`: List<Int>? = null,
override val notIn: List<Int>? = null,
@@ -249,7 +265,11 @@ data class FloatFilter(
override val isNull: Boolean? = null,
override val equalTo: Float? = null,
override val notEqualTo: Float? = null,
override val notEqualToAll: List<Float>? = null,
override val notEqualToAny: List<Float>? = null,
override val distinctFrom: Float? = null,
override val distinctFromAll: List<Float>? = null,
override val distinctFromAny: List<Float>? = null,
override val notDistinctFrom: Float? = null,
override val `in`: List<Float>? = null,
override val notIn: List<Float>? = null,
@@ -263,7 +283,11 @@ data class DoubleFilter(
override val isNull: Boolean? = null,
override val equalTo: Double? = null,
override val notEqualTo: Double? = null,
override val notEqualToAll: List<Double>? = null,
override val notEqualToAny: List<Double>? = null,
override val distinctFrom: Double? = null,
override val distinctFromAll: List<Double>? = null,
override val distinctFromAny: List<Double>? = null,
override val notDistinctFrom: Double? = null,
override val `in`: List<Double>? = null,
override val notIn: List<Double>? = null,
@@ -277,7 +301,11 @@ data class StringFilter(
override val isNull: Boolean? = null,
override val equalTo: String? = null,
override val notEqualTo: String? = null,
override val notEqualToAll: List<String>? = null,
override val notEqualToAny: List<String>? = null,
override val distinctFrom: String? = null,
override val distinctFromAll: List<String>? = null,
override val distinctFromAny: List<String>? = null,
override val notDistinctFrom: String? = null,
override val `in`: List<String>? = null,
override val notIn: List<String>? = null,
@@ -286,22 +314,56 @@ data class StringFilter(
override val greaterThan: String? = null,
override val greaterThanOrEqualTo: String? = null,
val includes: String? = null,
val includesAll: List<String>? = null,
val includesAny: List<String>? = null,
val notIncludes: String? = null,
val notIncludesAll: List<String>? = null,
val notIncludesAny: List<String>? = null,
val includesInsensitive: String? = null,
val includesInsensitiveAll: List<String>? = null,
val includesInsensitiveAny: List<String>? = null,
val notIncludesInsensitive: String? = null,
val notIncludesInsensitiveAll: List<String>? = null,
val notIncludesInsensitiveAny: List<String>? = null,
val startsWith: String? = null,
val startsWithAll: List<String>? = null,
val startsWithAny: List<String>? = null,
val notStartsWith: String? = null,
val notStartsWithAll: List<String>? = null,
val notStartsWithAny: List<String>? = null,
val startsWithInsensitive: String? = null,
val startsWithInsensitiveAll: List<String>? = null,
val startsWithInsensitiveAny: List<String>? = null,
val notStartsWithInsensitive: String? = null,
val notStartsWithInsensitiveAll: List<String>? = null,
val notStartsWithInsensitiveAny: List<String>? = null,
val endsWith: String? = null,
val endsWithAll: List<String>? = null,
val endsWithAny: List<String>? = null,
val notEndsWith: String? = null,
val notEndsWithAll: List<String>? = null,
val notEndsWithAny: List<String>? = null,
val endsWithInsensitive: String? = null,
val endsWithInsensitiveAll: List<String>? = null,
val endsWithInsensitiveAny: List<String>? = null,
val notEndsWithInsensitive: String? = null,
val notEndsWithInsensitiveAll: List<String>? = null,
val notEndsWithInsensitiveAny: List<String>? = null,
val like: String? = null,
val likeAll: List<String>? = null,
val likeAny: List<String>? = null,
val notLike: String? = null,
val notLikeAll: List<String>? = null,
val notLikeAny: List<String>? = null,
val likeInsensitive: String? = null,
val likeInsensitiveAll: List<String>? = null,
val likeInsensitiveAny: List<String>? = null,
val notLikeInsensitive: String? = null,
val notLikeInsensitiveAll: List<String>? = null,
val notLikeInsensitiveAny: List<String>? = null,
val distinctFromInsensitive: String? = null,
val distinctFromInsensitiveAll: List<String>? = null,
val distinctFromInsensitiveAny: List<String>? = null,
val notDistinctFromInsensitive: String? = null,
val inInsensitive: List<String>? = null,
val notInInsensitive: List<String>? = null,
@@ -315,7 +377,11 @@ data class StringListFilter(
override val isNull: Boolean? = null,
override val equalTo: String? = null,
override val notEqualTo: String? = null,
override val notEqualToAll: List<String>? = null,
override val notEqualToAny: List<String>? = null,
override val distinctFrom: String? = null,
override val distinctFromAll: List<String>? = null,
override val distinctFromAny: List<String>? = null,
override val notDistinctFrom: String? = null,
override val `in`: List<String>? = null,
override val notIn: List<String>? = null,
@@ -337,8 +403,12 @@ fun <T : String, S : T?> andFilterWithCompareString(
opAnd.andWhere(filter.isNull) { if (it) column.isNull() else column.isNotNull() }
opAnd.andWhere(filter.equalTo) { column eq it as S }
opAnd.andWhere(filter.notEqualTo) { column neq it as S }
opAnd.andWhere(filter.distinctFrom) { DistinctFromOp.distinctFrom(column, it as S) }
opAnd.andWhere(filter.notEqualTo, filter.notEqualToAll, filter.notEqualToAny) { column neq it as S }
opAnd.andWhere(
filter.distinctFrom,
filter.distinctFromAll,
filter.distinctFromAny,
) { DistinctFromOp.distinctFrom(column, it as S) }
opAnd.andWhere(filter.notDistinctFrom) { DistinctFromOp.notDistinctFrom(column, it as S) }
if (!filter.`in`.isNullOrEmpty()) {
opAnd.andWhere(filter.`in`) { column inList it as List<S> }
@@ -352,27 +422,43 @@ fun <T : String, S : T?> andFilterWithCompareString(
opAnd.andWhere(filter.greaterThan) { column greater it }
opAnd.andWhere(filter.greaterThanOrEqualTo) { column greaterEq it }
opAnd.andWhere(filter.includes) { column like "%$it%" }
opAnd.andWhere(filter.notIncludes) { column notLike "%$it%" }
opAnd.andWhere(filter.includesInsensitive) { ILikeEscapeOp.iLike(column, "%$it%") }
opAnd.andWhere(filter.notIncludesInsensitive) { ILikeEscapeOp.iNotLike(column, "%$it%") }
opAnd.andWhere(filter.includes, filter.includesAll, filter.includesAny) { column like "%$it%" }
opAnd.andWhere(filter.notIncludes, filter.notIncludesAll, filter.notIncludesAny) { column notLike "%$it%" }
opAnd.andWhere(filter.includesInsensitive, filter.includesInsensitiveAll, filter.includesInsensitiveAny) {
ILikeEscapeOp.iLike(column, "%$it%")
}
opAnd.andWhere(filter.notIncludesInsensitive, filter.notIncludesInsensitiveAll, filter.notIncludesInsensitiveAny) {
ILikeEscapeOp.iNotLike(column, "%$it%")
}
opAnd.andWhere(filter.startsWith) { column like "$it%" }
opAnd.andWhere(filter.notStartsWith) { column notLike "$it%" }
opAnd.andWhere(filter.startsWithInsensitive) { ILikeEscapeOp.iLike(column, "$it%") }
opAnd.andWhere(filter.notStartsWithInsensitive) { ILikeEscapeOp.iNotLike(column, "$it%") }
opAnd.andWhere(filter.startsWith, filter.startsWithAll, filter.startsWithAny) { column like "$it%" }
opAnd.andWhere(filter.notStartsWith, filter.notStartsWithAll, filter.notStartsWithAny) { column notLike "$it%" }
opAnd.andWhere(filter.startsWithInsensitive, filter.startsWithInsensitiveAll, filter.startsWithInsensitiveAny) {
ILikeEscapeOp.iLike(column, "$it%")
}
opAnd.andWhere(filter.notStartsWithInsensitive, filter.notStartsWithInsensitiveAll, filter.notStartsWithInsensitiveAny) {
ILikeEscapeOp.iNotLike(column, "$it%")
}
opAnd.andWhere(filter.endsWith) { column like "%$it" }
opAnd.andWhere(filter.notEndsWith) { column notLike "%$it" }
opAnd.andWhere(filter.endsWithInsensitive) { ILikeEscapeOp.iLike(column, "%$it") }
opAnd.andWhere(filter.notEndsWithInsensitive) { ILikeEscapeOp.iNotLike(column, "%$it") }
opAnd.andWhere(filter.endsWith, filter.endsWithAll, filter.endsWithAny) { column like "%$it" }
opAnd.andWhere(filter.notEndsWith, filter.notEndsWithAll, filter.notEndsWithAny) { column notLike "%$it" }
opAnd.andWhere(filter.endsWithInsensitive, filter.endsWithInsensitiveAll, filter.endsWithInsensitiveAny) {
ILikeEscapeOp.iLike(column, "%$it")
}
opAnd.andWhere(filter.notEndsWithInsensitive, filter.notEndsWithInsensitiveAll, filter.notEndsWithInsensitiveAny) {
ILikeEscapeOp.iNotLike(column, "%$it")
}
opAnd.andWhere(filter.like) { column like it }
opAnd.andWhere(filter.notLike) { column notLike it }
opAnd.andWhere(filter.likeInsensitive) { ILikeEscapeOp.iLike(column, it) }
opAnd.andWhere(filter.notLikeInsensitive) { ILikeEscapeOp.iNotLike(column, it) }
opAnd.andWhere(filter.like, filter.likeAll, filter.likeAny) { column like it }
opAnd.andWhere(filter.notLike, filter.notLikeAll, filter.notLikeAny) { column notLike it }
opAnd.andWhere(filter.likeInsensitive, filter.likeInsensitiveAll, filter.likeInsensitiveAny) { ILikeEscapeOp.iLike(column, it) }
opAnd.andWhere(filter.notLikeInsensitive, filter.notLikeInsensitiveAll, filter.notLikeInsensitiveAny) {
ILikeEscapeOp.iNotLike(column, it)
}
opAnd.andWhere(filter.distinctFromInsensitive) { DistinctFromOp.distinctFrom(column.upperCase(), it.uppercase() as S) }
opAnd.andWhere(filter.distinctFromInsensitive, filter.distinctFromInsensitiveAll, filter.distinctFromInsensitiveAny) {
DistinctFromOp.distinctFrom(column.upperCase(), it.uppercase() as S)
}
opAnd.andWhere(filter.notDistinctFromInsensitive) { DistinctFromOp.notDistinctFrom(column.upperCase(), it.uppercase() as S) }
opAnd.andWhere(filter.inInsensitive) { column.upperCase() inList (it.map { it.uppercase() } as List<S>) }
@@ -399,9 +485,35 @@ class OpAnd(var op: Op<Boolean>? = null) {
fun <T : Any> andWhere(
values: List<T>?,
andPart: SqlExpressionBuilder.(List<T>) -> Op<Boolean>,
) {
@Suppress("UNCHECKED_CAST")
return andWhere(values as T?, andPart as SqlExpressionBuilder.(Any) -> Op<Boolean>)
}
fun <T : Any> andWhere(
valueDefault: T?,
valueAll: List<T>?,
valueAny: List<T>?,
expr: SqlExpressionBuilder.(T) -> Op<Boolean>,
) {
andWhere(valueDefault, expr)
andWhereAll(valueAll, expr)
andWhereAny(valueAny, expr)
}
fun <T : Any> andWhereAll(
values: List<T>?,
andPart: SqlExpressionBuilder.(T) -> Op<Boolean>,
) {
values?.map { andWhere(it, andPart) }
}
fun <T : Any> andWhereAny(
values: List<T>?,
andPart: SqlExpressionBuilder.(T) -> Op<Boolean>,
) {
values ?: return
val expr = Op.build { andPart(values) }
val expr = values.map { Op.build { andPart(it) } }.reduce { acc, op -> acc or op }
op = if (op == null) expr else (op!! and expr)
}
@@ -414,22 +526,6 @@ class OpAnd(var op: Op<Boolean>? = null) {
value: T?,
column: Column<EntityID<T>>,
) = andWhere(value) { column eq it }
fun <T> inList(
values: List<T>?,
column: Column<T>,
) = andWhere(values) { column inList it }
@JvmName("inListComparable")
fun <T : Comparable<T>> inList(
values: List<T>?,
column: Column<EntityID<T>>,
) = andWhere(values) { column inList it }
fun like(
value: String?,
column: Column<String?>,
) = andWhere(value) { column like it }
}
fun <T : Comparable<T>, S : T?> andFilterWithCompare(
@@ -451,15 +547,8 @@ fun <T : Comparable<T>> andFilterWithCompareEntity(
column: Column<EntityID<T>>,
filter: ComparableScalarFilter<T>?,
): Op<Boolean>? {
filter ?: return null
val opAnd = OpAnd(andFilterEntity(column, filter))
opAnd.andWhere(filter.lessThan) { column less it }
opAnd.andWhere(filter.lessThanOrEqualTo) { column lessEq it }
opAnd.andWhere(filter.greaterThan) { column greater it }
opAnd.andWhere(filter.greaterThanOrEqualTo) { column greaterEq it }
return opAnd.op
@Suppress("UNCHECKED_CAST")
return andFilterWithCompare(column as Column<T>, filter)
}
@Suppress("UNCHECKED_CAST")
@@ -472,8 +561,8 @@ fun <T : Comparable<T>, S : T?> andFilter(
opAnd.andWhere(filter.isNull) { if (it) column.isNull() else column.isNotNull() }
opAnd.andWhere(filter.equalTo) { column eq it as S }
opAnd.andWhere(filter.notEqualTo) { column neq it as S }
opAnd.andWhere(filter.distinctFrom) { DistinctFromOp.distinctFrom(column, it as S) }
opAnd.andWhere(filter.notEqualTo, filter.notEqualToAll, filter.notEqualToAny) { column neq it as S }
opAnd.andWhere(filter.distinctFrom, filter.distinctFromAll, filter.distinctFromAny) { DistinctFromOp.distinctFrom(column, it as S) }
opAnd.andWhere(filter.notDistinctFrom) { DistinctFromOp.notDistinctFrom(column, it as S) }
if (!filter.`in`.isNullOrEmpty()) {
opAnd.andWhere(filter.`in`) { column inList it as List<S> }
@@ -483,24 +572,3 @@ fun <T : Comparable<T>, S : T?> andFilter(
}
return opAnd.op
}
fun <T : Comparable<T>> andFilterEntity(
column: Column<EntityID<T>>,
filter: ScalarFilter<T>?,
): Op<Boolean>? {
filter ?: return null
val opAnd = OpAnd()
opAnd.andWhere(filter.isNull) { if (filter.isNull!!) column.isNull() else column.isNotNull() }
opAnd.andWhere(filter.equalTo) { column eq filter.equalTo!! }
opAnd.andWhere(filter.notEqualTo) { column neq filter.notEqualTo!! }
opAnd.andWhere(filter.distinctFrom) { DistinctFromOp.distinctFrom(column, it) }
opAnd.andWhere(filter.notDistinctFrom) { DistinctFromOp.notDistinctFrom(column, it) }
if (!filter.`in`.isNullOrEmpty()) {
opAnd.andWhere(filter.`in`) { column inList filter.`in`!! }
}
if (!filter.notIn.isNullOrEmpty()) {
opAnd.andWhere(filter.notIn) { column notInList filter.notIn!! }
}
return opAnd.op
}