Skip to content

Commit

Permalink
Remove whereGroup, andGroup, and orGroup functions in favor of expres…
Browse files Browse the repository at this point in the history
…sion chaining
  • Loading branch information
Thijsiez committed Nov 2, 2024
1 parent ff558f5 commit df01eb9
Show file tree
Hide file tree
Showing 17 changed files with 275 additions and 294 deletions.
5 changes: 4 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
# Changelog

## 0.0.3
- Remove `whereGroup`, `andGroup`, and `orGroup` functions in favor of expression chaining

## 0.0.2
- Add the `@ColumnType` annotation to override the generated `Column<T>`'s type parameter
- Add the `@ColumnType` annotation to override the generated `Column`'s type parameter

## 0.0.1
- Initial release
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,14 +77,14 @@ TODO
- Supports the following expressions in a type-safe and null-safe way for all columns
- `eq`, `neq`, `lt`, `gt`, `lte`, `gte`, `in`, `notIn`, `between`, and `notBetween`
- Supports `like` and `notLike` expressions in a null-safe way for String columns
- Supports `and`/`andGroup` and `or`/`orGroup` expressions for building up a query
- Supports `and` and `or` expressions for building up a query
- Adds the `PanacheSingleResult<T>` sealed class and `singleResultSafe()` extension function to return a single result without throwing exceptions.
Allows you to handle no/multiple results with a `when (result) { ... }` block instead of try-catching
### Code Generation
- Generate `Column<T>`s for non-transient and non-mapped fields in classes annotated `@Entity` and extending `PanacheEntity`/`PanacheEntityBase`
- Generate `Column`s for non-transient and non-mapped fields in classes annotated `@Entity` and extending `PanacheEntity`/`PanacheEntityBase`
- Generate query entry point extension functions for classes with companion objects extending `PanacheCompanion`/`PanacheCompanionBase`
- `where`/`whereGroup`, `count`, `delete`, `find`, `stream`, `single`, `singleSafe`, and `multiple`
- Allows for overriding the generated `Column<T>`'s type parameter using `@ColumnType`
- `where`, `count`, `delete`, `find`, `stream`, `single`, `singleSafe`, and `multiple`
- Allows for overriding the generated `Column`'s type parameter using `@ColumnType`
- Especially useful when using a JPA `@Converter` when the field's type is different to the column's type
- Optionally annotate generated code with `@Generated` so it can more easily be excluded from test coverage reporting

