Skip to content

Commit

Permalink
Merge pull request #38 from ergon/feature/dope-229-existence-membersh…
Browse files Browse the repository at this point in the history
…ip-predicates

DOPE-229: existence and membership predicates
  • Loading branch information
martinagallati-ergon committed Jul 10, 2024
2 parents b6f8ec8 + 9c7a69b commit 7f54bf9
Show file tree
Hide file tree
Showing 23 changed files with 1,171 additions and 29 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ package ch.ergon.dope.resolvable.clause
import ch.ergon.dope.DopeQuery
import ch.ergon.dope.resolvable.Resolvable
import ch.ergon.dope.resolvable.expression.unaliased.type.ParameterManager
import ch.ergon.dope.resolvable.expression.unaliased.type.relational.IteratorManager
import ch.ergon.dope.resolvable.expression.unaliased.type.collection.IteratorManager

interface Clause : Resolvable {
fun build(): DopeQuery {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package ch.ergon.dope.resolvable.expression.unaliased.type.collection

import ch.ergon.dope.DopeQuery
import ch.ergon.dope.resolvable.expression.TypeExpression
import ch.ergon.dope.resolvable.expression.unaliased.type.toDopeType
import ch.ergon.dope.validtype.ArrayType
import ch.ergon.dope.validtype.BooleanType
import ch.ergon.dope.validtype.ValidType

class ExistsExpression<T : ValidType>(private val array: TypeExpression<ArrayType<T>>) : TypeExpression<BooleanType> {
override fun toDopeQuery(): DopeQuery {
val arrayDopeQuery = array.toDopeQuery()
return DopeQuery(
queryString = "EXISTS ${arrayDopeQuery.queryString}",
parameters = arrayDopeQuery.parameters,
)
}
}

fun <T : ValidType> exists(array: TypeExpression<ArrayType<T>>) = ExistsExpression(array)

fun <T : ValidType> exists(array: Collection<TypeExpression<T>>) = exists(array.toDopeType())
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
package ch.ergon.dope.resolvable.expression.unaliased.type.relational
package ch.ergon.dope.resolvable.expression.unaliased.type.collection

import ch.ergon.dope.DopeQuery
import ch.ergon.dope.resolvable.expression.TypeExpression
import ch.ergon.dope.resolvable.expression.unaliased.type.toDopeType
import ch.ergon.dope.resolvable.operator.InfixOperator
import ch.ergon.dope.validtype.ArrayType
import ch.ergon.dope.validtype.BooleanType
import ch.ergon.dope.validtype.NumberType
Expand All @@ -13,9 +11,7 @@ import ch.ergon.dope.validtype.ValidType
class InExpression<T : ValidType>(
value: TypeExpression<T>,
collection: TypeExpression<ArrayType<T>>,
) : TypeExpression<BooleanType>, InfixOperator(value, "IN", collection) {
override fun toDopeQuery(): DopeQuery = toInfixDopeQuery()
}
) : MembershipExpression<T>(value, "IN", collection)

fun <T : ValidType> TypeExpression<T>.inArray(array: TypeExpression<ArrayType<T>>): InExpression<T> =
InExpression(this, array)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package ch.ergon.dope.resolvable.expression.unaliased.type.collection

import ch.ergon.dope.resolvable.expression.TypeExpression
import ch.ergon.dope.resolvable.operator.InfixOperator
import ch.ergon.dope.validtype.ArrayType
import ch.ergon.dope.validtype.BooleanType
import ch.ergon.dope.validtype.ValidType

sealed class MembershipExpression<T : ValidType>(
value: TypeExpression<T>,
symbol: String,
collection: TypeExpression<ArrayType<T>>,
) : TypeExpression<BooleanType>, InfixOperator(value, symbol, collection) {
override fun toDopeQuery() = toInfixDopeQuery()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package ch.ergon.dope.resolvable.expression.unaliased.type.collection

import ch.ergon.dope.resolvable.expression.TypeExpression
import ch.ergon.dope.resolvable.expression.unaliased.type.toDopeType
import ch.ergon.dope.validtype.ArrayType
import ch.ergon.dope.validtype.BooleanType
import ch.ergon.dope.validtype.NumberType
import ch.ergon.dope.validtype.StringType
import ch.ergon.dope.validtype.ValidType

class NotInExpression<T : ValidType>(
value: TypeExpression<T>,
collection: TypeExpression<ArrayType<T>>,
) : MembershipExpression<T>(value, "NOT IN", collection)

fun <T : ValidType> TypeExpression<T>.notInArray(array: TypeExpression<ArrayType<T>>): NotInExpression<T> =
NotInExpression(this, array)

fun Number.notInArray(array: TypeExpression<ArrayType<NumberType>>): NotInExpression<NumberType> =
toDopeType().notInArray(array)

fun String.notInArray(array: TypeExpression<ArrayType<StringType>>): NotInExpression<StringType> =
toDopeType().notInArray(array)

fun Boolean.notInArray(array: TypeExpression<ArrayType<BooleanType>>): NotInExpression<BooleanType> =
toDopeType().notInArray(array)

fun <T : ValidType> TypeExpression<T>.notInArray(array: Collection<TypeExpression<T>>): NotInExpression<T> =
this.notInArray(array.toDopeType())

fun Number.notInArray(array: Collection<TypeExpression<NumberType>>): NotInExpression<NumberType> =
notInArray(array.toDopeType())

fun String.notInArray(array: Collection<TypeExpression<StringType>>): NotInExpression<StringType> =
notInArray(array.toDopeType())

fun Boolean.notInArray(array: Collection<TypeExpression<BooleanType>>): NotInExpression<BooleanType> =
notInArray(array.toDopeType())
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package ch.ergon.dope.resolvable.expression.unaliased.type.collection

import ch.ergon.dope.resolvable.expression.TypeExpression
import ch.ergon.dope.resolvable.expression.unaliased.type.toDopeType
import ch.ergon.dope.validtype.ArrayType
import ch.ergon.dope.validtype.BooleanType
import ch.ergon.dope.validtype.NumberType
import ch.ergon.dope.validtype.StringType
import ch.ergon.dope.validtype.ValidType

class NotWithinExpression<T : ValidType>(
value: TypeExpression<T>,
collection: TypeExpression<ArrayType<T>>,
) : MembershipExpression<T>(value, "NOT WITHIN", collection)

fun <T : ValidType> TypeExpression<T>.notWithinArray(collection: TypeExpression<ArrayType<T>>) = NotWithinExpression(this, collection)

fun Number.notWithinArray(collection: TypeExpression<ArrayType<NumberType>>): NotWithinExpression<NumberType> =
toDopeType().notWithinArray(collection)

fun String.notWithinArray(collection: TypeExpression<ArrayType<StringType>>): NotWithinExpression<StringType> =
toDopeType().notWithinArray(collection)

fun Boolean.notWithinArray(collection: TypeExpression<ArrayType<BooleanType>>): NotWithinExpression<BooleanType> =
toDopeType().notWithinArray(collection)

fun <T : ValidType> TypeExpression<T>.notWithinArray(collection: Collection<TypeExpression<T>>): NotWithinExpression<T> =
this.notWithinArray(collection.toDopeType())

fun Number.notWithinArray(collection: Collection<TypeExpression<NumberType>>): NotWithinExpression<NumberType> =
notWithinArray(collection.toDopeType())

fun String.notWithinArray(collection: Collection<TypeExpression<StringType>>): NotWithinExpression<StringType> =
notWithinArray(collection.toDopeType())

fun Boolean.notWithinArray(collection: Collection<TypeExpression<BooleanType>>): NotWithinExpression<BooleanType> =
notWithinArray(collection.toDopeType())
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
package ch.ergon.dope.resolvable.expression.unaliased.type.relational
package ch.ergon.dope.resolvable.expression.unaliased.type.collection

import ch.ergon.dope.DopeQuery
import ch.ergon.dope.resolvable.expression.TypeExpression
import ch.ergon.dope.resolvable.expression.unaliased.type.relational.SatisfiesType.ANY
import ch.ergon.dope.resolvable.expression.unaliased.type.relational.SatisfiesType.EVERY
import ch.ergon.dope.resolvable.expression.unaliased.type.collection.SatisfiesType.ANY
import ch.ergon.dope.resolvable.expression.unaliased.type.collection.SatisfiesType.EVERY
import ch.ergon.dope.resolvable.expression.unaliased.type.toDopeType
import ch.ergon.dope.validtype.ArrayType
import ch.ergon.dope.validtype.BooleanType
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package ch.ergon.dope.resolvable.expression.unaliased.type.collection

import ch.ergon.dope.resolvable.expression.TypeExpression
import ch.ergon.dope.resolvable.expression.unaliased.type.toDopeType
import ch.ergon.dope.validtype.ArrayType
import ch.ergon.dope.validtype.BooleanType
import ch.ergon.dope.validtype.NumberType
import ch.ergon.dope.validtype.StringType
import ch.ergon.dope.validtype.ValidType

class WithinExpression<T : ValidType>(
value: TypeExpression<T>,
collection: TypeExpression<ArrayType<T>>,
) : MembershipExpression<T>(value, "WITHIN", collection)

fun <T : ValidType> TypeExpression<T>.withinArray(array: TypeExpression<ArrayType<T>>): WithinExpression<T> =
WithinExpression(this, array)

fun Number.withinArray(array: TypeExpression<ArrayType<NumberType>>): WithinExpression<NumberType> =
toDopeType().withinArray(array)

fun String.withinArray(array: TypeExpression<ArrayType<StringType>>): WithinExpression<StringType> =
toDopeType().withinArray(array)

fun Boolean.withinArray(array: TypeExpression<ArrayType<BooleanType>>): WithinExpression<BooleanType> =
toDopeType().withinArray(array)

fun <T : ValidType> TypeExpression<T>.withinArray(array: Collection<TypeExpression<T>>): WithinExpression<T> =
this.withinArray(array.toDopeType())

fun Number.withinArray(array: Collection<TypeExpression<NumberType>>): WithinExpression<NumberType> =
withinArray(array.toDopeType())

fun String.withinArray(array: Collection<TypeExpression<StringType>>): WithinExpression<StringType> =
withinArray(array.toDopeType())

fun Boolean.withinArray(array: Collection<TypeExpression<BooleanType>>): WithinExpression<BooleanType> =
withinArray(array.toDopeType())
9 changes: 8 additions & 1 deletion core/src/test/kotlin/ch/ergon/dope/ArrayTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,16 @@ import ch.ergon.dope.helper.someStringArrayField
import ch.ergon.dope.helper.someStringField
import ch.ergon.dope.resolvable.expression.alias
import ch.ergon.dope.resolvable.expression.unaliased.type.FALSE
import ch.ergon.dope.resolvable.expression.unaliased.type.ParameterManager
import ch.ergon.dope.resolvable.expression.unaliased.type.TRUE
import ch.ergon.dope.resolvable.expression.unaliased.type.access.get
import ch.ergon.dope.resolvable.expression.unaliased.type.arithmetic.add
import ch.ergon.dope.resolvable.expression.unaliased.type.relational.inArray
import ch.ergon.dope.resolvable.expression.unaliased.type.collection.inArray
import ch.ergon.dope.resolvable.expression.unaliased.type.relational.isEqualTo
import ch.ergon.dope.resolvable.expression.unaliased.type.stringfunction.concat
import ch.ergon.dope.resolvable.expression.unaliased.type.toDopeType
import junit.framework.TestCase.assertEquals
import org.junit.jupiter.api.BeforeEach
import kotlin.test.BeforeTest
import kotlin.test.Test

Expand All @@ -27,6 +29,11 @@ class ArrayTest {
create = QueryBuilder()
}

@BeforeEach
fun reset() {
ParameterManager.resetCounter()
}

@Test
fun `should support arrays`() {
val person = someBucket("person")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@ import ch.ergon.dope.helper.someStringField
import ch.ergon.dope.helper.unifyString
import ch.ergon.dope.resolvable.expression.alias
import ch.ergon.dope.resolvable.expression.unaliased.type.asParameter
import ch.ergon.dope.resolvable.expression.unaliased.type.collection.inArray
import ch.ergon.dope.resolvable.expression.unaliased.type.logical.and
import ch.ergon.dope.resolvable.expression.unaliased.type.logical.or
import ch.ergon.dope.resolvable.expression.unaliased.type.relational.inArray
import ch.ergon.dope.resolvable.expression.unaliased.type.relational.isEqualTo
import ch.ergon.dope.resolvable.expression.unaliased.type.stringfunction.concat
import ch.ergon.dope.resolvable.expression.unaliased.type.toDopeType
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
package ch.ergon.dope.operators.collection

import ch.ergon.dope.DopeQuery
import ch.ergon.dope.helper.someNumberArrayField
import ch.ergon.dope.helper.someNumberField
import ch.ergon.dope.resolvable.expression.unaliased.type.ParameterManager
import ch.ergon.dope.resolvable.expression.unaliased.type.asParameter
import ch.ergon.dope.resolvable.expression.unaliased.type.collection.ExistsExpression
import ch.ergon.dope.resolvable.expression.unaliased.type.collection.exists
import ch.ergon.dope.resolvable.expression.unaliased.type.toDopeType
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test

class ExistsExpressionTest {
@BeforeEach
fun reset() {
ParameterManager.resetCounter()
}

@Test
fun `should support EXISTS expression`() {
val expected = DopeQuery(
"EXISTS `numberArrayField`",
emptyMap(),
)
val underTest = ExistsExpression(someNumberArrayField())

val actual = underTest.toDopeQuery()

assertEquals(expected, actual)
}

@Test
fun `should support EXISTS expression with parameter`() {
val parameterValue = listOf(1, 2, 3)
val expected = DopeQuery(
"EXISTS $1",
mapOf("$1" to parameterValue),
)
val underTest = ExistsExpression(parameterValue.asParameter())

val actual = underTest.toDopeQuery()

assertEquals(expected, actual)
}

@Test
fun `should support EXISTS extension`() {
val array = someNumberArrayField()
val expected = ExistsExpression(array)

val actual = exists(array)

assertEquals(expected.toDopeQuery(), actual.toDopeQuery())
}

@Test
fun `should support EXISTS extension collection`() {
val array = listOf(someNumberField(), someNumberField())
val expected = ExistsExpression(array.toDopeType())

val actual = exists(array)

assertEquals(expected.toDopeQuery(), actual.toDopeQuery())
}
}
Loading

0 comments on commit 7f54bf9

Please sign in to comment.