Skip to content

Commit

Permalink
DOPE-271: added new returnable interface, restructured selectable int…
Browse files Browse the repository at this point in the history
…erfaces and aggregate functions
  • Loading branch information
martinagallati committed Jan 31, 2025
1 parent 2e76c9f commit 2c054ca
Show file tree
Hide file tree
Showing 46 changed files with 252 additions and 243 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import ch.ergon.dope.integrationTest.TestCouchbaseDatabase.resetDatabase
import ch.ergon.dope.integrationTest.TestCouchbaseDatabase.testBucket
import ch.ergon.dope.integrationTest.toMapValues
import ch.ergon.dope.integrationTest.tryUntil
import ch.ergon.dope.resolvable.expression.AsteriskExpression
import ch.ergon.dope.resolvable.expression.asterisk
import ch.ergon.dope.resolvable.expression.unaliased.type.arithmetic.add
import ch.ergon.dope.resolvable.expression.unaliased.type.relational.isEqualTo
import ch.ergon.dope.resolvable.fromable.useKeys
Expand All @@ -29,7 +29,7 @@ class DeleteIntegrationTest : BaseIntegrationTest() {
)
.returning(
idField,
AsteriskExpression(),
asterisk(),
)
.build()

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,18 @@ import ch.ergon.dope.integrationTest.BaseIntegrationTest
import ch.ergon.dope.integrationTest.TestCouchbaseDatabase.idField
import ch.ergon.dope.integrationTest.TestCouchbaseDatabase.isActiveField
import ch.ergon.dope.integrationTest.TestCouchbaseDatabase.nameField
import ch.ergon.dope.integrationTest.TestCouchbaseDatabase.quantitiesField
import ch.ergon.dope.integrationTest.TestCouchbaseDatabase.testBucket
import ch.ergon.dope.integrationTest.TestCouchbaseDatabase.typeField
import ch.ergon.dope.integrationTest.toMapValues
import ch.ergon.dope.integrationTest.toRawValues
import ch.ergon.dope.resolvable.clause.model.setoperator.intersect
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.any
import ch.ergon.dope.resolvable.expression.unaliased.type.logical.and
import ch.ergon.dope.resolvable.expression.unaliased.type.relational.isEqualTo
import ch.ergon.dope.resolvable.expression.unaliased.type.relational.isGreaterOrEqualThan
import ch.ergon.dope.resolvable.expression.unaliased.type.relational.isGreaterThan
import ch.ergon.dope.resolvable.expression.unaliased.type.relational.isLessThan
import ch.ergon.dope.resolvable.expression.unaliased.type.relational.isNull
Expand Down Expand Up @@ -228,4 +231,26 @@ class SelectQueryIntegrationTest : BaseIntegrationTest() {

assertEquals(1, result["positionalParameter"])
}

