Skip to content

Commit

Permalink
Merge pull request #31 from ergon/feature/dope-190-any-every-satisfies
Browse files Browse the repository at this point in the history
Feature/dope 190 any every satisfies
  • Loading branch information
jansigi committed Jun 26, 2024
2 parents ed399e6 + c546a4f commit 92fd855
Show file tree
Hide file tree
Showing 6 changed files with 664 additions and 1 deletion.
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,12 @@ 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

interface Clause : Resolvable {
fun build(): DopeQuery {
ParameterManager.resetCounter()
IteratorManager.resetCounter()
return toDopeQuery()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
package ch.ergon.dope.resolvable.expression.unaliased.type.relational

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.toArrayType
import ch.ergon.dope.validtype.ArrayType
import ch.ergon.dope.validtype.BooleanType
import ch.ergon.dope.validtype.ValidType

object IteratorManager {
var count: Int = 1
get() = field++

fun resetCounter() {
count = 1
}
}

const val DEFAULT_ITERATOR_VARIABLE = "iterator"

enum class SatisfiesType {
ANY,
EVERY,
}

sealed class SatisfiesExpression<T : ValidType>(
private val satisfiesType: SatisfiesType,
private val list: TypeExpression<ArrayType<T>>,
private val variable: String,
private val predicate: (Iterator<T>) -> TypeExpression<BooleanType>,
) : TypeExpression<BooleanType> {
override fun toDopeQuery(): DopeQuery {
val listDopeQuery = list.toDopeQuery()
val iteratorVariable = if (variable == DEFAULT_ITERATOR_VARIABLE) variable + IteratorManager.count else variable

val predicateDopeQuery = predicate(Iterator(iteratorVariable)).toDopeQuery()
return DopeQuery(
queryString = "$satisfiesType `$iteratorVariable` IN ${listDopeQuery.queryString} SATISFIES ${predicateDopeQuery.queryString} END",
parameters = listDopeQuery.parameters + predicateDopeQuery.parameters,
)
}
}

class Iterator<T : ValidType>(private val variable: String) : TypeExpression<T> {
override fun toDopeQuery() = DopeQuery(
queryString = "`$variable`",
parameters = emptyMap(),
)
}

class AnySatisfiesExpression<T : ValidType>(
list: TypeExpression<ArrayType<T>>,
variable: String = DEFAULT_ITERATOR_VARIABLE,
predicate: (Iterator<T>) -> TypeExpression<BooleanType>,
) : SatisfiesExpression<T>(ANY, list, variable, predicate)

class EverySatisfiesExpression<T : ValidType>(
list: TypeExpression<ArrayType<T>>,
variable: String = DEFAULT_ITERATOR_VARIABLE,
predicate: (Iterator<T>) -> TypeExpression<BooleanType>,
) : SatisfiesExpression<T>(EVERY, list, variable, predicate)

fun <T : ValidType> TypeExpression<ArrayType<T>>.any(
variable: String = DEFAULT_ITERATOR_VARIABLE,
predicate: (Iterator<T>) -> TypeExpression<BooleanType>,
): AnySatisfiesExpression<T> = AnySatisfiesExpression(this, variable, predicate)

fun <T : ValidType> Collection<TypeExpression<T>>.any(
variable: String = DEFAULT_ITERATOR_VARIABLE,
predicate: (Iterator<T>) -> TypeExpression<BooleanType>,
): AnySatisfiesExpression<T> = AnySatisfiesExpression(toArrayType(), variable, predicate)

fun <T : ValidType> TypeExpression<ArrayType<T>>.every(
variable: String = DEFAULT_ITERATOR_VARIABLE,
predicate: (Iterator<T>) -> TypeExpression<BooleanType>,
): EverySatisfiesExpression<T> = EverySatisfiesExpression(this, variable, predicate)

fun <T : ValidType> Collection<TypeExpression<T>>.every(
variable: String = DEFAULT_ITERATOR_VARIABLE,
predicate: (Iterator<T>) -> TypeExpression<BooleanType>,
): EverySatisfiesExpression<T> = EverySatisfiesExpression(toArrayType(), variable, predicate)
241 changes: 241 additions & 0 deletions core/src/test/kotlin/ch/ergon/dope/SatisfiesTest.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,241 @@
package ch.ergon.dope

import ch.ergon.dope.helper.someBooleanArrayField
import ch.ergon.dope.helper.someNumberArrayField
import ch.ergon.dope.helper.someStringArrayField
import ch.ergon.dope.helper.someStringField
import ch.ergon.dope.resolvable.expression.unaliased.type.ParameterManager
import ch.ergon.dope.resolvable.expression.unaliased.type.arithmetic.mod
import ch.ergon.dope.resolvable.expression.unaliased.type.logical.and
import ch.ergon.dope.resolvable.expression.unaliased.type.relational.AnySatisfiesExpression
import ch.ergon.dope.resolvable.expression.unaliased.type.relational.EverySatisfiesExpression
import ch.ergon.dope.resolvable.expression.unaliased.type.relational.IteratorManager
import ch.ergon.dope.resolvable.expression.unaliased.type.relational.any
import ch.ergon.dope.resolvable.expression.unaliased.type.relational.every
import ch.ergon.dope.resolvable.expression.unaliased.type.relational.isEqualTo
import ch.ergon.dope.resolvable.expression.unaliased.type.stringfunction.upper
import org.junit.jupiter.api.BeforeEach
import kotlin.test.Test
import kotlin.test.assertEquals

class SatisfiesTest {

@BeforeEach
fun setup() {
ParameterManager.resetCounter()
IteratorManager.resetCounter()
}

@Test
fun `should support any satisfies number`() {
val expected = DopeQuery(
queryString = "ANY `iterator1` IN `numberArrayField` SATISFIES (`iterator1` % 2) = 1 END",
parameters = emptyMap(),
)

val actual = AnySatisfiesExpression(someNumberArrayField()) { x -> x.mod(2).isEqualTo(1) }.toDopeQuery()

assertEquals(expected, actual)
}

@Test
fun `should support any satisfies string`() {
val expected = DopeQuery(
queryString = "ANY `iterator1` IN `stringArrayField` SATISFIES UPPER(`iterator1`) = \"A\" END",
parameters = emptyMap(),
)

val actual = AnySatisfiesExpression(someStringArrayField()) { x -> upper(x).isEqualTo("A") }.toDopeQuery()

assertEquals(expected, actual)
}

@Test
fun `should support any satisfies boolean`() {
val expected = DopeQuery(
queryString = "ANY `iterator1` IN `booleanArrayField` SATISFIES `iterator1` END",
parameters = emptyMap(),
)

val actual = AnySatisfiesExpression(someBooleanArrayField()) { it }.toDopeQuery()

assertEquals(expected, actual)
}

@Test
fun `should support query with any satisfies`() {
val expected = DopeQuery(
queryString = "(`firstName` = \"Hans\" AND ANY `iterator1` IN `hobbies` SATISFIES `iterator1` = \"Football\" END)",
parameters = emptyMap(),
)

val actual = someStringField("firstName").isEqualTo("Hans")
.and(someStringArrayField("hobbies").any { it.isEqualTo("Football") }).toDopeQuery()

assertEquals(expected, actual)
}

@Test
fun `should support query with any satisfies and named iterator`() {
val expected = DopeQuery(
queryString = "ANY `hobby` IN `hobbies` SATISFIES `hobby` = \"Football\" END",
parameters = emptyMap(),
)

val actual = someStringArrayField("hobbies").any("hobby") { it.isEqualTo("Football") }.toDopeQuery()

assertEquals(expected, actual)
}

@Test
fun `should support any satisfies with collection`() {
val expected = DopeQuery(
queryString = "ANY `iterator1` IN [`stringField`, `stringField`] SATISFIES `iterator1` = \"something\" END",
parameters = emptyMap(),
)

val actual = listOf(someStringField(), someStringField()).any { it.isEqualTo("something") }.toDopeQuery()

assertEquals(expected, actual)
}

@Test
fun `should support query any satisfies with named iterator`() {
val expected = DopeQuery(
queryString = "(`firstName` = \"Hans\" AND ANY `hobby` IN `hobbies` SATISFIES `hobby` = \"Football\" END)",
parameters = emptyMap(),
)

val actual = someStringField("firstName").isEqualTo("Hans")
.and(someStringArrayField("hobbies").any("hobby") { it.isEqualTo("Football") })
.toDopeQuery()

assertEquals(expected, actual)
}

@Test
fun `should support nested any satisfies`() {
val expected = DopeQuery(
queryString = "ANY `iterator1` IN `stringArrayField` SATISFIES " +
"ANY `iterator2` IN `stringArrayField` SATISFIES `iterator2` = `iterator1` END END",
parameters = emptyMap(),
)

val actual = someStringArrayField().any { str1 -> someStringArrayField().any { it.isEqualTo(str1) } }.toDopeQuery()

assertEquals(expected, actual)
}

@Test
fun `should support every satisfies string`() {
val expected = DopeQuery(
queryString = "EVERY `iterator1` IN `stringArrayField` SATISFIES UPPER(`iterator1`) = \"A\" END",
parameters = emptyMap(),
)

val actual = EverySatisfiesExpression(someStringArrayField()) { x -> upper(x).isEqualTo("A") }.toDopeQuery()

assertEquals(expected, actual)
}

@Test
fun `should support every satisfies number`() {
val expected = DopeQuery(
queryString = "EVERY `iterator1` IN `numberArrayField` SATISFIES (`iterator1` % 2) = 1 END",
parameters = emptyMap(),
)

val actual = EverySatisfiesExpression(someNumberArrayField()) { x -> x.mod(2).isEqualTo(1) }.toDopeQuery()

assertEquals(expected, actual)
}

@Test
fun `should support every satisfies boolean`() {
val expected = DopeQuery(
queryString = "EVERY `iterator1` IN `booleanArrayField` SATISFIES `iterator1` END",
parameters = emptyMap(),
)

val actual = EverySatisfiesExpression(someBooleanArrayField()) { it }.toDopeQuery()

assertEquals(expected, actual)
}

@Test
fun `should support query with every satisfies and named iterator`() {
val expected = DopeQuery(
queryString = "EVERY `hobby` IN `hobbies` SATISFIES `hobby` = \"Football\" END",
parameters = emptyMap(),
)

val actual = someStringArrayField("hobbies").every("hobby") { it.isEqualTo("Football") }.toDopeQuery()

assertEquals(expected, actual)
}

@Test
fun `should support query with every satisfies`() {
val expected = DopeQuery(
queryString = "(`firstName` = \"Hans\" AND EVERY `iterator1` IN `hobbies` " +
"SATISFIES `iterator1` = \"Football\" END)",
parameters = emptyMap(),
)

val actual =
someStringField("firstName").isEqualTo("Hans").and(someStringArrayField("hobbies").every { it.isEqualTo("Football") }).toDopeQuery()

assertEquals(expected, actual)
}

@Test
fun `should support every satisfies with collection`() {
val expected = DopeQuery(
queryString = "EVERY `iterator1` IN [`stringField`, `stringField`] SATISFIES `iterator1` = \"something\" END",
parameters = emptyMap(),
)

val actual = listOf(someStringField(), someStringField()).every { it.isEqualTo("something") }.toDopeQuery()

assertEquals(expected, actual)
}

@Test
fun `should support query every satisfies with named iterator`() {
val expected = DopeQuery(
queryString = "(`firstName` = \"Hans\" AND EVERY `hobby` IN `hobbies` SATISFIES `hobby` = \"Football\" END)",
parameters = emptyMap(),
)

val actual = someStringField("firstName").isEqualTo("Hans")
.and(someStringArrayField("hobbies").every("hobby") { it.isEqualTo("Football") }).toDopeQuery()

assertEquals(expected, actual)
}

@Test
fun `should support nested every satisfies`() {
val expected = DopeQuery(
queryString = "EVERY `iterator1` IN `stringArrayField` SATISFIES " +
"EVERY `iterator2` IN `stringArrayField` SATISFIES `iterator2` = `iterator1` END END",
parameters = emptyMap(),
)

val actual = someStringArrayField().every { str1 -> someStringArrayField().every { it.isEqualTo(str1) } }.toDopeQuery()

assertEquals(expected, actual)
}

@Test
fun `should support mixed every and any satisfies`() {
val expected = DopeQuery(
queryString = "EVERY `iterator1` IN `stringArrayField` SATISFIES " +
"ANY `iterator2` IN `stringArrayField` SATISFIES `iterator2` = `iterator1` END END",
parameters = emptyMap(),
)

val actual = someStringArrayField().every { str1 -> someStringArrayField().any { it.isEqualTo(str1) } }.toDopeQuery()

assertEquals(expected, actual)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -47,4 +47,4 @@ fun CMList<Boolean>.asArrayField(): Field<ArrayType<BooleanType>> = Field(name,
fun CMList<out Any>.asArrayField(): Field<ArrayType<ValidType>> = Field(name, path)

// TODO: DOPE-192
fun CMObjectList<Schema>.asSchemaArray() = DopeSchemaArray(element, formatPathToQueryString(name, path))
fun <T : Schema> CMObjectList<T>.asSchemaArray() = DopeSchemaArray(element, formatPathToQueryString(name, path))
Loading

0 comments on commit 92fd855

Please sign in to comment.