Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

DOPE-255: add let clause with tests #63

Merged
merged 7 commits into from
Dec 17, 2024
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
package ch.ergon.dope.resolvable.clause

import ch.ergon.dope.resolvable.clause.model.AliasedUnnestClause
import ch.ergon.dope.resolvable.clause.model.DopeVariable
import ch.ergon.dope.resolvable.clause.model.FromClause
import ch.ergon.dope.resolvable.clause.model.GroupByClause
import ch.ergon.dope.resolvable.clause.model.InnerJoinClause
import ch.ergon.dope.resolvable.clause.model.LeftJoinClause
import ch.ergon.dope.resolvable.clause.model.LetClause
import ch.ergon.dope.resolvable.clause.model.OrderByType
import ch.ergon.dope.resolvable.clause.model.RightJoinClause
import ch.ergon.dope.resolvable.clause.model.SelectLimitClause
Expand Down Expand Up @@ -60,7 +62,15 @@ interface ISelectFromClause<T : ValidType> : ISelectWhereClause<T> {
fun where(whereExpression: TypeExpression<BooleanType>) = SelectWhereClause(whereExpression, this)
}

interface ISelectJoinClause<T : ValidType> : ISelectFromClause<T> {
interface ISelectLetClause<T : ValidType> : ISelectFromClause<T> {
fun withVariables(dopeVariable: DopeVariable<out ValidType>, vararg dopeVariables: DopeVariable<out ValidType>) = LetClause(
jansigi marked this conversation as resolved.
Show resolved Hide resolved
jansigi marked this conversation as resolved.
Show resolved Hide resolved
dopeVariable,
*dopeVariables,
parentClause = this,
)
}

interface ISelectJoinClause<T : ValidType> : ISelectLetClause<T> {
fun join(
joinable: Joinable,
onCondition: TypeExpression<BooleanType>,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package ch.ergon.dope.resolvable.clause.model

import ch.ergon.dope.DopeQuery
import ch.ergon.dope.DopeQueryManager
import ch.ergon.dope.resolvable.clause.ISelectLetClause
import ch.ergon.dope.resolvable.expression.TypeExpression
import ch.ergon.dope.resolvable.expression.unaliased.type.toDopeType
import ch.ergon.dope.resolvable.formatToQueryStringWithSymbol
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 LetClause<T : ValidType>(
jansigi marked this conversation as resolved.
Show resolved Hide resolved
private val dopeVariable: DopeVariable<out ValidType>,
private vararg val dopeVariables: DopeVariable<out ValidType>,
private val parentClause: ISelectLetClause<T>,
) : ISelectLetClause<T> {
override fun toDopeQuery(manager: DopeQueryManager): DopeQuery {
val parentDopeQuery = parentClause.toDopeQuery(manager)
val letExpressionDopeQuery = dopeVariable.toDefinitionDopeQuery(manager)
jansigi marked this conversation as resolved.
Show resolved Hide resolved
val letExpressionDopeQueries = dopeVariables.map { it.toDefinitionDopeQuery(manager) }
jansigi marked this conversation as resolved.
Show resolved Hide resolved

return DopeQuery(
queryString = formatToQueryStringWithSymbol(
parentDopeQuery.queryString,
"LET",
letExpressionDopeQuery.queryString,
*letExpressionDopeQueries.map { it.queryString }.toTypedArray(),
),
parameters = parentDopeQuery.parameters.merge(
letExpressionDopeQuery.parameters,
*letExpressionDopeQueries.map { it.parameters }.toTypedArray(),
),
)
}
}

class DopeVariable<T : ValidType>(private val name: String, private val value: TypeExpression<T>) : TypeExpression<T> {
jansigi marked this conversation as resolved.
Show resolved Hide resolved
override fun toDopeQuery(manager: DopeQueryManager): DopeQuery {
return DopeQuery(
queryString = "`$name`",
)
}

fun toDefinitionDopeQuery(manager: DopeQueryManager): DopeQuery {
val valueDopeQuery = value.toDopeQuery(manager)
return DopeQuery(
queryString = "`$name` = ${valueDopeQuery.queryString}",
parameters = valueDopeQuery.parameters,
)
}
}

fun <T : ValidType> String.assignTo(expression: TypeExpression<T>): DopeVariable<T> = DopeVariable(this, expression)
jansigi marked this conversation as resolved.
Show resolved Hide resolved

fun String.assignTo(expression: Number): DopeVariable<NumberType> = assignTo(expression.toDopeType())
jansigi marked this conversation as resolved.
Show resolved Hide resolved

fun String.assignTo(expression: String): DopeVariable<StringType> = assignTo(expression.toDopeType())
jansigi marked this conversation as resolved.
Show resolved Hide resolved

fun String.assignTo(expression: Boolean): DopeVariable<BooleanType> = assignTo(expression.toDopeType())
jansigi marked this conversation as resolved.
Show resolved Hide resolved
31 changes: 31 additions & 0 deletions core/src/test/kotlin/ch/ergon/dope/buildTest/QueryBuilderTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,10 @@ import ch.ergon.dope.helper.someBooleanField
import ch.ergon.dope.helper.someBucket
import ch.ergon.dope.helper.someFromClause
import ch.ergon.dope.helper.someNumberField
import ch.ergon.dope.helper.someStringArrayField
import ch.ergon.dope.helper.someStringField
import ch.ergon.dope.helper.unifyString
import ch.ergon.dope.resolvable.clause.model.assignTo
import ch.ergon.dope.resolvable.clause.model.setoperator.except
import ch.ergon.dope.resolvable.clause.model.setoperator.exceptAll
import ch.ergon.dope.resolvable.clause.model.setoperator.intersect
Expand All @@ -20,6 +22,8 @@ import ch.ergon.dope.resolvable.expression.unaliased.type.FALSE
import ch.ergon.dope.resolvable.expression.unaliased.type.MISSING
import ch.ergon.dope.resolvable.expression.unaliased.type.NULL
import ch.ergon.dope.resolvable.expression.unaliased.type.TRUE
import ch.ergon.dope.resolvable.expression.unaliased.type.collection.any
import ch.ergon.dope.resolvable.expression.unaliased.type.collection.inArray
import ch.ergon.dope.resolvable.expression.unaliased.type.conditional.case
import ch.ergon.dope.resolvable.expression.unaliased.type.conditional.condition
import ch.ergon.dope.resolvable.expression.unaliased.type.conditional.otherwise
Expand All @@ -35,6 +39,7 @@ import ch.ergon.dope.resolvable.expression.unaliased.type.relational.isLessOrEqu
import ch.ergon.dope.resolvable.expression.unaliased.type.relational.isLessThan
import ch.ergon.dope.resolvable.expression.unaliased.type.relational.isLike
import ch.ergon.dope.resolvable.expression.unaliased.type.relational.isNotEqualTo
import ch.ergon.dope.resolvable.expression.unaliased.type.relational.isNotNull
import ch.ergon.dope.resolvable.expression.unaliased.type.toDopeType
import ch.ergon.dope.resolvable.fromable.asterisk
import kotlin.test.BeforeTest
Expand Down Expand Up @@ -896,4 +901,30 @@ class QueryBuilderTest : ManagerDependentTest {

assertEquals(expected, actual)
}

@Test
fun `should support selecting let clause`() {
jansigi marked this conversation as resolved.
Show resolved Hide resolved
val t1 = someBucket("route").alias("t1")
val equip = "equip".assignTo(someStringArrayField("equipment", t1).any { it.isEqualTo("radio") })
val sourceAirport = someStringField("sourceAirport", someBucket("route").alias("t2"))
val sourceAirports = "source_airports".assignTo(create.selectRaw(sourceAirport).where(sourceAirport.isNotNull()).asExpression())
val destinationAirport = someStringField("destinationAirport", t1)

val expected = "SELECT `t1`.`destinationAirport`, `equip` AS `has_radio` " +
"FROM `route` AS `t1` " +
"LET `equip` = ANY `iterator1` IN `t1`.`equipment` SATISFIES `iterator1` = \"radio\" END, " +
"`source_airports` = (SELECT RAW `t2`.`sourceAirport` WHERE `t2`.`sourceAirport` IS NOT NULL) " +
"WHERE (`t1`.`airline` = \"AI\" AND `t1`.`destinationAirport` IN `source_airports`)"

val actual = create
.select(destinationAirport, equip.alias("has_radio"))
.from(t1)
.withVariables(equip, sourceAirports)
jansigi marked this conversation as resolved.
Show resolved Hide resolved
.where(
someStringField("airline", t1).isEqualTo("AI")
.and(destinationAirport.inArray(sourceAirports)),
).build().queryString

assertEquals(expected, actual)
}
}
116 changes: 116 additions & 0 deletions core/src/test/kotlin/ch/ergon/dope/resolvable/clause/LetClauseTest.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
package ch.ergon.dope.resolvable.clause

import ch.ergon.dope.DopeQuery
import ch.ergon.dope.DopeQueryManager
import ch.ergon.dope.helper.ManagerDependentTest
import ch.ergon.dope.helper.someBoolean
import ch.ergon.dope.helper.someFromClause
import ch.ergon.dope.helper.someNumber
import ch.ergon.dope.helper.someNumberField
import ch.ergon.dope.helper.someString
import ch.ergon.dope.helper.someStringField
import ch.ergon.dope.resolvable.clause.model.DopeVariable
import ch.ergon.dope.resolvable.clause.model.LetClause
import ch.ergon.dope.resolvable.clause.model.assignTo
import ch.ergon.dope.resolvable.expression.unaliased.type.function.stringfunction.lower
import ch.ergon.dope.resolvable.expression.unaliased.type.toDopeType
import kotlin.test.Test
import kotlin.test.assertEquals

class LetClauseTest : ManagerDependentTest {
override lateinit var manager: DopeQueryManager

@Test
fun `should support let`() {
val expected = DopeQuery(
queryString = "SELECT * FROM `someBucket` LET `someName` = `numberField`",
)
val underTest = LetClause(DopeVariable("someName", someNumberField()), parentClause = someFromClause())

val actual = underTest.toDopeQuery(manager)

assertEquals(expected, actual)
}

@Test
fun `should support let with multiple expressions`() {
val expected = DopeQuery(
queryString = "SELECT * FROM `someBucket` LET `name1` = `numberField`, `name2` = `stringField`",
jansigi marked this conversation as resolved.
Show resolved Hide resolved
)
val underTest = LetClause(
DopeVariable("name1", someNumberField()),
DopeVariable("name2", someStringField()),
jansigi marked this conversation as resolved.
Show resolved Hide resolved
parentClause = someFromClause(),
)

val actual = underTest.toDopeQuery(manager)

assertEquals(expected, actual)
}

@Test
fun `should support let function`() {
val parentClause = someFromClause()
val dopeVariable = DopeVariable("name1", someNumberField())
jansigi marked this conversation as resolved.
Show resolved Hide resolved
val expected = LetClause(dopeVariable, parentClause = parentClause)

val actual = parentClause.withVariables(dopeVariable)
jansigi marked this conversation as resolved.
Show resolved Hide resolved

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

@Test
fun `should support let expression`() {
val expression = lower("TEST")
val expected = DopeQuery("`name1`")
val underTest = DopeVariable("name1", expression)
jansigi marked this conversation as resolved.
Show resolved Hide resolved

val actual = underTest.toDopeQuery(manager)

assertEquals(expected, actual)
}

@Test
fun `should support let expression function`() {
val expression = lower("test")
val name = "name"
val expected = DopeVariable(name, expression)

val actual = name.assignTo(expression)

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

@Test
fun `should support let expression function number`() {
val expression = someNumber()
jansigi marked this conversation as resolved.
Show resolved Hide resolved
val name = "name"
val expected = DopeVariable(name, expression.toDopeType())

val actual = name.assignTo(expression)

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

@Test
fun `should support let expression function string`() {
val expression = someString()
jansigi marked this conversation as resolved.
Show resolved Hide resolved
val name = "name"
val expected = DopeVariable(name, expression.toDopeType())

val actual = name.assignTo(expression)

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

@Test
fun `should support let expression function boolean`() {
val expression = someBoolean()
jansigi marked this conversation as resolved.
Show resolved Hide resolved
val name = "name"
val expected = DopeVariable(name, expression.toDopeType())

val actual = name.assignTo(expression)

assertEquals(expected.toDopeQuery(manager), actual.toDopeQuery(manager))
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import ch.ergon.dope.resolvable.clause.ISelectOrderByClause
import ch.ergon.dope.resolvable.clause.ISelectUnnestClause
import ch.ergon.dope.resolvable.clause.ISelectWhereClause
import ch.ergon.dope.resolvable.clause.model.OrderByType
import ch.ergon.dope.resolvable.clause.model.assignTo
import ch.ergon.dope.resolvable.clause.model.joinHint.HashOrNestedLoopHint
import ch.ergon.dope.resolvable.clause.model.joinHint.KeysOrIndexHint
import ch.ergon.dope.resolvable.fromable.Bucket
Expand Down Expand Up @@ -82,3 +83,21 @@ fun <T : ValidType> ISelectUnnestClause<T>.unnest(arrayField: CMJsonList<Number>

@JvmName("unnestBoolean")
fun <T : ValidType> ISelectUnnestClause<T>.unnest(arrayField: CMJsonList<Boolean>) = unnest(arrayField.toDopeType())

@JvmName("assignToCMNumberField")
fun String.assignTo(value: CMJsonField<Number>) = assignTo(value.toDopeType())
jansigi marked this conversation as resolved.
Show resolved Hide resolved

@JvmName("assignToCMStringField")
fun String.assignTo(value: CMJsonField<String>) = assignTo(value.toDopeType())
jansigi marked this conversation as resolved.
Show resolved Hide resolved

@JvmName("assignToCMBooleanField")
fun String.assignTo(value: CMJsonField<Boolean>) = assignTo(value.toDopeType())
jansigi marked this conversation as resolved.
Show resolved Hide resolved

@JvmName("assignToCMNumberList")
fun String.assignTo(value: CMJsonList<Number>) = assignTo(value.toDopeType())
jansigi marked this conversation as resolved.
Show resolved Hide resolved

@JvmName("assignToCMStringList")
fun String.assignTo(value: CMJsonList<String>) = assignTo(value.toDopeType())
jansigi marked this conversation as resolved.
Show resolved Hide resolved

@JvmName("assignToCMBooleanList")
fun String.assignTo(value: CMJsonList<Boolean>) = assignTo(value.toDopeType())
jansigi marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package ch.ergon.dope.extensions.clause

import ch.ergon.dope.DopeQueryManager
import ch.ergon.dope.extension.clause.assignTo
import ch.ergon.dope.extension.clause.groupBy
import ch.ergon.dope.extension.clause.innerJoin
import ch.ergon.dope.extension.clause.join
Expand All @@ -22,6 +23,8 @@ import ch.ergon.dope.helper.someCMStringList
import ch.ergon.dope.helper.someFrom
import ch.ergon.dope.helper.someNumberField
import ch.ergon.dope.helper.someSelect
import ch.ergon.dope.helper.someString
import ch.ergon.dope.resolvable.clause.model.DopeVariable
import ch.ergon.dope.resolvable.clause.model.GroupByClause
import ch.ergon.dope.resolvable.clause.model.InnerJoinClause
import ch.ergon.dope.resolvable.clause.model.LeftJoinClause
Expand Down Expand Up @@ -262,4 +265,70 @@ class SelectClauseTest : ManagerDependentTest {

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

@Test
fun `should support DopeVariables with cmNumberField`() {
jansigi marked this conversation as resolved.
Show resolved Hide resolved
val name = someString()
val value = someCMNumberField()
val expected = DopeVariable(name, value.toDopeType())

val actual = name.assignTo(value)

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

@Test
fun `should support DopeVariables with cmStringField`() {
val name = someString()
val value = someCMStringField()
val expected = DopeVariable(name, value.toDopeType())

val actual = name.assignTo(value)

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

@Test
fun `should support DopeVariables with cmBooleanField`() {
val name = someString()
val value = someCMBooleanField()
val expected = DopeVariable(name, value.toDopeType())

val actual = name.assignTo(value)

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

@Test
fun `should support DopeVariables with cmNumberList`() {
val name = someString()
val value = someCMNumberList()
val expected = DopeVariable(name, value.toDopeType())

val actual = name.assignTo(value)

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

@Test
fun `should support DopeVariables with cmStringList`() {
val name = someString()
val value = someCMStringList()
val expected = DopeVariable(name, value.toDopeType())

val actual = name.assignTo(value)

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

@Test
fun `should support DopeVariables with cmBooleanList`() {
val name = someString()
val value = someCMBooleanList()
val expected = DopeVariable(name, value.toDopeType())

val actual = name.assignTo(value)

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