@Test
fun `select from subquery`() {
val create = QueryBuilder()
val subQuery = create
.selectRaw(quantitiesField)
.from(testBucket)
.where(typeField.isEqualTo("order"))
.limit(1)
.alias("subQuery")
val dopeQuery = create
.select(subQuery)
.from(subQuery)
.where(
subQuery.any { it.isGreaterOrEqualThan(1) },
).build()

val queryResult = queryWithoutParameters(dopeQuery)
val result = queryResult.toMapValues()

assertEquals(listOf(1, 2, 3), result["subQuery"])
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ import ch.ergon.dope.integrationTest.TestCouchbaseDatabase.testBucket
import ch.ergon.dope.integrationTest.TestCouchbaseDatabase.typeField
import ch.ergon.dope.integrationTest.toMapValues
import ch.ergon.dope.integrationTest.tryUntil
import ch.ergon.dope.resolvable.expression.alias
import ch.ergon.dope.resolvable.expression.unaliased.aggregator.AggregateQuantifier.DISTINCT
import ch.ergon.dope.resolvable.expression.unaliased.aggregator.alias
import ch.ergon.dope.resolvable.expression.unaliased.aggregator.arrayAggregate
import ch.ergon.dope.resolvable.expression.unaliased.aggregator.countAsterisk
import ch.ergon.dope.resolvable.expression.unaliased.aggregator.max
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import ch.ergon.dope.integrationTest.TestCouchbaseDatabase.testBucket
import ch.ergon.dope.resolvable.expression.unaliased.type.Field
import ch.ergon.dope.resolvable.fromable.Bucket
import ch.ergon.dope.resolvable.fromable.UnaliasedBucket
import ch.ergon.dope.validtype.ArrayType
import ch.ergon.dope.validtype.BooleanType
import ch.ergon.dope.validtype.NumberType
import ch.ergon.dope.validtype.StringType
Expand Down Expand Up @@ -31,6 +32,7 @@ object TestCouchbaseDatabase {
val isActiveField = Field<BooleanType>("isActive", testBucket.name)
val orderNumberField = Field<StringType>("orderNumber", testBucket.name)
val deliveryDateField = Field<StringType>("deliveryDate", testBucket.name)
val quantitiesField = Field<ArrayType<NumberType>>("quantities", testBucket.name)

init {
initContainer()
Expand Down Expand Up @@ -87,6 +89,7 @@ object TestCouchbaseDatabase {
"client" to "client:$i",
"employee" to "employee:$i",
"deliveryDate" to null,
"quantities" to listOf(1, 2, 3),
),
)
}
Expand Down
16 changes: 8 additions & 8 deletions core/src/main/kotlin/ch/ergon/dope/QueryBuilder.kt
Original file line number Diff line number Diff line change
Expand Up @@ -5,24 +5,24 @@ import ch.ergon.dope.resolvable.clause.model.SelectClause
import ch.ergon.dope.resolvable.clause.model.SelectDistinctClause
import ch.ergon.dope.resolvable.clause.model.SelectRawClause
import ch.ergon.dope.resolvable.clause.model.UpdateClause
import ch.ergon.dope.resolvable.expression.AsteriskExpression
import ch.ergon.dope.resolvable.expression.Expression
import ch.ergon.dope.resolvable.expression.SingleExpression
import ch.ergon.dope.resolvable.expression.Asterisk
import ch.ergon.dope.resolvable.fromable.Deletable
import ch.ergon.dope.resolvable.fromable.Fromable
import ch.ergon.dope.resolvable.fromable.RawSelectable
import ch.ergon.dope.resolvable.fromable.Selectable
import ch.ergon.dope.resolvable.fromable.Updatable
import ch.ergon.dope.validtype.ValidType

class QueryBuilder {
fun select(expression: Expression, vararg expressions: Expression) = SelectClause(expression, *expressions)
fun select(expression: Selectable, vararg expressions: Selectable) = SelectClause(expression, *expressions)

fun selectAsterisk() = SelectClause(AsteriskExpression())
fun selectAsterisk() = SelectClause(Asterisk())

fun selectDistinct(expression: Expression, vararg expressions: Expression) = SelectDistinctClause(expression, *expressions)
fun selectDistinct(expression: Selectable, vararg expressions: Selectable) = SelectDistinctClause(expression, *expressions)

fun <T : ValidType> selectRaw(expression: SingleExpression<T>) = SelectRawClause(expression)
fun <T : ValidType> selectRaw(expression: RawSelectable<T>) = SelectRawClause(expression)

fun selectFrom(fromable: Fromable) = SelectClause(AsteriskExpression()).from(fromable)
fun selectFrom(fromable: Fromable) = SelectClause(Asterisk()).from(fromable)

fun deleteFrom(deletable: Deletable) = DeleteClause(deletable)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,9 @@ import ch.ergon.dope.resolvable.clause.model.DeleteWhereClause
import ch.ergon.dope.resolvable.clause.model.ReturningType.ELEMENT
import ch.ergon.dope.resolvable.clause.model.ReturningType.RAW
import ch.ergon.dope.resolvable.clause.model.ReturningType.VALUE
import ch.ergon.dope.resolvable.expression.AsteriskExpression
import ch.ergon.dope.resolvable.expression.SingleExpression
import ch.ergon.dope.resolvable.expression.TypeExpression
import ch.ergon.dope.resolvable.expression.asterisk
import ch.ergon.dope.resolvable.expression.unaliased.type.toDopeType
import ch.ergon.dope.resolvable.fromable.Bucket
import ch.ergon.dope.resolvable.fromable.Returnable
Expand All @@ -22,13 +23,13 @@ interface IDeleteReturningClause : Clause
interface IDeleteOffsetClause : IDeleteReturningClause {
fun returning(returningExpression: Returnable, vararg additionalReturningExpressions: Returnable) =
DeleteReturningClause(returningExpression, *additionalReturningExpressions, parentClause = this)
fun returningAsterisk(bucket: Bucket? = null) = DeleteReturningClause(AsteriskExpression(bucket), parentClause = this)
fun returningAsterisk(bucket: Bucket? = null) = DeleteReturningClause(asterisk(bucket), parentClause = this)

fun returningRaw(returningExpression: TypeExpression<out ValidType>) =
fun returningRaw(returningExpression: SingleExpression<out ValidType>) =
DeleteReturningSingleClause(returningExpression, returningType = RAW, parentClause = this)
fun returningValue(returningExpression: TypeExpression<out ValidType>) =
fun returningValue(returningExpression: SingleExpression<out ValidType>) =
DeleteReturningSingleClause(returningExpression, returningType = VALUE, parentClause = this)
fun returningElement(returningExpression: TypeExpression<out ValidType>) =
fun returningElement(returningExpression: SingleExpression<out ValidType>) =
DeleteReturningSingleClause(returningExpression, returningType = ELEMENT, parentClause = this)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import ch.ergon.dope.resolvable.clause.model.UpdateReturningClause
import ch.ergon.dope.resolvable.clause.model.UpdateReturningSingleClause
import ch.ergon.dope.resolvable.clause.model.UpdateWhereClause
import ch.ergon.dope.resolvable.clause.model.to
import ch.ergon.dope.resolvable.expression.AsteriskExpression
import ch.ergon.dope.resolvable.expression.Asterisk
import ch.ergon.dope.resolvable.expression.TypeExpression
import ch.ergon.dope.resolvable.expression.unaliased.type.Field
import ch.ergon.dope.resolvable.expression.unaliased.type.toDopeType
Expand All @@ -26,7 +26,7 @@ interface IUpdateReturningClause : Clause
interface IUpdateLimitClause : IUpdateReturningClause {
fun returning(returningExpression: Returnable, vararg additionalReturningExpressions: Returnable) =
UpdateReturningClause(returningExpression, *additionalReturningExpressions, parentClause = this)
fun returningAsterisk(bucket: Bucket? = null) = UpdateReturningClause(AsteriskExpression(bucket), parentClause = this)
fun returningAsterisk(bucket: Bucket? = null) = UpdateReturningClause(Asterisk(bucket), parentClause = this)

fun returningRaw(returningExpression: TypeExpression<out ValidType>) =
UpdateReturningSingleClause(returningExpression, returningType = RAW, parentClause = this)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,11 @@ import ch.ergon.dope.resolvable.clause.IDeleteOffsetClause
import ch.ergon.dope.resolvable.clause.IDeleteReturningClause
import ch.ergon.dope.resolvable.clause.IUpdateLimitClause
import ch.ergon.dope.resolvable.clause.IUpdateReturningClause
import ch.ergon.dope.resolvable.expression.SingleExpression
import ch.ergon.dope.resolvable.formatToQueryStringWithSymbol
import ch.ergon.dope.resolvable.fromable.AliasedSelectClause
import ch.ergon.dope.resolvable.fromable.Returnable
import ch.ergon.dope.resolvable.fromable.SingleReturnable
import ch.ergon.dope.validtype.ValidType

private const val RETURNING = "RETURNING"

Expand Down Expand Up @@ -69,7 +70,7 @@ class UpdateReturningClause(
)

sealed class ReturningSingleClause(
private val singleReturnable: SingleReturnable,
private val singleReturnable: SingleExpression<out ValidType>,
private val returningType: ReturningType,
private val parentClause: Clause,
) : Resolvable {
Expand All @@ -87,13 +88,13 @@ sealed class ReturningSingleClause(
}

class DeleteReturningSingleClause(
singleReturnable: SingleReturnable,
singleReturnable: SingleExpression<out ValidType>,
returningType: ReturningType,
parentClause: IDeleteOffsetClause,
) : IDeleteReturningClause, ReturningSingleClause(singleReturnable, returningType, parentClause = parentClause)

class UpdateReturningSingleClause(
singleReturnable: SingleReturnable,
singleReturnable: SingleExpression<out ValidType>,
returningType: ReturningType,
parentClause: IUpdateLimitClause,
) : IUpdateReturningClause, ReturningSingleClause(singleReturnable, returningType, parentClause = parentClause)
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,15 @@ package ch.ergon.dope.resolvable.clause.model
import ch.ergon.dope.DopeQuery
import ch.ergon.dope.DopeQueryManager
import ch.ergon.dope.resolvable.clause.ISelectClause
import ch.ergon.dope.resolvable.expression.Expression
import ch.ergon.dope.resolvable.expression.SingleExpression
import ch.ergon.dope.resolvable.formatToQueryString
import ch.ergon.dope.resolvable.fromable.RawSelectable
import ch.ergon.dope.resolvable.fromable.Selectable
import ch.ergon.dope.validtype.ObjectType
import ch.ergon.dope.validtype.ValidType

class SelectClause(
private val expression: Expression,
private vararg val expressions: Expression,
private val expression: Selectable,
private vararg val expressions: Selectable,
) : ISelectClause<ObjectType> {
override fun toDopeQuery(manager: DopeQueryManager): DopeQuery {
val expressionDopeQuery = expression.toDopeQuery(manager)
Expand All @@ -27,7 +27,7 @@ class SelectClause(
}
}

class SelectRawClause<T : ValidType>(private val expression: SingleExpression<T>) : ISelectClause<T> {
class SelectRawClause<T : ValidType>(private val expression: RawSelectable<T>) : ISelectClause<T> {
override fun toDopeQuery(manager: DopeQueryManager): DopeQuery {
val expressionDopeQuery = expression.toDopeQuery(manager)
return DopeQuery(
Expand All @@ -37,7 +37,7 @@ class SelectRawClause<T : ValidType>(private val expression: SingleExpression<T>
}
}

class SelectDistinctClause(private val expression: Expression, private vararg val expressions: Expression) : ISelectClause<ObjectType> {
class SelectDistinctClause(private val expression: Selectable, private vararg val expressions: Selectable) : ISelectClause<ObjectType> {
override fun toDopeQuery(manager: DopeQueryManager): DopeQuery {
val expressionsDopeQuery = expressions.map { it.toDopeQuery(manager) }
val expressionDopeQuery = expression.toDopeQuery(manager)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,32 +4,16 @@ import ch.ergon.dope.DopeQuery
import ch.ergon.dope.DopeQueryManager
import ch.ergon.dope.resolvable.expression.unaliased.type.toDopeType
import ch.ergon.dope.resolvable.formatToQueryStringWithSymbol
import ch.ergon.dope.resolvable.fromable.SingleReturnable
import ch.ergon.dope.validtype.BooleanType
import ch.ergon.dope.validtype.NumberType
import ch.ergon.dope.validtype.ObjectType
import ch.ergon.dope.validtype.StringType
import ch.ergon.dope.validtype.ValidType

class AliasedExpression<T : ValidType>(
private val unaliasedExpression: UnaliasedExpression<T>,
private val alias: String,
) : SingleExpression<T> {
override fun toDopeQuery(manager: DopeQueryManager): DopeQuery {
val unaliasedExpressionDopeQuery = unaliasedExpression.toDopeQuery(manager)
return DopeQuery(
queryString = formatToQueryStringWithSymbol(unaliasedExpressionDopeQuery.queryString, "AS", "`$alias`"),
parameters = unaliasedExpressionDopeQuery.parameters,
)
}
}

fun <T : ValidType> UnaliasedExpression<T>.alias(string: String): AliasedExpression<T> = AliasedExpression(this, string)

class AliasedTypeExpression<T : ValidType>(
private val typeExpression: TypeExpression<T>,
private val alias: String,
) : SingleExpression<T>, SingleReturnable {
) : SingleExpression<T> {
override fun toDopeQuery(manager: DopeQueryManager): DopeQuery {
val typeExpressionDopeQuery = typeExpression.toDopeQuery(manager)
return DopeQuery(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,15 @@ import ch.ergon.dope.DopeQuery
import ch.ergon.dope.DopeQueryManager
import ch.ergon.dope.resolvable.fromable.Bucket
import ch.ergon.dope.resolvable.fromable.Returnable
import ch.ergon.dope.resolvable.fromable.Selectable

const val ASTERISK_STRING = "*"

class AsteriskExpression(private val bucket: Bucket? = null) : Expression, Returnable {
class Asterisk(private val bucket: Bucket? = null) : Selectable, Returnable {
override fun toDopeQuery(manager: DopeQueryManager): DopeQuery {
val queryString = bucket?.toDopeQuery(manager)?.queryString?.let { "$it.$ASTERISK_STRING" } ?: ASTERISK_STRING
return DopeQuery(queryString = queryString)
}
}

fun asterisk(bucket: Bucket? = null) = Asterisk(bucket)
Original file line number Diff line number Diff line change
@@ -1,13 +1,9 @@
package ch.ergon.dope.resolvable.expression

import ch.ergon.dope.resolvable.Resolvable
import ch.ergon.dope.resolvable.fromable.SingleReturnable
import ch.ergon.dope.resolvable.fromable.RawSelectable
import ch.ergon.dope.resolvable.fromable.Returnable
import ch.ergon.dope.validtype.ValidType

interface Expression : Resolvable
interface SingleExpression<T : ValidType> : RawSelectable<T>, Returnable

interface SingleExpression<T : ValidType> : Expression

interface UnaliasedExpression<T : ValidType> : SingleExpression<T>

interface TypeExpression<T : ValidType> : UnaliasedExpression<T>, SingleReturnable
interface TypeExpression<T : ValidType> : SingleExpression<T>
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,24 @@ package ch.ergon.dope.resolvable.expression.unaliased.aggregator

import ch.ergon.dope.DopeQuery
import ch.ergon.dope.DopeQueryManager
import ch.ergon.dope.resolvable.expression.UnaliasedExpression
import ch.ergon.dope.resolvable.expression.unaliased.type.Field
import ch.ergon.dope.resolvable.fromable.RawSelectable
import ch.ergon.dope.resolvable.operator.FunctionOperator
import ch.ergon.dope.validtype.ValidType

sealed class AggregateExpression<T : ValidType>(
interface AggregateExpression<T : ValidType> : RawSelectable<T>

sealed class AggregateFunctionExpression<T : ValidType>(
private val symbol: String,
private val field: Field<T>,
private val field: Field<out ValidType>,
private val quantifier: AggregateQuantifier?,
) : FunctionOperator, UnaliasedExpression<T> {
) : FunctionOperator, AggregateExpression<T> {
override fun toDopeQuery(manager: DopeQueryManager): DopeQuery {
val fieldDopeQuery = field.toDopeQuery(manager)
return DopeQuery(
queryString = toFunctionQueryString(symbol, quantifier, fieldDopeQuery.queryString),
queryString = quantifier?.let {
"$symbol($quantifier ${fieldDopeQuery.queryString})"
} ?: toFunctionQueryString(symbol, fieldDopeQuery.queryString),
parameters = fieldDopeQuery.parameters,
)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package ch.ergon.dope.resolvable.expression.unaliased.aggregator

import ch.ergon.dope.DopeQuery
import ch.ergon.dope.DopeQueryManager
import ch.ergon.dope.resolvable.formatToQueryStringWithSymbol
import ch.ergon.dope.resolvable.fromable.RawSelectable
import ch.ergon.dope.validtype.ValidType

class AliasedAggregateExpression<T : ValidType>(
private val aggregateExpression: AggregateExpression<T>,
private val alias: String,
) : RawSelectable<T> {
override fun toDopeQuery(manager: DopeQueryManager): DopeQuery {
val aggregateExpressionDopeQuery = aggregateExpression.toDopeQuery(manager)
return DopeQuery(
queryString = formatToQueryStringWithSymbol(
aggregateExpressionDopeQuery.queryString,
"AS",
"`$alias`",
),
parameters = aggregateExpressionDopeQuery.parameters,
)
}
}

fun <T : ValidType> AggregateExpression<T>.alias(alias: String) = AliasedAggregateExpression(this, alias)
Loading

0 comments on commit 2c054ca

Please sign in to comment.