Skip to content

Commit

Permalink
Continue update DSL implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
Thijsiez committed Nov 6, 2024
1 parent 825abf9 commit 50e547c
Show file tree
Hide file tree
Showing 7 changed files with 162 additions and 54 deletions.
52 changes: 35 additions & 17 deletions src/main/kotlin/ch/icken/processor/PanacheCompanionBaseProcessor.kt
Original file line number Diff line number Diff line change
Expand Up @@ -69,18 +69,16 @@ class PanacheCompanionBaseProcessor(
.plusParameter(className)
.plusParameter(idClassName)
.plusParameter(columnsObjectClassName)
val setterExpressionParameterLambdaType = LambdaTypeName.get(
receiver = columnsObjectClassName,
returnType = SetterClassName
)
val updateComponentType = UpdateComponentClassName
.plusParameter(className)
.plusParameter(idClassName)
.plusParameter(columnsObjectClassName)

//region update, where
val update = FunSpec.builder(FUNCTION_NAME_UPDATE)
.addModifiers(KModifier.INLINE)
.receiver(companionClassName)
//TODO setter providers parameter
.returns(UpdateComponentClassName.plusParameter(className)
.plusParameter(idClassName).plusParameter(columnsObjectClassName))
.addStatement("return %M(%T)",
MemberName(UpdateComponentClassName.packageName, FUNCTION_NAME_UPDATE), columnsObjectClassName)
.addAnnotation(jvmNameAnnotation("$FUNCTION_NAME_UPDATE$classSimpleName"))

//region where, and, or
val where = FunSpec.builder(FUNCTION_NAME_WHERE)
.addModifiers(KModifier.INLINE)
.receiver(companionClassName)
Expand All @@ -89,9 +87,7 @@ class PanacheCompanionBaseProcessor(
.addStatement("return %M($PARAM_NAME_EXPRESSION(%T))",
MemberName(QueryComponentClassName.packageName, FUNCTION_NAME_WHERE), columnsObjectClassName)
.addAnnotation(jvmNameAnnotation("$FUNCTION_NAME_WHERE$classSimpleName"))
//endregion

//region and, or
val and = FunSpec.builder(FUNCTION_NAME_AND)
.addModifiers(KModifier.INLINE)
.receiver(queryComponentType)
Expand Down Expand Up @@ -142,20 +138,20 @@ class PanacheCompanionBaseProcessor(
.addStatement("return $FUNCTION_NAME_WHERE($PARAM_NAME_EXPRESSION).$FUNCTION_NAME_FIND($PARAM_NAME_SORT)")
.addAnnotation(jvmNameAnnotation("$FUNCTION_NAME_FIND_SORTED$classSimpleName"))

val streamReturn = StreamClassName.plusParameter(className)
val streamReturns = StreamClassName.plusParameter(className)
val stream = FunSpec.builder(FUNCTION_NAME_STREAM)
.addModifiers(KModifier.INLINE)
.receiver(companionClassName)
.addParameter(PARAM_NAME_EXPRESSION, expressionParameterLambdaType)
.returns(streamReturn)
.returns(streamReturns)
.addStatement("return $FUNCTION_NAME_WHERE($PARAM_NAME_EXPRESSION).$FUNCTION_NAME_STREAM()")
.addAnnotation(jvmNameAnnotation("$FUNCTION_NAME_STREAM$classSimpleName"))
val streamSorted = FunSpec.builder(FUNCTION_NAME_STREAM)
.addModifiers(KModifier.INLINE)
.receiver(companionClassName)
.addParameter(PARAM_NAME_SORT, SortClassName)
.addParameter(PARAM_NAME_EXPRESSION, expressionParameterLambdaType)
.returns(streamReturn)
.returns(streamReturns)
.addStatement("return $FUNCTION_NAME_WHERE($PARAM_NAME_EXPRESSION).$FUNCTION_NAME_STREAM($PARAM_NAME_SORT)")
.addAnnotation(jvmNameAnnotation("$FUNCTION_NAME_STREAM_SORTED$classSimpleName"))
//endregion
Expand Down Expand Up @@ -194,6 +190,27 @@ class PanacheCompanionBaseProcessor(
.addAnnotation(jvmNameAnnotation("$FUNCTION_NAME_MULTIPLE_SORTED$classSimpleName"))
//endregion

//region update
val updateExtensionFunction = MemberName(UpdateComponentClassName.packageName, FUNCTION_NAME_UPDATE)
val update = FunSpec.builder(FUNCTION_NAME_UPDATE)
.receiver(companionClassName)
.addParameter(PARAM_NAME_SETTER, setterExpressionParameterLambdaType)
.returns(updateComponentType)
.addStatement("return %M(%T, $PARAM_NAME_SETTER)", updateExtensionFunction, columnsObjectClassName)
.addAnnotation(jvmNameAnnotation("$FUNCTION_NAME_UPDATE$classSimpleName"))
val updateMultiple = FunSpec.builder(FUNCTION_NAME_UPDATE)
.receiver(companionClassName)
.addParameter(PARAM_NAME_SETTERS, setterExpressionParameterLambdaType, KModifier.VARARG)
.returns(updateComponentType)
.addStatement("return %M(%T, $PARAM_NAME_SETTERS)", updateExtensionFunction, columnsObjectClassName)
.addAnnotation(jvmNameAnnotation("$FUNCTION_NAME_UPDATE_MULTIPLE$classSimpleName"))

//TODO whereUpdate

//TODO andUpdate
//TODO orUpdate
//endregion

//region andExpression, orExpression
val andExpression = FunSpec.builder(FUNCTION_NAME_AND)
.addModifiers(KModifier.INLINE)
Expand All @@ -211,9 +228,10 @@ class PanacheCompanionBaseProcessor(
.addAnnotation(jvmNameAnnotation("$FUNCTION_NAME_OR_EXPRESSION$classSimpleName"))
//endregion

val functions = listOf(update, where, and, or,
val functions = listOf(where, and, or,
count, delete, find, findSorted, stream, streamSorted,
single, singleSafe, multiple, multipleSorted,
update, updateMultiple,
andExpression, orExpression)

FileSpec.builder(packageName, extensionFileName)
Expand Down
4 changes: 3 additions & 1 deletion src/main/kotlin/ch/icken/processor/ProcessorCommon.kt
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ abstract class ProcessorCommon(options: Map<String, String>) {
val PanacheQueryClassName = PanacheQuery::class.asClassName()
val PanacheSingleResultClassName = PanacheSingleResult::class.asClassName()
val QueryComponentClassName = QueryComponent::class.asClassName()
val SetterClassName = UpdateComponent.Setter::class.asClassName()
val SetterClassName = UpdateComponent.InitialUpdateComponent.Setter::class.asClassName()
val SortClassName = Sort::class.asClassName()
val StreamClassName = Stream::class.asClassName()
val StringClassName = String::class.asClassName()
Expand All @@ -89,10 +89,12 @@ abstract class ProcessorCommon(options: Map<String, String>) {
const val FUNCTION_NAME_STREAM = "stream"
const val FUNCTION_NAME_STREAM_SORTED = "streamSorted"
const val FUNCTION_NAME_UPDATE = "update"
const val FUNCTION_NAME_UPDATE_MULTIPLE = "updateMultiple"
const val FUNCTION_NAME_WHERE = "where"
const val PARAM_NAME_COLUMNS_BASE_CLASS = "parent"
const val PARAM_NAME_EXPRESSION = "expression"
const val PARAM_NAME_MAPPED_BY = "mappedBy"
const val PARAM_NAME_SETTER = "setter"
const val PARAM_NAME_SETTERS = "setters"
const val PARAM_NAME_SORT = "sort"
const val PARAM_NAME_TYPE = "type"
Expand Down
4 changes: 3 additions & 1 deletion src/main/kotlin/ch/icken/query/Column.kt
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,9 @@ import ch.icken.query.Expression.BooleanExpression.BooleanValueExpression.*
import ch.icken.query.Expression.BooleanExpression.IsExpression.IsNotNull
import ch.icken.query.Expression.BooleanExpression.IsExpression.IsNull

class Column<Columns, T : Any?>(internal val name: String)
class Column<Columns, T : Any?>(internal val name: String) {
operator fun invoke(value: T) = Component.UpdateComponent.InitialUpdateComponent.Setter(name, value)
}

//region eq
private fun <Columns> eq(name: String, value: Any?): Expression<Columns> =
Expand Down
113 changes: 93 additions & 20 deletions src/main/kotlin/ch/icken/query/Component.kt
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,11 @@

package ch.icken.query

import ch.icken.query.Component.QueryComponent
import ch.icken.query.Component.QueryComponent.InitialQueryComponent
import ch.icken.query.Component.UpdateComponent
import ch.icken.query.Component.UpdateComponent.InitialUpdateComponent
import ch.icken.query.Component.UpdateComponent.InitialUpdateComponent.Setter
import io.quarkus.hibernate.orm.panache.kotlin.PanacheCompanionBase
import io.quarkus.hibernate.orm.panache.kotlin.PanacheEntityBase
import io.quarkus.panache.common.Sort
Expand All @@ -25,7 +30,7 @@ sealed class Component<Entity : PanacheEntityBase, Id : Any, Columns> private co
) {
//region compile
internal abstract fun compile(): Compiled
data class Compiled internal constructor(val query: String, val parameters: Map<String, Any>)
data class Compiled internal constructor(val component: String, val parameters: Map<String, Any>)
//endregion

sealed class QueryComponent<Entity : PanacheEntityBase, Id : Any, Columns> private constructor(
Expand All @@ -40,12 +45,12 @@ sealed class Component<Entity : PanacheEntityBase, Id : Any, Columns> private co
//endregion

//region Terminal operations
fun count() = with(compile()) { companion.count(query, parameters) }
fun delete() = with(compile()) { companion.delete(query, parameters) }
fun find() = with(compile()) { companion.find(query, parameters) }
fun find(sort: Sort) = with(compile()) { companion.find(query, sort, parameters) }
fun stream() = with(compile()) { companion.stream(query, parameters) }
fun stream(sort: Sort) = with(compile()) { companion.stream(query, sort, parameters) }
fun count() = with(compile()) { companion.count(component, parameters) }
fun delete() = with(compile()) { companion.delete(component, parameters) }
fun find() = with(compile()) { companion.find(component, parameters) }
fun find(sort: Sort) = with(compile()) { companion.find(component, sort, parameters) }
fun stream() = with(compile()) { companion.stream(component, parameters) }
fun stream(sort: Sort) = with(compile()) { companion.stream(component, sort, parameters) }

fun getSingle() = find().singleResult()
fun getSingleSafe() = find().singleResultSafe()
Expand All @@ -72,7 +77,7 @@ sealed class Component<Entity : PanacheEntityBase, Id : Any, Columns> private co
val compiledPrevious = previous.compile()
val compiledExpression = expression.compile()
return Compiled(
query = "${compiledPrevious.query} $operator ${compiledExpression.expression}",
component = "${compiledPrevious.component} $operator ${compiledExpression.expression}",
parameters = compiledPrevious.parameters + compiledExpression.parameters
)
}
Expand All @@ -90,26 +95,94 @@ sealed class Component<Entity : PanacheEntityBase, Id : Any, Columns> private co
}
}

class UpdateComponent<Entity : PanacheEntityBase, Id : Any, Columns> internal constructor(
companion: PanacheCompanionBase<Entity, Id>,
private val columns: Columns
//TODO setter providers
sealed class UpdateComponent<Entity : PanacheEntityBase, Id : Any, Columns> private constructor(
companion: PanacheCompanionBase<Entity, Id>
) : Component<Entity, Id, Columns>(companion) {
override fun compile(): Compiled {
TODO("Not yet implemented")
class InitialUpdateComponent<Entity : PanacheEntityBase, Id : Any, Columns> internal constructor(
companion: PanacheCompanionBase<Entity, Id>,
private val columns: Columns,
private val setters: Array<out Columns.() -> Setter>
) : UpdateComponent<Entity, Id, Columns>(companion) {
//region Chaining operations
fun where(expression: Expression<Columns>): UpdateComponent<Entity, Id, Columns> =
LogicalUpdateComponent.WhereUpdateComponent(companion, this, expression)
//endregion

//region Terminal operations
//TODO execute on all rows
//endregion

override fun compile(): Compiled {
val compiledSetters = setters.map { it(columns).compile() }
return Compiled(
component = compiledSetters.joinToString { it.assignment },
parameters = compiledSetters.mapNotNull { it.parameter }.associate { it }
)
}

data class Setter internal constructor(val columnName: String, val value: Any?) {
private val parameterName: String = generateParameterName()

internal fun compile(): Compiled = when (value) {
null -> Compiled("$columnName = null", null)
else -> Compiled("$columnName = $parameterName", parameterName to value)
}
data class Compiled internal constructor(val assignment: String, val parameter: Pair<String, Any>?)
}
}

//TODO where
sealed class LogicalUpdateComponent<Entity : PanacheEntityBase, Id : Any, Columns> private constructor(
companion: PanacheCompanionBase<Entity, Id>,
private val previous: UpdateComponent<Entity, Id, Columns>,
private val operator: String,
private val expression: Expression<Columns>
) : UpdateComponent<Entity, Id, Columns>(companion) {
//region Chaining operations
fun and(expression: Expression<Columns>): UpdateComponent<Entity, Id, Columns> =
AndUpdateComponent(companion, this, expression)
fun or(expression: Expression<Columns>): UpdateComponent<Entity, Id, Columns> =
OrUpdateComponent(companion, this, expression)
//endregion

//region Terminal operations
//TODO execute
//endregion

override fun compile(): Compiled {
val compiledPrevious = previous.compile()
val compiledExpression = expression.compile()
return Compiled(
component = "${compiledPrevious.component} $operator ${compiledExpression.expression}",
parameters = compiledPrevious.parameters + compiledExpression.parameters
)
}

data class Setter internal constructor(val key: String, val value: Any?)
class AndUpdateComponent<Entity : PanacheEntityBase, Id : Any, Columns> internal constructor(
companion: PanacheCompanionBase<Entity, Id>,
previous: UpdateComponent<Entity, Id, Columns>,
expression: Expression<Columns>
) : LogicalUpdateComponent<Entity, Id, Columns>(companion, previous, "AND", expression)
class OrUpdateComponent<Entity : PanacheEntityBase, Id : Any, Columns> internal constructor(
companion: PanacheCompanionBase<Entity, Id>,
previous: UpdateComponent<Entity, Id, Columns>,
expression: Expression<Columns>
) : LogicalUpdateComponent<Entity, Id, Columns>(companion, previous, "OR", expression)
class WhereUpdateComponent<Entity : PanacheEntityBase, Id : Any, Columns> internal constructor(
companion: PanacheCompanionBase<Entity, Id>,
previous: UpdateComponent<Entity, Id, Columns>,
expression: Expression<Columns>
) : LogicalUpdateComponent<Entity, Id, Columns>(companion, previous, "WHERE", expression)
}
}
}

fun <Entity : PanacheEntityBase, Id : Any, Columns>
PanacheCompanionBase<Entity, Id>.update(columns: Columns)://TODO setter providers
Component.UpdateComponent<Entity, Id, Columns> = Component.UpdateComponent(this, columns)
PanacheCompanionBase<Entity, Id>.update(columns: Columns, setter: Columns.() -> Setter):
UpdateComponent<Entity, Id, Columns> = InitialUpdateComponent(this, columns, arrayOf(setter))
fun <Entity : PanacheEntityBase, Id : Any, Columns>
PanacheCompanionBase<Entity, Id>.update(columns: Columns, setters: Array<out Columns.() -> Setter>):
UpdateComponent<Entity, Id, Columns> = InitialUpdateComponent(this, columns, setters)

fun <Entity : PanacheEntityBase, Id : Any, Columns>
PanacheCompanionBase<Entity, Id>.where(expression: Expression<Columns>):
Component.QueryComponent<Entity, Id, Columns> =
Component.QueryComponent.InitialQueryComponent(this, expression)
QueryComponent<Entity, Id, Columns> = InitialQueryComponent(this, expression)
16 changes: 2 additions & 14 deletions src/main/kotlin/ch/icken/query/Expression.kt
Original file line number Diff line number Diff line change
Expand Up @@ -16,21 +16,19 @@

package ch.icken.query

import kotlin.random.Random

sealed class Expression<Columns> {
//region Chaining operations
fun and(expression: Expression<Columns>): Expression<Columns> = LogicalExpression.AndExpression(this, expression)
fun or(expression: Expression<Columns>): Expression<Columns> = LogicalExpression.OrExpression(this, expression)
//endregion

//region compile
internal fun compile() = when (this) {
is BooleanExpression -> compileExpression()
internal fun compile(): Compiled = when (this) {
is LogicalExpression -> {
val compiledExpression = compileExpression()
Compiled("(${compiledExpression.expression})", compiledExpression.parameters)
}
else -> compileExpression()
}
protected abstract fun compileExpression(): Compiled
data class Compiled internal constructor(val expression: String, val parameters: Map<String, Any>)
Expand All @@ -40,16 +38,6 @@ sealed class Expression<Columns> {
protected val key: String,
protected val operator: String
) : Expression<Columns>() {
companion object {
private const val CHARS = //"0123456789" +
"abcdefghijklmnopqrstuvwxyz" +
"ABCDEFGHIJKLMNOPQRSTUVWXYZ"

protected fun generateParameterName() = (0 ..< 8)
.map { CHARS[Random.nextInt(CHARS.length)] }
.toCharArray().concatToString()
}

sealed class BooleanValueExpression<Columns> private constructor(
key: String,
operator: String,
Expand Down
24 changes: 24 additions & 0 deletions src/main/kotlin/ch/icken/query/Utils.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/*
* Copyright 2024 Thijs Koppen
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package ch.icken.query

import kotlin.random.Random

private const val CHARS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
internal fun generateParameterName() = (0 ..< 8)
.map { CHARS[Random.nextInt(CHARS.length)] }
.toCharArray().concatToString()
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ class PanacheCompanionBaseProcessorCompileTests : ProcessorCompileTestCommon() {
compilation.assertHasFile("EmployeeExtensions.kt")

val employeeExtensions = result.loadClass("EmployeeExtensionsKt")
employeeExtensions.assertNumberOfDeclaredMethods(16)
employeeExtensions.assertNumberOfDeclaredMethods(17)
employeeExtensions.assertHasDeclaredMethodWithName("andEmployee")
employeeExtensions.assertHasDeclaredMethodWithName("andExpressionEmployee")
employeeExtensions.assertHasDeclaredMethodWithName("countEmployee")
Expand All @@ -63,6 +63,7 @@ class PanacheCompanionBaseProcessorCompileTests : ProcessorCompileTestCommon() {
employeeExtensions.assertHasDeclaredMethodWithName("streamEmployee")
employeeExtensions.assertHasDeclaredMethodWithName("streamSortedEmployee")
employeeExtensions.assertHasDeclaredMethodWithName("updateEmployee")
employeeExtensions.assertHasDeclaredMethodWithName("updateMultipleEmployee")
employeeExtensions.assertHasDeclaredMethodWithName("whereEmployee")
}
}

0 comments on commit 50e547c

Please sign in to comment.