From 50e547c81264720ac2bdc9306c41eee9b15db4a4 Mon Sep 17 00:00:00 2001 From: Thijs Koppen Date: Thu, 7 Nov 2024 00:16:25 +0100 Subject: [PATCH] Continue update DSL implementation --- .../PanacheCompanionBaseProcessor.kt | 52 +++++--- .../ch/icken/processor/ProcessorCommon.kt | 4 +- src/main/kotlin/ch/icken/query/Column.kt | 4 +- src/main/kotlin/ch/icken/query/Component.kt | 113 ++++++++++++++---- src/main/kotlin/ch/icken/query/Expression.kt | 16 +-- src/main/kotlin/ch/icken/query/Utils.kt | 24 ++++ ...nacheCompanionBaseProcessorCompileTests.kt | 3 +- 7 files changed, 162 insertions(+), 54 deletions(-) create mode 100644 src/main/kotlin/ch/icken/query/Utils.kt diff --git a/src/main/kotlin/ch/icken/processor/PanacheCompanionBaseProcessor.kt b/src/main/kotlin/ch/icken/processor/PanacheCompanionBaseProcessor.kt index e38923c..a6a5b2c 100644 --- a/src/main/kotlin/ch/icken/processor/PanacheCompanionBaseProcessor.kt +++ b/src/main/kotlin/ch/icken/processor/PanacheCompanionBaseProcessor.kt @@ -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) @@ -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) @@ -142,12 +138,12 @@ 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) @@ -155,7 +151,7 @@ class PanacheCompanionBaseProcessor( .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 @@ -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) @@ -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) diff --git a/src/main/kotlin/ch/icken/processor/ProcessorCommon.kt b/src/main/kotlin/ch/icken/processor/ProcessorCommon.kt index 99d89f1..97c28b7 100644 --- a/src/main/kotlin/ch/icken/processor/ProcessorCommon.kt +++ b/src/main/kotlin/ch/icken/processor/ProcessorCommon.kt @@ -65,7 +65,7 @@ abstract class ProcessorCommon(options: Map) { 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() @@ -89,10 +89,12 @@ abstract class ProcessorCommon(options: Map) { 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" diff --git a/src/main/kotlin/ch/icken/query/Column.kt b/src/main/kotlin/ch/icken/query/Column.kt index 0adbcac..466b6ee 100644 --- a/src/main/kotlin/ch/icken/query/Column.kt +++ b/src/main/kotlin/ch/icken/query/Column.kt @@ -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(internal val name: String) +class Column(internal val name: String) { + operator fun invoke(value: T) = Component.UpdateComponent.InitialUpdateComponent.Setter(name, value) +} //region eq private fun eq(name: String, value: Any?): Expression = diff --git a/src/main/kotlin/ch/icken/query/Component.kt b/src/main/kotlin/ch/icken/query/Component.kt index 348ed2d..03492f1 100644 --- a/src/main/kotlin/ch/icken/query/Component.kt +++ b/src/main/kotlin/ch/icken/query/Component.kt @@ -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 @@ -25,7 +30,7 @@ sealed class Component private co ) { //region compile internal abstract fun compile(): Compiled - data class Compiled internal constructor(val query: String, val parameters: Map) + data class Compiled internal constructor(val component: String, val parameters: Map) //endregion sealed class QueryComponent private constructor( @@ -40,12 +45,12 @@ sealed class Component 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() @@ -72,7 +77,7 @@ sealed class Component 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 ) } @@ -90,26 +95,94 @@ sealed class Component private co } } - class UpdateComponent internal constructor( - companion: PanacheCompanionBase, - private val columns: Columns - //TODO setter providers + sealed class UpdateComponent private constructor( + companion: PanacheCompanionBase ) : Component(companion) { - override fun compile(): Compiled { - TODO("Not yet implemented") + class InitialUpdateComponent internal constructor( + companion: PanacheCompanionBase, + private val columns: Columns, + private val setters: Array Setter> + ) : UpdateComponent(companion) { + //region Chaining operations + fun where(expression: Expression): UpdateComponent = + 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?) + } } - //TODO where + sealed class LogicalUpdateComponent private constructor( + companion: PanacheCompanionBase, + private val previous: UpdateComponent, + private val operator: String, + private val expression: Expression + ) : UpdateComponent(companion) { + //region Chaining operations + fun and(expression: Expression): UpdateComponent = + AndUpdateComponent(companion, this, expression) + fun or(expression: Expression): UpdateComponent = + 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 internal constructor( + companion: PanacheCompanionBase, + previous: UpdateComponent, + expression: Expression + ) : LogicalUpdateComponent(companion, previous, "AND", expression) + class OrUpdateComponent internal constructor( + companion: PanacheCompanionBase, + previous: UpdateComponent, + expression: Expression + ) : LogicalUpdateComponent(companion, previous, "OR", expression) + class WhereUpdateComponent internal constructor( + companion: PanacheCompanionBase, + previous: UpdateComponent, + expression: Expression + ) : LogicalUpdateComponent(companion, previous, "WHERE", expression) + } } } fun - PanacheCompanionBase.update(columns: Columns)://TODO setter providers - Component.UpdateComponent = Component.UpdateComponent(this, columns) + PanacheCompanionBase.update(columns: Columns, setter: Columns.() -> Setter): + UpdateComponent = InitialUpdateComponent(this, columns, arrayOf(setter)) +fun + PanacheCompanionBase.update(columns: Columns, setters: Array Setter>): + UpdateComponent = InitialUpdateComponent(this, columns, setters) fun PanacheCompanionBase.where(expression: Expression): - Component.QueryComponent = - Component.QueryComponent.InitialQueryComponent(this, expression) + QueryComponent = InitialQueryComponent(this, expression) diff --git a/src/main/kotlin/ch/icken/query/Expression.kt b/src/main/kotlin/ch/icken/query/Expression.kt index c5329b9..bbc7645 100644 --- a/src/main/kotlin/ch/icken/query/Expression.kt +++ b/src/main/kotlin/ch/icken/query/Expression.kt @@ -16,8 +16,6 @@ package ch.icken.query -import kotlin.random.Random - sealed class Expression { //region Chaining operations fun and(expression: Expression): Expression = LogicalExpression.AndExpression(this, expression) @@ -25,12 +23,12 @@ sealed class 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) @@ -40,16 +38,6 @@ sealed class Expression { protected val key: String, protected val operator: String ) : Expression() { - 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 private constructor( key: String, operator: String, diff --git a/src/main/kotlin/ch/icken/query/Utils.kt b/src/main/kotlin/ch/icken/query/Utils.kt new file mode 100644 index 0000000..f5fc4fd --- /dev/null +++ b/src/main/kotlin/ch/icken/query/Utils.kt @@ -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() diff --git a/src/test/kotlin/ch/icken/processor/PanacheCompanionBaseProcessorCompileTests.kt b/src/test/kotlin/ch/icken/processor/PanacheCompanionBaseProcessorCompileTests.kt index 64aa3fd..04f95c7 100644 --- a/src/test/kotlin/ch/icken/processor/PanacheCompanionBaseProcessorCompileTests.kt +++ b/src/test/kotlin/ch/icken/processor/PanacheCompanionBaseProcessorCompileTests.kt @@ -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") @@ -63,6 +63,7 @@ class PanacheCompanionBaseProcessorCompileTests : ProcessorCompileTestCommon() { employeeExtensions.assertHasDeclaredMethodWithName("streamEmployee") employeeExtensions.assertHasDeclaredMethodWithName("streamSortedEmployee") employeeExtensions.assertHasDeclaredMethodWithName("updateEmployee") + employeeExtensions.assertHasDeclaredMethodWithName("updateMultipleEmployee") employeeExtensions.assertHasDeclaredMethodWithName("whereEmployee") } }