Expand Down
6 changes: 3 additions & 3 deletions examples/src/main/kotlin/ch/icken/Examples.kt
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,9 @@ fun findAllSons() = Employee.find { lastName like "%son" }.list()
// OR SALARY BETWEEN 75000.0 AND 85000.0
//Then we take the average using Java 8 streams
fun averageOfVerySpecificSalaryRanges() =
Employee
.whereGroup({ salary gt 50_000.0 }) {
and { salary lte 60_000.0 }
Employee.where {
salary.gt(50_000.0)
.and { salary lte 60_000.0 }
}
.or { salary.between(75_000.0, 85_000.0) }
.stream()
Expand Down
115 changes: 52 additions & 63 deletions src/main/kotlin/ch/icken/processor/PanacheCompanionBaseProcessor.kt
Original file line number Diff line number Diff line change
Expand Up @@ -53,128 +53,117 @@ class PanacheCompanionBaseProcessor(

val className = ksClass.toClassName()
val columnsObjectClassName = ClassName(packageName, classSimpleName + SUFFIX_OBJECT_COLUMNS)

val companionClassName = className.nestedClass(CLASS_NAME_COMPANION)
val idClassName = ksClass.getAllProperties()
.filter { it.hasAnnotation(JakartaPersistenceId) }
.single()
.type.resolve()
.toClassName()

val expressionParameterType = LambdaTypeName.get(
val expressionType = ExpressionClassName.plusParameter(columnsObjectClassName)
val expressionParameterLambdaType = LambdaTypeName.get(
receiver = columnsObjectClassName,
returnType = BooleanExpressionClassName
returnType = expressionType
)
val queryComponentType = QueryComponentClassName
.plusParameter(className)
.plusParameter(idClassName)
val groupComponentParameterType = LambdaTypeName.get(
receiver = queryComponentType,
returnType = queryComponentType
)
.plusParameter(columnsObjectClassName)

val whereReceiver = className.nestedClass("Companion")
//region where
val where = FunSpec.builder(FUNCTION_NAME_WHERE)
.addModifiers(KModifier.INLINE)
.receiver(whereReceiver)
.addParameter(PARAM_NAME_EXPRESSION, expressionParameterType)
.receiver(companionClassName)
.addParameter(PARAM_NAME_EXPRESSION, expressionParameterLambdaType)
.returns(queryComponentType)
.addStatement("return %M($PARAM_NAME_EXPRESSION(%T))",
MemberName(QueryComponentClassName.packageName, FUNCTION_NAME_WHERE), columnsObjectClassName)
.addAnnotation(jvmNameAnnotation("$FUNCTION_NAME_WHERE$classSimpleName"))
val whereGroup = FunSpec.builder(FUNCTION_NAME_WHERE_GROUP)
.receiver(whereReceiver)
.addParameter(PARAM_NAME_EXPRESSION, expressionParameterType)
.addParameter(PARAM_NAME_GROUP_COMPONENT, groupComponentParameterType)
.returns(queryComponentType)
.addStatement("return %M($PARAM_NAME_EXPRESSION(%T), $PARAM_NAME_GROUP_COMPONENT)",
MemberName(QueryComponentClassName.packageName, FUNCTION_NAME_WHERE_GROUP), columnsObjectClassName)
.addAnnotation(jvmNameAnnotation("$FUNCTION_NAME_WHERE_GROUP$classSimpleName"))
//endregion

//region and
//region and, or
val and = FunSpec.builder(FUNCTION_NAME_AND)
.addModifiers(KModifier.INLINE)
.receiver(queryComponentType)
.addParameter(PARAM_NAME_EXPRESSION, expressionParameterType)
.returns(AndQueryComponentClassName.plusParameter(className).plusParameter(idClassName))
.addParameter(PARAM_NAME_EXPRESSION, expressionParameterLambdaType)
.returns(AndQueryComponentClassName.plusParameter(className)
.plusParameter(idClassName).plusParameter(columnsObjectClassName))
.addStatement("return $FUNCTION_NAME_AND($PARAM_NAME_EXPRESSION(%T))", columnsObjectClassName)
.addAnnotation(jvmNameAnnotation("$FUNCTION_NAME_AND$classSimpleName"))
val andGroup = FunSpec.builder(FUNCTION_NAME_AND_GROUP)
.receiver(queryComponentType)
.addParameter(PARAM_NAME_EXPRESSION, expressionParameterType)
.addParameter(PARAM_NAME_GROUP_COMPONENT, groupComponentParameterType)
.returns(queryComponentType)
.addStatement("return $FUNCTION_NAME_AND_GROUP($PARAM_NAME_EXPRESSION(%T), $PARAM_NAME_GROUP_COMPONENT)",
columnsObjectClassName)
.addAnnotation(jvmNameAnnotation("$FUNCTION_NAME_AND_GROUP$classSimpleName"))
//endregion

//region or
val or = FunSpec.builder(FUNCTION_NAME_OR)
.addModifiers(KModifier.INLINE)
.receiver(queryComponentType)
.addParameter(PARAM_NAME_EXPRESSION, expressionParameterType)
.returns(OrQueryComponentClassName.plusParameter(className).plusParameter(idClassName))
.addParameter(PARAM_NAME_EXPRESSION, expressionParameterLambdaType)
.returns(OrQueryComponentClassName.plusParameter(className)
.plusParameter(idClassName).plusParameter(columnsObjectClassName))
.addStatement("return $FUNCTION_NAME_OR($PARAM_NAME_EXPRESSION(%T))", columnsObjectClassName)
.addAnnotation(jvmNameAnnotation("$FUNCTION_NAME_OR$classSimpleName"))
val orGroup = FunSpec.builder(FUNCTION_NAME_OR_GROUP)
.receiver(queryComponentType)
.addParameter(PARAM_NAME_EXPRESSION, expressionParameterType)
.addParameter(PARAM_NAME_GROUP_COMPONENT, groupComponentParameterType)
.returns(queryComponentType)
.addStatement("return $FUNCTION_NAME_OR_GROUP($PARAM_NAME_EXPRESSION(%T), $PARAM_NAME_GROUP_COMPONENT)",
columnsObjectClassName)
.addAnnotation(jvmNameAnnotation("$FUNCTION_NAME_OR_GROUP$classSimpleName"))
//endregion

//region andExpression, orExpression
val andExpression = FunSpec.builder(FUNCTION_NAME_AND)
.addModifiers(KModifier.INLINE)
.receiver(expressionType)
.addParameter(PARAM_NAME_EXPRESSION, expressionParameterLambdaType)
.returns(expressionType)
.addStatement("return $FUNCTION_NAME_AND($PARAM_NAME_EXPRESSION(%T))", columnsObjectClassName)
.addAnnotation(jvmNameAnnotation("$FUNCTION_NAME_AND_EXPRESSION$classSimpleName"))
val orExpression = FunSpec.builder(FUNCTION_NAME_OR)
.addModifiers(KModifier.INLINE)
.receiver(expressionType)
.addParameter(PARAM_NAME_EXPRESSION, expressionParameterLambdaType)
.returns(expressionType)
.addStatement("return $FUNCTION_NAME_OR($PARAM_NAME_EXPRESSION(%T))", columnsObjectClassName)
.addAnnotation(jvmNameAnnotation("$FUNCTION_NAME_OR_EXPRESSION$classSimpleName"))
//endregion

//region count, delete, find, stream
val count = FunSpec.builder(FUNCTION_NAME_COUNT)
.addModifiers(KModifier.INLINE)
.receiver(whereReceiver)
.addParameter(PARAM_NAME_EXPRESSION, expressionParameterType)
.receiver(companionClassName)
.addParameter(PARAM_NAME_EXPRESSION, expressionParameterLambdaType)
.returns(LongClassName)
.addStatement("return $FUNCTION_NAME_WHERE($PARAM_NAME_EXPRESSION).$FUNCTION_NAME_COUNT()")
.addAnnotation(jvmNameAnnotation("$FUNCTION_NAME_COUNT$classSimpleName"))

val delete = FunSpec.builder(FUNCTION_NAME_DELETE)
.addModifiers(KModifier.INLINE)
.receiver(whereReceiver)
.addParameter(PARAM_NAME_EXPRESSION, expressionParameterType)
.receiver(companionClassName)
.addParameter(PARAM_NAME_EXPRESSION, expressionParameterLambdaType)
.returns(LongClassName)
.addStatement("return $FUNCTION_NAME_WHERE($PARAM_NAME_EXPRESSION).$FUNCTION_NAME_DELETE()")
.addAnnotation(jvmNameAnnotation("$FUNCTION_NAME_DELETE$classSimpleName"))

val findReturns = PanacheQueryClassName.plusParameter(className)
val find = FunSpec.builder(FUNCTION_NAME_FIND)
.addModifiers(KModifier.INLINE)
.receiver(whereReceiver)
.addParameter(PARAM_NAME_EXPRESSION, expressionParameterType)
.receiver(companionClassName)
.addParameter(PARAM_NAME_EXPRESSION, expressionParameterLambdaType)
.returns(findReturns)
.addStatement("return $FUNCTION_NAME_WHERE($PARAM_NAME_EXPRESSION).$FUNCTION_NAME_FIND()")
.addAnnotation(jvmNameAnnotation("$FUNCTION_NAME_FIND$classSimpleName"))
val findSorted = FunSpec.builder(FUNCTION_NAME_FIND)
.addModifiers(KModifier.INLINE)
.receiver(whereReceiver)
.receiver(companionClassName)
.addParameter(PARAM_NAME_SORT, SortClassName)
.addParameter(PARAM_NAME_EXPRESSION, expressionParameterType)
.addParameter(PARAM_NAME_EXPRESSION, expressionParameterLambdaType)
.returns(findReturns)
.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 stream = FunSpec.builder(FUNCTION_NAME_STREAM)
.addModifiers(KModifier.INLINE)
.receiver(whereReceiver)
.addParameter(PARAM_NAME_EXPRESSION, expressionParameterType)
.receiver(companionClassName)
.addParameter(PARAM_NAME_EXPRESSION, expressionParameterLambdaType)
.returns(streamReturn)
.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(whereReceiver)
.receiver(companionClassName)
.addParameter(PARAM_NAME_SORT, SortClassName)
.addParameter(PARAM_NAME_EXPRESSION, expressionParameterType)
.addParameter(PARAM_NAME_EXPRESSION, expressionParameterLambdaType)
.returns(streamReturn)
.addStatement("return $FUNCTION_NAME_WHERE($PARAM_NAME_EXPRESSION).$FUNCTION_NAME_STREAM($PARAM_NAME_SORT)")
.addAnnotation(jvmNameAnnotation("$FUNCTION_NAME_STREAM_SORTED$classSimpleName"))
Expand All @@ -183,15 +172,15 @@ class PanacheCompanionBaseProcessor(
//region single
val single = FunSpec.builder(FUNCTION_NAME_SINGLE)
.addModifiers(KModifier.INLINE)
.receiver(whereReceiver)
.addParameter(PARAM_NAME_EXPRESSION, expressionParameterType)
.receiver(companionClassName)
.addParameter(PARAM_NAME_EXPRESSION, expressionParameterLambdaType)
.returns(className)
.addStatement("return $FUNCTION_NAME_WHERE($PARAM_NAME_EXPRESSION).getSingle()")
.addAnnotation(jvmNameAnnotation("$FUNCTION_NAME_SINGLE$classSimpleName"))
val singleSafe = FunSpec.builder(FUNCTION_NAME_SINGLE_SAFE)
.addModifiers(KModifier.INLINE)
.receiver(whereReceiver)
.addParameter(PARAM_NAME_EXPRESSION, expressionParameterType)
.receiver(companionClassName)
.addParameter(PARAM_NAME_EXPRESSION, expressionParameterLambdaType)
.returns(PanacheSingleResultClassName.plusParameter(WildcardTypeName.producerOf(className)))
.addStatement("return $FUNCTION_NAME_WHERE($PARAM_NAME_EXPRESSION).getSingleSafe()")
.addAnnotation(jvmNameAnnotation("$FUNCTION_NAME_SINGLE_SAFE$classSimpleName"))
Expand All @@ -201,22 +190,22 @@ class PanacheCompanionBaseProcessor(
val multipleReturns = ListClassName.plusParameter(className)
val multiple = FunSpec.builder(FUNCTION_NAME_MULTIPLE)
.addModifiers(KModifier.INLINE)
.receiver(whereReceiver)
.addParameter(PARAM_NAME_EXPRESSION, expressionParameterType)
.receiver(companionClassName)
.addParameter(PARAM_NAME_EXPRESSION, expressionParameterLambdaType)
.returns(multipleReturns)
.addStatement("return $FUNCTION_NAME_WHERE($PARAM_NAME_EXPRESSION).getMultiple()")
.addAnnotation(jvmNameAnnotation("$FUNCTION_NAME_MULTIPLE$classSimpleName"))
val multipleSorted = FunSpec.builder(FUNCTION_NAME_MULTIPLE)
.addModifiers(KModifier.INLINE)
.receiver(whereReceiver)
.receiver(companionClassName)
.addParameter(PARAM_NAME_SORT, SortClassName)
.addParameter(PARAM_NAME_EXPRESSION, expressionParameterType)
.addParameter(PARAM_NAME_EXPRESSION, expressionParameterLambdaType)
.returns(multipleReturns)
.addStatement("return $FUNCTION_NAME_WHERE($PARAM_NAME_EXPRESSION).getMultiple($PARAM_NAME_SORT)")
.addAnnotation(jvmNameAnnotation("$FUNCTION_NAME_MULTIPLE_SORTED$classSimpleName"))
//endregion

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

Expand Down
19 changes: 12 additions & 7 deletions src/main/kotlin/ch/icken/processor/PanacheEntityBaseProcessor.kt
Original file line number Diff line number Diff line change
Expand Up @@ -52,12 +52,14 @@ class PanacheEntityBaseProcessor(
internal fun createColumnsObject(ksClass: KSClassDeclaration, ksProperties: List<KSPropertyDeclaration>) {
val packageName = ksClass.packageName.asString() + SUFFIX_PACKAGE_GENERATED
val objectName = ksClass.simpleName.asString() + SUFFIX_OBJECT_COLUMNS
val baseClassName = objectName + SUFFIX_CLASS_COLUMNS_BASE
logger.info("Generating $packageName.$objectName (${ksProperties.size} columns)")

// Generate base class
val baseClassName = objectName + SUFFIX_CLASS_COLUMNS_BASE
val baseClassColumnsTypeVariable = TypeVariableName(TYPE_VARIABLE_NAME_COLUMNS)
val baseClassBuilder = TypeSpec.classBuilder(baseClassName)
.addModifiers(KModifier.OPEN)
.addTypeVariable(baseClassColumnsTypeVariable)
.addAnnotationIf(generatedAnnotation, addGeneratedAnnotation)
.apply {
// Generate constructor
Expand All @@ -76,17 +78,19 @@ class PanacheEntityBaseProcessor(

val propertyBuilder = if (isJoinColumn) {
val joinObjectName = ksProperty.typeName + SUFFIX_OBJECT_COLUMNS
val joinBaseClassName = joinObjectName + SUFFIX_CLASS_COLUMNS_BASE
val joinBaseClass = ClassName(packageName, joinBaseClassName)
val joinBaseClassType = ClassName(packageName, joinObjectName + SUFFIX_CLASS_COLUMNS_BASE)
.plusParameter(baseClassColumnsTypeVariable)

PropertySpec.builder(propertyName, joinBaseClass)
.initializer("%T(%S)", joinBaseClass, "$propertyName.")
PropertySpec.builder(propertyName, joinBaseClassType)
.initializer("%T(%S)", joinBaseClassType, "$propertyName.")
} else {
val ksPropertyType = ksProperty.type.resolve()
val columnTypeParameter = (ksProperty.columnTypeClassName ?: ksPropertyType.toClassName())
.copy(nullable = ksPropertyType.isMarkedNullable)
val columnType = ColumnClassName.plusParameter(baseClassColumnsTypeVariable)
.plusParameter(columnTypeParameter)

PropertySpec.builder(propertyName, ColumnClassName.plusParameter(columnTypeParameter))
PropertySpec.builder(propertyName, columnType)
.initializer("%T(%P)", ColumnClassName,
"\${${PARAM_NAME_COLUMNS_BASE_CLASS}.orEmpty()}$propertyName")
}.addAnnotationIf(generatedAnnotation, addGeneratedAnnotation)
Expand All @@ -97,7 +101,8 @@ class PanacheEntityBaseProcessor(

// Generate implementation
val objectBuilder = TypeSpec.objectBuilder(objectName)
.superclass(ClassName(packageName, baseClassName))
.superclass(ClassName(packageName, baseClassName)
.plusParameter(ClassName(packageName, objectName)))
.addAnnotationIf(generatedAnnotation, addGeneratedAnnotation)

// Generate actual source code file
Expand Down
Loading

0 comments on commit df01eb9

Please sign in to comment.